100 lines
3.4 KiB
TypeScript
100 lines
3.4 KiB
TypeScript
/**
|
|
* Client-side fetch helpers the Traffic Inspector uses to drive the TPROXY
|
|
* decrypt capture route (#4211). Pure integration logic — no DOM — so it is
|
|
* unit-testable by stubbing global.fetch: each helper must hit the right
|
|
* method/URL, unwrap the status, and surface the sanitized server error on
|
|
* !res.ok (with an HTTP-status fallback when the body has no message).
|
|
*/
|
|
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
const { fetchTproxyStatus, startTproxyCaptureMode, stopTproxyCaptureMode } = await import(
|
|
"../../src/lib/inspector/tproxyCaptureApi.ts"
|
|
);
|
|
|
|
type FetchCall = { url: string; init?: RequestInit };
|
|
|
|
function stubFetch(handler: (call: FetchCall) => { ok: boolean; status?: number; body: unknown }) {
|
|
const calls: FetchCall[] = [];
|
|
const original = global.fetch;
|
|
global.fetch = (async (url: string, init?: RequestInit) => {
|
|
const call = { url: String(url), init };
|
|
calls.push(call);
|
|
const { ok, status = ok ? 200 : 500, body } = handler(call);
|
|
return { ok, status, json: async () => body } as unknown as Response;
|
|
}) as typeof fetch;
|
|
return {
|
|
calls,
|
|
restore() {
|
|
global.fetch = original;
|
|
},
|
|
};
|
|
}
|
|
|
|
const ROUTE = "/api/tools/agent-bridge/tproxy";
|
|
|
|
test("fetchTproxyStatus GETs the route and returns the status", async () => {
|
|
const status = { running: true, available: true, interceptCount: 3, onPort: 8443 };
|
|
const f = stubFetch(() => ({ ok: true, body: status }));
|
|
try {
|
|
const result = await fetchTproxyStatus();
|
|
assert.equal(result.running, true);
|
|
assert.equal(result.interceptCount, 3);
|
|
assert.equal(f.calls[0].url, ROUTE);
|
|
assert.equal(f.calls[0].init, undefined, "status is a plain GET");
|
|
} finally {
|
|
f.restore();
|
|
}
|
|
});
|
|
|
|
test("startTproxyCaptureMode POSTs options and unwraps the resulting status", async () => {
|
|
const f = stubFetch(() => ({
|
|
ok: true,
|
|
body: { ok: true, status: { running: true, available: true, onPort: 9443 } },
|
|
}));
|
|
try {
|
|
const result = await startTproxyCaptureMode({ onPort: 9443, sudoPassword: "secret" });
|
|
assert.equal(result.running, true);
|
|
assert.equal(result.onPort, 9443);
|
|
assert.equal(f.calls[0].url, ROUTE);
|
|
assert.equal(f.calls[0].init?.method, "POST");
|
|
assert.equal(JSON.parse(String(f.calls[0].init?.body)).onPort, 9443);
|
|
} finally {
|
|
f.restore();
|
|
}
|
|
});
|
|
|
|
test("stopTproxyCaptureMode DELETEs and unwraps the resulting status", async () => {
|
|
const f = stubFetch(() => ({ ok: true, body: { ok: true, status: { running: false, available: true } } }));
|
|
try {
|
|
const result = await stopTproxyCaptureMode();
|
|
assert.equal(result.running, false);
|
|
assert.equal(f.calls[0].url, ROUTE);
|
|
assert.equal(f.calls[0].init?.method, "DELETE");
|
|
} finally {
|
|
f.restore();
|
|
}
|
|
});
|
|
|
|
test("a helper surfaces the sanitized server error message on !res.ok", async () => {
|
|
const f = stubFetch(() => ({
|
|
ok: false,
|
|
status: 500,
|
|
body: { error: { message: "TPROXY capture mode requires the native addon" } },
|
|
}));
|
|
try {
|
|
await assert.rejects(() => startTproxyCaptureMode(), /requires the native addon/);
|
|
} finally {
|
|
f.restore();
|
|
}
|
|
});
|
|
|
|
test("a helper falls back to the HTTP status when the error body has no message", async () => {
|
|
const f = stubFetch(() => ({ ok: false, status: 503, body: null }));
|
|
try {
|
|
await assert.rejects(() => fetchTproxyStatus(), /HTTP 503/);
|
|
} finally {
|
|
f.restore();
|
|
}
|
|
});
|