fix(sdk): block real network in test context, strip query from HttpError message
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a14c4e7747
commit
06a59e1b5c
3 files changed
+32
-2
No files matched your search
@@ -1,10 +1,20 @@
|
|||||||
import type { HttpClient } from "./types.ts";
|
import type { HttpClient } from "./types.ts";
|
||||||
|
|
||||||
|
/** Origin + pathname only: query strings may carry API keys or tokens. */
|
||||||
|
function safeUrl(url: string): string {
|
||||||
|
try {
|
||||||
|
const u = new URL(url);
|
||||||
|
return u.origin + u.pathname;
|
||||||
|
} catch {
|
||||||
|
return url.split(/[?#]/)[0] ?? "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class HttpError extends Error {
|
export class HttpError extends Error {
|
||||||
readonly status: number;
|
readonly status: number;
|
||||||
readonly body: string;
|
readonly body: string;
|
||||||
constructor(status: number, body: string, url = "") {
|
constructor(status: number, body: string, url = "") {
|
||||||
super(`HTTP ${status}${url ? ` for ${url}` : ""}`);
|
super(`HTTP ${status}${url ? ` for ${safeUrl(url)}` : ""}`);
|
||||||
this.name = "HttpError";
|
this.name = "HttpError";
|
||||||
this.status = status;
|
this.status = status;
|
||||||
this.body = body;
|
this.body = body;
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ export function createFixtureHttp(routes: Record<string, Route>): { fetch: typeo
|
|||||||
return { fetch: fn as typeof fetch, calls };
|
return { fetch: fn as typeof fetch, calls };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const noFetch = (async () => {
|
||||||
|
throw new Error("createTestContext: no fetch provided");
|
||||||
|
}) as unknown as typeof fetch;
|
||||||
|
|
||||||
export function createTestContext(
|
export function createTestContext(
|
||||||
partial: Partial<PluginContext> & { fetch?: typeof fetch } = {},
|
partial: Partial<PluginContext> & { fetch?: typeof fetch } = {},
|
||||||
): PluginContext & { saved: { credentials: Record<string, string>[]; cursor: string[] } } {
|
): PluginContext & { saved: { credentials: Record<string, string>[]; cursor: string[] } } {
|
||||||
@@ -29,7 +33,7 @@ export function createTestContext(
|
|||||||
let creds: Record<string, string> | null = null;
|
let creds: Record<string, string> | null = null;
|
||||||
let cursor: string | null = null;
|
let cursor: string | null = null;
|
||||||
const ctx: PluginContext = {
|
const ctx: PluginContext = {
|
||||||
http: createHttpClient({ userAgent: "sttracker-test/0", fetchImpl, sleep: async () => {} }),
|
http: createHttpClient({ userAgent: "sttracker-test/0", fetchImpl: fetchImpl ?? noFetch, sleep: async () => {} }),
|
||||||
log: () => {},
|
log: () => {},
|
||||||
config: {},
|
config: {},
|
||||||
credentials: {
|
credentials: {
|
||||||
|
|||||||
@@ -104,6 +104,22 @@ describe("http client", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("safety", () => {
|
||||||
|
test("HttpError message does not leak the query string", async () => {
|
||||||
|
const { fetchImpl } = seq(res(404, {}));
|
||||||
|
const http = createHttpClient({ userAgent: "t/1", fetchImpl, sleep: async () => {} });
|
||||||
|
const err = (await http.json("https://x.test/a?api_key=SECRET").catch((e) => e)) as HttpError;
|
||||||
|
expect(err).toBeInstanceOf(HttpError);
|
||||||
|
expect(err.message).not.toContain("SECRET");
|
||||||
|
expect(err.message).toContain("https://x.test/a");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("createTestContext without fetch never hits the network", async () => {
|
||||||
|
const ctx = createTestContext();
|
||||||
|
await expect(ctx.http.json("https://x.test/a")).rejects.toThrow("createTestContext: no fetch provided");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("testing harness", () => {
|
describe("testing harness", () => {
|
||||||
test("fixture routes match by full url or url without query; unknown throws", async () => {
|
test("fixture routes match by full url or url without query; unknown throws", async () => {
|
||||||
const fx = createFixtureHttp({
|
const fx = createFixtureHttp({
|
||||||
|
|||||||
Reference in new issue
Block a user