Skip to content

Commit d90f024

Browse files
committed
Implement ProfileApiClient.create_school_student
TODO: Better support for 422 responses from Profile API - at the very least for the `ERR_USER_EXISTS` error.
1 parent bb1a74f commit d90f024

File tree

6 files changed

+101
-15
lines changed

6 files changed

+101
-15
lines changed

lib/concepts/school_student/create.rb

+1-2
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,13 @@ def call(school:, school_student_params:, token:)
1616
private
1717

1818
def create_student(school, school_student_params, token)
19-
organisation_id = school.id
2019
username = school_student_params.fetch(:username)
2120
password = school_student_params.fetch(:password)
2221
name = school_student_params.fetch(:name)
2322

2423
validate(school:, username:, password:, name:)
2524

26-
ProfileApiClient.create_school_student(token:, username:, password:, name:, organisation_id:)
25+
ProfileApiClient.create_school_student(token:, username:, password:, name:, school:)
2726
end
2827

2928
def validate(school:, username:, password:, name:)

lib/concepts/school_student/create_batch.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def create_batch(school, uploaded_file, token)
2323
validate(school:, sheet:)
2424

2525
non_header_rows_with_content(sheet:).each do |name, username, password|
26-
ProfileApiClient.create_school_student(token:, username:, password:, name:, organisation_id: school.id)
26+
ProfileApiClient.create_school_student(token:, username:, password:, name:, school:)
2727
end
2828
end
2929

lib/profile_api_client.rb

+20-9
Original file line numberDiff line numberDiff line change
@@ -147,19 +147,30 @@ def list_school_students(token:, organisation_id:)
147147
# The API should respond:
148148
# - 404 Not Found if the user doesn't exist
149149
# - 422 Unprocessable if the constraints are not met
150-
def create_school_student(token:, username:, password:, name:, organisation_id:)
150+
# rubocop:disable Metrics/AbcSize
151+
def create_school_student(token:, username:, password:, name:, school:)
151152
return nil if token.blank?
152153

153-
_ = username
154-
_ = password
155-
_ = name
156-
_ = organisation_id
154+
response = connection.post("/api/v1/schools/#{school.id}/students") do |request|
155+
apply_default_headers(request, token)
156+
request.body = [{
157+
name:,
158+
username:,
159+
password:
160+
}].to_json
161+
end
157162

158-
# TODO: We should make Faraday raise a Ruby error for a non-2xx status
159-
# code so that SchoolStudent::Create propagates the error in the response.
160-
response = {}
161-
response.deep_symbolize_keys
163+
if response.status == 422
164+
error_code = JSON.parse(response.body)['errors'].first['error']
165+
message = error_code == 'ERR_USER_EXISTS' ? 'Username already exists' : "Unknown error code: #{error_code}"
166+
raise "Student not created in Profile API. HTTP response code 422. #{message}"
167+
end
168+
169+
raise "Student not created in Profile API. HTTP response code: #{response.status}" unless response.status == 201
170+
171+
JSON.parse(response.body)
162172
end
173+
# rubocop:enable Metrics/AbcSize
163174

164175
# The API should enforce these constraints:
165176
# - The token has the school-owner or school-teacher role for the given organisation ID

spec/concepts/school_student/create_batch_spec.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,15 @@
2121

2222
# TODO: Replace with WebMock assertion once the profile API has been built.
2323
expect(ProfileApiClient).to have_received(:create_school_student)
24-
.with(token:, username: 'jane123', password: 'secret123', name: 'Jane Doe', organisation_id: school.id)
24+
.with(token:, username: 'jane123', password: 'secret123', name: 'Jane Doe', school:)
2525
end
2626

2727
it "makes a profile API call to create John Doe's account" do
2828
described_class.call(school:, uploaded_file: file, token:)
2929

3030
# TODO: Replace with WebMock assertion once the profile API has been built.
3131
expect(ProfileApiClient).to have_received(:create_school_student)
32-
.with(token:, username: 'john123', password: 'secret456', name: 'John Doe', organisation_id: school.id)
32+
.with(token:, username: 'john123', password: 'secret456', name: 'John Doe', school:)
3333
end
3434

