OverviewFilesCommitsIssuesDocsReleasesPeers

P2P-Hiverelay / test / unit / api-anchor-status.test.js

378 lines · javascript ref:
import test from 'brittle'
import {
  anchorProofAppKeyFromPath,
  anchorStatusEntries,
  anchorStatusEntry,
  buildAnchorProofPayload,
  buildAnchorProofRoutePayload,
  buildAnchorStatusPayload,
  buildAnchorStatusRoutePayload,
  buildAnchorStatusRouteContext,
  isAnchorProofRoute,
  isDetailedAnchorStatusQuery,
  resolveAnchorProofRoute,
  resolveAnchorStatusRoute
} from '../../packages/core/core/relay-node/api-anchor-status.js'

test('api anchor status: detailed query parser is explicit', (t) => {
  t.ok(isDetailedAnchorStatusQuery('1'))
  t.ok(isDetailedAnchorStatusQuery('true'))
  t.absent(isDetailedAnchorStatusQuery('TRUE'))
  t.absent(isDetailedAnchorStatusQuery('0'))
  t.absent(isDetailedAnchorStatusQuery(null))
})

test('api anchor status: route helper maps exact aggregate read', (t) => {
  t.alike(resolveAnchorStatusRoute('GET', '/api/anchors'), {
    kind: 'anchor-status'
  })

  t.is(resolveAnchorStatusRoute('POST', '/api/anchors'), null)
  t.is(resolveAnchorStatusRoute('GET', '/api/anchors/extra'), null)
  t.is(resolveAnchorStatusRoute('GET', '/api/anchors/aaaaaaaa/proof'), null)
})

test('api anchor proof: route helpers isolate proof path parsing', async (t) => {
  const appKey = 'a'.repeat(64)
  const path = `/api/anchors/${appKey}/proof`
  const proof = { appKey, anchored: true }
  let calledWith = null

  t.ok(isAnchorProofRoute(path))
  t.ok(isAnchorProofRoute('/api/anchors/not-hex/proof'))
  t.absent(isAnchorProofRoute(`/api/anchors/${appKey}`))
  t.alike(resolveAnchorProofRoute('GET', path), { kind: 'anchor-proof' })
  t.is(resolveAnchorProofRoute('POST', path), null)
  t.is(resolveAnchorProofRoute('GET', `/api/anchors/${appKey}`), null)
  t.is(anchorProofAppKeyFromPath(path), appKey)
  t.is(anchorProofAppKeyFromPath('/api/anchors/not-hex/proof'), 'not-hex')
  t.is(anchorProofAppKeyFromPath('/api/anchors'), '')

  const result = await buildAnchorProofRoutePayload({
    path,
    node: {
      async createAnchorProof (key) {
        calledWith = key
        return proof
      }
    }
  })

  t.is(calledWith, appKey)
  t.alike(result, {
    ok: true,
    status: 200,
    payload: proof
  })

  t.alike(await buildAnchorProofRoutePayload({
    path: '/api/anchors/not-hex/proof',
    node: {
      async createAnchorProof () {
        t.fail('malformed route appKey must be rejected before proof generation')
      }
    }
  }), {
    ok: false,
    status: 400,
    payload: { error: 'invalid appKey' }
  })
})

test('api anchor status: route context owns detailed auth decision', (t) => {
  const detailed = buildAnchorStatusRouteContext(new URL('http://127.0.0.1/api/anchors?detailed=1'))
  t.alike(detailed, {
    detailed: true,
    requiresAuth: true
  })

  t.alike(buildAnchorStatusRouteContext(new URL('http://127.0.0.1/api/anchors?detailed=true')), {
    detailed: true,
    requiresAuth: true
  })

  t.alike(buildAnchorStatusRouteContext(new URL('http://127.0.0.1/api/anchors?detailed=TRUE')), {
    detailed: false,
    requiresAuth: false
  })

  t.alike(buildAnchorStatusRouteContext(null), {
    detailed: false,
    requiresAuth: false
  })
})

