mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
fix(ci): repair sms channel checks
This commit is contained in:
@@ -29,7 +29,7 @@ function parseList(raw: string | string[] | undefined): string[] {
|
||||
return [];
|
||||
}
|
||||
return (Array.isArray(raw) ? raw : normalizeStringEntries(raw.split(",")))
|
||||
.map((entry) => normalizeSmsAllowFrom(String(entry)))
|
||||
.map((entry) => normalizeSmsAllowFrom(entry))
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
@@ -83,17 +83,21 @@ export function resolveSmsAccount(
|
||||
): ResolvedSmsAccount {
|
||||
const channelCfg = getChannelConfig(cfg) ?? {};
|
||||
const id = normalizeOptionalAccountId(accountId) ?? resolveDefaultSmsAccountId(cfg);
|
||||
const accountConfig = resolveAccountEntry(
|
||||
channelCfg.accounts as
|
||||
| Record<string, Partial<Record<string, unknown> & SmsChannelConfig>>
|
||||
| undefined,
|
||||
id,
|
||||
);
|
||||
const accountConfig = resolveAccountEntry(channelCfg.accounts, id);
|
||||
const channelConfig: Record<string, unknown> & SmsChannelConfig = { ...channelCfg };
|
||||
const accountEntries:
|
||||
| Record<string, Partial<Record<string, unknown> & SmsChannelConfig>>
|
||||
| undefined = channelCfg.accounts
|
||||
? Object.fromEntries(
|
||||
Object.entries(channelCfg.accounts).map(([accountKey, account]) => [
|
||||
accountKey,
|
||||
{ ...account },
|
||||
]),
|
||||
)
|
||||
: undefined;
|
||||
const merged = resolveMergedAccountConfig<Record<string, unknown> & SmsChannelConfig>({
|
||||
channelConfig: channelCfg as Record<string, unknown> & SmsChannelConfig,
|
||||
accounts: channelCfg.accounts as
|
||||
| Record<string, Partial<Record<string, unknown> & SmsChannelConfig>>
|
||||
| undefined,
|
||||
channelConfig,
|
||||
accounts: accountEntries,
|
||||
accountId: id,
|
||||
omitKeys: ["defaultAccount"],
|
||||
});
|
||||
@@ -115,8 +119,8 @@ export function resolveSmsAccount(
|
||||
? process.env.SMS_DANGEROUSLY_DISABLE_SIGNATURE_VALIDATION
|
||||
: undefined;
|
||||
|
||||
const webhookPath = String(merged.webhookPath ?? envWebhookPath ?? DEFAULT_WEBHOOK_PATH).trim();
|
||||
const publicWebhookUrl = String(merged.publicWebhookUrl ?? envPublicWebhookUrl ?? "").trim();
|
||||
const webhookPath = (merged.webhookPath ?? envWebhookPath ?? DEFAULT_WEBHOOK_PATH).trim();
|
||||
const publicWebhookUrl = (merged.publicWebhookUrl ?? envPublicWebhookUrl ?? "").trim();
|
||||
const authToken =
|
||||
normalizeResolvedSecretInputString({
|
||||
value: merged.authToken ?? envAuthToken,
|
||||
@@ -128,11 +132,11 @@ export function resolveSmsAccount(
|
||||
return {
|
||||
accountId: id,
|
||||
enabled: channelCfg.enabled !== false && accountConfig?.enabled !== false,
|
||||
accountSid: String(merged.accountSid ?? envAccountSid ?? "").trim(),
|
||||
accountSid: (merged.accountSid ?? envAccountSid ?? "").trim(),
|
||||
authToken,
|
||||
fromNumber: normalizeSmsPhoneNumber(String(merged.fromNumber ?? envFromNumber ?? "")),
|
||||
messagingServiceSid: String(merged.messagingServiceSid ?? envMessagingServiceSid ?? "").trim(),
|
||||
defaultTo: normalizeSmsPhoneNumber(String(merged.defaultTo ?? "")),
|
||||
fromNumber: normalizeSmsPhoneNumber(merged.fromNumber ?? envFromNumber ?? ""),
|
||||
messagingServiceSid: (merged.messagingServiceSid ?? envMessagingServiceSid ?? "").trim(),
|
||||
defaultTo: normalizeSmsPhoneNumber(merged.defaultTo ?? ""),
|
||||
webhookPath: webhookPath || DEFAULT_WEBHOOK_PATH,
|
||||
publicWebhookUrl,
|
||||
dangerouslyDisableSignatureValidation:
|
||||
|
||||
@@ -103,15 +103,15 @@ function applySmsAccountConfig(params: {
|
||||
input: Record<string, unknown>;
|
||||
}): OpenClawConfig {
|
||||
const patch = smsSetupPatch(params.input);
|
||||
const channels = { ...(params.cfg.channels ?? {}) };
|
||||
const current = { ...((channels[CHANNEL_ID] as Record<string, unknown> | undefined) ?? {}) };
|
||||
const channels = { ...params.cfg.channels };
|
||||
const current = { ...(channels[CHANNEL_ID] as Record<string, unknown> | undefined) };
|
||||
if (params.accountId === DEFAULT_ACCOUNT_ID) {
|
||||
channels[CHANNEL_ID] = { ...current, ...patch };
|
||||
return { ...params.cfg, channels };
|
||||
}
|
||||
const accounts = { ...((current.accounts as Record<string, unknown> | undefined) ?? {}) };
|
||||
const accounts = { ...(current.accounts as Record<string, unknown> | undefined) };
|
||||
accounts[params.accountId] = {
|
||||
...((accounts[params.accountId] as Record<string, unknown> | undefined) ?? {}),
|
||||
...(accounts[params.accountId] as Record<string, unknown> | undefined),
|
||||
...patch,
|
||||
};
|
||||
channels[CHANNEL_ID] = { ...current, accounts };
|
||||
@@ -254,6 +254,12 @@ export const smsPlugin: ChannelPlugin<ResolvedSmsAccount> = createChatChannelPlu
|
||||
},
|
||||
},
|
||||
status: {
|
||||
buildAccountSnapshot: ({ account }) => ({
|
||||
accountId: account.accountId,
|
||||
name: account.fromNumber || account.messagingServiceSid || "SMS",
|
||||
configured: isSmsAccountConfigured(account),
|
||||
enabled: account.enabled,
|
||||
}),
|
||||
buildCapabilitiesDiagnostics: async ({ account }) => ({
|
||||
lines: collectSmsStartupWarnings(account).map((text) => ({ text, tone: "warn" })),
|
||||
}),
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { ResolvedSmsAccount } from "./types.js";
|
||||
const registerPluginHttpRoute = vi.hoisted(() => vi.fn(() => vi.fn()));
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/webhook-ingress", async (importOriginal) => ({
|
||||
...((await importOriginal()) as Record<string, unknown>),
|
||||
...(await importOriginal<typeof import("openclaw/plugin-sdk/webhook-ingress")>()),
|
||||
registerPluginHttpRoute,
|
||||
}));
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { dispatchSmsInboundEvent, type SmsChannelRuntime } from "./inbound.js";
|
||||
import type { sendSmsViaTwilio as sendSmsViaTwilioType } from "./twilio.js";
|
||||
import type { ResolvedSmsAccount } from "./types.js";
|
||||
|
||||
const sendSmsViaTwilio = vi.hoisted(() =>
|
||||
vi.fn(async () => ({ sid: "SM-pair", to: "+15551234567" })),
|
||||
vi.fn<typeof sendSmsViaTwilioType>(async () => ({ sid: "SM-pair", to: "+15551234567" })),
|
||||
);
|
||||
|
||||
vi.mock("./twilio.js", () => ({
|
||||
|
||||
@@ -29,6 +29,16 @@ function createAccount(overrides: Partial<ResolvedSmsAccount> = {}): ResolvedSms
|
||||
};
|
||||
}
|
||||
|
||||
function readUrlEncodedRequestBody(init: RequestInit | undefined): URLSearchParams {
|
||||
if (typeof init?.body === "string") {
|
||||
return new URLSearchParams(init.body);
|
||||
}
|
||||
if (init?.body instanceof URLSearchParams) {
|
||||
return init.body;
|
||||
}
|
||||
throw new Error("Expected Twilio request body to be URL-encoded.");
|
||||
}
|
||||
|
||||
describe("Twilio SMS helpers", () => {
|
||||
it("parses Twilio form bodies and inbound messages", () => {
|
||||
const form = parseTwilioFormBody(
|
||||
@@ -105,7 +115,7 @@ describe("Twilio SMS helpers", () => {
|
||||
});
|
||||
|
||||
it("sends SMS through Twilio's Messages API", async () => {
|
||||
const fetchImpl = vi.fn(
|
||||
const fetchImpl = vi.fn<typeof fetch>(
|
||||
async () =>
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
@@ -156,14 +166,14 @@ describe("Twilio SMS helpers", () => {
|
||||
authorization: `Basic ${Buffer.from("AC123:secret").toString("base64")}`,
|
||||
"content-type": "application/x-www-form-urlencoded",
|
||||
});
|
||||
const body = new URLSearchParams(String(init?.body));
|
||||
const body = readUrlEncodedRequestBody(init);
|
||||
expect(body.get("From")).toBe("+15557654321");
|
||||
expect(body.get("To")).toBe("+15551234567");
|
||||
expect(body.get("Body")).toBe("hello");
|
||||
});
|
||||
|
||||
it("can send through a Twilio Messaging Service SID", async () => {
|
||||
const fetchImpl = vi.fn(
|
||||
const fetchImpl = vi.fn<typeof fetch>(
|
||||
async () =>
|
||||
new Response(JSON.stringify({ sid: "SM789" }), {
|
||||
status: 201,
|
||||
@@ -193,14 +203,14 @@ describe("Twilio SMS helpers", () => {
|
||||
});
|
||||
|
||||
const [, init] = fetchImpl.mock.calls[0] ?? [];
|
||||
const body = new URLSearchParams(String(init?.body));
|
||||
const body = readUrlEncodedRequestBody(init);
|
||||
expect(body.get("MessagingServiceSid")).toBe("MG123");
|
||||
expect(body.get("To")).toBe("+15551234567");
|
||||
expect(body.get("Body")).toBe("hello");
|
||||
});
|
||||
|
||||
it("prefers an explicit from number when both sender options are resolved", async () => {
|
||||
const fetchImpl = vi.fn(
|
||||
const fetchImpl = vi.fn<typeof fetch>(
|
||||
async () =>
|
||||
new Response(JSON.stringify({ sid: "SM999" }), {
|
||||
status: 201,
|
||||
@@ -230,7 +240,7 @@ describe("Twilio SMS helpers", () => {
|
||||
});
|
||||
|
||||
const [, init] = fetchImpl.mock.calls[0] ?? [];
|
||||
const body = new URLSearchParams(String(init?.body));
|
||||
const body = readUrlEncodedRequestBody(init);
|
||||
expect(body.get("From")).toBe("+15557654321");
|
||||
expect(body.get("MessagingServiceSid")).toBeNull();
|
||||
});
|
||||
|
||||
@@ -76,7 +76,7 @@ function parseTwilioSuccessPayload(text: string): TwilioMessagePayload {
|
||||
if (err instanceof Error && err.message === "Twilio SMS send returned malformed JSON.") {
|
||||
throw err;
|
||||
}
|
||||
throw new Error("Twilio SMS send returned malformed JSON.");
|
||||
throw new Error("Twilio SMS send returned malformed JSON.", { cause: err });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ export function computeTwilioSignature(params: {
|
||||
const data =
|
||||
params.url +
|
||||
Object.keys(params.form)
|
||||
.sort()
|
||||
.toSorted()
|
||||
.map((key) => `${key}${params.form[key] ?? ""}`)
|
||||
.join("");
|
||||
return createHmac("sha1", params.authToken).update(data).digest("base64");
|
||||
|
||||
@@ -4,5 +4,13 @@
|
||||
"rootDir": "."
|
||||
},
|
||||
"include": ["./*.ts", "./src/**/*.ts"],
|
||||
"exclude": ["./**/*.test.ts", "./dist/**", "./node_modules/**"]
|
||||
"exclude": [
|
||||
"./**/*.test.ts",
|
||||
"./dist/**",
|
||||
"./node_modules/**",
|
||||
"./src/test-support/**",
|
||||
"./src/**/*test-helpers.ts",
|
||||
"./src/**/*test-harness.ts",
|
||||
"./src/**/*test-support.ts"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -357,7 +357,7 @@ describe("gateway server agent", () => {
|
||||
const res = await rpcReq(ws, "agent", {
|
||||
message: "hi",
|
||||
sessionKey: "main",
|
||||
channel: "sms",
|
||||
channel: "missing-channel",
|
||||
idempotencyKey: "idem-agent-bad-channel",
|
||||
});
|
||||
expect(res.ok).toBe(false);
|
||||
|
||||
Reference in New Issue
Block a user