haveapi 0.26.5 → 0.27.1
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 +66 -4
- data/lib/haveapi/parameters/resource.rb +8 -1
- 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 +413 -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 +607 -0
- data/test_support/client_test_server.rb +59 -0
- metadata +16 -3
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'active_record'
|
|
5
|
+
require 'sqlite3'
|
|
6
|
+
require_relative '../../lib/haveapi/model_adapters/active_record'
|
|
7
|
+
|
|
8
|
+
module ARAdapterSpec
|
|
9
|
+
class Environment < ActiveRecord::Base
|
|
10
|
+
has_many :groups, class_name: 'ARAdapterSpec::Group'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class Group < ActiveRecord::Base
|
|
14
|
+
belongs_to :environment, class_name: 'ARAdapterSpec::Environment', optional: true
|
|
15
|
+
has_many :users, class_name: 'ARAdapterSpec::User'
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class User < ActiveRecord::Base
|
|
19
|
+
belongs_to :group, class_name: 'ARAdapterSpec::Group', optional: true
|
|
20
|
+
|
|
21
|
+
validates :name, length: { minimum: 3, maximum: 20 }
|
|
22
|
+
validates :email, format: { with: /\A.+@.+\z/ }
|
|
23
|
+
validates :role, inclusion: { in: %w[user admin] }
|
|
24
|
+
validates :state, exclusion: { in: %w[banned] }
|
|
25
|
+
validates :age, numericality: {
|
|
26
|
+
greater_than_or_equal_to: 18,
|
|
27
|
+
less_than_or_equal_to: 100
|
|
28
|
+
}
|
|
29
|
+
validates :score, numericality: { equal_to: 7 }
|
|
30
|
+
validates :name, presence: true
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
describe HaveAPI::ModelAdapters::ActiveRecord do
|
|
35
|
+
api do
|
|
36
|
+
env_resource = define_resource(:Environment) do
|
|
37
|
+
version 1
|
|
38
|
+
auth false
|
|
39
|
+
model ARAdapterSpec::Environment
|
|
40
|
+
|
|
41
|
+
define_action(:Index, superclass: HaveAPI::Actions::Default::Index) do
|
|
42
|
+
authorize { allow }
|
|
43
|
+
|
|
44
|
+
output(:object_list) do
|
|
45
|
+
integer :id
|
|
46
|
+
string :label
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def exec
|
|
50
|
+
self.class.model.order(id: :asc).to_a
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
define_action(:Show, superclass: HaveAPI::Actions::Default::Show) do
|
|
55
|
+
authorize { allow }
|
|
56
|
+
|
|
57
|
+
output(:object) do
|
|
58
|
+
integer :id
|
|
59
|
+
string :label
|
|
60
|
+
string :note
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def exec
|
|
64
|
+
self.class.model.find(params['environment_id'])
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
group_resource = define_resource(:Group) do
|
|
70
|
+
version 1
|
|
71
|
+
auth false
|
|
72
|
+
model ARAdapterSpec::Group
|
|
73
|
+
|
|
74
|
+
define_action(:Index, superclass: HaveAPI::Actions::Default::Index) do
|
|
75
|
+
authorize { allow }
|
|
76
|
+
|
|
77
|
+
output(:object_list) do
|
|
78
|
+
integer :id
|
|
79
|
+
string :label
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def exec
|
|
83
|
+
self.class.model.order(id: :asc).to_a
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
define_action(:Show, superclass: HaveAPI::Actions::Default::Show) do
|
|
88
|
+
authorize { allow }
|
|
89
|
+
|
|
90
|
+
output(:object) do
|
|
91
|
+
integer :id
|
|
92
|
+
string :label
|
|
93
|
+
string :note
|
|
94
|
+
resource env_resource
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def exec
|
|
98
|
+
self.class.model.find(params['group_id'])
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
define_resource(:User) do
|
|
104
|
+
version 1
|
|
105
|
+
auth false
|
|
106
|
+
model ARAdapterSpec::User
|
|
107
|
+
|
|
108
|
+
define_action(:Index, superclass: HaveAPI::Actions::Default::Index) do
|
|
109
|
+
authorize { allow }
|
|
110
|
+
|
|
111
|
+
output(:object_list) do
|
|
112
|
+
integer :id
|
|
113
|
+
string :name
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def exec
|
|
117
|
+
with_pagination(self.class.model.order(id: :asc)).to_a
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
define_action(:IndexDesc, superclass: HaveAPI::Actions::Default::Index) do
|
|
122
|
+
route 'desc'
|
|
123
|
+
authorize { allow }
|
|
124
|
+
|
|
125
|
+
output(:object_list) do
|
|
126
|
+
integer :id
|
|
127
|
+
string :name
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def exec
|
|
131
|
+
with_desc_pagination(self.class.model.order(id: :desc)).to_a
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
define_action(:Show, superclass: HaveAPI::Actions::Default::Show) do
|
|
136
|
+
authorize { allow }
|
|
137
|
+
|
|
138
|
+
output(:object) do
|
|
139
|
+
integer :id
|
|
140
|
+
string :name
|
|
141
|
+
resource group_resource
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def exec
|
|
145
|
+
id = params['user_id'].to_i
|
|
146
|
+
with_includes(self.class.model.where(id: id)).take!
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
define_action(:Create, superclass: HaveAPI::Actions::Default::Create) do
|
|
151
|
+
authorize { allow }
|
|
152
|
+
|
|
153
|
+
input do
|
|
154
|
+
string :name
|
|
155
|
+
string :email
|
|
156
|
+
string :role
|
|
157
|
+
string :state
|
|
158
|
+
integer :age
|
|
159
|
+
integer :score
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def exec
|
|
163
|
+
{}
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
default_version 1
|
|
170
|
+
|
|
171
|
+
before(:all) do
|
|
172
|
+
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
|
|
173
|
+
ActiveRecord::Schema.verbose = false
|
|
174
|
+
|
|
175
|
+
ActiveRecord::Schema.define do
|
|
176
|
+
create_table :environments do |t|
|
|
177
|
+
t.string :label, null: false
|
|
178
|
+
t.string :note
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
create_table :groups do |t|
|
|
182
|
+
t.string :label, null: false
|
|
183
|
+
t.string :note
|
|
184
|
+
t.integer :environment_id
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
create_table :users do |t|
|
|
188
|
+
t.string :name
|
|
189
|
+
t.string :email
|
|
190
|
+
t.integer :age
|
|
191
|
+
t.integer :score
|
|
192
|
+
t.string :role
|
|
193
|
+
t.string :state
|
|
194
|
+
t.integer :group_id
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
before do
|
|
200
|
+
ARAdapterSpec::User.delete_all
|
|
201
|
+
ARAdapterSpec::Group.delete_all
|
|
202
|
+
ARAdapterSpec::Environment.delete_all
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
let(:dummy_action) do
|
|
206
|
+
Class.new do
|
|
207
|
+
include HaveAPI::ModelAdapters::ActiveRecord::Action::InstanceMethods
|
|
208
|
+
|
|
209
|
+
def self.model
|
|
210
|
+
ARAdapterSpec::User
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def input
|
|
214
|
+
{}
|
|
215
|
+
end
|
|
216
|
+
end.new
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def create_user(attrs = {})
|
|
220
|
+
defaults = {
|
|
221
|
+
name: 'user',
|
|
222
|
+
email: 'user@example.com',
|
|
223
|
+
role: 'user',
|
|
224
|
+
state: 'active',
|
|
225
|
+
age: 20,
|
|
226
|
+
score: 7
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
ARAdapterSpec::User.create!(defaults.merge(attrs))
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def action_class(resource, action)
|
|
233
|
+
klass, = find_action(1, resource, action)
|
|
234
|
+
klass
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def fetch_validator(validators, klass, short_name)
|
|
238
|
+
validators[klass] || validators[short_name.to_sym] || validators[short_name.to_s]
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
it 'translates ActiveModel validators to HaveAPI validators' do
|
|
242
|
+
app
|
|
243
|
+
create_action = action_class(:User, :create)
|
|
244
|
+
|
|
245
|
+
name_validators = create_action.input[:name].describe(nil)[:validators]
|
|
246
|
+
name_length = fetch_validator(name_validators, 'HaveAPI::Validators::Length', :length)
|
|
247
|
+
expect(name_length).to be_a(Hash)
|
|
248
|
+
expect(name_length[:min]).to eq(3)
|
|
249
|
+
expect(name_length[:max]).to eq(20)
|
|
250
|
+
expect(fetch_validator(name_validators, 'HaveAPI::Validators::Presence', :present)).to be_nil
|
|
251
|
+
|
|
252
|
+
email_validators = create_action.input[:email].describe(nil)[:validators]
|
|
253
|
+
expect(fetch_validator(email_validators, 'HaveAPI::Validators::Format', :format)).to be_a(Hash)
|
|
254
|
+
|
|
255
|
+
role_validators = create_action.input[:role].describe(nil)[:validators]
|
|
256
|
+
role_inclusion = fetch_validator(role_validators, 'HaveAPI::Validators::Inclusion', :include)
|
|
257
|
+
expect(role_inclusion).to be_a(Hash)
|
|
258
|
+
expect(role_inclusion[:values]).to include('user', 'admin')
|
|
259
|
+
|
|
260
|
+
state_validators = create_action.input[:state].describe(nil)[:validators]
|
|
261
|
+
state_exclusion = fetch_validator(state_validators, 'HaveAPI::Validators::Exclusion', :exclude)
|
|
262
|
+
expect(state_exclusion).to be_a(Hash)
|
|
263
|
+
expect(state_exclusion[:values]).to include('banned')
|
|
264
|
+
|
|
265
|
+
age_validators = create_action.input[:age].describe(nil)[:validators]
|
|
266
|
+
age_number = fetch_validator(age_validators, 'HaveAPI::Validators::Numericality', :number)
|
|
267
|
+
expect(age_number).to be_a(Hash)
|
|
268
|
+
expect(age_number[:min]).to eq(18)
|
|
269
|
+
expect(age_number[:max]).to eq(100)
|
|
270
|
+
|
|
271
|
+
score_validators = create_action.input[:score].describe(nil)[:validators]
|
|
272
|
+
score_acceptance = fetch_validator(score_validators, 'HaveAPI::Validators::Acceptance', :accept)
|
|
273
|
+
expect(score_acceptance).to be_a(Hash)
|
|
274
|
+
expect(score_acceptance[:value]).to eq(7)
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
it 'parses nested includes and drops unknown associations' do
|
|
278
|
+
parsed = dummy_action.ar_parse_includes(%w[group group__environment foo bar__baz])
|
|
279
|
+
|
|
280
|
+
expect(parsed).to include(:group)
|
|
281
|
+
expect(parsed.any? { |v| v.is_a?(Hash) && v.has_key?(:group) }).to be(true)
|
|
282
|
+
|
|
283
|
+
nested = parsed.detect { |v| v.is_a?(Hash) && v.has_key?(:group) }
|
|
284
|
+
expect(nested[:group].flatten).to include(:environment)
|
|
285
|
+
|
|
286
|
+
expect(parsed).not_to include(:foo)
|
|
287
|
+
expect(parsed).not_to include(:bar)
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
it 'returns unresolved associations without includes' do
|
|
291
|
+
environment = ARAdapterSpec::Environment.create!(label: 'env', note: 'ENV_NOTE')
|
|
292
|
+
group = ARAdapterSpec::Group.create!(label: 'grp', note: 'GRP_NOTE', environment: environment)
|
|
293
|
+
user = create_user(name: 'user', group: group)
|
|
294
|
+
|
|
295
|
+
get "/v1/users/#{user.id}", {}, input: ''
|
|
296
|
+
|
|
297
|
+
expect(api_response).to be_ok
|
|
298
|
+
ret = api_response[:user]
|
|
299
|
+
group_data = ret[:group]
|
|
300
|
+
|
|
301
|
+
expect(group_data[:_meta][:resolved]).to be(false)
|
|
302
|
+
expect(group_data).to include(id: group.id, label: group.label)
|
|
303
|
+
expect(group_data).not_to have_key(:note)
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
it 'resolves direct associations when included' do
|
|
307
|
+
environment = ARAdapterSpec::Environment.create!(label: 'env', note: 'ENV_NOTE')
|
|
308
|
+
group = ARAdapterSpec::Group.create!(label: 'grp', note: 'GRP_NOTE', environment: environment)
|
|
309
|
+
user = create_user(name: 'user', group: group)
|
|
310
|
+
|
|
311
|
+
get "/v1/users/#{user.id}", { _meta: { includes: 'group' } }, input: ''
|
|
312
|
+
|
|
313
|
+
expect(api_response).to be_ok
|
|
314
|
+
ret = api_response[:user]
|
|
315
|
+
group_data = ret[:group]
|
|
316
|
+
|
|
317
|
+
expect(group_data[:_meta][:resolved]).to be(true)
|
|
318
|
+
expect(group_data[:note]).to eq('GRP_NOTE')
|
|
319
|
+
expect(group_data[:environment][:_meta][:resolved]).to be(false)
|
|
320
|
+
expect(group_data[:environment]).not_to have_key(:note)
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
it 'resolves nested associations when included' do
|
|
324
|
+
environment = ARAdapterSpec::Environment.create!(label: 'env', note: 'ENV_NOTE')
|
|
325
|
+
group = ARAdapterSpec::Group.create!(label: 'grp', note: 'GRP_NOTE', environment: environment)
|
|
326
|
+
user = create_user(name: 'user', group: group)
|
|
327
|
+
|
|
328
|
+
get "/v1/users/#{user.id}", { _meta: { includes: 'group__environment' } }, input: ''
|
|
329
|
+
|
|
330
|
+
expect(api_response).to be_ok
|
|
331
|
+
ret = api_response[:user]
|
|
332
|
+
group_data = ret[:group]
|
|
333
|
+
env_data = group_data[:environment]
|
|
334
|
+
|
|
335
|
+
expect(group_data[:_meta][:resolved]).to be(true)
|
|
336
|
+
expect(env_data[:_meta][:resolved]).to be(true)
|
|
337
|
+
expect(env_data[:note]).to eq('ENV_NOTE')
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
it 'cleans resource input ids and maps invalid values to validation errors' do
|
|
341
|
+
environment = ARAdapterSpec::Environment.create!(id: 1, label: 'env')
|
|
342
|
+
|
|
343
|
+
expect(described_class::Input.clean(ARAdapterSpec::Environment, 1, {})).to eq(environment)
|
|
344
|
+
expect(described_class::Input.clean(ARAdapterSpec::Environment, '1', {})).to eq(environment)
|
|
345
|
+
expect(described_class::Input.clean(ARAdapterSpec::Environment, 1.0, {})).to eq(environment)
|
|
346
|
+
expect(described_class::Input.clean(ARAdapterSpec::Environment, '', { optional: true })).to be_nil
|
|
347
|
+
expect(described_class::Input.clean(ARAdapterSpec::Environment, ' ', { optional: true })).to be_nil
|
|
348
|
+
|
|
349
|
+
expect do
|
|
350
|
+
described_class::Input.clean(ARAdapterSpec::Environment, 'abc', {})
|
|
351
|
+
end.to raise_error(HaveAPI::ValidationError, /not a valid id/)
|
|
352
|
+
|
|
353
|
+
expect do
|
|
354
|
+
described_class::Input.clean(ARAdapterSpec::Environment, '', {})
|
|
355
|
+
end.to raise_error(HaveAPI::ValidationError, /not a valid id/)
|
|
356
|
+
|
|
357
|
+
expect do
|
|
358
|
+
described_class::Input.clean(ARAdapterSpec::Environment, 1.2, {})
|
|
359
|
+
end.to raise_error(HaveAPI::ValidationError, /not a valid id/)
|
|
360
|
+
|
|
361
|
+
expect do
|
|
362
|
+
described_class::Input.clean(ARAdapterSpec::Environment, false, {})
|
|
363
|
+
end.to raise_error(HaveAPI::ValidationError, /not a valid id/)
|
|
364
|
+
|
|
365
|
+
expect do
|
|
366
|
+
described_class::Input.clean(ARAdapterSpec::Environment, true, {})
|
|
367
|
+
end.to raise_error(HaveAPI::ValidationError, /not a valid id/)
|
|
368
|
+
|
|
369
|
+
expect do
|
|
370
|
+
described_class::Input.clean(ARAdapterSpec::Environment, 9999, {})
|
|
371
|
+
end.to raise_error(HaveAPI::ValidationError, /resource not found/)
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
it 'applies ascending pagination with with_pagination' do
|
|
375
|
+
5.times do |i|
|
|
376
|
+
create_user(
|
|
377
|
+
id: i + 1,
|
|
378
|
+
name: "user#{i + 1}",
|
|
379
|
+
email: "user#{i + 1}@example.com",
|
|
380
|
+
age: 20 + i
|
|
381
|
+
)
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
get '/v1/users', { user: { from_id: 2, limit: 2 } }, input: ''
|
|
385
|
+
|
|
386
|
+
expect(api_response).to be_ok
|
|
387
|
+
ids = api_response[:users].map { |u| u[:id] }
|
|
388
|
+
expect(ids).to eq([3, 4])
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
it 'applies descending pagination with with_desc_pagination' do
|
|
392
|
+
5.times do |i|
|
|
393
|
+
create_user(
|
|
394
|
+
id: i + 1,
|
|
395
|
+
name: "user#{i + 1}",
|
|
396
|
+
email: "user#{i + 1}@example.com",
|
|
397
|
+
age: 20 + i
|
|
398
|
+
)
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
get '/v1/users/desc', { user: { from_id: 4, limit: 2 } }, input: ''
|
|
402
|
+
|
|
403
|
+
expect(api_response).to be_ok
|
|
404
|
+
ids = api_response[:users].map { |u| u[:id] }
|
|
405
|
+
expect(ids).to eq([3, 2])
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
it 'raises when paginating a composite primary key model' do
|
|
409
|
+
allow(ARAdapterSpec::User).to receive(:primary_key).and_return(%w[a b])
|
|
410
|
+
|
|
411
|
+
expect { dummy_action.with_asc_pagination }.to raise_error(RuntimeError, /composite primary key/)
|
|
412
|
+
end
|
|
413
|
+
end
|
|
@@ -16,7 +16,7 @@ describe 'Parameters::Typed' do
|
|
|
16
16
|
required: true
|
|
17
17
|
}
|
|
18
18
|
p_arg(kwargs)
|
|
19
|
-
expect(kwargs.keys).to
|
|
19
|
+
expect(kwargs.keys).to match_array(%i[label desc required])
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
it 'automatically sets label' do
|
|
@@ -38,16 +38,34 @@ describe 'Parameters::Typed' do
|
|
|
38
38
|
it 'can be required' do
|
|
39
39
|
p = p_arg(required: true)
|
|
40
40
|
expect(p.required?).to be true
|
|
41
|
+
expect(p.optional?).to be false
|
|
41
42
|
end
|
|
42
43
|
|
|
43
44
|
it 'can be optional' do
|
|
44
45
|
p = p_arg
|
|
46
|
+
expect(p.required?).to be false
|
|
45
47
|
expect(p.optional?).to be true
|
|
46
48
|
|
|
47
49
|
p = p_arg(required: false)
|
|
50
|
+
expect(p.required?).to be false
|
|
48
51
|
expect(p.optional?).to be true
|
|
49
52
|
|
|
50
53
|
p = p_arg(required: nil)
|
|
54
|
+
expect(p.required?).to be false
|
|
55
|
+
expect(p.optional?).to be true
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it 'updates optional? when required is patched' do
|
|
59
|
+
p = p_arg(required: false)
|
|
60
|
+
expect(p.required?).to be false
|
|
61
|
+
expect(p.optional?).to be true
|
|
62
|
+
|
|
63
|
+
p.patch(required: true)
|
|
64
|
+
expect(p.required?).to be true
|
|
65
|
+
expect(p.optional?).to be false
|
|
66
|
+
|
|
67
|
+
p.patch(required: nil)
|
|
68
|
+
expect(p.required?).to be false
|
|
51
69
|
expect(p.optional?).to be true
|
|
52
70
|
end
|
|
53
71
|
|
|
@@ -55,10 +73,26 @@ describe 'Parameters::Typed' do
|
|
|
55
73
|
# Integer
|
|
56
74
|
p = p_type(Integer)
|
|
57
75
|
expect(p.clean('42')).to eq(42)
|
|
76
|
+
expect(p.clean(' -7 ')).to eq(-7)
|
|
77
|
+
expect(p.clean(12)).to eq(12)
|
|
78
|
+
expect(p.clean(12.0)).to eq(12)
|
|
79
|
+
expect { p.clean('abc') }.to raise_error(HaveAPI::ValidationError)
|
|
80
|
+
expect { p.clean('12abc') }.to raise_error(HaveAPI::ValidationError)
|
|
81
|
+
expect { p.clean('') }.to raise_error(HaveAPI::ValidationError)
|
|
82
|
+
expect { p.clean('12.0') }.to raise_error(HaveAPI::ValidationError)
|
|
83
|
+
expect { p.clean(12.3) }.to raise_error(HaveAPI::ValidationError)
|
|
84
|
+
expect { p.clean(true) }.to raise_error(HaveAPI::ValidationError)
|
|
58
85
|
|
|
59
86
|
# Float
|
|
60
87
|
p = p_type(Float)
|
|
61
88
|
expect(p.clean('3.1456')).to eq(3.1456)
|
|
89
|
+
expect(p.clean('1e3')).to eq(1000.0)
|
|
90
|
+
expect(p.clean(3)).to eq(3.0)
|
|
91
|
+
expect { p.clean('abc') }.to raise_error(HaveAPI::ValidationError)
|
|
92
|
+
expect { p.clean('') }.to raise_error(HaveAPI::ValidationError)
|
|
93
|
+
expect { p.clean('NaN') }.to raise_error(HaveAPI::ValidationError)
|
|
94
|
+
expect { p.clean(Float::NAN) }.to raise_error(HaveAPI::ValidationError)
|
|
95
|
+
expect { p.clean(Float::INFINITY) }.to raise_error(HaveAPI::ValidationError)
|
|
62
96
|
|
|
63
97
|
# Boolean
|
|
64
98
|
p = p_type(Boolean)
|
|
@@ -71,6 +105,13 @@ describe 'Parameters::Typed' do
|
|
|
71
105
|
expect(p.clean(v)).to be false
|
|
72
106
|
end
|
|
73
107
|
|
|
108
|
+
expect(p.clean(0)).to be false
|
|
109
|
+
expect(p.clean(1)).to be true
|
|
110
|
+
expect(p.clean(' YES ')).to be true
|
|
111
|
+
expect { p.clean('maybe') }.to raise_error(HaveAPI::ValidationError)
|
|
112
|
+
expect { p.clean('') }.to raise_error(HaveAPI::ValidationError)
|
|
113
|
+
expect { p.clean(2) }.to raise_error(HaveAPI::ValidationError)
|
|
114
|
+
|
|
74
115
|
# Datetime
|
|
75
116
|
p = p_type(Datetime)
|
|
76
117
|
t = Time.now
|
|
@@ -78,10 +119,22 @@ describe 'Parameters::Typed' do
|
|
|
78
119
|
|
|
79
120
|
expect(p.clean(t.iso8601)).to eq(t2)
|
|
80
121
|
expect { p.clean('bzz') }.to raise_error(HaveAPI::ValidationError)
|
|
122
|
+
expect { p.clean('') }.to raise_error(HaveAPI::ValidationError)
|
|
81
123
|
|
|
82
124
|
# String, Text
|
|
83
125
|
p = p_type(String)
|
|
84
126
|
expect(p.clean('bzz')).to eq('bzz')
|
|
127
|
+
expect(p.clean(123)).to eq('123')
|
|
128
|
+
expect(p.clean(true)).to eq('true')
|
|
129
|
+
expect { p.clean([]) }.to raise_error(HaveAPI::ValidationError)
|
|
130
|
+
expect { p.clean({}) }.to raise_error(HaveAPI::ValidationError)
|
|
131
|
+
|
|
132
|
+
p = p_type(Text)
|
|
133
|
+
expect(p.clean('bzz')).to eq('bzz')
|
|
134
|
+
expect(p.clean(123)).to eq('123')
|
|
135
|
+
expect(p.clean(true)).to eq('true')
|
|
136
|
+
expect { p.clean([]) }.to raise_error(HaveAPI::ValidationError)
|
|
137
|
+
expect { p.clean({}) }.to raise_error(HaveAPI::ValidationError)
|
|
85
138
|
|
|
86
139
|
# Defaults
|
|
87
140
|
p = p_type(String)
|
data/spec/params_spec.rb
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
module ParamsSpec
|
|
2
2
|
class MyResource < HaveAPI::Resource
|
|
3
3
|
params(:all) do
|
|
4
4
|
string :res_param1
|
|
@@ -17,63 +17,65 @@ describe HaveAPI::Params do
|
|
|
17
17
|
class Create < HaveAPI::Actions::Default::Create
|
|
18
18
|
end
|
|
19
19
|
end
|
|
20
|
+
end
|
|
20
21
|
|
|
22
|
+
describe HaveAPI::Params do
|
|
21
23
|
it 'executes all blocks' do
|
|
22
|
-
p =
|
|
24
|
+
p = described_class.new(:input, ParamsSpec::MyResource::Index)
|
|
23
25
|
p.add_block proc { string :param1 }
|
|
24
26
|
p.add_block proc { string :param2 }
|
|
25
27
|
p.add_block proc { string :param3 }
|
|
26
28
|
p.exec
|
|
27
|
-
expect(p.params.map(&:name)).to
|
|
29
|
+
expect(p.params.map(&:name)).to match_array(%i[param1 param2 param3])
|
|
28
30
|
end
|
|
29
31
|
|
|
30
32
|
it 'returns deduced singular namespace' do
|
|
31
|
-
p =
|
|
33
|
+
p = described_class.new(:input, ParamsSpec::MyResource::Index)
|
|
32
34
|
expect(p.namespace).to eq(:my_resource)
|
|
33
35
|
end
|
|
34
36
|
|
|
35
37
|
it 'returns deduced plural namespace' do
|
|
36
|
-
p =
|
|
38
|
+
p = described_class.new(:input, ParamsSpec::MyResource::Index)
|
|
37
39
|
p.layout = :object_list
|
|
38
40
|
expect(p.namespace).to eq(:my_resources)
|
|
39
41
|
end
|
|
40
42
|
|
|
41
43
|
it 'returns set namespace' do
|
|
42
|
-
p =
|
|
44
|
+
p = described_class.new(:input, ParamsSpec::MyResource::Index)
|
|
43
45
|
p.namespace = :custom_ns
|
|
44
46
|
expect(p.namespace).to eq(:custom_ns)
|
|
45
47
|
end
|
|
46
48
|
|
|
47
49
|
it 'uses params from parent resource' do
|
|
48
|
-
p =
|
|
50
|
+
p = described_class.new(:input, ParamsSpec::MyResource::Index)
|
|
49
51
|
p.add_block proc { use :all }
|
|
50
52
|
p.exec
|
|
51
|
-
expect(p.params.map(&:name)).to
|
|
53
|
+
expect(p.params.map(&:name)).to match_array(%i[res_param1 res_param2])
|
|
52
54
|
end
|
|
53
55
|
|
|
54
56
|
it 'has param requires' do
|
|
55
|
-
p =
|
|
57
|
+
p = described_class.new(:input, ParamsSpec::MyResource::Index)
|
|
56
58
|
p.add_block proc { requires :p_required }
|
|
57
59
|
p.exec
|
|
58
60
|
expect(p.params.first.required?).to be true
|
|
59
61
|
end
|
|
60
62
|
|
|
61
63
|
it 'has param optional' do
|
|
62
|
-
p =
|
|
64
|
+
p = described_class.new(:input, ParamsSpec::MyResource::Index)
|
|
63
65
|
p.add_block proc { optional :p_optional }
|
|
64
66
|
p.exec
|
|
65
67
|
expect(p.params.first.required?).to be false
|
|
66
68
|
end
|
|
67
69
|
|
|
68
70
|
it 'has param string' do
|
|
69
|
-
p =
|
|
71
|
+
p = described_class.new(:input, ParamsSpec::MyResource::Index)
|
|
70
72
|
p.add_block proc { string :p_string }
|
|
71
73
|
p.exec
|
|
72
74
|
expect(p.params.first.type).to eq(String)
|
|
73
75
|
end
|
|
74
76
|
|
|
75
77
|
it 'has param text' do
|
|
76
|
-
p =
|
|
78
|
+
p = described_class.new(:input, ParamsSpec::MyResource::Index)
|
|
77
79
|
p.add_block proc { text :p_text }
|
|
78
80
|
p.exec
|
|
79
81
|
expect(p.params.first.type).to eq(Text)
|
|
@@ -81,7 +83,7 @@ describe HaveAPI::Params do
|
|
|
81
83
|
|
|
82
84
|
%i[id integer foreign_key].each do |type|
|
|
83
85
|
it "has param #{type}" do
|
|
84
|
-
p =
|
|
86
|
+
p = described_class.new(:input, ParamsSpec::MyResource::Index)
|
|
85
87
|
p.add_block proc { send(type, :"p_#{type}") }
|
|
86
88
|
p.exec
|
|
87
89
|
expect(p.params.first.type).to eq(Integer)
|
|
@@ -89,42 +91,42 @@ describe HaveAPI::Params do
|
|
|
89
91
|
end
|
|
90
92
|
|
|
91
93
|
it 'has param float' do
|
|
92
|
-
p =
|
|
94
|
+
p = described_class.new(:input, ParamsSpec::MyResource::Index)
|
|
93
95
|
p.add_block proc { float :p_float }
|
|
94
96
|
p.exec
|
|
95
97
|
expect(p.params.first.type).to eq(Float)
|
|
96
98
|
end
|
|
97
99
|
|
|
98
100
|
it 'has param bool' do
|
|
99
|
-
p =
|
|
101
|
+
p = described_class.new(:input, ParamsSpec::MyResource::Index)
|
|
100
102
|
p.add_block proc { bool :p_bool }
|
|
101
103
|
p.exec
|
|
102
104
|
expect(p.params.first.type).to eq(Boolean)
|
|
103
105
|
end
|
|
104
106
|
|
|
105
107
|
it 'has param datetime' do
|
|
106
|
-
p =
|
|
108
|
+
p = described_class.new(:input, ParamsSpec::MyResource::Index)
|
|
107
109
|
p.add_block proc { datetime :p_datetime }
|
|
108
110
|
p.exec
|
|
109
111
|
expect(p.params.first.type).to eq(Datetime)
|
|
110
112
|
end
|
|
111
113
|
|
|
112
114
|
it 'has param param' do
|
|
113
|
-
p =
|
|
115
|
+
p = described_class.new(:input, ParamsSpec::MyResource::Index)
|
|
114
116
|
p.add_block proc { param :p_param, type: Integer }
|
|
115
117
|
p.exec
|
|
116
118
|
expect(p.params.first.type).to eq(Integer)
|
|
117
119
|
end
|
|
118
120
|
|
|
119
121
|
it 'has param resource' do
|
|
120
|
-
p =
|
|
121
|
-
p.add_block proc { resource MyResource }
|
|
122
|
+
p = described_class.new(:input, ParamsSpec::MyResource::Index)
|
|
123
|
+
p.add_block proc { resource ParamsSpec::MyResource }
|
|
122
124
|
p.exec
|
|
123
125
|
expect(p.params.first).to be_an_instance_of(HaveAPI::Parameters::Resource)
|
|
124
126
|
end
|
|
125
127
|
|
|
126
128
|
it 'patches params' do
|
|
127
|
-
p =
|
|
129
|
+
p = described_class.new(:input, ParamsSpec::MyResource::Index)
|
|
128
130
|
p.add_block(proc do
|
|
129
131
|
string :param1, label: 'Param 1'
|
|
130
132
|
string :param2, label: 'Param 2', desc: 'Implicit description'
|
|
@@ -137,15 +139,15 @@ describe HaveAPI::Params do
|
|
|
137
139
|
end
|
|
138
140
|
|
|
139
141
|
it 'validates upon build' do
|
|
140
|
-
p =
|
|
141
|
-
p.add_block proc { resource MyResource }
|
|
142
|
+
p = described_class.new(:output, ParamsSpec::MyResource::Index)
|
|
143
|
+
p.add_block proc { resource ParamsSpec::MyResource }
|
|
142
144
|
p.exec
|
|
143
145
|
expect(p.params.first).to be_an_instance_of(HaveAPI::Parameters::Resource)
|
|
144
146
|
expect { p.validate_build }.to raise_error(RuntimeError)
|
|
145
147
|
end
|
|
146
148
|
|
|
147
149
|
it 'accepts valid input layout' do
|
|
148
|
-
p =
|
|
150
|
+
p = described_class.new(:input, ParamsSpec::MyResource::Index)
|
|
149
151
|
p.add_block(proc do
|
|
150
152
|
string :param1, required: true
|
|
151
153
|
string :param2
|
|
@@ -160,7 +162,7 @@ describe HaveAPI::Params do
|
|
|
160
162
|
end
|
|
161
163
|
|
|
162
164
|
it 'rejects invalid input layout' do
|
|
163
|
-
p =
|
|
165
|
+
p = described_class.new(:input, ParamsSpec::MyResource::Index)
|
|
164
166
|
p.add_block(proc do
|
|
165
167
|
string :param1, required: true
|
|
166
168
|
string :param2
|
|
@@ -175,7 +177,7 @@ describe HaveAPI::Params do
|
|
|
175
177
|
end
|
|
176
178
|
|
|
177
179
|
it 'indexes parameters by name' do
|
|
178
|
-
p =
|
|
180
|
+
p = described_class.new(:input, ParamsSpec::MyResource::Index)
|
|
179
181
|
p.add_block(proc do
|
|
180
182
|
string :param1
|
|
181
183
|
string :param2
|