haveapi 0.28.1 → 0.28.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.
@@ -47,10 +47,24 @@ module ARAdapterSpec
47
47
  class StringAccount < ActiveRecord::Base
48
48
  self.primary_key = 'uuid'
49
49
  end
50
+
51
+ class Dataset < ActiveRecord::Base
52
+ has_many :snapshots, class_name: 'ARAdapterSpec::Snapshot'
53
+ end
54
+
55
+ class Snapshot < ActiveRecord::Base
56
+ belongs_to :dataset, class_name: 'ARAdapterSpec::Dataset'
57
+ end
58
+
59
+ class SnapshotLink < ActiveRecord::Base
60
+ belongs_to :snapshot, class_name: 'ARAdapterSpec::Snapshot'
61
+ end
50
62
  end
51
63
 
52
64
  describe HaveAPI::ModelAdapters::ActiveRecord do
53
65
  api do
66
+ snapshot_resource = nil
67
+
54
68
  env_resource = define_resource(:Environment) do
55
69
  version 1
56
70
  auth false
@@ -79,7 +93,7 @@ describe HaveAPI::ModelAdapters::ActiveRecord do
79
93
  end
80
94
 
81
95
  def exec
82
- self.class.model.find(params['environment_id'])
96
+ self.class.model.find(path_params['environment_id'])
83
97
  end
84
98
  end
85
99
  end
@@ -113,12 +127,12 @@ describe HaveAPI::ModelAdapters::ActiveRecord do
113
127
  end
114
128
 
115
129
  def prepare
116
- group = self.class.model.find(params[:group_id])
130
+ group = self.class.model.find(path_params['group_id'])
117
131
  error!('access denied') if group.note == 'PRIVATE_GROUP_NOTE'
118
132
  end
119
133
 
120
134
  def exec
121
- self.class.model.find(params['group_id'])
135
+ self.class.model.find(path_params['group_id'])
122
136
  end
123
137
  end
124
138
  end
@@ -158,7 +172,7 @@ describe HaveAPI::ModelAdapters::ActiveRecord do
158
172
  end
159
173
 
160
174
  def exec
161
- self.class.model.find(params['filtered_group_id'])
175
+ self.class.model.find(path_params['filtered_group_id'])
162
176
  end
163
177
  end
164
178
  end
@@ -178,7 +192,7 @@ describe HaveAPI::ModelAdapters::ActiveRecord do
178
192
  end
179
193
 
180
194
  def exec
181
- with_includes(self.class.model.where(id: params['filtered_member_id'])).take!
195
+ with_includes(self.class.model.where(id: path_params['filtered_member_id'])).take!
182
196
  end
183
197
  end
184
198
  end
@@ -225,7 +239,7 @@ describe HaveAPI::ModelAdapters::ActiveRecord do
225
239
  end
226
240
 
227
241
  def exec
228
- id = params['user_id'].to_i
242
+ id = path_params['user_id'].to_i
229
243
  with_includes(self.class.model.where(id: id)).take!
230
244
  end
231
245
  end
@@ -244,7 +258,7 @@ describe HaveAPI::ModelAdapters::ActiveRecord do
244
258
  end
245
259
 
246
260
  def exec
247
- self.class.model.find(params['user_id'])
261
+ self.class.model.find(path_params['user_id'])
248
262
  end
249
263
  end
250
264
 
@@ -312,7 +326,7 @@ describe HaveAPI::ModelAdapters::ActiveRecord do
312
326
  end
313
327
 
314
328
  def exec
315
- self.class.model.find(params['hidden_account_id'])
329
+ self.class.model.find(path_params['hidden_account_id'])
316
330
  end
317
331
  end
318
332
  end
@@ -332,7 +346,7 @@ describe HaveAPI::ModelAdapters::ActiveRecord do
332
346
  end
333
347
 
334
348
  def exec
335
- self.class.model.find(params['invoice_id'])
349
+ self.class.model.find(path_params['invoice_id'])
336
350
  end
337
351
  end
338
352
 
@@ -352,6 +366,95 @@ describe HaveAPI::ModelAdapters::ActiveRecord do
352
366
  end
353
367
  end
354
368
  end
