// END-TO-END migration verification against PROD'S REAL orgs data.
//
// Seeds an old-format (single-autobase) orgs view with the actual records
// pulled from the running prod daemon (/tmp/prod-orgs.json), runs the REAL
// migration (runner → orgsToPerOrg = readOldOrgs → migrateOrgs → per-org
// HyperOrgs), then opens the result in the experimental per-org HyperOrgs
// and asserts parity. No prod store touched, no device-file guard.
//
// If /tmp/prod-orgs.json is absent (fetch it with:
// pgh --json --app pear-git-v3 org list > /tmp/prod-orgs.json)
// the test skips.
import test from 'brittle'
import Corestore from 'corestore'
import Autobase from 'autobase'
import Hyperbee from 'hyperbee'
import { HyperOrgs } from 'hyper-orgs'
import { mkdtempSync, readFileSync, existsSync } from '#node:fs'
import { join } from '#node:path'
import { tmpdir } from '#node:os'
import { runMigrations } from '../lib/services/migrate/runner.js'
import { orgsToPerOrg } from '../lib/services/migrate/migrations.js'
const FIXTURE = '/tmp/prod-orgs.json'
async function makeOldOrgsBase (t) {
const store = new Corestore(mkdtempSync(join(tmpdir(), 'mro-old-')))
const base = new Autobase(store, null, {
valueEncoding: 'json',
open (s) { return new Hyperbee(s.get('view', { valueEncoding: 'json' }), { keyEncoding: 'utf-8', valueEncoding: 'json' }) },
async apply (nodes, view) {
for (const node of nodes) {
const op = node.value
if (op && op.type === 'create-org') await view.put('org:' + op.org.name, op.org)
}
}
})
await base.ready()
t.teardown(async () => { try { await base.close() } catch {}; try { await store.close() } catch {} })
return base
}
test('upgrade PROD real orgs: v1 single-autobase → v2 per-org, parity verified', async (t) => {
if (!existsSync(FIXTURE)) { t.pass('skipped — ' + FIXTURE + ' absent'); return }
const prodOrgs = JSON.parse(readFileSync(FIXTURE, 'utf8'))
t.ok(prodOrgs.length > 0, 'have prod orgs to migrate (' + prodOrgs.length + ')')
// 1. seed an OLD single-orgs autobase with prod's real records
const oldBase = await makeOldOrgsBase(t)
for (const o of prodOrgs) await oldBase.append({ type: 'create-org', org: o })
await oldBase.update()
// 2. target: a fresh per-org HyperOrgs (the experimental v2 model)
const newStore = new Corestore(mkdtempSync(join(tmpdir(), 'mro-new-')))
const newOrgs = new HyperOrgs(newStore, { namespace: 'orgs' })
await newOrgs.ready()
t.teardown(async () => { try { await newOrgs.close() } catch {}; try { await newStore.close() } catch {} })
// 3. run the REAL migration through the generic runner
let version = 0
const r = await runMigrations({
getVersion: async () => version,
setVersion: async (v) => { version = v },
migrations: [orgsToPerOrg],
ctx: { oldOrgsBase: oldBase, newOrgs }
})
t.is(r.version, 2, 'store reports schema v2 after migration')
t.alike(r.ran, [2])
// 4. PARITY: the experimental per-org store has every prod org
const migrated = await newOrgs.list()
t.is(migrated.length, prodOrgs.length, 'all ' + prodOrgs.length + ' orgs present in v2 model')
t.alike(migrated.map(o => o.name).sort(), prodOrgs.map(o => o.name).sort(), 'org names match exactly')
// 5. members preserved — check the org that has them (hyper-fleeky-dev: 4)
const prodWithMembers = prodOrgs.filter(o => (o.members || []).length > 0)
for (const po of prodWithMembers) {
const mig = await newOrgs.get(po.name)
const migIds = new Set((mig.members || []).map(m => m.userId))
for (const pm of po.members) {
t.ok(migIds.has(pm.userId), po.name + ' member ' + String(pm.userId).slice(0, 10) + ' present after migration')
}
}
// 6. each org is its OWN autobase now (the #37 property) — listOrgKeys
if (typeof newOrgs.listOrgKeys === 'function') {
const keys = newOrgs.listOrgKeys()
t.is(keys.length, prodOrgs.length, 'one per-org autobase per migrated org')
}
})