security = $this->createStub(Security::class); $this->employeeRepository = $this->createStub(EmployeeScopedRepositoryInterface::class); $this->workHourRepository = $this->createStub(WorkHourReadRepositoryInterface::class); $this->absenceRepository = $this->createStub(AbsenceReadRepositoryInterface::class); $this->requestStack = new RequestStack(); } public function testThrowsWhenAnonymous(): void { $this->security->method('getUser')->willReturn(null); $provider = new WorkHourWeeklySummaryProvider( $this->security, $this->requestStack, $this->employeeRepository, $this->workHourRepository, $this->absenceRepository, new AbsenceSegmentsResolver(), new WorkedHoursCreditPolicy() ); $this->expectException(AccessDeniedHttpException::class); $provider->provide(new Get()); } public function testBuildsWeeklyRowsWithOvertimeAndPresence(): void { $user = new User(); $timeEmployee = $this->buildEmployee(1, 'TIME', 35, 'Alice'); $presenceEmployee = $this->buildEmployee(2, 'PRESENCE', null, 'Bob'); $employees = [$timeEmployee, $presenceEmployee]; $workHours = []; foreach (['2026-02-16', '2026-02-17', '2026-02-18', '2026-02-19', '2026-02-20'] as $date) { $workHours[] = new WorkHour() ->setEmployee($timeEmployee) ->setWorkDate(new DateTimeImmutable($date)) ->setMorningFrom('09:00') ->setMorningTo('19:00') ; } $absenceType = new AbsenceType() ->setCode('CP') ->setLabel('Congé') ->setColor('#000') ->setCountAsWorkedHours(true) ; $presenceAbsence = new Absence() ->setEmployee($presenceEmployee) ->setType($absenceType) ->setStartDate(new DateTime('2026-02-16')) ->setEndDate(new DateTime('2026-02-16')) ->setStartHalf(HalfDay::AM) ->setEndHalf(HalfDay::PM) ; $this->requestStack->push(new Request(query: ['weekStart' => '2026-02-16'])); $this->security->method('getUser')->willReturn($user); $this->employeeRepository->method('findScoped')->with($user)->willReturn($employees); $this->workHourRepository->method('findByDateRangeAndEmployees')->willReturn($workHours); $this->absenceRepository->method('findForPrint')->willReturn([$presenceAbsence]); $provider = new WorkHourWeeklySummaryProvider( $this->security, $this->requestStack, $this->employeeRepository, $this->workHourRepository, $this->absenceRepository, new AbsenceSegmentsResolver(), new WorkedHoursCreditPolicy() ); $result = $provider->provide(new Get()); self::assertSame('2026-02-16', $result->weekStart); self::assertSame('2026-02-22', $result->weekEnd); self::assertCount(2, $result->rows); self::assertSame(3000, $result->rows[0]['weeklyTotalMinutes']); self::assertSame(900, $result->rows[0]['weeklyOvertimeTotalMinutes']); self::assertSame(120, $result->rows[0]['weeklyOvertime25Minutes']); self::assertSame(210, $result->rows[0]['weeklyOvertime50Minutes']); self::assertSame(1230, $result->rows[0]['weeklyRecoveryMinutes']); self::assertSame(1.0, $result->rows[1]['weeklyPresenceCount']); self::assertSame(0, $result->rows[1]['weeklyOvertimeTotalMinutes']); } private function buildEmployee(int $id, string $trackingMode, ?int $weeklyHours, string $firstName): Employee { $contract = new Contract() ->setName($trackingMode) ->setTrackingMode($trackingMode) ->setWeeklyHours($weeklyHours) ; $employee = new Employee() ->setFirstName($firstName) ->setLastName('Test') ->setContract($contract) ; $this->setEntityId($employee, $id); return $employee; } private function setEntityId(object $entity, int $id): void { $reflection = new ReflectionObject($entity); $property = $reflection->getProperty('id'); $property->setAccessible(true); $property->setValue($entity, $id); } }