feat: all-season assignments

This commit is contained in:
Björn Fromme authored and Björn Fromme committed 2026-09-26 12:13:16 +02:00
1 parent 675cec1b69
commit cf876ed224
119 files changed
+6194 -294

No files matched your search

@@ -0,0 +1,128 @@
<?php
declare(strict_types=1);
namespace App\Tests\Validator;
use App\Config\SeasonCalendar;
use App\Entity\SeasonAssignment;
use App\Entity\Teamer;
use App\Repository\SeasonAssignmentRepository;
use App\Validator\Constraints\SeasonAssignment as SeasonAssignmentConstraint;
use App\Validator\Constraints\SeasonAssignmentValidator;
use PHPUnit\Framework\MockObject\MockObject;
use Symfony\Component\Validator\Test\ConstraintValidatorTestCase;
/**
* "One season assignment per teamer per season" was a stated fact before this rule; what is
* pinned here is which season window a given assignment is counted against, and the cases that
* must not be refused - an unstaffed assignment, and the assignment being edited itself.
*/
class SeasonAssignmentValidatorTest extends ConstraintValidatorTestCase
{
private SeasonAssignmentRepository&MockObject $repository;
protected function createValidator(): SeasonAssignmentValidator
{
$this->repository = $this->createMock(SeasonAssignmentRepository::class);
return new SeasonAssignmentValidator($this->repository, new SeasonCalendar('11-01', '04-01'));
}
public function testASecondAssignmentInTheSameSeasonIsRefused(): void
{
$this->repository->method('countInSeasonWindow')->willReturn(1);
$this->validator->validate($this->seasonAssignment('2026-12-20'), $this->constraint());
$this->buildViolation('Für diese:n Teamer:in existiert in dieser Saison bereits ein Saisoneinsatz')
->assertRaised()
;
}
public function testTheFirstAssignmentOfASeasonPasses(): void
{
$this->repository->method('countInSeasonWindow')->willReturn(0);
$this->validator->validate($this->seasonAssignment('2026-12-20'), $this->constraint());
$this->assertNoViolation();
}
public function testItCountsAgainstTheSeasonTheAssignmentStartsIn(): void
{
// 20.02.2027 belongs to the season that began on 01.11.2026, so the window asked for has
// to be that one and not the calendar year the date happens to fall in
$this->repository
->expects($this->once())
->method('countInSeasonWindow')
->with(
$this->isInstanceOf(Teamer::class),
$this->callback(static fn (\DateTimeImmutable $start): bool => '2026-11-01' === $start->format('Y-m-d')),
$this->callback(static fn (\DateTimeImmutable $end): bool => '2027-04-01' === $end->format('Y-m-d')),
null
)
->willReturn(0)
;
$this->validator->validate($this->seasonAssignment('2027-02-20'), $this->constraint());
$this->assertNoViolation();
}
public function testEditingAnAssignmentDoesNotCountItAgainstItself(): void
{
$seasonAssignment = $this->seasonAssignment('2026-12-20');
// the id is never set by hand in the app; it is what tells the query to leave the row
// being edited out, so an edit that changes nothing cannot refuse itself
$id = new \ReflectionProperty(SeasonAssignment::class, 'id');
$id->setValue($seasonAssignment, 42);
$this->repository
->expects($this->once())
->method('countInSeasonWindow')
->with($this->anything(), $this->anything(), $this->anything(), 42)
->willReturn(0)
;
$this->validator->validate($seasonAssignment, $this->constraint());
$this->assertNoViolation();
}
public function testAnAssignmentWithoutATeamerPasses(): void
{
$this->repository->expects($this->never())->method('countInSeasonWindow');
$seasonAssignment = $this->seasonAssignment('2026-12-20')->setTeamer(null);
$this->validator->validate($seasonAssignment, $this->constraint());
$this->assertNoViolation();
}
public function testAnAssignmentOutsideAnySeasonWindowPasses(): void
{
$this->repository->expects($this->never())->method('countInSeasonWindow');
$this->validator->validate($this->seasonAssignment('2027-07-15'), $this->constraint());
$this->assertNoViolation();
}
private function constraint(): SeasonAssignmentConstraint
{
return new SeasonAssignmentConstraint();
}
private function seasonAssignment(string $dateFrom): SeasonAssignment
{
return (new SeasonAssignment())
->setTeamer(new Teamer())
->setDateFrom(new \DateTimeImmutable($dateFrom))
->setDateTo(new \DateTimeImmutable($dateFrom.' +6 months'))
->setHotelCode('AGR')
;
}
}