eaco 0.6.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -3
  3. data/features/authorization_parse_error.feature +157 -0
  4. data/features/enterprise_authorization.feature +159 -0
  5. data/features/rails_integration.feature +1 -1
  6. data/features/role_based_authorization.feature +30 -7
  7. data/features/step_definitions/actor_steps.rb +29 -25
  8. data/features/step_definitions/enterprise_steps.rb +81 -0
  9. data/features/step_definitions/error_steps.rb +49 -0
  10. data/features/step_definitions/fixture_steps.rb +14 -0
  11. data/features/step_definitions/resource_steps.rb +16 -24
  12. data/features/support/env.rb +4 -2
  13. data/lib/eaco.rb +2 -0
  14. data/lib/eaco/actor.rb +4 -3
  15. data/lib/eaco/adapters/active_record.rb +1 -1
  16. data/lib/eaco/adapters/active_record/compatibility.rb +19 -14
  17. data/lib/eaco/adapters/active_record/compatibility/scoped.rb +25 -0
  18. data/lib/eaco/adapters/active_record/compatibility/v40.rb +11 -3
  19. data/lib/eaco/adapters/active_record/compatibility/v41.rb +14 -3
  20. data/lib/eaco/adapters/active_record/compatibility/v42.rb +10 -2
  21. data/lib/eaco/controller.rb +16 -3
  22. data/lib/eaco/coverage.rb +83 -0
  23. data/lib/eaco/cucumber/active_record.rb +13 -18
  24. data/lib/eaco/cucumber/active_record/department.rb +4 -0
  25. data/lib/eaco/cucumber/active_record/position.rb +2 -0
  26. data/lib/eaco/cucumber/active_record/schema.rb +20 -2
  27. data/lib/eaco/cucumber/active_record/user.rb +9 -0
  28. data/lib/eaco/cucumber/active_record/user/designators.rb +4 -1
  29. data/lib/eaco/cucumber/active_record/user/designators/authenticated.rb +54 -0
  30. data/lib/eaco/cucumber/active_record/user/designators/department.rb +58 -0
  31. data/lib/eaco/cucumber/active_record/user/designators/position.rb +53 -0
  32. data/lib/eaco/cucumber/active_record/user/designators/user.rb +4 -0
  33. data/lib/eaco/cucumber/world.rb +115 -5
  34. data/lib/eaco/designator.rb +7 -2
  35. data/lib/eaco/dsl.rb +9 -1
  36. data/lib/eaco/dsl/acl.rb +2 -2
  37. data/lib/eaco/dsl/actor.rb +6 -3
  38. data/lib/eaco/dsl/base.rb +5 -0
  39. data/lib/eaco/error.rb +10 -1
  40. data/lib/eaco/rake.rb +1 -0
  41. data/lib/eaco/rake/default_task.rb +29 -9
  42. data/lib/eaco/rake/utils.rb +38 -0
  43. data/lib/eaco/version.rb +1 -1
  44. data/spec/eaco/acl_spec.rb +34 -0
  45. data/spec/spec_helper.rb +3 -3
  46. metadata +18 -2
@@ -16,6 +16,8 @@ module Eaco
16
16
  #
17
17
  class User < Eaco::Designator
18
18
  ##
19
+ # This {Designator} description
20
+ #
19
21
  # @return [String] the {User}'s name.
20
22
  #
21
23
  def describe(*)
@@ -23,6 +25,8 @@ module Eaco
23
25
  end
24
26
 
25
27
  ##
28
+ # {User}s matching this designator.
29
+ #
26
30
  # @return [Array] this very {User} wrapped in an +Array+.
27
31
  #
28
32
  def resolve
@@ -132,7 +132,7 @@ module Eaco
132
132
  class World
133
133
 
134
134
  ##
135
- # Set up the World:
135
+ # Sets up the World:
136
136
  #
137
137
  # * Connect to ActiveRecord
138
138
  #
