Initial commit

This commit is contained in:
Zoe
2026-08-07 22:13:25 -05:00
commit 8006fd67f3
40 changed files with 11017 additions and 0 deletions

View File

@@ -0,0 +1,35 @@
import test from "node:test";
import assert from "node:assert/strict";
import { loadModule } from "./test-helpers.mjs";
const contract = await loadModule("shared/utils/drop-contract.ts");
const types = await loadModule("shared/types/drop.ts");
const valid = {
version: 2,
chunkSize: types.CHUNK_SIZE,
chunkCount: 1,
noncePrefix: "AQIDBAUGBwg",
expiresIn: "24h",
maxReads: 1,
};
test("reservation validation accepts protocol geometry and rejects unsafe policies", () => {
assert.deepEqual(contract.validateReservation(valid), valid);
for (const bad of [
{ ...valid, chunkSize: 1 },
{ ...valid, chunkCount: 0 },
{ ...valid, chunkCount: 1.5 },
{ ...valid, expiresIn: "forever" },
{ ...valid, maxReads: 2 },
{ ...valid, unknown: true },
{ ...valid, noncePrefix: "AA" },
])
assert.throws(() => contract.validateReservation(bad));
});
test("manifest body must contain plaintext as well as the GCM tag", () => {
assert.throws(() => contract.validateManifestBodyLength(types.GCM_TAG_BYTES));
assert.doesNotThrow(() =>
contract.validateManifestBodyLength(types.GCM_TAG_BYTES + 1),
);
});

126
tests/drop-crypto.test.mjs Normal file
View File

@@ -0,0 +1,126 @@
import test from "node:test";
import assert from "node:assert/strict";
import { loadModule } from "./test-helpers.mjs";
const crypto = await loadModule("app/utils/crypto.ts");
const id = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
const key = crypto.generateKey();
const wrongKey = crypto.generateKey();
const nonce = crypto.generateNoncePrefix();
test("AES-GCM manifest and chunk round trips preserve plaintext", async () => {
const plain = new TextEncoder().encode("hello chunk");
const cipher = await crypto.encryptChunk(
plain,
key,
id,
nonce,
0,
8 * 1024 * 1024,
1,
);
assert.deepEqual(
[
...(await crypto.decryptChunk(
cipher,
key,
id,
nonce,
0,
plain.byteLength,
8 * 1024 * 1024,
1,
)),
],
[...plain],
);
const manifest = {
version: 2,
name: "hello.txt",
type: "text/plain",
size: plain.byteLength,
lastModified: 1,
chunkSize: 8 * 1024 * 1024,
chunkCount: 1,
};
const encrypted = await crypto.encryptManifest(manifest, key, id, nonce);
assert.deepEqual(
await crypto.decryptManifest(
encrypted,
key,
id,
nonce,
manifest.chunkSize,
manifest.chunkCount,
),
manifest,
);
});
test("empty chunks are authenticated and round-trip", async () => {
const cipher = await crypto.encryptChunk(
new Uint8Array(),
key,
id,
nonce,
0,
8 * 1024 * 1024,
1,
);
assert.equal(cipher.byteLength, 16);
assert.equal(
(
await crypto.decryptChunk(
cipher,
key,
id,
nonce,
0,
0,
8 * 1024 * 1024,
1,
)
).byteLength,
0,
);
});
test("tampering and wrong key or AAD fail integrity", async () => {
const cipher = await crypto.encryptChunk(
new Uint8Array([1, 2, 3]),
key,
id,
nonce,
0,
8 * 1024 * 1024,
1,
);
const tampered = new Uint8Array(cipher);
tampered[0] ^= 1;
await assert.rejects(
crypto.decryptChunk(tampered, key, id, nonce, 0, 3, 8 * 1024 * 1024, 1),
/integrity/,
);
await assert.rejects(
crypto.decryptChunk(cipher, wrongKey, id, nonce, 0, 3, 8 * 1024 * 1024, 1),
/integrity/,
);
await assert.rejects(
crypto.decryptChunk(cipher, key, id, nonce, 1, 3, 8 * 1024 * 1024, 1),
/integrity/,
);
});
test("tag-only manifests are rejected before decryption", async () => {
await assert.rejects(
crypto.decryptManifest(
new Uint8Array(16),
key,
id,
nonce,
8 * 1024 * 1024,
1,
),
/empty/,
);
});

153
tests/drop-store.test.mjs Normal file
View File

@@ -0,0 +1,153 @@
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);
});

207
tests/http-smoke.test.mjs Normal file
View File

