From 262b7a157ae6f84a243cd13cc44f260c6ece7270 Mon Sep 17 00:00:00 2001 From: cpojer Date: Tue, 17 Feb 2026 11:17:45 +0900 Subject: [PATCH] chore: chore: Fix types in tests 12/N. --- .../provider-usage.fetch.antigravity.test.ts | 4 +- src/infra/provider-usage.test.ts | 50 +++++++------- src/infra/session-cost-usage.ts | 14 ++-- src/runtime.ts | 8 +-- src/tui/tui-event-handlers.ts | 23 +++++-- ...resses-common-formats-jpeg-cap.e2e.test.ts | 66 ++++++++++--------- src/web/auto-reply/heartbeat-runner.test.ts | 32 +++++---- 7 files changed, 107 insertions(+), 90 deletions(-) diff --git a/src/infra/provider-usage.fetch.antigravity.test.ts b/src/infra/provider-usage.fetch.antigravity.test.ts index b22633e143..83e01741a3 100644 --- a/src/infra/provider-usage.fetch.antigravity.test.ts +++ b/src/infra/provider-usage.fetch.antigravity.test.ts @@ -13,7 +13,7 @@ const toRequestUrl = (input: Parameters[0]): string => const createAntigravityFetch = ( handler: (url: string, init?: Parameters[1]) => Promise | Response, ) => - vi.fn, ReturnType>(async (input, init) => + vi.fn(async (input: string | Request | URL, init?: RequestInit) => handler(toRequestUrl(input), init), ); @@ -38,7 +38,7 @@ function createEndpointFetch(spec: { } async function runUsage(mockFetch: ReturnType) { - return fetchAntigravityUsage("token-123", 5000, mockFetch); + return fetchAntigravityUsage("token-123", 5000, mockFetch as unknown as typeof fetch); } function findWindow(snapshot: Awaited>, label: string) { diff --git a/src/infra/provider-usage.test.ts b/src/infra/provider-usage.test.ts index c03022ef5e..8a2321c48d 100644 --- a/src/infra/provider-usage.test.ts +++ b/src/infra/provider-usage.test.ts @@ -23,7 +23,7 @@ function toRequestUrl(input: Parameters[0]): string { } function createMinimaxOnlyFetch(payload: unknown) { - return vi.fn, ReturnType>(async (input) => { + return vi.fn(async (input: string | Request | URL) => { if (toRequestUrl(input).includes(minimaxRemainsEndpoint)) { return makeResponse(200, payload); } @@ -41,7 +41,7 @@ async function expectMinimaxUsage( const summary = await loadProviderUsageSummary({ now: Date.UTC(2026, 0, 7, 0, 0, 0), auth: [{ provider: "minimax", token: "token-1b" }], - fetch: mockFetch, + fetch: mockFetch as unknown as typeof fetch, }); const minimax = summary.providers.find((p) => p.provider === "minimax"); @@ -113,7 +113,7 @@ describe("provider usage formatting", () => { describe("provider usage loading", () => { it("loads usage snapshots with injected auth", async () => { - const mockFetch = vi.fn, ReturnType>(async (input) => { + const mockFetch = vi.fn(async (input: string | Request | URL) => { const url = toRequestUrl(input); if (url.includes("api.anthropic.com")) { return makeResponse(200, { @@ -159,7 +159,7 @@ describe("provider usage loading", () => { { provider: "minimax", token: "token-1b" }, { provider: "zai", token: "token-2" }, ], - fetch: mockFetch, + fetch: mockFetch as unknown as typeof fetch, }); expect(summary.providers).toHaveLength(3); @@ -266,33 +266,27 @@ describe("provider usage loading", () => { return new Response(payload, { status, headers }); }; - const mockFetch = vi.fn, ReturnType>( - async (input, init) => { - const url = - typeof input === "string" - ? input - : input instanceof URL - ? input.toString() - : input.url; - if (url.includes("api.anthropic.com/api/oauth/usage")) { - const headers = (init?.headers ?? {}) as Record; - expect(headers.Authorization).toBe("Bearer token-1"); - return makeResponse(200, { - five_hour: { - utilization: 20, - resets_at: "2026-01-07T01:00:00Z", - }, - }); - } - return makeResponse(404, "not found"); - }, - ); + const mockFetch = vi.fn(async (input: string | Request | URL, init?: RequestInit) => { + const url = + typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url; + if (url.includes("api.anthropic.com/api/oauth/usage")) { + const headers = (init?.headers ?? {}) as Record; + expect(headers.Authorization).toBe("Bearer token-1"); + return makeResponse(200, { + five_hour: { + utilization: 20, + resets_at: "2026-01-07T01:00:00Z", + }, + }); + } + return makeResponse(404, "not found"); + }); const summary = await loadProviderUsageSummary({ now: Date.UTC(2026, 0, 7, 0, 0, 0), providers: ["anthropic"], agentDir, - fetch: mockFetch, + fetch: mockFetch as unknown as typeof fetch, }); expect(summary.providers).toHaveLength(1); @@ -321,7 +315,7 @@ describe("provider usage loading", () => { return new Response(payload, { status, headers }); }; - const mockFetch = vi.fn, ReturnType>(async (input) => { + const mockFetch = vi.fn(async (input: string | Request | URL) => { const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url; if (url.includes("api.anthropic.com/api/oauth/usage")) { @@ -349,7 +343,7 @@ describe("provider usage loading", () => { const summary = await loadProviderUsageSummary({ now: Date.UTC(2026, 0, 7, 0, 0, 0), auth: [{ provider: "anthropic", token: "sk-ant-oauth-1" }], - fetch: mockFetch, + fetch: mockFetch as unknown as typeof fetch, }); expect(summary.providers).toHaveLength(1); diff --git a/src/infra/session-cost-usage.ts b/src/infra/session-cost-usage.ts index 96bebb9a96..53aeb55ffb 100644 --- a/src/infra/session-cost-usage.ts +++ b/src/infra/session-cost-usage.ts @@ -2,8 +2,15 @@ import fs from "node:fs"; import path from "node:path"; import readline from "node:readline"; import type { NormalizedUsage, UsageLike } from "../agents/usage.js"; +import { normalizeUsage } from "../agents/usage.js"; import type { OpenClawConfig } from "../config/config.js"; +import { + resolveSessionFilePath, + resolveSessionTranscriptsDirForAgent, +} from "../config/sessions/paths.js"; import type { SessionEntry } from "../config/sessions/types.js"; +import { countToolResults, extractToolCallNames } from "../utils/transcript-tools.js"; +import { estimateUsageCost, resolveModelCostConfig } from "../utils/usage-format.js"; import type { CostBreakdown, CostUsageTotals, @@ -24,13 +31,6 @@ import type { SessionUsageTimePoint, SessionUsageTimeSeries, } from "./session-cost-usage.types.js"; -import { normalizeUsage } from "../agents/usage.js"; -import { - resolveSessionFilePath, - resolveSessionTranscriptsDirForAgent, -} from "../config/sessions/paths.js"; -import { countToolResults, extractToolCallNames } from "../utils/transcript-tools.js"; -import { estimateUsageCost, resolveModelCostConfig } from "../utils/usage-format.js"; export type { CostUsageDailyEntry, diff --git a/src/runtime.ts b/src/runtime.ts index 1d4a25b177..dcb1b305e6 100644 --- a/src/runtime.ts +++ b/src/runtime.ts @@ -2,9 +2,9 @@ import { clearActiveProgressLine } from "./terminal/progress-line.js"; import { restoreTerminalState } from "./terminal/restore.js"; export type RuntimeEnv = { - log: typeof console.log; - error: typeof console.error; - exit: (code: number) => never; + log: (...args: unknown[]) => void; + error: (...args: unknown[]) => void; + exit: (code: number) => void; }; function shouldEmitRuntimeLog(env: NodeJS.ProcessEnv = process.env): boolean { @@ -46,7 +46,7 @@ export const defaultRuntime: RuntimeEnv = { export function createNonExitingRuntime(): RuntimeEnv { return { ...createRuntimeIo(), - exit: (code: number): never => { + exit: (code: number) => { throw new Error(`exit ${code}`); }, }; diff --git a/src/tui/tui-event-handlers.ts b/src/tui/tui-event-handlers.ts index d7f0dcf2f0..d852924e96 100644 --- a/src/tui/tui-event-handlers.ts +++ b/src/tui/tui-event-handlers.ts @@ -1,12 +1,27 @@ -import { TUI } from "@mariozechner/pi-tui"; -import { ChatLog } from "./components/chat-log.js"; import { asString, extractTextFromMessage, isCommandMessage } from "./tui-formatters.js"; import { TuiStreamAssembler } from "./tui-stream-assembler.js"; import type { AgentEvent, ChatEvent, TuiStateAccess } from "./tui-types.js"; +type EventHandlerChatLog = { + startTool: (toolCallId: string, toolName: string, args: unknown) => void; + updateToolResult: ( + toolCallId: string, + result: unknown, + options?: { partial?: boolean; isError?: boolean }, + ) => void; + addSystem: (text: string) => void; + updateAssistant: (text: string, runId: string) => void; + finalizeAssistant: (text: string, runId: string) => void; + dropAssistant: (runId: string) => void; +}; + +type EventHandlerTui = { + requestRender: () => void; +}; + type EventHandlerContext = { - chatLog: ChatLog; - tui: TUI; + chatLog: EventHandlerChatLog; + tui: EventHandlerTui; state: TuiStateAccess; setActivityStatus: (text: string) => void; refreshSessionInfo?: () => Promise; diff --git a/src/web/auto-reply.web-auto-reply.compresses-common-formats-jpeg-cap.e2e.test.ts b/src/web/auto-reply.web-auto-reply.compresses-common-formats-jpeg-cap.e2e.test.ts index 8e1badbf47..da3f7ae0c9 100644 --- a/src/web/auto-reply.web-auto-reply.compresses-common-formats-jpeg-cap.e2e.test.ts +++ b/src/web/auto-reply.web-auto-reply.compresses-common-formats-jpeg-cap.e2e.test.ts @@ -8,11 +8,13 @@ import { resetLoadConfigMock, setLoadConfigMock, } from "./auto-reply.test-harness.js"; +import type { WebInboundMessage } from "./inbound.js"; installWebAutoReplyTestHomeHooks(); describe("web auto-reply", () => { installWebAutoReplyUnitTestHooks({ pinDns: true }); + type ListenerFactory = NonNullable[1]>; async function setupSingleInboundMessage(params: { resolverValue: { text: string; mediaUrl: string }; @@ -20,16 +22,12 @@ describe("web auto-reply", () => { reply?: ReturnType; }) { const reply = params.reply ?? vi.fn().mockResolvedValue(undefined); - const sendComposing = vi.fn(); + const sendComposing = vi.fn(async () => undefined); const resolver = vi.fn().mockResolvedValue(params.resolverValue); - let capturedOnMessage: - | ((msg: import("./inbound.js").WebInboundMessage) => Promise) - | undefined; - const listenerFactory = async (opts: { - onMessage: (msg: import("./inbound.js").WebInboundMessage) => Promise; - }) => { - capturedOnMessage = opts.onMessage; + let capturedOnMessage: ((msg: WebInboundMessage) => Promise) | undefined; + const listenerFactory: ListenerFactory = async ({ onMessage }) => { + capturedOnMessage = onMessage; return { close: vi.fn() }; }; @@ -42,12 +40,16 @@ describe("web auto-reply", () => { await capturedOnMessage?.({ body: "hello", from: "+1", + conversationId: "+1", to: "+2", + accountId: "default", + chatType: "direct", + chatId: "+1", id, sendComposing, reply, sendMedia: params.sendMedia, - }); + } as WebInboundMessage); }, }; } @@ -104,19 +106,15 @@ describe("web auto-reply", () => { setLoadConfigMock(() => ({ agents: { defaults: { mediaMaxMb: 1 } } })); const sendMedia = vi.fn(); const reply = vi.fn().mockResolvedValue(undefined); - const sendComposing = vi.fn(); + const sendComposing = vi.fn(async () => undefined); const resolver = vi.fn().mockResolvedValue({ text: "hi", mediaUrl: `https://example.com/big.${fmt.name}`, }); - let capturedOnMessage: - | ((msg: import("./inbound.js").WebInboundMessage) => Promise) - | undefined; - const listenerFactory = async (opts: { - onMessage: (msg: import("./inbound.js").WebInboundMessage) => Promise; - }) => { - capturedOnMessage = opts.onMessage; + let capturedOnMessage: ((msg: WebInboundMessage) => Promise) | undefined; + const listenerFactory: ListenerFactory = async ({ onMessage }) => { + capturedOnMessage = onMessage; return { close: vi.fn() }; }; @@ -129,7 +127,7 @@ describe("web auto-reply", () => { arrayBuffer: async () => big.buffer.slice(big.byteOffset, big.byteOffset + big.byteLength), headers: { get: () => fmt.mime }, status: 200, - } as Response); + } as unknown as Response); await monitorWebChannel(false, listenerFactory, false, resolver); expect(capturedOnMessage).toBeDefined(); @@ -137,12 +135,16 @@ describe("web auto-reply", () => { await capturedOnMessage?.({ body: "hello", from: "+1", + conversationId: "+1", to: "+2", + accountId: "default", + chatType: "direct", + chatId: "+1", id: `msg-${fmt.name}`, sendComposing, reply, sendMedia, - }); + } as WebInboundMessage); expect(sendMedia).toHaveBeenCalledTimes(1); const payload = sendMedia.mock.calls[0][0] as { @@ -162,19 +164,15 @@ describe("web auto-reply", () => { setLoadConfigMock(() => ({ agents: { defaults: { mediaMaxMb: 1 } } })); const sendMedia = vi.fn(); const reply = vi.fn().mockResolvedValue(undefined); - const sendComposing = vi.fn(); + const sendComposing = vi.fn(async () => undefined); const resolver = vi.fn().mockResolvedValue({ text: "hi", mediaUrl: "https://example.com/big.png", }); - let capturedOnMessage: - | ((msg: import("./inbound.js").WebInboundMessage) => Promise) - | undefined; - const listenerFactory = async (opts: { - onMessage: (msg: import("./inbound.js").WebInboundMessage) => Promise; - }) => { - capturedOnMessage = opts.onMessage; + let capturedOnMessage: ((msg: WebInboundMessage) => Promise) | undefined; + const listenerFactory: ListenerFactory = async ({ onMessage }) => { + capturedOnMessage = onMessage; return { close: vi.fn() }; }; @@ -197,7 +195,7 @@ describe("web auto-reply", () => { bigPng.buffer.slice(bigPng.byteOffset, bigPng.byteOffset + bigPng.byteLength), headers: { get: () => "image/png" }, status: 200, - } as Response); + } as unknown as Response); await monitorWebChannel(false, listenerFactory, false, resolver); expect(capturedOnMessage).toBeDefined(); @@ -205,12 +203,16 @@ describe("web auto-reply", () => { await capturedOnMessage?.({ body: "hello", from: "+1", + conversationId: "+1", to: "+2", + accountId: "default", + chatType: "direct", + chatId: "+1", id: "msg1", sendComposing, reply, sendMedia, - }); + } as WebInboundMessage); expect(sendMedia).toHaveBeenCalledTimes(1); const payload = sendMedia.mock.calls[0][0] as { @@ -237,7 +239,7 @@ describe("web auto-reply", () => { arrayBuffer: async () => Buffer.from("%PDF-1.4").buffer, headers: { get: () => "application/pdf" }, status: 200, - } as Response); + } as unknown as Response); await dispatch("msg-pdf"); @@ -282,7 +284,7 @@ describe("web auto-reply", () => { smallPng.buffer.slice(smallPng.byteOffset, smallPng.byteOffset + smallPng.byteLength), headers: { get: () => "image/png" }, status: 200, - } as Response); + } as unknown as Response); await dispatch("msg1"); @@ -347,7 +349,7 @@ describe("web auto-reply", () => { arrayBuffer: async () => png.buffer.slice(png.byteOffset, png.byteOffset + png.byteLength), headers: { get: () => "image/png" }, status: 200, - } as Response); + } as unknown as Response); await dispatch("msg1"); diff --git a/src/web/auto-reply/heartbeat-runner.test.ts b/src/web/auto-reply/heartbeat-runner.test.ts index 5b878641a7..e6e5d234f8 100644 --- a/src/web/auto-reply/heartbeat-runner.test.ts +++ b/src/web/auto-reply/heartbeat-runner.test.ts @@ -1,5 +1,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; +import type { getReplyFromConfig } from "../../auto-reply/reply.js"; import { HEARTBEAT_TOKEN } from "../../auto-reply/tokens.js"; +import type { sendMessageWhatsApp } from "../outbound.js"; const state = vi.hoisted(() => ({ visibility: { showAlerts: true, showOk: true, useIndicator: false }, @@ -87,8 +89,10 @@ vi.mock("../session.js", () => ({ })); describe("runWebHeartbeatOnce", () => { - let sender: ReturnType; - let replyResolver: ReturnType; + let senderMock: ReturnType; + let sender: typeof sendMessageWhatsApp; + let replyResolverMock: ReturnType; + let replyResolver: typeof getReplyFromConfig; const getModules = async () => await import("./heartbeat-runner.js"); @@ -105,8 +109,10 @@ describe("runWebHeartbeatOnce", () => { }; state.events = []; - sender = vi.fn(async () => ({ messageId: "m1" })); - replyResolver = vi.fn(async () => undefined); + senderMock = vi.fn(async () => ({ messageId: "m1" })); + sender = senderMock as unknown as typeof sendMessageWhatsApp; + replyResolverMock = vi.fn(async () => undefined); + replyResolver = replyResolverMock as unknown as typeof getReplyFromConfig; }); it("supports manual override body dry-run without sending", async () => { @@ -119,7 +125,7 @@ describe("runWebHeartbeatOnce", () => { overrideBody: "hello", dryRun: true, }); - expect(sender).not.toHaveBeenCalled(); + expect(senderMock).not.toHaveBeenCalled(); expect(state.events).toHaveLength(0); }); @@ -131,7 +137,7 @@ describe("runWebHeartbeatOnce", () => { sender, replyResolver, }); - expect(sender).toHaveBeenCalledWith("+123", HEARTBEAT_TOKEN, { verbose: false }); + expect(senderMock).toHaveBeenCalledWith("+123", HEARTBEAT_TOKEN, { verbose: false }); expect(state.events).toEqual( expect.arrayContaining([expect.objectContaining({ status: "ok-empty", silent: false })]), ); @@ -147,13 +153,13 @@ describe("runWebHeartbeatOnce", () => { dryRun: true, }); expect(replyResolver).toHaveBeenCalledTimes(1); - const ctx = replyResolver.mock.calls[0]?.[0]; + const ctx = replyResolverMock.mock.calls[0]?.[0]; expect(ctx?.Body).toContain("Ops check"); expect(ctx?.Body).toContain("Current time: 2026-02-15T00:00:00Z (mock)"); }); it("treats heartbeat token-only replies as ok-token and preserves session updatedAt", async () => { - replyResolver.mockResolvedValue({ text: HEARTBEAT_TOKEN }); + replyResolverMock.mockResolvedValue({ text: HEARTBEAT_TOKEN }); const { runWebHeartbeatOnce } = await getModules(); await runWebHeartbeatOnce({ cfg: { agents: { defaults: {} }, session: {} } as never, @@ -162,7 +168,7 @@ describe("runWebHeartbeatOnce", () => { replyResolver, }); expect(state.store.k?.updatedAt).toBe(123); - expect(sender).toHaveBeenCalledWith("+123", HEARTBEAT_TOKEN, { verbose: false }); + expect(senderMock).toHaveBeenCalledWith("+123", HEARTBEAT_TOKEN, { verbose: false }); expect(state.events).toEqual( expect.arrayContaining([expect.objectContaining({ status: "ok-token", silent: false })]), ); @@ -170,7 +176,7 @@ describe("runWebHeartbeatOnce", () => { it("skips sending alerts when showAlerts is disabled but still emits a skipped event", async () => { state.visibility = { showAlerts: false, showOk: true, useIndicator: true }; - replyResolver.mockResolvedValue({ text: "ALERT" }); + replyResolverMock.mockResolvedValue({ text: "ALERT" }); const { runWebHeartbeatOnce } = await getModules(); await runWebHeartbeatOnce({ cfg: { agents: { defaults: {} }, session: {} } as never, @@ -178,7 +184,7 @@ describe("runWebHeartbeatOnce", () => { sender, replyResolver, }); - expect(sender).not.toHaveBeenCalled(); + expect(senderMock).not.toHaveBeenCalled(); expect(state.events).toEqual( expect.arrayContaining([ expect.objectContaining({ status: "skipped", reason: "alerts-disabled", preview: "ALERT" }), @@ -187,8 +193,8 @@ describe("runWebHeartbeatOnce", () => { }); it("emits failed events when sending throws and rethrows the error", async () => { - replyResolver.mockResolvedValue({ text: "ALERT" }); - sender.mockRejectedValueOnce(new Error("nope")); + replyResolverMock.mockResolvedValue({ text: "ALERT" }); + senderMock.mockRejectedValueOnce(new Error("nope")); const { runWebHeartbeatOnce } = await getModules(); await expect( runWebHeartbeatOnce({