From 3dfdcc90b86ed875de8fa30196157ac3101df229 Mon Sep 17 00:00:00 2001 From: Graham Barber Date: Thu, 19 Mar 2026 09:44:37 -0700 Subject: [PATCH] add fetch and express adapter tests --- packages/express/package.json | 7 +- packages/express/src/__tests__/index.test.ts | 191 ++++++++++++++++++ packages/fetch/package.json | 5 +- packages/fetch/src/__tests__/index.test.ts | 182 +++++++++++++++++ pnpm-lock.yaml | 198 +++++++++++++++++++ 5 files changed, 579 insertions(+), 4 deletions(-) create mode 100644 packages/express/src/__tests__/index.test.ts create mode 100644 packages/fetch/src/__tests__/index.test.ts diff --git a/packages/express/package.json b/packages/express/package.json index b48c353..1fb881f 100644 --- a/packages/express/package.json +++ b/packages/express/package.json @@ -19,7 +19,7 @@ ], "scripts": { "build": "tsc --build", - "test": "echo \"No tests\" && exit 0" + "test": "vitest run" }, "packageManager": "pnpm@11.0.0-dev.1005", "dependencies": { @@ -29,6 +29,9 @@ "devDependencies": { "@standard-schema/spec": "^1.1.0", "@types/express": "^5.0.6", - "typescript": "^5.9.3" + "@types/supertest": "^7.2.0", + "supertest": "^7.2.2", + "typescript": "^5.9.3", + "vitest": "^4.1.0" } } diff --git a/packages/express/src/__tests__/index.test.ts b/packages/express/src/__tests__/index.test.ts new file mode 100644 index 0000000..d2ddc4d --- /dev/null +++ b/packages/express/src/__tests__/index.test.ts @@ -0,0 +1,191 @@ +import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; +import * as fs from "node:fs/promises"; +import * as os from "node:os"; +import * as path from "node:path"; +import express from "express"; +import request from "supertest"; +import { createLureHandler } from "../index.js"; + +let tmpDir: string; + +beforeEach(async () => { + tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "lure-express-test-")); +}); + +afterEach(async () => { + await fs.rm(tmpDir, { recursive: true, force: true }); +}); + +async function writeLure(name: string, content: string): Promise { + const filePath = path.join(tmpDir, name); + await fs.mkdir(path.dirname(filePath), { recursive: true }); + await fs.writeFile(filePath, content, "utf-8"); +} + +describe("createLureHandler (express)", () => { + it("calls next() for non-matching path", async () => { + const handle = await createLureHandler({ + basePath: "/webhooks", + luresDir: tmpDir, + callback: vi.fn(), + }); + + const app = express(); + app.use(handle); + app.use((_req, res) => res.status(200).json({ ok: true })); + + const res = await request(app).post("/other").send("data"); + expect(res.status).toBe(200); + }); + + it("calls next() when path matches base but no lure exists", async () => { + const handle = await createLureHandler({ + basePath: "/webhooks", + luresDir: tmpDir, + callback: vi.fn(), + }); + + const app = express(); + app.use(handle); + app.use((_req, res) => res.status(200).json({ ok: true })); + + const res = await request(app).post("/webhooks/nonexistent"); + expect(res.status).toBe(200); + }); + + it("responds 204 for a matching lure", async () => { + await writeLure("github.lure", "---\n---\nEvent received\n"); + const handle = await createLureHandler({ + basePath: "/webhooks", + luresDir: tmpDir, + callback: vi.fn().mockResolvedValue(undefined), + }); + + const app = express(); + app.use(handle); + + const res = await request(app).post("/webhooks/github").send("body"); + expect(res.status).toBe(204); + }); + + it("does not prefix-match partial basePath segments", async () => { + await writeLure("test.lure", "---\n---\nHello\n"); + const handle = await createLureHandler({ + basePath: "/webhooks", + luresDir: tmpDir, + callback: vi.fn(), + }); + + const app = express(); + app.use(handle); + app.use((_req, res) => res.status(200).json({ ok: true })); + + const res = await request(app).post("/webhooksextra/test"); + expect(res.status).toBe(200); + }); + + it("calls callback with rendered prompt", async () => { + await writeLure("push.lure", "---\n---\nPush event\n"); + let resolveCallback!: () => void; + const callbackDone = new Promise((r) => { + resolveCallback = r; + }); + const callback = vi.fn().mockImplementation(async () => { + resolveCallback(); + }); + + const handle = await createLureHandler({ + basePath: "/hooks", + luresDir: tmpDir, + callback, + }); + + const app = express(); + app.use(handle); + + await request(app).post("/hooks/push").send("body"); + await callbackDone; + expect(callback).toHaveBeenCalledWith("Push event", undefined); + }); + + it("forwards request headers to the template", async () => { + await writeLure( + "event.lure", + "---\n---\nToken: {{ headers['x-token'] }}\n", + ); + let resolveCallback!: () => void; + const callbackDone = new Promise((r) => { + resolveCallback = r; + }); + const callback = vi.fn().mockImplementation(async () => { + resolveCallback(); + }); + + const handle = await createLureHandler({ + basePath: "/hooks", + luresDir: tmpDir, + callback, + }); + + const app = express(); + app.use(handle); + + await request(app).post("/hooks/event").set("x-token", "abc123").send(""); + await callbackDone; + expect(callback).toHaveBeenCalledWith("Token: abc123", undefined); + }); + + it("forwards query params to the template", async () => { + await writeLure("search.lure", "---\n---\nQuery: {{ query.q }}\n"); + let resolveCallback!: () => void; + const callbackDone = new Promise((r) => { + resolveCallback = r; + }); + const callback = vi.fn().mockImplementation(async () => { + resolveCallback(); + }); + + const handle = await createLureHandler({ + basePath: "/hooks", + luresDir: tmpDir, + callback, + }); + + const app = express(); + app.use(handle); + + await request(app).post("/hooks/search?q=hello").send(""); + await callbackDone; + expect(callback).toHaveBeenCalledWith("Query: hello", undefined); + }); + + it("reads and forwards body as rawBody", async () => { + await writeLure( + "data.lure", + "---\npayload:\n contentType: json\n---\nAction: {{ payload.action }}\n", + ); + let resolveCallback!: () => void; + const callbackDone = new Promise((r) => { + resolveCallback = r; + }); + const callback = vi.fn().mockImplementation(async () => { + resolveCallback(); + }); + + const handle = await createLureHandler({ + basePath: "/hooks", + luresDir: tmpDir, + callback, + }); + + const app = express(); + app.use(handle); + + await request(app) + .post("/hooks/data") + .set("content-type", "application/json") + .send(JSON.stringify({ action: "opened" })); + await callbackDone; + expect(callback).toHaveBeenCalledWith("Action: opened", undefined); + }); +}); diff --git a/packages/fetch/package.json b/packages/fetch/package.json index 1208c5a..f6fdcfe 100644 --- a/packages/fetch/package.json +++ b/packages/fetch/package.json @@ -19,7 +19,7 @@ ], "scripts": { "build": "tsc --build", - "test": "echo \"No tests\" && exit 0" + "test": "vitest run" }, "packageManager": "pnpm@11.0.0-dev.1005", "dependencies": { @@ -27,6 +27,7 @@ }, "devDependencies": { "@standard-schema/spec": "^1.1.0", - "typescript": "^5.9.3" + "typescript": "^5.9.3", + "vitest": "^4.1.0" } } diff --git a/packages/fetch/src/__tests__/index.test.ts b/packages/fetch/src/__tests__/index.test.ts new file mode 100644 index 0000000..4f3d69d --- /dev/null +++ b/packages/fetch/src/__tests__/index.test.ts @@ -0,0 +1,182 @@ +import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; +import * as fs from "node:fs/promises"; +import * as os from "node:os"; +import * as path from "node:path"; +import { createLureHandler } from "../index.js"; + +let tmpDir: string; + +beforeEach(async () => { + tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "lure-fetch-test-")); +}); + +afterEach(async () => { + await fs.rm(tmpDir, { recursive: true, force: true }); +}); + +async function writeLure(name: string, content: string): Promise { + const filePath = path.join(tmpDir, name); + await fs.mkdir(path.dirname(filePath), { recursive: true }); + await fs.writeFile(filePath, content, "utf-8"); +} + +describe("createLureHandler (fetch)", () => { + it("returns null for non-matching path without reading body", async () => { + await writeLure("github.lure", "---\n---\nHello\n"); + const handle = await createLureHandler({ + basePath: "/webhooks", + luresDir: tmpDir, + callback: vi.fn(), + }); + + const req = new Request("http://localhost/other/path", { + method: "POST", + body: "data", + }); + const result = await handle(req); + + expect(result).toBeNull(); + expect(req.bodyUsed).toBe(false); + }); + + it("returns null for matching base but no lure", async () => { + const handle = await createLureHandler({ + basePath: "/webhooks", + luresDir: tmpDir, + callback: vi.fn(), + }); + + const req = new Request("http://localhost/webhooks/nonexistent", { + method: "POST", + }); + expect(await handle(req)).toBeNull(); + }); + + it("returns 204 for a matching lure", async () => { + await writeLure("github.lure", "---\n---\nEvent received\n"); + const handle = await createLureHandler({ + basePath: "/webhooks", + luresDir: tmpDir, + callback: vi.fn().mockResolvedValue(undefined), + }); + + const req = new Request("http://localhost/webhooks/github", { + method: "POST", + }); + const result = await handle(req); + expect(result?.status).toBe(204); + }); + + it("does not prefix-match partial basePath segments", async () => { + await writeLure("test.lure", "---\n---\nHello\n"); + const handle = await createLureHandler({ + basePath: "/webhooks", + luresDir: tmpDir, + callback: vi.fn(), + }); + + const req = new Request("http://localhost/webhooksextra/test", { + method: "POST", + }); + expect(await handle(req)).toBeNull(); + }); + + it("calls callback with rendered prompt", async () => { + await writeLure("push.lure", "---\n---\nPush event\n"); + let resolveCallback!: () => void; + const callbackDone = new Promise((r) => { + resolveCallback = r; + }); + const callback = vi.fn().mockImplementation(async () => { + resolveCallback(); + }); + + const handle = await createLureHandler({ + basePath: "/hooks", + luresDir: tmpDir, + callback, + }); + + const req = new Request("http://localhost/hooks/push", { method: "POST" }); + await handle(req); + await callbackDone; + expect(callback).toHaveBeenCalledWith("Push event", undefined); + }); + + it("forwards query params to the template", async () => { + await writeLure("search.lure", "---\n---\nQuery: {{ query.q }}\n"); + let resolveCallback!: () => void; + const callbackDone = new Promise((r) => { + resolveCallback = r; + }); + const callback = vi.fn().mockImplementation(async () => { + resolveCallback(); + }); + + const handle = await createLureHandler({ + basePath: "/hooks", + luresDir: tmpDir, + callback, + }); + + const req = new Request("http://localhost/hooks/search?q=hello", { + method: "POST", + }); + await handle(req); + await callbackDone; + expect(callback).toHaveBeenCalledWith("Query: hello", undefined); + }); + + it("forwards request headers to the template", async () => { + await writeLure( + "event.lure", + "---\n---\nToken: {{ headers['x-token'] }}\n", + ); + let resolveCallback!: () => void; + const callbackDone = new Promise((r) => { + resolveCallback = r; + }); + const callback = vi.fn().mockImplementation(async () => { + resolveCallback(); + }); + + const handle = await createLureHandler({ + basePath: "/hooks", + luresDir: tmpDir, + callback, + }); + + const req = new Request("http://localhost/hooks/event", { + method: "POST", + headers: { "x-token": "abc123" }, + }); + await handle(req); + await callbackDone; + expect(callback).toHaveBeenCalledWith("Token: abc123", undefined); + }); + + it("reads and forwards body as rawBody", async () => { + await writeLure("data.lure", "---\npayload:\n contentType: json\n---\nAction: {{ payload.action }}\n"); + let resolveCallback!: () => void; + const callbackDone = new Promise((r) => { + resolveCallback = r; + }); + const callback = vi.fn().mockImplementation(async () => { + resolveCallback(); + }); + + const handle = await createLureHandler({ + basePath: "/hooks", + luresDir: tmpDir, + callback, + }); + + const req = new Request("http://localhost/hooks/data", { + method: "POST", + body: JSON.stringify({ action: "opened" }), + }); + await handle(req); + await callbackDone; + expect(callback).toHaveBeenCalledWith("Action: opened", undefined); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1492083..819b49b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -58,9 +58,18 @@ importers: '@types/express': specifier: ^5.0.6 version: 5.0.6 + '@types/supertest': + specifier: ^7.2.0 + version: 7.2.0 + supertest: + specifier: ^7.2.2 + version: 7.2.2 typescript: specifier: ^5.9.3 version: 5.9.3 + vitest: + specifier: ^4.1.0 + version: 4.1.0(@types/node@25.5.0)(vite@8.0.0(@types/node@25.5.0)) packages/fetch: dependencies: @@ -74,6 +83,9 @@ importers: typescript: specifier: ^5.9.3 version: 5.9.3 + vitest: + specifier: ^4.1.0 + version: 4.1.0(@types/node@25.5.0)(vite@8.0.0(@types/node@25.5.0)) packages: @@ -98,6 +110,10 @@ packages: '@napi-rs/wasm-runtime@1.1.1': resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==} + '@noble/hashes@1.8.0': + resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} + engines: {node: ^14.21.3 || >=16} + '@oxc-project/runtime@0.115.0': resolution: {integrity: sha512-Rg8Wlt5dCbXhQnsXPrkOjL1DTSvXLgb2R/KYfnf1/K+R0k6UMLEmbQXPM+kwrWqSmWA2t0B1EtHy2/3zikQpvQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -333,6 +349,9 @@ packages: cpu: [x64] os: [win32] + '@paralleldrive/cuid2@2.3.1': + resolution: {integrity: sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==} + '@rolldown/binding-android-arm64@1.0.0-rc.9': resolution: {integrity: sha512-lcJL0bN5hpgJfSIz/8PIf02irmyL43P+j1pTCfbD1DbLkmGRuFIA4DD3B3ZOvGqG0XiVvRznbKtN0COQVaKUTg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -440,6 +459,9 @@ packages: '@types/connect@3.4.38': resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + '@types/cookiejar@2.1.5': + resolution: {integrity: sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==} + '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} @@ -455,6 +477,9 @@ packages: '@types/http-errors@2.0.5': resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + '@types/methods@1.1.4': + resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==} + '@types/node@25.5.0': resolution: {integrity: sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==} @@ -470,6 +495,12 @@ packages: '@types/serve-static@2.2.0': resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==} + '@types/superagent@8.1.9': + resolution: {integrity: sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==} + + '@types/supertest@7.2.0': + resolution: {integrity: sha512-uh2Lv57xvggst6lCqNdFAmDSvoMG7M/HDtX4iUCquxQ5EGPtaPM5PL5Hmi7LCvOG8db7YaCPNJEeoI8s/WzIQw==} + '@vitest/expect@4.1.0': resolution: {integrity: sha512-EIxG7k4wlWweuCLG9Y5InKFwpMEOyrMb6ZJ1ihYu02LVj/bzUwn2VMU+13PinsjRW75XnITeFrQBMH5+dLvCDA==} @@ -512,10 +543,16 @@ packages: arktype@2.2.0: resolution: {integrity: sha512-t54MZ7ti5BhOEvzEkgKnWvqj+UbDfWig+DHr5I34xatymPusKLS0lQpNJd8M6DzmIto2QGszHfNKoFIT8tMCZQ==} + asap@2.0.6: + resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + body-parser@2.2.2: resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} engines: {node: '>=18'} @@ -540,10 +577,17 @@ packages: resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} engines: {node: '>= 20.19.0'} + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + commander@10.0.1: resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} engines: {node: '>=14'} + component-emitter@1.3.1: + resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} + content-disposition@1.0.1: resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==} engines: {node: '>=18'} @@ -563,6 +607,9 @@ packages: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} + cookiejar@2.1.4: + resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -572,6 +619,10 @@ packages: supports-color: optional: true + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} @@ -580,6 +631,9 @@ packages: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} + dezalgo@1.0.4: + resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -606,6 +660,10 @@ packages: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} @@ -636,6 +694,9 @@ packages: resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} engines: {node: '>=0.10.0'} + fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -649,6 +710,14 @@ packages: resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} engines: {node: '>= 18.0.0'} + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + engines: {node: '>= 6'} + + formidable@3.5.4: + resolution: {integrity: sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==} + engines: {node: '>=14.0.0'} + forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} @@ -685,6 +754,10 @@ packages: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + hasown@2.0.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} @@ -809,14 +882,31 @@ packages: resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} engines: {node: '>=18'} + methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + mime-db@1.54.0: resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} engines: {node: '>= 0.6'} + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + mime-types@3.0.2: resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} engines: {node: '>=18'} + mime@2.6.0: + resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} + engines: {node: '>=4.0.0'} + hasBin: true + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -974,6 +1064,14 @@ packages: resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==} engines: {node: '>=0.10.0'} + superagent@10.3.0: + resolution: {integrity: sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ==} + engines: {node: '>=14.18.0'} + + supertest@7.2.2: + resolution: {integrity: sha512-oK8WG9diS3DlhdUkcFn4tkNIiIbBx9lI2ClF8K+b2/m8Eyv47LSawxUzZQSNKUrVb2KsqeTDCcjAAVPYaSLVTA==} + engines: {node: '>=14.18.0'} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -1139,6 +1237,8 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true + '@noble/hashes@1.8.0': {} + '@oxc-project/runtime@0.115.0': {} '@oxc-project/types@0.115.0': {} @@ -1257,6 +1357,10 @@ snapshots: '@oxlint/binding-win32-x64-msvc@1.56.0': optional: true + '@paralleldrive/cuid2@2.3.1': + dependencies: + '@noble/hashes': 1.8.0 + '@rolldown/binding-android-arm64@1.0.0-rc.9': optional: true @@ -1327,6 +1431,8 @@ snapshots: dependencies: '@types/node': 25.5.0 + '@types/cookiejar@2.1.5': {} + '@types/deep-eql@4.0.2': {} '@types/estree@1.0.8': {} @@ -1346,6 +1452,8 @@ snapshots: '@types/http-errors@2.0.5': {} + '@types/methods@1.1.4': {} + '@types/node@25.5.0': dependencies: undici-types: 7.18.2 @@ -1363,6 +1471,18 @@ snapshots: '@types/http-errors': 2.0.5 '@types/node': 25.5.0 + '@types/superagent@8.1.9': + dependencies: + '@types/cookiejar': 2.1.5 + '@types/methods': 1.1.4 + '@types/node': 25.5.0 + form-data: 4.0.5 + + '@types/supertest@7.2.0': + dependencies: + '@types/methods': 1.1.4 + '@types/superagent': 8.1.9 + '@vitest/expect@4.1.0': dependencies: '@standard-schema/spec': 1.1.0 @@ -1423,8 +1543,12 @@ snapshots: '@ark/util': 0.56.0 arkregex: 0.0.5 + asap@2.0.6: {} + assertion-error@2.0.1: {} + asynckit@0.4.0: {} + body-parser@2.2.2: dependencies: bytes: 3.1.2 @@ -1457,8 +1581,14 @@ snapshots: dependencies: readdirp: 5.0.0 + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + commander@10.0.1: {} + component-emitter@1.3.1: {} + content-disposition@1.0.1: {} content-type@1.0.5: {} @@ -1469,14 +1599,23 @@ snapshots: cookie@0.7.2: {} + cookiejar@2.1.4: {} + debug@4.4.3: dependencies: ms: 2.1.3 + delayed-stream@1.0.0: {} + depd@2.0.0: {} detect-libc@2.1.2: {} + dezalgo@1.0.4: + dependencies: + asap: 2.0.6 + wrappy: 1.0.2 + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -1497,6 +1636,13 @@ snapshots: dependencies: es-errors: 1.3.0 + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + escape-html@1.0.3: {} esprima@4.0.1: {} @@ -1548,6 +1694,8 @@ snapshots: dependencies: is-extendable: 0.1.1 + fast-safe-stringify@2.1.1: {} + fdir@6.5.0(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 @@ -1563,6 +1711,20 @@ snapshots: transitivePeerDependencies: - supports-color + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + formidable@3.5.4: + dependencies: + '@paralleldrive/cuid2': 2.3.1 + dezalgo: 1.0.4 + once: 1.4.0 + forwarded@0.2.0: {} fresh@2.0.0: {} @@ -1601,6 +1763,10 @@ snapshots: has-symbols@1.1.0: {} + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + hasown@2.0.2: dependencies: function-bind: 1.1.2 @@ -1695,12 +1861,22 @@ snapshots: merge-descriptors@2.0.0: {} + methods@1.1.2: {} + + mime-db@1.52.0: {} + mime-db@1.54.0: {} + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + mime-types@3.0.2: dependencies: mime-db: 1.54.0 + mime@2.6.0: {} + ms@2.1.3: {} nanoid@3.3.11: {} @@ -1915,6 +2091,28 @@ snapshots: strip-bom-string@1.0.0: {} + superagent@10.3.0: + dependencies: + component-emitter: 1.3.1 + cookiejar: 2.1.4 + debug: 4.4.3 + fast-safe-stringify: 2.1.1 + form-data: 4.0.5 + formidable: 3.5.4 + methods: 1.1.2 + mime: 2.6.0 + qs: 6.15.0 + transitivePeerDependencies: + - supports-color + + supertest@7.2.2: + dependencies: + cookie-signature: 1.2.2 + methods: 1.1.2 + superagent: 10.3.0 + transitivePeerDependencies: + - supports-color + tinybench@2.9.0: {} tinyexec@1.0.4: {} -- 2.51.2