@@ -141,7 +141,117 @@ module Eaco
141
141
  end
142
142
 
143
143
  ##
144
- # @return [Class] a model in the {ActiveRecord} namespace.
144
+ # Authorizes model with the given {DSL}
145
+ #
146
+ # @param name [String] the model name
147
+ # @param definition [String] the {DSL} code
148
+ # @see {#find_model}
149
+ #
150
+ # @return [void]
151
+ #
152
+ def authorize_model(name, definition)
153
+ model = find_model(name)
154
+
155
+ eval_dsl definition, model
156
+ end
157
+
158
+ ##
159
+ # Registers and persists an {Actor} instance with the given +name+.
160
+ #
161
+ # @param model [String] the {Actor} model name
162
+ # @param name [String] the {Actor} instance name
163
+ # @param options [Boolean] the only supported one is +admin+, and
164
+ # specifies whether this {Actor} an admin
165
+ #
166
+ # @return [Actor] the newly created {Actor} instance.
167
+ #
168
+ def register_actor(model, name, options = {})
169
+ actor_model = find_model(model)
170
+
171
+ actors[name] = actor_model.new.tap do |actor|
172
+ actor.name = name
173
+ actor.admin = options.fetch(:admin, false)
174
+ actor.save!
175
+ end
176
+ end
177
+
178
+ ##
179
+ # Fetches an {Actor} instance by name.
180
+ #
181
+ # @param name [String] the actor name
182
+ # @return [Actor] the registered actor name
183
+ # @raise [RuntimeError] if the actor is not found in the registry
184
+ #
185
+ def fetch_actor(name)
186
+ actors.fetch(name)
187
+ rescue KeyError
188
+ # :nocov:
189
+ raise "Actor '#{name}' not found in registry"
190
+ # :nocov:
191
+ end
192
+
193
+ ##
194
+ # Registers and persists {Resource} instance with the given name.
195
+ #
196
+ # @param model [String] the {Resource} model name
197
+ # @param name [String] the {Resource} name
198
+ #
199
+ # @return [Resource] the newly instantiated {Resource}
200
+ #
201
+ def register_resource(model, name)
202
+ resource_model = find_model(model)
203
+
204
+ resource = resource_model.new.tap do |resource|
205
+ resource.name = name
206
+ resource.save!
207
+ end
208
+
209
+ resources[model] ||= {}
210
+ resources[model][name] = resource
211
+ end
212
+
213
+ ##
214
+ # Fetches a {Resource} instance by name.
215
+ #
216
+ # @param model [String] the {Resource} model name
217
+ # @param name [String] the {Resource} name
218
+ #
219
+ def fetch_resource(model, name)
220
+ resources.fetch(model).fetch(name)
221
+ rescue KeyError
222
+ # :nocov:
223
+ raise "Resource #{model} '#{resource}' not found in registry"
224
+ # :nocov:
225
+ end
226
+
227
+ ##
228
+ # All registered {Actor} instances.
229
+ #
230
+ # @return [Hash] actors keyed by name
231
+ #
232
+ def actors
233
+ @actors ||= {}
234
+ end
235
+
236
+ ##
237
+ # All registered {Resource} instances.
238
+ #
239
+ # @return [Hash] resources keyed by model name with +Hash+es
240
+ # as values keyed by resource name.
241
+ #
242
+ def resources
243
+ @resources ||= {}
244
+ end
245
+
246
+ ##
247
+ # Returns a model in the {ActiveRecord} namespace.
248
+ #
249
+ # Example:
250
+ #
251
+ # World.find_model('Document')
252
+ #
253
+ # @param model_name [String] the model name
254
+ # @return [Class]
145
255
  #
146
256
  def find_model(model_name)
147
257
  Eaco::Cucumber::ActiveRecord.const_get(model_name)
@@ -152,13 +262,13 @@ module Eaco
152
262
  # +$MODEL+ string with the given model name.
153
263
  #
154
264
  # @param code [String] the DSL code to eval