test('api anchor status: route payload helper dispatches aggregate status', (t) => {
  const appRegistry = {
    anchorStats () {
      return { total: 1, anchored: 1, unanchored: 0, neverChecked: 0 }
    },
    catalog () {
      return [{
        appKey: 'a'.repeat(64),
        type: 'drive',
        anchored: true,
        anchoredAt: 99,
        anchoredLength: 123
      }]
    }
  }

  const pub = buildAnchorStatusRoutePayload({
    route: { kind: 'anchor-status' },
    url: new URL('http://127.0.0.1/api/anchors'),
    appRegistry,
    lastCheckedAt: 42
  })
  const detailed = buildAnchorStatusRoutePayload({
    route: { kind: 'anchor-status' },
    url: new URL('http://127.0.0.1/api/anchors?detailed=true'),
    appRegistry,
    lastCheckedAt: 42
  })
  const contextOverride = buildAnchorStatusRoutePayload({
    route: { kind: 'anchor-status' },
    context: { detailed: false },
    url: new URL('http://127.0.0.1/api/anchors?detailed=true'),
    appRegistry,
    lastCheckedAt: 42
  })
  const unknown = buildAnchorStatusRoutePayload({
    route: { kind: 'unknown' },
    appRegistry
  })

  t.is(pub.ok, true)
  t.is(pub.payload.entries, null)
  t.is(pub.payload.lastCheckedAt, 42)
  t.is(detailed.ok, true)
  t.is(detailed.payload.entries.length, 1)
  t.is(detailed.payload.entries[0].appKey, 'a'.repeat(64))
  t.is(contextOverride.payload.entries, null)
  t.is(unknown.status, 404)
  t.is(unknown.payload.error, 'unknown anchor status route')
})

test('api anchor proof: valid appKey delegates to proof signer', async (t) => {
  const appKey = 'a'.repeat(64)
  const proof = {
    appKey,
    anchored: true,
    version: 7,
    attestedAt: 123,
    relayPubkey: 'b'.repeat(64),
    signature: 'c'.repeat(128)
  }

  let calledWith = null
  const result = await buildAnchorProofPayload({
    appKey,
    node: {
      async createAnchorProof (key) {
        calledWith = key
        return proof
      }
    }
  })

  t.is(calledWith, appKey)
  t.alike(result, {
    ok: true,
    status: 200,
    payload: proof
  })
})

test('api anchor proof: malformed appKey is rejected before proof generation', async (t) => {
  let called = false
  const result = await buildAnchorProofPayload({
    appKey: 'not-hex',
    node: {
      async createAnchorProof () {
        called = true
        return {}
      }
    }
  })

  t.absent(called)
  t.alike(result, {
    ok: false,
    status: 400,
    payload: { error: 'invalid appKey' }
  })
})

test('api anchor proof: unavailable or throwing signer returns stable payloads', async (t) => {
  const appKey = 'd'.repeat(64)

  t.alike(await buildAnchorProofPayload({ appKey }), {
    ok: false,
    status: 503,
    payload: { error: 'proof generation failed' }
  })

  t.alike(await buildAnchorProofPayload({
    appKey,
    node: {
      async createAnchorProof () {
        throw new Error('storage unavailable')
      }
    }
  }), {
    ok: false,
    status: 503,
    payload: { error: 'storage unavailable' }
  })

  t.alike(await buildAnchorProofPayload({
    appKey,
    node: {
      async createAnchorProof () {
        throw new Error('invalid appKey')
      }
    }
  }), {
    ok: false,
    status: 400,
    payload: { error: 'invalid appKey' }
  })
})

