OverviewFilesCommitsIssuesDocsReleasesPeers

P2P-Hiverelay / test / unit / api-router-read.test.js

149 lines · javascript ref:
import test from 'brittle'
import {
  MAX_ROUTER_TOPICS,
  MAX_ROUTER_TOPIC_BYTES,
  buildRouterInfoPayload,
  buildRouterInfoRoutePayload,
  resolveRouterReadRoute
} from 'p2p-hiverelay/core/relay-node/api-router-read.js'

test('api router read: route helper maps exact router info route', (t) => {
  t.alike(resolveRouterReadRoute('GET', '/api/v1/router'), {
    kind: 'router-info'
  })

  t.is(resolveRouterReadRoute('POST', '/api/v1/router'), null)
  t.is(resolveRouterReadRoute('GET', '/api/v1/router/extra'), null)
  t.is(resolveRouterReadRoute('GET', '/api/v1/services'), null)
})

test('api router read: missing router returns stable disabled payload', (t) => {
  t.alike(buildRouterInfoPayload(), {
    status: 503,
    payload: { error: 'Router not enabled' }
  })
})

test('api router read: route payload helper dispatches bounded router info', (t) => {
  let statsCalls = 0
  const router = {
    getStats () {
      statsCalls++
      return { routes: 4 }
    },
    pubsub: {
      topics () {
        return [' chat/general ', 'bad\nname']
      },
      topicCount () {
        return 2
      },
      subscriberCount () {
        return 3
      }
    }
  }

  const result = buildRouterInfoRoutePayload({
    route: { kind: 'router-info' },
    router
  })
  const unknown = buildRouterInfoRoutePayload({
    route: { kind: 'unknown' },
    router
  })

  t.is(statsCalls, 1)
  t.is(result.status, 200)
  t.is(result.headers['Cache-Control'], 'public, max-age=10')
  t.is(result.payload.routes, 4)
  t.alike(result.payload.pubsub.topics, ['chat/general'])
  t.is(result.payload.pubsub.subscriberCount, 3)
  t.is(unknown.status, 404)
  t.is(unknown.payload.error, 'unknown router read route')
})

test('api router read: uses router stats for route count without materializing routes', (t) => {
  let routesCalled = false
  const result = buildRouterInfoPayload({
    router: {
      getStats () {
        return { routes: 3 }
      },
      routes () {
        routesCalled = true
        return ['should.not.read']
      },
      pubsub: null
    }
  })

  t.is(result.status, 200)
  t.is(result.payload.routes, 3)
  t.is(result.payload.pubsub, null)
  t.alike(result.headers, { 'Cache-Control': 'public, max-age=10' })
  t.absent(routesCalled)
})

test('api router read: bounds and sanitizes public pubsub topics', (t) => {
  const validTopics = Array.from({ length: MAX_ROUTER_TOPICS + 12 }, (_, i) => 'topic/' + i)
  const result = buildRouterInfoPayload({
    router: {
      getStats () {
        return { routes: 9 }
      },
      pubsub: {
        topics () {
          return [
            ' services/ai ',
            'bad\nname',
            'x'.repeat(MAX_ROUTER_TOPIC_BYTES + 1),
            ...validTopics
          ]
        },
        topicCount () {
          return 999
        },
        subscriberCount () {
          return 7.9
        }
      }
    }
  })

  t.is(result.status, 200)
  t.is(result.payload.routes, 9)
  t.is(result.payload.pubsub.topics.length, MAX_ROUTER_TOPICS)
  t.is(result.payload.pubsub.topics[0], 'services/ai')
  t.absent(result.payload.pubsub.topics.includes('bad\nname'))
  t.absent(result.payload.pubsub.topics.includes('x'.repeat(MAX_ROUTER_TOPIC_BYTES + 1)))
  t.is(result.payload.pubsub.topicCount, 999)
  t.is(result.payload.pubsub.subscriberCount, 7)
  t.is(result.payload.pubsub.truncated, true)
})

test('api router read: tolerates throwing router and pubsub helpers', (t) => {
  const result = buildRouterInfoPayload({
    router: {
      getStats () { throw new Error('stats failed') },
      routes () { throw new Error('routes failed') },
      pubsub: {
        topics () { throw new Error('topics failed') },
        topicCount () { throw new Error('count failed') },
        subscriberCount () { throw new Error('subs failed') }
      }
    }
  })

  t.is(result.status, 200)
  t.alike(result.payload, {
    routes: 0,
    pubsub: {
      topics: [],
      topicCount: 0,
      subscriberCount: 0,
      truncated: false
    }
  })
})