import { describe, it, expect, vi } from 'vitest'; import { detectImageUpdate } from '../services/imageUpdateDetect'; import { computeImagePreview } from '../services/UpdatePreviewService'; import type { DigestComparisonResult } from '../services/registry-api'; const PLATFORM = { os: 'linux', architecture: 'amd64' }; const LOCAL_DIGEST = `sha256:${'a'.repeat(64)}`; const CREDENTIALS = { username: 'u', password: 'p' }; const IMAGE = 'nginx:1.2.3'; /** * COR-1 regression: persisted sidebar status (via detectImageUpdate / * checkImage) and Fleet/Anatomy preview (computeImagePreview) must agree when * the declared-tag digest matches but a higher semantic tag exists. */ describe('cross-surface update detection (digest match + higher semver)', () => { async function runDetectionAndPreview( comparison: DigestComparisonResult, tags: string[], localDigests: string[] = [LOCAL_DIGEST], ) { const compareDigest = vi.fn().mockResolvedValue(comparison); const listRegistryTagsResult = vi.fn().mockResolvedValue({ ok: true, tags }); const detection = await detectImageUpdate({ localDigests, platform: PLATFORM, registry: 'registry-1.docker.io', repo: 'library/nginx', tag: '1.2.3', credentials: CREDENTIALS, deps: { compareDigest, listRegistryTagsResult }, }); const preview = await computeImagePreview('app', IMAGE, { getCredentials: vi.fn().mockResolvedValue(CREDENTIALS), getLocalDigest: vi.fn().mockResolvedValue({ digests: localDigests, platform: PLATFORM, emptyReason: null }), compareDigest, listRegistryTagsResult, }); return { detection, preview, compareDigest }; } it('shared detector and preview both report hasUpdate for app:1.2.3 when 1.2.4 exists', async () => { const { detection, preview } = await runDetectionAndPreview( { kind: 'match' }, ['1.2.3', '1.2.4'], ); expect(detection.hasUpdate).toBe(true); expect(detection.nextTag).toBe('1.2.4'); expect(detection.digestUpdate).toBe(false); expect(preview.has_update).toBe(true); expect(preview.next_tag).toBe('1.2.4'); expect(preview.has_update).toBe(detection.hasUpdate); }); it('shared detector and preview both clear when digest matches and no higher tag exists', async () => { const { detection, preview } = await runDetectionAndPreview({ kind: 'match' }, ['1.2.3']); expect(detection.hasUpdate).toBe(false); expect(preview.has_update).toBe(false); expect(preview.has_update).toBe(detection.hasUpdate); }); it('shared detector and preview both report hasUpdate when digest errors but 1.2.4 exists', async () => { const { detection, preview } = await runDetectionAndPreview( { kind: 'error', reason: 'Registry unreachable' }, ['1.2.3', '1.2.4'], ); expect(detection.hasUpdate).toBe(true); expect(detection.nextTag).toBe('1.2.4'); expect(detection.digestUpdate).toBe(false); expect(detection.digestError).toBe('Registry unreachable'); expect(preview.has_update).toBe(true); expect(preview.next_tag).toBe('1.2.4'); expect(preview.has_update).toBe(detection.hasUpdate); }); it('shared detector and preview both report hasUpdate with no local digests when 1.2.4 exists', async () => { const { detection, preview, compareDigest } = await runDetectionAndPreview( { kind: 'update' }, ['1.2.3', '1.2.4'], [], ); expect(compareDigest).not.toHaveBeenCalled(); expect(detection.hasUpdate).toBe(true); expect(detection.digestUpdate).toBe(false); expect(detection.nextTag).toBe('1.2.4'); expect(preview.has_update).toBe(true); expect(preview.next_tag).toBe('1.2.4'); expect(preview.has_update).toBe(detection.hasUpdate); }); });