// 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')
})