moonrope 1.3.3 → 2.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +5 -5
  2. data/Gemfile +9 -0
  3. data/Gemfile.lock +47 -0
  4. data/MIT-LICENCE +20 -0
  5. data/README.md +24 -0
  6. data/bin/moonrope +28 -0
  7. data/docs/authentication.md +114 -0
  8. data/docs/controllers.md +106 -0
  9. data/docs/exceptions.md +27 -0
  10. data/docs/introduction.md +29 -0
  11. data/docs/structures.md +214 -0
  12. data/example/authentication.rb +50 -0
  13. data/example/controllers/meta_controller.rb +14 -0
  14. data/example/controllers/users_controller.rb +92 -0
  15. data/example/structures/pet_structure.rb +12 -0
  16. data/example/structures/user_structure.rb +35 -0
  17. data/lib/moonrope.rb +5 -4
  18. data/lib/moonrope/action.rb +170 -40
  19. data/lib/moonrope/authenticator.rb +42 -0
  20. data/lib/moonrope/base.rb +67 -6
  21. data/lib/moonrope/controller.rb +4 -2
  22. data/lib/moonrope/doc_context.rb +94 -0
  23. data/lib/moonrope/doc_server.rb +123 -0
  24. data/lib/moonrope/dsl/action_dsl.rb +159 -9
  25. data/lib/moonrope/dsl/authenticator_dsl.rb +35 -0
  26. data/lib/moonrope/dsl/base_dsl.rb +21 -18
  27. data/lib/moonrope/dsl/controller_dsl.rb +60 -9
  28. data/lib/moonrope/dsl/filterable_dsl.rb +27 -0
  29. data/lib/moonrope/dsl/structure_dsl.rb +28 -2
  30. data/lib/moonrope/errors.rb +13 -0
  31. data/lib/moonrope/eval_environment.rb +82 -3
  32. data/lib/moonrope/eval_helpers.rb +47 -8
  33. data/lib/moonrope/eval_helpers/filter_helper.rb +82 -0
  34. data/lib/moonrope/guard.rb +35 -0
  35. data/lib/moonrope/html_generator.rb +65 -0
  36. data/lib/moonrope/param_set.rb +11 -1
  37. data/lib/moonrope/rack_middleware.rb +66 -37
  38. data/lib/moonrope/railtie.rb +31 -14
  39. data/lib/moonrope/request.rb +43 -15
  40. data/lib/moonrope/structure.rb +100 -18
  41. data/lib/moonrope/structure_attribute.rb +39 -0
  42. data/lib/moonrope/version.rb +1 -1
  43. data/moonrope.gemspec +21 -0
  44. data/spec/spec_helper.rb +32 -0
  45. data/spec/specs/action_spec.rb +455 -0
  46. data/spec/specs/base_spec.rb +29 -0
  47. data/spec/specs/controller_spec.rb +31 -0
  48. data/spec/specs/param_set_spec.rb +31 -0
  49. data/templates/basic/_action_form.erb +77 -0
  50. data/templates/basic/_errors_table.erb +32 -0
  51. data/templates/basic/_structure_attributes_list.erb +55 -0
  52. data/templates/basic/action.erb +168 -0
  53. data/templates/basic/assets/lock.svg +3 -0
  54. data/templates/basic/assets/reset.css +101 -0
  55. data/templates/basic/assets/style.css +348 -0
  56. data/templates/basic/assets/tool.svg +4 -0
  57. data/templates/basic/assets/try.js +157 -0
  58. data/templates/basic/authenticator.erb +52 -0
  59. data/templates/basic/controller.erb +20 -0
  60. data/templates/basic/index.erb +114 -0
  61. data/templates/basic/layout.erb +46 -0
  62. data/templates/basic/structure.erb +23 -0
  63. data/test/test_helper.rb +81 -0
  64. data/test/tests/action_access_test.rb +63 -0
  65. data/test/tests/actions_test.rb +524 -0
  66. data/test/tests/authenticators_test.rb +87 -0
  67. data/test/tests/base_test.rb +35 -0
  68. data/test/tests/controllers_test.rb +49 -0
  69. data/test/tests/eval_environment_test.rb +136 -0
  70. data/test/tests/evel_helpers_test.rb +60 -0
  71. data/test/tests/examples_test.rb +11 -0
  72. data/test/tests/helpers_test.rb +97 -0
  73. data/test/tests/param_set_test.rb +44 -0
  74. data/test/tests/rack_middleware_test.rb +131 -0
  75. data/test/tests/request_test.rb +232 -0
  76. data/test/tests/structures_param_extensions_test.rb +159 -0
  77. data/test/tests/structures_test.rb +398 -0
  78. metadata +71 -56
