From 0f8d1f175a6efab4f1364fe0d220bfb4667613a8 Mon Sep 17 00:00:00 2001 From: cpojer Date: Tue, 17 Feb 2026 10:11:34 +0900 Subject: [PATCH] chore: Fix type errors in `extensions/twitch` tests. --- extensions/twitch/src/access-control.test.ts | 4 +- extensions/twitch/src/outbound.test.ts | 95 ++++++++++++-------- extensions/twitch/src/send.test.ts | 10 +-- extensions/twitch/src/status.test.ts | 2 +- extensions/twitch/src/twitch-client.test.ts | 14 +-- 5 files changed, 76 insertions(+), 49 deletions(-) diff --git a/extensions/twitch/src/access-control.test.ts b/extensions/twitch/src/access-control.test.ts index 6c2015bb5b1..fc8fd184d1e 100644 --- a/extensions/twitch/src/access-control.test.ts +++ b/extensions/twitch/src/access-control.test.ts @@ -5,7 +5,9 @@ import type { TwitchAccountConfig, TwitchChatMessage } from "./types.js"; describe("checkTwitchAccessControl", () => { const mockAccount: TwitchAccountConfig = { username: "testbot", - token: "oauth:test", + accessToken: "test", + clientId: "test-client-id", + channel: "testchannel", }; const mockMessage: TwitchChatMessage = { diff --git a/extensions/twitch/src/outbound.test.ts b/extensions/twitch/src/outbound.test.ts index bf149f3b5a4..7b480df32dd 100644 --- a/extensions/twitch/src/outbound.test.ts +++ b/extensions/twitch/src/outbound.test.ts @@ -34,14 +34,24 @@ vi.mock("./utils/markdown.js", () => ({ vi.mock("./utils/twitch.js", () => ({ normalizeTwitchChannel: (channel: string) => channel.toLowerCase().replace(/^#/, ""), missingTargetError: (channel: string, hint: string) => - `Missing target for ${channel}. Provide ${hint}`, + new Error(`Missing target for ${channel}. Provide ${hint}`), })); +function assertResolvedTarget( + result: ReturnType>, +): string { + if (!result.ok) { + throw result.error; + } + return result.to; +} + describe("outbound", () => { const mockAccount = { ...BASE_TWITCH_TEST_ACCOUNT, accessToken: "oauth:test123", }; + const resolveTarget = twitchOutbound.resolveTarget!; const mockConfig = makeTwitchTestConfig(mockAccount); installTwitchTestHooks(); @@ -63,106 +73,121 @@ describe("outbound", () => { describe("resolveTarget", () => { it("should normalize and return target in explicit mode", () => { - const result = twitchOutbound.resolveTarget({ + const result = resolveTarget({ to: "#MyChannel", mode: "explicit", allowFrom: [], }); expect(result.ok).toBe(true); - expect(result.to).toBe("mychannel"); + expect(assertResolvedTarget(result)).toBe("mychannel"); }); it("should return target in implicit mode with wildcard allowlist", () => { - const result = twitchOutbound.resolveTarget({ + const result = resolveTarget({ to: "#AnyChannel", mode: "implicit", allowFrom: ["*"], }); expect(result.ok).toBe(true); - expect(result.to).toBe("anychannel"); + expect(assertResolvedTarget(result)).toBe("anychannel"); }); it("should return target in implicit mode when in allowlist", () => { - const result = twitchOutbound.resolveTarget({ + const result = resolveTarget({ to: "#allowed", mode: "implicit", allowFrom: ["#allowed", "#other"], }); expect(result.ok).toBe(true); - expect(result.to).toBe("allowed"); + expect(assertResolvedTarget(result)).toBe("allowed"); }); it("should error when target not in allowlist (implicit mode)", () => { - const result = twitchOutbound.resolveTarget({ + const result = resolveTarget({ to: "#notallowed", mode: "implicit", allowFrom: ["#primary", "#secondary"], }); expect(result.ok).toBe(false); - expect(result.error).toContain("Twitch"); + if (result.ok) { + throw new Error("expected resolveTarget to fail"); + } + expect(result.error.message).toContain("Twitch"); }); it("should accept any target when allowlist is empty", () => { - const result = twitchOutbound.resolveTarget({ + const result = resolveTarget({ to: "#anychannel", mode: "heartbeat", allowFrom: [], }); expect(result.ok).toBe(true); - expect(result.to).toBe("anychannel"); + expect(assertResolvedTarget(result)).toBe("anychannel"); }); it("should error when no target provided with allowlist", () => { - const result = twitchOutbound.resolveTarget({ + const result = resolveTarget({ to: undefined, mode: "implicit", allowFrom: ["#fallback", "#other"], }); expect(result.ok).toBe(false); - expect(result.error).toContain("Twitch"); + if (result.ok) { + throw new Error("expected resolveTarget to fail"); + } + expect(result.error.message).toContain("Twitch"); }); it("should return error when no target and no allowlist", () => { - const result = twitchOutbound.resolveTarget({ + const result = resolveTarget({ to: undefined, mode: "explicit", allowFrom: [], }); expect(result.ok).toBe(false); - expect(result.error).toContain("Missing target"); + if (result.ok) { + throw new Error("expected resolveTarget to fail"); + } + expect(result.error.message).toContain("Missing target"); }); it("should handle whitespace-only target", () => { - const result = twitchOutbound.resolveTarget({ + const result = resolveTarget({ to: " ", mode: "explicit", allowFrom: [], }); expect(result.ok).toBe(false); - expect(result.error).toContain("Missing target"); + if (result.ok) { + throw new Error("expected resolveTarget to fail"); + } + expect(result.error.message).toContain("Missing target"); }); it("should error when target normalizes to empty string", () => { - const result = twitchOutbound.resolveTarget({ + const result = resolveTarget({ to: "#", mode: "explicit", allowFrom: [], }); expect(result.ok).toBe(false); - expect(result.error).toContain("Twitch"); + if (result.ok) { + throw new Error("expected resolveTarget to fail"); + } + expect(result.error.message).toContain("Twitch"); }); it("should filter wildcard from allowlist when checking membership", () => { - const result = twitchOutbound.resolveTarget({ + const result = resolveTarget({ to: "#mychannel", mode: "implicit", allowFrom: ["*", "#specific"], @@ -170,7 +195,7 @@ describe("outbound", () => { // With wildcard, any target is accepted expect(result.ok).toBe(true); - expect(result.to).toBe("mychannel"); + expect(assertResolvedTarget(result)).toBe("mychannel"); }); }); @@ -185,7 +210,7 @@ describe("outbound", () => { messageId: "twitch-msg-123", }); - const result = await twitchOutbound.sendText({ + const result = await twitchOutbound.sendText!({ cfg: mockConfig, to: "#testchannel", text: "Hello Twitch!", @@ -211,7 +236,7 @@ describe("outbound", () => { vi.mocked(getAccountConfig).mockReturnValue(null); await expect( - twitchOutbound.sendText({ + twitchOutbound.sendText!({ cfg: mockConfig, to: "#testchannel", text: "Hello!", @@ -227,9 +252,9 @@ describe("outbound", () => { vi.mocked(getAccountConfig).mockReturnValue(accountWithoutChannel); await expect( - twitchOutbound.sendText({ + twitchOutbound.sendText!({ cfg: mockConfig, - to: undefined, + to: "", text: "Hello!", accountId: "default", }), @@ -246,9 +271,9 @@ describe("outbound", () => { messageId: "msg-456", }); - await twitchOutbound.sendText({ + await twitchOutbound.sendText!({ cfg: mockConfig, - to: undefined, + to: "", text: "Hello!", accountId: "default", }); @@ -268,13 +293,13 @@ describe("outbound", () => { abortController.abort(); await expect( - twitchOutbound.sendText({ + twitchOutbound.sendText!({ cfg: mockConfig, to: "#testchannel", text: "Hello!", accountId: "default", signal: abortController.signal, - }), + } as Parameters>[0]), ).rejects.toThrow("Outbound delivery aborted"); }); @@ -290,7 +315,7 @@ describe("outbound", () => { }); await expect( - twitchOutbound.sendText({ + twitchOutbound.sendText!({ cfg: mockConfig, to: "#testchannel", text: "Hello!", @@ -311,7 +336,7 @@ describe("outbound", () => { messageId: "media-msg-123", }); - const result = await twitchOutbound.sendMedia({ + const result = await twitchOutbound.sendMedia!({ cfg: mockConfig, to: "#testchannel", text: "Check this:", @@ -341,10 +366,10 @@ describe("outbound", () => { messageId: "media-only-msg", }); - await twitchOutbound.sendMedia({ + await twitchOutbound.sendMedia!({ cfg: mockConfig, to: "#testchannel", - text: undefined, + text: "", mediaUrl: "https://example.com/image.png", accountId: "default", }); @@ -364,14 +389,14 @@ describe("outbound", () => { abortController.abort(); await expect( - twitchOutbound.sendMedia({ + twitchOutbound.sendMedia!({ cfg: mockConfig, to: "#testchannel", text: "Check this:", mediaUrl: "https://example.com/image.png", accountId: "default", signal: abortController.signal, - }), + } as Parameters>[0]), ).rejects.toThrow("Outbound delivery aborted"); }); }); diff --git a/extensions/twitch/src/send.test.ts b/extensions/twitch/src/send.test.ts index bfe16dd90b2..e7185b3f5fb 100644 --- a/extensions/twitch/src/send.test.ts +++ b/extensions/twitch/src/send.test.ts @@ -48,7 +48,7 @@ describe("send", () => { const mockAccount = { ...BASE_TWITCH_TEST_ACCOUNT, - token: "oauth:test123", + accessToken: "test123", }; const mockConfig = makeTwitchTestConfig(mockAccount); @@ -66,7 +66,7 @@ describe("send", () => { ok: true, messageId: "twitch-msg-123", }), - } as ReturnType); + } as unknown as ReturnType); vi.mocked(stripMarkdownForTwitch).mockImplementation((text) => text); const result = await sendMessageTwitchInternal( @@ -93,7 +93,7 @@ describe("send", () => { ok: true, messageId: "twitch-msg-456", }), - } as ReturnType); + } as unknown as ReturnType); vi.mocked(stripMarkdownForTwitch).mockImplementation((text) => text.replace(/\*\*/g, "")); await sendMessageTwitchInternal( @@ -224,7 +224,7 @@ describe("send", () => { vi.mocked(isAccountConfigured).mockReturnValue(true); vi.mocked(getClientManager).mockReturnValue({ sendMessage: vi.fn().mockRejectedValue(new Error("Connection lost")), - } as ReturnType); + } as unknown as ReturnType); const result = await sendMessageTwitchInternal( "#testchannel", @@ -253,7 +253,7 @@ describe("send", () => { }); vi.mocked(getClientManager).mockReturnValue({ sendMessage: mockSend, - } as ReturnType); + } as unknown as ReturnType); await sendMessageTwitchInternal( "", diff --git a/extensions/twitch/src/status.test.ts b/extensions/twitch/src/status.test.ts index 8f7cd55abbf..7aa8b909df3 100644 --- a/extensions/twitch/src/status.test.ts +++ b/extensions/twitch/src/status.test.ts @@ -254,7 +254,7 @@ describe("status", () => { it("should skip non-Twitch accounts gracefully", () => { const snapshots: ChannelAccountSnapshot[] = [ { - accountId: undefined, + accountId: "unknown", configured: false, enabled: true, running: false, diff --git a/extensions/twitch/src/twitch-client.test.ts b/extensions/twitch/src/twitch-client.test.ts index eebc37717b8..24ffe75587a 100644 --- a/extensions/twitch/src/twitch-client.test.ts +++ b/extensions/twitch/src/twitch-client.test.ts @@ -86,7 +86,7 @@ describe("TwitchClientManager", () => { const testAccount: TwitchAccountConfig = { username: "testbot", - token: "oauth:test123456", + accessToken: "test123456", clientId: "test-client-id", channel: "testchannel", enabled: true, @@ -94,7 +94,7 @@ describe("TwitchClientManager", () => { const testAccount2: TwitchAccountConfig = { username: "testbot2", - token: "oauth:test789", + accessToken: "test789", clientId: "test-client-id-2", channel: "testchannel2", enabled: true, @@ -145,8 +145,8 @@ describe("TwitchClientManager", () => { it("should use account username as default channel when channel not specified", async () => { const accountWithoutChannel: TwitchAccountConfig = { ...testAccount, - channel: undefined, - }; + channel: "", + } as unknown as TwitchAccountConfig; await manager.getClient(accountWithoutChannel); @@ -172,7 +172,7 @@ describe("TwitchClientManager", () => { it("should normalize token by removing oauth: prefix", async () => { const accountWithPrefix: TwitchAccountConfig = { ...testAccount, - token: "oauth:actualtoken123", + accessToken: "oauth:actualtoken123", }; // Override the mock to return a specific token for this test @@ -207,8 +207,8 @@ describe("TwitchClientManager", () => { it("should throw error when clientId is missing", async () => { const accountWithoutClientId: TwitchAccountConfig = { ...testAccount, - clientId: undefined, - }; + clientId: "" as unknown as string, + } as unknown as TwitchAccountConfig; await expect(manager.getClient(accountWithoutClientId)).rejects.toThrow( "Missing Twitch client ID",