Files
sencho/backend/src/__tests__/fleet-update-cache.test.ts
T
Anso 0daddfde00 fix: reconcile sticky update indicators with Anatomy preview (#1698)
* fix: reconcile sticky update indicators with Anatomy preview

Sidebar, Updates filter, and Fleet treated retained partial/failed
scanner has_update as confirmed. Keep raw state for retention/notifications,
project confirmed-only to APIs, show distinct incomplete indicators, and
clear sticky rows only after an authoritative-negative preview.

Closes #1685

* test: align sidebar truncate E2E with failed-over-retained precedence

Purple update indicators are confirmed-only; hasUpdate with a failed
check correctly shows the failed trailing icon.

* fix: clear confirmed update rows on authoritative-negative preview

Address audit SF-1/SF-2/SF-3: observation-watermark clears for older
ok+has_update rows (DB + memory gens), Fleet checkability parity with
backend not_checkable, and Updates chip confirmed-only regressions.

* fix: tombstone equal-generation writers on preview clear

Advance the per-stack write generation when clearing at the observation
watermark so a scanner reserved before preview cannot recreate the row
after an authoritative-negative reconcile.

* fix: clear sticky updates with digest and tag preview parity

Share detection across scanner and preview, keep GET read-only with POST reconcile, gate Apply to digest and rebuild updates, and invalidate the hub fleet cache on clear.

* test: set digestUpdate on auto-update checkImage mocks

Scheduler and execute routes now gate Compose on digest drift; fixtures that expect an apply need digestUpdate so they exercise the update path.

* fix: clear unused lint errors on sticky update branch

Drop unused partial helper and fleet invalidate import; keep the CacheService inflight self-ref as let with an eslint exception so tsc stays green.

* fix: use inflight holder for CacheService prefer-const

Keep generation-aware ownership without a let self-reference that fights ESLint and tsc.
2026-07-25 15:42:19 -04:00

51 lines
1.9 KiB
TypeScript

import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { CacheService } from '../services/CacheService';
import {
FLEET_UPDATE_CACHE_KEY,
invalidateFleetUpdateCache,
isFullStackUpdatePath,
isUpdatePreviewPath,
} from '../helpers/fleetUpdateCache';
describe('isFullStackUpdatePath', () => {
it('matches full-stack update paths after the /api mount strip', () => {
expect(isFullStackUpdatePath('/stacks/paperless/update')).toBe(true);
expect(isFullStackUpdatePath('/stacks/paperless/update?nodeId=2')).toBe(true);
});
it('rejects service-scoped update and restore paths', () => {
expect(isFullStackUpdatePath('/stacks/paperless/services/redis/update')).toBe(false);
expect(isFullStackUpdatePath('/stacks/paperless/services/redis/restore')).toBe(false);
expect(isFullStackUpdatePath('/stacks/paperless/deploy')).toBe(false);
});
});
describe('isUpdatePreviewPath', () => {
it('matches update-preview paths after the /api mount strip', () => {
expect(isUpdatePreviewPath('/stacks/paperless/update-preview')).toBe(true);
expect(isUpdatePreviewPath('/stacks/paperless/update-preview?nodeId=2')).toBe(true);
});
it('rejects full-stack update and other stack paths', () => {
expect(isUpdatePreviewPath('/stacks/paperless/update')).toBe(false);
expect(isUpdatePreviewPath('/stacks/paperless/services/redis/update')).toBe(false);
});
});
describe('invalidateFleetUpdateCache', () => {
beforeEach(() => {
CacheService.getInstance().flush();
});
afterEach(() => {
CacheService.getInstance().flush();
});
it('drops the shared fleet-updates key', async () => {
const cache = CacheService.getInstance();
await cache.getOrFetch(FLEET_UPDATE_CACHE_KEY, 60_000, async () => ({ '1': { web: true } }));
invalidateFleetUpdateCache();
expect(cache.get(FLEET_UPDATE_CACHE_KEY)).toBeUndefined();
});
});