repository = new InMemoryParentInvitationRepository(); $this->clock = new class implements Clock { public function now(): DateTimeImmutable { return new DateTimeImmutable('2026-02-28 10:00:00'); } }; } #[Test] public function itExpiresInvitationsPastExpirationDate(): void { $invitation = $this->creerInvitation( createdAt: new DateTimeImmutable('2026-02-01 10:00:00'), code: 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4', ); $invitation->envoyer(new DateTimeImmutable('2026-02-01 11:00:00')); $this->repository->save($invitation); $tester = $this->executeCommand(); self::assertSame(0, $tester->getStatusCode()); self::assertStringContainsString('1 invitation(s) expirée(s) trouvée(s)', $tester->getDisplay()); self::assertStringContainsString('1 invitation(s) marquée(s) comme expirée(s)', $tester->getDisplay()); self::assertSame(InvitationStatus::EXPIRED, $invitation->status); } #[Test] public function itHandlesNoExpiredInvitations(): void { $tester = $this->executeCommand(); self::assertSame(0, $tester->getStatusCode()); self::assertStringContainsString('Aucune invitation expirée à traiter.', $tester->getDisplay()); } #[Test] public function itDoesNotExpirePendingInvitations(): void { $invitation = $this->creerInvitation( createdAt: new DateTimeImmutable('2026-02-01 10:00:00'), code: 'b1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4', ); $this->repository->save($invitation); $tester = $this->executeCommand(); self::assertSame(0, $tester->getStatusCode()); self::assertStringContainsString('Aucune invitation expirée à traiter.', $tester->getDisplay()); self::assertSame(InvitationStatus::PENDING, $invitation->status); } #[Test] public function itDoesNotExpireNonExpiredSentInvitations(): void { $invitation = $this->creerInvitation( createdAt: new DateTimeImmutable('2026-02-25 10:00:00'), code: 'c1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4', ); $invitation->envoyer(new DateTimeImmutable('2026-02-25 11:00:00')); $this->repository->save($invitation); $tester = $this->executeCommand(); self::assertSame(0, $tester->getStatusCode()); self::assertStringContainsString('Aucune invitation expirée à traiter.', $tester->getDisplay()); self::assertSame(InvitationStatus::SENT, $invitation->status); } private function executeCommand(): CommandTester { $command = new ExpireInvitationsCommand( $this->repository, $this->clock, new NullLogger(), ); $tester = new CommandTester($command); $tester->execute([]); return $tester; } private function creerInvitation( DateTimeImmutable $createdAt, string $code, ): ParentInvitation { return ParentInvitation::creer( tenantId: TenantId::fromString(self::TENANT_ID), studentId: UserId::fromString(self::STUDENT_ID), parentEmail: new Email('parent@example.com'), code: new InvitationCode($code), createdAt: $createdAt, createdBy: UserId::fromString(self::CREATED_BY_ID), ); } }