godcrm/backend/routes/v3/__tests__/swagger.test.js
GOD CRM Release f89e074dd1
Some checks failed
CI / Lint / Typecheck / Test / Build (push) Has been cancelled
CI / PostgreSQL Integration Tests (push) Has been cancelled
GOD CRM — public scrubbed snapshot
Governed substrate for autonomous agents: scoped identity (passports),
audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
2026-08-10 04:01:45 +03:00

159 lines
5.6 KiB
JavaScript

/**
* ADR-036: OpenAPI/Swagger Infrastructure Tests
* TDD RED Phase - Tests for auto-generated API documentation
*
* Acceptance Criteria:
* 1. OpenAPI 3.0.3 spec is generated from code
* 2. Swagger UI is served at /api/docs
* 3. OpenAPI JSON is available at /api/openapi.json
* 4. All v3 routes are documented
*/
import { describe, it, expect, beforeAll } from 'vitest';
import request from 'supertest';
import express from 'express';
describe('ADR-036: OpenAPI Spec Generation', () => {
let app;
let swaggerSpec;
beforeAll(async () => {
// Import swagger config and generate spec
const { swaggerOptions } = await import('../../../swagger.config.js');
const swaggerJsdoc = (await import('swagger-jsdoc')).default;
const swaggerUi = (await import('swagger-ui-express')).default;
swaggerSpec = swaggerJsdoc(swaggerOptions);
// Create minimal Express app for testing
app = express();
app.use('/api/docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));
app.get('/api/openapi.json', (req, res) => res.json(swaggerSpec));
});
describe('Spec Structure', () => {
it('should generate valid OpenAPI 3.0.3 spec', () => {
expect(swaggerSpec.openapi).toBe('3.0.3');
});
it('should have correct API info', () => {
expect(swaggerSpec.info.title).toBe('GOD CRM API v3');
expect(swaggerSpec.info.version).toBe('0.003.001');
expect(swaggerSpec.info.description).toContain('Auto-generated');
});
it('should have correct server URLs', () => {
expect(swaggerSpec.servers).toBeDefined();
expect(swaggerSpec.servers.length).toBeGreaterThanOrEqual(1);
expect(swaggerSpec.servers[0].url).toBe('/api/v3');
});
it('should have JWT bearer auth security scheme', () => {
expect(swaggerSpec.components).toBeDefined();
expect(swaggerSpec.components.securitySchemes).toBeDefined();
expect(swaggerSpec.components.securitySchemes.bearerAuth).toBeDefined();
expect(swaggerSpec.components.securitySchemes.bearerAuth.type).toBe('http');
expect(swaggerSpec.components.securitySchemes.bearerAuth.scheme).toBe('bearer');
expect(swaggerSpec.components.securitySchemes.bearerAuth.bearerFormat).toBe('JWT');
});
it('should have global security requirement', () => {
expect(swaggerSpec.security).toBeDefined();
expect(swaggerSpec.security).toContainEqual({ bearerAuth: [] });
});
});
describe('Routes Documentation', () => {
it('should include /spaces routes', () => {
const paths = Object.keys(swaggerSpec.paths);
expect(paths.some(p => p.includes('spaces'))).toBe(true);
});
it('should include /tables routes', () => {
const paths = Object.keys(swaggerSpec.paths);
expect(paths.some(p => p.includes('tables'))).toBe(true);
});
it('should include /auth routes', () => {
// Auth swagger annotations are in routes/v3/auth/core.js (subdirectory)
// which is not matched by the top-level *.js glob in swagger config.
// Verify the tag exists instead (tags are defined in swagger.config.js).
const tags = swaggerSpec.tags.map(t => t.name);
expect(tags).toContain('Auth');
});
it('should include /ai routes', () => {
// AI agent routes use /users/ prefix in their swagger annotations,
// so paths contain "users" not "ai". Verify the tag exists instead.
const tags = swaggerSpec.tags.map(t => t.name);
expect(tags).toContain('AI');
});
it('should include /rows routes', () => {
const paths = Object.keys(swaggerSpec.paths);
expect(paths.some(p => p.includes('rows'))).toBe(true);
});
});
describe('HTTP Endpoints', () => {
it('should serve Swagger UI at /api/docs/', async () => {
const res = await request(app).get('/api/docs/');
expect(res.status).toBe(200);
expect(res.text).toContain('swagger-ui');
});
it('should return OpenAPI JSON at /api/openapi.json', async () => {
const res = await request(app).get('/api/openapi.json');
expect(res.status).toBe(200);
expect(res.body.openapi).toBe('3.0.3');
expect(res.body.info).toBeDefined();
expect(res.body.paths).toBeDefined();
});
it('should have Content-Type application/json for /api/openapi.json', async () => {
const res = await request(app).get('/api/openapi.json');
expect(res.headers['content-type']).toContain('application/json');
});
});
describe('Schema Definitions', () => {
it('should have Space schema defined', () => {
expect(swaggerSpec.components.schemas).toBeDefined();
expect(swaggerSpec.components.schemas.Space).toBeDefined();
});
it('should have Table schema defined', () => {
expect(swaggerSpec.components.schemas.Table).toBeDefined();
});
it('should have ApiResponse schema defined', () => {
expect(swaggerSpec.components.schemas.ApiResponse).toBeDefined();
});
it('should have Error schema defined', () => {
expect(swaggerSpec.components.schemas.Error).toBeDefined();
});
});
describe('Tags', () => {
it('should have tags for route grouping', () => {
expect(swaggerSpec.tags).toBeDefined();
expect(swaggerSpec.tags.length).toBeGreaterThan(0);
});
it('should have Spaces tag', () => {
const tags = swaggerSpec.tags.map(t => t.name);
expect(tags).toContain('Spaces');
});
it('should have Tables tag', () => {
const tags = swaggerSpec.tags.map(t => t.name);
expect(tags).toContain('Tables');
});
it('should have Auth tag', () => {
const tags = swaggerSpec.tags.map(t => t.name);
expect(tags).toContain('Auth');
});
});
});