diff --git a/backend/tests/Feature/Schedule/ScheduleEndpointTest.php b/backend/tests/Feature/Schedule/ScheduleEndpointTest.php index 5fed467..3a68664 100644 --- a/backend/tests/Feature/Schedule/ScheduleEndpointTest.php +++ b/backend/tests/Feature/Schedule/ScheduleEndpointTest.php @@ -196,6 +196,40 @@ class ScheduleEndpointTest extends TestCase $this->assertDatabaseCount('schedules', 1); } + public function test_it_packs_sparse_assignments_at_the_requested_end(): void + { + $user = $this->createUser('reader@example.com'); + $set = $this->createSet($user, 'Course'); + $lessonLevel = $this->createLevel($set, 'lesson'); + $repository = app(ElementRepository::class); + + foreach (range(1, 4) as $number) { + $repository->create(new CreateElementDto( + name: "Lesson {$number}", + level: $lessonLevel, + parentElement: null, + )); + } + $this->createSession($user, 'valid-token'); + + $response = $this->credentialedPost('/api/schedules', [ + 'setId' => $set->getId(), + 'levelId' => $lessonLevel->getId(), + 'startDate' => '2026-08-10', + 'targetDate' => '2026-08-15', + 'workloadPlacement' => 'end', + ])->assertCreated(); + + $days = $response->json('schedule.days'); + $this->assertIsArray($days); + $this->assertSame( + [0, 0, 1, 1, 1, 1], + array_map(function (array $day): int { + return count($day['assignments']); + }, $days), + ); + } + public function test_it_rejects_an_unknown_workload_placement(): void { $user = $this->createUser('reader@example.com'); @@ -215,7 +249,34 @@ class ScheduleEndpointTest extends TestCase 'targetDate' => '2026-08-14', 'workloadPlacement' => 'sideways', ])->assertBadRequest()->assertExactJson([ - 'error' => 'workloadPlacement must be start, middle, or end', + 'error' => 'workloadPlacement must be spread, start, middle, or end', + ]); + } + + public function test_it_rejects_spread_placement_for_a_dense_schedule(): void + { + $user = $this->createUser('reader@example.com'); + $set = $this->createSet($user, 'Course'); + $lessonLevel = $this->createLevel($set, 'lesson'); + $repository = app(ElementRepository::class); + + foreach (range(1, 5) as $number) { + $repository->create(new CreateElementDto( + name: "Lesson {$number}", + level: $lessonLevel, + parentElement: null, + )); + } + $this->createSession($user, 'valid-token'); + + $this->credentialedPost('/api/schedules', [ + 'setId' => $set->getId(), + 'levelId' => $lessonLevel->getId(), + 'startDate' => '2026-08-10', + 'targetDate' => '2026-08-13', + 'workloadPlacement' => 'spread', + ])->assertBadRequest()->assertExactJson([ + 'error' => 'spread placement requires fewer assignments than days', ]); } diff --git a/backend/tests/Unit/Schedule/EvenDistributionSchedulerTest.php b/backend/tests/Unit/Schedule/EvenDistributionSchedulerTest.php index f9e63a7..2c0bf0d 100644 --- a/backend/tests/Unit/Schedule/EvenDistributionSchedulerTest.php +++ b/backend/tests/Unit/Schedule/EvenDistributionSchedulerTest.php @@ -58,19 +58,74 @@ class EvenDistributionSchedulerTest extends TestCase } } - public function test_it_preserves_sparse_distribution_across_the_full_range(): void + public function test_it_places_sparse_assignments_at_the_requested_position(): void { - $dates = (new EvenDistributionScheduler)->scheduledDates( - assignmentCount: 3, - startDate: $this->utc('2026-08-10'), - targetDate: $this->utc('2026-08-16'), - workloadPlacement: WorkloadPlacement::Middle, - ); + $scheduler = new EvenDistributionScheduler; + $expectedDates = [ + WorkloadPlacement::Spread->value => [ + '2026-08-10', + '2026-08-12', + '2026-08-13', + '2026-08-15', + ], + WorkloadPlacement::Start->value => [ + '2026-08-10', + '2026-08-11', + '2026-08-12', + '2026-08-13', + ], + WorkloadPlacement::Middle->value => [ + '2026-08-11', + '2026-08-12', + '2026-08-13', + '2026-08-14', + ], + WorkloadPlacement::End->value => [ + '2026-08-12', + '2026-08-13', + '2026-08-14', + '2026-08-15', + ], + ]; - $this->assertSame( - ['2026-08-10', '2026-08-13', '2026-08-16'], - $this->formattedDates($dates), - ); + foreach (WorkloadPlacement::cases() as $placement) { + $dates = $scheduler->scheduledDates( + assignmentCount: 4, + startDate: $this->utc('2026-08-10'), + targetDate: $this->utc('2026-08-15'), + workloadPlacement: $placement, + ); + + $this->assertSame( + $expectedDates[$placement->value], + $this->formattedDates($dates), + ); + } + } + + public function test_it_places_one_assignment_at_the_requested_position(): void + { + $scheduler = new EvenDistributionScheduler; + $expectedDates = [ + WorkloadPlacement::Spread->value => '2026-08-10', + WorkloadPlacement::Start->value => '2026-08-10', + WorkloadPlacement::Middle->value => '2026-08-12', + WorkloadPlacement::End->value => '2026-08-15', + ]; + + foreach (WorkloadPlacement::cases() as $placement) { + $dates = $scheduler->scheduledDates( + assignmentCount: 1, + startDate: $this->utc('2026-08-10'), + targetDate: $this->utc('2026-08-15'), + workloadPlacement: $placement, + ); + + $this->assertSame( + [$expectedDates[$placement->value]], + $this->formattedDates($dates), + ); + } } public function test_it_distributes_even_and_single_day_schedules(): void diff --git a/backend/tests/Unit/Schedule/UseCases/CreateScheduleTest.php b/backend/tests/Unit/Schedule/UseCases/CreateScheduleTest.php index 47cee8d..b5059c4 100644 --- a/backend/tests/Unit/Schedule/UseCases/CreateScheduleTest.php +++ b/backend/tests/Unit/Schedule/UseCases/CreateScheduleTest.php @@ -156,7 +156,7 @@ class CreateScheduleTest extends TestCase ); } - public function test_it_spreads_sparse_work_across_the_full_range(): void + public function test_it_defaults_sparse_work_to_spread_across_the_full_range(): void { $user = $this->user(); $setRepository = new FakeSetRepository; @@ -192,7 +192,7 @@ class CreateScheduleTest extends TestCase levelId: $lessonLevel->getId(), startDate: '2026-08-10', targetDate: '2026-08-16', - workloadPlacement: 'middle', + workloadPlacement: null, )); $this->assertSame(