@@ -1,3 +1,5 @@
1
+ require 'moonrope/dsl/structure_dsl'
2
+
1
3
  module Moonrope
2
4
  class Structure
3
5
 
@@ -22,6 +24,9 @@ module Moonrope
22
24
  # @return [Hash] attributes which should be included in this structure
23
25
  attr_reader :attributes
24
26
 
27
+ # @return [Bool] should this structure be documented
28
+ attr_accessor :doc
29
+
25
30
  #
26
31
  # Initialize a new structure
27
32
  #
@@ -37,6 +42,15 @@ module Moonrope
37
42
  @dsl.instance_eval(&block) if block_given?
38
43
  end
39
44
 
45
+ #
46
+ # Return details for the given attribute
47
+ #
48
+ def attribute(name)
49
+ @attributes[:basic].select { |p| p.name == name }.first ||
50
+ @attributes[:full].select { |p| p.name == name }.first ||
51
+ @attributes[:expansion].select { |p| p.name == name }.first
52
+ end
53
+
40
54
  #
41
55
  # Return a hash for this struture
42
56
  #
@@ -46,7 +60,7 @@ module Moonrope
46
60
  #
47
61
  def hash(object, options = {})
48
62
  # Set up an environment
49
- environment = EvalEnvironment.new(base, options[:request], :o => object)
63
+ environment = EvalEnvironment.new(base, options[:request], options[:request] ? options[:request].action : nil, :o => object)
50
64
 
51
65
  # Set a new hash
52
66
  hash = Hash.new
@@ -71,19 +85,36 @@ module Moonrope
71
85
  end
72
86
  end
73
87
 
88
+ if options[:attributes]
89
+ hash.reject! { |k,v| !options[:attributes].include?(k.to_sym) }
90
+ end
91
+
74
92
  # Add expansions
75
93
  if options[:expansions]
76
94
 
95
+ if options[:expansions].is_a?(Array)
96
+ expansions_to_include = options[:expansions].each_with_object({}) do |expan, hash|
97
+ if expan.is_a?(Symbol) || expan.is_a?(String)
98
+ hash[expan.to_sym] = nil
99
+ elsif expan.is_a?(Hash)
100
+ hash[expan.first.first.to_sym] = expan.first.last
101
+ end
102
+ end
103
+ else
104
+ expansions_to_include = true
105
+ end
106
+
77
107
  # Add structured expansions
78
108
  @attributes[:expansion].each do |attribute|
79
- next if options[:expansions].is_a?(Array) && !options[:expansions].include?(attribute.name.to_sym)
80
- DeepMerge.deep_merge! hash_for_attributes([attribute], object, environment), hash
109
+ next if expansions_to_include.is_a?(Hash) && !expansions_to_include.keys.include?(attribute.name.to_sym)
110
+ DeepMerge.deep_merge! hash_for_attributes([attribute], object, environment, :structure_opts => expansions_to_include.is_a?(Hash) && expansions_to_include[attribute.name.to_sym]), hash
81
111
  end
82
112
 
83
113
  # Add the expansions
84
114
  expansions.each do |name, expansion|
85
115
  next if options[:expansions].is_a?(Array) && !options[:expansions].include?(name.to_sym)
86
- DeepMerge.deep_merge!({name.to_sym => environment.instance_eval(&expansion)}, hash)
116
+ next unless check_conditions(environment, expansion[:conditions])
117
+ DeepMerge.deep_merge!({name.to_sym => environment.instance_eval(&expansion[:block])}, hash)
87
118
  end
88
119
  end
89
120
 
@@ -91,25 +122,69 @@ module Moonrope
91
122
  hash
92
123
  end
93
124
 
125
+ #
126
+ # Return an array of all expansions which are available on this structure
127
+ #
128
+ def all_expansions
129
+ @attributes[:expansion].map(&:name) + expansions.keys
130
+ end
131
+
132
+ #
133
+ # Return the description for a given condition hash
134
+ #
135
+ def description_for_condition(condition)
136
+ if condition[:authenticator] && condition[:access_rule]
137
+ if authenticator = base.authenticators[condition[:authenticator]]
138
+ if access_rule = authenticator.rules[condition[:access_rule]]
139
+ access_rule[:description]
140
+ end
141
+ end
142
+ else
143
+ condition[:description]
144
+ end
145
+ end
146
+
94
147
  private