369
+
370
+ define_resource(:Dataset) do
371
+ version 1
372
+ auth false
373
+ route 'datasets/{dataset_id}'
374
+ model ARAdapterSpec::Dataset
375
+
376
+ snapshot_resource = define_resource(:Snapshot) do
377
+ auth false
378
+ model ARAdapterSpec::Snapshot
379
+
380
+ define_action(:Index, superclass: HaveAPI::Actions::Default::Index) do
381
+ authorize { allow }
382
+
383
+ output(:object_list) do
384
+ integer :id
385
+ string :label
386
+ end
387
+
388
+ def exec
389
+ self.class.model.where(dataset_id: path_params['dataset_id']).order(id: :asc).to_a
390
+ end
391
+ end
392
+
393
+ define_action(:Show, superclass: HaveAPI::Actions::Default::Show) do
394
+ resolve ->(snapshot) { [snapshot.dataset_id, snapshot.id] }
395
+ authorize do |_u, params|
396
+ snapshot = ARAdapterSpec::Snapshot.find_by(id: params['snapshot_id'])
397
+
398
+ if snapshot && snapshot.dataset_id == params['dataset_id'].to_i
399
+ allow
400
+ else
401
+ deny
402
+ end
403
+ end
404
+
405
+ output(:object) do
406
+ integer :id
407
+ string :label
408
+ end
409
+
410
+ def prepare
411
+ snapshot = self.class.model.find(path_params['snapshot_id'])
412
+ error!('wrong dataset') if snapshot.dataset_id != path_params['dataset_id'].to_i
413
+ end
414
+
415
+ def exec
416
+ self.class.model.find(path_params['snapshot_id'])
417
+ end
418
+ end
419
+ end
420
+ end
421
+
422
+ define_resource(:SnapshotLink) do
423
+ version 1
424
+ auth false
425
+ model ARAdapterSpec::SnapshotLink
426
+
427
+ define_action(:Show, superclass: HaveAPI::Actions::Default::Show) do
428
+ authorize { allow }
429
+
430
+ output(:object) do
431
+ integer :id
432
+ string :label
433
+ resource snapshot_resource
434
+ end
435
+
436
+ def exec
437
+ id = path_params['snapshot_link_id']
438
+ with_includes(self.class.model.where(id:)).take!
439
+ end
440
+ end
441
+
442
+ define_action(:Update, superclass: HaveAPI::Actions::Default::Update) do
443
+ authorize { allow }
444
+
445
+ input(:hash) do
446
+ resource snapshot_resource
447
+ end
448
+
449
+ output(:hash) do
450
+ bool :assigned
451
+ end
452
+
453
+ def exec
454
+ { assigned: input[:snapshot].is_a?(ARAdapterSpec::Snapshot) }
455
+ end
456
+ end
457
+ end
355
458
  end
356
459
 
357
460
  default_version 1
@@ -396,6 +499,20 @@ describe HaveAPI::ModelAdapters::ActiveRecord do
396
499
  t.string :uuid, primary_key: true
397
500
  t.string :label, null: false
398
501
  end
502
+
503
+ create_table :datasets do |t|
504
+ t.string :label, null: false
505
+ end
506
+
507
+ create_table :snapshots do |t|
508
+ t.integer :dataset_id, null: false
509
+ t.string :label, null: false
510
+ end
511
+
512
+ create_table :snapshot_links do |t|
513
+ t.integer :snapshot_id, null: false
514
+ t.string :label, null: false
515
+ end
399
516
  end
400
517
  end
401
518
 
@@ -406,6 +523,9 @@ describe HaveAPI::ModelAdapters::ActiveRecord do
406
523
  ARAdapterSpec::Invoice.delete_all
407
524
  ARAdapterSpec::HiddenAccount.delete_all
408
525
  ARAdapterSpec::StringAccount.delete_all
526
+ ARAdapterSpec::SnapshotLink.delete_all
527
+ ARAdapterSpec::Snapshot.delete_all
528
+ ARAdapterSpec::Dataset.delete_all
409
529
  end
410
530
 
411
531
  let(:dummy_action) do
@@ -435,6 +555,14 @@ describe HaveAPI::ModelAdapters::ActiveRecord do
435
555
  ARAdapterSpec::User.create!(defaults.merge(attrs))
436
556
  end
437
557
 
558
+ def create_snapshot_link
559
+ dataset = ARAdapterSpec::Dataset.create!(label: 'dataset')
560
+ snapshot = ARAdapterSpec::Snapshot.create!(dataset:, label: 'snapshot')
561
+ link = ARAdapterSpec::SnapshotLink.create!(snapshot:, label: 'link')
562
+
563
+ [dataset, snapshot, link]
564
+ end
565
+
438
566
  def action_class(resource, action)
439
567
  klass, = find_action(1, resource, action)
440
568
  klass
@@ -533,7 +661,7 @@ describe HaveAPI::ModelAdapters::ActiveRecord do
533
661
  expect(group_data[:environment]).not_to have_key(:note)
534
662
  end
535
663
 
