moonrope 1.3.3 → 2.0.2

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 (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