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') ; } }