OverviewFilesCommitsIssuesDocsReleasesPeers

P2P-Hiverelay / test / unit / client-custody.test.js

166 lines · javascript ref:
/**
 * Client SDK custody methods — verify each method posts to the right
 * endpoint with the right body and headers, and surfaces relay errors.
 *
 * Mocks globalThis.fetch so we can assert on the wire format without a
 * real relay. Higher-level integration tests (observe-testnet) cover
 * the round-trip.
 */

import test from 'brittle'
import { HiveRelayClient } from 'p2p-hiverelay-client'

function withMockFetch (handler, fn) {
  const originalFetch = globalThis.fetch
  const calls = []
  globalThis.fetch = async (url, opts = {}) => {
    calls.push({ url, opts })
    return handler({ url, opts })
  }
  return fn(calls).finally(() => { globalThis.fetch = originalFetch })
}

function jsonResponse (body, status = 200) {
  return {
    ok: status >= 200 && status < 300,
    status,
    text: async () => JSON.stringify(body)
  }
}

test('client.publishCustodyIntent: POSTs intent to /api/custody/intent with API key', async (t) => {
  await withMockFetch(
    () => jsonResponse({ intentId: 'i-1', signature: 'sig' }),
    async (calls) => {
      const client = new HiveRelayClient()
      const result = await client.publishCustodyIntent(
        'http://relay.example:9100',
        { blindContentId: 'b-1', ciphertextRoot: 'c-1', requiredReplicas: 3 },
        { apiKey: 'test-key' }
      )
      t.is(result.intentId, 'i-1')
      t.is(calls.length, 1)
      t.is(calls[0].url, 'http://relay.example:9100/api/custody/intent')
      t.is(calls[0].opts.method, 'POST')
      // Server reads only Authorization: Bearer (api.js _checkAuth, line 248).
      // X-API-Key was the previous (broken) header — silently ignored.
      t.is(calls[0].opts.headers.Authorization, 'Bearer test-key')
      t.absent(calls[0].opts.headers['X-API-Key'], 'X-API-Key must NOT be set')
      const body = JSON.parse(calls[0].opts.body)
      t.is(body.blindContentId, 'b-1')
    }
  )
})

test('client.publishCustodyCommit: POSTs to /api/custody/<intentId>/commit', async (t) => {
  await withMockFetch(
    () => jsonResponse({ ok: true, signature: 'sig' }),
    async (calls) => {
      const client = new HiveRelayClient()
      await client.publishCustodyCommit('http://relay.example:9100', 'intent-id-123', {}, { apiKey: 'k' })
      t.is(calls[0].url, 'http://relay.example:9100/api/custody/intent-id-123/commit')
    }
  )
})

test('client.publishSourceRetired: POSTs to /api/custody/<intentId>/source-retired', async (t) => {
  await withMockFetch(
    () => jsonResponse({ ok: true }),
    async (calls) => {
      const client = new HiveRelayClient()
      await client.publishSourceRetired('http://relay.example:9100', 'intent-id-123', {}, { apiKey: 'k' })
      t.is(calls[0].url, 'http://relay.example:9100/api/custody/intent-id-123/source-retired')
    }
  )
})

test('client.recordCustodyProof: POSTs to /api/custody/proof', async (t) => {
  await withMockFetch(
    () => jsonResponse({ ok: true }),
    async (calls) => {
      const client = new HiveRelayClient()
      await client.recordCustodyProof('http://relay.example:9100', { relayPubkey: 'p', passed: true }, { apiKey: 'k' })
      t.is(calls[0].url, 'http://relay.example:9100/api/custody/proof')
    }
  )
})

test('client.recordCustodyNonServingProof: POSTs to /api/custody/<intentId>/non-serving-proof', async (t) => {
  await withMockFetch(
    () => jsonResponse({ ok: true }),
    async (calls) => {
      const client = new HiveRelayClient()
      await client.recordCustodyNonServingProof('http://relay.example:9100', 'i-1', {}, { apiKey: 'k' })
      t.is(calls[0].url, 'http://relay.example:9100/api/custody/i-1/non-serving-proof')
    }
  )
})

test('client.recordCustodyExpiryWitness: POSTs to /api/custody/<intentId>/witness', async (t) => {
  await withMockFetch(
    () => jsonResponse({ ok: true, signature: 'sig' }),
    async (calls) => {
      const client = new HiveRelayClient()
      await client.recordCustodyExpiryWitness(
        'http://relay.example:9100', 'i-1',
        { relayPubkey: 'r', nonServingProofHash: 'h' },
        { apiKey: 'k' }
      )
      t.is(calls[0].url, 'http://relay.example:9100/api/custody/i-1/witness')
      const body = JSON.parse(calls[0].opts.body)
      t.is(body.relayPubkey, 'r')
    }
  )
})

test('client.getCustodyStatus: GETs /api/custody/<intentId>/status', async (t) => {
  await withMockFetch(
    () => jsonResponse({ intentId: 'i-1', quorumReached: true, committed: true, receiptCount: 3 }),
    async (calls) => {
      const client = new HiveRelayClient()
      const status = await client.getCustodyStatus('http://relay.example:9100', 'i-1')
      t.is(status.committed, true)
      t.is(status.receiptCount, 3)
      t.is(calls[0].url, 'http://relay.example:9100/api/custody/i-1/status')
      t.is(calls[0].opts.method, 'GET')
    }
  )
})

test('client.getCustodyStatus: returns null on 404', async (t) => {
  await withMockFetch(
    () => jsonResponse({ error: 'not found' }, 404),
    async () => {
      const client = new HiveRelayClient()
      const status = await client.getCustodyStatus('http://relay.example:9100', 'unknown')
      t.is(status, null)
    }
  )
})

test('client.publishCustodyIntent: surfaces relay error body and status', async (t) => {
  await withMockFetch(
    () => jsonResponse({ error: 'invalid blindContentId' }, 400),
    async () => {
      const client = new HiveRelayClient()
      try {
        await client.publishCustodyIntent('http://relay.example:9100', { blindContentId: 'bad' }, { apiKey: 'k' })
        t.fail('should have thrown')
      } catch (err) {
        t.is(err.status, 400)
        t.ok(err.message.includes('invalid blindContentId'))
      }
    }
  )
})

test('client.publishCustodyIntent: throws on missing relayUrl', async (t) => {
  const client = new HiveRelayClient()
  try {
    await client.publishCustodyIntent('', {}, {})
    t.fail('should have thrown')
  } catch (err) {
    t.ok(err.message.includes('relayUrl required'))
  }
})