Skip to content

Commit 481ffcc

Browse files
committed
Implement ProfileApiClient.list_school_students
Scott has confirmed that we're not currently expecting students created in Profile API to be exposed by the UserInfo API. Instead we can construct `User`s using the data returned from the students list endpoint. API docs: https://my.raspberrypi.org/api/v1/documentation/#/Students/post_schools__schoolId__students_list
1 parent c439948 commit 481ffcc

File tree

6 files changed

+102
-15
lines changed

6 files changed

+102
-15
lines changed

lib/concepts/school_student/list.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ def call(school:, token:)
1616
private
1717

1818
def list_students(school, token)
19-
response = ProfileApiClient.list_school_students(token:, organisation_id: school.id)
20-
user_ids = response.fetch(:ids)
21-
22-
User.from_userinfo(ids: user_ids)
19+
student_ids = Role.student.where(school:).map(&:user_id)
20+
ProfileApiClient.list_school_students(token:, school_id: school.id, student_ids:).map do |student|
21+
User.new(student.to_h.slice(:id, :username, :name))
22+
end
2323
end
2424
end
2525
end

lib/profile_api_client.rb

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ class ProfileApiClient
88

99
School = Data.define(:id, :schoolCode, :updatedAt, :createdAt, :discardedAt)
1010
SafeguardingFlag = Data.define(:id, :userId, :flag, :email, :createdAt, :updatedAt, :discardedAt)
11+
Student = Data.define(:id, :schoolId, :name, :username, :createdAt, :updatedAt, :discardedAt)
1112

1213
class Error < StandardError; end
1314

@@ -78,12 +79,16 @@ def remove_school_teacher(*)
7879
{}
7980
end
8081

81-
def list_school_students(token:, organisation_id:)
82+
def list_school_students(token:, school_id:, student_ids:)
8283
return [] if token.blank?
8384

84-
_ = organisation_id
85+
response = connection(token).post("/api/v1/schools/#{school_id}/students/list") do |request|
86+
request.body = student_ids
87+
end
8588

86-
{}
89+
raise UnexpectedResponse, response unless response.status == 200
90+
91+
response.body.map { |attrs| Student.new(**attrs.symbolize_keys) }
8792
end
8893

8994
def create_school_student(token:, username:, password:, name:, school_id:)

spec/concepts/school_student/list_spec.rb

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
let(:student) { create(:student, school:) }
99

1010
before do
11-
stub_profile_api_list_school_students(user_id: student.id)
11+
stub_profile_api_list_school_students(school:, id: student.id, name: 'name', username: 'username')
1212
stub_user_info_api_for(student)
1313
end
1414

@@ -21,12 +21,13 @@
2121
described_class.call(school:, token:)
2222

2323
# TODO: Replace with WebMock assertion once the profile API has been built.
24-
expect(ProfileApiClient).to have_received(:list_school_students).with(token:, organisation_id: school.id)
24+
expect(ProfileApiClient).to have_received(:list_school_students).with(token:, school_id: school.id, student_ids: [student.id])
2525
end
2626

2727
it 'returns the school students in the operation response' do
2828
response = described_class.call(school:, token:)
29-
expect(response[:school_students].first).to be_a(User)
29+
expected_user = User.new(id: student.id, name: 'name', username: 'username')
30+
expect(response[:school_students].first).to eq(expected_user)
3031
end
3132

3233
context 'when listing fails' do

spec/features/school_student/listing_school_students_spec.rb

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,13 @@
55
RSpec.describe 'Listing school students', type: :request do
66
before do
77
authenticated_in_hydra_as(owner)
8-
stub_profile_api_list_school_students(user_id: student.id)
9-
stub_user_info_api_for(student)
8+
stub_profile_api_list_school_students(school:, id: student.id, name: 'School Student')
109
stub_profile_api_create_safeguarding_flag
1110
end
1211

1312
let(:headers) { { Authorization: UserProfileMock::TOKEN } }
1413
let(:school) { create(:school) }
15-
let(:student) { create(:student, school:, name: 'School Student') }
14+
let(:student) { create(:student, school:) }
1615
let(:owner) { create(:owner, school:) }
1716

1817
it 'responds 200 OK' do

