Files
headlamp-sealed-secrets-plugin/src/lib/controller.test.ts
T
DevContainer User 9d9bc5f22f fix: remove any types, dead code, unused exports; add comprehensive tests
- Fix handleRotate bug ignoring Result from rotateSealedSecret()
- Fix dead code branch in useControllerHealth
- Replace all `any` types with `unknown` + type guards
- Delete unused functions/exports (452 lines removed)
- Add 18 new test files covering all hooks, libs, and components
- 233 tests passing, zero tsc errors, zero lint issues

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 17:13:00 +00:00

290 lines
8.6 KiB
TypeScript

/**
* Unit tests for controller API helpers
*/
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import {
checkControllerHealth,
fetchPublicCertificate,
getPluginConfig,
rotateSealedSecret,
savePluginConfig,
} from './controller';
// Mock retry to avoid real delays
vi.mock('./retry', () => ({
retryWithBackoff: vi.fn((fn: () => Promise<unknown>) => fn()),
}));
describe('controller', () => {
let originalFetch: typeof global.fetch;
beforeEach(() => {
originalFetch = global.fetch;
localStorage.clear();
});
afterEach(() => {
global.fetch = originalFetch;
localStorage.clear();
vi.restoreAllMocks();
});
describe('getPluginConfig / savePluginConfig', () => {
it('should return default config when no stored config', () => {
const config = getPluginConfig();
expect(config.controllerName).toBe('sealed-secrets-controller');
expect(config.controllerNamespace).toBe('kube-system');
expect(config.controllerPort).toBe(8080);
});
it('should round-trip saved config', () => {
const custom = {
controllerName: 'my-controller',
controllerNamespace: 'sealed-secrets',
controllerPort: 9090,
};
savePluginConfig(custom);
const loaded = getPluginConfig();
expect(loaded).toEqual(custom);
});
it('should return default config on invalid JSON', () => {
localStorage.setItem('sealed-secrets-plugin-config', 'not json');
const config = getPluginConfig();
expect(config.controllerName).toBe('sealed-secrets-controller');
});
it('should overwrite previous config', () => {
savePluginConfig({
controllerName: 'first',
controllerNamespace: 'ns1',
controllerPort: 1111,
});
savePluginConfig({
controllerName: 'second',
controllerNamespace: 'ns2',
controllerPort: 2222,
});
const config = getPluginConfig();
expect(config.controllerName).toBe('second');
});
});
describe('fetchPublicCertificate', () => {
it('should return certificate on success', async () => {
const certPEM = '-----BEGIN CERTIFICATE-----\nfake\n-----END CERTIFICATE-----';
global.fetch = vi.fn().mockResolvedValue({
ok: true,
text: () => Promise.resolve(certPEM),
});
const config = getPluginConfig();
const result = await fetchPublicCertificate(config);
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.value).toBe(certPEM);
}
expect(global.fetch).toHaveBeenCalledWith(expect.stringContaining('/v1/cert.pem'));
});
it('should return error on HTTP failure', async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: false,
status: 503,
statusText: 'Service Unavailable',
});
const config = getPluginConfig();
const result = await fetchPublicCertificate(config);
expect(result.ok).toBe(false);
if (result.ok === false) {
expect(result.error).toContain('Unable to fetch controller certificate');
}
});
it('should return error on network failure', async () => {
global.fetch = vi.fn().mockRejectedValue(new Error('Network error'));
const config = getPluginConfig();
const result = await fetchPublicCertificate(config);
expect(result.ok).toBe(false);
if (result.ok === false) {
expect(result.error).toContain('Unable to fetch controller certificate');
}
});
});
describe('checkControllerHealth', () => {
it('should return healthy status on 200', async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: true,
headers: new Headers({ 'X-Controller-Version': '0.24.0' }),
});
const config = getPluginConfig();
const result = await checkControllerHealth(config);
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.value.healthy).toBe(true);
expect(result.value.reachable).toBe(true);
expect(result.value.version).toBe('0.24.0');
expect(result.value.latencyMs).toBeDefined();
}
});
it('should return unhealthy reachable on non-200', async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: false,
status: 500,
statusText: 'Internal Server Error',
headers: new Headers(),
});
const config = getPluginConfig();
const result = await checkControllerHealth(config);
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.value.healthy).toBe(false);
expect(result.value.reachable).toBe(true);
expect(result.value.error).toContain('500');
}
});
it('should return unreachable on network error', async () => {
global.fetch = vi.fn().mockRejectedValue(new Error('Connection refused'));
const config = getPluginConfig();
const result = await checkControllerHealth(config);
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.value.healthy).toBe(false);
expect(result.value.reachable).toBe(false);
expect(result.value.error).toBe('Connection refused');
}
});
it('should handle timeout (AbortError)', async () => {
const abortError = new Error('The operation was aborted');
abortError.name = 'AbortError';
global.fetch = vi.fn().mockRejectedValue(abortError);
const config = getPluginConfig();
const result = await checkControllerHealth(config);
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.value.healthy).toBe(false);
expect(result.value.reachable).toBe(false);
expect(result.value.error).toContain('timed out');
}
});
it('should return undefined version when header is absent', async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: true,
headers: new Headers(),
});
const config = getPluginConfig();
const result = await checkControllerHealth(config);
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.value.version).toBeUndefined();
}
});
it('should use correct healthz endpoint', async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: true,
headers: new Headers(),
});
const config = {
controllerName: 'my-ss',
controllerNamespace: 'my-ns',
controllerPort: 9090,
};
await checkControllerHealth(config);
expect(global.fetch).toHaveBeenCalledWith(
'/api/v1/namespaces/my-ns/services/http:my-ss:9090/proxy/healthz',
expect.objectContaining({ method: 'GET' })
);
});
});
describe('rotateSealedSecret', () => {
it('should return rotated YAML on success', async () => {
const rotatedYaml = '{"apiVersion":"bitnami.com/v1alpha1","kind":"SealedSecret"}';
global.fetch = vi.fn().mockResolvedValue({
ok: true,
text: () => Promise.resolve(rotatedYaml),
});
const config = getPluginConfig();
const result = await rotateSealedSecret(config, '{"old":"data"}');
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.value).toBe(rotatedYaml);
}
});
it('should return error on HTTP failure', async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: false,
status: 400,
statusText: 'Bad Request',
});
const config = getPluginConfig();
const result = await rotateSealedSecret(config, '{"data":"test"}');
expect(result.ok).toBe(false);
if (result.ok === false) {
expect(result.error).toContain('Unable to rotate');
}
});
it('should return error on network failure', async () => {
global.fetch = vi.fn().mockRejectedValue(new Error('fetch failed'));
const config = getPluginConfig();
const result = await rotateSealedSecret(config, '{}');
expect(result.ok).toBe(false);
if (result.ok === false) {
expect(result.error).toContain('Unable to rotate');
}
});
it('should POST to rotate endpoint with JSON content type', async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: true,
text: () => Promise.resolve('rotated'),
});
const config = getPluginConfig();
const yaml = '{"test":"data"}';
await rotateSealedSecret(config, yaml);
expect(global.fetch).toHaveBeenCalledWith(
expect.stringContaining('/v1/rotate'),
expect.objectContaining({
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: yaml,
})
);
});
});
});