Files
dockhand-upstream/tests/system-container-filter.test.ts
T

156 lines
5.7 KiB
TypeScript

/**
* Unit tests for system container detection and filtering.
*
* Verifies that Dockhand and Hawser containers are correctly identified
* and excluded from batch update operations (#485).
*
* Run with: bun test tests/system-container-filter.test.ts
*/
import { describe, test, expect } from 'bun:test';
import {
isDockhandContainer,
isHawserContainer,
isSystemContainer,
isPodmanInfraContainer
} from '../src/lib/server/scheduler/tasks/update-utils';
describe('isDockhandContainer', () => {
test('matches fnsys/dockhand', () => {
expect(isDockhandContainer('fnsys/dockhand:latest')).toBe(true);
expect(isDockhandContainer('fnsys/dockhand:v1.0.0')).toBe(true);
expect(isDockhandContainer('fnsys/dockhand')).toBe(true);
});
test('matches registry-prefixed dockhand', () => {
expect(isDockhandContainer('registry.example.com/dockhand:abc123')).toBe(true);
expect(isDockhandContainer('ghcr.io/finsys/dockhand:latest')).toBe(true);
});
test('matches plain dockhand', () => {
expect(isDockhandContainer('dockhand:latest')).toBe(true);
expect(isDockhandContainer('dockhand')).toBe(true);
});
test('is case insensitive', () => {
expect(isDockhandContainer('Fnsys/Dockhand:Latest')).toBe(true);
expect(isDockhandContainer('FNSYS/DOCKHAND')).toBe(true);
});
test('does not match unrelated images', () => {
expect(isDockhandContainer('nginx:latest')).toBe(false);
expect(isDockhandContainer('my-dockhand-fork:v1')).toBe(false);
});
});
describe('isHawserContainer', () => {
test('matches finsys/hawser', () => {
expect(isHawserContainer('finsys/hawser:latest')).toBe(true);
expect(isHawserContainer('finsys/hawser:v0.5.0')).toBe(true);
expect(isHawserContainer('finsys/hawser')).toBe(true);
});
test('matches ghcr.io/finsys/hawser', () => {
expect(isHawserContainer('ghcr.io/finsys/hawser:latest')).toBe(true);
expect(isHawserContainer('ghcr.io/finsys/hawser')).toBe(true);
});
test('is case insensitive', () => {
expect(isHawserContainer('Finsys/Hawser:Latest')).toBe(true);
expect(isHawserContainer('GHCR.IO/FINSYS/HAWSER')).toBe(true);
});
test('does not match unrelated images', () => {
expect(isHawserContainer('nginx:latest')).toBe(false);
expect(isHawserContainer('hawser-custom:v1')).toBe(false);
});
});
describe('isSystemContainer', () => {
test('returns "dockhand" for Dockhand images', () => {
expect(isSystemContainer('fnsys/dockhand:latest')).toBe('dockhand');
});
test('returns "hawser" for Hawser images', () => {
expect(isSystemContainer('finsys/hawser:latest')).toBe('hawser');
expect(isSystemContainer('ghcr.io/finsys/hawser:v1')).toBe('hawser');
});
test('returns null for regular images', () => {
expect(isSystemContainer('nginx:latest')).toBeNull();
expect(isSystemContainer('redis:7')).toBeNull();
expect(isSystemContainer('postgres:16')).toBeNull();
});
});
describe('batch update filtering (#485)', () => {
// Simulates the check-updates response filtering
interface CheckResult {
containerId: string;
containerName: string;
imageName: string;
hasUpdate: boolean;
systemContainer: string | null;
}
const mockResults: CheckResult[] = [
{ containerId: 'c1', containerName: 'nginx', imageName: 'nginx:latest', hasUpdate: true, systemContainer: null },
{ containerId: 'c2', containerName: 'redis', imageName: 'redis:7', hasUpdate: true, systemContainer: null },
{ containerId: 'c3', containerName: 'dockhand', imageName: 'fnsys/dockhand:latest', hasUpdate: true, systemContainer: 'dockhand' },
{ containerId: 'c4', containerName: 'hawser-agent', imageName: 'ghcr.io/finsys/hawser:v0.5', hasUpdate: true, systemContainer: 'hawser' },
{ containerId: 'c5', containerName: 'postgres', imageName: 'postgres:16', hasUpdate: false, systemContainer: null },
];
test('updatesFound count excludes system containers', () => {
const updatesFound = mockResults.filter(r => r.hasUpdate && !r.systemContainer).length;
expect(updatesFound).toBe(2); // nginx + redis only
});
test('containersWithUpdates excludes system containers', () => {
const containersWithUpdates = mockResults.filter(r => r.hasUpdate && !r.systemContainer);
expect(containersWithUpdates.map(r => r.containerName)).toEqual(['nginx', 'redis']);
});
test('system containers with updates are not included', () => {
const containersWithUpdates = mockResults.filter(r => r.hasUpdate && !r.systemContainer);
const ids = containersWithUpdates.map(r => r.containerId);
expect(ids).not.toContain('c3'); // dockhand
expect(ids).not.toContain('c4'); // hawser
});
});
describe('isPodmanInfraContainer (#1221)', () => {
test('accepts pod-id-prefixed infra (real podman 5.4.2 shape)', () => {
expect(isPodmanInfraContainer('fb575a549efc-infra')).toBe(true);
});
test('accepts pod-name-prefixed infra (the #1221 reporter shape)', () => {
expect(isPodmanInfraContainer('someapp-pod-infra')).toBe(true);
expect(isPodmanInfraContainer('testpod-infra')).toBe(true);
});
test('accepts underscore separator', () => {
expect(isPodmanInfraContainer('foo_infra')).toBe(true);
});
test('is case-insensitive', () => {
expect(isPodmanInfraContainer('TESTPOD-INFRA')).toBe(true);
});
test('rejects user containers that merely contain "infra"', () => {
expect(isPodmanInfraContainer('my-infrastructure')).toBe(false);
expect(isPodmanInfraContainer('core-infra-svc')).toBe(false);
expect(isPodmanInfraContainer('infra-monitoring')).toBe(false);
expect(isPodmanInfraContainer('infra')).toBe(false); // no separator
});
test('rejects empty / undefined', () => {
expect(isPodmanInfraContainer(undefined)).toBe(false);
expect(isPodmanInfraContainer('')).toBe(false);
});
test('handles leading-slash names (Docker API returns /name)', () => {
expect(isPodmanInfraContainer('/fb575a549efc-infra')).toBe(true);
});
});