gds-api-adapters 69.3.0 → 71.3.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: 36d53c764d0ec5abb1685e55ff91057da30c9ce250dd46c4d5ae350b7684f66c
4
- data.tar.gz: 50687996dcb29b5ca7d8e0ec5cc3fca4ef40228ea2700d901528b9b2f98b30c4
3
+ metadata.gz: ea739e39026f239c010c4810ba88c6c0f8f77a0fb2375e24c9026cd2a388465e
4
+ data.tar.gz: c18db19adc96aab183ab99edfc30db4691d7ab5d055def3fbcfc406f6b16c1a8
5
5
  SHA512:
6
- metadata.gz: 1c5cb0ce0c5bd0147374a6abff9a1e121ce811775b5120c73fc319df47aebd8c4acdbe264f3507be9e91b19e0a01e55e0711e9538237c940ad96f9de9ce9b08e
7
- data.tar.gz: 6584391dac6990ab4adeedc04491a1a9dec7f7ced21e8adc0da65e7c7fcea2bb242ac56e194e8b6572039a9917b9c7b78601899e822b101ba89f294fbb3a7d34
6
+ metadata.gz: 1b03304634769b69d8549664720619081836366bacb76948e19275751b0f4d2482f0adf14977b82569e4e265b081e590c338f9a2167e78170bab9af0f8c27a68
7
+ data.tar.gz: b5dfc1690a1e783e67bf350d2d5fb8d66c9aa0eaaa758bdbb7be381cd34a76780de465c9bfc78b539c9712ba3e6b586788f7139edab07aa8fab7ea2d6ae974f9
data/Rakefile CHANGED
@@ -30,5 +30,5 @@ end
30
30
 
31
31
  desc "Run the linter against changed files"
32
32
  task :lint do
33
- sh "bundle exec rubocop --format clang"
33
+ sh "bundle exec rubocop"
34
34
  end
data/lib/gds_api.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require "addressable"
2
2
  require "plek"
3
3
  require "time"
4
+ require "gds_api/account_api"
4
5
  require "gds_api/asset_manager"
5
6
  require "gds_api/calendars"
6
7
  require "gds_api/content_store"
@@ -21,6 +22,19 @@ require "gds_api/worldwide"
21
22
 
22
23
  # @api documented
23
24
  module GdsApi
25
+ # Creates a GdsApi::AccountApi adapter
26
+ #
27
+ # This will set a bearer token if a ACCOUNT_API_BEARER_TOKEN environment
28
+ # variable is set
29
+ #
30
+ # @return [GdsApi::AccountApi]
31
+ def self.account_api(options = {})
32
+ GdsApi::AccountApi.new(
33
+ Plek.find("account-api"),
34
+ { bearer_token: ENV["ACCOUNT_API_BEARER_TOKEN"] }.merge(options),
35
+ )
36
+ end
37
+
24
38
  # Creates a GdsApi::AssetManager adapter
25
39
  #
26
40
  # This will set a bearer token if a ASSET_MANAGER_BEARER_TOKEN environment
