fix(signal): normalize accounts during noninteractive setup (#118932)

This commit is contained in:
Peter Steinberger
2026-08-03 13:57:53 -07:00
committed by GitHub
parent be9a34a79d
commit 9e5220cf2c
2 changed files with 77 additions and 1 deletions
+72
View File
@@ -106,6 +106,41 @@ describe("signalSetupAdapter", () => {
expect(detectSignalTransportMock).not.toHaveBeenCalled();
});
it.each([
{
accountId: "default",
signalNumber: " +1 (555) 555-0123 ",
expectedAccount: "+15555550123",
},
{
accountId: "work",
signalNumber: "signal: +1 (555) 555-0124",
expectedAccount: "+15555550124",
},
{ accountId: "default", signalNumber: "15555550125", expectedAccount: "+15555550125" },
{ accountId: "work", signalNumber: "+12345", expectedAccount: "+12345" },
{
accountId: "default",
signalNumber: "+123456789012345",
expectedAccount: "+123456789012345",
},
])(
"stores the canonical Signal number for the $accountId account",
({ accountId, signalNumber, expectedAccount }) => {
const next = signalSetupAdapter.applyAccountConfig?.({
cfg: {},
accountId,
input: { signalNumber },
});
const account =
accountId === "default"
? next?.channels?.signal?.account
: next?.channels?.signal?.accounts?.[accountId]?.account;
expect(account).toBe(expectedAccount);
},
);
it("restores a generically promoted default account before writing a named account", () => {
const next = signalSetupAdapter.applyAccountConfig?.({
cfg: {
@@ -427,6 +462,19 @@ describe("signalSetupAdapter", () => {
expect(next?.channels?.signal?.accounts?.Default).not.toHaveProperty("transport");
});
it.each(["abc", "++12345", "+1+2345", "+1234", "+1234567890123456", " ", ""])(
"rejects invalid Signal account number %s",
(signalNumber) => {
expect(
signalSetupAdapter.validateInput?.({
cfg: {},
accountId: "work",
input: { signalNumber },
}),
).toBe("Invalid E.164 phone number (must start with + and country code, e.g. +15555550123)");
},
);
it.each(["0", "abc", "65536"])("rejects invalid managed HTTP port %s", (httpPort) => {
expect(
signalSetupAdapter.validateInput?.({
@@ -473,6 +521,30 @@ describe("signalSetupAdapter", () => {
).toBe("Signal container transport requires --signal-number or an existing account.");
});
it("rejects an invalid replacement without overwriting an existing container account", () => {
const cfg: OpenClawConfig = {
channels: {
signal: {
account: "+15555550123",
transport: { kind: "container", url: "http://signal-container:8080" },
},
},
};
const input = {
signalNumber: "abc",
signalTransport: "container" as const,
httpUrl: "http://signal-container:8080",
};
expect(signalSetupAdapter.validateInput?.({ cfg, accountId: "default", input })).toBe(
"Invalid E.164 phone number (must start with + and country code, e.g. +15555550123)",
);
expect(
signalSetupAdapter.applyAccountConfig?.({ cfg, accountId: "default", input })?.channels
?.signal?.account,
).toBe("+15555550123");
});
it("allows a container transport to reuse the configured Signal account", () => {
expect(
signalSetupAdapter.validateInput?.({
+5 -1
View File
@@ -134,6 +134,7 @@ function parseSignalAllowFromEntries(raw: string): { entries: string[]; error?:
}
export function buildSignalSetupPatch(input: SignalSetupInput) {
const account = normalizeSignalAccountInput(input.signalNumber);
const transport = input.httpUrl
? {
// Bare --http-url is classified once by prepareAccountConfigInput. Keep the historical
@@ -150,7 +151,7 @@ export function buildSignalSetupPatch(input: SignalSetupInput) {
}
: undefined;
return {
...(input.signalNumber ? { account: input.signalNumber } : {}),
...(account ? { account } : {}),
...(transport ? { transport } : {}),
};
}
@@ -332,6 +333,9 @@ const signalSetupAdapterBase = createPatchedAccountSetupAdapter<SignalSetupInput
return "Signal --http-host must be a hostname or IP address.";
}
}
if (input.signalNumber !== undefined && !normalizeSignalAccountInput(input.signalNumber)) {
return INVALID_SIGNAL_ACCOUNT_ERROR;
}
if (
input.signalTransport === "container" &&
!normalizeSignalAccountInput(input.signalNumber) &&