OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-orgs / test / org-pairing.test.js

116 lines · javascript ref:
// Org join + write-enrollment via blind-pairing over Hyperswarm (the P2P
// replacement for the bearer-invite + owner-enroll flow). Supersedes the
// old in-memory tests (scope-isolation / multi-org-join / writer-vs-reader /
// enroll-from-invite) which exercised the now-removed bearer+enroll API.
//
// Proves, over a real testnet:
//   - contributor: pairs → becomes a writer → its writes replicate to owner
//   - reader: pairs → read-only mirror (sees owner data, NOT a writer)
//   - scope isolation: a joiner that joined ONLY alpha never sees beta
//   - multi-org: a joiner can join two orgs and write to both

import test from 'brittle'
import Corestore from 'corestore'
import Hyperswarm from 'hyperswarm'
import createTestnet from '@hyperswarm/testnet'
import { mkdtempSync } from 'fs'
import { join } from 'path'
import { tmpdir } from 'os'

import { HyperOrgs } from '../index.js'

let n = 0
const tmp = () => mkdtempSync(join(tmpdir(), 'op-' + (n++) + '-'))

async function waitFor (pred, ms = 25000) {
  const t0 = Date.now()
  while (Date.now() - t0 < ms) {
    try { if (await pred()) return true } catch {}
    await new Promise((r) => setTimeout(r, 100))
  }
  return false
}

async function makeOrgs (t, bootstrap) {
  const store = new Corestore(tmp())
  const swarm = new Hyperswarm({ bootstrap })
  swarm.on('connection', (conn) => store.replicate(conn))
  const orgs = new HyperOrgs(store)
  await orgs.ready()
  t.teardown(async () => { try { await orgs.close() } catch {}; try { await swarm.destroy() } catch {} })
  return { orgs, swarm }
}

test('contributor pairs into an org and becomes a writer (P2P, no HTTP)', async (t) => {
  const testnet = await createTestnet(3, t.teardown)
  const A = await makeOrgs(t, testnet.bootstrap)
  const B = await makeOrgs(t, testnet.bootstrap)

  await A.orgs.create('alpha', 'group', { displayName: 'Alpha' })
  await A.orgs.create('beta', 'group', { displayName: 'Beta' })

  const { invitation } = await A.orgs.createOrgInvite('alpha', { role: 'contributor', swarm: A.swarm })
  const joined = await B.orgs.joinOrg(invitation, { swarm: B.swarm })
  t.is(joined.name, 'alpha', 'joined the named org')

  const became = await waitFor(() => { const r = B.orgs.getOrgRoom('alpha'); return r && r.writable })
  t.ok(became, 'contributor became a writer via pairing (over the swarm)')

  // Joiner writes; owner sees it.
  await B.orgs.addResource('alpha', 'repo:from-joiner')
  const seen = await waitFor(async () => {
    const org = await A.orgs.get('alpha')
    return !!(org && Array.isArray(org.resources) && org.resources.includes('repo:from-joiner'))
  })
  t.ok(seen, 'owner sees the joiner-authored write (bidirectional)')

  // Scope isolation: B joined ONLY alpha — beta never replicates to it.
  t.absent(B.orgs.getOrgRoom('beta'), 'joiner has NOT mounted beta (scope-isolated)')
  const beta = await B.orgs.get('beta').catch(() => null)
  t.ok(!beta, 'joiner cannot see beta (only the invited org replicates)')
})

test('reader pairs into an org and stays a read-only mirror', async (t) => {
  const testnet = await createTestnet(3, t.teardown)
  const A = await makeOrgs(t, testnet.bootstrap)
  const B = await makeOrgs(t, testnet.bootstrap)

  await A.orgs.create('proj', 'group', { displayName: 'Proj' })
  await A.orgs.addResource('proj', 'repo:owner-added')

  const { invitation } = await A.orgs.createOrgInvite('proj', { role: 'reader', swarm: A.swarm })
  const joined = await B.orgs.joinOrg(invitation, { swarm: B.swarm })
  t.is(joined.role, 'reader', 'role echoed as reader')

  // Reader replicates (reads) the org...
  const sawRead = await waitFor(async () => {
    const org = await B.orgs.get('proj')
    return !!(org && Array.isArray(org.resources) && org.resources.includes('repo:owner-added'))
  })
  t.ok(sawRead, 'reader replicates the org read-only')

  // ...but is NOT a writer.
  await new Promise((r) => setTimeout(r, 1500))
  t.absent(B.orgs.getOrgRoom('proj').writable, 'reader is NOT enrolled as a writer')
})

test('a joiner can pair into TWO orgs and write to both', async (t) => {
  const testnet = await createTestnet(3, t.teardown)
  const A = await makeOrgs(t, testnet.bootstrap)
  const B = await makeOrgs(t, testnet.bootstrap)

  await A.orgs.create('one', 'group', { displayName: 'One' })
  await A.orgs.create('two', 'group', { displayName: 'Two' })

  const i1 = (await A.orgs.createOrgInvite('one', { role: 'contributor', swarm: A.swarm })).invitation
  const i2 = (await A.orgs.createOrgInvite('two', { role: 'contributor', swarm: A.swarm })).invitation
  await B.orgs.joinOrg(i1, { swarm: B.swarm })
  await B.orgs.joinOrg(i2, { swarm: B.swarm })

  const both = await waitFor(() => {
    const r1 = B.orgs.getOrgRoom('one'); const r2 = B.orgs.getOrgRoom('two')
    return r1 && r1.writable && r2 && r2.writable
  })
  t.ok(both, 'joiner is a writer on both orgs')
})