Governed substrate for autonomous agents: scoped identity (passports), audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
159 lines
5.6 KiB
JavaScript
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');
|
|
});
|
|
});
|
|
});
|