import { describe, expect, it, vi } from "vitest" ;
import {
createPluginSetupWizardConfigure,
createTestWizardPrompter,
runSetupWizardConfigure,
} from "../../../test/helpers/plugins/setup-wizard.js" ;
import type { OpenClawConfig } from "../runtime-api.js" ;
import "./zalo-js.test-mocks.js" ;
import { zalouserSetupWizard } from "./setup-surface.js" ;
import { zalouserSetupPlugin } from "./setup-test-helpers.js" ;
const zalouserConfigure = createPluginSetupWizardConfigure(zalouserSetupPlugin);
async function runSetup(params: {
cfg?: OpenClawConfig;
prompter: ReturnType<typeof createTestWizardPrompter>;
options?: Record<string, unknown>;
forceAllowFrom?: boolean ;
}) {
return await runSetupWizardConfigure({
configure: zalouserConfigure,
cfg: params.cfg,
prompter: params.prompter,
options: params.options,
forceAllowFrom: params.forceAllowFrom,
});
}
describe("zalouser setup wizard" , () => {
function expectEnabledDefaultSetup(
result: Awaited<ReturnType<typeof runSetup>>,
dmPolicy?: "pairing" | "allowlist" ,
) {
expect(result.accountId).toBe("default" );
expect(result.cfg.channels?.zalouser?.enabled).toBe(true );
expect(result.cfg.plugins?.entries?.zalouser?.enabled).toBe(true );
if (dmPolicy) {
expect(result.cfg.channels?.zalouser?.dmPolicy).toBe(dmPolicy);
}
}
function createQuickstartPrompter(params?: {
note?: ReturnType<typeof createTestWizardPrompter>["note" ];
seen?: string[];
dmPolicy?: "pairing" | "allowlist" ;
groupAccess?: boolean ;
groupPolicy?: "allowlist" ;
textByMessage?: Record<string, string>;
}) {
const select = vi.fn(
async ({ message, options }: { message: string; options: Array<{ value: string }> }) => {
const first = options[0 ];
if (!first) {
throw new Error("no options" );
}
params?.seen?.push(message);
if (message === "Zalo Personal DM policy" && params?.dmPolicy) {
return params.dmPolicy;
}
if (message === "Zalo groups access" && params?.groupPolicy) {
return params.groupPolicy;
}
return first.value;
},
) as ReturnType<typeof createTestWizardPrompter>["select" ];
const text = vi.fn(
async ({ message }: { message: string }) => params?.textByMessage?.[message] ?? "" ,
) as ReturnType<typeof createTestWizardPrompter>["text" ];
return createTestWizardPrompter({
...(params?.note ? { note: params.note } : {}),
confirm: vi.fn(async ({ message }: { message: string }) => {
params?.seen?.push(message);
if (message === "Login via QR code now?" ) {
return false ;
}
if (message === "Configure Zalo groups access?" ) {
return params?.groupAccess ?? false ;
}
return false ;
}),
select,
text,
});
}
it("enables the account without forcing QR login" , async () => {
const prompter = createTestWizardPrompter({
confirm: vi.fn(async ({ message }: { message: string }) => {
if (message === "Login via QR code now?" ) {
return false ;
}
if (message === "Configure Zalo groups access?" ) {
return false ;
}
return false ;
}),
});
const result = await runSetup({ prompter });
expect(result.accountId).toBe("default" );
expect(result.cfg.channels?.zalouser?.enabled).toBe(true );
expect(result.cfg.plugins?.entries?.zalouser?.enabled).toBe(true );
});
it("prompts DM policy before group access in quickstart" , async () => {
const seen: string[] = [];
const prompter = createQuickstartPrompter({ seen, dmPolicy: "pairing" });
const result = await runSetup({
prompter,
options: { quickstartDefaults: true },
});
expectEnabledDefaultSetup(result, "pairing" );
expect(seen.indexOf("Zalo Personal DM policy" )).toBeGreaterThanOrEqual(0 );
expect(seen.indexOf("Configure Zalo groups access?" )).toBeGreaterThanOrEqual(0 );
expect(seen.indexOf("Zalo Personal DM policy" )).toBeLessThan(
seen.indexOf("Configure Zalo groups access?" ),
);
});
it("allows an empty quickstart DM allowlist with a warning" , async () => {
const note = vi.fn(async (_message: string, _title?: string) => {});
const prompter = createQuickstartPrompter({
note,
dmPolicy: "allowlist" ,
textByMessage: {
"Zalouser allowFrom (name or user id)" : "" ,
},
});
const result = await runSetup({
prompter,
options: { quickstartDefaults: true },
});
expectEnabledDefaultSetup(result, "allowlist" );
expect(result.cfg.channels?.zalouser?.allowFrom).toEqual([]);
expect(
note.mock.calls.some(([message]) => message.includes("No DM allowlist entries added yet." )),
).toBe(true );
});
it("allows an empty group allowlist with a warning" , async () => {
const note = vi.fn(async (_message: string, _title?: string) => {});
const prompter = createQuickstartPrompter({
note,
groupAccess: true ,
groupPolicy: "allowlist" ,
textByMessage: {
"Zalo groups allowlist (comma-separated)" : "" ,
},
});
const result = await runSetup({ prompter });
expect(result.cfg.channels?.zalouser?.groupPolicy).toBe("allowlist" );
expect(result.cfg.channels?.zalouser?.groups).toEqual({});
expect(
note.mock.calls.some(([message]) =>
message.includes("No group allowlist entries added yet." ),
),
).toBe(true );
});
it("writes canonical enabled entries for configured groups" , async () => {
const prompter = createQuickstartPrompter({
groupAccess: true ,
groupPolicy: "allowlist" ,
textByMessage: {
"Zalo groups allowlist (comma-separated)" : "Family, Work" ,
},
});
const result = await runSetup({ prompter });
expect(result.cfg.channels?.zalouser?.groups).toEqual({
Family: { enabled: true , requireMention: true },
Work: { enabled: true , requireMention: true },
});
});
it("preserves non-quickstart forceAllowFrom behavior" , async () => {
const note = vi.fn(async (_message: string, _title?: string) => {});
const seen: string[] = [];
const prompter = createTestWizardPrompter({
note,
confirm: vi.fn(async ({ message }: { message: string }) => {
seen.push(message);
if (message === "Login via QR code now?" ) {
return false ;
}
if (message === "Configure Zalo groups access?" ) {
return false ;
}
return false ;
}),
text: vi.fn(async ({ message }: { message: string }) => {
seen.push(message);
if (message === "Zalouser allowFrom (name or user id)" ) {
return "" ;
}
return "" ;
}) as ReturnType<typeof createTestWizardPrompter>["text" ],
});
const result = await runSetup({ prompter, forceAllowFrom: true });
expect(result.cfg.channels?.zalouser?.dmPolicy).toBe("allowlist" );
expect(result.cfg.channels?.zalouser?.allowFrom).toEqual([]);
expect(seen).not.toContain("Zalo Personal DM policy" );
expect(seen).toContain("Zalouser allowFrom (name or user id)" );
expect(
note.mock.calls.some(([message]) => message.includes("No DM allowlist entries added yet." )),
).toBe(true );
});
it("allowlists the plugin when a plugin allowlist already exists" , async () => {
const prompter = createTestWizardPrompter({
confirm: vi.fn(async ({ message }: { message: string }) => {
if (message === "Login via QR code now?" ) {
return false ;
}
if (message === "Configure Zalo groups access?" ) {
return false ;
}
return false ;
}),
});
const result = await runSetup({
cfg: {
plugins: {
allow: ["telegram" ],
},
} as OpenClawConfig,
prompter,
});
expect(result.cfg.plugins?.entries?.zalouser?.enabled).toBe(true );
expect(result.cfg.plugins?.allow).toEqual(["telegram" , "zalouser" ]);
});
it("reads the named-account DM policy instead of the channel root" , () => {
expect(
zalouserSetupWizard.dmPolicy?.getCurrent(
{
channels: {
zalouser: {
dmPolicy: "disabled" ,
accounts: {
work: {
profile: "work" ,
dmPolicy: "allowlist" ,
},
},
},
},
} as OpenClawConfig,
"work" ,
),
).toBe("allowlist" );
});
it("reports account-scoped config keys for named accounts" , () => {
expect(zalouserSetupWizard.dmPolicy?.resolveConfigKeys?.({} as OpenClawConfig, "work" )).toEqual(
{
policyKey: "channels.zalouser.accounts.work.dmPolicy" ,
allowFromKey: "channels.zalouser.accounts.work.allowFrom" ,
},
);
});
it("uses configured defaultAccount for omitted DM policy account context" , () => {
const cfg = {
channels: {
zalouser: {
defaultAccount: "work" ,
dmPolicy: "disabled" ,
allowFrom: ["123456789" ],
accounts: {
work: {
dmPolicy: "allowlist" ,
profile: "work-profile" ,
},
},
},
},
} as OpenClawConfig;
expect(zalouserSetupWizard.dmPolicy?.getCurrent(cfg)).toBe("allowlist" );
expect(zalouserSetupWizard.dmPolicy?.resolveConfigKeys?.(cfg)).toEqual({
policyKey: "channels.zalouser.accounts.work.dmPolicy" ,
allowFromKey: "channels.zalouser.accounts.work.allowFrom" ,
});
const next = zalouserSetupWizard.dmPolicy?.setPolicy(cfg, "open" );
expect(next?.channels?.zalouser?.dmPolicy).toBe("disabled" );
const workAccount = next?.channels?.zalouser?.accounts?.work as
| { dmPolicy?: string; allowFrom?: Array<string | number> }
| undefined;
expect(workAccount?.dmPolicy).toBe("open" );
});
it('writes open policy state to the named account and preserves inherited allowFrom with "*"' , () => {
const next = zalouserSetupWizard.dmPolicy?.setPolicy(
{
channels: {
zalouser: {
allowFrom: ["123456789" ],
accounts: {
work: {
profile: "work" ,
},
},
},
},
} as OpenClawConfig,
"open" ,
"work" ,
);
expect(next?.channels?.zalouser?.dmPolicy).toBeUndefined();
const workAccount = next?.channels?.zalouser?.accounts?.work as
| { dmPolicy?: string; allowFrom?: Array<string | number> }
| undefined;
expect(workAccount?.dmPolicy).toBe("open" );
expect(workAccount?.allowFrom).toEqual(["123456789" , "*" ]);
});
it("shows the account-scoped current DM policy in quickstart notes" , async () => {
const note = vi.fn(async (_message: string, _title?: string) => {});
const prompter = createQuickstartPrompter({ note, dmPolicy: "pairing" });
await runSetupWizardConfigure({
configure: zalouserConfigure,
cfg: {
channels: {
zalouser: {
dmPolicy: "disabled" ,
accounts: {
work: {
profile: "work" ,
dmPolicy: "allowlist" ,
allowFrom: ["123456789" ],
},
},
},
},
} as OpenClawConfig,
prompter,
options: { quickstartDefaults: true },
accountOverrides: { zalouser: "work" },
});
expect(
note.mock.calls.some(([message]) =>
message.includes("Current: dmPolicy=allowlist, allowFrom=123456789" ),
),
).toBe(true );
});
});
Messung V0.5 in Prozent C=100 H=96 G=97
¤ Dauer der Verarbeitung: 0.13 Sekunden
(vorverarbeitet am 2026-05-26)
¤
*© Formatika GbR, Deutschland