green_flag 0.1.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 +7 -0
- data/.gitignore +7 -0
- data/Gemfile +17 -0
- data/Gemfile.lock +138 -0
- data/MIT-LICENSE +20 -0
- data/README.md +43 -0
- data/Rakefile +38 -0
- data/app/assets/images/green_flag/.gitkeep +0 -0
- data/app/assets/javascripts/green_flag/admin/features.js +353 -0
- data/app/assets/javascripts/green_flag/admin/rules.js +2 -0
- data/app/assets/javascripts/green_flag/application.js +14 -0
- data/app/assets/stylesheets/green_flag/admin/features.css.scss +36 -0
- data/app/assets/stylesheets/green_flag/admin/rules.css.scss +3 -0
- data/app/assets/stylesheets/green_flag/application.css +13 -0
- data/app/controllers/green_flag/admin/feature_decision_summaries_controller.rb +33 -0
- data/app/controllers/green_flag/admin/features_controller.rb +32 -0
- data/app/controllers/green_flag/admin/rule_lists_controller.rb +28 -0
- data/app/controllers/green_flag/admin/white_list_users_controller.rb +39 -0
- data/app/controllers/green_flag/site_visitor_management.rb +51 -0
- data/app/helpers/green_flag/application_helper.rb +4 -0
- data/app/models/green_flag/feature.rb +84 -0
- data/app/models/green_flag/feature_decision.rb +77 -0
- data/app/models/green_flag/feature_event.rb +9 -0
- data/app/models/green_flag/rule.rb +66 -0
- data/app/models/green_flag/site_visitor.rb +49 -0
- data/app/models/green_flag/user_group.rb +8 -0
- data/app/models/green_flag/visitor_group.rb +60 -0
- data/app/views/green_flag/admin/features/index.html.erb +14 -0
- data/app/views/green_flag/admin/features/show.html.erb +144 -0
- data/app/views/layouts/green_flag/application.html.erb +25 -0
- data/config/routes.rb +12 -0
- data/db/migrate/20140502112602_create_green_flag_site_visitors.rb +9 -0
- data/db/migrate/20140502221059_create_green_flag_features.rb +10 -0
- data/db/migrate/20140502221423_create_green_flag_feature_decisions.rb +13 -0
- data/db/migrate/20140505204611_add_visitor_code_to_site_visitors.rb +8 -0
- data/db/migrate/20140511045110_create_green_flag_rules.rb +12 -0
- data/db/migrate/20140513203728_set_default_percentage_in_green_flag_rules.rb +10 -0
- data/db/migrate/20140514202337_require_ordering_for_green_flag_rules.rb +13 -0
- data/db/migrate/20140516214909_add_restrictions_to_green_flag_rules.rb +13 -0
- data/db/migrate/20150211214159_create_green_flag_feature_events.rb +13 -0
- data/db/migrate/20150213191101_add_rule_id_to_green_flag_feature_decisions.rb +5 -0
- data/db/migrate/20150218035000_add_version_number_to_green_flag_rules.rb +9 -0
- data/db/migrate/20150218035805_add_version_number_to_green_flag_features.rb +9 -0
- data/db/migrate/20150218171852_add_version_number_to_green_flag_rules_indices.rb +19 -0
- data/green_flag.gemspec +34 -0
- data/green_flag.png +0 -0
- data/green_flag_small.png +0 -0
- data/lib/green_flag/engine.rb +16 -0
- data/lib/green_flag/version.rb +3 -0
- data/lib/green_flag.rb +4 -0
- data/lib/tasks/green_flag_tasks.rake +4 -0
- data/script/rails +8 -0
- data/spec/controllers/admin/feature_decision_summaries_controller_spec.rb +17 -0
- data/spec/controllers/admin/features_controller_spec.rb +21 -0
- data/spec/controllers/admin/rule_lists_controller_spec.rb +25 -0
- data/spec/controllers/admin/white_list_users_controller_spec.rb +15 -0
- data/spec/controllers/site_visitor_management_spec.rb +54 -0
- data/spec/dummy/README.rdoc +261 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/assets/javascripts/application.js +15 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/controllers/application_controller.rb +7 -0
- data/spec/dummy/app/controllers/feature_check_controller.rb +10 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/mailers/.gitkeep +0 -0
- data/spec/dummy/app/models/.gitkeep +0 -0
- data/spec/dummy/app/models/user.rb +3 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config/application.rb +65 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +43 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +37 -0
- data/spec/dummy/config/environments/production.rb +67 -0
- data/spec/dummy/config/environments/test.rb +37 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +15 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +6 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/db/migrate/20150726195118_create_users.rb +8 -0
- data/spec/dummy/db/migrate/20150726204409_add_email_to_users.rb +5 -0
- data/spec/dummy/db/schema.rb +78 -0
- data/spec/dummy/lib/assets/.gitkeep +0 -0
- data/spec/dummy/log/.gitkeep +0 -0
- data/spec/dummy/log/development.log +8341 -0
- data/spec/dummy/log/test.log +34578 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +25 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/dummy/tmp/cache/assets/C47/3D0/sprockets%2F8f17c33229239b023190617bf2e915a3 +0 -0
- data/spec/dummy/tmp/cache/assets/C81/770/sprockets%2Fdbcf34796b062155788f0b550808541a +0 -0
- data/spec/dummy/tmp/cache/assets/C89/D60/sprockets%2F73cd073739a0655341b7278fae57518f +0 -0
- data/spec/dummy/tmp/cache/assets/CB6/8F0/sprockets%2F5ea0f1f2583683678e122a9a9391a80f +0 -0
- data/spec/dummy/tmp/cache/assets/CD8/370/sprockets%2F357970feca3ac29060c1e3861e2c0953 +0 -0
- data/spec/dummy/tmp/cache/assets/CE7/FF0/sprockets%2Fe45f3a7675a8c5a5b064117792bf5e28 +0 -0
- data/spec/dummy/tmp/cache/assets/D07/670/sprockets%2F761d03a66b753d628feccd12072c814c +0 -0
- data/spec/dummy/tmp/cache/assets/D10/860/sprockets%2F4582878dbb5b72bfa76615d31b34ed51 +0 -0
- data/spec/dummy/tmp/cache/assets/D13/270/sprockets%2F701a30cd450ae3cfa114092bafc16004 +0 -0
- data/spec/dummy/tmp/cache/assets/D15/C10/sprockets%2F6f42f843c916d7864a0dfa912fb3194a +0 -0
- data/spec/dummy/tmp/cache/assets/D18/860/sprockets%2Fce00769800ae939cebb28501947ea96f +0 -0
- data/spec/dummy/tmp/cache/assets/D32/A10/sprockets%2F13fe41fee1fe35b49d145bcc06610705 +0 -0
- data/spec/dummy/tmp/cache/assets/D4A/970/sprockets%2F2a7d3b403cbdd8b59d57d26964ea5768 +0 -0
- data/spec/dummy/tmp/cache/assets/D4E/1B0/sprockets%2Ff7cbd26ba1d28d48de824f0e94586655 +0 -0
- data/spec/dummy/tmp/cache/assets/D59/090/sprockets%2F88788ba6a64e6279624e5c2ff7eead53 +0 -0
- data/spec/dummy/tmp/cache/assets/D5A/EA0/sprockets%2Fd771ace226fc8215a3572e0aa35bb0d6 +0 -0
- data/spec/dummy/tmp/cache/assets/D5C/330/sprockets%2Fd1b1c9a53f4a8a5827e5b02bf0e100e8 +0 -0
- data/spec/dummy/tmp/cache/assets/D68/760/sprockets%2Fea24808c41a3dff75b995b0f090e1a6a +0 -0
- data/spec/dummy/tmp/cache/assets/D6A/580/sprockets%2F18c12847aa1bb46ce9b5661f0e9e5fb0 +0 -0
- data/spec/dummy/tmp/cache/assets/DA1/210/sprockets%2F25a2979e392c8bc3adce7075cf19ab4b +0 -0
- data/spec/dummy/tmp/cache/assets/DA9/2C0/sprockets%2Ff95d82b2bbb6db8ffe1a87f67b415291 +0 -0
- data/spec/dummy/tmp/cache/assets/DB2/0C0/sprockets%2F95cf35cd3e97774df3c41ee0ef564a8d +0 -0
- data/spec/dummy/tmp/cache/assets/DDC/400/sprockets%2Fcffd775d018f68ce5dba1ee0d951a994 +0 -0
- data/spec/dummy/tmp/cache/assets/E04/890/sprockets%2F2f5173deea6c795b8fdde723bb4b63af +0 -0
- data/spec/dummy/tmp/cache/assets/E07/200/sprockets%2F82a8ce7f5bcfb07f773df4cbfeb04762 +0 -0
- data/spec/dummy/tmp/cache/assets/E34/D30/sprockets%2F99c2d0bbd78f1b867beeb3a2eefda618 +0 -0
- data/spec/factories/green_flag/feature.rb +7 -0
- data/spec/factories/green_flag/rule.rb +9 -0
- data/spec/features/admin_spec.rb +16 -0
- data/spec/features/visitor_spec.rb +36 -0
- data/spec/models/green_flag/feature_decision_spec.rb +102 -0
- data/spec/models/green_flag/feature_event_spec.rb +4 -0
- data/spec/models/green_flag/feature_spec.rb +135 -0
- data/spec/models/green_flag/rule_spec.rb +107 -0
- data/spec/models/green_flag/site_visitor_spec.rb +95 -0
- data/spec/models/green_flag/user_group_spec.rb +24 -0
- data/spec/models/green_flag/visitor_group_spec.rb +81 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/support/controller_helpers.rb +7 -0
- metadata +359 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>The page you were looking for doesn't exist (404)</title>
|
|
5
|
+
<style type="text/css">
|
|
6
|
+
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
|
|
7
|
+
div.dialog {
|
|
8
|
+
width: 25em;
|
|
9
|
+
padding: 0 4em;
|
|
10
|
+
margin: 4em auto 0 auto;
|
|
11
|
+
border: 1px solid #ccc;
|
|
12
|
+
border-right-color: #999;
|
|
13
|
+
border-bottom-color: #999;
|
|
14
|
+
}
|
|
15
|
+
h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
|
|
16
|
+
</style>
|
|
17
|
+
</head>
|
|
18
|
+
|
|
19
|
+
<body>
|
|
20
|
+
<!-- This file lives in public/404.html -->
|
|
21
|
+
<div class="dialog">
|
|
22
|
+
<h1>The page you were looking for doesn't exist.</h1>
|
|
23
|
+
<p>You may have mistyped the address or the page may have moved.</p>
|
|
24
|
+
</div>
|
|
25
|
+
</body>
|
|
26
|
+
</html>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>The change you wanted was rejected (422)</title>
|
|
5
|
+
<style type="text/css">
|
|
6
|
+
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
|
|
7
|
+
div.dialog {
|
|
8
|
+
width: 25em;
|
|
9
|
+
padding: 0 4em;
|
|
10
|
+
margin: 4em auto 0 auto;
|
|
11
|
+
border: 1px solid #ccc;
|
|
12
|
+
border-right-color: #999;
|
|
13
|
+
border-bottom-color: #999;
|
|
14
|
+
}
|
|
15
|
+
h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
|
|
16
|
+
</style>
|
|
17
|
+
</head>
|
|
18
|
+
|
|
19
|
+
<body>
|
|
20
|
+
<!-- This file lives in public/422.html -->
|
|
21
|
+
<div class="dialog">
|
|
22
|
+
<h1>The change you wanted was rejected.</h1>
|
|
23
|
+
<p>Maybe you tried to change something you didn't have access to.</p>
|
|
24
|
+
</div>
|
|
25
|
+
</body>
|
|
26
|
+
</html>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>We're sorry, but something went wrong (500)</title>
|
|
5
|
+
<style type="text/css">
|
|
6
|
+
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
|
|
7
|
+
div.dialog {
|
|
8
|
+
width: 25em;
|
|
9
|
+
padding: 0 4em;
|
|
10
|
+
margin: 4em auto 0 auto;
|
|
11
|
+
border: 1px solid #ccc;
|
|
12
|
+
border-right-color: #999;
|
|
13
|
+
border-bottom-color: #999;
|
|
14
|
+
}
|
|
15
|
+
h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
|
|
16
|
+
</style>
|
|
17
|
+
</head>
|
|
18
|
+
|
|
19
|
+
<body>
|
|
20
|
+
<!-- This file lives in public/500.html -->
|
|
21
|
+
<div class="dialog">
|
|
22
|
+
<h1>We're sorry, but something went wrong.</h1>
|
|
23
|
+
</div>
|
|
24
|
+
</body>
|
|
25
|
+
</html>
|
|
File without changes
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
|
|
3
|
+
|
|
4
|
+
APP_PATH = File.expand_path('../../config/application', __FILE__)
|
|
5
|
+
require File.expand_path('../../config/boot', __FILE__)
|
|
6
|
+
require 'rails/commands'
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
feature 'Editing a feature in the admin system' do
|
|
4
|
+
|
|
5
|
+
let(:feature) { FactoryGirl.create(:green_flag_feature) }
|
|
6
|
+
|
|
7
|
+
scenario 'view edit page' do
|
|
8
|
+
code = feature.code
|
|
9
|
+
|
|
10
|
+
visit '/green_flag/admin/features'
|
|
11
|
+
expect(page).to have_text(code)
|
|
12
|
+
click_link code
|
|
13
|
+
expect(page).to have_text(code)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
feature 'Visiting a page that checks for a feature' do
|
|
4
|
+
|
|
5
|
+
let(:feature) { FactoryGirl.create(:green_flag_feature, code: 'test_feature') }
|
|
6
|
+
|
|
7
|
+
scenario 'when the feature does not exist' do
|
|
8
|
+
visit '/feature_checks'
|
|
9
|
+
expect(page).to have_text("NOT ENABLED")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
scenario 'when the feature exists, and is closed' do
|
|
13
|
+
feature
|
|
14
|
+
visit '/feature_checks'
|
|
15
|
+
expect(page).to have_text("NOT ENABLED")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
scenario 'when the feature exists, and is closed, but the user is whitelisted' do
|
|
19
|
+
visit '/feature_checks'
|
|
20
|
+
expect(page).to have_text("NOT ENABLED")
|
|
21
|
+
|
|
22
|
+
# manually enable the feature for the visitor
|
|
23
|
+
fd = GreenFlag::FeatureDecision.order(:created_at).last
|
|
24
|
+
fd.enabled = true
|
|
25
|
+
fd.save!
|
|
26
|
+
|
|
27
|
+
visit '/feature_checks'
|
|
28
|
+
expect(page).to have_text("IS ENABLED")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
scenario 'when the feature exists, and is open' do
|
|
32
|
+
pending "Set up a rule with everyone 100% open"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe GreenFlag::FeatureDecision do
|
|
4
|
+
|
|
5
|
+
let(:feature) { GreenFlag::Feature.create(code: 'test_feature_code') }
|
|
6
|
+
let(:site_visitor) { GreenFlag::SiteVisitor.create(visitor_code: '123') }
|
|
7
|
+
|
|
8
|
+
describe '.feature_enabled?' do
|
|
9
|
+
subject { GreenFlag::FeatureDecision.feature_enabled?(:test_feature_code, site_visitor.id) }
|
|
10
|
+
|
|
11
|
+
it "creates the Feature when it doesn't exist" do
|
|
12
|
+
expect { subject }.to change { GreenFlag::Feature.count }.by(1)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
context 'when the FeatureDecision does not exist' do
|
|
16
|
+
it 'is false by default' do
|
|
17
|
+
expect(subject).to be_false
|
|
18
|
+
end
|
|
19
|
+
it 'creates a new FeatureDecision' do
|
|
20
|
+
expect { subject }.to change { GreenFlag::FeatureDecision.count }.by(1)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
context 'when the FeatureDecision exists' do
|
|
25
|
+
let!(:feature_decision) {
|
|
26
|
+
GreenFlag::FeatureDecision.create(
|
|
27
|
+
feature_id: feature.id,
|
|
28
|
+
site_visitor_id: site_visitor.id,
|
|
29
|
+
enabled: false)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
it 'is false when FeatureDecision is not enabled' do
|
|
33
|
+
feature_decision.update_attribute(:enabled, false)
|
|
34
|
+
expect(subject).to be_false
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it 'is true when FeatureDecision is enabled' do
|
|
38
|
+
feature_decision.update_attribute(:enabled, true)
|
|
39
|
+
expect(subject).to be_true
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
describe '.ensure_feature_enabled' do
|
|
45
|
+
let(:user) { double(id: 1) }
|
|
46
|
+
subject { GreenFlag::FeatureDecision.ensure_feature_enabled(feature.code, user) }
|
|
47
|
+
|
|
48
|
+
context 'when the user has no SiteVisitor' do
|
|
49
|
+
it 'creates a SiteVisitor for the user' do
|
|
50
|
+
expect { subject }.to change { GreenFlag::SiteVisitor.count }.by(1)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
context 'when the FeatureDecision does not exist' do
|
|
55
|
+
it 'creates a new FeatureDecision' do
|
|
56
|
+
expect { subject }.to change { GreenFlag::FeatureDecision.count }.by(1)
|
|
57
|
+
end
|
|
58
|
+
it { should be_enabled }
|
|
59
|
+
it { should_not be_manual }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
context 'when the FeatureDecision already exits' do
|
|
63
|
+
let!(:site_visitor) { GreenFlag::SiteVisitor.create(visitor_code: '123', user_id: user.id) }
|
|
64
|
+
let!(:feature_decision) { GreenFlag::FeatureDecision.create(feature: feature, site_visitor: site_visitor) }
|
|
65
|
+
|
|
66
|
+
it 'does not creates a new FeatureDecision' do
|
|
67
|
+
expect { subject }.to_not change { GreenFlag::FeatureDecision.count }
|
|
68
|
+
end
|
|
69
|
+
it { should be_enabled }
|
|
70
|
+
it { should_not be_manual }
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
describe 'safe_save!' do
|
|
75
|
+
let(:feature_id) { 1 }
|
|
76
|
+
let(:site_visitor_id) { 1 }
|
|
77
|
+
|
|
78
|
+
context 'when the database already contains a feature decision with the site visitor ID and feature ID' do
|
|
79
|
+
before(:each) do
|
|
80
|
+
@existing_fd = GreenFlag::FeatureDecision.create!(feature_id: feature.id, site_visitor_id: site_visitor_id, enabled: false)
|
|
81
|
+
@new_fd = GreenFlag::FeatureDecision.new(feature_id: feature.id, site_visitor_id: site_visitor_id, enabled: false)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it 'returns the matching feature decision that was already stored' do
|
|
85
|
+
expect(@new_fd.safe_save!).to eq @existing_fd
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
context 'when the database not already contain a feature decision with the site visitor ID and feature ID' do
|
|
90
|
+
let(:feature_decision) { GreenFlag::FeatureDecision.new(feature_id: 2, site_visitor_id: 2, enabled: false) }
|
|
91
|
+
|
|
92
|
+
it 'saves the record' do
|
|
93
|
+
expect(feature_decision).to receive(:save!)
|
|
94
|
+
feature_decision.safe_save!
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
it 'returns true' do
|
|
98
|
+
expect(feature_decision.safe_save!).to eq true
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
describe GreenFlag::Feature do
|
|
3
|
+
describe '.for_code!' do
|
|
4
|
+
subject { GreenFlag::Feature.for_code!('foo') }
|
|
5
|
+
|
|
6
|
+
it 'when feature does not exist, creates a feature' do
|
|
7
|
+
expect{ subject }.to change { GreenFlag::Feature.count }.by(1)
|
|
8
|
+
end
|
|
9
|
+
it 'when feature does exists, does not create a feature' do
|
|
10
|
+
FactoryGirl.create(:green_flag_feature, code: 'foo')
|
|
11
|
+
expect{ subject }.to_not change { GreenFlag::Feature.count }
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
describe '#decide_if_enabled_for_visitor' do
|
|
16
|
+
let(:feature) { GreenFlag::Feature.create(code: 'big_font') }
|
|
17
|
+
let(:site_visitor_id) { 1 }
|
|
18
|
+
|
|
19
|
+
let(:non_applying_rule) { double(id: 1, applies_to?: false, decision?: true) }
|
|
20
|
+
let(:true_rule) { double(id: 2, applies_to?: true, decision?: true) }
|
|
21
|
+
let(:false_rule) { double(id: 3, applies_to?: true, decision?: false) }
|
|
22
|
+
|
|
23
|
+
context 'there are no rules' do
|
|
24
|
+
it 'returns a feature decision with the correct attributes' do
|
|
25
|
+
feature_decision = feature.decide_if_enabled_for_visitor(site_visitor_id)
|
|
26
|
+
|
|
27
|
+
expect(feature_decision.rule_id).to be_nil
|
|
28
|
+
expect(feature_decision.enabled).to be_nil
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
context 'the first applying rule is true' do
|
|
33
|
+
it 'returns a feature decision with the correct attributes' do
|
|
34
|
+
feature.stub(rules: [non_applying_rule, true_rule])
|
|
35
|
+
|
|
36
|
+
feature_decision = feature.decide_if_enabled_for_visitor(site_visitor_id)
|
|
37
|
+
|
|
38
|
+
expect(feature_decision.rule_id).to eq true_rule.id
|
|
39
|
+
expect(feature_decision.enabled).to eq true
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
context 'the first applying rule is false' do
|
|
44
|
+
it 'returns a feature decision with the correct attributes' do
|
|
45
|
+
feature.stub(rules: [false_rule, true_rule])
|
|
46
|
+
|
|
47
|
+
feature_decision = feature.decide_if_enabled_for_visitor(site_visitor_id)
|
|
48
|
+
|
|
49
|
+
expect(feature_decision.rule_id).to eq false_rule.id
|
|
50
|
+
expect(feature_decision.enabled).to eq false
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
describe '#forget_non_manual_decisions!' do
|
|
56
|
+
let(:feature) { GreenFlag::Feature.create(code: 'big_font') }
|
|
57
|
+
let(:enabled) { true }
|
|
58
|
+
|
|
59
|
+
context 'when there are no decisions' do
|
|
60
|
+
subject { feature.forget_non_manual_decisions!(enabled) }
|
|
61
|
+
|
|
62
|
+
it 'should not attempt to delete non-manual decisions' do
|
|
63
|
+
expect(GreenFlag::FeatureDecision.non_manual).to receive(:delete_all).never
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it 'should not create a feature event' do
|
|
67
|
+
expect(GreenFlag::FeatureEvent.count).to eq 0
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
context 'when there are manual decisions' do
|
|
72
|
+
subject { feature.forget_non_manual_decisions!(enabled) }
|
|
73
|
+
|
|
74
|
+
before(:each) do
|
|
75
|
+
@manual_fd = GreenFlag::FeatureDecision.create!(feature_id: feature.id, manual: true, site_visitor_id: 1)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it 'should not delete the manual decisions' do
|
|
79
|
+
expect(GreenFlag::FeatureDecision.find_by_id(@manual_fd.id)).to be_present
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it 'should not create a feature event' do
|
|
83
|
+
expect(GreenFlag::FeatureEvent.count).to eq 0
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
context 'when there are automatic decisions' do
|
|
88
|
+
before(:each) do
|
|
89
|
+
@feature2 = GreenFlag::Feature.create!(code: 'huge_banner_ad')
|
|
90
|
+
@auto_fd = GreenFlag::FeatureDecision.create!(feature_id: @feature2.id, manual: false, enabled: true, site_visitor_id: 1)
|
|
91
|
+
|
|
92
|
+
@feature2.forget_non_manual_decisions!(enabled)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it 'should delete the automatic decisions' do
|
|
96
|
+
@feature2.forget_non_manual_decisions!(enabled)
|
|
97
|
+
expect(GreenFlag::FeatureDecision.find_by_id(@auto_fd)).to be_nil
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it 'should create a feature event with the correct attributes' do
|
|
101
|
+
feature_event = GreenFlag::FeatureEvent.last
|
|
102
|
+
expect(feature_event).to be_present
|
|
103
|
+
expect(feature_event.feature_id).to eq @feature2.id
|
|
104
|
+
expect(feature_event.event_type_code).to eq GreenFlag::FeatureEvent::ENABLED_DECISIONS_FORGOTTEN
|
|
105
|
+
expect(feature_event.count).to eq 1
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
describe 'latest_version' do
|
|
111
|
+
let(:feature) { FactoryGirl.create(:green_flag_feature, version_number: 1) }
|
|
112
|
+
|
|
113
|
+
subject { feature.latest_version }
|
|
114
|
+
|
|
115
|
+
context 'when there are rules' do
|
|
116
|
+
before(:each) do
|
|
117
|
+
@latest_version_number = 15
|
|
118
|
+
|
|
119
|
+
(0...3).each do |i|
|
|
120
|
+
FactoryGirl.create(:green_flag_rule, feature_id: feature.id, version_number: @latest_version_number - i)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
it 'returns the version number of the last rule' do
|
|
125
|
+
expect(subject).to eq @latest_version_number
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
context 'when there are no rules' do
|
|
130
|
+
it 'returns the current version number of the feature' do
|
|
131
|
+
expect(subject).to eq feature.version_number
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe GreenFlag::Rule do
|
|
4
|
+
describe '.set_rules!' do
|
|
5
|
+
let(:feature) { FactoryGirl.create(:green_flag_feature, version_number: 1) }
|
|
6
|
+
|
|
7
|
+
subject { GreenFlag::Rule.set_rules!(feature.id, rules_array) }
|
|
8
|
+
|
|
9
|
+
context 'with no rules in rules_array' do
|
|
10
|
+
let(:rules_array) { [] }
|
|
11
|
+
|
|
12
|
+
it { should be_empty }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
context 'with rules in rules_array' do
|
|
16
|
+
let(:rules_array) { [{"created_at"=>"2015-02-18T18:30:56Z",
|
|
17
|
+
"feature_id"=>2,
|
|
18
|
+
"group_key"=>"Pre-existing Visitors",
|
|
19
|
+
"id"=>14,
|
|
20
|
+
"order_by"=>0,
|
|
21
|
+
"percentage"=>22,
|
|
22
|
+
"updated_at"=>"2015-02-18T18:30:56Z",
|
|
23
|
+
"version_number"=>7},
|
|
24
|
+
{"group_key"=>"Everyone", "percentage"=>"40", "order_by"=>1}]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
it 'increments the feature version' do
|
|
28
|
+
subject
|
|
29
|
+
|
|
30
|
+
expect(feature.reload.version_number).to eq 2
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it 'creates the correct number of new rules' do
|
|
34
|
+
expect { subject }.to change { GreenFlag::Rule.count }.by(2)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it 'creates new rules with the correct attributes' do
|
|
38
|
+
subject
|
|
39
|
+
feature.reload
|
|
40
|
+
|
|
41
|
+
rule1 = feature.rules.first
|
|
42
|
+
rule2 = feature.rules.last
|
|
43
|
+
|
|
44
|
+
expect(rule1.group_key).to eq 'Pre-existing Visitors'
|
|
45
|
+
expect(rule1.order_by).to eq 0
|
|
46
|
+
expect(rule1.percentage).to eq 22
|
|
47
|
+
expect(rule1.version_number).to eq feature.version_number
|
|
48
|
+
|
|
49
|
+
expect(rule2.group_key).to eq 'Everyone'
|
|
50
|
+
expect(rule2.order_by).to eq 1
|
|
51
|
+
expect(rule2.percentage).to eq 40
|
|
52
|
+
expect(rule2.version_number).to eq feature.version_number
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it 'returns the newly-created rules' do
|
|
56
|
+
actual_return_value = subject
|
|
57
|
+
feature.reload
|
|
58
|
+
|
|
59
|
+
expected_return_value = [feature.rules.first, feature.rules.last]
|
|
60
|
+
|
|
61
|
+
expect(actual_return_value).to eq expected_return_value
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
context 'when the feature is not on the current version due to it having been reverted' do
|
|
66
|
+
let(:rules_array) { [{"created_at"=>"2015-02-18T18:30:56Z",
|
|
67
|
+
"feature_id"=>2,
|
|
68
|
+
"group_key"=>"Pre-existing Visitors",
|
|
69
|
+
"id"=>14,
|
|
70
|
+
"order_by"=>0,
|
|
71
|
+
"percentage"=>22,
|
|
72
|
+
"updated_at"=>"2015-02-18T18:30:56Z",
|
|
73
|
+
"version_number"=>7},
|
|
74
|
+
{"group_key"=>"Everyone", "percentage"=>"40", "order_by"=>1}]
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
before(:each) do
|
|
78
|
+
@latest_feature_version = 10
|
|
79
|
+
|
|
80
|
+
FactoryGirl.create(:green_flag_rule, feature_id: feature.id, version_number: @latest_feature_version)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it 'increments the feature version to a higher number than the latest version' do
|
|
84
|
+
GreenFlag::Rule.set_rules!(feature.id, rules_array)
|
|
85
|
+
|
|
86
|
+
expect(feature.reload.version_number).to eq @latest_feature_version + 1
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
describe '#decision?' do
|
|
92
|
+
it 'is always false when percentage is 0' do
|
|
93
|
+
rule = GreenFlag::Rule.new(percentage: 0)
|
|
94
|
+
10000.times do
|
|
95
|
+
expect(rule.decision?).to be_false
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it 'is always true when percentage is 100' do
|
|
100
|
+
rule = GreenFlag::Rule.new(percentage: 100)
|
|
101
|
+
10000.times do
|
|
102
|
+
expect(rule.decision?).to be_true
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
end
|