flipflop 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +5 -0
- data/.travis.yml +51 -0
- data/Gemfile +20 -0
- data/LICENSE +22 -0
- data/README.md +261 -0
- data/Rakefile +16 -0
- data/app/assets/stylesheets/flipflop.scss +109 -0
- data/app/controllers/concerns/flipflop/environment_filters.rb +5 -0
- data/app/controllers/flipflop/features_controller.rb +59 -0
- data/app/controllers/flipflop/strategies_controller.rb +30 -0
- data/app/models/flipflop/feature.rb +3 -0
- data/app/views/flipflop/features/index.html.erb +60 -0
- data/app/views/layouts/flipflop.html.erb +1 -0
- data/config/routes.rb +5 -0
- data/flipflop.gemspec +23 -0
- data/lib/flipflop/configurable.rb +27 -0
- data/lib/flipflop/engine.rb +58 -0
- data/lib/flipflop/facade.rb +23 -0
- data/lib/flipflop/feature_cache.rb +64 -0
- data/lib/flipflop/feature_definition.rb +15 -0
- data/lib/flipflop/feature_set.rb +99 -0
- data/lib/flipflop/strategies/abstract_strategy.rb +103 -0
- data/lib/flipflop/strategies/active_record_strategy.rb +43 -0
- data/lib/flipflop/strategies/cookie_strategy.rb +44 -0
- data/lib/flipflop/strategies/default_strategy.rb +15 -0
- data/lib/flipflop/strategies/lambda_strategy.rb +25 -0
- data/lib/flipflop/strategies/query_string_strategy.rb +17 -0
- data/lib/flipflop/strategies/session_strategy.rb +29 -0
- data/lib/flipflop/strategies/test_strategy.rb +40 -0
- data/lib/flipflop/version.rb +3 -0
- data/lib/flipflop.rb +26 -0
- data/lib/generators/flipflop/features/USAGE +8 -0
- data/lib/generators/flipflop/features/features_generator.rb +7 -0
- data/lib/generators/flipflop/features/templates/features.rb +21 -0
- data/lib/generators/flipflop/install/install_generator.rb +21 -0
- data/lib/generators/flipflop/migration/USAGE +5 -0
- data/lib/generators/flipflop/migration/migration_generator.rb +23 -0
- data/lib/generators/flipflop/migration/templates/create_features.rb +10 -0
- data/lib/generators/flipflop/routes/USAGE +7 -0
- data/lib/generators/flipflop/routes/routes_generator.rb +5 -0
- data/test/integration/app_test.rb +32 -0
- data/test/integration/dashboard_test.rb +162 -0
- data/test/test_helper.rb +96 -0
- data/test/unit/configurable_test.rb +104 -0
- data/test/unit/feature_cache_test.rb +142 -0
- data/test/unit/feature_definition_test.rb +42 -0
- data/test/unit/feature_set_test.rb +136 -0
- data/test/unit/flipflop_test.rb +99 -0
- data/test/unit/strategies/abstract_strategy_request_test.rb +42 -0
- data/test/unit/strategies/abstract_strategy_test.rb +124 -0
- data/test/unit/strategies/active_record_strategy_test.rb +157 -0
- data/test/unit/strategies/cookie_strategy_test.rb +126 -0
- data/test/unit/strategies/default_strategy_test.rb +44 -0
- data/test/unit/strategies/lambda_strategy_test.rb +137 -0
- data/test/unit/strategies/query_string_strategy_test.rb +70 -0
- data/test/unit/strategies/session_strategy_test.rb +101 -0
- data/test/unit/strategies/test_strategy_test.rb +76 -0
- metadata +134 -0
@@ -0,0 +1,136 @@
|
|
1
|
+
require File.expand_path("../../test_helper", __FILE__)
|
2
|
+
|
3
|
+
class NullStrategy < Flipflop::Strategies::AbstractStrategy
|
4
|
+
def enabled?(feature)
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class TrueStrategy < Flipflop::Strategies::AbstractStrategy
|
9
|
+
def enabled?(feature)
|
10
|
+
true
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class FalseStrategy < Flipflop::Strategies::AbstractStrategy
|
15
|
+
def enabled?(feature)
|
16
|
+
false
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe Flipflop::FeatureSet do
|
21
|
+
subject do
|
22
|
+
Flipflop::FeatureSet.current.reset!
|
23
|
+
Flipflop::FeatureSet.current.tap do |set|
|
24
|
+
set.add(Flipflop::FeatureDefinition.new(:one))
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "current" do
|
29
|
+
it "should return same instance" do
|
30
|
+
current = subject
|
31
|
+
assert_equal current, Flipflop::FeatureSet.current
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should return same instance in different thread" do
|
35
|
+
current = subject
|
36
|
+
assert_equal current, Thread.new { Flipflop::FeatureSet.current }.value
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "test" do
|
41
|
+
it "should freeze strategies" do
|
42
|
+
subject.test!
|
43
|
+
assert_raises RuntimeError do
|
44
|
+
subject.use(Flipflop::Strategies::AbstractStrategy.new)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should replace strategies with test strategy" do
|
49
|
+
subject.test!
|
50
|
+
assert_equal [Flipflop::Strategies::TestStrategy], subject.strategies.map(&:class)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should replace strategies with given strategy" do
|
54
|
+
subject.test!(Flipflop::Strategies::LambdaStrategy.new)
|
55
|
+
assert_equal [Flipflop::Strategies::LambdaStrategy], subject.strategies.map(&:class)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should return test strategy" do
|
59
|
+
returned = subject.test!(strategy = Flipflop::Strategies::LambdaStrategy.new)
|
60
|
+
assert_equal strategy, returned
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "enabled" do
|
65
|
+
it "should return false by default" do
|
66
|
+
subject.use(NullStrategy.new)
|
67
|
+
assert_equal false, subject.enabled?(:one)
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should return value of next true strategy if unknown" do
|
71
|
+
subject.use(NullStrategy.new)
|
72
|
+
subject.use(TrueStrategy.new)
|
73
|
+
assert_equal true, subject.enabled?(:one)
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should return value of next false strategy if unknown" do
|
77
|
+
subject.use(NullStrategy.new)
|
78
|
+
subject.use(FalseStrategy.new)
|
79
|
+
assert_equal false, subject.enabled?(:one)
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should stop resolving at first true value" do
|
83
|
+
subject.use(TrueStrategy.new)
|
84
|
+
subject.use(FalseStrategy.new)
|
85
|
+
subject.use(NullStrategy.new)
|
86
|
+
assert_equal true, subject.enabled?(:one)
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should stop resolving at first false value" do
|
90
|
+
subject.use(FalseStrategy.new)
|
91
|
+
subject.use(TrueStrategy.new)
|
92
|
+
subject.use(NullStrategy.new)
|
93
|
+
assert_equal false, subject.enabled?(:one)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
describe "add" do
|
98
|
+
it "should add feature" do
|
99
|
+
subject.add(feature = Flipflop::FeatureDefinition.new(:feature))
|
100
|
+
assert_equal feature, subject.feature(feature.key)
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should freeze feature" do
|
104
|
+
subject.add(feature = Flipflop::FeatureDefinition.new(:feature))
|
105
|
+
assert subject.feature(feature.key).frozen?
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe "use" do
|
110
|
+
it "should add strategy" do
|
111
|
+
subject.use(strategy = NullStrategy.new)
|
112
|
+
assert_equal strategy, subject.strategy(strategy.key)
|
113
|
+
end
|
114
|
+
|
115
|
+
it "should freeze strategy" do
|
116
|
+
subject.use(strategy = NullStrategy.new)
|
117
|
+
assert subject.strategy(strategy.key).frozen?
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe "feature" do
|
122
|
+
it "should raise if feature is unknown" do
|
123
|
+
assert_raises Flipflop::FeatureError do
|
124
|
+
subject.feature(:unknown)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe "strategy" do
|
130
|
+
it "should raise if strategy is unknown" do
|
131
|
+
assert_raises Flipflop::StrategyError do
|
132
|
+
subject.strategy("12345")
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require File.expand_path("../../test_helper", __FILE__)
|
2
|
+
|
3
|
+
describe Flipflop do
|
4
|
+
before do
|
5
|
+
Flipflop.configure do
|
6
|
+
feature :one, default: true
|
7
|
+
feature :two, default: false
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "configure" do
|
12
|
+
before do
|
13
|
+
Flipflop.configure do
|
14
|
+
feature :config_feature, default: true
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should reset feature set" do
|
19
|
+
Flipflop.configure do
|
20
|
+
end
|
21
|
+
assert_equal [], Flipflop::FeatureSet.current.features
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should add features" do
|
25
|
+
assert_equal [:config_feature],
|
26
|
+
Flipflop::FeatureSet.current.features.map(&:key)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should freeze features" do
|
30
|
+
assert_raises RuntimeError do
|
31
|
+
Flipflop::FeatureSet.current.add(Flipflop::FeatureDefinition.new(:foo))
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should freeze strategies" do
|
36
|
+
assert_raises RuntimeError do
|
37
|
+
Flipflop::FeatureSet.current.use(Flipflop::Strategies::AbstractStrategy.new)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "enabled?" do
|
43
|
+
it "should return true for enabled features" do
|
44
|
+
assert_equal true, Flipflop.on?(:one)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should return false for disabled features" do
|
48
|
+
assert_equal false, Flipflop.on?(:two)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should call strategy once if cached" do
|
52
|
+
called = 0
|
53
|
+
counter = Class.new(Flipflop::Strategies::AbstractStrategy) do
|
54
|
+
define_method :enabled? do |feature|
|
55
|
+
called += 1
|
56
|
+
false
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
Flipflop.configure do
|
61
|
+
strategy counter
|
62
|
+
feature :one, default: true
|
63
|
+
end
|
64
|
+
|
65
|
+
begin
|
66
|
+
Flipflop::FeatureCache.current.enable!
|
67
|
+
Flipflop.on?(:one)
|
68
|
+
Flipflop.on?(:one)
|
69
|
+
assert_equal 1, called
|
70
|
+
ensure
|
71
|
+
Flipflop::FeatureCache.current.disable!
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "dynamic predicate method" do
|
77
|
+
it "should respond to feature predicate" do
|
78
|
+
assert Flipflop.respond_to?(:one?)
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should not respond to incorrectly formatted predicate" do
|
82
|
+
refute Flipflop.respond_to?(:foobar!)
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should return true for enabled features" do
|
86
|
+
assert_equal true, Flipflop.one?
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should return false for disabled features" do
|
90
|
+
assert_equal false, Flipflop.two?
|
91
|
+
end
|
92
|
+
|
93
|
+
it "raises error for incorrectly formatted predicate" do
|
94
|
+
assert_raises NoMethodError do
|
95
|
+
Flipflop.foobar!
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require File.expand_path("../../../test_helper", __FILE__)
|
2
|
+
|
3
|
+
describe Flipflop::Strategies::AbstractStrategy::RequestInterceptor do
|
4
|
+
subject do
|
5
|
+
Class.new(ActionController::Metal) do
|
6
|
+
class << self
|
7
|
+
attr_accessor :request
|
8
|
+
end
|
9
|
+
|
10
|
+
include AbstractController::Callbacks
|
11
|
+
include Flipflop::Strategies::AbstractStrategy::RequestInterceptor
|
12
|
+
|
13
|
+
def index
|
14
|
+
self.class.request = Flipflop::Strategies::AbstractStrategy::RequestInterceptor.request
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
after do
|
20
|
+
Flipflop::Strategies::AbstractStrategy::RequestInterceptor.request = nil
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should add before filter to controller" do
|
24
|
+
filters = subject._process_action_callbacks.select { |f| f.kind == :before }
|
25
|
+
assert_equal 1, filters.length
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should add after filter to controller" do
|
29
|
+
filters = subject._process_action_callbacks.select { |f| f.kind == :after }
|
30
|
+
assert_equal 1, filters.length
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should set request" do
|
34
|
+
subject.action(:index).call({})
|
35
|
+
assert_instance_of ActionDispatch::Request, subject.request
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should clear request" do
|
39
|
+
subject.action(:index).call({})
|
40
|
+
assert_nil Flipflop::Strategies::AbstractStrategy::RequestInterceptor.request
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require File.expand_path("../../../test_helper", __FILE__)
|
2
|
+
|
3
|
+
describe Flipflop::Strategies::AbstractStrategy do
|
4
|
+
after do
|
5
|
+
Flipflop::Strategies::AbstractStrategy::RequestInterceptor.request = nil
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "with defaults" do
|
9
|
+
subject do
|
10
|
+
Flipflop::Strategies::AbstractStrategy.new.freeze
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should have default name" do
|
14
|
+
assert_equal "abstract", subject.name
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should have no default description" do
|
18
|
+
assert_nil subject.description
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should not be switchable" do
|
22
|
+
assert_equal false, subject.switchable?
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should not be hidden" do
|
26
|
+
assert_equal false, subject.hidden?
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should have unique key" do
|
30
|
+
assert_match /^\d+$/, subject.key
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "request" do
|
34
|
+
it "should return request" do
|
35
|
+
Flipflop::Strategies::AbstractStrategy::RequestInterceptor.request = 3
|
36
|
+
assert_equal 3, subject.send(:request)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should raise if request is missing" do
|
40
|
+
Flipflop::Strategies::AbstractStrategy::RequestInterceptor.request = nil
|
41
|
+
assert_raises Flipflop::StrategyError do
|
42
|
+
subject.send(:request)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should raise with message if request is missing" do
|
47
|
+
Flipflop::Strategies::AbstractStrategy::RequestInterceptor.request = nil
|
48
|
+
message = nil
|
49
|
+
begin
|
50
|
+
subject.send(:request)
|
51
|
+
rescue => err
|
52
|
+
message = err.message
|
53
|
+
end
|
54
|
+
assert_equal "Strategy 'abstract' required request, but was used outside request context.", message
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should raise if request is missing in thread" do
|
58
|
+
Flipflop::Strategies::AbstractStrategy::RequestInterceptor.request = 3
|
59
|
+
assert_raises Flipflop::StrategyError do
|
60
|
+
Thread.new { subject.send(:request) }.value
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "request predicate" do
|
66
|
+
it "should return true if request is present" do
|
67
|
+
Flipflop::Strategies::AbstractStrategy::RequestInterceptor.request = 3
|
68
|
+
assert_equal true, subject.send(:request?)
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should return false if request is missing" do
|
72
|
+
Flipflop::Strategies::AbstractStrategy::RequestInterceptor.request = nil
|
73
|
+
assert_equal false, subject.send(:request?)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe "with options" do
|
79
|
+
subject do
|
80
|
+
Flipflop::Strategies::AbstractStrategy.new(
|
81
|
+
name: "strategy",
|
82
|
+
description: "my strategy",
|
83
|
+
hidden: true,
|
84
|
+
).freeze
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should have specified name" do
|
88
|
+
assert_equal "strategy", subject.name
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should have specified description" do
|
92
|
+
assert_equal "my strategy", subject.description
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should be hidden" do
|
96
|
+
assert_equal true, subject.hidden?
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
describe "with unknown options" do
|
101
|
+
subject do
|
102
|
+
Flipflop::Strategies::AbstractStrategy.new(
|
103
|
+
unknown: "one",
|
104
|
+
other: "two",
|
105
|
+
).freeze
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should raise error" do
|
109
|
+
assert_raises Flipflop::StrategyError do
|
110
|
+
subject
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should raise with message" do
|
115
|
+
message = nil
|
116
|
+
begin
|
117
|
+
subject
|
118
|
+
rescue => err
|
119
|
+
message = err.message
|
120
|
+
end
|
121
|
+
assert_equal "Strategy 'abstract' did not understand option :unknown, :other.", message
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
require File.expand_path("../../../test_helper", __FILE__)
|
2
|
+
|
3
|
+
class ResultSet
|
4
|
+
def initialize(key, results = [])
|
5
|
+
@key, @results = key, results
|
6
|
+
end
|
7
|
+
|
8
|
+
def first_or_initialize
|
9
|
+
@results.first or My::Feature.new(@key, false)
|
10
|
+
end
|
11
|
+
|
12
|
+
def first
|
13
|
+
@results.first
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module My
|
18
|
+
class Feature < Struct.new(:key, :enabled)
|
19
|
+
class << self
|
20
|
+
attr_accessor :results
|
21
|
+
|
22
|
+
def where(conditions)
|
23
|
+
results[conditions[:key].to_sym]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
alias_method :enabled?, :enabled
|
28
|
+
|
29
|
+
def destroy
|
30
|
+
My::Feature.results[key] = ResultSet.new(key)
|
31
|
+
end
|
32
|
+
|
33
|
+
def save!
|
34
|
+
My::Feature.results[key] = ResultSet.new(key, [self])
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe Flipflop::Strategies::ActiveRecordStrategy do
|
40
|
+
describe "with defaults" do
|
41
|
+
subject do
|
42
|
+
Flipflop::Strategies::ActiveRecordStrategy.new(class: My::Feature).freeze
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should have default name" do
|
46
|
+
assert_equal "active_record", subject.name
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should have default description" do
|
50
|
+
assert_equal "Stores features in database. Applies to all users.",
|
51
|
+
subject.description
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should be switchable" do
|
55
|
+
assert_equal true, subject.switchable?
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should have unique key" do
|
59
|
+
assert_match /^\d+$/, subject.key
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "with enabled feature" do
|
63
|
+
before do
|
64
|
+
My::Feature.results = {
|
65
|
+
one: ResultSet.new(:one, [My::Feature.new(:one, true)]),
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should have feature enabled" do
|
70
|
+
assert_equal true, subject.enabled?(:one)
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should be able to switch feature off" do
|
74
|
+
subject.switch!(:one, false)
|
75
|
+
assert_equal false, subject.enabled?(:one)
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should be able to clear feature" do
|
79
|
+
subject.clear!(:one)
|
80
|
+
assert_nil subject.enabled?(:one)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe "with disabled feature" do
|
85
|
+
before do
|
86
|
+
My::Feature.results = {
|
87
|
+
two: ResultSet.new(:two, [My::Feature.new(:two, false)]),
|
88
|
+
}
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should not have feature enabled" do
|
92
|
+
assert_equal false, subject.enabled?(:two)
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should be able to switch feature on" do
|
96
|
+
subject.switch!(:two, true)
|
97
|
+
assert_equal true, subject.enabled?(:two)
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should be able to clear feature" do
|
101
|
+
subject.clear!(:two)
|
102
|
+
assert_nil subject.enabled?(:two)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe "with unsaved feature" do
|
107
|
+
before do
|
108
|
+
My::Feature.results = {
|
109
|
+
three: ResultSet.new(:three),
|
110
|
+
}
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should not know feature" do
|
114
|
+
assert_nil subject.enabled?(:three)
|
115
|
+
end
|
116
|
+
|
117
|
+
it "should be able to switch feature on" do
|
118
|
+
subject.switch!(:three, true)
|
119
|
+
assert_equal true, subject.enabled?(:three)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
describe "with string class name" do
|
125
|
+
subject do
|
126
|
+
Flipflop::Strategies::ActiveRecordStrategy.new(class: "My::Feature").freeze
|
127
|
+
end
|
128
|
+
|
129
|
+
before do
|
130
|
+
My::Feature.results = {
|
131
|
+
one: ResultSet.new(:one, [My::Feature.new(:one, true)]),
|
132
|
+
}
|
133
|
+
end
|
134
|
+
|
135
|
+
it "should be able to switch feature off" do
|
136
|
+
subject.switch!(:one, false)
|
137
|
+
assert_equal false, subject.enabled?(:one)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe "with symbol class name" do
|
142
|
+
subject do
|
143
|
+
Flipflop::Strategies::ActiveRecordStrategy.new(class: :"My::Feature").freeze
|
144
|
+
end
|
145
|
+
|
146
|
+
before do
|
147
|
+
My::Feature.results = {
|
148
|
+
one: ResultSet.new(:one, [My::Feature.new(:one, true)]),
|
149
|
+
}
|
150
|
+
end
|
151
|
+
|
152
|
+
it "should be able to switch feature off" do
|
153
|
+
subject.switch!(:one, false)
|
154
|
+
assert_equal false, subject.enabled?(:one)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
require File.expand_path("../../../test_helper", __FILE__)
|
2
|
+
|
3
|
+
describe Flipflop::Strategies::CookieStrategy do
|
4
|
+
subject do
|
5
|
+
Flipflop::Strategies::CookieStrategy.new.freeze
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "in request context" do
|
9
|
+
before do
|
10
|
+
Flipflop::Strategies::AbstractStrategy::RequestInterceptor.request = create_request
|
11
|
+
end
|
12
|
+
|
13
|
+
after do
|
14
|
+
Flipflop::Strategies::AbstractStrategy::RequestInterceptor.request = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should have default name" do
|
18
|
+
assert_equal "cookie", subject.name
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should have default description" do
|
22
|
+
assert_equal "Stores features in a browser cookie. Applies to current user.",
|
23
|
+
subject.description
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should be switchable" do
|
27
|
+
assert_equal true, subject.switchable?
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should have unique key" do
|
31
|
+
assert_match /^\d+$/, subject.key
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "with enabled feature" do
|
35
|
+
before do
|
36
|
+
subject.send(:request).cookie_jar[subject.send(:cookie_name, :one)] = "1"
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should have feature enabled" do
|
40
|
+
assert_equal true, subject.enabled?(:one)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should be able to switch feature off" do
|
44
|
+
subject.switch!(:one, false)
|
45
|
+
assert_equal false, subject.enabled?(:one)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should be able to clear feature" do
|
49
|
+
subject.clear!(:one)
|
50
|
+
assert_nil subject.enabled?(:one)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "with disabled feature" do
|
55
|
+
before do
|
56
|
+
subject.send(:request).cookie_jar[subject.send(:cookie_name, :two)] = "0"
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should not have feature enabled" do
|
60
|
+
assert_equal false, subject.enabled?(:two)
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should be able to switch feature on" do
|
64
|
+
subject.switch!(:two, true)
|
65
|
+
assert_equal true, subject.enabled?(:two)
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should be able to clear feature" do
|
69
|
+
subject.clear!(:two)
|
70
|
+
assert_nil subject.enabled?(:two)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe "with uncookied feature" do
|
75
|
+
it "should not know feature" do
|
76
|
+
assert_nil subject.enabled?(:three)
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should be able to switch feature on" do
|
80
|
+
subject.switch!(:three, true)
|
81
|
+
assert_equal true, subject.enabled?(:three)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe "with options" do
|
86
|
+
subject do
|
87
|
+
Flipflop::Strategies::CookieStrategy.new(
|
88
|
+
domain: :all,
|
89
|
+
path: "/foo",
|
90
|
+
httponly: true,
|
91
|
+
).freeze
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should pass options when setting value" do
|
95
|
+
subject.switch!(:one, true)
|
96
|
+
subject.send(:request).cookie_jar.write(headers = {})
|
97
|
+
assert_equal "flipflop_one=1; domain=.example.com; path=/foo; HttpOnly",
|
98
|
+
headers["Set-Cookie"]
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should pass options when deleting value" do
|
102
|
+
subject.switch!(:one, true)
|
103
|
+
subject.clear!(:one)
|
104
|
+
subject.send(:request).cookie_jar.write(headers = {})
|
105
|
+
assert_equal "flipflop_one=; domain=.example.com; path=/foo; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000; HttpOnly",
|
106
|
+
headers["Set-Cookie"]
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
describe "outside request context" do
|
112
|
+
it "should not know feature" do
|
113
|
+
assert_nil subject.enabled?(:one)
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should not be switchable" do
|
117
|
+
assert_equal false, subject.switchable?
|
118
|
+
end
|
119
|
+
|
120
|
+
it "should not be able to switch feature on" do
|
121
|
+
assert_raises Flipflop::StrategyError do
|
122
|
+
subject.switch!(:one, true)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|