536
- it 'passes symbol path params to associated show prepare when included' do
664
+ it 'passes path params to associated show prepare when included' do
537
665
  group = ARAdapterSpec::Group.create!(label: 'grp', note: 'GROUP_NOTE')
538
666
  user = create_user(name: 'user', group: group)
539
667
 
@@ -580,6 +708,33 @@ describe HaveAPI::ModelAdapters::ActiveRecord do
580
708
  expect(env_data[:note]).to eq('ENV_NOTE')
581
709
  end
582
710
 
711
+ it 'uses full nested paths for associated show prepare' do
712
+ _dataset, snapshot, link = create_snapshot_link
713
+
714
+ get "/v1/snapshot_links/#{link.id}", { _meta: { includes: 'snapshot' } }, input: ''
715
+
716
+ expect(last_response.status).to eq(200)
717
+ expect(api_response).to be_ok
718
+
719
+ snapshot_data = api_response[:snapshot_link][:snapshot]
720
+ expect(snapshot_data[:_meta][:resolved]).to be(true)
721
+ expect(snapshot_data).to include(id: snapshot.id, label: 'snapshot')
722
+ end
723
+
724
+ it 'uses full nested paths when authorizing resource input records' do
725
+ _dataset, snapshot, link = create_snapshot_link
726
+
727
+ put "/v1/snapshot_links/#{link.id}", {
728
+ snapshot_link: {
729
+ snapshot: snapshot.id
730
+ }
731
+ }.to_json, 'CONTENT_TYPE' => 'application/json'
732
+
733
+ expect(last_response.status).to eq(200)
734
+ expect(api_response).to be_ok
735
+ expect(api_response[:snapshot_link]).to eq(assigned: true)
736
+ end
737
+
583
738
  it 'drops invalid nested include paths from requests' do
584
739
  group = ARAdapterSpec::Group.create!(label: 'grp', note: 'GRP_NOTE')
585
740
  user = create_user(name: 'user', group: group)
@@ -698,7 +853,7 @@ describe HaveAPI::ModelAdapters::ActiveRecord do
698
853
  }
699
854
  }.to_json, 'CONTENT_TYPE' => 'application/json'
700
855
 
701
- expect(last_response.status).to eq(400)
856
+ expect(last_response.status).to eq(200)
702
857
  expect(api_response).not_to be_ok
703
858
  expect(api_response.errors[:hidden_account]).to include('resource not found')
704
859
  end
@@ -799,7 +954,7 @@ describe HaveAPI::ModelAdapters::ActiveRecord do
799
954
  it 'rejects excessive pagination limits' do
800
955
  get '/v1/users', { user: { limit: HaveAPI::Actions::Paginable::MAX_LIMIT + 1 } }, input: ''
801
956
 
802
- expect(last_response.status).to eq(400)
957
+ expect(last_response.status).to eq(200)
803
958
  expect(api_response).not_to be_ok
804
959
  expect(api_response.errors[:limit].first).to include(
805
960
  "range <0, #{HaveAPI::Actions::Paginable::MAX_LIMIT}>"
@@ -193,6 +193,60 @@ describe 'Parameters::Typed' do
193
193
  expect(p.clean('value')).to be_nil
194
194
  end
195
195
 
196
+ it 'deep stringifies custom parameter keys by default' do
197
+ p = p_arg(type: Custom)
198
+ raw = {
199
+ rawId: 'credential-id',
200
+ response: {
201
+ clientDataJSON: 'client-data'
202
+ },
203
+ transports: [
204
+ { type: :usb }
205
+ ]
206
+ }
207
+
208
+ expect(p.clean(raw)).to eq({
209
+ 'rawId' => 'credential-id',
210
+ 'response' => {
211
+ 'clientDataJSON' => 'client-data'
212
+ },
213
+ 'transports' => [
214
+ { 'type' => :usb }
215
+ ]
216
+ })
217
+ expect(raw).to have_key(:rawId)
218
+ end
219
+
220
+ it 'deep symbolizes custom parameter keys when requested' do
221
+ p = p_arg(type: Custom, symbolize_keys: true)
222
+
223
+ expect(p.clean({
224
+ 'rawId' => 'credential-id',
225
+ 'response' => {
226
+ 'clientDataJSON' => 'client-data'
227
+ },
228
+ 'transports' => [
229
+ { 'type' => 'usb' }
230
+ ]
231
+ })).to eq({
232
+ rawId: 'credential-id',
233
+ response: {
234
+ clientDataJSON: 'client-data'
235
+ },
236
+ transports: [
237
+ { type: 'usb' }
238
+ ]
239
+ })
240
+ end
241
+
242
+ it 'passes normalized custom keys to custom cleaners' do
243
+ p = p_arg(type: Custom, clean: proc { |v| v.fetch('rawId') })
244
+ expect(p.clean({ rawId: 'credential-id' })).to eq('credential-id')
245
+
246
+ p = p_arg(type: Custom, symbolize_keys: true, clean: proc { |v| v.fetch(:rawId) })
247
+ expect(p.clean({ 'rawId' => 'credential-id' })).to eq('credential-id')
248
+ end
249
+
196
250
  it 'rejects invalid string encoding during coercion' do
197
251
  invalid = "\xff".b.force_encoding(Encoding::UTF_8)
198
252
 
@@ -106,7 +106,7 @@ describe HaveAPI::Server do
106
106
  'CONTENT_TYPE' => 'application/x-www-form-urlencoded'
107
107
  }
