mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 04:27:21 +00:00
updated media api and added tests, fixed body limit
This commit is contained in:
1
app/__test__/_assets/.gitignore
vendored
Normal file
1
app/__test__/_assets/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
tmp/*
|
||||
BIN
app/__test__/_assets/image.png
Normal file
BIN
app/__test__/_assets/image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 66 KiB |
149
app/__test__/api/MediaApi.spec.ts
Normal file
149
app/__test__/api/MediaApi.spec.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
/// <reference types="@types/bun" />
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import { Hono } from "hono";
|
||||
import { getFileFromContext, isFile, isReadableStream } from "../../src/core/utils";
|
||||
import { MediaApi } from "../../src/media/api/MediaApi";
|
||||
import { assetsPath, assetsTmpPath } from "../helper";
|
||||
|
||||
const mockedBackend = new Hono()
|
||||
.basePath("/api/media")
|
||||
.post("/upload/:name", async (c) => {
|
||||
const { name } = c.req.param();
|
||||
const body = await getFileFromContext(c);
|
||||
return c.json({ name, is_file: isFile(body), size: body.size });
|
||||
})
|
||||
.get("/file/:name", async (c) => {
|
||||
const { name } = c.req.param();
|
||||
const file = Bun.file(`${assetsPath}/${name}`);
|
||||
return new Response(file, {
|
||||
headers: {
|
||||
"Content-Type": file.type,
|
||||
"Content-Length": file.size.toString()
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("MediaApi", () => {
|
||||
it("should give correct file upload url", () => {
|
||||
const host = "http://localhost";
|
||||
const basepath = "/api/media";
|
||||
// @ts-ignore tests
|
||||
const api = new MediaApi({
|
||||
host,
|
||||
basepath
|
||||
});
|
||||
expect(api.getFileUploadUrl({ path: "path" })).toBe(`${host}${basepath}/upload/path`);
|
||||
});
|
||||
|
||||
it("should have correct upload headers", () => {
|
||||
// @ts-ignore tests
|
||||
const api = new MediaApi({
|
||||
token: "token"
|
||||
});
|
||||
expect(api.getUploadHeaders().get("Authorization")).toBe("Bearer token");
|
||||
});
|
||||
|
||||
it("should upload file directly", async () => {
|
||||
const name = "image.png";
|
||||
const file = await Bun.file(`${assetsPath}/${name}`);
|
||||
|
||||
// @ts-ignore tests
|
||||
const api = new MediaApi({}, mockedBackend.request);
|
||||
const result = await api.uploadFile(file as any, name);
|
||||
expect(result.name).toBe(name);
|
||||
expect(result.is_file).toBe(true);
|
||||
expect(result.size).toBe(file.size);
|
||||
});
|
||||
|
||||
it("should get file: native", async () => {
|
||||
const name = "image.png";
|
||||
const path = `${assetsTmpPath}/${name}`;
|
||||
const res = await mockedBackend.request("/api/media/file/" + name);
|
||||
await Bun.write(path, res);
|
||||
|
||||
const file = await Bun.file(path);
|
||||
expect(file.size).toBeGreaterThan(0);
|
||||
expect(file.type).toBe("image/png");
|
||||
await file.delete();
|
||||
});
|
||||
|
||||
it("getFile", async () => {
|
||||
// @ts-ignore tests
|
||||
const api = new MediaApi({}, mockedBackend.request);
|
||||
|
||||
const name = "image.png";
|
||||
const file = await api.getFile(name);
|
||||
expect(isFile(file)).toBe(true);
|
||||
expect(file.size).toBeGreaterThan(0);
|
||||
expect(file.type).toBe("image/png");
|
||||
expect(file.name).toContain(name);
|
||||
});
|
||||
|
||||
it("getFileResponse", async () => {
|
||||
// @ts-ignore tests
|
||||
const api = new MediaApi({}, mockedBackend.request);
|
||||
|
||||
const name = "image.png";
|
||||
const res = await api.getFileResponse(name);
|
||||
expect(res.ok).toBe(true);
|
||||
// make sure it's a normal api request as usual
|
||||
expect(res.res.ok).toBe(true);
|
||||
expect(isReadableStream(res)).toBe(true);
|
||||
expect(isReadableStream(res.body)).toBe(true);
|
||||
expect(isReadableStream(res.res.body)).toBe(true);
|
||||
|
||||
const blob = await res.res.blob();
|
||||
expect(isFile(blob)).toBe(true);
|
||||
expect(blob.size).toBeGreaterThan(0);
|
||||
expect(blob.type).toBe("image/png");
|
||||
expect(blob.name).toContain(name);
|
||||
});
|
||||
|
||||
it("getFileStream", async () => {
|
||||
// @ts-ignore tests
|
||||
const api = new MediaApi({}, mockedBackend.request);
|
||||
|
||||
const name = "image.png";
|
||||
const res = await api.getFileStream(name);
|
||||
expect(isReadableStream(res)).toBe(true);
|
||||
|
||||
const blob = await new Response(res).blob();
|
||||
expect(isFile(blob)).toBe(true);
|
||||
expect(blob.size).toBeGreaterThan(0);
|
||||
expect(blob.type).toBe("image/png");
|
||||
expect(blob.name).toContain(name);
|
||||
});
|
||||
|
||||
it("should upload file in various ways", async () => {
|
||||
// @ts-ignore tests
|
||||
const api = new MediaApi({}, mockedBackend.request);
|
||||
const file = Bun.file(`${assetsPath}/image.png`);
|
||||
|
||||
async function matches(req: Promise<any>, filename: string) {
|
||||
const res: any = await req;
|
||||
expect(res.name).toBe(filename);
|
||||
expect(res.is_file).toBe(true);
|
||||
expect(res.size).toBe(file.size);
|
||||
}
|
||||
|
||||
const url = "http://localhost/api/media/file/image.png";
|
||||
|
||||
// upload bun file
|
||||
await matches(api.upload(file as any, "bunfile.png"), "bunfile.png");
|
||||
|
||||
// upload via request
|
||||
await matches(api.upload(new Request(url), "request.png"), "request.png");
|
||||
|
||||
// upload via url
|
||||
await matches(api.upload(url, "url.png"), "url.png");
|
||||
|
||||
// upload via response
|
||||
{
|
||||
const response = await mockedBackend.request(url);
|
||||
await matches(api.upload(response, "response.png"), "response.png");
|
||||
}
|
||||
|
||||
// upload via readable
|
||||
await matches(await api.upload(file.stream(), "readable.png"), "readable.png");
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { Perf } from "../../src/core/utils";
|
||||
import { Perf, isBlob, ucFirst } from "../../src/core/utils";
|
||||
import * as utils from "../../src/core/utils";
|
||||
|
||||
async function wait(ms: number) {
|
||||
@@ -75,6 +75,57 @@ describe("Core Utils", async () => {
|
||||
const result3 = utils.encodeSearch(obj3, { encode: true });
|
||||
expect(result3).toBe("id=123&name=%7B%22test%22%3A%22test%22%7D");
|
||||
});
|
||||
|
||||
describe("guards", () => {
|
||||
const types = {
|
||||
blob: new Blob(),
|
||||
file: new File([""], "file.txt"),
|
||||
stream: new ReadableStream(),
|
||||
arrayBuffer: new ArrayBuffer(10),
|
||||
arrayBufferView: new Uint8Array(new ArrayBuffer(10))
|
||||
};
|
||||
|
||||
const fns = [
|
||||
[utils.isReadableStream, "stream"],
|
||||
[utils.isBlob, "blob", ["stream", "arrayBuffer", "arrayBufferView"]],
|
||||
[utils.isFile, "file", ["stream", "arrayBuffer", "arrayBufferView"]],
|
||||
[utils.isArrayBuffer, "arrayBuffer"],
|
||||
[utils.isArrayBufferView, "arrayBufferView"]
|
||||
] as const;
|
||||
|
||||
const additional = [0, 0.0, "", null, undefined, {}, []];
|
||||
|
||||
for (const [fn, type, _to_test] of fns) {
|
||||
test(`is${ucFirst(type)}`, () => {
|
||||
const to_test = _to_test ?? (Object.keys(types) as string[]);
|
||||
for (const key of to_test) {
|
||||
const value = types[key as keyof typeof types];
|
||||
const result = fn(value);
|
||||
expect(result).toBe(key === type);
|
||||
}
|
||||
|
||||
for (const value of additional) {
|
||||
const result = fn(value);
|
||||
expect(result).toBe(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test("getContentName", () => {
|
||||
const name = "test.json";
|
||||
const text = "attachment; filename=" + name;
|
||||
const headers = new Headers({
|
||||
"Content-Disposition": text
|
||||
});
|
||||
const request = new Request("http://example.com", {
|
||||
headers
|
||||
});
|
||||
|
||||
expect(utils.getContentName(text)).toBe(name);
|
||||
expect(utils.getContentName(headers)).toBe(name);
|
||||
expect(utils.getContentName(request)).toBe(name);
|
||||
});
|
||||
});
|
||||
|
||||
describe("perf", async () => {
|
||||
@@ -134,8 +185,8 @@ describe("Core Utils", async () => {
|
||||
[true, true, true],
|
||||
[true, false, false],
|
||||
[false, false, true],
|
||||
[1, NaN, false],
|
||||
[NaN, NaN, true],
|
||||
[1, Number.NaN, false],
|
||||
[Number.NaN, Number.NaN, true],
|
||||
[null, null, true],
|
||||
[null, undefined, false],
|
||||
[undefined, undefined, true],
|
||||
|
||||
@@ -68,3 +68,6 @@ export function schemaToEm(s: ReturnType<typeof protoEm>, conn?: Connection): En
|
||||
const connection = conn ? conn : getDummyConnection().dummyConnection;
|
||||
return new EntityManager(Object.values(s.entities), connection, s.relations, s.indices);
|
||||
}
|
||||
|
||||
export const assetsPath = `${import.meta.dir}/_assets`;
|
||||
export const assetsTmpPath = `${import.meta.dir}/_assets/tmp`;
|
||||
|
||||
@@ -1,56 +1,96 @@
|
||||
import { describe, test } from "bun:test";
|
||||
import { Hono } from "hono";
|
||||
import { Guard } from "../../src/auth";
|
||||
import { EventManager } from "../../src/core/events";
|
||||
import { EntityManager } from "../../src/data";
|
||||
import { AppMedia } from "../../src/media/AppMedia";
|
||||
import { MediaController } from "../../src/media/api/MediaController";
|
||||
import { getDummyConnection } from "../helper";
|
||||
/// <reference types="@types/bun" />
|
||||
|
||||
const { dummyConnection, afterAllCleanup } = getDummyConnection();
|
||||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||
import { createApp, registries } from "../../src";
|
||||
import { StorageLocalAdapter } from "../../src/adapter/node";
|
||||
import { mergeObject, randomString } from "../../src/core/utils";
|
||||
import type { TAppMediaConfig } from "../../src/media/media-schema";
|
||||
import { assetsPath, assetsTmpPath, disableConsoleLog, enableConsoleLog } from "../helper";
|
||||
|
||||
/**
|
||||
* R2
|
||||
* value: ReadableStream | ArrayBuffer | ArrayBufferView | string | Blob | null,
|
||||
* Node writefile
|
||||
* data: string | NodeJS.ArrayBufferView | Iterable<string | NodeJS.ArrayBufferView> | AsyncIterable<string | NodeJS.ArrayBufferView> | Stream,
|
||||
*/
|
||||
const ALL_TESTS = !!process.env.ALL_TESTS;
|
||||
describe.skipIf(ALL_TESTS)("MediaController", () => {
|
||||
test("..", async () => {
|
||||
const ctx: any = {
|
||||
em: new EntityManager([], dummyConnection, []),
|
||||
guard: new Guard(),
|
||||
emgr: new EventManager(),
|
||||
server: new Hono()
|
||||
};
|
||||
beforeAll(() => {
|
||||
registries.media.register("local", StorageLocalAdapter);
|
||||
});
|
||||
|
||||
const media = new AppMedia(
|
||||
// @ts-ignore
|
||||
{
|
||||
enabled: true,
|
||||
adapter: {
|
||||
type: "s3",
|
||||
config: {
|
||||
access_key: process.env.R2_ACCESS_KEY as string,
|
||||
secret_access_key: process.env.R2_SECRET_ACCESS_KEY as string,
|
||||
url: process.env.R2_URL as string
|
||||
const path = `${assetsPath}/image.png`;
|
||||
|
||||
async function makeApp(mediaOverride: Partial<TAppMediaConfig> = {}) {
|
||||
const app = createApp({
|
||||
initialConfig: {
|
||||
media: mergeObject(
|
||||
{
|
||||
enabled: true,
|
||||
adapter: {
|
||||
type: "local",
|
||||
config: {
|
||||
path: assetsTmpPath
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
ctx
|
||||
);
|
||||
await media.build();
|
||||
const app = new MediaController(media).getController();
|
||||
},
|
||||
mediaOverride
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
const file = Bun.file(`${import.meta.dir}/adapters/icon.png`);
|
||||
console.log("file", file);
|
||||
const form = new FormData();
|
||||
form.append("file", file);
|
||||
await app.build();
|
||||
return app;
|
||||
}
|
||||
|
||||
await app.request("/upload/test.png", {
|
||||
function makeName(ext: string) {
|
||||
return randomString(10) + "." + ext;
|
||||
}
|
||||
|
||||
beforeAll(disableConsoleLog);
|
||||
afterAll(enableConsoleLog);
|
||||
|
||||
describe("MediaController", () => {
|
||||
test("accepts direct", async () => {
|
||||
const app = await makeApp();
|
||||
|
||||
const file = Bun.file(path);
|
||||
const name = makeName("png");
|
||||
const res = await app.server.request("/api/media/upload/" + name, {
|
||||
method: "POST",
|
||||
body: file
|
||||
});
|
||||
const result = (await res.json()) as any;
|
||||
expect(result.name).toBe(name);
|
||||
|
||||
const destFile = Bun.file(assetsTmpPath + "/" + name);
|
||||
expect(destFile.exists()).resolves.toBe(true);
|
||||
await destFile.delete();
|
||||
});
|
||||
|
||||
test("accepts form data", async () => {
|
||||
const app = await makeApp();
|
||||
|
||||
const file = Bun.file(path);
|
||||
const name = makeName("png");
|
||||
const form = new FormData();
|
||||
form.append("file", file);
|
||||
|
||||
const res = await app.server.request("/api/media/upload/" + name, {
|
||||
method: "POST",
|
||||
body: form
|
||||
});
|
||||
const result = (await res.json()) as any;
|
||||
expect(result.name).toBe(name);
|
||||
|
||||
const destFile = Bun.file(assetsTmpPath + "/" + name);
|
||||
expect(destFile.exists()).resolves.toBe(true);
|
||||
await destFile.delete();
|
||||
});
|
||||
|
||||
test("limits body", async () => {
|
||||
const app = await makeApp({ storage: { body_max_size: 1 } });
|
||||
|
||||
const file = await Bun.file(path);
|
||||
const name = makeName("png");
|
||||
const res = await app.server.request("/api/media/upload/" + name, {
|
||||
method: "POST",
|
||||
body: file
|
||||
});
|
||||
|
||||
expect(res.status).toBe(413);
|
||||
expect(await Bun.file(assetsTmpPath + "/" + name).exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,7 +15,7 @@ describe("StorageLocalAdapter", () => {
|
||||
|
||||
test("puts an object", async () => {
|
||||
objects = (await adapter.listObjects()).length;
|
||||
expect(await adapter.putObject(filename, file)).toBeString();
|
||||
expect(await adapter.putObject(filename, file as unknown as File)).toBeString();
|
||||
});
|
||||
|
||||
test("lists objects", async () => {
|
||||
|
||||
Reference in New Issue
Block a user