import { describe, expect, test } from 'bun:test'; import { classifyVersionDelta, parseVersion, resolveDeployPosture } from './deploy-posture'; describe('parseVersion', () => { test('parses major.minor.patch+build', () => { expect(parseVersion('1.2.3+28')).toEqual({ major: 1, minor: 2, patch: 3, prerelease: '', build: '28', }); }); test('parses a prerelease', () => { expect(parseVersion('0.5.0-alpha.9')).toEqual({ major: 0, minor: 5, patch: 0, prerelease: 'alpha.9', build: '', }); }); test('tolerates a bare version', () => { expect(parseVersion('2.0.0')).toEqual({ major: 2, minor: 0, patch: 0, prerelease: '', build: '', }); }); }); describe('classifyVersionDelta (highest-order component that changed)', () => { test('major / minor / patch', () => { expect(classifyVersionDelta('1.0.0', '2.0.0')).toBe('major'); expect(classifyVersionDelta('1.0.0', '1.1.0')).toBe('minor'); expect(classifyVersionDelta('1.0.0', '1.0.1')).toBe('patch'); }); test('build-metadata-only change is a revision', () => { expect(classifyVersionDelta('1.0.1+27', '1.0.1+28')).toBe('revision'); }); test('prerelease-only change counts as patch (still fast)', () => { expect(classifyVersionDelta('0.5.0-alpha.8', '0.5.0-alpha.9')).toBe('patch'); }); test('identical versions → none', () => { expect(classifyVersionDelta('1.0.1+28', '1.0.1+28')).toBe('none'); }); test('highest-order wins when several components differ', () => { expect(classifyVersionDelta('1.0.1+5', '1.1.0+1')).toBe('minor'); }); }); describe('resolveDeployPosture (precedence)', () => { test('default by-semver: revision + patch ⇒ fast', () => { expect(resolveDeployPosture({ installed: '1.0.1+1', next: '1.0.1+2' }).posture).toBe('fast'); expect(resolveDeployPosture({ installed: '1.0.1', next: '1.0.2' }).posture).toBe('fast'); }); test('default by-semver: minor + major ⇒ safe', () => { expect(resolveDeployPosture({ installed: '1.0.0', next: '1.1.0' }).posture).toBe('safe'); expect(resolveDeployPosture({ installed: '1.0.0', next: '2.0.0' }).posture).toBe('safe'); }); test('per-release deploy_posture overrides the semver default', () => { // a minor bump would default to safe; the release pins fast. const r = resolveDeployPosture({ installed: '1.0.0', next: '1.1.0', releasePosture: 'fast' }); expect(r.posture).toBe('fast'); expect(r.reason).toContain('release deploy_posture'); }); test('module always-safe is a hard floor — beats a per-release fast', () => { const r = resolveDeployPosture({ installed: '1.0.1', next: '1.0.2', releasePosture: 'fast', modulePolicy: 'always-safe', }); expect(r.posture).toBe('safe'); expect(r.reason).toContain('always-safe'); }); test('module always-fast forces fast on a minor bump (when no release override)', () => { expect( resolveDeployPosture({ installed: '1.0.0', next: '1.1.0', modulePolicy: 'always-fast' }) .posture, ).toBe('fast'); }); test('a per-release safe still beats always-fast (release intent wins below the floor)', () => { expect( resolveDeployPosture({ installed: '1.0.1', next: '1.0.2', releasePosture: 'safe', modulePolicy: 'always-fast', }).posture, ).toBe('safe'); }); });