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 { Test } from '@nestjs/testing';
|
||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
import { DuplicateModule } from '../src/duplicate/duplicate.module.js';
|
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 { MultiUserModule } from '../src/multi-user/multi-user.module.js';
|
||||||
import { listenOnLoopback } from '../../_support/listen-on-loopback.js';
|
import { listenOnLoopback } from '../../_support/listen-on-loopback.js';
|
||||||
|
|
||||||
@@ -138,4 +139,23 @@ describe('Route conflict policy (Node)', () => {
|
|||||||
.expect(200, { from: 'A' });
|
.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 leftHosts = Array.isArray(leftHost) ? leftHost : [leftHost];
|
||||||
const rightHosts = Array.isArray(rightHost) ? rightHost : [rightHost];
|
const rightHosts = Array.isArray(rightHost) ? rightHost : [rightHost];
|
||||||
if (leftHosts.length !== rightHosts.length) return false;
|
|
||||||
|
|
||||||
// Order-insensitive set comparison: ['a', 'b'] and ['b', 'a']
|
// Set comparison, in both directions: ['a', 'b'] and ['b', 'a'] allow
|
||||||
// describe the same allowed-host set, so they are identical for
|
// the same hosts, and so do ['a', 'a'] and ['a'], while ['a', 'a'] and
|
||||||
// duplicate-classification purposes.
|
// ['a', 'b'] do not, although they have the same length.
|
||||||
return leftHosts.every(leftValue =>
|
const isSubset = (
|
||||||
rightHosts.some(rightValue =>
|
hosts: Array<string | RegExp>,
|
||||||
RouteConflictDetector.hostValuesAreIdentical(leftValue, rightValue),
|
otherHosts: Array<string | RegExp>,
|
||||||
),
|
) =>
|
||||||
);
|
hosts.every(value =>
|
||||||
|
otherHosts.some(otherValue =>
|
||||||
|
RouteConflictDetector.hostValuesAreIdentical(value, otherValue),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return isSubset(leftHosts, rightHosts) && isSubset(rightHosts, leftHosts);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static hostValuesAreIdentical(
|
private static hostValuesAreIdentical(
|
||||||
|
|||||||
@@ -379,6 +379,58 @@ describe('RouteConflictDetector', () => {
|
|||||||
expect(conflicts[0].kind).toBe('duplicate');
|
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', () => {
|
it('should compare across multiple routes only once per unique pair', () => {
|
||||||
const firstRoute = makeResolvedRoute({ path: '/users/me' });
|
const firstRoute = makeResolvedRoute({ path: '/users/me' });
|
||||||
const secondRoute = makeResolvedRoute({ path: '/users/me' });
|
const secondRoute = makeResolvedRoute({ path: '/users/me' });
|
||||||
|
|||||||
Reference in new issue
Block a user