Harden platform security, reliability, and CI after full audit.
Close deployment IDOR and gate stub payment endpoints, add production secret validation, health probes, Redis-backed build progress, GitHub Actions CI, expanded tests, billing/k8s refactors, and ops runbooks. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,105 +1,105 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
import { NotFoundException } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { ClustersService } from './clusters.service';
|
||||
import { Cluster } from './entities/cluster.entity';
|
||||
import { ClusterHealth } from './entities/cluster-health.entity';
|
||||
import { ClusterPool } from './entities/cluster-pool.entity';
|
||||
import { ClusterAllocationLog } from './entities/cluster-allocation-log.entity';
|
||||
import { ClusterStatus } from '../common/enums';
|
||||
import { RegistryService } from '../kubernetes/registry.service';
|
||||
import { DataSource } from 'typeorm';
|
||||
|
||||
/**
|
||||
* Tests for ClustersService — getDefault and delete logic.
|
||||
*/
|
||||
describe('ClustersService', () => {
|
||||
let service: ClustersService;
|
||||
|
||||
describe('ClustersService getDefault logic', () => {
|
||||
// Simulate the fixed getDefault behavior
|
||||
function getDefault(clusters: { id: string; isDefault: boolean; status: string }[]): { id: string } | null {
|
||||
// Step 1: active + default
|
||||
let result = clusters.find(c => c.isDefault && c.status === ClusterStatus.ACTIVE);
|
||||
if (result) return { id: result.id };
|
||||
const clustersRepository = {
|
||||
findOne: jest.fn(),
|
||||
save: jest.fn().mockImplementation((x) => Promise.resolve(x)),
|
||||
find: jest.fn(),
|
||||
create: jest.fn(),
|
||||
delete: jest.fn(),
|
||||
count: jest.fn(),
|
||||
};
|
||||
|
||||
// Step 2: any active (fallback)
|
||||
result = clusters.find(c => c.status === ClusterStatus.ACTIVE);
|
||||
if (result) return { id: result.id };
|
||||
const healthRepository = { find: jest.fn(), save: jest.fn() };
|
||||
const poolRepository = { find: jest.fn(), findOne: jest.fn(), save: jest.fn() };
|
||||
const allocationLogsRepository = { save: jest.fn(), find: jest.fn() };
|
||||
const dataSource = { transaction: jest.fn() };
|
||||
const registryService = { ensureRegistryPullSecret: jest.fn() };
|
||||
|
||||
return null;
|
||||
}
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
it('should return active default cluster', () => {
|
||||
const clusters = [
|
||||
{ id: '1', isDefault: true, status: ClusterStatus.ACTIVE },
|
||||
{ id: '2', isDefault: false, status: ClusterStatus.ACTIVE },
|
||||
];
|
||||
expect(getDefault(clusters)?.id).toBe('1');
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
ClustersService,
|
||||
{ provide: getRepositoryToken(Cluster), useValue: clustersRepository },
|
||||
{ provide: getRepositoryToken(ClusterPool), useValue: poolRepository },
|
||||
{ provide: getRepositoryToken(ClusterHealth), useValue: healthRepository },
|
||||
{ provide: getRepositoryToken(ClusterAllocationLog), useValue: allocationLogsRepository },
|
||||
{ provide: DataSource, useValue: dataSource },
|
||||
{ provide: RegistryService, useValue: registryService },
|
||||
{
|
||||
provide: ConfigService,
|
||||
useValue: {
|
||||
get: jest.fn((key: string) => {
|
||||
if (key === 'CLUSTER_KUBECONFIG_KEY') return '';
|
||||
if (key === 'cluster.kubeconfigKey') return '';
|
||||
return undefined;
|
||||
}),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get(ClustersService);
|
||||
});
|
||||
|
||||
it('should skip inactive default and return active cluster', () => {
|
||||
const clusters = [
|
||||
{ id: '1', isDefault: true, status: ClusterStatus.INACTIVE },
|
||||
{ id: '2', isDefault: false, status: ClusterStatus.ACTIVE },
|
||||
];
|
||||
expect(getDefault(clusters)?.id).toBe('2');
|
||||
});
|
||||
describe('getDefault', () => {
|
||||
it('returns active default cluster', async () => {
|
||||
const cluster = {
|
||||
id: 'c-1',
|
||||
name: 'primary',
|
||||
isDefault: true,
|
||||
status: ClusterStatus.ACTIVE,
|
||||
kubeconfig: 'apiVersion: v1',
|
||||
} as Cluster;
|
||||
|
||||
it('should return null when no active clusters exist', () => {
|
||||
const clusters = [
|
||||
{ id: '1', isDefault: true, status: ClusterStatus.INACTIVE },
|
||||
];
|
||||
expect(getDefault(clusters)).toBeNull();
|
||||
});
|
||||
clustersRepository.findOne.mockResolvedValueOnce(cluster);
|
||||
|
||||
it('should handle both clusters being default (picks active one)', () => {
|
||||
const clusters = [
|
||||
{ id: 'inactive', isDefault: true, status: ClusterStatus.INACTIVE },
|
||||
{ id: 'active', isDefault: true, status: ClusterStatus.ACTIVE },
|
||||
];
|
||||
expect(getDefault(clusters)?.id).toBe('active');
|
||||
});
|
||||
});
|
||||
|
||||
describe('ClustersService delete logic', () => {
|
||||
it('should reassign apps to replacement cluster on delete', () => {
|
||||
// Simulate: cluster A (being deleted) has 3 apps, cluster B is active
|
||||
const apps = [
|
||||
{ id: 'app1', clusterId: 'A' },
|
||||
{ id: 'app2', clusterId: 'A' },
|
||||
{ id: 'app3', clusterId: 'B' },
|
||||
];
|
||||
const deletedClusterId = 'A';
|
||||
const replacementId = 'B';
|
||||
|
||||
// Reassign
|
||||
for (const app of apps) {
|
||||
if (app.clusterId === deletedClusterId) {
|
||||
app.clusterId = replacementId;
|
||||
}
|
||||
}
|
||||
|
||||
expect(apps.filter(a => a.clusterId === 'A')).toHaveLength(0);
|
||||
expect(apps.filter(a => a.clusterId === 'B')).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('should promote another cluster to default when default is deleted', () => {
|
||||
const clusters = [
|
||||
{ id: 'A', isDefault: true, status: ClusterStatus.ACTIVE },
|
||||
{ id: 'B', isDefault: false, status: ClusterStatus.ACTIVE },
|
||||
];
|
||||
|
||||
// Delete A
|
||||
const deleted = clusters.splice(0, 1)[0];
|
||||
expect(deleted.isDefault).toBe(true);
|
||||
|
||||
// Promote
|
||||
const newDefault = clusters.find(c => c.status === ClusterStatus.ACTIVE);
|
||||
if (newDefault) newDefault.isDefault = true;
|
||||
|
||||
expect(clusters[0].isDefault).toBe(true);
|
||||
expect(clusters[0].id).toBe('B');
|
||||
});
|
||||
|
||||
it('should nullify clusterId when no replacement cluster exists', () => {
|
||||
const apps = [{ id: 'app1', clusterId: 'A' as string | null }];
|
||||
const hasReplacement = false;
|
||||
|
||||
if (!hasReplacement) {
|
||||
for (const app of apps) {
|
||||
app.clusterId = null;
|
||||
}
|
||||
}
|
||||
|
||||
expect(apps[0].clusterId).toBeNull();
|
||||
const result = await service.getDefault();
|
||||
|
||||
expect(result.id).toBe('c-1');
|
||||
expect(clustersRepository.findOne).toHaveBeenCalledWith({
|
||||
where: { isDefault: true, status: ClusterStatus.ACTIVE },
|
||||
});
|
||||
});
|
||||
|
||||
it('falls back to any active cluster when no default is set', async () => {
|
||||
const fallback = {
|
||||
id: 'c-2',
|
||||
name: 'fallback',
|
||||
isDefault: false,
|
||||
status: ClusterStatus.ACTIVE,
|
||||
kubeconfig: 'apiVersion: v1',
|
||||
} as Cluster;
|
||||
|
||||
clustersRepository.findOne
|
||||
.mockResolvedValueOnce(null)
|
||||
.mockResolvedValueOnce(fallback);
|
||||
|
||||
const result = await service.getDefault();
|
||||
|
||||
expect(result.id).toBe('c-2');
|
||||
expect(clustersRepository.save).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('throws when no active cluster exists', async () => {
|
||||
clustersRepository.findOne.mockResolvedValue(null);
|
||||
|
||||
await expect(service.getDefault()).rejects.toThrow(NotFoundException);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user