OverviewFilesCommitsIssuesDocsReleasesPeers

P2P-Hiverelay / test / unit / api-federation-management.test.js

566 lines · javascript ref:
import test from 'brittle'
import { Federation } from '../../packages/core/core/federation.js'
import {
  MAX_FEDERATION_SNAPSHOT_PEER_APPS,
  MAX_FEDERATION_SNAPSHOT_RELAYS,
  buildFederationSnapshotPayload,
  buildFederationSnapshotRoutePayload,
  federationPersistFailureResult,
  MAX_FEDERATION_NOTE_LENGTH,
  resolveFederationManagementRoute,
  resolveFederationSnapshotRoute,
  runFederationManagementAction,
  runFederationManagementRouteAction
} from '../../packages/core/core/relay-node/api-federation-management.js'

test('api federation management: snapshot route helper maps exact operator read route', (t) => {
  t.alike(resolveFederationSnapshotRoute('GET', '/api/manage/federation'), {
    kind: 'federation-snapshot',
    authMessage: 'Unauthorized — API key required for /api/manage/federation'
  })

  t.is(resolveFederationSnapshotRoute('POST', '/api/manage/federation'), null)
  t.is(resolveFederationSnapshotRoute('GET', '/api/manage/federation/follow'), null)
  t.is(resolveFederationSnapshotRoute('GET', '/api/manage/federation/extra'), null)
})

test('api federation management: snapshot route payload helper dispatches operator read', (t) => {
  const route = resolveFederationSnapshotRoute('GET', '/api/manage/federation')
  const result = buildFederationSnapshotRoutePayload({
    route,
    federation: {
      snapshot: () => ({
        followed: [{ url: 'https://relay.example', pubkey: 'A'.repeat(64), addedAt: 1 }],
        mirrored: [],
        republished: [],
        peerCatalogs: [],
        running: true
      })
    }
  })

  t.is(result.ok, true)
  t.is(result.status, undefined)
  t.alike(result.payload.followed, [{
    url: 'https://relay.example',
    pubkey: 'a'.repeat(64),
    addedAt: 1
  }])
  t.is(result.payload.running, true)

  t.alike(buildFederationSnapshotRoutePayload({ route: null, federation: null }), {
    ok: false,
    status: 404,
    payload: { error: 'unknown federation snapshot route' }
  })
})

test('api federation management: disabled profile rejects reads and writes before runtime access', async (t) => {
  const route = resolveFederationSnapshotRoute('GET', '/api/manage/federation')
  const federation = {
    snapshot () {
      t.fail('disabled federation read must not reach snapshot')
    },
    follow () {
      t.fail('disabled federation write must not mutate runtime state')
    },
    async save () {
      t.fail('disabled federation write must not persist runtime state')
    }
  }

  const read = buildFederationSnapshotRoutePayload({
    route,
    federation,
    disabled: true
  })
  t.is(read.ok, false)
  t.is(read.kind, 'disabled-profile')
  t.is(read.status, 409)
  t.ok(read.payload.error.startsWith('not-enabled: '), 'read returns formatted disabled-profile error')

  const write = await runFederationManagementAction({
    action: 'follow',
    body: {},
    federation,
    disabled: true
  })
  t.is(write.ok, false)
  t.is(write.kind, 'disabled-profile')
  t.is(write.status, 409)
  t.ok(write.payload.error.startsWith('not-enabled: '), 'write returns formatted disabled-profile error')
})

