




















1-import { describe, expect, it, vi } from "vitest";
1+import { afterEach, describe, expect, it, vi } from "vitest";
22import {
33buildTwilioInboundMessage,
44computeTwilioSignature,
@@ -13,6 +13,16 @@ import {
1313} from "./twilio.js";
1414import type { ResolvedSmsAccount } from "./types.js";
151516+const fetchWithSsrFGuardMock = vi.hoisted(() => vi.fn());
17+18+vi.mock("openclaw/plugin-sdk/ssrf-runtime", async (importOriginal) => {
19+const actual = await importOriginal<typeof import("openclaw/plugin-sdk/ssrf-runtime")>();
20+return {
21+ ...actual,
22+fetchWithSsrFGuard: (...args: unknown[]) => fetchWithSsrFGuardMock(...args),
23+};
24+});
25+1626function createAccount(overrides: Partial<ResolvedSmsAccount> = {}): ResolvedSmsAccount {
1727return {
1828accountId: "default",
@@ -43,6 +53,10 @@ function readUrlEncodedRequestBody(init: RequestInit | undefined): URLSearchPara
4353}
44544555describe("Twilio SMS helpers", () => {
56+afterEach(() => {
57+fetchWithSsrFGuardMock.mockReset();
58+});
59+4660it("parses Twilio form bodies and inbound messages", () => {
4761const form = parseTwilioFormBody(
4862"From=%2B15551234567&To=%2B15557654321&Body=hello+there&MessageSid=SM123",
@@ -439,6 +453,33 @@ describe("Twilio SMS helpers", () => {
439453).rejects.toThrow("Twilio SMS send failed (503): upstream unavailable");
440454});
441455456+it("releases guarded Twilio egress on failed send responses", async () => {
457+const release = vi.fn(async () => {});
458+fetchWithSsrFGuardMock.mockResolvedValue({
459+response: new Response("upstream unavailable", { status: 503 }),
460+ release,
461+});
462+463+await expect(
464+sendSmsViaTwilio({
465+account: createAccount(),
466+to: "+15551234567",
467+text: "hello",
468+}),
469+).rejects.toThrow("Twilio SMS send failed (503): upstream unavailable");
470+471+expect(fetchWithSsrFGuardMock).toHaveBeenCalledWith(
472+expect.objectContaining({
473+auditContext: "sms-twilio-api",
474+policy: { allowedHostnames: ["api.twilio.com"] },
475+requireHttps: true,
476+timeoutMs: 30_000,
477+url: "https://api.twilio.com/2010-04-01/Accounts/AC123/Messages.json",
478+}),
479+);
480+expect(release).toHaveBeenCalledTimes(1);
481+});
482+442483it("rejects malformed JSON from successful Twilio sends", async () => {
443484const fetchImpl = vi.fn<typeof fetch>(async () => new Response("not json", { status: 201 }));
444485@@ -452,6 +493,24 @@ describe("Twilio SMS helpers", () => {
452493).rejects.toThrow("Twilio SMS send returned malformed JSON.");
453494});
454495496+it("releases guarded Twilio egress on malformed successful send responses", async () => {
497+const release = vi.fn(async () => {});
498+fetchWithSsrFGuardMock.mockResolvedValue({
499+response: new Response("not json", { status: 201 }),
500+ release,
501+});
502+503+await expect(
504+sendSmsViaTwilio({
505+account: createAccount(),
506+to: "+15551234567",
507+text: "hello",
508+}),
509+).rejects.toThrow("Twilio SMS send returned malformed JSON.");
510+511+expect(release).toHaveBeenCalledTimes(1);
512+});
513+455514it("exposes a typed Twilio SMS API error", () => {
456515const error = new TwilioSmsApiError(
457516429,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。