OverviewFilesCommitsIssuesDocsReleasesPeers

P2P-Hiverelay / test / unit / api-eviction-purge.test.js

140 lines · javascript ref:
import test from 'brittle'
import {
  EVICTION_PURGE_AUTH_MESSAGE,
  MAX_PURGE_APP_KEYS,
  resolveEvictionPurgeRoute,
  runEvictionPurgeAction
} from '../../packages/core/core/relay-node/api-eviction-purge.js'

function key (char) {
  return char.repeat(64)
}

test('api eviction purge: route helper maps exact operator purge route', (t) => {
  t.alike(resolveEvictionPurgeRoute('POST', '/api/eviction/purge'), {
    kind: 'eviction-purge',
    authMessage: EVICTION_PURGE_AUTH_MESSAGE
  })

  t.is(resolveEvictionPurgeRoute('GET', '/api/eviction/purge'), null)
  t.is(resolveEvictionPurgeRoute('POST', '/api/eviction'), null)
  t.is(resolveEvictionPurgeRoute('POST', '/api/eviction/purge/all'), null)
})

test('api eviction purge: validates request body before purging', async (t) => {
  const node = {
    async manualPurge () {
      t.fail('invalid request must not purge')
    }
  }

  let out = await runEvictionPurgeAction({ body: null, node })
  t.is(out.status, 400)
  t.ok(out.payload.error.includes('appKeys (non-empty array) required'))

  out = await runEvictionPurgeAction({ body: { appKeys: [] }, node })
  t.is(out.status, 400)
  t.ok(out.payload.error.includes('appKeys (non-empty array) required'))
})

test('api eviction purge: caps batch size before purging', async (t) => {
  const node = {
    async manualPurge () {
      t.fail('oversized request must not purge')
    }
  }
  const out = await runEvictionPurgeAction({
    body: { appKeys: Array.from({ length: MAX_PURGE_APP_KEYS + 1 }, () => key('a')) },
    node
  })

  t.is(out.status, 400)
  t.ok(out.payload.error.includes('max 50 appKeys per request'))
})

test('api eviction purge: reports invalid keys per item and keeps valid purges moving', async (t) => {
  const calls = []
  const node = {
    async manualPurge (appKey) {
      calls.push(appKey)
      return { bytes: 12 }
    }
  }

  const out = await runEvictionPurgeAction({
    body: { appKeys: ['not-hex', key('b')] },
    node
  })

  t.is(out.status, 200)
  t.is(out.payload.ok, true)
  t.is(out.payload.purged, 1)
  t.is(out.payload.freedBytes, 12)
  t.alike(calls, [key('b')])
  t.alike(out.payload.results[0], { appKey: 'not-hex', ok: false, error: 'invalid appKey' })
  t.alike(out.payload.results[1], { appKey: key('b'), ok: true, bytes: 12 })
})

test('api eviction purge: isolates manual purge failures per key', async (t) => {
  const node = {
    async manualPurge (appKey) {
      if (appKey === key('c')) {
        const err = new Error('custody-bound')
        err.code = 'NOT_PURGABLE'
        throw err
      }
      return { bytes: 5 }
    }
  }

  const out = await runEvictionPurgeAction({
    body: { appKeys: [key('c'), key('d')] },
    node
  })

  t.is(out.status, 200)
  t.is(out.payload.purged, 1)
  t.is(out.payload.freedBytes, 5)
  t.alike(out.payload.results, [
    { appKey: key('c'), ok: false, error: 'NOT_PURGABLE' },
    { appKey: key('d'), ok: true, bytes: 5 }
  ])
})

test('api eviction purge: freed byte aggregation ignores malformed byte results', async (t) => {
  const bytes = new Map([
    [key('a'), 10],
    [key('b'), '11'],
    [key('c'), -4],
    [key('d'), Infinity]
  ])
  const node = {
    async manualPurge (appKey) {
      return { bytes: bytes.get(appKey) }
    }
  }

  const out = await runEvictionPurgeAction({
    body: { appKeys: [key('a'), key('b'), key('c'), key('d')] },
    node
  })

  t.is(out.status, 200)
  t.is(out.payload.purged, 4)
  t.is(out.payload.freedBytes, 10)
  t.alike(out.payload.results.map(result => result.bytes), [10, 0, 0, 0])
})

test('api eviction purge: unavailable manual purge is reported per valid key', async (t) => {
  const out = await runEvictionPurgeAction({
    body: { appKeys: [key('e')] },
    node: {}
  })

  t.is(out.status, 200)
  t.is(out.payload.purged, 0)
  t.alike(out.payload.results, [
    { appKey: key('e'), ok: false, error: 'manual purge unavailable' }
  ])
})