reader = $this->createMock(PaginatedParentInvitationsReader::class); $this->cache = new PaginatedQueryCache( new TagAwareAdapter(new ArrayAdapter()), ); $this->handler = new GetParentInvitationsHandler($this->reader, $this->cache); } #[Test] public function returnsItemsForTenant(): void { $dto = $this->createInvitationDto(); $this->reader->method('findPaginated')->willReturn( new PaginatedResult(items: [$dto], total: 1, page: 1, limit: 30), ); $result = ($this->handler)(new GetParentInvitationsQuery(tenantId: 'tenant-1')); self::assertCount(1, $result->items); self::assertSame(1, $result->total); } #[Test] public function mapsDtoFields(): void { $dto = $this->createInvitationDto(); $this->reader->method('findPaginated')->willReturn( new PaginatedResult(items: [$dto], total: 1, page: 1, limit: 30), ); $result = ($this->handler)(new GetParentInvitationsQuery(tenantId: 'tenant-1')); $item = $result->items[0]; self::assertSame('inv-1', $item->id); self::assertSame('student-1', $item->studentId); self::assertSame('parent@test.com', $item->parentEmail); self::assertSame('sent', $item->status); self::assertSame('Alice', $item->studentFirstName); self::assertSame('Dupont', $item->studentLastName); self::assertNull($item->activatedAt); self::assertNull($item->activatedUserId); } #[Test] public function paginatesResults(): void { $this->reader->method('findPaginated')->willReturn( new PaginatedResult(items: [], total: 50, page: 2, limit: 10), ); $result = ($this->handler)(new GetParentInvitationsQuery(tenantId: 'tenant-1', page: 2, limit: 10)); self::assertSame(50, $result->total); self::assertSame(2, $result->page); self::assertSame(10, $result->limit); } #[Test] public function cachesResult(): void { $dto = $this->createInvitationDto(); $this->reader->expects(self::once())->method('findPaginated')->willReturn( new PaginatedResult(items: [$dto], total: 1, page: 1, limit: 30), ); $query = new GetParentInvitationsQuery(tenantId: 'tenant-1'); ($this->handler)($query); $result = ($this->handler)($query); self::assertCount(1, $result->items); } #[Test] public function clampsPageToMinimumOne(): void { $this->reader->method('findPaginated')->willReturn( new PaginatedResult(items: [], total: 0, page: 1, limit: 30), ); $result = ($this->handler)(new GetParentInvitationsQuery(tenantId: 'tenant-1', page: -5)); self::assertSame(1, $result->page); } #[Test] public function clampsLimitToMaximumHundred(): void { $this->reader->method('findPaginated')->willReturn( new PaginatedResult(items: [], total: 0, page: 1, limit: 100), ); $result = ($this->handler)(new GetParentInvitationsQuery(tenantId: 'tenant-1', limit: 500)); self::assertSame(100, $result->limit); } private function createInvitationDto(): ParentInvitationDto { return new ParentInvitationDto( id: 'inv-1', studentId: 'student-1', parentEmail: 'parent@test.com', status: 'sent', createdAt: new DateTimeImmutable('2026-02-07'), expiresAt: new DateTimeImmutable('2026-03-07'), sentAt: new DateTimeImmutable('2026-02-07'), activatedAt: null, activatedUserId: null, studentFirstName: 'Alice', studentLastName: 'Dupont', ); } }