From ac48dc9b83bfdb41c935447fcd787e2507a552e9 Mon Sep 17 00:00:00 2001 From: Kevin Date: Wed, 6 May 2026 21:04:16 +0100 Subject: [PATCH] Added parent homework method --- README.md | 32 +++++++++++++++ src/core/parentClient.ts | 75 ++++++++++++++++++++++++++++++++++- src/core/parentClient_test.ts | 50 ++++++++++++++++++++++- 3 files changed, 155 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6e4ad72..eea8484 100644 --- a/README.md +++ b/README.md @@ -353,6 +353,38 @@ Selects a pupil to make subsequent requests for. await client.selectPupil(123); ``` +## `.getHomeworksForPupil` + +Gets homework for a specific pupil ID without permanently changing the selected pupil. + +```typescript +const homework = await client.getHomeworksForPupil(123, { + from: "2026-05-01", + to: "2026-05-31", + displayDate: "due_date", +}); +console.log(homework); +``` + +## `.getHomeworksForEachPupil` + +Gets homework for each pupil ID. If no IDs are passed, it fetches for all attached pupils. + +```typescript +const allHomework = await client.getHomeworksForEachPupil({ + from: "2026-05-01", + to: "2026-05-31", +}); + +const selectedHomework = await client.getHomeworksForEachPupil( + { displayDate: "issue_date" }, + [123, 456], +); + +console.log(allHomework[123]); +console.log(selectedHomework[456]); +``` + ## `.changePassword` Changes the parent's password. diff --git a/src/core/parentClient.ts b/src/core/parentClient.ts index 8d155cb..db146cb 100644 --- a/src/core/parentClient.ts +++ b/src/core/parentClient.ts @@ -1,4 +1,9 @@ -import type { ChangePasswordResponse, GetPupilsResponse } from "../types.ts"; +import type { + ChangePasswordResponse, + GetHomeworkOptions, + GetPupilsResponse, + HomeworksResponse, +} from "../types.ts"; import { BaseClient } from "../core/baseClient.ts"; import { API_BASE_PARENT, BASE_URL } from "../utils/consts.ts"; @@ -104,6 +109,74 @@ export class ParentClient extends BaseClient { } throw new Error("No pupil with specified ID returned"); } + + /** + * Gets homework for a specific pupil ID without changing the selected pupil permanently. + * @param pupilId Pupil ID obtained from this.pupils or getPupils() + * @param options GetHomeworkOptions + * @returns Homeworks response for the specified pupil + */ + async getHomeworksForPupil( + pupilId: number, + options?: GetHomeworkOptions, + ): Promise { + const previousStudentId = this.studentId; + try { + this.selectPupil(pupilId); + return await this.getHomeworks(options); + } finally { + this.studentId = previousStudentId; + } + } + + /** + * Gets homework for multiple pupil IDs. + * + * If no pupil IDs are passed, this fetches homework for every pupil attached + * to the parent account. + * + * @param options GetHomeworkOptions + * @param pupilIds Optional list of pupil IDs to fetch homework for + * @returns A record where each key is a pupil ID and value is that pupil's homework response + */ + async getHomeworksForEachPupil( + options?: GetHomeworkOptions, + pupilIds?: number[], + ): Promise> { + const targetPupilIds = pupilIds?.length + ? pupilIds + : this.pupils.map((pupil) => pupil.id); + + if (!targetPupilIds.length) { + throw new Error("No pupils available"); + } + + const invalidPupilIds = targetPupilIds.filter((pupilId) => + !this.pupils.some((pupil) => pupil.id === pupilId) + ); + if (invalidPupilIds.length) { + throw new Error( + `No pupil with specified ID returned: ${invalidPupilIds.join(", ")}`, + ); + } + + const previousStudentId = this.studentId; + const homeworksByPupilId: Record = {}; + + try { + for (const pupilId of targetPupilIds) { + homeworksByPupilId[pupilId] = await this.getHomeworksForPupil( + pupilId, + options, + ); + } + } finally { + this.studentId = previousStudentId; + } + + return homeworksByPupilId; + } + /** * Changes the login password for the current parent account * @param currentPassword Current password diff --git a/src/core/parentClient_test.ts b/src/core/parentClient_test.ts index 3c828e8..47cc66d 100644 --- a/src/core/parentClient_test.ts +++ b/src/core/parentClient_test.ts @@ -1,4 +1,4 @@ -import { assertRejects } from "@std/assert"; +import { assertEquals, assertRejects } from "@std/assert"; import { ParentClient } from "../core/parentClient.ts"; Deno.test("Throws when no email is provided", async () => { @@ -33,3 +33,51 @@ Deno.test("Throws with invalid username and password", async () => { "Unauthenticated: ClassCharts didn't return authentication cookies", ); }); + +Deno.test("getHomeworksForPupil requests homework for provided pupil ID", async () => { + const client = new ParentClient("email", "password"); + client.pupils = [{ id: 1 }, { id: 2 }] as never; + client.studentId = 1; + + let requestedStudentId = 0; + client.getHomeworks = async () => { + requestedStudentId = client.studentId; + return { success: 1, data: [], meta: {} } as never; + }; + + await client.getHomeworksForPupil(2); + + assertEquals(requestedStudentId, 2); + assertEquals(client.studentId, 1); +}); + +Deno.test("getHomeworksForEachPupil requests homework for each attached pupil", async () => { + const client = new ParentClient("email", "password"); + client.pupils = [{ id: 10 }, { id: 20 }, { id: 30 }] as never; + client.studentId = 20; + + const requestedStudentIds: number[] = []; + client.getHomeworks = async () => { + requestedStudentIds.push(client.studentId); + return { success: 1, data: [], meta: {} } as never; + }; + + const result = await client.getHomeworksForEachPupil(); + + assertEquals(requestedStudentIds, [10, 20, 30]); + assertEquals(Object.keys(result), ["10", "20", "30"]); + assertEquals(client.studentId, 20); +}); + +Deno.test("getHomeworksForEachPupil rejects unknown pupil IDs", async () => { + const client = new ParentClient("email", "password"); + client.pupils = [{ id: 1 }, { id: 2 }] as never; + + await assertRejects( + async () => { + await client.getHomeworksForEachPupil(undefined, [2, 99]); + }, + Error, + "No pupil with specified ID returned: 99", + ); +});