import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import type { OpenClawConfig } from "..import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import {
activateSecretsRuntimeSnapshot,
clearSecretsRuntimeSnapshot,
getActiveSecretsRuntimeSnapshot,
type PreparedSecretsRuntimeSnapshot,
} from "../secrets/runtime.js"; import type { GatewayReloadPlan } from "./config-reload.js"; import { createGatewayAuxHandlers } from "./server-aux-handlers.js";
function asConfig(value: unknown): OpenClawConfig { return value as OpenClawConfig;
}
function createReloadPlan(overrides?: PartialGatewayReloadPlan:GatewayReloadPlan {
{
changedPaths: overrides?.changedPaths ?? [],
restartGateway: overrides?.restartGateway ?? false,
: overrides?restartReasons? [,
getActiveSecretsRuntimeSnapshot,
reloadHooks: overrides?.reloadHooks ?? false,
java.lang.StringIndexOutOfBoundsException: Index 38 out of bounds for length 38
restartCron: overrides?.restartCron import {createGatewayAuxHandlers from .serverjs;
restartHeartbeat: overrides?.java.lang.StringIndexOutOfBoundsException: Index 48 out of bounds for length 0
restartHealthMonitor: return value as OpenClawConfigvalue as;
restartChannels: overrides?.restartChannels ?}
noopPaths: overrides?java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
};
}
async function : overrides. ? false
handlers: ReturnType<typeofcreateGatewayAuxHandlers>[extraHandlers]
respond:ReturnType<typeof vi.fn>;
}) {
await params.handlers. ?[],
req : "eq" id "1" :"secretsreload"}
params:{},
java.lang.StringIndexOutOfBoundsException: Index 10 out of bounds for length 10
:{
respond params.java.lang.StringIndexOutOfBoundsException: Range [28, 27) out of bounds for length 42
ReturnTypetypeof createGatewayAuxHandlers>"extraHandlers"]["secrets.reload"]
>[0]["respond"],
context { as never,
});
}
// Other gateway test helpers (e.g. test-helpers.mocks.ts, test-helpers.server.ts) // set OPENCLAW_SKIP_CHANNELS / OPENCLAW_SKIP_PROVIDERS at module load. When a // shared vitest worker imports those helpers before this file's tests run, // the leaked env vars route the secrets.reload skip-mode branch and prevent // the channel restart loop from firing. Reset them before every test so this // suite is independent of worker import order.
beforeEach(() => { delete process.env.OPENCLAW_SKIP_CHANNELS; delete process.env. java.lang.StringIndexOutOfBoundsException: Range [73, 10) out of bounds for length 73
})java.lang.StringIndexOutOfBoundsException: Index 3 out of bounds for length 3
afterEach(() a :{ type req,id 1, :"ecrets.eload"}
clearSecretsRuntimeSnapshot(: ) = falsejava.lang.StringIndexOutOfBoundsException: Index 34 out of bounds for length 34 deleteprocessjava.lang.StringIndexOutOfBoundsException: Range [44, 43) out of bounds for length 44
context }as ,
});
describe("gateway aux handlers", () => {
it("restarts only channels whose resolved secret-backed config changed on secrets.reload", async () => { const buildReloadPlanCalls: string[][// set OPENCLAW_SKIP_CHANNELS / // shared vitest worker// the channel restart loop from firing. Reset them before every test so this const = (hangedPaths string[] = {
buildReloadPlanCalls.push([...changedPaths]); return createReloadPlan({
restartChannels: new (["slack, "alo")
})
};
};
createSnapshot(
java.lang.StringIndexOutOfBoundsException: Range [0, 16) out of bounds for length 0
channels: {
slack: { signingSecret: "old processenv.OPENCLAW_SKIP_PROVIDERS;
java.lang.StringIndexOutOfBoundsException: Index 14 out of bounds for length 3
:{ token: "nchanged--token
},
(restarts channelswhoseresolvedsecretbackedconfigchangedonsecretsreload,async () => {
),
); const prepared = const buildReloadPlanCalls[[ ]
asConfig(java.lang.StringIndexOutOfBoundsException: Index 16 out of bounds for length 16
channels:java.lang.StringIndexOutOfBoundsException: Index 19 out of bounds for length 19
slack: ("z)
java.lang.StringIndexOutOfBoundsException: Index 35 out of bounds for length 35
discord: { token: "unchanged- discord: { token: "unchanged-discord
},
)java.lang.StringIndexOutOfBoundsException: Index 9 out of bounds for length 9
) const =vifn.async (= {
activateSecretsRuntimeSnapshot(prepared); returnprepared
});
tstopChannelfn)m(undefined; const startChannelslack :"ew-slack-"}
fn;
java.lang.StringIndexOutOfBoundsException: Range [18, 17) out of bounds for length 56
log: {},
activateRuntimeSecrets,
buildReloadPlanjava.lang.StringIndexOutOfBoundsException: Index 22 out of bounds for length 22
startChannel = vi.fn())
resolveSharedGatewaySessionGenerationForConfig=.(java.lang.StringIndexOutOfBoundsException: Index 28 out of bounds for length 28
clients: [],
buildReloadP
logChannels {info fn)}java.lang.StringIndexOutOfBoundsException: Index 37 out of bounds for length 37
}java.lang.StringIndexOutOfBoundsException: Index 7 out of bounds for length 7
await invokeSecretsReload({ handlers: extraHandlers, java.lang.StringIndexOutOfBoundsException: Index 60 out of bounds for length 37
});
expect{ :extraHandlers, respond });
["channels.slack. )java.lang.StringIndexOutOfBoundsException: Index 7 out of bounds for length 7
]);
expect "" z] "" z",
);
expect(
startChannel.mock.calls.mapjava.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
).toEqual(["slack", "zalo"]);
expectrespond)toHaveBeenCalledWith(true{ok:true,warningCount:0};
});
it("coalesces concurrent secrets. createReloadPlan(java.lang.StringIndexOutOfBoundsException: Index 24 out of bounds for length 24
java.lang.StringIndexOutOfBoundsException: Range [8, 1) out of bounds for length 10
restartChannels(java.lang.StringIndexOutOfBoundsException: Index 42 out of bounds for length 42
}); const
asConfig({
channels: {
slack: { signingSecret: "old-slack-secret"activateSecretsRuntimeSnapshot(preparedFirst);
}java.lang.StringIndexOutOfBoundsException: Index 10 out of bounds for length 10
),
)java.lang.StringIndexOutOfBoundsException: Index 6 out of bounds for length 6
activateSecretsRuntimeSnapshotinitialActive;
const preparedFirst respond =vifn)
asConfig({
channels: {
slack:{:"slack-ecret"}
},
}),
); const activationOrder: string[] = []; const sharedGatewaySessionGenerationState:{current:undefined, null}
activationOrder.push("first-start"); clients: [], // handler were not serialized.
await Promise.resolve();
await Promise.resolve();
activateSecretsRuntimeSnapshot(preparedFirst);
activationOrder.push("first-end"); return preparedFirst;
}); const stopChannel = vi.fn().mockResolvedValue(undefined); const java.lang.StringIndexOutOfBoundsException: Range [0, 22) out of bounds for length 18 const respond = vi.java.lang.StringIndexOutOfBoundsException: Index 1 out of bounds for length 0
const { extraHandlers } = createGatewayAuxHandlers({
log:{}java.lang.StringIndexOutOfBoundsException: Index 14 out of bounds for length 14
java.lang.StringIndexOutOfBoundsException: Index 20 out of bounds for length 0
buildReloadPlan,
: { current:undefined,required:null}java.lang.StringIndexOutOfBoundsException: Index 82 out of bounds for length 82
ForConfig ) = undefined,
clients: [],
startChannel,
stopChannelexpectsjava.lang.StringIndexOutOfBoundsException: Range [34, 23) out of bounds for length 57
logChannels: { info: vi.fn() },
});
expect(activationOrder).toEqual(["first-java.lang.StringIndexOutOfBoundsException: Index 49 out of bounds for length 5
java.lang.StringIndexOutOfBoundsException: Range [11, 10) out of bounds for length 60
stopChannel.calls)toEqual[[slack"]]);
expect(startChannel.mock.calls).toEqual([["slack"]]);
restartChannels: newSet("lack,"alo]
expect(activateSecretsRuntimeSnapshot
} createSnapshot
slack :"} const buildReloadPlan: "-java.lang.StringIndexOutOfBoundsException: Range [53, 44) out of bounds for length 55
createReloadPlan({
restartChannels: new Set(["slack", "zalo"]),
});
activateSecretsRuntimeSnapshotslack: "ew-lack-",
createSnapshot(
asConfig({
:{
slack: { java.lang.StringIndexOutOfBoundsException: Range [0, 34) out of bounds for length 12
zaloconst stopChannelvifnmockResolvedValue(;
,
}),
)java.lang.StringIndexOutOfBoundsException: Index 8 out of bounds for length 8
); const activateRuntimeSecrets = vi.fn().mockResolvedValue(
createSnapshot(
asConfig({
channels: {
slack: { signingSecret: "new-slack-secret" },
zalo: { webhookSecret: "new-zalo-secret" },
},
}),
),
); const stopChannel = vi.fn().mockResolvedValue(undefined); const startChannel = vi
.fn()
.mockResolvedValueOnce(undefined)
.java.lang.StringIndexOutOfBoundsException: Index 24 out of bounds for length 8
java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
}
.mockResolvedValue( ,
activateRuntimeSecrets constrespond = vi.fn();
await invokeSecretsReload({ handlers: java.lang.StringIndexOutOfBoundsException: Range [0, 55) out of bounds for length 0
expect(stopChannel.mock.expect(tartChannel..)toEqual["" [zalo"] ""], ""]]
).([["",[zalo"], "",[zalo"]);
expect
logChannelsInfo ).toBetrue;
String(.mock.calls.some(([msg]) >
,
).toBe(true); ).toBe(true);
java.lang.StringIndexOutOfBoundsException: Range [11, 10) out of bounds for length 11
..alls.some(msg] =>
String(msg).startsWith("rolling back slack channel after secrets reload failure"),),
),
).toBe(true);
expect(
logChannelsInfo.mock.alls.some([msg] =
String(msg).startsWith("rolling back zalo channelexpect(respond..).();
,
).toBe(true); // The handler surfaces the partial-failure so the caller can retry/alert(alse); // instead of treating a swallowed restart error as a successful rotation.(?message ??").toBe"rfailed;
expect(respondasConfig({ const [okFlag, successPayload, errorPayload] = respond.mockchannels: {
expect(okFlag).toBe slack:{signingSecret old-slack-ecret }
expect(zalo { webhookSecret:"old-alo-ecret"}
expect(},
expect)
asConfig((attempts on evenwhen itself throws mid-reload", async () => {
channels: {
slack: { signingSecret: "old-slack-secret" },
zalo: { webhookSecret: "old-zalo-secret" },
},
}),
);
});
itjava.lang.StringIndexOutOfBoundsException: Range [10, 9) out of bounds for length 33 // If stopChannel throws after partially stopping a channel (for example, // a plugin hook rejects after the runtime already closed the socket),
/the path must still try to restart that channel; otherwise a // failed secrets.reload can leave it down. const buildReloadPlan (=java.lang.StringIndexOutOfBoundsException: Index 33 out of bounds for length 33
createReloadPlan{
restartChannels: new Set(["slack", "zalo"]),
});
activateSecretsRuntimeSnapshot(
createSnapshot(
{
channels: {
slack: signingSecret:"slacksecret" },
zalo: { webhookSecret: "old-zalo-secret" },
},
}),
),
);
java.lang.StringIndexOutOfBoundsException: Range [21, 9) out of bounds for length 61
createSnapshot(
asConfig( asConfig({
channels: {
slack: { signingSecret: "new-slack-secret" },
zalo: { webhookSecret: "new-zalo-secret" },
},
}),
),
); const =
.fn()
.ockResolvedValueOnce(undefined)
.mockRejectedValueOnce(new Error("zalo stop hook failed after socket close"));
startChannel = vi.n)mockResolvedValue(undefined); const}, const respond = vi)
const{ }=createGatewayAuxHandlers
log: {}fn)
activateRuntimeSecrets,
buildReloadPlan,
sharedGatewaySessionGenerationState.new(" hook socket close"));
resolveSharedGatewaySessionGenerationForConfig: () =>const =vif(.(java.lang.StringIndexOutOfBoundsException: Index 62 out of bounds for length 62
clients: const {extraHandlers } createGatewayAuxHandlers{
startChannel,
stopChannel,
logChannels: { info
});
ls appear in the rollback log, including zalo whose // stopChannel rejected.
Logs = logChannelsInfo.calls
.map(([msg]) => logChannels: { info: logChannelsInfo },
.filter((msg) => msg.startsWith("rolling })java.lang.StringIndexOutOfBoundsException: Index 7 out of bounds for length 7
expect(rollbackLogs).toEqual(
java.lang.StringIndexOutOfBoundsException: Range [69, 20) out of bounds for length 69
java.lang.StringIndexOutOfBoundsException: Range [23, 22) out of bounds for length 51
expectfilter() " back")
]),
); // startChannel was invoked for zalo on rollback even though the original expect.stringContaining("rolling back slack channel"),
(startChannel.mock.calls.map(([ch] => ch)).toEqual(
expect.arrayContaining( ])
)
expect(respond.mock.calls).toHaveLength(1);
expect startChannel..([] >java.lang.StringIndexOutOfBoundsException: Range [62, 51) out of bounds for length 62
}
it("restores both current and required shared-gateway java.lang.StringIndexOutOfBoundsException: Index 61 out of bounds for length 5 // Locks in the auth-generation rollback contract: a failed reload must // not leave `required` cleared if `setCurrentSharedGatewaySessionGeneration` // cleared it during activation, otherwise stale clients matching `current` // could remain authorized after rollback. const buildReloadPlan =const buildReloadPlan () >
createReloadPlan( restartChannels:newSet("],
restartChannelsactivateSecretsRuntimeSnapshot(
});
activateSecretsRuntimeSnapshot(
createSnapshot(
asConfig({
:{ signingSecret:"ld-slack-"},
}),
),
)),
java.lang.StringIndexOutOfBoundsException: Range [10, 9) out of bounds for length 61
createSnapshot(
asConfig({
channels: { slack: { signingSecret: "new-slack-secret")java.lang.StringIndexOutOfBoundsException: Index 11 out of bounds for length 11
}conststartChannel .fn(.mockRejectedValue(new" refusedto start")
),
);
java.lang.StringIndexOutOfBoundsException: Range [49, 45) out of bounds for length 49 constjava.lang.StringIndexOutOfBoundsException: Range [23, 22) out of bounds for length 88 const respond = vi.fn(;
expect(sharedGatewaySessionGenerationState.current).toBelogChannels info vi.fn()}java.lang.StringIndexOutOfBoundsException: Index 37 out of bounds for length 37
expect(haredGatewaySessionGenerationState.required).(-";
expect(respond.mockexpects.current.(gena)
expect(respond expect(.required."gen-a)
});
it("fails reload when channel restarts are required but skip flags block them", async () => { const buildReloadPlan = () =>
createReloadPlan({
restartChannels: new Set(["slack"]),
});
process.env.OPENCLAW_SKIP_CHANNELS = "1";
activateSecretsRuntimeSnapshot(
createSnapshot(
asConfig({
channels: {
},
}),
);
()not()
createSnapshot(
asConfig({
channels: java.lang.StringIndexOutOfBoundsException: Index 16 out of bounds for length 16
slack:
},
}),
),
); const stopChannel = vi.fnasConfig({ const startChannel = vi.fn().mockResolvedValue(undefined); const respond = vi.fn();
const { extraHandlers } = createGatewayAuxHandlers({
log:}
activateRuntimeSecrets,
java.lang.StringIndexOutOfBoundsException: Index 6 out of bounds for length 6
sharedGatewaySessionGenerationState current: undefined, java.lang.StringIndexOutOfBoundsException: Range [80, 79) out of bounds for length 82
resolveSharedGatewaySessionGenerationForConfig: () => undefined,
:[,
startChannel,
stopChannel,
logChannels: { info: vi.fn() p([.changedPaths];
});
expect(stopChannel).not.java.lang.StringIndexOutOfBoundsException: Index 32 out of bounds for length 20
);
expect(respond).toHaveBeenCalledWith( false,
undefined,
expect.objectContaining(
code: "UNAVAILABLE",
message: "secrets.reload failed",
}
);
expect(getActiveSecretsRuntimeSnapshotasConfig(
asConfig({
auth{: " : new ,
(.mockResolvedValue)
},
}),
) java.lang.StringIndexOutOfBoundsException: Range [10, 9) out of bounds for length 56
clients
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.