108
108
 
109
- expect(last_response.status).to eq(400)
109
+ expect(last_response.status).to eq(200)
110
110
  expect(api_response).not_to be_ok
111
111
  expect(ServerIntegrationSpec::State.writes).to be_empty
112
112
  end
@@ -292,7 +292,7 @@ module HaveAPI
292
292
  authorize { allow }
293
293
 
294
294
  def exec
295
- project = HaveAPI::ClientTestAPI::Store.find_project(params[:project_id])
295
+ project = HaveAPI::ClientTestAPI::Store.find_project(path_params['project_id'])
296
296
  error!('project not found', {}, http_status: 404) unless project
297
297
  project
298
298
  end
@@ -333,11 +333,11 @@ module HaveAPI
333
333
  authorize { allow }
334
334
 
335
335
  def exec
336
- HaveAPI::ClientTestAPI::Store.list_tasks(params[:project_id])
336
+ HaveAPI::ClientTestAPI::Store.list_tasks(path_params['project_id'])
337
337
  end
338
338
 
339
339
  def count
340
- HaveAPI::ClientTestAPI::Store.list_tasks(params[:project_id]).size
340
+ HaveAPI::ClientTestAPI::Store.list_tasks(path_params['project_id']).size
341
341
  end
342
342
  end
343
343
 
@@ -349,7 +349,7 @@ module HaveAPI
349
349
  authorize { allow }
350
350
 
351
351
  def exec
352
- task = HaveAPI::ClientTestAPI::Store.find_task(params[:project_id], params[:task_id])
352
+ task = HaveAPI::ClientTestAPI::Store.find_task(path_params['project_id'], path_params['task_id'])
353
353
  error!('task not found', {}, http_status: 404) unless task
354
354
  task
355
355
  end
@@ -368,7 +368,7 @@ module HaveAPI
368
368
 
369
369
  def exec
370
370
  HaveAPI::ClientTestAPI::Store.create_task(
371
- params[:project_id],
371
+ path_params['project_id'],
372
372
  input[:label],
373
373
  input[:done]
374
374
  )
@@ -387,8 +387,8 @@ module HaveAPI
387
387
 
388
388
  def exec
389
389
  task = HaveAPI::ClientTestAPI::Store.update_task(
390
- params[:project_id],
391
- params[:task_id],
390
+ path_params['project_id'],
391
+ path_params['task_id'],
392
392
  input[:done]
393
393
  )
394
394
  error!('task not found', {}, http_status: 404) unless task
@@ -406,7 +406,7 @@ module HaveAPI
406
406
  authorize { allow }
407
407
 
408
408
  def exec
409
- task = HaveAPI::ClientTestAPI::Store.find_task(params[:project_id], params[:task_id])
409
+ task = HaveAPI::ClientTestAPI::Store.find_task(path_params['project_id'], path_params['task_id'])
410
410
  error!('task not found', {}, http_status: 404) unless task
411
411
 
412
412
  @state_id = HaveAPI::ClientTestAPI::ActionStateBackend.create_state(
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: haveapi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.28.1
4
+ version: 0.28.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jakub Skokan
@@ -29,14 +29,14 @@ dependencies:
29
29
  requirements:
30
30
  - - "~>"
31
31
  - !ruby/object:Gem::Version
32
- version: 0.28.1
32
+ version: 0.28.2
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: 0.28.1
39
+ version: 0.28.2
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: json
42
42
  requirement: !ruby/object:Gem::Requirement
@@ -297,6 +297,7 @@ files:
297
297
  - spec/action/authorize_spec.rb
298
298
  - spec/action/dsl_spec.rb
299
299
  - spec/action/runtime_spec.rb
300
+ - spec/action/validation_http_status_spec.rb
300
301
  - spec/action_state_spec.rb
301
302
  - spec/authentication/basic_spec.rb
302
303
  - spec/authentication/oauth2_spec.rb