// A path-keyed chunk map that no longer describes the file must not be served.
//
// 2026-10-11, tabertron serving hyper-tps texture packs: the stored map for
// `+0med_but1_normal.png` was ONE 191-byte chunk — the git POINTER the path held
// when it was first served — while the file is the real 6196-byte PNG with the
// same mtime. prepareFile trusted it (mtime matched), _onChunkNeed served by it,
// and stortron asked for chunk 0 forever: the bytes never matched the hash, and
// ~2.6k receives stayed open. Maps < 1 MB were never rewritten.
import test from 'brittle'
import { tmpStore, tmpDir, fs, path } from './helpers.js'
import { FileIndex } from '../lib/file-index.js'
import { TransferManager } from '../lib/transfer.js'
import { hashChunk } from '../lib/chunker.js'
test('prepareFile — a cached map whose chunks do not cover the file is rebuilt and replaced', async (t) => {
const index = new FileIndex(tmpStore('stale-map'))
await index.ready()
const transfer = new TransferManager(index)
const pointer = Buffer.from('hyper-overlay v1\noid blake2b:' + 'a'.repeat(64) + '\nsize 6196\n' + 'x'.repeat(91))
const real = Buffer.alloc(6196, 7)
const dir = tmpDir('stale-map')
const filePath = path.join(dir, 'tex.png')
fs.writeFileSync(filePath, real)
const st = fs.statSync(filePath)
// The state tabertron was in: stale pointer-sized map, metadata of the real file.
await index.putChunkMap('/tex.png', [{ hash: hashChunk(pointer), offset: 0, length: pointer.length }])
await index.putFile('/tex.png', { contentHash: 'b'.repeat(64), size: real.length, mtime: st.mtimeMs })
const res = await transfer.prepareFile(filePath, '/tex.png')
const total = res.chunks.reduce((n, c) => n + c.length, 0)
t.is(total, real.length, 'served map covers the whole file')
t.is(res.chunks[0].hash, hashChunk(real.subarray(0, res.chunks[0].length)), 'chunk hash is of the real bytes')
const stored = await index.getChunkMap('/tex.png')
t.alike(stored, res.chunks, 'the path-keyed map (what _onChunkNeed serves by) is replaced')
})
test('prepareFile — a cached map that does cover the file is still reused', async (t) => {
const index = new FileIndex(tmpStore('good-map'))
await index.ready()
const transfer = new TransferManager(index)
const dir = tmpDir('good-map')
const filePath = path.join(dir, 'f.bin')
fs.writeFileSync(filePath, Buffer.alloc(3000, 1))
const first = await transfer.prepareFile(filePath, '/f.bin')
await index.putChunkMap('/f.bin', first.chunks)
const marker = first.chunks.map(c => ({ ...c }))
marker[0].hash = 'c'.repeat(64) // same lengths, different hash: proves the cache path was taken
await index.putChunkMap('/f.bin', marker)
const again = await transfer.prepareFile(filePath, '/f.bin')
t.is(again.chunks[0].hash, 'c'.repeat(64), 'cache hit when the map covers the file size')
})