155
- # @param model [Class] the model name to substitute
265
+ # @param model [Class] the model name to substitute (optional)
156
266
  #
157
267
  # @return [void]
158
268
  #
159
- def eval_dsl(code, model)
269
+ def eval_dsl(code, model = nil)
160
270
  # Sub in place to print final code when running cucumber
161
- code.sub! '$MODEL', model.name
271
+ code.sub! '$MODEL', model.name if model
162
272
  Eaco.eval! code, '(feature)'
163
273
  end
164
274
  end
@@ -63,7 +63,7 @@ module Eaco
63
63
  # @return [Array] resolved actors, application-dependant.
64
64
  #
65
65
  def resolve(designators)
66
- Array.new(designators||[]).inject([]) {|ret, d| ret.concat parse(d).resolve}
66
+ Array.new(designators||[]).inject(Set.new) {|ret, d| ret.merge parse(d).resolve}
67
67
  end
68
68
 
69
69
  ##
@@ -108,7 +108,9 @@ module Eaco
108
108
  # @see Actor
109
109
  #
110
110
  def harvest(actor)
111
- Array.new([actor.send(@method)].flatten).map! {|value| new(value) }
111
+ list = actor.send(@method)
112
+ list = list.to_a if list.respond_to?(:to_a)
113
+ Array.new([list]).flatten.map! {|value| new(value) }
112
114
  end
113
115
 
114
116
  ##
@@ -167,6 +169,9 @@ module Eaco
167
169
  # in a typeahead menu, for your Enterprise authorization management
168
170
  # UI... :-)
169
171
  #
172
+ # @param query [String] the query to search against
173
+ # @return [Enumerable] application {Actor}s collection
174
+ #
170
175
  # @raise [NotImplementedError]
171
176
  #
172
177
  def search(query)
data/lib/eaco/dsl.rb CHANGED
@@ -19,6 +19,10 @@ module Eaco
19
19
  ##
20
20
  # Entry point for the {Resource} authorization definition.
21
21
  #
22
+ # @param resource_class [Class] the application resource class
23
+ # @param options [Hash] options passed to {DSL::Resource} and
24
+ # and {DSL::ACL}.
25
+ #
22
26
  # @see DSL::Resource
23
27
  # @see DSL::ACL
24
28
  #
@@ -28,7 +32,11 @@ module Eaco
28
32
  end
29
33
 
30
34
  ##
31
- # Entry point for the {Actor} designators definition.
35
+ # Entry point for an {Actor} definition.
36
+ #
37
+ # @param actor_class [Class] the application actor class
38
+ # @param options [Hash] currently unused
39
+ # @param block [Proc] the DSL code to eval
32
40
  #
33
41
  # @see DSL::Actor
34
42
  #
data/lib/eaco/dsl/acl.rb CHANGED
@@ -50,7 +50,7 @@ module Eaco
50
50
  #
51
51
  def define_acl_subclass
52
52
  target_eval do
53
- remove_const(:ACL) if const_defined?(:ACL)
53
+ remove_const(:ACL) if constants.include?(:ACL)
54
54
 
55
55
  Class.new(Eaco::ACL).tap do |acl_class|
56
56
  define_singleton_method(:acl) { acl_class }
@@ -97,7 +97,7 @@ module Eaco
97
97
  target.send(:include, adapter)
98
98
  install_authorized_collection_strategy
99
99
 
100
- elsif target.respond_to?(:acl) && target.respond_to?(:acl=)
100
+ elsif (target.instance_methods & [:acl, :acl=]).size != 2
101
101
  raise Malformed, <<-EOF
102
102
  Don't know how to persist ACLs using <#{target}>'s ORM
