publishing_platform_api_adapters 0.8.3 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 33684b80242329f1ddf7d6fd786c86adffa6118b3f9b4e4d81243148d90755c0
4
- data.tar.gz: 3b3c0d5c7ea79a368629f349b71432e374b19f989d4a176b08b28c829e609f64
3
+ metadata.gz: c6ec8b5ba55f54de30a2717ff57716d1e121ca531cbcc86208e653dda3f2c675
4
+ data.tar.gz: 652114d6c57ea3550e48bd039ec2769617e9721e20a46ae069759ad8be072bfd
5
5
  SHA512:
6
- metadata.gz: 1189194963db42488c2dcd753b9fe0e41c43adcaf002cef403d2ae4b864af4d959398f4fedc34247dce88e2d2d2df3ea41b5800054e4a9ae17242bbea9042d01
7
- data.tar.gz: e43738770632d4739fe78080e4faf2b1067294e456366e7fa0b7b8218d1d292978485c1ec13f6e2642c1a888bf52e657c1c6741fbb5982dbd0261a160141b0e1
6
+ metadata.gz: 3c19ef7a204e52ad5179dae74e62399a52ed2005c0b6020166f93ed083183ecb2a26bdc355a967a1b147eecb1666a758a09fb5c00e9ce2a328500a630134832d
7
+ data.tar.gz: 3e6f94598b557c5494a6399e0b6b9de732944ee7ed8dfc48037a53d31fc3d0905cbba7dc34d6e5b68ecde14a7c515b12a72baaab8b6af0dec90caf7d0e57b725
@@ -61,7 +61,7 @@ module PublishingPlatformApi
61
61
  alias_method :r_maxage, :reverse_max_age
62
62
 
63
63
  def shared_max_age
64
- self["s-maxage"].to_i if key?("r-maxage")
64
+ self["s-maxage"].to_i if key?("s-maxage")
65
65
  end
66
66
  alias_method :s_maxage, :shared_max_age
67
67
 
@@ -1,20 +1,6 @@
1
1
  require_relative "base"
2
2
 
3
3
  class PublishingPlatformApi::Router < PublishingPlatformApi::Base
4
- ### Backends
5
-
6
- def get_backend(id)
7
- get_json("#{endpoint}/backends/#{CGI.escape(id)}")
8
- end
9
-
10
- def add_backend(id, url)
11
- put_json("#{endpoint}/backends/#{CGI.escape(id)}", backend: { backend_url: url })
12
- end
13
-
14
- def delete_backend(id)
15
- delete_json("#{endpoint}/backends/#{CGI.escape(id)}")
16
- end
17
-
18
4
  ### Routes
19
5
 
20
6
  def get_route(path)