95
148
 
149
+ #
150
+ # Call all conditions provided and return whether they pass or not
151
+ #
152
+ def check_conditions(environment, conditions)
153
+ conditions.each do |condition|
154
+ if condition[:block]
155
+ unless environment.instance_eval(&condition[:block])
156
+ return false
157
+ end
158
+ elsif condition[:authenticator] && condition[:access_rule]
159
+ if authenticator = base.authenticators[condition[:authenticator]]
160
+ if access_rule = authenticator.rules[condition[:access_rule]]
161
+ # If we have an authenticator and access rule, use the access rule
162
+ # block with this environment to determine if we should include the
163
+ # given block or not.
164
+ unless environment.instance_exec(self, &access_rule[:block])
165
+ return false
166
+ end
167
+ else
168
+ raise Moonrope::Errors::MissingAccessRule, "The rule '#{condition[:access_rule]}' was not found on '#{authenticator.name}' authenticator"
169
+ end
170
+ else
171
+ raise Moonrope::Errors::MissingAuthenticator, "The authentication '#{condition[:authenticator]}' was not found"
172
+ end
173
+ end
174
+ end
175
+ true
176
+ end
177
+
96
178
  #
97
179
  # Return a returnable hash for a given set of structured fields.
98
180
  #
99
- def hash_for_attributes(attributes, object, environment)
181
+ def hash_for_attributes(attributes, object, environment, value_options = {})
100
182
  return {} unless attributes.is_a?(Array)
101
183
  Hash.new.tap do |hash|
102
184
  attributes.each do |attribute|
103
185
 
104
186
  unless attribute.conditions.empty?
105
- matched = false
106
- attribute.conditions.each do |condition|
107
- if !environment.instance_eval(&condition)
108
- matched = true
109
- break
110
- end
111
- end
112
- if matched
187
+ unless check_conditions(environment, attribute.conditions)
113
188
  # Skip this item because a condition didn't evaluate
114
189
  # to true.
115
190
  next
@@ -121,9 +196,11 @@ module Moonrope
121
196
  elsif attribute.value
122
197
  value = attribute.value
123
198
  else
124
- value = value_for_attribute(object, environment, attribute)
199
+ value = value_for_attribute(object, environment, attribute, value_options)
125
200
  end
126
201
 
202
+ value = attribute.mutate(value)
203
+
127
204
  if attribute.groups.empty?
128
205
  hash[attribute.name] = value
129
206
  else
@@ -144,14 +221,19 @@ module Moonrope
144
221
  #
145
222
  # Return a value for a structured field.
146
223
  #
147
- def value_for_attribute(object, environment, attribute)
148
- value = object.send(attribute.source_attribute)
224
+ def value_for_attribute(object, environment, attribute, options = {})
225
+ if attribute.source_attribute.is_a?(Proc)
226
+ value = environment.instance_eval(&attribute.source_attribute)
227
+ else
228
+ value = object.send(attribute.source_attribute)
229
+ end
230
+
149
231
  if value && attribute.structure
150
232
  # If a structure is required, lookup the desired structure and set the
151
233
  # hash value as appropriate.
152
234
  if structure = self.base.structure(attribute.structure)
153
- structure_opts = attribute.structure_opts || {}
154
- if value.respond_to?(:map)
235
+ structure_opts = options[:structure_opts] || attribute.structure_opts || {}
236
+ if value.is_a?(Enumerable) && value.respond_to?(:map)
155
237
  value.map do |v|
156
238
  structure.hash(v, structure_opts.merge(:request => environment.request))
157
239
  end
@@ -11,6 +11,8 @@ module Moonrope
11
11
  attr_accessor :structure_opts
12
12
  attr_accessor :value
13
13
  attr_accessor :example
14
+ attr_accessor :doc
15
+ attr_accessor :mutation
14
16
 
15
17
  def initialize(type, name)
16
18
  @type = type
@@ -23,5 +25,42 @@ module Moonrope
23
25
  @source_attribute || @name
24
26
  end
25
27
 
