Files
cloudly/test/test.servicecleanup.node.ts

138 lines
4.5 KiB
TypeScript

import { tap, expect } from '@git.zone/tstest/tapbundle';
import { ServiceManager } from '../ts/manager.service/classes.servicemanager.js';
const createDeleteable = (labelArg: string, callsArg: string[]) => ({
id: labelArg,
delete: async () => callsArg.push(`delete:${labelArg}`),
});
const createManager = (optionsArg: {
calls: string[];
failBackups?: boolean;
}) => {
const calls = optionsArg.calls;
const service = {
id: 'service-1',
data: {
name: 'ghost',
imageId: 'image-1',
appTemplateId: 'ghost',
secretBundleId: 'bundle-1',
registryTarget: { repository: 'workloads/ghost-service-1' },
domains: [{ name: 'ghost.example.com' }],
},
removeDnsEntries: async () => calls.push('delete:dns'),
delete: async () => calls.push('delete:service'),
};
const manager = Object.create(ServiceManager.prototype) as any;
manager.CService = {
getInstance: async () => service,
};
manager.cloudlyRef = {
appStoreManager: {
clearOperationsForService: (serviceIdArg: string) => calls.push(`clear-upgrades:${serviceIdArg}`),
},
deploymentManager: {
CDeployment: {
getInstances: async () => [createDeleteable('deployment-1', calls)],
},
},
platformManager: {
CPlatformBinding: {
getInstances: async (queryArg: { serviceId: string }) => queryArg.serviceId === 'service-1'
? [createDeleteable('platform-binding-1', calls)]
: [],
},
},
backupManager: {
deleteBackupsForService: async (serviceIdArg: string) => {
calls.push(`delete-backups:${serviceIdArg}`);
if (optionsArg.failBackups) {
throw new Error('backup cleanup failed');
}
},
},
registryManager: {
deleteServiceRepository: async () => calls.push('delete:registry-repository'),
},
secretManager: {
CSecretBundle: {
getInstance: async () => ({
id: 'bundle-1',
data: {
serviceId: 'service-1',
includedSecretGroupIds: ['group-1'],
},
delete: async () => calls.push('delete:secret-bundle'),
}),
getInstances: async () => [],
},
CSecretGroup: {
getInstance: async () => createDeleteable('secret-group-1', calls),
},
},
imageManager: {
deleteImageIfUnreferenced: async (imageIdArg: string, serviceIdArg: string) => {
calls.push(`delete-image:${imageIdArg}:${serviceIdArg}`);
return true;
},
},
settingsManager: {
getSettings: async () => ({
dcrouterGatewayUrl: 'https://dcrouter.example.com',
dcrouterGatewayApiToken: 'token',
dcrouterWorkHosterId: 'cloudly-main',
}),
},
clusterManager: {
getAllClusters: async () => [],
},
coreflowManager: {
pushClusterConfigToConnectedCoreflows: async () => calls.push('push:coreflow'),
},
};
manager.fireDcRouterRequest = async (_url: string, methodArg: string, payloadArg: any) => {
calls.push(`${methodArg}:${payloadArg.ownership.hostname}:${payloadArg.ownership.workHosterId}`);
return { success: true, action: 'deleted' };
};
return { manager, service };
};
tap.test('should delete Cloudly service-owned resources before deleting the service row', async () => {
const calls: string[] = [];
const { manager } = createManager({ calls });
await manager.deleteServiceById('service-1');
expect(calls).toContain('syncWorkAppRoute:ghost.example.com:cloudly-main');
expect(calls).toContain('clear-upgrades:service-1');
expect(calls).toContain('delete:deployment-1');
expect(calls).toContain('delete:dns');
expect(calls).toContain('delete:platform-binding-1');
expect(calls).toContain('delete-backups:service-1');
expect(calls).toContain('delete:registry-repository');
expect(calls).toContain('delete:secret-bundle');
expect(calls).toContain('delete:secret-group-1');
expect(calls).toContain('delete-image:image-1:service-1');
expect(calls.at(-2)).toEqual('delete:service');
expect(calls.at(-1)).toEqual('push:coreflow');
});
tap.test('should keep Cloudly service row when required cleanup fails', async () => {
const calls: string[] = [];
const { manager } = createManager({ calls, failBackups: true });
let errorMessage = '';
try {
await manager.deleteServiceById('service-1');
} catch (error) {
errorMessage = (error as Error).message;
}
expect(errorMessage).toEqual('backup cleanup failed');
expect(calls).not.toContain('delete:service');
expect(calls).not.toContain('push:coreflow');
});
export default tap.start();