133 lines
4.0 KiB
TypeScript
133 lines
4.0 KiB
TypeScript
import 'reflect-metadata';
|
|
import { Body, Controller, Get, HttpCode, Post, Query } from '@nestjs/common';
|
|
import { Test } from '@nestjs/testing';
|
|
import request from 'supertest';
|
|
import { z } from 'zod';
|
|
import { configureApp } from '../configure-app';
|
|
import { parseEnvironment } from '../config/environment';
|
|
import { SchemaPipe } from '../common/validation.pipe';
|
|
import { Public } from '../identity/access.decorator';
|
|
import { configureSwagger } from './configure-swagger';
|
|
|
|
@Controller('sample')
|
|
class SampleController {
|
|
@Public()
|
|
@Post()
|
|
create(
|
|
@Body(
|
|
new SchemaPipe(
|
|
z.strictObject({
|
|
email: z.email(),
|
|
date: z.iso.datetime().transform((value) => new Date(value)),
|
|
}),
|
|
),
|
|
)
|
|
input: unknown,
|
|
) {
|
|
return input;
|
|
}
|
|
@Get()
|
|
list(
|
|
@Query(
|
|
new SchemaPipe(
|
|
z.object({
|
|
limit: z.coerce.number().int().min(1).max(100).default(20),
|
|
}),
|
|
),
|
|
)
|
|
input: unknown,
|
|
) {
|
|
return input;
|
|
}
|
|
@Post('logout')
|
|
@HttpCode(204)
|
|
logout() {}
|
|
}
|
|
|
|
describe('Swagger documentation', () => {
|
|
async function fixture(nodeEnv: string, enabled?: string) {
|
|
const module = await Test.createTestingModule({
|
|
controllers: [SampleController],
|
|
}).compile();
|
|
const app = module.createNestApplication();
|
|
app.useLogger(false);
|
|
const env = parseEnvironment({
|
|
DATABASE_URL: 'postgresql://local/test',
|
|
NODE_ENV: nodeEnv,
|
|
SWAGGER_ENABLED: enabled,
|
|
});
|
|
configureApp(app, env);
|
|
configureSwagger(app, env);
|
|
await app.init();
|
|
return { app, api: request(app.getHttpServer()) };
|
|
}
|
|
it('serves UI, local assets and accurate input/auth/error documentation', async () => {
|
|
const { app, api } = await fixture('development');
|
|
try {
|
|
const ui = await api.get('/api/docs/').expect(200);
|
|
expect(ui.text).toContain('swagger-ui');
|
|
expect(ui.headers['content-security-policy']).not.toContain(
|
|
'upgrade-insecure-requests',
|
|
);
|
|
await api.get('/api/docs/swagger-ui-bundle.js').expect(200);
|
|
const init = await api.get('/api/docs/swagger-ui-init.js').expect(200);
|
|
expect(init.text).toContain('"persistAuthorization": false');
|
|
const { body: doc } = await api.get('/api/docs-json').expect(200);
|
|
const sample = doc.paths['/api/v1/sample'];
|
|
expect(sample.post.security).toEqual([]);
|
|
expect(sample.get.security).toEqual([{ bearer: [] }]);
|
|
expect(
|
|
sample.post.requestBody.content['application/json'].schema,
|
|
).toMatchObject({
|
|
required: ['email', 'date'],
|
|
properties: { email: { format: 'email' }, date: { type: 'string' } },
|
|
});
|
|
expect(sample.get.parameters).toContainEqual(
|
|
expect.objectContaining({
|
|
name: 'limit',
|
|
in: 'query',
|
|
schema: expect.objectContaining({ maximum: 100, default: 20 }),
|
|
}),
|
|
);
|
|
expect(
|
|
doc.paths['/api/v1/sample/logout'].post.responses['204'],
|
|
).toBeDefined();
|
|
expect(doc.components.schemas.ApiError.properties.code.enum).toContain(
|
|
'REQUEST_INVALID',
|
|
);
|
|
expect(
|
|
(await api.get('/api/v1/sample')).headers['content-security-policy'],
|
|
).toContain('upgrade-insecure-requests');
|
|
} finally {
|
|
await app.close();
|
|
}
|
|
});
|
|
it.each([
|
|
['production', undefined],
|
|
['test', undefined],
|
|
['development', 'false'],
|
|
])('hides docs in %s when enabled=%s', async (mode, enabled) => {
|
|
const { app, api } = await fixture(mode!, enabled);
|
|
try {
|
|
await api.get('/api/docs-json').expect(404);
|
|
await api.get('/api/docs').expect(404);
|
|
} finally {
|
|
await app.close();
|
|
}
|
|
});
|
|
it('allows an explicit opt-in and rejects invalid settings', async () => {
|
|
const { app, api } = await fixture('production', 'true');
|
|
try {
|
|
await api.get('/api/docs-json').expect(200);
|
|
} finally {
|
|
await app.close();
|
|
}
|
|
expect(() =>
|
|
parseEnvironment({
|
|
DATABASE_URL: 'postgresql://local/test',
|
|
SWAGGER_ENABLED: 'yes',
|
|
}),
|
|
).toThrow('SWAGGER_ENABLED');
|
|
});
|
|
});
|