import test from "node:test"; import assert from "node:assert/strict"; import { mkdtemp, readFile, rm, stat, utimes, access } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { loadModule } from "./test-helpers.mjs"; const { DropStore, DropStoreError, pathsFor } = await loadModule( "server/utils/drop-store.ts", ); const { CHUNK_SIZE } = await loadModule("shared/types/drop.ts"); const noncePrefix = "AQIDBAUGBwg"; let roots = []; async function fresh(clock = { now: () => Date.now() }) { const root = await mkdtemp(join(tmpdir(), "quickdrop-test-")); roots.push(root); return { store: new DropStore({ root, clock }), root }; } async function reserve(store, maxReads = 1, expiresIn = "24h") { return store.reserve({ version: 2, chunkSize: CHUNK_SIZE, chunkCount: 1, noncePrefix, expiresIn, maxReads, }); } async function ready(store, maxReads = 1, expiresIn = "24h") { const result = await reserve(store, maxReads, expiresIn); await store.putChunk(result.id, 0, new Uint8Array(16)); await store.finalize(result.id, new Uint8Array(17)); return result; } async function exists(path) { try { await access(path); return true; } catch { return false; } } test.after(async () => { await Promise.all( roots.map((root) => rm(root, { recursive: true, force: true })), ); }); test("finalize starts expiry and rejects missing chunks", async () => { let now = 1000; const { store } = await fresh({ now: () => now }); const result = await reserve(store, 1, "1h"); await assert.rejects( store.finalize(result.id, new Uint8Array(17)), (error) => error instanceof DropStoreError && error.code === "incomplete", ); await store.putChunk(result.id, 0, new Uint8Array(16)); const metadata = await store.finalize(result.id, new Uint8Array(17)); assert.equal(metadata.expiresAt, now + 3600000); now += 3599999; assert.equal( (await store.descriptor(result.id, result.access)).id, result.id, ); now++; await assert.rejects( store.descriptor(result.id, result.access), (error) => error instanceof DropStoreError && error.code === "expired", ); }); test("chunk retries are idempotent and conflicting contents are rejected", async () => { const { store } = await fresh(); const result = await reserve(store); const body = new Uint8Array(16).fill(7); assert.equal(await store.putChunk(result.id, 0, body), "created"); assert.equal(await store.putChunk(result.id, 0, body), "same"); await assert.rejects( store.putChunk(result.id, 0, new Uint8Array(16).fill(8)), (error) => error instanceof DropStoreError && error.code === "conflict", ); }); test("maxReads claims are serialized, valid lease reuse does not increment, and completion cleans up", async () => { const { store, root } = await fresh(); const result = await ready(store, 1); const claims = await Promise.allSettled([ store.claim(result.id, result.access), store.claim(result.id, result.access), ]); assert.equal( claims.filter((value) => value.status === "fulfilled").length, 1, ); assert.equal(claims.filter((value) => value.status === "rejected").length, 1); const first = claims.find((value) => value.status === "fulfilled").value; const metadataBefore = await store.readMetadata(result.id); const reused = await store.claim(result.id, result.access, first.token); assert.equal(reused.reused, true); assert.equal( (await store.readMetadata(result.id)).readsClaimed, metadataBefore.readsClaimed, ); const chunk = await store.readChunk(result.id, 0, first.token); assert.equal(chunk.size, 16); await chunk.handle.close(); await store.complete(result.id, first.token); assert.equal(await exists(pathsFor(result.id, root).directory), false); }); test("unlimited drops allow multiple leases and metadata remains parseable", async () => { const { store, root } = await fresh(); const result = await ready(store, null); const one = await store.claim(result.id, result.access); const two = await store.claim(result.id, result.access); assert.notEqual(one.token, two.token); assert.equal((await store.readMetadata(result.id)).readsClaimed, 2); JSON.parse(await readFile(pathsFor(result.id, root).metadata, "utf8")); await store.complete(result.id, one.token); assert.equal(await exists(pathsFor(result.id, root).directory), true); }); test("idle lease cleanup and stale temporary files are safe", async () => { let now = 100000; const { store, root } = await fresh({ now: () => now }); const result = await ready(store, 1); await store.claim(result.id, result.access); const paths = pathsFor(result.id, root); const stale = join(paths.directory, ".chunk-stale"); await (await import("node:fs/promises")).writeFile(stale, "stale"); const old = new Date(now - 2 * 3600000); await utimes(stale, old, old); now += 16 * 60 * 1000; await store.cleanup(); assert.equal(await exists(stale), false); assert.equal(await exists(paths.directory), false); }); test("metadata-less valid directories are removed only after grace period", async () => { let now = 10_000_000; const { store, root } = await fresh({ now: () => now }); const result = await reserve(store); const paths = pathsFor(result.id, root); await rm(paths.metadata); const recent = new Date(now); await utimes(paths.directory, recent, recent); await store.cleanup(); assert.equal(await exists(paths.directory), true); const old = new Date(now - 2 * 3600000); await utimes(paths.directory, old, old); await store.cleanup(); assert.equal(await exists(paths.directory), false); });