OverviewFilesCommitsIssuesDocsReleasesPeers

P2P-Hiverelay / test / unit / bare-http-server.test.js

216 lines · javascript ref:
import test from 'brittle'
import { BareHttpServer } from '../../packages/core/core/relay-node/bare-http-server.js'
import { CATALOG_TYPE_ERROR } from '../../packages/core/core/relay-node/api-catalog-read.js'

test('bare http server: public JSON responses use hardened headers', (t) => {
  const server = new BareHttpServer(fakeRelay())
  const res = fakeRes()

  server._handle({ url: '/health', headers: { host: 'localhost' } }, res)

  t.is(res.statusCode, 200)
  t.is(res.headers['Content-Type'], 'application/json; charset=utf-8')
  t.is(res.headers['X-Content-Type-Options'], 'nosniff')
  t.is(res.headers['Cache-Control'], 'no-store, max-age=0')
  t.is(res.headers['Access-Control-Allow-Origin'], '*')
  t.is(JSON.parse(res.body).runtime, 'bare')
})

test('bare http server: capability docs preserve public cache while keeping JSON hardening', (t) => {
  const server = new BareHttpServer(fakeRelay(), { version: '1.2.3' })
  const res = fakeBareRes()

  server._handle({ url: '/api/capabilities', headers: { host: 'localhost' } }, res)

  t.is(res.statusCode, 200)
  t.is(res.header('Content-Type'), 'application/json; charset=utf-8')
  t.is(res.header('X-Content-Type-Options'), 'nosniff')
  t.is(res.header('Cache-Control'), 'public, max-age=60')
  t.is(res.header('Access-Control-Allow-Origin'), '*')
  const body = JSON.parse(res.body)
  t.is(body.runtime, 'bare')
  t.is(body.version, '1.2.3')
})

test('bare http server: anchors route reuses bounded public anchor payload', (t) => {
  const relay = fakeRelay()
  relay._lastAnchorCheckAt = 12345
  relay.appRegistry.anchorStats = function () {
    return {
      total: 2,
      anchored: 1,
      unanchored: 1,
      neverChecked: 0,
      rawSchedulerState: 'should-not-leak'
    }
  }
  relay.appRegistry.catalog = function () {
    t.fail('Bare public anchors route must not enumerate catalog entries')
    return []
  }

  const server = new BareHttpServer(relay)
  const res = fakeRes()

  server._handle({ url: '/api/anchors', headers: { host: 'localhost' } }, res)

  t.is(res.statusCode, 200)
  t.alike(JSON.parse(res.body), {
    total: 2,
    anchored: 1,
    unanchored: 1,
    neverChecked: 0,
    lastCheckedAt: 12345,
    entries: null
  })
  t.absent(res.body.includes('should-not-leak'))
})

test('bare http server: catalog route reuses bounded relay catalog helper', (t) => {
  const relay = fakeRelay()
  const entries = [
    { type: 'drive', key: 'drive-1', name: 'Drive 1', categories: ['docs'] },
    { type: 'drive', key: 'drive-2', name: 'Drive 2', categories: ['docs'] },
    { type: 'drive', key: 'mounted-1', parentKey: 'drive-1', name: 'Mounted resource' },
    { type: 'app', key: 'app-1', name: 'Filtered app' }
  ]
  let catalogOpts = null
  relay.publicKey = Buffer.alloc(32, 9)
  relay.config.operator = 'operator-one'
  relay.appRegistry.catalog = function (opts) {
    catalogOpts = opts
    return entries
  }

  const server = new BareHttpServer(relay)
  const res = fakeRes()

  server._handle({ url: '/catalog.json?pageSize=1&type=drive', headers: { host: 'localhost' } }, res)

  const body = JSON.parse(res.body)
  t.is(res.statusCode, 200)
  t.is(body.version, 2)
  t.is(body.relayKey, '09'.repeat(32))
  t.is(body.region, 'test')
  t.is(body.operator, 'operator-one')
  t.alike(body.filters, { type: 'drive', parent: null, category: null })
  t.alike(body.pagination, {
    page: 1,
    pageSize: 1,
    total: 3,
    totalPages: 3,
    hasNext: true,
    hasPrev: false
  })
  t.alike(body.count, {
    total: 3,
    apps: 0,
    drives: 2,
    resources: 1,
    datasets: 0,
    media: 0
  })
  t.alike(body.entries, [entries[0]])
  t.alike(body.drives, [entries[0]])
  t.alike(body.resources, [])
  t.alike(catalogOpts, { redactPrivate: true })
  t.absent(res.body.includes('app-1'))
})

test('bare http server: catalog route rejects invalid type filters', (t) => {
  const server = new BareHttpServer(fakeRelay())
  const res = fakeRes()

  server._handle({ url: '/catalog.json?type=unknown', headers: { host: 'localhost' } }, res)

  t.is(res.statusCode, 400)
  t.alike(JSON.parse(res.body), { error: CATALOG_TYPE_ERROR })
})

test('bare http server: 404 responses use the same hardened JSON path', (t) => {
  const server = new BareHttpServer(fakeRelay())
  const res = fakeRes()

  server._handle({ url: '/missing?token=secret', headers: { host: 'localhost' } }, res)

  t.is(res.statusCode, 404)
  t.is(res.headers['Content-Type'], 'application/json; charset=utf-8')
  t.is(res.headers['X-Content-Type-Options'], 'nosniff')
  t.is(res.headers['Cache-Control'], 'no-store, max-age=0')
  t.is(res.headers['Access-Control-Allow-Origin'], '*')
  t.alike(JSON.parse(res.body), { error: 'Not found', path: '/missing' })
})

function fakeRelay () {
  return {
    startedAt: Date.now() - 1000,
    publicKey: null,
    config: {
      regions: ['test'],
      maxStorageBytes: 123,
      discovery: { dht: true }
    },
    connections: new Map(),
    seeder: { totalBytesStored: 0 },
    appRegistry: {
      apps: new Map(),
      anchorStats () {
        return { total: 0, anchored: 0, unanchored: 0, neverChecked: 0 }
      },
      catalog () {
        return []
      }
    },
    serviceRegistry: null
  }
}

function fakeRes () {
  const headers = {}
  return {
    headers,
    statusCode: null,
    body: null,
    setHeader (name, value) {
      headers[name] = value
    },
    getHeader (name) {
      const lower = name.toLowerCase()
      const key = Object.keys(headers).find(key => key.toLowerCase() === lower)
      return key ? headers[key] : undefined
    },
    hasHeader (name) {
      const lower = name.toLowerCase()
      return Object.keys(headers).some(key => key.toLowerCase() === lower)
    },
    writeHead (status) {
      this.statusCode = status
    },
    end (body) {
      this.body = body
    }
  }
}

function fakeBareRes () {
  const headers = {}
  return {
    statusCode: null,
    body: null,
    setHeader (name, value) {
      headers[name] = value
    },
    header (name) {
      const lower = name.toLowerCase()
      const key = Object.keys(headers).find(key => key.toLowerCase() === lower)
      return key ? headers[key] : undefined
    },
    writeHead (status) {
      this.statusCode = status
    },
    end (body) {
      this.body = body
    }
  }
}