OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git-v3 / test / read-old-orgs.test.js

106 lines · javascript ref:
// readOldOrgs (#51): reads org:* records from a legacy single-orgs
// autobase view. The fixture builds a REAL autobase (mirroring hyper-rooms'
// open/apply) on a fresh tmp store — no prod data, no copy, so no
// hypercore-storage device-file guard.

import test from 'brittle'
import Corestore from 'corestore'
import Autobase from 'autobase'
import Hyperbee from 'hyperbee'
import { mkdtempSync } from '#node:fs'
import { join } from '#node:path'
import { tmpdir } from '#node:os'

import { readOldOrgs } from '../lib/services/migrate/read-old-orgs.js'

// A minimal single-`orgs` autobase: append create-org ops, the apply puts
// `org:{name}` into the Hyperbee view — the same view shape the old
// HyperOrgs (single-autobase) produced.
async function makeOldOrgsBase (t) {
  const store = new Corestore(mkdtempSync(join(tmpdir(), 'old-orgs-')))
  const base = new Autobase(store, null, {
    valueEncoding: 'json',
    open (viewStore) {
      return new Hyperbee(viewStore.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)
        else if (op && op.type === 'put-raw') await view.put(op.key, op.value) // non-org view entries (writer:/grant:)
      }
    }
  })
  await base.ready()
  t.teardown(async () => {
    try { await base.close() } catch {}
    try { await store.close() } catch {}
  })
  return base
}

async function collect (gen) {
  const out = []
  for await (const x of gen) out.push(x)
  return out
}

test('readOldOrgs streams every org record from the single-orgs view', async (t) => {
  const base = await makeOldOrgsBase(t)
  const ORGS = [
    { name: 'alpha', displayName: 'Alpha', type: 'group', members: [{ userId: 'a', role: 'owner' }, { userId: 'b', role: 'contributor' }], resources: ['repo-1', 'repo-2'], visibility: 'public' },
    { name: 'beta', type: 'group', members: [{ userId: 'c', role: 'owner' }], resources: [] },
    { name: 'gamma', type: 'group', members: [], resources: ['repo-3'] }
  ]
  for (const org of ORGS) await base.append({ type: 'create-org', org })
  await base.update()

  const read = await collect(readOldOrgs(base))
  t.is(read.length, 3, 'all three orgs read back')
  t.alike(read.map(o => o.name).sort(), ['alpha', 'beta', 'gamma'])
  const alpha = read.find(o => o.name === 'alpha')
  t.alike(alpha.resources, ['repo-1', 'repo-2'], 'resources preserved')
  t.is(alpha.members.length, 2, 'members preserved')
})

test('readOldOrgs only yields org:* keys (ignores other view entries)', async (t) => {
  const base = await makeOldOrgsBase(t)
  await base.append({ type: 'create-org', org: { name: 'alpha', members: [], resources: [] } })
  // writer:/grant: entries live in the same view in the real old format —
  // written through apply (the view isn't directly writable).
  await base.append({ type: 'put-raw', key: 'writer:someuser', value: { role: 'owner' } })
  await base.append({ type: 'put-raw', key: 'grant:xyz', value: { permission: 'admin' } })
  await base.update()

  const read = await collect(readOldOrgs(base))
  t.alike(read.map(o => o.name), ['alpha'], 'only the org record, not writer:/grant:')
})

test('readOldOrgs on an empty orgs autobase yields nothing', async (t) => {
  const base = await makeOldOrgsBase(t)
  const read = await collect(readOldOrgs(base))
  t.alike(read, [], 'no orgs → empty')
})

test('readOldOrgs feeds migrateOrgs end-to-end (reader → transform)', async (t) => {
  const { migrateOrgs } = await import('../lib/services/migrate/migrate-orgs.js')
  const base = await makeOldOrgsBase(t)
  for (const org of [
    { name: 'alpha', type: 'group', members: [{ userId: 'a', role: 'owner' }], resources: ['r1'] },
    { name: 'beta', type: 'group', members: [], resources: [] }
  ]) await base.append({ type: 'create-org', org })
  await base.update()

  const created = []
  const summary = await migrateOrgs({
    oldOrgs: readOldOrgs(base),          // the reader as the async-iterable source
    create: async (o) => created.push(o.name),
    addMember: async () => {},
    addResource: async () => {}
  })
  t.is(summary.orgs, 2, 'reader → migrateOrgs migrated both orgs')
  t.alike(created.sort(), ['alpha', 'beta'])
})