userRepository = new InMemoryUserRepository(); $this->clock = new class implements Clock { public function now(): DateTimeImmutable { return new DateTimeImmutable('2026-02-07 10:00:00'); } }; $this->handler = new GetUsersHandler($this->userRepository, $this->clock); } #[Test] public function returnsAllUsersForTenant(): void { $this->seedUsers(); $query = new GetUsersQuery(tenantId: self::TENANT_ID); $result = ($this->handler)($query); self::assertCount(3, $result); } #[Test] public function filtersUsersByRole(): void { $this->seedUsers(); $query = new GetUsersQuery( tenantId: self::TENANT_ID, role: Role::PROF->value, ); $result = ($this->handler)($query); self::assertCount(2, $result); foreach ($result as $dto) { self::assertSame(Role::PROF->value, $dto->role); } } #[Test] public function filtersUsersByStatut(): void { $this->seedUsers(); $query = new GetUsersQuery( tenantId: self::TENANT_ID, statut: 'pending', ); $result = ($this->handler)($query); self::assertCount(2, $result); foreach ($result as $dto) { self::assertSame('pending', $dto->statut); } } #[Test] public function excludesUsersFromOtherTenants(): void { $this->seedUsers(); // Add user to different tenant $otherUser = User::inviter( email: new Email('other@example.com'), role: Role::ADMIN, tenantId: TenantId::fromString(self::OTHER_TENANT_ID), schoolName: 'Autre École', firstName: 'Autre', lastName: 'User', invitedAt: new DateTimeImmutable('2026-02-01 10:00:00'), ); $this->userRepository->save($otherUser); $query = new GetUsersQuery(tenantId: self::TENANT_ID); $result = ($this->handler)($query); self::assertCount(3, $result); } #[Test] public function calculatesInvitationExpiree(): void { // Invited 10 days ago — should be expired $user = User::inviter( email: new Email('old@example.com'), role: Role::PROF, tenantId: TenantId::fromString(self::TENANT_ID), schoolName: 'École Alpha', firstName: 'Old', lastName: 'Invitation', invitedAt: new DateTimeImmutable('2026-01-25 10:00:00'), ); $this->userRepository->save($user); $query = new GetUsersQuery(tenantId: self::TENANT_ID); $result = ($this->handler)($query); self::assertCount(1, $result); self::assertTrue($result[0]->invitationExpiree); } private function seedUsers(): void { $tenantId = TenantId::fromString(self::TENANT_ID); $teacher1 = User::inviter( email: new Email('teacher1@example.com'), role: Role::PROF, tenantId: $tenantId, schoolName: 'École Alpha', firstName: 'Jean', lastName: 'Dupont', invitedAt: new DateTimeImmutable('2026-02-01 10:00:00'), ); $this->userRepository->save($teacher1); $teacher2 = User::inviter( email: new Email('teacher2@example.com'), role: Role::PROF, tenantId: $tenantId, schoolName: 'École Alpha', firstName: 'Marie', lastName: 'Martin', invitedAt: new DateTimeImmutable('2026-02-01 10:00:00'), ); // Activate teacher2 $teacher2->activer( '$argon2id$hashed', new DateTimeImmutable('2026-02-02 10:00:00'), new ConsentementParentalPolicy($this->clock), ); $this->userRepository->save($teacher2); $parent = User::inviter( email: new Email('parent@example.com'), role: Role::PARENT, tenantId: $tenantId, schoolName: 'École Alpha', firstName: 'Pierre', lastName: 'Parent', invitedAt: new DateTimeImmutable('2026-02-01 10:00:00'), ); $this->userRepository->save($parent); } }