diff --git a/src/commands/status-all/channels.mattermost-token-summary.test.ts b/src/commands/status-all/channels.mattermost-token-summary.test.ts new file mode 100644 index 000000000..cff4f9169 --- /dev/null +++ b/src/commands/status-all/channels.mattermost-token-summary.test.ts @@ -0,0 +1,51 @@ +import { describe, expect, it, vi } from "vitest"; +import type { ChannelPlugin } from "../../channels/plugins/types.js"; +import { listChannelPlugins } from "../../channels/plugins/index.js"; +import { buildChannelsTable } from "./channels.js"; + +vi.mock("../../channels/plugins/index.js", () => ({ + listChannelPlugins: vi.fn(), +})); + +function makeMattermostPlugin(): ChannelPlugin { + return { + id: "mattermost", + meta: { + id: "mattermost", + label: "Mattermost", + selectionLabel: "Mattermost", + docsPath: "/channels/mattermost", + blurb: "test", + }, + config: { + listAccountIds: () => ["echo"], + defaultAccountId: () => "echo", + resolveAccount: () => ({ + name: "Echo", + enabled: true, + botToken: "bot-token-value", + baseUrl: "https://mm.example.com", + }), + isConfigured: () => true, + isEnabled: () => true, + }, + actions: { + listActions: () => ["send"], + }, + }; +} + +describe("buildChannelsTable - mattermost token summary", () => { + it("does not require appToken for mattermost accounts", async () => { + vi.mocked(listChannelPlugins).mockReturnValue([makeMattermostPlugin()]); + + const table = await buildChannelsTable({ channels: {} } as never, { + showSecrets: false, + }); + + const mattermostRow = table.rows.find((row) => row.id === "mattermost"); + expect(mattermostRow).toBeDefined(); + expect(mattermostRow?.state).toBe("ok"); + expect(mattermostRow?.detail).not.toContain("need bot+app"); + }); +}); diff --git a/src/commands/status-all/channels.ts b/src/commands/status-all/channels.ts index fe14e7935..3ab0e3316 100644 --- a/src/commands/status-all/channels.ts +++ b/src/commands/status-all/channels.ts @@ -211,14 +211,15 @@ function summarizeTokenConfig(params: { } const accountRecs = enabled.map((a) => asRecord(a.account)); - const hasBotOrAppTokenFields = accountRecs.some((r) => "botToken" in r || "appToken" in r); + const hasBotTokenField = accountRecs.some((r) => "botToken" in r); + const hasAppTokenField = accountRecs.some((r) => "appToken" in r); const hasTokenField = accountRecs.some((r) => "token" in r); - if (!hasBotOrAppTokenFields && !hasTokenField) { + if (!hasBotTokenField && !hasAppTokenField && !hasTokenField) { return { state: null, detail: null }; } - if (hasBotOrAppTokenFields) { + if (hasBotTokenField && hasAppTokenField) { const ready = enabled.filter((a) => { const rec = asRecord(a.account); const bot = typeof rec.botToken === "string" ? rec.botToken.trim() : ""; @@ -265,6 +266,30 @@ function summarizeTokenConfig(params: { }; } + if (hasBotTokenField) { + const ready = enabled.filter((a) => { + const rec = asRecord(a.account); + const bot = typeof rec.botToken === "string" ? rec.botToken.trim() : ""; + return Boolean(bot); + }); + + if (ready.length === 0) { + return { state: "setup", detail: "no bot token" }; + } + + const sample = ready[0]?.account ? asRecord(ready[0].account) : {}; + const botToken = typeof sample.botToken === "string" ? sample.botToken : ""; + const botHint = botToken.trim() + ? formatTokenHint(botToken, { showSecrets: params.showSecrets }) + : ""; + const hint = botHint ? ` (${botHint})` : ""; + + return { + state: "ok", + detail: `bot token config${hint} ยท accounts ${ready.length}/${enabled.length || 1}`, + }; + } + const ready = enabled.filter((a) => { const rec = asRecord(a.account); return typeof rec.token === "string" ? Boolean(rec.token.trim()) : false;