3cebde0673
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
213 lines
7.2 KiB
TypeScript
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'));
|
|
});
|