feat: all-season assignments
This commit is contained in:
1 parent
675cec1b69
commit
cf876ed224
119 files changed
+6194
-294
No files matched your search
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Service\Common;
|
||||
|
||||
use App\Model\TeamerFilterDto;
|
||||
use App\Service\Common\TeamerFilterHandler;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpFoundation\Session\Session;
|
||||
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
|
||||
|
||||
/**
|
||||
* Two teamer lists share one filter form, so the only thing keeping them apart is the session
|
||||
* bucket the settings are stored under. Without the split, filtering the Teamübersicht would
|
||||
* silently narrow the Saisonteamer list as well - and the other way round.
|
||||
*/
|
||||
class TeamerFilterHandlerTest extends TestCase
|
||||
{
|
||||
public function testEachListKeepsItsOwnFilterSettings(): void
|
||||
{
|
||||
$handler = $this->createHandler();
|
||||
|
||||
$this->save($handler->setScope(null), 'Fromme');
|
||||
$this->save($handler->setScope(TeamerFilterHandler::SCOPE_ALL_SEASON), 'Musterfrau');
|
||||
|
||||
$this->assertSame('Fromme', $handler->setScope(null)->getFilterSettings()->getName());
|
||||
$this->assertSame(
|
||||
'Musterfrau',
|
||||
$handler->setScope(TeamerFilterHandler::SCOPE_ALL_SEASON)->getFilterSettings()->getName()
|
||||
);
|
||||
}
|
||||
|
||||
public function testResettingOneListLeavesTheOtherAlone(): void
|
||||
{
|
||||
$handler = $this->createHandler();
|
||||
|
||||
$this->save($handler->setScope(null), 'Fromme');
|
||||
$this->save($handler->setScope(TeamerFilterHandler::SCOPE_ALL_SEASON), 'Musterfrau');
|
||||
|
||||
$handler->setScope(TeamerFilterHandler::SCOPE_ALL_SEASON)->resetFilterSettings();
|
||||
|
||||
$this->assertSame('Fromme', $handler->setScope(null)->getFilterSettings()->getName());
|
||||
$this->assertNull(
|
||||
$handler->setScope(TeamerFilterHandler::SCOPE_ALL_SEASON)->getFilterSettings()->getName()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The bucket is what makes a list an all-season list, so every reader of the settings gets
|
||||
* the scope without having to remember to apply it - the mailing above all.
|
||||
*/
|
||||
public function testTheScopeTravelsWithTheSettings(): void
|
||||
{
|
||||
$handler = $this->createHandler();
|
||||
|
||||
$this->assertFalse($handler->setScope(null)->getFilterSettings()->isAllSeason());
|
||||
$this->assertTrue(
|
||||
$handler->setScope(TeamerFilterHandler::SCOPE_ALL_SEASON)->getFilterSettings()->isAllSeason()
|
||||
);
|
||||
}
|
||||
|
||||
public function testAnUnknownScopeFallsBackToTheGeneralList(): void
|
||||
{
|
||||
$handler = $this->createHandler();
|
||||
|
||||
$this->save($handler->setScope(null), 'Fromme');
|
||||
|
||||
$this->assertSame('Fromme', $handler->setScope('nonsense')->getFilterSettings()->getName());
|
||||
$this->assertFalse($handler->setScope('nonsense')->getFilterSettings()->isAllSeason());
|
||||
}
|
||||
|
||||
private function save(TeamerFilterHandler $handler, string $name): void
|
||||
{
|
||||
$reflection = new \ReflectionMethod($handler, 'saveFilterSettings');
|
||||
$reflection->invoke($handler, (new TeamerFilterDto())->setName($name));
|
||||
}
|
||||
|
||||
private function createHandler(): TeamerFilterHandler
|
||||
{
|
||||
$request = new Request();
|
||||
$request->setSession(new Session(new MockArraySessionStorage()));
|
||||
|
||||
$requestStack = new RequestStack();
|
||||
$requestStack->push($request);
|
||||
|
||||
return new TeamerFilterHandler($requestStack, $this->createMock(EntityManagerInterface::class));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Service\Cron;
|
||||
|
||||
use App\Config\EmailTextKey;
|
||||
use App\Config\HouseCatalog;
|
||||
use App\Config\SeasonCalendar;
|
||||
use App\Config\SeasonTask;
|
||||
use App\Email\Mailer;
|
||||
use App\Entity\Embeddable\Communication;
|
||||
use App\Entity\SeasonAssignment;
|
||||
use App\Entity\SeasonAssignmentTask;
|
||||
use App\Entity\Teamer;
|
||||
use App\Entity\User;
|
||||
use App\Repository\SeasonAssignmentTaskRepository;
|
||||
use App\Repository\UserRepository;
|
||||
use App\Service\Cron\SeasonTaskReminderService;
|
||||
use App\Service\Season\SeasonAssignmentLabeler;
|
||||
use App\Service\Season\SeasonTaskDeadlineResolver;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\NullLogger;
|
||||
|
||||
/**
|
||||
* Both rules pick out exactly one day, which is what lets the cron run twice without mailing
|
||||
* anyone twice. That property, and what does and does not silence a reminder, is what is pinned
|
||||
* here - the queries behind them are asserted in the repository test.
|
||||
*/
|
||||
class SeasonTaskReminderServiceTest extends TestCase
|
||||
{
|
||||
private SeasonAssignmentTaskRepository&MockObject $repository;
|
||||
private UserRepository&MockObject $userRepository;
|
||||
private Mailer&MockObject $mailer;
|
||||
private SeasonTaskReminderService $service;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->repository = $this->createMock(SeasonAssignmentTaskRepository::class);
|
||||
$this->userRepository = $this->createMock(UserRepository::class);
|
||||
$this->mailer = $this->createMock(Mailer::class);
|
||||
|
||||
$this->service = new SeasonTaskReminderService(
|
||||
$this->repository,
|
||||
new SeasonTaskDeadlineResolver('-1 month'),
|
||||
new SeasonAssignmentLabeler(
|
||||
new SeasonCalendar('11-01', '04-01'),
|
||||
new HouseCatalog(['AGR' => 'Rotbach'])
|
||||
),
|
||||
new SeasonCalendar('11-01', '04-01'),
|
||||
$this->userRepository,
|
||||
$this->mailer,
|
||||
new NullLogger(),
|
||||
);
|
||||
}
|
||||
|
||||
public function testTheSummaryFiresOnTheDeadlineDay(): void
|
||||
{
|
||||
$this->repository->method('findOpen')->willReturn([$this->task('2026-11-01')]);
|
||||
$this->userRepository->method('getUsersByRoles')->willReturn([$this->admin()]);
|
||||
|
||||
$this->mailer
|
||||
->expects($this->once())
|
||||
->method('createAndSendEmail')
|
||||
->with(
|
||||
$this->callback(static function (array $context): bool {
|
||||
$entries = reset($context['violations']);
|
||||
|
||||
return 1 === count($entries) && 1 === $entries[0]['level'];
|
||||
}),
|
||||
$this->anything()
|
||||
)
|
||||
;
|
||||
|
||||
$this->service->sendDeadlineSummary(new \DateTimeImmutable('2026-10-01'));
|
||||
}
|
||||
|
||||
public function testTheSummaryDoesNotFireTheDayAfter(): void
|
||||
{
|
||||
$this->repository->method('findOpen')->willReturn([$this->task('2026-11-01')]);
|
||||
|
||||
$this->mailer->expects($this->never())->method('createAndSendEmail');
|
||||
|
||||
$this->service->sendDeadlineSummary(new \DateTimeImmutable('2026-10-02'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Frist 2 defaults to Frist 1, so the two coincide unless the office moves one. The entry is
|
||||
* then reported once and as Frist 1 - a second, identical line would be pure noise.
|
||||
*/
|
||||
public function testCoincidingDeadlinesAreReportedOnceAsTheFirst(): void
|
||||
{
|
||||
$this->repository->method('findOpen')->willReturn([$this->task('2026-11-01')]);
|
||||
$this->userRepository->method('getUsersByRoles')->willReturn([$this->admin()]);
|
||||
|
||||
$levels = [];
|
||||
|
||||
$this->mailer
|
||||
->method('createAndSendEmail')
|
||||
->willReturnCallback(static function (array $context) use (&$levels): void {
|
||||
foreach (reset($context['violations']) as $entry) {
|
||||
$levels[] = $entry['level'];
|
||||
}
|
||||
})
|
||||
;
|
||||
|
||||
$this->service->sendDeadlineSummary(new \DateTimeImmutable('2026-10-01'));
|
||||
|
||||
$this->assertSame([1], $levels);
|
||||
}
|
||||
|
||||
public function testAPushedOutSecondDeadlineGetsItsOwnDay(): void
|
||||
{
|
||||
$task = $this->task('2026-11-01')->setDeadlineOverride2(new \DateTimeImmutable('2026-10-15'));
|
||||
|
||||
$this->repository->method('findOpen')->willReturn([$task]);
|
||||
$this->userRepository->method('getUsersByRoles')->willReturn([$this->admin()]);
|
||||
|
||||
$levels = [];
|
||||
|
||||
$this->mailer
|
||||
->method('createAndSendEmail')
|
||||
->willReturnCallback(static function (array $context) use (&$levels): void {
|
||||
foreach (reset($context['violations']) as $entry) {
|
||||
$levels[] = $entry['level'];
|
||||
}
|
||||
})
|
||||
;
|
||||
|
||||
$this->service->sendDeadlineSummary(new \DateTimeImmutable('2026-10-15'));
|
||||
|
||||
$this->assertSame([2], $levels);
|
||||
}
|
||||
|
||||
public function testAnAdminMutingNotificationsIsSkipped(): void
|
||||
{
|
||||
$this->repository->method('findOpen')->willReturn([$this->task('2026-11-01')]);
|
||||
$this->userRepository->method('getUsersByRoles')->willReturn([
|
||||
$this->admin()->setMuteNotifications(true),
|
||||
]);
|
||||
|
||||
$this->mailer->expects($this->never())->method('createAndSendEmail');
|
||||
|
||||
$this->service->sendDeadlineSummary(new \DateTimeImmutable('2026-10-01'));
|
||||
}
|
||||
|
||||
public function testTheDigestOnlyGoesOutOnMondays(): void
|
||||
{
|
||||
$this->repository->expects($this->never())->method('findOpen');
|
||||
$this->mailer->expects($this->never())->method('createAndSendText');
|
||||
|
||||
// 2026-10-06 is a tuesday
|
||||
$this->service->sendTeamerDigest(new \DateTimeImmutable('2026-10-06'));
|
||||
}
|
||||
|
||||
public function testTheDigestListsTheOpenReturnEntries(): void
|
||||
{
|
||||
$this->repository->method('findOpen')->willReturn([$this->task('2026-11-01')]);
|
||||
|
||||
$this->mailer
|
||||
->expects($this->once())
|
||||
->method('createAndSendText')
|
||||
->with(
|
||||
EmailTextKey::REMINDER_SEASON_TASKS,
|
||||
$this->callback(static fn (array $placeholders): bool => SeasonTask::CONTRACT->label() === $placeholders['tasks']),
|
||||
$this->anything()
|
||||
)
|
||||
;
|
||||
|
||||
$this->service->sendTeamerDigest($this->monday());
|
||||
}
|
||||
|
||||
public function testAnOfficeOnlyEntryNeverReachesTheTeamer(): void
|
||||
{
|
||||
$this->repository
|
||||
->method('findOpen')
|
||||
->willReturn([$this->task('2026-11-01', SeasonTask::SAAS_REGISTERED)])
|
||||
;
|
||||
|
||||
$this->mailer->expects($this->never())->method('createAndSendText');
|
||||
|
||||
$this->service->sendTeamerDigest($this->monday());
|
||||
}
|
||||
|
||||
/**
|
||||
* The flag is the office's own note and silences nothing: only ticking the entry off does.
|
||||
*/
|
||||
public function testDoneTeamerDoesNotSuppressTheDigest(): void
|
||||
{
|
||||
$task = $this->task('2026-11-01');
|
||||
$task->getSeasonAssignment()->setDoneTeamer(true);
|
||||
|
||||
$this->repository->method('findOpen')->willReturn([$task]);
|
||||
|
||||
$this->mailer->expects($this->once())->method('createAndSendText');
|
||||
|
||||
$this->service->sendTeamerDigest($this->monday());
|
||||
}
|
||||
|
||||
private function monday(): \DateTimeImmutable
|
||||
{
|
||||
// 2026-10-05 is a monday, and inside the season the digest is scoped to
|
||||
return new \DateTimeImmutable('2026-10-05');
|
||||
}
|
||||
|
||||
private function task(string $dateFrom, SeasonTask $definition = SeasonTask::CONTRACT): SeasonAssignmentTask
|
||||
{
|
||||
$teamer = new Teamer();
|
||||
$teamer->setCommunication((new Communication())->setEmail('[email protected]'));
|
||||
|
||||
$seasonAssignment = (new SeasonAssignment())
|
||||
->setTeamer($teamer)
|
||||
->setDateFrom(new \DateTimeImmutable($dateFrom))
|
||||
->setDateTo(new \DateTimeImmutable($dateFrom.' +5 months'))
|
||||
->setHotelCode('AGR')
|
||||
;
|
||||
|
||||
$task = (new SeasonAssignmentTask())->setTaskKey($definition->value);
|
||||
$seasonAssignment->addTask($task);
|
||||
|
||||
return $task;
|
||||
}
|
||||
|
||||
private function admin(): User
|
||||
{
|
||||
return (new User())->setEmail('[email protected]');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Service\Season;
|
||||
|
||||
use App\Config\HouseCatalog;
|
||||
use App\Config\SeasonCalendar;
|
||||
use App\Entity\SeasonAssignment;
|
||||
use App\Service\Season\SeasonAssignmentLabeler;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Every list, dialog, log line and mail names a placement through this one class, so what it
|
||||
* produces in the awkward cases - no dates yet, a date outside any season, a house that has left
|
||||
* the configured map - is what the office would see there.
|
||||
*/
|
||||
class SeasonAssignmentLabelerTest extends TestCase
|
||||
{
|
||||
public function testItNamesTheSeasonAndTheHouse(): void
|
||||
{
|
||||
$this->assertSame(
|
||||
'Saison 2026/27 - Rotbach',
|
||||
$this->labeler()->label($this->seasonAssignment('2026-11-01', 'AGR'))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The season window is 01.11.-01.04., so a placement starting in July belongs to no season.
|
||||
* It still has to name itself rather than render blank.
|
||||
*/
|
||||
public function testAPlacementOutsideAnySeasonSaysSo(): void
|
||||
{
|
||||
$this->assertSame(
|
||||
'außerhalb der Saison - Rotbach',
|
||||
$this->labeler()->label($this->seasonAssignment('2027-07-15', 'AGR'))
|
||||
);
|
||||
}
|
||||
|
||||
public function testAHalfFilledRecordStillNamesItself(): void
|
||||
{
|
||||
$this->assertSame('? - ?', $this->labeler()->label(new SeasonAssignment()));
|
||||
}
|
||||
|
||||
/**
|
||||
* The houses map is curated and can lose an entry; a placement that named that house falls
|
||||
* back to the raw code rather than losing its name.
|
||||
*/
|
||||
public function testACodeThatLeftTheMapFallsBackToTheCode(): void
|
||||
{
|
||||
$this->assertSame(
|
||||
'Saison 2026/27 - XYZ',
|
||||
$this->labeler()->label($this->seasonAssignment('2026-11-01', 'XYZ'))
|
||||
);
|
||||
}
|
||||
|
||||
private function labeler(): SeasonAssignmentLabeler
|
||||
{
|
||||
return new SeasonAssignmentLabeler(
|
||||
new SeasonCalendar('11-01', '04-01'),
|
||||
new HouseCatalog(['AGR' => 'Rotbach'])
|
||||
);
|
||||
}
|
||||
|
||||
private function seasonAssignment(string $dateFrom, string $hotelCode): SeasonAssignment
|
||||
{
|
||||
return (new SeasonAssignment())
|
||||
->setDateFrom(new \DateTimeImmutable($dateFrom))
|
||||
->setDateTo(new \DateTimeImmutable($dateFrom.' +5 months'))
|
||||
->setHotelCode($hotelCode)
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Service\Season;
|
||||
|
||||
use App\Config\SeasonTask;
|
||||
use App\Entity\SeasonAssignment;
|
||||
use App\Entity\SeasonAssignmentTask;
|
||||
use App\Service\Season\SeasonTaskDeadlineResolver;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Neither deadline is stored, so what is asserted here is the arithmetic every reminder, every
|
||||
* checklist row and the teamer's own page read - a deadline computed one day off is as silent a
|
||||
* failure as a reminder that never fires.
|
||||
*/
|
||||
class SeasonTaskDeadlineResolverTest extends TestCase
|
||||
{
|
||||
public function testTheFirstDeadlineIsTheOffsetAppliedToTheTravelStart(): void
|
||||
{
|
||||
$task = $this->task('2026-11-01');
|
||||
|
||||
$this->assertSame('2026-10-01', $this->resolver()->resolveFirst($task)?->format('Y-m-d'));
|
||||
}
|
||||
|
||||
public function testTheOffsetComesFromTheParameter(): void
|
||||
{
|
||||
$task = $this->task('2026-11-01');
|
||||
|
||||
$this->assertSame(
|
||||
'2026-10-18',
|
||||
$this->resolver('-14 days')->resolveFirst($task)?->format('Y-m-d')
|
||||
);
|
||||
}
|
||||
|
||||
public function testAnOverriddenFirstDeadlineWins(): void
|
||||
{
|
||||
$task = $this->task('2026-11-01')->setDeadlineOverride1(new \DateTimeImmutable('2026-09-15'));
|
||||
|
||||
$this->assertSame('2026-09-15', $this->resolver()->resolveFirst($task)?->format('Y-m-d'));
|
||||
}
|
||||
|
||||
public function testTheSecondDeadlineDefaultsToTheFirst(): void
|
||||
{
|
||||
$task = $this->task('2026-11-01');
|
||||
|
||||
$this->assertSame('2026-10-01', $this->resolver()->resolveSecond($task)?->format('Y-m-d'));
|
||||
}
|
||||
|
||||
public function testTheSecondDeadlineFollowsAnOverriddenFirstOne(): void
|
||||
{
|
||||
$task = $this->task('2026-11-01')->setDeadlineOverride1(new \DateTimeImmutable('2026-09-15'));
|
||||
|
||||
$this->assertSame('2026-09-15', $this->resolver()->resolveSecond($task)?->format('Y-m-d'));
|
||||
}
|
||||
|
||||
public function testAnOverriddenSecondDeadlineWinsOverBoth(): void
|
||||
{
|
||||
$task = $this
|
||||
->task('2026-11-01')
|
||||
->setDeadlineOverride1(new \DateTimeImmutable('2026-09-15'))
|
||||
->setDeadlineOverride2(new \DateTimeImmutable('2026-10-20'))
|
||||
;
|
||||
|
||||
$this->assertSame('2026-09-15', $this->resolver()->resolveFirst($task)?->format('Y-m-d'));
|
||||
$this->assertSame('2026-10-20', $this->resolver()->resolveSecond($task)?->format('Y-m-d'));
|
||||
}
|
||||
|
||||
public function testAnAssignmentWithoutAStartDateHasNoDeadlines(): void
|
||||
{
|
||||
$task = (new SeasonAssignmentTask())
|
||||
->setTaskKey(SeasonTask::CONTRACT->value)
|
||||
->setSeasonAssignment(new SeasonAssignment())
|
||||
;
|
||||
|
||||
$this->assertNull($this->resolver()->resolveFirst($task));
|
||||
$this->assertNull($this->resolver()->resolveSecond($task));
|
||||
}
|
||||
|
||||
private function resolver(string $offset = '-1 month'): SeasonTaskDeadlineResolver
|
||||
{
|
||||
return new SeasonTaskDeadlineResolver($offset);
|
||||
}
|
||||
|
||||
private function task(string $dateFrom): SeasonAssignmentTask
|
||||
{
|
||||
$seasonAssignment = (new SeasonAssignment())
|
||||
->setDateFrom(new \DateTimeImmutable($dateFrom))
|
||||
->setDateTo(new \DateTimeImmutable($dateFrom.' +5 months'))
|
||||
->setHotelCode('AGR')
|
||||
;
|
||||
|
||||
$task = (new SeasonAssignmentTask())->setTaskKey(SeasonTask::CONTRACT->value);
|
||||
$seasonAssignment->addTask($task);
|
||||
|
||||
return $task;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Service\Season;
|
||||
|
||||
use App\Config\SeasonTask;
|
||||
use App\Entity\SeasonAssignment;
|
||||
use App\Entity\SeasonAssignmentTask;
|
||||
use App\Service\Season\SeasonTaskSynchronizer;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
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.
|
||||
*/
|
||||
class SeasonTaskSynchronizerTest extends TestCase
|
||||
{
|
||||
private EntityManagerInterface&MockObject $entityManager;
|
||||
private SeasonTaskSynchronizer $synchronizer;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->entityManager = $this->createMock(EntityManagerInterface::class);
|
||||
$this->synchronizer = new SeasonTaskSynchronizer($this->entityManager);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function keysOf(SeasonAssignment $seasonAssignment): array
|
||||
{
|
||||
return array_map(
|
||||
static fn (SeasonAssignmentTask $task): string => $task->getTaskKey(),
|
||||
$seasonAssignment->getTasks()->toArray()
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user