import { describe, expect, it } from 'vitest'; import { isCleanOneShotCompletion, isNoRestartPolicy, normalizeComposeRestartIntent, } from '../utils/oneShotCompletion'; describe('isNoRestartPolicy', () => { it('accepts only explicit no', () => { expect(isNoRestartPolicy('no')).toBe(true); }); it('rejects absent, empty, and restarting policies', () => { expect(isNoRestartPolicy(undefined)).toBe(false); expect(isNoRestartPolicy(null)).toBe(false); expect(isNoRestartPolicy('')).toBe(false); expect(isNoRestartPolicy('unless-stopped')).toBe(false); expect(isNoRestartPolicy('always')).toBe(false); expect(isNoRestartPolicy('on-failure')).toBe(false); }); }); describe('isCleanOneShotCompletion', () => { const clean = { state: 'exited', exitCode: 0 as number | null, restartPolicy: 'no' as string | null | undefined, }; it('returns true only for exited + exit 0 + explicit restart no', () => { expect(isCleanOneShotCompletion(clean)).toBe(true); }); it('returns false for absent or empty declared restart', () => { expect(isCleanOneShotCompletion({ ...clean, restartPolicy: undefined })).toBe(false); expect(isCleanOneShotCompletion({ ...clean, restartPolicy: null })).toBe(false); expect(isCleanOneShotCompletion({ ...clean, restartPolicy: '' })).toBe(false); }); it('returns false for non-exited states', () => { expect(isCleanOneShotCompletion({ ...clean, state: 'running' })).toBe(false); expect(isCleanOneShotCompletion({ ...clean, state: 'restarting' })).toBe(false); expect(isCleanOneShotCompletion({ ...clean, state: 'created' })).toBe(false); expect(isCleanOneShotCompletion({ ...clean, state: 'dead' })).toBe(false); }); it('returns false for non-zero and null exit codes (fail closed)', () => { expect(isCleanOneShotCompletion({ ...clean, exitCode: 1 })).toBe(false); expect(isCleanOneShotCompletion({ ...clean, exitCode: 137 })).toBe(false); expect(isCleanOneShotCompletion({ ...clean, exitCode: null })).toBe(false); }); it('returns false for restarting policies even with exit 0', () => { expect(isCleanOneShotCompletion({ ...clean, restartPolicy: 'unless-stopped' })).toBe(false); expect(isCleanOneShotCompletion({ ...clean, restartPolicy: 'always' })).toBe(false); expect(isCleanOneShotCompletion({ ...clean, restartPolicy: 'on-failure' })).toBe(false); }); }); describe('normalizeComposeRestartIntent', () => { it('falls back to service restart when deploy.restart_policy is unset', () => { expect(normalizeComposeRestartIntent('no')).toBe('no'); expect(normalizeComposeRestartIntent('unless-stopped')).toBe('unless-stopped'); expect(normalizeComposeRestartIntent(undefined)).toBeNull(); expect(normalizeComposeRestartIntent(null, {})).toBeNull(); expect(normalizeComposeRestartIntent('always', { replicas: 2 })).toBe('always'); }); it('maps deploy.restart_policy.condition with Compose defaults and precedence', () => { expect(normalizeComposeRestartIntent('unless-stopped', { restart_policy: { condition: 'none' }, })).toBe('no'); expect(normalizeComposeRestartIntent(null, { restart_policy: { condition: 'any' }, })).toBe('always'); expect(normalizeComposeRestartIntent('no', { restart_policy: { condition: 'on-failure' }, })).toBe('on-failure'); expect(normalizeComposeRestartIntent('no', { restart_policy: {}, })).toBe('always'); }); it('fails closed on malformed restart_policy shapes', () => { expect(normalizeComposeRestartIntent('no', { restart_policy: null })).toBe('always'); expect(normalizeComposeRestartIntent('no', { restart_policy: 'none' })).toBe('always'); expect(normalizeComposeRestartIntent('no', { restart_policy: [] })).toBe('always'); expect(normalizeComposeRestartIntent('no', { restart_policy: { condition: 'weird' }, })).toBe('always'); }); });