Files
rea/src/domain/javascript/javascriptStaticAnalysis.partialEndpoints.test.ts
T
Ahmed 3984d12a30 fix(js): resolve literal URL from Request constructor in fetch calls (#6364)
* fix(js): resolve literal URL from Request constructor in fetch calls

* fix(js): gate Request URL extraction on fetch and require new operator
2026-10-23 05:38:17 +08:00

377 lines
8.7 KiB
TypeScript

import { expect, it } from "vitest";
import { analyzeJavaScriptStaticSource } from "./javascriptStaticAnalysis.js";
const endpoints = (source: string) =>
analyzeJavaScriptStaticSource(source).endpoints.map((endpoint) => ({
kind: endpoint.kind,
value: endpoint.value,
mechanism: endpoint.mechanism,
resolution: endpoint.resolution,
}));
it("resolves unchanged lexical strings and object properties", () => {
expect(
endpoints(`
const target = "/items";
const config = { url: "/accounts" };
fetch(target);
fetch("/direct");
fetch(config.url);
`),
).toEqual([
{
kind: "network",
value: "/items",
mechanism: "call:fetch",
resolution: undefined,
},
{
kind: "network",
value: "/direct",
mechanism: "call:fetch",
resolution: undefined,
},
{
kind: "network",
value: "/accounts",
mechanism: "call:fetch",
resolution: undefined,
},
]);
});
it("resolves unchanged URL objects and aliases using URL serialization", () => {
expect(
endpoints(`
const socket = new URL("HTTPS://EXAMPLE.COM:443");
const alias = socket;
const relative = new URL("/items", "https://api.example.test/base");
fetch(alias);
fetch(relative);
`),
).toEqual([
{
kind: "network",
value: "https://example.com/",
mechanism: "call:fetch",
resolution: undefined,
},
{
kind: "network",
value: "https://api.example.test/items",
mechanism: "call:fetch",
resolution: undefined,
},
]);
});
it("keeps an interpolated request expression verbatim", () => {
expect(
endpoints("fetch(`https://${hosts['a@b']}/items/${id}`);"),
).toContainEqual({
kind: "network",
value: "`https://${hosts['a@b']}/items/${id}`",
mechanism: "call:fetch",
resolution: "partial",
});
});
it("keeps writes to a config property partial instead of resolving its initializer", () => {
expect(
endpoints(`
const config = { url: "https://old.example.test" };
config.url = "https://new.example.test";
fetch(config.url);
`),
).toContainEqual({
kind: "network",
value: "config.url",
mechanism: "call:fetch",
resolution: "partial",
});
});
it("keeps a reassigned endpoint binding partial", () => {
expect(
endpoints(`
let endpoint = "https://old.example.test";
endpoint = "https://new.example.test";
fetch(endpoint);
`),
).toContainEqual({
kind: "network",
value: "endpoint",
mechanism: "call:fetch",
resolution: "partial",
});
});
it("does not resolve an endpoint from a declaration after the request", () => {
expect(
endpoints(`
fetch(endpoint);
const endpoint = "https://api.example.test/items";
`),
).toEqual([
{
kind: "network",
value: "endpoint",
mechanism: "call:fetch",
resolution: "partial",
},
]);
});
it("keeps URLSearchParams mutations partial", () => {
expect(
endpoints(`
const socket = new URL("wss://old.example.test/socket");
socket.searchParams.set("token", token);
new WebSocket(socket);
`),
).toContainEqual({
kind: "network",
value: "socket",
mechanism: "call:WebSocket",
resolution: "partial",
});
});
it("resolves a URL object against a literal base", () => {
expect(
endpoints(`
const socket = new URL("/socket", "wss://api.example.test");
new WebSocket(socket);
`),
).toEqual([
{
kind: "network",
value: "wss://api.example.test/socket",
mechanism: "call:WebSocket",
resolution: undefined,
},
]);
});
it("resolves an unchanged URL object's toString target", () => {
expect(
endpoints(`
const socket = new URL("wss://api.example.test/socket");
new WebSocket(socket.toString());
`),
).toEqual([
{
kind: "network",
value: "wss://api.example.test/socket",
mechanism: "call:WebSocket",
resolution: undefined,
},
]);
});
it("keeps a URL object partial when its base is unresolved", () => {
expect(
endpoints(`
const socket = new URL("/socket", apiOrigin);
new WebSocket(socket);
`),
).toContainEqual({
kind: "network",
value: "socket",
mechanism: "call:WebSocket",
resolution: "partial",
});
});
it("keeps URL aliases partial after a member mutation", () => {
expect(
endpoints(`
const socket = new URL("wss://old.example.test/socket");
const alias = socket;
alias.hostname = "new.example.test";
fetch(socket);
`),
).toContainEqual({
kind: "network",
value: "socket",
mechanism: "call:fetch",
resolution: "partial",
});
});
it("keeps an escaped URL object partial", () => {
expect(
endpoints(`
const socket = new URL("wss://old.example.test/socket");
configure(socket);
new WebSocket(socket);
`),
).toContainEqual({
kind: "network",
value: "socket",
mechanism: "call:WebSocket",
resolution: "partial",
});
});
it("retains dynamic targets on known global fetch receivers", () => {
expect(
endpoints(`
window.fetch(endpoint);
globalThis.fetch(endpoint);
self.fetch(endpoint);
`),
).toEqual([
{
kind: "network",
value: "endpoint",
mechanism: "call:window.fetch",
resolution: "partial",
},
{
kind: "network",
value: "endpoint",
mechanism: "call:globalThis.fetch",
resolution: "partial",
},
{
kind: "network",
value: "endpoint",
mechanism: "call:self.fetch",
resolution: "partial",
},
]);
});
it("does not treat a locally bound fetch function as a network API", () => {
expect(
endpoints(`
function fetch(key) { return records[key]; }
fetch(key);
`),
).toEqual([]);
});
it("keeps exact literal targets exact", () => {
expect(endpoints('fetch("https://api.example.test/items");')).toEqual([
{
kind: "network",
value: "https://api.example.test/items",
mechanism: "call:fetch",
resolution: undefined,
},
]);
});
it("resolves inline Request constructor targets with literal URLs", () => {
expect(
endpoints(`
fetch(new Request("/api/chat", { method: "POST" }));
window.fetch(new Request("/api/window"));
`),
).toEqual([
{
kind: "network",
value: "/api/chat",
mechanism: "call:fetch",
resolution: undefined,
},
{
kind: "network",
value: "/api/window",
mechanism: "call:window.fetch",
resolution: undefined,
},
]);
});
it("keeps Request calls without new partial", () => {
expect(endpoints('fetch(Request("/api/stream"));')).toEqual([
{
kind: "network",
value: 'Request("/api/stream")',
mechanism: "call:fetch",
resolution: "partial",
},
]);
});
it("resolves unchanged Request objects and aliases", () => {
expect(
endpoints(`
const req = new Request("/api/chat", { method: "POST" });
const alias = req;
fetch(alias);
`),
).toEqual([
{
kind: "network",
value: "/api/chat",
mechanism: "call:fetch",
resolution: undefined,
},
]);
});
it("keeps Request objects partial when called with toString", () => {
expect(
endpoints(`
const req = new Request("/api/chat");
fetch(req.toString());
`),
).toEqual([
{
kind: "network",
value: "req.toString()",
mechanism: "call:fetch",
resolution: "partial",
},
]);
});
it("keeps Request targets partial on non-fetch network APIs", () => {
expect(
endpoints(`
const socketReq = new Request("wss://host/socket");
new WebSocket(new Request("wss://host/direct"));
new WebSocket(socketReq);
navigator.sendBeacon(new Request("https://api.example.test/beacon"));
`),
).toEqual([
{
kind: "network",
value: 'new Request("wss://host/direct")',
mechanism: "call:WebSocket",
resolution: "partial",
},
{
kind: "network",
value: "socketReq",
mechanism: "call:WebSocket",
resolution: "partial",
},
{
kind: "network",
value: 'new Request("https://api.example.test/beacon")',
mechanism: "call:navigator.sendBeacon",
resolution: "partial",
},
]);
});
it("keeps Request objects partial when Request is locally shadowed", () => {
expect(
endpoints(`
function test(Request) {
fetch(new Request("/api/shadowed"));
}
`),
).toContainEqual({
kind: "network",
value: 'new Request("/api/shadowed")',
mechanism: "call:fetch",
resolution: "partial",
});
});