103
103
  (identified as <#{orm}>). Please define an `acl' instance
@@ -21,7 +21,7 @@ module Eaco
21
21
  autoload :Designators, 'eaco/dsl/actor/designators'
22
22
 
23
23
  ##
24
- # Initializes an Actor class.
24
+ # Makes an application model a valid {Eaco::Actor}.
25
25
  #
26
26
  # @see Eaco::Actor
27
27
  #
@@ -79,7 +79,7 @@ module Eaco
79
79
 
80
80
  ##
81
81
  # Defines the boolean logic that determines whether an {Actor} is an
82
- # admin. Usually you'll have an +admin+ method on your model, that you
82
+ # admin. Usually you'll have an +admin?+ method on your model, that you
83
83
  # can call from here. Or, feel free to just return +false+ to disable
84
84
  # this functionality.
85
85
  #
@@ -91,9 +91,12 @@ module Eaco
91
91
  # end
92
92
  # end
93
93
  #
94
+ # @param block [Proc]
95
+ # @return [void]
96
+ #
94
97
  def admin(&block)
95
98
  target_eval do
96
- @admin_logic = block
99
+ @_admin_logic = block
97
100
  end
98
101
  end
99
102
 
data/lib/eaco/dsl/base.rb CHANGED
@@ -32,6 +32,8 @@ module Eaco
32
32
  attr_reader :options
33
33
 
34
34
  ##
35
+ # Sets up the DSL instance target class and the options.
36
+ #
35
37
  # @param target [Class]
36
38
  # @param options [Hash]
37
39
  #
@@ -43,6 +45,9 @@ module Eaco
43
45
  ##
44
46
  # Evaluates the given block in the context of the target class
45
47
  #
48
+ # @param block [Proc]
49
+ # @return [void]
50
+ #
46
51
  def target_eval(&block)
47
52
  target.instance_eval(&block)
48
53
  end
data/lib/eaco/error.rb CHANGED
@@ -5,7 +5,16 @@ module Eaco
5
5
  #
6
6
  class Error < StandardError
7
7
  # As we make use of heredoc for long error messages, squeeze subsequent
8
- # spaces and remove newlines.
8
+ # spaces and remove newlines. If the message looks like an internal error
9
+ # though, newlines are preserved.
10
+ #
11
+ # Example:
12
+ #
13
+ # raise Eaco::Error, <<-EOF
14
+ # Some fancy message
15
+ # end
16
+ #
17
+ # @param message [String]
9
18
  #
10
19
  def initialize(message)
11
20
  unless message =~ %r{EACO.+Error}
data/lib/eaco/rake.rb CHANGED
@@ -4,6 +4,7 @@ module Eaco
4
4
  # Namespace to all rake-related functionality.
5
5
  #
6
6
  module Rake
7
+ autoload :Utils, 'eaco/rake/utils.rb'
7
8
  autoload :DefaultTask, 'eaco/rake/default_task.rb'
8
9
  end
9
10
 
@@ -1,3 +1,5 @@
1
+ require 'eaco/coverage'
2
+
1
3
  module Eaco
2
4
  module Rake
3
5
 
@@ -30,12 +32,14 @@ module Eaco
30
32
  task :default do
31
33
  run_specs
32
34
  run_cucumber
35
+ output_coverage
33
36
  end
34
37
 
35
38
  elsif running_in_travis?
36
39
  task :default do
37
40
  run_specs
38
41
  run_cucumber
42
+ report_coverage
39
43
  generate_documentation
40
44
  end
41
45
 
@@ -114,6 +118,25 @@ module Eaco
114
118
  ::Rake::Task[task].invoke
115
119
  end
116
120
 
121
+ ##
122
+ # Reports coverage data
123
+ #
124
+ # @return [void]
125
+ #
126
+ def report_coverage
127
+ Eaco::Coverage.report!
128
+ end
129
+
130
+ ##
131
+ # Formats code coverage results and prints a summary
132
+ #
133
+ # @return [void]
134
+ #
135
+ def output_coverage
136
+ summary = Eaco::Coverage.format!
137
+ croak summary
138
+ end
139
+
117
140
  ##
118
141
  # Fancily logs the given +msg+ to +$stderr+.
119
142
  #
@@ -142,8 +165,8 @@ module Eaco
142
165
  # @return [String]
143
166
  #
144
167
  def with_appraisal(msg)
145
- if appraisal
146
- msg = "%s \033[1;31m[%s]" % [msg, appraisal]
168
+ if gemfile
169
+ msg = "%s \033[1;31m[%s]" % [msg, gemfile]
147
170
  end
148
171
 
149
172
  return msg
@@ -166,14 +189,11 @@ module Eaco
166
189
  end
167
190
 
168
191
  ##
169
- # @return [String] the current appraisal name, or nil
192
+ # @see {Rake::Utils.gemfile}
193
+ # @private
170
194
  #
171
- def appraisal
172
- return unless running_appraisals?
173
-
174
- gemfile = ENV['BUNDLE_GEMFILE']
175
-
176
- File.basename(gemfile, '.*') if gemfile
195
+ def gemfile
196
+ Rake::Utils.gemfile
177
197
  end
178
198
 
179
199
  ##
@@ -0,0 +1,38 @@
1
+ module Eaco
2
+ module Rake
3
+
4
+ ##
5
+ # Assorted utilities.
6
+ #
7
+ module Utils
8
+ extend self
9
+
10
+ ##
11
+ # Captures the stdout emitted by the given +block+
12
+ #
13
+ # @param block [Proc]
14
+ # @return [String] the captured output
15
+ #
16
+ def capture_stdout(&block)
17
+ stdout, string = $stdout, StringIO.new
18
+ $stdout = string
19
+
20
+ yield
21
+
22
+ string.tap(&:rewind).read
23
+ ensure
24
+ $stdout = stdout
25
+ end
26
+
27
+ ##
28
+ # @return [String] the current gemfile name
29
+ #
30
+ def gemfile
31
+ gemfile = ENV['BUNDLE_GEMFILE']
32
+
33
+ File.basename(gemfile, '.*') if gemfile
34
+ end
35
+ end
36
+
37
+ end
38
+ end
data/lib/eaco/version.rb CHANGED
@@ -2,6 +2,6 @@ module Eaco
2
2
 
3
3
  # Current version
4
4
  #
5
- VERSION = '0.6.1'
5
+ VERSION = '0.7.0'
6
6
 
7
7
  end
@@ -31,6 +31,24 @@ RSpec.describe Eaco::ACL do
31
31
  it { expect(subject).to be_a(described_class) }
32
32
  it { expect(subject).to include({designator => :reader}) }
33
33
  end
34
+
35
+ context 'when looking up designators' do
36
+ after { acl.add(:reader, :foo, 1) }
37
+
38
+ let(:acl) { described_class.new }
39
+
40
+ it { expect(Eaco::Designator).to receive(:make).with(:foo, 1) }
41
+ end
42
+
43
+ context 'when giving rubbish' do
44
+ subject { acl.add(:reader, 'rubbish') }
45
+
46
+ let(:acl) { described_class.new }
47
+
48
+ it { expect { subject }.to \
49
+ raise_error(Eaco::Error).
50
+ with_message(/Cannot infer designator from "rubbish"/) }
51
+ end
34
52
  end
35
53
 
36
54
  describe '#del' do
@@ -144,4 +162,20 @@ RSpec.describe Eaco::ACL do
144
162
  end
145
163
  end
146
164
 
165
+ describe '#inspect' do
166
+ let(:acl) { described_class.new('foo' => :bar) }
167
+
168
+ subject { acl.inspect }
169
+
170
+ it { expect(subject).to eq('#<Eaco::ACL: {"foo"=>:bar}>') }
171
+ end
172
+
173
+ describe '#pretty_inspect' do
174
+ let(:acl) { described_class.new('foo' => :bar) }
175
+
176
+ subject { acl.pretty_inspect }
177
+
178
+ it { expect(subject).to eq("Eaco::ACL\n{\"foo\"=>:bar}\n") }
179
+ end
180
+
147
181
  end