import { afterAll, beforeEach, describe, expect, it } from
"vitest" ;
import type { OpenClawConfig } from
"../../config/config.js" ;
import { resetPluginRuntimeStateForTest, setActivePluginRegistry } from
"../../plugins/runtime.js" ;
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from
"../../routing/session-key.js" ;
import {
createChannelTestPluginBase,
createTestRegistry,
} from
"../../test-utils/channel-plugins.js" ;
import {
applySetupAccountConfigPatch,
clearSetupPromotionRuntimeModuleCache,
createEnvPatchedAccountSetupAdapter,
createPatchedAccountSetupAdapter,
moveSingleAccountChannelSectionToDefaultAccount,
prepareScopedSetupConfig,
} from
"./setup-helpers.js" ;
function asConfig(value: unknown): OpenClawConfig {
return value as OpenClawConfig;
}
const matrixSingleAccountKeysToMove = [
"allowBots" ,
"deviceId" ,
"deviceName" ,
"encryption" ,
] as
const ;
const matrixNamedAccountPromotionKeys = [
"accessToken" ,
"deviceId" ,
"deviceName" ,
"encryption" ,
"homeserver" ,
"userId" ,
] as
const ;
const telegramSingleAccountKeysToMove = [
"streaming" ] as
const ;
function resolveMatrixSingleAccountPromotionTarget(params: {
channel: { defaultAccount?: string; accounts?: Record<string, unknown> };
}): string {
const accounts = params.channel.accounts ?? {};
const normalizedDefaultAccount = params.channel.defaultAccount?.trim()
? normalizeAccountId(params.channel.defaultAccount)
: undefined;
if (normalizedDefaultAccount) {
return (
Object.keys(accounts).find(
(accountId) => normalizeAccountId(accountId) === normalizedDefaultAccount,
) ?? DEFAULT_ACCOUNT_ID
);
}
const namedAccounts = Object.keys(accounts).filter(
Boolean );
return namedAccounts.length ===
1 ? namedAccounts[
0 ] : DEFAULT_ACCOUNT_ID;
}
beforeEach(() => {
resetPluginRuntimeStateForTest();
setActivePluginRegistry(
createTestRegistry([
{
pluginId:
"matrix" ,
source:
"test" ,
plugin: {
...createChannelTestPluginBase({ id:
"matrix" , label:
"Matrix" }),
setup: {
singleAccountKeysToMove: matrixSingleAccountKeysToMove,
namedAccountPromotionKeys: matrixNamedAccountPromotionKeys,
resolveSingleAccountPromotionTarget: resolveMatrixSingleAccountPromotionTarget,
},
},
},
{
pluginId:
"telegram" ,
source:
"test" ,
plugin: {
...createChannelTestPluginBase({ id:
"telegram" , label:
"Telegram" }),
setup: {
singleAccountKeysToMove: telegramSingleAccountKeysToMove,
},
},
},
]),
);
});
afterAll(() => {
clearSetupPromotionRuntimeModuleCache();
resetPluginRuntimeStateForTest();
});
describe(
"applySetupAccountConfigPatch" , () => {
it(
"patches top-level config for default account and enables channel" , () => {
const next = applySetupAccountConfigPatch({
cfg: asConfig({
channels: {
"demo-setup" : {
webhookPath:
"/old" ,
enabled:
false ,
},
},
}),
channelKey:
"demo-setup" ,
accountId: DEFAULT_ACCOUNT_ID,
patch: { webhookPath:
"/new" , botToken:
"tok" },
});
expect(next.channels?.[
"demo-setup" ]).toMatchObject({
enabled:
true ,
webhookPath:
"/new" ,
botToken:
"tok" ,
});
});
it(
"patches named account config and preserves existing account enabled flag" , () => {
const next = applySetupAccountConfigPatch({
cfg: asConfig({
channels: {
"demo-setup" : {
enabled:
false ,
accounts: {
work: { botToken:
"old" , enabled:
false },
},
},
},
}),
channelKey:
"demo-setup" ,
accountId:
"work" ,
patch: { botToken:
"new" },
});
expect(next.channels?.[
"demo-setup" ]).toMatchObject({
enabled:
true ,
accounts: {
work: { enabled:
false , botToken:
"new" },
},
});
});
it(
"normalizes account id and preserves other accounts" , () => {
const next = applySetupAccountConfigPatch({
cfg: asConfig({
channels: {
"demo-setup" : {
accounts: {
personal: { botToken:
"personal-token" },
},
},
},
}),
channelKey:
"demo-setup" ,
accountId:
"Work Team" ,
patch: { botToken:
"work-token" },
});
expect(next.channels?.[
"demo-setup" ]).toMatchObject({
accounts: {
personal: { botToken:
"personal-token" },
"work-team" : { enabled:
true , botToken:
"work-token" },
},
});
});
});
describe(
"createPatchedAccountSetupAdapter" , () => {
it(
"stores default-account patch at channel root" , () => {
const adapter = createPatchedAccountSetupAdapter({
channelKey:
"demo-setup" ,
buildPatch: (input) => ({ botToken: input.token }),
});
const next = adapter.applyAccountConfig({
cfg: asConfig({ channels: {
"demo-setup" : { enabled:
false } } }),
accountId: DEFAULT_ACCOUNT_ID,
input: { name:
"Personal" , token:
"tok" },
});
expect(next.channels?.[
"demo-setup" ]).toMatchObject({
enabled:
true ,
name:
"Personal" ,
botToken:
"tok" ,
});
});
it(
"migrates base name into the default account before patching a named account" , () => {
const adapter = createPatchedAccountSetupAdapter({
channelKey:
"demo-setup" ,
buildPatch: (input) => ({ botToken: input.token }),
});
const next = adapter.applyAccountConfig({
cfg: asConfig({
channels: {
"demo-setup" : {
name:
"Personal" ,
accounts: {
work: { botToken:
"old" },
},
},
},
}),
accountId:
"Work Team" ,
input: { name:
"Work" , token:
"new" },
});
expect(next.channels?.[
"demo-setup" ]).toMatchObject({
accounts: {
default : { name:
"Personal" },
work: { botToken:
"old" },
"work-team" : { enabled:
true , name:
"Work" , botToken:
"new" },
},
});
expect(next.channels?.[
"demo-setup" ]).not.toHaveProperty(
"name" );
});
it(
"can store the default account in accounts.default" , () => {
const adapter = createPatchedAccountSetupAdapter({
channelKey:
"demo-accounts" ,
alwaysUseAccounts:
true ,
buildPatch: (input) => ({ authDir: input.authDir }),
});
const next = adapter.applyAccountConfig({
cfg: asConfig({ channels: {
"demo-accounts" : {} } }),
accountId: DEFAULT_ACCOUNT_ID,
input: { name:
"Phone" , authDir:
"/tmp/auth" },
});
expect(next.channels?.[
"demo-accounts" ]).toMatchObject({
accounts: {
default : {
enabled:
true ,
name:
"Phone" ,
authDir:
"/tmp/auth" ,
},
},
});
expect(next.channels?.[
"demo-accounts" ]).not.toHaveProperty(
"enabled" );
expect(next.channels?.[
"demo-accounts" ]).not.toHaveProperty(
"authDir" );
});
});
describe(
"moveSingleAccountChannelSectionToDefaultAccount" , () => {
it(
"moves Matrix allowBots into the promoted default account" , () => {
const next = moveSingleAccountChannelSectionToDefaultAccount({
cfg: asConfig({
channels: {
matrix: {
homeserver:
"https://matrix.example.org ",
userId:
"@bot:example.org" ,
accessToken:
"token" ,
allowBots:
"mentions" ,
},
},
}),
channelKey:
"matrix" ,
});
expect(next.channels?.matrix).toMatchObject({
accounts: {
default : {
homeserver:
"https://matrix.example.org ",
userId:
"@bot:example.org" ,
accessToken:
"token" ,
allowBots:
"mentions" ,
},
},
});
expect(next.channels?.matrix?.allowBots).toBeUndefined();
});
it(
"promotes legacy Matrix keys into the sole named account when defaultAccount is unset" , ()
=> {
const next = moveSingleAccountChannelSectionToDefaultAccount({
cfg: asConfig({
channels: {
matrix: {
homeserver: "https://matrix.example.org ",
userId: "@bot:example.org" ,
accessToken: "token" ,
accounts: {
main: {
enabled: true ,
},
},
},
},
}),
channelKey: "matrix" ,
});
expect(next.channels?.matrix).toMatchObject({
accounts: {
main: {
enabled: true ,
homeserver: "https://matrix.example.org ",
userId: "@bot:example.org" ,
accessToken: "token" ,
},
},
});
expect(next.channels?.matrix?.accounts?.default ).toBeUndefined();
expect(next.channels?.matrix?.homeserver).toBeUndefined();
expect(next.channels?.matrix?.userId).toBeUndefined();
expect(next.channels?.matrix?.accessToken).toBeUndefined();
});
it("promotes legacy Matrix keys into an existing non-canonical default account key" , () => {
const next = moveSingleAccountChannelSectionToDefaultAccount({
cfg: asConfig({
channels: {
matrix: {
defaultAccount: "ops" ,
homeserver: "https://matrix.example.org ",
userId: "@ops:example.org" ,
accessToken: "token" ,
accounts: {
Ops: {
enabled: true ,
},
},
},
},
}),
channelKey: "matrix" ,
});
expect(next.channels?.matrix).toMatchObject({
defaultAccount: "ops" ,
accounts: {
Ops: {
enabled: true ,
homeserver: "https://matrix.example.org ",
userId: "@ops:example.org" ,
accessToken: "token" ,
},
},
});
expect(next.channels?.matrix?.accounts?.ops).toBeUndefined();
expect(next.channels?.matrix?.accounts?.default ).toBeUndefined();
expect(next.channels?.matrix?.homeserver).toBeUndefined();
expect(next.channels?.matrix?.userId).toBeUndefined();
expect(next.channels?.matrix?.accessToken).toBeUndefined();
});
});
describe("createEnvPatchedAccountSetupAdapter" , () => {
it("rejects env mode for named accounts and requires credentials otherwise" , () => {
const adapter = createEnvPatchedAccountSetupAdapter({
channelKey: "demo-env" ,
defaultAccountOnlyEnvError: "env only on default" ,
missingCredentialError: "token required" ,
hasCredentials: (input) => Boolean (input.token || input.tokenFile),
buildPatch: (input) => ({ token: input.token }),
});
expect(
adapter.validateInput?.({
cfg: asConfig({}),
accountId: "work" ,
input: { useEnv: true },
}),
).toBe("env only on default" );
expect(
adapter.validateInput?.({
cfg: asConfig({}),
accountId: DEFAULT_ACCOUNT_ID,
input: {},
}),
).toBe("token required" );
expect(
adapter.validateInput?.({
cfg: asConfig({}),
accountId: DEFAULT_ACCOUNT_ID,
input: { token: "tok" },
}),
).toBeNull();
});
});
describe("prepareScopedSetupConfig" , () => {
it("stores the name and migrates it for named accounts when requested" , () => {
const next = prepareScopedSetupConfig({
cfg: asConfig({
channels: {
"demo-scoped" : {
name: "Personal" ,
},
},
}),
channelKey: "demo-scoped" ,
accountId: "Work Team" ,
name: "Work" ,
migrateBaseName: true ,
});
expect(next.channels?.["demo-scoped" ]).toMatchObject({
accounts: {
default : { name: "Personal" },
"work-team" : { name: "Work" },
},
});
expect(next.channels?.["demo-scoped" ]).not.toHaveProperty("name" );
});
it("keeps the base shape for the default account when migration is disabled" , () => {
const next = prepareScopedSetupConfig({
cfg: asConfig({ channels: { "demo-base" : { enabled: true } } }),
channelKey: "demo-base" ,
accountId: DEFAULT_ACCOUNT_ID,
name: "Libera" ,
});
expect(next.channels?.["demo-base" ]).toMatchObject({
enabled: true ,
name: "Libera" ,
});
});
});
Messung V0.5 in Prozent C=95 H=95 G=94
¤ Dauer der Verarbeitung: 0.20 Sekunden
(vorverarbeitet am 2026-06-05)
¤
*© Formatika GbR, Deutschland