shamu 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (207) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +26 -0
  3. data/.gitignore +2 -1
  4. data/.rubocop.yml +89 -30
  5. data/.yardopts +4 -5
  6. data/Gemfile +24 -12
  7. data/Guardfile +5 -0
  8. data/LABELS.md +22 -0
  9. data/README.md +41 -0
  10. data/Rakefile +12 -0
  11. data/circle.yml +7 -3
  12. data/config.ru +7 -0
  13. data/lib/shamu/active_record.rb +7 -0
  14. data/lib/shamu/attributes/assignment.rb +114 -0
  15. data/lib/shamu/attributes/equality.rb +40 -0
  16. data/lib/shamu/attributes/fluid_assignment.rb +49 -0
  17. data/lib/shamu/attributes/validation.rb +74 -0
  18. data/lib/shamu/attributes.rb +255 -0
  19. data/lib/shamu/auditing/README.md +0 -0
  20. data/lib/shamu/auditing/audit_record.rb +32 -0
  21. data/lib/shamu/auditing/auditing_service.rb +32 -0
  22. data/lib/shamu/auditing/list_scope.rb +22 -0
  23. data/lib/shamu/auditing/logging_auditing_service.rb +16 -0
  24. data/lib/shamu/auditing/support.rb +75 -0
  25. data/lib/shamu/auditing/transaction.rb +58 -0
  26. data/lib/shamu/auditing.rb +12 -0
  27. data/lib/shamu/entities/README.md +1 -0
  28. data/lib/shamu/entities/active_record.rb +123 -0
  29. data/lib/shamu/entities/active_record_soft_destroy.rb +91 -0
  30. data/lib/shamu/entities/entity.rb +196 -0
  31. data/lib/shamu/entities/entity_path.rb +87 -0
  32. data/lib/shamu/entities/identity_cache.rb +64 -0
  33. data/lib/shamu/entities/list.rb +54 -0
  34. data/lib/shamu/entities/list_scope/dates.rb +57 -0
  35. data/lib/shamu/entities/list_scope/paging.rb +51 -0
  36. data/lib/shamu/entities/list_scope/scoped_paging.rb +65 -0
  37. data/lib/shamu/entities/list_scope/sorting.rb +76 -0
  38. data/lib/shamu/entities/list_scope.rb +105 -0
  39. data/lib/shamu/entities/null_entity.rb +88 -0
  40. data/lib/shamu/entities.rb +11 -0
  41. data/lib/shamu/error.rb +23 -5
  42. data/lib/shamu/events/README.md +0 -0
  43. data/lib/shamu/events/active_record/channel.rb +36 -0
  44. data/lib/shamu/events/active_record/message.rb +52 -0
  45. data/lib/shamu/events/active_record/migration.rb +49 -0
  46. data/lib/shamu/events/active_record/runner.rb +28 -0
  47. data/lib/shamu/events/active_record/service.rb +174 -0
  48. data/lib/shamu/events/active_record.rb +13 -0
  49. data/lib/shamu/events/channel_stats.rb +23 -0
  50. data/lib/shamu/events/error.rb +24 -0
  51. data/lib/shamu/events/events_service.rb +136 -0
  52. data/lib/shamu/events/in_memory/async_service.rb +48 -0
  53. data/lib/shamu/events/in_memory/service.rb +97 -0
  54. data/lib/shamu/events/in_memory.rb +10 -0
  55. data/lib/shamu/events/message.rb +38 -0
  56. data/lib/shamu/events/support.rb +60 -0
  57. data/lib/shamu/events.rb +12 -0
  58. data/lib/shamu/features/README.md +0 -0
  59. data/lib/shamu/features/conditions/condition.rb +39 -0
  60. data/lib/shamu/features/conditions/env.rb +37 -0
  61. data/lib/shamu/features/conditions/hosts.rb +25 -0
  62. data/lib/shamu/features/conditions/matching.rb +16 -0
  63. data/lib/shamu/features/conditions/not_matching.rb +16 -0
  64. data/lib/shamu/features/conditions/percentage.rb +44 -0
  65. data/lib/shamu/features/conditions/proc.rb +54 -0
  66. data/lib/shamu/features/conditions/roles.rb +23 -0
  67. data/lib/shamu/features/conditions/schedule_at.rb +27 -0
  68. data/lib/shamu/features/conditions.rb +18 -0
  69. data/lib/shamu/features/config_service.rb +10 -0
  70. data/lib/shamu/features/context.rb +80 -0
  71. data/lib/shamu/features/env_store.rb +88 -0
  72. data/lib/shamu/features/errors.rb +29 -0
  73. data/lib/shamu/features/features_service.rb +168 -0
  74. data/lib/shamu/features/list_scope.rb +30 -0
  75. data/lib/shamu/features/selector.rb +50 -0
  76. data/lib/shamu/features/support.rb +51 -0
  77. data/lib/shamu/features/toggle.rb +149 -0
  78. data/lib/shamu/features/toggle_codec.rb +69 -0
  79. data/lib/shamu/features.rb +16 -0
  80. data/lib/shamu/locale/en.yml +22 -2
  81. data/lib/shamu/logger.rb +13 -0
  82. data/lib/shamu/rack/README.md +0 -0
  83. data/lib/shamu/rack/cookies.rb +115 -0
  84. data/lib/shamu/rack/cookies_middleware.rb +26 -0
  85. data/lib/shamu/rack/query_params.rb +41 -0
  86. data/lib/shamu/rack/query_params_middleware.rb +24 -0
  87. data/lib/shamu/rack.rb +12 -0
  88. data/lib/shamu/rails/controller.rb +131 -0
  89. data/lib/shamu/rails/entity.rb +168 -0
  90. data/lib/shamu/rails/features.rb +13 -0
  91. data/lib/shamu/rails/railtie.rb +30 -0
  92. data/lib/shamu/rails.rb +10 -0
  93. data/lib/shamu/rspec/matchers.rb +44 -0
  94. data/lib/shamu/rspec.rb +1 -0
  95. data/lib/shamu/security/README.md +0 -0
  96. data/lib/shamu/security/active_record_policy.rb +106 -0
  97. data/lib/shamu/security/error.rb +65 -0
  98. data/lib/shamu/security/hashed_value.rb +71 -0
  99. data/lib/shamu/security/no_policy.rb +15 -0
  100. data/lib/shamu/security/policy.rb +289 -0
  101. data/lib/shamu/security/policy_refinement.rb +50 -0
  102. data/lib/shamu/security/policy_rule.rb +59 -0
  103. data/lib/shamu/security/principal.rb +72 -0
  104. data/lib/shamu/security/roles.rb +62 -0
  105. data/lib/shamu/security/roles_service.rb +30 -0
  106. data/lib/shamu/security/support.rb +83 -0
  107. data/lib/shamu/security.rb +43 -0
  108. data/lib/shamu/services/README.md +2 -0
  109. data/lib/shamu/services/active_record.rb +58 -0
  110. data/lib/shamu/services/active_record_crud.rb +378 -0
  111. data/lib/shamu/services/error.rb +24 -0
  112. data/lib/shamu/services/lazy_association.rb +31 -0
  113. data/lib/shamu/services/lazy_transform.rb +97 -0
  114. data/lib/shamu/services/request.rb +122 -0
  115. data/lib/shamu/services/request_support.rb +124 -0
  116. data/lib/shamu/services/result.rb +75 -0
  117. data/lib/shamu/services/service.rb +355 -0
  118. data/lib/shamu/services.rb +12 -0
  119. data/lib/shamu/sessions/README.md +2 -0
  120. data/lib/shamu/sessions/cookie_store.rb +79 -0
  121. data/lib/shamu/sessions/session_store.rb +42 -0
  122. data/lib/shamu/sessions.rb +8 -0
  123. data/lib/shamu/to_bool_extension.rb +57 -0
  124. data/lib/shamu/to_model_id_extension.rb +50 -0
  125. data/lib/shamu/version.rb +10 -4
  126. data/lib/shamu.rb +18 -6
  127. data/shamu.gemspec +21 -10
  128. data/spec/internal/README.md +4 -0
  129. data/spec/internal/config/database.yml +3 -0
  130. data/spec/internal/config/routes.rb +3 -0
  131. data/spec/internal/db/schema.rb +3 -0
  132. data/spec/internal/log/.gitignore +1 -0
  133. data/spec/internal/public/favicon.ico +0 -0
  134. data/spec/lib/shamu/active_record_support.rb +32 -0
  135. data/spec/lib/shamu/attributes/assignment_spec.rb +129 -0
  136. data/spec/lib/shamu/attributes/equality_spec.rb +63 -0
  137. data/spec/lib/shamu/attributes/fluid_assignment_spec.rb +31 -0
  138. data/spec/lib/shamu/attributes/validation_spec.rb +53 -0
  139. data/spec/lib/shamu/attributes_spec.rb +331 -0
  140. data/spec/lib/shamu/auditing/logging_auditing_service_spec.rb +18 -0
  141. data/spec/lib/shamu/auditing/support_spec.rb +41 -0
  142. data/spec/lib/shamu/entities/active_record_soft_destroy_spec.rb +82 -0
  143. data/spec/lib/shamu/entities/active_record_spec.rb +66 -0
  144. data/spec/lib/shamu/entities/entity_path_spec.rb +40 -0
  145. data/spec/lib/shamu/entities/entity_spec.rb +56 -0
  146. data/spec/lib/shamu/entities/identity_cache_spec.rb +69 -0
  147. data/spec/lib/shamu/entities/list_scope/dates_spec.rb +47 -0
  148. data/spec/lib/shamu/entities/list_scope/paging_spec.rb +41 -0
  149. data/spec/lib/shamu/entities/list_scope/scoped_paging_spec.rb +40 -0
  150. data/spec/lib/shamu/entities/list_scope/sorting_spec.rb +59 -0
  151. data/spec/lib/shamu/entities/list_scope_spec.rb +127 -0
  152. data/spec/lib/shamu/entities/list_spec.rb +60 -0
  153. data/spec/lib/shamu/entities/null_entity_spec.rb +94 -0
  154. data/spec/lib/shamu/events/active_record/migration_spec.rb +11 -0
  155. data/spec/lib/shamu/events/active_record/service_spec.rb +139 -0
  156. data/spec/lib/shamu/events/events_service_spec.rb +57 -0
  157. data/spec/lib/shamu/events/in_memory/async_service_spec.rb +37 -0
  158. data/spec/lib/shamu/events/in_memory/service_spec.rb +36 -0
  159. data/spec/lib/shamu/events/message_spec.rb +7 -0
  160. data/spec/lib/shamu/events/support_spec.rb +44 -0
  161. data/spec/lib/shamu/features/conditions/condition_spec.rb +8 -0
  162. data/spec/lib/shamu/features/conditions/env_spec.rb +29 -0
  163. data/spec/lib/shamu/features/conditions/hosts_spec.rb +21 -0
  164. data/spec/lib/shamu/features/conditions/matching_spec.rb +23 -0
  165. data/spec/lib/shamu/features/conditions/percentage_spec.rb +71 -0
  166. data/spec/lib/shamu/features/conditions/proc_spec.rb +28 -0
  167. data/spec/lib/shamu/features/env_store_spec.rb +48 -0
  168. data/spec/lib/shamu/features/features.yml +34 -0
  169. data/spec/lib/shamu/features/features_service_spec.rb +109 -0
  170. data/spec/lib/shamu/features/secondary.yml +5 -0
  171. data/spec/lib/shamu/features/selector_spec.rb +17 -0
  172. data/spec/lib/shamu/features/support_spec.rb +45 -0
  173. data/spec/lib/shamu/features/toggle_codec_spec.rb +28 -0
  174. data/spec/lib/shamu/features/toggle_spec.rb +42 -0
  175. data/spec/lib/shamu/rack/cookies_middleware_spec.rb +33 -0
  176. data/spec/lib/shamu/rack/cookies_spec.rb +43 -0
  177. data/spec/lib/shamu/rack/query_params_middleware_spec.rb +33 -0
  178. data/spec/lib/shamu/rack/query_params_spec.rb +23 -0
  179. data/spec/lib/shamu/rails/controller_spec.rb +74 -0
  180. data/spec/lib/shamu/rails/entity_spec.rb +150 -0
  181. data/spec/lib/shamu/rails/features.yml +13 -0
  182. data/spec/lib/shamu/rails/features_spec.rb +45 -0
  183. data/spec/lib/shamu/security/active_record_policy_spec.rb +38 -0
  184. data/spec/lib/shamu/security/hashed_value_spec.rb +41 -0
  185. data/spec/lib/shamu/security/policy_refinement_spec.rb +61 -0
  186. data/spec/lib/shamu/security/policy_rule_spec.rb +60 -0
  187. data/spec/lib/shamu/security/policy_spec.rb +158 -0
  188. data/spec/lib/shamu/security/roles_spec.rb +46 -0
  189. data/spec/lib/shamu/services/active_record_crud_spec.rb +460 -0
  190. data/spec/lib/shamu/services/active_record_spec.rb +92 -0
  191. data/spec/lib/shamu/services/lazy_association_spec.rb +31 -0
  192. data/spec/lib/shamu/services/lazy_transform_spec.rb +96 -0
  193. data/spec/lib/shamu/services/request_spec.rb +58 -0
  194. data/spec/lib/shamu/services/request_support_spec.rb +129 -0
  195. data/spec/lib/shamu/services/result_spec.rb +37 -0
  196. data/spec/lib/shamu/services/service_spec.rb +307 -0
  197. data/spec/lib/shamu/sessions/cookie_store_spec.rb +44 -0
  198. data/spec/lib/shamu/to_bool_extension_spec.rb +67 -0
  199. data/spec/lib/shamu/to_model_id_extension_spec.rb +54 -0
  200. data/spec/rails_helper.rb +13 -0
  201. data/spec/spec_helper.rb +17 -12
  202. data/spec/support/active_record.rb +17 -0
  203. data/spec/support/database.rb +14 -0
  204. data/spec/support/logger.rb +0 -0
  205. metadata +383 -9
  206. data/spec/lib/shamu_spec.rb +0 -5
  207. /data/{spec/internal/log/test.log → lib/shamu/attributes/README.md} +0 -0
