fix: clear typecheck backlog

This commit is contained in:
Peter Steinberger 2026-03-13 22:08:54 +00:00
parent a66a0852bb
commit d0337a18b6
No known key found for this signature in database
10 changed files with 63 additions and 46 deletions

View File

@ -328,13 +328,14 @@ describe("BlueBubbles webhook monitor", () => {
} }
function createHangingWebhookRequest(url = "/bluebubbles-webhook?password=test-password") { function createHangingWebhookRequest(url = "/bluebubbles-webhook?password=test-password") {
const req = new EventEmitter() as IncomingMessage & { destroy: ReturnType<typeof vi.fn> }; const req = new EventEmitter() as IncomingMessage;
const destroyMock = vi.fn();
req.method = "POST"; req.method = "POST";
req.url = url; req.url = url;
req.headers = {}; req.headers = {};
req.destroy = vi.fn(); req.destroy = destroyMock as unknown as IncomingMessage["destroy"];
setRequestRemoteAddress(req, "127.0.0.1"); setRequestRemoteAddress(req, "127.0.0.1");
return req; return { req, destroyMock };
} }
function registerWebhookTargets( function registerWebhookTargets(
@ -415,7 +416,7 @@ describe("BlueBubbles webhook monitor", () => {
setupWebhookTarget(); setupWebhookTarget();
// Create a request that never sends data or ends (simulates slow-loris) // Create a request that never sends data or ends (simulates slow-loris)
const req = createHangingWebhookRequest(); const { req, destroyMock } = createHangingWebhookRequest();
const res = createMockResponse(); const res = createMockResponse();
@ -427,7 +428,7 @@ describe("BlueBubbles webhook monitor", () => {
const handled = await handledPromise; const handled = await handledPromise;
expect(handled).toBe(true); expect(handled).toBe(true);
expect(res.statusCode).toBe(408); expect(res.statusCode).toBe(408);
expect(req.destroy).toHaveBeenCalled(); expect(destroyMock).toHaveBeenCalled();
} finally { } finally {
vi.useRealTimers(); vi.useRealTimers();
} }
@ -436,7 +437,7 @@ describe("BlueBubbles webhook monitor", () => {
it("rejects unauthorized requests before reading the body", async () => { it("rejects unauthorized requests before reading the body", async () => {
const account = createMockAccount({ password: "secret-token" }); const account = createMockAccount({ password: "secret-token" });
setupWebhookTarget({ account }); setupWebhookTarget({ account });
const req = createHangingWebhookRequest("/bluebubbles-webhook?password=wrong-token"); const { req } = createHangingWebhookRequest("/bluebubbles-webhook?password=wrong-token");
const onSpy = vi.spyOn(req, "on"); const onSpy = vi.spyOn(req, "on");
await expectWebhookStatus(req, 401); await expectWebhookStatus(req, 401);
expect(onSpy).not.toHaveBeenCalledWith("data", expect.any(Function)); expect(onSpy).not.toHaveBeenCalledWith("data", expect.any(Function));

View File

@ -40,7 +40,7 @@ function setupSuccessClient() {
async function expectDefaultSuccessResult( async function expectDefaultSuccessResult(
creds = DEFAULT_CREDS, creds = DEFAULT_CREDS,
expected = DEFAULT_SUCCESS_RESULT, expected: Awaited<ReturnType<typeof probeFeishu>> = DEFAULT_SUCCESS_RESULT,
) { ) {
const result = await probeFeishu(creds); const result = await probeFeishu(creds);
expect(result).toEqual(expected); expect(result).toEqual(expected);

View File

@ -14,6 +14,17 @@ vi.mock("../send.js", () => ({
describe("registerMatrixMonitorEvents", () => { describe("registerMatrixMonitorEvents", () => {
const roomId = "!room:example.org"; const roomId = "!room:example.org";
function makeEvent(overrides: Partial<MatrixRawEvent>): MatrixRawEvent {
return {
event_id: "$event",
sender: "@alice:example.org",
type: "m.room.message",
origin_server_ts: 0,
content: {},
...overrides,
};
}
beforeEach(() => { beforeEach(() => {
sendReadReceiptMatrixMock.mockClear(); sendReadReceiptMatrixMock.mockClear();
}); });
@ -67,10 +78,10 @@ describe("registerMatrixMonitorEvents", () => {
it("sends read receipt immediately for non-self messages", async () => { it("sends read receipt immediately for non-self messages", async () => {
const { client, onRoomMessage, roomMessageHandler } = createHarness(); const { client, onRoomMessage, roomMessageHandler } = createHarness();
const event = { const event = makeEvent({
event_id: "$e1", event_id: "$e1",
sender: "@alice:example.org", sender: "@alice:example.org",
} as MatrixRawEvent; });
roomMessageHandler("!room:example.org", event); roomMessageHandler("!room:example.org", event);
@ -81,22 +92,27 @@ describe("registerMatrixMonitorEvents", () => {
}); });
it("does not send read receipts for self messages", async () => { it("does not send read receipts for self messages", async () => {
await expectForwardedWithoutReadReceipt({ await expectForwardedWithoutReadReceipt(
event_id: "$e2", makeEvent({
sender: "@bot:example.org", event_id: "$e2",
}); sender: "@bot:example.org",
}),
);
}); });
it("skips receipt when message lacks sender or event id", async () => { it("skips receipt when message lacks sender or event id", async () => {
await expectForwardedWithoutReadReceipt({ await expectForwardedWithoutReadReceipt(
sender: "@alice:example.org", makeEvent({
}); sender: "@alice:example.org",
event_id: "",
}),
);
}); });
it("caches self user id across messages", async () => { it("caches self user id across messages", async () => {
const { getUserId, roomMessageHandler } = createHarness(); const { getUserId, roomMessageHandler } = createHarness();
const first = { event_id: "$e3", sender: "@alice:example.org" } as MatrixRawEvent; const first = makeEvent({ event_id: "$e3", sender: "@alice:example.org" });
const second = { event_id: "$e4", sender: "@bob:example.org" } as MatrixRawEvent; const second = makeEvent({ event_id: "$e4", sender: "@bob:example.org" });
roomMessageHandler("!room:example.org", first); roomMessageHandler("!room:example.org", first);
roomMessageHandler("!room:example.org", second); roomMessageHandler("!room:example.org", second);
@ -110,7 +126,7 @@ describe("registerMatrixMonitorEvents", () => {
it("logs and continues when sending read receipt fails", async () => { it("logs and continues when sending read receipt fails", async () => {
sendReadReceiptMatrixMock.mockRejectedValueOnce(new Error("network boom")); sendReadReceiptMatrixMock.mockRejectedValueOnce(new Error("network boom"));
const { roomMessageHandler, onRoomMessage, logVerboseMessage } = createHarness(); const { roomMessageHandler, onRoomMessage, logVerboseMessage } = createHarness();
const event = { event_id: "$e5", sender: "@alice:example.org" } as MatrixRawEvent; const event = makeEvent({ event_id: "$e5", sender: "@alice:example.org" });
roomMessageHandler("!room:example.org", event); roomMessageHandler("!room:example.org", event);
@ -126,7 +142,7 @@ describe("registerMatrixMonitorEvents", () => {
const { roomMessageHandler, onRoomMessage, getUserId } = createHarness({ const { roomMessageHandler, onRoomMessage, getUserId } = createHarness({
getUserId: vi.fn().mockRejectedValue(new Error("cannot resolve self")), getUserId: vi.fn().mockRejectedValue(new Error("cannot resolve self")),
}); });
const event = { event_id: "$e6", sender: "@alice:example.org" } as MatrixRawEvent; const event = makeEvent({ event_id: "$e6", sender: "@alice:example.org" });
roomMessageHandler("!room:example.org", event); roomMessageHandler("!room:example.org", event);

View File

@ -115,6 +115,13 @@ function createMockContext(overrides?: Partial<NostrProfileHttpContext>): NostrP
}; };
} }
function expectOkResponse(res: ReturnType<typeof createMockResponse>) {
expect(res._getStatusCode()).toBe(200);
const data = JSON.parse(res._getData());
expect(data.ok).toBe(true);
return data;
}
function mockSuccessfulProfileImport() { function mockSuccessfulProfileImport() {
vi.mocked(importProfileFromRelays).mockResolvedValue({ vi.mocked(importProfileFromRelays).mockResolvedValue({
ok: true, ok: true,
@ -217,13 +224,6 @@ describe("nostr-profile-http", () => {
}); });
} }
function expectOkResponse(res: ReturnType<typeof createMockResponse>) {
expect(res._getStatusCode()).toBe(200);
const data = JSON.parse(res._getData());
expect(data.ok).toBe(true);
return data;
}
function expectBadRequestResponse(res: ReturnType<typeof createMockResponse>) { function expectBadRequestResponse(res: ReturnType<typeof createMockResponse>) {
expect(res._getStatusCode()).toBe(400); expect(res._getStatusCode()).toBe(400);
const data = JSON.parse(res._getData()); const data = JSON.parse(res._getData());

View File

@ -2,6 +2,7 @@ import { EventEmitter } from "node:events";
import type { IncomingMessage, ServerResponse } from "node:http"; import type { IncomingMessage, ServerResponse } from "node:http";
import { describe, it, expect, vi, beforeEach } from "vitest"; import { describe, it, expect, vi, beforeEach } from "vitest";
import type { ResolvedSynologyChatAccount } from "./types.js"; import type { ResolvedSynologyChatAccount } from "./types.js";
import type { WebhookHandlerDeps } from "./webhook-handler.js";
import { import {
clearSynologyWebhookRateLimiterStateForTest, clearSynologyWebhookRateLimiterStateForTest,
createWebhookHandler, createWebhookHandler,
@ -118,7 +119,7 @@ describe("createWebhookHandler", () => {
async function expectForbiddenByPolicy(params: { async function expectForbiddenByPolicy(params: {
account: Partial<ResolvedSynologyChatAccount>; account: Partial<ResolvedSynologyChatAccount>;
bodyContains: string; bodyContains: string;
deliver?: ReturnType<typeof vi.fn>; deliver?: WebhookHandlerDeps["deliver"];
}) { }) {
const deliver = params.deliver ?? vi.fn(); const deliver = params.deliver ?? vi.fn();
const handler = createWebhookHandler({ const handler = createWebhookHandler({

View File

@ -289,7 +289,7 @@ async function handleTextMessage(
} }
async function handleImageMessage(params: ZaloImageMessageParams): Promise<void> { async function handleImageMessage(params: ZaloImageMessageParams): Promise<void> {
const { message, mediaMaxMb } = params; const { message, mediaMaxMb, account, core, runtime } = params;
const { photo, caption } = message; const { photo, caption } = message;
let mediaPath: string | undefined; let mediaPath: string | undefined;

View File

@ -77,21 +77,14 @@ function resolveValidatedSendContext(
return { ok: true, chatId: trimmedChatId, token, fetcher }; return { ok: true, chatId: trimmedChatId, token, fetcher };
} }
function toInvalidContextResult(
context: ReturnType<typeof resolveValidatedSendContext>,
): ZaloSendResult | null {
return context.ok ? null : { ok: false, error: context.error };
}
export async function sendMessageZalo( export async function sendMessageZalo(
chatId: string, chatId: string,
text: string, text: string,
options: ZaloSendOptions = {}, options: ZaloSendOptions = {},
): Promise<ZaloSendResult> { ): Promise<ZaloSendResult> {
const context = resolveValidatedSendContext(chatId, options); const context = resolveValidatedSendContext(chatId, options);
const invalidResult = toInvalidContextResult(context); if (!context.ok) {
if (invalidResult) { return { ok: false, error: context.error };
return invalidResult;
} }
if (options.mediaUrl) { if (options.mediaUrl) {
@ -120,9 +113,8 @@ export async function sendPhotoZalo(
options: ZaloSendOptions = {}, options: ZaloSendOptions = {},
): Promise<ZaloSendResult> { ): Promise<ZaloSendResult> {
const context = resolveValidatedSendContext(chatId, options); const context = resolveValidatedSendContext(chatId, options);
const invalidResult = toInvalidContextResult(context); if (!context.ok) {
if (invalidResult) { return { ok: false, error: context.error };
return invalidResult;
} }
if (!photoUrl?.trim()) { if (!photoUrl?.trim()) {

View File

@ -1,6 +1,7 @@
import fs from "node:fs/promises"; import fs from "node:fs/promises";
import path from "node:path"; import path from "node:path";
import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest";
import type { RuntimeEnv } from "../runtime.js";
import { makeTempWorkspace } from "../test-helpers/workspace.js"; import { makeTempWorkspace } from "../test-helpers/workspace.js";
import { captureEnv } from "../test-utils/env.js"; import { captureEnv } from "../test-utils/env.js";
import { createThrowingRuntime, readJsonFile } from "./onboard-non-interactive.test-helpers.js"; import { createThrowingRuntime, readJsonFile } from "./onboard-non-interactive.test-helpers.js";
@ -408,11 +409,17 @@ describe("onboard (non-interactive): gateway and remote auth", () => {
})); }));
let capturedError = ""; let capturedError = "";
const runtimeWithCapture = { const runtimeWithCapture: RuntimeEnv = {
log: () => {}, log: () => {},
error: (message: string) => { error: (...args: unknown[]) => {
capturedError = message; const firstArg = args[0];
throw new Error(message); capturedError =
typeof firstArg === "string"
? firstArg
: firstArg instanceof Error
? firstArg.message
: (JSON.stringify(firstArg) ?? "");
throw new Error(capturedError);
}, },
exit: (_code: number) => { exit: (_code: number) => {
throw new Error("exit should not be reached after runtime.error"); throw new Error("exit should not be reached after runtime.error");

View File

@ -34,6 +34,7 @@ import {
} from "./invoke-system-run-plan.js"; } from "./invoke-system-run-plan.js";
import type { import type {
ExecEventPayload, ExecEventPayload,
ExecFinishedResult,
ExecFinishedEventParams, ExecFinishedEventParams,
RunResult, RunResult,
SkillBinsProvider, SkillBinsProvider,

View File

@ -17,7 +17,6 @@ vi.mock("../../../pairing/pairing-store.js", () => ({
})); }));
type MessageHandler = (args: { event: Record<string, unknown>; body: unknown }) => Promise<void>; type MessageHandler = (args: { event: Record<string, unknown>; body: unknown }) => Promise<void>;
type AppMentionHandler = MessageHandler;
type RegisteredEventName = "message" | "app_mention"; type RegisteredEventName = "message" | "app_mention";
type MessageCase = { type MessageCase = {