OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git / test / unit / deploy-hook.test.js

150 lines · javascript ref:
/**
 * Unit tests for lib/ci/deploy-hook.js
 *
 * Tests deploy lock mechanism and autoDeployAfterPush guard logic.
 */

import test from 'brittle'
import { executeDeploy, autoDeployAfterPush } from '../../lib/ci/deploy-hook.js'

// ========================================
// executeDeploy - Input Validation
// ========================================

test('executeDeploy - rejects when repo not found', async (t) => {
  const mockServer = {
    repoRegistry: {
      get: async () => null
    }
  }

  try {
    await executeDeploy(mockServer, 'nonexistent')
    t.fail('should have thrown')
  } catch (err) {
    t.ok(err.message.includes('not found'), 'error mentions not found')
  }
})

test('executeDeploy - rejects when deploy not configured', async (t) => {
  const mockServer = {
    repoRegistry: {
      get: async () => ({ name: 'repo', deploy: null })
    }
  }

  try {
    await executeDeploy(mockServer, 'repo')
    t.fail('should have thrown')
  } catch (err) {
    t.ok(err.message.includes('not configured'), 'error mentions not configured')
  }
})

test('executeDeploy - rejects when deploy not enabled', async (t) => {
  const mockServer = {
    repoRegistry: {
      get: async () => ({ name: 'repo', deploy: { enabled: false } })
    }
  }

  try {
    await executeDeploy(mockServer, 'repo')
    t.fail('should have thrown')
  } catch (err) {
    t.ok(err.message.includes('not configured'), 'error mentions not configured')
  }
})

// ========================================
// Deploy Lock
// ========================================

test('executeDeploy - prevents concurrent deploys', async (t) => {
  let deployStarted = false
  const mockServer = {
    repoRegistry: {
      get: async () => ({ name: 'repo', path: '/tmp/test', deploy: { enabled: true, channel: 'test' } }),
      execGit: async () => { deployStarted = true; await new Promise(r => setTimeout(r, 100)); return '' },
      register: async () => {},
      listAll: async () => []
    },
    _deployLocks: new Map(),
    storage: '/tmp/test-storage',
    registry: { put: async () => {} },
    deployer: {
      stage: async () => ({ version: 1, key: 'abc' }),
      release: async () => ({ version: 1 }),
      seed: async () => {},
      unseed: async () => {}
    }
  }

  // Start first deploy (will hang on execGit)
  const first = executeDeploy(mockServer, 'repo').catch(() => {})

  // Try second deploy immediately
  try {
    await executeDeploy(mockServer, 'repo')
    t.fail('should have thrown')
  } catch (err) {
    t.ok(err.message.includes('already in progress'), 'second deploy rejected')
  }

  await first.catch(() => {})
})

// ========================================
// autoDeployAfterPush - Guard Logic
// ========================================

test('autoDeployAfterPush - skips when server is null', (t) => {
  // Should not throw
  autoDeployAfterPush(null, 'repo')
  t.pass('no error when server is null')
})

test('autoDeployAfterPush - skips when repoRegistry is missing', (t) => {
  autoDeployAfterPush({}, 'repo')
  t.pass('no error when repoRegistry is missing')
})

test('autoDeployAfterPush - skips when deploy not configured', (t) => {
  const mockServer = {
    repoRegistry: {
      get: async () => ({ name: 'repo', deploy: null })
    }
  }

  autoDeployAfterPush(mockServer, 'repo')
  t.pass('no error when deploy not configured')
})

test('autoDeployAfterPush - skips when autoDeploy is false', (t) => {
  const mockServer = {
    repoRegistry: {
      get: async () => ({
        name: 'repo',
        deploy: { enabled: true, channel: 'test', autoDeploy: false }
      })
    }
  }

  autoDeployAfterPush(mockServer, 'repo')
  t.pass('no error when autoDeploy is false')
})

test('autoDeployAfterPush - skips when no channel configured', (t) => {
  const mockServer = {
    repoRegistry: {
      get: async () => ({
        name: 'repo',
        deploy: { enabled: true, channel: null }
      })
    }
  }

  autoDeployAfterPush(mockServer, 'repo')
  t.pass('no error when no channel')
})