// A corrupt tree-meta file must not take the store reader down.
//
// Regression: getMeta guarded a MISSING file but not an EMPTY one, and JSON.parse('') throws
// "Unexpected end of JSON input". ONE zero-byte meta file on stortron therefore broke
// `pgh store show` and `pgh store pin` for EVERY hash — the reader throws before it can even say
// which file is at fault. hyper-social #24 recorded the consequence: rollback pins could not be
// taken on that host, which is exactly the safety net you want before a risky deploy.
//
// Meta is a CACHE (pulledAt/source/pinned/schema), never the tree, so degrading to "no meta" is
// always safe. _readRefs in the same class already tolerated this; getMeta was the outlier.
import test from 'brittle'
import fs from '#node:fs'
import os from '#node:os'
import path from '#node:path'
import { TreeStore } from '../lib/store.js'
const tmp = () => fs.mkdtempSync(path.join(os.tmpdir(), 'pgh-meta-'))
const HASH = 'a'.repeat(64)
function storeWith (metaContent) {
const root = tmp()
const s = new TreeStore(root)
s.init()
if (metaContent !== null) fs.writeFileSync(s.metaPath(HASH), metaContent)
return s
}
test('an EMPTY meta file reads as no-meta rather than throwing', (t) => {
const s = storeWith('')
t.is(s.getMeta(HASH), null, 'the exact stortron case: zero bytes')
})
test('a TRUNCATED meta file reads as no-meta', (t) => {
const s = storeWith('{"pulledAt": 17')
t.is(s.getMeta(HASH), null, 'a half-written file (crash mid-write) does not throw')
})
test('whitespace-only reads as no-meta', (t) => {
t.is(storeWith(' \n ').getMeta(HASH), null)
})
test('non-object JSON reads as no-meta', (t) => {
t.is(storeWith('42').getMeta(HASH), null, 'valid JSON that is not a meta object is still no-meta')
t.is(storeWith('null').getMeta(HASH), null)
})
test('a MISSING meta file still reads as no-meta (unchanged)', (t) => {
t.is(storeWith(null).getMeta(HASH), null)
})
test('a VALID meta file still round-trips — the guard must not swallow good data', (t) => {
const s = storeWith(JSON.stringify({ pulledAt: 123, source: 'x', pinned: true }))
const m = s.getMeta(HASH)
t.is(m.pulledAt, 123)
t.is(m.pinned, true, 'pinned survives')
})
// The payoff: pin() already tolerates a null meta, so a corrupt entry HEALS on the next pin
// instead of needing a hand-edit on the box.
test('pin() repairs a corrupt meta rather than failing', (t) => {
const s = storeWith('')
s.pin(HASH)
const m = s.getMeta(HASH)
t.ok(m, 'meta is readable again')
t.is(m.pinned, true, 'and the pin actually took — this is what #24 could not do')
})