@@ -0,0 +1,37 @@
1
+ require "spec_helper"
2
+
3
+ describe Shamu::Events::InMemory::AsyncService do
4
+ let( :service ) { scorpion.new Shamu::Events::InMemory::AsyncService }
5
+ let( :message ) { Shamu::Events::Message.new }
6
+
7
+ describe "#publish" do
8
+ it "adds message to channel" do
9
+ expect do
10
+ service.publish( "spec", message )
11
+ end.to change { service.channel_stats( "spec" )[ :queue_size ] }
12
+ end
13
+
14
+ it "serializes the message" do
15
+ expect( service ).to receive( :serialize ).and_call_original
16
+ service.publish( "spec", message )
17
+ end
18
+ end
19
+
20
+ describe "#subscribe" do
21
+ it "receives a message" do
22
+ expect do |b|
23
+ service.subscribe "spec", &b
24
+ service.publish "spec", message
25
+
26
+ wait_for { service.channel_stats( "spec" )[ :queue_size ] }.to eq 0
27
+ end.to yield_control
28
+ end
29
+
30
+ it "deserializes the message" do
31
+ expect( service ).to receive( :deserialize ).and_call_original
32
+ service.publish "spec", message
33
+ wait_for { service.channel_stats( "spec" )[ :queue_size ] }.to eq 0
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,36 @@
1
+ require "spec_helper"
2
+
3
+ describe Shamu::Events::InMemory::Service do
4
+ let( :service ) { scorpion.new Shamu::Events::InMemory::Service }
5
+ let( :message ) { Shamu::Events::Message.new }
6
+
7
+ describe "#publish" do
8
+ it "adds message to channel" do
9
+ expect do
10
+ service.publish( "spec", message )
11
+ end.to change { service.channel_stats( "spec" )[ :queue_size ] }
12
+ end
13
+
14
+ it "serializes the message" do
15
+ expect( service ).to receive( :serialize ).and_call_original
16
+ service.publish( "spec", message )
17
+ end
18
+ end
19
+
20
+ describe "#subscribe" do
21
+ it "receives a message" do
22
+ expect do |b|
23
+ service.subscribe "spec", &b
24
+ service.publish "spec", message
25
+ service.dispatch
26
+ end.to yield_control
27
+ end
28
+
29
+ it "deserializes the message" do
30
+ expect( service ).to receive( :deserialize ).and_call_original
31
+ service.publish "spec", message
32
+ service.dispatch
33
+ end
34
+ end
35
+
36
+ end
@@ -0,0 +1,7 @@
1
+ require "spec_helper"
2
+
3
+ describe Shamu::Events::Message do
4
+ it "generates an ID" do
5
+ expect( Shamu::Events::Message.new.id ).to be_present
6
+ end
7
+ end
@@ -0,0 +1,44 @@
1
+ require "spec_helper"
2
+
3
+ describe Shamu::Events::Support do
4
+ describe "#event_channel" do
5
+ {
6
+ "Users::UsersService" => "users",
7
+ "Users::ProfileService" => "users/profile",
8
+ "Users::Profiles::ProfilesService" => "users/profiles",
9
+ "Service" => "",
10
+ "Users::Service" => "users"
11
+ }.each do |name, channel|
12
+
13
+ it "is #{ channel } for #{ name }" do
14
+ klass = Class.new( Shamu::Services::Service ) do
15
+ include Shamu::Events::Support
16
+
17
+ public :event_channel
18
+ end
19
+
20
+ allow( klass ).to receive( :name ).and_return name
21
+
22
+ expect( klass.new.event_channel ).to eq channel
23
+ end
24
+ end
25
+ end
26
+
27
+ describe "event!" do
28
+ hunt( :events_service, Shamu::Events::EventsService )
29
+
30
+ let( :klass ) do
31
+ Class.new( Shamu::Services::Service ) do
32
+ include Shamu::Events::Support
33
+
34
+ public :event!
35
+ end
36
+ end
37
+ let( :service ) { scorpion.new klass }
38
+
39
+ it "publishes message to events_service" do
40
+ expect( events_service ).to receive( :publish )
41
+ service.event! Shamu::Events::Message.new
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,8 @@
1
+ require "spec_helper"
2
+
3
+ describe Shamu::Features::Conditions do
4
+ it "finds the proper condition" do
5
+ condition = Shamu::Features::Conditions::Condition.create "schedule_at", Time.now, double
6
+ expect( condition ).to be_a Shamu::Features::Conditions::ScheduleAt
7
+ end
8
+ end
@@ -0,0 +1,29 @@
1
+ require "spec_helper"
2
+
3
+
4
+ describe Shamu::Features::Conditions::Env do
5
+ let( :context ) { double( Shamu::Features::Context ) }
6
+ let( :toggle ) { double( Shamu::Features::Toggle ) }
7
+
8
+ it "matches on presence of truthy variable" do
9
+ condition = scorpion.new Shamu::Features::Conditions::Env, "CANARY", toggle
10
+
11
+ expect( context ).to receive( :env ).with( "CANARY" ).and_return "true"
12
+ expect( condition.match?( context ) ).to be_truthy
13
+ end
14
+
15
+ it "matches on variable value" do
16
+ condition = scorpion.new Shamu::Features::Conditions::Env, { "CANARY" => "example" }, toggle
17
+
18
+ expect( context ).to receive( :env ).with( "CANARY" ).and_return "example"
19
+ expect( condition.match?( context ) ).to be_truthy
20
+ end
21
+
22
+ it "doesn't match with invalid variable" do
23
+ condition = scorpion.new Shamu::Features::Conditions::Env, "MISSING_ENV_VARIABLE_NAME", toggle
24
+
25
+ expect( context ).to receive( :env ).with( "MISSING_ENV_VARIABLE_NAME" ).and_return nil
26
+ expect( condition.match?( context ) ).to be_falsy
27
+ end
28
+
29
+ end
@@ -0,0 +1,21 @@
1
+ require "spec_helper"
2
+
3
+
4
+ describe Shamu::Features::Conditions::Hosts do
5
+ let( :context ) { double( Shamu::Features::Context ) }
6
+ let( :toggle ) { double( Shamu::Features::Toggle ) }
7
+
8
+ it "matches using regex" do
9
+ condition = scorpion.new Shamu::Features::Conditions::Hosts, [ 'web\d+' ], toggle
10
+
11
+ expect( context ).to receive( :host ).and_return "web3"
12
+ expect( condition.match?( context ) ).to be_truthy
13
+ end
14
+
15
+ it "doesn't match using regex" do
16
+ condition = scorpion.new Shamu::Features::Conditions::Hosts, [ 'web\d+-staging' ], toggle
17
+
18
+ expect( context ).to receive( :host ).and_return "web3"
19
+ expect( condition.match?( context ) ).to be_falsy
20
+ end
21
+ end
@@ -0,0 +1,23 @@
1
+ require "spec_helper"
2
+
3
+
4
+ describe Shamu::Features::Conditions::Matching do
5
+ let( :context ) { double( Shamu::Features::Context ) }
6
+ let( :toggle ) { double( Shamu::Features::Toggle ) }
7
+
8
+ it "matches if another feature is enabled" do
9
+ condition = scorpion.new Shamu::Features::Conditions::Matching, "spec/example", toggle
10
+
11
+ expect( context ).to receive( :enabled? ).and_return true
12
+ expect( condition.match?( context ) ).to be_truthy
13
+ end
14
+
15
+ it "doesn't matche if another feature is disabled" do
16
+ condition = scorpion.new Shamu::Features::Conditions::Matching, "spec/example", toggle
17
+
18
+ expect( context ).to receive( :enabled? ).and_return false
19
+ expect( condition.match?( context ) ).to be_falsy
20
+ end
21
+
22
+
23
+ end
@@ -0,0 +1,71 @@
1
+ require "spec_helper"
2
+
3
+
4
+ describe Shamu::Features::Conditions::Percentage do
5
+ let( :context ) { double( Shamu::Features::Context ) }
6
+ let( :toggle ) { double( Shamu::Features::Toggle ) }
7
+
8
+ before( :each ) do
9
+ allow( toggle ).to receive( :name ).and_return "example"
10
+ end
11
+
12
+ it "matches integer user id" do
13
+ condition = scorpion.new Shamu::Features::Conditions::Percentage, 5, toggle
14
+
15
+ expect( context ).to receive( :user_id ).at_least(:once).and_return 55
16
+ expect( condition.match?( context ) ).to be_truthy
17
+ end
18
+
19
+ it "matches same user id when percentage grows" do
20
+ condition = scorpion.new Shamu::Features::Conditions::Percentage, 15, toggle
21
+
22
+ expect( context ).to receive( :user_id ).at_least(:once).and_return 55
23
+ expect( condition.match?( context ) ).to be_truthy
24
+ end
25
+
26
+ it "excludes integer user id" do
27
+ condition = scorpion.new Shamu::Features::Conditions::Percentage, 5, toggle
28
+
29
+ expect( context ).to receive( :user_id ).at_least(:once).and_return 111
30
+ expect( condition.match?( context ) ).to be_falsy
31
+ end
32
+
33
+
34
+ it "matches uuid user id" do
35
+ condition = scorpion.new Shamu::Features::Conditions::Percentage, 23, toggle
36
+
37
+ expect( context ).to receive( :user_id ).at_least(:once).and_return "45561ca3-5bf9-4f3a-9b4f-89a15ea0e387"
38
+ expect( condition.match?( context ) ).to be_truthy
39
+ end
40
+
41
+ context "when no user_id" do
42
+ before( :each ) do
43
+ allow( context ).to receive( :user_id ).and_return nil
44
+ allow( context ).to receive( :sticky! )
45
+ end
46
+
47
+ it "assigns randomly" do
48
+ condition = scorpion.new Shamu::Features::Conditions::Percentage, 5, toggle
49
+
50
+ expect( Random ).to receive( :rand ).and_return 0
51
+ expect( condition.match?( context ) ).to be_truthy
52
+
53
+ expect( Random ).to receive( :rand ).and_return 8
54
+ expect( condition.match?( context ) ).to be_falsy
55
+ end
56
+
57
+ it "makes the assignment sticky" do
58
+ condition = scorpion.new Shamu::Features::Conditions::Percentage, 5, toggle
59
+
60
+ expect( Random ).to receive( :rand ).and_return 0
61
+ expect( context ).to receive( :sticky! )
62
+ condition.match?( context )
63
+ end
64
+
65
+ it "is always true at 100%" do
66
+ condition = scorpion.new Shamu::Features::Conditions::Percentage, 100, toggle
67
+ expect( condition.match?( context ) ).to be_truthy
68
+ end
69
+ end
70
+
71
+ end
@@ -0,0 +1,28 @@
1
+ require "spec_helper"
2
+
3
+ module ProcSpec
4
+ class CustomProc
5
+ def match?( context )
6
+ end
7
+ end
8
+ end
9
+
10
+ describe Shamu::Features::Conditions::Proc do
11
+ let( :context ) { double( Shamu::Features::Context ) }
12
+ let( :toggle ) { double( Shamu::Features::Toggle ) }
13
+
14
+ it "invokes the specified match method" do
15
+ expect( context ).to receive( :scorpion ).and_return scorpion
16
+
17
+ condition = scorpion.new Shamu::Features::Conditions::Proc, "ProcSpec::CustomProc#match?", toggle
18
+
19
+ instance = condition.send( :instance, context )
20
+ expect( condition ).to receive( :instance ).and_return instance
21
+
22
+ expect( instance ).to be_a ProcSpec::CustomProc
23
+ expect( instance ).to receive( :match? ).with( context, toggle )
24
+
25
+ condition.match?( context )
26
+ end
27
+
28
+ end
@@ -0,0 +1,48 @@
1
+ require "spec_helper"
2
+
3
+ describe Shamu::Features::EnvStore do
4
+ hunt( :codec, Shamu::Features::ToggleCodec ) { scorpion.new Shamu::Features::ToggleCodec }
5
+
6
+ it "reads from rack header" do
7
+ packed = codec.pack( "buy_now" => true )
8
+ env = {
9
+ "rack.input" => StringIO.new,
10
+ Shamu::Features::EnvStore::RACK_ENV_KEY => packed
11
+ }
12
+ scorpion.hunt_for Scorpion::Rack::Env, return: env
13
+
14
+ store = scorpion.fetch( Shamu::Features::EnvStore )
15
+ expect( store.fetch( "buy_now" ) ).to eq true
16
+ end
17
+
18
+ it "reads from rack params" do
19
+ packed = codec.pack( "buy_now" => true )
20
+ env = {
21
+ "rack.input" => StringIO.new,
22
+ "QUERY_STRING" => "#{ Shamu::Features::EnvStore::RACK_PARAMS_KEY }=#{ URI.escape( packed ) }"
23
+ }
24
+ scorpion.hunt_for Scorpion::Rack::Env, return: env
25
+
26
+ store = scorpion.fetch( Shamu::Features::EnvStore )
27
+ expect( store.fetch( "buy_now" ) ).to eq true
28
+ end
29
+
30
+ it "falls back to env" do
31
+ scorpion.hunt_for Scorpion::Rack::Env, return: { "rack.input" => StringIO.new }
32
+ store = scorpion.fetch( Shamu::Features::EnvStore )
33
+ key = store.class.env_key_name( "buy_now" )
34
+
35
+ expect( ENV ).to receive( :key? ).with( key ).and_return true
36
+ expect( ENV ).to receive( :[] ).with( key ).and_return "false"
37
+
38
+ expect( store.fetch( "buy_now" ) ).to eq false
39
+ end
40
+
41
+ it "falls back to fall back block" do
42
+ scorpion.hunt_for Scorpion::Rack::Env, return: { "rack.input" => StringIO.new }
43
+ store = scorpion.fetch( Shamu::Features::EnvStore )
44
+
45
+ expect( store.fetch( "buy_now" ) { "yep" } ).to eq "yep"
46
+ end
47
+
48
+ end
@@ -0,0 +1,34 @@
1
+ ---
2
+ shopping:
3
+
4
+ buy_now:
5
+ description: Show "Buy Now" one-click purchase option to customers.
6
+ retire_at: 2055-10-1
7
+ type: release
8
+ select:
9
+ - proc: "FeaturesServiceSpec::ProcToggle#buy_now?"
10
+ - schedule_at: 2015-4-1
11
+ percentage: 100
12
+ roles:
13
+ - first-time
14
+ hosts:
15
+ - web[\d]-sf
16
+ - web2
17
+ env:
18
+ - CANARY
19
+ matching:
20
+ - uploads/previews
21
+ not_matching:
22
+ - shopping/inline-ads
23
+ reject: true
24
+
25
+ # Import the feature configuration from the given path.
26
+ import: "secondary.yml"
27
+
28
+ uploads:
29
+ previews:
30
+ description: "Show previews on uploads"
31
+ retire_at: 2005-10-1
32
+ type: release
33
+ select:
34
+ - reject: true
@@ -0,0 +1,109 @@
1
+ require "spec_helper"
2
+ require "fileutils"
3
+
4
+ module FeaturesServiceSpec
5
+ class ProcToggle
6
+ def buy_now?( context, toggle )
7
+ false
8
+ end
9
+ end
10
+ end
11
+
12
+ describe Shamu::Features::FeaturesService do
13
+ let( :config_path ) { File.expand_path( "../features.yml", __FILE__ ) }
14
+ let( :service ) { scorpion.new Shamu::Features::FeaturesService, config_path }
15
+ let( :codec ) { scorpion.new Shamu::Features::ToggleCodec }
16
+
17
+ hunt( :session_store, Shamu::Sessions::SessionStore )
18
+ hunt( :env_store, Shamu::Features::EnvStore )
19
+
20
+ before( :each ) do
21
+ allow( session_store ).to receive( :fetch ).and_return nil
22
+ allow( env_store ).to receive( :fetch ).and_return nil
23
+ end
24
+
25
+ it "lists known features" do
26
+ expect( service.list ).to have_key "shopping/buy_now"
27
+ expect( service.list ).to have_key "uploads/previews"
28
+ end
29
+
30
+ it "lists known features by prefix" do
31
+ expect( service.list( "shopping" ) ).to have_key "shopping/buy_now"
32
+ expect( service.list( "shopping" ) ).not_to have_key "uploads/previews"
33
+ end
34
+
35
+ # TODO: Figure out how to make this work
36
+ #
37
+ # Listen does something with the threads and doesn't actually fire the change
38
+ # event until after the spec has executed :/.
39
+ #
40
+ # It's been verified to work manually but I'd like to get an automated test in
41
+ # as well.
42
+ #
43
+ # context "with file changes" do
44
+ # let( :config_path ) { File.expand_path( "../../../../../tmp/#{ SecureRandom.hex( 16 ) }.yml", __FILE__ ) }
45
+ # it "reloads when any source changes", :focus do
46
+ # expect( Shamu::Features::Toggle ).to receive( :load ).twice.and_call_original
47
+ #
48
+ # begin
49
+ # File.write config_path, {}.to_yaml
50
+ # service.list
51
+ # File.write config_path, {}.to_yaml
52
+ # sleep 1
53
+ # ensure
54
+ # File.unlink config_path
55
+ # end
56
+ # end
57
+ # end
58
+
59
+ context "with session information" do
60
+ let( :packed ) { codec.pack( "shopping/buy_now" => true ) }
61
+
62
+ it "reads sticky features" do
63
+ expect( session_store ).to receive( :fetch ).with( "shamu.toggles" ).and_return packed
64
+ expect( service ).not_to receive( :resolve_toggle )
65
+
66
+ expect( service.enabled?( "shopping/buy_now" ) ).to eq true
67
+ end
68
+
69
+ it "persists sticky context" do
70
+ expect( session_store ).to receive( :set ).with( "shamu.toggles", codec.pack( "shopping/buy_now" => false ) )
71
+
72
+ service.enabled?( "shopping/buy_now" )
73
+ end
74
+
75
+ it "appends to sticky context" do
76
+ existing_packed = codec.pack( "example" => true )
77
+ expect( session_store ).to receive( :fetch ).with( "shamu.toggles" ).and_return existing_packed
78
+ compbined_packed = codec.pack( "example" => true, "shopping/buy_now" => false )
79
+ expect( session_store ).to receive( :set ).with( "shamu.toggles", compbined_packed )
80
+
81
+ service.enabled?( "shopping/buy_now" )
82
+ end
83
+ end
84
+
85
+ context "with env information" do
86
+ it "overrides from environment" do
87
+ expect( env_store ).to receive( :fetch ).with( "shopping/buy_now" ).and_return true
88
+
89
+ expect( service.enabled?( "shopping/buy_now" ) ).to eq true
90
+ end
91
+ end
92
+
93
+ it "is always false for unknown toggles" do
94
+ allow( service.logger ).to receive( :info )
95
+ expect( service.enabled?( "not/yet/set" ) ).to be_falsy
96
+ end
97
+
98
+ it "logs when an unknown toggle is checked" do
99
+ expect( service.logger ).to receive( :info ).with /configured/
100
+ service.enabled?( "not/yet/set" )
101
+ end
102
+
103
+ it "raises when expired toggle is checked" do
104
+ expect do
105
+ service.enabled?( "uploads/previews" )
106
+ end.to raise_error Shamu::Features::RetiredToggleError, /retired/
107
+ end
108
+
109
+ end
@@ -0,0 +1,5 @@
1
+ offers:
2
+ at_checkout:
3
+ description: "Show offers after purchase"
4
+ retire_at: 2050-1-1
5
+ type: ops
@@ -0,0 +1,17 @@
1
+ require "spec_helper"
2
+
3
+ describe Shamu::Features::Selector do
4
+ it "parses conditions" do
5
+ selector = Shamu::Features::Selector.new double, "schedule_at" => Time.now
6
+
7
+ expect( selector ).to be_a Shamu::Features::Selector
8
+ expect( selector.reject ).to be_falsy
9
+ expect( selector.conditions.first ).to be_a Shamu::Features::Conditions::ScheduleAt
10
+ end
11
+
12
+ it "parses reject option" do
13
+ selector = Shamu::Features::Selector.new double, "reject" => true
14
+
15
+ expect( selector.reject ).to be_truthy
16
+ end
17
+ end
@@ -0,0 +1,45 @@
1
+ require "spec_helper"
2
+
3
+ describe Shamu::Features::FeaturesService do
4
+ hunt( :features_service, Shamu::Features::FeaturesService )
5
+ let( :klass ) do
6
+ Class.new( Shamu::Services::Service ) do
7
+ include Shamu::Features::Support
8
+
9
+ public :when_feature
10
+ end
11
+ end
12
+ let( :service ) { scorpion.new klass }
13
+
14
+ it "executes the block if enabled" do
15
+ allow( features_service ).to receive( :enabled? ).and_return true
16
+
17
+ expect do |b|
18
+ service.when_feature( "example", &b )
19
+ end.to yield_control
20
+ end
21
+
22
+ it "doesn't execute if not enabled" do
23
+ allow( features_service ).to receive( :enabled? ).and_return false
24
+
25
+ expect do |b|
26
+ service.when_feature( "example", &b )
27
+ end.not_to yield_control
28
+ end
29
+
30
+ it "can override enabled" do
31
+ allow( features_service ).to receive( :enabled? ).and_return false
32
+
33
+ expect do |b|
34
+ service.when_feature( "example", override: true, &b )
35
+ end.to yield_control
36
+ end
37
+
38
+ it "can override disabled" do
39
+ allow( features_service ).to receive( :enabled? ).and_return true
40
+
41
+ expect do |b|
42
+ service.when_feature( "example", override: false, &b )
43
+ end.not_to yield_control
44
+ end
45
+ end
@@ -0,0 +1,28 @@
1
+ require "spec_helper"
2
+
3
+ describe Shamu::Features::ToggleCodec do
4
+ let( :codec ) { Shamu::Features::ToggleCodec.new( SecureRandom.random_bytes( 64 ) ) }
5
+
6
+ describe "#pack" do
7
+ subject { codec.pack( "buy_now" => true, "suggestions" => false ) }
8
+
9
+ it { is_expected.to match /,/ }
10
+ it { is_expected.to match /[^!]buy_now/ }
11
+ it { is_expected.to match /!suggestions/ }
12
+ end
13
+
14
+ describe "#unpack" do
15
+ let( :packed ) { codec.pack( "buy_now" => true, "suggestions" => false ) }
16
+
17
+ subject { codec.unpack( packed ) }
18
+
19
+ its(["buy_now"]) { is_expected.to eq true }
20
+ its(["suggestions"]) { is_expected.to eq false }
21
+ its(["not/set"]) { is_expected.to be_nil }
22
+
23
+ it "handles an empty feature hash" do
24
+ packed = codec.pack( {} )
25
+ expect( codec.unpack( packed ) ).to eq( {} )
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,42 @@
1
+ require "spec_helper"
2
+
3
+ describe Shamu::Features::Toggle do
4
+ let( :path ) { File.expand_path( "../features.yml", __FILE__ ) }
5
+ let( :toggles ) { Shamu::Features::Toggle.load( path ) }
6
+
7
+ it "collapses path to name" do
8
+ expect( toggles ).to have_key "shopping/buy_now"
9
+ end
10
+
11
+ it "imports relative files" do
12
+ expect( toggles ).to have_key "shopping/offers/at_checkout"
13
+ end
14
+
15
+ context "with toggle" do
16
+ let( :toggle ) { toggles[ "shopping/buy_now" ] }
17
+
18
+ it "creates the toggle" do
19
+ expect( toggle ).to be_a Shamu::Features::Toggle
20
+ end
21
+
22
+ it "parses selectors LIFO" do
23
+ expect( toggle.selectors.count ).to eq 2
24
+ end
25
+
26
+ it "parses selector conditions" do
27
+ expect( toggle.selectors.last.conditions.count ).to eq 7
28
+ end
29
+ end
30
+
31
+ it "requires a retire_at date" do
32
+ expect do
33
+ Shamu::Features::Toggle.new( {} )
34
+ end.to raise_error ArgumentError, /retire_at/
35
+ end
36
+
37
+ it "requires a type" do
38
+ expect do
39
+ Shamu::Features::Toggle.new( "retire_at" => Time.now )
40
+ end.to raise_error ArgumentError, /Type/
41
+ end
42
+ end
@@ -0,0 +1,33 @@
1
+ require "spec_helper"
2
+ require "shamu/rack"
3
+
4
+ module CookiesMiddlewareSpec
5
+ class App
6
+ include Scorpion::Rack
7
+ attr_accessor :next
8
+
9
+ def call( env )
10
+ @next ||= proc { [ 200, {}, [ "Sting!" ] ] }
11
+ @next.call( env, self )
12
+ end
13
+ end
14
+ end
15
+
16
+ describe Shamu::Rack::CookiesMiddleware do
17
+ let(:app) { CookiesMiddlewareSpec::App.new }
18
+ let(:scorpion_middleware) { Scorpion::Rack::Middleware.new( cookies_middleware ) }
19
+ let(:cookies_middleware) { Shamu::Rack::CookiesMiddleware.new( app ) }
20
+ let(:request) { Rack::MockRequest.new( scorpion_middleware ) }
21
+ let(:response) { request.get( "/" ) }
22
+
23
+ it "prepares it with the environment" do
24
+
25
+ app.next = proc do |env, app|
26
+ cookies = app.send( :scorpion, env ).fetch Shamu::Rack::Cookies
27
+ expect( cookies ).to be_a Shamu::Rack::Cookies
28
+ [ 200, {}, [ "Yum!" ] ]
29
+ end
30
+
31
+ request.get "/"
32
+ end
33
+ end