spec/lib/profile_api_client_spec.rb

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,4 +419,80 @@ def create_school_student
419419
described_class.create_school_student(token:, username:, password:, name:, school_id: school.id)
420420
end
421421
end
422+
423+
describe '.list_school_student' do
424+
let(:school) { build(:school, id: SecureRandom.uuid) }
425+
let(:list_students_url) { "#{api_url}/api/v1/schools/#{school.id}/students/list" }
426+
let(:student_ids) { [SecureRandom.uuid] }
427+
428+
before do
429+
stub_request(:post, list_students_url).to_return(status: 200, body: '[]', headers: { 'content-type' => 'application/json' })
430+
end
431+
432+
it 'makes a request to the profile api host' do
433+
list_school_students
434+
expect(WebMock).to have_requested(:post, list_students_url)
435+
end
436+
437+
it 'includes token in the authorization request header' do
438+
list_school_students
439+
expect(WebMock).to have_requested(:post, list_students_url).with(headers: { authorization: "Bearer #{token}" })
440+
end
441+
442+
it 'includes the profile api key in the x-api-key request header' do
443+
list_school_students
444+
expect(WebMock).to have_requested(:post, list_students_url).with(headers: { 'x-api-key' => api_key })
445+
end
446+
447+
it 'sets content-type of request to json' do
448+
list_school_students
449+
expect(WebMock).to have_requested(:post, list_students_url).with(headers: { 'content-type' => 'application/json' })
450+
end
451+
452+
it 'sets accept header to json' do
453+
list_school_students
454+
expect(WebMock).to have_requested(:post, list_students_url).with(headers: { 'accept' => 'application/json' })
455+
end
456+
457+
it 'sets body to the student IDs' do
458+
list_school_students
459+
expect(WebMock).to have_requested(:post, list_students_url).with(body: student_ids)
460+
end
461+
462+
# rubocop:disable RSpec/ExampleLength
463+
it 'returns the student(s) if successful' do
464+
student = {
465+
id: '549e4674-6ffd-4ac6-9a97-b4d7e5c0e5c5',
466+
schoolId: '132383f1-702a-46a0-9eb2-a40dd4f212e3',
467+
name: 'student-name',
468+
username: 'student-username',
469+
createdAt: '2024-07-03T13:00:40.041Z',
470+
updatedAt: '2024-07-03T13:00:40.041Z',
471+
discardedAt: nil
472+
}
473+
expected = ProfileApiClient::Student.new(**student)
474+
stub_request(:post, list_students_url)
475+
.to_return(status: 200, body: [student].to_json, headers: { 'content-type' => 'application/json' })
476+
expect(list_school_students).to eq([expected])
477+
end
478+
# rubocop:enable RSpec/ExampleLength
479+
480+
it 'raises exception if anything other that 200 status code is returned' do
481+
stub_request(:post, list_students_url)
482+
.to_return(status: 201)
483+
484+
expect { list_school_students }.to raise_error(ProfileApiClient::UnexpectedResponse)
485+
end
486+
487+
it 'raises faraday exception for 4xx and 5xx responses' do
488+
stub_request(:post, list_students_url)
489+
.to_return(status: 401)
490+
491+
expect { list_school_students }.to raise_error(Faraday::Error)
492+
end
493+
494+
def list_school_students
495+
described_class.list_school_students(token:, school_id: school.id, student_ids:)
496+
end
497+
end
422498
end

spec/support/profile_api_mock.rb

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,14 @@ def stub_profile_api_remove_school_teacher
2323
allow(ProfileApiClient).to receive(:remove_school_teacher)
2424
end
2525

26-
def stub_profile_api_list_school_students(user_id:)
27-
allow(ProfileApiClient).to receive(:list_school_students).and_return(ids: [user_id])
26+
def stub_profile_api_list_school_students(school:, id:, username: '', name: '')
27+
now = Time.current.to_fs(:iso8601) # rubocop:disable Naming/VariableNumber
28+
student = ProfileApiClient::Student.new(
29+
schoolId: school.id,
30+
id:, username:, name:,
31+
createdAt: now, updatedAt: now, discardedAt: nil
32+
)
33+
allow(ProfileApiClient).to receive(:list_school_students).and_return([student])
2834
end
2935

3036
def stub_profile_api_create_school_student(user_id: SecureRandom.uuid)

0 commit comments

Comments
 (0)