12345678910111213141516171819import { deflate } from "pako";20import { describe, expect, it } from "vitest";21import {22 CHUNK_SIZE,23 MAX_DATA_SHARDS_PER_STRIPE,24 computeParityShards,25 type CodexPayload,26} from "../../shared/codex-types";27import {28 encodeIntoStripes,29 type EncodeResult,30} from "../../popup/codex/erasure";31import {32 reconstructPayloadFromStripes,33 type StripeChunks,34} from "../../popup/codex/generate";353637383940function makeCodexPayload(paddingKb: number): { payload: CodexPayload; json: string } {41 const payload: CodexPayload = {42 vaultBlob: "A".repeat(paddingKb * 1024),43 verifyCode: "TEST-CODE",44 issuedAt: 0,45 fingerprint: "test-fingerprint",46 entries: [],47 identities: [],48 settings: {},49 } as unknown as CodexPayload;50 return { payload, json: JSON.stringify(payload) };51}5253function encodeResultToStripeChunks(r: EncodeResult): StripeChunks {54 const chunks = new Map<number, Uint8Array>();55 for (let i = 0; i < r.dataChunks.length; i++) {56 chunks.set(i, r.dataChunks[i]!);57 }58 for (let i = 0; i < r.parityChunks.length; i++) {59 chunks.set(r.dataShards + i, r.parityChunks[i]!);60 }61 return { chunks, dataShards: r.dataShards, totalChunks: r.totalChunks };62}6364function encodeResultToStripeChunksWithoutParity(r: EncodeResult): StripeChunks {65 const chunks = new Map<number, Uint8Array>();66 for (let i = 0; i < r.dataChunks.length; i++) {67 chunks.set(i, r.dataChunks[i]!);68 }69 70 return { chunks, dataShards: r.dataShards, totalChunks: r.totalChunks };71}7273function buildStripeChunksWithErasures(74 r: EncodeResult,75 eraseParityCount: number,76): StripeChunks {77 const chunks = new Map<number, Uint8Array>();78 for (let i = 0; i < r.dataChunks.length; i++) {79 chunks.set(i, r.dataChunks[i]!);80 }81 82 const keep = r.parityChunks.length - eraseParityCount;83 for (let i = 0; i < keep; i++) {84 chunks.set(r.dataShards + i, r.parityChunks[i]!);85 }86 return { chunks, dataShards: r.dataShards, totalChunks: r.totalChunks };87}888990919293describe("T-017: Codex multi-stripe encode/reconstruct", () => {94 95 96 97 98 99 it("T-017a: payload > one-stripe capacity → encodeIntoStripes returns 2+ stripes", async () => {100 101 const hugeText = Array.from({ length: 250 * 1024 }, (_, i) => String.fromCharCode(33 + (i % 90))).join("");102 const compressed = deflate(hugeText);103104 const result = await encodeIntoStripes(compressed);105106 expect(result.stripes.length).toBeGreaterThanOrEqual(2);107 expect(result.totalDataChunks).toBeGreaterThan(MAX_DATA_SHARDS_PER_STRIPE);108 for (const stripe of result.stripes) {109 110 expect(stripe.dataShards + stripe.parityShards).toBeLessThanOrEqual(255);111 112 expect(stripe.dataShards).toBeLessThanOrEqual(MAX_DATA_SHARDS_PER_STRIPE);113 }114 });115116 117 118 119 120 121 122 123 124 it("T-017b: 2-stripe round-trip → reconstructed payload matches original", async () => {125 126 127 const { json } = makeCodexPayload(110);128 const compressed = deflate(json);129130 const encoded = await encodeIntoStripes(compressed);131 expect(encoded.stripes.length).toBeGreaterThanOrEqual(2);132133 const stripeChunks: StripeChunks[] = encoded.stripes.map(encodeResultToStripeChunks);134135 const restored = await reconstructPayloadFromStripes(stripeChunks);136 expect(JSON.stringify(restored)).toBe(json);137 });138139 140 141 142 143 144 145 146 147 it("T-017c: 2-stripe payload with 1 parity shard erased per stripe → reconstruction succeeds", async () => {148 const { json } = makeCodexPayload(110);149 const compressed = deflate(json);150 const encoded = await encodeIntoStripes(compressed);151 expect(encoded.stripes.length).toBeGreaterThanOrEqual(2);152153 154 const stripeChunks: StripeChunks[] = encoded.stripes.map((r) => {155 const parityBudget = computeParityShards(r.dataShards);156 157 if (parityBudget < 1) return encodeResultToStripeChunks(r); 158 return buildStripeChunksWithErasures(r, 1);159 });160161 const restored = await reconstructPayloadFromStripes(stripeChunks);162 expect(JSON.stringify(restored)).toBe(json);163 });164165 166 167 it("T-017d: empty stripes array → throws INSUFFICIENT_CHUNKS", async () => {168 await expect(reconstructPayloadFromStripes([])).rejects.toThrow(/INSUFFICIENT_CHUNKS/i);169 });170171 172 173 it("T-017e: single-stripe small payload round-trips through reconstructPayloadFromStripes", async () => {174 const { json } = makeCodexPayload(1); 175 const compressed = deflate(json);176 const encoded = await encodeIntoStripes(compressed);177 expect(encoded.stripes.length).toBe(1);178179 const stripeChunks: StripeChunks[] = encoded.stripes.map(encodeResultToStripeChunks);180 const restored = await reconstructPayloadFromStripes(stripeChunks);181 expect(JSON.stringify(restored)).toBe(json);182 });183});