policy 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +2 -0
  3. data/.metrics +5 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +2 -0
  6. data/.travis.yml +18 -0
  7. data/.yardopts +3 -0
  8. data/Gemfile +7 -0
  9. data/Guardfile +15 -0
  10. data/LICENSE +21 -0
  11. data/README.md +223 -0
  12. data/Rakefile +17 -0
  13. data/config/metrics/STYLEGUIDE +231 -0
  14. data/config/metrics/cane.yml +5 -0
  15. data/config/metrics/churn.yml +6 -0
  16. data/config/metrics/flay.yml +2 -0
  17. data/config/metrics/metric_fu.yml +14 -0
  18. data/config/metrics/pippi.yml +3 -0
  19. data/config/metrics/reek.yml +1 -0
  20. data/config/metrics/roodi.yml +24 -0
  21. data/config/metrics/rubocop.yml +87 -0
  22. data/config/metrics/saikuro.yml +3 -0
  23. data/config/metrics/simplecov.yml +5 -0
  24. data/config/metrics/yardstick.yml +37 -0
  25. data/lib/policy/follower/followed_policies.rb +45 -0
  26. data/lib/policy/follower/followed_policy.rb +104 -0
  27. data/lib/policy/follower/names.rb +29 -0
  28. data/lib/policy/follower.rb +143 -0
  29. data/lib/policy/interface.rb +48 -0
  30. data/lib/policy/validations.rb +28 -0
  31. data/lib/policy/version.rb +9 -0
  32. data/lib/policy/violation_error.rb +52 -0
  33. data/lib/policy.rb +40 -0
  34. data/policy.gemspec +23 -0
  35. data/spec/features/follower_spec.rb +95 -0
  36. data/spec/spec_helper.rb +10 -0
  37. data/spec/tests/policy/follower/followed_policies_spec.rb +87 -0
  38. data/spec/tests/policy/follower/followed_policy_spec.rb +117 -0
  39. data/spec/tests/policy/follower/names_spec.rb +19 -0
  40. data/spec/tests/policy/follower_spec.rb +220 -0
  41. data/spec/tests/policy/interface_spec.rb +83 -0
  42. data/spec/tests/policy/validations_spec.rb +13 -0
  43. data/spec/tests/policy/violation_error_spec.rb +75 -0
  44. data/spec/tests/policy_spec.rb +35 -0
  45. metadata +142 -0
