mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
refactor(gateway): consolidate chat voice and reload test fixtures (#114420)
This commit is contained in:
committed by
GitHub
parent
13bbcabeab
commit
4029070c3d
@@ -598,6 +598,10 @@ function createWatcherMock(effectiveUsePolling?: boolean) {
|
||||
return watcher;
|
||||
}
|
||||
|
||||
function makeGatewayPortConfig(port: number): OpenClawConfig {
|
||||
return { gateway: { reload: {}, port } };
|
||||
}
|
||||
|
||||
function makeSnapshot(partial: Partial<ConfigFileSnapshot> = {}): ConfigFileSnapshot {
|
||||
const config = partial.config ?? {};
|
||||
const sourceConfig = (partial.sourceConfig ??
|
||||
@@ -827,6 +831,11 @@ function createReloaderHarness(
|
||||
|
||||
type ReloaderHarness = ReturnType<typeof createReloaderHarness>;
|
||||
|
||||
async function flushWatcherChange(harness: ReloaderHarness) {
|
||||
harness.watcher.emit("change");
|
||||
await vi.runAllTimersAsync();
|
||||
}
|
||||
|
||||
function getOnlyRestartCall(harness: ReloaderHarness): [GatewayReloadPlan, OpenClawConfig] {
|
||||
expect(harness.onRestart).toHaveBeenCalledTimes(1);
|
||||
const call = harness.onRestart.mock.calls[0];
|
||||
@@ -976,12 +985,8 @@ describe("startGatewayConfigReloader", () => {
|
||||
});
|
||||
|
||||
it("watches resolved includes and reconciles them after an accepted reload", async () => {
|
||||
const initialConfig: OpenClawConfig = {
|
||||
gateway: { reload: {}, port: 18789 },
|
||||
};
|
||||
const nextConfig: OpenClawConfig = {
|
||||
gateway: { reload: {}, port: 18790 },
|
||||
};
|
||||
const initialConfig = makeGatewayPortConfig(18789);
|
||||
const nextConfig = makeGatewayPortConfig(18790);
|
||||
const initialIncludePath = "/tmp/initial.json5";
|
||||
const retainedIncludePath = "/tmp/retained.json5";
|
||||
const addedIncludePath = "/tmp/added.json5";
|
||||
@@ -1005,8 +1010,7 @@ describe("startGatewayConfigReloader", () => {
|
||||
expect.objectContaining({ ignoreInitial: true }),
|
||||
);
|
||||
|
||||
harness.watcher.emit("change");
|
||||
await vi.runAllTimersAsync();
|
||||
await flushWatcherChange(harness);
|
||||
|
||||
// Candidate discovery adds the new include before acceptance; acceptance
|
||||
// then retires the old include in a second readiness-reconciled watcher.
|
||||
@@ -1044,16 +1048,14 @@ describe("startGatewayConfigReloader", () => {
|
||||
initialIncludedPaths: [acceptedIncludePath],
|
||||
});
|
||||
|
||||
harness.watcher.emit("change");
|
||||
await vi.runAllTimersAsync();
|
||||
await flushWatcherChange(harness);
|
||||
expect(harness.watcher.close).toHaveBeenCalledOnce();
|
||||
expect(chokidar.watch).toHaveBeenLastCalledWith(
|
||||
["/tmp/openclaw.json", acceptedIncludePath, firstCandidatePath],
|
||||
expect.objectContaining({ ignoreInitial: true }),
|
||||
);
|
||||
|
||||
harness.watcher.emit("change");
|
||||
await vi.runAllTimersAsync();
|
||||
await flushWatcherChange(harness);
|
||||
expect(harness.watcher.close).toHaveBeenCalledTimes(2);
|
||||
expect(chokidar.watch).toHaveBeenLastCalledWith(
|
||||
["/tmp/openclaw.json", acceptedIncludePath, secondCandidatePath],
|
||||
@@ -1092,19 +1094,14 @@ describe("startGatewayConfigReloader", () => {
|
||||
});
|
||||
|
||||
it("journals valid external watcher edits and advances the snapshot slot", async () => {
|
||||
const initialConfig: OpenClawConfig = {
|
||||
gateway: { reload: {}, port: 18789 },
|
||||
};
|
||||
const nextConfig: OpenClawConfig = {
|
||||
gateway: { reload: {}, port: 18790 },
|
||||
};
|
||||
const initialConfig = makeGatewayPortConfig(18789);
|
||||
const nextConfig = makeGatewayPortConfig(18790);
|
||||
const readSnapshot = vi.fn(async () =>
|
||||
makeSnapshot({ config: nextConfig, parsed: nextConfig, hash: "next-raw-hash" }),
|
||||
);
|
||||
const harness = createReloaderHarness(readSnapshot, { initialConfig });
|
||||
|
||||
harness.watcher.emit("change");
|
||||
await vi.runAllTimersAsync();
|
||||
await flushWatcherChange(harness);
|
||||
|
||||
expect(configAuditMocks.append).toHaveBeenCalledOnce();
|
||||
expect(configAuditMocks.append.mock.calls[0]?.[0]?.record).toMatchObject({
|
||||
@@ -1127,12 +1124,8 @@ describe("startGatewayConfigReloader", () => {
|
||||
});
|
||||
|
||||
it("does not duplicate another OpenClaw process's journaled write", async () => {
|
||||
const initialConfig: OpenClawConfig = {
|
||||
gateway: { reload: {}, port: 18789 },
|
||||
};
|
||||
const nextConfig: OpenClawConfig = {
|
||||
gateway: { reload: {}, port: 18790 },
|
||||
};
|
||||
const initialConfig = makeGatewayPortConfig(18789);
|
||||
const nextConfig = makeGatewayPortConfig(18790);
|
||||
const harness = createReloaderHarness(
|
||||
vi.fn(async () =>
|
||||
makeSnapshot({ config: nextConfig, parsed: nextConfig, hash: "other-write" }),
|
||||
@@ -1146,8 +1139,7 @@ describe("startGatewayConfigReloader", () => {
|
||||
});
|
||||
configAuditMocks.append.mockClear();
|
||||
|
||||
harness.watcher.emit("change");
|
||||
await vi.runAllTimersAsync();
|
||||
await flushWatcherChange(harness);
|
||||
|
||||
expect(configAuditMocks.append).not.toHaveBeenCalled();
|
||||
expect(configAuditMocks.upsertSnapshot).toHaveBeenLastCalledWith(
|
||||
@@ -1173,8 +1165,7 @@ describe("startGatewayConfigReloader", () => {
|
||||
);
|
||||
configAuditMocks.upsertSnapshot.mockClear();
|
||||
|
||||
harness.watcher.emit("change");
|
||||
await vi.runAllTimersAsync();
|
||||
await flushWatcherChange(harness);
|
||||
|
||||
expect(configAuditMocks.append.mock.calls[0]?.[0]?.record).toMatchObject({
|
||||
event: "config.external",
|
||||
@@ -1203,15 +1194,12 @@ describe("startGatewayConfigReloader", () => {
|
||||
const harness = createReloaderHarness(vi.fn(async () => activeSnapshot));
|
||||
configAuditMocks.append.mockClear();
|
||||
|
||||
harness.watcher.emit("change");
|
||||
await vi.runAllTimersAsync();
|
||||
harness.watcher.emit("change");
|
||||
await vi.runAllTimersAsync();
|
||||
await flushWatcherChange(harness);
|
||||
await flushWatcherChange(harness);
|
||||
|
||||
expect(configAuditMocks.append).toHaveBeenCalledOnce();
|
||||
activeSnapshot = secondInvalid;
|
||||
harness.watcher.emit("change");
|
||||
await vi.runAllTimersAsync();
|
||||
await flushWatcherChange(harness);
|
||||
|
||||
expect(configAuditMocks.append).toHaveBeenCalledTimes(2);
|
||||
expect(configAuditMocks.append.mock.calls[1]?.[0]?.record).toMatchObject({
|
||||
@@ -1224,17 +1212,13 @@ describe("startGatewayConfigReloader", () => {
|
||||
});
|
||||
|
||||
it("uses the last observed hash when a valid edit follows an invalid one", async () => {
|
||||
const initialConfig: OpenClawConfig = {
|
||||
gateway: { reload: {}, port: 18789 },
|
||||
};
|
||||
const initialConfig = makeGatewayPortConfig(18789);
|
||||
const invalid = makeSnapshot({
|
||||
valid: false,
|
||||
hash: "invalid-raw-hash",
|
||||
issues: [{ path: "gateway.port", message: "expected number" }],
|
||||
});
|
||||
const nextConfig: OpenClawConfig = {
|
||||
gateway: { reload: {}, port: 18790 },
|
||||
};
|
||||
const nextConfig = makeGatewayPortConfig(18790);
|
||||
let activeSnapshot = invalid;
|
||||
const harness = createReloaderHarness(
|
||||
vi.fn(async () => activeSnapshot),
|
||||
@@ -1242,15 +1226,13 @@ describe("startGatewayConfigReloader", () => {
|
||||
);
|
||||
configAuditMocks.append.mockClear();
|
||||
|
||||
harness.watcher.emit("change");
|
||||
await vi.runAllTimersAsync();
|
||||
await flushWatcherChange(harness);
|
||||
activeSnapshot = makeSnapshot({
|
||||
config: nextConfig,
|
||||
parsed: nextConfig,
|
||||
hash: "valid-raw-hash",
|
||||
});
|
||||
harness.watcher.emit("change");
|
||||
await vi.runAllTimersAsync();
|
||||
await flushWatcherChange(harness);
|
||||
|
||||
expect(configAuditMocks.append.mock.calls[1]?.[0]?.record).toMatchObject({
|
||||
detectedBy: "watch",
|
||||
@@ -1263,9 +1245,7 @@ describe("startGatewayConfigReloader", () => {
|
||||
});
|
||||
|
||||
it("journals a return to the accepted bytes after an invalid edit", async () => {
|
||||
const initialConfig: OpenClawConfig = {
|
||||
gateway: { reload: {}, port: 18789 },
|
||||
};
|
||||
const initialConfig = makeGatewayPortConfig(18789);
|
||||
const invalid = makeSnapshot({
|
||||
valid: false,
|
||||
hash: "invalid-raw-hash",
|
||||
@@ -1278,15 +1258,13 @@ describe("startGatewayConfigReloader", () => {
|
||||
);
|
||||
configAuditMocks.append.mockClear();
|
||||
|
||||
harness.watcher.emit("change");
|
||||
await vi.runAllTimersAsync();
|
||||
await flushWatcherChange(harness);
|
||||
activeSnapshot = makeSnapshot({
|
||||
config: initialConfig,
|
||||
parsed: initialConfig,
|
||||
hash: "initial-raw-hash",
|
||||
});
|
||||
harness.watcher.emit("change");
|
||||
await vi.runAllTimersAsync();
|
||||
await flushWatcherChange(harness);
|
||||
|
||||
expect(configAuditMocks.append.mock.calls[1]?.[0]?.record).toMatchObject({
|
||||
detectedBy: "watch",
|
||||
@@ -1323,8 +1301,7 @@ describe("startGatewayConfigReloader", () => {
|
||||
);
|
||||
configAuditMocks.append.mockClear();
|
||||
|
||||
harness.watcher.emit("change");
|
||||
await vi.runAllTimersAsync();
|
||||
await flushWatcherChange(harness);
|
||||
|
||||
expect(configAuditMocks.append.mock.calls[0]?.[0]?.record).toMatchObject({
|
||||
detectedBy: "watch",
|
||||
@@ -1520,8 +1497,7 @@ describe("startGatewayConfigReloader", () => {
|
||||
);
|
||||
configAuditMocks.append.mockClear();
|
||||
|
||||
harness.watcher.emit("change");
|
||||
await vi.runAllTimersAsync();
|
||||
await flushWatcherChange(harness);
|
||||
|
||||
expect(configAuditMocks.append).not.toHaveBeenCalled();
|
||||
await harness.reloader.stop();
|
||||
@@ -1545,8 +1521,7 @@ describe("startGatewayConfigReloader", () => {
|
||||
);
|
||||
configAuditMocks.append.mockClear();
|
||||
|
||||
harness.watcher.emit("change");
|
||||
await vi.runAllTimersAsync();
|
||||
await flushWatcherChange(harness);
|
||||
|
||||
expect(configAuditMocks.append.mock.calls[0]?.[0]?.record).toMatchObject({
|
||||
event: "config.external",
|
||||
@@ -1603,8 +1578,7 @@ describe("startGatewayConfigReloader", () => {
|
||||
);
|
||||
const harness = createReloaderHarness(readSnapshot, { initialConfig });
|
||||
|
||||
harness.watcher.emit("change");
|
||||
await vi.runAllTimersAsync();
|
||||
await flushWatcherChange(harness);
|
||||
|
||||
expect(harness.onConfigCandidateCommitted).toHaveBeenCalledOnce();
|
||||
expect(harness.onConfigCandidateCommitted).toHaveBeenCalledWith({
|
||||
@@ -1615,8 +1589,7 @@ describe("startGatewayConfigReloader", () => {
|
||||
|
||||
// A same-content echo must not re-notify: nothing changed.
|
||||
harness.onConfigCandidateCommitted.mockClear();
|
||||
harness.watcher.emit("change");
|
||||
await vi.runAllTimersAsync();
|
||||
await flushWatcherChange(harness);
|
||||
expect(harness.onConfigCandidateCommitted).not.toHaveBeenCalled();
|
||||
await harness.reloader.stop();
|
||||
});
|
||||
@@ -1634,8 +1607,7 @@ describe("startGatewayConfigReloader", () => {
|
||||
);
|
||||
const harness = createReloaderHarness(readSnapshot, { initialConfig });
|
||||
|
||||
harness.watcher.emit("change");
|
||||
await vi.runAllTimersAsync();
|
||||
await flushWatcherChange(harness);
|
||||
|
||||
expect(harness.onHotReload).not.toHaveBeenCalled();
|
||||
expect(harness.onRestart).not.toHaveBeenCalled();
|
||||
@@ -1644,16 +1616,13 @@ describe("startGatewayConfigReloader", () => {
|
||||
});
|
||||
|
||||
it("notifies lifecycle owners when a persisted edit reverts to the current baseline", async () => {
|
||||
const initialConfig: OpenClawConfig = {
|
||||
gateway: { reload: {}, port: 18789 },
|
||||
};
|
||||
const initialConfig = makeGatewayPortConfig(18789);
|
||||
const readSnapshot = vi.fn(async () =>
|
||||
makeSnapshot({ config: initialConfig, hash: "reverted-restart-edit" }),
|
||||
);
|
||||
const harness = createReloaderHarness(readSnapshot, { initialConfig });
|
||||
|
||||
harness.watcher.emit("change");
|
||||
await vi.runAllTimersAsync();
|
||||
await flushWatcherChange(harness);
|
||||
|
||||
expect(harness.onConfigAccepted).toHaveBeenCalledOnce();
|
||||
expect(harness.onConfigApplied).not.toHaveBeenCalled();
|
||||
@@ -1679,8 +1648,7 @@ describe("startGatewayConfigReloader", () => {
|
||||
onConfigCandidateObserved,
|
||||
});
|
||||
|
||||
harness.watcher.emit("change");
|
||||
await vi.runAllTimersAsync();
|
||||
await flushWatcherChange(harness);
|
||||
|
||||
expect(onConfigCandidateObserved).toHaveBeenCalledOnce();
|
||||
expect(harness.onConfigAccepted).toHaveBeenCalledOnce();
|
||||
@@ -1743,8 +1711,7 @@ describe("startGatewayConfigReloader", () => {
|
||||
await vi.runAllTimersAsync();
|
||||
harness.onConfigAccepted.mockClear();
|
||||
|
||||
harness.watcher.emit("change");
|
||||
await vi.runAllTimersAsync();
|
||||
await flushWatcherChange(harness);
|
||||
|
||||
expect(onRestart).toHaveBeenCalledOnce();
|
||||
expect(onRestart.mock.calls[0]?.[3]).toEqual(effectiveConfig);
|
||||
@@ -1962,8 +1929,7 @@ describe("startGatewayConfigReloader", () => {
|
||||
initialInternalWriteHash: "accepted-write",
|
||||
});
|
||||
|
||||
harness.watcher.emit("change");
|
||||
await vi.runAllTimersAsync();
|
||||
await flushWatcherChange(harness);
|
||||
|
||||
expect(harness.onConfigAccepted).not.toHaveBeenCalled();
|
||||
expect(harness.onRestart).not.toHaveBeenCalled();
|
||||
@@ -2212,8 +2178,7 @@ describe("startGatewayConfigReloader", () => {
|
||||
sourceFingerprint: "source-queued",
|
||||
writtenAtMs: Date.now(),
|
||||
});
|
||||
harness.watcher.emit("change");
|
||||
await vi.runAllTimersAsync();
|
||||
await flushWatcherChange(harness);
|
||||
|
||||
expect(readSnapshot).toHaveBeenCalledTimes(1);
|
||||
expect(harness.onNoopConfigCommit).not.toHaveBeenCalled();
|
||||
@@ -2500,8 +2465,7 @@ describe("startGatewayConfigReloader", () => {
|
||||
const readSnapshot = vi.fn(async () => makeSnapshot({ config: nextConfig, hash: "sandbox" }));
|
||||
const harness = createReloaderHarness(readSnapshot, { initialConfig });
|
||||
|
||||
harness.watcher.emit("change");
|
||||
await vi.runAllTimersAsync();
|
||||
await flushWatcherChange(harness);
|
||||
|
||||
expect(harness.onConfigChange).toHaveBeenCalledTimes(1);
|
||||
expect(harness.onConfigChange.mock.calls[0]?.[0].noopPaths).toContain(
|
||||
@@ -2528,8 +2492,7 @@ describe("startGatewayConfigReloader", () => {
|
||||
);
|
||||
const harness = createReloaderHarness(readSnapshot, { initialConfig });
|
||||
|
||||
harness.watcher.emit("change");
|
||||
await vi.runAllTimersAsync();
|
||||
await flushWatcherChange(harness);
|
||||
|
||||
expect(harness.onNoopConfigCommit).toHaveBeenCalledTimes(1);
|
||||
expect(harness.onNoopConfigCommit.mock.calls[0]?.[0].noopPaths).toContain(
|
||||
@@ -2582,8 +2545,7 @@ describe("startGatewayConfigReloader", () => {
|
||||
|
||||
pinActivePluginChannelRegistry(channelRegistry);
|
||||
try {
|
||||
harness.watcher.emit("change");
|
||||
await vi.runAllTimersAsync();
|
||||
await flushWatcherChange(harness);
|
||||
|
||||
const [plan] = getOnlyHotReloadCall(harness);
|
||||
expect(plan.restartChannelAccounts).toEqual(new Map([["mattermost", new Set(["alpha"])]]));
|
||||
@@ -2677,8 +2639,7 @@ describe("startGatewayConfigReloader", () => {
|
||||
const readSnapshot = vi.fn(async () => makeSnapshot({ config: nextConfig, hash: "hot" }));
|
||||
const harness = createReloaderHarness(readSnapshot, { initialConfig });
|
||||
|
||||
harness.watcher.emit("change");
|
||||
await vi.runAllTimersAsync();
|
||||
await flushWatcherChange(harness);
|
||||
|
||||
expect(harness.onConfigChange.mock.invocationCallOrder[0]).toBeLessThan(
|
||||
harness.onHotReload.mock.invocationCallOrder[0] ?? Number.POSITIVE_INFINITY,
|
||||
@@ -2702,8 +2663,7 @@ describe("startGatewayConfigReloader", () => {
|
||||
const readSnapshot = vi.fn(async () => makeSnapshot({ config: nextConfig, hash: "terminal" }));
|
||||
const harness = createReloaderHarness(readSnapshot, { initialConfig });
|
||||
|
||||
harness.watcher.emit("change");
|
||||
await vi.runAllTimersAsync();
|
||||
await flushWatcherChange(harness);
|
||||
await Promise.resolve();
|
||||
|
||||
expect(harness.onConfigChange).toHaveBeenCalledTimes(1);
|
||||
@@ -2763,8 +2723,7 @@ describe("startGatewayConfigReloader", () => {
|
||||
const readSnapshot = vi.fn(async () => makeSnapshot({ config: nextConfig, hash: "off" }));
|
||||
const harness = createReloaderHarness(readSnapshot, { initialConfig });
|
||||
|
||||
harness.watcher.emit("change");
|
||||
await vi.runAllTimersAsync();
|
||||
await flushWatcherChange(harness);
|
||||
|
||||
expect(harness.onConfigChange).not.toHaveBeenCalled();
|
||||
expect(harness.onHotReload).not.toHaveBeenCalled();
|
||||
@@ -2782,8 +2741,7 @@ describe("startGatewayConfigReloader", () => {
|
||||
const readSnapshot = vi.fn(async () => makeSnapshot({ config: nextConfig, hash: "hot" }));
|
||||
const harness = createReloaderHarness(readSnapshot, { initialConfig });
|
||||
|
||||
harness.watcher.emit("change");
|
||||
await vi.runAllTimersAsync();
|
||||
await flushWatcherChange(harness);
|
||||
|
||||
expect(harness.onConfigChange).toHaveBeenCalledOnce();
|
||||
expect(harness.onHotReload).not.toHaveBeenCalled();
|
||||
@@ -3083,8 +3041,7 @@ describe("startGatewayConfigReloader", () => {
|
||||
|
||||
harness.emitWrite(makeZeroDebounceHookWrite("internal-retry-1"));
|
||||
await vi.runAllTimersAsync();
|
||||
harness.watcher.emit("change");
|
||||
await vi.runAllTimersAsync();
|
||||
await flushWatcherChange(harness);
|
||||
|
||||
expect(harness.onHotReload).toHaveBeenCalledTimes(2);
|
||||
expect(readSnapshot).toHaveBeenCalledTimes(1);
|
||||
@@ -3398,8 +3355,7 @@ describe("startGatewayConfigReloader", () => {
|
||||
}),
|
||||
});
|
||||
|
||||
harness.watcher.emit("change");
|
||||
await vi.runAllTimersAsync();
|
||||
await flushWatcherChange(harness);
|
||||
expect(targetEnv[envKey]).toBe("c");
|
||||
|
||||
harness.emitWrite({
|
||||
@@ -3573,8 +3529,7 @@ describe("startGatewayConfigReloader", () => {
|
||||
...makeZeroDebounceHookWrite("same-root-hash"),
|
||||
afterWrite: { mode: "none", reason: "stale resolved intent" },
|
||||
});
|
||||
harness.watcher.emit("change");
|
||||
await vi.runAllTimersAsync();
|
||||
await flushWatcherChange(harness);
|
||||
|
||||
const [, hotConfig] = getOnlyHotReloadCall(harness);
|
||||
expect(hotConfig).toEqual(freshConfig);
|
||||
@@ -3630,8 +3585,7 @@ describe("startGatewayConfigReloader", () => {
|
||||
writtenAtMs: Date.now(),
|
||||
afterWrite: { mode: "none", reason: "secret-aware writer intent" },
|
||||
});
|
||||
harness.watcher.emit("change");
|
||||
await vi.runAllTimersAsync();
|
||||
await flushWatcherChange(harness);
|
||||
|
||||
expect(harness.onConfigAccepted).toHaveBeenCalledWith(
|
||||
runtimeConfig,
|
||||
@@ -3814,8 +3768,7 @@ describe("startGatewayConfigReloader", () => {
|
||||
},
|
||||
});
|
||||
await vi.runAllTimersAsync();
|
||||
harness.watcher.emit("change");
|
||||
await vi.runAllTimersAsync();
|
||||
await flushWatcherChange(harness);
|
||||
|
||||
expect(harness.onConfigAccepted).toHaveBeenCalledTimes(2);
|
||||
const replayOwnership = harness.onConfigAccepted.mock.calls[1]?.[1];
|
||||
@@ -3845,8 +3798,7 @@ describe("startGatewayConfigReloader", () => {
|
||||
await vi.runAllTimersAsync();
|
||||
const originalOwnership = harness.onConfigAccepted.mock.calls[0]?.[1];
|
||||
|
||||
harness.watcher.emit("change");
|
||||
await vi.runAllTimersAsync();
|
||||
await flushWatcherChange(harness);
|
||||
|
||||
expect(harness.onConfigAccepted.mock.calls.map((call) => call[3])).toEqual([
|
||||
{ runtimeApplied: false },
|
||||
@@ -3979,8 +3931,7 @@ describe("startGatewayConfigReloader", () => {
|
||||
...makeZeroDebounceHookWrite("same-invalid-root-hash"),
|
||||
afterWrite: { mode: "restart", reason: "must not replay invalid config" },
|
||||
});
|
||||
harness.watcher.emit("change");
|
||||
await vi.runAllTimersAsync();
|
||||
await flushWatcherChange(harness);
|
||||
|
||||
expect(harness.onConfigAccepted).not.toHaveBeenCalled();
|
||||
expect(harness.onRestart).not.toHaveBeenCalled();
|
||||
@@ -4066,11 +4017,9 @@ describe("startGatewayConfigReloader", () => {
|
||||
...makeZeroDebounceHookWrite("replay-retry"),
|
||||
afterWrite: { mode: "restart", reason: "retry original intent" },
|
||||
});
|
||||
harness.watcher.emit("change");
|
||||
await vi.runAllTimersAsync();
|
||||
await flushWatcherChange(harness);
|
||||
|
||||
harness.watcher.emit("change");
|
||||
await vi.runAllTimersAsync();
|
||||
await flushWatcherChange(harness);
|
||||
|
||||
expect(readSnapshot).toHaveBeenCalledTimes(2);
|
||||
expect(harness.onRestart).toHaveBeenCalledTimes(2);
|
||||
@@ -4091,8 +4040,7 @@ describe("startGatewayConfigReloader", () => {
|
||||
});
|
||||
await vi.runAllTimersAsync();
|
||||
|
||||
harness.watcher.emit("change");
|
||||
await vi.runAllTimersAsync();
|
||||
await flushWatcherChange(harness);
|
||||
|
||||
expect(readSnapshot).toHaveBeenCalledOnce();
|
||||
expect(harness.onRestart).toHaveBeenCalledTimes(2);
|
||||
@@ -4522,8 +4470,7 @@ describe("startGatewayConfigReloader", () => {
|
||||
...makeZeroDebounceHookWrite("startup-internal-1"),
|
||||
afterWrite: { mode: "restart", reason: "live writer owns startup hash" },
|
||||
});
|
||||
harness.watcher.emit("change");
|
||||
await vi.runAllTimersAsync();
|
||||
await flushWatcherChange(harness);
|
||||
|
||||
expect(harness.onRestart).toHaveBeenCalledOnce();
|
||||
expect(harness.onRestart.mock.calls[0]?.[0].restartReasons).toContain(
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -379,6 +379,17 @@ vi.mock("./server-cron.js", async () => {
|
||||
};
|
||||
});
|
||||
|
||||
function createRecordedChannelHandlers(events: string[]) {
|
||||
return {
|
||||
stop: vi.fn(async (channel: ChannelKind, accountId?: string) => {
|
||||
events.push(`stop:${channel}:${accountId}`);
|
||||
}),
|
||||
start: vi.fn(async (channel: ChannelKind, accountId?: string) => {
|
||||
events.push(`start:${channel}:${accountId}`);
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
function createTestCronReconciliation() {
|
||||
const complete = vi.fn<() => Promise<void>>(async () => {});
|
||||
return {
|
||||
@@ -389,21 +400,11 @@ function createTestCronReconciliation() {
|
||||
}
|
||||
|
||||
function createCronRestartPlan(): GatewayReloadPlan {
|
||||
return {
|
||||
return createHotTailPlan({
|
||||
changedPaths: ["cron"],
|
||||
restartGateway: false,
|
||||
restartReasons: [],
|
||||
hotReasons: ["cron"],
|
||||
reloadHooks: false,
|
||||
restartGmailWatcher: false,
|
||||
restartCron: true,
|
||||
restartHeartbeat: false,
|
||||
restartHealthMonitor: false,
|
||||
reloadPlugins: false,
|
||||
restartChannels: new Set(),
|
||||
disposeMcpRuntimes: false,
|
||||
noopPaths: [],
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function createHotTailPlan(overrides: Partial<GatewayReloadPlan> = {}): GatewayReloadPlan {
|
||||
@@ -1989,21 +1990,12 @@ describe("gateway restart deferral preflight", () => {
|
||||
...configA,
|
||||
gateway: { port: 19_001 },
|
||||
} as OpenClawConfig;
|
||||
const plan = {
|
||||
const plan = createHotTailPlan({
|
||||
changedPaths: ["channels.discord.token", "logging.level"],
|
||||
restartGateway: false,
|
||||
restartReasons: [],
|
||||
hotReasons: ["channels.discord.token"],
|
||||
reloadHooks: false,
|
||||
restartGmailWatcher: false,
|
||||
restartCron: false,
|
||||
restartHeartbeat: false,
|
||||
restartHealthMonitor: false,
|
||||
reloadPlugins: false,
|
||||
restartChannels: new Set<ChannelKind>(["discord"]),
|
||||
disposeMcpRuntimes: false,
|
||||
noopPaths: ["logging.level"],
|
||||
} satisfies GatewayReloadPlan;
|
||||
}) satisfies GatewayReloadPlan;
|
||||
const configRestartPlan = {
|
||||
...createHotTailPlan(),
|
||||
changedPaths: ["gateway.port"],
|
||||
@@ -2717,14 +2709,7 @@ describe("gateway channel hot reload handlers", () => {
|
||||
|
||||
it("promotes unlisted accounts to a wholesale restart", async () => {
|
||||
const events: string[] = [];
|
||||
const channels = {
|
||||
stop: vi.fn(async (channel: ChannelKind, accountId?: string) => {
|
||||
events.push(`stop:${channel}:${accountId}`);
|
||||
}),
|
||||
start: vi.fn(async (channel: ChannelKind, accountId?: string) => {
|
||||
events.push(`start:${channel}:${accountId}`);
|
||||
}),
|
||||
};
|
||||
const channels = createRecordedChannelHandlers(events);
|
||||
const { applyHotReload } = createReloadHandlersForTest(undefined, channels);
|
||||
|
||||
await withChannelReloadsEnabled(async () => {
|
||||
@@ -2738,14 +2723,7 @@ describe("gateway channel hot reload handlers", () => {
|
||||
|
||||
it("promotes unresolvable accounts to a wholesale restart before stopping any account", async () => {
|
||||
const events: string[] = [];
|
||||
const channels = {
|
||||
stop: vi.fn(async (channel: ChannelKind, accountId?: string) => {
|
||||
events.push(`stop:${channel}:${accountId}`);
|
||||
}),
|
||||
start: vi.fn(async (channel: ChannelKind, accountId?: string) => {
|
||||
events.push(`start:${channel}:${accountId}`);
|
||||
}),
|
||||
};
|
||||
const channels = createRecordedChannelHandlers(events);
|
||||
const { applyHotReload, logChannels } = createReloadHandlersForTest(undefined, channels);
|
||||
|
||||
await withChannelReloadsEnabled(async () => {
|
||||
@@ -2801,14 +2779,7 @@ describe("gateway channel hot reload handlers", () => {
|
||||
|
||||
it("skips per-account restarts for channels already queued for wholesale restart", async () => {
|
||||
const events: string[] = [];
|
||||
const channels = {
|
||||
stop: vi.fn(async (channel: ChannelKind, accountId?: string) => {
|
||||
events.push(`stop:${channel}:${accountId}`);
|
||||
}),
|
||||
start: vi.fn(async (channel: ChannelKind, accountId?: string) => {
|
||||
events.push(`start:${channel}:${accountId}`);
|
||||
}),
|
||||
};
|
||||
const channels = createRecordedChannelHandlers(events);
|
||||
const { applyHotReload } = createReloadHandlersForTest(undefined, channels);
|
||||
|
||||
await withChannelReloadsEnabled(async () => {
|
||||
@@ -2866,14 +2837,7 @@ describe("gateway channel hot reload handlers", () => {
|
||||
|
||||
it("stops account targets without restarting them while autostart is suppressed", async () => {
|
||||
const events: string[] = [];
|
||||
const channels = {
|
||||
stop: vi.fn(async (channel: ChannelKind, accountId?: string) => {
|
||||
events.push(`stop:${channel}:${accountId}`);
|
||||
}),
|
||||
start: vi.fn(async (channel: ChannelKind, accountId?: string) => {
|
||||
events.push(`start:${channel}:${accountId}`);
|
||||
}),
|
||||
};
|
||||
const channels = createRecordedChannelHandlers(events);
|
||||
const { applyHotReload } = createReloadHandlersForTest(
|
||||
undefined,
|
||||
channels,
|
||||
@@ -2899,14 +2863,7 @@ describe("gateway channel hot reload handlers", () => {
|
||||
|
||||
it("rechecks agent work admitted after plugin reload leaves the channel running", async () => {
|
||||
const events: string[] = [];
|
||||
const channels = {
|
||||
stop: vi.fn(async (channel: ChannelKind, accountId?: string) => {
|
||||
events.push(`stop:${channel}:${accountId}`);
|
||||
}),
|
||||
start: vi.fn(async (channel: ChannelKind, accountId?: string) => {
|
||||
events.push(`start:${channel}:${accountId}`);
|
||||
}),
|
||||
};
|
||||
const channels = createRecordedChannelHandlers(events);
|
||||
const reloadPlugins = vi.fn(async (params): Promise<GatewayPluginReloadResult> => {
|
||||
await params.beforeReplace(new Set());
|
||||
hoisted.activeEmbeddedRunCount.value = 1;
|
||||
@@ -4600,21 +4557,11 @@ describe("gateway plugin hot reload handlers", () => {
|
||||
|
||||
try {
|
||||
await handlers.applyHotReload(
|
||||
{
|
||||
createHotTailPlan({
|
||||
changedPaths: [`env.vars.${envKey}`, "channels.discord.token"],
|
||||
restartGateway: false,
|
||||
restartReasons: [],
|
||||
hotReasons: [`env.vars.${envKey}`, "channels.discord.token"],
|
||||
reloadHooks: false,
|
||||
restartGmailWatcher: false,
|
||||
restartCron: false,
|
||||
restartHeartbeat: false,
|
||||
restartHealthMonitor: false,
|
||||
reloadPlugins: false,
|
||||
restartChannels: new Set(["discord"]),
|
||||
disposeMcpRuntimes: false,
|
||||
noopPaths: [],
|
||||
},
|
||||
}),
|
||||
{},
|
||||
{
|
||||
runtimeEnv: runtimeEnv.env,
|
||||
@@ -4667,21 +4614,11 @@ describe("gateway plugin hot reload handlers", () => {
|
||||
|
||||
try {
|
||||
await handlers.applyHotReload(
|
||||
{
|
||||
createHotTailPlan({
|
||||
changedPaths: [`env.vars.${envKey}`, "channels.discord.token"],
|
||||
restartGateway: false,
|
||||
restartReasons: [],
|
||||
hotReasons: [`env.vars.${envKey}`, "channels.discord.token"],
|
||||
reloadHooks: false,
|
||||
restartGmailWatcher: false,
|
||||
restartCron: false,
|
||||
restartHeartbeat: false,
|
||||
restartHealthMonitor: false,
|
||||
reloadPlugins: false,
|
||||
restartChannels: new Set(["discord"]),
|
||||
disposeMcpRuntimes: false,
|
||||
noopPaths: [],
|
||||
},
|
||||
}),
|
||||
nextConfig,
|
||||
{
|
||||
runtimeEnv: runtimeEnv.env,
|
||||
@@ -4833,21 +4770,12 @@ describe("gateway plugin hot reload handlers", () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
const reload = handlers.applyHotReload(
|
||||
{
|
||||
createHotTailPlan({
|
||||
changedPaths: ["hooks.path", "plugins.enabled"],
|
||||
restartGateway: false,
|
||||
restartReasons: [],
|
||||
hotReasons: ["hooks.path", "plugins.enabled"],
|
||||
reloadHooks: true,
|
||||
restartGmailWatcher: false,
|
||||
restartCron: false,
|
||||
restartHeartbeat: false,
|
||||
restartHealthMonitor: false,
|
||||
reloadPlugins: true,
|
||||
restartChannels: new Set(),
|
||||
disposeMcpRuntimes: false,
|
||||
noopPaths: [],
|
||||
},
|
||||
}),
|
||||
{ hooks: { enabled: true, token: "token", path: "/next" } },
|
||||
{
|
||||
isCurrent: () => true,
|
||||
@@ -5366,21 +5294,11 @@ describe("gateway plugin hot reload handlers", () => {
|
||||
});
|
||||
|
||||
describe("deferred channel reload abort generation", () => {
|
||||
const abortChannelReloadPlan: GatewayReloadPlan = {
|
||||
const abortChannelReloadPlan: GatewayReloadPlan = createHotTailPlan({
|
||||
changedPaths: ["channels.whatsapp.enabled"],
|
||||
restartGateway: false,
|
||||
restartReasons: [],
|
||||
hotReasons: ["channels"],
|
||||
reloadHooks: false,
|
||||
restartGmailWatcher: false,
|
||||
restartCron: false,
|
||||
restartHeartbeat: false,
|
||||
restartHealthMonitor: false,
|
||||
reloadPlugins: false,
|
||||
restartChannels: new Set(["whatsapp"]),
|
||||
disposeMcpRuntimes: false,
|
||||
noopPaths: [],
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
hoisted.activeTaskCount.value = 0;
|
||||
|
||||
Reference in New Issue
Block a user