Initial commit
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { gunzipSync, inflateRawSync } from "node:zlib";
|
||||
import { makeTarGz, makeZip, sha256hex } from "../src/bundle.js";
|
||||
|
||||
const files = [
|
||||
{ path: "src/app/page.tsx", bytes: Buffer.from("hello world hello world") },
|
||||
{ path: "b.txt", bytes: Buffer.from("x") },
|
||||
];
|
||||
|
||||
test("makeTarGz: deterministic (order-independent), valid gzip, contains files", () => {
|
||||
const a = makeTarGz(files);
|
||||
const b = makeTarGz([...files].reverse());
|
||||
assert.equal(sha256hex(a), sha256hex(b), "sorted entries → deterministic regardless of input order");
|
||||
|
||||
const tar = gunzipSync(a).toString("latin1");
|
||||
assert.ok(tar.includes("src/app/page.tsx"), "path present in tar header");
|
||||
assert.ok(tar.includes("ustar"), "ustar magic present");
|
||||
assert.ok(tar.includes("hello world hello world"), "content present");
|
||||
});
|
||||
|
||||
test("makeZip: deterministic, valid signatures, deflate round-trips", () => {
|
||||
const z = makeZip(files);
|
||||
assert.equal(sha256hex(z), sha256hex(makeZip(files)), "deterministic");
|
||||
assert.equal(z.readUInt32LE(0), 0x04034b50, "local file header signature");
|
||||
assert.equal(z.readUInt32LE(z.length - 22), 0x06054b50, "end-of-central-directory signature");
|
||||
|
||||
// Extract the first entry and verify the deflate stream round-trips.
|
||||
const nameLen = z.readUInt16LE(26);
|
||||
const compSize = z.readUInt32LE(18);
|
||||
const dataStart = 30 + nameLen;
|
||||
const out = inflateRawSync(z.subarray(dataStart, dataStart + compSize));
|
||||
// entries are sorted: "b.txt" sorts before "src/app/page.tsx"
|
||||
assert.equal(out.toString(), "x");
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { collectFileMap } from "@cloner/core";
|
||||
import { LocalArtifactStore } from "../src/local.js";
|
||||
|
||||
test("LocalArtifactStore: persists, inlines text, references binaries, round-trips bytes", async () => {
|
||||
const work = mkdtempSync(join(tmpdir(), "store-src-"));
|
||||
const blobs = mkdtempSync(join(tmpdir(), "store-blobs-"));
|
||||
try {
|
||||
// Synthesize a generated app and collect it.
|
||||
const app = join(work, "generated", "app");
|
||||
mkdirSync(join(app, "src", "app"), { recursive: true });
|
||||
mkdirSync(join(app, "public", "assets", "cloned", "images"), { recursive: true });
|
||||
writeFileSync(join(app, "src", "app", "page.tsx"), "export default () => null\n");
|
||||
const png = Buffer.from([1, 2, 3, 4, 5]);
|
||||
writeFileSync(join(app, "public", "assets", "cloned", "images", "a.png"), png);
|
||||
const files = collectFileMap(work);
|
||||
|
||||
const store = new LocalArtifactStore(blobs);
|
||||
const manifest = await store.putClone("job-1", files);
|
||||
|
||||
const page = manifest.files.find((f) => f.path === "src/app/page.tsx")!;
|
||||
assert.equal(page.kind, "text");
|
||||
assert.ok(page.kind === "text" && page.content.includes("export default"));
|
||||
|
||||
const bin = manifest.files.find((f) => f.path.endsWith("a.png"))!;
|
||||
assert.equal(bin.kind, "binary");
|
||||
assert.ok(bin.kind === "binary" && bin.key === "job-1/public/assets/cloned/images/a.png");
|
||||
|
||||
const got = await store.getFile("job-1", "public/assets/cloned/images/a.png");
|
||||
assert.ok(got);
|
||||
assert.deepEqual([...got!.bytes], [1, 2, 3, 4, 5]);
|
||||
|
||||
assert.equal(await store.binaryUrl("job-1", "public/assets/cloned/images/a.png"), "/v1/clones/job-1/files/public/assets/cloned/images/a.png");
|
||||
|
||||
await store.remove("job-1");
|
||||
assert.equal(await store.getFile("job-1", "public/assets/cloned/images/a.png"), null);
|
||||
} finally {
|
||||
rmSync(work, { recursive: true, force: true });
|
||||
rmSync(blobs, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("LocalArtifactStore: rejects path traversal", async () => {
|
||||
const blobs = mkdtempSync(join(tmpdir(), "store-blobs2-"));
|
||||
try {
|
||||
const store = new LocalArtifactStore(blobs);
|
||||
await assert.rejects(() => store.getFile("job-1", "../../etc/passwd"));
|
||||
} finally {
|
||||
rmSync(blobs, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { collectFileMap } from "@cloner/core";
|
||||
import { ObjectArtifactStore, InMemoryBlobClient } from "../src/index.js";
|
||||
|
||||
test("ObjectArtifactStore: binaries → blob (presigned URL), text inline, getFile, bundle, remove", async () => {
|
||||
const work = mkdtempSync(join(tmpdir(), "obj-src-"));
|
||||
try {
|
||||
const app = join(work, "generated", "app");
|
||||
mkdirSync(join(app, "src", "app"), { recursive: true });
|
||||
mkdirSync(join(app, "public", "assets", "cloned", "images"), { recursive: true });
|
||||
writeFileSync(join(app, "src", "app", "page.tsx"), "export default () => null\n");
|
||||
writeFileSync(join(app, "public", "assets", "cloned", "images", "a.png"), Buffer.from([9, 8, 7]));
|
||||
const files = collectFileMap(work);
|
||||
|
||||
const blob = new InMemoryBlobClient("https://cdn.test");
|
||||
const store = new ObjectArtifactStore(blob);
|
||||
const manifest = await store.putClone("job-1", files);
|
||||
|
||||
const page = manifest.files.find((f) => f.path === "src/app/page.tsx")!;
|
||||
assert.equal(page.kind, "text");
|
||||
|
||||
const bin = manifest.files.find((f) => f.path.endsWith("a.png"))!;
|
||||
assert.equal(bin.kind, "binary");
|
||||
assert.ok(bin.kind === "binary" && bin.key === "clones/job-1/public/assets/cloned/images/a.png");
|
||||
assert.ok(blob.has("clones/job-1/public/assets/cloned/images/a.png"), "binary uploaded to blob");
|
||||
assert.ok(!blob.has("clones/job-1/src/app/page.tsx"), "text NOT uploaded (stays in manifest)");
|
||||
|
||||
const got = await store.getFile("job-1", "public/assets/cloned/images/a.png");
|
||||
assert.deepEqual([...got!.bytes], [9, 8, 7]);
|
||||
|
||||
const url = await store.binaryUrl("job-1", "public/assets/cloned/images/a.png");
|
||||
assert.ok(url.startsWith("https://cdn.test/clones/job-1/"), "presigned/public URL");
|
||||
|
||||
const bundleUrl = await store.uploadBundle("job-1", "tgz", Buffer.from("archive"));
|
||||
assert.ok(bundleUrl.includes("clones/job-1/bundle/clone.tgz"));
|
||||
|
||||
await store.remove("job-1");
|
||||
assert.equal(await store.getFile("job-1", "public/assets/cloned/images/a.png"), null);
|
||||
} finally {
|
||||
rmSync(work, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user