OverviewFilesCommitsIssuesDocsReleasesPeers

P2P-Hiverelay / test / unit / api-mode-transport.test.js

322 lines · javascript ref:
import test from 'brittle'
import {
  AVAILABLE_MODES,
  resolveModeTransportManagementRoute,
  runModeTransportManagementRouteAction,
  runModeSwitchAction,
  runTransportToggleAction
} from '../../packages/core/core/relay-node/api-mode-transport.js'

test('api mode transport: route helper maps exact mode and transport mutation routes', (t) => {
  t.alike(resolveModeTransportManagementRoute('POST', '/api/manage/mode'), {
    kind: 'mode-switch'
  })
  t.alike(resolveModeTransportManagementRoute('POST', '/api/manage/transport'), {
    kind: 'transport-toggle'
  })

  t.is(resolveModeTransportManagementRoute('GET', '/api/manage/mode'), null)
  t.is(resolveModeTransportManagementRoute('POST', '/api/manage/modes'), null)
  t.is(resolveModeTransportManagementRoute('POST', '/api/manage/transport/tor'), null)
})

test('api mode transport: route action helper dispatches mode and transport mutations', async (t) => {
  const persisted = []
  const node = {
    config: { mode: 'standard' },
    mode: 'standard',
    _operatingMode: 'standard',
    async applyMode (mode, overrides) {
      this.mode = mode
      this._operatingMode = mode
      this.config = { ...this.config, mode, ...overrides }
    }
  }
  const persistConfig = async () => {
    persisted.push({ ...node.config })
  }

  let result = await runModeTransportManagementRouteAction({
    route: resolveModeTransportManagementRoute('POST', '/api/manage/mode'),
    body: { mode: 'homehive' },
    node,
    config: node.config,
    persistConfig
  })
  t.is(result.ok, true)
  t.is(result.payload.mode, 'homehive')
  t.is(node.mode, 'homehive')

  result = await runModeTransportManagementRouteAction({
    route: resolveModeTransportManagementRoute('POST', '/api/manage/transport'),
    body: { transport: 'tor', enabled: false },
    node,
    config: node.config,
    persistConfig
  })
  t.is(result.ok, true)
  t.alike(result.payload, {
    ok: true,
    transport: 'tor',
    enabled: false,
    note: 'Transport changes may require a node restart to take full effect'
  })
  t.is(node.config.transports.tor, false)
  t.is(persisted.length, 2)

  const unknown = await runModeTransportManagementRouteAction({
    route: { kind: 'unknown' },
    body: {},
    node,
    config: node.config,
    persistConfig
  })
  t.is(unknown.status, 404)
  t.alike(unknown.payload, { error: 'unknown mode/transport management route' })
})

test('api mode transport: validates mode action before applying', async (t) => {
  let applyCalls = 0
  const node = {
    config: {},
    async applyMode () {
      applyCalls++
    }
  }

  t.ok(AVAILABLE_MODES.includes('homehive'))
  t.ok(AVAILABLE_MODES.includes('relaykernel'))

  const missing = await runModeSwitchAction({ body: {}, node })
  t.is(missing.status, 400)
  t.alike(missing.payload, { error: 'mode required' })

  const unknown = await runModeSwitchAction({ body: { mode: 'warp' }, node })
  t.is(unknown.status, 400)
  t.is(unknown.payload.error, 'Unknown mode: warp')
  t.alike(unknown.payload.available, AVAILABLE_MODES)

  const malformed = await runModeSwitchAction({
    body: { mode: 'homehive', maxConnections: '12abc' },
    node
  })
  t.is(malformed.status, 400)
  t.alike(malformed.payload, { error: 'maxConnections must be a valid integer' })
  t.is(applyCalls, 0)

  for (const [field, value] of [
    ['discovery', []],
    ['access', null],
    ['pairing', 'enabled']
  ]) {
    const out = await runModeSwitchAction({
      body: { mode: 'homehive', [field]: value },
      node
    })
    t.is(out.status, 400)
    t.alike(out.payload, { error: `${field} must be an object` })
    t.is(applyCalls, 0)
  }

  const malformedBoolean = await runModeSwitchAction({
    body: { mode: 'homehive', registryAutoAccept: 'false' },
    node
  })
  t.is(malformedBoolean.status, 400)
  t.alike(malformedBoolean.payload, { error: 'registryAutoAccept must be a boolean' })
  t.is(applyCalls, 0)
})

