mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-12 19:57:37 +00:00
719180f156
* fix(fleet): verify update status before removing readiness cards Full-stack Apply now rechecks persisted status after the health gate starts, reloads the live preview before dropping a card, and invalidates the hub fleet aggregation so cleared updates cannot resurrect from a stale cache. Closes #1686 * fix(fleet): align persisted update status with preview semver detection Share digest-plus-tag detection so post-Apply sidebar status matches Fleet and Anatomy. * fix(fleet): keep tag-only updates advisory for Compose automation Expose digestUpdate vs tagUpdate from checkImage so scheduled and API auto-update only apply same-tag digest drift Compose can pull. * docs: clarify scheduled auto-update applies digest drift only Document that higher pinned tags stay advisory until Compose is changed, matching schedule and Run Now behavior. * docs: require Compose pin edits for higher-tag advisories Stop recommending Apply now or Update as remedies that cannot rewrite a pinned image tag. * docs: clarify Apply now pulls pinned tags only Align the detection-cadence bullet with digest-rebuild vs higher-tag guidance. * fix(fleet): keep tag advisories after apply and scheduled updates Tag-only previews were treated as cleared on Fleet reload, and scheduled/ Run Now paths wiped status without rechecking. Align post-update verification with the manual Apply path (health gate first, recheck, no blind clear) and block digest apply when sibling image checks failed. * fix(fleet): clear eslint unused-arg and containers assignment
98 lines
3.7 KiB
TypeScript
98 lines
3.7 KiB
TypeScript
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);
|
|
});
|
|
});
|