mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
chore: Fix type errors in extensions/twitch tests.
This commit is contained in:
@@ -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 = {
|
||||
|
||||
@@ -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<NonNullable<typeof twitchOutbound.resolveTarget>>,
|
||||
): 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<NonNullable<typeof twitchOutbound.sendText>>[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<NonNullable<typeof twitchOutbound.sendMedia>>[0]),
|
||||
).rejects.toThrow("Outbound delivery aborted");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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<typeof getClientManager>);
|
||||
} as unknown as ReturnType<typeof getClientManager>);
|
||||
vi.mocked(stripMarkdownForTwitch).mockImplementation((text) => text);
|
||||
|
||||
const result = await sendMessageTwitchInternal(
|
||||
@@ -93,7 +93,7 @@ describe("send", () => {
|
||||
ok: true,
|
||||
messageId: "twitch-msg-456",
|
||||
}),
|
||||
} as ReturnType<typeof getClientManager>);
|
||||
} as unknown as ReturnType<typeof getClientManager>);
|
||||
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<typeof getClientManager>);
|
||||
} as unknown as ReturnType<typeof getClientManager>);
|
||||
|
||||
const result = await sendMessageTwitchInternal(
|
||||
"#testchannel",
|
||||
@@ -253,7 +253,7 @@ describe("send", () => {
|
||||
});
|
||||
vi.mocked(getClientManager).mockReturnValue({
|
||||
sendMessage: mockSend,
|
||||
} as ReturnType<typeof getClientManager>);
|
||||
} as unknown as ReturnType<typeof getClientManager>);
|
||||
|
||||
await sendMessageTwitchInternal(
|
||||
"",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user