From 6afe7864beb32292190fcbc017767fa817e16e9e Mon Sep 17 00:00:00 2001 From: Yisroel Baum Date: Sat, 15 Aug 2026 21:48:34 +0300 Subject: [PATCH] test dated assignment listing --- .../Feature/Schedule/ScheduleEndpointTest.php | 132 ++++++++++++++++ .../UseCases/ListAssignmentsForDateTest.php | 143 ++++++++++++++++++ 2 files changed, 275 insertions(+) create mode 100644 backend/tests/Unit/Schedule/UseCases/ListAssignmentsForDateTest.php diff --git a/backend/tests/Feature/Schedule/ScheduleEndpointTest.php b/backend/tests/Feature/Schedule/ScheduleEndpointTest.php index 0303dfe..a0e9cf8 100644 --- a/backend/tests/Feature/Schedule/ScheduleEndpointTest.php +++ b/backend/tests/Feature/Schedule/ScheduleEndpointTest.php @@ -221,6 +221,136 @@ class ScheduleEndpointTest extends TestCase ]); } + public function test_it_lists_the_users_assignments_for_a_date(): void + { + $user = $this->createUser('reader@example.com'); + $otherUser = $this->createUser('other@example.com'); + $olderSet = $this->createSet($user, 'Older course'); + $olderLevel = $this->createLevel($olderSet, 'lesson'); + $olderRepository = app(ElementRepository::class); + $olderRepository->create(new CreateElementDto( + name: 'First lesson', + level: $olderLevel, + parentElement: null, + )); + $olderRepository->create(new CreateElementDto( + name: 'Second lesson', + level: $olderLevel, + parentElement: null, + )); + $newerSet = $this->createSet($user, 'Newer course'); + $newerLevel = $this->createLevel($newerSet, 'chapter'); + app(ElementRepository::class)->create(new CreateElementDto( + name: 'Only chapter', + level: $newerLevel, + parentElement: null, + )); + $this->createSession($user, 'valid-token'); + $this->createSession($otherUser, 'other-token'); + + $this->credentialedPost('/api/schedules', [ + 'setId' => $olderSet->getId(), + 'levelId' => $olderLevel->getId(), + 'startDate' => '2026-08-15', + 'targetDate' => '2026-08-15', + ])->assertCreated(); + $this->credentialedPost('/api/schedules', [ + 'setId' => $newerSet->getId(), + 'levelId' => $newerLevel->getId(), + 'startDate' => '2026-08-15', + 'targetDate' => '2026-08-15', + ])->assertCreated(); + $this->withCredentials() + ->withUnencryptedCookie( + AuthMiddleware::COOKIE_NAME, + 'other-token', + )->postJson('/api/schedules', [ + 'setId' => $newerSet->getId(), + 'levelId' => $newerLevel->getId(), + 'startDate' => '2026-08-15', + 'targetDate' => '2026-08-15', + ])->assertCreated(); + $this->credentialedPost('/api/schedules', [ + 'setId' => $olderSet->getId(), + 'levelId' => $olderLevel->getId(), + 'startDate' => '2026-08-16', + 'targetDate' => '2026-08-16', + ])->assertCreated(); + + $this->credentialedGet('/api/assignments?date=2026-08-15') + ->assertOk() + ->assertExactJson([ + 'date' => '2026-08-15', + 'assignments' => [ + [ + 'id' => 3, + 'schedule' => [ + 'id' => 2, + 'set' => [ + 'name' => 'Newer course', + ], + ], + 'element' => [ + 'name' => 'Only chapter', + 'kind' => 'chapter', + 'path' => ['Only chapter'], + ], + ], + [ + 'id' => 1, + 'schedule' => [ + 'id' => 1, + 'set' => [ + 'name' => 'Older course', + ], + ], + 'element' => [ + 'name' => 'First lesson', + 'kind' => 'lesson', + 'path' => ['First lesson'], + ], + ], + [ + 'id' => 2, + 'schedule' => [ + 'id' => 1, + 'set' => [ + 'name' => 'Older course', + ], + ], + 'element' => [ + 'name' => 'Second lesson', + 'kind' => 'lesson', + 'path' => ['Second lesson'], + ], + ], + ], + ]); + } + + public function test_it_returns_an_empty_assignment_list_for_a_date(): void + { + $user = $this->createUser('reader@example.com'); + $this->createSession($user, 'valid-token'); + + $this->credentialedGet('/api/assignments?date=2026-08-15') + ->assertOk() + ->assertExactJson([ + 'date' => '2026-08-15', + 'assignments' => [], + ]); + } + + public function test_it_rejects_invalid_assignment_dates(): void + { + $user = $this->createUser('reader@example.com'); + $this->createSession($user, 'valid-token'); + + $this->credentialedGet('/api/assignments') + ->assertBadRequest() + ->assertExactJson(['error' => 'date is required']); + } + public function test_it_is_stable_after_sources_change_or_are_deleted(): void { $user = $this->createUser('reader@example.com'); @@ -323,6 +453,8 @@ class ScheduleEndpointTest extends TestCase public function test_schedule_endpoints_require_authentication(): void { + $this->getJson('/api/assignments?date=2026-08-15') + ->assertStatus(401); $this->getJson('/api/schedules')->assertStatus(401); $this->getJson('/api/schedules/1')->assertStatus(401); $this->postJson('/api/schedules', [])->assertStatus(401); diff --git a/backend/tests/Unit/Schedule/UseCases/ListAssignmentsForDateTest.php b/backend/tests/Unit/Schedule/UseCases/ListAssignmentsForDateTest.php new file mode 100644 index 0000000..1d1e3fd --- /dev/null +++ b/backend/tests/Unit/Schedule/UseCases/ListAssignmentsForDateTest.php @@ -0,0 +1,143 @@ +user(1, 'reader@example.com'); + $otherUser = $this->user(2, 'other@example.com'); + $repository = new FakeScheduleRepository; + $repository->create($this->schedule( + user: $user, + setName: 'Older plan', + date: '2026-08-15', + assignmentNames: ['First', 'Second'], + )); + $repository->create($this->schedule( + user: $user, + setName: 'Newer plan', + date: '2026-08-15', + assignmentNames: ['Third'], + )); + $repository->create($this->schedule( + user: $otherUser, + setName: 'Private plan', + date: '2026-08-15', + assignmentNames: ['Hidden'], + )); + $repository->create($this->schedule( + user: $user, + setName: 'Tomorrow plan', + date: '2026-08-16', + assignmentNames: ['Later'], + )); + + $assignments = (new ListAssignmentsForDate($repository))->execute( + new ListAssignmentsForDateRequest( + user: $user, + date: '2026-08-15', + ), + ); + + $this->assertSame( + ['Newer plan', 'Older plan', 'Older plan'], + array_map(function ($assignment): string { + return $assignment->getSetName(); + }, $assignments), + ); + $this->assertSame( + ['Third', 'First', 'Second'], + array_map(function ($assignment): string { + return $assignment->getAssignment()->getName(); + }, $assignments), + ); + $this->assertSame(2, $assignments[0]->getScheduleId()); + } + + public function test_it_rejects_a_missing_date(): void + { + $this->expectException(BadRequestException::class); + $this->expectExceptionMessage('date is required'); + + (new ListAssignmentsForDate(new FakeScheduleRepository))->execute( + new ListAssignmentsForDateRequest( + user: $this->user(1, 'reader@example.com'), + date: null, + ), + ); + } + + public function test_it_rejects_an_invalid_date(): void + { + $this->expectException(BadRequestException::class); + $this->expectExceptionMessage( + 'date must be a valid date in YYYY-MM-DD format', + ); + + (new ListAssignmentsForDate(new FakeScheduleRepository))->execute( + new ListAssignmentsForDateRequest( + user: $this->user(1, 'reader@example.com'), + date: '2026-02-30', + ), + ); + } + + private function user(int $id, string $email): User + { + return new User( + id: $id, + email: new EmailAddress($email), + passwordHash: 'hashed-password', + ); + } + + /** + * @param list $assignmentNames + */ + private function schedule( + User $user, + string $setName, + string $date, + array $assignmentNames, + ): CreateScheduleDto { + $scheduledDate = new DateTimeImmutable( + $date, + new DateTimeZone('UTC'), + ); + $assignments = []; + + foreach ($assignmentNames as $index => $name) { + $assignments[] = new CreateScheduleAssignmentDto( + name: $name, + kind: 'lesson', + path: [$setName, $name], + scheduledDate: $scheduledDate, + position: $index + 1, + ); + } + + return new CreateScheduleDto( + user: $user, + setName: $setName, + elementKind: 'lesson', + startDate: $scheduledDate, + targetDate: $scheduledDate, + assignments: $assignments, + ); + } +}