@@ -0,0 +1,157 @@
1
+ require_relative "base"
2
+ require_relative "exceptions"
3
+
4
+ # Adapter for the Account API
5
+ #
6
+ # @see https://github.com/alphagov/account-api
7
+ # @api documented
8
+ class GdsApi::AccountApi < GdsApi::Base
9
+ AUTH_HEADER_NAME = "GOVUK-Account-Session".freeze
10
+
11
+ # Get an OAuth sign-in URL to redirect the user to
12
+ #
13
+ # @param [String, nil] redirect_path path on GOV.UK to send the user to after authentication
14
+ # @param [String, nil] state_id identifier originally returned by #create_registration_state
15
+ # @param [String, nil] level_of_authentication either "level1" (require MFA) or "level0" (do not require MFA)
16
+ #
17
+ # @return [Hash] An authentication URL and the OAuth state parameter (for CSRF protection)
18
+ def get_sign_in_url(redirect_path: nil, state_id: nil, level_of_authentication: nil)
19
+ querystring = nested_query_string(
20
+ {
21
+ redirect_path: redirect_path,
22
+ state_id: state_id,
23
+ level_of_authentication: level_of_authentication,
24
+ }.compact,
25
+ )
26
+ get_json("#{endpoint}/api/oauth2/sign-in?#{querystring}")
27
+ end
28
+
29
+ # Validate an OAuth authentication response
30
+ #
31
+ # @param [String] code The OAuth code parameter, from the auth server.
32
+ # @param [String] state The OAuth state parameter, from the auth server.
33
+ #
34
+ # @return [Hash] The value for the govuk_account_session header, the path to redirect the user to, and the GA client ID (if there is one)
35
+ def validate_auth_response(code:, state:)
36
+ post_json("#{endpoint}/api/oauth2/callback", code: code, state: state)
37
+ end
38
+
39
+ # Register some initial state, to pass to get_sign_in_url, which is used to initialise the account if the user signs up
40
+ #
41
+ # @param [Hash, nil] attributes Initial attributes to store
42
+ #
43
+ # @return [Hash] The state ID to pass to get_sign_in_url
44
+ def create_registration_state(attributes:)
45
+ post_json("#{endpoint}/api/oauth2/state", attributes: attributes)
46
+ end
47
+
48
+ # Get all the information about a user needed to render the account home page
49
+ #
50
+ # @param [String] govuk_account_session Value of the session header
51
+ #
52
+ # @return [Hash] Information about the user and the services they've used, and a new session header
53
+ def get_user(govuk_account_session:)
54
+ get_json("#{endpoint}/api/user", auth_headers(govuk_account_session))
55
+ end
56
+
57
+ # Check if a user has an email subscription for the Transition Checker
58
+ #
59
+ # @param [String] govuk_account_session Value of the session header
60
+ #
61
+ # @return [Hash] Whether the user has a subscription, and a new session header
62
+ def check_for_email_subscription(govuk_account_session:)
63
+ get_json("#{endpoint}/api/transition-checker-email-subscription", auth_headers(govuk_account_session))
64
+ end
65
+
66
+ # Create or update a user's email subscription for the Transition Checker
67
+ #
68
+ # @param [String] govuk_account_session Value of the session header
69
+ # @param [String] slug The email topic slug
70
+ #
71
+ # @return [Hash] Whether the user has a subscription, and a new session header
72
+ def set_email_subscription(govuk_account_session:, slug:)
73
+ post_json("#{endpoint}/api/transition-checker-email-subscription", { slug: slug }, auth_headers(govuk_account_session))
74
+ end
75
+
76
+ # Look up the values of a user's attributes
77
+ #
78
+ # @param [String] attributes Names of the attributes to check
79
+ # @param [String] govuk_account_session Value of the session header
80
+ #
81
+ # @return [Hash] The attribute values (if present), and a new session header
82
+ def get_attributes(attributes:, govuk_account_session:)
83
+ querystring = nested_query_string({ attributes: attributes }.compact)
84
+ get_json("#{endpoint}/api/attributes?#{querystring}", auth_headers(govuk_account_session))
85
+ end
86
+
87
+ # Create or update attributes for a user
88
+ #
89
+ # @param [String] attributes Hash of new attribute values
90
+ # @param [String] govuk_account_session Value of the session header
91
+ #
92
+ # @return [Hash] A new session header
93
+ def set_attributes(attributes:, govuk_account_session:)
94
+ patch_json("#{endpoint}/api/attributes", { attributes: attributes }, auth_headers(govuk_account_session))
95
+ end
96
+
97
+ # Look up the names of a user's attributes
98
+ #
99
+ # @param [String] attributes Names of the attributes to check
100
+ # @param [String] govuk_account_session Value of the session header
101
+ #
102
+ # @return [Hash] The attribute names (if present), and a new session header
103
+ def get_attributes_names(attributes:, govuk_account_session:)
104
+ querystring = nested_query_string({ attributes: attributes }.compact)
105
+ get_json("#{endpoint}/api/attributes/names?#{querystring}", auth_headers(govuk_account_session))
106
+ end
107
+
108
+ # Look up all pages saved by a user in their Account
109
+ #
110
+ # @param [String] govuk_account_session Value of the session header
111
+ #
112
+ # @return [Hash] containing :saved_pages, an array of single saved page hashes def get_saved_pages(govuk_account_session:)
113
+ def get_saved_pages(govuk_account_session:)
114
+ get_json("#{endpoint}/api/saved_pages", auth_headers(govuk_account_session))
115
+ end
116
+
117
+ # Return a single page by unique URL
118
+ #
119
+ # @param [String] the path of a page to check
120
+ # @param [String] govuk_account_session Value of the session header
121
+ #
122
+ # @return [Hash] containing :saved_page, a hash of a single saved page value
123
+ def get_saved_page(page_path:, govuk_account_session:)
124
+ get_json("#{endpoint}/api/saved_pages/#{CGI.escape(page_path)}", auth_headers(govuk_account_session))
125
+ end
126
+
127
+ # Upsert a single saved page entry in a users account
128
+ #
129
+ # @param [String] the path of a page to check
130
+ # @param [String] govuk_account_session Value of the session header
131
+ #
132
+ # @return [Hash] A single saved page value (if sucessful)
133
+ def save_page(page_path:, govuk_account_session:)
134
+ put_json("#{endpoint}/api/saved_pages/#{CGI.escape(page_path)}", {}, auth_headers(govuk_account_session))
135
+ end
136
+
137
+ # Delete a single saved page entry from a users account
138
+ #
139
+ # @param [String] the path of a page to check
140
+ # @param [String] govuk_account_session Value of the session header
141
+ #
142
+ # @return [GdsApi::Response] A status code of 204 indicates the saved page has been successfully deleted.
143
+ # A status code of 404 indicates there is no saved page with this path.
144
+ def delete_saved_page(page_path:, govuk_account_session:)
145
+ delete_json("#{endpoint}/api/saved_pages/#{CGI.escape(page_path)}", {}, auth_headers(govuk_account_session))
146
+ end
147
+
148
+ private
149
+
150
+ def nested_query_string(params)
151
+ Rack::Utils.build_nested_query(params)
152
+ end
153
+
154
+ def auth_headers(govuk_account_session)
155
+ { AUTH_HEADER_NAME => govuk_account_session }
156
+ end
157
+ end
data/lib/gds_api/base.rb CHANGED
@@ -71,7 +71,7 @@ private
71
71
  case value
