chore(tsgo/format): fix CI errors

This commit is contained in:
Gustavo Madeira Santana
2026-02-21 17:51:56 -05:00
parent 6ac89757ba
commit 0e1aa77928
15 changed files with 133 additions and 59 deletions

View File

@@ -449,7 +449,7 @@ describe("parseLineDirectives", () => {
if (testCase.expectFooter) { if (testCase.expectFooter) {
expect(flexMessage?.contents?.footer?.contents?.length, testCase.name).toBeGreaterThan(0); expect(flexMessage?.contents?.footer?.contents?.length, testCase.name).toBeGreaterThan(0);
} }
if (testCase.expectBodyContents) { if ("expectBodyContents" in testCase && testCase.expectBodyContents) {
expect(flexMessage?.contents?.body?.contents, testCase.name).toBeDefined(); expect(flexMessage?.contents?.body?.contents, testCase.name).toBeDefined();
} }
} }

View File

@@ -626,7 +626,7 @@ describe("initSessionState reset triggers in WhatsApp groups", () => {
}); });
const cfg = makeCfg({ const cfg = makeCfg({
storePath, storePath,
allowFrom: testCase.allowFrom, allowFrom: [...testCase.allowFrom],
}); });
const result = await initSessionState({ const result = await initSessionState({

View File

@@ -34,7 +34,7 @@ describe("program helpers", () => {
it("resolveActionArgs returns empty array for missing/invalid args", () => { it("resolveActionArgs returns empty array for missing/invalid args", () => {
const command = new Command(); const command = new Command();
(command as Command & { args?: unknown }).args = "not-an-array"; (command as unknown as { args?: unknown }).args = "not-an-array";
expect(resolveActionArgs(command)).toEqual([]); expect(resolveActionArgs(command)).toEqual([]);
expect(resolveActionArgs(undefined)).toEqual([]); expect(resolveActionArgs(undefined)).toEqual([]);
}); });

View File

@@ -1,5 +1,6 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { migrateLegacyConfig, validateConfigObject } from "./config.js"; import { migrateLegacyConfig, validateConfigObject } from "./config.js";
import type { OpenClawConfig } from "./config.js";
function getLegacyRouting(config: unknown) { function getLegacyRouting(config: unknown) {
return (config as { routing?: Record<string, unknown> } | undefined)?.routing; return (config as { routing?: Record<string, unknown> } | undefined)?.routing;
@@ -470,13 +471,16 @@ describe("legacy config detection", () => {
const res = validateConfigObject(testCase.input); const res = validateConfigObject(testCase.input);
expect(res.ok, testCase.name).toBe(true); expect(res.ok, testCase.name).toBe(true);
if (res.ok) { if (res.ok) {
if (testCase.expectedTopLevel !== undefined) { if ("expectedTopLevel" in testCase && testCase.expectedTopLevel !== undefined) {
expect(res.config.channels?.telegram?.streaming, testCase.name).toBe( expect(res.config.channels?.telegram?.streaming, testCase.name).toBe(
testCase.expectedTopLevel, testCase.expectedTopLevel,
); );
expect(res.config.channels?.telegram?.streamMode, testCase.name).toBeUndefined(); expect(res.config.channels?.telegram?.streamMode, testCase.name).toBeUndefined();
} }
if (testCase.expectedAccountStreaming !== undefined) { if (
"expectedAccountStreaming" in testCase &&
testCase.expectedAccountStreaming !== undefined
) {
expect(res.config.channels?.telegram?.accounts?.ops?.streaming, testCase.name).toBe( expect(res.config.channels?.telegram?.accounts?.ops?.streaming, testCase.name).toBe(
testCase.expectedAccountStreaming, testCase.expectedAccountStreaming,
); );

View File

@@ -409,12 +409,12 @@ describe("redactConfigSnapshot", () => {
}), }),
assert: ({ redacted, restored }) => { assert: ({ redacted, restored }) => {
const cfg = redacted as Record<string, Record<string, unknown>>; const cfg = redacted as Record<string, Record<string, unknown>>;
const cfgCustom2 = cfg.custom2 as unknown[]; const cfgCustom2 = cfg.custom2 as unknown as unknown[];
expect(cfgCustom2.length).toBeGreaterThan(0); expect(cfgCustom2.length).toBeGreaterThan(0);
expect((cfg.custom1.anykey as Record<string, unknown>).mySecret).toBe(REDACTED_SENTINEL); expect((cfg.custom1.anykey as Record<string, unknown>).mySecret).toBe(REDACTED_SENTINEL);
expect((cfgCustom2[0] as Record<string, unknown>).mySecret).toBe(REDACTED_SENTINEL); expect((cfgCustom2[0] as Record<string, unknown>).mySecret).toBe(REDACTED_SENTINEL);
const out = restored as Record<string, Record<string, unknown>>; const out = restored as Record<string, Record<string, unknown>>;
const outCustom2 = out.custom2 as unknown[]; const outCustom2 = out.custom2 as unknown as unknown[];
expect(outCustom2.length).toBeGreaterThan(0); expect(outCustom2.length).toBeGreaterThan(0);
expect((out.custom1.anykey as Record<string, unknown>).mySecret).toBe( expect((out.custom1.anykey as Record<string, unknown>).mySecret).toBe(
"this-is-a-custom-secret-value", "this-is-a-custom-secret-value",
@@ -436,12 +436,12 @@ describe("redactConfigSnapshot", () => {
}), }),
assert: ({ redacted, restored }) => { assert: ({ redacted, restored }) => {
const cfg = redacted as Record<string, Record<string, unknown>>; const cfg = redacted as Record<string, Record<string, unknown>>;
const cfgCustom2 = cfg.custom2 as unknown[]; const cfgCustom2 = cfg.custom2 as unknown as unknown[];
expect(cfgCustom2.length).toBeGreaterThan(0); expect(cfgCustom2.length).toBeGreaterThan(0);
expect((cfg.custom1.anykey as Record<string, unknown>).mySecret).toBe(REDACTED_SENTINEL); expect((cfg.custom1.anykey as Record<string, unknown>).mySecret).toBe(REDACTED_SENTINEL);
expect((cfgCustom2[0] as Record<string, unknown>).mySecret).toBe(REDACTED_SENTINEL); expect((cfgCustom2[0] as Record<string, unknown>).mySecret).toBe(REDACTED_SENTINEL);
const out = restored as Record<string, Record<string, unknown>>; const out = restored as Record<string, Record<string, unknown>>;
const outCustom2 = out.custom2 as unknown[]; const outCustom2 = out.custom2 as unknown as unknown[];
expect(outCustom2.length).toBeGreaterThan(0); expect(outCustom2.length).toBeGreaterThan(0);
expect((out.custom1.anykey as Record<string, unknown>).mySecret).toBe( expect((out.custom1.anykey as Record<string, unknown>).mySecret).toBe(
"this-is-a-custom-secret-value", "this-is-a-custom-secret-value",

View File

@@ -9,7 +9,9 @@ vi.mock("../../config/sessions.js", () => ({
})); }));
vi.mock("../../infra/outbound/channel-selection.js", () => ({ vi.mock("../../infra/outbound/channel-selection.js", () => ({
resolveMessageChannelSelection: vi.fn().mockResolvedValue({ channel: "telegram" }), resolveMessageChannelSelection: vi
.fn()
.mockResolvedValue({ channel: "telegram", configured: ["telegram"] }),
})); }));
vi.mock("../../pairing/pairing-store.js", () => ({ vi.mock("../../pairing/pairing-store.js", () => ({
@@ -261,7 +263,10 @@ describe("resolveDeliveryTarget", () => {
it("uses channel selection result when no previous session target exists", async () => { it("uses channel selection result when no previous session target exists", async () => {
setMainSessionEntry(undefined); setMainSessionEntry(undefined);
vi.mocked(resolveMessageChannelSelection).mockResolvedValueOnce({ channel: "telegram" }); vi.mocked(resolveMessageChannelSelection).mockResolvedValueOnce({
channel: "telegram",
configured: ["telegram"],
});
const result = await resolveForAgent({ const result = await resolveForAgent({
cfg: makeCfg({ bindings: [] }), cfg: makeCfg({ bindings: [] }),

View File

@@ -705,7 +705,7 @@ describe("discord reaction notification gating", () => {
botId: "bot-1", botId: "bot-1",
messageAuthorId: "user-1", messageAuthorId: "user-1",
userId: "user-2", userId: "user-2",
allowlist: [], allowlist: [] as string[],
}, },
expected: false, expected: false,
}, },
@@ -717,7 +717,7 @@ describe("discord reaction notification gating", () => {
messageAuthorId: "user-1", messageAuthorId: "user-1",
userId: "123", userId: "123",
userName: "steipete", userName: "steipete",
allowlist: ["123", "other"], allowlist: ["123", "other"] as string[],
}, },
expected: true, expected: true,
}, },
@@ -984,7 +984,7 @@ describe("discord reaction notification modes", () => {
{ {
name: "allowlist mode", name: "allowlist mode",
reactionNotifications: "allowlist" as const, reactionNotifications: "allowlist" as const,
users: ["123"], users: ["123"] as string[],
userId: "123", userId: "123",
channelType: ChannelType.GuildText, channelType: ChannelType.GuildText,
channelId: undefined, channelId: undefined,

View File

@@ -24,6 +24,7 @@ import {
resolveExecApprovalsSocketPath, resolveExecApprovalsSocketPath,
resolveSafeBins, resolveSafeBins,
type ExecAllowlistEntry, type ExecAllowlistEntry,
type ExecApprovalsAgent,
type ExecApprovalsFile, type ExecApprovalsFile,
} from "./exec-approvals.js"; } from "./exec-approvals.js";
import { SAFE_BIN_PROFILE_FIXTURES, SAFE_BIN_PROFILES } from "./exec-safe-bin-policy.js"; import { SAFE_BIN_PROFILE_FIXTURES, SAFE_BIN_PROFILES } from "./exec-safe-bin-policy.js";
@@ -204,7 +205,7 @@ describe("exec approvals command resolution", () => {
return { return {
command: "./scripts/run.sh --flag", command: "./scripts/run.sh --flag",
cwd, cwd,
envPath: undefined as string | undefined, envPath: undefined as NodeJS.ProcessEnv | undefined,
expectedPath: script, expectedPath: script,
expectedExecutableName: undefined, expectedExecutableName: undefined,
}; };
@@ -222,7 +223,7 @@ describe("exec approvals command resolution", () => {
return { return {
command: '"./bin/tool" --version', command: '"./bin/tool" --version',
cwd, cwd,
envPath: undefined as string | undefined, envPath: undefined as NodeJS.ProcessEnv | undefined,
expectedPath: script, expectedPath: script,
expectedExecutableName: undefined, expectedExecutableName: undefined,
}; };
@@ -258,7 +259,7 @@ describe("exec approvals shell parsing", () => {
for (const testCase of cases) { for (const testCase of cases) {
const res = analyzeShellCommand({ command: testCase.command }); const res = analyzeShellCommand({ command: testCase.command });
expect(res.ok, testCase.name).toBe(true); expect(res.ok, testCase.name).toBe(true);
if (testCase.expectedSegments) { if ("expectedSegments" in testCase) {
expect( expect(
res.segments.map((seg) => seg.argv[0]), res.segments.map((seg) => seg.argv[0]),
testCase.name, testCase.name,
@@ -1197,7 +1198,7 @@ describe("normalizeExecApprovals handles string allowlist entries (#9790)", () =
const patterns = getMainAllowlistPatterns({ const patterns = getMainAllowlistPatterns({
version: 1, version: 1,
agents: { agents: {
main: { allowlist: testCase.allowlist } as ExecApprovalsFile["agents"]["main"], main: { allowlist: testCase.allowlist } as ExecApprovalsAgent,
}, },
}); });
expect(patterns, testCase.name).toEqual(testCase.expectedPatterns); expect(patterns, testCase.name).toEqual(testCase.expectedPatterns);
@@ -1205,7 +1206,7 @@ describe("normalizeExecApprovals handles string allowlist entries (#9790)", () =
const entries = normalizeExecApprovals({ const entries = normalizeExecApprovals({
version: 1, version: 1,
agents: { agents: {
main: { allowlist: testCase.allowlist } as ExecApprovalsFile["agents"]["main"], main: { allowlist: testCase.allowlist } as ExecApprovalsAgent,
}, },
}).agents?.main?.allowlist; }).agents?.main?.allowlist;
expectNoSpreadStringArtifacts(entries ?? []); expectNoSpreadStringArtifacts(entries ?? []);

View File

@@ -17,6 +17,7 @@ import { buildAgentPeerSessionKey } from "../routing/session-key.js";
import { createOutboundTestPlugin, createTestRegistry } from "../test-utils/channel-plugins.js"; import { createOutboundTestPlugin, createTestRegistry } from "../test-utils/channel-plugins.js";
import { import {
isHeartbeatEnabledForAgent, isHeartbeatEnabledForAgent,
type HeartbeatDeps,
resolveHeartbeatIntervalMs, resolveHeartbeatIntervalMs,
resolveHeartbeatPrompt, resolveHeartbeatPrompt,
runHeartbeatOnce, runHeartbeatOnce,
@@ -439,7 +440,10 @@ describe("resolveHeartbeatSenderContext", () => {
}); });
describe("runHeartbeatOnce", () => { describe("runHeartbeatOnce", () => {
const createHeartbeatDeps = (sendWhatsApp: ReturnType<typeof vi.fn>, nowMs = 0) => ({ const createHeartbeatDeps = (
sendWhatsApp: NonNullable<HeartbeatDeps["sendWhatsApp"]>,
nowMs = 0,
): HeartbeatDeps => ({
sendWhatsApp, sendWhatsApp,
getQueueSize: () => 0, getQueueSize: () => 0,
nowMs: () => nowMs, nowMs: () => nowMs,
@@ -516,7 +520,7 @@ describe("runHeartbeatOnce", () => {
); );
replySpy.mockResolvedValue([{ text: "Let me check..." }, { text: "Final alert" }]); replySpy.mockResolvedValue([{ text: "Let me check..." }, { text: "Final alert" }]);
const sendWhatsApp = vi.fn().mockResolvedValue({ const sendWhatsApp = vi.fn<NonNullable<HeartbeatDeps["sendWhatsApp"]>>().mockResolvedValue({
messageId: "m1", messageId: "m1",
toJid: "jid", toJid: "jid",
}); });
@@ -569,7 +573,7 @@ describe("runHeartbeatOnce", () => {
}), }),
); );
replySpy.mockResolvedValue([{ text: "Final alert" }]); replySpy.mockResolvedValue([{ text: "Final alert" }]);
const sendWhatsApp = vi.fn().mockResolvedValue({ const sendWhatsApp = vi.fn<NonNullable<HeartbeatDeps["sendWhatsApp"]>>().mockResolvedValue({
messageId: "m1", messageId: "m1",
toJid: "jid", toJid: "jid",
}); });
@@ -645,7 +649,7 @@ describe("runHeartbeatOnce", () => {
); );
replySpy.mockResolvedValue([{ text: "Final alert" }]); replySpy.mockResolvedValue([{ text: "Final alert" }]);
const sendWhatsApp = vi.fn().mockResolvedValue({ const sendWhatsApp = vi.fn<NonNullable<HeartbeatDeps["sendWhatsApp"]>>().mockResolvedValue({
messageId: "m1", messageId: "m1",
toJid: "jid", toJid: "jid",
}); });
@@ -749,7 +753,9 @@ describe("runHeartbeatOnce", () => {
replySpy.mockReset(); replySpy.mockReset();
replySpy.mockResolvedValue([{ text: testCase.message }]); replySpy.mockResolvedValue([{ text: testCase.message }]);
const sendWhatsApp = vi.fn().mockResolvedValue({ messageId: "m1", toJid: "jid" }); const sendWhatsApp = vi
.fn<NonNullable<HeartbeatDeps["sendWhatsApp"]>>()
.mockResolvedValue({ messageId: "m1", toJid: "jid" });
await runHeartbeatOnce({ await runHeartbeatOnce({
cfg, cfg,
@@ -811,7 +817,9 @@ describe("runHeartbeatOnce", () => {
); );
replySpy.mockResolvedValue([{ text: "Final alert" }]); replySpy.mockResolvedValue([{ text: "Final alert" }]);
const sendWhatsApp = vi.fn().mockResolvedValue({ messageId: "m1", toJid: "jid" }); const sendWhatsApp = vi
.fn<NonNullable<HeartbeatDeps["sendWhatsApp"]>>()
.mockResolvedValue({ messageId: "m1", toJid: "jid" });
await runHeartbeatOnce({ await runHeartbeatOnce({
cfg, cfg,
@@ -827,7 +835,12 @@ describe("runHeartbeatOnce", () => {
it("handles reasoning payload delivery variants", async () => { it("handles reasoning payload delivery variants", async () => {
const replySpy = vi.spyOn(replyModule, "getReplyFromConfig"); const replySpy = vi.spyOn(replyModule, "getReplyFromConfig");
try { try {
const cases = [ const cases: Array<{
name: string;
caseDir: string;
replies: Array<{ text: string }>;
expectedTexts: string[];
}> = [
{ {
name: "reasoning + final payload", name: "reasoning + final payload",
caseDir: "hb-reasoning", caseDir: "hb-reasoning",
@@ -840,7 +853,7 @@ describe("runHeartbeatOnce", () => {
replies: [{ text: "Reasoning:\n_Because it helps_" }, { text: "HEARTBEAT_OK" }], replies: [{ text: "Reasoning:\n_Because it helps_" }, { text: "HEARTBEAT_OK" }],
expectedTexts: ["Reasoning:\n_Because it helps_"], expectedTexts: ["Reasoning:\n_Because it helps_"],
}, },
] as const; ];
for (const testCase of cases) { for (const testCase of cases) {
const tmpDir = await createCaseDir(testCase.caseDir); const tmpDir = await createCaseDir(testCase.caseDir);
@@ -876,7 +889,9 @@ describe("runHeartbeatOnce", () => {
replySpy.mockReset(); replySpy.mockReset();
replySpy.mockResolvedValue(testCase.replies); replySpy.mockResolvedValue(testCase.replies);
const sendWhatsApp = vi.fn().mockResolvedValue({ messageId: "m1", toJid: "jid" }); const sendWhatsApp = vi
.fn<NonNullable<HeartbeatDeps["sendWhatsApp"]>>()
.mockResolvedValue({ messageId: "m1", toJid: "jid" });
await runHeartbeatOnce({ await runHeartbeatOnce({
cfg, cfg,
@@ -934,7 +949,7 @@ describe("runHeartbeatOnce", () => {
); );
replySpy.mockResolvedValue({ text: "Hello from heartbeat" }); replySpy.mockResolvedValue({ text: "Hello from heartbeat" });
const sendWhatsApp = vi.fn().mockResolvedValue({ const sendWhatsApp = vi.fn<NonNullable<HeartbeatDeps["sendWhatsApp"]>>().mockResolvedValue({
messageId: "m1", messageId: "m1",
toJid: "jid", toJid: "jid",
}); });
@@ -1020,7 +1035,9 @@ describe("runHeartbeatOnce", () => {
const replySpy = vi.spyOn(replyModule, "getReplyFromConfig"); const replySpy = vi.spyOn(replyModule, "getReplyFromConfig");
replySpy.mockResolvedValue({ text: params.replyText ?? "Checked logs and PRs" }); replySpy.mockResolvedValue({ text: params.replyText ?? "Checked logs and PRs" });
const sendWhatsApp = vi.fn().mockResolvedValue({ messageId: "m1", toJid: "jid" }); const sendWhatsApp = vi
.fn<NonNullable<HeartbeatDeps["sendWhatsApp"]>>()
.mockResolvedValue({ messageId: "m1", toJid: "jid" });
const res = await runHeartbeatOnce({ const res = await runHeartbeatOnce({
cfg, cfg,
reason: params.reason, reason: params.reason,

View File

@@ -407,7 +407,7 @@ describe("DirectoryCache", () => {
] as const, ] as const,
expected: { a: "value-a2", b: undefined, c: "value-c" }, expected: { a: "value-a2", b: undefined, c: "value-c" },
}, },
] as const; ];
for (const testCase of cases) { for (const testCase of cases) {
const cache = new DirectoryCache<string>(60_000, 2); const cache = new DirectoryCache<string>(60_000, 2);
@@ -477,7 +477,7 @@ describe("buildOutboundResultEnvelope", () => {
input: { delivery: discordDelivery, flattenDelivery: false }, input: { delivery: discordDelivery, flattenDelivery: false },
expected: { delivery: discordDelivery }, expected: { delivery: discordDelivery },
}, },
] as const; ];
for (const testCase of cases) { for (const testCase of cases) {
expect(buildOutboundResultEnvelope(testCase.input), testCase.name).toEqual(testCase.expected); expect(buildOutboundResultEnvelope(testCase.input), testCase.name).toEqual(testCase.expected);
} }
@@ -519,7 +519,7 @@ describe("formatOutboundDeliverySummary", () => {
}, },
expected: "✅ Sent via Discord. Message ID: d1 (channel chan)", expected: "✅ Sent via Discord. Message ID: d1 (channel chan)",
}, },
] as const; ];
for (const testCase of cases) { for (const testCase of cases) {
expect(formatOutboundDeliverySummary(testCase.channel, testCase.result), testCase.name).toBe( expect(formatOutboundDeliverySummary(testCase.channel, testCase.result), testCase.name).toBe(
@@ -581,7 +581,7 @@ describe("buildOutboundDeliveryJson", () => {
timestamp: 123, timestamp: 123,
}, },
}, },
] as const; ];
for (const testCase of cases) { for (const testCase of cases) {
expect(buildOutboundDeliveryJson(testCase.input), testCase.name).toEqual(testCase.expected); expect(buildOutboundDeliveryJson(testCase.input), testCase.name).toEqual(testCase.expected);
@@ -602,7 +602,7 @@ describe("formatGatewaySummary", () => {
input: { action: "Poll sent", channel: "discord", messageId: "p1" }, input: { action: "Poll sent", channel: "discord", messageId: "p1" },
expected: "✅ Poll sent via gateway (discord). Message ID: p1", expected: "✅ Poll sent via gateway (discord). Message ID: p1",
}, },
] as const; ];
for (const testCase of cases) { for (const testCase of cases) {
expect(formatGatewaySummary(testCase.input), testCase.name).toBe(testCase.expected); expect(formatGatewaySummary(testCase.input), testCase.name).toBe(testCase.expected);
@@ -844,7 +844,7 @@ describe("normalizeOutboundPayloadsForJson", () => {
}, },
], ],
}, },
] as const; ];
for (const testCase of cases) { for (const testCase of cases) {
expect(normalizeOutboundPayloadsForJson(testCase.input)).toEqual(testCase.expected); expect(normalizeOutboundPayloadsForJson(testCase.input)).toEqual(testCase.expected);
@@ -879,7 +879,7 @@ describe("formatOutboundPayloadLog", () => {
}, },
expected: "MEDIA:https://x.test/a.png", expected: "MEDIA:https://x.test/a.png",
}, },
] as const; ];
for (const testCase of cases) { for (const testCase of cases) {
expect(formatOutboundPayloadLog(testCase.input), testCase.name).toBe(testCase.expected); expect(formatOutboundPayloadLog(testCase.input), testCase.name).toBe(testCase.expected);

View File

@@ -48,9 +48,19 @@ describe("jaccardSimilarity", () => {
expected: 1, expected: 1,
}, },
{ name: "disjoint sets", left: new Set(["a", "b"]), right: new Set(["c", "d"]), expected: 0 }, { name: "disjoint sets", left: new Set(["a", "b"]), right: new Set(["c", "d"]), expected: 0 },
{ name: "two empty sets", left: new Set(), right: new Set(), expected: 1 }, { name: "two empty sets", left: new Set<string>(), right: new Set<string>(), expected: 1 },
{ name: "left non-empty right empty", left: new Set(["a"]), right: new Set(), expected: 0 }, {
{ name: "left empty right non-empty", left: new Set(), right: new Set(["a"]), expected: 0 }, name: "left non-empty right empty",
left: new Set(["a"]),
right: new Set<string>(),
expected: 0,
},
{
name: "left empty right non-empty",
left: new Set<string>(),
right: new Set(["a"]),
expected: 0,
},
{ {
name: "partial overlap", name: "partial overlap",
left: new Set(["a", "b", "c"]), left: new Set(["a", "b", "c"]),

View File

@@ -1249,7 +1249,7 @@ describe("QmdMemoryManager", () => {
for (const testCase of cases) { for (const testCase of cases) {
const { manager } = await createManager(); const { manager } = await createManager();
const restoreOpen = testCase.installOpenSpy?.(); const restoreOpen = "installOpenSpy" in testCase ? testCase.installOpenSpy() : undefined;
try { try {
const result = await manager.readFile(testCase.request); const result = await manager.readFile(testCase.request);
expect(result, testCase.name).toEqual({ text: "", path: testCase.expectedPath }); expect(result, testCase.name).toEqual({ text: "", path: testCase.expectedPath });

View File

@@ -237,11 +237,15 @@ describe("edge cases", () => {
] as const; ] as const;
for (const testCase of cases) { for (const testCase of cases) {
const result = markdownToTelegramHtml(testCase.input); const result = markdownToTelegramHtml(testCase.input);
for (const expected of testCase.contains ?? []) { if ("contains" in testCase) {
expect(result, testCase.name).toContain(expected); for (const expected of testCase.contains) {
expect(result, testCase.name).toContain(expected);
}
} }
for (const unexpected of testCase.notContains ?? []) { if ("notContains" in testCase) {
expect(result, testCase.name).not.toContain(unexpected); for (const unexpected of testCase.notContains) {
expect(result, testCase.name).not.toContain(unexpected);
}
} }
} }
}); });
@@ -297,8 +301,10 @@ describe("edge cases", () => {
if ("expectedExact" in testCase) { if ("expectedExact" in testCase) {
expect(result, testCase.name).toBe(testCase.expectedExact); expect(result, testCase.name).toBe(testCase.expectedExact);
} }
for (const expected of testCase.contains ?? []) { if ("contains" in testCase) {
expect(result, testCase.name).toContain(expected); for (const expected of testCase.contains) {
expect(result, testCase.name).toContain(expected);
}
} }
} }
}); });

View File

@@ -166,7 +166,7 @@ describe("buildModelsKeyboard", () => {
for (const testCase of cases) { for (const testCase of cases) {
const result = buildModelsKeyboard({ const result = buildModelsKeyboard({
provider: "anthropic", provider: "anthropic",
models: testCase.params.models, models: [...testCase.params.models],
currentPage: testCase.params.currentPage, currentPage: testCase.params.currentPage,
totalPages: 3, totalPages: 3,
pageSize: 2, pageSize: 2,

View File

@@ -74,7 +74,11 @@ describe("sent-message-cache", () => {
describe("buildInlineKeyboard", () => { describe("buildInlineKeyboard", () => {
it("normalizes keyboard inputs", () => { it("normalizes keyboard inputs", () => {
const cases = [ const cases: Array<{
name: string;
input: Parameters<typeof buildInlineKeyboard>[0];
expected: ReturnType<typeof buildInlineKeyboard>;
}> = [
{ {
name: "empty input", name: "empty input",
input: undefined, input: undefined,
@@ -141,7 +145,7 @@ describe("buildInlineKeyboard", () => {
inline_keyboard: [[{ text: "Ok", callback_data: "cmd:ok" }]], inline_keyboard: [[{ text: "Ok", callback_data: "cmd:ok" }]],
}, },
}, },
] as const; ];
for (const testCase of cases) { for (const testCase of cases) {
expect(buildInlineKeyboard(testCase.input), testCase.name).toEqual(testCase.expected); expect(buildInlineKeyboard(testCase.input), testCase.name).toEqual(testCase.expected);
} }
@@ -539,7 +543,12 @@ describe("sendMessageTelegram", () => {
it("applies reply markup and thread options to split video-note sends", async () => { it("applies reply markup and thread options to split video-note sends", async () => {
const chatId = "123"; const chatId = "123";
const cases = [ const cases: Array<{
text: string;
options: Partial<NonNullable<Parameters<typeof sendMessageTelegram>[2]>>;
expectedVideoNote: Record<string, unknown>;
expectedMessage: Record<string, unknown>;
}> = [
{ {
text: "Check this out", text: "Check this out",
options: { options: {
@@ -564,7 +573,7 @@ describe("sendMessageTelegram", () => {
reply_to_message_id: 999, reply_to_message_id: 999,
}, },
}, },
] as const; ];
for (const testCase of cases) { for (const testCase of cases) {
const sendVideoNote = vi.fn().mockResolvedValue({ const sendVideoNote = vi.fn().mockResolvedValue({
@@ -681,7 +690,19 @@ describe("sendMessageTelegram", () => {
}); });
it("routes audio media to sendAudio/sendVoice based on voice compatibility", async () => { it("routes audio media to sendAudio/sendVoice based on voice compatibility", async () => {
const cases = [ const cases: Array<{
name: string;
chatId: string;
text: string;
mediaUrl: string;
contentType: string;
fileName: string;
asVoice?: boolean;
messageThreadId?: number;
replyToMessageId?: number;
expectedMethod: "sendAudio" | "sendVoice";
expectedOptions: Record<string, unknown>;
}> = [
{ {
name: "default audio send", name: "default audio send",
chatId: "123", chatId: "123",
@@ -732,7 +753,7 @@ describe("sendMessageTelegram", () => {
expectedMethod: "sendVoice" as const, expectedMethod: "sendVoice" as const,
expectedOptions: { caption: "caption", parse_mode: "HTML" }, expectedOptions: { caption: "caption", parse_mode: "HTML" },
}, },
] as const; ];
for (const testCase of cases) { for (const testCase of cases) {
const sendAudio = vi.fn().mockResolvedValue({ const sendAudio = vi.fn().mockResolvedValue({
@@ -1210,12 +1231,22 @@ describe("editMessageTelegram", () => {
}); });
it("handles button payload + parse fallback behavior", async () => { it("handles button payload + parse fallback behavior", async () => {
const cases = [ const cases: Array<{
name: string;
setup: () => {
text: string;
buttons: Parameters<typeof buildInlineKeyboard>[0];
};
expectedCalls: number;
firstExpectNoReplyMarkup?: boolean;
firstExpectReplyMarkup?: Record<string, unknown>;
secondExpectReplyMarkup?: Record<string, unknown>;
}> = [
{ {
name: "buttons undefined keeps existing keyboard", name: "buttons undefined keeps existing keyboard",
setup: () => { setup: () => {
botApi.editMessageText.mockResolvedValue({ message_id: 1, chat: { id: "123" } }); botApi.editMessageText.mockResolvedValue({ message_id: 1, chat: { id: "123" } });
return { text: "hi", buttons: undefined as [] | undefined }; return { text: "hi", buttons: undefined };
}, },
expectedCalls: 1, expectedCalls: 1,
firstExpectNoReplyMarkup: true, firstExpectNoReplyMarkup: true,
@@ -1224,7 +1255,7 @@ describe("editMessageTelegram", () => {
name: "buttons empty clears keyboard", name: "buttons empty clears keyboard",
setup: () => { setup: () => {
botApi.editMessageText.mockResolvedValue({ message_id: 1, chat: { id: "123" } }); botApi.editMessageText.mockResolvedValue({ message_id: 1, chat: { id: "123" } });
return { text: "hi", buttons: [] as [] }; return { text: "hi", buttons: [] };
}, },
expectedCalls: 1, expectedCalls: 1,
firstExpectReplyMarkup: { inline_keyboard: [] }, firstExpectReplyMarkup: { inline_keyboard: [] },
@@ -1235,13 +1266,13 @@ describe("editMessageTelegram", () => {
botApi.editMessageText botApi.editMessageText
.mockRejectedValueOnce(new Error("400: Bad Request: can't parse entities")) .mockRejectedValueOnce(new Error("400: Bad Request: can't parse entities"))
.mockResolvedValueOnce({ message_id: 1, chat: { id: "123" } }); .mockResolvedValueOnce({ message_id: 1, chat: { id: "123" } });
return { text: "<bad> html", buttons: [] as [] }; return { text: "<bad> html", buttons: [] };
}, },
expectedCalls: 2, expectedCalls: 2,
firstExpectReplyMarkup: { inline_keyboard: [] }, firstExpectReplyMarkup: { inline_keyboard: [] },
secondExpectReplyMarkup: { inline_keyboard: [] }, secondExpectReplyMarkup: { inline_keyboard: [] },
}, },
] as const; ];
for (const testCase of cases) { for (const testCase of cases) {
botApi.editMessageText.mockReset(); botApi.editMessageText.mockReset();