OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git / test / unit / pear-deployer.test.js

202 lines · javascript ref:
/**
 * Unit tests for PearDeployer and deploy-hook
 *
 * Tests deployer construction, state management, and deploy-hook logic.
 * IPC calls are not tested here (require running Pear sidecar).
 */

import test from 'brittle'
import { PearDeployer } from '../../lib/ci/pear-deployer.js'
import { getDeployLog } from '../../lib/ci/deploy-hook.js'

// ========================================
// PearDeployer construction
// ========================================

test('PearDeployer - constructs with defaults', async (t) => {
  const deployer = new PearDeployer()
  t.ok(deployer, 'deployer created')
  t.is(deployer._ready, false, 'not ready until .ready() called')
  t.ok(deployer._seedStreams instanceof Map, 'seed streams map exists')
  t.is(deployer._seedStreams.size, 0, 'no seed streams initially')
})

test('PearDeployer - constructs with custom socket path', async (t) => {
  const deployer = new PearDeployer({ socketPath: '/tmp/test.sock' })
  t.is(deployer._socketPath, '/tmp/test.sock', 'custom socket path set')
})

test('PearDeployer - default socket path is ~/.config/pear/pear.sock', async (t) => {
  const deployer = new PearDeployer()
  t.ok(deployer._socketPath.endsWith('/pear.sock'), 'socket path ends with pear.sock')
  t.ok(deployer._socketPath.includes('.config/pear'), 'socket path includes .config/pear')
})

// ========================================
// PearDeployer state management
// ========================================

test('PearDeployer - isSeeding returns false for unknown channel', async (t) => {
  const deployer = new PearDeployer()
  t.is(deployer.isSeeding('nonexistent'), false)
})

test('PearDeployer - getSeededChannels returns empty array initially', async (t) => {
  const deployer = new PearDeployer()
  const channels = deployer.getSeededChannels()
  t.ok(Array.isArray(channels), 'returns array')
  t.is(channels.length, 0, 'empty initially')
})

test('PearDeployer - unseed is safe on unknown channel', async (t) => {
  const deployer = new PearDeployer()
  await deployer.unseed('nonexistent') // should not throw
  t.pass('unseed on unknown channel is safe')
})

test('PearDeployer - close is safe when not ready', async (t) => {
  const deployer = new PearDeployer()
  await deployer.close() // should not throw
  t.pass('close when not ready is safe')
  t.is(deployer._ready, false, 'still not ready')
})

// ========================================
// PearDeployer guards
// ========================================

test('PearDeployer - stage throws when not ready', async (t) => {
  const deployer = new PearDeployer()
  try {
    await deployer.stage('/tmp/test', 'test-channel')
    t.fail('should have thrown')
  } catch (err) {
    t.is(err.message, 'PearDeployer not ready')
  }
})

test('PearDeployer - release throws when not ready', async (t) => {
  const deployer = new PearDeployer()
  try {
    await deployer.release('test-channel')
    t.fail('should have thrown')
  } catch (err) {
    t.is(err.message, 'PearDeployer not ready')
  }
})

test('PearDeployer - seed throws when not ready', async (t) => {
  const deployer = new PearDeployer()
  try {
    await deployer.seed('test-channel')
    t.fail('should have thrown')
  } catch (err) {
    t.is(err.message, 'PearDeployer not ready')
  }
})

test('PearDeployer - info throws when not ready', async (t) => {
  const deployer = new PearDeployer()
  try {
    await deployer.info('test-channel')
    t.fail('should have thrown')
  } catch (err) {
    t.is(err.message, 'PearDeployer not ready')
  }
})

// ========================================
// Deploy config schema
// ========================================

test('deploy config - has expected shape', async (t) => {
  const config = {
    enabled: true,
    channel: 'test-app',
    autoRelease: true,
    autoSeed: true,
    configuredAt: Date.now(),
    lastDeploy: null,
    lastVersion: null,
    lastKey: null
  }

  t.is(typeof config.enabled, 'boolean')
  t.is(typeof config.channel, 'string')
  t.is(typeof config.autoRelease, 'boolean')
  t.is(typeof config.autoSeed, 'boolean')
  t.is(typeof config.configuredAt, 'number')
  t.is(config.lastDeploy, null)
  t.is(config.lastVersion, null)
  t.is(config.lastKey, null)
})

// ========================================
// Deploy log with mock registry
// ========================================

test('getDeployLog - returns empty array for no entries', async (t) => {
  const mockServer = {
    registry: {
      createReadStream () {
        return {
          async * [Symbol.asyncIterator] () {
            // No entries
          }
        }
      }
    }
  }

  const entries = await getDeployLog(mockServer, 'test-repo')
  t.ok(Array.isArray(entries), 'returns array')
  t.is(entries.length, 0, 'empty when no entries')
})

test('getDeployLog - returns entries in reverse order', async (t) => {
  const mockEntries = [
    { value: { repoName: 'test', version: 3, timestamp: 3000 } },
    { value: { repoName: 'test', version: 2, timestamp: 2000 } },
    { value: { repoName: 'test', version: 1, timestamp: 1000 } }
  ]

  const mockServer = {
    registry: {
      createReadStream (opts) {
        t.ok(opts.reverse, 'requests reverse order')
        t.ok(opts.gte.startsWith('deploy-log:test-repo:'), 'correct key prefix')
        return {
          async * [Symbol.asyncIterator] () {
            for (const entry of mockEntries) {
              yield entry
            }
          }
        }
      }
    }
  }

  const entries = await getDeployLog(mockServer, 'test-repo')
  t.is(entries.length, 3, 'returns all entries')
  t.is(entries[0].version, 3, 'first entry is newest')
  t.is(entries[2].version, 1, 'last entry is oldest')
})

test('getDeployLog - respects limit option', async (t) => {
  const mockServer = {
    registry: {
      createReadStream (opts) {
        t.is(opts.limit, 5, 'passes limit to stream')
        return {
          async * [Symbol.asyncIterator] () {
            // empty
          }
        }
      }
    }
  }

  await getDeployLog(mockServer, 'test-repo', { limit: 5 })
  t.pass('limit passed correctly')
})