Skip to content

Commit 0acd1f6

Browse files
committed
test: share startup account lifecycle helpers
1 parent b61bc49 commit 0acd1f6

File tree

5 files changed

+143
-85
lines changed

5 files changed

+143
-85
lines changed

extensions/googlechat/src/channel.startup.test.ts

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import type { ChannelAccountSnapshot } from "openclaw/plugin-sdk/googlechat";
22
import { afterEach, describe, expect, it, vi } from "vitest";
3-
import { createStartAccountContext } from "../../test-utils/start-account-context.js";
3+
import {
4+
abortStartedAccount,
5+
expectPendingUntilAbort,
6+
startAccountAndTrackLifecycle,
7+
} from "../../test-utils/start-account-lifecycle.js";
48
import type { ResolvedGoogleChatAccount } from "./accounts.js";
59

610
const hoisted = vi.hoisted(() => ({
@@ -39,29 +43,25 @@ describe("googlechatPlugin gateway.startAccount", () => {
3943
},
4044
};
4145

42-
const patches: ChannelAccountSnapshot[] = [];
43-
const abort = new AbortController();
44-
const task = googlechatPlugin.gateway!.startAccount!(
45-
createStartAccountContext({
46-
account,
47-
abortSignal: abort.signal,
48-
statusPatchSink: (next) => patches.push({ ...next }),
49-
}),
50-
);
51-
let settled = false;
52-
void task.then(() => {
53-
settled = true;
46+
const { abort, patches, task, isSettled } = startAccountAndTrackLifecycle({
47+
startAccount: googlechatPlugin.gateway!.startAccount!,
48+
account,
5449
});
55-
await vi.waitFor(() => {
56-
expect(hoisted.startGoogleChatMonitor).toHaveBeenCalledOnce();
50+
await expectPendingUntilAbort({
51+
waitForStarted: () =>
52+
vi.waitFor(() => {
53+
expect(hoisted.startGoogleChatMonitor).toHaveBeenCalledOnce();
54+
}),
55+
isSettled,
56+
abort,
57+
task,
58+
assertBeforeAbort: () => {
59+
expect(unregister).not.toHaveBeenCalled();
60+
},
61+
assertAfterAbort: () => {
62+
expect(unregister).toHaveBeenCalledOnce();
63+
},
5764
});
58-
expect(settled).toBe(false);
59-
expect(unregister).not.toHaveBeenCalled();
60-
61-
abort.abort();
62-
await task;
63-
64-
expect(unregister).toHaveBeenCalledOnce();
6565
expect(patches.some((entry) => entry.running === true)).toBe(true);
6666
expect(patches.some((entry) => entry.running === false)).toBe(true);
6767
});

extensions/irc/src/channel.startup.test.ts

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { afterEach, describe, expect, it, vi } from "vitest";
2-
import { createStartAccountContext } from "../../test-utils/start-account-context.js";
2+
import {
3+
expectStopPendingUntilAbort,
4+
startAccountAndTrackLifecycle,
5+
} from "../../test-utils/start-account-lifecycle.js";
36
import type { ResolvedIrcAccount } from "./accounts.js";
47

58
const hoisted = vi.hoisted(() => ({
@@ -41,27 +44,20 @@ describe("ircPlugin gateway.startAccount", () => {
4144
config: {} as ResolvedIrcAccount["config"],
4245
};
4346

44-
const abort = new AbortController();
45-
const task = ircPlugin.gateway!.startAccount!(
46-
createStartAccountContext({
47-
account,
48-
abortSignal: abort.signal,
49-
}),
50-
);
51-
let settled = false;
52-
void task.then(() => {
53-
settled = true;
47+
const { abort, task, isSettled } = startAccountAndTrackLifecycle({
48+
startAccount: ircPlugin.gateway!.startAccount!,
49+
account,
5450
});
5551

56-
await vi.waitFor(() => {
57-
expect(hoisted.monitorIrcProvider).toHaveBeenCalledOnce();
52+
await expectStopPendingUntilAbort({
53+
waitForStarted: () =>
54+
vi.waitFor(() => {
55+
expect(hoisted.monitorIrcProvider).toHaveBeenCalledOnce();
56+
}),
57+
isSettled,
58+
abort,
59+
task,
60+
stop,
5861
});
59-
expect(settled).toBe(false);
60-
expect(stop).not.toHaveBeenCalled();
61-
62-
abort.abort();
63-
await task;
64-
65-
expect(stop).toHaveBeenCalledOnce();
6662
});
6763
});

extensions/nextcloud-talk/src/channel.startup.test.ts

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { afterEach, describe, expect, it, vi } from "vitest";
22
import { createStartAccountContext } from "../../test-utils/start-account-context.js";
3+
import {
4+
expectStopPendingUntilAbort,
5+
startAccountAndTrackLifecycle,
6+
} from "../../test-utils/start-account-lifecycle.js";
37
import type { ResolvedNextcloudTalkAccount } from "./accounts.js";
48

59
const hoisted = vi.hoisted(() => ({
@@ -40,28 +44,20 @@ describe("nextcloudTalkPlugin gateway.startAccount", () => {
4044
it("keeps startAccount pending until abort, then stops the monitor", async () => {
4145
const stop = vi.fn();
4246
hoisted.monitorNextcloudTalkProvider.mockResolvedValue({ stop });
43-
const abort = new AbortController();
44-
45-
const task = nextcloudTalkPlugin.gateway!.startAccount!(
46-
createStartAccountContext({
47-
account: buildAccount(),
48-
abortSignal: abort.signal,
49-
}),
50-
);
51-
let settled = false;
52-
void task.then(() => {
53-
settled = true;
47+
const { abort, task, isSettled } = startAccountAndTrackLifecycle({
48+
startAccount: nextcloudTalkPlugin.gateway!.startAccount!,
49+
account: buildAccount(),
5450
});
55-
await vi.waitFor(() => {
56-
expect(hoisted.monitorNextcloudTalkProvider).toHaveBeenCalledOnce();
51+
await expectStopPendingUntilAbort({
52+
waitForStarted: () =>
53+
vi.waitFor(() => {
54+
expect(hoisted.monitorNextcloudTalkProvider).toHaveBeenCalledOnce();
55+
}),
56+
isSettled,
57+
abort,
58+
task,
59+
stop,
5760
});
58-
expect(settled).toBe(false);
59-
expect(stop).not.toHaveBeenCalled();
60-
61-
abort.abort();
62-
await task;
63-
64-
expect(stop).toHaveBeenCalledOnce();
6561
});
6662

6763
it("stops immediately when startAccount receives an already-aborted signal", async () => {
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import type { ChannelAccountSnapshot, ChannelGatewayContext } from "openclaw/plugin-sdk/test-utils";
2+
import { expect, vi } from "vitest";
3+
import { createStartAccountContext } from "./start-account-context.js";
4+
5+
export function startAccountAndTrackLifecycle<TAccount extends { accountId: string }>(params: {
6+
startAccount: (ctx: ChannelGatewayContext<TAccount>) => Promise<unknown>;
7+
account: TAccount;
8+
}) {
9+
const patches: ChannelAccountSnapshot[] = [];
10+
const abort = new AbortController();
11+
const task = params.startAccount(
12+
createStartAccountContext({
13+
account: params.account,
14+
abortSignal: abort.signal,
15+
statusPatchSink: (next) => patches.push({ ...next }),
16+
}),
17+
);
18+
let settled = false;
19+
void task.then(() => {
20+
settled = true;
21+
});
22+
return {
23+
abort,
24+
patches,
25+
task,
26+
isSettled: () => settled,
27+
};
28+
}
29+
30+
export async function abortStartedAccount(params: {
31+
abort: AbortController;
32+
task: Promise<unknown>;
33+
}) {
34+
params.abort.abort();
35+
await params.task;
36+
}
37+
38+
export async function expectPendingUntilAbort(params: {
39+
waitForStarted: () => Promise<void>;
40+
isSettled: () => boolean;
41+
abort: AbortController;
42+
task: Promise<unknown>;
43+
assertBeforeAbort?: () => void;
44+
assertAfterAbort?: () => void;
45+
}) {
46+
await params.waitForStarted();
47+
expect(params.isSettled()).toBe(false);
48+
params.assertBeforeAbort?.();
49+
await abortStartedAccount({ abort: params.abort, task: params.task });
50+
params.assertAfterAbort?.();
51+
}
52+
53+
export async function expectStopPendingUntilAbort(params: {
54+
waitForStarted: () => Promise<void>;
55+
isSettled: () => boolean;
56+
abort: AbortController;
57+
task: Promise<unknown>;
58+
stop: ReturnType<typeof vi.fn>;
59+
}) {
60+
await expectPendingUntilAbort({
61+
waitForStarted: params.waitForStarted,
62+
isSettled: params.isSettled,
63+
abort: params.abort,
64+
task: params.task,
65+
assertBeforeAbort: () => {
66+
expect(params.stop).not.toHaveBeenCalled();
67+
},
68+
assertAfterAbort: () => {
69+
expect(params.stop).toHaveBeenCalledOnce();
70+
},
71+
});
72+
}

extensions/zalo/src/channel.startup.test.ts

Lines changed: 17 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import type { ChannelAccountSnapshot } from "openclaw/plugin-sdk/zalo";
22
import { afterEach, describe, expect, it, vi } from "vitest";
3-
import { createStartAccountContext } from "../../test-utils/start-account-context.js";
3+
import {
4+
expectPendingUntilAbort,
5+
startAccountAndTrackLifecycle,
6+
} from "../../test-utils/start-account-lifecycle.js";
47
import type { ResolvedZaloAccount } from "./accounts.js";
58

69
const hoisted = vi.hoisted(() => ({
@@ -57,37 +60,28 @@ describe("zaloPlugin gateway.startAccount", () => {
5760
}),
5861
);
5962

60-
const patches: ChannelAccountSnapshot[] = [];
61-
const abort = new AbortController();
62-
const task = zaloPlugin.gateway!.startAccount!(
63-
createStartAccountContext({
64-
account: buildAccount(),
65-
abortSignal: abort.signal,
66-
statusPatchSink: (next) => patches.push({ ...next }),
67-
}),
68-
);
69-
70-
let settled = false;
71-
void task.then(() => {
72-
settled = true;
63+
const { abort, patches, task, isSettled } = startAccountAndTrackLifecycle({
64+
startAccount: zaloPlugin.gateway!.startAccount!,
65+
account: buildAccount(),
7366
});
7467

75-
await vi.waitFor(() => {
76-
expect(hoisted.probeZalo).toHaveBeenCalledOnce();
77-
expect(hoisted.monitorZaloProvider).toHaveBeenCalledOnce();
68+
await expectPendingUntilAbort({
69+
waitForStarted: () =>
70+
vi.waitFor(() => {
71+
expect(hoisted.probeZalo).toHaveBeenCalledOnce();
72+
expect(hoisted.monitorZaloProvider).toHaveBeenCalledOnce();
73+
}),
74+
isSettled,
75+
abort,
76+
task,
7877
});
7978

80-
expect(settled).toBe(false);
8179
expect(patches).toContainEqual(
8280
expect.objectContaining({
8381
accountId: "default",
8482
}),
8583
);
86-
87-
abort.abort();
88-
await task;
89-
90-
expect(settled).toBe(true);
84+
expect(isSettled()).toBe(true);
9185
expect(hoisted.monitorZaloProvider).toHaveBeenCalledWith(
9286
expect.objectContaining({
9387
token: "test-token",

0 commit comments

Comments
 (0)