/** * Swagger / OpenAPI document metadata tests (R5.3). * * These tests verify OpenAPI decorator metadata on the AcadeDoc controllers * without booting a full NestJS application (avoids ESM module chain issues * with prosemirror/collaboration utilities in the test environment). * * Strategy: * - Import only the controller class (not its full DI tree) * - Use `Reflect.getMetadata` to inspect @nestjs/swagger decorator metadata * - Use `DocumentBuilder` standalone to verify tag/security configuration */ import 'reflect-metadata'; import { DocumentBuilder } from '@nestjs/swagger'; import { DECORATORS } from '@nestjs/swagger/dist/constants'; // --------------------------------------------------------------------------- // Minimal stubs so controller imports do not pull the full DI graph // --------------------------------------------------------------------------- jest.mock( '../../../../common/helpers/prosemirror/html/index', () => ({}), { virtual: true }, ); jest.mock( '../../../../collaboration/collaboration.util', () => ({}), { virtual: true }, ); // Import only what we need to verify — just the controller classes for metadata // We avoid importing services/entities to sidestep the prosemirror/collab chain. // The Swagger decorator metadata is stored on the class constructor itself. describe('Swagger OpenAPI configuration (R5.3)', () => { // --------------------------------------------------------------------------- // Test 1: DocumentBuilder produces a valid OpenAPI 3 spec skeleton // --------------------------------------------------------------------------- it('should build a valid OpenAPI 3 config object', () => { const config = new DocumentBuilder() .setTitle('AcadeDoc API') .setDescription('API officielle AcadeDoc') .setVersion('1.0') .addBearerAuth() .addCookieAuth('authToken') .addTag('templates', 'Templates de pages') .addTag('sync-blocks', 'Sync blocks (cross-page content)') .addTag('audit-log', 'Audit log (read-only)') .addTag('api-keys', 'Personal API tokens') .addTag('clipper', 'Web clipper') .addTag('graph', 'Knowledge graph') .addTag('rbac', 'RBAC permissions/roles') .addTag('comments', 'Page + row comments') .addTag('notifications', 'Notifications + preferences') .addTag('security', 'OIDC / SSO config') .addTag('backlinks', 'Bidirectional backlinks') .addTag('slash-commands', 'Custom slash commands') .build(); expect(config.openapi).toBe('3.0.0'); expect(config.info.title).toBe('AcadeDoc API'); expect(config.info.version).toBe('1.0'); expect(config.tags).toBeDefined(); }); // --------------------------------------------------------------------------- // Test 2: All expected tags are present // --------------------------------------------------------------------------- it('should declare all 12 expected resource tags', () => { const config = new DocumentBuilder() .addTag('templates') .addTag('sync-blocks') .addTag('audit-log') .addTag('api-keys') .addTag('clipper') .addTag('graph') .addTag('rbac') .addTag('comments') .addTag('notifications') .addTag('security') .addTag('backlinks') .addTag('slash-commands') .build(); const tagNames = (config.tags ?? []).map((t) => t.name); const expected = [ 'templates', 'sync-blocks', 'audit-log', 'api-keys', 'clipper', 'graph', 'rbac', 'comments', 'notifications', 'security', 'backlinks', 'slash-commands', ]; for (const tag of expected) { expect(tagNames).toContain(tag); } }); // --------------------------------------------------------------------------- // Test 3: BearerAuth security scheme is configured // --------------------------------------------------------------------------- it('should include BearerAuth and CookieAuth security schemes', () => { const config = new DocumentBuilder() .addBearerAuth() .addCookieAuth('authToken') .build(); const schemes = config.components?.securitySchemes ?? {}; const schemeValues = Object.values(schemes) as any[]; const hasBearerAuth = schemeValues.some( (s) => s.type === 'http' && s.scheme === 'bearer', ); const hasCookieAuth = schemeValues.some((s) => s.type === 'apiKey' && s.in === 'cookie'); expect(hasBearerAuth).toBe(true); expect(hasCookieAuth).toBe(true); }); // --------------------------------------------------------------------------- // Test 4: @ApiTags metadata is resolvable on controller classes // We load the file dynamically in this test only to isolate the import error. // --------------------------------------------------------------------------- it('should find @ApiTags on RowCommentsController', async () => { // RowCommentsController has no service import issues const { RowCommentsController } = await import( './comments/controllers/row-comments.controller' ); const tags = Reflect.getMetadata( DECORATORS.API_TAGS, RowCommentsController, ); expect(tags).toContain('comments'); }); // --------------------------------------------------------------------------- // Test 5: @ApiTags metadata is resolvable on SyncBlocksController // --------------------------------------------------------------------------- it('should find @ApiTags on SyncBlocksController', async () => { const { SyncBlocksController } = await import( './sync-blocks/controllers/sync-blocks.controller' ); const tags = Reflect.getMetadata( DECORATORS.API_TAGS, SyncBlocksController, ); expect(tags).toContain('sync-blocks'); }); // --------------------------------------------------------------------------- // Test 6: @ApiTags metadata on AcadenicePermissionsController // --------------------------------------------------------------------------- it('should find @ApiTags on AcadenicePermissionsController', async () => { const { AcadenicePermissionsController } = await import( './rbac/controllers/permissions.controller' ); const tags = Reflect.getMetadata( DECORATORS.API_TAGS, AcadenicePermissionsController, ); expect(tags).toContain('rbac'); }); // --------------------------------------------------------------------------- // Test 7: @ApiTags on BacklinksController // --------------------------------------------------------------------------- it('should find @ApiTags on BacklinksController', async () => { const { BacklinksController } = await import( './backlinks/controllers/backlinks.controller' ); const tags = Reflect.getMetadata( DECORATORS.API_TAGS, BacklinksController, ); expect(tags).toContain('backlinks'); }); // --------------------------------------------------------------------------- // Test 8: @ApiTags on AcadeniceOidcStatusController // --------------------------------------------------------------------------- it('should find @ApiTags on AcadeniceOidcStatusController', async () => { const { AcadeniceOidcStatusController } = await import( './security/controllers/oidc-status.controller' ); const tags = Reflect.getMetadata( DECORATORS.API_TAGS, AcadeniceOidcStatusController, ); expect(tags).toContain('security'); }); });