Skip to content

Commit 9d21df2

Browse files
committed
fix: clear changed gate regressions
1 parent a379ac0 commit 9d21df2

3 files changed

Lines changed: 81 additions & 27 deletions

File tree

extensions/kilocode/provider-models.test.ts

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,27 @@
11
import { describe, expect, it, vi } from "vitest";
22
import { discoverKilocodeModels, KILOCODE_MODELS_URL } from "./provider-models.js";
33

4+
const { fetchWithSsrFGuardMock } = vi.hoisted(() => ({
5+
fetchWithSsrFGuardMock: vi.fn(),
6+
}));
7+
8+
vi.mock("openclaw/plugin-sdk/ssrf-runtime", () => ({
9+
fetchWithSsrFGuard: fetchWithSsrFGuardMock,
10+
}));
11+
12+
type MockKilocodeFetchResponse = {
13+
ok: boolean;
14+
status?: number;
15+
json?: () => Promise<unknown>;
16+
};
17+
18+
type MockKilocodeFetch = ((
19+
url: string,
20+
init?: RequestInit,
21+
) => Promise<MockKilocodeFetchResponse>) & {
22+
mock: { calls: unknown[][] };
23+
};
24+
425
function makeGatewayModel(overrides: Record<string, unknown> = {}) {
526
return {
627
id: "anthropic/claude-sonnet-4",
@@ -51,20 +72,24 @@ function makeAutoModel(overrides: Record<string, unknown> = {}) {
5172
});
5273
}
5374

