mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-15 18:30:39 +09:00
79 lines
2.8 KiB
TypeScript
79 lines
2.8 KiB
TypeScript
|
|
import { afterEach, describe, expect, it } from "vitest";
|
||
|
|
import os from "node:os";
|
||
|
|
import path from "node:path";
|
||
|
|
import { promises as fs } from "node:fs";
|
||
|
|
import { createLocalDiskStorageProvider } from "../storage/local-disk-provider.js";
|
||
|
|
import { createStorageService } from "../storage/service.js";
|
||
|
|
|
||
|
|
async function readStreamToBuffer(stream: NodeJS.ReadableStream): Promise<Buffer> {
|
||
|
|
const chunks: Buffer[] = [];
|
||
|
|
for await (const chunk of stream) {
|
||
|
|
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
||
|
|
}
|
||
|
|
return Buffer.concat(chunks);
|
||
|
|
}
|
||
|
|
|
||
|
|
describe("local disk storage provider", () => {
|
||
|
|
const tempRoots: string[] = [];
|
||
|
|
|
||
|
|
afterEach(async () => {
|
||
|
|
await Promise.all(tempRoots.map((root) => fs.rm(root, { recursive: true, force: true })));
|
||
|
|
tempRoots.length = 0;
|
||
|
|
});
|
||
|
|
|
||
|
|
it("round-trips bytes through storage service", async () => {
|
||
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-storage-"));
|
||
|
|
tempRoots.push(root);
|
||
|
|
|
||
|
|
const service = createStorageService(createLocalDiskStorageProvider(root));
|
||
|
|
const content = Buffer.from("hello image bytes", "utf8");
|
||
|
|
const stored = await service.putFile({
|
||
|
|
companyId: "company-1",
|
||
|
|
namespace: "issues/issue-1",
|
||
|
|
originalFilename: "demo.png",
|
||
|
|
contentType: "image/png",
|
||
|
|
body: content,
|
||
|
|
});
|
||
|
|
|
||
|
|
const fetched = await service.getObject("company-1", stored.objectKey);
|
||
|
|
const fetchedBody = await readStreamToBuffer(fetched.stream);
|
||
|
|
|
||
|
|
expect(fetchedBody.toString("utf8")).toBe("hello image bytes");
|
||
|
|
expect(stored.sha256).toHaveLength(64);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("blocks cross-company object access", async () => {
|
||
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-storage-"));
|
||
|
|
tempRoots.push(root);
|
||
|
|
|
||
|
|
const service = createStorageService(createLocalDiskStorageProvider(root));
|
||
|
|
const stored = await service.putFile({
|
||
|
|
companyId: "company-a",
|
||
|
|
namespace: "issues/issue-1",
|
||
|
|
originalFilename: "demo.png",
|
||
|
|
contentType: "image/png",
|
||
|
|
body: Buffer.from("hello", "utf8"),
|
||
|
|
});
|
||
|
|
|
||
|
|
await expect(service.getObject("company-b", stored.objectKey)).rejects.toMatchObject({ status: 403 });
|
||
|
|
});
|
||
|
|
|
||
|
|
it("delete is idempotent", async () => {
|
||
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-storage-"));
|
||
|
|
tempRoots.push(root);
|
||
|
|
|
||
|
|
const service = createStorageService(createLocalDiskStorageProvider(root));
|
||
|
|
const stored = await service.putFile({
|
||
|
|
companyId: "company-1",
|
||
|
|
namespace: "issues/issue-1",
|
||
|
|
originalFilename: "demo.png",
|
||
|
|
contentType: "image/png",
|
||
|
|
body: Buffer.from("hello", "utf8"),
|
||
|
|
});
|
||
|
|
|
||
|
|
await service.deleteObject("company-1", stored.objectKey);
|
||
|
|
await service.deleteObject("company-1", stored.objectKey);
|
||
|
|
await expect(service.getObject("company-1", stored.objectKey)).rejects.toMatchObject({ status: 404 });
|
||
|
|
});
|
||
|
|
});
|