test sparse schedule placement

This commit is contained in:
Yisroel Baum 2026-08-19 22:38:34 +03:00
parent 6c8250b3a4
commit add0684610
Signed by: yisroelbaum
GPG key ID: 0FA60884F75520A9
3 changed files with 130 additions and 14 deletions

View file

@ -196,6 +196,40 @@ class ScheduleEndpointTest extends TestCase
$this->assertDatabaseCount('schedules', 1); $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 public function test_it_rejects_an_unknown_workload_placement(): void
{ {
$user = $this->createUser('reader@example.com'); $user = $this->createUser('reader@example.com');
@ -215,7 +249,34 @@ class ScheduleEndpointTest extends TestCase
'targetDate' => '2026-08-14', 'targetDate' => '2026-08-14',
'workloadPlacement' => 'sideways', 'workloadPlacement' => 'sideways',
])->assertBadRequest()->assertExactJson([ ])->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',
]); ]);
} }

View file

@ -58,20 +58,75 @@ 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( $scheduler = new EvenDistributionScheduler;
assignmentCount: 3, $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',
],
];
foreach (WorkloadPlacement::cases() as $placement) {
$dates = $scheduler->scheduledDates(
assignmentCount: 4,
startDate: $this->utc('2026-08-10'), startDate: $this->utc('2026-08-10'),
targetDate: $this->utc('2026-08-16'), targetDate: $this->utc('2026-08-15'),
workloadPlacement: WorkloadPlacement::Middle, workloadPlacement: $placement,
); );
$this->assertSame( $this->assertSame(
['2026-08-10', '2026-08-13', '2026-08-16'], $expectedDates[$placement->value],
$this->formattedDates($dates), $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 public function test_it_distributes_even_and_single_day_schedules(): void
{ {

View file

@ -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(); $user = $this->user();
$setRepository = new FakeSetRepository; $setRepository = new FakeSetRepository;
@ -192,7 +192,7 @@ class CreateScheduleTest extends TestCase
levelId: $lessonLevel->getId(), levelId: $lessonLevel->getId(),
startDate: '2026-08-10', startDate: '2026-08-10',
targetDate: '2026-08-16', targetDate: '2026-08-16',
workloadPlacement: 'middle', workloadPlacement: null,
)); ));
$this->assertSame( $this->assertSame(