



























@@ -21,6 +21,7 @@ import { createTestRegistry } from "../test-utils/channel-plugins.js";
2121import {
2222buildGatewayReloadPlan,
2323diffConfigPaths,
24+type GatewayReloadPlan,
2425listPluginInstallTimestampMetadataPaths,
2526listPluginInstallWholeRecordPaths,
2627resolveGatewayReloadSettings,
@@ -260,9 +261,7 @@ describe("buildGatewayReloadPlan", () => {
260261]);
261262expect(plan.restartGateway).toBe(false);
262263expect(plan.restartHeartbeat).toBe(true);
263-expect(plan.hotReasons).toEqual(
264-expect.arrayContaining(["models.providers.openai.models", "agents.defaults.model"]),
265-);
264+expect(plan.hotReasons).toEqual(["models.providers.openai.models", "agents.defaults.model"]);
266265});
267266268267it("requires restart when model pricing bootstrap changes", () => {
@@ -589,8 +588,8 @@ function createReloaderHarness(
589588) {
590589const watcher = createWatcherMock();
591590vi.spyOn(chokidar, "watch").mockReturnValue(watcher as unknown as never);
592-const onHotReload = vi.fn(async () => {});
593-const onRestart = vi.fn();
591+const onHotReload = vi.fn(async (_plan: GatewayReloadPlan, _nextConfig: OpenClawConfig) => {});
592+const onRestart = vi.fn((_plan: GatewayReloadPlan, _nextConfig: OpenClawConfig) => {});
594593let writeListener: ((event: ConfigWriteNotification) => void) | null = null;
595594const subscribeToWrites = vi.fn((listener: (event: ConfigWriteNotification) => void) => {
596595writeListener = listener;
@@ -631,6 +630,26 @@ function createReloaderHarness(
631630};
632631}
633632633+type ReloaderHarness = ReturnType<typeof createReloaderHarness>;
634+635+function getOnlyRestartCall(harness: ReloaderHarness): [GatewayReloadPlan, OpenClawConfig] {
636+expect(harness.onRestart).toHaveBeenCalledTimes(1);
637+const call = harness.onRestart.mock.calls[0];
638+if (!call) {
639+throw new Error("expected one restart call");
640+}
641+return call;
642+}
643+644+function getOnlyHotReloadCall(harness: ReloaderHarness): [GatewayReloadPlan, OpenClawConfig] {
645+expect(harness.onHotReload).toHaveBeenCalledTimes(1);
646+const call = harness.onHotReload.mock.calls[0];
647+if (!call) {
648+throw new Error("expected one hot reload call");
649+}
650+return call;
651+}
652+634653describe("startGatewayConfigReloader", () => {
635654beforeEach(() => {
636655vi.useFakeTimers();
@@ -745,7 +764,7 @@ describe("startGatewayConfigReloader", () => {
745764hash: "bad-1",
746765}),
747766);
748-const promoteSnapshot = vi.fn(async () => true);
767+const promoteSnapshot = vi.fn(async (_snapshot: ConfigFileSnapshot, _reason: string) => true);
749768const { watcher, onHotReload, onRestart, log, reloader } = createReloaderHarness(readSnapshot, {
750769 promoteSnapshot,
751770});
@@ -795,7 +814,7 @@ describe("startGatewayConfigReloader", () => {
795814const readSnapshot = vi
796815.fn<() => Promise<ConfigFileSnapshot>>()
797816.mockResolvedValueOnce(invalidSnapshot);
798-const promoteSnapshot = vi.fn(async () => true);
817+const promoteSnapshot = vi.fn(async (_snapshot: ConfigFileSnapshot, _reason: string) => true);
799818const previousConfig: OpenClawConfig = {
800819 ...activeConfig,
801820plugins: {
@@ -819,11 +838,13 @@ describe("startGatewayConfigReloader", () => {
819838expect(onRestart).not.toHaveBeenCalled();
820839expect(onHotReload).not.toHaveBeenCalled();
821840expect(promoteSnapshot).not.toHaveBeenCalled();
822-expect(log.warn).toHaveBeenCalledWith(
823-expect.stringContaining(
824-"config reload skipped (invalid config): plugins.entries.lossless-claw.config.cacheAwareCompaction:",
841+expect(
842+log.warn.mock.calls.some(([message]) =>
843+message.includes(
844+"config reload skipped (invalid config): plugins.entries.lossless-claw.config.cacheAwareCompaction:",
845+),
825846),
826-);
847+).toBe(true);
827848828849await reloader.stop();
829850});
@@ -839,7 +860,7 @@ describe("startGatewayConfigReloader", () => {
839860const readSnapshot = vi
840861.fn<() => Promise<ConfigFileSnapshot>>()
841862.mockResolvedValueOnce(acceptedSnapshot);
842-const promoteSnapshot = vi.fn(async () => true);
863+const promoteSnapshot = vi.fn(async (_snapshot: ConfigFileSnapshot, _reason: string) => true);
843864const { watcher, onHotReload, reloader } = createReloaderHarness(readSnapshot, {
844865 promoteSnapshot,
845866});
@@ -864,7 +885,7 @@ describe("startGatewayConfigReloader", () => {
864885const readSnapshot = vi
865886.fn<() => Promise<ConfigFileSnapshot>>()
866887.mockResolvedValueOnce(acceptedSnapshot);
867-const promoteSnapshot = vi.fn(async () => true);
888+const promoteSnapshot = vi.fn(async (_snapshot: ConfigFileSnapshot, _reason: string) => true);
868889const { watcher, onHotReload, log, reloader } = createReloaderHarness(readSnapshot, {
869890 promoteSnapshot,
870891});
@@ -929,18 +950,17 @@ describe("startGatewayConfigReloader", () => {
929950hash: "external-1",
930951}),
931952);
932-const promoteSnapshot = vi.fn(async () => true);
953+const promoteSnapshot = vi.fn(async (_snapshot: ConfigFileSnapshot, _reason: string) => true);
933954const harness = createReloaderHarness(readSnapshot, { promoteSnapshot });
934955935956harness.emitWrite(makeZeroDebounceHookWrite("internal-1"));
936957await vi.runOnlyPendingTimersAsync();
937958938959expect(readSnapshot).toHaveBeenCalledTimes(1);
939960expect(harness.onHotReload).toHaveBeenCalledTimes(1);
940-expect(promoteSnapshot).toHaveBeenCalledWith(
941-expect.objectContaining({ hash: "internal-1" }),
942-"in-process-write",
943-);
961+const [promotedSnapshot, promotionReason] = promoteSnapshot.mock.calls[0] ?? [];
962+expect(promotedSnapshot?.hash).toBe("internal-1");
963+expect(promotionReason).toBe("in-process-write");
944964945965harness.watcher.emit("change");
946966harness.watcher.emit("change");
@@ -963,7 +983,7 @@ describe("startGatewayConfigReloader", () => {
963983const readSnapshot = vi
964984.fn<() => Promise<ConfigFileSnapshot>>()
965985.mockResolvedValueOnce(makeZeroDebounceHookSnapshot("internal-none"));
966-const promoteSnapshot = vi.fn(async () => true);
986+const promoteSnapshot = vi.fn(async (_snapshot: ConfigFileSnapshot, _reason: string) => true);
967987const harness = createReloaderHarness(readSnapshot, { promoteSnapshot });
968988969989harness.emitWrite({
@@ -977,10 +997,9 @@ describe("startGatewayConfigReloader", () => {
977997expect(harness.log.info).toHaveBeenCalledWith(
978998"config reload skipped by writer intent (caller handles follow-up)",
979999);
980-expect(promoteSnapshot).toHaveBeenCalledWith(
981-expect.objectContaining({ hash: "internal-none" }),
982-"in-process-write",
983-);
1000+const [promotedSnapshot, promotionReason] = promoteSnapshot.mock.calls[0] ?? [];
1001+expect(promotedSnapshot?.hash).toBe("internal-none");
1002+expect(promotionReason).toBe("in-process-write");
98410039851004await harness.reloader.stop();
9861005});
@@ -998,14 +1017,13 @@ describe("startGatewayConfigReloader", () => {
9981017await vi.runOnlyPendingTimersAsync();
999101810001019expect(harness.onHotReload).not.toHaveBeenCalled();
1001-expect(harness.onRestart).toHaveBeenCalledTimes(1);
1002-expect(harness.onRestart).toHaveBeenCalledWith(
1003-expect.objectContaining({
1004-restartGateway: true,
1005-restartReasons: expect.arrayContaining(["plugin runtime contract changed"]),
1006-}),
1007-expect.any(Object),
1008-);
1020+const [plan, nextConfig] = getOnlyRestartCall(harness);
1021+expect(plan.restartGateway).toBe(true);
1022+expect(plan.restartReasons).toEqual(["plugin runtime contract changed"]);
1023+expect(nextConfig).toEqual({
1024+gateway: { reload: { debounceMs: 0 } },
1025+hooks: { enabled: true },
1026+});
1009102710101028await harness.reloader.stop();
10111029});
@@ -1090,9 +1108,9 @@ describe("startGatewayConfigReloader", () => {
1090110810911109expect(harness.onHotReload).not.toHaveBeenCalled();
10921110expect(harness.onRestart).not.toHaveBeenCalled();
1093-expect(harness.log.info).not.toHaveBeenCalledWith(
1094-expect.stringContaining("gateway.auth.token"),
1095-);
1111+expect(
1112+harness.log.info.mock.calls.some(([message]) => message.includes("gateway.auth.token")),
1113+).toBe(false);
1096111410971115await harness.reloader.stop();
10981116});
@@ -1146,29 +1164,17 @@ describe("startGatewayConfigReloader", () => {
11461164await vi.runOnlyPendingTimersAsync();
1147116511481166expect(harness.onHotReload).not.toHaveBeenCalled();
1149-expect(harness.onRestart).toHaveBeenCalledTimes(1);
1150-expect(harness.onRestart).toHaveBeenCalledWith(
1151-expect.objectContaining({
1152-changedPaths: [
1153-"plugins.installs.lossless.resolvedAt",
1154-"plugins.installs.lossless.resolvedAt",
1155-],
1156-restartGateway: true,
1157-restartReasons: [
1158-"plugins.installs.lossless.resolvedAt",
1159-"plugins.installs.lossless.resolvedAt",
1160-],
1161-}),
1162-expect.objectContaining({
1163-plugins: expect.objectContaining({
1164-installs: expect.objectContaining({
1165-"lossless.resolvedAt": expect.objectContaining({
1166-source: "npm",
1167-}),
1168-}),
1169-}),
1170-}),
1171-);
1167+const [plan, nextConfig] = getOnlyRestartCall(harness);
1168+expect(plan.changedPaths).toEqual([
1169+"plugins.installs.lossless.resolvedAt",
1170+"plugins.installs.lossless.resolvedAt",
1171+]);
1172+expect(plan.restartGateway).toBe(true);
1173+expect(plan.restartReasons).toEqual([
1174+"plugins.installs.lossless.resolvedAt",
1175+"plugins.installs.lossless.resolvedAt",
1176+]);
1177+expect(nextConfig.plugins?.installs?.["lossless.resolvedAt"]?.source).toBe("npm");
1172117811731179await harness.reloader.stop();
11741180});
@@ -1209,15 +1215,11 @@ describe("startGatewayConfigReloader", () => {
12091215await vi.runOnlyPendingTimersAsync();
1210121612111217expect(harness.onHotReload).not.toHaveBeenCalled();
1212-expect(harness.onRestart).toHaveBeenCalledTimes(1);
1213-expect(harness.onRestart).toHaveBeenCalledWith(
1214-expect.objectContaining({
1215-changedPaths: ["plugins.installs.lossless-claw"],
1216-restartGateway: true,
1217-restartReasons: ["plugins.installs.lossless-claw"],
1218-}),
1219-activeConfig,
1220-);
1218+const [plan, nextConfig] = getOnlyRestartCall(harness);
1219+expect(plan.changedPaths).toEqual(["plugins.installs.lossless-claw"]);
1220+expect(plan.restartGateway).toBe(true);
1221+expect(plan.restartReasons).toEqual(["plugins.installs.lossless-claw"]);
1222+expect(nextConfig).toBe(activeConfig);
1221122312221224await harness.reloader.stop();
12231225});
@@ -1265,16 +1267,12 @@ describe("startGatewayConfigReloader", () => {
12651267await vi.runOnlyPendingTimersAsync();
1266126812671269expect(harness.onRestart).not.toHaveBeenCalled();
1268-expect(harness.onHotReload).toHaveBeenCalledTimes(1);
1269-expect(harness.onHotReload).toHaveBeenCalledWith(
1270-expect.objectContaining({
1271-changedPaths: ["plugins.entries.telegram.enabled"],
1272-restartGateway: false,
1273-reloadPlugins: true,
1274-hotReasons: ["plugins.entries.telegram.enabled"],
1275-}),
1276-nextConfig,
1277-);
1270+const [plan, hotConfig] = getOnlyHotReloadCall(harness);
1271+expect(plan.changedPaths).toEqual(["plugins.entries.telegram.enabled"]);
1272+expect(plan.restartGateway).toBe(false);
1273+expect(plan.reloadPlugins).toBe(true);
1274+expect(plan.hotReasons).toEqual(["plugins.entries.telegram.enabled"]);
1275+expect(hotConfig).toBe(nextConfig);
1278127612791277await harness.reloader.stop();
12801278});
@@ -1321,15 +1319,11 @@ describe("startGatewayConfigReloader", () => {
13211319await vi.runOnlyPendingTimersAsync();
1322132013231321expect(harness.onHotReload).not.toHaveBeenCalled();
1324-expect(harness.onRestart).toHaveBeenCalledTimes(1);
1325-expect(harness.onRestart).toHaveBeenCalledWith(
1326-expect.objectContaining({
1327-changedPaths: ["plugins.entries", "plugins.installs.lossless-claw"],
1328-restartGateway: true,
1329-restartReasons: ["plugins.installs.lossless-claw"],
1330-}),
1331-nextConfig,
1332-);
1322+const [plan, restartedConfig] = getOnlyRestartCall(harness);
1323+expect(plan.changedPaths).toEqual(["plugins.entries", "plugins.installs.lossless-claw"]);
1324+expect(plan.restartGateway).toBe(true);
1325+expect(plan.restartReasons).toEqual(["plugins.installs.lossless-claw"]);
1326+expect(restartedConfig).toBe(nextConfig);
1333132713341328await harness.reloader.stop();
13351329});
@@ -1496,9 +1490,7 @@ describe("startGatewayConfigReloader skills invalidation", () => {
1496149014971491const after = getSkillsSnapshotVersion();
14981492expect(after).toBeGreaterThan(before);
1499-expect(log.info).toHaveBeenCalledWith(
1500-expect.stringContaining("skills snapshot invalidated by config change"),
1501-);
1493+expect(log.info).toHaveBeenCalledWith("skills snapshot invalidated by config change (skills)");
1502149415031495await reloader.stop();
15041496});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。