import { beforeEach, vi, type Mock } from "vitest"; import { resetInboundDedupe } from "../../../src/auto-reply/reply/inbound-dedupe.js"; export const useSpy: Mock = vi.fn(); export const middlewareUseSpy: Mock = vi.fn(); export const onSpy: Mock = vi.fn(); export const stopSpy: Mock = vi.fn(); export const sendChatActionSpy: Mock = vi.fn(); export const undiciFetchSpy: Mock = vi.fn((input: RequestInfo | URL, init?: RequestInit) => globalThis.fetch(input, init), ); async function defaultSaveMediaBuffer(buffer: Buffer, contentType?: string) { return { id: "media", path: "/tmp/telegram-media", size: buffer.byteLength, contentType: contentType ?? "application/octet-stream", }; } const saveMediaBufferSpy: Mock = vi.fn(defaultSaveMediaBuffer); export function setNextSavedMediaPath(params: { path: string; id?: string; contentType?: string; size?: number; }) { saveMediaBufferSpy.mockImplementationOnce( async (buffer: Buffer, detectedContentType?: string) => ({ id: params.id ?? "media", path: params.path, size: params.size ?? buffer.byteLength, contentType: params.contentType ?? detectedContentType ?? "application/octet-stream", }), ); } export function resetSaveMediaBufferMock() { saveMediaBufferSpy.mockReset(); saveMediaBufferSpy.mockImplementation(defaultSaveMediaBuffer); } type ApiStub = { config: { use: (arg: unknown) => void }; sendChatAction: Mock; sendMessage: Mock; setMyCommands: (commands: Array<{ command: string; description: string }>) => Promise; }; const apiStub: ApiStub = { config: { use: useSpy }, sendChatAction: sendChatActionSpy, sendMessage: vi.fn(async () => ({ message_id: 1 })), setMyCommands: vi.fn(async () => undefined), }; beforeEach(() => { resetInboundDedupe(); resetSaveMediaBufferMock(); }); vi.mock("grammy", () => ({ Bot: class { api = apiStub; use = middlewareUseSpy; on = onSpy; command = vi.fn(); stop = stopSpy; catch = vi.fn(); constructor(public token: string) {} }, InputFile: class {}, webhookCallback: vi.fn(), })); vi.mock("@grammyjs/runner", () => ({ sequentialize: () => vi.fn(), })); const throttlerSpy = vi.fn(() => "throttler"); vi.mock("@grammyjs/transformer-throttler", () => ({ apiThrottler: () => throttlerSpy(), })); vi.mock("undici", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, fetch: (...args: Parameters) => undiciFetchSpy(...args), }; }); vi.mock("../media/store.js", async (importOriginal) => { const actual = await importOriginal(); const mockModule = Object.create(null) as Record; Object.defineProperties(mockModule, Object.getOwnPropertyDescriptors(actual)); Object.defineProperty(mockModule, "saveMediaBuffer", { configurable: true, enumerable: true, writable: true, value: (...args: Parameters) => saveMediaBufferSpy(...args), }); return mockModule; }); vi.mock("../config/config.js", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, loadConfig: () => ({ channels: { telegram: { dmPolicy: "open", allowFrom: ["*"] } }, }), }; }); vi.mock("../config/sessions.js", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, updateLastRoute: vi.fn(async () => undefined), }; }); vi.mock("../pairing/pairing-store.js", () => ({ readChannelAllowFromStore: vi.fn(async () => [] as string[]), upsertChannelPairingRequest: vi.fn(async () => ({ code: "PAIRCODE", created: true, })), })); vi.mock("../auto-reply/reply.js", () => { const replySpy = vi.fn(async (_ctx, opts) => { await opts?.onReplyStart?.(); return undefined; }); return { getReplyFromConfig: replySpy, __replySpy: replySpy }; });