import test from 'brittle'
import { serialize, parse, isPointer, INLINE_MAX } from '../lib/pointer.js'
test('serialize + parse round-trip (small file, inline chunks)', (t) => {
const info = {
oid: 'a'.repeat(64),
size: 32768,
chunkCount: 3,
chunks: [
{ hash: 'b'.repeat(64), size: 12288 },
{ hash: 'c'.repeat(64), size: 15360 },
{ hash: 'd'.repeat(64), size: 5120 }
]
}
const text = serialize(info)
const parsed = parse(text)
t.is(parsed.version, 1)
t.is(parsed.oid, info.oid)
t.is(parsed.size, info.size)
t.is(parsed.chunkCount, 3)
t.is(parsed.chunks.length, 3)
t.is(parsed.chunks[0].hash, 'b'.repeat(64))
t.is(parsed.chunks[0].size, 12288)
t.is(parsed.chunks[2].hash, 'd'.repeat(64))
t.is(parsed.manifestHash, null)
})
test('serialize + parse round-trip (large file, manifest reference)', (t) => {
const info = {
oid: 'e'.repeat(64),
size: 4294967296, // 4GB
chunkCount: 262144,
manifestHash: 'f'.repeat(64)
}
const text = serialize(info)
const parsed = parse(text)
t.is(parsed.oid, info.oid)
t.is(parsed.size, info.size)
t.is(parsed.chunkCount, 262144)
t.is(parsed.manifestHash, 'f'.repeat(64))
t.is(parsed.chunks, null)
})
test('isPointer detects valid pointers', (t) => {
const text = serialize({
oid: 'a'.repeat(64),
size: 100,
chunkCount: 1,
chunks: [{ hash: 'b'.repeat(64), size: 100 }]
})
t.is(isPointer(text), true)
t.is(isPointer(Buffer.from(text)), true)
})
test('isPointer rejects non-pointer content', (t) => {
t.is(isPointer('hello world'), false)
t.is(isPointer('version https://git-lfs.github.com/spec/v1'), false)
t.is(isPointer(''), false)
t.is(isPointer(Buffer.from([0x89, 0x50, 0x4e, 0x47])), false) // PNG header
})
test('parse rejects malformed pointer', (t) => {
try {
parse('not a pointer')
t.fail('should throw')
} catch (err) {
t.ok(err.message.includes('Invalid'))
}
})
test('INLINE_MAX is 64', (t) => {
t.is(INLINE_MAX, 64)
})
test('serialize uses inline for exactly 64 chunks', (t) => {
const chunks = Array.from({ length: 64 }, (_, i) => ({
hash: i.toString(16).padStart(64, '0'),
size: 16384
}))
const text = serialize({
oid: 'a'.repeat(64),
size: 64 * 16384,
chunkCount: 64,
chunks
})
t.ok(text.includes('blake2b:'), 'inline chunks present')
t.ok(!text.includes('manifest'), 'no manifest reference')
})
test('serialize uses manifest for 65+ chunks', (t) => {
const text = serialize({
oid: 'a'.repeat(64),
size: 65 * 16384,
chunkCount: 65,
manifestHash: 'b'.repeat(64)
})
t.ok(text.includes('manifest'), 'manifest reference present')
// Only the manifest line contains blake2b — no per-chunk lines
const lines = text.trim().split('\n')
const chunkLines = lines.filter(l => l.match(/^blake2b:[0-9a-f]+ \d+$/))
t.is(chunkLines.length, 0, 'no inline chunk lines')
})
test('parse accepts legacy 3-line pointer (header + oid + size, no chunks)', (t) => {
// Older serializers omitted the `chunks N` line. Existing repos still
// contain pointers in this shape; sync paths only need oid+size to
// fetch by content hash, so parse must accept them.
const legacy = 'hyper-overlay v1\n' +
'oid blake2b:' + 'a'.repeat(64) + '\n' +
'size 4096\n'
const parsed = parse(legacy)
t.is(parsed.version, 1)
t.is(parsed.oid, 'a'.repeat(64))
t.is(parsed.size, 4096)
t.is(parsed.chunkCount, 0, 'chunkCount defaults to 0 for legacy pointers')
t.is(parsed.chunks, null)
t.is(parsed.manifestHash, null)
})