streaming 7-Zip archive extractor
1import { assertEquals, assertThrows } from '@std/assert';
2
3import { MalformedArchiveError, TruncatedArchiveError } from '../lib/errors.ts';
4import { Cursor, readAllOrBoolVector, readBoolVector } from '../lib/utils/cursor.ts';
5
6Deno.test('Cursor: seek rejects negative offsets', () => {
7 const c = new Cursor(new Uint8Array(16));
8 assertThrows(() => c.seek(-1), MalformedArchiveError);
9});
10
11Deno.test('Cursor: seek rejects offsets past end', () => {
12 const c = new Cursor(new Uint8Array(16));
13 assertThrows(() => c.seek(17), MalformedArchiveError);
14});
15
16Deno.test('Cursor: skip past end throws TruncatedArchiveError', () => {
17 const c = new Cursor(new Uint8Array(4));
18 assertThrows(() => c.skip(5), TruncatedArchiveError);
19});
20
21Deno.test('Cursor: readU8 / peekU8 / position track movement', () => {
22 const c = new Cursor(new Uint8Array([0x10, 0x20, 0x30]));
23 assertEquals(c.peekU8(), 0x10);
24 assertEquals(c.position, 0);
25 assertEquals(c.readU8(), 0x10);
26 assertEquals(c.position, 1);
27 assertEquals(c.readU8(), 0x20);
28 assertEquals(c.remaining, 1);
29});
30
31Deno.test('Cursor: readU32LE / readU64LE round-trip a known little-endian payload', () => {
32 const bytes = new Uint8Array([0x78, 0x56, 0x34, 0x12, 0xef, 0xcd, 0xab, 0x90, 0x78, 0x56, 0x34, 0x12]);
33 const c = new Cursor(bytes);
34 assertEquals(c.readU32LE(), 0x12345678);
35 assertEquals(c.readU64LE(), 0x1234567890abcdefn);
36});
37
38Deno.test('Cursor: readVarInt decodes a short value', () => {
39 // 0x05 = 0000_0101 → high bit clear, value = 5.
40 const c = new Cursor(new Uint8Array([0x05]));
41 assertEquals(c.readVarInt(), 5);
42});
43
44Deno.test('Cursor: readVarInt decodes a 2-byte value', () => {
45 // 0x80 marker = "1 follow-up byte", tail = 0, follow-up 0x80 → 128.
46 const c = new Cursor(new Uint8Array([0x80, 0x80]));
47 assertEquals(c.readVarInt(), 128);
48});
49
50Deno.test('Cursor: readVarInt rejects values beyond MAX_SAFE_INTEGER', () => {
51 // 0xff prefix = "8 follow-up bytes, no tail bits"; eight 0xff bytes ≈ 2^64-1.
52 const c = new Cursor(new Uint8Array([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]));
53 assertThrows(() => c.readVarInt(), MalformedArchiveError);
54});
55
56Deno.test('Cursor: readBytes returns a fresh copy independent of the source', () => {
57 const src = new Uint8Array([1, 2, 3, 4]);
58 const c = new Cursor(src);
59 const copy = c.readBytes(2);
60 copy[0] = 0xff;
61 assertEquals(src[0], 1); // source untouched
62});
63
64Deno.test('Cursor: view returns a zero-copy alias into the source', () => {
65 const src = new Uint8Array([1, 2, 3, 4]);
66 const c = new Cursor(src);
67 const v = c.view(2);
68 v[0] = 0xff;
69 assertEquals(src[0], 0xff); // confirms aliasing
70});
71
72Deno.test('readBoolVector: unpacks MSB-first bits across multiple bytes', () => {
73 // 0b1010_1010 then 0b1100_0000 → MSB-first 12-bit run
74 const c = new Cursor(new Uint8Array([0b1010_1010, 0b1100_0000]));
75 assertEquals(readBoolVector(c, 12), [
76 true,
77 false,
78 true,
79 false,
80 true,
81 false,
82 true,
83 false,
84 true,
85 true,
86 false,
87 false,
88 ]);
89});
90
91Deno.test('readAllOrBoolVector: leading non-zero byte expands to all true', () => {
92 const c = new Cursor(new Uint8Array([0x01]));
93 assertEquals(readAllOrBoolVector(c, 3), [true, true, true]);
94});
95
96Deno.test('readAllOrBoolVector: leading zero byte falls through to readBoolVector', () => {
97 const c = new Cursor(new Uint8Array([0x00, 0b1010_0000]));
98 assertEquals(readAllOrBoolVector(c, 3), [true, false, true]);
99});