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.
Files changed (136) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +7 -0
  3. data/Gemfile +17 -0
  4. data/Gemfile.lock +138 -0
  5. data/MIT-LICENSE +20 -0
  6. data/README.md +43 -0
  7. data/Rakefile +38 -0
  8. data/app/assets/images/green_flag/.gitkeep +0 -0
  9. data/app/assets/javascripts/green_flag/admin/features.js +353 -0
  10. data/app/assets/javascripts/green_flag/admin/rules.js +2 -0
  11. data/app/assets/javascripts/green_flag/application.js +14 -0
  12. data/app/assets/stylesheets/green_flag/admin/features.css.scss +36 -0
  13. data/app/assets/stylesheets/green_flag/admin/rules.css.scss +3 -0
  14. data/app/assets/stylesheets/green_flag/application.css +13 -0
  15. data/app/controllers/green_flag/admin/feature_decision_summaries_controller.rb +33 -0
  16. data/app/controllers/green_flag/admin/features_controller.rb +32 -0
  17. data/app/controllers/green_flag/admin/rule_lists_controller.rb +28 -0
  18. data/app/controllers/green_flag/admin/white_list_users_controller.rb +39 -0
  19. data/app/controllers/green_flag/site_visitor_management.rb +51 -0
  20. data/app/helpers/green_flag/application_helper.rb +4 -0
  21. data/app/models/green_flag/feature.rb +84 -0
  22. data/app/models/green_flag/feature_decision.rb +77 -0
  23. data/app/models/green_flag/feature_event.rb +9 -0
  24. data/app/models/green_flag/rule.rb +66 -0
  25. data/app/models/green_flag/site_visitor.rb +49 -0
  26. data/app/models/green_flag/user_group.rb +8 -0
  27. data/app/models/green_flag/visitor_group.rb +60 -0
  28. data/app/views/green_flag/admin/features/index.html.erb +14 -0
  29. data/app/views/green_flag/admin/features/show.html.erb +144 -0
  30. data/app/views/layouts/green_flag/application.html.erb +25 -0
  31. data/config/routes.rb +12 -0
  32. data/db/migrate/20140502112602_create_green_flag_site_visitors.rb +9 -0
  33. data/db/migrate/20140502221059_create_green_flag_features.rb +10 -0
  34. data/db/migrate/20140502221423_create_green_flag_feature_decisions.rb +13 -0
  35. data/db/migrate/20140505204611_add_visitor_code_to_site_visitors.rb +8 -0
  36. data/db/migrate/20140511045110_create_green_flag_rules.rb +12 -0
  37. data/db/migrate/20140513203728_set_default_percentage_in_green_flag_rules.rb +10 -0
  38. data/db/migrate/20140514202337_require_ordering_for_green_flag_rules.rb +13 -0
  39. data/db/migrate/20140516214909_add_restrictions_to_green_flag_rules.rb +13 -0
  40. data/db/migrate/20150211214159_create_green_flag_feature_events.rb +13 -0
  41. data/db/migrate/20150213191101_add_rule_id_to_green_flag_feature_decisions.rb +5 -0
  42. data/db/migrate/20150218035000_add_version_number_to_green_flag_rules.rb +9 -0
  43. data/db/migrate/20150218035805_add_version_number_to_green_flag_features.rb +9 -0
  44. data/db/migrate/20150218171852_add_version_number_to_green_flag_rules_indices.rb +19 -0
  45. data/green_flag.gemspec +34 -0
  46. data/green_flag.png +0 -0
  47. data/green_flag_small.png +0 -0
  48. data/lib/green_flag/engine.rb +16 -0
  49. data/lib/green_flag/version.rb +3 -0
  50. data/lib/green_flag.rb +4 -0
  51. data/lib/tasks/green_flag_tasks.rake +4 -0
  52. data/script/rails +8 -0
  53. data/spec/controllers/admin/feature_decision_summaries_controller_spec.rb +17 -0
  54. data/spec/controllers/admin/features_controller_spec.rb +21 -0
  55. data/spec/controllers/admin/rule_lists_controller_spec.rb +25 -0
  56. data/spec/controllers/admin/white_list_users_controller_spec.rb +15 -0
  57. data/spec/controllers/site_visitor_management_spec.rb +54 -0
  58. data/spec/dummy/README.rdoc +261 -0
  59. data/spec/dummy/Rakefile +7 -0
  60. data/spec/dummy/app/assets/javascripts/application.js +15 -0
  61. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  62. data/spec/dummy/app/controllers/application_controller.rb +7 -0
  63. data/spec/dummy/app/controllers/feature_check_controller.rb +10 -0
  64. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  65. data/spec/dummy/app/mailers/.gitkeep +0 -0
  66. data/spec/dummy/app/models/.gitkeep +0 -0
  67. data/spec/dummy/app/models/user.rb +3 -0
  68. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  69. data/spec/dummy/config/application.rb +65 -0
  70. data/spec/dummy/config/boot.rb +10 -0
  71. data/spec/dummy/config/database.yml +43 -0
  72. data/spec/dummy/config/environment.rb +5 -0
  73. data/spec/dummy/config/environments/development.rb +37 -0
  74. data/spec/dummy/config/environments/production.rb +67 -0
  75. data/spec/dummy/config/environments/test.rb +37 -0
  76. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  77. data/spec/dummy/config/initializers/inflections.rb +15 -0
  78. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  79. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  80. data/spec/dummy/config/initializers/session_store.rb +8 -0
  81. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  82. data/spec/dummy/config/locales/en.yml +5 -0
  83. data/spec/dummy/config/routes.rb +6 -0
  84. data/spec/dummy/config.ru +4 -0
  85. data/spec/dummy/db/migrate/20150726195118_create_users.rb +8 -0
  86. data/spec/dummy/db/migrate/20150726204409_add_email_to_users.rb +5 -0
  87. data/spec/dummy/db/schema.rb +78 -0
  88. data/spec/dummy/lib/assets/.gitkeep +0 -0
  89. data/spec/dummy/log/.gitkeep +0 -0
  90. data/spec/dummy/log/development.log +8341 -0
  91. data/spec/dummy/log/test.log +34578 -0
  92. data/spec/dummy/public/404.html +26 -0
  93. data/spec/dummy/public/422.html +26 -0
  94. data/spec/dummy/public/500.html +25 -0
  95. data/spec/dummy/public/favicon.ico +0 -0
  96. data/spec/dummy/script/rails +6 -0
  97. data/spec/dummy/tmp/cache/assets/C47/3D0/sprockets%2F8f17c33229239b023190617bf2e915a3 +0 -0
  98. data/spec/dummy/tmp/cache/assets/C81/770/sprockets%2Fdbcf34796b062155788f0b550808541a +0 -0
  99. data/spec/dummy/tmp/cache/assets/C89/D60/sprockets%2F73cd073739a0655341b7278fae57518f +0 -0
  100. data/spec/dummy/tmp/cache/assets/CB6/8F0/sprockets%2F5ea0f1f2583683678e122a9a9391a80f +0 -0
  101. data/spec/dummy/tmp/cache/assets/CD8/370/sprockets%2F357970feca3ac29060c1e3861e2c0953 +0 -0
  102. data/spec/dummy/tmp/cache/assets/CE7/FF0/sprockets%2Fe45f3a7675a8c5a5b064117792bf5e28 +0 -0
  103. data/spec/dummy/tmp/cache/assets/D07/670/sprockets%2F761d03a66b753d628feccd12072c814c +0 -0
  104. data/spec/dummy/tmp/cache/assets/D10/860/sprockets%2F4582878dbb5b72bfa76615d31b34ed51 +0 -0
  105. data/spec/dummy/tmp/cache/assets/D13/270/sprockets%2F701a30cd450ae3cfa114092bafc16004 +0 -0
  106. data/spec/dummy/tmp/cache/assets/D15/C10/sprockets%2F6f42f843c916d7864a0dfa912fb3194a +0 -0
  107. data/spec/dummy/tmp/cache/assets/D18/860/sprockets%2Fce00769800ae939cebb28501947ea96f +0 -0
  108. data/spec/dummy/tmp/cache/assets/D32/A10/sprockets%2F13fe41fee1fe35b49d145bcc06610705 +0 -0
  109. data/spec/dummy/tmp/cache/assets/D4A/970/sprockets%2F2a7d3b403cbdd8b59d57d26964ea5768 +0 -0
  110. data/spec/dummy/tmp/cache/assets/D4E/1B0/sprockets%2Ff7cbd26ba1d28d48de824f0e94586655 +0 -0
  111. data/spec/dummy/tmp/cache/assets/D59/090/sprockets%2F88788ba6a64e6279624e5c2ff7eead53 +0 -0
  112. data/spec/dummy/tmp/cache/assets/D5A/EA0/sprockets%2Fd771ace226fc8215a3572e0aa35bb0d6 +0 -0
  113. data/spec/dummy/tmp/cache/assets/D5C/330/sprockets%2Fd1b1c9a53f4a8a5827e5b02bf0e100e8 +0 -0
  114. data/spec/dummy/tmp/cache/assets/D68/760/sprockets%2Fea24808c41a3dff75b995b0f090e1a6a +0 -0
  115. data/spec/dummy/tmp/cache/assets/D6A/580/sprockets%2F18c12847aa1bb46ce9b5661f0e9e5fb0 +0 -0
  116. data/spec/dummy/tmp/cache/assets/DA1/210/sprockets%2F25a2979e392c8bc3adce7075cf19ab4b +0 -0
  117. data/spec/dummy/tmp/cache/assets/DA9/2C0/sprockets%2Ff95d82b2bbb6db8ffe1a87f67b415291 +0 -0
  118. data/spec/dummy/tmp/cache/assets/DB2/0C0/sprockets%2F95cf35cd3e97774df3c41ee0ef564a8d +0 -0
  119. data/spec/dummy/tmp/cache/assets/DDC/400/sprockets%2Fcffd775d018f68ce5dba1ee0d951a994 +0 -0
  120. data/spec/dummy/tmp/cache/assets/E04/890/sprockets%2F2f5173deea6c795b8fdde723bb4b63af +0 -0
  121. data/spec/dummy/tmp/cache/assets/E07/200/sprockets%2F82a8ce7f5bcfb07f773df4cbfeb04762 +0 -0
  122. data/spec/dummy/tmp/cache/assets/E34/D30/sprockets%2F99c2d0bbd78f1b867beeb3a2eefda618 +0 -0
  123. data/spec/factories/green_flag/feature.rb +7 -0
  124. data/spec/factories/green_flag/rule.rb +9 -0
  125. data/spec/features/admin_spec.rb +16 -0
  126. data/spec/features/visitor_spec.rb +36 -0
  127. data/spec/models/green_flag/feature_decision_spec.rb +102 -0
  128. data/spec/models/green_flag/feature_event_spec.rb +4 -0
  129. data/spec/models/green_flag/feature_spec.rb +135 -0
  130. data/spec/models/green_flag/rule_spec.rb +107 -0
  131. data/spec/models/green_flag/site_visitor_spec.rb +95 -0
  132. data/spec/models/green_flag/user_group_spec.rb +24 -0
  133. data/spec/models/green_flag/visitor_group_spec.rb +81 -0
  134. data/spec/spec_helper.rb +20 -0
  135. data/spec/support/controller_helpers.rb +7 -0
  136. 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'
@@ -0,0 +1,7 @@
1
+ FactoryGirl.define do
2
+ factory :green_flag_feature, class: GreenFlag::Feature do
3
+ code 'feature_code'
4
+ description 'Feature Description'
5
+ version_number 1
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ FactoryGirl.define do
2
+ factory :green_flag_rule, class: GreenFlag::Rule do
3
+ group_key 'Factory Test Group'
4
+ feature_id 1
5
+ order_by 1
6
+ percentage 100
7
+ version_number 1
8
+ end
9
+ end
@@ -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,4 @@
1
+ require 'spec_helper'
2
+
3
+ describe GreenFlag::FeatureEvent do
4
+ 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