test('api federation management: route helper maps management paths to actions', (t) => {
  t.alike(resolveFederationManagementRoute('POST', '/api/manage/federation/follow'), {
    action: 'follow',
    authMessage: 'Unauthorized — API key required for /api/manage/federation/follow'
  })
  t.alike(resolveFederationManagementRoute('POST', '/api/manage/federation/mirror'), {
    action: 'mirror',
    authMessage: 'Unauthorized — API key required for /api/manage/federation/mirror'
  })
  t.alike(resolveFederationManagementRoute('POST', '/api/manage/federation/unfollow'), {
    action: 'unfollow',
    authMessage: 'Unauthorized — API key required for /api/manage/federation/unfollow'
  })
  t.alike(resolveFederationManagementRoute('POST', '/api/manage/federation/republish'), {
    action: 'republish',
    authMessage: 'Unauthorized — API key required for /api/manage/federation/republish'
  })
  t.alike(resolveFederationManagementRoute('POST', '/api/manage/federation/unrepublish'), {
    action: 'unrepublish',
    authMessage: 'Unauthorized — API key required for /api/manage/federation/unrepublish'
  })
  t.is(resolveFederationManagementRoute('GET', '/api/manage/federation/follow'), null)
  t.is(resolveFederationManagementRoute('POST', '/api/manage/federation'), null)
  t.is(resolveFederationManagementRoute('POST', '/api/manage/federation/follow/extra'), null)
  t.is(resolveFederationManagementRoute('POST', '/api/manage/catalog/follow'), null)
})

test('api federation management: route action helper dispatches management mutations', async (t) => {
  const pubkey = 'A'.repeat(64)
  const calls = []
  const federation = {
    snapshot () {
      calls.push(['snapshot'])
      return { followed: [] }
    },
    follow (url, opts) {
      calls.push(['follow', url, opts])
    },
    async save (opts) {
      calls.push(['save', opts])
    }
  }

  const result = await runFederationManagementRouteAction({
    route: resolveFederationManagementRoute('POST', '/api/manage/federation/follow'),
    body: { url: ' https://relay.example ', pubkey },
    federation
  })

  t.alike(result.payload, { ok: true, mode: 'follow', url: 'https://relay.example' })
  t.alike(calls, [
    ['snapshot'],
    ['follow', 'https://relay.example', { pubkey: pubkey.toLowerCase(), persist: false }],
    ['save', { throwOnError: true }]
  ])

  const unknown = await runFederationManagementRouteAction({
    route: null,
    body: { url: 'https://relay.example' },
    federation
  })
  t.is(unknown.status, 404)
  t.alike(unknown.payload, { error: 'unknown federation management route' })
})

test('api federation management: validates action bodies before federation access', async (t) => {
  const missingUrl = await runFederationManagementAction({
    action: 'follow',
    body: {},
    federation: null
  })
  t.is(missingUrl.status, 400)
  t.alike(missingUrl.payload, { error: 'url required' })

  const missingKey = await runFederationManagementAction({
    action: 'republish',
    body: {},
    federation: null
  })
  t.is(missingKey.status, 400)
  t.alike(missingKey.payload, { error: 'appKey required' })

  const malformedKey = await runFederationManagementAction({
    action: 'unrepublish',
    body: { appKey: 'xyz' },
    federation: null
  })
  t.is(malformedKey.status, 400)
  t.alike(malformedKey.payload, { error: 'appKey must be 64 hex characters' })

  const malformedMirrorPubkey = await runFederationManagementAction({
    action: 'mirror',
    body: { url: 'https://relay.example', pubkey: 'xyz' },
    federation: null
  })
  t.is(malformedMirrorPubkey.status, 400)
  t.alike(malformedMirrorPubkey.payload, { error: 'pubkey must be 64 hex characters' })

  const malformedSourcePubkey = await runFederationManagementAction({
    action: 'republish',
    body: { appKey: 'a'.repeat(64), sourcePubkey: 'xyz' },
    federation: null
  })
  t.is(malformedSourcePubkey.status, 400)
  t.alike(malformedSourcePubkey.payload, { error: 'sourcePubkey must be 64 hex characters' })

  const malformedChannel = await runFederationManagementAction({
    action: 'republish',
    body: { appKey: 'a'.repeat(64), channel: { name: 'stable' } },
    federation: null
  })
  t.is(malformedChannel.status, 400)
  t.alike(malformedChannel.payload, { error: 'channel must be a string' })

  const oversizedNote = await runFederationManagementAction({
    action: 'republish',
    body: { appKey: 'a'.repeat(64), note: 'x'.repeat(MAX_FEDERATION_NOTE_LENGTH + 1) },
    federation: null
  })
  t.is(oversizedNote.status, 400)
  t.alike(oversizedNote.payload, { error: `note exceeds max length (${MAX_FEDERATION_NOTE_LENGTH})` })

  const unavailable = await runFederationManagementAction({
    action: 'follow',
    body: { url: 'https://relay.example' },
    federation: null
  })
  t.is(unavailable.status, 503)
  t.alike(unavailable.payload, { error: 'Federation not initialized' })
})

