/**
* 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')
})