@@ -0,0 +1,207 @@
import test from "node:test";
import assert from "node:assert/strict";
import { access, mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { execFile, spawn } from "node:child_process";
import { promisify } from "node:util";
try {
await access(".output/server/index.mjs");
} catch {
await promisify(execFile)("npm", ["run", "build"], { stdio: "inherit" });
}
const port = 19000 + Math.floor(Math.random() * 500);
const storage = await mkdtemp(join(tmpdir(), "quickdrop-http-"));
const child = spawn(process.execPath, [".output/server/index.mjs"], {
env: {
...process.env,
NITRO_HOST: "127.0.0.1",
NITRO_PORT: String(port),
AUTH_PASSCODE: "smoke-passcode",
AUTH_SECRET: "smoke-secret-for-tests",
QUICKDROP_STORAGE_DIR: storage,
},
stdio: "ignore",
});
async function request(url, init) {
return fetch(`http://127.0.0.1:${port}${url}`, init);
}
async function waitForServer() {
for (let attempt = 0; attempt < 50; attempt++) {
try {
const response = await request("/api/auth/session");
if (response.status === 200) return;
} catch {}
await new Promise((resolve) => setTimeout(resolve, 100));
}
throw new Error("built server did not start");
}
test("built server enforces auth and serves capability-only lease/chunk APIs", async () => {
try {
await waitForServer();
const reservationBody = JSON.stringify({
version: 2,
chunkSize: 8 * 1024 * 1024,
chunkCount: 1,
noncePrefix: "AQIDBAUGBwg",
expiresIn: "24h",
maxReads: 1,
});
assert.equal(
(
await request("/api/drops", {
method: "POST",
headers: { "content-type": "application/json" },
body: reservationBody,
})
).status,
401,
);
const login = await request("/api/auth/login", {
method: "POST",
headers: {
"content-type": "application/json",
origin: `http://127.0.0.1:${port}`,
},
body: JSON.stringify({ passcode: "smoke-passcode" }),
});
assert.equal(login.status, 204);
const cookie = login.headers.get("set-cookie").split(";", 1)[0];
const reserve = await request("/api/drops", {
method: "POST",
headers: {
"content-type": "application/json",
cookie,
origin: `http://127.0.0.1:${port}`,
},
body: reservationBody,
});
assert.equal(reserve.status, 201);
const created = await reserve.json();
const chunk = new Uint8Array(16);
const put = await request(`/api/drops/${created.id}/chunks/0`, {
method: "PUT",
headers: {
"content-type": "application/octet-stream",
cookie,
origin: `http://127.0.0.1:${port}`,
},
body: chunk,
});
assert.equal(put.status, 201);
const finalize = await request(`/api/drops/${created.id}/finalize`, {
method: "POST",
headers: {
"content-type": "application/octet-stream",
cookie,
origin: `http://127.0.0.1:${port}`,
},
body: new Uint8Array(17),
});
assert.equal(finalize.status, 200);
const descriptor = await request(`/api/drops/${created.id}`, {
headers: { "x-quickdrop-access": created.access },
});
assert.equal(descriptor.status, 200);
const first = await request(`/api/drops/${created.id}/leases`, {
method: "POST",
headers: { "x-quickdrop-access": created.access },
});
assert.equal(first.status, 201);
const lease = await first.json();
const second = await request(`/api/drops/${created.id}/leases`, {
method: "POST",
headers: { "x-quickdrop-access": created.access },
});
assert.equal(second.status, 410);
const reused = await request(`/api/drops/${created.id}/leases`, {
method: "POST",
headers: {
"x-quickdrop-access": created.access,
authorization: `Bearer ${lease.token}`,
},
});
assert.equal(reused.status, 200);
assert.equal((await reused.json()).token, lease.token);
const chunkGet = await request(`/api/drops/${created.id}/chunks/0`, {
headers: { authorization: `Bearer ${lease.token}` },
});
assert.equal(chunkGet.status, 200);
assert.equal((await chunkGet.arrayBuffer()).byteLength, 16);
const complete = await request(`/api/drops/${created.id}/complete`, {
method: "POST",
headers: { authorization: `Bearer ${lease.token}` },
});
assert.equal(complete.status, 204);
// Completion retries must also be idempotent when the drop remains (unlimited reads).
const unlimitedBody = JSON.stringify({
...JSON.parse(reservationBody),
maxReads: null,
});
const unlimitedReserve = await request("/api/drops", {
method: "POST",
headers: {
"content-type": "application/json",
cookie,
origin: `http://127.0.0.1:${port}`,
},
body: unlimitedBody,
});
assert.equal(unlimitedReserve.status, 201);
const unlimited = await unlimitedReserve.json();
assert.equal(
(
await request(`/api/drops/${unlimited.id}/chunks/0`, {
method: "PUT",
headers: {
"content-type": "application/octet-stream",
cookie,
origin: `http://127.0.0.1:${port}`,
},
body: chunk,
})
).status,
201,
);
assert.equal(
(
await request(`/api/drops/${unlimited.id}/finalize`, {
method: "POST",
headers: {
"content-type": "application/octet-stream",
cookie,
origin: `http://127.0.0.1:${port}`,
},
body: new Uint8Array(17),
})
).status,
200,
);
const unlimitedClaim = await request(`/api/drops/${unlimited.id}/leases`, {
method: "POST",
headers: { "x-quickdrop-access": unlimited.access },
});
assert.equal(unlimitedClaim.status, 201);
const unlimitedLease = await unlimitedClaim.json();
const completionInit = {
method: "POST",
headers: { authorization: `Bearer ${unlimitedLease.token}` },
};
assert.equal(
(await request(`/api/drops/${unlimited.id}/complete`, completionInit))
.status,
204,
);
assert.equal(
(await request(`/api/drops/${unlimited.id}/complete`, completionInit))
.status,
204,
);
} finally {
child.kill("SIGTERM");
await rm(storage, { recursive: true, force: true });
}
});

16
tests/test-helpers.mjs Normal file
View File

@@ -0,0 +1,16 @@
import { createServer } from "vite";
import { resolve } from "node:path";
export const root = resolve(new URL("..", import.meta.url).pathname);
export async function loadModule(path) {
const vite = await createServer({
root,
logLevel: "error",
resolve: { alias: { "~": resolve(root, "app") } },
});
try {
return await vite.ssrLoadModule(`/${path}`);
} finally {
await vite.close();
}
}