193 lines
6.1 KiB
JavaScript
193 lines
6.1 KiB
JavaScript
// update-manager.js creates a Docker instance at module level.
|
|
// On test machines without Docker, this is fine — Docker methods are only called
|
|
// in async methods that we won't invoke in unit tests.
|
|
|
|
const updateManager = require('../update-manager');
|
|
|
|
beforeEach(() => {
|
|
// Reset singleton state
|
|
updateManager.history = [];
|
|
updateManager.availableUpdates = new Map();
|
|
updateManager.config = { autoUpdate: {} };
|
|
updateManager.checking = false;
|
|
if (updateManager.checkInterval) {
|
|
clearInterval(updateManager.checkInterval);
|
|
updateManager.checkInterval = null;
|
|
}
|
|
});
|
|
|
|
afterAll(() => {
|
|
updateManager.stop();
|
|
});
|
|
|
|
describe('extractTag', () => {
|
|
test('extracts tag from "nginx:latest"', () => {
|
|
expect(updateManager.extractTag('nginx:latest')).toBe('latest');
|
|
});
|
|
|
|
test('returns "latest" when no tag specified', () => {
|
|
expect(updateManager.extractTag('nginx')).toBe('latest');
|
|
});
|
|
|
|
test('extracts tag from registry/repo:tag format', () => {
|
|
expect(updateManager.extractTag('docker.io/library/nginx:1.21')).toBe('1.21');
|
|
});
|
|
|
|
test('handles tags with dots and hyphens', () => {
|
|
expect(updateManager.extractTag('myapp:v1.2.3-rc1')).toBe('v1.2.3-rc1');
|
|
});
|
|
});
|
|
|
|
describe('parseAuthHeader', () => {
|
|
test('returns null for null header', () => {
|
|
expect(updateManager.parseAuthHeader(null)).toBeNull();
|
|
});
|
|
|
|
test('returns null for non-Bearer header', () => {
|
|
expect(updateManager.parseAuthHeader('Basic realm="test"')).toBeNull();
|
|
});
|
|
|
|
test('parses Bearer realm URL', () => {
|
|
const header = 'Bearer realm="https://auth.docker.io/token"';
|
|
const result = updateManager.parseAuthHeader(header);
|
|
expect(result).toContain('https://auth.docker.io/token');
|
|
});
|
|
|
|
test('includes service parameter', () => {
|
|
const header = 'Bearer realm="https://auth.docker.io/token",service="registry.docker.io"';
|
|
const result = updateManager.parseAuthHeader(header);
|
|
expect(result).toContain('service=registry.docker.io');
|
|
});
|
|
|
|
test('includes scope parameter', () => {
|
|
const header = 'Bearer realm="https://auth.docker.io/token",service="registry.docker.io",scope="repository:library/nginx:pull"';
|
|
const result = updateManager.parseAuthHeader(header);
|
|
expect(result).toContain('scope=');
|
|
});
|
|
});
|
|
|
|
describe('getAvailableUpdates', () => {
|
|
test('returns empty array initially', () => {
|
|
expect(updateManager.getAvailableUpdates()).toEqual([]);
|
|
});
|
|
|
|
test('returns array from availableUpdates map', () => {
|
|
updateManager.availableUpdates.set('c1', { containerId: 'c1', imageName: 'nginx' });
|
|
const updates = updateManager.getAvailableUpdates();
|
|
expect(updates).toHaveLength(1);
|
|
expect(updates[0].containerId).toBe('c1');
|
|
});
|
|
});
|
|
|
|
describe('getHistory', () => {
|
|
test('returns entries in reverse order', () => {
|
|
updateManager.addToHistory({ containerId: 'c1', status: 'success' });
|
|
updateManager.addToHistory({ containerId: 'c2', status: 'success' });
|
|
const history = updateManager.getHistory();
|
|
expect(history[0].containerId).toBe('c2');
|
|
});
|
|
|
|
test('returns empty array when no history', () => {
|
|
expect(updateManager.getHistory()).toEqual([]);
|
|
});
|
|
|
|
test('respects limit parameter', () => {
|
|
for (let i = 0; i < 10; i++) {
|
|
updateManager.addToHistory({ containerId: `c${i}` });
|
|
}
|
|
expect(updateManager.getHistory(3)).toHaveLength(3);
|
|
});
|
|
});
|
|
|
|
describe('addToHistory', () => {
|
|
test('appends entry', () => {
|
|
updateManager.addToHistory({ containerId: 'c1' });
|
|
expect(updateManager.history).toHaveLength(1);
|
|
});
|
|
|
|
test('trims to 100 entries', () => {
|
|
for (let i = 0; i < 105; i++) {
|
|
updateManager.addToHistory({ containerId: `c${i}` });
|
|
}
|
|
expect(updateManager.history.length).toBeLessThanOrEqual(100);
|
|
});
|
|
});
|
|
|
|
describe('configureAutoUpdate', () => {
|
|
test('creates autoUpdate config section', () => {
|
|
updateManager.configureAutoUpdate('c1', { enabled: true });
|
|
expect(updateManager.config.autoUpdate['c1']).toBeDefined();
|
|
});
|
|
|
|
test('stores container-specific config', () => {
|
|
updateManager.configureAutoUpdate('c1', {
|
|
enabled: true,
|
|
schedule: 'daily',
|
|
securityOnly: true,
|
|
});
|
|
expect(updateManager.config.autoUpdate['c1'].schedule).toBe('daily');
|
|
expect(updateManager.config.autoUpdate['c1'].securityOnly).toBe(true);
|
|
});
|
|
|
|
test('defaults autoRollback to true', () => {
|
|
updateManager.configureAutoUpdate('c1', { enabled: true });
|
|
expect(updateManager.config.autoUpdate['c1'].autoRollback).toBe(true);
|
|
});
|
|
|
|
test('defaults schedule to weekly', () => {
|
|
updateManager.configureAutoUpdate('c1', {});
|
|
expect(updateManager.config.autoUpdate['c1'].schedule).toBe('weekly');
|
|
});
|
|
});
|
|
|
|
describe('scheduleUpdate', () => {
|
|
test('throws for past scheduled time', () => {
|
|
const past = new Date(Date.now() - 60000).toISOString();
|
|
expect(() => updateManager.scheduleUpdate('c1', past)).toThrow('Scheduled time must be in the future');
|
|
});
|
|
|
|
test('accepts future scheduled time', () => {
|
|
jest.useFakeTimers();
|
|
const future = new Date(Date.now() + 60000).toISOString();
|
|
expect(() => updateManager.scheduleUpdate('c1', future)).not.toThrow();
|
|
jest.useRealTimers();
|
|
});
|
|
});
|
|
|
|
describe('getChangelog', () => {
|
|
test('returns placeholder response', async () => {
|
|
const result = await updateManager.getChangelog('nginx:latest');
|
|
expect(result.imageName).toBe('nginx:latest');
|
|
expect(result.changelog).toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe('start / stop', () => {
|
|
test('start sets checking flag', () => {
|
|
jest.useFakeTimers();
|
|
updateManager.start();
|
|
expect(updateManager.checking).toBe(true);
|
|
updateManager.stop();
|
|
jest.useRealTimers();
|
|
});
|
|
|
|
test('stop clears interval', () => {
|
|
jest.useFakeTimers();
|
|
updateManager.start();
|
|
updateManager.stop();
|
|
expect(updateManager.checking).toBe(false);
|
|
expect(updateManager.checkInterval).toBeNull();
|
|
jest.useRealTimers();
|
|
});
|
|
|
|
test('start is idempotent', () => {
|
|
jest.useFakeTimers();
|
|
updateManager.start();
|
|
const first = updateManager.checkInterval;
|
|
updateManager.start();
|
|
expect(updateManager.checkInterval).toBe(first);
|
|
updateManager.stop();
|
|
jest.useRealTimers();
|
|
});
|
|
});
|