3535
context 'when an .xlsx file is provided' do

spec/concepts/school_student/create_spec.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828

2929
# TODO: Replace with WebMock assertion once the profile API has been built.
3030
expect(ProfileApiClient).to have_received(:create_school_student)
31-
.with(token:, username: 'student-to-create', password: 'at-least-8-characters', name: 'School Student', organisation_id: school.id)
31+
.with(token:, username: 'student-to-create', password: 'at-least-8-characters', name: 'School Student', school:)
3232
end
3333

3434
context 'when creation fails' do

spec/lib/profile_api_client_spec.rb

+76
Original file line numberDiff line numberDiff line change
@@ -287,4 +287,80 @@ def delete_safeguarding_flag
287287
described_class.delete_safeguarding_flag(token:, flag:)
288288
end
289289
end
290+
291+
describe '.create_school_student' do
292+
let(:username) { 'username' }
293+
let(:password) { 'password' }
294+
let(:name) { 'name' }
295+
let(:school) { build(:school, id: SecureRandom.uuid) }
296+
let(:create_students_url) { "#{api_url}/api/v1/schools/#{school.id}/students" }
297+
298+
before do
299+
stub_request(:post, create_students_url).to_return(status: 201, body: '{}')
300+
end
301+
302+
it 'makes a request to the profile api host' do
303+
create_school_student
304+
expect(WebMock).to have_requested(:post, create_students_url)
305+
end
306+
307+
it 'includes token in the authorization request header' do
308+
create_school_student
309+
expect(WebMock).to have_requested(:post, create_students_url).with(headers: { authorization: "Bearer #{token}" })
310+
end
311+
312+
it 'includes the profile api key in the x-api-key request header' do
313+
create_school_student
314+
expect(WebMock).to have_requested(:post, create_students_url).with(headers: { 'x-api-key' => api_key })
315+
end
316+
317+
it 'sets content-type of request to json' do
318+
create_school_student
319+
expect(WebMock).to have_requested(:post, create_students_url).with(headers: { 'content-type' => 'application/json' })
320+
end
321+
322+
it 'sets accept header to json' do
323+
create_school_student
324+
expect(WebMock).to have_requested(:post, create_students_url).with(headers: { 'accept' => 'application/json' })
325+
end
326+
327+
it 'returns the id of the created student(s) if successful' do
328+
data = { 'created' => ['student-id'] }
329+
stub_request(:post, create_students_url)
330+
.to_return(status: 201, body: data.to_json)
331+
expect(create_school_student).to eq(data)
332+
end
333+
334+
it 'raises exception with details of the error if 422 response indicates that the user already exists' do
335+
response = { 'errors' => [{ 'username' => 'jdoe', 'error' => 'ERR_USER_EXISTS' }] }
336+
stub_request(:post, create_students_url)
337+
.to_return(status: 422, body: response.to_json)
338+
expect { create_school_student }.to raise_error('Student not created in Profile API. HTTP response code 422. Username already exists')
339+
end
340+
341+
it 'raises exception including the error code if 422 response indicates that some other error occurred' do
342+
response = { 'errors' => [{ 'username' => 'jdoe', 'error' => 'ERR_UNKNOWN' }] }
343+
stub_request(:post, create_students_url)
344+
.to_return(status: 422, body: response.to_json)
345+
expect { create_school_student }.to raise_error('Student not created in Profile API. HTTP response code 422. Unknown error code: ERR_UNKNOWN')
346+
end
347+
348+
it 'raises exception if anything other than a 201 status code is returned' do
349+
stub_request(:post, create_students_url)
350+
.to_return(status: 200)
351+
352+
expect { create_school_student }.to raise_error(RuntimeError)
353+
end
354+
355+
it 'includes details of underlying response when exception is raised' do
356+
stub_request(:post, create_students_url)
357+
.to_return(status: 401)
358+
359+
expect { create_school_student }.to raise_error('Student not created in Profile API. HTTP response code: 401')
360+
end
361+
362+
def create_school_student
363+
described_class.create_school_student(token:, username:, password:, name:, school:)
364+
end
365+
end
290366
end

0 commit comments

Comments
 (0)