publishing_platform_api_adapters 0.9.0 → 0.11.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: 5ecd9f3eb97245e845c73fc3ff0c2328e1ffb836bdccb89bd6b8334642dd4c20
4
- data.tar.gz: 525e15f6248bc90427bd82be0b5e8f2373d20ab3fc0d39c3f127e68a5840b452
3
+ metadata.gz: 54dea58b3d1bcd3fe96e91cb75249bb29a240ba5ab41bba121292b695b8d160b
4
+ data.tar.gz: f345c52fbb488893dc130248ad994e80df33df0bd80d193cd757f05adaea9493
5
5
  SHA512:
6
- metadata.gz: ea0ce9e7e2db339d7d7cfd74345c0fb85a831cfbe0ab3178c4e0ea9dfc9b59132f8b61df51732d9d43785acab96c9882f3d715d283d4109fce3036f1a2453a22
7
- data.tar.gz: 497e045eb4399485d38db4fe80433f761f61b9c9c6f47179ae0d7f045edc9bbd558957d778c1392550adbb354b35ecf17a081982993e7aee2fddbe66cc8f2c38
6
+ metadata.gz: b0e418637bbe6e824c3e31e4334d69862e37c1eaddfaa56fc6e1c9caa4c57d7cf0f464878fbce80901664b9d563300156e1a0e268be14ab75d650046d164a4aa
7
+ data.tar.gz: 3133b2aa1676a494fe27c5593acdb1b0bc10d1b4933638cbbc4b14262cced73f4651149f1f7bd12151e8618ec7e2f7ffc856f953e663f82c5c64300f95d0f9ff
@@ -0,0 +1,156 @@
1
+ require_relative "base"
2
+ require_relative "exceptions"
3
+
4
+ # @api documented
5
+ class PublishingPlatformApi::AssetManager < PublishingPlatformApi::Base
6
+ # Creates an asset given a hash with one +file+ attribute
7
+ #
8
+ # Makes a +POST+ request to the asset manager api to create an asset.
9
+ #
10
+ # The asset must be provided as a +Hash+ with a +file+ attribute that
11
+ # behaves like a +File+ object. The +content-type+ that the asset manager will
12
+ # subsequently serve will be based on the file's extension (derived from
13
+ # +#path+). If you supply a +content-type+ via, for example
14
+ # +ActionDispatch::Http::UploadedFile+ or another multipart wrapper, it will
15
+ # be ignored. To provide a +content-type+ directly you must be specify it
16
+ # as a +content_type+ attribute of the hash.
17
+ #
18
+ # @param asset [Hash] The attributes for the asset to send to the api. Must
19
+ # contain +file+, which behaves like a +File+. All other attributes will be
20
+ # ignored.
21
+ # @return [PublishingPlatformApi::Response] The wrapped http response from the api. Behaves
22
+ # both as a +Hash+ and an +OpenStruct+, and responds to the following:
23
+ # :id the URL of the asset
24
+ # :name the filename of the asset that will be served
25
+ # :content_type the content_type of the asset
26
+ # :file_url the URL from which the asset will be served when it has
27
+ # passed a virus scan
28
+ # :state One of 'unscanned', 'clean', or 'infected'. Unless the state is
29
+ # 'clean' the asset at the :file_url will 404
30
+ #
31
+ # @raise [HTTPErrorResponse] if the request returns an error
32
+ #
33
+ # @example Upload a file from disk
34
+ # response = asset_manager.create_asset(file: File.new('image.jpg', 'r'))
35
+ # response['id'] #=> "http://asset-manager.dev.gov.uk/assets/576bbc52759b74196b000012"
36
+ # response['content_type'] #=> "image/jpeg"
37
+ # @example Upload a file from a Rails param, (typically a multipart wrapper)
38
+ # params[:file] #=> #<ActionDispatch::Http::UploadedFile:0x007fc60b43c5c8
39
+ # # @content_type="application/foofle",
40
+ # # @original_filename="cma_case_image.jpg",
41
+ # # @tempfile="spec/support/images/cma_case_image.jpg">
42
+ #
43
+ # # Though we sent a file with a +content_type+ of 'application/foofle',
44
+ # # this was ignored
45
+ # response = asset_manager.create_asset(file: params[:file])
46
+ # response['content_type'] #=> "image/jpeg"
47
+ def create_asset(asset)
48
+ post_multipart("#{base_url}/assets", asset:)
49
+ end
50
+
51
+ # Updates an asset given a hash with one +file+ attribute
52
+ #
53
+ # Makes a +PUT+ request to the asset manager api to update an asset.
54
+ #
55
+ # The asset must be provided as a +Hash+ with a +file+ attribute that
56
+ # behaves like a +File+ object. The +content-type+ of the file will be based
57
+ # on the files extension unless you specify a +content_type+ attribute of
58
+ # the hash to set it.
59
+ #
60
+ # @param id [String] The asset identifier (a UUID).
61
+ # @param asset [Hash] The attributes for the asset to send to the api. Must
62
+ # contain +file+, which behaves like a +File+. All other attributes will be
63
+ # ignored.
64
+ # @return [PublishingPlatformApi::Response] The wrapped http response from the api. Behaves
65
+ # both as a +Hash+ and an +OpenStruct+, and responds to the following:
66
+ # :id the URL of the asset
67
+ # :name the filename of the asset that will be served
68
+ # :content_type the content_type of the asset
69
+ # :file_url the URL from which the asset will be served when it has
70
+ # passed a virus scan
71
+ # :state One of 'unscanned', 'clean', or 'infected'. Unless the state is
72
+ # 'clean' the asset at the :file_url will 404
73
+ #
74
+ # @raise [HTTPErrorResponse] if the request returns an error
75
+ # @example Update a file from disk
76
+ # uuid = '594602dd-75b3-4e6f-b5d1-cacf8c4d4164'
77
+ # asset_manager.update_asset(uuid, file: File.new('image.jpg', 'r'))
78
+ def update_asset(id, asset)
79
+ put_multipart("#{base_url}/assets/#{id}", asset:)
80
+ end
81
+
82
+ # Fetches an asset's metadata given the id
83
+ #
84
+ # @param id [String] The asset identifier (a UUID).
85
+ # @return [PublishingPlatformApi::Response, nil] A response object containing the parsed JSON response. If
86
+ # the asset cannot be found, +nil+ wil be returned.
87
+ #
88
+ # @raise [HTTPErrorResponse] if the request returns an error
89
+ def asset(id)
90
+ get_json("#{base_url}/assets/#{id}")
91
+ end
92
+
93
+ # Deletes an asset given an id
94
+ #
95
+ # Makes a +DELETE+ request to the asset manager api to delete an asset.
96
+ #
97
+ # @param id [String] The asset identifier (a UUID).
98
+ # @return [PublishingPlatformApi::Response] The wrapped http response from the api. Behaves
99
+ # both as a +Hash+ and an +OpenStruct+, and responds to the following:
100
+ # :id the URL of the asset
101
+ # :name the filename of the asset that will be served
102
+ # :content_type the content_type of the asset
103
+ # :file_url the URL from which the asset will be served when it has
104
+ # passed a virus scan
105
+ # :state One of 'unscanned', 'clean', or 'infected'. Unless the state is
106
+ # 'clean' the asset at the :file_url will 404
107
+ #
108
+ # @raise [HTTPErrorResponse] if the request returns an error
109
+ # @example Delete a file from disk
110
+ # uuid = '594602dd-75b3-4e6f-b5d1-cacf8c4d4164'
111
+ # asset_manager.delete_asset(uuid)
112
+ def delete_asset(id)
113
+ delete_json("#{base_url}/assets/#{id}")
114
+ end
115
+
116
+ # Restores an asset given an id
117
+ #
118
+ # Makes a +POST+ request to the asset manager api to restore an asset.
119
+ #
120
+ # @param id [String] The asset identifier (a UUID).
121
+ # @return [PublishingPlatformApi::Response] The wrapped http response from the api. Behaves
122
+ # both as a +Hash+ and an +OpenStruct+, and responds to the following:
123
+ # :id the URL of the asset
124
+ # :name the filename of the asset that will be served
125
+ # :content_type the content_type of the asset
126
+ # :file_url the URL from which the asset will be served when it has
127
+ # passed a virus scan
128
+ # :state One of 'unscanned', 'clean', or 'infected'. Unless the state is
129
+ # 'clean' the asset at the :file_url will 404
130
+ #
131
+ # @raise [HTTPErrorResponse] if the request returns an error
132
+ # @example Restore a deleted file
133
+ # uuid = '594602dd-75b3-4e6f-b5d1-cacf8c4d4164'
134
+ # asset_manager.restore_asset(uuid)
135
+ def restore_asset(id)
136
+ post_json("#{base_url}/assets/#{id}/restore")
137
+ end
138
+
139
+ # Fetches an asset given the id and filename
140
+ #
141
+ # @param id [String] The asset identifier.
142
+ # @param filename [String] Filename of the asset.
143
+ # @return [PublishingPlatformApi::Response] A response object containing the raw asset.
144
+ # If the asset cannot be found, +PublishingPlatformApi::HTTPNotFound+ will be raised.
145
+ #
146
+ # @raise [HTTPErrorResponse] if the request returns an error
147
+ def media(id, filename)
148
+ get_raw("#{base_url}/media/#{id}/#{filename}")
149
+ end
150
+
151
+ private
152
+
153
+ def base_url
154
+ endpoint
155
+ end
156
+ end
@@ -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,100 @@
1
+ module PublishingPlatformApi
2
+ module TestHelpers
3
+ module AssetManager
4
+ ASSET_MANAGER_ENDPOINT = PublishingPlatformLocation.find("asset-manager")
5
+
6
+ def stub_any_asset_manager_call
7
+ stub_request(:any, %r{\A#{ASSET_MANAGER_ENDPOINT}}).to_return(status: 200)
8
+ end
9
+
10
+ def stub_asset_manager_isnt_available
11
+ stub_request(:any, %r{\A#{ASSET_MANAGER_ENDPOINT}}).to_return(status: 503)
12
+ end
13
+
14
+ def stub_asset_manager_updates_any_asset(body = {})
15
+ stub_request(:put, %r{\A#{ASSET_MANAGER_ENDPOINT}/assets})
16
+ .to_return(body: body.to_json, status: 200)
17
+ end
18
+
19
+ def stub_asset_manager_deletes_any_asset(body = {})
20
+ stub_request(:delete, %r{\A#{ASSET_MANAGER_ENDPOINT}/assets})
21
+ .to_return(body: body.to_json, status: 200)
22
+ end
23
+
24
+ def stub_asset_manager_has_an_asset(id, atts, filename = "")
25
+ response = atts.merge("_response_info" => { "status" => "ok" })
26
+
27
+ stub_request(:get, "#{ASSET_MANAGER_ENDPOINT}/assets/#{id}")
28
+ .to_return(body: response.to_json, status: 200)
29
+
30
+ stub_request(:get, "#{ASSET_MANAGER_ENDPOINT}/media/#{id}/#{filename}")
31
+ .to_return(body: "Some file content", status: 200)
32
+ end
33
+
34
+ def stub_asset_manager_does_not_have_an_asset(id)
35
+ response = {
36
+ "_response_info" => { "status" => "not found" },
37
+ }
38
+
39
+ stub_request(:any, "#{ASSET_MANAGER_ENDPOINT}/assets/#{id}")
40
+ .to_return(body: response.to_json, status: 404)
41
+ end
42
+
43
+ # This can take a string of an exact url or a hash of options
44
+ #
45
+ # with a string:
46
+ # `stub_asset_manager_receives_an_asset("https://asset-manager/media/619ce797-b415-42e5-b2b1-2ffa0df52302/file.jpg")`
47
+ #
48
+ # with a hash:
49
+ # `stub_asset_manager_receives_an_asset(id: "20d04259-e3ae-4f71-8157-e6c843096e96", filename: "file.jpg")`
50
+ # which would return a file url of "https://asset-manager/media/20d04259-e3ae-4f71-8157-e6c843096e96/file.jpg"
51
+ #
52
+ # with no argument
53
+ #
54
+ # `stub_asset_manager_receives_an_asset`
55
+ # which would return a file url of "https://asset-manager/media/0053adbf-0737-4923-9d8a-8180f2c723af/0d19136c4a94f07"
56
+ def stub_asset_manager_receives_an_asset(response_url = {})
57
+ stub_request(:post, "#{ASSET_MANAGER_ENDPOINT}/assets").to_return do
58
+ if response_url.is_a?(String)
59
+ file_url = response_url
60
+ else
61
+ options = {
62
+ id: SecureRandom.uuid,
63
+ filename: SecureRandom.hex(8),
64
+ }.merge(response_url)
65
+
66
+ file_url = "#{ASSET_MANAGER_ENDPOINT}/media/#{options[:id]}/#{options[:filename]}"
67
+ end
68
+ { body: { file_url: }.to_json, status: 200 }
69
+ end
70
+ end
71
+
72
+ def stub_asset_manager_upload_failure
73
+ stub_request(:post, "#{ASSET_MANAGER_ENDPOINT}/assets").to_return(status: 500)
74
+ end
75
+
76
+ def stub_asset_manager_update_asset(asset_id, body = {})
77
+ stub_request(:put, "#{ASSET_MANAGER_ENDPOINT}/assets/#{asset_id}")
78
+ .to_return(body: body.to_json, status: 200)
79
+ end
80
+
81
+ def stub_asset_manager_update_asset_failure(asset_id)
82
+ stub_request(:put, "#{ASSET_MANAGER_ENDPOINT}/assets/#{asset_id}").to_return(status: 500)
83
+ end
84
+
85
+ def stub_asset_manager_delete_asset(asset_id, body = {})
86
+ stub_request(:delete, "#{ASSET_MANAGER_ENDPOINT}/assets/#{asset_id}")
87
+ .to_return(body: body.to_json, status: 200)
88
+ end
89
+
90
+ def stub_asset_manager_delete_asset_missing(asset_id)
91
+ stub_request(:delete, "#{ASSET_MANAGER_ENDPOINT}/assets/#{asset_id}")
92
+ .to_return(status: 404)
93
+ end
94
+
95
+ def stub_asset_manager_delete_asset_failure(asset_id)
96
+ stub_request(:delete, "#{ASSET_MANAGER_ENDPOINT}/assets/#{asset_id}").to_return(status: 500)
97
+ end
98
+ end
99
+ end
100
+ end
@@ -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