From 245c91f11040bec392bf685fc7c6df686970e9b6 Mon Sep 17 00:00:00 2001 From: David Young Date: Wed, 31 Jul 2024 17:52:12 +0100 Subject: [PATCH 1/7] Added `/candidates/:candidate_id` endpoint to CandidateAPI This endpoint follows the same data structure and query pattern as the index page. API Documentation has also been updated, with this endpoint being added to all previous versions as a non-breaking change. --- .../candidate_api/candidates_controller.rb | 22 +++++++ config/candidate_api/v1.1.yml | 30 ++++++++++ config/candidate_api/v1.2.yml | 30 ++++++++++ config/candidate_api/v1.3.yml | 30 ++++++++++ config/routes/api/candidate.rb | 1 + .../candidate_api/get_candidate_spec.rb | 58 +++++++++++++++++++ 6 files changed, 171 insertions(+) create mode 100644 spec/requests/candidate_api/get_candidate_spec.rb diff --git a/app/controllers/candidate_api/candidates_controller.rb b/app/controllers/candidate_api/candidates_controller.rb index 8dd67ca243a..f3d136f89d8 100644 --- a/app/controllers/candidate_api/candidates_controller.rb +++ b/app/controllers/candidate_api/candidates_controller.rb @@ -24,6 +24,28 @@ def index } end + def show + serializer ||= + if version_param == 'v1.3' + CandidateAPI::Serializers::V13.new(updated_since: nil) + elsif version_param == 'v1.2' + CandidateAPI::Serializers::V12.new(updated_since: nil) + else + CandidateAPI::Serializers::V11.new(updated_since: nil) + end + + candidate = Candidate + .left_outer_joins(:application_forms) + .where(application_forms: { recruitment_cycle_year: RecruitmentCycle.current_year }) + .or(Candidate.where('candidates.created_at > ? ', CycleTimetable.apply_deadline(RecruitmentCycle.previous_year))) + .includes(application_forms: :application_choices) + .find(params[:candidate_id].gsub(/^C/, '')) + + render json: { + data: serializer.serialize([candidate]).first, + } + end + def parameter_missing(e) error_message = e.message.split("\n").first render json: { errors: [{ error: 'ParameterMissing', message: error_message }] }, status: :unprocessable_entity diff --git a/config/candidate_api/v1.1.yml b/config/candidate_api/v1.1.yml index 6a3e609b8cf..b3380a72f5f 100644 --- a/config/candidate_api/v1.1.yml +++ b/config/candidate_api/v1.1.yml @@ -51,6 +51,28 @@ paths: "$ref": "#/components/responses/Unauthorized" '422': "$ref": "#/components/responses/UnprocessableEntity" + "/candidates/{candidate_id}": + get: + summary: Get a single candidate + parameters: + - in: path + name: candidate_id + schema: + type: string + example: C1234 + required: true + description: The candidate’s id + responses: + '200': + description: Candidate data + content: + application/json: + schema: + "$ref": "#/components/schemas/CandidateDetail" + '401': + "$ref": "#/components/responses/Unauthorized" + '422': + "$ref": "#/components/responses/UnprocessableEntity" components: responses: Unauthorized: @@ -81,6 +103,14 @@ components: type: array items: "$ref": "#/components/schemas/Candidate" + CandidateDetail: + type: object + additionalProperties: false + required: + - data + properties: + data: + "$ref": "#/components/schemas/Candidate" Candidate: type: object additionalProperties: false diff --git a/config/candidate_api/v1.2.yml b/config/candidate_api/v1.2.yml index d0641be0192..e4c092254e1 100644 --- a/config/candidate_api/v1.2.yml +++ b/config/candidate_api/v1.2.yml @@ -51,6 +51,28 @@ paths: "$ref": "#/components/responses/Unauthorized" '422': "$ref": "#/components/responses/UnprocessableEntity" + "/candidates/{candidate_id}": + get: + summary: Get a single candidate + parameters: + - in: path + name: candidate_id + schema: + type: string + example: C1234 + required: true + description: The candidate’s id + responses: + '200': + description: Candidate data + content: + application/json: + schema: + "$ref": "#/components/schemas/CandidateDetail" + '401': + "$ref": "#/components/responses/Unauthorized" + '422': + "$ref": "#/components/responses/UnprocessableEntity" components: responses: Unauthorized: @@ -81,6 +103,14 @@ components: type: array items: "$ref": "#/components/schemas/Candidate" + CandidateDetail: + type: object + additionalProperties: false + required: + - data + properties: + data: + "$ref": "#/components/schemas/Candidate" Candidate: type: object additionalProperties: false diff --git a/config/candidate_api/v1.3.yml b/config/candidate_api/v1.3.yml index 9cb092a9064..64e91d8a216 100644 --- a/config/candidate_api/v1.3.yml +++ b/config/candidate_api/v1.3.yml @@ -51,6 +51,28 @@ paths: "$ref": "#/components/responses/Unauthorized" '422': "$ref": "#/components/responses/UnprocessableEntity" + "/candidates/{candidate_id}": + get: + summary: Get a single candidate + parameters: + - in: path + name: candidate_id + schema: + type: string + example: C1234 + required: true + description: The candidate’s id + responses: + '200': + description: Candidate data + content: + application/json: + schema: + "$ref": "#/components/schemas/CandidateDetail" + '401': + "$ref": "#/components/responses/Unauthorized" + '422': + "$ref": "#/components/responses/UnprocessableEntity" components: responses: Unauthorized: @@ -81,6 +103,14 @@ components: type: array items: "$ref": "#/components/schemas/Candidate" + CandidateDetail: + type: object + additionalProperties: false + required: + - data + properties: + data: + "$ref": "#/components/schemas/Candidate" Candidate: type: object additionalProperties: false diff --git a/config/routes/api/candidate.rb b/config/routes/api/candidate.rb index 53035ec6dfb..6d0c318fdea 100644 --- a/config/routes/api/candidate.rb +++ b/config/routes/api/candidate.rb @@ -1,5 +1,6 @@ defaults api_version: CandidateAPISpecification::CURRENT_VERSION do namespace :candidate_api, path: 'candidate-api(/:api_version)', api_version: /v[.0-9]+/, constraints: ValidCandidateApiRoute do get '/candidates' => 'candidates#index' + get '/candidates/:candidate_id' => 'candidates#show' end end diff --git a/spec/requests/candidate_api/get_candidate_spec.rb b/spec/requests/candidate_api/get_candidate_spec.rb new file mode 100644 index 00000000000..098d9052e7b --- /dev/null +++ b/spec/requests/candidate_api/get_candidate_spec.rb @@ -0,0 +1,58 @@ +require 'rails_helper' + +RSpec.describe 'GET /candidate-api/:versions/candidates/:candidate_id' do + include CandidateAPISpecHelper + + versions = %w[v1.1 v1.2 v1.3] + + versions.each do |version| + context "for version #{version}" do + it 'does not allow access to the API from other data users' do + service_api_token = ServiceAPIUser.test_data_user.create_magic_link_token! + candidate = create(:candidate) + candidate_id_param = "C#{candidate.id}" + + get_api_request "/candidate-api/#{version}/candidates/#{candidate_id_param}", token: service_api_token + expect(response).to have_http_status(:unauthorized) + expect(parsed_response).to be_valid_against_openapi_schema('UnauthorizedResponse', version.to_s) + end + + it 'allows access to the API for Candidate users' do + candidate = create(:candidate) + candidate_id_param = "C#{candidate.id}" + + get_api_request "/candidate-api/#{version}/candidates/#{candidate_id_param}", token: candidate_api_token + + expect(response).to have_http_status(:success) + end + + it 'conforms to the API spec' do + candidate = create(:candidate) + candidate_id_param = "C#{candidate.id}" + + get_api_request "/candidate-api/#{version}/candidates/#{candidate_id_param}", token: candidate_api_token + + expect(parsed_response).to be_valid_against_openapi_schema('CandidateDetail', version.to_s) + expect(parsed_response.dig('data', 'id')).to eq(candidate_id_param.to_s) + end + + it 'returns applications for the candidate' do + candidate = create(:candidate) + candidate_id_param = "C#{candidate.id}" + create_list(:completed_application_form, 2, candidate:) + + get_api_request "/candidate-api/#{version}/candidates/#{candidate_id_param}", token: candidate_api_token + + application_forms_response = parsed_response.dig('data', 'attributes', 'application_forms') + + expect(application_forms_response.count).to eq(2) + end + + it "returns a 404 if the candidate doesn't exist" do + get_api_request "/candidate-api/#{version}/candidates/C123", token: candidate_api_token + + expect(response).to have_http_status(:not_found) + end + end + end +end From 12af25c6651968025dbdf00967361a154d9ecaba Mon Sep 17 00:00:00 2001 From: David Young Date: Thu, 1 Aug 2024 14:11:08 +0100 Subject: [PATCH 2/7] Refactored Candidate Serializer query method `#index_query` now takes the `updated_since` argument --- .../candidate_api/candidates_controller.rb | 14 +++++----- .../candidate_api/serializers/base.rb | 6 ----- .../candidate_api/serializers/v1_1.rb | 2 +- .../candidate_api/serializers/v1_2.rb | 2 +- .../candidate_api/serializers/v1_3.rb | 27 +------------------ 5 files changed, 10 insertions(+), 41 deletions(-) diff --git a/app/controllers/candidate_api/candidates_controller.rb b/app/controllers/candidate_api/candidates_controller.rb index f3d136f89d8..41572844b68 100644 --- a/app/controllers/candidate_api/candidates_controller.rb +++ b/app/controllers/candidate_api/candidates_controller.rb @@ -20,18 +20,18 @@ class CandidatesController < ApplicationAPIController def index render json: { - data: serializer.serialize(paginate(serializer.query)), + data: serializer.serialize(paginate(serializer.index_query(updated_since: updated_since_params))), } end def show serializer ||= if version_param == 'v1.3' - CandidateAPI::Serializers::V13.new(updated_since: nil) + CandidateAPI::Serializers::V13.new elsif version_param == 'v1.2' - CandidateAPI::Serializers::V12.new(updated_since: nil) + CandidateAPI::Serializers::V12.new else - CandidateAPI::Serializers::V11.new(updated_since: nil) + CandidateAPI::Serializers::V11.new end candidate = Candidate @@ -105,11 +105,11 @@ def page def serializer @serializer ||= if version_param == 'v1.3' - CandidateAPI::Serializers::V13.new(updated_since: updated_since_params) + CandidateAPI::Serializers::V13.new elsif version_param == 'v1.2' - CandidateAPI::Serializers::V12.new(updated_since: updated_since_params) + CandidateAPI::Serializers::V12.new else - CandidateAPI::Serializers::V11.new(updated_since: updated_since_params) + CandidateAPI::Serializers::V11.new end end diff --git a/app/services/candidate_api/serializers/base.rb b/app/services/candidate_api/serializers/base.rb index 0db84872391..1a5bfd8f281 100644 --- a/app/services/candidate_api/serializers/base.rb +++ b/app/services/candidate_api/serializers/base.rb @@ -1,12 +1,6 @@ module CandidateAPI module Serializers class Base - attr_reader :updated_since - - def initialize(updated_since:) - @updated_since = updated_since - end - def serialize(candidates) candidates.map do |candidate| { diff --git a/app/services/candidate_api/serializers/v1_1.rb b/app/services/candidate_api/serializers/v1_1.rb index 888504880a6..95b347bddde 100644 --- a/app/services/candidate_api/serializers/v1_1.rb +++ b/app/services/candidate_api/serializers/v1_1.rb @@ -1,7 +1,7 @@ module CandidateAPI module Serializers class V11 < Base - def query + def index_query(updated_since:) Candidate .left_outer_joins(:application_forms) .where(application_forms: { recruitment_cycle_year: RecruitmentCycle.current_year }) diff --git a/app/services/candidate_api/serializers/v1_2.rb b/app/services/candidate_api/serializers/v1_2.rb index 10a90d29c97..72456483ae6 100644 --- a/app/services/candidate_api/serializers/v1_2.rb +++ b/app/services/candidate_api/serializers/v1_2.rb @@ -17,7 +17,7 @@ def serialize(candidates) end end - def query + def index_query(updated_since:) Candidate .left_outer_joins(application_forms: { application_choices: %i[provider course interviews], application_references: [], application_qualifications: [] }) .includes(application_forms: { application_choices: %i[provider course course_option interviews], application_qualifications: [], application_references: [] }) diff --git a/app/services/candidate_api/serializers/v1_3.rb b/app/services/candidate_api/serializers/v1_3.rb index 3b629aea36b..dcd77d4eb1c 100644 --- a/app/services/candidate_api/serializers/v1_3.rb +++ b/app/services/candidate_api/serializers/v1_3.rb @@ -1,31 +1,6 @@ module CandidateAPI module Serializers - class V13 < Base - def serialize(candidates) - super.map do |candidate| - candidate[:attributes][:application_forms].each do |form| - application_form = ApplicationForm.find(form[:id]) - form.merge!( - application_choices: serialize_application_choices(application_form), - references: serialize_references(application_form), - qualifications: serialize_qualifications(application_form), - personal_statement: serialize_personal_statement(application_form), - ) - end - - candidate - end - end - - def query - Candidate - .left_outer_joins(application_forms: { application_choices: %i[provider course interviews], application_references: [], application_qualifications: [] }) - .includes(application_forms: { application_choices: %i[provider course course_option interviews], application_qualifications: [], application_references: [] }) - .where('candidates.updated_at > :updated_since OR application_forms.updated_at > :updated_since OR application_choices.updated_at > :updated_since OR "references".updated_at > :updated_since OR application_qualifications.updated_at > :updated_since', updated_since:) - .where('application_forms.recruitment_cycle_year = ? OR candidates.created_at > ?', RecruitmentCycle.current_year, CycleTimetable.apply_deadline(RecruitmentCycle.previous_year)) - .order(id: :asc) - .distinct - end + class V13 < V12 private From aa837cb2b6e75ab1303c97ac68249dd874ebb03b Mon Sep 17 00:00:00 2001 From: David Young Date: Thu, 1 Aug 2024 14:12:18 +0100 Subject: [PATCH 3/7] Removed duplicated serializer calculation --- app/controllers/candidate_api/candidates_controller.rb | 9 --------- 1 file changed, 9 deletions(-) diff --git a/app/controllers/candidate_api/candidates_controller.rb b/app/controllers/candidate_api/candidates_controller.rb index 41572844b68..b6abf2014fb 100644 --- a/app/controllers/candidate_api/candidates_controller.rb +++ b/app/controllers/candidate_api/candidates_controller.rb @@ -25,15 +25,6 @@ def index end def show - serializer ||= - if version_param == 'v1.3' - CandidateAPI::Serializers::V13.new - elsif version_param == 'v1.2' - CandidateAPI::Serializers::V12.new - else - CandidateAPI::Serializers::V11.new - end - candidate = Candidate .left_outer_joins(:application_forms) .where(application_forms: { recruitment_cycle_year: RecruitmentCycle.current_year }) From 6f5a155676af479849f83771db7213a2d30129db Mon Sep 17 00:00:00 2001 From: David Young Date: Thu, 1 Aug 2024 14:25:31 +0100 Subject: [PATCH 4/7] Refactored queries to serializer Moved the controller query to the serializers. This keeps all the information together in the serializer --- .../candidate_api/candidates_controller.rb | 12 +++++------- app/services/candidate_api/serializers/v1_1.rb | 9 +++++++++ app/services/candidate_api/serializers/v1_2.rb | 12 ++++++++++-- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/app/controllers/candidate_api/candidates_controller.rb b/app/controllers/candidate_api/candidates_controller.rb index b6abf2014fb..02372a05380 100644 --- a/app/controllers/candidate_api/candidates_controller.rb +++ b/app/controllers/candidate_api/candidates_controller.rb @@ -19,18 +19,16 @@ class CandidatesController < ApplicationAPIController MAX_PER_PAGE = 500 def index + serializer_index_query = serializer.index_query(updated_since: updated_since_params) + render json: { - data: serializer.serialize(paginate(serializer.index_query(updated_since: updated_since_params))), + data: serializer.serialize(paginate(serializer_index_query)), } end def show - candidate = Candidate - .left_outer_joins(:application_forms) - .where(application_forms: { recruitment_cycle_year: RecruitmentCycle.current_year }) - .or(Candidate.where('candidates.created_at > ? ', CycleTimetable.apply_deadline(RecruitmentCycle.previous_year))) - .includes(application_forms: :application_choices) - .find(params[:candidate_id].gsub(/^C/, '')) + candidate_id = params[:candidate_id].gsub(/^C/, '') + candidate = serializer.find_query(candidate_id:) render json: { data: serializer.serialize([candidate]).first, diff --git a/app/services/candidate_api/serializers/v1_1.rb b/app/services/candidate_api/serializers/v1_1.rb index 95b347bddde..7eafe39283e 100644 --- a/app/services/candidate_api/serializers/v1_1.rb +++ b/app/services/candidate_api/serializers/v1_1.rb @@ -11,6 +11,15 @@ def index_query(updated_since:) .where('candidate_api_updated_at > ?', updated_since) .order('candidates.candidate_api_updated_at DESC') end + + def find_query(candidate_id:) + Candidate + .left_outer_joins(:application_forms) + .where(application_forms: { recruitment_cycle_year: RecruitmentCycle.current_year }) + .or(Candidate.where('candidates.created_at > ? ', CycleTimetable.apply_deadline(RecruitmentCycle.previous_year))) + .includes(application_forms: :application_choices) + .find(candidate_id) + end end end end diff --git a/app/services/candidate_api/serializers/v1_2.rb b/app/services/candidate_api/serializers/v1_2.rb index 72456483ae6..d46ccea1bbb 100644 --- a/app/services/candidate_api/serializers/v1_2.rb +++ b/app/services/candidate_api/serializers/v1_2.rb @@ -1,6 +1,6 @@ module CandidateAPI module Serializers - class V12 < Base + class V12 < V11 def serialize(candidates) super.map do |candidate| candidate[:attributes][:application_forms].each do |form| @@ -21,12 +21,20 @@ def index_query(updated_since:) Candidate .left_outer_joins(application_forms: { application_choices: %i[provider course interviews], application_references: [], application_qualifications: [] }) .includes(application_forms: { application_choices: %i[provider course course_option interviews], application_qualifications: [], application_references: [] }) - .where('candidates.updated_at > :updated_since OR application_forms.updated_at > :updated_since OR application_choices.updated_at > :updated_since OR "references".updated_at > :updated_since OR application_qualifications.updated_at > :updated_since', updated_since:) .where('application_forms.recruitment_cycle_year = ? OR candidates.created_at > ?', RecruitmentCycle.current_year, CycleTimetable.apply_deadline(RecruitmentCycle.previous_year)) + .where('candidates.updated_at > :updated_since OR application_forms.updated_at > :updated_since OR application_choices.updated_at > :updated_since OR "references".updated_at > :updated_since OR application_qualifications.updated_at > :updated_since', updated_since:) .order(id: :asc) .distinct end + def find_query(candidate_id:) + Candidate + .left_outer_joins(application_forms: { application_choices: %i[provider course interviews], application_references: [], application_qualifications: [] }) + .includes(application_forms: { application_choices: %i[provider course course_option interviews], application_qualifications: [], application_references: [] }) + .where('application_forms.recruitment_cycle_year = ? OR candidates.created_at > ?', RecruitmentCycle.current_year, CycleTimetable.apply_deadline(RecruitmentCycle.previous_year)) + .find(candidate_id) + end + private def serialize_application_choices(application_form) From 0e00360fccd871120a813d50a951b81b63188523 Mon Sep 17 00:00:00 2001 From: David Young Date: Thu, 1 Aug 2024 15:01:10 +0100 Subject: [PATCH 5/7] Added error handling for 404 not found errors Added documentation --- .../candidate_api/candidates_controller.rb | 5 ++++ config/candidate_api/v1.1.yml | 23 +++++++++++++++++++ config/candidate_api/v1.2.yml | 23 +++++++++++++++++++ config/candidate_api/v1.3.yml | 23 +++++++++++++++++++ .../candidate_api/get_candidate_spec.rb | 3 ++- 5 files changed, 76 insertions(+), 1 deletion(-) diff --git a/app/controllers/candidate_api/candidates_controller.rb b/app/controllers/candidate_api/candidates_controller.rb index 02372a05380..4d07a8f5fc5 100644 --- a/app/controllers/candidate_api/candidates_controller.rb +++ b/app/controllers/candidate_api/candidates_controller.rb @@ -6,6 +6,7 @@ class CandidatesController < ApplicationAPIController rescue_from ActionController::ParameterMissing, with: :parameter_missing rescue_from ParameterInvalid, with: :parameter_invalid + rescue_from ActiveRecord::RecordNotFound, with: :not_found # Makes PG::QueryCanceled statement timeout errors appear in Skylight # against the controller action that triggered them @@ -44,6 +45,10 @@ def parameter_invalid(e) render json: { errors: [{ error: 'ParameterInvalid', message: e }] }, status: :unprocessable_entity end + def not_found(_e) + render json: { errors: [{ error: 'NotFound', message: 'Unable to find Candidate' }] }, status: :not_found + end + def statement_timeout render json: { errors: [ diff --git a/config/candidate_api/v1.1.yml b/config/candidate_api/v1.1.yml index b3380a72f5f..19efb14d83c 100644 --- a/config/candidate_api/v1.1.yml +++ b/config/candidate_api/v1.1.yml @@ -71,10 +71,18 @@ paths: "$ref": "#/components/schemas/CandidateDetail" '401': "$ref": "#/components/responses/Unauthorized" + '404': + "$ref": "#/components/responses/NotFound" '422': "$ref": "#/components/responses/UnprocessableEntity" components: responses: + NotFound: + description: Record not found + content: + application/json: + schema: + "$ref": "#/components/schemas/NotFoundResponse" Unauthorized: description: Unauthorized content: @@ -216,6 +224,21 @@ components: format: date-time description: Time of last change example: 2021-05-20T12:34:00Z + NotFoundResponse: + type: object + additionalProperties: false + required: + - errors + properties: + errors: + type: array + minItems: 1 + description: Error objects describing the problem + items: + "$ref": "#/components/schemas/Error" + example: + - error: NotFound + message: Unable to find Candidate UnauthorizedResponse: type: object additionalProperties: false diff --git a/config/candidate_api/v1.2.yml b/config/candidate_api/v1.2.yml index e4c092254e1..4fbf74b1ada 100644 --- a/config/candidate_api/v1.2.yml +++ b/config/candidate_api/v1.2.yml @@ -71,10 +71,18 @@ paths: "$ref": "#/components/schemas/CandidateDetail" '401': "$ref": "#/components/responses/Unauthorized" + '404': + "$ref": "#/components/responses/NotFound" '422': "$ref": "#/components/responses/UnprocessableEntity" components: responses: + NotFound: + description: Record not found + content: + application/json: + schema: + "$ref": "#/components/schemas/NotFoundResponse" Unauthorized: description: Unauthorized content: @@ -454,6 +462,21 @@ components: example: - error: Unauthorized message: Please provide a valid authentication token + NotFoundResponse: + type: object + additionalProperties: false + required: + - errors + properties: + errors: + type: array + minItems: 1 + description: Error objects describing the problem + items: + "$ref": "#/components/schemas/Error" + example: + - error: NotFound + message: Unable to find Candidate ParameterMissingResponse: type: object additionalProperties: false diff --git a/config/candidate_api/v1.3.yml b/config/candidate_api/v1.3.yml index 64e91d8a216..cf097e8f4d5 100644 --- a/config/candidate_api/v1.3.yml +++ b/config/candidate_api/v1.3.yml @@ -71,10 +71,18 @@ paths: "$ref": "#/components/schemas/CandidateDetail" '401': "$ref": "#/components/responses/Unauthorized" + '404': + "$ref": "#/components/responses/NotFound" '422': "$ref": "#/components/responses/UnprocessableEntity" components: responses: + NotFound: + description: Record not found + content: + application/json: + schema: + "$ref": "#/components/schemas/NotFoundResponse" Unauthorized: description: Unauthorized content: @@ -460,6 +468,21 @@ components: example: - error: Unauthorized message: Please provide a valid authentication token + NotFoundResponse: + type: object + additionalProperties: false + required: + - errors + properties: + errors: + type: array + minItems: 1 + description: Error objects describing the problem + items: + "$ref": "#/components/schemas/Error" + example: + - error: NotFound + message: Unable to find Candidate ParameterMissingResponse: type: object additionalProperties: false diff --git a/spec/requests/candidate_api/get_candidate_spec.rb b/spec/requests/candidate_api/get_candidate_spec.rb index 098d9052e7b..7b58f10e5f3 100644 --- a/spec/requests/candidate_api/get_candidate_spec.rb +++ b/spec/requests/candidate_api/get_candidate_spec.rb @@ -48,10 +48,11 @@ expect(application_forms_response.count).to eq(2) end - it "returns a 404 if the candidate doesn't exist" do + it "returns a NotFound error if the candidate doesn't exist" do get_api_request "/candidate-api/#{version}/candidates/C123", token: candidate_api_token expect(response).to have_http_status(:not_found) + expect(parsed_response).to be_valid_against_openapi_schema('NotFoundResponse', version.to_s) end end end From 7737c733c47ea810f98208f7f8948b7dbd7c38c6 Mon Sep 17 00:00:00 2001 From: David Young Date: Thu, 1 Aug 2024 15:11:33 +0100 Subject: [PATCH 6/7] undo minor change --- app/services/candidate_api/serializers/v1_2.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/candidate_api/serializers/v1_2.rb b/app/services/candidate_api/serializers/v1_2.rb index d46ccea1bbb..3196716335b 100644 --- a/app/services/candidate_api/serializers/v1_2.rb +++ b/app/services/candidate_api/serializers/v1_2.rb @@ -21,8 +21,8 @@ def index_query(updated_since:) Candidate .left_outer_joins(application_forms: { application_choices: %i[provider course interviews], application_references: [], application_qualifications: [] }) .includes(application_forms: { application_choices: %i[provider course course_option interviews], application_qualifications: [], application_references: [] }) - .where('application_forms.recruitment_cycle_year = ? OR candidates.created_at > ?', RecruitmentCycle.current_year, CycleTimetable.apply_deadline(RecruitmentCycle.previous_year)) .where('candidates.updated_at > :updated_since OR application_forms.updated_at > :updated_since OR application_choices.updated_at > :updated_since OR "references".updated_at > :updated_since OR application_qualifications.updated_at > :updated_since', updated_since:) + .where('application_forms.recruitment_cycle_year = ? OR candidates.created_at > ?', RecruitmentCycle.current_year, CycleTimetable.apply_deadline(RecruitmentCycle.previous_year)) .order(id: :asc) .distinct end From ee3da3e34ae9bdfc62c4dc0641d5854904e515bb Mon Sep 17 00:00:00 2001 From: David Young Date: Fri, 2 Aug 2024 09:59:57 +0100 Subject: [PATCH 7/7] Rubocop fix --- app/services/candidate_api/serializers/v1_3.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/services/candidate_api/serializers/v1_3.rb b/app/services/candidate_api/serializers/v1_3.rb index dcd77d4eb1c..546e577f36b 100644 --- a/app/services/candidate_api/serializers/v1_3.rb +++ b/app/services/candidate_api/serializers/v1_3.rb @@ -1,7 +1,6 @@ module CandidateAPI module Serializers class V13 < V12 - private def serialize_application_choices(application_form)