28
+ def name_with_groups
29
+ ([groups] + [name]).flatten.compact.join('.')
30
+ end
31
+
32
+ def mutate(value)
33
+ if mutation
34
+ value ? value.public_send(mutation) : nil
35
+ else
36
+ auto_mutate(value)
37
+ end
38
+ end
39
+
40
+ def auto_mutate(value)
41
+ case value_type
42
+ when :timestamp
43
+ value.is_a?(Time) ? value.to_s : value
44
+ when :unix_timestamp
45
+ value.nil? ? nil : value.to_i
46
+ else
47
+ value
48
+ end
49
+ end
50
+
51
+ def example
52
+ @example ||= begin
53
+ if value_type == :timestamp
54
+ "2016-12-25 09:42:00 +0000"
55
+ elsif value_type == :unix_timestamp
56
+ "1491070507"
57
+ elsif value_type == :boolean
58
+ "false"
59
+ elsif value_type == :uuid
60
+ "017dabc1-3f4f-47ab-ab7d-86e2ed0de679"
61
+ end
62
+ end
63
+ end
64
+
26
65
  end
27
66
  end
@@ -1,3 +1,3 @@
1
1
  module Moonrope
2
- VERSION = '1.3.3'
2
+ VERSION = '2.0.2'
3
3
  end