@@ -0,0 +1,95 @@
1
+ # encoding: utf-8
2
+
3
+ # The integration test for policy follower
4
+ describe Policy::Follower do
5
+
6
+ before do
7
+
8
+ Transaction = Class.new Struct.new(:sum)
9
+
10
+ module Policies
11
+
12
+ class Consistency < Policy.new(:debet, :credit)
13
+
14
+ validates :debet, :credit, presence: true
15
+ validates :sum, numericality: { equal_to: 0 }, allow_nil: true
16
+
17
+ private
18
+
19
+ def sum
20
+ debet && credit && (credit.sum + debet.sum)
21
+ end
22
+
23
+ end # class Consistency
24
+
25
+ end # module Policy
26
+
27
+ # The follower class to be tested
28
+ class Transfer < Struct.new(:withdrawal, :enrollment)
29
+ include Policy::Follower
30
+
31
+ use_policies Policies do
32
+ follow_policy :Consistency, :withdrawal, :enrollment, as: :consistency
33
+ end
34
+
35
+ end # class Transfer
36
+
37
+ end # before
38
+
39
+ let(:withdrawal) { Transaction.new(-100) }
40
+ subject { Transfer.new withdrawal, enrollment }
41
+
42
+ describe "#follow_policies!" do
43
+
44
+ context "when policy is met" do
45
+
46
+ let(:enrollment) { Transaction.new(100) }
47
+
48
+ it "passes if the policy is met" do
49
+ expect { subject.follow_policies! }.not_to raise_error
50
+ end
51
+
52
+ end # context
53
+
54
+ context "when policy is broken" do
55
+
56
+ let(:enrollment) { Transaction.new(200) }
57
+
58
+ it "fails if the policy is broken" do
59
+ expect { subject.follow_policies! }.to raise_error
60
+ end
61
+
62
+ end # context
63
+
64
+ end # describe #follow_policies!
65
+
66
+ describe "#follow_policies?" do
67
+
68
+ context "when policy is met" do
69
+
70
+ let(:enrollment) { Transaction.new(100) }
71
+
72
+ it "returns true if the policy is met" do
73
+ expect(subject).to be_follow_policies
74
+ end
75
+
76
+ end # context
77
+
78
+ context "when policy is broken" do
79
+
80
+ let(:enrollment) { Transaction.new(200) }
81
+
82
+ it "returns false if the policy is broken" do
83
+ expect(subject).not_to be_follow_policies
84
+ end
85
+
86
+ end # context
87
+
88
+ end # describe #follow_policies!
89
+
90
+ after do
91
+ %i(Transaction Transfer Policies)
92
+ .each { |const| Object.__send__(:remove_const, const) }
93
+ end
94
+
95
+ end # describe Policy::Follower
@@ -0,0 +1,10 @@
1
+ # encoding: utf-8
2
+
3
+ # Loads the RSpec test suit.
4
+ require "hexx-suit"
5
+
6
+ # Loads runtime metrics in the current scope
7
+ Hexx::Suit.load_metrics_for(self)
8
+
9
+ # Loads the code of the module.
10
+ require "policy"
@@ -0,0 +1,87 @@
1
+ # encoding: utf-8
2
+ require "ostruct"
3
+
4
+ describe Policy::Follower::FollowedPolicies do
5
+
6
+ it "is a hash" do
7
+ expect(subject).to be_kind_of Hash
8
+ end
9
+
10
+ describe "#add" do
11
+
12
+ let(:policy) { double :policy, name: :foo }
13
+
14
+ it "registers a policy" do
15
+ expect { subject.add policy }.to change { subject }.to(foo: policy)
16
+ end
17
+
18
+ end # describe #add
19
+
20
+ describe "#apply_to" do
21
+
22
+ let(:follower) { double }
23
+
24
+ before do
25
+ %i(first second third).each do |item|
26
+ subject.add double(name: item, apply_to: nil)
27
+ end
28
+ end
29
+
30
+ shared_examples "applying policies" do |applied_policies = nil|
31
+
32
+ before { applied_policies ||= subject.keys }
33
+ let(:skipped_policies) { subject.keys - applied_policies }
34
+
35
+ it "[applies policies]" do
36
+ policies = applied_policies.map(&subject.method(:[]))
37
+
38
+ policies.each do |policy|
39
+ expect(policy).to receive(:apply_to).with(follower).ordered
40
+ end
41
+ end
42
+
43
+ it "[skips policies]" do
44
+ policies = skipped_policies.map(&subject.method(:[]))
45
+
46
+ policies.each do |policy|
47
+ expect(policy).not_to receive(:apply_to)
48
+ end
49
+ end
50
+
51
+ end # shared examples
52
+
53
+ context "by default" do
54
+
55
+ after { subject.apply_to follower }
56
+
57
+ it_behaves_like "applying policies"
58
+
59
+ end # context
60
+
61
+ context "with a list of names" do
62
+
63
+ after { subject.apply_to follower, :third, :first }
64
+
65
+ it_behaves_like "applying policies", %i(third first)
66
+
67
+ end # context
68
+
69
+ context "with a repetitive names" do
70
+
71
+ after { subject.apply_to follower, :third, :third, :third }
72
+
73
+ it_behaves_like "applying policies", %i(third third third)
74
+
75
+ end # context
76
+
77
+ context "with an array of names" do
78
+
79
+ after { subject.apply_to follower, %w(third first forth) }
80
+
81
+ it_behaves_like "applying policies", %i(third first)
82
+
83
+ end # context
84
+
85
+ end # describe #apply_to
86
+
87
+ end # describe Policy::Follower::FollowedPolicies
@@ -0,0 +1,117 @@
1
+ # encoding: utf-8
2
+ require "ostruct"
3
+
4
+ describe Policy::Follower::FollowedPolicy do
5
+
6
+ before { module Namespace; class Policy < ::Policy.new(:foo, :bar); end; end }
7
+ after { Object.send :remove_const, :Namespace }
8
+
9
+ let(:namespace) { Namespace }
10
+ let(:policy_class) { Namespace::Policy }
11
+
12
+ describe ".new" do
13
+
14
+ shared_examples "refusing wrong number of attributes" do |*list|
15
+
16
+ subject { described_class.new nil, policy_class, :policy, *list }
17
+
18
+ it "raises ArgumentError" do
19
+ expect { subject }.to raise_error(ArgumentError)
20
+ end
21
+
22
+ it "sets a proper message for the exception" do
23
+ begin
24
+ subject
25
+ rescue => err
26
+ expect(err.message).to eq(
27
+ "#{ policy_class } requires 2 attribute(s)." \
28
+ " #{ list } cannot be assigned."
29
+ )
30
+ end
31
+ end
32
+
33
+ end # shared examples
34
+
35
+ it_behaves_like "refusing wrong number of attributes", :foo
36
+ it_behaves_like "refusing wrong number of attributes", :foo, :bar, :baz
37
+
38
+ end # describe .new
39
+
40
+ describe "#name" do
41
+
42
+ context "when name is set to nil" do
43
+
44
+ subject { described_class.new nil, policy_class, nil, :foo, :bar }
45
+
46
+ it "assigns uuid" do
47
+ expect(subject.name.to_s).to match(/^\h{8}-(\h{4}-){3}\h{12}$/)
48
+ end
49
+
50
+ it "is a symbol" do
51
+ expect(subject.name).to be_kind_of Symbol
52
+ end
53
+
54
+ end # context
55
+
56
+ context "when name is set explicitly" do
57
+
58
+ subject { described_class.new nil, policy_class, "policy", :foo, :bar }
59
+
60
+ it "returns the symbolized name" do
61
+ expect(subject.name).to eq :policy
62
+ end
63
+
64
+ end # context
65
+
66
+ end # describe #name
67
+
68
+ describe "#policy" do
69
+
70
+ let(:policy_name) { :Policy }
71
+
72
+ shared_examples "policy object class" do
73
+
74
+ it "returns the constant" do
75
+ expect(subject.policy).to eq policy_class
76
+ end
77
+
78
+ end # context
79
+
80
+ it_behaves_like "policy object class" do
81
+ subject { described_class.new :foo, policy_class, :foo, :bar, :baz }
82
+ end
83
+
84
+ it_behaves_like "policy object class" do
85
+ subject { described_class.new namespace, policy_name, :foo, :bar, :baz }
86
+ end
87
+
88
+ end # describe #policy
89
+
90
+ describe "#attributes" do
91
+
92
+ let(:attributes) { %i(foo bar) }
93
+ subject { described_class.new nil, policy_class, :foo, *attributes }
94
+
95
+ it "is set by the initializer" do
96
+ expect(subject.attributes).to eq attributes
97
+ end
98
+
99
+ end # describe #attributes
100
+
101
+ describe "#apply_to" do
102
+
103
+ let(:attributes) { { foo: :bar, bar: :baz } }
104
+ let(:follower) { OpenStruct.new(attributes) }
105
+
106
+ subject do
107
+ described_class.new nil, policy_class, nil, *attributes.keys
108
+ end
109
+
110
+ it "calls policy_class validation with follower attributes" do
111
+ expect(policy_class).to receive(:apply).with(*attributes.values)
112
+ subject.apply_to(follower)
113
+ end
114
+
115
+ end # describe #apply_to
116
+
117
+ end # describe Policy::Follower::Policy
@@ -0,0 +1,19 @@
1
+ # encoding: utf-8
2
+
3
+ describe Policy::Follower::Names do
4
+
5
+ describe ".from" do
6
+
7
+ shared_examples "normalizing from" do |*source|
8
+
9
+ subject { described_class.from(*source) }
10
+ it { is_expected.to eq %i(foo bar baz foo) }
11
+
12
+ end # shared examples
13
+
14
+ it_behaves_like "normalizing from", %w(foo bar baz foo)
15
+ it_behaves_like "normalizing from", *%w(foo bar baz foo)
16
+
17
+ end # describe .from
18
+
19
+ end # describe Policy::Follower::Names
@@ -0,0 +1,220 @@
1
+ # encoding: utf-8
2
+
3
+ describe Policy::Follower do
4
+
5
+ before { Test = Class.new.__send__(:include, described_class) }
6
+ after { Object.send :remove_const, :Test }
7
+
8
+ let(:test_class) { Test }
9
+ subject { test_class.new }
10
+
11
+ it "includes Policy::Validations" do
12
+ expect(test_class).to include Policy::Validations
13
+ end
14
+
15
+ describe ".followed_policies" do
16
+
17
+ let(:followed_policies) { Policy::Follower::FollowedPolicies }
18
+
19
+ it "returns the AppiedPolicies object" do
20
+ expect(test_class.followed_policies).to be_kind_of followed_policies
21
+ end
22
+
23
+ end # describe .followed_policies
24
+
25
+ describe ".follow_policy" do
26
+
27
+ let(:followed_policy) { Policy::Follower::FollowedPolicy }
28
+
29
+ let(:policy) { double name: :foo }
30
+ let(:policy_class) { double }
31
+ let(:name) { double }
32
+ let(:attributes) { [double, double] }
33
+
34
+ before { allow(followed_policy).to receive(:new) { policy } }
35
+
36
+ context "by default" do
37
+
38
+ after { test_class.follow_policy policy_class, *attributes }
39
+
40
+ it "creates new followed policy" do
41
+ expect(followed_policy)
42
+ .to receive(:new)
43
+ .with(test_class, policy_class, nil, *attributes)
44
+ end
45
+
46
+ it "adds new policy to .followed_policies" do
47
+ expect(test_class.followed_policies).to receive(:add).with(policy)
48
+ end
49
+
50
+ end # context
51
+
52
+ context "as: name" do
53
+
54
+ after { test_class.follow_policy(policy_class, *attributes, as: name) }
55
+
56
+ it "creates named followed policy" do
57
+ expect(followed_policy)
58
+ .to receive(:new)
59
+ .with(test_class, policy_class, name, *attributes)
60
+ end
61
+
62
+ end # context
63
+
64
+ context "inside #use_policies" do
65
+
66
+ let(:namespace) { double }
67
+
68
+ subject do
69
+ test_class.use_policies namespace do
70
+ follow_policy :foo, as: :bar
71
+ end
72
+ end
73
+
74
+ it "sends the namespace to followed policy" do
75
+ expect(followed_policy)
76
+ .to receive(:new).with(namespace, :foo, :bar)
77
+ subject
78
+ end
79
+
80
+ it "doesn't change default namespace" do
81
+ subject
82
+ expect(followed_policy)
83
+ .to receive(:new).with(test_class, :foo, :bar)
84
+ test_class.follow_policy :foo, as: :bar
85
+ end
86
+
87
+ end # context
88
+
89
+ end # describe .follow_policy
90
+
91
+ describe "#follow_policies!" do
92
+
93
+ context "without names" do
94
+
95
+ after { subject.follow_policies! }
96
+
97
+ it "applies .followed_policies to itself" do
98
+ expect(test_class.followed_policies)
99
+ .to receive(:apply_to).with(subject)
100
+ end
101
+
102
+ end # context
103
+
104
+ context "with names" do
105
+
106
+ let(:names) { %i(foo bar baz) }
107
+ after { subject.follow_policies!(*names) }
108
+
109
+ it "applies .followed_policies to itself with names" do
110
+ expect(test_class.followed_policies)
111
+ .to receive(:apply_to).with(subject, *names)
112
+ end
113
+
114
+ end # context
115
+
116
+ context "when a ViolationError is raised" do
117
+
118
+ let(:error) { Policy::ViolationError.allocate }
119
+ let(:messages) { %w(foo bar baz) }
120
+
121
+ before do
122
+ allow(error).to receive(:messages) { messages }
123
+ allow(test_class.followed_policies).to receive(:apply_to) { fail error }
124
+ end
125
+
126
+ it "populates messages with errors" do
127
+ expect { subject.follow_policies! rescue nil }
128
+ .to change { subject.__send__(:errors).messages }
129
+ .to(base: messages)
130
+ end
131
+
132
+ it "re-raises the exception" do
133
+ expect { subject.follow_policies! }.to raise_error(error)
134
+ end
135
+
136
+ end # context
137
+
138
+ end # describe #follow_policies!
139
+
140
+ describe "#follow_policies?" do
141
+
142
+ before { allow(subject).to receive(:follow_policies!) }
143
+
144
+ it "calls #follow_policies! without names" do
145
+ expect(subject).to receive(:follow_policies!)
146
+ subject.follow_policies?
147
+ end
148
+
149
+ it "calls #follow_policies! with names" do
150
+ names = %i(foo bar baz)
151
+
152
+ expect(subject).to receive(:follow_policies!).with(*names)
153
+ subject.follow_policies?(*names)
154
+ end
155
+
156
+ context "wheh #follow_policies! doesn't raise an error" do
157
+
158
+ before { allow(subject).to receive(:follow_policies!) { nil } }
159
+
160
+ it "returns true" do
161
+ expect(subject.follow_policies?).to eq true
162
+ end
163
+
164
+ end # context
165
+
166
+ context "wheh #folow_policies! raises ViolationError" do
167
+
168
+ let(:error) { Policy::ViolationError.allocate }
169
+ before { allow(subject).to receive(:follow_policies!) { fail error } }
170
+
171
+ it "returns false" do
172
+ expect(subject.follow_policies?).to eq false
173
+ end
174
+
175
+ end # context
176
+
177
+ context "wheh #follow_policies! raises RuntimeError" do
178
+
179
+ let(:error) { StandardError.allocate }
180
+ before { allow(subject).to receive(:follow_policies!) { fail error } }
181
+
182
+ it "fails" do
183
+ expect { subject.follow_policies? }.to raise_error(error)
184
+ end
185
+
186
+ end # context
187
+
188
+ end # describe #follow_policies?
189
+
190
+ describe "#follow_policy!" do
191
+
192
+ before { allow(subject).to receive(:follow_policies!) }
193
+
194
+ it "is an alias for #follow_policies!" do
195
+ expect(subject).to receive(:follow_policies!).with :foo
196
+ subject.follow_policy! :foo
197
+ end
198
+
199
+ it "requires an argument" do
200
+ expect(subject.method(:follow_policy!).arity).to eq 1
201
+ end
202
+
203
+ end # describe #follow_policy!
204
+
205
+ describe "#follow_policy?" do
206
+
207
+ before { allow(subject).to receive(:follow_policies?) }
208
+
209
+ it "is an alias for #follow_policies?" do
210
+ expect(subject).to receive(:follow_policies?).with :foo
211
+ subject.follow_policy? :foo
212
+ end
213
+
214
+ it "requires an argument" do
215
+ expect(subject.method(:follow_policy?).arity).to eq 1
216
+ end
217
+
218
+ end # describe #follow_policy?
219
+
220
+ end # describe Policy::Follower
@@ -0,0 +1,83 @@
1
+ # encoding: utf-8
2
+
3
+ describe Policy::Interface do
4
+
5
+ before { Test = Class.new.send :include, described_class }
6
+ after { Object.send :remove_const, :Test }
7
+
8
+ let(:test_class) { Test }
9
+ subject { test_class.new }
10
+
11
+ it "includes Policy::Validations" do
12
+ expect(test_class).to include Policy::Validations
13
+ end
14
+
15
+ describe "#apply" do
16
+
17
+ context "when #valid? returns true" do
18
+
19
+ before { allow(subject).to receive(:valid?).and_return true }
20
+
21
+ it "doesn't raise error" do
22
+ expect { subject.apply }.not_to raise_error
23
+ end
24
+ end
25
+
26
+ context "when #valid? returns false" do
27
+
28
+ before { allow(subject).to receive(:valid?).and_return false }
29
+
30
+ it "raises ViolationError" do
31
+ expect { subject.apply }.to raise_error(Policy::ViolationError)
32
+ end
33
+
34
+ it "adds the policy to Exception" do
35
+ expect(Policy::ViolationError).to receive(:new).with(subject).once
36
+ subject.apply rescue nil
37
+ end
38
+ end
39
+
40
+ end # describe #apply
41
+
42
+ describe "#messages" do
43
+
44
+ context "when #errors are present" do
45
+
46
+ let(:messages) { %w(foo bar) }
47
+ let(:errors) { double :errors, messages: { foo: messages } }
48
+
49
+ it "extracts a plain array of error messages" do
50
+ allow(subject).to receive(:errors) { errors }
51
+ expect(subject.messages).to eq messages
52
+ end
53
+
54
+ end # context
55
+
56
+ context "when #errors are absent" do
57
+
58
+ it "returns an empty array" do
59
+ expect(subject.messages).to eq []
60
+ end
61
+
62
+ end # context
63
+
64
+ end # describe #messages
65
+
66
+ describe ".apply" do
67
+
68
+ let(:attributes) { %i(foo bar) }
69
+ let(:follower) { double apply: nil }
70
+
71
+ it "creates a policy object with the attributes" do
72
+ expect(test_class).to receive(:new).with(attributes) { follower }
73
+ test_class.apply attributes
74
+ end
75
+
76
+ it "validates the policy object" do
77
+ expect(test_class).to receive_message_chain :new, :apply
78
+ test_class.apply attributes
79
+ end
80
+
81
+ end # describe .apply
82
+
83
+ end # describe Policy::Inteface
@@ -0,0 +1,13 @@
1
+ # encoding: utf-8
2
+
3
+ describe Policy::Validations do
4
+
5
+ let(:validations) { ActiveModel::Validations }
6
+ let(:methods) { validations.public_instance_methods }
7
+ let(:test_class) { Class.new.send :include, described_class }
8
+
9
+ it "includes ActiveModel::Validatons" do
10
+ expect(test_class).to include validations
11
+ end
12
+
13
+ end