Repository: hashgraph/hedera-local-node
Pull request #509 (2024-01-24 11:17:04)

feat: Create unit test for StopState #507
**Description**:
Create unit test for StopState

**Related issue(s)**:
https://github.com/hashgraph/hedera-local-node/issues/507

sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=hashgraph_hedera-local-node&pullRequest=509) **Quality Gate passed**  
Kudos, no new issues were introduced!

[0 New issues](https://sonarcloud.io/project/issues?id=hashgraph_hedera-local-node&pullRequest=509&resolved=false&inNewCodePeriod=true)  
[0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=hashgraph_hedera-local-node&pullRequest=509&resolved=false&inNewCodePeriod=true)  
No data about Coverage  
[0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=hashgraph_hedera-local-node&pullRequest=509&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=hashgraph_hedera-local-node&pullRequest=509)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=hashgraph_hedera-local-node&pullRequest=509) **Quality Gate passed**  
Kudos, no new issues were introduced!

[0 New issues](https://sonarcloud.io/project/issues?id=hashgraph_hedera-local-node&pullRequest=509&resolved=false&inNewCodePeriod=true)  
[0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=hashgraph_hedera-local-node&pullRequest=509&resolved=false&inNewCodePeriod=true)  
No data about Coverage  
[0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=hashgraph_hedera-local-node&pullRequest=509&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=hashgraph_hedera-local-node&pullRequest=509)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=hashgraph_hedera-local-node&pullRequest=509) **Quality Gate passed**  
Kudos, no new issues were introduced!

[0 New issues](https://sonarcloud.io/project/issues?id=hashgraph_hedera-local-node&pullRequest=509&resolved=false&inNewCodePeriod=true)  
[0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=hashgraph_hedera-local-node&pullRequest=509&resolved=false&inNewCodePeriod=true)  
No data about Coverage  
[0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=hashgraph_hedera-local-node&pullRequest=509&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=hashgraph_hedera-local-node&pullRequest=509)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=hashgraph_hedera-local-node&pullRequest=509) **Quality Gate passed**  
Kudos, no new issues were introduced!

