refactor(test): dedupe startup channel test helpers

This commit is contained in:
Peter Steinberger
2026-03-22 03:41:25 +00:00
parent 36feecf018
commit 89bc66feef
4 changed files with 101 additions and 97 deletions

View File

@@ -46,6 +46,31 @@ function createCfg(): OpenClawConfig {
} as OpenClawConfig;
}
function resolveAccount(cfg: OpenClawConfig): ResolvedDiscordAccount {
return discordPlugin.config.resolveAccount(cfg, "default") as ResolvedDiscordAccount;
}
function startDiscordAccount(cfg: OpenClawConfig) {
return discordPlugin.gateway!.startAccount!(
createStartAccountContext({
account: resolveAccount(cfg),
cfg,
runtime: createRuntimeEnv(),
}),
);
}
function installDiscordRuntime(discord: Record<string, unknown>) {
setDiscordRuntime({
channel: {
discord,
},
logging: {
shouldLogVerbose: () => false,
},
} as unknown as PluginRuntime);
}
afterEach(() => {
probeDiscordMock.mockReset();
monitorDiscordProviderMock.mockReset();
@@ -87,16 +112,9 @@ describe("discordPlugin outbound", () => {
const runtimeProbeDiscord = vi.fn(async () => {
throw new Error("runtime Discord probe should not be used");
});
setDiscordRuntime({
channel: {
discord: {
probeDiscord: runtimeProbeDiscord,
},
},
logging: {
shouldLogVerbose: () => false,
},
} as unknown as PluginRuntime);
installDiscordRuntime({
probeDiscord: runtimeProbeDiscord,
});
probeDiscordMock.mockResolvedValue({
ok: true,
bot: { username: "Bob" },
@@ -111,7 +129,7 @@ describe("discordPlugin outbound", () => {
});
const cfg = createCfg();
const account = discordPlugin.config.resolveAccount(cfg, "default");
const account = resolveAccount(cfg);
await discordPlugin.status!.probeAccount!({
account,
@@ -132,17 +150,10 @@ describe("discordPlugin outbound", () => {
const runtimeMonitorDiscordProvider = vi.fn(async () => {
throw new Error("runtime Discord monitor should not be used");
});
setDiscordRuntime({
channel: {
discord: {
probeDiscord: runtimeProbeDiscord,
monitorDiscordProvider: runtimeMonitorDiscordProvider,
},
},
logging: {
shouldLogVerbose: () => false,
},
} as unknown as PluginRuntime);
installDiscordRuntime({
probeDiscord: runtimeProbeDiscord,
monitorDiscordProvider: runtimeMonitorDiscordProvider,
});
probeDiscordMock.mockResolvedValue({
ok: true,
bot: { username: "Bob" },
@@ -158,13 +169,7 @@ describe("discordPlugin outbound", () => {
monitorDiscordProviderMock.mockResolvedValue(undefined);
const cfg = createCfg();
await discordPlugin.gateway!.startAccount!(
createStartAccountContext({
account: discordPlugin.config.resolveAccount(cfg, "default") as ResolvedDiscordAccount,
cfg,
runtime: createRuntimeEnv(),
}),
);
await startDiscordAccount(cfg);
expect(probeDiscordMock).toHaveBeenCalledWith("discord-token", 2500, {
includeApplication: true,

View File

@@ -39,53 +39,50 @@ function createAccount(params: { token: string; secret: string }): ResolvedLineA
};
}
function startLineAccount(params: { account: ResolvedLineAccount; abortSignal?: AbortSignal }) {
const { runtime, monitorLineProvider } = createRuntime();
setLineRuntime(runtime);
return {
monitorLineProvider,
task: linePlugin.gateway!.startAccount!(
createStartAccountContext({
account: params.account,
abortSignal: params.abortSignal,
runtime: createRuntimeEnv(),
}),
),
};
}
describe("linePlugin gateway.startAccount", () => {
it("fails startup when channel secret is missing", async () => {
const { runtime, monitorLineProvider } = createRuntime();
setLineRuntime(runtime);
const { monitorLineProvider, task } = startLineAccount({
account: createAccount({ token: "token", secret: " " }),
});
await expect(
linePlugin.gateway!.startAccount!(
createStartAccountContext({
account: createAccount({ token: "token", secret: " " }),
runtime: createRuntimeEnv(),
}),
),
).rejects.toThrow(
await expect(task).rejects.toThrow(
'LINE webhook mode requires a non-empty channel secret for account "default".',
);
expect(monitorLineProvider).not.toHaveBeenCalled();
});
it("fails startup when channel access token is missing", async () => {
const { runtime, monitorLineProvider } = createRuntime();
setLineRuntime(runtime);
const { monitorLineProvider, task } = startLineAccount({
account: createAccount({ token: " ", secret: "secret" }),
});
await expect(
linePlugin.gateway!.startAccount!(
createStartAccountContext({
account: createAccount({ token: " ", secret: "secret" }),
runtime: createRuntimeEnv(),
}),
),
).rejects.toThrow(
await expect(task).rejects.toThrow(
'LINE webhook mode requires a non-empty channel access token for account "default".',
);
expect(monitorLineProvider).not.toHaveBeenCalled();
});
it("starts provider when token and secret are present", async () => {
const { runtime, monitorLineProvider } = createRuntime();
setLineRuntime(runtime);
const abort = new AbortController();
const task = linePlugin.gateway!.startAccount!(
createStartAccountContext({
account: createAccount({ token: "token", secret: "secret" }),
runtime: createRuntimeEnv(),
abortSignal: abort.signal,
}),
);
const { monitorLineProvider, task } = startLineAccount({
account: createAccount({ token: "token", secret: "secret" }),
abortSignal: abort.signal,
});
await vi.waitFor(() => {
expect(monitorLineProvider).toHaveBeenCalledWith(

View File

@@ -37,14 +37,28 @@ function buildAccount(): ResolvedNextcloudTalkAccount {
};
}
function mockStartedMonitor() {
const stop = vi.fn();
hoisted.monitorNextcloudTalkProvider.mockResolvedValue({ stop });
return stop;
}
function startNextcloudAccount(abortSignal?: AbortSignal) {
return nextcloudTalkPlugin.gateway!.startAccount!(
createStartAccountContext({
account: buildAccount(),
abortSignal,
}),
);
}
describe("nextcloudTalkPlugin gateway.startAccount", () => {
afterEach(() => {
vi.clearAllMocks();
});
it("keeps startAccount pending until abort, then stops the monitor", async () => {
const stop = vi.fn();
hoisted.monitorNextcloudTalkProvider.mockResolvedValue({ stop });
const stop = mockStartedMonitor();
const { abort, task, isSettled } = startAccountAndTrackLifecycle({
startAccount: nextcloudTalkPlugin.gateway!.startAccount!,
account: buildAccount(),
@@ -59,17 +73,11 @@ describe("nextcloudTalkPlugin gateway.startAccount", () => {
});
it("stops immediately when startAccount receives an already-aborted signal", async () => {
const stop = vi.fn();
hoisted.monitorNextcloudTalkProvider.mockResolvedValue({ stop });
const stop = mockStartedMonitor();
const abort = new AbortController();
abort.abort();
await nextcloudTalkPlugin.gateway!.startAccount!(
createStartAccountContext({
account: buildAccount(),
abortSignal: abort.signal,
}),
);
await startNextcloudAccount(abort.signal);
expect(hoisted.monitorNextcloudTalkProvider).toHaveBeenCalledOnce();
expect(stop).toHaveBeenCalledOnce();

View File

@@ -59,6 +59,18 @@ function resolveAccount(cfg: OpenClawConfig, accountId: string): ResolvedTelegra
return telegramPlugin.config.resolveAccount(cfg, accountId) as ResolvedTelegramAccount;
}
function createStartTelegramContext(cfg: OpenClawConfig, accountId: string) {
return createStartAccountContext({
account: resolveAccount(cfg, accountId),
cfg,
runtime: createRuntimeEnv(),
});
}
function startTelegramAccount(cfg: OpenClawConfig, accountId: string) {
return telegramPlugin.gateway!.startAccount!(createStartTelegramContext(cfg, accountId));
}
function installGatewayRuntime(params?: { probeOk?: boolean; botUsername?: string }) {
const monitorTelegramProvider = vi
.spyOn(monitorModule, "monitorTelegramProvider")
@@ -167,9 +179,9 @@ describe("telegramPlugin groups", () => {
describe("telegramPlugin duplicate token guard", () => {
it("marks secondary account as not configured when token is shared", async () => {
const cfg = createCfg();
const alertsAccount = telegramPlugin.config.resolveAccount(cfg, "alerts");
const workAccount = telegramPlugin.config.resolveAccount(cfg, "work");
const opsAccount = telegramPlugin.config.resolveAccount(cfg, "ops");
const alertsAccount = resolveAccount(cfg, "alerts");
const workAccount = resolveAccount(cfg, "work");
const opsAccount = resolveAccount(cfg, "ops");
expect(await telegramPlugin.config.isConfigured!(alertsAccount, cfg)).toBe(true);
expect(await telegramPlugin.config.isConfigured!(workAccount, cfg)).toBe(false);
@@ -182,7 +194,7 @@ describe("telegramPlugin duplicate token guard", () => {
it("surfaces duplicate-token reason in status snapshot", async () => {
const cfg = createCfg();
const workAccount = telegramPlugin.config.resolveAccount(cfg, "work");
const workAccount = resolveAccount(cfg, "work");
const snapshot = await telegramPlugin.status!.buildAccountSnapshot!({
account: workAccount,
cfg,
@@ -201,15 +213,7 @@ describe("telegramPlugin duplicate token guard", () => {
});
const cfg = createCfg();
await expect(
telegramPlugin.gateway!.startAccount!(
createStartAccountContext({
account: resolveAccount(cfg, "work"),
cfg,
runtime: createRuntimeEnv(),
}),
),
).rejects.toThrow("Duplicate Telegram bot token");
await expect(startTelegramAccount(cfg, "work")).rejects.toThrow("Duplicate Telegram bot token");
expect(probeTelegramMock).not.toHaveBeenCalled();
expect(monitorTelegramProviderMock).not.toHaveBeenCalled();
@@ -237,13 +241,7 @@ describe("telegramPlugin duplicate token guard", () => {
webhookPort: 9876,
};
await telegramPlugin.gateway!.startAccount!(
createStartAccountContext({
account: resolveAccount(cfg, "ops"),
cfg,
runtime: createRuntimeEnv(),
}),
);
await startTelegramAccount(cfg, "ops");
expect(probeTelegramMock).toHaveBeenCalledWith("token-ops", 2500, {
accountId: "ops",
@@ -282,7 +280,7 @@ describe("telegramPlugin duplicate token guard", () => {
const cfg = createCfg();
configureOpsProxyNetwork(cfg);
const account = telegramPlugin.config.resolveAccount(cfg, "ops");
const account = resolveAccount(cfg, "ops");
await telegramPlugin.status!.probeAccount!({
account,
@@ -341,7 +339,7 @@ describe("telegramPlugin duplicate token guard", () => {
"-100123": { requireMention: false },
},
};
const account = telegramPlugin.config.resolveAccount(cfg, "ops");
const account = resolveAccount(cfg, "ops");
await telegramPlugin.status!.auditAccount!({
account,
@@ -484,7 +482,7 @@ describe("telegramPlugin duplicate token guard", () => {
const cfg = createCfg();
cfg.channels!.telegram!.accounts!.ops = {} as never;
const alertsAccount = telegramPlugin.config.resolveAccount(cfg, "alerts");
const alertsAccount = resolveAccount(cfg, "alerts");
expect(await telegramPlugin.config.isConfigured!(alertsAccount, cfg)).toBe(true);
});
@@ -496,11 +494,7 @@ describe("telegramPlugin duplicate token guard", () => {
monitorTelegramProviderMock.mockResolvedValue(undefined);
const cfg = createCfg();
const ctx = createStartAccountContext({
account: resolveAccount(cfg, "ops"),
cfg,
runtime: createRuntimeEnv(),
});
const ctx = createStartTelegramContext(cfg, "ops");
ctx.account = {
...ctx.account,
token: undefined as unknown as string,