1/**
2 * `inflateEntry` (src/Git/Protocol/Zlib.ts): the exact-span inflate the pack
3 * parser runs per entry. Pack entries are back-to-back zlib streams with no
4 * length prefix, so the consumed-input count is as important as the output.
5 */
6import { inflateEntry } from "@/Git/Protocol/Zlib.ts";
7import { describe, expect, test } from "alchemy-test";
8import * as Effect from "effect/Effect";
9import zlib from "node:zlib";
10
11const content = (n: number, seed: number) => {
12 const out = new Uint8Array(n);
13 for (let i = 0; i < n; i++) out[i] = (i * 31 + seed * 7) & 0xff;
14 return out;
15};
16
17describe("inflateEntry", { tags: ["unit", "local"] }, () => {
18 test("returns the content and the exact compressed span, ignoring trailing bytes", async () => {
19 const a = content(5000, 1);
20 const b = content(300, 2);
21 const za = new Uint8Array(zlib.deflateSync(a));
22 const zb = new Uint8Array(zlib.deflateSync(b));
23 const pack = new Uint8Array(za.length + zb.length + 3);
24 pack.set(za, 0);
25 pack.set(zb, za.length);
26 pack.set([9, 9, 9], za.length + zb.length);
27 const first = await Effect.runPromise(
28 inflateEntry(pack, 0, { expectedSize: a.length }),
29 );
30 expect(first.bytesConsumed).toBe(za.length);
31 expect(Array.from(first.content)).toEqual(Array.from(a));
32 const second = await Effect.runPromise(
33 inflateEntry(pack, first.bytesConsumed, { expectedSize: b.length }),
34 );
35 expect(second.bytesConsumed).toBe(zb.length);
36 expect(Array.from(second.content)).toEqual(Array.from(b));
37 });
38
39 test("a wrong expectedSize is not trusted from the fast path: the result is still exact", async () => {
40 const a = content(2000, 3);
41 const za = new Uint8Array(zlib.deflateSync(a));
42 const buf = new Uint8Array(za.length + 5);
43 buf.set(za, 0);
44 // The fast paths refuse (output != expectedSize) and the streaming path
45 // answers; either way the caller gets the real span and content.
46 const out = await Effect.runPromise(
47 inflateEntry(buf, 0, { expectedSize: a.length + 1 }),
48 );
49 expect(out.bytesConsumed).toBe(za.length);
50 expect(out.content.length).toBe(a.length);
51 });
52
53 test("an incompressible 1 MiB entry round-trips (multi-chunk output)", async () => {
54 const big = new Uint8Array(1 << 20);
55 crypto.getRandomValues(big.subarray(0, 65536));
56 for (let at = 65536; at < big.length; at += 65536)
57 big.copyWithin(at, 0, 65536);
58 for (let i = 0; i < big.length; i += 4099) big[i] ^= 0x5a;
59 const z = new Uint8Array(zlib.deflateSync(big));
60 const buf = new Uint8Array(z.length + 2);
61 buf.set(z, 0);
62 const out = await Effect.runPromise(
63 inflateEntry(buf, 0, { expectedSize: big.length }),
64 );
65 expect(out.bytesConsumed).toBe(z.length);
66 expect(out.content.length).toBe(big.length);
67 expect(out.content[4099]).toBe(big[4099]);
68 });
69
70 test("corrupt input fails with a typed ZlibError", async () => {
71 const junk = new Uint8Array([0x78, 0x9c, 1, 2, 3, 4, 5, 6, 7, 8]);
72 const result = await Effect.runPromise(
73 Effect.result(inflateEntry(junk, 0, { expectedSize: 10 })),
74 );
75 expect(result._tag).toBe("Failure");
76 });
77});