Skip to content

Commit 53575f2

Browse files
committed
fix: add googlechat lifecycle regression test (#27384) (thanks @junsuwhy)
1 parent eb6fa0d commit 53575f2

File tree

2 files changed

+103
-0
lines changed

2 files changed

+103
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Docs: https://docs.openclaw.ai
1818
### Fixes
1919

2020
- Delivery queue/recovery backoff: prevent retry starvation by persisting `lastAttemptAt` on failed sends and deferring recovery retries until each entry's `lastAttemptAt + backoff` window is eligible, while continuing to recover ready entries behind deferred ones. Landed from contributor PR #27710 by @Jimmy-xuzimo. Thanks @Jimmy-xuzimo.
21+
- Google Chat/Lifecycle: keep Google Chat `startAccount` pending until abort in webhook mode so startup is no longer interpreted as immediate exit, preventing auto-restart loops and webhook-target churn. (#27384) thanks @junsuwhy.
2122
- Microsoft Teams/File uploads: acknowledge `fileConsent/invoke` immediately (`invokeResponse` before upload + file card send) so Teams no longer shows false "Something went wrong" timeout banners while upload completion continues asynchronously; includes updated async regression coverage. Landed from contributor PR #27641 by @scz2011.
2223
- Queue/Drain/Cron reliability: harden lane draining with guaranteed `draining` flag reset on synchronous pump failures, reject new queue enqueues during gateway restart drain windows (instead of silently killing accepted tasks), add `/stop` queued-backlog cutoff metadata with stale-message skipping (while avoiding cross-session native-stop cutoff bleed), and raise isolated cron `agentTurn` outer safety timeout to avoid false 10-minute timeout races against longer agent session timeouts. (#27407, #27332, #27427)
2324
- Typing/Main reply pipeline: always mark dispatch idle in `agent-runner` finalization so typing cleanup runs even when dispatcher `onIdle` does not fire, preventing stuck typing indicators after run completion. (#27250) Thanks @Sid-Qin.
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import type {
2+
ChannelAccountSnapshot,
3+
ChannelGatewayContext,
4+
OpenClawConfig,
5+
} from "openclaw/plugin-sdk";
6+
import { afterEach, describe, expect, it, vi } from "vitest";
7+
import { createRuntimeEnv } from "../../test-utils/runtime-env.js";
8+
import type { ResolvedGoogleChatAccount } from "./accounts.js";
9+
10+
const hoisted = vi.hoisted(() => ({
11+
startGoogleChatMonitor: vi.fn(),
12+
}));
13+
14+
vi.mock("./monitor.js", async () => {
15+
const actual = await vi.importActual<typeof import("./monitor.js")>("./monitor.js");
16+
return {
17+
...actual,
18+
startGoogleChatMonitor: hoisted.startGoogleChatMonitor,
19+
};
20+
});
21+
22+
import { googlechatPlugin } from "./channel.js";
23+
24+
function createStartAccountCtx(params: {
25+
account: ResolvedGoogleChatAccount;
26+
abortSignal: AbortSignal;
27+
statusPatchSink?: (next: ChannelAccountSnapshot) => void;
28+
}): ChannelGatewayContext<ResolvedGoogleChatAccount> {
29+
const snapshot: ChannelAccountSnapshot = {
30+
accountId: params.account.accountId,
31+
configured: true,
32+
enabled: true,
33+
running: false,
34+
};
35+
return {
36+
accountId: params.account.accountId,
37+
account: params.account,
38+
cfg: {} as OpenClawConfig,
39+
runtime: createRuntimeEnv(),
40+
abortSignal: params.abortSignal,
41+
log: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() },
42+
getStatus: () => snapshot,
43+
setStatus: (next) => {
44+
Object.assign(snapshot, next);
45+
params.statusPatchSink?.(snapshot);
46+
},
47+
};
48+
}
49+
50+
describe("googlechatPlugin gateway.startAccount", () => {
51+
afterEach(() => {
52+
vi.clearAllMocks();
53+
});
54+
55+
it("keeps startAccount pending until abort, then unregisters", async () => {
56+
const unregister = vi.fn();
57+
hoisted.startGoogleChatMonitor.mockResolvedValue(unregister);
58+
59+
const account: ResolvedGoogleChatAccount = {
60+
accountId: "default",
61+
enabled: true,
62+
credentialSource: "inline",
63+
credentials: {},
64+
config: {
65+
webhookPath: "/googlechat",
66+
webhookUrl: "https://example.com/googlechat",
67+
audienceType: "app-url",
68+
audience: "https://example.com/googlechat",
69+
},
70+
};
71+
72+
const patches: ChannelAccountSnapshot[] = [];
73+
const abort = new AbortController();
74+
const task = googlechatPlugin.gateway!.startAccount!(
75+
createStartAccountCtx({
76+
account,
77+
abortSignal: abort.signal,
78+
statusPatchSink: (next) => patches.push({ ...next }),
79+
}),
80+
);
81+
82+
await new Promise((resolve) => setTimeout(resolve, 20));
83+
84+
let settled = false;
85+
void task.then(() => {
86+
settled = true;
87+
});
88+
89+
await new Promise((resolve) => setTimeout(resolve, 20));
90+
expect(settled).toBe(false);
91+
92+
expect(hoisted.startGoogleChatMonitor).toHaveBeenCalledOnce();
93+
expect(unregister).not.toHaveBeenCalled();
94+
95+
abort.abort();
96+
await task;
97+
98+
expect(unregister).toHaveBeenCalledOnce();
99+
expect(patches.some((entry) => entry.running === true)).toBe(true);
100+
expect(patches.some((entry) => entry.running === false)).toBe(true);
101+
});
102+
});

0 commit comments

Comments
 (0)