test('api mode transport: applies mode overrides and persists before success', async (t) => {
  const persisted = []
  const node = {
    config: { mode: 'standard' },
    mode: 'standard',
    _operatingMode: 'standard',
    async applyMode (mode, overrides) {
      this.mode = mode
      this._operatingMode = mode
      this.config = { ...this.config, mode, ...overrides }
    }
  }

  const result = await runModeSwitchAction({
    body: {
      mode: 'homehive',
      maxConnections: '12',
      maxRelayBandwidthMbps: '12.5',
      discovery: { mdns: false },
      access: { privateMode: true },
      pairing: { enabled: true },
      registryAutoAccept: false
    },
    node,
    persistConfig: async () => {
      persisted.push({ ...node.config })
    }
  })

  t.is(result.ok, true)
  t.is(result.payload.mode, 'homehive')
  t.is(result.payload.note, 'HomeHive mode active — low resource, LAN-priority')
  t.is(node.config.maxConnections, 12)
  t.is(node.config.maxRelayBandwidthMbps, 12.5)
  t.alike(node.config.discovery, { mdns: false })
  t.alike(node.config.access, { privateMode: true })
  t.alike(node.config.pairing, { enabled: true })
  t.is(node.config.registryAutoAccept, false)
  t.is(persisted.length, 1)
  t.is(persisted[0].mode, 'homehive')
})

test('api mode transport: applies relaykernel mode', async (t) => {
  const node = {
    config: { mode: 'relay-core' },
    mode: 'relay-core',
    _operatingMode: 'relay-core',
    async applyMode (mode, overrides) {
      this.mode = mode
      this._operatingMode = mode
      this.config = { ...this.config, mode, ...overrides }
    }
  }

  const result = await runModeSwitchAction({
    body: { mode: 'relaykernel' },
    node
  })

  t.is(result.ok, true)
  t.is(result.payload.mode, 'relaykernel')
  t.is(result.payload.note, 'RelayKernel profile active — seed/proof/circuit/meta/accounting only')
  t.is(node.mode, 'relaykernel')
})

test('api mode transport: rolls back mode when persistence fails', async (t) => {
  const previousConfig = { mode: 'standard' }
  let syncCalls = 0
  const events = []
  const node = {
    config: previousConfig,
    mode: 'standard',
    _operatingMode: 'standard',
    running: true,
    async applyMode (mode, overrides) {
      this.mode = mode
      this._operatingMode = mode
      this.config = { ...this.config, mode, ...overrides }
    },
    async _syncAccessControl () {
      syncCalls++
    }
  }

  const result = await runModeSwitchAction({
    body: { mode: 'homehive', maxConnections: 12 },
    node,
    persistConfig: async () => { throw new Error('readonly config') },
    emit: (event, payload) => events.push({ event, payload })
  })

  t.is(result.ok, false)
  t.is(result.kind, 'config-persist')
  t.is(result.error.message, 'readonly config')
  t.is(node.config, previousConfig)
  t.is(node.mode, 'standard')
  t.is(node._operatingMode, 'standard')
  t.is(syncCalls, 1)
  t.alike(events, [])
})

test('api mode transport: reports applyMode failures as bad requests', async (t) => {
  let persisted = false
  const node = {
    config: {},
    async applyMode () {
      throw new Error('mode unavailable')
    }
  }

  const result = await runModeSwitchAction({
    body: { mode: 'homehive' },
    node,
    persistConfig: async () => { persisted = true }
  })

  t.is(result.ok, false)
  t.is(result.kind, 'apply-mode')
  t.is(result.status, 400)
  t.alike(result.payload, { error: 'mode unavailable' })
  t.is(persisted, false)
})

test('api mode transport: validates transport names before mutation', async (t) => {
  const config = { transports: { udp: true } }
  let persisted = false

  for (const transport of [null, '__proto__', 'constructor', 'prototype', 'bad.name', 'bad name']) {
    const result = await runTransportToggleAction({
      body: { transport, enabled: true },
      config,
      persistConfig: async () => { persisted = true }
    })
    t.is(result.status, 400)
  }

  t.alike(config.transports, { udp: true })
  t.is(persisted, false)

  const missingConfig = {}
  const malformedEnabled = await runTransportToggleAction({
    body: { transport: 'tor', enabled: 'false' },
    config: missingConfig,
    persistConfig: async () => { persisted = true }
  })
  t.is(malformedEnabled.status, 400)
  t.alike(malformedEnabled.payload, { error: 'enabled must be a boolean' })
  t.absent(missingConfig.transports)
  t.is(persisted, false)
})

test('api mode transport: persists transport toggles and rolls back failures', async (t) => {
  {
    const config = {}
    const persisted = []
    const result = await runTransportToggleAction({
      body: { transport: 'tor', enabled: true },
      config,
      persistConfig: async () => persisted.push({ ...config.transports })
    })

    t.is(result.ok, true)
    t.is(config.transports.udp, true)
    t.is(config.transports.tor, true)
    t.alike(persisted, [{ udp: true, tor: true }])
  }

  {
    const config = {}
    const result = await runTransportToggleAction({
      body: { transport: 'tor', enabled: true },
      config,
      persistConfig: async () => { throw new Error('readonly config') }
    })

    t.is(result.ok, false)
    t.is(result.kind, 'config-persist')
    t.absent(config.transports)
  }

  {
    const config = { transports: { udp: true } }
    const result = await runTransportToggleAction({
      body: { transport: 'tor', enabled: true },
      config,
      persistConfig: async () => { throw new Error('readonly config') }
    })

    t.is(result.ok, false)
    t.alike(config.transports, { udp: true })
  }
})