OverviewFilesCommitsIssuesDocsReleasesPeers

P2P-Hiverelay / test / unit / api-fleet.test.js

242 lines · javascript ref:
import test from 'brittle'
import {
  aggregateFleet,
  buildFleetPayload,
  buildFleetRoutePayload,
  isAllowedFleetBaseUrl,
  normalizeFleetBaseUrl,
  normalizeFleetPeers,
  resolveFleetConfig,
  resolveFleetRoute,
  sanitizeFleetPeerCard,
  sanitizeFleetPrivacy
} from 'p2p-hiverelay/core/relay-node/api-fleet.js'

test('api fleet: route helper maps management fleet route', (t) => {
  t.alike(resolveFleetRoute('GET', '/api/fleet'), {
    kind: 'fleet',
    authMessage: 'Unauthorized — API key required for /api/fleet'
  })
  t.is(resolveFleetRoute('POST', '/api/fleet'), null)
  t.is(resolveFleetRoute('GET', '/api/fleet/extra'), null)
})

test('api fleet: base URL allow-list rejects secrets and non-http schemes', (t) => {
  t.ok(isAllowedFleetBaseUrl('http://127.0.0.1:9100'))
  t.ok(isAllowedFleetBaseUrl('https://relay.example.com'))
  t.absent(isAllowedFleetBaseUrl('file:///etc/passwd'))
  t.absent(isAllowedFleetBaseUrl('http://user:pass@host:9100'))
  t.absent(isAllowedFleetBaseUrl('not a url'))
  t.is(normalizeFleetBaseUrl('https://a.example/path/'), 'https://a.example/path')
})

test('api fleet: peer config is bounded and deduped', (t) => {
  const peers = normalizeFleetPeers([
    { id: 'bern', baseUrl: 'http://45.59.123.112:9100', region: 'EU', pubkey: 'bc421fedea8a' },
    { id: 'dup', baseUrl: 'http://45.59.123.112:9100/' },
    { id: 'bad', baseUrl: 'ftp://nope' },
    'https://milkyb.example',
    { id: 'x', baseUrl: 'javascript:alert(1)' }
  ], { maxPeers: 10 })

  t.is(peers.length, 2)
  t.is(peers[0].id, 'bern')
  t.is(peers[0].region, 'EU')
  t.is(peers[0].declaredPubkey, 'bc421fedea8a')
  t.is(peers[1].baseUrl, 'https://milkyb.example')
})

