From 5384738b67218971910fe447ab9061b6f2caf7c4 Mon Sep 17 00:00:00 2001 From: dswbx Date: Fri, 9 Jan 2026 11:28:19 +0100 Subject: [PATCH] tests: ensuring `overwrite` is passed and respected in media api --- app/__test__/api/MediaApi.spec.ts | 34 +++++++-- app/__test__/media/MediaController.spec.ts | 84 ++++++++++++++++++++++ 2 files changed, 114 insertions(+), 4 deletions(-) diff --git a/app/__test__/api/MediaApi.spec.ts b/app/__test__/api/MediaApi.spec.ts index 02a35a6..7db9850 100644 --- a/app/__test__/api/MediaApi.spec.ts +++ b/app/__test__/api/MediaApi.spec.ts @@ -1,7 +1,7 @@ /// -import { describe, expect, it } from "bun:test"; +import { describe, expect, it, mock } from "bun:test"; import { Hono } from "hono"; -import { getFileFromContext, isFile, isReadableStream } from "core/utils"; +import { getFileFromContext, isFile, isReadableStream, s, jsc } from "core/utils"; import { MediaApi } from "media/api/MediaApi"; import { assetsPath, assetsTmpPath } from "../helper"; @@ -98,7 +98,7 @@ describe("MediaApi", () => { expect(isReadableStream(res.body)).toBe(true); expect(isReadableStream(res.res.body)).toBe(true); - const blob = await res.res.blob(); + const blob = (await res.res.blob()) as File; expect(isFile(blob)).toBe(true); expect(blob.size).toBeGreaterThan(0); expect(blob.type).toBe("image/png"); @@ -113,7 +113,7 @@ describe("MediaApi", () => { const res = await api.getFileStream(name); expect(isReadableStream(res)).toBe(true); - const blob = await new Response(res).blob(); + const blob = (await new Response(res).blob()) as File; expect(isFile(blob)).toBe(true); expect(blob.size).toBeGreaterThan(0); expect(blob.type).toBe("image/png"); @@ -162,4 +162,30 @@ describe("MediaApi", () => { await matches(api.upload(response.body!, { filename: "readable.png" }), "readable.png"); } }); + + it.only("should add overwrite query for entity upload", async (c) => { + const call = mock(() => null); + const hono = new Hono().post( + "/api/media/entity/:entity/:id/:field", + jsc("query", s.object({ overwrite: s.boolean().optional() })), + async (c) => { + const { overwrite } = c.req.valid("query"); + expect(overwrite).toBe(true); + call(); + return c.json({ ok: true }); + }, + ); + const api = new MediaApi( + { + upload_fetcher: hono.request, + }, + hono.request, + ); + const file = Bun.file(`${assetsPath}/image.png`); + const res = await api.uploadToEntity("posts", 1, "cover", file as any, { + overwrite: true, + }); + expect(res.ok).toBe(true); + expect(call).toHaveBeenCalled(); + }); }); diff --git a/app/__test__/media/MediaController.spec.ts b/app/__test__/media/MediaController.spec.ts index 3eae83e..39da5a4 100644 --- a/app/__test__/media/MediaController.spec.ts +++ b/app/__test__/media/MediaController.spec.ts @@ -8,6 +8,7 @@ import type { TAppMediaConfig } from "../../src/media/media-schema"; import { StorageLocalAdapter } from "adapter/node/storage/StorageLocalAdapter"; import { assetsPath, assetsTmpPath } from "../helper"; import { disableConsoleLog, enableConsoleLog } from "core/utils/test"; +import * as proto from "data/prototype"; beforeAll(() => { disableConsoleLog(); @@ -128,4 +129,87 @@ describe("MediaController", () => { expect(destFile.exists()).resolves.toBe(true); await destFile.delete(); }); + + test("entity upload with max_items and overwrite", async () => { + const app = createApp({ + config: { + media: mergeObject( + { + enabled: true, + adapter: { + type: "local", + config: { + path: assetsTmpPath, + }, + }, + }, + {}, + ), + data: { + entities: { + posts: proto + .entity("posts", { + title: proto.text(), + cover: proto.medium(), + }) + .toJSON(), + }, + }, + }, + }); + await app.build(); + + // create a post first + const createRes = await app.server.request("/api/data/entity/posts", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ title: "Test Post" }), + }); + expect(createRes.status).toBe(201); + const { data: post } = (await createRes.json()) as any; + + const file = Bun.file(path); + const uploadedFiles: string[] = []; + + // upload first file to entity (should succeed) + const res1 = await app.server.request(`/api/media/entity/posts/${post.id}/cover`, { + method: "POST", + body: file, + }); + expect(res1.status).toBe(201); + const result1 = (await res1.json()) as any; + uploadedFiles.push(result1.name); + + // upload second file without overwrite (should fail - max_items reached) + const res2 = await app.server.request(`/api/media/entity/posts/${post.id}/cover`, { + method: "POST", + body: file, + }); + expect(res2.status).toBe(400); + const result2 = (await res2.json()) as any; + expect(result2.error).toContain("Max items"); + + // upload third file with overwrite=true (should succeed and delete old file) + const res3 = await app.server.request( + `/api/media/entity/posts/${post.id}/cover?overwrite=true`, + { + method: "POST", + body: file, + }, + ); + expect(res3.status).toBe(201); + const result3 = (await res3.json()) as any; + uploadedFiles.push(result3.name); + + // verify old file was deleted from storage + const oldFile = Bun.file(assetsTmpPath + "/" + uploadedFiles[0]); + expect(await oldFile.exists()).toBe(false); + + // verify new file exists + const newFile = Bun.file(assetsTmpPath + "/" + uploadedFiles[1]); + expect(await newFile.exists()).toBe(true); + + // cleanup + await newFile.delete(); + }); });