mirror of
https://github.com/nestjs/nest.git
synced 2026-10-28 09:45:43 +08:00
162 lines
5.2 KiB
TypeScript
162 lines
5.2 KiB
TypeScript
import { NestApplicationOptions } from '@nestjs/common';
|
|
import { RouteConflictException } from '@nestjs/core/errors/exceptions/route-conflict.exception.js';
|
|
import { NestNodeApplication, NodeAdapter } from '@nestjs/platform-node';
|
|
import { Test } from '@nestjs/testing';
|
|
import request from 'supertest';
|
|
import { DuplicateModule } from '../src/duplicate/duplicate.module.js';
|
|
import { HostListModule } from '../src/host-list/host-list.module.js';
|
|
import { MultiUserModule } from '../src/multi-user/multi-user.module.js';
|
|
import { listenOnLoopback } from '../../_support/listen-on-loopback.js';
|
|
|
|
interface CapturedLogger {
|
|
warnings: string[];
|
|
logger: {
|
|
log(message: any, ...rest: any[]): void;
|
|
warn(message: any, ...rest: any[]): void;
|
|
error(message: any, ...rest: any[]): void;
|
|
debug(message: any, ...rest: any[]): void;
|
|
verbose(message: any, ...rest: any[]): void;
|
|
fatal(message: any, ...rest: any[]): void;
|
|
};
|
|
}
|
|
|
|
function createCaptureLogger(): CapturedLogger {
|
|
const warnings: string[] = [];
|
|
return {
|
|
warnings,
|
|
logger: {
|
|
log: () => {},
|
|
warn: (message: any) => warnings.push(String(message)),
|
|
error: () => {},
|
|
debug: () => {},
|
|
verbose: () => {},
|
|
fatal: () => {},
|
|
},
|
|
};
|
|
}
|
|
|
|
async function buildNodeApp(
|
|
moduleClass: any,
|
|
options: NestApplicationOptions,
|
|
capture?: CapturedLogger,
|
|
): Promise<NestNodeApplication> {
|
|
const moduleRef = await Test.createTestingModule({
|
|
imports: [moduleClass],
|
|
}).compile();
|
|
const app = moduleRef.createNestApplication<NestNodeApplication>(
|
|
new NodeAdapter(),
|
|
options,
|
|
);
|
|
if (capture) {
|
|
app.useLogger(capture.logger);
|
|
}
|
|
return app;
|
|
}
|
|
|
|
describe('Route conflict policy (Node)', () => {
|
|
let app: NestNodeApplication | undefined;
|
|
|
|
afterEach(async () => {
|
|
if (app) {
|
|
await app.close();
|
|
app = undefined;
|
|
}
|
|
});
|
|
|
|
describe('multi-user fixture', () => {
|
|
it('boots even with shadow=error because the Node adapter is not order-sensitive', async () => {
|
|
app = await buildNodeApp(MultiUserModule, {
|
|
routeConflictPolicy: { shadow: 'error' },
|
|
});
|
|
await expect(listenOnLoopback(app)).resolves.toBeDefined();
|
|
});
|
|
|
|
it('natively routes every endpoint correctly without any strategy', async () => {
|
|
app = await buildNodeApp(MultiUserModule, {});
|
|
await listenOnLoopback(app);
|
|
|
|
const cases: Array<{ url: string; body: Record<string, unknown> }> = [
|
|
{ url: '/users/me', body: { handler: 'me' } },
|
|
{ url: '/users/images', body: { handler: 'images' } },
|
|
{
|
|
url: '/users/images/42',
|
|
body: { handler: 'imageById', imageId: '42' },
|
|
},
|
|
{ url: '/users/abc', body: { handler: 'byId', userId: 'abc' } },
|
|
];
|
|
|
|
for (const testCase of cases) {
|
|
await request(app.getHttpServer())
|
|
.get(testCase.url)
|
|
.expect(200, testCase.body);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('duplicate fixture', () => {
|
|
it('aborts on duplicate=error with the aggregated RouteConflictException', async () => {
|
|
const builtApp = await buildNodeApp(DuplicateModule, {
|
|
routeConflictPolicy: { duplicate: 'error' },
|
|
});
|
|
await expect(builtApp.init()).rejects.toBeInstanceOf(
|
|
RouteConflictException,
|
|
);
|
|
});
|
|
|
|
it('boots on duplicate=warn and emits one warning', async () => {
|
|
const capture = createCaptureLogger();
|
|
app = await buildNodeApp(
|
|
DuplicateModule,
|
|
{ routeConflictPolicy: { duplicate: 'warn' } },
|
|
capture,
|
|
);
|
|
await expect(listenOnLoopback(app)).resolves.toBeDefined();
|
|
expect(capture.warnings).toHaveLength(1);
|
|
expect(capture.warnings[0]).toContain('/users/me');
|
|
});
|
|
|
|
it('boots on duplicate=off silently (later duplicate is dropped, first wins)', async () => {
|
|
const capture = createCaptureLogger();
|
|
app = await buildNodeApp(
|
|
DuplicateModule,
|
|
{ routeConflictPolicy: { duplicate: 'off' } },
|
|
capture,
|
|
);
|
|
await expect(listenOnLoopback(app)).resolves.toBeDefined();
|
|
|
|
await request(app.getHttpServer())
|
|
.get('/users/me')
|
|
.expect(200, { from: 'A' });
|
|
expect(capture.warnings).toHaveLength(0);
|
|
});
|
|
|
|
it('boots without a policy, and the first duplicate wins (Fastify throws at registration)', async () => {
|
|
app = await buildNodeApp(DuplicateModule, {});
|
|
await expect(listenOnLoopback(app)).resolves.toBeDefined();
|
|
|
|
await request(app.getHttpServer())
|
|
.get('/users/me')
|
|
.expect(200, { from: 'A' });
|
|
});
|
|
});
|
|
|
|
describe('host-list fixture', () => {
|
|
it.each([{ duplicate: 'off' }, { duplicate: 'error' }] as const)(
|
|
'keeps a route whose host list only partly matches a repeated one (%j)',
|
|
async routeConflictPolicy => {
|
|
app = await buildNodeApp(HostListModule, { routeConflictPolicy });
|
|
await expect(listenOnLoopback(app)).resolves.toBeDefined();
|
|
|
|
await request(app.getHttpServer())
|
|
.get('/users/me')
|
|
.set('Host', 'a.example.com')
|
|
.expect(200, { from: 'repeated' });
|
|
await request(app.getHttpServer())
|
|
.get('/users/me')
|
|
.set('Host', 'b.example.com')
|
|
.expect(200, { from: 'two-hosts' });
|
|
},
|
|
);
|
|
});
|
|
});
|