import { beforeEach, describe, expect, mock, test } from "bun:test"; import { Hono } from "hono"; import { createTestDb } from "../helpers/db.ts"; // Mock @aws-sdk/* to prevent real S3 connections — avoids module-level mock // contamination of storage.service.test.ts (storage.service uses dynamic imports). const mockSend = mock(() => Promise.resolve({})); mock.module("@aws-sdk/client-s3", () => ({ S3Client: class MockS3Client { send = mockSend; }, PutObjectCommand: class { input: Record; constructor(input: Record) { this.input = input; } }, DeleteObjectCommand: class { input: Record; constructor(input: Record) { this.input = input; } }, GetObjectCommand: class { input: Record; constructor(input: Record) { this.input = input; } }, })); mock.module("@aws-sdk/s3-request-presigner", () => ({ getSignedUrl: mock(() => Promise.resolve("https://example.com/test.png")), })); const { imageRoutes } = await import("../../src/server/routes/images.ts"); let app: Hono; beforeEach(async () => { const { db, userId } = await createTestDb(); app = new Hono(); app.use("*", async (c, next) => { c.set("db", db); c.set("userId", userId); await next(); }); app.route("/api/images", imageRoutes); mockSend.mockClear(); }); describe("POST /api/images/from-url", () => { test("returns 400 for missing URL", async () => { const res = await app.request("/api/images/from-url", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({}), }); expect(res.status).toBe(400); }); test("returns 400 for invalid URL", async () => { const res = await app.request("/api/images/from-url", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ url: "not-a-url" }), }); expect(res.status).toBe(400); }); }); describe("POST /api/images", () => { test("returns 400 when no image file provided", async () => { const formData = new FormData(); const res = await app.request("/api/images", { method: "POST", body: formData, }); expect(res.status).toBe(400); const json = await res.json(); expect(json.error).toBe("No image file provided"); }); test("returns 400 for invalid file type", async () => { const formData = new FormData(); formData.append( "image", new Blob(["test"], { type: "text/plain" }), "test.txt", ); const res = await app.request("/api/images", { method: "POST", body: formData, }); expect(res.status).toBe(400); const json = await res.json(); expect(json.error).toContain("Invalid file type"); }); test("uploads valid image and calls storage service", async () => { const formData = new FormData(); const pngBlob = new Blob([new Uint8Array([0x89, 0x50, 0x4e, 0x47])], { type: "image/png", }); formData.append("image", pngBlob, "test.png"); const res = await app.request("/api/images", { method: "POST", body: formData, }); expect(res.status).toBe(201); const json = await res.json(); expect(json.filename).toMatch(/^\d+-[\w-]+\.png$/); expect(mockSend).toHaveBeenCalledTimes(1); }); });