test('api anchor status: unavailable registry returns stable 503 payload', (t) => {
  t.alike(buildAnchorStatusPayload(), {
    ok: false,
    status: 503,
    payload: { error: 'anchor stats unavailable' }
  })
  t.alike(buildAnchorStatusPayload({ appRegistry: {} }), {
    ok: false,
    status: 503,
    payload: { error: 'anchor stats unavailable' }
  })
})

test('api anchor status: public payload keeps aggregate stats without entry details', (t) => {
  const appRegistry = {
    anchorStats () {
      return {
        total: 2,
        anchored: 1,
        unanchored: 1,
        neverChecked: 0,
        rawSchedulerState: 'should-not-leak'
      }
    },
    catalog () {
      t.fail('public anchor status must not enumerate catalog entries')
      return []
    }
  }

  t.alike(buildAnchorStatusPayload({
    appRegistry,
    lastCheckedAt: 12345
  }), {
    ok: true,
    status: 200,
    payload: {
      total: 2,
      anchored: 1,
      unanchored: 1,
      neverChecked: 0,
      lastCheckedAt: 12345,
      entries: null
    }
  })
  t.absent(JSON.stringify(buildAnchorStatusPayload({ appRegistry }).payload).includes('should-not-leak'))
})

test('api anchor status: malformed aggregate stats become bounded public counters', (t) => {
  const appRegistry = {
    anchorStats () {
      return {
        total: -1,
        anchored: 1.9,
        unanchored: Number.POSITIVE_INFINITY,
        neverChecked: '3'
      }
    }
  }

  t.alike(buildAnchorStatusPayload({
    appRegistry,
    lastCheckedAt: Number.POSITIVE_INFINITY
  }).payload, {
    total: 0,
    anchored: 1,
    unanchored: 0,
    neverChecked: 3,
    lastCheckedAt: null,
    entries: null
  })
})

test('api anchor status: detailed entries expose only custody diagnostics', (t) => {
  const entry = anchorStatusEntry({
    appKey: 'a'.repeat(64),
    type: 'drive',
    anchored: false,
    anchoredAt: null,
    anchoredLength: 0,
    custodyIntentId: 'b'.repeat(64),
    blind: true,
    storageClass: 'temporary',
    availabilityClass: 'atomic-handoff',
    publisherPubkey: 'secret-publisher',
    retainUntil: 999,
    ciphertextRoot: 'secret-ciphertext',
    shareBundleKey: 'secret-share-bundle'
  })

  t.alike(entry, {
    appKey: 'a'.repeat(64),
    type: 'drive',
    anchored: false,
    anchoredAt: null,
    anchoredLength: 0,
    custodyIntentId: 'b'.repeat(64),
    blind: true,
    storageClass: 'temporary',
    availabilityClass: 'atomic-handoff'
  })
  t.absent(Object.prototype.hasOwnProperty.call(entry, 'publisherPubkey'))
  t.absent(Object.prototype.hasOwnProperty.call(entry, 'retainUntil'))
  t.absent(Object.prototype.hasOwnProperty.call(entry, 'ciphertextRoot'))
  t.absent(Object.prototype.hasOwnProperty.call(entry, 'shareBundleKey'))
})

test('api anchor status: detailed payload maps catalog through public helper', (t) => {
  const appRegistry = {
    anchorStats () {
      return { total: 1, anchored: 0, unanchored: 1, neverChecked: 0 }
    },
    catalog () {
      return [{
        appKey: 'c'.repeat(64),
        type: 'app',
        anchored: undefined,
        custodyIntentId: '',
        blind: false
      }]
    }
  }

  t.alike(anchorStatusEntries(appRegistry), [{
    appKey: 'c'.repeat(64),
    type: 'app',
    anchored: undefined,
    anchoredAt: undefined,
    anchoredLength: undefined,
    custodyIntentId: null,
    blind: false,
    storageClass: null,
    availabilityClass: null
  }])
  t.is(buildAnchorStatusPayload({ appRegistry, detailed: true }).payload.entries.length, 1)
})