@@ -0,0 +1,41 @@
1
+ module PublishingPlatformApi
2
+ module TestHelpers
3
+ module CommonResponses
4
+ def titleize_slug(slug, options = {})
5
+ if options[:title_case]
6
+ slug.tr("-", " ").gsub(/\b./, &:upcase)
7
+ else
8
+ slug.tr("-", " ").capitalize
9
+ end
10
+ end
11
+
12
+ # expects a slug like "ministry-of-funk"
13
+ # returns an acronym like "MOF"
14
+ def acronymize_slug(slug)
15
+ initials = slug.gsub(/\b\w+/) { |m| m[0] }.delete("-")
16
+ initials.upcase
17
+ end
18
+
19
+ def response_base
20
+ {
21
+ "_response_info" => {
22
+ "status" => "ok",
23
+ },
24
+ }
25
+ end
26
+ alias_method :singular_response_base, :response_base
27
+
28
+ def plural_response_base
29
+ response_base.merge(
30
+ "description" => "Tags!",
31
+ "total" => 100,
32
+ "start_index" => 1,
33
+ "page_size" => 100,
34
+ "current_page" => 1,
35
+ "pages" => 1,
36
+ "results" => [],
37
+ )
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,103 @@
1
+ # require "gds_api/test_helpers/json_client_helper"
2
+ require "publishing_platform_api/test_helpers/common_responses"
3
+ # require "plek"
4
+ # require "securerandom"
5
+
6
+ module PublishingPlatformApi
7
+ module TestHelpers
8
+ module Organisations
9
+ include PublishingPlatformApi::TestHelpers::CommonResponses
10
+
11
+ WEBSITE_ROOT = PublishingPlatformLocation.new.website_root
12
+
13
+ def stub_organisations_api_has_organisations(organisation_slugs)
14
+ bodies = organisation_slugs.map { |slug| organisation_for_slug(slug) }
15
+ stub_organisations_api_has_organisations_with_bodies(bodies)
16
+ end
17
+
18
+ # Sets up the index endpoints for the given organisation slugs
19
+ # The stubs are setup to paginate in chunks of 20
20
+ #
21
+ # This also sets up the individual endpoints for each slug
22
+ # by calling organisations_api_has_organisation below
23
+ def stub_organisations_api_has_organisations_with_bodies(organisation_bodies)
24
+ # Stub API call to the endpoint for an individual organisation
25
+ organisation_bodies.each do |body|
26
+ slug = body["details"]["slug"]
27
+ stub_organisations_api_has_organisation(slug, body)
28
+ end
29
+
30
+ pages = []
31
+ organisation_bodies.each_slice(20) do |bodies|
32
+ pages << bodies
33
+ end
34
+
35
+ pages.each_with_index do |page, i|
36
+ page_details = plural_response_base.merge(
37
+ "results" => page,
38
+ "total" => organisation_bodies.size,
39
+ "pages" => pages.size,
40
+ "current_page" => i + 1,
41
+ "page_size" => 20,
42
+ "start_index" => i * 20 + 1,
43
+ )
44
+
45
+ links = { self: "#{WEBSITE_ROOT}/api/organisations?page=#{i + 1}" }
46
+ links[:next] = "#{WEBSITE_ROOT}/api/organisations?page=#{i + 2}" if pages[i + 1]
47
+ links[:previous] = "#{WEBSITE_ROOT}/api/organisations?page=#{i}" unless i.zero?
48
+ page_details["_response_info"]["links"] = []
49
+ link_headers = []
50
+ links.each do |rel, href|
51
+ page_details["_response_info"]["links"] << { "rel" => rel, "href" => href }
52
+ link_headers << "<#{href}>; rel=\"#{rel}\""
53
+ end
54
+
55
+ stub_request(:get, links[:self])
56
+ .to_return(status: 200, body: page_details.to_json, headers: { "Link" => link_headers.join(", ") })
57
+
58
+ next unless i.zero?
59
+
60
+ # First page exists at URL with and without page param
61
+ stub_request(:get, links[:self].sub(/\?page=1/, ""))
62
+ .to_return(status: 200, body: page_details.to_json, headers: { "Link" => link_headers.join(", ") })
63
+ end
64
+
65
+ if pages.empty?
66
+ # If there are no pages - and so no organisations specified - then stub /api/organisations.
67
+ stub_request(:get, "#{WEBSITE_ROOT}/api/organisations").to_return(status: 200, body: plural_response_base.to_json, headers: {})
68
+ end
69
+ end
70
+
71
+ def stub_organisations_api_has_organisation(organisation_slug, details = nil)
72
+ details ||= organisation_for_slug(organisation_slug)
73
+ stub_request(:get, "#{WEBSITE_ROOT}/api/organisations/#{organisation_slug}")
74
+ .to_return(status: 200, body: details.to_json)
75
+ end
76
+
77
+ def stub_organisations_api_does_not_have_organisation(organisation_slug)
78
+ stub_request(:get, "#{WEBSITE_ROOT}/api/organisations/#{organisation_slug}").to_return(status: 404)
79
+ end
80
+
81
+ def organisation_for_slug(slug)
82
+ singular_response_base.merge(organisation_details_for_slug(slug))
83
+ end
84
+
85
+ # Constructs a sample organisation
86
+ def organisation_details_for_slug(slug, content_id = SecureRandom.uuid)
87
+ {
88
+ "id" => "#{WEBSITE_ROOT}/api/organisations/#{slug}",
89
+ "title" => titleize_slug(slug, title_case: true),
90
+ "format" => "Department",
91
+ "updated_at" => "2013-03-25T13:06:42+00:00",
92
+ "web_url" => "#{WEBSITE_ROOT}/government/organisations/#{slug}",
93
+ "details" => {
94
+ "slug" => slug,
95
+ "abbreviation" => acronymize_slug(slug),
96
+ "status" => "live",
97
+ "content_id" => content_id,
98
+ },
99
+ }
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,762 @@
1
+ require "publishing_platform_api/test_helpers/content_item_helpers"
2
+ require "json"
3
+
4
+ module PublishingPlatformApi
5
+ module TestHelpers
6
+ # @api documented
7
+ module PublishingApi
8
+ include ContentItemHelpers
9
+
10
+ PUBLISHING_API_ENDPOINT = PublishingPlatformLocation.find("publishing-api")
11
+
12
+ # Stub a PUT /content/:content_id request with the given content id and request body.
13
+ # if no response_hash is given, a default response as follows is created:
14
+ # {status: 200, body: '{}', headers: {"Content-Type" => "application/json; charset=utf-8"}}
15
+ #
16
+ # if a response is given, then it will be merged with the default response.
17
+ # if the given parameter for the response body is a Hash, it will be converted to JSON.
18
+ #
19
+ # The following two examples are equivalent:
20
+ # @example
21
+ # stub_publishing_api_put_content(my_content_id, my_request_body, { status: 201, body: {version: 33}.to_json })
22
+ #
23
+ # @example
24
+ # stub_publishing_api_put_content(my_content_id, my_request_body, { status: 201, body: {version: 33} })
25
+ #
26
+ # @param content_id [UUID]
27
+ # @param body [String]
28
+ # @param response_hash [Hash]
29
+ def stub_publishing_api_put_content(content_id, body, response_hash = {})
30
+ stub_publishing_api_put(content_id, body, "/content", response_hash)
31
+ end
32
+
33
+ # Stub a PATCH /links/:content_id request
34
+ #
35
+ # @example
36
+ # stub_publishing_api_patch_links(
37
+ # my_content_id,
38
+ # "links" => {
39
+ # "taxons" => %w(level_one_topic level_two_topic),
40
+ # },
41
+ # "previous_version" => 3,
42
+ # )
43
+ #
44
+ # @param content_id [UUID]
45
+ # @param body [String]
46
+ def stub_publishing_api_patch_links(content_id, body)
47
+ stub_publishing_api_patch(content_id, body, "/links")
48
+ end
49
+
50
+ # Stub a PATCH /links/:content_id request to return a 409 response
51
+ #
52
+ # @example
53
+ # stub_publishing_api_patch_links_conflict(
54
+ # my_content_id,
55
+ # "links" => {
56
+ # "taxons" => %w(level_one_topic level_two_topic),
57
+ # },
58
+ # "previous_version" => 3,
59
+ # )
60
+ #
61
+ # @param content_id [UUID]
62
+ # @param body [String]
63
+ def stub_publishing_api_patch_links_conflict(content_id, body)
64
+ previous_version = JSON.parse(body.to_json)["previous_version"]
65
+ override_response_hash = { status: 409, body: version_conflict(previous_version) }
66
+ stub_publishing_api_patch(content_id, body, "/links", override_response_hash)
67
+ end
68
+
69
+ # Stub a POST /content/:content_id/publish request
70
+ #
71
+ # @param content_id [UUID]
72
+ # @param body [String]
73
+ # @param response_hash [Hash]
74
+ def stub_publishing_api_publish(content_id, body, response_hash = {})
75
+ url = PUBLISHING_API_ENDPOINT + "/content/#{content_id}/publish"
76
+ response = {
77
+ status: 200,
78
+ body: "{}",
79
+ headers: { "Content-Type" => "application/json; charset=utf-8" },
80
+ }.merge(response_hash)
81
+ stub_request(:post, url).with(body:).to_return(response)
82
+ end
83
+
84
+ # Stub a POST /content/:content_id/republish request
85
+ #
86
+ # @param content_id [UUID]
87
+ # @param body [String]
88
+ # @param response_hash [Hash]
89
+ def stub_publishing_api_republish(content_id, body = {}, response_hash = {})
90
+ url = PUBLISHING_API_ENDPOINT + "/content/#{content_id}/republish"
91
+ response = {
92
+ status: 200,
93
+ body: "{}",
94
+ headers: { "Content-Type" => "application/json; charset=utf-8" },
95
+ }.merge(response_hash)
96
+ stub_request(:post, url).with(body:).to_return(response)
97
+ end
98
+
99
+ # Stub a POST /content/:content_id/unpublish request
100
+ #
101
+ # @param content_id [UUID]
102
+ # @param params [Hash]
103
+ # @param body [String]
104
+ def stub_publishing_api_unpublish(content_id, params, response_hash = {})
105
+ url = PUBLISHING_API_ENDPOINT + "/content/#{content_id}/unpublish"
106
+ response = {
107
+ status: 200,
108
+ body: "{}",
109
+ headers: { "Content-Type" => "application/json; charset=utf-8" },
110
+ }.merge(response_hash)
111
+ stub_request(:post, url).with(params).to_return(response)
112
+ end
113
+
114
+ # Stub a POST /content/:content_id/discard-draft request
115
+ #
116
+ # @param content_id [UUID]
117
+ def stub_publishing_api_discard_draft(content_id)
118
+ url = PUBLISHING_API_ENDPOINT + "/content/#{content_id}/discard-draft"
119
+ stub_request(:post, url).to_return(status: 200, headers: { "Content-Type" => "application/json; charset=utf-8" })
120
+ end
121
+
122
+ # Stub requests issued when publishing a new draft.
123
+ # - PUT /content/:content_id
124
+ # - POST /content/:content_id/publish
125
+ # - PATCH /links/:content_id
126
+ #
127
+ # @param body [String]
128
+ # @param content_id [UUID]
129
+ # @param publish_body [Hash]
130
+ def stub_publishing_api_put_content_links_and_publish(body, content_id = nil, publish_body = nil)
131
+ content_id ||= body[:content_id]
132
+ if publish_body.nil?
133
+ publish_body = { update_type: body.fetch(:update_type) }
134
+ publish_body[:locale] = body[:locale] if body[:locale]
135
+ end
136
+ stubs = []
137
+ stubs << stub_publishing_api_put_content(content_id, body.except(:links))
138
+ stubs << stub_publishing_api_patch_links(content_id, body.slice(:links)) unless body.slice(:links).empty?
139
+ stubs << stub_publishing_api_publish(content_id, publish_body)
140
+ stubs
141
+ end
142
+
143
+ # Stub any PUT /content/* request
144
+ def stub_any_publishing_api_put_content
145
+ stub_request(:put, %r{\A#{PUBLISHING_API_ENDPOINT}/content/})
146
+ end
147
+
148
+ # Stub any PATCH /links/* request
149
+ def stub_any_publishing_api_patch_links
150
+ stub_request(:patch, %r{\A#{PUBLISHING_API_ENDPOINT}/links/})
151
+ end
152
+
153
+ # Stub any POST /content/*/publish request
154
+ def stub_any_publishing_api_publish
155
+ stub_request(:post, %r{\A#{PUBLISHING_API_ENDPOINT}/content/.*/publish})
156
+ end
157
+
158
+ # Stub any POST /content/*/publish request
159
+ def stub_any_publishing_api_republish
160
+ stub_request(:post, %r{\A#{PUBLISHING_API_ENDPOINT}/content/.*/republish})
161
+ end
162
+
163
+ # Stub any POST /content/*/unpublish request
164
+ def stub_any_publishing_api_unpublish
165
+ stub_request(:post, %r{\A#{PUBLISHING_API_ENDPOINT}/content/.*/unpublish})
166
+ end
167
+
168
+ # Stub any POST /content/*/discard-draft request
169
+ def stub_any_publishing_api_discard_draft
170
+ stub_request(:post, %r{\A#{PUBLISHING_API_ENDPOINT}/content/.*/discard-draft})
171
+ end
172
+
173
+ # Stub any request to the publishing API
174
+ def stub_any_publishing_api_call
175
+ stub_request(:any, %r{\A#{PUBLISHING_API_ENDPOINT}})
176
+ end
177
+
178
+ # Stub any request to the publishing API to return a 404 response
179
+ def stub_any_publishing_api_call_to_return_not_found
180
+ stub_request(:any, %r{\A#{PUBLISHING_API_ENDPOINT}})
181
+ .to_return(status: 404, headers: { "Content-Type" => "application/json; charset=utf-8" })
182
+ end
183
+
184
+ # Stub any request to the publishing API to return a 503 response
185
+ def stub_publishing_api_isnt_available
186
+ stub_request(:any, /#{PUBLISHING_API_ENDPOINT}\/.*/).to_return(status: 503)
187
+ end
188
+
189
+ # Assert that a draft was saved and published, and links were updated.
190
+ # - PUT /content/:content_id
191
+ # - POST /content/:content_id/publish
192
+ # - PATCH /links/:content_id
193
+ #
194
+ # @param body [String]
195
+ # @param content_id [UUID]
196
+ # @param publish_body [Hash]
197
+ def assert_publishing_api_put_content_links_and_publish(body, content_id = nil, publish_body = nil)
198
+ content_id ||= body[:content_id]
199
+ if publish_body.nil?
200
+ publish_body = { update_type: body.fetch(:update_type) }
201
+ publish_body[:locale] = body[:locale] if body[:locale]
202
+ end
203
+ assert_publishing_api_put_content(content_id, body.except(:links))
204
+ assert_publishing_api_patch_links(content_id, body.slice(:links)) unless body.slice(:links).empty?
205
+ assert_publishing_api_publish(content_id, publish_body)
206
+ end
207
+
208
+ # Assert that content was saved (PUT /content/:content_id)
209
+ #
210
+ # @param content_id [UUID]
211
+ # @param attributes_or_matcher [Object]
212
+ # @param times [Integer]
213
+ def assert_publishing_api_put_content(content_id, attributes_or_matcher = nil, times = 1)
214
+ url = "#{PUBLISHING_API_ENDPOINT}/content/#{content_id}"
215
+ assert_publishing_api(:put, url, attributes_or_matcher, times)
216
+ end
217
+
218
+ # Assert that content was published (POST /content/:content_id/publish)
219
+ #
220
+ # @param content_id [UUID]
221
+ # @param attributes_or_matcher [Object]
222
+ # @param times [Integer]
223
+ def assert_publishing_api_publish(content_id, attributes_or_matcher = nil, times = 1)
224
+ url = PUBLISHING_API_ENDPOINT + "/content/#{content_id}/publish"
225
+ assert_publishing_api(:post, url, attributes_or_matcher, times)
226
+ end
227
+
228
+ # Assert that content was unpublished (POST /content/:content_id/unpublish)
229
+ #
230
+ # @param content_id [UUID]
231
+ # @param attributes_or_matcher [Object]
232
+ # @param times [Integer]
233
+ def assert_publishing_api_unpublish(content_id, attributes_or_matcher = nil, times = 1)
234
+ url = PUBLISHING_API_ENDPOINT + "/content/#{content_id}/unpublish"
235
+ assert_publishing_api(:post, url, attributes_or_matcher, times)
236
+ end
237
+
238
+ # Assert that links were updated (PATCH /links/:content_id)
239
+ #
240
+ # @param content_id [UUID]
241
+ # @param attributes_or_matcher [Object]
242
+ # @param times [Integer]
243
+ def assert_publishing_api_patch_links(content_id, attributes_or_matcher = nil, times = 1)
244
+ url = "#{PUBLISHING_API_ENDPOINT}/links/#{content_id}"
245
+ assert_publishing_api(:patch, url, attributes_or_matcher, times)
246
+ end
247
+
248
+ # Assert that a draft was discarded (POST /content/:content_id/discard-draft)
249
+ #
250
+ # @param content_id [UUID]
251
+ # @param attributes_or_matcher [Object]
252
+ # @param times [Integer]
253
+ def assert_publishing_api_discard_draft(content_id, attributes_or_matcher = nil, times = 1)
254
+ url = PUBLISHING_API_ENDPOINT + "/content/#{content_id}/discard-draft"
255
+ assert_publishing_api(:post, url, attributes_or_matcher, times)
256
+ end
257
+
258
+ # Assert that a request was made to the publishing API
259
+ #
260
+ # @param verb [String]
261
+ # @param url [String]
262
+ # @param attributes_or_matcher [Object]
263
+ # @param times [Integer]
264
+ def assert_publishing_api(verb, url, attributes_or_matcher = nil, times = 1)
265
+ matcher = if attributes_or_matcher.is_a?(Hash)
266
+ request_json_matches(attributes_or_matcher)
267
+ else
268
+ attributes_or_matcher
269
+ end
270
+
271
+ if matcher
272
+ assert_requested(verb, url, times:, &matcher)
273
+ else
274
+ assert_requested(verb, url, times:)
275
+ end
276
+ end
277
+
278
+ # Get a request matcher that checks if a JSON request includes a set of attributes
279
+ def request_json_includes(required_attributes)
280
+ lambda do |request|
281
+ data = JSON.parse(request.body)
282
+ deep_stringify_keys(required_attributes)
283
+ .to_a.all? { |key, value| data[key] == value }
284
+ end
285
+ end
286
+
287
+ # Get a request matcher that checks if a JSON request matches a hash
288
+ def request_json_matches(required_attributes)
289
+ lambda do |request|
290
+ data = JSON.parse(request.body)
291
+ deep_stringify_keys(required_attributes) == data
292
+ end
293
+ end
294
+
295
+ # Stub GET /content/ to return a set of content items
296
+ #
297
+ # @example
298
+ #
299
+ # stub_publishing_api_has_content(
300
+ # vehicle_recalls_and_faults, # this is a variable containing an array of content items
301
+ # document_type: described_class.publishing_api_document_type, #example of a document_type: "vehicle_recalls_and_faults_alert"
302
+ # fields: fields, #example: let(:fields) { %i[base_path content_id public_updated_at title publication_state] }
303
+ # page: 1,
304
+ # per_page: 50
305
+ # )
306
+ # @param items [Array]
307
+ # @param params [Hash]
308
+ def stub_publishing_api_has_content(items, params = {})
309
+ url = "#{PUBLISHING_API_ENDPOINT}/content"
310
+
311
+ if params.respond_to? :fetch
312
+ per_page = params.fetch(:per_page, 50)
313
+ page = params.fetch(:page, 1)
314
+ else
315
+ per_page = 50
316
+ page = 1
317
+ end
318
+
319
+ start_position = (page - 1) * per_page
320
+ page_items = items.slice(start_position, per_page) || []
321
+
322
+ number_of_pages =
323
+ if items.count < per_page
324
+ 1
325
+ else
326
+ (items.count / per_page.to_f).ceil
327
+ end
328
+
329
+ body = {
330
+ results: page_items,
331
+ total: items.count,
332
+ pages: number_of_pages,
333
+ current_page: page,
334
+ }
335
+
336
+ stub_request(:get, url)
337
+ .with(query: params)
338
+ .to_return(status: 200, body: body.to_json, headers: {})
339
+ end
340
+
341
+ # This method has been refactored into publishing_api_has_content (above)
342
+ # publishing_api_has_content allows for flexible passing in of arguments, please use instead
343
+ def stub_publishing_api_has_fields_for_document(document_type, items, fields)
344
+ body = Array(items).map do |item|
345
+ deep_stringify_keys(item).slice(*fields)
346
+ end
347
+
348
+ query_params = fields.map do |f|
349
+ "&fields%5B%5D=#{f}"
350
+ end
351
+
352
+ url = PUBLISHING_API_ENDPOINT + "/content?document_type=#{document_type}#{query_params.join('')}"
353
+
354
+ stub_request(:get, url).to_return(status: 200, body: { results: body }.to_json, headers: {})
355
+ end
356
+
357
+ # Stub GET /linkables to return a set of content items with a specific document type
358
+ #
359
+ # @param linkables [Array]
360
+ def stub_publishing_api_has_linkables(linkables, document_type:)
361
+ url = PUBLISHING_API_ENDPOINT + "/linkables?document_type=#{document_type}"
362
+ stub_request(:get, url).to_return(status: 200, body: linkables.to_json, headers: {})
363
+ end
364
+
365
+ # Stub GET /content/:content_id to return a specific content item hash
366
+ #
367
+ # @param item [Hash]
368
+ def stub_publishing_api_has_item(item, params = {})
369
+ item = deep_transform_keys(item, &:to_sym)
370
+ url = "#{PUBLISHING_API_ENDPOINT}/content/#{item[:content_id]}"
371
+ stub_request(:get, url)
372
+ .with(query: hash_including(params))
373
+ .to_return(status: 200, body: item.to_json, headers: {})
374
+ end
375
+
376
+ # Stub GET /content/:content_id to progress through a series of responses.
377
+ #
378
+ # @param items [Array]
379
+ def stub_publishing_api_has_item_in_sequence(content_id, items)
380
+ items = items.each { |item| deep_transform_keys(item, &:to_sym) }
381
+ url = "#{PUBLISHING_API_ENDPOINT}/content/#{content_id}"
382
+ calls = -1
383
+
384
+ stub_request(:get, url).to_return do |_request|
385
+ calls += 1
386
+ item = items[calls] || items.last
387
+
388
+ { status: 200, body: item.to_json, headers: {} }
389
+ end
390
+ end
391
+
392
+ # Stub GET /content/:content_id to return a 404 response
393
+ #
394
+ # @param content_id [UUID]
395
+ def stub_publishing_api_does_not_have_item(content_id, params = {})
396
+ url = "#{PUBLISHING_API_ENDPOINT}/content/#{content_id}"
397
+ stub_request(:get, url)
398
+ .with(query: hash_including(params))
399
+ .to_return(status: 404, body: resource_not_found(content_id, "content item").to_json, headers: {})
400
+ end
401
+
402
+ # Stub a request to links endpoint
403
+ #
404
+ # @param [Hash] links the structure of the links hash
405
+ #
406
+ # @example
407
+ #
408
+ # stub_publishing_api_has_links(
409
+ # {
410
+ # "content_id" => "64aadc14-9bca-40d9-abb6-4f21f9792a05",
411
+ # "links" => {
412
+ # "mainstream_browse_pages" => ["df2e7a3e-2078-45de-a75a-fd37d027427e"],
413
+ # "parent" => ["df2e7a3e-2078-45de-a75a-fd37d027427e"],
414
+ # "organisations" => ["569a9ee5-c195-4b7f-b9dc-edc17a09113f", "5c54ae52-341b-499e-a6dd-67f04633b8cf"]
415
+ # },
416
+ # "version" => 6
417
+ # }
418
+ # )
419
+ #
420
+ # @example
421
+ #
422
+ # Services.publishing_api.get_links("64aadc14-9bca-40d9-abb6-4f21f9792a05")
423
+ # => {
424
+ # "content_id" => "64aadc14-9bca-40d9-abb6-4f21f9792a05",
425
+ # "links" => {
426
+ # "mainstream_browse_pages" => ["df2e7a3e-2078-45de-a75a-fd37d027427e"],
427
+ # "parent" => ["df2e7a3e-2078-45de-a75a-fd37d027427e"],
428
+ # "organisations" => ["569a9ee5-c195-4b7f-b9dc-edc17a09113f", "5c54ae52-341b-499e-a6dd-67f04633b8cf"]
429
+ # },
430
+ # "version" => 6
431
+ # }
432
+ def stub_publishing_api_has_links(links)
433
+ links = deep_transform_keys(links, &:to_sym)
434
+ url = "#{PUBLISHING_API_ENDPOINT}/links/#{links[:content_id]}"
435
+ stub_request(:get, url).to_return(status: 200, body: links.to_json, headers: {})
436
+ end
437
+
438
+ # Stub a request to the expanded links endpoint
439
+ #
440
+ # @param [Hash] links the structure of the links hash
441
+ #
442
+ # @example
443
+ # stub_publishing_api_has_expanded_links(
444
+ # {
445
+ # "content_id" => "64aadc14-9bca-40d9-abb4-4f21f9792a05",
446
+ # "expanded_links" => {
447
+ # "mainstream_browse_pages" => [
448
+ # {
449
+ # "content_id" => "df2e7a3e-2078-45de-a76a-fd37d027427a",
450
+ # "base_path" => "/a/base/path",
451
+ # "document_type" => "mainstream_browse_page",
452
+ # "locale" => "en",
453
+ # "links" => {},
454
+ # # ...
455
+ # }
456
+ # ],
457
+ # "parent" => [
458
+ # {
459
+ # "content_id" => "df2e7a3e-2028-45de-a75a-fd37d027427e",
460
+ # "document_type" => "mainstream_browse_page",
461
+ # # ...
462
+ # },
463
+ # ]
464
+ # }
465
+ # }
466
+ # )
467
+ #
468
+ # @example
469
+ # Services.publishing_api.expanded_links("64aadc14-9bca-40d9-abb4-4f21f9792a05")
470
+ # => {
471
+ # "content_id" => "64aadc14-9bca-40d9-abb4-4f21f9792a05",
472
+ # "expanded_links" => {
473
+ # "mainstream_browse_pages" => [
474
+ # {
475
+ # "content_id" => "df2e7a3e-2078-45de-a76a-fd37d027427a",
476
+ # "base_path" => "/a/base/path",
477
+ # "document_type" => "mainstream_browse_page",
478
+ # "locale" => "en",
479
+ # "links" => {},
480
+ # ...
481
+ # }
482
+ # ],
483
+ # "parent" => [
484
+ # {
485
+ # "content_id" => "df2e7a3e-2028-45de-a75a-fd37d027427e",
486
+ # "document_type" => "mainstream_browse_page",
487
+ # ...
488
+ # },
489
+ # ]
490
+ # }
491
+ # }
492
+ def stub_publishing_api_has_expanded_links(links, with_drafts: true, generate: false)
493
+ links = deep_transform_keys(links, &:to_sym)
494
+ request_params = {}
495
+ request_params["with_drafts"] = false unless with_drafts
496
+ request_params["generate"] = true if generate
497
+
498
+ url = "#{PUBLISHING_API_ENDPOINT}/expanded-links/#{links[:content_id]}"
499
+ stub_request(:get, url)
500
+ .with(query: request_params)
501
+ .to_return(status: 200, body: links.to_json, headers: {})
502
+ end
503
+
504
+ # Stub a request to get links for content ids
505
+ #
506
+ # @param [Hash] links the links for each content id
507
+ #
508
+ # @example
509
+ # stub_publishing_api_has_links_for_content_ids(
510
+ # { "2878337b-bed9-4e7f-85b6-10ed2cbcd504" => {
511
+ # "links" => { "taxons" => ["eb6965c7-3056-45d0-ae50-2f0a5e2e0854"] }
512
+ # },
513
+ # "eec13cea-219d-4896-9c97-60114da23559" => {
514
+ # "links" => {}
515
+ # }
516
+ # }
517
+ # )
518
+ #
519
+ # @example
520
+ # Services.publishing_api.get_links_for_content_ids(["2878337b-bed9-4e7f-85b6-10ed2cbcd504"])
521
+ # => {
522
+ # "2878337b-bed9-4e7f-85b6-10ed2cbcd504" => {
523
+ # "links" => [
524
+ # "eb6965c7-3056-45d0-ae50-2f0a5e2e0854"
525
+ # ]
526
+ # }
527
+ # }
528
+ def stub_publishing_api_has_links_for_content_ids(links)
529
+ url = "#{PUBLISHING_API_ENDPOINT}/links/by-content-id"
530
+ stub_request(:post, url).with(body: { content_ids: links.keys }).to_return(status: 200, body: links.to_json, headers: {})
531
+ end
532
+
533
+ # Stub GET /links/:content_id to return a 404 response
534
+ #
535
+ # @param content_id [UUID]
536
+ def stub_publishing_api_does_not_have_links(content_id)
537
+ url = "#{PUBLISHING_API_ENDPOINT}/links/#{content_id}"
538
+ stub_request(:get, url).to_return(status: 404, body: resource_not_found(content_id, "link set").to_json, headers: {})
539
+ end
540
+
541
+ def stub_publishing_api_unreserve_path(base_path, publishing_app = /.*/)
542
+ stub_publishing_api_unreserve_path_with_code(base_path, publishing_app, 200)
543
+ end
544
+
545
+ def stub_publishing_api_unreserve_path_not_found(base_path, publishing_app = /.*/)
546
+ stub_publishing_api_unreserve_path_with_code(base_path, publishing_app, 404)
547
+ end
548
+
549
+ def stub_publishing_api_unreserve_path_invalid(base_path, publishing_app = /.*/)
550
+ stub_publishing_api_unreserve_path_with_code(base_path, publishing_app, 422)
551
+ end
552
+
553
+ def stub_any_publishing_api_unreserve_path
554
+ stub_request(:delete, %r{\A#{PUBLISHING_API_ENDPOINT}/paths/})
555
+ end
556
+
557
+ def assert_publishing_api_put(url, attributes_or_matcher = {}, times = 1)
558
+ matcher = if attributes_or_matcher.is_a?(Hash)
559
+ attributes_or_matcher.empty? ? nil : request_json_matching(attributes_or_matcher)
560
+ else
561
+ attributes_or_matcher
562
+ end
563
+
564
+ if matcher
565
+ assert_requested(:put, url, times:, &matcher)
566
+ else
567
+ assert_requested(:put, url, times:)
568
+ end
569
+ end
570
+
571
+ def request_json_matching(required_attributes)
572
+ lambda do |request|
573
+ data = JSON.parse(request.body)
574
+ required_attributes.to_a.all? { |key, value| data[key.to_s] == value }
575
+ end
576
+ end
577
+
578
+ def request_json_including(required_attributes)
579
+ lambda do |request|
580
+ data = JSON.parse(request.body)
581
+ values_match_recursively(required_attributes, data)
582
+ end
583
+ end
584
+
585
+ # Stub a PUT /paths/:base_path request with the given base_path and
586
+ # request body.
587
+ #
588
+ # @example
589
+ # stub_publishing_api_path_reservation(
590
+ # "/path/to",
591
+ # publishing_app: "content-publisher",
592
+ # override_existing: true,
593
+ # )
594
+ #
595
+ # @param base_path [String]
596
+ # @param params [Hash]
597
+ def stub_publishing_api_path_reservation(base_path, params = {})
598
+ url = PUBLISHING_API_ENDPOINT + "/paths#{base_path}"
599
+ response = {
600
+ status: 200,
601
+ headers: { content_type: "application/json" },
602
+ body: params.merge(base_path:).to_json,
603
+ }
604
+
605
+ stub_request(:put, url).with(body: params).to_return(response)
606
+ end
607
+
608
+ # Stub all PUT /paths/:base_path requests
609
+ #
610
+ # @example
611
+ # stub_any_publishing_api_path_reservation
612
+ def stub_any_publishing_api_path_reservation
613
+ stub_request(:put, %r{\A#{PUBLISHING_API_ENDPOINT}/paths/}).to_return do |request|
614
+ base_path = request.uri.path.sub(%r{\A/paths}, "")
615
+ body = JSON.parse(request.body).merge(base_path:)
616
+ {
617
+ status: 200,
618
+ headers: { content_type: "application/json" },
619
+ body: body.to_json,
620
+ }
621
+ end
622
+ end
623
+
624
+ # Stub a PUT /paths/:base_path request for a particular publishing
625
+ # application. Calling for a different publishing application will return
626
+ # a 422 response.
627
+ #
628
+ # @example
629
+ # stub_publishing_api_has_path_reservation_for("/foo", "content-publisher")
630
+ #
631
+ # @param base_path [String]
632
+ # @param publishing_app [String]
633
+ def stub_publishing_api_has_path_reservation_for(path, publishing_app)
634
+ message = "#{path} is already reserved by #{publishing_app}"
635
+ error = { code: 422,
636
+ message: "Base path #{message}",
637
+ fields: { base_path: [message] } }
638
+
639
+ stub_request(:put, "#{PUBLISHING_API_ENDPOINT}/paths#{path}")
640
+ .to_return(status: 422,
641
+ headers: { content_type: "application/json" },
642
+ body: { error: }.to_json)
643
+
644
+ stub_request(:put, "#{PUBLISHING_API_ENDPOINT}/paths#{path}")
645
+ .with(body: { "publishing_app" => publishing_app })
646
+ .to_return(status: 200,
647
+ headers: { content_type: "application/json" },
648
+ body: { publishing_app:, base_path: path }.to_json)
649
+ end
650
+
651
+ # Stub a PUT /paths/:base_path request for a particular publishing
652
+ # application. Calling for a different publishing application will return
653
+ # a 422 response.
654
+ #
655
+ # @example
656
+ # stub_publishing_api_returns_path_reservation_validation_error_for(
657
+ # "/foo",
658
+ # "field" => ["error 1", "error 2"]
659
+ # )
660
+ #
661
+ # @param base_path [String]
662
+ # @param error_fields [Hash]
663
+ def stub_publishing_api_returns_path_reservation_validation_error_for(base_path, error_fields = {})
664
+ error_fields = { "base_path" => ["Computer says no"] } if error_fields.empty?
665
+
666
+ message = "#{error_fields.keys.first.to_s.capitalize.gsub(/_/, ' ')} #{error_fields.values.flatten.first}"
667
+
668
+ error = { code: 422, message:, fields: error_fields }
669
+
670
+ stub_request(:put, "#{PUBLISHING_API_ENDPOINT}/paths#{base_path}")
671
+ .to_return(status: 422,
672
+ headers: { content_type: "application/json" },
673
+ body: { error: }.to_json)
674
+ end
675
+
676
+ private
677
+
678
+ def stub_publishing_api_put(*args)
679
+ stub_publishing_api_postlike_call(:put, *args)
680
+ end
681
+
682
+ def stub_publishing_api_patch(*args)
683
+ stub_publishing_api_postlike_call(:patch, *args)
684
+ end
685
+
686
+ def stub_publishing_api_postlike_call(method, content_id, body, resource_path, override_response_hash = {})
687
+ response_hash = { status: 200, body: "{}", headers: { "Content-Type" => "application/json; charset=utf-8" } }
688
+ response_hash.merge!(override_response_hash)
689
+ response_hash[:body] = response_hash[:body].to_json if response_hash[:body].is_a?(Hash)
690
+ url = "#{PUBLISHING_API_ENDPOINT}#{resource_path}/#{content_id}"
691
+ stub_request(method, url).with(body:).to_return(response_hash)
692
+ end
693
+
694
+ def deep_stringify_keys(hash)
695
+ deep_transform_keys(hash, &:to_s)
696
+ end
697
+
698
+ def deep_transform_keys(object, &block)
699
+ case object
700
+ when Hash
701
+ object.each_with_object({}) do |(key, value), result|
702
+ result[yield(key)] = deep_transform_keys(value, &block)
703
+ end
704
+ when Array
705
+ object.map { |item| deep_transform_keys(item, &block) }
706
+ else
707
+ object
708
+ end
709
+ end
710
+
711
+ def resource_not_found(content_id, type)
712
+ {
713
+ error: {
714
+ code: 404,
715
+ message: "Could not find #{type} with content_id: #{content_id}",
716
+ },
717
+ }
718
+ end
719
+
720
+ def version_conflict(expected_version, actual_version = expected_version + 1)
721
+ {
722
+ error: {
723
+ code: 409,
724
+ 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}).",
725
+ fields: { previous_version: ["does not match"] },
726
+ },
727
+ }
728
+ end
729
+
730
+ def stub_publishing_api_unreserve_path_with_code(base_path, publishing_app, code)
731
+ url = "#{PUBLISHING_API_ENDPOINT}/paths#{base_path}"
732
+ body = { publishing_app: }
733
+ stub_request(:delete, url).with(body:).to_return(status: code, body: "{}", headers: { "Content-Type" => "application/json; charset=utf-8" })
734
+ end
735
+
736
+ def values_match_recursively(expected_value, actual_value)
737
+ case expected_value
738
+ when Hash
739
+ return false unless actual_value.is_a?(Hash)
740
+
741
+ expected_value.all? do |expected_sub_key, expected_sub_value|
742
+ actual_value.key?(expected_sub_key.to_s) &&
743
+ values_match_recursively(expected_sub_value, actual_value[expected_sub_key.to_s])
744
+ end
745
+ when Array
746
+ return false unless actual_value.is_a?(Array)
747
+ return false unless actual_value.size == expected_value.size
748
+
749
+ expected_value.each.with_index.all? do |expected_sub_value, i|
750
+ values_match_recursively(expected_sub_value, actual_value[i])
751
+ end
752
+ else
753
+ expected_value == actual_value
754
+ end
755
+ end
756
+
757
+ def content_item_for_publishing_api(base_path, publishing_app = "publisher")
758
+ content_item_for_base_path(base_path).merge("publishing_app" => publishing_app)
759
+ end
760
+ end
761
+ end
762
+ end
@@ -19,8 +19,8 @@ module PublishingPlatformApi
19
19
  stub_router_has_route(path, handler: "backend", backend_id:, disabled:, route_type:)
20
20
  end
21
21
 
22
- def stub_router_has_redirect_route(path, redirect_to:, route_type: "exact", disabled: false)
23
- stub_router_has_route(path, handler: "redirect", redirect_to:, disabled:, route_type:)
22
+ def stub_router_has_redirect_route(path, redirect_to:, redirect_type: "permanent", route_type: "exact", disabled: false)
23
+ stub_router_has_route(path, handler: "redirect", redirect_to:, redirect_type:, disabled:, route_type:)
24
24
  end
25
25
 
26
26
  def stub_router_has_gone_route(path, route_type: "exact", disabled: false)
@@ -42,13 +42,14 @@ module PublishingPlatformApi
42
42
  })
43
43
  end
44
44
 
45
- def stub_redirect_registration(path, type, destination, segments_mode = nil)
45
+ def stub_redirect_registration(path, type, destination, redirect_type, segments_mode = nil)
46
46
  stub_route_put({
47
47
  route: {
48
48
  incoming_path: path,
49
49
  route_type: type,
50
50
  handler: "redirect",
51
51
  redirect_to: destination,
52
+ redirect_type:,
52
53
  segments_mode:,
53
54
  },
54
55
  })
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PublishingPlatformApi
4
- VERSION = "0.8.3"
4
+ VERSION = "0.10.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: publishing_platform_api_adapters
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.3
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Publishing Platform
@@ -79,6 +79,20 @@ dependencies:
79
79
  - - "~>"
80
80
  - !ruby/object:Gem::Version
81
81
  version: '2.0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: climate_control
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
82
96
  - !ruby/object:Gem::Dependency
83
97
  name: publishing_platform_rubocop
84
98
  requirement: !ruby/object:Gem::Requirement
@@ -93,6 +107,76 @@ dependencies:
93
107
  - - ">="
94
108
  - !ruby/object:Gem::Version
95
109
  version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: publishing_platform_schemas
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ type: :development
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ - !ruby/object:Gem::Dependency
125
+ name: rack-test
126
+ requirement: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ type: :development
132
+ prerelease: false
133
+ version_requirements: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ - !ruby/object:Gem::Dependency
139
+ name: simplecov
140
+ requirement: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ type: :development
146
+ prerelease: false
147
+ version_requirements: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ - !ruby/object:Gem::Dependency
153
+ name: timecop
154
+ requirement: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
159
+ type: :development
160
+ prerelease: false
161
+ version_requirements: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ - !ruby/object:Gem::Dependency
167
+ name: webmock
168
+ requirement: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - ">="
171
+ - !ruby/object:Gem::Version
172
+ version: '0'
173
+ type: :development
174
+ prerelease: false
175
+ version_requirements: !ruby/object:Gem::Requirement
176
+ requirements:
177
+ - - ">="
178
+ - !ruby/object:Gem::Version
179
+ version: '0'
96
180
  description: Adapters to work with Publishing Platform APIs
97
181
  executables: []
98
182
  extensions: []
@@ -114,8 +198,11 @@ files:
114
198
  - lib/publishing_platform_api/railtie.rb
115
199
  - lib/publishing_platform_api/response.rb
116
200
  - lib/publishing_platform_api/router.rb
201
+ - lib/publishing_platform_api/test_helpers/common_responses.rb
117
202
  - lib/publishing_platform_api/test_helpers/content_item_helpers.rb
118
203
  - lib/publishing_platform_api/test_helpers/content_store.rb
204
+ - lib/publishing_platform_api/test_helpers/organisations.rb
205
+ - lib/publishing_platform_api/test_helpers/publishing_api.rb
119
206
  - lib/publishing_platform_api/test_helpers/router.rb
120
207
  - lib/publishing_platform_api/version.rb
121
208
  - lib/publishing_platform_api_adapters.rb