OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-issues / test / repo-pairing.test.js

95 lines · javascript ref:
// Repo-tracker join + write-enrollment via blind-pairing over Hyperswarm
// (the P2P replacement for the bearer invite + enrollFromInvite). Supersedes
// the in-memory repo-invite.test.js, which exercised the now-removed API.
//
// Proves, over a real testnet:
//   - contributor: pairs → becomes a writer → its issue replicates to owner
//   - reader: pairs → read-only mirror (sees owner's issues, NOT a writer)

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 { HyperIssues, joinRepoPairing } from '../index.js'

let n = 0
const tmp = () => mkdtempSync(join(tmpdir(), 'rp-' + (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
}

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

test('contributor pairs into a repo tracker and becomes a writer (P2P, no HTTP)', async (t) => {
  const testnet = await createTestnet(3, t.teardown)
  const a = makeNode(t, testnet.bootstrap)
  const b = makeNode(t, testnet.bootstrap)

  const owner = new HyperIssues(a.store, { namespace: 'myrepo' })
  await owner.ready()
  t.teardown(async () => { try { await owner.close() } catch {} })
  t.ok(owner.writable, 'owner (creator) is writable')
  await owner.create({ title: 'owner issue', author: 'owner' })

  const { invitation } = owner.createInvite({ name: 'myrepo', role: 'contributor', swarm: a.swarm })
  t.ok(invitation.startsWith('roompair1:'), 'invite code minted')

  const { tracker: joiner, role } = await joinRepoPairing(invitation, { corestore: b.store, swarm: b.swarm })
  t.teardown(async () => { try { await joiner.close() } catch {} })
  t.is(role, 'contributor', 'role echoed')
  t.is(joiner.keyHex, owner.keyHex, 'joiner mounted the SAME tracker autobase')

  const became = await waitFor(() => joiner.writable)
  t.ok(became, 'contributor became a writer via pairing — over the swarm, no HTTP')

  const id = await joiner.create({ title: 'joiner issue', author: 'joiner' })
  const seen = await waitFor(async () => {
    await owner.sync()
    const iss = await owner.get(id)
    return !!(iss && iss.title === 'joiner issue')
  })
  t.ok(seen, 'owner sees the joiner-authored issue (bidirectional)')
})

test('reader pairs into a repo tracker and stays a read-only mirror', async (t) => {
  const testnet = await createTestnet(3, t.teardown)
  const a = makeNode(t, testnet.bootstrap)
  const b = makeNode(t, testnet.bootstrap)

  const owner = new HyperIssues(a.store, { namespace: 'proj' })
  await owner.ready()
  t.teardown(async () => { try { await owner.close() } catch {} })
  const id = await owner.create({ title: 'owner-only', author: 'owner' })

  const { invitation } = owner.createInvite({ name: 'proj', role: 'reader', swarm: a.swarm })
  const { tracker: reader, role } = await joinRepoPairing(invitation, { corestore: b.store, swarm: b.swarm })
  t.teardown(async () => { try { await reader.close() } catch {} })
  t.is(role, 'reader', 'role echoed as reader')

  const sawRead = await waitFor(async () => {
    await reader.sync()
    const iss = await reader.get(id)
    return !!(iss && iss.title === 'owner-only')
  })
  t.ok(sawRead, 'reader replicates the tracker read-only')

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