72
72
  when Array
73
73
  value.map do |v|
74
- "#{CGI.escape(key.to_s + '[]')}=#{CGI.escape(v.to_s)}"
74
+ "#{CGI.escape("#{key}[]")}=#{CGI.escape(v.to_s)}"
75
75
  end
76
76
  else
77
77
  "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}"
@@ -93,7 +93,7 @@ private
93
93
  def prefix_destination(redirect, path, query)
94
94
  uri = URI.parse(redirect["destination"])
95
95
  start_char = redirect["path"].length
96
- suffix = path[start_char..-1]
96
+ suffix = path[start_char..]
97
97
 
98
98
  if uri.path == "" && suffix[0] != "/"
99
99
  uri.path = "/#{suffix}"
@@ -14,49 +14,66 @@ module GdsApi
14
14
  end
15
15
 
16
16
  class EndpointNotFound < BaseError; end
17
+
17
18
  class TimedOutException < BaseError; end
19
+
18
20
  class InvalidUrl < BaseError; end
21
+
19
22
  class SocketErrorException < BaseError; end
20
23
 
21
24
  # Superclass for all 4XX and 5XX errors
22
25
  class HTTPErrorResponse < BaseError
23
- attr_accessor :code, :error_details
26
+ attr_accessor :code, :error_details, :http_body
24
27
 
25
- def initialize(code, message = nil, error_details = nil)
28
+ def initialize(code, message = nil, error_details = nil, http_body = nil)
26
29
  super(message)
27
30
  @code = code
28
31
  @error_details = error_details
32
+ @http_body = http_body
29
33
  end
30
34
  end
31
35
 
32
36
  # Superclass & fallback for all 4XX errors
33
37
  class HTTPClientError < HTTPErrorResponse; end
38
+
34
39
  class HTTPIntermittentClientError < HTTPClientError; end
35
40
 
36
41
  class HTTPNotFound < HTTPClientError; end
42
+
37
43
  class HTTPGone < HTTPClientError; end
44
+
38
45
  class HTTPPayloadTooLarge < HTTPClientError; end
46
+
39
47
  class HTTPUnauthorized < HTTPClientError; end
48
+
40
49
  class HTTPForbidden < HTTPClientError; end
50
+
41
51
  class HTTPConflict < HTTPClientError; end
52
+
42
53
  class HTTPUnprocessableEntity < HTTPClientError; end
54
+
43
55
  class HTTPBadRequest < HTTPClientError; end
56
+
44
57
  class HTTPTooManyRequests < HTTPIntermittentClientError; end
45
58
 
46
59
  # Superclass & fallback for all 5XX errors
47
60
  class HTTPServerError < HTTPErrorResponse; end
61
+
48
62
  class HTTPIntermittentServerError < HTTPServerError; end
49
63
 
50
64
  class HTTPInternalServerError < HTTPServerError; end
65
+
51
66
  class HTTPBadGateway < HTTPIntermittentServerError; end
