countSuspendedDaysInMonth( new DateTimeImmutable('2026-03-01'), new DateTimeImmutable('2026-03-31'), [] ); self::assertSame(0, $result); } public function testFullMonthSuspension(): void { $calc = new SuspensionDaysCalculator(); $suspension = $this->buildSuspension('2026-03-01', '2026-03-31'); $result = $calc->countSuspendedDaysInMonth( new DateTimeImmutable('2026-03-01'), new DateTimeImmutable('2026-03-31'), [$suspension] ); self::assertSame(31, $result); } public function testPartialMonthSuspension(): void { $calc = new SuspensionDaysCalculator(); $suspension = $this->buildSuspension('2026-03-10', '2026-03-20'); $result = $calc->countSuspendedDaysInMonth( new DateTimeImmutable('2026-03-01'), new DateTimeImmutable('2026-03-31'), [$suspension] ); self::assertSame(11, $result); } public function testSuspensionSpanningMultipleMonths(): void { $calc = new SuspensionDaysCalculator(); $suspension = $this->buildSuspension('2026-02-15', '2026-04-10'); // March fully covered $result = $calc->countSuspendedDaysInMonth( new DateTimeImmutable('2026-03-01'), new DateTimeImmutable('2026-03-31'), [$suspension] ); self::assertSame(31, $result); } public function testSuspensionWithoutEndDate(): void { $calc = new SuspensionDaysCalculator(); $suspension = $this->buildSuspension('2026-03-15', null); $result = $calc->countSuspendedDaysInMonth( new DateTimeImmutable('2026-03-01'), new DateTimeImmutable('2026-03-31'), [$suspension] ); self::assertSame(17, $result); } public function testMultipleSuspensionsInSameMonth(): void { $calc = new SuspensionDaysCalculator(); $s1 = $this->buildSuspension('2026-03-01', '2026-03-10'); $s2 = $this->buildSuspension('2026-03-20', '2026-03-25'); $result = $calc->countSuspendedDaysInMonth( new DateTimeImmutable('2026-03-01'), new DateTimeImmutable('2026-03-31'), [$s1, $s2] ); self::assertSame(16, $result); } public function testSuspensionOutsideMonthReturnsZero(): void { $calc = new SuspensionDaysCalculator(); $suspension = $this->buildSuspension('2026-01-01', '2026-01-31'); $result = $calc->countSuspendedDaysInMonth( new DateTimeImmutable('2026-03-01'), new DateTimeImmutable('2026-03-31'), [$suspension] ); self::assertSame(0, $result); } public function testCountSuspendedBusinessDays(): void { $calc = new SuspensionDaysCalculator(); // March 2-6, 2026 = Mon-Fri = 5 business days $suspension = $this->buildSuspension('2026-03-02', '2026-03-06'); $result = $calc->countSuspendedBusinessDays( new DateTimeImmutable('2026-01-01'), new DateTimeImmutable('2026-12-31'), [$suspension], [] ); self::assertSame(5, $result); } public function testFirstMonthGraceShiftsStartByOneMonth(): void { $calc = new SuspensionDaysCalculator(); $suspension = $this->buildSuspension('2026-03-15', '2026-06-30'); $result = $calc->applyFirstMonthGrace([$suspension]); self::assertCount(1, $result); self::assertEquals(new DateTimeImmutable('2026-04-15'), $result[0]->getStartDate()); self::assertEquals(new DateTimeImmutable('2026-06-30'), $result[0]->getEndDate()); } public function testFirstMonthGraceRemovesSuspensionShorterThanOneMonth(): void { $calc = new SuspensionDaysCalculator(); $suspension = $this->buildSuspension('2026-03-10', '2026-03-25'); $result = $calc->applyFirstMonthGrace([$suspension]); self::assertCount(0, $result); } public function testFirstMonthGraceOpenEndedSuspension(): void { $calc = new SuspensionDaysCalculator(); $suspension = $this->buildSuspension('2026-03-01', null); $result = $calc->applyFirstMonthGrace([$suspension]); self::assertCount(1, $result); self::assertEquals(new DateTimeImmutable('2026-04-01'), $result[0]->getStartDate()); self::assertNull($result[0]->getEndDate()); } private function buildSuspension(string $start, ?string $end): ContractSuspension { $s = new ContractSuspension(); $s->setStartDate(new DateTimeImmutable($start)); if (null !== $end) { $s->setEndDate(new DateTimeImmutable($end)); } return $s; } }