UNPKG

@hashgraph/solo

Version:

An opinionated CLI tool to deploy and manage private Hedera Networks.

135 lines 7.62 kB
// SPDX-License-Identifier: Apache-2.0 import sinon from 'sinon'; import { before, beforeEach, afterEach, describe, it } from 'mocha'; import { expect } from 'chai'; import { container } from 'tsyringe-neo'; import { resetForTest } from '../../test-container.js'; import { InjectTokens } from '../../../src/core/dependency-injection/inject-tokens.js'; import { Flags as flags } from '../../../src/commands/flags.js'; import { NamespaceName } from '../../../src/types/namespace/namespace-name.js'; import { Argv } from '../../helpers/argv-wrapper.js'; import { ValueContainer } from '../../../src/core/dependency-injection/value-container.js'; import { K8Client } from '../../../src/integration/kube/k8-client/k8-client.js'; describe('DeploymentCommand unit tests', () => { const namespace = NamespaceName.of('solo-e2e'); const deploymentName = 'deployment'; const k8FactoryStub = sinon.stub(); let containerOverrides; let realK8Factory; let namespacesStub; let configMapsStub; let k8Stub; before(() => { realK8Factory = container.resolve(InjectTokens.K8Factory); }); beforeEach(async () => { namespacesStub = sinon.stub(); configMapsStub = sinon.stub(); k8Stub = {}; const factoryStubbed = k8FactoryStub; factoryStubbed.getK8 = sinon.stub().returns(k8Stub); factoryStubbed.default = sinon.stub().returns(k8Stub); k8Stub.namespaces = sinon.stub().returns({ has: namespacesStub, list: sinon.stub().resolves([]), }); k8Stub.configMaps = sinon.stub().returns({ exists: configMapsStub, listForAllNamespaces: sinon.stub().resolves([]), }); k8Stub.contexts = sinon.stub().returns({ readCurrent: sinon .stub() .returns(new K8Client(undefined, realK8Factory.default().getKubectlExecutablePath()).contexts().readCurrent()), }); k8Stub.clusters = sinon.stub().returns({ readCurrent: sinon.stub().returns(realK8Factory.default().clusters().readCurrent()), }); k8Stub.leases = sinon.stub().returns({ read: sinon.stub().rejects(new Error('not found')), create: sinon.stub().resolves(), delete: sinon.stub().resolves(), update: sinon.stub().resolves(), }); containerOverrides = new Map([[InjectTokens.K8Factory, new ValueContainer(InjectTokens.K8Factory, k8FactoryStub)]]); resetForTest(undefined, undefined, true, containerOverrides); }); afterEach(() => { sinon.restore(); }); describe('create() - stale local config detection', () => { it('should detect stale local config and clean up when namespace does not exist in cluster', async () => { // The test data has a "deployment" entry with cluster-1 → context-1 // Simulate namespace NOT existing in the cluster (stale local config scenario) namespacesStub.resolves(false); const deploymentCommand = container.resolve(InjectTokens.DeploymentCommand); const localConfig = container.resolve(InjectTokens.LocalConfigRuntimeState); const argv = Argv.getDefaultArgv(namespace); argv.setArg(flags.deployment, deploymentName); argv.setArg(flags.namespace, namespace.name); // Should succeed - stale config is cleaned up automatically await expect(deploymentCommand.create(argv.build())).to.eventually.be.true; // Verify the deployment was re-created (still present in local config) await localConfig.load(); const deployment = localConfig.configuration.deployments.find((d) => d.name === deploymentName); expect(deployment).to.not.be.undefined; expect(deployment?.namespace).to.equal(namespace.name); }); it('should detect stale local config and clean up when cluster connection fails', async () => { // Simulate cluster connection failure (e.g., Kind cluster was deleted) k8Stub.namespaces = sinon.stub().returns({ has: sinon.stub().rejects(new Error('connection refused - cluster no longer exists')), list: sinon.stub().rejects(new Error('connection refused')), }); const deploymentCommand = container.resolve(InjectTokens.DeploymentCommand); const argv = Argv.getDefaultArgv(namespace); argv.setArg(flags.deployment, deploymentName); argv.setArg(flags.namespace, namespace.name); // Should succeed - stale config is cleaned up when cluster is unreachable await expect(deploymentCommand.create(argv.build())).to.eventually.be.true; }); it('should throw "already exists" error when deployment genuinely exists in cluster', async () => { // Simulate namespace AND remote config both existing (genuine deployment) namespacesStub.resolves(true); configMapsStub.resolves(true); const deploymentCommand = container.resolve(InjectTokens.DeploymentCommand); const argv = Argv.getDefaultArgv(namespace); argv.setArg(flags.deployment, deploymentName); argv.setArg(flags.namespace, namespace.name); // The outer error is "Error creating deployment" wrapping the actual cause await expect(deploymentCommand.create(argv.build())).to.be.rejectedWith('Error creating deployment'); }); it('should proceed normally when deployment does not exist in local config', async () => { const newDeploymentName = 'brand-new-deployment'; const newNamespace = NamespaceName.of('brand-new-namespace'); const deploymentCommand = container.resolve(InjectTokens.DeploymentCommand); const argv = Argv.getDefaultArgv(newNamespace); argv.setArg(flags.deployment, newDeploymentName); argv.setArg(flags.namespace, newNamespace.name); // Should succeed - new deployment, no conflict await expect(deploymentCommand.create(argv.build())).to.eventually.be.true; }); }); describe('create() - deployment with no cluster refs is treated as stale', () => { it('should clean up stale deployment with no cluster refs and create fresh', async () => { // Manually add a deployment with no cluster refs to local config const localConfig = container.resolve(InjectTokens.LocalConfigRuntimeState); await localConfig.load(); const noClusterDeploymentName = 'no-cluster-deployment'; const noClusterNamespace = NamespaceName.of('no-cluster-ns'); const staleDeployment = localConfig.configuration.deployments.addNew(); staleDeployment.name = noClusterDeploymentName; staleDeployment.namespace = noClusterNamespace.name; staleDeployment.realm = 0; staleDeployment.shard = 0; await localConfig.persist(); const deploymentCommand = container.resolve(InjectTokens.DeploymentCommand); const argv = Argv.getDefaultArgv(noClusterNamespace); argv.setArg(flags.deployment, noClusterDeploymentName); argv.setArg(flags.namespace, noClusterNamespace.name); // Should succeed - deployment with no cluster refs is treated as stale await expect(deploymentCommand.create(argv.build())).to.eventually.be.true; }); }); }); //# sourceMappingURL=deployment.test.js.map