Files
headlamp-tns-csi-plugin/src/api/k8s.test.ts
T
DevContainer User 3cebde0673 fix: remove unused imports and format source files
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 01:06:13 +00:00

213 lines
7.2 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import {
filterTnsCsiPersistentVolumes,
filterTnsCsiPVCs,
filterTnsCsiStorageClasses,
filterTnsCsiVolumeSnapshots,
findBoundPv,
formatAccessModes,
formatAge,
formatProtocol,
isTnsCsiPersistentVolume,
isTnsCsiStorageClass,
phaseToStatus,
TnsCsiPersistentVolume,
TnsCsiPersistentVolumeClaim,
TnsCsiStorageClass,
} from './k8s';
// ---------------------------------------------------------------------------
// Test fixtures
// ---------------------------------------------------------------------------
function makeSc(name: string, provisioner: string, protocol = 'nfs'): TnsCsiStorageClass {
return {
metadata: { name },
provisioner,
parameters: { protocol },
};
}
function makePv(
name: string,
driver: string,
claimRef?: { name: string; namespace: string }
): TnsCsiPersistentVolume {
return {
metadata: { name },
spec: {
csi: { driver, volumeAttributes: { protocol: 'nfs' } },
capacity: { storage: '10Gi' },
claimRef,
},
status: { phase: 'Bound' },
};
}
function makePvc(name: string, namespace: string): TnsCsiPersistentVolumeClaim {
return {
metadata: { name, namespace },
spec: {},
status: { phase: 'Bound' },
};
}
// ---------------------------------------------------------------------------
// StorageClass filtering
// ---------------------------------------------------------------------------
describe('isTnsCsiStorageClass', () => {
it('returns true for tns.csi.io provisioner', () => {
expect(isTnsCsiStorageClass(makeSc('sc1', 'tns.csi.io'))).toBe(true);
});
it('returns false for other provisioners', () => {
expect(isTnsCsiStorageClass(makeSc('sc1', 'kubernetes.io/nfs'))).toBe(false);
});
it('returns false for non-objects', () => {
expect(isTnsCsiStorageClass(null)).toBe(false);
expect(isTnsCsiStorageClass(undefined)).toBe(false);
expect(isTnsCsiStorageClass('string')).toBe(false);
});
});
describe('filterTnsCsiStorageClasses', () => {
it('filters to only tns-csi storage classes', () => {
const items = [
makeSc('tns-sc', 'tns.csi.io'),
makeSc('other-sc', 'kubernetes.io/aws-ebs'),
makeSc('another', 'rancher.io/local-path'),
];
const result = filterTnsCsiStorageClasses(items);
expect(result).toHaveLength(1);
expect(result[0]?.metadata.name).toBe('tns-sc');
});
it('returns empty array when no tns-csi classes', () => {
expect(filterTnsCsiStorageClasses([makeSc('x', 'other')])).toHaveLength(0);
});
});
// ---------------------------------------------------------------------------
// PV filtering
// ---------------------------------------------------------------------------
describe('isTnsCsiPersistentVolume', () => {
it('returns true for tns.csi.io driver', () => {
expect(isTnsCsiPersistentVolume(makePv('pv1', 'tns.csi.io'))).toBe(true);
});
it('returns false for other drivers', () => {
expect(isTnsCsiPersistentVolume(makePv('pv1', 'ebs.csi.aws.com'))).toBe(false);
});
it('returns false for PVs without CSI spec', () => {
const pv = { metadata: { name: 'pv1' }, spec: {}, status: {} };
expect(isTnsCsiPersistentVolume(pv)).toBe(false);
});
});
describe('filterTnsCsiPersistentVolumes', () => {
it('filters to only tns-csi PVs', () => {
const items = [makePv('tns-pv', 'tns.csi.io'), makePv('other-pv', 'ebs.csi.aws.com')];
const result = filterTnsCsiPersistentVolumes(items);
expect(result).toHaveLength(1);
expect(result[0]?.metadata.name).toBe('tns-pv');
});
});
// ---------------------------------------------------------------------------
// PVC filtering
// ---------------------------------------------------------------------------
describe('filterTnsCsiPVCs', () => {
it('includes PVCs bound to tns-csi PVs via claimRef', () => {
const pv = makePv('tns-pv', 'tns.csi.io', { name: 'my-pvc', namespace: 'default' });
const pvc = makePvc('my-pvc', 'default');
const unrelatedPvc = makePvc('other-pvc', 'default');
const result = filterTnsCsiPVCs([pvc, unrelatedPvc], [pv]);
expect(result).toHaveLength(1);
expect(result[0]?.metadata.name).toBe('my-pvc');
});
it('returns empty array when no PVs', () => {
const pvc = makePvc('my-pvc', 'default');
expect(filterTnsCsiPVCs([pvc], [])).toHaveLength(0);
});
});
describe('findBoundPv', () => {
it('finds the PV bound to a PVC', () => {
const pv = makePv('tns-pv', 'tns.csi.io', { name: 'my-pvc', namespace: 'default' });
const pvc = makePvc('my-pvc', 'default');
expect(findBoundPv(pvc, [pv])).toBe(pv);
});
it('returns undefined when no match', () => {
const pv = makePv('tns-pv', 'tns.csi.io', { name: 'other-pvc', namespace: 'default' });
const pvc = makePvc('my-pvc', 'default');
expect(findBoundPv(pvc, [pv])).toBeUndefined();
});
});
// ---------------------------------------------------------------------------
// VolumeSnapshot filtering
// ---------------------------------------------------------------------------
describe('filterTnsCsiVolumeSnapshots', () => {
it('filters snapshots to matching snapshot classes', () => {
const snapshots = [
{ metadata: { name: 's1' }, spec: { volumeSnapshotClassName: 'tns-vsc' } },
{ metadata: { name: 's2' }, spec: { volumeSnapshotClassName: 'other-vsc' } },
];
const result = filterTnsCsiVolumeSnapshots(snapshots, new Set(['tns-vsc']));
expect(result).toHaveLength(1);
expect(result[0]?.metadata.name).toBe('s1');
});
});
// ---------------------------------------------------------------------------
// Formatting utilities
// ---------------------------------------------------------------------------
describe('formatProtocol', () => {
it('maps nfs → NFS', () => expect(formatProtocol('nfs')).toBe('NFS'));
it('maps nvmeof → NVMe-oF', () => expect(formatProtocol('nvmeof')).toBe('NVMe-oF'));
it('maps iscsi → iSCSI', () => expect(formatProtocol('iscsi')).toBe('iSCSI'));
it('passes through unknown values', () => expect(formatProtocol('custom')).toBe('custom'));
it('returns — for undefined', () => expect(formatProtocol(undefined)).toBe('—'));
});
describe('formatAccessModes', () => {
it('abbreviates access modes', () => {
expect(formatAccessModes(['ReadWriteOnce', 'ReadWriteMany'])).toBe('RWO, RWX');
});
it('returns — for empty', () => expect(formatAccessModes([])).toBe('—'));
it('returns — for undefined', () => expect(formatAccessModes(undefined)).toBe('—'));
});
describe('formatAge', () => {
it('returns "unknown" for undefined', () => {
expect(formatAge(undefined)).toBe('unknown');
});
it('returns seconds for very recent timestamps', () => {
const ts = new Date(Date.now() - 30_000).toISOString();
expect(formatAge(ts)).toBe('30s');
});
it('returns minutes for ~5min ago', () => {
const ts = new Date(Date.now() - 5 * 60_000).toISOString();
expect(formatAge(ts)).toBe('5m');
});
});
describe('phaseToStatus', () => {
it('maps Bound → success', () => expect(phaseToStatus('Bound')).toBe('success'));
it('maps Pending → warning', () => expect(phaseToStatus('Pending')).toBe('warning'));
it('maps Failed → error', () => expect(phaseToStatus('Failed')).toBe('error'));
it('maps undefined → error', () => expect(phaseToStatus(undefined)).toBe('error'));
});