haveapi 0.26.4 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rspec +1 -0
- data/Gemfile +7 -3
- data/haveapi.gemspec +1 -1
- data/lib/haveapi/action.rb +1 -1
- data/lib/haveapi/authentication/base.rb +2 -0
- data/lib/haveapi/authentication/oauth2/provider.rb +10 -2
- data/lib/haveapi/authentication/token/provider.rb +25 -1
- data/lib/haveapi/model_adapters/active_record.rb +61 -4
- data/lib/haveapi/parameters/typed.rb +94 -10
- data/lib/haveapi/params.rb +6 -1
- data/lib/haveapi/resource.rb +1 -1
- data/lib/haveapi/server.rb +10 -1
- data/lib/haveapi/spec/api_builder.rb +8 -3
- data/lib/haveapi/spec/spec_methods.rb +20 -10
- data/lib/haveapi/version.rb +1 -1
- data/spec/action/authorize_spec.rb +317 -0
- data/spec/action/dsl_spec.rb +98 -100
- data/spec/action/runtime_spec.rb +207 -0
- data/spec/action_state_spec.rb +301 -0
- data/spec/authentication/basic_spec.rb +108 -0
- data/spec/authentication/oauth2_spec.rb +127 -0
- data/spec/authentication/token_spec.rb +233 -0
- data/spec/authorization_spec.rb +23 -18
- data/spec/common_spec.rb +19 -17
- data/spec/documentation/auth_filtering_spec.rb +111 -0
- data/spec/documentation_spec.rb +165 -2
- data/spec/envelope_spec.rb +5 -9
- data/spec/extensions/action_exceptions_spec.rb +163 -0
- data/spec/hooks_spec.rb +32 -38
- data/spec/model_adapters/active_record_spec.rb +411 -0
- data/spec/parameters/typed_spec.rb +54 -1
- data/spec/params_spec.rb +27 -25
- data/spec/resource_spec.rb +36 -22
- data/spec/server/integration_spec.rb +71 -0
- data/spec/spec_helper.rb +2 -2
- data/spec/validators/acceptance_spec.rb +10 -12
- data/spec/validators/confirmation_spec.rb +14 -16
- data/spec/validators/custom_spec.rb +1 -1
- data/spec/validators/exclusion_spec.rb +13 -15
- data/spec/validators/format_spec.rb +20 -22
- data/spec/validators/inclusion_spec.rb +13 -15
- data/spec/validators/length_spec.rb +6 -6
- data/spec/validators/numericality_spec.rb +10 -10
- data/spec/validators/presence_spec.rb +16 -22
- data/test_support/client_test_api.rb +583 -0
- data/test_support/client_test_server.rb +59 -0
- metadata +16 -3
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
module AuthorizeSpec
|
|
6
|
+
User = Struct.new(:id, :login, :admin) do
|
|
7
|
+
def admin?
|
|
8
|
+
admin
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class BasicProvider < HaveAPI::Authentication::Basic::Provider
|
|
13
|
+
protected
|
|
14
|
+
|
|
15
|
+
def find_user(_request, username, password)
|
|
16
|
+
return nil unless password == 'pass'
|
|
17
|
+
|
|
18
|
+
case username
|
|
19
|
+
when 'user'
|
|
20
|
+
User.new(1, 'user', false)
|
|
21
|
+
when 'admin'
|
|
22
|
+
User.new(2, 'admin', true)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
describe AuthorizeSpec do
|
|
29
|
+
api do
|
|
30
|
+
define_resource(:Item) do
|
|
31
|
+
version 1
|
|
32
|
+
route 'items'
|
|
33
|
+
|
|
34
|
+
define_action(:AdminOnly) do
|
|
35
|
+
route 'admin_only'
|
|
36
|
+
http_method :get
|
|
37
|
+
|
|
38
|
+
authorize do |user|
|
|
39
|
+
allow if user&.admin?
|
|
40
|
+
deny
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
output do
|
|
44
|
+
string :msg
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def exec
|
|
48
|
+
{ msg: 'ok' }
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
define_action(:Echo) do
|
|
53
|
+
route 'echo'
|
|
54
|
+
http_method :post
|
|
55
|
+
|
|
56
|
+
authorize do |user|
|
|
57
|
+
unless user&.admin?
|
|
58
|
+
input blacklist: %i[secret nested.hidden]
|
|
59
|
+
output blacklist: %i[secret nested.hidden]
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
allow
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
input do
|
|
66
|
+
string :public
|
|
67
|
+
string :secret
|
|
68
|
+
string :'nested.visible'
|
|
69
|
+
string :'nested.hidden'
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
output do
|
|
73
|
+
string :public
|
|
74
|
+
string :secret
|
|
75
|
+
string :'nested.visible'
|
|
76
|
+
string :'nested.hidden'
|
|
77
|
+
bool :seen_secret
|
|
78
|
+
bool :seen_nested_hidden
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def exec
|
|
82
|
+
{
|
|
83
|
+
public: input[:public],
|
|
84
|
+
secret: input[:secret],
|
|
85
|
+
'nested.visible': input[:'nested.visible'],
|
|
86
|
+
'nested.hidden': input[:'nested.hidden'],
|
|
87
|
+
seen_secret: input.has_key?(:secret),
|
|
88
|
+
seen_nested_hidden: input.has_key?(:'nested.hidden')
|
|
89
|
+
}
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
items = [
|
|
94
|
+
{ id: 1, owner_id: 1, name: 'u1' },
|
|
95
|
+
{ id: 2, owner_id: 2, name: 'u2' },
|
|
96
|
+
{ id: 3, owner_id: 1, name: 'u1b' }
|
|
97
|
+
].freeze
|
|
98
|
+
|
|
99
|
+
define_action(:List) do
|
|
100
|
+
route 'list'
|
|
101
|
+
http_method :get
|
|
102
|
+
|
|
103
|
+
authorize do |user|
|
|
104
|
+
restrict owner_id: user.id
|
|
105
|
+
allow
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
input do
|
|
109
|
+
integer :owner_id
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
output(:object_list) do
|
|
113
|
+
integer :id
|
|
114
|
+
integer :owner_id
|
|
115
|
+
string :name
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
define_method(:exec) do
|
|
119
|
+
restrictions = with_restricted(owner_id: input[:owner_id])
|
|
120
|
+
items.select { |item| item[:owner_id] == restrictions[:owner_id] }
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
default_version 1
|
|
127
|
+
auth_chain AuthorizeSpec::BasicProvider
|
|
128
|
+
|
|
129
|
+
let(:echo_input) do
|
|
130
|
+
{
|
|
131
|
+
item: {
|
|
132
|
+
public: 'pub',
|
|
133
|
+
secret: 'shh',
|
|
134
|
+
'nested.visible': 'visible',
|
|
135
|
+
'nested.hidden': 'hidden'
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def call_get_action(resource, action, params = {})
|
|
141
|
+
env 'rack.input', StringIO.new('')
|
|
142
|
+
call_api(resource, action, params)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
it 'denies non-admins from admin-only action' do
|
|
146
|
+
login('user', 'pass')
|
|
147
|
+
call_get_action([:Item], :admin_only, {})
|
|
148
|
+
|
|
149
|
+
expect(last_response.status).to eq(403)
|
|
150
|
+
expect(api_response).to be_failed
|
|
151
|
+
expect(api_response.message).to match(/not authorized|forbidden|denied|access denied/i)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
it 'allows admins to access admin-only action' do
|
|
155
|
+
login('admin', 'pass')
|
|
156
|
+
call_get_action([:Item], :admin_only, {})
|
|
157
|
+
|
|
158
|
+
expect(last_response.status).to eq(200)
|
|
159
|
+
expect(api_response).to be_ok
|
|
160
|
+
expect(api_response[:item][:msg]).to eq('ok')
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
it 'filters output fields for non-admins' do
|
|
164
|
+
login('user', 'pass')
|
|
165
|
+
call_api([:Item], :echo, echo_input)
|
|
166
|
+
|
|
167
|
+
expect(last_response.status).to eq(200)
|
|
168
|
+
expect(api_response).to be_ok
|
|
169
|
+
|
|
170
|
+
item = api_response[:item]
|
|
171
|
+
expect(item).to include(:public, :'nested.visible', :seen_secret, :seen_nested_hidden)
|
|
172
|
+
expect(item).not_to have_key(:secret)
|
|
173
|
+
expect(item).not_to have_key(:'nested.hidden')
|
|
174
|
+
expect(item[:seen_secret]).to be(false)
|
|
175
|
+
expect(item[:seen_nested_hidden]).to be(false)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
it 'ignores forbidden input fields for non-admins' do
|
|
179
|
+
login('user', 'pass')
|
|
180
|
+
call_api([:Item], :echo, echo_input)
|
|
181
|
+
|
|
182
|
+
expect(last_response.status).to eq(200)
|
|
183
|
+
expect(api_response).to be_ok
|
|
184
|
+
expect(api_response[:item][:seen_secret]).to be(false)
|
|
185
|
+
expect(api_response[:item][:seen_nested_hidden]).to be(false)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
it 'accepts allowed input for non-admins' do
|
|
189
|
+
login('user', 'pass')
|
|
190
|
+
call_api([:Item], :echo, { item: { public: 'pub', 'nested.visible': 'visible' } })
|
|
191
|
+
|
|
192
|
+
expect(last_response.status).to eq(200)
|
|
193
|
+
expect(api_response).to be_ok
|
|
194
|
+
expect(api_response[:item][:public]).to eq('pub')
|
|
195
|
+
expect(api_response[:item]).not_to have_key(:secret)
|
|
196
|
+
expect(api_response[:item]).not_to have_key(:'nested.hidden')
|
|
197
|
+
expect(api_response[:item][:seen_secret]).to be(false)
|
|
198
|
+
expect(api_response[:item][:seen_nested_hidden]).to be(false)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
it 'shows full output for admins' do
|
|
202
|
+
login('admin', 'pass')
|
|
203
|
+
call_api([:Item], :echo, echo_input)
|
|
204
|
+
|
|
205
|
+
expect(last_response.status).to eq(200)
|
|
206
|
+
expect(api_response).to be_ok
|
|
207
|
+
|
|
208
|
+
item = api_response[:item]
|
|
209
|
+
expect(item[:secret]).to eq('shh')
|
|
210
|
+
expect(item[:'nested.hidden']).to eq('hidden')
|
|
211
|
+
expect(item[:seen_secret]).to be(true)
|
|
212
|
+
expect(item[:seen_nested_hidden]).to be(true)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
it 'does not cache authorization filters between requests' do
|
|
216
|
+
login('user', 'pass')
|
|
217
|
+
call_api([:Item], :echo, echo_input)
|
|
218
|
+
|
|
219
|
+
expect(api_response).to be_ok
|
|
220
|
+
expect(api_response[:item]).not_to have_key(:secret)
|
|
221
|
+
|
|
222
|
+
login('admin', 'pass')
|
|
223
|
+
call_api([:Item], :echo, echo_input)
|
|
224
|
+
|
|
225
|
+
expect(api_response).to be_ok
|
|
226
|
+
expect(api_response[:item]).to have_key(:secret)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
it 'restricts list results to the current user' do
|
|
230
|
+
login('user', 'pass')
|
|
231
|
+
call_get_action([:Item], :list, {})
|
|
232
|
+
|
|
233
|
+
expect(last_response.status).to eq(200)
|
|
234
|
+
expect(api_response).to be_ok
|
|
235
|
+
|
|
236
|
+
owners = api_response[:items].map { |item| item[:owner_id] }.uniq
|
|
237
|
+
expect(owners).to eq([1])
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
it 'restricts list results to the admin user id' do
|
|
241
|
+
login('admin', 'pass')
|
|
242
|
+
call_get_action([:Item], :list, {})
|
|
243
|
+
|
|
244
|
+
expect(last_response.status).to eq(200)
|
|
245
|
+
expect(api_response).to be_ok
|
|
246
|
+
|
|
247
|
+
owners = api_response[:items].map { |item| item[:owner_id] }.uniq
|
|
248
|
+
expect(owners).to eq([2])
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
it 'overrides client-supplied filters with restrictions' do
|
|
252
|
+
login('user', 'pass')
|
|
253
|
+
call_get_action([:Item], :list, { item: { owner_id: 2 } })
|
|
254
|
+
|
|
255
|
+
expect(last_response.status).to eq(200)
|
|
256
|
+
expect(api_response).to be_ok
|
|
257
|
+
|
|
258
|
+
owners = api_response[:items].map { |item| item[:owner_id] }.uniq
|
|
259
|
+
expect(owners).to eq([1])
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
it 'hides admin-only actions from non-admin documentation' do
|
|
263
|
+
login('user', 'pass')
|
|
264
|
+
call_api(:options, '/v1/')
|
|
265
|
+
|
|
266
|
+
expect(last_response.status).to eq(200)
|
|
267
|
+
expect(api_response).to be_ok
|
|
268
|
+
|
|
269
|
+
actions = api_response[:resources][:item][:actions]
|
|
270
|
+
expect(actions).to have_key(:echo)
|
|
271
|
+
expect(actions).to have_key(:list)
|
|
272
|
+
expect(actions).not_to have_key(:admin_only)
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
it 'shows admin-only actions for admin documentation' do
|
|
276
|
+
login('admin', 'pass')
|
|
277
|
+
call_api(:options, '/v1/')
|
|
278
|
+
|
|
279
|
+
expect(last_response.status).to eq(200)
|
|
280
|
+
expect(api_response).to be_ok
|
|
281
|
+
|
|
282
|
+
actions = api_response[:resources][:item][:actions]
|
|
283
|
+
expect(actions).to have_key(:admin_only)
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
it 'filters action docs input and output for non-admins' do
|
|
287
|
+
login('user', 'pass')
|
|
288
|
+
call_api(:options, '/v1/items/echo?method=POST')
|
|
289
|
+
|
|
290
|
+
expect(last_response.status).to eq(200)
|
|
291
|
+
expect(api_response).to be_ok
|
|
292
|
+
|
|
293
|
+
input_params = api_response[:input][:parameters]
|
|
294
|
+
output_params = api_response[:output][:parameters]
|
|
295
|
+
|
|
296
|
+
expect(input_params).not_to have_key(:secret)
|
|
297
|
+
expect(input_params).not_to have_key(:'nested.hidden')
|
|
298
|
+
expect(output_params).not_to have_key(:secret)
|
|
299
|
+
expect(output_params).not_to have_key(:'nested.hidden')
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
it 'shows full action docs input and output for admins' do
|
|
303
|
+
login('admin', 'pass')
|
|
304
|
+
call_api(:options, '/v1/items/echo?method=POST')
|
|
305
|
+
|
|
306
|
+
expect(last_response.status).to eq(200)
|
|
307
|
+
expect(api_response).to be_ok
|
|
308
|
+
|
|
309
|
+
input_params = api_response[:input][:parameters]
|
|
310
|
+
output_params = api_response[:output][:parameters]
|
|
311
|
+
|
|
312
|
+
expect(input_params).to have_key(:secret)
|
|
313
|
+
expect(input_params).to have_key(:'nested.hidden')
|
|
314
|
+
expect(output_params).to have_key(:secret)
|
|
315
|
+
expect(output_params).to have_key(:'nested.hidden')
|
|
316
|
+
end
|
|
317
|
+
end
|
data/spec/action/dsl_spec.rb
CHANGED
|
@@ -1,15 +1,27 @@
|
|
|
1
1
|
describe HaveAPI::Action do
|
|
2
|
-
|
|
2
|
+
def stub_resource_class(const_name)
|
|
3
|
+
resource_class = Class.new(HaveAPI::Resource)
|
|
4
|
+
stub_const(const_name, resource_class)
|
|
5
|
+
resource_class
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def build_action(resource_const, action_const, superclass = HaveAPI::Action, &block)
|
|
9
|
+
klass = Class.new(superclass)
|
|
10
|
+
stub_const("#{resource_const}::#{action_const}", klass)
|
|
11
|
+
klass.superclass.delayed_inherited(klass)
|
|
12
|
+
klass.class_exec(&block) if block
|
|
13
|
+
klass
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
context 'with DSL' do
|
|
3
17
|
it 'inherits input' do
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
end
|
|
18
|
+
stub_resource_class('Resource')
|
|
19
|
+
input_action_class = build_action('Resource', 'InputAction') do
|
|
20
|
+
input do
|
|
21
|
+
string :param
|
|
9
22
|
end
|
|
10
|
-
|
|
11
|
-
class SubInputAction < InputAction; end
|
|
12
23
|
end
|
|
24
|
+
build_action('Resource', 'SubInputAction', input_action_class)
|
|
13
25
|
|
|
14
26
|
# Invokes execution of input/output blocks
|
|
15
27
|
Resource.routes
|
|
@@ -17,15 +29,13 @@ describe HaveAPI::Action do
|
|
|
17
29
|
end
|
|
18
30
|
|
|
19
31
|
it 'inherits output' do
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
end
|
|
32
|
+
stub_resource_class('Resource')
|
|
33
|
+
output_action_class = build_action('Resource', 'OutputAction') do
|
|
34
|
+
output do
|
|
35
|
+
string :param
|
|
25
36
|
end
|
|
26
|
-
|
|
27
|
-
class SubOutputAction < OutputAction; end
|
|
28
37
|
end
|
|
38
|
+
build_action('Resource', 'SubOutputAction', output_action_class)
|
|
29
39
|
|
|
30
40
|
# Invokes execution of input/output blocks
|
|
31
41
|
Resource.routes
|
|
@@ -33,15 +43,14 @@ describe HaveAPI::Action do
|
|
|
33
43
|
end
|
|
34
44
|
|
|
35
45
|
it 'chains input' do
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
46
|
+
stub_resource_class('Resource')
|
|
47
|
+
build_action('Resource', 'InputChainAction') do
|
|
48
|
+
input do
|
|
49
|
+
string :param1
|
|
50
|
+
end
|
|
41
51
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
end
|
|
52
|
+
input do
|
|
53
|
+
string :param2
|
|
45
54
|
end
|
|
46
55
|
end
|
|
47
56
|
|
|
@@ -53,15 +62,14 @@ describe HaveAPI::Action do
|
|
|
53
62
|
end
|
|
54
63
|
|
|
55
64
|
it 'chains output' do
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
65
|
+
stub_resource_class('Resource')
|
|
66
|
+
build_action('Resource', 'OutputChainAction') do
|
|
67
|
+
output do
|
|
68
|
+
string :param1
|
|
69
|
+
end
|
|
61
70
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
end
|
|
71
|
+
output do
|
|
72
|
+
string :param2
|
|
65
73
|
end
|
|
66
74
|
end
|
|
67
75
|
|
|
@@ -73,43 +81,41 @@ describe HaveAPI::Action do
|
|
|
73
81
|
end
|
|
74
82
|
|
|
75
83
|
it 'can combine chaining and inheritance' do
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
input do
|
|
83
|
-
string :inbase2
|
|
84
|
-
end
|
|
84
|
+
stub_resource_class('Resource')
|
|
85
|
+
base_action_class = build_action('Resource', 'BaseAction') do
|
|
86
|
+
input do
|
|
87
|
+
string :inbase1
|
|
88
|
+
end
|
|
85
89
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
90
|
+
input do
|
|
91
|
+
string :inbase2
|
|
92
|
+
end
|
|
89
93
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
end
|
|
94
|
+
output do
|
|
95
|
+
string :outbase1
|
|
93
96
|
end
|
|
94
97
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
output do
|
|
99
|
+
string :outbase2
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
build_action('Resource', 'SubAction', base_action_class) do
|
|
103
|
+
input do
|
|
104
|
+
string :insub1
|
|
105
|
+
string :insub2
|
|
106
|
+
end
|
|
100
107
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
108
|
+
input do
|
|
109
|
+
string :insub3
|
|
110
|
+
end
|
|
104
111
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
112
|
+
output do
|
|
113
|
+
string :outsub1
|
|
114
|
+
string :outsub2
|
|
115
|
+
end
|
|
109
116
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
end
|
|
117
|
+
output do
|
|
118
|
+
string :outsub3
|
|
113
119
|
end
|
|
114
120
|
end
|
|
115
121
|
|
|
@@ -119,38 +125,32 @@ describe HaveAPI::Action do
|
|
|
119
125
|
input = Resource::SubAction.input.params.map(&:name)
|
|
120
126
|
output = Resource::SubAction.output.params.map(&:name)
|
|
121
127
|
|
|
122
|
-
expect(input).to
|
|
123
|
-
expect(output).to
|
|
128
|
+
expect(input).to match_array(%i[inbase1 inbase2 insub1 insub2 insub3])
|
|
129
|
+
expect(output).to match_array(%i[outbase1 outbase2 outsub1 outsub2 outsub3])
|
|
124
130
|
end
|
|
125
131
|
|
|
126
132
|
it 'sets layout' do
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
class CombinedLayoutAction < HaveAPI::Action
|
|
151
|
-
input(:hash) {}
|
|
152
|
-
output(:object_list) {}
|
|
153
|
-
end
|
|
133
|
+
stub_resource_class('Resource')
|
|
134
|
+
build_action('Resource', 'DefaultLayoutAction')
|
|
135
|
+
build_action('Resource', 'ObjectLayoutAction') do
|
|
136
|
+
input(:object) {}
|
|
137
|
+
output(:object) {}
|
|
138
|
+
end
|
|
139
|
+
build_action('Resource', 'ObjectListLayoutAction') do
|
|
140
|
+
input(:object_list) {}
|
|
141
|
+
output(:object_list) {}
|
|
142
|
+
end
|
|
143
|
+
build_action('Resource', 'HashLayoutAction') do
|
|
144
|
+
input(:hash) {}
|
|
145
|
+
output(:hash) {}
|
|
146
|
+
end
|
|
147
|
+
build_action('Resource', 'HashListLayoutAction') do
|
|
148
|
+
input(:hash_list) {}
|
|
149
|
+
output(:hash_list) {}
|
|
150
|
+
end
|
|
151
|
+
build_action('Resource', 'CombinedLayoutAction') do
|
|
152
|
+
input(:hash) {}
|
|
153
|
+
output(:object_list) {}
|
|
154
154
|
end
|
|
155
155
|
|
|
156
156
|
expect(Resource::DefaultLayoutAction.input.layout).to eq(:object)
|
|
@@ -173,11 +173,10 @@ describe HaveAPI::Action do
|
|
|
173
173
|
end
|
|
174
174
|
|
|
175
175
|
it 'catches exceptions in input' do
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
end
|
|
176
|
+
stub_resource_class('ExResourceIn')
|
|
177
|
+
build_action('ExResourceIn', 'ExInputAction') do
|
|
178
|
+
input do
|
|
179
|
+
raise 'this is terrible!'
|
|
181
180
|
end
|
|
182
181
|
end
|
|
183
182
|
|
|
@@ -185,11 +184,10 @@ describe HaveAPI::Action do
|
|
|
185
184
|
end
|
|
186
185
|
|
|
187
186
|
it 'catches exceptions in output' do
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
end
|
|
187
|
+
stub_resource_class('ExResourceOut')
|
|
188
|
+
build_action('ExResourceOut', 'ExOutputAction') do
|
|
189
|
+
output do
|
|
190
|
+
raise 'this is terrible!'
|
|
193
191
|
end
|
|
194
192
|
end
|
|
195
193
|
|