From 84db697cd66ae3310e2b7d8445ffece64549ed79 Mon Sep 17 00:00:00 2001 From: Tak Hoffman <781889+Takhoffman@users.noreply.github.com> Date: Fri, 3 Apr 2026 14:49:46 -0500 Subject: [PATCH] fix: honor twitch default outbound account --- extensions/twitch/src/actions.test.ts | 75 ++++++++++++++++++++++++++ extensions/twitch/src/actions.ts | 4 +- extensions/twitch/src/outbound.test.ts | 51 ++++++++++++++++++ extensions/twitch/src/outbound.ts | 2 +- 4 files changed, 129 insertions(+), 3 deletions(-) create mode 100644 extensions/twitch/src/actions.test.ts diff --git a/extensions/twitch/src/actions.test.ts b/extensions/twitch/src/actions.test.ts new file mode 100644 index 00000000000..3cbeb26ea60 --- /dev/null +++ b/extensions/twitch/src/actions.test.ts @@ -0,0 +1,75 @@ +import { describe, expect, it, vi, beforeEach } from "vitest"; +import { twitchMessageActions } from "./actions.js"; +import { twitchOutbound } from "./outbound.js"; +import { resolveTwitchAccountContext } from "./config.js"; + +vi.mock("./config.js", () => ({ + DEFAULT_ACCOUNT_ID: "default", + resolveTwitchAccountContext: vi.fn(), +})); + +vi.mock("./outbound.js", () => ({ + twitchOutbound: { + sendText: vi.fn(), + }, +})); + +describe("twitchMessageActions", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("uses configured defaultAccount when action accountId is omitted", async () => { + vi.mocked(resolveTwitchAccountContext) + .mockImplementationOnce(() => ({ + accountId: "secondary", + account: { + channel: "secondary-channel", + username: "secondary", + accessToken: "oauth:secondary-token", + clientId: "secondary-client", + enabled: true, + }, + tokenResolution: { source: "config", token: "oauth:secondary-token" }, + configured: true, + availableAccountIds: ["default", "secondary"], + })) + .mockImplementation((_cfg, accountId) => ({ + accountId: accountId?.trim() || "secondary", + account: { + channel: "secondary-channel", + username: "secondary", + accessToken: "oauth:secondary-token", + clientId: "secondary-client", + enabled: true, + }, + tokenResolution: { source: "config", token: "oauth:secondary-token" }, + configured: true, + availableAccountIds: ["default", "secondary"], + })); + vi.mocked(twitchOutbound.sendText!).mockResolvedValue({ + channel: "twitch", + messageId: "msg-1", + timestamp: 1, + }); + + await twitchMessageActions.handleAction!({ + action: "send", + params: { message: "Hello!" }, + cfg: { + channels: { + twitch: { + defaultAccount: "secondary", + }, + }, + }, + } as never); + + expect(twitchOutbound.sendText).toHaveBeenCalledWith( + expect.objectContaining({ + accountId: "secondary", + to: "secondary-channel", + }), + ); + }); +}); diff --git a/extensions/twitch/src/actions.ts b/extensions/twitch/src/actions.ts index ffdfbaa2c80..3c3d0a6891b 100644 --- a/extensions/twitch/src/actions.ts +++ b/extensions/twitch/src/actions.ts @@ -4,7 +4,7 @@ * Handles tool-based actions for Twitch, such as sending messages. */ -import { DEFAULT_ACCOUNT_ID, resolveTwitchAccountContext } from "./config.js"; +import { resolveTwitchAccountContext } from "./config.js"; import { twitchOutbound } from "./outbound.js"; import type { ChannelMessageActionAdapter, ChannelMessageActionContext } from "./types.js"; @@ -130,7 +130,7 @@ export const twitchMessageActions: ChannelMessageActionAdapter = { const message = readStringParam(ctx.params, "message", { required: true }); const to = readStringParam(ctx.params, "to", { required: false }); - const accountId = ctx.accountId ?? DEFAULT_ACCOUNT_ID; + const accountId = ctx.accountId ?? resolveTwitchAccountContext(ctx.cfg).accountId; const { account, availableAccountIds } = resolveTwitchAccountContext(ctx.cfg, accountId); if (!account) { diff --git a/extensions/twitch/src/outbound.test.ts b/extensions/twitch/src/outbound.test.ts index 21be13d656b..693e843309d 100644 --- a/extensions/twitch/src/outbound.test.ts +++ b/extensions/twitch/src/outbound.test.ts @@ -305,6 +305,57 @@ describe("outbound", () => { ); }); + it("uses configured defaultAccount when accountId is omitted", async () => { + const { sendMessageTwitchInternal } = await import("./send.js"); + + vi.mocked(resolveTwitchAccountContext) + .mockImplementationOnce(() => ({ + accountId: "secondary", + account: { + ...mockAccount, + channel: "secondary-channel", + }, + tokenResolution: { source: "config", token: mockAccount.accessToken }, + configured: true, + availableAccountIds: ["default", "secondary"], + })) + .mockImplementation((_cfg, accountId) => ({ + accountId: accountId?.trim() || "secondary", + account: { + ...mockAccount, + channel: "secondary-channel", + }, + tokenResolution: { source: "config", token: mockAccount.accessToken }, + configured: true, + availableAccountIds: ["default", "secondary"], + })); + vi.mocked(sendMessageTwitchInternal).mockResolvedValue({ + ok: true, + messageId: "msg-secondary", + }); + + await twitchOutbound.sendText!({ + cfg: { + channels: { + twitch: { + defaultAccount: "secondary", + }, + }, + } as typeof mockConfig, + to: "#secondary-channel", + text: "Hello!", + }); + + expect(sendMessageTwitchInternal).toHaveBeenCalledWith( + "secondary-channel", + "Hello!", + expect.any(Object), + "secondary", + true, + console, + ); + }); + it("should handle abort signal", async () => { const abortController = new AbortController(); abortController.abort(); diff --git a/extensions/twitch/src/outbound.ts b/extensions/twitch/src/outbound.ts index c1936780885..6608bf3136d 100644 --- a/extensions/twitch/src/outbound.ts +++ b/extensions/twitch/src/outbound.ts @@ -113,7 +113,7 @@ export const twitchOutbound: ChannelOutboundAdapter = { throw new Error("Outbound delivery aborted"); } - const resolvedAccountId = accountId ?? DEFAULT_ACCOUNT_ID; + const resolvedAccountId = accountId ?? resolveTwitchAccountContext(cfg).accountId; const { account, availableAccountIds } = resolveTwitchAccountContext(cfg, resolvedAccountId); if (!account) { throw new Error(