flipflop 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|