@@ -0,0 +1,21 @@
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+
3
+ require "moonrope/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "moonrope"
7
+ s.version = Moonrope::VERSION
8
+ s.authors = ["Adam Cooke"]
9
+ s.email = ["adam@atechmedia.com"]
10
+ s.homepage = "http://adamcooke.io"
11
+ s.licenses = ['MIT']
12
+ s.summary = "An API server DSL."
13
+ s.description = "A full library allowing you to create sexy DSLs to define your RPC-like APIs."
14
+ s.files = Dir["**/*"]
15
+ s.bindir = "bin"
16
+ s.executables << 'moonrope'
17
+ s.add_dependency "json"
18
+ s.add_dependency "rack", ">= 1.4"
19
+ s.add_dependency "deep_merge", "~> 1.0"
20
+ s.add_development_dependency "rake"
21
+ end
@@ -0,0 +1,32 @@
1
+ $:.unshift(File.expand_path(File.join('..', 'lib')))
2
+
3
+ RSpec.configure do |config|
4
+ config.color = true
5
+ config.expect_with :rspec do |expectations|
6
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
7
+ end
8
+ end
9
+
10
+ class FakeRequest
11
+
12
+ def initialize(options = {})
13
+ @options = options
14
+ end
15
+
16
+ def params
17
+ @params ||= Moonrope::ParamSet.new(@options[:params] || {})
18
+ end
19
+
20
+ def version
21
+ @options[:version]
22
+ end
23
+
24
+ def identity
25
+ @options[:identity]
26
+ end
27
+
28
+ def action
29
+ nil
30
+ end
31
+
32
+ end
@@ -0,0 +1,455 @@
1
+ require 'spec_helper'
2
+ require 'moonrope/base'
3
+ require 'moonrope/controller'
4
+ require 'moonrope/action'
5
+ require 'moonrope/param_set'
6
+ require 'moonrope/eval_environment'
7
+
8
+ describe Moonrope::Action do
9
+ subject(:base) { Moonrope::Base.new }
10
+ subject(:controller) { Moonrope::Controller.new(base, :users) }
11
+ subject(:action) { Moonrope::Action.new(controller, :list) }
12
+ subject(:request) { FakeRequest.new }
13
+ subject(:env) { Moonrope::EvalEnvironment.new(base, request, action) }
14
+
15
+ context "an action" do
16
+ it "should be able to have a name" do
17
+ expect(action.name).to eq :list
18
+ end
19
+
20
+ it "should be able to have a description" do
21
+ action.dsl.description "Some description"
22
+ expect(action.description).to eq("Some description")
23
+ end
24
+
25
+ it "should have a hash of params" do
26
+ action.dsl.param :username
27
+ action.dsl.param :password
28
+ expect(action.params).to be_a(Hash)
29
+ expect(action.params.size).to eq(2)
30
+ end
31
+
32
+ it "should have a return value" do
33
+ action.dsl.returns :hash
34
+ expect(action.returns).to be_a(Hash)
35
+ end
36
+
37
+ it "should have errors" do
38
+ action.dsl.error 'SomeError', "With Description"
39
+ expect(action.errors).to be_a(Hash)
40
+ expect(action.errors.size).to eq 1
41
+ end
42
+
43
+ it "should be able to use shared actions from the controller" do
44
+ controller.dsl.shared_action :crud do
45
+ param :username
46
+ end
47
+ action.dsl.use :crud
48
+ expect(action.params.size).to eq 1
49
+ end
50
+
51
+ it "shuold raise an error if tries to use a share that doesn't exist" do
52
+ expect { action.dsl.use :crud }.to raise_error(Moonrope::Errors::InvalidSharedAction)
53
+ end
54
+
55
+ it "should be able to use shared actions from the base" do
56
+ base.dsl.shared_action :some_base_thing do
57
+ param :username
58
+ end
59
+ action.dsl.use :some_base_thing
60
+ expect(action.params.size).to eq 1
61
+ end
62
+
63
+ it "should have a action blocks" do
64
+ action.dsl.action { true }
65
+ expect(action.actions).to be_a(Array)
66
+ expect(action.actions.first).to be_a(Proc)
67
+ end
68
+ end
69
+
70
+ context "#default_params" do
71
+ it "should return the default params for the action" do
72
+ action.dsl.param :param_with_default, :default => 100
73
+ action.dsl.param :param_with_no_default
74
+ expect(action.default_params).to be_a Hash
75
+ expect(action.default_params.size).to eq 1
76
+ expect(action.default_params['param_with_default']).to eq 100
77
+ end
78
+ end
79
+
80
+ context "#validate_parameters" do
81
+ it "should return an error if a required parameter is missing" do
82
+ action = Moonrope::Action.new(controller, :list) do
83
+ param :param, :required => true
84
+ end
85
+ param_set = Moonrope::ParamSet.new
86
+ expect { action.validate_parameters(param_set) }.to raise_error(Moonrope::Errors::ParameterError)
87
+ end
88
+
89
+ it "should return an error if a parameter doesn't match its regex" do
90
+ action = Moonrope::Action.new(controller, :list) do
91
+ param :param, :regex => /\Ahello\z/
92
+ end
93
+ param_set = Moonrope::ParamSet.new('param' => 'nope')
94
+ expect { action.validate_parameters(param_set) }.to raise_error(Moonrope::Errors::ParameterError)
95
+ param_set = Moonrope::ParamSet.new('param' => 'hello')
96
+ expect { action.validate_parameters(param_set) }.to_not raise_error
97
+ end
98
+
99
+ it "should return an error if a parameter isn't included in an options list" do
100
+ action = Moonrope::Action.new(controller, :list) do
101
+ param :param, :options => ['apple', 'orange']
102
+ end
103
+ param_set = Moonrope::ParamSet.new('param' => 'banana')
104
+ expect { action.validate_parameters(param_set) }.to raise_error(Moonrope::Errors::ParameterError)
105
+ param_set = Moonrope::ParamSet.new('param' => 'apple')
106
+ expect { action.validate_parameters(param_set) }.to_not raise_error
107
+ end
108
+
109
+ it "should return an error if the type is invalid" do
110
+ action = Moonrope::Action.new(controller, :list) do
111
+ param :param, :type => String
112
+ end
113
+ param_set = Moonrope::ParamSet.new('param' => 123)
114
+ expect { action.validate_parameters(param_set) }.to raise_error(Moonrope::Errors::ParameterError)
115
+ param_set = Moonrope::ParamSet.new('param' => 'apple')
116
+ expect { action.validate_parameters(param_set) }.to_not raise_error
117
+ end
118
+
119
+ it "should return an error if the type is a boolean and it is invalid" do
120
+ action = Moonrope::Action.new(controller, :list) do
121
+ param :param, :type => :boolean
122
+ end
123
+ param_set = Moonrope::ParamSet.new('param' => 123)
124
+ expect { action.validate_parameters(param_set) }.to raise_error(Moonrope::Errors::ParameterError)
125
+ param_set = Moonrope::ParamSet.new('param' => 'true')
126
+ expect { action.validate_parameters(param_set) }.to_not raise_error
127
+ param_set = Moonrope::ParamSet.new('param' => 1)
128
+ expect { action.validate_parameters(param_set) }.to_not raise_error
129
+ param_set = Moonrope::ParamSet.new('param' => false)
130
+ expect { action.validate_parameters(param_set) }.to_not raise_error
131
+ end
132
+
133
+ it "should not return an error if the type is a symbol" do
134
+ action = Moonrope::Action.new(controller, :list) do
135
+ param :param, :type => :something
136
+ end
137
+ param_set = Moonrope::ParamSet.new('param' => 'anything')
138
+ expect { action.validate_parameters(param_set) }.to_not raise_error
139
+ param_set = Moonrope::ParamSet.new('param' => 1234.3)
140
+ expect { action.validate_parameters(param_set) }.to_not raise_error
141
+ end
142
+ end
143
+
144
+ context "#access_rule_to_use" do
145
+ it "should return the action's access rule if defined on action" do
146
+ action.dsl.access_rule :rule
147
+ expect(action.access_rule_to_use).to eq(:rule)
148
+ end
149
+
150
+ it "should return the controller's access rule if none on action" do
151
+ controller.access_rule = :crule
152
+ expect(action.access_rule_to_use).to eq(:crule)
153
+ end
154
+
155
+ it "should return the default access rule if none on action or controller" do
156
+ expect(action.access_rule_to_use).to eq(:default)
157
+ end
158
+ end
159
+
160
+ context "#authenticator_to_use" do
161
+ it "should return the action's authentication if defined on action" do
162
+ base.dsl.authenticator :something
163
+ action.dsl.authenticator :something
164
+ expect(action.authenticator_to_use).to be_a(Moonrope::Authenticator)
165
+ expect(action.authenticator_to_use.name).to eq(:something)
166
+ end
167
+
168
+ it "should return the controller's authenticator if none on action" do
169
+ base.dsl.authenticator :csomething
170
+ controller.dsl.authenticator :csomething
171
+ expect(action.authenticator_to_use).to be_a(Moonrope::Authenticator)
172
+ expect(action.authenticator_to_use.name).to eq(:csomething)
173
+ end
174
+
175
+ it "should return no authenticator if none on action or controller and none are defined" do
176
+ expect(action.authenticator_to_use).to eq :none
177
+ end
178
+
179
+ it "should return default authenticator if none on action or controller and there is a default" do
180
+ base.dsl.authenticator :default
181
+ expect(action.authenticator_to_use).to be_a(Moonrope::Authenticator)
182
+ expect(action.authenticator_to_use.name).to eq(:default)
183
+ end
184
+
185
+ it "should return not_found if the chosen authenticator isn't valid" do
186
+ action.dsl.authenticator :something
187
+ expect(action.authenticator_to_use).to eq :not_found
188
+ end
189
+ end
190
+
191
+ context "#convert_errors_to_action_result" do
192
+ it "should return the block result if no errors" do
193
+ result = action.convert_errors_to_action_result { 1234 }
194
+ expect(result).to eq(1234)
195
+ end
196
+
197
+ it "should return an ActionResult if a request error is encountered" do
198
+ result = action.convert_errors_to_action_result do
199
+ raise Moonrope::Errors::ParameterError, "Invalid param"
200
+ end
201
+ expect(result).to be_a(Moonrope::ActionResult)
202
+ end
203
+
204
+ it "should return an ActionResult if a registered external error is encountered" do
205
+ class SomeError < StandardError
206
+ end
207
+ base.register_external_error SomeError do |exception, result|
208
+ result.status = 'some-error'
209
+ result.data = {:hello => "world"}
210
+ end
211
+ result = action.convert_errors_to_action_result do
212
+ raise SomeError
213
+ end
214
+ expect(result).to be_a(Moonrope::ActionResult)
215
+ expect(result.status).to eq('some-error')
216
+ expect(result.data).to be_a(Hash)
217
+ expect(result.data[:hello]).to eq 'world'
218
+ end
219
+
220
+ it "should raise as normal for any non recognized error" do
221
+ expect { action.convert_errors_to_action_result{ raise StandardError }}.to raise_error(StandardError)
222
+ end
223
+ end
224
+
225
+ context "#check_access" do
226
+ it "should return true if no authenticator is available" do
227
+ expect(action.check_access(env)).to be true
228
+ end
229
+
230
+ it "should return an error if the given access rule is not defined on the authenticator" do
231
+ base.dsl.authenticator :default
232
+ action.dsl.access_rule :invalid_rule
233
+ expect { action.check_access(env) }.to raise_error(Moonrope::Errors::MissingAccessRule)
234
+ end
235
+
236
+ it "should return true if the authenticator has no default rule and the default has been requested" do
237
+ base.dsl.authenticator :default
238
+ expect(action.access_rule_to_use).to eq :default
239
+ expect(action.check_access(env)).to be true
240
+ end
241
+
242
+ it "should return the value of the authenticators access block" do
243
+ rule_has_executed = false
244
+ base.dsl.authenticator :default do
245
+ rule :default, "NotPermitted" do
246
+ rule_has_executed = true
247
+ false
248
+ end
249
+ end
250
+ expect(action.check_access(env)).to eq false
251
+ expect(rule_has_executed).to be true
252
+ end
253
+ end
254
+
255
+ context "#execute" do
256
+
257
+ it "should validate parameters are valid" do
258
+ allow(action).to receive(:validate_parameters).and_return true
259
+ action.dsl.action { true }
260
+ action.execute(env)
261
+ expect(action).to have_received(:validate_parameters).once
262
+ end
263
+
264
+ it "should execute before actions from the controller" do
265
+ before_action_run = false
266
+ controller.dsl.before { before_action_run = true }
267
+ action.dsl.action { true }
268
+ action.execute(env)
269
+ expect(before_action_run).to be true
270
+ end
271
+
272
+ it "should return an ActionResult instance" do
273
+ action.dsl.action { true }
274
+ result = action.execute(env)
275
+ expect(result).to be_a Moonrope::ActionResult
276
+ end
277
+
278
+ it "should have a status" do
279
+ action.dsl.action { true }
280
+ expect(action.execute(env).status).to eq 'success'
281
+ end
282
+
283
+ it "should return a time" do
284
+ action.dsl.action { true }
285
+ expect(action.execute(env).time).to be_a(Float)
286
+ end
287
+
288
+ it "should return the result" do
289
+ action.dsl.action { 1234 }
290
+ expect(action.execute(env).data).to eq 1234
291
+ end
292
+
293
+ it "should include flags from the eval environment" do
294
+ action.dsl.action do
295
+ set_flag 'hello', 'world'
296
+ end
297
+ result = action.execute(env)
298
+ expect(result.flags).to be_a(Hash)
299
+ expect(result.flags['hello']).to eq 'world'
300
+ end
301
+
302
+ it "should include headers from the eval environment" do
303
+ action.dsl.action do
304
+ set_header 'X-Something', 'Monkey'
305
+ end
306
+ result = action.execute(env)
307
+ expect(result.headers).to be_a(Hash)
308
+ expect(result.headers['X-Something']).to eq 'Monkey'
309
+ end
310
+ end
311
+
312
+ context "#can_change_full?" do
313
+ it "should return true if the action is fully paramable" do
314
+ action.dsl.returns :hash, :structure => :user, :structure_opts => {:paramable => true}
315
+ expect(action.can_change_full?).to be true
316
+ end
317
+
318
+ it "should return true if the action paramable allow full changes" do
319
+ action.dsl.returns :hash, :structure => :user, :structure_opts => {:paramable => {:full => true}}
320
+ expect(action.can_change_full?).to be true
321
+ action.dsl.returns :hash, :structure => :user, :structure_opts => {:paramable => {:full => false}}
322
+ expect(action.can_change_full?).to be true
323
+ end
324
+
325
+ it "should return false otherwise" do
326
+ action.dsl.returns :hash, :structure => :user, :structure_opts => {}
327
+ expect(action.can_change_full?).to be false
328
+ action.dsl.returns :hash, :structure => :user, :structure_opts => {:full => true}
329
+ expect(action.can_change_full?).to be false
330
+ end
331
+ end
332
+
333
+ context "#includes_full_attributes?" do
334
+ it "should return true if the action is paramable" do
335
+ action.dsl.returns :hash, :structure => :user, :structure_opts => {:paramable => {:full => true}}
336
+ expect(action.includes_full_attributes?).to be true
337
+ action.dsl.returns :hash, :structure => :user, :structure_opts => {:paramable => {:full => false}}
338
+ expect(action.includes_full_attributes?).to be false
339
+ end
340
+
341
+ it "should return true if it always returns full attributes" do
342
+ action.dsl.returns :hash, :structure => :user, :structure_opts => {:full => true}
343
+ expect(action.includes_full_attributes?).to be true
344
+ end
345
+
346
+ it "should be false if paramable is enabled" do
347
+ action.dsl.returns :hash, :structure => :user, :structure_opts => {:paramable => true}
348
+ expect(action.includes_full_attributes?).to be false
349
+ end
350
+
351
+ it "should return false otherwise" do
352
+ action.dsl.returns :hash, :structure => :user, :structure_opts => {:full => false}
353
+ expect(action.includes_full_attributes?).to be false
354
+ action.dsl.returns :hash, :structure => :user, :structure_opts => {}
355
+ expect(action.includes_full_attributes?).to be false
356
+ end
357
+ end
358
+
359
+ context "#can_change_expansions?" do
360
+ it "should return true if the action is fully paramable" do
361
+ action.dsl.returns :hash, :structure => :user, :structure_opts => {:paramable => true}
362
+ expect(action.can_change_expansions?).to be true
363
+ end
364
+ it "should return true if the action paramable allow expansion changes" do
365
+ action.dsl.returns :hash, :structure => :user, :structure_opts => {:paramable => {:expansions => true}}
366
+ expect(action.can_change_expansions?).to be true
367
+ action.dsl.returns :hash, :structure => :user, :structure_opts => {:paramable => {:expansions => false}}
368
+ expect(action.can_change_expansions?).to be true
369
+ action.dsl.returns :hash, :structure => :user, :structure_opts => {:paramable => {:expansions => []}}
370
+ expect(action.can_change_expansions?).to be true
371
+ end
372
+
373
+ it "should return false otherwise" do
374
+ action.dsl.returns :hash, :structure => :user, :structure_opts => {}
375
+ expect(action.can_change_expansions?).to be false
376
+ end
377
+ end
378
+
379
+ context "#includes_expansion?" do
380
+ it "should return false if the action's paramable expansions are true" do
381
+ action.dsl.returns :hash, :structure => :user, :structure_opts => {:paramable => true}
382
+ expect(action.includes_expansion?(:user)).to be false
383
+ end
384
+
385
+ it "should return true if the action always returns all expansions" do
386
+ action.dsl.returns :hash, :structure => :user, :structure_opts => {:expansions => true}
387
+ expect(action.includes_expansion?(:user)).to be true
388
+ end
389
+
390
+ it "should return true if the action's paramable expansions is an array and it includes the expansion" do
391
+ action.dsl.returns :hash, :structure => :user, :structure_opts => {:paramable => {:expansions => [:user]}}
392
+ expect(action.includes_expansion?(:user)).to be true
393
+ expect(action.includes_expansion?(:another)).to be false
394
+ end
395
+
396
+ it "should return true if the action expansions is an array and it includes the expansion" do
397
+ action.dsl.returns :hash, :structure => :user, :structure_opts => {:expansions => [:user]}
398
+ expect(action.includes_expansion?(:user)).to be true
399
+ expect(action.includes_expansion?(:another)).to be false
400
+ end
401
+
402
+ it "should return false otherwise" do
403
+ action.dsl.returns :hash, :structure => :user
404
+ expect(action.includes_expansion?(:user)).to be false
405
+ end
406
+ end
407
+
408
+ context "#available_expansions" do
409
+ it "should include all the structure's expansions if no array if expansions is provided" do
410
+ base.dsl.structure :user do
411
+ expansion :owner
412
+ expansion :admin
413
+ end
414
+ action.dsl.returns :hash, :structure => :user, :structure_opts => {:paramable => true}
415
+ expect(action.available_expansions).to eq([:owner, :admin])
416
+ end
417
+
418
+ it "should include only listed expansions if an array is set" do
419
+ action.dsl.returns :hash, :structure => :user, :structure_opts => {:paramable => {:expansions => [:owner]}}
420
+ expect(action.available_expansions).to eq([:owner])
421
+ end
422
+
423
+ it "should be empty if there's no structure" do
424
+ action.dsl.returns :hash
425
+ expect(action.available_expansions).to eq([])
426
+ end
427
+ end
428
+
429
+ context "dsl#filterable" do
430
+ before do
431
+ action.dsl.filterable do
432
+ attribute :name
433
+ attribute :user_id, :operators => [:eq, :not_eq, :in, :not_in] do |operator, value, scope|
434
+ scope.where(:user => User.find_by_id(value) || error('InvalidUser'))
435
+ end
436
+ end
437
+ end
438
+
439
+ it "should have an hash of fields" do
440
+ expect(action.filters).to be_a(Hash)
441
+ expect(action.filters[:name]).to be_a(Hash)
442
+ expect(action.filters[:user_id]).to be_a(Hash)
443
+ expect(action.filters[:user_id][:block]).to be_a(Proc)
444
+ end
445
+
446
+ it "should add a 'filters' param" do
447
+ expect(action.params[:filters]).to be_a Hash
448
+ end
449
+
450
+ it "should add an error for filter errors" do
451
+ expect(action.errors['FilterError']).to be_a Hash
452
+ end
453
+
454
+ end
455
+ end