shamu 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.codeclimate.yml +26 -0
- data/.gitignore +2 -1
- data/.rubocop.yml +89 -30
- data/.yardopts +4 -5
- data/Gemfile +24 -12
- data/Guardfile +5 -0
- data/LABELS.md +22 -0
- data/README.md +41 -0
- data/Rakefile +12 -0
- data/circle.yml +7 -3
- data/config.ru +7 -0
- data/lib/shamu/active_record.rb +7 -0
- data/lib/shamu/attributes/assignment.rb +114 -0
- data/lib/shamu/attributes/equality.rb +40 -0
- data/lib/shamu/attributes/fluid_assignment.rb +49 -0
- data/lib/shamu/attributes/validation.rb +74 -0
- data/lib/shamu/attributes.rb +255 -0
- data/lib/shamu/auditing/README.md +0 -0
- data/lib/shamu/auditing/audit_record.rb +32 -0
- data/lib/shamu/auditing/auditing_service.rb +32 -0
- data/lib/shamu/auditing/list_scope.rb +22 -0
- data/lib/shamu/auditing/logging_auditing_service.rb +16 -0
- data/lib/shamu/auditing/support.rb +75 -0
- data/lib/shamu/auditing/transaction.rb +58 -0
- data/lib/shamu/auditing.rb +12 -0
- data/lib/shamu/entities/README.md +1 -0
- data/lib/shamu/entities/active_record.rb +123 -0
- data/lib/shamu/entities/active_record_soft_destroy.rb +91 -0
- data/lib/shamu/entities/entity.rb +196 -0
- data/lib/shamu/entities/entity_path.rb +87 -0
- data/lib/shamu/entities/identity_cache.rb +64 -0
- data/lib/shamu/entities/list.rb +54 -0
- data/lib/shamu/entities/list_scope/dates.rb +57 -0
- data/lib/shamu/entities/list_scope/paging.rb +51 -0
- data/lib/shamu/entities/list_scope/scoped_paging.rb +65 -0
- data/lib/shamu/entities/list_scope/sorting.rb +76 -0
- data/lib/shamu/entities/list_scope.rb +105 -0
- data/lib/shamu/entities/null_entity.rb +88 -0
- data/lib/shamu/entities.rb +11 -0
- data/lib/shamu/error.rb +23 -5
- data/lib/shamu/events/README.md +0 -0
- data/lib/shamu/events/active_record/channel.rb +36 -0
- data/lib/shamu/events/active_record/message.rb +52 -0
- data/lib/shamu/events/active_record/migration.rb +49 -0
- data/lib/shamu/events/active_record/runner.rb +28 -0
- data/lib/shamu/events/active_record/service.rb +174 -0
- data/lib/shamu/events/active_record.rb +13 -0
- data/lib/shamu/events/channel_stats.rb +23 -0
- data/lib/shamu/events/error.rb +24 -0
- data/lib/shamu/events/events_service.rb +136 -0
- data/lib/shamu/events/in_memory/async_service.rb +48 -0
- data/lib/shamu/events/in_memory/service.rb +97 -0
- data/lib/shamu/events/in_memory.rb +10 -0
- data/lib/shamu/events/message.rb +38 -0
- data/lib/shamu/events/support.rb +60 -0
- data/lib/shamu/events.rb +12 -0
- data/lib/shamu/features/README.md +0 -0
- data/lib/shamu/features/conditions/condition.rb +39 -0
- data/lib/shamu/features/conditions/env.rb +37 -0
- data/lib/shamu/features/conditions/hosts.rb +25 -0
- data/lib/shamu/features/conditions/matching.rb +16 -0
- data/lib/shamu/features/conditions/not_matching.rb +16 -0
- data/lib/shamu/features/conditions/percentage.rb +44 -0
- data/lib/shamu/features/conditions/proc.rb +54 -0
- data/lib/shamu/features/conditions/roles.rb +23 -0
- data/lib/shamu/features/conditions/schedule_at.rb +27 -0
- data/lib/shamu/features/conditions.rb +18 -0
- data/lib/shamu/features/config_service.rb +10 -0
- data/lib/shamu/features/context.rb +80 -0
- data/lib/shamu/features/env_store.rb +88 -0
- data/lib/shamu/features/errors.rb +29 -0
- data/lib/shamu/features/features_service.rb +168 -0
- data/lib/shamu/features/list_scope.rb +30 -0
- data/lib/shamu/features/selector.rb +50 -0
- data/lib/shamu/features/support.rb +51 -0
- data/lib/shamu/features/toggle.rb +149 -0
- data/lib/shamu/features/toggle_codec.rb +69 -0
- data/lib/shamu/features.rb +16 -0
- data/lib/shamu/locale/en.yml +22 -2
- data/lib/shamu/logger.rb +13 -0
- data/lib/shamu/rack/README.md +0 -0
- data/lib/shamu/rack/cookies.rb +115 -0
- data/lib/shamu/rack/cookies_middleware.rb +26 -0
- data/lib/shamu/rack/query_params.rb +41 -0
- data/lib/shamu/rack/query_params_middleware.rb +24 -0
- data/lib/shamu/rack.rb +12 -0
- data/lib/shamu/rails/controller.rb +131 -0
- data/lib/shamu/rails/entity.rb +168 -0
- data/lib/shamu/rails/features.rb +13 -0
- data/lib/shamu/rails/railtie.rb +30 -0
- data/lib/shamu/rails.rb +10 -0
- data/lib/shamu/rspec/matchers.rb +44 -0
- data/lib/shamu/rspec.rb +1 -0
- data/lib/shamu/security/README.md +0 -0
- data/lib/shamu/security/active_record_policy.rb +106 -0
- data/lib/shamu/security/error.rb +65 -0
- data/lib/shamu/security/hashed_value.rb +71 -0
- data/lib/shamu/security/no_policy.rb +15 -0
- data/lib/shamu/security/policy.rb +289 -0
- data/lib/shamu/security/policy_refinement.rb +50 -0
- data/lib/shamu/security/policy_rule.rb +59 -0
- data/lib/shamu/security/principal.rb +72 -0
- data/lib/shamu/security/roles.rb +62 -0
- data/lib/shamu/security/roles_service.rb +30 -0
- data/lib/shamu/security/support.rb +83 -0
- data/lib/shamu/security.rb +43 -0
- data/lib/shamu/services/README.md +2 -0
- data/lib/shamu/services/active_record.rb +58 -0
- data/lib/shamu/services/active_record_crud.rb +378 -0
- data/lib/shamu/services/error.rb +24 -0
- data/lib/shamu/services/lazy_association.rb +31 -0
- data/lib/shamu/services/lazy_transform.rb +97 -0
- data/lib/shamu/services/request.rb +122 -0
- data/lib/shamu/services/request_support.rb +124 -0
- data/lib/shamu/services/result.rb +75 -0
- data/lib/shamu/services/service.rb +355 -0
- data/lib/shamu/services.rb +12 -0
- data/lib/shamu/sessions/README.md +2 -0
- data/lib/shamu/sessions/cookie_store.rb +79 -0
- data/lib/shamu/sessions/session_store.rb +42 -0
- data/lib/shamu/sessions.rb +8 -0
- data/lib/shamu/to_bool_extension.rb +57 -0
- data/lib/shamu/to_model_id_extension.rb +50 -0
- data/lib/shamu/version.rb +10 -4
- data/lib/shamu.rb +18 -6
- data/shamu.gemspec +21 -10
- data/spec/internal/README.md +4 -0
- data/spec/internal/config/database.yml +3 -0
- data/spec/internal/config/routes.rb +3 -0
- data/spec/internal/db/schema.rb +3 -0
- data/spec/internal/log/.gitignore +1 -0
- data/spec/internal/public/favicon.ico +0 -0
- data/spec/lib/shamu/active_record_support.rb +32 -0
- data/spec/lib/shamu/attributes/assignment_spec.rb +129 -0
- data/spec/lib/shamu/attributes/equality_spec.rb +63 -0
- data/spec/lib/shamu/attributes/fluid_assignment_spec.rb +31 -0
- data/spec/lib/shamu/attributes/validation_spec.rb +53 -0
- data/spec/lib/shamu/attributes_spec.rb +331 -0
- data/spec/lib/shamu/auditing/logging_auditing_service_spec.rb +18 -0
- data/spec/lib/shamu/auditing/support_spec.rb +41 -0
- data/spec/lib/shamu/entities/active_record_soft_destroy_spec.rb +82 -0
- data/spec/lib/shamu/entities/active_record_spec.rb +66 -0
- data/spec/lib/shamu/entities/entity_path_spec.rb +40 -0
- data/spec/lib/shamu/entities/entity_spec.rb +56 -0
- data/spec/lib/shamu/entities/identity_cache_spec.rb +69 -0
- data/spec/lib/shamu/entities/list_scope/dates_spec.rb +47 -0
- data/spec/lib/shamu/entities/list_scope/paging_spec.rb +41 -0
- data/spec/lib/shamu/entities/list_scope/scoped_paging_spec.rb +40 -0
- data/spec/lib/shamu/entities/list_scope/sorting_spec.rb +59 -0
- data/spec/lib/shamu/entities/list_scope_spec.rb +127 -0
- data/spec/lib/shamu/entities/list_spec.rb +60 -0
- data/spec/lib/shamu/entities/null_entity_spec.rb +94 -0
- data/spec/lib/shamu/events/active_record/migration_spec.rb +11 -0
- data/spec/lib/shamu/events/active_record/service_spec.rb +139 -0
- data/spec/lib/shamu/events/events_service_spec.rb +57 -0
- data/spec/lib/shamu/events/in_memory/async_service_spec.rb +37 -0
- data/spec/lib/shamu/events/in_memory/service_spec.rb +36 -0
- data/spec/lib/shamu/events/message_spec.rb +7 -0
- data/spec/lib/shamu/events/support_spec.rb +44 -0
- data/spec/lib/shamu/features/conditions/condition_spec.rb +8 -0
- data/spec/lib/shamu/features/conditions/env_spec.rb +29 -0
- data/spec/lib/shamu/features/conditions/hosts_spec.rb +21 -0
- data/spec/lib/shamu/features/conditions/matching_spec.rb +23 -0
- data/spec/lib/shamu/features/conditions/percentage_spec.rb +71 -0
- data/spec/lib/shamu/features/conditions/proc_spec.rb +28 -0
- data/spec/lib/shamu/features/env_store_spec.rb +48 -0
- data/spec/lib/shamu/features/features.yml +34 -0
- data/spec/lib/shamu/features/features_service_spec.rb +109 -0
- data/spec/lib/shamu/features/secondary.yml +5 -0
- data/spec/lib/shamu/features/selector_spec.rb +17 -0
- data/spec/lib/shamu/features/support_spec.rb +45 -0
- data/spec/lib/shamu/features/toggle_codec_spec.rb +28 -0
- data/spec/lib/shamu/features/toggle_spec.rb +42 -0
- data/spec/lib/shamu/rack/cookies_middleware_spec.rb +33 -0
- data/spec/lib/shamu/rack/cookies_spec.rb +43 -0
- data/spec/lib/shamu/rack/query_params_middleware_spec.rb +33 -0
- data/spec/lib/shamu/rack/query_params_spec.rb +23 -0
- data/spec/lib/shamu/rails/controller_spec.rb +74 -0
- data/spec/lib/shamu/rails/entity_spec.rb +150 -0
- data/spec/lib/shamu/rails/features.yml +13 -0
- data/spec/lib/shamu/rails/features_spec.rb +45 -0
- data/spec/lib/shamu/security/active_record_policy_spec.rb +38 -0
- data/spec/lib/shamu/security/hashed_value_spec.rb +41 -0
- data/spec/lib/shamu/security/policy_refinement_spec.rb +61 -0
- data/spec/lib/shamu/security/policy_rule_spec.rb +60 -0
- data/spec/lib/shamu/security/policy_spec.rb +158 -0
- data/spec/lib/shamu/security/roles_spec.rb +46 -0
- data/spec/lib/shamu/services/active_record_crud_spec.rb +460 -0
- data/spec/lib/shamu/services/active_record_spec.rb +92 -0
- data/spec/lib/shamu/services/lazy_association_spec.rb +31 -0
- data/spec/lib/shamu/services/lazy_transform_spec.rb +96 -0
- data/spec/lib/shamu/services/request_spec.rb +58 -0
- data/spec/lib/shamu/services/request_support_spec.rb +129 -0
- data/spec/lib/shamu/services/result_spec.rb +37 -0
- data/spec/lib/shamu/services/service_spec.rb +307 -0
- data/spec/lib/shamu/sessions/cookie_store_spec.rb +44 -0
- data/spec/lib/shamu/to_bool_extension_spec.rb +67 -0
- data/spec/lib/shamu/to_model_id_extension_spec.rb +54 -0
- data/spec/rails_helper.rb +13 -0
- data/spec/spec_helper.rb +17 -12
- data/spec/support/active_record.rb +17 -0
- data/spec/support/database.rb +14 -0
- data/spec/support/logger.rb +0 -0
- metadata +383 -9
- data/spec/lib/shamu_spec.rb +0 -5
- /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,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,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
|