test: speed up nextcloud talk and zalo status tests

This commit is contained in:
Peter Steinberger
2026-04-07 13:58:36 +01:00
parent 67da64f98d
commit cc70e663f1
4 changed files with 120 additions and 116 deletions

View File

@@ -7,16 +7,6 @@ import {
} from "../../../test/helpers/plugins/start-account-lifecycle.js";
import type { ResolvedNextcloudTalkAccount } from "./accounts.js";
vi.mock("../../../test/helpers/config/bundled-channel-config-runtime.js", () => ({
getBundledChannelRuntimeMap: () => new Map(),
getBundledChannelConfigSchemaMap: () => new Map(),
}));
vi.mock("../../../src/channels/plugins/bundled.js", () => ({
bundledChannelPlugins: [],
bundledChannelSetupPlugins: [],
}));
const hoisted = vi.hoisted(() => ({
monitorNextcloudTalkProvider: vi.fn(),
}));
@@ -29,7 +19,7 @@ vi.mock("./monitor.js", async () => {
};
});
const { nextcloudTalkPlugin } = await import("./channel.js");
const { nextcloudTalkGatewayAdapter } = await import("./gateway.js");
function buildAccount(): ResolvedNextcloudTalkAccount {
return {
@@ -54,7 +44,7 @@ function mockStartedMonitor() {
}
function startNextcloudAccount(abortSignal?: AbortSignal) {
return nextcloudTalkPlugin.gateway!.startAccount!(
return nextcloudTalkGatewayAdapter.startAccount(
createStartAccountContext({
account: buildAccount(),
abortSignal,
@@ -70,7 +60,7 @@ describe("nextcloud-talk startAccount lifecycle", () => {
it("keeps startAccount pending until abort, then stops the monitor", async () => {
const stop = mockStartedMonitor();
const { abort, task, isSettled } = startAccountAndTrackLifecycle({
startAccount: nextcloudTalkPlugin.gateway!.startAccount!,
startAccount: nextcloudTalkGatewayAdapter.startAccount,
account: buildAccount(),
});
await expectStopPendingUntilAbort({

View File

@@ -6,13 +6,11 @@ import {
createScopedDmSecurityResolver,
} from "openclaw/plugin-sdk/channel-config-helpers";
import { createChatChannelPlugin } from "openclaw/plugin-sdk/channel-core";
import { createAccountStatusSink } from "openclaw/plugin-sdk/channel-lifecycle";
import {
createLoggedPairingApprovalNotifier,
createPairingPrefixStripper,
} from "openclaw/plugin-sdk/channel-pairing";
import { createAllowlistProviderRouteAllowlistWarningCollector } from "openclaw/plugin-sdk/channel-policy";
import { runStoppablePassiveMonitor } from "openclaw/plugin-sdk/extension-shared";
import {
buildWebhookChannelStatusSummary,
createComputedAccountStatusAdapter,
@@ -25,16 +23,10 @@ import {
type ResolvedNextcloudTalkAccount,
} from "./accounts.js";
import { nextcloudTalkApprovalAuth } from "./approval-auth.js";
import {
buildChannelConfigSchema,
clearAccountEntryFields,
DEFAULT_ACCOUNT_ID,
type ChannelPlugin,
type OpenClawConfig,
} from "./channel-api.js";
import { buildChannelConfigSchema, DEFAULT_ACCOUNT_ID, type ChannelPlugin } from "./channel-api.js";
import { NextcloudTalkConfigSchema } from "./config-schema.js";
import { nextcloudTalkDoctor } from "./doctor.js";
import { monitorNextcloudTalkProvider } from "./monitor.js";
import { nextcloudTalkGatewayAdapter } from "./gateway.js";
import {
looksLikeNextcloudTalkTargetId,
normalizeNextcloudTalkMessagingTarget,
@@ -197,97 +189,7 @@ export const nextcloudTalkPlugin: ChannelPlugin<ResolvedNextcloudTalkAccount> =
},
}),
}),
gateway: {
startAccount: async (ctx) => {
const account = ctx.account;
if (!account.secret || !account.baseUrl) {
throw new Error(
`Nextcloud Talk not configured for account "${account.accountId}" (missing secret or baseUrl)`,
);
}
ctx.log?.info(`[${account.accountId}] starting Nextcloud Talk webhook server`);
const statusSink = createAccountStatusSink({
accountId: ctx.accountId,
setStatus: ctx.setStatus,
});
await runStoppablePassiveMonitor({
abortSignal: ctx.abortSignal,
start: async () =>
await monitorNextcloudTalkProvider({
accountId: account.accountId,
config: ctx.cfg as CoreConfig,
runtime: ctx.runtime,
abortSignal: ctx.abortSignal,
statusSink,
}),
});
},
logoutAccount: async ({ accountId, cfg }) => {
const nextCfg = { ...cfg } as OpenClawConfig;
const nextSection = cfg.channels?.["nextcloud-talk"]
? { ...cfg.channels["nextcloud-talk"] }
: undefined;
let cleared = false;
let changed = false;
if (nextSection) {
if (accountId === DEFAULT_ACCOUNT_ID && nextSection.botSecret) {
delete nextSection.botSecret;
cleared = true;
changed = true;
}
const accountCleanup = clearAccountEntryFields({
accounts: nextSection.accounts as Record<string, object> | undefined,
accountId,
fields: ["botSecret"],
});
if (accountCleanup.changed) {
changed = true;
if (accountCleanup.cleared) {
cleared = true;
}
if (accountCleanup.nextAccounts) {
nextSection.accounts = accountCleanup.nextAccounts as Record<string, unknown>;
} else {
delete nextSection.accounts;
}
}
}
if (changed) {
if (nextSection && Object.keys(nextSection).length > 0) {
nextCfg.channels = { ...nextCfg.channels, "nextcloud-talk": nextSection };
} else {
const nextChannels = { ...nextCfg.channels } as Record<string, unknown>;
delete nextChannels["nextcloud-talk"];
if (Object.keys(nextChannels).length > 0) {
nextCfg.channels = nextChannels as OpenClawConfig["channels"];
} else {
delete nextCfg.channels;
}
}
}
const resolved = resolveNextcloudTalkAccount({
cfg: changed ? (nextCfg as CoreConfig) : (cfg as CoreConfig),
accountId,
});
const loggedOut = resolved.secretSource === "none";
if (changed) {
await getNextcloudTalkRuntime().config.writeConfigFile(nextCfg);
}
return {
cleared,
envSecret: Boolean(process.env.NEXTCLOUD_TALK_BOT_SECRET?.trim()),
loggedOut,
};
},
},
gateway: nextcloudTalkGatewayAdapter,
},
pairing: {
text: {

View File

@@ -0,0 +1,106 @@
import { createAccountStatusSink } from "openclaw/plugin-sdk/channel-lifecycle";
import { runStoppablePassiveMonitor } from "openclaw/plugin-sdk/extension-shared";
import { resolveNextcloudTalkAccount } from "./accounts.js";
import {
clearAccountEntryFields,
DEFAULT_ACCOUNT_ID,
type ChannelPlugin,
type OpenClawConfig,
} from "./channel-api.js";
import { monitorNextcloudTalkProvider } from "./monitor.js";
import { getNextcloudTalkRuntime } from "./runtime.js";
import type { CoreConfig, ResolvedNextcloudTalkAccount } from "./types.js";
export const nextcloudTalkGatewayAdapter: NonNullable<
ChannelPlugin<ResolvedNextcloudTalkAccount>["gateway"]
> = {
startAccount: async (ctx) => {
const account = ctx.account;
if (!account.secret || !account.baseUrl) {
throw new Error(
`Nextcloud Talk not configured for account "${account.accountId}" (missing secret or baseUrl)`,
);
}
ctx.log?.info(`[${account.accountId}] starting Nextcloud Talk webhook server`);
const statusSink = createAccountStatusSink({
accountId: ctx.accountId,
setStatus: ctx.setStatus,
});
await runStoppablePassiveMonitor({
abortSignal: ctx.abortSignal,
start: async () =>
await monitorNextcloudTalkProvider({
accountId: account.accountId,
config: ctx.cfg as CoreConfig,
runtime: ctx.runtime,
abortSignal: ctx.abortSignal,
statusSink,
}),
});
},
logoutAccount: async ({ accountId, cfg }) => {
const nextCfg = { ...cfg } as OpenClawConfig;
const nextSection = cfg.channels?.["nextcloud-talk"]
? { ...cfg.channels["nextcloud-talk"] }
: undefined;
let cleared = false;
let changed = false;
if (nextSection) {
if (accountId === DEFAULT_ACCOUNT_ID && nextSection.botSecret) {
delete nextSection.botSecret;
cleared = true;
changed = true;
}
const accountCleanup = clearAccountEntryFields({
accounts: nextSection.accounts as Record<string, object> | undefined,
accountId,
fields: ["botSecret"],
});
if (accountCleanup.changed) {
changed = true;
if (accountCleanup.cleared) {
cleared = true;
}
if (accountCleanup.nextAccounts) {
nextSection.accounts = accountCleanup.nextAccounts as Record<string, unknown>;
} else {
delete nextSection.accounts;
}
}
}
if (changed) {
if (nextSection && Object.keys(nextSection).length > 0) {
nextCfg.channels = { ...nextCfg.channels, "nextcloud-talk": nextSection };
} else {
const nextChannels = { ...nextCfg.channels } as Record<string, unknown>;
delete nextChannels["nextcloud-talk"];
if (Object.keys(nextChannels).length > 0) {
nextCfg.channels = nextChannels as OpenClawConfig["channels"];
} else {
delete nextCfg.channels;
}
}
}
const resolved = resolveNextcloudTalkAccount({
cfg: changed ? (nextCfg as CoreConfig) : (cfg as CoreConfig),
accountId,
});
const loggedOut = resolved.secretSource === "none";
if (changed) {
await getNextcloudTalkRuntime().config.writeConfigFile(nextCfg);
}
return {
cleared,
envSecret: Boolean(process.env.NEXTCLOUD_TALK_BOT_SECRET?.trim()),
loggedOut,
};
},
};

View File

@@ -1,9 +1,15 @@
import { describe, expect, it } from "vitest";
import { createPluginSetupWizardStatus } from "../../../test/helpers/plugins/setup-wizard.js";
import type { OpenClawConfig } from "../runtime-api.js";
import { zaloPlugin } from "./channel.js";
import { zaloSetupWizard } from "./setup-surface.js";
const zaloGetStatus = createPluginSetupWizardStatus(zaloPlugin);
const zaloGetStatus = createPluginSetupWizardStatus({
id: "zalo",
meta: {
label: "Zalo",
},
setupWizard: zaloSetupWizard,
} as never);
describe("zalo setup wizard status", () => {
it("treats SecretRef botToken as configured", async () => {