invitationRepository = new InMemoryParentInvitationRepository(); $this->userRepository = new InMemoryUserRepository(); $this->handler = new GetParentInvitationsHandler( $this->invitationRepository, $this->userRepository, ); } #[Test] public function itReturnsAllInvitationsForTenant(): void { $student = $this->createAndSaveStudent('Alice', 'Dupont'); $this->createAndSaveInvitation($student->id, 'parent1@example.com'); $this->createAndSaveInvitation($student->id, 'parent2@example.com'); $result = ($this->handler)(new GetParentInvitationsQuery( tenantId: self::TENANT_ID, )); self::assertSame(2, $result->total); self::assertCount(2, $result->items); } #[Test] public function itFiltersInvitationsByStatus(): void { $student = $this->createAndSaveStudent('Bob', 'Martin'); $invitation = $this->createAndSaveInvitation($student->id, 'parent@example.com'); $this->createPendingInvitation($student->id, 'parent2@example.com'); $result = ($this->handler)(new GetParentInvitationsQuery( tenantId: self::TENANT_ID, status: 'sent', )); self::assertSame(1, $result->total); self::assertSame('parent@example.com', $result->items[0]->parentEmail); } #[Test] public function itFiltersInvitationsByStudentId(): void { $student1 = $this->createAndSaveStudent('Alice', 'Dupont'); $student2 = $this->createAndSaveStudent('Bob', 'Martin'); $this->createAndSaveInvitation($student1->id, 'parent1@example.com'); $this->createAndSaveInvitation($student2->id, 'parent2@example.com'); $result = ($this->handler)(new GetParentInvitationsQuery( tenantId: self::TENANT_ID, studentId: (string) $student1->id, )); self::assertSame(1, $result->total); self::assertSame('parent1@example.com', $result->items[0]->parentEmail); } #[Test] public function itSearchesByParentEmailOrStudentName(): void { $student = $this->createAndSaveStudent('Alice', 'Dupont'); $this->createAndSaveInvitation($student->id, 'parent@example.com'); $this->createAndSaveInvitation($student->id, 'other@example.com'); $result = ($this->handler)(new GetParentInvitationsQuery( tenantId: self::TENANT_ID, search: 'Alice', )); self::assertSame(2, $result->total); $result = ($this->handler)(new GetParentInvitationsQuery( tenantId: self::TENANT_ID, search: 'parent@', )); self::assertSame(1, $result->total); } #[Test] public function itPaginatesResults(): void { $student = $this->createAndSaveStudent('Alice', 'Dupont'); for ($i = 0; $i < 5; ++$i) { $this->createAndSaveInvitation($student->id, "parent{$i}@example.com"); } $result = ($this->handler)(new GetParentInvitationsQuery( tenantId: self::TENANT_ID, page: 1, limit: 2, )); self::assertSame(5, $result->total); self::assertCount(2, $result->items); } #[Test] public function itEnrichesResultsWithStudentNames(): void { $student = $this->createAndSaveStudent('Alice', 'Dupont'); $this->createAndSaveInvitation($student->id, 'parent@example.com'); $result = ($this->handler)(new GetParentInvitationsQuery( tenantId: self::TENANT_ID, )); self::assertSame('Alice', $result->items[0]->studentFirstName); self::assertSame('Dupont', $result->items[0]->studentLastName); } #[Test] public function itIsolatesByTenant(): void { $student = $this->createAndSaveStudent('Alice', 'Dupont'); $this->createAndSaveInvitation($student->id, 'parent@example.com'); $result = ($this->handler)(new GetParentInvitationsQuery( tenantId: self::OTHER_TENANT_ID, )); self::assertSame(0, $result->total); } private function createAndSaveStudent(string $firstName, string $lastName): User { $student = User::inviter( email: new Email($firstName . '@example.com'), role: Role::ELEVE, tenantId: TenantId::fromString(self::TENANT_ID), schoolName: 'École Alpha', firstName: $firstName, lastName: $lastName, invitedAt: new DateTimeImmutable('2026-02-07 10:00:00'), ); $student->pullDomainEvents(); $this->userRepository->save($student); return $student; } private function createAndSaveInvitation(UserId $studentId, string $parentEmail): ParentInvitation { $code = bin2hex(random_bytes(16)); $invitation = ParentInvitation::creer( tenantId: TenantId::fromString(self::TENANT_ID), studentId: $studentId, parentEmail: new Email($parentEmail), code: new InvitationCode($code), createdAt: new DateTimeImmutable('2026-02-07 10:00:00'), createdBy: UserId::generate(), ); $invitation->envoyer(new DateTimeImmutable('2026-02-07 10:00:00')); $invitation->pullDomainEvents(); $this->invitationRepository->save($invitation); return $invitation; } private function createPendingInvitation(UserId $studentId, string $parentEmail): ParentInvitation { $code = bin2hex(random_bytes(16)); $invitation = ParentInvitation::creer( tenantId: TenantId::fromString(self::TENANT_ID), studentId: $studentId, parentEmail: new Email($parentEmail), code: new InvitationCode($code), createdAt: new DateTimeImmutable('2026-02-07 10:00:00'), createdBy: UserId::generate(), ); $invitation->pullDomainEvents(); $this->invitationRepository->save($invitation); return $invitation; } }