gds-api-adapters 63.0.0 → 63.1.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.
@@ -5,12 +5,630 @@ require "json"
5
5
 
6
6
  module GdsApi
7
7
  module TestHelpers
8
+ # @api documented
8
9
  module PublishingApi
9
10
  include ContentItemHelpers
10
11
  include IntentHelpers
11
12
 
13
+ PUBLISHING_API_V2_ENDPOINT = Plek.current.find("publishing-api") + "/v2"
12
14
  PUBLISHING_API_ENDPOINT = Plek.current.find("publishing-api")
13
15
 
16
+ # Stub a PUT /v2/content/:content_id request with the given content id and request body.
17
+ # if no response_hash is given, a default response as follows is created:
18
+ # {status: 200, body: '{}', headers: {"Content-Type" => "application/json; charset=utf-8"}}
19
+ #
20
+ # if a response is given, then it will be merged with the default response.
21
+ # if the given parameter for the response body is a Hash, it will be converted to JSON.
22
+ #
23
+ # The following two examples are equivalent:
24
+ # @example
25
+ # stub_publishing_api_put_content(my_content_id, my_request_body, { status: 201, body: {version: 33}.to_json })
26
+ #
27
+ # @example
28
+ # stub_publishing_api_put_content(my_content_id, my_request_body, { status: 201, body: {version: 33} })
29
+ #
30
+ # @param content_id [UUID]
31
+ # @param body [String]
32
+ # @param response_hash [Hash]
33
+ def stub_publishing_api_put_content(content_id, body, response_hash = {})
34
+ stub_publishing_api_put(content_id, body, "/content", response_hash)
35
+ end
36
+
37
+ # Stub a PATCH /v2/links/:content_id request
38
+ #
39
+ # @example
40
+ # stub_publishing_api_patch_links(
41
+ # my_content_id,
42
+ # "links" => {
43
+ # "taxons" => %w(level_one_topic level_two_topic),
44
+ # },
45
+ # "previous_version" => 3,
46
+ # )
47
+ #
48
+ # @param content_id [UUID]
49
+ # @param body [String]
50
+ def stub_publishing_api_patch_links(content_id, body)
51
+ stub_publishing_api_patch(content_id, body, "/links")
52
+ end
53
+
54
+ # Stub a PATCH /v2/links/:content_id request to return a 409 response
55
+ #
56
+ # @example
57
+ # stub_publishing_api_patch_links_conflict(
58
+ # my_content_id,
59
+ # "links" => {
60
+ # "taxons" => %w(level_one_topic level_two_topic),
61
+ # },
62
+ # "previous_version" => 3,
63
+ # )
64
+ #
65
+ # @param content_id [UUID]
66
+ # @param body [String]
67
+ def stub_publishing_api_patch_links_conflict(content_id, body)
68
+ previous_version = JSON.parse(body.to_json)["previous_version"]
69
+ override_response_hash = { status: 409, body: version_conflict(previous_version) }
70
+ stub_publishing_api_patch(content_id, body, "/links", override_response_hash)
71
+ end
72
+
73
+ # Stub a POST /v2/content/:content_id/publish request
74
+ #
75
+ # @param content_id [UUID]
76
+ # @param body [String]
77
+ # @param response_hash [Hash]
78
+ def stub_publishing_api_publish(content_id, body, response_hash = {})
79
+ url = PUBLISHING_API_V2_ENDPOINT + "/content/#{content_id}/publish"
80
+ response = {
81
+ status: 200,
82
+ body: "{}",
83
+ headers: { "Content-Type" => "application/json; charset=utf-8" },
84
+ }.merge(response_hash)
85
+ stub_request(:post, url).with(body: body).to_return(response)
86
+ end
87
+
88
+ # Stub a POST /v2/content/:content_id/republish request
89
+ #
90
+ # @param content_id [UUID]
91
+ # @param body [String]
92
+ # @param response_hash [Hash]
93
+ def stub_publishing_api_republish(content_id, body = {}, response_hash = {})
94
+ url = PUBLISHING_API_V2_ENDPOINT + "/content/#{content_id}/republish"
95
+ response = {
96
+ status: 200,
97
+ body: "{}",
98
+ headers: { "Content-Type" => "application/json; charset=utf-8" },
99
+ }.merge(response_hash)
100
+ stub_request(:post, url).with(body: body).to_return(response)
101
+ end
102
+
103
+ # Stub a POST /v2/content/:content_id/unpublish request
104
+ #
105
+ # @param content_id [UUID]
106
+ # @param params [Hash]
107
+ # @param body [String]
108
+ def stub_publishing_api_unpublish(content_id, params, response_hash = {})
109
+ url = PUBLISHING_API_V2_ENDPOINT + "/content/#{content_id}/unpublish"
110
+ response = {
111
+ status: 200,
112
+ body: "{}",
113
+ headers: { "Content-Type" => "application/json; charset=utf-8" },
114
+ }.merge(response_hash)
115
+ stub_request(:post, url).with(params).to_return(response)
116
+ end
117
+
118
+ # Stub a POST /v2/content/:content_id/discard-draft request
119
+ #
120
+ # @param content_id [UUID]
121
+ def stub_publishing_api_discard_draft(content_id)
122
+ url = PUBLISHING_API_V2_ENDPOINT + "/content/#{content_id}/discard-draft"
123
+ stub_request(:post, url).to_return(status: 200, headers: { "Content-Type" => "application/json; charset=utf-8" })
124
+ end
125
+
126
+ # Stub requests issued when publishing a new draft.
127
+ # - PUT /v2/content/:content_id
128
+ # - POST /v2/content/:content_id/publish
129
+ # - PATCH /v2/links/:content_id
130
+ #
131
+ # @param body [String]
132
+ # @param content_id [UUID]
133
+ # @param publish_body [Hash]
134
+ def stub_publishing_api_put_content_links_and_publish(body, content_id = nil, publish_body = nil)
135
+ content_id ||= body[:content_id]
136
+ if publish_body.nil?
137
+ publish_body = { update_type: body.fetch(:update_type) }
138
+ publish_body[:locale] = body[:locale] if body[:locale]
139
+ end
140
+ stubs = []
141
+ stubs << stub_publishing_api_put_content(content_id, body.except(:links))
142
+ stubs << stub_publishing_api_patch_links(content_id, body.slice(:links)) unless body.slice(:links).empty?
143
+ stubs << stub_publishing_api_publish(content_id, publish_body)
144
+ stubs
145
+ end
146
+
147
+ # Stub any PUT /v2/content/* request
148
+ def stub_any_publishing_api_put_content
149
+ stub_request(:put, %r{\A#{PUBLISHING_API_V2_ENDPOINT}/content/})
150
+ end
151
+
152
+ # Stub any PATCH /v2/links/* request
153
+ def stub_any_publishing_api_patch_links
154
+ stub_request(:patch, %r{\A#{PUBLISHING_API_V2_ENDPOINT}/links/})
155
+ end
156
+
157
+ # Stub any POST /v2/content/*/publish request
158
+ def stub_any_publishing_api_publish
159
+ stub_request(:post, %r{\A#{PUBLISHING_API_V2_ENDPOINT}/content/.*/publish})
160
+ end
161
+
162
+ # Stub any POST /v2/content/*/publish request
163
+ def stub_any_publishing_api_republish
164
+ stub_request(:post, %r{\A#{PUBLISHING_API_V2_ENDPOINT}/content/.*/republish})
165
+ end
166
+
167
+ # Stub any POST /v2/content/*/unpublish request
168
+ def stub_any_publishing_api_unpublish
169
+ stub_request(:post, %r{\A#{PUBLISHING_API_V2_ENDPOINT}/content/.*/unpublish})
170
+ end
171
+
172
+ # Stub any POST /v2/content/*/discard-draft request
173
+ def stub_any_publishing_api_discard_draft
174
+ stub_request(:post, %r{\A#{PUBLISHING_API_V2_ENDPOINT}/content/.*/discard-draft})
175
+ end
176
+
177
+ # Stub any version 2 request to the publishing API
178
+ def stub_any_publishing_api_call
179
+ stub_request(:any, %r{\A#{PUBLISHING_API_V2_ENDPOINT}})
180
+ end
181
+
182
+ # Stub any version 2 request to the publishing API to return a 404 response
183
+ def stub_any_publishing_api_call_to_return_not_found
184
+ stub_request(:any, %r{\A#{PUBLISHING_API_V2_ENDPOINT}})
185
+ .to_return(status: 404, headers: { "Content-Type" => "application/json; charset=utf-8" })
186
+ end
187
+
188
+ # Stub any version 2 request to the publishing API to return a 503 response
189
+ def stub_publishing_api_isnt_available
190
+ stub_request(:any, /#{PUBLISHING_API_V2_ENDPOINT}\/.*/).to_return(status: 503)
191
+ stub_request(:any, /#{PUBLISHING_API_ENDPOINT}\/.*/).to_return(status: 503)
192
+ end
193
+
194
+ # Assert that a draft was saved and published, and links were updated.
195
+ # - PUT /v2/content/:content_id
196
+ # - POST /v2/content/:content_id/publish
197
+ # - PATCH /v2/links/:content_id
198
+ #
199
+ # @param body [String]
200
+ # @param content_id [UUID]
201
+ # @param publish_body [Hash]
202
+ def assert_publishing_api_put_content_links_and_publish(body, content_id = nil, publish_body = nil)
203
+ content_id ||= body[:content_id]
204
+ if publish_body.nil?
205
+ publish_body = { update_type: body.fetch(:update_type) }
206
+ publish_body[:locale] = body[:locale] if body[:locale]
207
+ end
208
+ assert_publishing_api_put_content(content_id, body.except(:links))
209
+ assert_publishing_api_patch_links(content_id, body.slice(:links)) unless body.slice(:links).empty?
210
+ assert_publishing_api_publish(content_id, publish_body)
211
+ end
212
+
213
+ # Assert that content was saved (PUT /v2/content/:content_id)
214
+ #
215
+ # @param content_id [UUID]
216
+ # @param attributes_or_matcher [Object]
217
+ # @param times [Integer]
218
+ def assert_publishing_api_put_content(content_id, attributes_or_matcher = nil, times = 1)
219
+ url = PUBLISHING_API_V2_ENDPOINT + "/content/" + content_id
220
+ assert_publishing_api(:put, url, attributes_or_matcher, times)
221
+ end
222
+
223
+ # Assert that content was published (POST /v2/content/:content_id/publish)
224
+ #
225
+ # @param content_id [UUID]
226
+ # @param attributes_or_matcher [Object]
227
+ # @param times [Integer]
228
+ def assert_publishing_api_publish(content_id, attributes_or_matcher = nil, times = 1)
229
+ url = PUBLISHING_API_V2_ENDPOINT + "/content/#{content_id}/publish"
230
+ assert_publishing_api(:post, url, attributes_or_matcher, times)
231
+ end
232
+
233
+ # Assert that content was unpublished (POST /v2/content/:content_id/unpublish)
234
+ #
235
+ # @param content_id [UUID]
236
+ # @param attributes_or_matcher [Object]
237
+ # @param times [Integer]
238
+ def assert_publishing_api_unpublish(content_id, attributes_or_matcher = nil, times = 1)
239
+ url = PUBLISHING_API_V2_ENDPOINT + "/content/#{content_id}/unpublish"
240
+ assert_publishing_api(:post, url, attributes_or_matcher, times)
241
+ end
242
+
243
+ # Assert that links were updated (PATCH /v2/links/:content_id)
244
+ #
245
+ # @param content_id [UUID]
246
+ # @param attributes_or_matcher [Object]
247
+ # @param times [Integer]
248
+ def assert_publishing_api_patch_links(content_id, attributes_or_matcher = nil, times = 1)
249
+ url = PUBLISHING_API_V2_ENDPOINT + "/links/" + content_id
250
+ assert_publishing_api(:patch, url, attributes_or_matcher, times)
251
+ end
252
+
253
+ # Assert that a draft was discarded (POST /v2/content/:content_id/discard-draft)
254
+ #
255
+ # @param content_id [UUID]
256
+ # @param attributes_or_matcher [Object]
257
+ # @param times [Integer]
258
+ def assert_publishing_api_discard_draft(content_id, attributes_or_matcher = nil, times = 1)
259
+ url = PUBLISHING_API_V2_ENDPOINT + "/content/#{content_id}/discard-draft"
260
+ assert_publishing_api(:post, url, attributes_or_matcher, times)
261
+ end
262
+
263
+ # Assert that a request was made to the publishing API
264
+ #
265
+ # @param verb [String]
266
+ # @param url [String]
267
+ # @param attributes_or_matcher [Object]
268
+ # @param times [Integer]
269
+ def assert_publishing_api(verb, url, attributes_or_matcher = nil, times = 1)
270
+ if attributes_or_matcher.is_a?(Hash)
271
+ matcher = request_json_matches(attributes_or_matcher)
272
+ else
273
+ matcher = attributes_or_matcher
274
+ end
275
+
276
+ if matcher
277
+ assert_requested(verb, url, times: times, &matcher)
278
+ else
279
+ assert_requested(verb, url, times: times)
280
+ end
281
+ end
282
+
283
+ # Get a request matcher that checks if a JSON request includes a set of attributes
284
+ def request_json_includes(required_attributes)
285
+ ->(request) do
286
+ data = JSON.parse(request.body)
287
+ deep_stringify_keys(required_attributes).
288
+ to_a.all? { |key, value| data[key] == value }
289
+ end
290
+ end
291
+
292
+ # Get a request matcher that checks if a JSON request matches a hash
293
+ def request_json_matches(required_attributes)
294
+ ->(request) do
295
+ data = JSON.parse(request.body)
296
+ deep_stringify_keys(required_attributes) == data
297
+ end
298
+ end
299
+
300
+ # Stub GET /v2/content/ to return a set of content items
301
+ #
302
+ # @example
303
+ #
304
+ # stub_publishing_api_has_content(
305
+ # vehicle_recalls_and_faults, # this is a variable containing an array of content items
306
+ # document_type: described_class.publishing_api_document_type, #example of a document_type: "vehicle_recalls_and_faults_alert"
307
+ # fields: fields, #example: let(:fields) { %i[base_path content_id public_updated_at title publication_state] }
308
+ # page: 1,
309
+ # per_page: 50
310
+ # )
311
+ # @param items [Array]
312
+ # @param params [Hash]
313
+ def stub_publishing_api_has_content(items, params = {})
314
+ url = PUBLISHING_API_V2_ENDPOINT + "/content"
315
+
316
+ if params.respond_to? :fetch
317
+ per_page = params.fetch(:per_page, 50)
318
+ page = params.fetch(:page, 1)
319
+ else
320
+ per_page = 50
321
+ page = 1
322
+ end
323
+
324
+ start_position = (page - 1) * per_page
325
+ page_items = items.slice(start_position, per_page) || []
326
+
327
+ number_of_pages =
328
+ if items.count < per_page
329
+ 1
330
+ else
331
+ (items.count / per_page.to_f).ceil
332
+ end
333
+
334
+ body = {
335
+ results: page_items,
336
+ total: items.count,
337
+ pages: number_of_pages,
338
+ current_page: page,
339
+ }
340
+
341
+ stub_request(:get, url)
342
+ .with(query: params)
343
+ .to_return(status: 200, body: body.to_json, headers: {})
344
+ end
345
+
346
+ # This method has been refactored into publishing_api_has_content (above)
347
+ # publishing_api_has_content allows for flexible passing in of arguments, please use instead
348
+ def stub_publishing_api_has_fields_for_document(document_type, items, fields)
349
+ body = Array(items).map { |item|
350
+ deep_stringify_keys(item).slice(*fields)
351
+ }
352
+
353
+ query_params = fields.map { |f|
354
+ "&fields%5B%5D=#{f}"
355
+ }
356
+
357
+ url = PUBLISHING_API_V2_ENDPOINT + "/content?document_type=#{document_type}#{query_params.join('')}"
358
+
359
+ stub_request(:get, url).to_return(status: 200, body: { results: body }.to_json, headers: {})
360
+ end
361
+
362
+ # Stub GET /v2/linkables to return a set of content items with a specific document type
363
+ #
364
+ # @param linkables [Array]
365
+ def stub_publishing_api_has_linkables(linkables, document_type:)
366
+ url = PUBLISHING_API_V2_ENDPOINT + "/linkables?document_type=#{document_type}"
367
+ stub_request(:get, url).to_return(status: 200, body: linkables.to_json, headers: {})
368
+ end
369
+
370
+ # Stub GET /v2/content/:content_id to return a specific content item hash
371
+ #
372
+ # @param item [Hash]
373
+ def stub_publishing_api_has_item(item, params = {})
374
+ item = deep_transform_keys(item, &:to_sym)
375
+ url = PUBLISHING_API_V2_ENDPOINT + "/content/" + item[:content_id]
376
+ stub_request(:get, url)
377
+ .with(query: hash_including(params))
378
+ .to_return(status: 200, body: item.to_json, headers: {})
379
+ end
380
+
381
+ # Stub GET /v2/content/:content_id to progress through a series of responses.
382
+ #
383
+ # @param items [Array]
384
+ def stub_publishing_api_has_item_in_sequence(content_id, items)
385
+ items = items.each { |item| deep_transform_keys(item, &:to_sym) }
386
+ url = PUBLISHING_API_V2_ENDPOINT + "/content/" + content_id
387
+ calls = -1
388
+
389
+ stub_request(:get, url).to_return do |_request|
390
+ calls += 1
391
+ item = items[calls] || items.last
392
+
393
+ { status: 200, body: item.to_json, headers: {} }
394
+ end
395
+ end
396
+
397
+ # Stub GET /v2/content/:content_id to return a 404 response
398
+ #
399
+ # @param content_id [UUID]
400
+ def stub_publishing_api_does_not_have_item(content_id)
401
+ url = PUBLISHING_API_V2_ENDPOINT + "/content/" + content_id
402
+ stub_request(:get, url).to_return(status: 404, body: resource_not_found(content_id, "content item").to_json, headers: {})
403
+ end
404
+
405
+ # Stub a request to links endpoint
406
+ #
407
+ # @param [Hash] links the structure of the links hash
408
+ #
409
+ # @example
410
+ #
411
+ # stub_publishing_api_has_links(
412
+ # {
413
+ # "content_id" => "64aadc14-9bca-40d9-abb6-4f21f9792a05",
414
+ # "links" => {
415
+ # "mainstream_browse_pages" => ["df2e7a3e-2078-45de-a75a-fd37d027427e"],
416
+ # "parent" => ["df2e7a3e-2078-45de-a75a-fd37d027427e"],
417
+ # "organisations" => ["569a9ee5-c195-4b7f-b9dc-edc17a09113f", "5c54ae52-341b-499e-a6dd-67f04633b8cf"]
418
+ # },
419
+ # "version" => 6
420
+ # }
421
+ # )
422
+ #
423
+ # @example
424
+ #
425
+ # Services.publishing_api.get_links("64aadc14-9bca-40d9-abb6-4f21f9792a05")
426
+ # => {
427
+ # "content_id" => "64aadc14-9bca-40d9-abb6-4f21f9792a05",
428
+ # "links" => {
429
+ # "mainstream_browse_pages" => ["df2e7a3e-2078-45de-a75a-fd37d027427e"],
430
+ # "parent" => ["df2e7a3e-2078-45de-a75a-fd37d027427e"],
431
+ # "organisations" => ["569a9ee5-c195-4b7f-b9dc-edc17a09113f", "5c54ae52-341b-499e-a6dd-67f04633b8cf"]
432
+ # },
433
+ # "version" => 6
434
+ # }
435
+ def stub_publishing_api_has_links(links)
436
+ links = deep_transform_keys(links, &:to_sym)
437
+ url = PUBLISHING_API_V2_ENDPOINT + "/links/" + links[:content_id]
438
+ stub_request(:get, url).to_return(status: 200, body: links.to_json, headers: {})
439
+ end
440
+
441
+ # Stub a request to the expanded links endpoint
442
+ #
443
+ # @param [Hash] links the structure of the links hash
444
+ #
445
+ # @example
446
+ # stub_publishing_api_has_expanded_links(
447
+ # {
448
+ # "content_id" => "64aadc14-9bca-40d9-abb4-4f21f9792a05",
449
+ # "expanded_links" => {
450
+ # "mainstream_browse_pages" => [
451
+ # {
452
+ # "content_id" => "df2e7a3e-2078-45de-a76a-fd37d027427a",
453
+ # "base_path" => "/a/base/path",
454
+ # "document_type" => "mainstream_browse_page",
455
+ # "locale" => "en",
456
+ # "links" => {},
457
+ # # ...
458
+ # }
459
+ # ],
460
+ # "parent" => [
461
+ # {
462
+ # "content_id" => "df2e7a3e-2028-45de-a75a-fd37d027427e",
463
+ # "document_type" => "mainstream_browse_page",
464
+ # # ...
465
+ # },
466
+ # ]
467
+ # }
468
+ # }
469
+ # )
470
+ #
471
+ # @example
472
+ # Services.publishing_api.expanded_links("64aadc14-9bca-40d9-abb4-4f21f9792a05")
473
+ # => {
474
+ # "content_id" => "64aadc14-9bca-40d9-abb4-4f21f9792a05",
475
+ # "expanded_links" => {
476
+ # "mainstream_browse_pages" => [
477
+ # {
478
+ # "content_id" => "df2e7a3e-2078-45de-a76a-fd37d027427a",
479
+ # "base_path" => "/a/base/path",
480
+ # "document_type" => "mainstream_browse_page",
481
+ # "locale" => "en",
482
+ # "links" => {},
483
+ # ...
484
+ # }
485
+ # ],
486
+ # "parent" => [
487
+ # {
488
+ # "content_id" => "df2e7a3e-2028-45de-a75a-fd37d027427e",
489
+ # "document_type" => "mainstream_browse_page",
490
+ # ...
491
+ # },
492
+ # ]
493
+ # }
494
+ # }
495
+ def stub_publishing_api_has_expanded_links(links, with_drafts: true, generate: false)
496
+ links = deep_transform_keys(links, &:to_sym)
497
+ request_params = {}
498
+ request_params["with_drafts"] = false if !with_drafts
499
+ request_params["generate"] = true if generate
500
+
501
+ url = PUBLISHING_API_V2_ENDPOINT + "/expanded-links/" + links[:content_id]
502
+ stub_request(:get, url)
503
+ .with(query: request_params)
504
+ .to_return(status: 200, body: links.to_json, headers: {})
505
+ end
506
+
507
+ # Stub a request to get links for content ids
508
+ #
509
+ # @param [Hash] links the links for each content id
510
+ #
511
+ # @example
512
+ # stub_publishing_api_has_links_for_content_ids(
513
+ # { "2878337b-bed9-4e7f-85b6-10ed2cbcd504" => {
514
+ # "links" => { "taxons" => ["eb6965c7-3056-45d0-ae50-2f0a5e2e0854"] }
515
+ # },
516
+ # "eec13cea-219d-4896-9c97-60114da23559" => {
517
+ # "links" => {}
518
+ # }
519
+ # }
520
+ # )
521
+ #
522
+ # @example
523
+ # Services.publishing_api.get_links_for_content_ids(["2878337b-bed9-4e7f-85b6-10ed2cbcd504"])
524
+ # => {
525
+ # "2878337b-bed9-4e7f-85b6-10ed2cbcd504" => {
526
+ # "links" => [
527
+ # "eb6965c7-3056-45d0-ae50-2f0a5e2e0854"
528
+ # ]
529
+ # }
530
+ # }
531
+ def stub_publishing_api_has_links_for_content_ids(links)
532
+ url = PUBLISHING_API_V2_ENDPOINT + "/links/by-content-id"
533
+ stub_request(:post, url).with(body: { content_ids: links.keys }).to_return(status: 200, body: links.to_json, headers: {})
534
+ end
535
+
536
+ # Stub GET /v2/links/:content_id to return a 404 response
537
+ #
538
+ # @param content_id [UUID]
539
+ def stub_publishing_api_does_not_have_links(content_id)
540
+ url = PUBLISHING_API_V2_ENDPOINT + "/links/" + content_id
541
+ stub_request(:get, url).to_return(status: 404, body: resource_not_found(content_id, "link set").to_json, headers: {})
542
+ end
543
+
544
+ # Stub calls to the lookups endpoint
545
+ #
546
+ # @param lookup_hash [Hash] Hash with base_path as key, content_id as value.
547
+ #
548
+ # @example
549
+ #
550
+ # stub_publishing_api_has_lookups({
551
+ # "/foo" => "51ac4247-fd92-470a-a207-6b852a97f2db",
552
+ # "/bar" => "261bd281-f16c-48d5-82d2-9544019ad9ca"
553
+ # })
554
+ #
555
+ def stub_publishing_api_has_lookups(lookup_hash)
556
+ url = PUBLISHING_API_ENDPOINT + "/lookup-by-base-path"
557
+ stub_request(:post, url).to_return(body: lookup_hash.to_json)
558
+ end
559
+
560
+ #
561
+ # Stub calls to the get linked items endpoint
562
+ #
563
+ # @param items [Array] The linked items we wish to return
564
+ # @param params [Hash] A hash of parameters
565
+ #
566
+ # @example
567
+ #
568
+ # stub_publishing_api_has_linked_items(
569
+ # [ item_1, item_2 ],
570
+ # {
571
+ # content_id: "51ac4247-fd92-470a-a207-6b852a97f2db",
572
+ # link_type: "taxons",
573
+ # fields: ["title", "description", "base_path"]
574
+ # }
575
+ # )
576
+ #
577
+ def stub_publishing_api_has_linked_items(items, params = {})
578
+ content_id = params.fetch(:content_id)
579
+ link_type = params.fetch(:link_type)
580
+ fields = params.fetch(:fields, %w(base_path content_id document_type title))
581
+
582
+ url = PUBLISHING_API_V2_ENDPOINT + "/linked/#{content_id}"
583
+
584
+ request_parmeters = {
585
+ "fields" => fields,
586
+ "link_type" => link_type,
587
+ }
588
+
589
+ stub_request(:get, url)
590
+ .with(query: request_parmeters)
591
+ .and_return(
592
+ body: items.to_json,
593
+ status: 200,
594
+ )
595
+ end
596
+
597
+ # Stub GET /v2/editions to return a set of editions
598
+ #
599
+ # @example
600
+ #
601
+ # stub_publishing_api_get_editions(
602
+ # vehicle_recalls_and_faults, # this is a variable containing an array of editions
603
+ # fields: fields, #example: let(:fields) { %i[base_path content_id public_updated_at title publication_state] }
604
+ # per_page: 50
605
+ # )
606
+ # @param items [Array]
607
+ # @param params [Hash]
608
+ def stub_publishing_api_get_editions(editions, params = {})
609
+ url = PUBLISHING_API_V2_ENDPOINT + "/editions"
610
+
611
+ results = editions.map do |edition|
612
+ next edition unless params[:fields]
613
+
614
+ edition.select { |k| params[:fields].include?(k) }
615
+ end
616
+
617
+ per_page = (params[:per_page] || 100).to_i
618
+ results = results.take(per_page)
619
+
620
+ body = {
621
+ results: results,
622
+ links: [
623
+ { rel: "self", href: "#{PUBLISHING_API_V2_ENDPOINT}/editions" },
624
+ ],
625
+ }
626
+
627
+ stub_request(:get, url)
628
+ .with(query: params)
629
+ .to_return(status: 200, body: body.to_json, headers: {})
630
+ end
631
+
14
632
  def stub_publishing_api_unreserve_path(base_path, publishing_app = /.*/)
15
633
  stub_publishing_api_unreserve_path_with_code(base_path, publishing_app, 200)
16
634
  end
@@ -34,7 +652,7 @@ module GdsApi
34
652
  stub_request(:delete, url).to_return(status: 200, body: "{}", headers: { "Content-Type" => "application/json; charset=utf-8" })
35
653
  end
36
654
 
37
- def stub_default_publishing_api_put_intent
655
+ def stub_any_publishing_api_put_intent
38
656
  stub_request(:put, %r{\A#{PUBLISHING_API_ENDPOINT}/publish-intent})
39
657
  end
40
658
 
@@ -71,48 +689,171 @@ module GdsApi
71
689
  end
72
690
  end
73
691
 
74
- def stub_publishing_api_isnt_available
75
- stub_request(:any, /#{PUBLISHING_API_ENDPOINT}\/.*/).to_return(status: 503)
692
+ # Stub a PUT /paths/:base_path request with the given content id and request body.
693
+ #
694
+ # @example
695
+ # stub_publishing_api_path_reservation(
696
+ # my_content_id,
697
+ # publishing_app: "content-publisher",
698
+ # override_existing: true,
699
+ # )
700
+ #
701
+ # @param base_path [String]
702
+ # @param params [Hash]
703
+ def stub_publishing_api_path_reservation(base_path, params = {})
704
+ url = PUBLISHING_API_ENDPOINT + "/paths#{base_path}"
705
+ response = {
706
+ status: 200,
707
+ headers: { content_type: "application/json" },
708
+ body: params.merge(base_path: base_path).to_json,
709
+ }
710
+
711
+ stub_request(:put, url).with(body: params).to_return(response)
76
712
  end
77
713
 
78
- def stub_default_publishing_api_path_reservation
79
- stub_request(:put, %r[\A#{PUBLISHING_API_ENDPOINT}/paths/]).to_return { |request|
80
- base_path = request.uri.path.sub(%r{\A/paths/}, "")
81
- { status: 200, headers: { content_type: "application/json" },
82
- body: publishing_api_path_data_for(base_path).to_json }
83
- }
714
+ # Stub all PUT /paths/:base_path requests
715
+ #
716
+ # @example
717
+ # stub_any_publishing_api_path_reservation
718
+ def stub_any_publishing_api_path_reservation
719
+ stub_request(:put, %r[\A#{PUBLISHING_API_ENDPOINT}/paths/]).to_return do |request|
720
+ base_path = request.uri.path.sub(%r{\A/paths}, "")
721
+ body = JSON.parse(request.body).merge(base_path: base_path)
722
+ {
723
+ status: 200,
724
+ headers: { content_type: "application/json" },
725
+ body: body.to_json,
726
+ }
727
+ end
84
728
  end
85
729
 
730
+ # Stub a PUT /paths/:base_path request for a particular publishing
731
+ # application. Calling for a different publishing application will return
732
+ # a 422 response.
733
+ #
734
+ # @example
735
+ # stub_publishing_api_has_path_reservation_for("/foo", "content-publisher")
736
+ #
737
+ # @param base_path [String]
738
+ # @param publishing_app [String]
86
739
  def stub_publishing_api_has_path_reservation_for(path, publishing_app)
87
- data = publishing_api_path_data_for(path, "publishing_app" => publishing_app)
88
- error_data = data.merge("errors" => { "path" => ["is already reserved by the #{publishing_app} application"] })
740
+ message = "#{path} is already reserved by #{publishing_app}"
741
+ error = { code: 422,
742
+ message: "Base path #{message}",
743
+ fields: { base_path: [message] } }
89
744
 
90
745
  stub_request(:put, "#{PUBLISHING_API_ENDPOINT}/paths#{path}").
91
- to_return(status: 422, body: error_data.to_json,
92
- headers: { content_type: "application/json" })
746
+ to_return(status: 422,
747
+ headers: { content_type: "application/json" },
748
+ body: { error: error }.to_json)
93
749
 
94
750
  stub_request(:put, "#{PUBLISHING_API_ENDPOINT}/paths#{path}").
95
751
  with(body: { "publishing_app" => publishing_app }).
96
752
  to_return(status: 200,
97
753
  headers: { content_type: "application/json" },
98
- body: data.to_json)
754
+ body: { publishing_app: publishing_app, base_path: path }.to_json)
99
755
  end
100
756
 
101
- def stub_publishing_api_returns_path_reservation_validation_error_for(path, error_details = nil)
102
- error_details ||= { "base" => ["computer says no"] }
103
- error_data = publishing_api_path_data_for(path).merge("errors" => error_details)
757
+ # Stub a PUT /paths/:base_path request for a particular publishing
758
+ # application. Calling for a different publishing application will return
759
+ # a 422 response.
760
+ #
761
+ # @example
762
+ # stub_publishing_api_returns_path_reservation_validation_error_for(
763
+ # "/foo",
764
+ # "field" => ["error 1", "error 2"]
765
+ # )
766
+ #
767
+ # @param base_path [String]
768
+ # @param error_fields [Hash]
769
+ def stub_publishing_api_returns_path_reservation_validation_error_for(base_path, error_fields = {})
770
+ error_fields = { "base_path" => ["Computer says no"] } if error_fields.empty?
104
771
 
105
- stub_request(:put, "#{PUBLISHING_API_ENDPOINT}/paths#{path}").
106
- to_return(status: 422, body: error_data.to_json, headers: { content_type: "application/json" })
772
+ message = error_fields.keys.first.to_s.capitalize.gsub(/_/, " ") + " " +
773
+ error_fields.values.flatten.first
774
+
775
+ error = { code: 422, message: message, fields: error_fields }
776
+
777
+ stub_request(:put, "#{PUBLISHING_API_ENDPOINT}/paths#{base_path}").
778
+ to_return(status: 422,
779
+ headers: { content_type: "application/json" },
780
+ body: { error: error }.to_json)
107
781
  end
108
782
 
109
783
  # Aliases for DEPRECATED methods
110
784
  alias_method :publishing_api_isnt_available, :stub_publishing_api_isnt_available
785
+ alias_method :publishing_api_has_content, :stub_publishing_api_has_content
786
+ alias_method :publishing_api_has_fields_for_document, :stub_publishing_api_has_fields_for_document
787
+ alias_method :publishing_api_has_linkables, :stub_publishing_api_has_linkables
788
+ alias_method :publishing_api_has_item, :stub_publishing_api_has_item
789
+ alias_method :publishing_api_has_item_in_sequence, :stub_publishing_api_has_item_in_sequence
790
+ alias_method :publishing_api_does_not_have_item, :stub_publishing_api_does_not_have_item
791
+ alias_method :publishing_api_has_links, :stub_publishing_api_has_links
792
+ alias_method :publishing_api_has_expanded_links, :stub_publishing_api_has_expanded_links
793
+ alias_method :publishing_api_has_links_for_content_ids, :stub_publishing_api_has_links_for_content_ids
794
+ alias_method :publishing_api_does_not_have_links, :stub_publishing_api_does_not_have_links
795
+ alias_method :publishing_api_has_lookups, :stub_publishing_api_has_lookups
796
+ alias_method :publishing_api_has_linked_items, :stub_publishing_api_has_linked_items
797
+ alias_method :publishing_api_get_editions, :stub_publishing_api_get_editions
111
798
  alias_method :publishing_api_has_path_reservation_for, :stub_publishing_api_has_path_reservation_for
112
799
  alias_method :publishing_api_returns_path_reservation_validation_error_for, :stub_publishing_api_returns_path_reservation_validation_error_for
800
+ alias_method :stub_default_publishing_api_path_reservation, :stub_any_publishing_api_path_reservation
801
+ alias_method :stub_default_publishing_api_put_intent, :stub_any_publishing_api_put_intent
113
802
 
114
803
  private
115
804
 
805
+ def stub_publishing_api_put(*args)
806
+ stub_publishing_api_postlike_call(:put, *args)
807
+ end
808
+
809
+ def stub_publishing_api_patch(*args)
810
+ stub_publishing_api_postlike_call(:patch, *args)
811
+ end
812
+
813
+ def stub_publishing_api_postlike_call(method, content_id, body, resource_path, override_response_hash = {})
814
+ response_hash = { status: 200, body: "{}", headers: { "Content-Type" => "application/json; charset=utf-8" } }
815
+ response_hash.merge!(override_response_hash)
816
+ response_hash[:body] = response_hash[:body].to_json if response_hash[:body].is_a?(Hash)
817
+ url = PUBLISHING_API_V2_ENDPOINT + resource_path + "/" + content_id
818
+ stub_request(method, url).with(body: body).to_return(response_hash)
819
+ end
820
+
821
+ def deep_stringify_keys(hash)
822
+ deep_transform_keys(hash, &:to_s)
823
+ end
824
+
825
+ def deep_transform_keys(object, &block)
826
+ case object
827
+ when Hash
828
+ object.each_with_object({}) do |(key, value), result|
829
+ result[yield(key)] = deep_transform_keys(value, &block)
830
+ end
831
+ when Array
832
+ object.map { |item| deep_transform_keys(item, &block) }
833
+ else
834
+ object
835
+ end
836
+ end
837
+
838
+ def resource_not_found(content_id, type)
839
+ {
840
+ error: {
841
+ code: 404,
842
+ message: "Could not find #{type} with content_id: #{content_id}",
843
+ },
844
+ }
845
+ end
846
+
847
+ def version_conflict(expected_version, actual_version = expected_version + 1)
848
+ {
849
+ error: {
850
+ code: 409,
851
+ message: "A lock-version conflict occurred. The `previous_version` you've sent (#{expected_version}) is not the same as the current lock version of the edition (#{actual_version}).",
852
+ fields: { previous_version: ["does not match"] },
853
+ },
854
+ }
855
+ end
856
+
116
857
  def stub_publishing_api_unreserve_path_with_code(base_path, publishing_app, code)
117
858
  url = PUBLISHING_API_ENDPOINT + "/paths" + base_path
118
859
  body = { publishing_app: publishing_app }
@@ -147,16 +888,6 @@ module GdsApi
147
888
  def intent_for_publishing_api(base_path, publishing_app = "publisher")
148
889
  intent_for_base_path(base_path).merge("publishing_app" => publishing_app)
149
890
  end
150
-
151
- def publishing_api_path_data_for(path, override_attributes = {})
152
- now = Time.zone.now.utc.iso8601
153
- {
154
- "path" => path,
155
- "publishing_app" => "foo-publisher",
156
- "created_at" => now,
157
- "updated_at" => now,
158
- }.merge(override_attributes)
159
- end
160
891
  end
161
892
  end
162
893
  end