54-
async function withFetchPathTest(
55-
mockFetch: ReturnType<typeof vi.fn>,
56-
runAssertions: () => Promise<void>,
57-
) {
75+
async function withFetchPathTest(mockFetch: MockKilocodeFetch, runAssertions: () => Promise<void>) {
5876
const origNodeEnv = process.env.NODE_ENV;
5977
const origVitest = process.env.VITEST;
78+
const release = vi.fn(async () => {});
6079
delete process.env.NODE_ENV;
6180
delete process.env.VITEST;
6281

63-
vi.stubGlobal("fetch", mockFetch);
82+
fetchWithSsrFGuardMock.mockImplementation(
83+
async (params: { url: string; init?: RequestInit }) => ({
84+
response: await mockFetch(params.url, params.init),
85+
release,
86+
}),
87+
);
6488

6589
try {
6690
await runAssertions();
6791
} finally {
92+
fetchWithSsrFGuardMock.mockReset();
6893
if (origNodeEnv === undefined) {
6994
delete process.env.NODE_ENV;
7095
} else {
@@ -75,7 +100,6 @@ async function withFetchPathTest(
75100
} else {
76101
process.env.VITEST = origVitest;
77102
}
78-
vi.unstubAllGlobals();
79103
}
80104
}
81105

@@ -111,6 +135,14 @@ describe("discoverKilocodeModels (fetch path)", () => {
111135
await withFetchPathTest(mockFetch, async () => {
112136
const models = await discoverKilocodeModels();
113137

138+
expect(fetchWithSsrFGuardMock).toHaveBeenCalledWith(
139+
expect.objectContaining({
140+
url: KILOCODE_MODELS_URL,
141+
init: expect.objectContaining({
142+
headers: { Accept: "application/json" },
143+
}),
144+
}),
145+
);
114146
expect(mockFetch).toHaveBeenCalledWith(
115147
KILOCODE_MODELS_URL,
116148
expect.objectContaining({

extensions/voice-call/src/providers/twilio/api.test.ts

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,25 @@
11
import { afterEach, describe, expect, it, vi } from "vitest";
22
import { TwilioApiError, twilioApiRequest } from "./api.js";
33

4-
const originalFetch = globalThis.fetch;
4+
const apiMocks = vi.hoisted(() => ({
5+
fetchWithSsrFGuard: vi.fn(),
6+
}));
7+
8+
vi.mock("openclaw/plugin-sdk/ssrf-runtime", () => ({
9+
fetchWithSsrFGuard: apiMocks.fetchWithSsrFGuard,
10+
}));
511

612
describe("twilioApiRequest", () => {
713
afterEach(() => {
8-
globalThis.fetch = originalFetch;
14+
apiMocks.fetchWithSsrFGuard.mockReset();
915
});
1016

1117
it("posts form bodies with basic auth and parses json", async () => {
12-
globalThis.fetch = vi.fn(async () => {
13-
return new Response(JSON.stringify({ sid: "CA123" }), { status: 200 });
14-
}) as unknown as typeof fetch;
18+
const release = vi.fn(async () => {});
19+
apiMocks.fetchWithSsrFGuard.mockResolvedValue({
20+
response: new Response(JSON.stringify({ sid: "CA123" }), { status: 200 }),
21+
release,
22+
});
1523

1624
await expect(
1725
twilioApiRequest({
@@ -26,8 +34,10 @@ describe("twilioApiRequest", () => {
2634
}),
2735
).resolves.toEqual({ sid: "CA123" });
2836

29-
const [url, init] = vi.mocked(globalThis.fetch).mock.calls[0] ?? [];
37+
const [{ url, init, auditContext, policy }] = apiMocks.fetchWithSsrFGuard.mock.calls[0] ?? [];
3038
expect(url).toBe("https://api.twilio.com/Calls.json");
39+
expect(auditContext).toBe("voice-call.twilio.api");
40+
expect(policy).toEqual({ allowedHostnames: ["api.twilio.com"] });
3141
expect(init).toEqual(
3242
expect.objectContaining({
3343
method: "POST",
@@ -44,14 +54,19 @@ describe("twilioApiRequest", () => {
4454
expect(requestBody.toString()).toBe(
4555
"To=%2B14155550123&StatusCallbackEvent=initiated&StatusCallbackEvent=completed",
4656
);
57+
expect(release).toHaveBeenCalledTimes(1);
4758
});
4859

4960
it("passes through URLSearchParams, allows 404s, and returns undefined for empty bodies", async () => {
5061
const responses = [
5162
new Response(null, { status: 204 }),
5263
new Response("missing", { status: 404 }),
5364
];
54-
globalThis.fetch = vi.fn(async () => responses.shift()!) as unknown as typeof fetch;
65+
const release = vi.fn(async () => {});
66+
apiMocks.fetchWithSsrFGuard.mockImplementation(async () => ({
67+
response: responses.shift()!,
68+
release,
69+
}));
5570

5671
await expect(
5772
twilioApiRequest({
@@ -73,12 +88,15 @@ describe("twilioApiRequest", () => {
7388
allowNotFound: true,
7489
}),
7590
).resolves.toBeUndefined();
91+
expect(release).toHaveBeenCalledTimes(2);
7692
});
7793

7894
it("throws twilio api errors for non-ok responses", async () => {
79-
globalThis.fetch = vi.fn(
80-
async () => new Response("bad request", { status: 400 }),
81-
) as unknown as typeof fetch;
95+
const release = vi.fn(async () => {});
96+
apiMocks.fetchWithSsrFGuard.mockResolvedValue({
97+
response: new Response("bad request", { status: 400 }),
98+
release,
99+
});
82100

83101
await expect(
84102
twilioApiRequest({
@@ -89,19 +107,21 @@ describe("twilioApiRequest", () => {
89107
body: {},
90108
}),
91109
).rejects.toThrow("Twilio API error: 400 bad request");
110+
expect(release).toHaveBeenCalledTimes(1);
92111
});
93112

94113
it("exposes structured Twilio error codes from json error bodies", async () => {
95-
globalThis.fetch = vi.fn(
96-
async () =>
97-
new Response(
98-
JSON.stringify({
99-
code: 21220,
100-
message: "Call is not in-progress. Cannot redirect.",
101-
}),
102-
{ status: 400 },
103-
),
104-
) as unknown as typeof fetch;
114+
const release = vi.fn(async () => {});
115+
apiMocks.fetchWithSsrFGuard.mockResolvedValue({
116+
response: new Response(
117+
JSON.stringify({
118+
code: 21220,
119+
message: "Call is not in-progress. Cannot redirect.",
120+
}),
121+
{ status: 400 },
122+
),
123+
release,
124+
});
105125

106126
await expect(
107127
twilioApiRequest({
@@ -117,5 +137,6 @@ describe("twilioApiRequest", () => {
117137
twilioCode: 21220,
118138
message: "Twilio API error: 400 Call is not in-progress. Cannot redirect.",
119139
} satisfies Partial<TwilioApiError>);
140+
expect(release).toHaveBeenCalledTimes(1);
120141
});
121142
});

extensions/voice-call/src/providers/twilio/api.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,9 @@ export async function twilioApiRequest<T = unknown>(params: {
7171
},
7272
body: bodyParams,
7373
},
74+
policy: { allowedHostnames: ["api.twilio.com"] },
7475
timeoutMs: TWILIO_API_TIMEOUT_MS,
75-
auditContext: "voice-call.twilio_api",
76+
auditContext: "voice-call.twilio.api",
7677
});
7778
try {
7879
if (!response.ok) {

0 commit comments

Comments
 (0)