Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline (Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/* packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
46 lines
1.6 KiB
TypeScript
46 lines
1.6 KiB
TypeScript
import { mkdtemp, rm, stat, symlink, writeFile } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import path from "node:path";
|
|
import { afterEach, describe, expect, it } from "vitest";
|
|
import { LocalArtifactStore } from "./artifacts.js";
|
|
|
|
const dirs: string[] = [];
|
|
const context = { spaceId: "space-1" } as never;
|
|
|
|
afterEach(async () => {
|
|
await Promise.all(dirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
|
|
});
|
|
|
|
describe("LocalArtifactStore", () => {
|
|
it("creates artifact files with owner-only permissions", async () => {
|
|
const root = await mkdtemp(path.join(tmpdir(), "rakazo-artifacts-"));
|
|
dirs.push(root);
|
|
const store = new LocalArtifactStore(root);
|
|
|
|
const stored = await store.put(
|
|
{ name: "private.txt", mimeType: "text/plain", bytes: new TextEncoder().encode("private") },
|
|
context,
|
|
);
|
|
|
|
const info = await stat(path.join(root, "artifacts", "space-1", stored.id));
|
|
expect(info.mode & 0o777).toBe(0o600);
|
|
});
|
|
|
|
it("does not follow a replacement symlink when reading an artifact", async () => {
|
|
const root = await mkdtemp(path.join(tmpdir(), "rakazo-artifacts-"));
|
|
dirs.push(root);
|
|
const store = new LocalArtifactStore(root);
|
|
const stored = await store.put(
|
|
{ name: "private.txt", mimeType: "text/plain", bytes: new TextEncoder().encode("private") },
|
|
context,
|
|
);
|
|
const file = path.join(root, "artifacts", "space-1", stored.id);
|
|
const target = path.join(root, "outside.txt");
|
|
await writeFile(target, "outside");
|
|
await rm(file);
|
|
await symlink(target, file);
|
|
|
|
await expect(store.get(stored.id, context)).rejects.toThrow();
|
|
});
|
|
});
|