mirror of
https://github.com/nestjs/nest.git
synced 2026-10-28 09:45:43 +08:00
fix(core): tell host lists apart when one repeats a host (#18305)
This commit is contained in:
1 parent
a415cff0cf
commit
6ce26bc666
6 files changed
+111
-9
No files matched your search
@@ -4,6 +4,7 @@ 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';
|
||||
|
||||
@@ -138,4 +139,23 @@ describe('Route conflict policy (Node)', () => {
|
||||
.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' });
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { RepeatedHostController } from './repeated-host.controller.js';
|
||||
import { TwoHostsController } from './two-hosts.controller.js';
|
||||
|
||||
@Module({
|
||||
controllers: [RepeatedHostController, TwoHostsController],
|
||||
})
|
||||
export class HostListModule {}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
|
||||
@Controller({ host: ['a.example.com', 'a.example.com'] })
|
||||
export class RepeatedHostController {
|
||||
@Get('users/me')
|
||||
me() {
|
||||
return { from: 'repeated' };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
|
||||
@Controller({ host: ['a.example.com', 'b.example.com'] })
|
||||
export class TwoHostsController {
|
||||
@Get('users/me')
|
||||
me() {
|
||||
return { from: 'two-hosts' };
|
||||
}
|
||||
}
|
||||
@@ -358,16 +358,20 @@ export class RouteConflictDetector {
|
||||
|
||||
const leftHosts = Array.isArray(leftHost) ? leftHost : [leftHost];
|
||||
const rightHosts = Array.isArray(rightHost) ? rightHost : [rightHost];
|
||||
if (leftHosts.length !== rightHosts.length) return false;
|
||||
|
||||
// Order-insensitive set comparison: ['a', 'b'] and ['b', 'a']
|
||||
// describe the same allowed-host set, so they are identical for
|
||||
// duplicate-classification purposes.
|
||||
return leftHosts.every(leftValue =>
|
||||
rightHosts.some(rightValue =>
|
||||
RouteConflictDetector.hostValuesAreIdentical(leftValue, rightValue),
|
||||
),
|
||||
);
|
||||
// Set comparison, in both directions: ['a', 'b'] and ['b', 'a'] allow
|
||||
// the same hosts, and so do ['a', 'a'] and ['a'], while ['a', 'a'] and
|
||||
// ['a', 'b'] do not, although they have the same length.
|
||||
const isSubset = (
|
||||
hosts: Array<string | RegExp>,
|
||||
otherHosts: Array<string | RegExp>,
|
||||
) =>
|
||||
hosts.every(value =>
|
||||
otherHosts.some(otherValue =>
|
||||
RouteConflictDetector.hostValuesAreIdentical(value, otherValue),
|
||||
),
|
||||
);
|
||||
return isSubset(leftHosts, rightHosts) && isSubset(rightHosts, leftHosts);
|
||||
}
|
||||
|
||||
private static hostValuesAreIdentical(
|
||||
|
||||
@@ -379,6 +379,58 @@ describe('RouteConflictDetector', () => {
|
||||
expect(conflicts[0].kind).toBe('duplicate');
|
||||
});
|
||||
|
||||
it.each([
|
||||
[
|
||||
['api.example.com', 'api.example.com'],
|
||||
['api.example.com', 'admin.example.com'],
|
||||
],
|
||||
[
|
||||
['api.example.com', 'admin.example.com'],
|
||||
['api.example.com', 'api.example.com'],
|
||||
],
|
||||
])(
|
||||
'should classify host arrays %j and %j as a shadow, not a duplicate',
|
||||
(earlierHosts, laterHosts) => {
|
||||
const earlierRoute = makeResolvedRoute({
|
||||
path: '/users/me',
|
||||
host: earlierHosts,
|
||||
});
|
||||
const laterRoute = makeResolvedRoute({
|
||||
path: '/users/me',
|
||||
host: laterHosts,
|
||||
methodName: 'meAgain',
|
||||
});
|
||||
|
||||
const conflicts = RouteConflictDetector.detect(
|
||||
[earlierRoute, laterRoute],
|
||||
undefined,
|
||||
);
|
||||
|
||||
expect(conflicts).toHaveLength(1);
|
||||
expect(conflicts[0].kind).toBe('shadow');
|
||||
},
|
||||
);
|
||||
|
||||
it('should classify a host array with a repeated value as a duplicate of the same hosts listed once', () => {
|
||||
const earlierRoute = makeResolvedRoute({
|
||||
path: '/users/me',
|
||||
host: ['api.example.com', 'api.example.com'],
|
||||
});
|
||||
const laterRoute = makeResolvedRoute({
|
||||
path: '/users/me',
|
||||
host: ['api.example.com'],
|
||||
methodName: 'meAgain',
|
||||
});
|
||||
|
||||
const conflicts = RouteConflictDetector.detect(
|
||||
[earlierRoute, laterRoute],
|
||||
undefined,
|
||||
);
|
||||
|
||||
expect(conflicts).toHaveLength(1);
|
||||
expect(conflicts[0].kind).toBe('duplicate');
|
||||
});
|
||||
|
||||
it('should compare across multiple routes only once per unique pair', () => {
|
||||
const firstRoute = makeResolvedRoute({ path: '/users/me' });
|
||||
const secondRoute = makeResolvedRoute({ path: '/users/me' });
|
||||
|
||||
Reference in new issue
Block a user