From 5e26ac01c4c8ce7fcd855d9d159497b7a9cca7a9 Mon Sep 17 00:00:00 2001 From: Pete Simonovic <69108995+PetarSimonovic@users.noreply.github.com> Date: Fri, 1 May 2026 11:07:19 +0100 Subject: [PATCH] Add update_school_email_domains to ProfileApiClient Allow the client to PATCH a collection of domains --- lib/profile_api_client.rb | 24 ++++++++-- spec/lib/profile_api_client_spec.rb | 71 ++++++++++++++++++++++++++--- spec/support/profile_api_mock.rb | 3 +- 3 files changed, 87 insertions(+), 11 deletions(-) diff --git a/lib/profile_api_client.rb b/lib/profile_api_client.rb index 1a64efb23..44215e4a6 100644 --- a/lib/profile_api_client.rb +++ b/lib/profile_api_client.rb @@ -7,7 +7,7 @@ class ProfileApiClient }.freeze # rubocop:disable Naming/MethodName - School = Data.define(:id, :schoolCode, :updatedAt, :createdAt, :discardedAt) + School = Data.define(:id, :schoolCode, :studentEmailDomains, :updatedAt, :createdAt, :discardedAt) SafeguardingFlag = Data.define(:id, :userId, :schoolId, :flag, :email, :createdAt, :updatedAt, :discardedAt) Student = Data.define(:id, :schoolId, :name, :username, :createdAt, :updatedAt, :discardedAt, :email, :ssoProviders) # rubocop:enable Naming/MethodName @@ -51,13 +51,14 @@ def check_auth(token:) false end - def create_school(token:, id:, code:) - return { 'id' => id, 'schoolCode' => code } if ENV['BYPASS_OAUTH'].present? + def create_school(token:, id:, code:, school_email_domains: []) + return { 'id' => id, 'schoolCode' => code, 'studentEmailDomains' => school_email_domains } if ENV['BYPASS_OAUTH'].present? response = connection(token).post('/api/v1/schools') do |request| request.body = { id:, - schoolCode: code + schoolCode: code, + studentEmailDomains: school_email_domains } end @@ -214,6 +215,21 @@ def delete_safeguarding_flag(token:, flag:) raise UnexpectedResponse, response unless response.status == 204 end + def update_school_email_domains(token:, school_id:, school_email_domains: []) + return { 'id' => school_id, 'studentEmailDomains' => school_email_domains } if ENV['BYPASS_OAUTH'].present? + + response = connection(token).patch("/api/v1/schools/#{school_id}") do |request| + request.body = { + studentEmailDomains: school_email_domains + } + end + + unauthorized!(response) + raise UnexpectedResponse, response unless response.status == 200 + + School.new(**response.body) + end + private def connection(token) diff --git a/spec/lib/profile_api_client_spec.rb b/spec/lib/profile_api_client_spec.rb index 3a2c40def..1a69d740c 100644 --- a/spec/lib/profile_api_client_spec.rb +++ b/spec/lib/profile_api_client_spec.rb @@ -58,7 +58,7 @@ stub_request(:post, create_school_url) .to_return( status: 201, - body: '{"id":"","schoolCode":"","updatedAt":"","createdAt":"","discardedAt":""}', + body: '{"id":"","schoolCode":"","updatedAt":"","createdAt":"","discardedAt":"","studentEmailDomains":[]}', headers: { 'content-type' => 'application/json' } ) end @@ -67,14 +67,15 @@ it_behaves_like 'a request that handles standard HTTP errors', :post, url: -> { create_school_url } it_behaves_like 'a request that handles an unexpected response status', :post, url: -> { "#{api_url}/api/v1/schools" }, status: 200 - it 'sends the school id and code in the request body as json' do + it 'sends the school id, code and studentEmailDomains in the request body as json' do create_school_response - expected_body = { id: school.id, schoolCode: school.code }.to_json + expected_body = { id: school.id, schoolCode: school.code, studentEmailDomains: [] }.to_json expect(WebMock).to have_requested(:post, create_school_url).with(body: expected_body) end it 'returns the created school if successful' do - data = { id: 'id', schoolCode: 'code', updatedAt: '2024-07-09T10:31:13.196Z', createdAt: '2024-07-09T10:31:13.196Z', discardedAt: nil } + data = { id: 'id', schoolCode: 'code', updatedAt: '2024-07-09T10:31:13.196Z', createdAt: '2024-07-09T10:31:13.196Z', discardedAt: nil, + studentEmailDomains: [] } expected = ProfileApiClient::School.new(**data) stub_request(:post, create_school_url) .to_return(status: 201, body: data.to_json, headers: { 'Content-Type' => 'application/json' }) @@ -91,8 +92,8 @@ expect(WebMock).not_to have_requested(:post, create_school_url) end - it 'returns the id and code of the school supplied' do - expected = { 'id' => school.id, 'schoolCode' => school.code } + it 'returns the id, code and email domains of the school supplied' do + expected = { 'id' => school.id, 'schoolCode' => school.code, 'studentEmailDomains' => [] } expect(create_school_response).to eq(expected) end end @@ -558,4 +559,62 @@ def school_student described_class.school_student(token:, school_id: school.id, student_id:) end end + + describe '.update_school_email_domains' do + subject(:update_school_email_domains_response) { update_school_email_domains } + + let(:school) { build(:school, id: SecureRandom.uuid, code: SecureRandom.uuid) } + let(:update_school_email_domains_url) { "#{api_url}/api/v1/schools/#{school.id}" } + let(:school_email_domains) { ['student.example.edu', 'mail.example.org'] } + + before do + stub_request(:patch, update_school_email_domains_url) + .to_return( + status: 200, + body: '{"id":"","schoolCode":"","updatedAt":"","createdAt":"","discardedAt":"","studentEmailDomains":[]}', + headers: { 'content-type' => 'application/json' } + ) + end + + it_behaves_like 'an authenticated JSON API request', :patch, url: -> { update_school_email_domains_url } + it_behaves_like 'a request that handles standard HTTP errors', :patch, url: -> { update_school_email_domains_url } + it_behaves_like 'a request that handles an unexpected response status', :patch, url: -> { "#{api_url}/api/v1/schools/#{school.id}" }, status: 201 + + it 'sends studentEmailDomains in the JSON body' do + update_school_email_domains_response + expected_body = { studentEmailDomains: school_email_domains }.to_json + expect(WebMock).to have_requested(:patch, update_school_email_domains_url).with(body: expected_body) + end + + it 'returns a school if successful' do + data = { id: 'id', schoolCode: 'code', updatedAt: '2024-07-09T10:31:13.196Z', createdAt: '2024-07-09T10:31:13.196Z', discardedAt: nil, + studentEmailDomains: school_email_domains } + expected = ProfileApiClient::School.new(**data) + stub_request(:patch, update_school_email_domains_url) + .to_return(status: 200, body: data.to_json, headers: { 'Content-Type' => 'application/json' }) + expect(update_school_email_domains_response).to eq(expected) + end + + describe 'when BYPASS_OAUTH is true' do + before do + allow(ENV).to receive(:[]).with('BYPASS_OAUTH').and_return(true) + end + + it 'does not make a request to Profile API' do + update_school_email_domains_response + expect(WebMock).not_to have_requested(:patch, update_school_email_domains_url) + end + + it 'returns the id and email domains of the school supplied' do + expected = { 'id' => school.id, 'studentEmailDomains' => school_email_domains } + expect(update_school_email_domains_response).to eq(expected) + end + end + + private + + def update_school_email_domains + described_class.update_school_email_domains(token:, school_id: school.id, school_email_domains:) + end + end end diff --git a/spec/support/profile_api_mock.rb b/spec/support/profile_api_mock.rb index bbfa938a3..db337f56e 100644 --- a/spec/support/profile_api_mock.rb +++ b/spec/support/profile_api_mock.rb @@ -30,12 +30,13 @@ def stub_profile_api_create_school_student(user_id: SecureRandom.uuid) allow(ProfileApiClient).to receive(:create_school_student).and_return(created: [user_id]) end - def stub_profile_api_create_school(id: SecureRandom.uuid, code: '99-12-34') + def stub_profile_api_create_school(id: SecureRandom.uuid, code: '99-12-34', student_email_domains: []) now = Time.current.to_fs(:iso8601) # rubocop:disable Naming/VariableNumber allow(ProfileApiClient).to receive(:create_school).and_return( ProfileApiClient::School.new( id:, schoolCode: code, + studentEmailDomains: student_email_domains, updatedAt: now, createdAt: now, discardedAt: nil