fix: honor twitch default outbound account

This commit is contained in:
Tak Hoffman
2026-04-03 14:49:46 -05:00
parent 2a5fbf0fd6
commit 84db697cd6
4 changed files with 129 additions and 3 deletions

View File

@@ -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",
}),
);
});
});

View File

@@ -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) {

View File

@@ -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();

View File

@@ -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(