test('api federation management: persists every mutation before success', async (t) => {
  const federation = new Federation({})
  const saved = []
  federation.save = async (opts = {}) => {
    t.is(opts.throwOnError, true)
    saved.push(federation.snapshot())
  }

  const followed = await runFederationManagementAction({
    action: 'follow',
    body: { url: 'http://relay-a.example' },
    federation
  })
  t.is(followed.ok, true)
  t.is(followed.payload.mode, 'follow')

  const mirrored = await runFederationManagementAction({
    action: 'mirror',
    body: { url: 'https://relay-b.example', pubkey: 'b'.repeat(64) },
    federation
  })
  t.is(mirrored.ok, true)
  t.is(mirrored.payload.mode, 'mirror')

  const republished = await runFederationManagementAction({
    action: 'republish',
    body: {
      appKey: 'a'.repeat(64),
      sourceUrl: 'https://source.example',
      sourcePubkey: 'c'.repeat(64),
      channel: 'stable',
      note: 'operator curated'
    },
    federation
  })
  t.is(republished.ok, true)
  t.is(republished.payload.appKey, 'a'.repeat(64))

  const unfollowed = await runFederationManagementAction({
    action: 'unfollow',
    body: { url: 'http://relay-a.example' },
    federation
  })
  t.is(unfollowed.ok, true)
  t.is(unfollowed.payload.removed, true)

  const unrepublished = await runFederationManagementAction({
    action: 'unrepublish',
    body: { appKey: 'a'.repeat(64) },
    federation
  })
  t.is(unrepublished.ok, true)
  t.is(unrepublished.payload.removed, true)

  const snap = federation.snapshot()
  t.alike(snap.followed, [])
  t.is(snap.mirrored.length, 1)
  t.is(snap.mirrored[0].url, 'https://relay-b.example')
  t.alike(snap.republished, [])
  t.is(saved.length, 5)
})

test('api federation management: canonicalizes optional trusted metadata before persistence', async (t) => {
  const federation = new Federation({})
  federation.save = async () => {}

  const mirrored = await runFederationManagementAction({
    action: 'mirror',
    body: { url: ' https://relay.example ', pubkey: 'B'.repeat(64) },
    federation
  })
  t.is(mirrored.ok, true)

  const republished = await runFederationManagementAction({
    action: 'republish',
    body: {
      appKey: 'A'.repeat(64),
      sourceUrl: ' https://source.example ',
      sourcePubkey: 'C'.repeat(64),
      channel: ' stable ',
      note: ' curated '
    },
    federation
  })
  t.is(republished.ok, true)

  const snap = federation.snapshot()
  t.is(snap.mirrored[0].url, 'https://relay.example')
  t.is(snap.mirrored[0].pubkey, 'b'.repeat(64))
  t.is(snap.republished[0].appKey, 'a'.repeat(64))
  t.is(snap.republished[0].sourceUrl, 'https://source.example')
  t.is(snap.republished[0].sourcePubkey, 'c'.repeat(64))
  t.is(snap.republished[0].channel, 'stable')
  t.is(snap.republished[0].note, 'curated')
})

test('api federation management: persistence failure restores snapshot', async (t) => {
  const federation = new Federation({})
  federation.follow('http://existing.example', { persist: false })
  const events = []
  federation.save = async () => {
    throw new Error('readonly federation volume')
  }

  const result = await runFederationManagementAction({
    action: 'follow',
    body: { url: 'http://new.example' },
    federation,
    emit: (event, payload) => events.push({ event, payload })
  })

  t.is(result.ok, false)
  t.is(result.kind, 'federation-persist')
  t.is(result.status, 500)
  t.is(result.payload.errorCode, 'persist-failed')
  t.ok(result.payload.error.startsWith('persist-failed: '), 'public payload is stable and prefixed')
  t.absent(result.payload.error.includes('readonly federation volume'), 'public payload does not leak local storage error')
  t.is(events.length, 1)
  t.is(events[0].event, 'federation-persist-error')
  t.is(events[0].payload.message, 'readonly federation volume')
  const snap = federation.snapshot()
  t.is(snap.followed.length, 1)
  t.is(snap.followed[0].url, 'http://existing.example')
})