67
+
52
68
  class HTTPUnavailable < HTTPIntermittentServerError; end
69
+
53
70
  class HTTPGatewayTimeout < HTTPIntermittentServerError; end
54
71
 
55
72
  module ExceptionHandling
56
73
  def build_specific_http_error(error, url, details = nil)
57
74
  message = "URL: #{url}\nResponse body:\n#{error.http_body}"
58
75
  code = error.http_code
59
- error_class_for_code(code).new(code, message, details)
76
+ error_class_for_code(code).new(code, message, details, error.http_body)
60
77
  end
61
78
 
62
79
  def error_class_for_code(code)
@@ -101,10 +101,10 @@ module GdsApi
101
101
  rescue RestClient::Exception => e
102
102
  # Attempt to parse the body as JSON if possible
103
103
  error_details = begin
104
- e.http_body ? JSON.parse(e.http_body) : nil
105
- rescue JSON::ParserError
106
- nil
107
- end
104
+ e.http_body ? JSON.parse(e.http_body) : nil
105
+ rescue JSON::ParserError
106
+ nil
107
+ end
108
108
  raise build_specific_http_error(e, url, error_details)
109
109
  end
110
110
 
@@ -180,10 +180,10 @@ module GdsApi
180
180
  raise GdsApi::EndpointNotFound, "Could not connect to #{url}"
181
181
  rescue RestClient::Exceptions::Timeout => e
182
182
  logger.error loggable.merge(status: "timeout", error_message: e.message, error_class: e.class.name, end_time: Time.now.to_f).to_json
183
- raise GdsApi::TimedOutException
183
+ raise GdsApi::TimedOutException, e.message
184
184
  rescue URI::InvalidURIError => e
185
185
  logger.error loggable.merge(status: "invalid_uri", error_message: e.message, error_class: e.class.name, end_time: Time.now.to_f).to_json
186
- raise GdsApi::InvalidUrl
186
+ raise GdsApi::InvalidUrl, e.message
187
187
  rescue RestClient::Exception => e
188
188
  # Log the error here, since we have access to loggable, but raise the
189
189
  # exception up to the calling method to deal with
@@ -192,10 +192,10 @@ module GdsApi
192
192
  raise
193
193
  rescue Errno::ECONNRESET => e
194
194
  logger.error loggable.merge(status: "connection_reset", error_message: e.message, error_class: e.class.name, end_time: Time.now.to_f).to_json
195
- raise GdsApi::TimedOutException
195
+ raise GdsApi::TimedOutException, e.message
196
196
  rescue SocketError => e
197
197
  logger.error loggable.merge(status: "socket_error", error_message: e.message, error_class: e.class.name, end_time: Time.now.to_f).to_json
198
- raise GdsApi::SocketErrorException
198
+ raise GdsApi::SocketErrorException, e.message
199
199
  end
200
200
  end
201
201
  end
@@ -89,37 +89,6 @@ class GdsApi::LinkCheckerApi < GdsApi::Base
89
89
  )
90
90
  end
91
91
 
92
- # Update or create a set of links to be monitored for a resource.
93
- #
94
- # Makes a +POST+ request to the link checker api to create a resource monitor.
95
- #
96
- # @param links [Array] A list of URIs to monitor.
97
- # @param reference [String] A unique id for the resource being monitored
98
- # @param app [String] The name of the service the call originated e.g. 'whitehall'
99
- # @return [MonitorReport] A +SimpleDelegator+ of the +GdsApi::Response+ which
100
- # responds to:
101
- # :id the ID of the created resource monitor
102
- #
103
- # @raise [HTTPErrorResponse] if the request returns an error
104
-
105
- def upsert_resource_monitor(links, app, reference)
106
- payload = {
107
- links: links,
108
- app: app,
109
- reference: reference,
110
- }
111
-
112
- response = post_json("#{endpoint}/monitor", payload)
113
-
114
- MonitorReport.new(response.to_hash)
115
- end
116
-
117
- class MonitorReport < SimpleDelegator
118
- def id
119
- self["id"]
120
- end
121
- end
122
-
123
92
  class LinkReport < SimpleDelegator
124
93
  def uri
125
94
  self["uri"]
@@ -24,6 +24,7 @@ module GdsApi
24
24
  PATTERN = /([-a-z]+)(?:\s*=\s*([^,\s]+))?,?+/i.freeze
25
25
 
26
26
  def initialize(value = nil)
27
+ super()
27
28
  parse(value)
28
29
  end
29
30
 
@@ -67,7 +67,7 @@ module GdsApi
67
67
  #
