OverviewFilesCommitsIssuesDocsReleasesPeers

P2P-Hiverelay / test / unit / verifier.test.js

222 lines · javascript ref:
import test from 'brittle'
import { verifyRelays, compareDrive, fetchAnchorProof } from 'p2p-hiverelay-verifier'

// We use an injected fetch so we never hit real network. The
// verifier's contract is "same input bytes → same verdict" — perfect
// for deterministic unit testing.

function mockFetch (responses) {
  return async (url) => {
    const handler = responses[url]
    if (!handler) {
      return { ok: false, status: 404, text: async () => '' }
    }
    if (typeof handler === 'function') return handler(url)
    return {
      ok: handler.ok !== false,
      status: handler.status || 200,
      text: async () => JSON.stringify(handler.body || {})
    }
  }
}

const RELAY_A = 'https://relay-a.example.com'
const RELAY_B = 'https://relay-b.example.com'

const sameCapabilityDoc = {
  schemaVersion: 1,
  software: 'https://github.com/bigdestiny2/p2p-hiverelay',
  version: '0.6.0',
  features: ['capability-doc', 'federation']
}

test('verifyRelays requires at least 2 URLs', async (t) => {
  try {
    await verifyRelays([RELAY_A], { fetch: mockFetch({}) })
    t.fail('should throw')
  } catch (err) {
    t.ok(err.message.includes('at least 2'))
  }
})

test('verifyRelays returns AGREE when both relays serve identical caps + catalogs', async (t) => {
  const fetch = mockFetch({
    [RELAY_A + '/.well-known/hiverelay.json']: { body: sameCapabilityDoc },
    [RELAY_B + '/.well-known/hiverelay.json']: { body: sameCapabilityDoc },
    [RELAY_A + '/catalog.json']: { body: { entries: [] } },
    [RELAY_B + '/catalog.json']: { body: { entries: [] } }
  })
  const report = await verifyRelays([RELAY_A, RELAY_B], { fetch })
  t.is(report.verdict, 'agree')
  t.is(report.divergenceCount, 0)
  t.is(report.fetchErrors.length, 0)
  t.ok(report.capabilitiesOK)
  t.ok(report.catalogsOK)
})

test('verifyRelays flags schemaVersion divergence', async (t) => {
  const fetch = mockFetch({
    [RELAY_A + '/.well-known/hiverelay.json']: { body: { ...sameCapabilityDoc, schemaVersion: 1 } },
    [RELAY_B + '/.well-known/hiverelay.json']: { body: { ...sameCapabilityDoc, schemaVersion: 2 } },
    [RELAY_A + '/catalog.json']: { body: { entries: [] } },
    [RELAY_B + '/catalog.json']: { body: { entries: [] } }
  })
  const report = await verifyRelays([RELAY_A, RELAY_B], { fetch })
  t.is(report.verdict, 'diverge')
  const cap = report.divergences.find(d => d.field === 'schemaVersion')
  t.ok(cap, 'schemaVersion divergence reported')
  t.is(cap.valueA, 1)
  t.is(cap.valueB, 2)
})

test('verifyRelays flags catalog entry divergence', async (t) => {
  const sharedAppKey = 'a'.repeat(64)
  const fetch = mockFetch({
    [RELAY_A + '/.well-known/hiverelay.json']: { body: sameCapabilityDoc },
    [RELAY_B + '/.well-known/hiverelay.json']: { body: sameCapabilityDoc },
    [RELAY_A + '/catalog.json']: {
      body: { entries: [{ appKey: sharedAppKey, type: 'app', publisherPubkey: 'pubA', version: '1.0.0' }] }
    },
    [RELAY_B + '/catalog.json']: {
      body: { entries: [{ appKey: sharedAppKey, type: 'app', publisherPubkey: 'pubA', version: '2.0.0' }] }
    }
  })
  const report = await verifyRelays([RELAY_A, RELAY_B], { fetch })
  t.is(report.verdict, 'diverge')
  const div = report.divergences.find(d => d.category === 'catalog-entry')
  t.ok(div)
  t.is(div.appKey, sharedAppKey)
  t.alike(div.divergentFields, ['version'])
})

test('verifyRelays handles partial relay failures gracefully', async (t) => {
  const fetch = mockFetch({
    [RELAY_A + '/.well-known/hiverelay.json']: { body: sameCapabilityDoc },
    [RELAY_B + '/.well-known/hiverelay.json']: { ok: false, status: 503 },
    [RELAY_A + '/catalog.json']: { body: { entries: [] } },
    [RELAY_B + '/catalog.json']: { ok: false, status: 503 }
  })
  const report = await verifyRelays([RELAY_A, RELAY_B], { fetch })
  t.absent(report.capabilitiesOK)
  t.absent(report.catalogsOK)
  t.is(report.fetchErrors.length, 2)
  t.is(report.fetchErrors[0].relay, RELAY_B)
})

