gds-api-adapters 69.3.0 → 71.3.0

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