assertUrlIsSecured('/api/doc'); } public function testGetDocs(): void { $client = $this->getClientForAuthenticatedUser(User::ROLE_USER); $this->assertAccessIsGranted($client, '/api/doc'); $content = $client->getResponse()->getContent(); self::assertIsString($content); self::assertStringContainsString('Kimai', $content); self::assertStringContainsString('docs.apiDescriptionDocument', $content); self::assertStringContainsString('const config = {"basePath":"/api/doc","router":"memory","logo":"/touch-icon-192x192.png","hideInternal":true};', $content); $results = preg_match('/docs\.apiDescriptionDocument\ \=\ (.*)\.spec;/', $content, $matches); self::assertNotFalse($results); self::assertArrayHasKey(1, $matches); $swaggerJson = json_decode($matches[1], true); self::assertIsArray($swaggerJson); self::assertArrayHasKey('spec', $swaggerJson); $json = $swaggerJson['spec']; self::assertArrayHasKey('paths', $json); $tags = []; foreach ($json['paths'] as $path) { foreach ($path as $method) { foreach ($method['tags'] as $tag) { $tags[$tag] = $tag; } } } $expectedKeys = ['Actions', 'Activity', 'Default', 'Customer', 'Project', 'Tag', 'Team', 'Timesheet', 'User', 'Invoice', 'Export']; $actual = array_keys($tags); sort($actual); sort($expectedKeys); self::assertEquals($expectedKeys, $actual, \sprintf('Expected %s sections in API docs, but found %s.', \count($actual), \count($expectedKeys))); $paths = [ '/api/actions/timesheet/{id}/{view}/{locale}', '/api/actions/activity/{id}/{view}/{locale}', '/api/actions/project/{id}/{view}/{locale}', '/api/actions/customer/{id}/{view}/{locale}', '/api/activities', '/api/activities/{id}', '/api/activities/{id}/meta', '/api/activities/{id}/rates', '/api/activities/{id}/rates/{rateId}', '/api/activities/{id}/team', '/api/config/timesheet', '/api/config/colors', '/api/customers', '/api/customers/{id}', '/api/customers/{id}/meta', '/api/customers/{id}/rates', '/api/customers/{id}/rates/{rateId}', '/api/customers/{id}/comments', '/api/customers/{id}/comments/{comment}/pin', '/api/customers/{id}/comments/{comment}', '/api/customers/{id}/team', '/api/export/{id}', '/api/invoices', '/api/invoices/{id}', '/api/invoices/{id}/custom-fields', '/api/invoices/{id}/download', '/api/projects', '/api/projects/{id}', '/api/projects/{id}/meta', '/api/projects/{id}/rates', '/api/projects/{id}/rates/{rateId}', '/api/projects/{id}/comments', '/api/projects/{id}/comments/{comment}/pin', '/api/projects/{id}/comments/{comment}', '/api/projects/{id}/team', '/api/ping', '/api/version', '/api/plugins', '/api/tags', '/api/tags/find', '/api/tags/{id}', '/api/teams', '/api/teams/{id}', '/api/teams/{id}/members/{userId}', '/api/teams/{id}/customers/{customerId}', '/api/teams/{id}/projects/{projectId}', '/api/teams/{id}/activities/{activityId}', '/api/timesheets', '/api/timesheets/{id}', '/api/timesheets/recent', '/api/timesheets/active', '/api/timesheets/{id}/stop', '/api/timesheets/{id}/restart', '/api/timesheets/{id}/duplicate', '/api/timesheets/{id}/export', '/api/timesheets/{id}/meta', '/api/users', '/api/users/{id}', '/api/users/me', '/api/users/api-token/{id}', '/api/users/{id}/preferences', ]; self::assertArrayHasKey('openapi', $json); self::assertEquals('3.0.0', $json['openapi']); self::assertArrayHasKey('info', $json); self::assertStringStartsWith('Kimai', $json['info']['title']); self::assertEquals('1.1', $json['info']['version']); self::assertArrayHasKey('paths', $json); self::assertEquals($paths, array_keys($json['paths'])); self::assertArrayHasKey('security', $json); self::assertEquals(['bearer' => []], $json['security'][0]); self::assertArrayHasKey('components', $json); self::assertArrayHasKey('schemas', $json['components']); self::assertArrayHasKey('securitySchemes', $json['components']); } protected function createUrl(string $url): string { return '/' . ltrim($url, '/'); } }