68
68
  # @param args [Hash] A valid search query. See search-api documentation for options.
69
69
  #
70
- # @see https://github.com/alphagov/search-api/blob/master/doc/search-api.md
70
+ # @see https://github.com/alphagov/search-api/blob/master/docs/search-api.md
71
71
  def search(args, additional_headers = {})
72
72
  request_url = "#{base_url}/search.json?#{Rack::Utils.build_nested_query(args)}"
73
73
  get_json(request_url, additional_headers)
@@ -77,7 +77,7 @@ module GdsApi
77
77
  #
78
78
  # @param searches [Array] An array valid search queries. Maximum of 6. See search-api documentation for options.
79
79
  #
80
- # # @see https://github.com/alphagov/search-api/blob/master/doc/search-api.md
80
+ # # @see https://github.com/alphagov/search-api/blob/master/docs/search-api.md
81
81
  def batch_search(searches, additional_headers = {})
82
82
  url_friendly_searches = searches.each_with_index.map do |search, index|
83
83
  { index => search }
@@ -95,7 +95,7 @@ module GdsApi
95
95
  # @param args [Hash] A valid search query. See search-api documentation for options.
96
96
  # @param page_size [Integer] Number of results in each page.
97
97
  #
98
- # @see https://github.com/alphagov/search-api/blob/master/doc/search-api.md
98
+ # @see https://github.com/alphagov/search-api/blob/master/docs/search-api.md
99
99
  def search_enum(args, page_size: 100, additional_headers: {})
100
100
  Enumerator.new do |yielder|
101
101
  (0..Float::INFINITY).step(page_size).each do |index|
@@ -120,7 +120,7 @@ module GdsApi
120
120
  # GOV.UK - we only allow deletion from metasearch
121
121
  # @return [GdsApi::Response] A status code of 202 indicates the document has been successfully queued.
122
122
  #
123
- # @see https://github.com/alphagov/search-api/blob/master/doc/documents.md
123
+ # @see https://github.com/alphagov/search-api/blob/master/docs/documents.md
124
124
  def add_document(*args)
125
125
  @api.add_document(*args)
126
126
  end
@@ -132,7 +132,7 @@ module GdsApi
132
132
  # and contacts, which may be deleted with `delete_document`.
133
133
  #
134
134
  # @param base_path Base path of the page on GOV.UK.
135
- # @see https://github.com/alphagov/search-api/blob/master/doc/content-api.md
135
+ # @see https://github.com/alphagov/search-api/blob/master/docs/content-api.md
136
136
  def delete_content(base_path)
137
137
  request_url = "#{base_url}/content?link=#{base_path}"
138
138
  delete_json(request_url)
@@ -145,7 +145,7 @@ module GdsApi
145
145
  # and contacts.
146
146
  #
147
147
  # @param base_path [String] Base path of the page on GOV.UK.
148
- # @see https://github.com/alphagov/search-api/blob/master/doc/content-api.md
148
+ # @see https://github.com/alphagov/search-api/blob/master/docs/content-api.md
149
149
  def get_content(base_path)
150
150
  request_url = "#{base_url}/content?link=#{base_path}"
151
151
  get_json(request_url)