test('api fleet: status cards allow-list fields and never keep onion material', (t) => {
  const card = sanitizeFleetPeerCard({
    id: 'peer-a',
    label: 'Utah',
    region: 'NA',
    baseUrl: 'http://144.172.101.215:9100',
    health: { ok: true, running: true, uptime: { ms: 60000 } },
    status: {
      running: true,
      publicKey: 'a'.repeat(64),
      connections: 4,
      seededApps: 2,
      health: { healthy: true },
      disk: { usedPct: 22, status: 'ok', freeGB: 40 },
      storage: { totalBytes: 1024, max: 4096 },
      transports: {
        tor: {
          running: true,
          health: 'ready',
          onionAddress: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.onion'
        }
      },
      holesailKey: 'should-not-leak',
      tor: { onionAddress: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb.onion' }
    },
    fetchedAt: 1000
  })

  t.is(card.up, true)
  t.is(card.publicKey, 'aaaaaaaaaaaa')
  t.is(card.connections, 4)
  t.is(card.disk.usedPct, 22)
  t.is(card.storage.used, 1024)
  t.ok(card.privacyTransports)
  t.is(card.privacyTransports[0].health, 'ready')
  t.absent(JSON.stringify(card).includes('.onion'))
  t.absent(JSON.stringify(card).includes('should-not-leak'))
  t.absent(JSON.stringify(card).includes('holesail'))
})

test('api fleet: privacy sanitizer is coarse-only', (t) => {
  t.alike(sanitizeFleetPrivacy({
    tor: { running: false, health: 'down', onionAddress: 'x.onion' }
  }), [{ id: 'tor-v3-onion-v1', running: false, health: 'down' }])
  t.absent(JSON.stringify(sanitizeFleetPrivacy({
    tor: { running: true, health: 'degraded', onionAddress: 'y.onion' }
  })).includes('.onion'))
})

test('api fleet: aggregate counts up/down and disk alerts', (t) => {
  const summary = aggregateFleet([
    { up: true, region: 'EU', healthy: true, disk: { usedPct: 20 } },
    { up: false, region: 'NA', healthy: false, disk: { usedPct: 92, status: 'critical' } },
    {
      up: true,
      region: 'EU',
      privacyTransports: [{ health: 'degraded' }],
      disk: { usedPct: 80, status: 'warn' }
    }
  ], { now: 5000 })

  t.is(summary.peerCount, 3)
  t.is(summary.up, 2)
  t.is(summary.down, 1)
  t.is(summary.regions, 2)
  t.is(summary.diskCritical, 1)
  t.is(summary.diskWarn, 1)
  t.is(summary.unhealthy, 1)
  t.is(summary.privacyDegraded, 1)
  t.is(summary.updatedAt, 5000)
})

test('api fleet: buildFleetPayload polls peers via injected fetch and includes self', async (t) => {
  const calls = []
  const fetchJson = async (url) => {
    calls.push(url)
    if (url.endsWith('/health')) return { ok: true, running: true, uptime: { ms: 1000 } }
    if (url.endsWith('/status')) {
      return {
        running: true,
        publicKey: 'b'.repeat(64),
        connections: 1,
        seededApps: 0,
        health: { healthy: true },
        transports: { tor: { running: true, health: 'ready', onionAddress: 'secret.onion' } }
      }
    }
    throw new Error('unexpected ' + url)
  }

  const payload = await buildFleetPayload({
    config: {
      fleet: {
        includeSelf: true,
        peers: [{ id: 'bern', baseUrl: 'http://45.59.123.112:9100', region: 'EU' }]
      }
    },
    selfStatus: {
      running: true,
      publicKey: 'c'.repeat(64),
      connections: 9,
      health: { healthy: true }
    },
    selfHealth: { ok: true, running: true },
    selfPublicKey: 'c'.repeat(64),
    selfRegion: 'NA',
    fetchJson,
    now: 9000
  })

  t.is(payload.peers.length, 2)
  t.is(payload.peers[0].self, true)
  t.is(payload.peers[0].id, 'self')
  t.is(payload.peers[1].id, 'bern')
  t.is(payload.peers[1].up, true)
  t.is(payload.summary.up, 2)
  t.is(payload.config.peerTargets, 1)
  t.ok(calls.some((u) => u.includes('/health')))
  t.ok(calls.some((u) => u.includes('/status')))
  t.absent(JSON.stringify(payload).includes('.onion'))
})

test('api fleet: unreachable peers mark down without throwing', async (t) => {
  const payload = await buildFleetPayload({
    config: {
      fleet: {
        includeSelf: false,
        peers: [{ id: 'gone', baseUrl: 'http://203.0.113.9:9100' }]
      }
    },
    fetchJson: async () => { throw new Error('timeout') },
    now: 1
  })
  t.is(payload.peers.length, 1)
  t.is(payload.peers[0].up, false)
  t.ok(payload.peers[0].errors.length >= 1)
  t.is(payload.summary.down, 1)
})

test('api fleet: route payload unknown route 404', async (t) => {
  const bad = await buildFleetRoutePayload({ route: null })
  t.is(bad.ok, false)
  t.is(bad.status, 404)
})

test('api fleet: route payload assembles from node + fetch', async (t) => {
  const node = {
    config: {
      regions: ['EU'],
      fleet: {
        includeSelf: true,
        peers: [{ id: 'peer', baseUrl: 'http://198.51.100.10:9100' }]
      }
    },
    metrics: { startedAt: 1000 },
    getStats () {
      return {
        running: true,
        publicKey: 'd'.repeat(64),
        connections: 3,
        seededApps: 1,
        storage: { totalBytes: 50 }
      }
    },
    getHealthStatus () {
      return { healthy: true }
    }
  }

  const result = await buildFleetRoutePayload({
    route: { kind: 'fleet' },
    node,
    fetchJson: async (url) => {
      if (url.endsWith('/health')) return { ok: false }
      if (url.endsWith('/status')) throw new Error('down')
      throw new Error('nope')
    },
    now: 5000
  })

  t.is(result.ok, true)
  t.is(result.status, 200)
  t.ok(result.payload.summary.peerCount >= 2)
  t.ok(result.payload.peers.some((p) => p.self === true))
  t.absent(JSON.stringify(result.payload).includes('.onion'))
})

test('api fleet: resolveFleetConfig defaults', (t) => {
  const cfg = resolveFleetConfig({})
  t.is(cfg.includeSelf, true)
  t.is(cfg.peers.length, 0)
  t.ok(cfg.timeoutMs > 0)
})