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,21 @@
|
|
1
|
+
Flipflop.configure do
|
2
|
+
# Strategies will be used in the order listed here.
|
3
|
+
strategy :cookie
|
4
|
+
strategy :active_record
|
5
|
+
strategy :default
|
6
|
+
|
7
|
+
# Other strategies:
|
8
|
+
#
|
9
|
+
# strategy :session
|
10
|
+
# strategy :query_string
|
11
|
+
#
|
12
|
+
# strategy :my_strategy do |feature|
|
13
|
+
# # ... your custom code here; return true/false/nil.
|
14
|
+
# end
|
15
|
+
|
16
|
+
# Declare your features, e.g:
|
17
|
+
#
|
18
|
+
# feature :world_domination,
|
19
|
+
# default: true,
|
20
|
+
# description: "Take over the world."
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "generators/flipflop/features/features_generator"
|
2
|
+
require "generators/flipflop/migration/migration_generator"
|
3
|
+
require "generators/flipflop/routes/routes_generator"
|
4
|
+
|
5
|
+
class Flipflop::InstallGenerator < Rails::Generators::Base
|
6
|
+
def invoke_generators
|
7
|
+
Flipflop::FeaturesGenerator.new([], options).invoke_all
|
8
|
+
Flipflop::MigrationGenerator.new([], options).invoke_all
|
9
|
+
Flipflop::RoutesGenerator.new([], options).invoke_all
|
10
|
+
end
|
11
|
+
|
12
|
+
def configure_dashboard
|
13
|
+
environment <<-CONFIG
|
14
|
+
# Replace this with your own 'before_action' filter in ApplicationController
|
15
|
+
# to implement access control for the Flipflop dashboard, or provide a
|
16
|
+
# filter as lambda directly.
|
17
|
+
config.flipflop.dashboard_access_filter = :require_development
|
18
|
+
|
19
|
+
CONFIG
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require "rails"
|
2
|
+
require "rails/generators/migration"
|
3
|
+
require "rails/generators/active_record"
|
4
|
+
|
5
|
+
class Flipflop::MigrationGenerator < Rails::Generators::Base
|
6
|
+
include Rails::Generators::Migration
|
7
|
+
|
8
|
+
source_root File.expand_path("../templates", __FILE__)
|
9
|
+
|
10
|
+
def create_migration_file
|
11
|
+
migration_template("create_features.rb", "db/migrate/create_features.rb")
|
12
|
+
end
|
13
|
+
|
14
|
+
# Stubbed in railties/lib/rails/generators/migration.rb
|
15
|
+
#
|
16
|
+
# This implementation is a simplified version of:
|
17
|
+
# activerecord/lib/rails/generators/active_record/migration.rb
|
18
|
+
#
|
19
|
+
# See: http://www.ruby-forum.com/topic/203205
|
20
|
+
def self.next_migration_number(dirname)
|
21
|
+
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
class CreateFeatures < ActiveRecord::Migration<%= Rails.version >= "5" ? "[#{ActiveRecord::Migration.current_version}]" : "" %>
|
2
|
+
def change
|
3
|
+
create_table :features do |t|
|
4
|
+
t.string :key, null: false
|
5
|
+
t.boolean :enabled, null: false, default: false
|
6
|
+
|
7
|
+
t.timestamps null: false
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require File.expand_path("../../test_helper", __FILE__)
|
2
|
+
|
3
|
+
describe Flipflop do
|
4
|
+
before do
|
5
|
+
@app = TestApp.new
|
6
|
+
end
|
7
|
+
|
8
|
+
subject do
|
9
|
+
@app
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "middleware" do
|
13
|
+
it "should include cache middleware" do
|
14
|
+
middlewares = Rails.application.middleware.map(&:klass)
|
15
|
+
assert_includes middlewares, Flipflop::FeatureCache::Middleware
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "module" do
|
20
|
+
before do
|
21
|
+
Flipflop::FeatureSet.current.instance_variable_set(:@features, {})
|
22
|
+
Module.new do
|
23
|
+
extend Flipflop::Configurable
|
24
|
+
feature :world_domination
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should allow querying for features" do
|
29
|
+
assert_equal false, Flipflop.world_domination?
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
require File.expand_path("../../test_helper", __FILE__)
|
2
|
+
|
3
|
+
require "capybara/dsl"
|
4
|
+
|
5
|
+
describe Flipflop do
|
6
|
+
include Capybara::DSL
|
7
|
+
|
8
|
+
before do
|
9
|
+
@app = TestApp.new
|
10
|
+
end
|
11
|
+
|
12
|
+
subject do
|
13
|
+
@app
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "outside development and test" do
|
17
|
+
before do
|
18
|
+
Rails.env.stub(:test?, false) do
|
19
|
+
visit "/flipflop"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should be forbidden" do
|
24
|
+
assert_equal 403, page.status_code
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "without features" do
|
29
|
+
before do
|
30
|
+
visit "/flipflop"
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should show feature table with header" do
|
34
|
+
assert_equal ["Cookie", "Active record", "Default"],
|
35
|
+
all("thead th").map(&:text)[3..-1]
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should show no features" do
|
39
|
+
assert all("tbody tr").empty?
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "with features" do
|
44
|
+
before do
|
45
|
+
Flipflop::FeatureSet.current.instance_variable_set(:@features, {})
|
46
|
+
Module.new do
|
47
|
+
extend Flipflop::Configurable
|
48
|
+
feature :world_domination, description: "Try and take over the world!"
|
49
|
+
feature :shiny_things, default: true
|
50
|
+
end
|
51
|
+
|
52
|
+
Capybara.current_session.driver.browser.clear_cookies
|
53
|
+
Flipflop::Feature.delete_all
|
54
|
+
|
55
|
+
visit "/flipflop"
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should show feature rows" do
|
59
|
+
assert_equal ["World domination", "Shiny things"],
|
60
|
+
all("tr[data-feature] td.name").map(&:text)
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should show feature descriptions" do
|
64
|
+
assert_equal ["Try and take over the world!", "Shiny things."],
|
65
|
+
all("tr[data-feature] td.description").map(&:text)
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "with cookie strategy" do
|
69
|
+
it "should enable feature" do
|
70
|
+
within("tr[data-feature=world-domination] td[data-strategy=cookie]") do
|
71
|
+
click_on "on"
|
72
|
+
end
|
73
|
+
|
74
|
+
within("tr[data-feature=world-domination]") do
|
75
|
+
assert_equal "on", first("td.status").text
|
76
|
+
assert_equal "on", first("td[data-strategy=cookie] input.active[type=submit]").value
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should disable feature" do
|
81
|
+
within("tr[data-feature=world-domination] td[data-strategy=cookie]") do
|
82
|
+
click_on "off"
|
83
|
+
end
|
84
|
+
|
85
|
+
within("tr[data-feature=world-domination]") do
|
86
|
+
assert_equal "off", first("td.status").text
|
87
|
+
assert_equal "off", first("td[data-strategy=cookie] input.active[type=submit]").value
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should enable and clear feature" do
|
92
|
+
within("tr[data-feature=world-domination] td[data-strategy=cookie]") do
|
93
|
+
click_on "on"
|
94
|
+
end
|
95
|
+
|
96
|
+
within("tr[data-feature=world-domination] td[data-strategy=cookie]") do
|
97
|
+
click_on "clear"
|
98
|
+
end
|
99
|
+
|
100
|
+
within("tr[data-feature=world-domination]") do
|
101
|
+
assert_equal "off", first("td.status").text
|
102
|
+
refute has_selector?("td[data-strategy=cookie] input.active[type=submit]")
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe "with active record strategy" do
|
108
|
+
it "should enable feature" do
|
109
|
+
within("tr[data-feature=world-domination] td[data-strategy=active-record]") do
|
110
|
+
click_on "on"
|
111
|
+
end
|
112
|
+
|
113
|
+
within("tr[data-feature=world-domination]") do
|
114
|
+
assert_equal "on", first("td.status").text
|
115
|
+
assert_equal "on", first("td[data-strategy=active-record] input.active[type=submit]").value
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
it "should disable feature" do
|
120
|
+
within("tr[data-feature=world-domination] td[data-strategy=active-record]") do
|
121
|
+
click_on "off"
|
122
|
+
end
|
123
|
+
|
124
|
+
within("tr[data-feature=world-domination]") do
|
125
|
+
assert_equal "off", first("td.status").text
|
126
|
+
assert_equal "off", first("td[data-strategy=active-record] input.active[type=submit]").value
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
it "should enable and clear feature" do
|
131
|
+
within("tr[data-feature=world-domination] td[data-strategy=active-record]") do
|
132
|
+
click_on "on"
|
133
|
+
end
|
134
|
+
|
135
|
+
within("tr[data-feature=world-domination] td[data-strategy=active-record]") do
|
136
|
+
click_on "clear"
|
137
|
+
end
|
138
|
+
|
139
|
+
within("tr[data-feature=world-domination]") do
|
140
|
+
assert_equal "off", first("td.status").text
|
141
|
+
refute has_selector?("td[data-strategy=active-record] input.active[type=submit]")
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
describe "with hidden strategy" do
|
148
|
+
before do
|
149
|
+
Flipflop::FeatureSet.current.instance_variable_set(:@strategies, {})
|
150
|
+
Module.new do
|
151
|
+
extend Flipflop::Configurable
|
152
|
+
strategy :query_string, hidden: true
|
153
|
+
end
|
154
|
+
|
155
|
+
visit "/flipflop"
|
156
|
+
end
|
157
|
+
|
158
|
+
it "should not show hidden strategy" do
|
159
|
+
assert_equal [], all("thead th").map(&:text)[3..-1]
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
require "bundler/setup"
|
2
|
+
require "flipflop"
|
3
|
+
|
4
|
+
gem "minitest"
|
5
|
+
require "minitest/autorun"
|
6
|
+
|
7
|
+
require "action_controller"
|
8
|
+
|
9
|
+
# Who is setting this to true? :o
|
10
|
+
$VERBOSE = false
|
11
|
+
|
12
|
+
def create_request
|
13
|
+
env = Rack::MockRequest.env_for("/example")
|
14
|
+
request = ActionDispatch::TestRequest.new(env)
|
15
|
+
request.host = "example.com"
|
16
|
+
|
17
|
+
class << request
|
18
|
+
def cookie_jar
|
19
|
+
@cookie_jar ||= begin
|
20
|
+
method = ActionDispatch::Cookies::CookieJar.method(:build)
|
21
|
+
if method.arity == 2 # Rails 5.0
|
22
|
+
method.call(self, {})
|
23
|
+
else
|
24
|
+
method.call(self)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
request
|
31
|
+
end
|
32
|
+
|
33
|
+
def reload_constant(name)
|
34
|
+
ActiveSupport::Dependencies.remove_constant(name.to_s)
|
35
|
+
path = ActiveSupport::Dependencies.search_for_file(name.to_s.underscore).sub!(/\.rb\z/, "")
|
36
|
+
ActiveSupport::Dependencies.loaded.delete(path)
|
37
|
+
Object.const_get(name)
|
38
|
+
end
|
39
|
+
|
40
|
+
class TestApp
|
41
|
+
class << self
|
42
|
+
def new
|
43
|
+
ActiveSupport::Dependencies.remove_constant("App")
|
44
|
+
super.tap do |current|
|
45
|
+
current.create!
|
46
|
+
current.load!
|
47
|
+
current.migrate!
|
48
|
+
reload_constant("Flipflop::Feature")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def create!
|
54
|
+
require "rails/generators"
|
55
|
+
require "rails/generators/rails/app/app_generator"
|
56
|
+
require "generators/flipflop/install/install_generator"
|
57
|
+
|
58
|
+
FileUtils.rm_rf(File.expand_path("../../tmp/app", __FILE__))
|
59
|
+
Dir.chdir(File.expand_path("../..", __FILE__))
|
60
|
+
|
61
|
+
Rails::Generators::AppGenerator.new(["tmp/app"],
|
62
|
+
quiet: true,
|
63
|
+
skip_active_job: true,
|
64
|
+
skip_bundle: true,
|
65
|
+
skip_gemfile: true,
|
66
|
+
skip_git: true,
|
67
|
+
skip_javascript: true,
|
68
|
+
skip_keeps: true,
|
69
|
+
skip_spring: true,
|
70
|
+
skip_test_unit: true,
|
71
|
+
skip_turbolinks: true,
|
72
|
+
).invoke_all
|
73
|
+
|
74
|
+
Flipflop::InstallGenerator.new([],
|
75
|
+
quiet: true,
|
76
|
+
).invoke_all
|
77
|
+
end
|
78
|
+
|
79
|
+
def load!
|
80
|
+
ENV["RAILS_ENV"] = "test"
|
81
|
+
require "rails"
|
82
|
+
require "flipflop/engine"
|
83
|
+
require File.expand_path("../../tmp/app/config/environment", __FILE__)
|
84
|
+
ActiveSupport::Dependencies.mechanism = :load
|
85
|
+
load(Rails.application.paths["config/features.rb"].existent.first)
|
86
|
+
require "capybara/rails"
|
87
|
+
end
|
88
|
+
|
89
|
+
def migrate!
|
90
|
+
ActiveRecord::Base.establish_connection
|
91
|
+
|
92
|
+
ActiveRecord::Tasks::DatabaseTasks.create_current
|
93
|
+
ActiveRecord::Migration.verbose = false
|
94
|
+
ActiveRecord::Migrator.migrate(Rails.application.paths["db/migrate"].to_a)
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require File.expand_path("../../test_helper", __FILE__)
|
2
|
+
|
3
|
+
describe Flipflop::Configurable do
|
4
|
+
subject do
|
5
|
+
Flipflop::FeatureSet.current.reset!
|
6
|
+
Module.new do
|
7
|
+
extend Flipflop::Configurable
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "feature" do
|
12
|
+
it "should append feature definition" do
|
13
|
+
subject.feature(:one, default: true)
|
14
|
+
subject.feature(:two, default: false)
|
15
|
+
|
16
|
+
assert_equal [:one, :two],
|
17
|
+
Flipflop::FeatureSet.current.features.map(&:key)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should append feature definition with default" do
|
21
|
+
subject.feature(:one, default: true)
|
22
|
+
subject.feature(:two, default: false)
|
23
|
+
|
24
|
+
assert_equal [true, false],
|
25
|
+
Flipflop::FeatureSet.current.features.map(&:default)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "strategy" do
|
30
|
+
it "should append strategy objects" do
|
31
|
+
strategy_class = Class.new(Flipflop::Strategies::AbstractStrategy)
|
32
|
+
strategies = [
|
33
|
+
strategy_class.new,
|
34
|
+
strategy_class.new,
|
35
|
+
]
|
36
|
+
|
37
|
+
subject.strategy(strategies[0])
|
38
|
+
subject.strategy(strategies[1])
|
39
|
+
|
40
|
+
assert_equal strategies, Flipflop::FeatureSet.current.strategies
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should append strategy classes" do
|
44
|
+
strategies = [
|
45
|
+
Class.new(Flipflop::Strategies::AbstractStrategy),
|
46
|
+
Class.new(Flipflop::Strategies::AbstractStrategy),
|
47
|
+
]
|
48
|
+
|
49
|
+
subject.strategy(strategies[0])
|
50
|
+
subject.strategy(strategies[1])
|
51
|
+
|
52
|
+
assert_equal strategies, Flipflop::FeatureSet.current.strategies.map(&:class)
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should append strategy classes with options" do
|
56
|
+
strategy_class = Class.new(Flipflop::Strategies::AbstractStrategy)
|
57
|
+
|
58
|
+
subject.strategy(strategy_class, name: "my strategy")
|
59
|
+
subject.strategy(strategy_class, name: "awesome strategy")
|
60
|
+
|
61
|
+
assert_equal ["my strategy", "awesome strategy"],
|
62
|
+
Flipflop::FeatureSet.current.strategies.map(&:name)
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should append strategy symbols" do
|
66
|
+
subject.strategy(:cookie)
|
67
|
+
subject.strategy(:query_string)
|
68
|
+
|
69
|
+
assert_equal [
|
70
|
+
Flipflop::Strategies::CookieStrategy,
|
71
|
+
Flipflop::Strategies::QueryStringStrategy
|
72
|
+
], Flipflop::FeatureSet.current.strategies.map(&:class)
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should append strategy symbols with options" do
|
76
|
+
subject.strategy(:cookie, name: "my strategy")
|
77
|
+
subject.strategy(:query_string, name: "awesome strategy")
|
78
|
+
|
79
|
+
assert_equal ["my strategy", "awesome strategy"],
|
80
|
+
Flipflop::FeatureSet.current.strategies.map(&:name)
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should append strategy lambda" do
|
84
|
+
subject.strategy { |feature| "hi!" }
|
85
|
+
|
86
|
+
assert_equal [Flipflop::Strategies::LambdaStrategy],
|
87
|
+
Flipflop::FeatureSet.current.strategies.map(&:class)
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should append strategy lambda with name" do
|
91
|
+
subject.strategy(:my_strategy) { |feature| "hi!" }
|
92
|
+
|
93
|
+
assert_equal ["my_strategy"],
|
94
|
+
Flipflop::FeatureSet.current.strategies.map(&:name)
|
95
|
+
end
|
96
|
+
|
97
|
+
it "should append strategy lambda with name and options" do
|
98
|
+
subject.strategy("my strategy", description: "awesome") { |feature| "hi!" }
|
99
|
+
|
100
|
+
assert_equal ["awesome"],
|
101
|
+
Flipflop::FeatureSet.current.strategies.map(&:description)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require File.expand_path("../../test_helper", __FILE__)
|
2
|
+
|
3
|
+
describe Flipflop::FeatureCache do
|
4
|
+
subject do
|
5
|
+
Flipflop::FeatureCache.current
|
6
|
+
end
|
7
|
+
|
8
|
+
after do
|
9
|
+
Flipflop::FeatureCache.current.disable!
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "current" do
|
13
|
+
it "should return same instance" do
|
14
|
+
current = subject
|
15
|
+
assert_equal current, Flipflop::FeatureCache.current
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should return new instance in different thread" do
|
19
|
+
current = subject
|
20
|
+
refute_equal current, Thread.new { Flipflop::FeatureCache.current }.value
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "when enabled" do
|
25
|
+
before do
|
26
|
+
subject.enable!
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "enabled" do
|
30
|
+
it "should return true" do
|
31
|
+
assert_equal true, subject.enabled?
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "fetch" do
|
36
|
+
it "should store value by key" do
|
37
|
+
subject.fetch(:key) { 1 }
|
38
|
+
assert_equal 1, subject.fetch(:key) { 2 }
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should not call block if cached" do
|
42
|
+
called = false
|
43
|
+
subject.fetch(:key) { 1 }
|
44
|
+
subject.fetch(:key) { called = true }
|
45
|
+
assert_equal false, called
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "clear" do
|
50
|
+
it "should empty cache" do
|
51
|
+
subject.fetch(:key) { 1 }
|
52
|
+
subject.clear!
|
53
|
+
assert_equal 2, subject.fetch(:key) { 2 }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "when disabled" do
|
59
|
+
before do
|
60
|
+
subject.disable!
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "enabled" do
|
64
|
+
it "should return false" do
|
65
|
+
assert_equal false, subject.enabled?
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "fetch" do
|
70
|
+
it "should not store value" do
|
71
|
+
subject.fetch(:key) { 1 }
|
72
|
+
assert_equal 2, subject.fetch(:key) { 2 }
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should always call block" do
|
76
|
+
called = false
|
77
|
+
subject.fetch(:key) { 1 }
|
78
|
+
subject.fetch(:key) { called = true }
|
79
|
+
assert_equal true, called
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe "enable" do
|
85
|
+
it "should not clear cache" do
|
86
|
+
subject.enable!
|
87
|
+
subject.fetch(:key) { 1 }
|
88
|
+
subject.enable!
|
89
|
+
assert_equal 1, subject.fetch(:key) { 2 }
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe "disable" do
|
94
|
+
it "should clear cache" do
|
95
|
+
subject.enable!
|
96
|
+
subject.fetch(:key) { 1 }
|
97
|
+
subject.disable!
|
98
|
+
subject.enable!
|
99
|
+
assert_equal 2, subject.fetch(:key) { 2 }
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe "middleware" do
|
104
|
+
subject do
|
105
|
+
app = ->(env) {
|
106
|
+
raise env["error"] if env["error"]
|
107
|
+
env["cache"] = Flipflop::FeatureCache.current.enabled?
|
108
|
+
return [200, {}, ["ok"]]
|
109
|
+
}
|
110
|
+
Flipflop::FeatureCache::Middleware.new(app)
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should call app" do
|
114
|
+
response = subject.call({})
|
115
|
+
assert_equal "ok", response[2].to_a.join
|
116
|
+
end
|
117
|
+
|
118
|
+
it "should enable cache before request" do
|
119
|
+
response = subject.call(env = {})
|
120
|
+
response[2].try(:close)
|
121
|
+
assert_equal true, env["cache"]
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should disable cache after request" do
|
125
|
+
response = subject.call({})
|
126
|
+
response[2].try(:close)
|
127
|
+
assert_equal false, Flipflop::FeatureCache.current.enabled?
|
128
|
+
end
|
129
|
+
|
130
|
+
it "should disable cache after error" do
|
131
|
+
subject.call({ "error" => "boo!" }) rescue nil
|
132
|
+
assert_equal false, Flipflop::FeatureCache.current.enabled?
|
133
|
+
end
|
134
|
+
|
135
|
+
it "should not change cache if already enabled" do
|
136
|
+
Flipflop::FeatureCache.current.enable!
|
137
|
+
response = subject.call({})
|
138
|
+
response[2].try(:close)
|
139
|
+
assert_equal true, Flipflop::FeatureCache.current.enabled?
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require File.expand_path("../../test_helper", __FILE__)
|
2
|
+
|
3
|
+
describe Flipflop::FeatureDefinition do
|
4
|
+
describe "with defaults" do
|
5
|
+
subject do
|
6
|
+
Flipflop::FeatureDefinition.new(:my_key)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should have specified key" do
|
10
|
+
assert_equal :my_key, subject.key
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should have humanized description" do
|
14
|
+
assert_equal "My key.", subject.description
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should default to false" do
|
18
|
+
assert_equal false, subject.default
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "with options" do
|
23
|
+
subject do
|
24
|
+
Flipflop::FeatureDefinition.new(:my_key,
|
25
|
+
default: true,
|
26
|
+
description: "Awesome feature",
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should have specified key" do
|
31
|
+
assert_equal :my_key, subject.key
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should have specified description" do
|
35
|
+
assert_equal "Awesome feature", subject.description
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should have specified default" do
|
39
|
+
assert_equal true, subject.default
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|