setUsername('usine') ->setAction('create') ->setEntityType('work_hour') ->setDescription('desc') ->setIpAddress('203.0.113.7') ->setUserAgent('UA-string') ->setDeviceLabel('Mobile · Android · Chrome') ->setDeviceId('device-abc') ; $response = $this->provideWith($this->spyRepository([$log], 1), []); $item = json_decode((string) $response->getContent(), true)['items'][0]; self::assertSame('203.0.113.7', $item['ipAddress']); self::assertSame('UA-string', $item['userAgent']); self::assertSame('Mobile · Android · Chrome', $item['deviceLabel']); self::assertSame('device-abc', $item['deviceId']); } public function testPassesNewFiltersToRepository(): void { $repo = $this->spyRepository(); $this->provideWith($repo, [ 'employeeId' => '5', 'employee' => 'dupont', 'username' => 'usine', 'ip' => '10.0.', 'device' => 'android', 'entityType' => ['work_hour', 'absence'], 'action' => ['create'], 'perPage' => '25', 'page' => '2', ]); self::assertSame(5, $repo->findArgs['employeeId']); self::assertSame('dupont', $repo->findArgs['employeeName']); self::assertSame('usine', $repo->findArgs['username']); self::assertSame('10.0.', $repo->findArgs['ip']); self::assertSame('android', $repo->findArgs['device']); self::assertSame(['work_hour', 'absence'], $repo->findArgs['entityTypes']); self::assertSame(['create'], $repo->findArgs['actions']); self::assertSame(25, $repo->findArgs['limit']); self::assertSame(25, $repo->findArgs['offset']); // page 2, perPage 25 -> offset 25 } public function testBlankFiltersBecomeNull(): void { $repo = $this->spyRepository(); $this->provideWith($repo, ['username' => ' ', 'ip' => '', 'device' => '']); self::assertNull($repo->findArgs['username']); self::assertNull($repo->findArgs['ip']); self::assertNull($repo->findArgs['device']); self::assertNull($repo->findArgs['entityTypes']); self::assertNull($repo->findArgs['actions']); } public function testPerPageOutOfRangeFallsBackToDefault(): void { $repo = $this->spyRepository(); $response = $this->provideWith($repo, ['perPage' => '999']); self::assertSame(10, $repo->findArgs['limit']); self::assertSame(10, json_decode((string) $response->getContent(), true)['perPage']); } public function testDefaultPerPageIs10(): void { $repo = $this->spyRepository(); $response = $this->provideWith($repo, []); self::assertSame(10, $repo->findArgs['limit']); self::assertSame(10, json_decode((string) $response->getContent(), true)['perPage']); } private function spyRepository(array $items = [], int $count = 0): AuditLogReadRepositoryInterface { return new class($items, $count) implements AuditLogReadRepositoryInterface { public array $findArgs = []; public array $countArgs = []; public function __construct(private array $items, private int $count) {} public function findByFilters(?int $employeeId = null, ?DateTimeImmutable $from = null, ?DateTimeImmutable $to = null, ?array $entityTypes = null, ?array $actions = null, ?string $username = null, ?string $ip = null, ?string $device = null, ?string $employeeName = null, int $limit = 50, int $offset = 0): array { $this->findArgs = compact('employeeId', 'from', 'to', 'entityTypes', 'actions', 'username', 'ip', 'device', 'employeeName', 'limit', 'offset'); return $this->items; } public function countByFilters(?int $employeeId = null, ?DateTimeImmutable $from = null, ?DateTimeImmutable $to = null, ?array $entityTypes = null, ?array $actions = null, ?string $username = null, ?string $ip = null, ?string $device = null, ?string $employeeName = null): int { $this->countArgs = compact('employeeId', 'from', 'to', 'entityTypes', 'actions', 'username', 'ip', 'device', 'employeeName'); return $this->count; } }; } private function provideWith(AuditLogReadRepositoryInterface $repo, array $query): JsonResponse { $stack = new RequestStack(); $stack->push(Request::create('/api/audit-logs', 'GET', $query)); $provider = new AuditLogProvider($stack, $repo); return $provider->provide($this->createStub(Operation::class)); } }