gds-api-adapters 63.0.0 → 63.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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