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

View File

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

View File

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

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() {
vi.mocked(importProfileFromRelays).mockResolvedValue({
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>) {
expect(res._getStatusCode()).toBe(400);
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 { describe, it, expect, vi, beforeEach } from "vitest";
import type { ResolvedSynologyChatAccount } from "./types.js";
import type { WebhookHandlerDeps } from "./webhook-handler.js";
import {
clearSynologyWebhookRateLimiterStateForTest,
createWebhookHandler,
@ -118,7 +119,7 @@ describe("createWebhookHandler", () => {
async function expectForbiddenByPolicy(params: {
account: Partial<ResolvedSynologyChatAccount>;
bodyContains: string;
deliver?: ReturnType<typeof vi.fn>;
deliver?: WebhookHandlerDeps["deliver"];
}) {
const deliver = params.deliver ?? vi.fn();
const handler = createWebhookHandler({

View File

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

View File

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

View File

@ -1,6 +1,7 @@
import fs from "node:fs/promises";
import path from "node:path";
import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest";
import type { RuntimeEnv } from "../runtime.js";
import { makeTempWorkspace } from "../test-helpers/workspace.js";
import { captureEnv } from "../test-utils/env.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 = "";
const runtimeWithCapture = {
const runtimeWithCapture: RuntimeEnv = {
log: () => {},
error: (message: string) => {
capturedError = message;
throw new Error(message);
error: (...args: unknown[]) => {
const firstArg = args[0];
capturedError =
typeof firstArg === "string"
? firstArg
: firstArg instanceof Error
? firstArg.message
: (JSON.stringify(firstArg) ?? "");
throw new Error(capturedError);
},
exit: (_code: number) => {
throw new Error("exit should not be reached after runtime.error");

View File

@ -34,6 +34,7 @@ import {
} from "./invoke-system-run-plan.js";
import type {
ExecEventPayload,
ExecFinishedResult,
ExecFinishedEventParams,
RunResult,
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 AppMentionHandler = MessageHandler;
type RegisteredEventName = "message" | "app_mention";
type MessageCase = {