test('api federation management: federation validation failures roll back without save', async (t) => {
  const federation = new Federation({})
  let saveCalls = 0
  federation.save = async () => { saveCalls++ }

  const result = await runFederationManagementAction({
    action: 'republish',
    body: {
      appKey: 'd'.repeat(64),
      sourceUrl: 'javascript:alert(1)'
    },
    federation
  })

  t.is(result.ok, false)
  t.is(result.status, 400)
  t.ok(result.payload.error.startsWith('Federation:'))
  t.alike(federation.snapshot().republished, [])
  t.is(saveCalls, 0)
})

test('api federation management: rollback errors are emitted without hiding persistence failure', async (t) => {
  const restoreError = new Error('restore failed')
  const persistError = new Error('readonly federation volume')
  const events = []
  const federation = {
    snapshot: () => ({ followed: [] }),
    follow () {},
    async save () { throw persistError },
    restoreSnapshot () { throw restoreError }
  }

  const result = await runFederationManagementAction({
    action: 'follow',
    body: { url: 'https://relay.example' },
    federation,
    emit: (event, payload) => events.push({ event, payload })
  })

  t.is(result.ok, false)
  t.is(result.kind, 'federation-persist')
  t.is(result.status, 500)
  t.is(result.payload.errorCode, 'persist-failed')
  t.is(events.length, 2)
  t.is(events[0].event, 'federation-rollback-error')
  t.is(events[0].payload.error, restoreError)
  t.is(events[1].event, 'federation-persist-error')
  t.is(events[1].payload.error, persistError)
})

test('api federation management: persist failure mapper emits internal diagnostics', (t) => {
  const err = new Error('readonly federation volume')
  const events = []
  const result = federationPersistFailureResult({
    error: err,
    emit: (event, payload) => events.push({ event, payload })
  })

  t.is(result.ok, false)
  t.is(result.kind, 'federation-persist')
  t.is(result.status, 500)
  t.is(result.payload.errorCode, 'persist-failed')
  t.ok(result.payload.error.startsWith('persist-failed: '))
  t.absent(result.payload.error.includes('readonly federation volume'))
  t.alike(events, [{
    event: 'federation-persist-error',
    payload: {
      message: 'readonly federation volume',
      error: err
    }
  }])
})