[0 New issues](https://sonarcloud.io/project/issues?id=hashgraph_hedera-local-node&pullRequest=509&resolved=false&inNewCodePeriod=true)  
[0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=hashgraph_hedera-local-node&pullRequest=509&resolved=false&inNewCodePeriod=true)  
No data about Coverage  
[0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=hashgraph_hedera-local-node&pullRequest=509&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=hashgraph_hedera-local-node&pullRequest=509)



Patch:
diff --git a/package.json b/package.json
index d972672..f4b6d6b 100644
--- a/package.json
+++ b/package.json
@@ -18,7 +18,7 @@
     "precommit": "npm run pretty",
     "prepush": "npm run lint",
     "prepublishOnly": "npm run build",
-    "test": "ts-mocha ./test/unit/controller/StateController.spec.ts"
+    "test": "ts-mocha ./test/**/*.spec.ts"
   },
   "bin": {
     "hedera": "./build/index.js"
diff --git a/src/constants.ts b/src/constants.ts
index b851db5..a87558f 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -68,3 +68,13 @@ export const INFO_COLOR = '\x1b[32m'
 export const DEBUG_COLOR = '\x1b[36m'
 // white
 export const TRACE_COLOR = '\x1b[37m'
+
+// Stop State
+export const STOP_STATE_INIT_MESSAGE = 'Stop State Initialized!';
+export const STOP_STATE_ON_START_MESSAGE = 'Initiating stop procedure. Trying to stop docker containers and clean up volumes...';
+export const STOP_STATE_STOPPING_MESSAGE = 'Stopping the network...';
+export const STOP_STATE_STOPPED_MESSAGE = 'Hedera Local Node was stopped successfully.';
+
+// Docker commands
+export const DOCKER_STOPPING_CONTAINERS_MESSAGE = 'Stopping the docker containers...';
+export const DOCKER_CLEANING_VALUMES_MESSAGE = 'Cleaning the volumes and temp files...';
diff --git a/src/state/StopState.ts b/src/state/StopState.ts
index 89f4f06..55e9da5 100644
--- a/src/state/StopState.ts
+++ b/src/state/StopState.ts
@@ -25,11 +25,19 @@ import { LoggerService } from '../services/LoggerService';
 import { ServiceLocator } from '../services/ServiceLocator';
 import { IState } from './IState';
 import { EventType } from '../types/EventType';
-import { IS_WINDOWS } from '../constants';
+import {
+    DOCKER_CLEANING_VALUMES_MESSAGE,
+    DOCKER_STOPPING_CONTAINERS_MESSAGE,
+    IS_WINDOWS,
+    STOP_STATE_INIT_MESSAGE,
+    STOP_STATE_ON_START_MESSAGE,
+    STOP_STATE_STOPPED_MESSAGE,
+    STOP_STATE_STOPPING_MESSAGE
+} from '../constants';
 import { CLIOptions } from '../types/CLIOptions';
 import { CLIService } from '../services/CLIService';
 
-export class StopState implements IState{
+export class StopState implements IState {
     /**
      * The logger service used for logging.
      */
@@ -58,7 +66,7 @@ export class StopState implements IState{
         this.stateName = StopState.name;
         this.logger = ServiceLocator.Current.get<LoggerService>(LoggerService.name);
         this.cliOptions = ServiceLocator.Current.get<CLIService>(CLIService.name).getCurrentArgv();
-        this.logger.trace('Stop State Initialized!', this.stateName);
+        this.logger.trace(STOP_STATE_INIT_MESSAGE, this.stateName);
     }
 
     /**
@@ -78,24 +86,24 @@ export class StopState implements IState{
      * @returns {Promise<void>} A promise that resolves when the procedure is finished.
      */
     public async onStart(): Promise<void> {
-        this.logger.info('Initiating stop procedure. Trying to stop docker containers and clean up volumes...', this.stateName);
+        this.logger.info(STOP_STATE_ON_START_MESSAGE, this.stateName);
 
         const nullOutput = this.getNullOutput();
         const rootPath = process.cwd();
 
-        this.logger.info('Stopping the network...', this.stateName);
+        this.logger.info(STOP_STATE_STOPPING_MESSAGE, this.stateName);
         shell.cd(__dirname);
         shell.cd('../../');
-        this.logger.trace('Stopping the docker containers...', this.stateName);
+        this.logger.trace(DOCKER_STOPPING_CONTAINERS_MESSAGE, this.stateName);
         shell.exec(`docker compose kill --remove-orphans 2>${nullOutput}`);
         shell.exec(`docker compose down -v --remove-orphans 2>${nullOutput}`);
-        this.logger.trace('Cleaning the volumes and temp files...', this.stateName);
+        this.logger.trace(DOCKER_CLEANING_VALUMES_MESSAGE, this.stateName);
         shell.exec(`rm -rf network-logs/* >${nullOutput} 2>&1`);
         this.logger.trace(`Working dir is ${this.cliOptions.workDir}`, this.stateName);
         shell.exec(`rm -rf "${join(this.cliOptions.workDir, 'network-logs')}" >${nullOutput} 2>&1`);
         shell.exec(`docker network prune -f 2>${nullOutput}`);
         shell.cd(rootPath);
-        this.logger.info('Hedera Local Node was stopped successfully.', this.stateName);
+        this.logger.info(STOP_STATE_STOPPED_MESSAGE, this.stateName);
         this.observer!.update(EventType.Finish);
     }
 
@@ -105,8 +113,9 @@ export class StopState implements IState{
      * On other operating systems, it returns "/dev/null".
      * @returns {string}
      */
-    private getNullOutput (): "null" | "/dev/null" {
+    private getNullOutput(): "null" | "/dev/null" {
         if (IS_WINDOWS) return 'null';
         return '/dev/null';
     }
+
 }


Test patch:
diff --git a/test/unit/controller/StateController.spec.ts b/test/unit/controller/StateController.spec.ts
index 8dc97ea..4f78b25 100644
--- a/test/unit/controller/StateController.spec.ts
+++ b/test/unit/controller/StateController.spec.ts
@@ -136,8 +136,18 @@ describe('StateController', () => {
   describe('update', () => {
     let stateController: StateController;
     let getStartConfigurationStub: sinon.SinonStub;
+    let getStub1;
 
     before(() => {
+      const loggerServiceStub = sinon.createStubInstance(LoggerService);
+      const cliServiceStub = sinon.createStubInstance(CLIService);
+      
+      // Stub the first call to ServiceLocator.Current.get
+      getStub1 = sinon.stub(ServiceLocator.Current, 'get');
+      getStub1.onFirstCall().returns(loggerServiceStub);
+      getStub1.onSecondCall().returns(cliServiceStub);
+      getStub1.onThirdCall().returns(loggerServiceStub);
+
       getStartConfigurationStub = sinon.stub(StateData.prototype, <any>"getStartConfiguration").returns({
         'stateMachineName' : 'start',
         'states' : []
@@ -147,6 +157,7 @@ describe('StateController', () => {
 
     after(() => {
       getStartConfigurationStub.restore();
+      getStub1.restore();
     });
 
     it('should handle other events correctly', async () => {
diff --git a/test/unit/states/AccountCreationState.spec.ts b/test/unit/states/AccountCreationState.spec.ts
new file mode 100644
index 0000000..fd77c43
--- /dev/null
+++ b/test/unit/states/AccountCreationState.spec.ts
@@ -0,0 +1,373 @@
+/*-
+ *
+ * Hedera Local Node
+ *
+ * Copyright (C) 2023 Hedera Hashgraph, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import { expect } from 'chai';
+import sinon from 'sinon';
+import { AccountCreationState } from '../../../src/state/AccountCreationState';
+import { LoggerService } from '../../../src/services/LoggerService';
+import { CLIService } from '../../../src/services/CLIService'
+import { ClientService } from '../../../src/services/ClientService';
+import { ServiceLocator } from '../../../src/services/ServiceLocator';
+import { PrivateKey } from '@hashgraph/sdk';
+
+describe('AccountCreationState', () => {
+  let accountCreationState: AccountCreationState;
+  let loggerServiceStub: sinon.SinonStubbedInstance<LoggerService>;
+  let cliServiceStub: sinon.SinonStubbedInstance<CLIService>;
+  let clientServiceStub: sinon.SinonStubbedInstance<ClientService>;
+  let serviceLocatorStub: sinon.SinonStub;
+  const observer = { update: sinon.stub() };
+
+  beforeEach(() => {
+    loggerServiceStub = sinon.createStubInstance(LoggerService);
+    cliServiceStub = sinon.createStubInstance(CLIService, {
+      getCurrentArgv: {
+        async: false,
+        blocklisting: false,
+        balance: 1000,
+        accounts: 10,
+        startup: false,
+      } as any
+    });
+    clientServiceStub = sinon.createStubInstance(ClientService);
+
+    serviceLocatorStub = sinon.stub(ServiceLocator.Current, 'get');
+    serviceLocatorStub.withArgs('LoggerService').returns(loggerServiceStub);
+    serviceLocatorStub.withArgs('CLIService').returns(cliServiceStub);
+    serviceLocatorStub.withArgs('ClientService').returns(clientServiceStub);
+    accountCreationState = new AccountCreationState();
+  });
+
+  afterEach(() => {
+    serviceLocatorStub.restore();
+    cliServiceStub.getCurrentArgv.restore();
+  })
+
+  describe('onStart', () => {
+    it('should log information when starting in synchronous mode', async () => {
+      const generateECDSAStub = sinon.stub(AccountCreationState.prototype, <any>'generateECDSA').resolves();
+      const generateAliasECDSA = sinon.stub(AccountCreationState.prototype, <any>'generateAliasECDSA').resolves();
+      const generateED25519 = sinon.stub(AccountCreationState.prototype, <any>'generateED25519').resolves();
+      await accountCreationState.subscribe(observer);
+      await accountCreationState.onStart();
+
+      sinon.assert.calledWith(loggerServiceStub.info, 'Starting Account Creation state in synchronous mode ', 'AccountCreationState');
+      sinon.assert.called(generateECDSAStub);
+      sinon.assert.called(generateAliasECDSA);
+      sinon.assert.called(generateED25519);
+      sinon.assert.called(observer.update);
+      generateECDSAStub.restore();
+      generateAliasECDSA.restore();
+      generateED25519.restore();
+    });
+
+    it('should log information when starting in asynchronous mode', async () => {
+      cliServiceStub.getCurrentArgv.returns({
+        async: true,
+        blocklisting: false,
+        balance: 1000,
+        accounts: 5,
+        startup: false,
+      } as any);
+      const generateAsyncStub = sinon.stub(AccountCreationState.prototype, <any>'generateAsync').resolves();
+
+      await accountCreationState.subscribe(observer);
+      await accountCreationState.onStart();
+      sinon.assert.calledWith(loggerServiceStub.info, 'Starting Account Creation state in asynchronous mode ', 'AccountCreationState');
+      sinon.assert.called(generateAsyncStub);
+      generateAsyncStub.restore();
+    });
+
+    it('should log information when blocklisting is enabled', async () => {
+      cliServiceStub.getCurrentArgv.returns({
+        async: true,
+        blocklisting: true,
+        balance: 1000,
+        accounts: 5,
+        startup: false,
+      } as any);
+      const getBlocklistedAccountsCountStub = sinon.stub(AccountCreationState.prototype, <any>'getBlocklistedAccountsCount').returns(1);
+      const generateAsyncStub = sinon.stub(AccountCreationState.prototype, <any>'generateAsync').resolves();
+      await accountCreationState.subscribe(observer);
+      await accountCreationState.onStart();
+
+      sinon.assert.calledWith(loggerServiceStub.info, 'Starting Account Creation state in asynchronous mode with 1 blocklisted accounts', 'AccountCreationState');
+      sinon.assert.called(generateAsyncStub);
+      sinon.assert.called(getBlocklistedAccountsCountStub);
+      getBlocklistedAccountsCountStub.restore();
+      generateAsyncStub.restore();
+    });
+  });
+
+  describe('subscribe', () => {
+    it('should set the observer', () => {
+      accountCreationState.subscribe(observer);
+      expect(accountCreationState['observer']).to.equal(observer);
+    });
+  });
+
+  describe('generate methods', () => {
+    let logAccountTitleStub: sinon.SinonStub;
+    let createAccountStub: sinon.SinonStub;
+    let logAliasAccountStub: sinon.SinonStub;
+    let logAliasAccountTitleStub: sinon.SinonStub;
+    let logAccountDividerStub: sinon.SinonStub;
+    let createAccountAsyncStub: sinon.SinonStub;
+
+    beforeEach(() => {
+      logAccountTitleStub = sinon.stub(AccountCreationState.prototype, <any>'logAccountTitle').resolves();
+      createAccountStub = sinon.stub(AccountCreationState.prototype, <any>'createAccount').resolves();
+      logAliasAccountStub = sinon.stub(AccountCreationState.prototype, <any>'logAliasAccount').resolves();
+      logAliasAccountTitleStub = sinon.stub(AccountCreationState.prototype, <any>'logAliasAccountTitle').resolves();
+      logAccountDividerStub = sinon.stub(AccountCreationState.prototype, <any>'logAccountDivider').resolves();
+      createAccountAsyncStub = sinon.stub(AccountCreationState.prototype, <any>'createAccountAsync');
+    })
+
+    afterEach(() => {
+      logAccountTitleStub.restore();
+      createAccountStub.restore();
+      logAliasAccountStub.restore();
+      logAliasAccountTitleStub.restore();
+      logAccountDividerStub.restore();
+      createAccountAsyncStub.restore();
+    })
+
+    describe('generateECDSA', () => {
+      let generateAliasECDSA: sinon.SinonStub;
+      let generateED25519: sinon.SinonStub;
+      let privateKeyStub: sinon.SinonStub;
+  
+      beforeEach(() => {
+        generateAliasECDSA = sinon.stub(AccountCreationState.prototype, <any>'generateAliasECDSA').resolves();
+        generateED25519 = sinon.stub(AccountCreationState.prototype, <any>'generateED25519').resolves();
+        privateKeyStub = sinon.stub(PrivateKey, 'generateECDSA').returns({'privateKey': 'some'} as any);
+      })
+  
+      afterEach(() => {
+        generateAliasECDSA.restore();
+        generateED25519.restore();
+        privateKeyStub.restore();
+      })
+  
+      it('should generate ECDSA accounts synchronously and log the title and divider', async () => {
+        cliServiceStub.getCurrentArgv.returns({
+          async: true,
+          blocklisting: false,
+          balance: 1000,
+          accounts: 5,
+          startup: false,
+        } as any);
+        await accountCreationState.subscribe(observer);
+        await accountCreationState.onStart();
+
+        sinon.assert.calledWith(loggerServiceStub.info, 'Starting Account Creation state in asynchronous mode ', 'AccountCreationState');
+        sinon.assert.called(generateAliasECDSA);
+        sinon.assert.called(generateED25519);
+        sinon.assert.callCount(privateKeyStub, 5);
+        sinon.assert.calledOnce(logAccountTitleStub);
+        sinon.assert.called(observer.update);
+        sinon.assert.called(logAccountDividerStub);
+        sinon.assert.callCount(createAccountAsyncStub, 5);
+      });
+
+      it('should generate ECDSA accounts аsynchronously and log the title and divider', async () => {
+        await accountCreationState.subscribe(observer);
+        await accountCreationState.onStart();
+
+        sinon.assert.calledWith(loggerServiceStub.info, 'Starting Account Creation state in synchronous mode ', 'AccountCreationState');
+        sinon.assert.called(generateAliasECDSA);
+        sinon.assert.called(generateED25519);
+        sinon.assert.callCount(privateKeyStub, 10);
+        sinon.assert.callCount(createAccountStub, 10);
+        sinon.assert.calledOnce(logAccountTitleStub);
+        sinon.assert.called(observer.update);
+        sinon.assert.called(logAccountDividerStub);
+      });
+    });
+  
+    describe('generateAliasECDSA', () => {
+      let generateECDSA: sinon.SinonStub;
+      let generateED25519: sinon.SinonStub;
+      let createAliasAccountStub: sinon.SinonStub;
+  
+      beforeEach(() => {
+        generateECDSA = sinon.stub(AccountCreationState.prototype, <any>'generateECDSA').resolves();
+        generateED25519 = sinon.stub(AccountCreationState.prototype, <any>'generateED25519').resolves();
+        createAliasAccountStub = sinon.stub(AccountCreationState.prototype, <any>'createAliasAccount').returns({
+          'accountId': 'someid',
+          'wallet': {'signingKey': {'privateKey': ''}},
+          'balance': 'balance'
+        });
+      })
+  
+      afterEach(() => {
+        generateECDSA.restore();
+        generateED25519.restore();
+        createAliasAccountStub.restore();
+      })
+  
+      it('should generate ECDSA accounts synchronously and log the title and divider', async () => {
+        await accountCreationState.subscribe(observer);
+        await accountCreationState.onStart();
+
+        sinon.assert.calledWith(loggerServiceStub.info, 'Starting Account Creation state in synchronous mode ', 'AccountCreationState');
+        sinon.assert.called(generateECDSA);
+        sinon.assert.called(generateED25519);
+        sinon.assert.callCount(logAliasAccountStub, 10);
+        sinon.assert.callCount(createAliasAccountStub, 10);
+        sinon.assert.calledOnce(logAliasAccountTitleStub);
+        sinon.assert.called(observer.update);
+      });
+
+      it('should generate ECDSA accounts asynchronously and log the title and divider', async () => {
+        cliServiceStub.getCurrentArgv.returns({
+          async: true,
+          blocklisting: false,
+          balance: 1000,
+          accounts: 6,
+          startup: false,
+        } as any);
+        await accountCreationState.subscribe(observer);
+        await accountCreationState.onStart();
+
+        sinon.assert.calledWith(loggerServiceStub.info, 'Starting Account Creation state in asynchronous mode ', 'AccountCreationState');
+        sinon.assert.called(generateECDSA);
+        sinon.assert.called(generateED25519);
+        sinon.assert.callCount(createAliasAccountStub, 6);
+        sinon.assert.called(observer.update);
+      });
+    });
+  
+    describe('generateED25519', () => {
+      let generateAliasECDSA: sinon.SinonStub;
+      let generateECDSA: sinon.SinonStub;
+      let privateKeyStub: sinon.SinonStub;
+  
+      beforeEach(() => {
+        generateAliasECDSA = sinon.stub(AccountCreationState.prototype, <any>'generateAliasECDSA').resolves();
+        generateECDSA = sinon.stub(AccountCreationState.prototype, <any>'generateECDSA').resolves();
+        privateKeyStub = sinon.stub(PrivateKey, 'generateED25519').returns({'privateKey': 'some'} as any);
+      })
+  
+      afterEach(() => {
+        generateAliasECDSA.restore();
+        generateECDSA.restore();
+        privateKeyStub.restore();
+      })
+  
+      it('should generate ED25519 accounts synchronously and log the title and divider', async () => {
+        await accountCreationState.subscribe(observer);
+        await accountCreationState.onStart();
+
+        sinon.assert.calledWith(loggerServiceStub.info, 'Starting Account Creation state in synchronous mode ', 'AccountCreationState');
+        sinon.assert.called(generateAliasECDSA);
+        sinon.assert.called(generateECDSA);
+        sinon.assert.callCount(privateKeyStub, 10);
+        sinon.assert.callCount(createAccountStub, 10);
+        sinon.assert.calledOnceWithExactly(logAccountTitleStub, 'ED25519');
+        sinon.assert.called(observer.update);
+      });
+
+      it('should generate ED25519 accounts asynchronously and log the title and divider', async () => {
+        cliServiceStub.getCurrentArgv.returns({
+          async: true,
+          blocklisting: false,
+          balance: 1000,
+          accounts: 6,
+          startup: false,
+        } as any);
+        await accountCreationState.subscribe(observer);
+        await accountCreationState.onStart();
+
+        sinon.assert.calledWith(loggerServiceStub.info, 'Starting Account Creation state in asynchronous mode ', 'AccountCreationState');
+        sinon.assert.called(generateAliasECDSA);
+        sinon.assert.called(generateECDSA);
+        sinon.assert.callCount(privateKeyStub, 6);
+        sinon.assert.callCount(createAccountAsyncStub, 6);
+        sinon.assert.called(observer.update);
+      });
+    });
+  
+    describe('generateAsync', () => {
+      let generateAliasECDSA: sinon.SinonStub;
+      let generateECDSA: sinon.SinonStub;
+      let generateED25519: sinon.SinonStub;
+      let logAccountStub: sinon.SinonStub;
+  
+      beforeEach(() => {
+        const account = { 'accountId': 'someid', 'wallet': {'signingKey': {'privateKey': ''}}, 'balance': 'balance'};
+        generateAliasECDSA = sinon.stub(AccountCreationState.prototype, <any>'generateAliasECDSA').returns([
+          { 'accountId': 'someid1', 'wallet': '', 'balance': 'balance'},
+          { 'accountId': 'someid2', 'wallet': '', 'balance': 'balance'},
+          { 'accountId': 'someid3', 'wallet': '', 'balance': 'balance'},
+          { 'accountId': 'someid4', 'wallet': '', 'balance': 'balance'},
+          { 'accountId': 'someid5', 'wallet': '', 'balance': 'balance'}
+        ]);
+        generateED25519 = sinon.stub(AccountCreationState.prototype, <any>'generateED25519').returns([
+          account,
+          account,
+          account,
+          account,
+          account
+        ]);
+        generateECDSA = sinon.stub(AccountCreationState.prototype, <any>'generateECDSA').returns([
+          account,
+          account,
+          account,
+          account,
+          account
+        ]);
+        logAccountStub = sinon.stub(AccountCreationState.prototype, <any>'logAccount').resolves();
+      })
+  
+      afterEach(() => {
+        generateAliasECDSA.restore();
+        generateECDSA.restore();
+        generateED25519.restore();
+        logAccountStub.restore();
+      })
+  
+      it('should generate accounts asynchronously and log the titles and dividers', async () => {
+        cliServiceStub.getCurrentArgv.returns({
+                async: true,
+                blocklisting: false,
+                balance: 1000,
+                accounts: 5,
+                startup: false,
+              } as any);
+        await accountCreationState.subscribe(observer);
+        await accountCreationState.onStart();
+
+        sinon.assert.calledWith(loggerServiceStub.info, 'Starting Account Creation state in asynchronous mode ', 'AccountCreationState');
+        sinon.assert.called(generateECDSA);
+        sinon.assert.called(generateAliasECDSA);
+        sinon.assert.called(generateED25519);
+        sinon.assert.callCount(logAccountStub, 10);
+        sinon.assert.callCount(logAliasAccountStub, 5);
+        sinon.assert.called(logAliasAccountTitleStub);
+        logAccountTitleStub.calledWithExactly(logAccountTitleStub.getCall(0),' ECDSA ');
+        logAccountTitleStub.calledWithExactly(logAccountTitleStub.getCall(1),'ED25519');
+        sinon.assert.called(observer.update);
+        sinon.assert.calledTwice(logAccountDividerStub);
+      });
+    });
+  })
+});
+
diff --git a/test/unit/states/CleanUpState.spec.ts b/test/unit/states/CleanUpState.spec.ts
new file mode 100644
index 0000000..5a0a2c6
--- /dev/null
+++ b/test/unit/states/CleanUpState.spec.ts
@@ -0,0 +1,208 @@
+/*-
+ *
+ * Hedera Local Node
+ *
+ * Copyright (C) 2023-2024 Hedera Hashgraph, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import { expect } from 'chai';
+import sinon, { SinonStub, SinonStubbedInstance } from 'sinon';
+import { CleanUpState } from '../../../src/state/CleanUpState';
+import { LoggerService } from '../../../src/services/LoggerService';
+import { ServiceLocator } from '../../../src/services/ServiceLocator';
+import { CLIService } from '../../../src/services/CLIService';
+import { EventType } from '../../../src/types/EventType';
+import yaml from 'js-yaml';
+import fs from 'fs';
+import path from 'path';
+
+describe('CleanUpState', () => {
+  let cleanUpState: CleanUpState;
+  let observerStub: SinonStub;
+  let serviceLocatorStub: SinonStub;
+  let loggerServiceStub: SinonStubbedInstance<LoggerService>;
+  let cliServiceStub: SinonStubbedInstance<CLIService>;
+
+  beforeEach(() => {
+    // Mock dependencies
+    observerStub = sinon.stub();
+    loggerServiceStub = sinon.createStubInstance(LoggerService);
+    cliServiceStub = sinon.createStubInstance(CLIService, {
+      getCurrentArgv: {
+        async: false,
+        blocklisting: false,
+        balance: 1000,
+        accounts: 10,
+        startup: false,
+      } as any
+    });
+    serviceLocatorStub = sinon.stub(ServiceLocator.Current, 'get');
+    serviceLocatorStub.withArgs('LoggerService').returns(loggerServiceStub);
+    serviceLocatorStub.withArgs('CLIService').returns(cliServiceStub);
+
+    // Create an instance of CleanUpState
+    cleanUpState = new CleanUpState();
+  });
+
+  afterEach(() => {
+    // Restore stubs after each test
+    serviceLocatorStub.restore();
+  });
+
+  describe('subscribe', () => {
+    it('should set the observer', () => {
+      const observer = { update: sinon.stub() };
+      cleanUpState.subscribe(observer);
+
+      // Verify that the observer is set
+      expect((cleanUpState as any).observer).to.equal(observer);
+    });
+  });
+
+  describe('onStart', () => {
+    let revertMirrorNodePropertiesStub: SinonStub;
+    let revertNodePropertiesStub: SinonStub;
+
+    beforeEach(() => {
+      revertNodePropertiesStub = sinon.stub(CleanUpState.prototype, <any>'revertNodeProperties');
+      revertMirrorNodePropertiesStub = sinon.stub(CleanUpState.prototype, <any>'revertMirrorNodeProperties');
+    })
+
+    afterEach(() => {
+      revertNodePropertiesStub.restore();
+      revertMirrorNodePropertiesStub.restore();
+    })
+
+    it('should call revertNodeProperties and revertMirrorNodeProperties', async () => {
+      // Act
+      await cleanUpState.onStart();
+
+      // Assert
+      // Verify that revertNodeProperties and revertMirrorNodeProperties are called
+      expect(revertNodePropertiesStub.calledOnce).to.be.true;
+      expect(revertMirrorNodePropertiesStub.calledOnce).to.be.true;
+      sinon.assert.calledOnceWithExactly(loggerServiceStub.info, 'Initiating clean up procedure. Trying to revert unneeded changes to files...', 'CleanUpState');
+    });
+
+    it('should call observer.update with EventType.Finish', async () => {
+      // Arrange
+      (cleanUpState as any).observer = { update: observerStub };
+
+      // Act
+      await cleanUpState.onStart();
+
+      // Assert
+      expect(observerStub.calledOnceWithExactly(EventType.Finish)).to.be.true;
+    });
+  });
+
+  describe('revertMirrorNodeProperties', () => {
+    let revertNodePropertiesStub: SinonStub;
+    let writeFileSyncStub: SinonStub;
+    let existsSyncStub: SinonStub;
+    let joinStub: SinonStub;
+    const filePath = 'compose-network/mirror-node/application.yml';
+
+    beforeEach(() => {
+      revertNodePropertiesStub = sinon.stub(CleanUpState.prototype, <any>'revertNodeProperties');
+      writeFileSyncStub = sinon.stub(fs, 'writeFileSync');
+      existsSyncStub = sinon.stub(fs, 'existsSync');
+      joinStub = sinon.stub(path, 'join').returns(filePath);
+    })
+
+    afterEach(() => {
+      revertNodePropertiesStub.restore();
+      writeFileSyncStub.restore();
+      existsSyncStub.restore();
+      joinStub.restore();
+    })
+
+    it('should revert mirror node properties', () => {
+      // Arrange
+      const readFileSyncStub = sinon.stub(fs, 'readFileSync');
+      const yamlLoadStub = sinon.stub(yaml, 'load').resolves();
+      const yamlDumpStub = sinon.stub(yaml, 'dump').returns('3');
+      existsSyncStub.returns(true);
+      // Mock file operations
+      const fileContent = '...'; // original content
+      readFileSyncStub.withArgs(filePath).returns(fileContent);
+      yamlLoadStub.withArgs(fileContent).returns({
+        hedera: {
+          mirror: {
+            importer: {
+              dataPath: 'some-data-path',
+              downloader: {
+                sources: 'some-sources',
+                local: 'some-local',
+              },
+            },
+            monitor: {
+              nodes: 'some-nodes',
+            },
+          },
+        },
+      });
+
+      // Act
+      cleanUpState.onStart();
+
+      // Assert
+      // Verify that the properties are deleted and the file is updated
+      sinon.assert.calledOnce(revertNodePropertiesStub);
+      sinon.assert.calledOnceWithExactly(existsSyncStub, 'compose-network/mirror-node/application.yml');
+      sinon.assert.calledOnce(readFileSyncStub);
+      sinon.assert.calledOnce(yamlLoadStub);
+      sinon.assert.calledOnce(writeFileSyncStub);
+      sinon.assert.calledOnceWithExactly(writeFileSyncStub, filePath, '3');
+      yamlDumpStub.restore();
+    });
+
+    it('tests revertMirrorNodeProperties with existsSync false', () => {
+      // Arrange
+      existsSyncStub.returns(false);
+
+      // Act
+      cleanUpState.onStart();
+
+      // Assert
+      sinon.assert.calledOnce(existsSyncStub);
+      sinon.assert.calledOnce(joinStub);
+      sinon.assert.calledThrice(loggerServiceStub.trace);
+      sinon.assert.calledWithExactly(loggerServiceStub.trace, `Mirror Node Properties File doesn't exist at path ${filePath}`,'CleanUpState')
+    });
+  });
+
+  describe('revertNodeProperties', () => {
+    it('should revert node properties', () => {
+      // Arrange
+      sinon.stub(CleanUpState.prototype, <any>'revertMirrorNodeProperties');
+      const writeFileSyncStub = sinon.stub(fs, 'writeFileSync');
+      const existsSyncStub = sinon.stub(fs, 'existsSync').returns(true);
+      const filePath = 'compose-network/mirror-node/application.yml';
+      const joinStub = sinon.stub(path, 'join').returns(filePath);
+
+      // Act
+       cleanUpState.onStart();
+
+      // Assert
+      // Verify that the original properties are written back to the file
+      sinon.assert.calledOnce(existsSyncStub);
+      sinon.assert.calledOnce(joinStub);
+      sinon.assert.calledTwice(loggerServiceStub.trace);
+      sinon.assert.calledOnce(writeFileSyncStub);
+    });
+  });
+});
diff --git a/test/unit/states/StopState.spec.ts b/test/unit/states/StopState.spec.ts
new file mode 100644
index 0000000..d96af8c
--- /dev/null
+++ b/test/unit/states/StopState.spec.ts
@@ -0,0 +1,103 @@
+
+/*-
+ *
+ * Hedera Local Node
+ *
+ * Copyright (C) 2023 Hedera Hashgraph, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import { expect } from 'chai';
+import sinon from 'sinon';
+import { StopState } from '../../../src/state/StopState';
+import shell from 'shelljs';
+import {
+  DOCKER_CLEANING_VALUMES_MESSAGE,
+  DOCKER_STOPPING_CONTAINERS_MESSAGE,
+  STOP_STATE_INIT_MESSAGE,
+  STOP_STATE_ON_START_MESSAGE,
+  STOP_STATE_STOPPED_MESSAGE,
+  STOP_STATE_STOPPING_MESSAGE
+} from '../../../src/constants';
+import path from 'path';
+import { getTestBed } from '../testBed';
+import { LoggerService } from '../../../src/services/LoggerService';
+
+describe('StopState tests', () => {
+    let stopState: StopState,
+        testSandbox: sinon.SinonSandbox, 
+        loggerService: sinon.SinonStubbedInstance<LoggerService>,
+        serviceLocator: sinon.SinonStub<any[], any>;
+
+    const TEST_DIR_MESSAGE = `Working dir is testDir`;
+    const stateDir = path.resolve(__dirname, '../../../src/state');
+
+    before(() => {
+      let { sandbox, loggerServiceStub, serviceLocatorStub } = getTestBed({
+        workDir: 'testDir',
+      });
+
+      testSandbox = sandbox
+      loggerService = loggerServiceStub
+      serviceLocator = serviceLocatorStub
+
+      loggerServiceStub.trace.resetHistory();
+      stopState = new StopState();
+    });
+
+    it('should initialize the Stop State', async () => {
+        expect(stopState).to.be.instanceOf(StopState);
+        testSandbox.assert.calledOnce(loggerService.trace);
+        testSandbox.assert.calledWith(loggerService.trace, STOP_STATE_INIT_MESSAGE, StopState.name);
+        testSandbox.assert.calledWith(serviceLocator, 'LoggerService');
+        testSandbox.assert.calledWith(serviceLocator, 'CLIService');
+    })
+
+    it('should have a subscribe method', async () => {
+        expect(stopState.subscribe).to.be.a('function');
+    })
+
+    it('should execute onStart properly', async () => {
+        const processStub = sinon.stub(process, 'cwd').returns('testDir');
+        sinon.stub(process, 'platform').returns('test');
+        const shellCDStub = sinon.stub(shell, 'cd');
+        const shellExecStub = sinon.stub(shell, 'exec');
+        stopState.onStart();
+
+        // loggin messages
+        testSandbox.assert.calledWith(loggerService.info, STOP_STATE_ON_START_MESSAGE, StopState.name);
+        testSandbox.assert.calledWith(loggerService.info, STOP_STATE_STOPPING_MESSAGE, StopState.name);
+        testSandbox.assert.calledWith(loggerService.trace, DOCKER_STOPPING_CONTAINERS_MESSAGE, StopState.name);
+        testSandbox.assert.calledWith(loggerService.trace, DOCKER_CLEANING_VALUMES_MESSAGE, StopState.name);
+        testSandbox.assert.calledWith(loggerService.trace, TEST_DIR_MESSAGE, StopState.name);
+        testSandbox.assert.calledWith(loggerService.info, STOP_STATE_STOPPED_MESSAGE, StopState.name);
+        
+        // shell commands: 'cd'
+        testSandbox.assert.calledWith(shellCDStub, stateDir);
+        testSandbox.assert.calledWith(shellCDStub, '../../');
+        testSandbox.assert.calledWith(shellCDStub, 'testDir');
+        testSandbox.assert.calledThrice(shellCDStub);
+
+        // shell commands: 'exec'
+        testSandbox.assert.calledWith(shellExecStub, 'docker compose kill --remove-orphans 2>/dev/null');
+        testSandbox.assert.calledWith(shellExecStub, 'docker compose down -v --remove-orphans 2>/dev/null');
+        testSandbox.assert.calledWith(shellExecStub, 'rm -rf network-logs/* >/dev/null 2>&1');
+        testSandbox.assert.calledWith(shellExecStub, 'rm -rf "testDir/network-logs" >/dev/null 2>&1');
+        testSandbox.assert.calledWith(shellExecStub, 'docker network prune -f 2>/dev/null');
+
+        expect(processStub.calledOnce).to.be.true;
+    })
+
+});
diff --git a/test/unit/testBed.ts b/test/unit/testBed.ts
new file mode 100644
index 0000000..1fe64cb
--- /dev/null
+++ b/test/unit/testBed.ts
@@ -0,0 +1,54 @@
+import sinon from "sinon";
+import { LoggerService } from "../../src/services/LoggerService";
+import { CLIService } from "../../src/services/CLIService";
+import { ServiceLocator } from "../../src/services/ServiceLocator";
+import { DockerService } from "../../src/services/DockerService";
+import { ConnectionService } from "../../src/services/ConnectionService";
+
+export interface LocalNodeTestBed {
+    sandbox: sinon.SinonSandbox;
+    loggerServiceStub: sinon.SinonStubbedInstance<LoggerService>;
+    cliServiceStub: sinon.SinonStubbedInstance<CLIService>;
+    serviceLocatorStub: sinon.SinonStub;
+}
+
+let testBead: LocalNodeTestBed
+
+export function getTestBed(cliServiceArgs?: any) {
+    if (testBead) {
+        testBead.sandbox.resetHistory();
+        testBead.loggerServiceStub.trace.resetHistory();
+        return {
+            ...testBead
+        };
+    }
+
+    const sandbox = sinon.createSandbox();
+    const dockerServiceStub = sandbox.createStubInstance(DockerService);
+    const connectionServiceStub = sandbox.createStubInstance(ConnectionService);
+    const loggerServiceStub = sandbox.createStubInstance(LoggerService);
+    const cliServiceStub = sandbox.createStubInstance(CLIService, {
+      getCurrentArgv: {
+        ...cliServiceArgs,
+      } as any
+    });
+
+    const getStub = sandbox.stub();
+    getStub.withArgs(LoggerService.name).returns(loggerServiceStub);
+    getStub.withArgs(CLIService.name).returns(cliServiceStub);
+    getStub.withArgs(DockerService.name).returns(dockerServiceStub);
+    getStub.withArgs(ConnectionService.name).returns(connectionServiceStub);
+
+    sandbox.replaceGetter(ServiceLocator, 'Current', () => sandbox.createStubInstance(ServiceLocator, {
+        get: getStub
+    }));
+
+    testBead = {
+        sandbox,
+        loggerServiceStub,
+        cliServiceStub,
+        serviceLocatorStub: getStub
+    }
+
+    return testBead;
+}
\ No newline at end of file