test('verifyRelays does NOT flag entries unique to one relay', async (t) => {
  // Federation is selective — one relay may carry an app another doesn't.
  // That's not divergence, that's design.
  const fetch = mockFetch({
    [RELAY_A + '/.well-known/hiverelay.json']: { body: sameCapabilityDoc },
    [RELAY_B + '/.well-known/hiverelay.json']: { body: sameCapabilityDoc },
    [RELAY_A + '/catalog.json']: { body: { entries: [{ appKey: 'a'.repeat(64), type: 'app', version: '1.0.0' }] } },
    [RELAY_B + '/catalog.json']: { body: { entries: [{ appKey: 'b'.repeat(64), type: 'app', version: '1.0.0' }] } }
  })
  const report = await verifyRelays([RELAY_A, RELAY_B], { fetch })
  t.is(report.verdict, 'agree', 'unique-to-one-relay entries are not divergence')
})

test('compareDrive validates input', async (t) => {
  try {
    await compareDrive('not-hex', [RELAY_A, RELAY_B], { fetch: async () => ({ ok: true, text: async () => '{}' }) })
    t.fail('should throw')
  } catch (err) {
    t.ok(err.message.includes('64 hex chars'))
  }
})

test('compareDrive returns AGREE when both views match', async (t) => {
  const drive = 'a'.repeat(64)
  const driveInfo = { length: 100, version: 1, contentHash: 'abc123' }
  const fetch = mockFetch({
    [RELAY_A + '/v1/hyper/' + drive + '/info']: { body: driveInfo },
    [RELAY_B + '/v1/hyper/' + drive + '/info']: { body: driveInfo }
  })
  const report = await compareDrive(drive, [RELAY_A, RELAY_B], { fetch })
  t.is(report.agreement, 'agree')
})

test('compareDrive flags divergent length/version/contentHash', async (t) => {
  const drive = 'a'.repeat(64)
  const fetch = mockFetch({
    [RELAY_A + '/v1/hyper/' + drive + '/info']: { body: { length: 100, version: 1, contentHash: 'abc123' } },
    [RELAY_B + '/v1/hyper/' + drive + '/info']: { body: { length: 99, version: 1, contentHash: 'def456' } }
  })
  const report = await compareDrive(drive, [RELAY_A, RELAY_B], { fetch })
  t.is(report.agreement, 'diverge')
  t.is(report.divergentFrom.length, 1)
  t.is(report.divergentFrom[0].relay, RELAY_B)
})

test('compareDrive returns insufficient-data when only one relay responds', async (t) => {
  const drive = 'a'.repeat(64)
  const fetch = mockFetch({
    [RELAY_A + '/v1/hyper/' + drive + '/info']: { body: { length: 100, version: 1 } },
    [RELAY_B + '/v1/hyper/' + drive + '/info']: { ok: false, status: 503 }
  })
  const report = await compareDrive(drive, [RELAY_A, RELAY_B], { fetch })
  t.is(report.agreement, 'insufficient-data')
})

test('software-URL divergence is reported with severity=info (different impls allowed)', async (t) => {
  const fetch = mockFetch({
    [RELAY_A + '/.well-known/hiverelay.json']: {
      body: { ...sameCapabilityDoc, software: 'https://github.com/bigdestiny2/p2p-hiverelay' }
    },
    [RELAY_B + '/.well-known/hiverelay.json']: {
      body: { ...sameCapabilityDoc, software: 'https://github.com/some-other/impl' }
    },
    [RELAY_A + '/catalog.json']: { body: { entries: [] } },
    [RELAY_B + '/catalog.json']: { body: { entries: [] } }
  })
  const report = await verifyRelays([RELAY_A, RELAY_B], { fetch })
  const sw = report.divergences.find(d => d.field === 'software')
  t.ok(sw)
  t.is(sw.severity, 'info')
})

const DRIVE_X = 'a'.repeat(64)
const DRIVE_Y = 'b'.repeat(64)

test('fetchAnchorProof rejects a proof whose appKey is not the requested drive', async (t) => {
  // A relay answers a query for DRIVE_X with a proof for DRIVE_Y (which it
  // genuinely anchored, so the signature would verify). Without the
  // appKey↔driveKey binding this would pass the audit for DRIVE_X.
  const fetch = mockFetch({
    [RELAY_A + '/api/anchors/' + DRIVE_X + '/proof']: {
      body: {
        appKey: DRIVE_Y,
        relayPubkey: 'c'.repeat(64),
        signature: 'd'.repeat(128),
        version: 1,
        attestedAt: Date.now(),
        anchored: true
      }
    }
  })
  const res = await fetchAnchorProof(RELAY_A, DRIVE_X, { fetch })
  t.is(res.ok, false, 'mismatched proof is rejected')
  t.is(res.error, 'appkey-mismatch', 'rejected for the binding, not signature')
  t.absent(res.verified, 'never reports verified for the wrong drive')
})

test('fetchAnchorProof passes the binding when appKey matches the requested drive', async (t) => {
  // appKey matches, so the binding check passes and we proceed to the
  // signature step (which fails here on a bogus signature — the point is
  // it is NOT short-circuited as appkey-mismatch).
  const fetch = mockFetch({
    [RELAY_A + '/api/anchors/' + DRIVE_X + '/proof']: {
      body: {
        appKey: DRIVE_X.toUpperCase(), // case-insensitive match
        relayPubkey: 'c'.repeat(64),
        signature: 'd'.repeat(128),
        version: 1,
        attestedAt: Date.now(),
        anchored: true
      }
    }
  })
  const res = await fetchAnchorProof(RELAY_A, DRIVE_X, { fetch })
  t.not(res.error, 'appkey-mismatch', 'matching appKey is not rejected by the binding')
})