@@ -0,0 +1,380 @@
1
+ require "json"
2
+
3
+ module GdsApi
4
+ module TestHelpers
5
+ module AccountApi
6
+ ACCOUNT_API_ENDPOINT = Plek.find("account-api")
7
+
8
+ def stub_account_api_get_sign_in_url(redirect_path: nil, state_id: nil, level_of_authentication: nil, auth_uri: "http://auth/provider", state: "state")
9
+ querystring = Rack::Utils.build_nested_query({ redirect_path: redirect_path, state_id: state_id, level_of_authentication: level_of_authentication }.compact)
10
+ stub_request(:get, "#{ACCOUNT_API_ENDPOINT}/api/oauth2/sign-in?#{querystring}")
11
+ .to_return(
12
+ status: 200,
13
+ body: { auth_uri: auth_uri, state: state }.to_json,
14
+ )
15
+ end
16
+
17
+ def stub_account_api_validates_auth_response(code: nil, state: nil, govuk_account_session: "govuk-account-session", redirect_path: "/", ga_client_id: "ga-client-id")
18
+ stub_request(:post, "#{ACCOUNT_API_ENDPOINT}/api/oauth2/callback")
19
+ .with(body: hash_including({ code: code, state: state }.compact))
20
+ .to_return(
21
+ status: 200,
22
+ body: { govuk_account_session: govuk_account_session, redirect_path: redirect_path, ga_client_id: ga_client_id }.to_json,
23
+ )
24
+ end
25
+
26
+ def stub_account_api_rejects_auth_response(code: nil, state: nil)
27
+ stub_request(:post, "#{ACCOUNT_API_ENDPOINT}/api/oauth2/callback")
28
+ .with(body: hash_including({ code: code, state: state }.compact))
29
+ .to_return(status: 401)
30
+ end
31
+
32
+ def stub_account_api_create_registration_state(attributes: nil, state_id: "state-id")
33
+ stub_request(:post, "#{ACCOUNT_API_ENDPOINT}/api/oauth2/state")
34
+ .with(body: hash_including({ attributes: attributes }.compact))
35
+ .to_return(
36
+ status: 200,
37
+ body: { state_id: state_id }.to_json,
38
+ )
39
+ end
40
+
41
+ def stub_account_api_user_info(level_of_authentication: "level0", email: "email@example.com", email_verified: true, services: {}, **options)
42
+ stub_account_api_request(
43
+ :get,
44
+ "/api/user",
45
+ response_body: {
46
+ level_of_authentication: level_of_authentication,
47
+ email: email,
48
+ email_verified: email_verified,
49
+ services: services,
50
+ },
51
+ **options,
52
+ )
53
+ end
54
+
55
+ def stub_account_api_user_info_service_state(service:, service_state: "yes", **options)
56
+ stub_account_api_user_info(
57
+ **options.merge(
58
+ services: options.fetch(:services, {}).merge(service => service_state),
59
+ ),
60
+ )
61
+ end
62
+
63
+ def stub_account_api_unauthorized_user_info(**options)
64
+ stub_account_api_request(
65
+ :get,
66
+ "/api/user",
67
+ response_status: 401,
68
+ **options,
69
+ )
70
+ end
71
+
72
+ def stub_account_api_has_email_subscription(**options)
73
+ stub_account_api_request(
74
+ :get,
75
+ "/api/transition-checker-email-subscription",
76
+ response_body: { has_subscription: true },
77
+ **options,
78
+ )
79
+ end
80
+
81
+ def stub_account_api_does_not_have_email_subscription(**options)
82
+ stub_account_api_request(
83
+ :get,
84
+ "/api/transition-checker-email-subscription",
85
+ response_body: { has_subscription: false },
86
+ **options,
87
+ )
88
+ end
89
+
90
+ def stub_account_api_unauthorized_get_email_subscription(**options)
91
+ stub_account_api_request(
92
+ :get,
93
+ "/api/transition-checker-email-subscription",
94
+ response_status: 401,
95
+ **options,
96
+ )
97
+ end
98
+
99
+ def stub_account_api_forbidden_get_email_subscription(needed_level_of_authentication: "level1", **options)
100
+ stub_account_api_request(
101
+ :get,
102
+ "/api/transition-checker-email-subscription",
103
+ response_status: 403,
104
+ response_body: { needed_level_of_authentication: needed_level_of_authentication },
105
+ **options,
106
+ )
107
+ end
108
+
109
+ def stub_account_api_set_email_subscription(slug: nil, **options)
110
+ stub_account_api_request(
111
+ :post,
112
+ "/api/transition-checker-email-subscription",
113
+ with: { body: hash_including({ slug: slug }.compact) },
114
+ **options,
115
+ )
116
+ end
117
+
118
+ def stub_account_api_unauthorized_set_email_subscription(slug: nil, **options)
119
+ stub_account_api_request(
120
+ :post,
121
+ "/api/transition-checker-email-subscription",
122
+ with: { body: hash_including({ slug: slug }.compact) },
123
+ response_status: 401,
124
+ **options,
125
+ )
126
+ end
127
+
128
+ def stub_account_api_forbidden_set_email_subscription(slug: nil, needed_level_of_authentication: "level1", **options)
129
+ stub_account_api_request(
130
+ :post,
131
+ "/api/transition-checker-email-subscription",
132
+ with: { body: hash_including({ slug: slug }.compact) },
133
+ response_status: 403,
134
+ response_body: { needed_level_of_authentication: needed_level_of_authentication },
135
+ **options,
136
+ )
137
+ end
138
+
139
+ def stub_account_api_has_attributes(attributes: [], values: {}, **options)
140
+ querystring = Rack::Utils.build_nested_query({ attributes: attributes }.compact)
141
+ stub_account_api_request(
142
+ :get,
143
+ "/api/attributes?#{querystring}",
144
+ response_body: { values: values },
145
+ **options,
146
+ )
147
+ end
148
+
149
+ def stub_account_api_unauthorized_has_attributes(attributes: [], **options)
150
+ querystring = Rack::Utils.build_nested_query({ attributes: attributes }.compact)
151
+ stub_account_api_request(
152
+ :get,
153
+ "/api/attributes?#{querystring}",
154
+ response_status: 401,
155
+ **options,
156
+ )
157
+ end
158
+
159
+ def stub_account_api_forbidden_has_attributes(attributes: [], needed_level_of_authentication: "level1", **options)
160
+ querystring = Rack::Utils.build_nested_query({ attributes: attributes }.compact)
161
+ stub_account_api_request(
162
+ :get,
163
+ "/api/attributes?#{querystring}",
164
+ response_status: 403,
165
+ response_body: { needed_level_of_authentication: needed_level_of_authentication },
166
+ **options,
167
+ )
168
+ end
169
+
170
+ def stub_account_api_set_attributes(attributes: nil, **options)
171
+ stub_account_api_request(
172
+ :patch,
173
+ "/api/attributes",
174
+ with: { body: hash_including({ attributes: attributes }.compact) },
175
+ **options,
176
+ )
177
+ end
178
+
179
+ def stub_account_api_unauthorized_set_attributes(attributes: nil, **options)
180
+ stub_account_api_request(
181
+ :patch,
182
+ "/api/attributes",
183
+ with: { body: hash_including({ attributes: attributes }.compact) },
184
+ response_status: 401,
185
+ **options,
186
+ )
187
+ end
188
+
189
+ def stub_account_api_forbidden_set_attributes(attributes: nil, needed_level_of_authentication: "level1", **options)
190
+ stub_account_api_request(
191
+ :patch,
192
+ "/api/attributes",
193
+ with: { body: hash_including({ attributes: attributes }.compact) },
194
+ response_status: 403,
195
+ response_body: { needed_level_of_authentication: needed_level_of_authentication },
196
+ **options,
197
+ )
198
+ end
199
+
200
+ def stub_account_api_get_attributes_names(attributes: [], **options)
201
+ querystring = Rack::Utils.build_nested_query({ attributes: attributes }.compact)
202
+ stub_account_api_request(
203
+ :get,
204
+ "/api/attributes/names?#{querystring}",
205
+ response_body: { values: attributes },
206
+ **options,
207
+ )
208
+ end
209
+
210
+ def stub_account_api_unauthorized_get_attributes_names(attributes: [], **options)
211
+ querystring = Rack::Utils.build_nested_query({ attributes: attributes }.compact)
212
+ stub_account_api_request(
213
+ :get,
214
+ "/api/attributes/names?#{querystring}",
215
+ response_status: 401,
216
+ **options,
217
+ )
218
+ end
219
+
220
+ def stub_account_api_forbidden_get_attributes_names(attributes: [], needed_level_of_authentication: "level1", **options)
221
+ querystring = Rack::Utils.build_nested_query({ attributes: attributes }.compact)
222
+ stub_account_api_request(
223
+ :get,
224
+ "/api/attributes/names?#{querystring}",
225
+ response_status: 403,
226
+ response_body: { needed_level_of_authentication: needed_level_of_authentication },
227
+ **options,
228
+ )
229
+ end
230
+
231
+ def stub_account_api_request(method, path, with: {}, response_status: 200, response_body: {}, govuk_account_session: nil, new_govuk_account_session: nil)
232
+ with.merge!(headers: { GdsApi::AccountApi::AUTH_HEADER_NAME => govuk_account_session }) if govuk_account_session
233
+ new_govuk_account_session = nil if response_status >= 400
234
+ to_return = { status: response_status, body: response_body.merge(govuk_account_session: new_govuk_account_session).compact.to_json }
235
+ if with.empty?
236
+ stub_request(method, "#{ACCOUNT_API_ENDPOINT}#{path}").to_return(**to_return)
237
+ else
238
+ stub_request(method, "#{ACCOUNT_API_ENDPOINT}#{path}").with(**with).to_return(**to_return)
239
+ end
240
+ end
241
+
242
+ ######################
243
+ # GET /api/saved_pages
244
+ ######################
245
+ def stub_account_api_returning_saved_pages(saved_pages: [], **options)
246
+ stub_account_api_request(
247
+ :get,
248
+ "/api/saved_pages",
249
+ response_body: { saved_pages: saved_pages },
250
+ **options,
251
+ )
252
+ end
253
+
254
+ def stub_account_api_unauthorized_get_saved_pages(**options)
255
+ stub_account_api_request(
256
+ :get,
257
+ "/api/saved_pages",
258
+ response_status: 401,
259
+ **options,
260
+ )
261
+ end
262
+
263
+ #################################
264
+ # GET /api/saved_pages/:page_path
265
+ #################################
266
+ def stub_account_api_get_saved_page(page_path:, content_id: "46163ed2-1777-4ee6-bdd4-6a2007e49d8f", title: "Ministry of Magic", **options)
267
+ stub_account_api_request(
268
+ :get,
269
+ "/api/saved_pages/#{CGI.escape(page_path)}",
270
+ response_body: {
271
+ saved_page: {
272
+ page_path: page_path,
273
+ content_id: content_id,
274
+ title: title,
275
+ },
276
+ },
277
+ **options,
278
+ )
279
+ end
280
+
281
+ def stub_account_api_does_not_have_saved_page(page_path:, **options)
282
+ stub_account_api_request(
283
+ :get,
284
+ "/api/saved_pages/#{CGI.escape(page_path)}",
285
+ response_status: 404,
286
+ **options,
287
+ )
288
+ end
289
+
290
+ def stub_account_api_unauthorized_get_saved_page(page_path:, **options)
291
+ stub_account_api_request(
292
+ :get,
293
+ "/api/saved_pages/#{CGI.escape(page_path)}",
294
+ response_status: 401,
295
+ **options,
296
+ )
297
+ end
298
+
299
+ #################################
300
+ # PUT /api/saved_pages/:page_path
301
+ #################################
302
+ def stub_account_api_save_page(page_path:, content_id: "c840bfa2-011a-42cc-ac7a-a6da990aff0b", title: "Ministry of Magic", **options)
303
+ stub_account_api_request(
304
+ :put,
305
+ "/api/saved_pages/#{CGI.escape(page_path)}",
306
+ response_body: {
307
+ saved_page: {
308
+ page_path: page_path,
309
+ content_id: content_id,
310
+ title: title,
311
+ },
312
+ },
313
+ **options,
314
+ )
315
+ end
316
+
317
+ def stub_account_api_save_page_already_exists(page_path:, **options)
318
+ stub_account_api_save_page(page_path: page_path, **options)
319
+ end
320
+
321
+ def stub_account_api_save_page_cannot_save_page(page_path:, **options)
322
+ stub_account_api_request(
323
+ :put,
324
+ "/api/saved_pages/#{CGI.escape(page_path)}",
325
+ response_status: 422,
326
+ response_body: cannot_save_page_problem_detail({ page_path: page_path }),
327
+ **options,
328
+ )
329
+ end
330
+
331
+ def stub_account_api_unauthorized_save_page(page_path:, **options)
332
+ stub_account_api_request(
333
+ :put,
334
+ "/api/saved_pages/#{CGI.escape(page_path)}",
335
+ response_status: 401,
336
+ **options,
337
+ )
338
+ end
339
+
340
+ def cannot_save_page_problem_detail(option = {})
341
+ {
342
+ title: "Cannot save page",
343
+ detail: "Cannot save page with path #{option['page_path']}, check it is not blank, and is a well formatted url path.",
344
+ type: "https://github.com/alphagov/account-api/blob/main/docs/api.md#cannot-save-page",
345
+ **option,
346
+ }
347
+ end
348
+
349
+ ####################################
350
+ # DELETE /api/saved-pages/:page_path
351
+ ####################################
352
+ def stub_account_api_delete_saved_page(page_path:, **options)
353
+ stub_account_api_request(
354
+ :delete,
355
+ "/api/saved_pages/#{CGI.escape(page_path)}",
356
+ response_status: 204,
357
+ **options,
358
+ )
359
+ end
360
+
361
+ def stub_account_api_delete_saved_page_does_not_exist(page_path:, **options)
362
+ stub_account_api_request(
363
+ :delete,
364
+ "/api/saved_pages/#{CGI.escape(page_path)}",
365
+ response_status: 404,
366
+ **options,
367
+ )
368
+ end
369
+
370
+ def stub_account_api_delete_saved_page_unauthorised(page_path:, **options)
371
+ stub_account_api_request(
372
+ :delete,
373
+ "/api/saved_pages/#{CGI.escape(page_path)}",
374
+ response_status: 401,
375
+ **options,
376
+ )
377
+ end
378
+ end
379
+ end
380
+ end