import test from 'brittle'
import { safeStringify } from '../lib/host/safe-json.js'
test('ordinary payloads pass through untouched', (t) => {
const r = safeStringify({ t: 'log', stage: 'node ready', n: 1, ok: true, nil: null })
t.absent(r.degraded)
t.alike(JSON.parse(r.line), { t: 'log', stage: 'node ready', n: 1, ok: true, nil: null })
t.is(r.dropped.length, 0)
})
test('★★★ a circular payload still produces a line, and NAMES what closed the circle', (t) => {
// The handset's shape: a Bare timer reachable from a log payload.
function Scheduler () { this._timeouts = { _data: [] } }
function Timeout (s) { this._scheduler = s }
const sched = new Scheduler()
sched._timeouts._data.push(new Timeout(sched))
const r = safeStringify({ t: 'log', msg: 'ota: something', detail: sched })
t.ok(r.degraded, 'it had to degrade')
const parsed = JSON.parse(r.line)
t.is(parsed.msg, 'ota: something', 'the part that was serializable survived')
t.ok(r.dropped.some((d) => /circular/.test(d)), 'and the circle is reported rather than guessed at: ' + r.dropped.join(', '))
})
test('⛔ it never throws — that is the entire point', (t) => {
const evil = {}
Object.defineProperty(evil, 'boom', { enumerable: true, get () { throw new Error('nope') } })
const r = safeStringify({ t: 'log', evil })
t.ok(typeof r.line === 'string' && r.line.length > 0, 'a line came back')
t.ok(r.degraded)
JSON.parse(r.line) // and it is valid JSON
})
test('functions and bigints are dropped by name, not silently', (t) => {
const r = safeStringify({ t: 'log', fn () {}, big: 10n, keep: 'yes' })
t.is(JSON.parse(r.line).keep, 'yes')
t.ok(r.dropped.some((d) => /fn:function/.test(d)))
t.ok(r.dropped.some((d) => /big:bigint/.test(d)))
})
test('a root that JSON cannot represent still yields valid JSON', (t) => {
const r = safeStringify(undefined)
const parsed = JSON.parse(r.line)
t.ok(r.degraded)
t.ok(/not serializable/.test(parsed.msg))
})