From 7d11f6cf69535f84b1d0ac47cedbe0696637f980 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sun, 22 Mar 2026 19:07:04 -0700 Subject: [PATCH] test(msteams): cover upload and webhook helpers --- .../msteams/src/pending-uploads.test.ts | 84 +++++++++++++++++++ .../msteams/src/webhook-timeouts.test.ts | 36 ++++++++ 2 files changed, 120 insertions(+) create mode 100644 extensions/msteams/src/pending-uploads.test.ts create mode 100644 extensions/msteams/src/webhook-timeouts.test.ts diff --git a/extensions/msteams/src/pending-uploads.test.ts b/extensions/msteams/src/pending-uploads.test.ts new file mode 100644 index 00000000000..9c8da0b8e88 --- /dev/null +++ b/extensions/msteams/src/pending-uploads.test.ts @@ -0,0 +1,84 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { + clearPendingUploads, + getPendingUpload, + getPendingUploadCount, + removePendingUpload, + storePendingUpload, +} from "./pending-uploads.js"; + +describe("msteams pending uploads", () => { + beforeEach(() => { + vi.useFakeTimers(); + clearPendingUploads(); + }); + + afterEach(() => { + clearPendingUploads(); + vi.useRealTimers(); + }); + + it("stores uploads, exposes them by id, and tracks count", () => { + const id = storePendingUpload({ + buffer: Buffer.from("hello"), + filename: "hello.txt", + contentType: "text/plain", + conversationId: "conv-1", + }); + + expect(getPendingUploadCount()).toBe(1); + expect(getPendingUpload(id)).toEqual( + expect.objectContaining({ + id, + filename: "hello.txt", + contentType: "text/plain", + conversationId: "conv-1", + }), + ); + }); + + it("removes uploads explicitly and ignores empty ids", () => { + const id = storePendingUpload({ + buffer: Buffer.from("hello"), + filename: "hello.txt", + conversationId: "conv-1", + }); + + removePendingUpload(undefined); + expect(getPendingUploadCount()).toBe(1); + + removePendingUpload(id); + expect(getPendingUpload(id)).toBeUndefined(); + expect(getPendingUploadCount()).toBe(0); + }); + + it("expires uploads by ttl even if the timeout callback has not been observed yet", () => { + const id = storePendingUpload({ + buffer: Buffer.from("hello"), + filename: "hello.txt", + conversationId: "conv-1", + }); + + vi.advanceTimersByTime(5 * 60 * 1000 + 1); + + expect(getPendingUpload(id)).toBeUndefined(); + expect(getPendingUploadCount()).toBe(0); + }); + + it("clears all uploads for test cleanup", () => { + storePendingUpload({ + buffer: Buffer.from("a"), + filename: "a.txt", + conversationId: "conv-1", + }); + storePendingUpload({ + buffer: Buffer.from("b"), + filename: "b.txt", + conversationId: "conv-2", + }); + + clearPendingUploads(); + + expect(getPendingUploadCount()).toBe(0); + }); +}); diff --git a/extensions/msteams/src/webhook-timeouts.test.ts b/extensions/msteams/src/webhook-timeouts.test.ts new file mode 100644 index 00000000000..76d350acd35 --- /dev/null +++ b/extensions/msteams/src/webhook-timeouts.test.ts @@ -0,0 +1,36 @@ +import { describe, expect, it, vi } from "vitest"; +import { applyMSTeamsWebhookTimeouts } from "./webhook-timeouts.js"; + +describe("applyMSTeamsWebhookTimeouts", () => { + it("applies default timeouts and header clamp", () => { + const httpServer = { + setTimeout: vi.fn(), + requestTimeout: 0, + headersTimeout: 0, + } as never; + + applyMSTeamsWebhookTimeouts(httpServer); + + expect(httpServer.setTimeout).toHaveBeenCalledWith(30_000); + expect(httpServer.requestTimeout).toBe(30_000); + expect(httpServer.headersTimeout).toBe(15_000); + }); + + it("uses explicit overrides and clamps headers timeout to request timeout", () => { + const httpServer = { + setTimeout: vi.fn(), + requestTimeout: 0, + headersTimeout: 0, + } as never; + + applyMSTeamsWebhookTimeouts(httpServer, { + inactivityTimeoutMs: 12_000, + requestTimeoutMs: 9_000, + headersTimeoutMs: 15_000, + }); + + expect(httpServer.setTimeout).toHaveBeenCalledWith(12_000); + expect(httpServer.requestTimeout).toBe(9_000); + expect(httpServer.headersTimeout).toBe(9_000); + }); +});