@hashgraph/solo
Version:
An opinionated CLI tool to deploy and manage private Hedera Networks.
135 lines • 7.62 kB
JavaScript
// 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