OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git-v3 / test / repo-follow-payload-rpc.test.js

147 lines · javascript ref:
// repo-follow-payload-rpc.test.js — proves the source-side RPC emits
// a payload that decodes back to the source's tracker keyHex + identity.
// Also exercises the auth gate: HTTP transport refused.
//
// Mocks svc directly — no daemon spin-up needed.

import test from 'brittle'
import Corestore from 'corestore'
import { mkdtempSync } from '#node:fs'
import { tmpdir } from '#node:os'
import { join } from '#node:path'
import crypto from 'hypercore-crypto'
import b4a from 'b4a'

import { IssueRegistry } from '../lib/issue-registry.js'
import { decode, decodeB64 } from '../lib/services/follow-payload.js'
import { repoHandlers } from '../lib/handlers/repos.js'

function makeStore () {
  return new Corestore(mkdtempSync(join(tmpdir(), 'pgv3-rfp-')))
}

function findHandler (handlers, name) {
  return handlers.find((h) => h.name === name)
}

async function makeMockSvc (t, repoName, opts = {}) {
  const store = makeStore()
  const issueRegistry = new IssueRegistry(store)
  if (repoName) await issueRegistry.getForRepo(repoName) // open so tracker has a keyHex

  const identityKp = crypto.keyPair()
  const repoEventLogKey = b4a.toString(crypto.randomBytes(32), 'hex')

  const svc = {
    identity: {
      publicKey: identityKp.publicKey,
      publicKeyHex: b4a.toString(identityKp.publicKey, 'hex')
    },
    bootstrapKeys: { repoEventLog: repoEventLogKey },
    issueRegistry,
    _sync: {
      info (name) {
        if (opts.unknownRepo === name) return null
        if (name === repoName) {
          return { name, branchCount: 1, head: 'abc123', refs: {} }
        }
        return null
      }
    }
  }

  t.teardown(async () => {
    try { await issueRegistry.close() } catch {}
    try { await store.close() } catch {}
  })
  return { svc, identityKp, repoEventLogKey }
}

test('get-repo returns punchUrl built from identity + name', async (t) => {
  const { svc } = await makeMockSvc(t, 'foo')
  const handlers = repoHandlers(svc)
  const h = findHandler(handlers, 'get-repo')
  t.ok(h, 'get-repo registered')

  const out = await h.fn({ transport: 'uds' }, { name: 'foo' })
  t.is(out.name, 'foo')
  t.is(out.punchUrl, 'punch://' + svc.identity.publicKeyHex + '/foo')
  t.ok(out.info, 'includes info block')
})

test('get-repo missing repo throws', async (t) => {
  const { svc } = await makeMockSvc(t, 'foo', { unknownRepo: 'foo' })
  const h = findHandler(repoHandlers(svc), 'get-repo')
  await t.exception(h.fn({ transport: 'uds' }, { name: 'foo' }), /repo not found/)
})

test('repo-follow-payload over UDS returns decodable payload + URL', async (t) => {
  const { svc } = await makeMockSvc(t, 'bar')
  const h = findHandler(repoHandlers(svc), 'repo-follow-payload')
  t.ok(h, 'repo-follow-payload registered')

  const out = await h.fn({ transport: 'uds' }, { name: 'bar' })

  t.is(out.name, 'bar')
  t.is(out.sourceServerKey, svc.identity.publicKeyHex)
  t.ok(out.payload, 'payload b64 present')
  t.ok(out.url.startsWith('punch+follow://'), 'URL is punch+follow scheme')

  // Decode and verify contents
  const decoded = decodeB64(out.payload)
  t.is(decoded.kind, 'repo')
  t.is(decoded.sourceServerKey, svc.identity.publicKeyHex)
  t.is(decoded.name, 'bar')
  t.ok(decoded.issueBootstrapKey, 'tracker key in payload')
  t.is(decoded.issueBootstrapKey, out.issueBootstrapKey, 'tracker key matches RPC return')
  t.is(decoded.repoEventLogKey, svc.bootstrapKeys.repoEventLog)
})

test('repo-follow-payload over HTTP throws EAUTH', async (t) => {
  const { svc } = await makeMockSvc(t, 'baz')
  const h = findHandler(repoHandlers(svc), 'repo-follow-payload')
  try {
    await h.fn({ transport: 'http' }, { name: 'baz' })
    t.fail('should have thrown')
  } catch (err) {
    t.is(err.code, 'EAUTH')
    t.is(err.status, 403)
  }
})

test('repo-follow-payload over swarm works (matches dump-keys model)', async (t) => {
  const { svc } = await makeMockSvc(t, 'qux')
  const h = findHandler(repoHandlers(svc), 'repo-follow-payload')
  const out = await h.fn({ transport: 'swarm' }, { name: 'qux' })
  t.is(out.name, 'qux')
})

test('repo-follow-payload without tracker still emits payload (key=null)', async (t) => {
  // Build an svc whose issueRegistry refuses to open (strict bootstrap).
  const store = makeStore()
  const issueRegistry = new IssueRegistry(store, { requireBootstrap: true })

  const identityKp = crypto.keyPair()
  const svc = {
    identity: {
      publicKey: identityKp.publicKey,
      publicKeyHex: b4a.toString(identityKp.publicKey, 'hex')
    },
    bootstrapKeys: { repoEventLog: null },
    issueRegistry,
    _sync: { info: () => ({ name: 'no-tracker' }) }
  }
  t.teardown(async () => {
    try { await issueRegistry.close() } catch {}
    try { await store.close() } catch {}
  })

  const h = findHandler(repoHandlers(svc), 'repo-follow-payload')
  const out = await h.fn({ transport: 'uds' }, { name: 'no-tracker' })
  t.is(out.issueBootstrapKey, null, 'tracker absent OK')
  t.is(out.repoEventLogKey, null, 'event log absent OK')
  const decoded = decode(b4a.from(out.payload, 'base64url'))
  t.is(decoded.kind, 'repo')
  t.is(decoded.name, 'no-tracker')
})