Attainly/backend/tests/Unit/Schedule/UseCases/CreateScheduleTest.php

314 lines
9.6 KiB
PHP

<?php
namespace Tests\Unit\Schedule\UseCases;
use App\Element\CreateElementDto;
use App\Exceptions\BadRequestException;
use App\Exceptions\NotFoundException;
use App\Schedule\UseCases\CreateSchedule\CreateSchedule;
use App\Schedule\UseCases\CreateSchedule\CreateScheduleRequest;
use App\Set\CreateSetDto;
use App\Shared\ValueObject\EmailAddress;
use App\User\User;
use PHPUnit\Framework\TestCase;
use Tests\Fakes\FakeElementRepository;
use Tests\Fakes\FakeScheduleRepository;
use Tests\Fakes\FakeSetRepository;
class CreateScheduleTest extends TestCase
{
public function test_it_schedules_every_matching_element_in_tree_order(): void
{
$user = $this->user();
$setRepository = new FakeSetRepository;
$elementRepository = new FakeElementRepository;
$scheduleRepository = new FakeScheduleRepository;
$set = $setRepository->create(new CreateSetDto(
name: 'Bible',
creator: $user,
));
$genesis = $elementRepository->create(new CreateElementDto(
set: $set,
name: 'Genesis',
kind: 'book',
parentElement: null,
));
$exodus = $elementRepository->create(new CreateElementDto(
set: $set,
name: 'Exodus',
kind: 'book',
parentElement: null,
));
$exodusChapterOne = $elementRepository->create(
new CreateElementDto(
set: $set,
name: 'Exodus 1',
kind: 'chapter',
parentElement: $exodus,
),
);
$creation = $elementRepository->create(new CreateElementDto(
set: $set,
name: 'Creation',
kind: 'portion',
parentElement: $genesis,
));
$genesisChapterOne = $elementRepository->create(
new CreateElementDto(
set: $set,
name: 'Genesis 1',
kind: 'chapter',
parentElement: $creation,
),
);
$genesisChapterTwo = $elementRepository->create(
new CreateElementDto(
set: $set,
name: 'Genesis 2',
kind: 'chapter',
parentElement: $creation,
),
);
$genesisChapterThree = $elementRepository->create(
new CreateElementDto(
set: $set,
name: 'Genesis 3',
kind: 'chapter',
parentElement: $creation,
),
);
$exodusChapterTwo = $elementRepository->create(
new CreateElementDto(
set: $set,
name: 'Exodus 2',
kind: 'chapter',
parentElement: $exodus,
),
);
$schedule = (new CreateSchedule(
$setRepository,
$elementRepository,
$scheduleRepository,
))->execute(new CreateScheduleRequest(
user: $user,
setId: $set->getId(),
elementKind: 'chapter',
startDate: '2026-08-10',
targetDate: '2026-08-12',
));
$this->assertSame('chapter', $schedule->getElementKind());
$this->assertSame('2026-08-10', $schedule->getStartDate()->format(
'Y-m-d',
));
$this->assertSame('2026-08-12', $schedule->getTargetDate()->format(
'Y-m-d',
));
$this->assertSame(
[
$genesisChapterOne->getId(),
$genesisChapterTwo->getId(),
$genesisChapterThree->getId(),
$exodusChapterOne->getId(),
$exodusChapterTwo->getId(),
],
array_map(function ($assignment): int {
return $assignment->getElement()->getId();
}, $schedule->getAssignments()),
);
$this->assertSame(
[
'2026-08-10',
'2026-08-10',
'2026-08-11',
'2026-08-11',
'2026-08-12',
],
array_map(function ($assignment): string {
return $assignment->getScheduledDate()->format('Y-m-d');
}, $schedule->getAssignments()),
);
}
public function test_it_spreads_sparse_work_across_the_full_range(): void
{
$user = $this->user();
$setRepository = new FakeSetRepository;
$elementRepository = new FakeElementRepository;
$set = $setRepository->create(new CreateSetDto(
name: 'Course',
creator: $user,
));
foreach (['First', 'Second', 'Third'] as $name) {
$elementRepository->create(new CreateElementDto(
set: $set,
name: $name,
kind: 'lesson',
parentElement: null,
));
}
$schedule = (new CreateSchedule(
$setRepository,
$elementRepository,
new FakeScheduleRepository,
))->execute(new CreateScheduleRequest(
user: $user,
setId: $set->getId(),
elementKind: 'lesson',
startDate: '2026-08-10',
targetDate: '2026-08-16',
));
$this->assertSame(
['2026-08-10', '2026-08-13', '2026-08-16'],
array_map(function ($assignment): string {
return $assignment->getScheduledDate()->format('Y-m-d');
}, $schedule->getAssignments()),
);
}
public function test_it_places_one_element_on_the_start_date(): void
{
$user = $this->user();
$setRepository = new FakeSetRepository;
$elementRepository = new FakeElementRepository;
$set = $setRepository->create(new CreateSetDto(
name: 'Project',
creator: $user,
));
$elementRepository->create(new CreateElementDto(
set: $set,
name: 'Ship it',
kind: 'milestone',
parentElement: null,
));
$schedule = (new CreateSchedule(
$setRepository,
$elementRepository,
new FakeScheduleRepository,
))->execute(new CreateScheduleRequest(
user: $user,
setId: $set->getId(),
elementKind: 'milestone',
startDate: '2020-01-01',
targetDate: '2030-01-01',
));
$this->assertSame(
'2020-01-01',
$schedule->getAssignments()[0]
->getScheduledDate()
->format('Y-m-d'),
);
}
public function test_it_rejects_an_unknown_set(): void
{
$this->expectException(NotFoundException::class);
$this->expectExceptionMessage('set not found');
(new CreateSchedule(
new FakeSetRepository,
new FakeElementRepository,
new FakeScheduleRepository,
))->execute(new CreateScheduleRequest(
user: $this->user(),
setId: 999,
elementKind: 'chapter',
startDate: '2026-08-10',
targetDate: '2026-08-12',
));
}
public function test_it_rejects_an_unavailable_element_kind(): void
{
$user = $this->user();
$setRepository = new FakeSetRepository;
$set = $setRepository->create(new CreateSetDto(
name: 'Bible',
creator: $user,
));
$this->expectException(BadRequestException::class);
$this->expectExceptionMessage(
'elementKind is not available for set',
);
(new CreateSchedule(
$setRepository,
new FakeElementRepository,
new FakeScheduleRepository,
))->execute(new CreateScheduleRequest(
user: $user,
setId: $set->getId(),
elementKind: 'Chapter',
startDate: '2026-08-10',
targetDate: '2026-08-12',
));
}
public function test_it_rejects_invalid_dates(): void
{
$user = $this->user();
$setRepository = new FakeSetRepository;
$set = $setRepository->create(new CreateSetDto(
name: 'Bible',
creator: $user,
));
$this->expectException(BadRequestException::class);
$this->expectExceptionMessage('startDate must be a valid date');
(new CreateSchedule(
$setRepository,
new FakeElementRepository,
new FakeScheduleRepository,
))->execute(new CreateScheduleRequest(
user: $user,
setId: $set->getId(),
elementKind: 'chapter',
startDate: '2026-02-30',
targetDate: '2026-08-12',
));
}
public function test_it_rejects_a_target_before_the_start(): void
{
$user = $this->user();
$setRepository = new FakeSetRepository;
$set = $setRepository->create(new CreateSetDto(
name: 'Bible',
creator: $user,
));
$this->expectException(BadRequestException::class);
$this->expectExceptionMessage(
'targetDate must not be before startDate',
);
(new CreateSchedule(
$setRepository,
new FakeElementRepository,
new FakeScheduleRepository,
))->execute(new CreateScheduleRequest(
user: $user,
setId: $set->getId(),
elementKind: 'chapter',
startDate: '2026-08-12',
targetDate: '2026-08-10',
));
}
private function user(): User
{
return new User(
id: 7,
email: new EmailAddress('reader@example.com'),
passwordHash: 'hashed-password',
);
}
}