mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-21 16:41:56 +00:00
refactor(cli): dedupe channel auth resolution flow
This commit is contained in:
129
src/cli/channel-auth.test.ts
Normal file
129
src/cli/channel-auth.test.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { DEFAULT_CHAT_CHANNEL } from "../channels/registry.js";
|
||||
import { runChannelLogin, runChannelLogout } from "./channel-auth.js";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
resolveChannelDefaultAccountId: vi.fn(),
|
||||
getChannelPlugin: vi.fn(),
|
||||
normalizeChannelId: vi.fn(),
|
||||
loadConfig: vi.fn(),
|
||||
setVerbose: vi.fn(),
|
||||
login: vi.fn(),
|
||||
logoutAccount: vi.fn(),
|
||||
resolveAccount: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../channels/plugins/helpers.js", () => ({
|
||||
resolveChannelDefaultAccountId: mocks.resolveChannelDefaultAccountId,
|
||||
}));
|
||||
|
||||
vi.mock("../channels/plugins/index.js", () => ({
|
||||
getChannelPlugin: mocks.getChannelPlugin,
|
||||
normalizeChannelId: mocks.normalizeChannelId,
|
||||
}));
|
||||
|
||||
vi.mock("../config/config.js", () => ({
|
||||
loadConfig: mocks.loadConfig,
|
||||
}));
|
||||
|
||||
vi.mock("../globals.js", () => ({
|
||||
setVerbose: mocks.setVerbose,
|
||||
}));
|
||||
|
||||
describe("channel-auth", () => {
|
||||
const runtime = { log: vi.fn(), error: vi.fn(), exit: vi.fn() };
|
||||
const plugin = {
|
||||
auth: { login: mocks.login },
|
||||
gateway: { logoutAccount: mocks.logoutAccount },
|
||||
config: { resolveAccount: mocks.resolveAccount },
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mocks.normalizeChannelId.mockReturnValue("whatsapp");
|
||||
mocks.getChannelPlugin.mockReturnValue(plugin);
|
||||
mocks.loadConfig.mockReturnValue({ channels: {} });
|
||||
mocks.resolveChannelDefaultAccountId.mockReturnValue("default-account");
|
||||
mocks.resolveAccount.mockReturnValue({ id: "resolved-account" });
|
||||
mocks.login.mockResolvedValue(undefined);
|
||||
mocks.logoutAccount.mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
it("runs login with explicit trimmed account and verbose flag", async () => {
|
||||
await runChannelLogin({ channel: "wa", account: " acct-1 ", verbose: true }, runtime);
|
||||
|
||||
expect(mocks.setVerbose).toHaveBeenCalledWith(true);
|
||||
expect(mocks.resolveChannelDefaultAccountId).not.toHaveBeenCalled();
|
||||
expect(mocks.login).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
cfg: { channels: {} },
|
||||
accountId: "acct-1",
|
||||
runtime,
|
||||
verbose: true,
|
||||
channelInput: "wa",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("runs login with default channel/account when opts are empty", async () => {
|
||||
await runChannelLogin({}, runtime);
|
||||
|
||||
expect(mocks.normalizeChannelId).toHaveBeenCalledWith(DEFAULT_CHAT_CHANNEL);
|
||||
expect(mocks.resolveChannelDefaultAccountId).toHaveBeenCalledWith({
|
||||
plugin,
|
||||
cfg: { channels: {} },
|
||||
});
|
||||
expect(mocks.login).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
accountId: "default-account",
|
||||
channelInput: DEFAULT_CHAT_CHANNEL,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("throws for unsupported channel aliases", async () => {
|
||||
mocks.normalizeChannelId.mockReturnValueOnce(undefined);
|
||||
|
||||
await expect(runChannelLogin({ channel: "bad-channel" }, runtime)).rejects.toThrow(
|
||||
"Unsupported channel: bad-channel",
|
||||
);
|
||||
expect(mocks.login).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("throws when channel does not support login", async () => {
|
||||
mocks.getChannelPlugin.mockReturnValueOnce({
|
||||
auth: {},
|
||||
gateway: { logoutAccount: mocks.logoutAccount },
|
||||
config: { resolveAccount: mocks.resolveAccount },
|
||||
});
|
||||
|
||||
await expect(runChannelLogin({ channel: "whatsapp" }, runtime)).rejects.toThrow(
|
||||
"Channel whatsapp does not support login",
|
||||
);
|
||||
});
|
||||
|
||||
it("runs logout with resolved account and explicit account id", async () => {
|
||||
await runChannelLogout({ channel: "whatsapp", account: " acct-2 " }, runtime);
|
||||
|
||||
expect(mocks.resolveAccount).toHaveBeenCalledWith({ channels: {} }, "acct-2");
|
||||
expect(mocks.logoutAccount).toHaveBeenCalledWith({
|
||||
cfg: { channels: {} },
|
||||
accountId: "acct-2",
|
||||
account: { id: "resolved-account" },
|
||||
runtime,
|
||||
});
|
||||
expect(mocks.setVerbose).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("throws when channel does not support logout", async () => {
|
||||
mocks.getChannelPlugin.mockReturnValueOnce({
|
||||
auth: { login: mocks.login },
|
||||
gateway: {},
|
||||
config: { resolveAccount: mocks.resolveAccount },
|
||||
});
|
||||
|
||||
await expect(runChannelLogout({ channel: "whatsapp" }, runtime)).rejects.toThrow(
|
||||
"Channel whatsapp does not support logout",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -11,24 +11,42 @@ type ChannelAuthOptions = {
|
||||
verbose?: boolean;
|
||||
};
|
||||
|
||||
export async function runChannelLogin(
|
||||
type ChannelPlugin = NonNullable<ReturnType<typeof getChannelPlugin>>;
|
||||
type ChannelAuthMode = "login" | "logout";
|
||||
|
||||
function resolveChannelPluginForMode(
|
||||
opts: ChannelAuthOptions,
|
||||
runtime: RuntimeEnv = defaultRuntime,
|
||||
) {
|
||||
mode: ChannelAuthMode,
|
||||
): { channelInput: string; channelId: string; plugin: ChannelPlugin } {
|
||||
const channelInput = opts.channel ?? DEFAULT_CHAT_CHANNEL;
|
||||
const channelId = normalizeChannelId(channelInput);
|
||||
if (!channelId) {
|
||||
throw new Error(`Unsupported channel: ${channelInput}`);
|
||||
}
|
||||
const plugin = getChannelPlugin(channelId);
|
||||
if (!plugin?.auth?.login) {
|
||||
throw new Error(`Channel ${channelId} does not support login`);
|
||||
const supportsMode =
|
||||
mode === "login" ? Boolean(plugin?.auth?.login) : Boolean(plugin?.gateway?.logoutAccount);
|
||||
if (!supportsMode) {
|
||||
throw new Error(`Channel ${channelId} does not support ${mode}`);
|
||||
}
|
||||
// Auth-only flow: do not mutate channel config here.
|
||||
setVerbose(Boolean(opts.verbose));
|
||||
return { channelInput, channelId, plugin: plugin as ChannelPlugin };
|
||||
}
|
||||
|
||||
function resolveAccountContext(plugin: ChannelPlugin, opts: ChannelAuthOptions) {
|
||||
const cfg = loadConfig();
|
||||
const accountId = opts.account?.trim() || resolveChannelDefaultAccountId({ plugin, cfg });
|
||||
await plugin.auth.login({
|
||||
return { cfg, accountId };
|
||||
}
|
||||
|
||||
export async function runChannelLogin(
|
||||
opts: ChannelAuthOptions,
|
||||
runtime: RuntimeEnv = defaultRuntime,
|
||||
) {
|
||||
const { channelInput, plugin } = resolveChannelPluginForMode(opts, "login");
|
||||
// Auth-only flow: do not mutate channel config here.
|
||||
setVerbose(Boolean(opts.verbose));
|
||||
const { cfg, accountId } = resolveAccountContext(plugin, opts);
|
||||
await plugin.auth!.login({
|
||||
cfg,
|
||||
accountId,
|
||||
runtime,
|
||||
@@ -41,20 +59,11 @@ export async function runChannelLogout(
|
||||
opts: ChannelAuthOptions,
|
||||
runtime: RuntimeEnv = defaultRuntime,
|
||||
) {
|
||||
const channelInput = opts.channel ?? DEFAULT_CHAT_CHANNEL;
|
||||
const channelId = normalizeChannelId(channelInput);
|
||||
if (!channelId) {
|
||||
throw new Error(`Unsupported channel: ${channelInput}`);
|
||||
}
|
||||
const plugin = getChannelPlugin(channelId);
|
||||
if (!plugin?.gateway?.logoutAccount) {
|
||||
throw new Error(`Channel ${channelId} does not support logout`);
|
||||
}
|
||||
const { plugin } = resolveChannelPluginForMode(opts, "logout");
|
||||
// Auth-only flow: resolve account + clear session state only.
|
||||
const cfg = loadConfig();
|
||||
const accountId = opts.account?.trim() || resolveChannelDefaultAccountId({ plugin, cfg });
|
||||
const { cfg, accountId } = resolveAccountContext(plugin, opts);
|
||||
const account = plugin.config.resolveAccount(cfg, accountId);
|
||||
await plugin.gateway.logoutAccount({
|
||||
await plugin.gateway!.logoutAccount({
|
||||
cfg,
|
||||
accountId,
|
||||
account,
|
||||
|
||||
Reference in New Issue
Block a user