218 lines
7.4 KiB
PHP
218 lines
7.4 KiB
PHP
<?php
|
|
|
|
namespace Tests\Unit\Schedule\UseCases;
|
|
|
|
use App\Exceptions\BadRequestException;
|
|
use App\Exceptions\NotFoundException;
|
|
use App\Schedule\CreateScheduleAssignmentDto;
|
|
use App\Schedule\CreateScheduleDto;
|
|
use App\Schedule\EvenDistributionScheduler;
|
|
use App\Schedule\ScheduleAssignment;
|
|
use App\Schedule\UseCases\RescheduleSchedule\RescheduleSchedule;
|
|
use App\Schedule\UseCases\RescheduleSchedule\RescheduleScheduleRequest;
|
|
use App\Shared\ValueObject\EmailAddress;
|
|
use App\User\User;
|
|
use DateTimeImmutable;
|
|
use DateTimeZone;
|
|
use PHPUnit\Framework\TestCase;
|
|
use Tests\Fakes\FakeScheduleRepository;
|
|
|
|
class RescheduleScheduleTest extends TestCase
|
|
{
|
|
public function test_it_redistributes_only_unfinished_assignments(): void
|
|
{
|
|
$user = $this->user(1, 'reader@example.com');
|
|
$repository = new FakeScheduleRepository;
|
|
$schedule = $repository->create($this->schedule($user));
|
|
$assignments = $schedule->getAssignments();
|
|
$firstCompletion = $this->utc('2026-08-15T10:00:00');
|
|
$secondCompletion = $this->utc('2026-08-16T11:00:00');
|
|
$assignments[1]->complete($firstCompletion);
|
|
$assignments[3]->complete($secondCompletion);
|
|
$repository->updateAssignment($assignments[1]);
|
|
$repository->updateAssignment($assignments[3]);
|
|
|
|
$rescheduled = $this->useCase($repository)->execute(
|
|
new RescheduleScheduleRequest(
|
|
scheduleId: $schedule->getId(),
|
|
user: $user,
|
|
startDate: '2026-08-20',
|
|
targetDate: '2026-08-21',
|
|
workloadPlacement: 'end',
|
|
),
|
|
);
|
|
|
|
$this->assertSame('2026-08-20', $rescheduled->getStartDate()
|
|
->format('Y-m-d'));
|
|
$this->assertSame('2026-08-21', $rescheduled->getTargetDate()
|
|
->format('Y-m-d'));
|
|
$this->assertSame(
|
|
[
|
|
'2026-08-20',
|
|
'2026-08-12',
|
|
'2026-08-21',
|
|
'2026-08-14',
|
|
'2026-08-21',
|
|
],
|
|
array_map(function (ScheduleAssignment $assignment): string {
|
|
return $assignment->getScheduledDate()->format('Y-m-d');
|
|
}, $rescheduled->getAssignments()),
|
|
);
|
|
$this->assertSame(
|
|
[null, $firstCompletion, null, $secondCompletion, null],
|
|
array_map(function (
|
|
ScheduleAssignment $assignment,
|
|
): ?DateTimeImmutable {
|
|
return $assignment->getCompletedAt();
|
|
}, $rescheduled->getAssignments()),
|
|
);
|
|
$this->assertSame(
|
|
[1, 2, 3, 4, 5],
|
|
array_map(function (ScheduleAssignment $assignment): int {
|
|
return $assignment->getId();
|
|
}, $rescheduled->getAssignments()),
|
|
);
|
|
$this->assertSame(
|
|
['Lesson 1', 'Lesson 2', 'Lesson 3', 'Lesson 4', 'Lesson 5'],
|
|
array_map(function (ScheduleAssignment $assignment): string {
|
|
return $assignment->getName();
|
|
}, $rescheduled->getAssignments()),
|
|
);
|
|
}
|
|
|
|
public function test_it_rejects_a_target_before_the_start(): void
|
|
{
|
|
$user = $this->user(1, 'reader@example.com');
|
|
$repository = new FakeScheduleRepository;
|
|
$schedule = $repository->create($this->schedule($user));
|
|
|
|
$this->expectException(BadRequestException::class);
|
|
$this->expectExceptionMessage(
|
|
'targetDate must not be before startDate',
|
|
);
|
|
|
|
$this->useCase($repository)->execute(
|
|
new RescheduleScheduleRequest(
|
|
scheduleId: $schedule->getId(),
|
|
user: $user,
|
|
startDate: '2026-08-21',
|
|
targetDate: '2026-08-20',
|
|
workloadPlacement: 'middle',
|
|
),
|
|
);
|
|
}
|
|
|
|
public function test_it_rejects_an_unknown_workload_placement(): void
|
|
{
|
|
$user = $this->user(1, 'reader@example.com');
|
|
$repository = new FakeScheduleRepository;
|
|
$schedule = $repository->create($this->schedule($user));
|
|
|
|
$this->expectException(BadRequestException::class);
|
|
$this->expectExceptionMessage(
|
|
'workloadPlacement must be start, middle, or end',
|
|
);
|
|
|
|
$this->useCase($repository)->execute(
|
|
new RescheduleScheduleRequest(
|
|
scheduleId: $schedule->getId(),
|
|
user: $user,
|
|
startDate: '2026-08-20',
|
|
targetDate: '2026-08-21',
|
|
workloadPlacement: 'sideways',
|
|
),
|
|
);
|
|
}
|
|
|
|
public function test_it_rejects_a_schedule_without_unfinished_work(): void
|
|
{
|
|
$user = $this->user(1, 'reader@example.com');
|
|
$repository = new FakeScheduleRepository;
|
|
$schedule = $repository->create($this->schedule($user));
|
|
foreach ($schedule->getAssignments() as $assignment) {
|
|
$assignment->complete($this->utc('2026-08-15T10:00:00'));
|
|
$repository->updateAssignment($assignment);
|
|
}
|
|
|
|
$this->expectException(BadRequestException::class);
|
|
$this->expectExceptionMessage(
|
|
'schedule has no unfinished assignments',
|
|
);
|
|
|
|
$this->useCase($repository)->execute(
|
|
new RescheduleScheduleRequest(
|
|
scheduleId: $schedule->getId(),
|
|
user: $user,
|
|
startDate: '2026-08-20',
|
|
targetDate: '2026-08-21',
|
|
workloadPlacement: 'middle',
|
|
),
|
|
);
|
|
}
|
|
|
|
public function test_it_hides_another_users_schedule(): void
|
|
{
|
|
$repository = new FakeScheduleRepository;
|
|
$owner = $this->user(1, 'owner@example.com');
|
|
$schedule = $repository->create($this->schedule($owner));
|
|
|
|
$this->expectException(NotFoundException::class);
|
|
$this->expectExceptionMessage('schedule not found');
|
|
|
|
$this->useCase($repository)->execute(
|
|
new RescheduleScheduleRequest(
|
|
scheduleId: $schedule->getId(),
|
|
user: $this->user(2, 'viewer@example.com'),
|
|
startDate: '2026-08-20',
|
|
targetDate: '2026-08-21',
|
|
workloadPlacement: 'middle',
|
|
),
|
|
);
|
|
}
|
|
|
|
private function useCase(
|
|
FakeScheduleRepository $repository,
|
|
): RescheduleSchedule {
|
|
return new RescheduleSchedule(
|
|
$repository,
|
|
new EvenDistributionScheduler,
|
|
);
|
|
}
|
|
|
|
private function schedule(User $user): CreateScheduleDto
|
|
{
|
|
$assignments = [];
|
|
foreach (range(1, 5) as $number) {
|
|
$assignments[] = new CreateScheduleAssignmentDto(
|
|
name: "Lesson {$number}",
|
|
kind: 'lesson',
|
|
path: ['Course', "Lesson {$number}"],
|
|
scheduledDate: $this->utc("2026-08-1{$number}"),
|
|
position: $number,
|
|
);
|
|
}
|
|
|
|
return new CreateScheduleDto(
|
|
user: $user,
|
|
setName: 'Course',
|
|
elementKind: 'lesson',
|
|
startDate: $this->utc('2026-08-10'),
|
|
targetDate: $this->utc('2026-08-15'),
|
|
assignments: $assignments,
|
|
);
|
|
}
|
|
|
|
private function user(int $id, string $email): User
|
|
{
|
|
return new User(
|
|
id: $id,
|
|
email: new EmailAddress($email),
|
|
passwordHash: 'hashed-password',
|
|
);
|
|
}
|
|
|
|
private function utc(string $value): DateTimeImmutable
|
|
{
|
|
return new DateTimeImmutable($value, new DateTimeZone('UTC'));
|
|
}
|
|
}
|