// normalizeMembers — the roster shape rule, at the boundary that consumes it.
//
// WHY THIS IS A TEST AND NOT A COMMENT. `members` is written to config.json in three shapes:
// [{key,role}], an array of hex strings (what coownerJoin writes), and a "hex,hex" comma-string (what
// wj62 carries in production). Downstream, projectMembers (atproto-room/lib/state-base.js) reads
// `m.key` — so the two string shapes yield undefined for every entry, are skipped, and the roster
// arrives EMPTY. An empty roster is not a loud failure: the room quietly refuses to project it
// ("empty roster would wipe N existing member(s)"), so a device stays unlisted while its own config
// plainly names members. That is a whole evening of debugging, and it has now happened three times in
// this codebase (coowner.js documents its own instance).
//
// So bootSwarmDevice normalises what it is HANDED rather than trusting each caller to have used
// loadConfig. These assertions pin that rule.
//
// Run: bare test/roster-normalize.test.mjs
import { normalizeMembers } from '../lib/config.js'
const assert = (c, m) => { if (!c) { console.error('FAIL:', m); process.exit(1) } console.log(' ok -', m) }
const A = 'a'.repeat(64)
const B = 'b'.repeat(64)
const keys = (r) => (r || []).map((m) => m.key)
console.log('the three shapes all reach one form')
assert(JSON.stringify(normalizeMembers([{ key: A, role: 1 }])) === JSON.stringify([{ key: A, role: 1 }]),
'already-normalised input is returned unchanged — idempotent, so every fleet node is unaffected')
assert(JSON.stringify(keys(normalizeMembers([A, B]))) === JSON.stringify([A, B]),
'ARRAY OF HEX STRINGS — what coownerJoin writes, and what silently emptied the phone roster')
assert(JSON.stringify(keys(normalizeMembers(A + ',' + B))) === JSON.stringify([A, B]),
'COMMA-STRING — the shape wj62 carries in production')
assert(normalizeMembers([{ key: A }])[0].role === 1, 'role defaults to 1 (all devices role-equal)')
assert(normalizeMembers([A.toUpperCase()])[0].key === A, 'normalised to lower case, so key comparisons hold')
assert(normalizeMembers([' ' + A + ' '])[0].key === A, 'entries are trimmed')
console.log('absence stays absence — never an empty roster')
// null (not []) matters: bootSwarmDevice falls back to [{key:self,role:1}] on a falsy roster, and an
// empty ARRAY would instead project "no members", which is the wipe the room has to defend against.
assert(normalizeMembers(null) === null, 'null in, null out')
assert(normalizeMembers([]) === null, 'an empty array yields null, NOT an empty roster')
assert(normalizeMembers('') === null, 'an empty string yields null')
assert(normalizeMembers(['', ' ']) === null, 'entries that are all blank yield null, not [{key:""}]')
console.log('\nall roster-normalize tests passed')