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.
- checksums.yaml +5 -5
- data/Gemfile +9 -0
- data/Gemfile.lock +47 -0
- data/MIT-LICENCE +20 -0
- data/README.md +24 -0
- data/bin/moonrope +28 -0
- data/docs/authentication.md +114 -0
- data/docs/controllers.md +106 -0
- data/docs/exceptions.md +27 -0
- data/docs/introduction.md +29 -0
- data/docs/structures.md +214 -0
- data/example/authentication.rb +50 -0
- data/example/controllers/meta_controller.rb +14 -0
- data/example/controllers/users_controller.rb +92 -0
- data/example/structures/pet_structure.rb +12 -0
- data/example/structures/user_structure.rb +35 -0
- data/lib/moonrope.rb +5 -4
- data/lib/moonrope/action.rb +170 -40
- data/lib/moonrope/authenticator.rb +42 -0
- data/lib/moonrope/base.rb +67 -6
- data/lib/moonrope/controller.rb +4 -2
- data/lib/moonrope/doc_context.rb +94 -0
- data/lib/moonrope/doc_server.rb +123 -0
- data/lib/moonrope/dsl/action_dsl.rb +159 -9
- data/lib/moonrope/dsl/authenticator_dsl.rb +35 -0
- data/lib/moonrope/dsl/base_dsl.rb +21 -18
- data/lib/moonrope/dsl/controller_dsl.rb +60 -9
- data/lib/moonrope/dsl/filterable_dsl.rb +27 -0
- data/lib/moonrope/dsl/structure_dsl.rb +28 -2
- data/lib/moonrope/errors.rb +13 -0
- data/lib/moonrope/eval_environment.rb +82 -3
- data/lib/moonrope/eval_helpers.rb +47 -8
- data/lib/moonrope/eval_helpers/filter_helper.rb +82 -0
- data/lib/moonrope/guard.rb +35 -0
- data/lib/moonrope/html_generator.rb +65 -0
- data/lib/moonrope/param_set.rb +11 -1
- data/lib/moonrope/rack_middleware.rb +66 -37
- data/lib/moonrope/railtie.rb +31 -14
- data/lib/moonrope/request.rb +43 -15
- data/lib/moonrope/structure.rb +100 -18
- data/lib/moonrope/structure_attribute.rb +39 -0
- data/lib/moonrope/version.rb +1 -1
- data/moonrope.gemspec +21 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/specs/action_spec.rb +455 -0
- data/spec/specs/base_spec.rb +29 -0
- data/spec/specs/controller_spec.rb +31 -0
- data/spec/specs/param_set_spec.rb +31 -0
- data/templates/basic/_action_form.erb +77 -0
- data/templates/basic/_errors_table.erb +32 -0
- data/templates/basic/_structure_attributes_list.erb +55 -0
- data/templates/basic/action.erb +168 -0
- data/templates/basic/assets/lock.svg +3 -0
- data/templates/basic/assets/reset.css +101 -0
- data/templates/basic/assets/style.css +348 -0
- data/templates/basic/assets/tool.svg +4 -0
- data/templates/basic/assets/try.js +157 -0
- data/templates/basic/authenticator.erb +52 -0
- data/templates/basic/controller.erb +20 -0
- data/templates/basic/index.erb +114 -0
- data/templates/basic/layout.erb +46 -0
- data/templates/basic/structure.erb +23 -0
- data/test/test_helper.rb +81 -0
- data/test/tests/action_access_test.rb +63 -0
- data/test/tests/actions_test.rb +524 -0
- data/test/tests/authenticators_test.rb +87 -0
- data/test/tests/base_test.rb +35 -0
- data/test/tests/controllers_test.rb +49 -0
- data/test/tests/eval_environment_test.rb +136 -0
- data/test/tests/evel_helpers_test.rb +60 -0
- data/test/tests/examples_test.rb +11 -0
- data/test/tests/helpers_test.rb +97 -0
- data/test/tests/param_set_test.rb +44 -0
- data/test/tests/rack_middleware_test.rb +131 -0
- data/test/tests/request_test.rb +232 -0
- data/test/tests/structures_param_extensions_test.rb +159 -0
- data/test/tests/structures_test.rb +398 -0
- metadata +71 -56
data/lib/moonrope/structure.rb
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
148
|
-
|
|
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
|
data/lib/moonrope/version.rb
CHANGED
data/moonrope.gemspec
ADDED
|
@@ -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
|
data/spec/spec_helper.rb
ADDED
|
@@ -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
|