mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-03 02:21:44 +00:00
Some checks are pending
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Gitleaks / gitleaks (push) Waiting to run
Publish / RC guard (marker + release-PR skip) (push) Blocked by required conditions
Publish / Classify release event (push) Waiting to run
Publish / ci (push) Blocked by required conditions
Publish / Publish to npm (push) Blocked by required conditions
Publish / Build & Push RC Docker images (push) Blocked by required conditions
Scorecard / Scorecard analysis (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-cli) (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-web) (push) Waiting to run
819 lines
25 KiB
TypeScript
819 lines
25 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import Parser from 'tree-sitter';
|
|
import JavaScript from 'tree-sitter-javascript';
|
|
import TypeScript from 'tree-sitter-typescript';
|
|
import {
|
|
DATA_ROUTE_TABLE_SOURCE,
|
|
extractDataRouteTableRoutes,
|
|
scanDataRouteTables,
|
|
} from '../../src/core/ingestion/route-extractors/data-route-table.js';
|
|
import { JAVASCRIPT_HTTP_PLUGIN } from '../../src/core/group/extractors/http-patterns/node.js';
|
|
|
|
const jsParser = new Parser();
|
|
jsParser.setLanguage(JavaScript);
|
|
|
|
const compact = (source: string) =>
|
|
extractDataRouteTableRoutes(jsParser.parse(source), 'src/routes.js').map((route) => ({
|
|
path: route.routePath,
|
|
method: route.httpMethod,
|
|
handler: route.handlerName,
|
|
source: route.source,
|
|
}));
|
|
|
|
const dispatch = (table: string) => `
|
|
for (const route of ${table}) {
|
|
if (route.path === request.path && route.method === request.method) route.handler();
|
|
}
|
|
`;
|
|
|
|
describe('data route table extraction', () => {
|
|
it('extracts literal entries and preserves method + URL identity', () => {
|
|
expect(
|
|
compact(`
|
|
const apiRoutes = [
|
|
{ path: '/users', method: 'get', handler: listUsers },
|
|
{ path: '/users', method: 'POST', handler: users.create, auth: true },
|
|
];
|
|
${dispatch('apiRoutes')}
|
|
`),
|
|
).toEqual([
|
|
{
|
|
path: '/users',
|
|
method: 'GET',
|
|
handler: 'listUsers',
|
|
source: DATA_ROUTE_TABLE_SOURCE,
|
|
},
|
|
{
|
|
path: '/users',
|
|
method: 'POST',
|
|
handler: 'users.create',
|
|
source: DATA_ROUTE_TABLE_SOURCE,
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('supports quoted keys, static templates, and one-level member handlers', () => {
|
|
expect(
|
|
compact(`
|
|
const ROUTE_TABLE = [{
|
|
'path': \`/auth/me\`,
|
|
'method': 'GET',
|
|
'handler': auth.getCurrentUser,
|
|
}];
|
|
${dispatch('ROUTE_TABLE')}
|
|
`),
|
|
).toMatchObject([{ path: '/auth/me', method: 'GET', handler: 'auth.getCurrentUser' }]);
|
|
});
|
|
|
|
it('decodes JavaScript escapes in static route strings and quoted keys', () => {
|
|
expect(
|
|
compact(`
|
|
const routes = [{
|
|
'p\\u0061th': \`\\/users\`,
|
|
method: 'G\\x45T',
|
|
handler: escaped,
|
|
}];
|
|
${dispatch('routes')}
|
|
`),
|
|
).toMatchObject([{ path: '/users', method: 'GET', handler: 'escaped' }]);
|
|
});
|
|
|
|
it('rejects malformed or legacy-octal route string escapes', () => {
|
|
expect(
|
|
compact(`
|
|
const routes = [{ path: '/\\8users', method: 'GET', handler: malformed }];
|
|
${dispatch('routes')}
|
|
`),
|
|
).toEqual([]);
|
|
});
|
|
|
|
it('rejects executable values on extra route properties', () => {
|
|
expect(
|
|
compact(`
|
|
const routes = [{
|
|
path: '/users',
|
|
method: 'GET',
|
|
handler: listUsers,
|
|
metadata: buildMetadata(),
|
|
}];
|
|
${dispatch('routes')}
|
|
`),
|
|
).toEqual([]);
|
|
});
|
|
|
|
it('keeps declarative metadata on route entries', () => {
|
|
expect(
|
|
compact(`
|
|
const routes = [{
|
|
path: '/users',
|
|
method: 'GET',
|
|
handler: listUsers,
|
|
auth: true,
|
|
metadata: { audience: 'staff', flags: ['audit'] },
|
|
}];
|
|
${dispatch('routes')}
|
|
`),
|
|
).toMatchObject([{ path: '/users', method: 'GET', handler: 'listUsers' }]);
|
|
});
|
|
|
|
it('allows line and block comments between static route properties', () => {
|
|
expect(
|
|
compact(`
|
|
const routes = [{
|
|
path: '/comments',
|
|
// The verb remains a direct literal.
|
|
method: 'GET',
|
|
/* The handler remains a direct designator. */
|
|
handler: commented,
|
|
}];
|
|
${dispatch('routes')}
|
|
`),
|
|
).toMatchObject([{ path: '/comments', method: 'GET', handler: 'commented' }]);
|
|
});
|
|
|
|
it('has JavaScript / TypeScript grammar parity', () => {
|
|
const source = `
|
|
type Route = { path: string; method: string; handler: Function };
|
|
const routes: Route[] = [{ path: '/typed', method: 'GET', handler: typedHandler }];
|
|
${dispatch('routes')}
|
|
`;
|
|
const tsParser = new Parser();
|
|
tsParser.setLanguage(TypeScript.typescript);
|
|
expect(scanDataRouteTables(tsParser.parse(source))).toMatchObject([
|
|
{ path: '/typed', method: 'GET', handlerName: 'typedHandler' },
|
|
]);
|
|
});
|
|
|
|
it.each([
|
|
['dynamic path', `const routes = [{ path: runtimePath, method: 'GET', handler: h }]`, 'routes'],
|
|
[
|
|
'dynamic method',
|
|
`const routes = [{ path: '/x', method: runtimeMethod, handler: h }]`,
|
|
'routes',
|
|
],
|
|
[
|
|
'called handler',
|
|
`const routes = [{ path: '/x', method: 'GET', handler: makeHandler() }]`,
|
|
'routes',
|
|
],
|
|
[
|
|
'inline handler',
|
|
`const routes = [{ path: '/x', method: 'GET', handler: () => {} }]`,
|
|
'routes',
|
|
],
|
|
[
|
|
'spread entry',
|
|
`const routes = [{ ...base, path: '/x', method: 'GET', handler: h }]`,
|
|
'routes',
|
|
],
|
|
['computed key', `const routes = [{ ['path']: '/x', method: 'GET', handler: h }]`, 'routes'],
|
|
['unknown verb', `const routes = [{ path: '/x', method: 'CONNECT', handler: h }]`, 'routes'],
|
|
[
|
|
'multi-level member',
|
|
`const routes = [{ path: '/x', method: 'GET', handler: services.auth.h }]`,
|
|
'routes',
|
|
],
|
|
[
|
|
'non-route binding',
|
|
`const requests = [{ path: '/x', method: 'GET', handler: h }]`,
|
|
'requests',
|
|
],
|
|
])('suppresses %s', (_name, source, table) => {
|
|
expect(compact(`${source}; ${dispatch(table)}`)).toEqual([]);
|
|
});
|
|
|
|
it('suppresses an unconsumed route-named descriptor table', () => {
|
|
expect(
|
|
compact(`const mockRoutes = [{ path: '/admin', method: 'DELETE', handler: onResponse }];`),
|
|
).toEqual([]);
|
|
});
|
|
|
|
it('does not use a dispatch loop hidden in a nested function', () => {
|
|
expect(
|
|
compact(`
|
|
const routes = [{ path: '/admin', method: 'DELETE', handler: removeAdmin }];
|
|
function dispatch(path, method) {
|
|
for (const route of routes) {
|
|
if (route.path === request.path && route.method === request.method) route.handler();
|
|
}
|
|
}
|
|
`),
|
|
).toEqual([]);
|
|
});
|
|
|
|
it('suppresses outbound client tables that pass fields to fetch and then callbacks', () => {
|
|
expect(
|
|
compact(`
|
|
const routeRequests = [
|
|
{ path: '/admin', method: 'DELETE', handler: onResponse },
|
|
];
|
|
for (const route of routeRequests) {
|
|
fetch(route.path, { method: route.method }).then(route.handler);
|
|
}
|
|
`),
|
|
).toEqual([]);
|
|
});
|
|
|
|
it('requires path and method comparisons in the same guard as direct dispatch', () => {
|
|
expect(
|
|
compact(`
|
|
const routes = [{ path: '/admin', method: 'DELETE', handler: onResponse }];
|
|
for (const route of routes) {
|
|
if (route.path) console.log(route.method);
|
|
route.handler();
|
|
}
|
|
`),
|
|
).toEqual([]);
|
|
});
|
|
|
|
it('does not treat negated equality as a positive dispatch guard', () => {
|
|
expect(
|
|
compact(`
|
|
const routes = [{ path: '/admin', method: 'DELETE', handler: removeAdmin }];
|
|
for (const route of routes) {
|
|
if (!(route.path === request.path && route.method === request.method)) {
|
|
route.handler();
|
|
}
|
|
}
|
|
`),
|
|
).toEqual([]);
|
|
});
|
|
|
|
it('does not treat disjunctive comparisons as a complete dispatch guard', () => {
|
|
expect(
|
|
compact(`
|
|
const routes = [{ path: '/admin', method: 'DELETE', handler: removeAdmin }];
|
|
for (const route of routes) {
|
|
if (route.path === request.path || route.method === request.method) route.handler();
|
|
}
|
|
`),
|
|
).toEqual([]);
|
|
});
|
|
|
|
it.each([
|
|
['direct', 'route.path', 'route.method'],
|
|
['parenthesized', '(route.path)', '(route.method)'],
|
|
['computed', "route['path']", "route['method']"],
|
|
['unary', '+route.path', '+route.method'],
|
|
['self-derived', 'normalize(route.path)', 'normalize(route.method)'],
|
|
])(
|
|
'does not accept %s route-field self-comparisons as dispatch evidence',
|
|
(_name, path, method) => {
|
|
expect(
|
|
compact(`
|
|
const routes = [{ path: '/admin', method: 'DELETE', handler: removeAdmin }];
|
|
for (const route of routes) {
|
|
if (route.path === ${path} && route.method === ${method}) route.handler();
|
|
}
|
|
`),
|
|
).toEqual([]);
|
|
},
|
|
);
|
|
|
|
it('suppresses duplicate required keys but preserves route candidates for resolution', () => {
|
|
expect(
|
|
compact(`
|
|
const routes = [
|
|
{ path: '/bad', path: '/other', method: 'GET', handler: bad },
|
|
{ path: '/once', method: 'GET', handler: first },
|
|
{ path: '/once', method: 'GET', handler: second },
|
|
];
|
|
${dispatch('routes')}
|
|
`),
|
|
).toMatchObject([
|
|
{ path: '/once', method: 'GET', handler: 'first' },
|
|
{ path: '/once', method: 'GET', handler: 'second' },
|
|
]);
|
|
});
|
|
|
|
it('rejects literal-only guards that select only one table entry', () => {
|
|
expect(
|
|
compact(`
|
|
const routes = [
|
|
{ path: '/only', method: 'GET', handler: only },
|
|
{ path: '/never', method: 'POST', handler: never },
|
|
];
|
|
for (const route of routes) {
|
|
if (route.path === '/only' && route.method === 'GET') route.handler();
|
|
}
|
|
`),
|
|
).toEqual([]);
|
|
});
|
|
|
|
it('rejects route-derived aliases even when their names resemble request fields', () => {
|
|
expect(
|
|
compact(`
|
|
const routes = [{ path: '/copied', method: 'GET', handler: copied }];
|
|
for (const route of routes) {
|
|
const copiedPath = route.path;
|
|
const copiedMethod = route.method;
|
|
if (route.path === copiedPath && route.method === copiedMethod) route.handler();
|
|
}
|
|
`),
|
|
).toEqual([]);
|
|
});
|
|
|
|
it('requires request fields to come from an unshadowed ingress binding', () => {
|
|
expect(
|
|
compact(`
|
|
const request = { path: '/only', method: 'GET' };
|
|
const routes = [
|
|
{ path: '/only', method: 'GET', handler: only },
|
|
{ path: '/never', method: 'POST', handler: never },
|
|
];
|
|
${dispatch('routes')}
|
|
`),
|
|
).toEqual([]);
|
|
|
|
expect(
|
|
compact(`
|
|
const routes = [{ path: '/nested', method: 'GET', handler: nested }];
|
|
for (const route of routes) {
|
|
if (
|
|
route.path === request.config.path &&
|
|
route.method === request.options.method
|
|
) route.handler();
|
|
}
|
|
`),
|
|
).toEqual([]);
|
|
|
|
expect(
|
|
compact(`
|
|
function dispatchRequest(request) {
|
|
const routes = [{ path: '/parameter', method: 'GET', handler: parameterHandler }];
|
|
for (const route of routes) {
|
|
if (route.path === request.path && route.method === request.method) route.handler();
|
|
}
|
|
}
|
|
`),
|
|
).toMatchObject([{ path: '/parameter', method: 'GET', handler: 'parameterHandler' }]);
|
|
});
|
|
|
|
it.each([
|
|
['reassigned request parameter', 'request = { path: "/x", method: "GET" };'],
|
|
['mutated request fields', 'request.path = "/x"; request.method = "GET";'],
|
|
['escaped request parameter', 'observe(request);'],
|
|
[
|
|
'request mutation through a called closure',
|
|
'const overwrite = () => { request.path = "/x"; request.method = "GET"; }; overwrite();',
|
|
],
|
|
])('rejects an unstable ingress binding: %s', (_name, mutation) => {
|
|
expect(
|
|
compact(`
|
|
function dispatchRequest(request) {
|
|
const routes = [{ path: '/x', method: 'GET', handler: handler }];
|
|
${mutation}
|
|
for (const route of routes) {
|
|
if (route.path === request.path && route.method === request.method) route.handler();
|
|
}
|
|
}
|
|
`),
|
|
).toEqual([]);
|
|
});
|
|
|
|
it.each([
|
|
[
|
|
'a route-literal narrowing conjunct',
|
|
`route.path === request.path && route.method === request.method && route.path === '/only'`,
|
|
],
|
|
[
|
|
'a statically false conjunct',
|
|
`route.path === request.path && route.method === request.method && false`,
|
|
],
|
|
[
|
|
'a statically false equality',
|
|
`route.path === request.path && route.method === request.method && 1 === 2`,
|
|
],
|
|
])('rejects %s', (_name, condition) => {
|
|
expect(
|
|
compact(`
|
|
const routes = [
|
|
{ path: '/only', method: 'GET', handler: only },
|
|
{ path: '/other', method: 'GET', handler: other },
|
|
];
|
|
for (const route of routes) {
|
|
if (${condition}) route.handler();
|
|
}
|
|
`),
|
|
).toEqual([]);
|
|
});
|
|
|
|
it('rejects a conditionally unreachable handler call in the consequence', () => {
|
|
expect(
|
|
compact(`
|
|
const routes = [{ path: '/x', method: 'GET', handler: unreachable }];
|
|
for (const route of routes) {
|
|
if (route.path === request.path && route.method === request.method) {
|
|
false && route.handler();
|
|
}
|
|
}
|
|
`),
|
|
).toEqual([]);
|
|
});
|
|
|
|
it('rejects a handler call after a statically terminating branch', () => {
|
|
expect(
|
|
compact(`
|
|
const routes = [{ path: '/x', method: 'GET', handler: unreachable }];
|
|
for (const route of routes) {
|
|
if (route.path === request.path && route.method === request.method) {
|
|
if (true) return;
|
|
route.handler();
|
|
}
|
|
}
|
|
`),
|
|
).toEqual([]);
|
|
});
|
|
|
|
it.each(['return', 'throw new Error("stop")', 'break', 'continue'])(
|
|
'rejects a handler call after an unconditional %s',
|
|
(transfer) => {
|
|
expect(
|
|
compact(`
|
|
const routes = [{ path: '/x', method: 'GET', handler: unreachable }];
|
|
for (const route of routes) {
|
|
if (route.path === request.path && route.method === request.method) {
|
|
${transfer};
|
|
route.handler();
|
|
}
|
|
}
|
|
`),
|
|
).toEqual([]);
|
|
},
|
|
);
|
|
|
|
it.each(['return', 'throw new Error("stop")', 'break', 'continue'])(
|
|
'rejects a dispatch guard after an unconditional %s',
|
|
(transfer) => {
|
|
expect(
|
|
compact(`
|
|
const routes = [{ path: '/x', method: 'GET', handler: unreachable }];
|
|
for (const route of routes) {
|
|
${transfer};
|
|
if (route.path === request.path && route.method === request.method) {
|
|
route.handler();
|
|
}
|
|
}
|
|
`),
|
|
).toEqual([]);
|
|
},
|
|
);
|
|
|
|
it('rejects a dispatch guard nested in a statically false branch', () => {
|
|
expect(
|
|
compact(`
|
|
const routes = [{ path: '/x', method: 'GET', handler: unreachable }];
|
|
for (const route of routes) {
|
|
if (false) {
|
|
if (route.path === request.path && route.method === request.method) {
|
|
route.handler();
|
|
}
|
|
}
|
|
}
|
|
`),
|
|
).toEqual([]);
|
|
});
|
|
|
|
it('rejects mutation of the loop entry before dispatch', () => {
|
|
expect(
|
|
compact(`
|
|
const routes = [{ path: '/mutated', method: 'GET', handler: mutated }];
|
|
for (const route of routes) {
|
|
route.path = request.path;
|
|
route.method = request.method;
|
|
if (route.path === request.path && route.method === request.method) route.handler();
|
|
}
|
|
`),
|
|
).toEqual([]);
|
|
|
|
expect(
|
|
compact(`
|
|
const routes = [{ path: '/reassigned', method: 'GET', handler: original }];
|
|
for (let route of routes) {
|
|
route = replacement;
|
|
if (route.path === request.path && route.method === request.method) route.handler();
|
|
}
|
|
`),
|
|
).toEqual([]);
|
|
});
|
|
|
|
it.each([
|
|
['deleting an entry field', 'delete route.path;'],
|
|
['deleting a wrapped entry field', 'delete (route.path);'],
|
|
['writing through a destructuring target', '({ path: route.path } = request);'],
|
|
['augmenting the handler field', 'route.handler ||= replacement;'],
|
|
['aliasing the entry', 'const alias = route; alias.path = request.path;'],
|
|
])('rejects %s', (_name, mutation) => {
|
|
expect(
|
|
compact(`
|
|
const routes = [{ path: '/mutated', method: 'GET', handler: original }];
|
|
for (const route of routes) {
|
|
${mutation}
|
|
if (route.path === request.path && route.method === request.method) route.handler();
|
|
}
|
|
`),
|
|
).toEqual([]);
|
|
});
|
|
|
|
it.each([
|
|
[
|
|
'a mutable declaration',
|
|
`let routes = [{ path: '/stale', method: 'GET', handler: stale }];
|
|
${dispatch('routes')}`,
|
|
],
|
|
[
|
|
'a reassigned table',
|
|
`const routes = [{ path: '/stale', method: 'GET', handler: stale }];
|
|
routes = [];
|
|
${dispatch('routes')}`,
|
|
],
|
|
[
|
|
'an aliased table',
|
|
`const routes = [{ path: '/stale', method: 'GET', handler: stale }];
|
|
const alias = routes;
|
|
${dispatch('routes')}`,
|
|
],
|
|
[
|
|
'a truncated table',
|
|
`const routes = [{ path: '/stale', method: 'GET', handler: stale }];
|
|
routes.length = 0;
|
|
${dispatch('routes')}`,
|
|
],
|
|
])('rejects %s', (_name, source) => {
|
|
expect(compact(source)).toEqual([]);
|
|
});
|
|
|
|
it.each([
|
|
[
|
|
'a reassigned bare handler',
|
|
`function original() {}
|
|
const routes = [{ path: '/stale', method: 'GET', handler: original }];
|
|
original = replacement;
|
|
${dispatch('routes')}`,
|
|
],
|
|
[
|
|
'a reassigned member handler',
|
|
`const auth = {};
|
|
auth.handle = replacement;
|
|
const routes = [{ path: '/stale', method: 'GET', handler: auth.handle }];
|
|
${dispatch('routes')}`,
|
|
],
|
|
[
|
|
'a handler owner mutated through an alias',
|
|
`const auth = {};
|
|
const alias = auth;
|
|
alias.handle = replacement;
|
|
const routes = [{ path: '/stale', method: 'GET', handler: auth.handle }];
|
|
${dispatch('routes')}`,
|
|
],
|
|
[
|
|
'a handler owner hidden by parentheses',
|
|
`const auth = {};
|
|
const alias = (auth);
|
|
alias.handle = replacement;
|
|
const routes = [{ path: '/stale', method: 'GET', handler: auth.handle }];
|
|
${dispatch('routes')}`,
|
|
],
|
|
[
|
|
'a handler owner nested in an initializer',
|
|
`const auth = {};
|
|
const box = { auth };
|
|
box.auth.handle = replacement;
|
|
const routes = [{ path: '/stale', method: 'GET', handler: auth.handle }];
|
|
${dispatch('routes')}`,
|
|
],
|
|
[
|
|
'a handler owner stored under an unrelated handler property',
|
|
`const auth = {};
|
|
const box = { handler: auth };
|
|
box.handler.handle = replacement;
|
|
const routes = [{ path: '/stale', method: 'GET', handler: auth.handle }];
|
|
${dispatch('routes')}`,
|
|
],
|
|
[
|
|
'a handler owner assigned to an alias',
|
|
`const auth = {};
|
|
let alias;
|
|
alias = auth;
|
|
alias.handle = replacement;
|
|
const routes = [{ path: '/stale', method: 'GET', handler: auth.handle }];
|
|
${dispatch('routes')}`,
|
|
],
|
|
[
|
|
'an escaped handler owner',
|
|
`const auth = {};
|
|
auth.configure();
|
|
const routes = [{ path: '/stale', method: 'GET', handler: auth.handle }];
|
|
${dispatch('routes')}`,
|
|
],
|
|
[
|
|
'a handler owner escaped through a computed member call',
|
|
`const auth = {};
|
|
auth['configure']();
|
|
const routes = [{ path: '/stale', method: 'GET', handler: auth.handle }];
|
|
${dispatch('routes')}`,
|
|
],
|
|
[
|
|
'a parenthesized handler owner escaped through a member call',
|
|
`const auth = {};
|
|
(auth).configure();
|
|
const routes = [{ path: '/stale', method: 'GET', handler: auth.handle }];
|
|
${dispatch('routes')}`,
|
|
],
|
|
[
|
|
'a returned handler owner',
|
|
`const auth = {};
|
|
function expose() { return auth; }
|
|
expose().handle = replacement;
|
|
const routes = [{ path: '/stale', method: 'GET', handler: auth.handle }];
|
|
${dispatch('routes')}`,
|
|
],
|
|
])('rejects %s', (_name, source) => {
|
|
expect(compact(source)).toEqual([]);
|
|
});
|
|
|
|
it('preserves an unresolved identity tombstone for unsupported duplicate handlers', () => {
|
|
expect(
|
|
compact(`
|
|
function valid() {}
|
|
const routes = [
|
|
{ path: '/users', method: 'GET', handler: makeHandler() },
|
|
{ path: '/users', method: 'GET', handler: valid },
|
|
];
|
|
${dispatch('routes')}
|
|
`),
|
|
).toEqual([]);
|
|
});
|
|
|
|
it('tombstones recoverable identities from entries with duplicate route keys', () => {
|
|
expect(
|
|
compact(`
|
|
function first() {}
|
|
function second() {}
|
|
const routes = [
|
|
{ path: '/users', path: '/users', method: 'GET', handler: first },
|
|
{ path: '/users', method: 'GET', handler: second },
|
|
];
|
|
${dispatch('routes')}
|
|
`),
|
|
).toEqual([]);
|
|
});
|
|
|
|
it('uses the final duplicate route keys when tombstoning an invalid entry', () => {
|
|
expect(
|
|
compact(`
|
|
function first() {}
|
|
function second() {}
|
|
const routes = [
|
|
{ path: '/ignored', path: '/users', method: 'POST', method: 'GET', handler: first },
|
|
{ path: '/users', method: 'GET', handler: second },
|
|
];
|
|
${dispatch('routes')}
|
|
`),
|
|
).toEqual([]);
|
|
});
|
|
|
|
it('suppresses later candidates when a trailing spread leaves identity unknown', () => {
|
|
expect(
|
|
compact(`
|
|
function first() {}
|
|
function second() {}
|
|
const routes = [
|
|
{ path: '/unknown', method: 'POST', handler: first, ...dynamicRoute },
|
|
{ path: '/users', method: 'GET', handler: second },
|
|
];
|
|
${dispatch('routes')}
|
|
`),
|
|
).toEqual([]);
|
|
});
|
|
|
|
it.each([
|
|
['a dynamic path', `{ path: runtimePath, method: 'GET', handler: first }`],
|
|
[
|
|
'a computed overwrite',
|
|
`{ path: '/other', method: 'GET', handler: first, ['path']: '/users' }`,
|
|
],
|
|
])('suppresses later candidates after %s', (_name, firstEntry) => {
|
|
expect(
|
|
compact(`
|
|
function first() {}
|
|
function second() {}
|
|
const routes = [
|
|
${firstEntry},
|
|
{ path: '/users', method: 'GET', handler: second },
|
|
];
|
|
${dispatch('routes')}
|
|
`),
|
|
).toEqual([]);
|
|
});
|
|
|
|
it('rejects tables containing array-level spreads', () => {
|
|
expect(
|
|
compact(`
|
|
function second() {}
|
|
const routes = [
|
|
...baseRoutes,
|
|
{ path: '/users', method: 'GET', handler: second },
|
|
];
|
|
${dispatch('routes')}
|
|
`),
|
|
).toEqual([]);
|
|
});
|
|
|
|
it('propagates unresolved identity tombstones across route tables', () => {
|
|
expect(
|
|
compact(`
|
|
function first() {}
|
|
function second() {}
|
|
const primaryRoutes = [{ path: '/users', method: 'GET', handler: first }];
|
|
const fallbackRoutes = [{ path: '/users', method: 'GET', handler: makeHandler() }];
|
|
${dispatch('primaryRoutes')}
|
|
${dispatch('fallbackRoutes')}
|
|
`),
|
|
).toEqual([]);
|
|
});
|
|
|
|
it.each([
|
|
[
|
|
'an unstable duplicate handler binding',
|
|
`function first() {}
|
|
function second() {}
|
|
first = replacement;
|
|
const routes = [
|
|
{ path: '/users', method: 'GET', handler: first },
|
|
{ path: '/users', method: 'GET', handler: second },
|
|
];`,
|
|
],
|
|
[
|
|
'a spread-first duplicate entry',
|
|
`function second() {}
|
|
const routes = [
|
|
{ ...base, path: '/users', method: 'GET', handler: first },
|
|
{ path: '/users', method: 'GET', handler: second },
|
|
];`,
|
|
],
|
|
])('tombstones %s', (_name, table) => {
|
|
expect(compact(`${table} ${dispatch('routes')}`)).toEqual([]);
|
|
});
|
|
|
|
it('rejects shadowed table and loop-entry bindings', () => {
|
|
expect(
|
|
compact(`
|
|
const routes = [{ path: '/outer', method: 'GET', handler: outer }];
|
|
{
|
|
const routes = [{ path: '/inner', method: 'GET', handler: inner }];
|
|
for (const route of routes) {
|
|
if (route.path === request.path && route.method === request.method) route.handler();
|
|
}
|
|
}
|
|
`),
|
|
).toEqual([]);
|
|
|
|
expect(
|
|
compact(`
|
|
const routes = [{ path: '/outer', method: 'GET', handler: outer }];
|
|
for (const route of routes) {
|
|
{
|
|
const route = fakeRoute;
|
|
if (route.path === request.path && route.method === request.method) route.handler();
|
|
}
|
|
}
|
|
`),
|
|
).toEqual([]);
|
|
});
|
|
|
|
it('suppresses a named import shadowed anywhere in the file', () => {
|
|
const detections = JAVASCRIPT_HTTP_PLUGIN.scan(
|
|
jsParser.parse(`
|
|
import { listUsers } from './handlers.js';
|
|
function wrapper(listUsers) { return listUsers; }
|
|
const routes = [{ path: '/users', method: 'GET', handler: listUsers }];
|
|
${dispatch('routes')}
|
|
`),
|
|
);
|
|
expect(detections).not.toContainEqual(
|
|
expect.objectContaining({ framework: DATA_ROUTE_TABLE_SOURCE, path: '/users' }),
|
|
);
|
|
});
|
|
|
|
it('feeds the Node group scanner with named-import provenance', () => {
|
|
const detections = JAVASCRIPT_HTTP_PLUGIN.scan(
|
|
jsParser.parse(`
|
|
import { listUsers as handleUsers } from './handlers.js';
|
|
const routes = [{ path: '/users', method: 'GET', handler: handleUsers }];
|
|
${dispatch('routes')}
|
|
`),
|
|
);
|
|
expect(detections).toContainEqual({
|
|
role: 'provider',
|
|
framework: DATA_ROUTE_TABLE_SOURCE,
|
|
method: 'GET',
|
|
path: '/users',
|
|
name: 'listUsers',
|
|
handlerImport: { name: 'listUsers', module: './handlers.js' },
|
|
strictHandlerResolution: true,
|
|
line: 3,
|
|
confidence: 0.8,
|
|
});
|
|
});
|
|
});
|