test('api federation management: snapshot payload sanitizes remote federation state', (t) => {
  const followed = [
    { url: 'https://relay-a.example', pubkey: 'A'.repeat(64), addedAt: 1, token: 'do-not-leak' },
    { url: 'https://user:pass@relay-secret.example', pubkey: 'b'.repeat(64), addedAt: 2 },
    { url: 'javascript:alert(1)', pubkey: 'c'.repeat(64), addedAt: 3 }
  ]
  for (let i = 0; i < MAX_FEDERATION_SNAPSHOT_RELAYS + 4; i++) {
    followed.push({
      url: `https://relay-${i}.example`,
      pubkey: (i % 16).toString(16).repeat(64),
      addedAt: i + 10
    })
  }

  const peerApps = [
    {
      appKey: 'D'.repeat(64),
      publisherPubkey: 'E'.repeat(64),
      type: 'app',
      privacyTier: 'public',
      storageClass: 'archive',
      availabilityClass: 'standard',
      blind: true,
      secretToken: 'do-not-leak-app'
    },
    { appKey: 'not-a-key', name: 'ignored', secret: 'hidden' }
  ]
  for (let i = 0; i < MAX_FEDERATION_SNAPSHOT_PEER_APPS + 4; i++) {
    peerApps.push({
      appKey: (i % 16).toString(16).repeat(64),
      publisherPubkey: 'f'.repeat(64),
      type: 'drive'
    })
  }

  const federation = {
    snapshot () {
      return {
        followed,
        mirrored: [
          { url: 'http://mirror.example', pubkey: 'B'.repeat(64), addedAt: 5, privateKey: 'hidden' },
          { url: 'file:///etc/passwd', pubkey: 'c'.repeat(64), addedAt: 6 }
        ],
        republished: [
          {
            appKey: 'C'.repeat(64),
            sourceUrl: 'https://source.example',
            sourcePubkey: 'D'.repeat(64),
            channel: 'stable',
            note: 'operator curated',
            addedAt: 7,
            secret: 'do-not-leak-note'
          },
          {
            appKey: 'E'.repeat(64),
            sourceUrl: 'https://user:pass@source-secret.example',
            sourcePubkey: 'not-a-key',
            channel: 'bad\nchannel',
            note: 'bad\nnote',
            addedAt: -1
          },
          { appKey: 'not-a-key', sourceUrl: 'https://ignored.example' }
        ],
        followIntervalMs: 30_000,
        running: true,
        peerCatalogs: [
          {
            url: 'https://peer.example',
            pubkey: 'F'.repeat(64),
            region: 'EU',
            operator: 'operator-a',
            fetchedAt: 8,
            apps: peerApps,
            secret: 'do-not-leak-peer'
          },
          { url: 'https://user:pass@peer-secret.example', apps: [] }
        ]
      }
    }
  }

  const result = buildFederationSnapshotPayload({ federation })
  t.is(result.ok, true)
  const payload = result.payload

  t.is(payload.followed.length, MAX_FEDERATION_SNAPSHOT_RELAYS)
  t.is(payload.followedTotal, MAX_FEDERATION_SNAPSHOT_RELAYS + 7)
  t.ok(payload.followedTruncated)
  t.alike(payload.followed[0], {
    url: 'https://relay-a.example',
    pubkey: 'a'.repeat(64),
    addedAt: 1
  })
  t.absent(payload.followed.some(entry => entry.url.includes('@')), 'credential URLs are omitted')

  t.alike(payload.mirrored, [{
    url: 'http://mirror.example',
    pubkey: 'b'.repeat(64),
    addedAt: 5
  }])
  t.is(payload.mirroredTotal, 2)
  t.ok(payload.mirroredTruncated)

  t.alike(payload.republished[0], {
    appKey: 'c'.repeat(64),
    sourceUrl: 'https://source.example',
    sourcePubkey: 'd'.repeat(64),
    channel: 'stable',
    note: 'operator curated',
    addedAt: 7
  })
  t.alike(payload.republished[1], {
    appKey: 'e'.repeat(64),
    sourceUrl: null,
    sourcePubkey: null,
    channel: null,
    note: null,
    addedAt: null
  })
  t.is(payload.republishedTotal, 3)
  t.ok(payload.republishedTruncated)

  t.is(payload.peerCatalogs.length, 1)
  t.is(payload.peerCatalogsTotal, 2)
  t.ok(payload.peerCatalogsTruncated)
  t.is(payload.peerCatalogs[0].apps.length, MAX_FEDERATION_SNAPSHOT_PEER_APPS)
  t.is(payload.peerCatalogs[0].appsTotal, MAX_FEDERATION_SNAPSHOT_PEER_APPS + 6)
  t.ok(payload.peerCatalogs[0].appsTruncated)
  t.alike(payload.peerCatalogs[0].apps[0], {
    appKey: 'd'.repeat(64),
    publisherPubkey: 'e'.repeat(64),
    type: 'app',
    privacyTier: 'public',
    storageClass: 'archive',
    availabilityClass: 'standard',
    blind: true
  })

  const json = JSON.stringify(payload)
  for (const hidden of ['do-not-leak', 'do-not-leak-app', 'do-not-leak-note', 'do-not-leak-peer', 'privateKey', 'secretToken', 'user:pass']) {
    t.absent(json.includes(hidden), hidden + ' omitted from federation snapshot')
  }
})