eaco 0.6.1 → 0.7.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 (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
@@ -1,8 +1,9 @@
1
1
  begin
2
2
  require 'active_support/concern'
3
3
  rescue LoadError
4
- # This is falsely true during specs ran by Guard. FIXME.
4
+ # :nocov: This is falsely true during specs ran by Guard. FIXME.
5
5
  abort 'Eaco::Controller requires activesupport. Please add it to Gemfile.'
6
+ # :nocov:
6
7
  end
7
8
 
8
9
  module Eaco
@@ -59,7 +60,14 @@ module Eaco
59
60
  end
60
61
 
61
62
  ##
62
- # @return [Symbol] the permission required to access the given action.
63
+ # Gets the permission required to access the given +action+, falling
64
+ # back on the default +:all+ action, or +nil+ if no permission is
65
+ # defined.
66
+ #
67
+ # @return [Symbol] the required permission or nil
68
+ #
69
+ # @see {Eaco::Resource}
70
+ # @see {Eaco::DSL::Resource}
63
71
  #
64
72
  def permission_for(action)
65
73
  authorization_permissions[action] || authorization_permissions[:all]
@@ -67,7 +75,12 @@ module Eaco
67
75
 
68
76
  protected
69
77
  ##
70
- # Permission requirements configured on this controller.
78
+ # Permission requirements configured on this controller, keyed by
79
+ # permission symbol and with role symbols as values.
80
+ #
81
+ # @return [Hash]
82
+ #
83
+ # @see {Eaco::DSL::Resource}
71
84
  #
72
85
  def authorization_permissions
73
86
  @_authorization_permissions ||= {}
@@ -0,0 +1,83 @@
1
+ require 'coveralls'
2
+ require 'simplecov'
3
+ require 'eaco/rake'
4
+
5
+ module Eaco
6
+
7
+ ##
8
+ # Integration with code coverage tools.
9
+ #
10
+ # Loading this module will start collecting coverage data.
11
+ #
12
+ module Coverage
13
+ extend self
14
+
15
+ ##
16
+ # Starts collecting coverage data.
17
+ #
18
+ # @return [nil]
19
+ #
20
+ def start!
21
+ Coveralls.wear_merged!(&simplecov_configuration)
22
+
23
+ nil
24
+ end
25
+
26
+ ##
27
+ # Reports coverage data to the remote service
28
+ #
29
+ # @return [nil]
30
+ #
31
+ def report!
32
+ simplecov
33
+ Coveralls.push!
34
+
35
+ nil
36
+ end
37
+
38
+ ##
39
+ # Formats coverage results using the default formatter.
40
+ #
41
+ # @return [String] Coverage summary
42
+ #
43
+ def format!
44
+ Rake::Utils.capture_stdout do
45
+ result && result.format!
46
+ end.strip
47
+ end
48
+
49
+ private
50
+
51
+ ##
52
+ # The coverage result
53
+ #
54
+ # @return [SimpleCov::Result]
55
+ #
56
+ def result
57
+ simplecov.result
58
+ end
59
+
60
+ ##
61
+ # Configures simplecov using {.simplecov_configuration}
62
+ #
63
+ # @return [Class] +SimpleCov+
64
+ #
65
+ def simplecov
66
+ SimpleCov.configure(&simplecov_configuration)
67
+ end
68
+
69
+ ##
70
+ # Configures +SimpleCov+ to use a different directory
71
+ # for each different appraisal +Gemfile+.
72
+ #
73
+ # @return [Proc] a +SimpleCov+ configuration block.
74
+ #
75
+ def simplecov_configuration
76
+ proc do
77
+ gemfile = Eaco::Rake::Utils.gemfile
78
+ coverage_dir "coverage/#{gemfile}"
79
+ end
80
+ end
81
+ end
82
+
83
+ end
@@ -1,7 +1,9 @@
1
1
  begin
2
2
  require 'active_record'
3
3
  rescue LoadError
4
+ # :nocov:
4
5
  abort "ActiveRecord requires the rails appraisal. Try `appraisal cucumber`"
6
+ # :nocov:
5
7
  end
6
8
 
7
9
  require 'yaml'
@@ -58,15 +60,6 @@ module Eaco
58
60
  active_record.logger
59
61
  end
60
62
 
61
- ##
62
- # @return [ActiveRecord::Connection] the current +ActiveRecord+ connection
63
- # object.
64
- #
65
- def connection
66
- active_record.connection
67
- end
68
- alias adapter connection
69
-
70
63
  ##
71
64
  # Returns an Hash wit the database configuration.
72
65
  #
@@ -80,9 +73,11 @@ module Eaco
80
73
  def configuration
81
74
  @_config ||= YAML.load(config_file.read).tap do |conf|
82
75
  def conf.to_s
76
+ # :nocov:
83
77
  'pgsql://%s:%s@%s/%s' % values_at(
84
78
  :username, :password, :hostname, :database
85
79
  )
80
+ # :nocov:
86
81
  end
87
82
  end
88
83
  end
@@ -104,6 +99,7 @@ module Eaco
104
99
  Pathname.new('features/active_record.yml').realpath
105
100
 
106
101
  rescue Errno::ENOENT => error
102
+ # :nocov:
107
103
  raise error.class.new, <<-EOF.squeeze(' ')
108
104
 
109
105
  #{error.message}.
@@ -112,6 +108,7 @@ module Eaco
112
108
  default location, or specify your configuration file location by
113
109
  passing the `EACO_AR_CONFIG' environment variable.
114
110
  EOF
111
+ # :nocov:
115
112
  end
116
113
 
117
114
  ##
@@ -143,23 +140,21 @@ module Eaco
143
140
  protected
144
141
 
145
142
  ##
146
- # Captures stdout and logs it
143
+ # Captures stdout emitted by the given +block+ and logs it
144
+ # as +info+ messages.
147
145
  #
146
+ # @param block [Proc]
148
147
  # @return [nil]
148
+ # @see {Rake::Utils.capture_stdout}
149
149
  #
150
- def log_stdout
151
- stdout, string = $stdout, StringIO.new
152
- $stdout = string
153
-
154
- yield
150
+ def log_stdout(&block)
151
+ stdout = Rake::Utils.capture_stdout(&block)
155
152
 
156
- string.tap(&:rewind).read.split("\n").each do |line|
153
+ stdout.split("\n").each do |line|
157
154
  logger.info line
158
155
  end
159
156
 
160
157
  nil
161
- ensure
162
- $stdout = stdout
163
158
  end
164
159
  end
165
160
 
@@ -12,6 +12,10 @@ module Eaco
12
12
  # @see Eaco::Cucumber::World
13
13
  #
14
14
  class Department < ::ActiveRecord::Base
15
+ has_many :positions
16
+ has_many :users, through: :positions
17
+
18
+ validates :name, uniqueness: true
15
19
  end
16
20
 
17
21
  end
@@ -14,6 +14,8 @@ module Eaco
14
14
  class Position < ::ActiveRecord::Base
15
15
  belongs_to :user
16
16
  belongs_to :department
17
+
18
+ validates :user, :department, presence: :true
17
19
  end
18
20
 
19
21
  end
@@ -2,6 +2,18 @@ module Eaco
2
2
  module Cucumber
3
3
  module ActiveRecord
4
4
 
5
+ # @!method clean
6
+ #
7
+ # Drops all tables currently instantiated in the database.
8
+ #
9
+ # @see Eaco::Cucumber::World
10
+ #
11
+ ::ActiveRecord::Base.connection.tap do |connection|
12
+ connection.tables.each do |table_name|
13
+ connection.drop_table table_name
14
+ end
15
+ end
16
+
5
17
  # @!method schema
6
18
  #
7
19
  # Defines the database schema for the {Eaco::Cucumber::World} scenario.
@@ -9,21 +21,27 @@ module Eaco
9
21
  # @see Eaco::Cucumber::World
10
22
  #
11
23
  ::ActiveRecord::Schema.define(version: '2015022301') do
24
+ # Resource
12
25
  create_table 'documents', force: true do |t|
13
26
  t.string :name
14
27
  t.column :acl, :jsonb
15
28
  end
16
29
 
30
+ # Actor
17
31
  create_table 'users', force: true do |t|
18
32
  t.string :name
33
+ t.boolean :admin, default: false
19
34
  end
20
35
 
36
+ # Designator source
21
37
  create_table 'departments', force: true do |t|
22
- t.string :abbr
38
+ t.string :name
23
39
  end
40
+ add_index :departments, :name, unique: true
24
41
 
42
+ # Designator source
25
43
  create_table 'positions', force: true do |t|
26
- t.string :job_title
44
+ t.string :name
27
45
 
28
46
  t.references :user
29
47
  t.references :department
@@ -17,6 +17,15 @@ module Eaco
17
17
 
18
18
  has_many :positions
19
19
  has_many :departments, through: :positions
20
+
21
+ ##
22
+ # The {Department} names this User has a {Position} in.
23
+ #
24
+ # @return [Array] the {Department} names as +String+s.
25
+ #
26
+ def department_names
27
+ departments.to_set(&:name)
28
+ end
20
29
  end
21
30
 
22
31
  end
@@ -9,7 +9,10 @@ module Eaco
9
9
  # @see World
10
10
  #
11
11
  module Designators
12
- autoload :User, 'eaco/cucumber/active_record/user/designators/user.rb'
12
+ autoload :Authenticated, 'eaco/cucumber/active_record/user/designators/authenticated.rb'
13
+ autoload :Department, 'eaco/cucumber/active_record/user/designators/department.rb'
14
+ autoload :Position, 'eaco/cucumber/active_record/user/designators/position.rb'
15
+ autoload :User, 'eaco/cucumber/active_record/user/designators/user.rb'
13
16
  end
14
17
 
15
18
  end
@@ -0,0 +1,54 @@
1
+ module Eaco
2
+ module Cucumber
3
+ module ActiveRecord
4
+ class User
5
+ module Designators
6
+
7
+ ##
8
+ # A {Designator} based on a the {User} class.
9
+ #
10
+ # This is an example on how to grant rights to all instances
11
+ # of a given model.
12
+ #
13
+ # The class name is available as the {Designator#value}.
14
+ #
15
+ # The String representation for an example User is
16
+ # +"authenticated:User"+.
17
+ #
18
+ class Authenticated < Eaco::Designator
19
+ label "Any user"
20
+
21
+ ##
22
+ # This {Designator} description.
23
+ #
24
+ # @return [String] an hardcoded description
25
+ #
26
+ def describe(*)
27
+ "Any authenticated user"
28
+ end
29
+
30
+ ##
31
+ # {User}s matching this designator.
32
+ #
33
+ # @return [Array] All {User}s.
34
+ #
35
+ def resolve
36
+ klass.all
37
+ end
38
+
39
+ private
40
+ ##
41
+ # Looks up this class by constantizing it.
42
+ #
43
+ # @return [Class]
44
+ #
45
+ def klass
46
+ @_klass ||= self.value.constantize
47
+ end
48
+ end
49
+
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,58 @@
1
+ module Eaco
2
+ module Cucumber
3
+ module ActiveRecord
4
+ class User
5
+ module Designators
6
+
7
+ ##
8
+ # A {Designator} based on a the {Department} an {User} occupies
9
+ # a {Position} in. It resolves {Actor}s by id looking them up
10
+ # through the {Position} model.
11
+ #
12
+ # As {Department}s have unique names, their name instead of
13
+ # their ID is used in this example.
14
+ #
15
+ # The Department name is available as the {Designator#value}.
16
+ #
17
+ # The String representation for an example ICT Department is
18
+ # +"department:ICT"+.
19
+ #
20
+ class Department < Eaco::Designator
21
+ ##
22
+ # This {Designator} description.
23
+ #
24
+ # @return [String] the {Department} name, such as ICT or COM
25
+ # or EXE or BAT.
26
+ #
27
+ def describe(*)
28
+ department.name
29
+ end
30
+
31
+ ##
32
+ # {User}s matching this designator.
33
+ #
34
+ # @return [Array] all users currently occupying a position in
35
+ # this Department
36
+ #
37
+ def resolve
38
+ department.users
39
+ end
40
+
41
+ private
42
+ ##
43
+ # Looks up this Department by name, and memoizes it in an
44
+ # instance variable.
45
+ #
46
+ # @return [ActiveRecord::Department] the referenced department
47
+ #
48
+ def department
49
+ @_department ||= ActiveRecord::Department.
50
+ where(name: self.value).first!
51
+ end
52
+ end
53
+
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,53 @@
1
+ module Eaco
2
+ module Cucumber
3
+ module ActiveRecord
4
+ class User
5
+ module Designators
6
+
7
+ ##
8
+ # A {Designator} based on a position an {User} occupies in an
9
+ # organigram. It resolves {Actor}s by id looking them up from
10
+ # the +user_id+ field.
11
+ #
12
+ # The Position ID is available as the {Designator#value}.
13
+ #
14
+ # The String representation for an example Position 42 is
15
+ # +"position:42"+.
16
+ #
17
+ class Position < Eaco::Designator
18
+ ##
19
+ # This {Designator} description.
20
+ #
21
+ # @return [String] the {Position} name, such as "Manager" or
22
+ # or "Systems Analyst" or "Consultant".
23
+ #
24
+ def describe(*)
25
+ "#{position.name} in #{position.department.name}"
26
+ end
27
+
28
+ ##
29
+ # {User}s matching this designator.
30
+ #
31
+ # @return [Array] the user currently occupying this Position.
32
+ #
33
+ def resolve
34
+ [position.user]
35
+ end
36
+
37
+ private
38
+ ##
39
+ # Looks up this position by ID, and memoizes it in an instance
40
+ # variable.
41
+ #
42
+ # @return [ActiveRecord::Position] the referenced Position.
43
+ #
44
+ def position
45
+ @_position ||= ActiveRecord::Position.find(self.value)
46
+ end
47
+ end
48
+
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end