218 lines
7.2 KiB
PHP
218 lines
7.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Service\Season;
|
|
|
|
use App\Config\SeasonTask;
|
|
use App\Entity\SeasonAssignment;
|
|
use App\Entity\SeasonAssignmentTask;
|
|
use App\Entity\SeasonTaskDocument;
|
|
use App\Entity\Upload;
|
|
use App\Repository\SeasonTaskDocumentRepository;
|
|
use App\Service\Season\SeasonTaskSynchronizer;
|
|
use Doctrine\ORM\EntityManagerInterface;
|
|
use PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations;
|
|
use PHPUnit\Framework\MockObject\MockObject;
|
|
use PHPUnit\Framework\MockObject\Stub;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
/**
|
|
* What makes the enum extensible is that this fills in whatever an assignment is missing, so
|
|
* that is what is asserted: new entries appear on assignments that already exist, and rows of
|
|
* an entry that was removed are left alone rather than deleted.
|
|
*/
|
|
#[AllowMockObjectsWithoutExpectations]
|
|
class SeasonTaskSynchronizerTest extends TestCase
|
|
{
|
|
private EntityManagerInterface&MockObject $entityManager;
|
|
private SeasonTaskDocumentRepository&Stub $seasonTaskDocumentRepository;
|
|
private SeasonTaskSynchronizer $synchronizer;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
$this->entityManager = $this->createMock(EntityManagerInterface::class);
|
|
$this->seasonTaskDocumentRepository = $this->createStub(SeasonTaskDocumentRepository::class);
|
|
|
|
$this->synchronizer = new SeasonTaskSynchronizer(
|
|
$this->entityManager,
|
|
$this->seasonTaskDocumentRepository
|
|
);
|
|
}
|
|
|
|
public function testItCreatesOneRowPerEntryInEnumOrder(): void
|
|
{
|
|
$seasonAssignment = new SeasonAssignment();
|
|
|
|
$this->entityManager->expects($this->exactly(count(SeasonTask::cases())))->method('persist');
|
|
$this->entityManager->expects($this->once())->method('flush');
|
|
|
|
$this->synchronizer->sync($seasonAssignment);
|
|
|
|
$this->assertSame(
|
|
array_map(static fn (SeasonTask $task): string => $task->value, SeasonTask::cases()),
|
|
$this->keysOf($seasonAssignment)
|
|
);
|
|
}
|
|
|
|
public function testItOnlyCreatesWhatIsMissing(): void
|
|
{
|
|
$seasonAssignment = new SeasonAssignment();
|
|
$seasonAssignment->addTask((new SeasonAssignmentTask())->setTaskKey(SeasonTask::CONTRACT->value));
|
|
|
|
$this->entityManager->expects($this->exactly(count(SeasonTask::cases()) - 1))->method('persist');
|
|
|
|
$this->synchronizer->sync($seasonAssignment);
|
|
|
|
$this->assertCount(count(SeasonTask::cases()), $seasonAssignment->getTasks());
|
|
$this->assertSame(
|
|
1,
|
|
count(array_filter(
|
|
$this->keysOf($seasonAssignment),
|
|
static fn (string $key): bool => SeasonTask::CONTRACT->value === $key
|
|
))
|
|
);
|
|
}
|
|
|
|
public function testASyncedAssignmentIsNotFlushedAgain(): void
|
|
{
|
|
$seasonAssignment = new SeasonAssignment();
|
|
|
|
foreach (SeasonTask::cases() as $case) {
|
|
$seasonAssignment->addTask((new SeasonAssignmentTask())->setTaskKey($case->value));
|
|
}
|
|
|
|
$this->entityManager->expects($this->never())->method('persist');
|
|
$this->entityManager->expects($this->never())->method('flush');
|
|
|
|
$this->synchronizer->sync($seasonAssignment);
|
|
}
|
|
|
|
public function testARowOfAnUnknownEntryIsKept(): void
|
|
{
|
|
$seasonAssignment = new SeasonAssignment();
|
|
$seasonAssignment->addTask((new SeasonAssignmentTask())->setTaskKey('entry_removed_from_the_enum'));
|
|
|
|
$this->entityManager->expects($this->never())->method('remove');
|
|
|
|
$this->synchronizer->sync($seasonAssignment);
|
|
|
|
$this->assertCount(count(SeasonTask::cases()) + 1, $seasonAssignment->getTasks());
|
|
$this->assertContains('entry_removed_from_the_enum', $this->keysOf($seasonAssignment));
|
|
}
|
|
|
|
public function testAnEntryHandingOutADepositedDocumentStartsTickedForTheOffice(): void
|
|
{
|
|
$this->depositDocumentsFor(SeasonTask::MASTER_DATA);
|
|
|
|
$seasonAssignment = new SeasonAssignment();
|
|
|
|
$this->synchronizer->sync($seasonAssignment);
|
|
|
|
$this->assertSame([SeasonTask::MASTER_DATA->value], $this->officeDoneKeysOf($seasonAssignment));
|
|
}
|
|
|
|
/**
|
|
* The row keeps the file it was given. Replacing the library document later is what the next
|
|
* season gets, and what the propagate action carries over by hand - never a read of this row.
|
|
*/
|
|
public function testTheDocumentIsCopiedOntoTheRow(): void
|
|
{
|
|
$document = new Upload();
|
|
|
|
$this->depositDocuments([SeasonTask::MASTER_DATA], $document);
|
|
|
|
$seasonAssignment = new SeasonAssignment();
|
|
|
|
$this->synchronizer->sync($seasonAssignment);
|
|
|
|
foreach ($seasonAssignment->getTasks() as $task) {
|
|
$this->assertSame(
|
|
SeasonTask::MASTER_DATA->value === $task->getTaskKey() ? $document : null,
|
|
$task->getDocument(),
|
|
sprintf('Entry "%s" was given the wrong document.', $task->getTaskKey())
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The entry hands a document out, it is just not deposited yet - so the office still has to
|
|
* put it together and the row starts open.
|
|
*/
|
|
public function testAnEntryWithoutADepositedDocumentStartsOpen(): void
|
|
{
|
|
$this->depositDocumentsFor();
|
|
|
|
$seasonAssignment = new SeasonAssignment();
|
|
|
|
$this->synchronizer->sync($seasonAssignment);
|
|
|
|
$this->assertSame([], $this->officeDoneKeysOf($seasonAssignment));
|
|
}
|
|
|
|
public function testThePreTickNeverOverwritesAnExistingRow(): void
|
|
{
|
|
$this->depositDocumentsFor(SeasonTask::MASTER_DATA);
|
|
|
|
$seasonAssignment = new SeasonAssignment();
|
|
$seasonAssignment->addTask(
|
|
(new SeasonAssignmentTask())->setTaskKey(SeasonTask::MASTER_DATA->value)
|
|
);
|
|
|
|
$this->synchronizer->sync($seasonAssignment);
|
|
|
|
$this->assertSame(
|
|
[],
|
|
$this->officeDoneKeysOf($seasonAssignment),
|
|
'The office unticked this row, and the next sync has to leave it alone.'
|
|
);
|
|
}
|
|
|
|
private function depositDocumentsFor(SeasonTask ...$tasks): void
|
|
{
|
|
$this->depositDocuments($tasks, new Upload());
|
|
}
|
|
|
|
/**
|
|
* @param SeasonTask[] $tasks
|
|
*/
|
|
private function depositDocuments(array $tasks, Upload $document): void
|
|
{
|
|
$documents = [];
|
|
|
|
foreach ($tasks as $task) {
|
|
$documents[$task->value] = (new SeasonTaskDocument())
|
|
->setTaskKey($task->value)
|
|
->setDocument($document)
|
|
;
|
|
}
|
|
|
|
$this->seasonTaskDocumentRepository->method('findIndexedByTaskKey')->willReturn($documents);
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
private function officeDoneKeysOf(SeasonAssignment $seasonAssignment): array
|
|
{
|
|
return array_values(array_map(
|
|
static fn (SeasonAssignmentTask $task): string => $task->getTaskKey(),
|
|
array_filter(
|
|
$seasonAssignment->getTasks()->toArray(),
|
|
static fn (SeasonAssignmentTask $task): bool => $task->isOfficeDone()
|
|
)
|
|
));
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
private function keysOf(SeasonAssignment $seasonAssignment): array
|
|
{
|
|
return array_map(
|
|
static fn (SeasonAssignmentTask $task): string => $task->getTaskKey(),
|
|
$seasonAssignment->getTasks()->toArray()
|
|
);
|
|
}
|
|
}
|