import { getPublicKey, randomPrivateKey, } from "@suaveplan/crypto-curve/ed25519"; import { Drive } from "@suaveplan/drive"; import { MemoryStorage } from "@suaveplan/storage/memory"; import { describe, expect, it } from "@suaveplan/testing/runner"; import { parseChunk } from "./codec/chunk.js"; import { buildManifest } from "./manifest/build.js"; import { chunksPlugin } from "./plugin.js"; function createDriveStorage(id: string) { return Object.assign(new MemoryStorage(), { id, }) as import("@suaveplan/drive").DriveStorage; } describe("CHUNKS-INVARIANT-01 — Store-as-is", () => { it("putChunk persists raw bytes unchanged", { timeout: 5000 }, async () => { const privKey = randomPrivateKey(); const pubKey = getPublicKey(privKey); const storage = createDriveStorage("default"); const drive = new Drive(storage); await drive.initialize(); const chunks = await drive.usePlugin( chunksPlugin({ serverPrivKey: privKey, serverPubKey: pubKey, chunkSize: 1024, }) ); await drive.write("test.txt", new TextEncoder().encode("hello world")); const fileId = await chunks.ingest("test.txt"); const raw = await chunks.getChunk(fileId, 0); expect(raw).toBeDefined(); const parsed = parseChunk(raw!); expect(parsed.metadata.fileId).toEqual(fileId); await drive.dispose(); }); }); describe("CHUNKS-VERIFY-01 — Sig-verify gate on receipt", () => { it("putChunk rejects invalid signature before persist", { timeout: 5000, }, async () => { const privKey = randomPrivateKey(); const _pubKey = getPublicKey(privKey); const wrongPubKey = getPublicKey(randomPrivateKey()); const storage = createDriveStorage("default"); const drive = new Drive(storage); await drive.initialize(); const chunks = await drive.usePlugin( chunksPlugin({ serverPrivKey: privKey, serverPubKey: wrongPubKey, chunkSize: 1024, }) ); await drive.write("test.txt", new TextEncoder().encode("hello world")); const fileId = await chunks.ingest("test.txt"); const raw = await chunks.getChunk(fileId, 0); expect(raw).toBeDefined(); // Now try to put the same chunk with a different pubKey (wrong signer) const chunks2 = await drive.usePlugin( chunksPlugin({ serverPrivKey: privKey, serverPubKey: wrongPubKey, chunkSize: 1024, }) ); await expect(chunks2.putChunk(raw!)).rejects.toThrow(); try { await chunks2.putChunk(raw!); } catch (e) { expect((e as { code?: string }).code).toBe("CHUNKS.SIG_INVALID"); } await drive.dispose(); }); }); describe("CHUNKS-READ-01 — Read middleware reassembles", () => { it("read returns original plaintext after ingest", { timeout: 5000, }, async () => { const privKey = randomPrivateKey(); const pubKey = getPublicKey(privKey); const storage = createDriveStorage("default"); const drive = new Drive(storage); await drive.initialize(); const chunks = await drive.usePlugin( chunksPlugin({ serverPrivKey: privKey, serverPubKey: pubKey, chunkSize: 1024, }) ); const plaintext = new TextEncoder().encode( "hello world from chunked drive" ); await drive.write("test.txt", plaintext); await chunks.ingest("test.txt"); const result = await drive.read("test.txt"); expect(result).toEqual(plaintext); await drive.dispose(); }); it("throws when chunk is missing during reassembly", { timeout: 5000, }, async () => { const privKey = randomPrivateKey(); const pubKey = getPublicKey(privKey); const storage = createDriveStorage("default"); const drive = new Drive(storage); await drive.initialize(); // Use chunk-keys layout so we can delete individual chunks const chunks = await drive.usePlugin( chunksPlugin({ serverPrivKey: privKey, serverPubKey: pubKey, chunkSize: 1024, layout: "chunk-keys", }) ); const plaintext = new TextEncoder().encode("x".repeat(2000)); await drive.write("test.txt", plaintext); const fileId = await chunks.ingest("test.txt"); // Delete chunk 1 to simulate corruption await storage.delete( `chunks/${Array.from(fileId, (b) => b.toString(16).padStart(2, "0")).join("")}/1` ); await expect(drive.read("test.txt")).rejects.toThrow(); try { await drive.read("test.txt"); } catch (e) { expect((e as { code?: string }).code).toBe("CHUNKS.FILE_NOT_FOUND"); } await drive.dispose(); }); }); describe("CHUNKS-PLUGIN-INIT-01 — Storage resolution", () => { it("throws when drive has no getStorage", { timeout: 5000 }, async () => { const privKey = randomPrivateKey(); const pubKey = getPublicKey(privKey); const fakeDrive = { useMiddleware: () => {}, getStorage: () => { throw new Error("no storage"); }, } as unknown as import("@suaveplan/drive").DriveLike; const plugin = chunksPlugin({ serverPrivKey: privKey, serverPubKey: pubKey, }); await expect(plugin.install(fakeDrive)).rejects.toThrow(); try { await plugin.install(fakeDrive); } catch (e) { expect((e as { code?: string }).code).toBe("CHUNKS.FILE_NOT_FOUND"); } }); }); describe("CHUNKS-INGEST-01 — Edge cases", () => { it("ingests empty file", { timeout: 5000 }, async () => { const privKey = randomPrivateKey(); const pubKey = getPublicKey(privKey); const storage = createDriveStorage("default"); const drive = new Drive(storage); await drive.initialize(); const chunks = await drive.usePlugin( chunksPlugin({ serverPrivKey: privKey, serverPubKey: pubKey, chunkSize: 1024, }) ); await drive.write("empty.txt", new Uint8Array(0)); const fileId = await chunks.ingest("empty.txt"); // Empty file produces zero chunks, but manifest should exist const manifestRaw = await storage.get( `manifest:${Array.from(fileId, (b) => b.toString(16).padStart(2, "0")).join("")}` ); expect(manifestRaw).toBeDefined(); await drive.dispose(); }); }); describe("CHUNKS-SOURCE-01 — ChunkSource interface", () => { it("hasChunks and availableChunks reflect stored chunks", { timeout: 5000, }, async () => { const privKey = randomPrivateKey(); const pubKey = getPublicKey(privKey); const storage = createDriveStorage("default"); const drive = new Drive(storage); await drive.initialize(); const chunks = await drive.usePlugin( chunksPlugin({ serverPrivKey: privKey, serverPubKey: pubKey, chunkSize: 1024, layout: "chunk-keys", }) ); const plaintext = new TextEncoder().encode( "hello world from chunked drive" ); await drive.write("test.txt", plaintext); const fileId = await chunks.ingest("test.txt"); const has = await chunks.hasChunks(fileId); expect(has[0]).toBe(true); const available = Array.from(await chunks.availableChunks(fileId)); expect(available).toEqual([0]); await drive.dispose(); }); it("putChunk stores a valid chunk", { timeout: 5000 }, async () => { const privKey = randomPrivateKey(); const pubKey = getPublicKey(privKey); const storage = createDriveStorage("default"); const drive = new Drive(storage); await drive.initialize(); const chunks = await drive.usePlugin( chunksPlugin({ serverPrivKey: privKey, serverPubKey: pubKey, chunkSize: 1024, layout: "chunk-keys", }) ); const plaintext = new TextEncoder().encode( "hello world from chunked drive" ); await drive.write("test.txt", plaintext); const fileId = await chunks.ingest("test.txt"); const raw = await chunks.getChunk(fileId, 0); expect(raw).toBeDefined(); // Re-put the same chunk (valid signature) await chunks.putChunk(raw!); // Verify it was stored const stored = await chunks.getChunk(fileId, 0); expect(stored).toBeDefined(); expect(stored!).toEqual(raw!); await drive.dispose(); }); it("exposes layout name", { timeout: 5000 }, async () => { const privKey = randomPrivateKey(); const pubKey = getPublicKey(privKey); const storage = createDriveStorage("default"); const drive = new Drive(storage); await drive.initialize(); const chunks = await drive.usePlugin( chunksPlugin({ serverPrivKey: privKey, serverPubKey: pubKey, chunkSize: 1024, layout: "chunk-keys", }) ); expect(chunks.layout).toBe("chunk-keys"); await drive.dispose(); }); it("uses default chunkSize when not specified", { timeout: 5000, }, async () => { const privKey = randomPrivateKey(); const pubKey = getPublicKey(privKey); const storage = createDriveStorage("default"); const drive = new Drive(storage); await drive.initialize(); const chunks = await drive.usePlugin( chunksPlugin({ serverPrivKey: privKey, serverPubKey: pubKey }) ); const plaintext = new TextEncoder().encode("hello world"); await drive.write("default-size.txt", plaintext); const fileId = await chunks.ingest("default-size.txt"); expect(fileId).toBeDefined(); await drive.dispose(); }); it("uses explicit sparse-file layout", { timeout: 5000 }, async () => { const privKey = randomPrivateKey(); const pubKey = getPublicKey(privKey); const storage = createDriveStorage("default"); const drive = new Drive(storage); await drive.initialize(); const chunks = await drive.usePlugin( chunksPlugin({ serverPrivKey: privKey, serverPubKey: pubKey, layout: "sparse-file", }) ); expect(chunks.layout).toBe("sparse-file"); const plaintext = new TextEncoder().encode("hello world"); await drive.write("sparse.txt", plaintext); const fileId = await chunks.ingest("sparse.txt"); expect(fileId).toBeDefined(); await drive.dispose(); }); it("throws when drive.getStorage is missing", { timeout: 5000, }, async () => { const privKey = randomPrivateKey(); const pubKey = getPublicKey(privKey); const fakeDrive = { useMiddleware: () => {}, } as unknown as import("@suaveplan/drive").DriveLike; const plugin = chunksPlugin({ serverPrivKey: privKey, serverPubKey: pubKey, }); await expect(plugin.install(fakeDrive)).rejects.toThrow(); try { await plugin.install(fakeDrive); } catch (e) { expect((e as { code?: string }).code).toBe("CHUNKS.FILE_NOT_FOUND"); } }); }); describe("CHUNKS-MANIFEST-LOAD-01 — Manifest loading", () => { it("throws when manifest is missing", { timeout: 5000 }, async () => { const privKey = randomPrivateKey(); const pubKey = getPublicKey(privKey); const storage = createDriveStorage("default"); const drive = new Drive(storage); await drive.initialize(); const chunks = await drive.usePlugin( chunksPlugin({ serverPrivKey: privKey, serverPubKey: pubKey, chunkSize: 1024, }) ); const plaintext = new TextEncoder().encode("hello world"); await drive.write("test.txt", plaintext); const fileId = await chunks.ingest("test.txt"); // Delete the manifest await storage.delete( `manifest:${Array.from(fileId, (b) => b.toString(16).padStart(2, "0")).join("")}` ); await expect(drive.read("test.txt")).rejects.toThrow(); try { await drive.read("test.txt"); } catch (e) { expect((e as { code?: string }).code).toBe( "CHUNKS.MANIFEST_NOT_FOUND" ); } await drive.dispose(); }); it("loads manifest from server when configured", { timeout: 5000, }, async () => { const privKey = randomPrivateKey(); const pubKey = getPublicKey(privKey); const storage = createDriveStorage("default"); const drive = new Drive(storage); await drive.initialize(); const manifest = buildManifest({ fileId: new Uint8Array(32), fileSize: 1n, chunkSize: 65536, totalChunks: 1, encryption: { algo: "xchacha20poly1305", fileKeyWrapped: new Uint8Array(32), baseNonce: new Uint8Array(24), }, chunks: [{ chunkId: 0, hash: new Uint8Array(32), payloadLen: 1 }], serverPrivKey: privKey, }); const raw = new TextEncoder().encode( JSON.stringify(manifest, (_k, v) => { if (v instanceof Uint8Array) return `base64:${btoa(String.fromCharCode(...v))}`; if (typeof v === "bigint") return v.toString(); return v; }) ); const originalFetch = globalThis.fetch; // @ts-expect-error - mocking global fetch globalThis.fetch = async () => ({ ok: true, arrayBuffer: async () => raw.buffer, }); try { const chunks = await drive.usePlugin( chunksPlugin({ serverPrivKey: privKey, serverPubKey: pubKey, chunkSize: 1024, manifestSource: "server-only", manifestServerUrl: "http://test-server", }) ); const plaintext = new TextEncoder().encode("hello world"); await drive.write("server-test.txt", plaintext); const fileId = await chunks.ingest("server-test.txt"); // Remove local manifest so server path is taken await storage.delete( `manifest:${Array.from(fileId, (b) => b.toString(16).padStart(2, "0")).join("")}` ); // loadManifest should call the server const loaded = await chunks.loadManifest(fileId); expect(loaded.version).toBe(0x0001); } finally { globalThis.fetch = originalFetch; } await drive.dispose(); }); });