Repository: hashgraph/hedera-local-node
Pull request #512 (2024-01-25 08:17:50)

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


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=512) **Quality Gate passed**  
Kudos, no new issues were introduced!

[0 New issues](https://sonarcloud.io/project/issues?id=hashgraph_hedera-local-node&pullRequest=512&resolved=false&inNewCodePeriod=true)  
[0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=hashgraph_hedera-local-node&pullRequest=512&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=512&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=hashgraph_hedera-local-node&pullRequest=512)


sonarcloud[bot]: ## [![Quality Gate Failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-failed-20px.png 'Quality Gate Failed')](https://sonarcloud.io/dashboard?id=hashgraph_hedera-local-node&pullRequest=512) **Quality Gate failed**  
Failed conditions

[5.1% Duplication on New Code](https://sonarcloud.io/component_measures?id=hashgraph_hedera-local-node&pullRequest=512&metric=new_duplicated_lines_density&view=list) (required ≤ 3%)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=hashgraph_hedera-local-node&pullRequest=512)


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=512) **Quality Gate passed**  
The SonarCloud Quality Gate passed, but some issues were introduced.

[7 New issues](https://sonarcloud.io/project/issues?id=hashgraph_hedera-local-node&pullRequest=512&resolved=false&inNewCodePeriod=true)  
[0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=hashgraph_hedera-local-node&pullRequest=512&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=512&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=hashgraph_hedera-local-node&pullRequest=512)


stefan-stefanooov: > ## [![Quality Gate Passed](https://camo.githubusercontent.com/5889b9ad78888934928f8f18bb6431c836cd7cabe6c3a6a60a1a01434d67cfdb/68747470733a2f2f736f6e6172736f757263652e6769746875622e696f2f736f6e6172636c6f75642d6769746875622d7374617469632d7265736f75726365732f76322f636865636b732f5175616c6974794761746542616467652f71672d7061737365642d323070782e706e67)](https://sonarcloud.io/dashboard?id=hashgraph_hedera-local-node&pullRequest=512) **Quality Gate passed**
> The SonarCloud Quality Gate passed, but some issues were introduced.
> 
> [7 New issues](https://sonarcloud.io/project/issues?id=hashgraph_hedera-local-node&pullRequest=512&resolved=false&inNewCodePeriod=true) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=hashgraph_hedera-local-node&pullRequest=512&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=512&metric=new_duplicated_lines_density&view=list)
> 
> [See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=hashgraph_hedera-local-node&pullRequest=512)

Handled
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=512) **Quality Gate passed**  
The SonarCloud Quality Gate passed, but some issues were introduced.

[7 New issues](https://sonarcloud.io/project/issues?id=hashgraph_hedera-local-node&pullRequest=512&resolved=false&inNewCodePeriod=true)  
[0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=hashgraph_hedera-local-node&pullRequest=512&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=512&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=hashgraph_hedera-local-node&pullRequest=512)


stefan-stefanooov: > Handled in a later request. Please ignore

Handled in a later request. Please ignore
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=512) **Quality Gate passed**  
The SonarCloud Quality Gate passed, but some issues were introduced.

[7 New issues](https://sonarcloud.io/project/issues?id=hashgraph_hedera-local-node&pullRequest=512&resolved=false&inNewCodePeriod=true)  
[0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=hashgraph_hedera-local-node&pullRequest=512&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=512&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=hashgraph_hedera-local-node&pullRequest=512)



Patch:
diff --git a/src/constants.ts b/src/constants.ts
index a87558f..b83d024 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -69,6 +69,13 @@ export const DEBUG_COLOR = '\x1b[36m'
 // white
 export const TRACE_COLOR = '\x1b[37m'
 
+// Start State
+export const START_STATE_INIT_MESSAGE = 'Start State Initialized!';
+export const START_STATE_STARTING_MESSAGE = 'Starting Hedera Local Node...';
+export const START_STATE_ON_START_MESSAGE = 'Initiating start procedure. Trying to start docker containers...';
+export const START_STATE_STARTED_DETECTING = 'Detecting network...';
+export const START_STATE_STARTED_MESSAGE = 'Hedera Local Node successfully started!';
+
 // 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...';
diff --git a/src/services/DockerService.ts b/src/services/DockerService.ts
index 373c37d..38dc143 100644
--- a/src/services/DockerService.ts
+++ b/src/services/DockerService.ts
@@ -21,6 +21,7 @@
 import Dockerode from 'dockerode';
 import shell from 'shelljs';
 import semver from'semver';
+import fs from 'fs';
 import { IS_WINDOWS, NECESSARY_PORTS, UNKNOWN_VERSION, OPTIONAL_PORTS, 
          MIN_CPUS, MIN_MEMORY_MULTI_MODE, MIN_MEMORY_SINGLE_MODE,
          RECOMMENDED_CPUS, RECOMMENDED_MEMORY_SINGLE_MODE } from '../constants';
@@ -29,6 +30,8 @@ import { LoggerService } from './LoggerService';
 import { ServiceLocator } from './ServiceLocator';
 import detectPort from 'detect-port';
 import * as dotenv from 'dotenv';
+import { CLIOptions } from '../types/CLIOptions';
+import path from 'path';
 
 dotenv.config();
 
@@ -313,4 +316,63 @@ export class DockerService implements IService{
           });
         });
     }
+
+
+    /**
+     * Executes the docker compose up command.
+     * 
+     * @private
+     * @returns {Promise<shell.ShellString>} A promise that resolves with the output of the command.
+     */
+    public async dockerComposeUp(cliOptions: CLIOptions): Promise<shell.ShellString> {
+      // TODO: Add multi node option
+      const composeFiles = ['docker-compose.yml'];
+      const { fullMode } = cliOptions;
+      const { userCompose } = cliOptions;
+      const { userComposeDir } = cliOptions;
+      const { multiNode } = cliOptions;
+
+      if (!fullMode) {
+          composeFiles.push('docker-compose.evm.yml');
+      }
+
+      if (multiNode) {
+          composeFiles.push('docker-compose.multinode.yml');
+      }
+
+      if (!fullMode && multiNode) {
+          composeFiles.push('docker-compose.multinode.evm.yml');
+      }
+
+      if (userCompose) {
+          composeFiles.push(...this.getUserComposeFiles(userComposeDir));
+      }
+
+      return shell.exec(
+          `docker compose -f ${composeFiles.join(' -f ')} up -d 2>${this.getNullOutput()}`
+      );
+    }
+
+    /**
+     *  Retrieves an array of user compose files from the specified directory.
+     * 
+     * @private
+     * @param {string} userComposeDir - The directory path where the user compose files are located. Defaults to './overrides/'.
+     * @returns {Array<string>} An array of user compose file paths.
+     */
+    private getUserComposeFiles(userComposeDir: string = './overrides/'): Array<string> {
+        let dirPath = path.normalize(userComposeDir);
+        if (!dirPath.endsWith(path.sep)) {
+          dirPath += path.sep;
+        }
+        if (fs.existsSync(dirPath)) {
+          const files = fs
+            .readdirSync(dirPath)
+            .filter((file) => path.extname(file).toLowerCase() === '.yml')
+            .sort()
+            .map((file) => dirPath.concat(file));
+          return files;
+        } 
+          return [];
+    }
 }
diff --git a/src/state/StartState.ts b/src/state/StartState.ts
index 5ee44e1..3a55edb 100644
--- a/src/state/StartState.ts
+++ b/src/state/StartState.ts
@@ -31,6 +31,7 @@ import { IState } from './IState';
 import { ConnectionService } from '../services/ConnectionService';
 import { LocalNodeErrors } from '../Errors/LocalNodeErrors';
 import { DockerService } from '../services/DockerService';
+import { START_STATE_INIT_MESSAGE, START_STATE_STARTED_DETECTING, START_STATE_STARTED_MESSAGE, START_STATE_STARTING_MESSAGE } from '../constants';
 
 export class StartState implements IState{
     /**
@@ -74,7 +75,7 @@ export class StartState implements IState{
         this.cliOptions = ServiceLocator.Current.get<CLIService>(CLIService.name).getCurrentArgv();
         this.dockerService = ServiceLocator.Current.get<DockerService>(DockerService.name);
         this.connectionService = ServiceLocator.Current.get<ConnectionService>(ConnectionService.name);
-        this.logger.trace('Start State Initialized!', this.stateName);
+        this.logger.trace(START_STATE_INIT_MESSAGE, this.stateName);
     }
 
     /**
@@ -94,19 +95,20 @@ export class StartState implements IState{
      * @returns {Promise<void>} A Promise that resolves when the Hedera Local Node is successfully started.
      */
     public async onStart(): Promise<void> {
-        this.logger.info('Starting Hedera Local Node...', this.stateName);
+        this.logger.info(START_STATE_STARTING_MESSAGE, this.stateName);
 
         const rootPath = process.cwd();
 
         shell.cd(__dirname);
         shell.cd('../../');
-        const output = await this.dockerComposeUp();
+        const output = await this.dockerService.dockerComposeUp(this.cliOptions);
+        
         if (output.code === 1) {
             this.observer?.update(EventType.DockerError);
-            await this.dockerComposeUp();
+            await this.dockerService.dockerComposeUp(this.cliOptions);
         }
         shell.cd(rootPath);
-        this.logger.info('Detecting network...', this.stateName);
+        this.logger.info(START_STATE_STARTED_DETECTING, this.stateName);
 
         try {
             await this.connectionService.waitForFiringUp(5600);
@@ -120,65 +122,7 @@ export class StartState implements IState{
         }
 
         await this.logger.updateStatusBoard();
-        this.logger.info('Hedera Local Node successfully started!', this.stateName);
-        this.observer!.update(EventType.Finish);
-    }
-
-    /**
-     * Executes the docker compose up command.
-     * 
-     * @private
-     * @returns {Promise<shell.ShellString>} A promise that resolves with the output of the command.
-     */
-    private async dockerComposeUp(): Promise<shell.ShellString> {
-        // TODO: Add multi node option
-        const composeFiles = ['docker-compose.yml'];
-        const { fullMode } = this.cliOptions;
-        const { userCompose } = this.cliOptions;
-        const { userComposeDir } = this.cliOptions;
-        const { multiNode } = this.cliOptions;
-
-        if (!fullMode) {
-            composeFiles.push('docker-compose.evm.yml');
-        }
-
-        if (multiNode) {
-            composeFiles.push('docker-compose.multinode.yml');
-        }
-
-        if (!fullMode && multiNode) {
-            composeFiles.push('docker-compose.multinode.evm.yml');
-        }
-
-        if (userCompose) {
-            composeFiles.push(...this.getUserComposeFiles(userComposeDir));
-        }
-
-        return shell.exec(
-            `docker compose -f ${composeFiles.join(' -f ')} up -d 2>${this.dockerService.getNullOutput()}`
-        );
-    }
-
-    /**
-     *  Retrieves an array of user compose files from the specified directory.
-     * 
-     * @private
-     * @param {string} userComposeDir - The directory path where the user compose files are located. Defaults to './overrides/'.
-     * @returns {Array<string>} An array of user compose file paths.
-     */
-    private getUserComposeFiles(userComposeDir: string = './overrides/'): Array<string> {
-        let dirPath = path.normalize(userComposeDir);
-        if (!dirPath.endsWith(path.sep)) {
-          dirPath += path.sep;
-        }
-        if (fs.existsSync(dirPath)) {
-          const files = fs
-            .readdirSync(dirPath)
-            .filter((file) => path.extname(file).toLowerCase() === '.yml')
-            .sort()
-            .map((file) => dirPath.concat(file));
-          return files;
-        } 
-          return [];
+        this.logger.info(START_STATE_STARTED_MESSAGE, this.stateName);
+        this.observer?.update(EventType.Finish);
     }
 }
diff --git a/src/state/StopState.ts b/src/state/StopState.ts
index 55e9da5..8454032 100644
--- a/src/state/StopState.ts
+++ b/src/state/StopState.ts
@@ -104,7 +104,7 @@ export class StopState implements IState {
         shell.exec(`docker network prune -f 2>${nullOutput}`);
         shell.cd(rootPath);
         this.logger.info(STOP_STATE_STOPPED_MESSAGE, this.stateName);
-        this.observer!.update(EventType.Finish);
+        this.observer?.update(EventType.Finish);
     }
 
     /**


Test patch:
diff --git a/test/unit/states/StopState.spec.ts b/test/unit/states/StopState.spec.ts
index d96af8c..1d68771 100644
--- a/test/unit/states/StopState.spec.ts
+++ b/test/unit/states/StopState.spec.ts
@@ -39,19 +39,29 @@ describe('StopState tests', () => {
     let stopState: StopState,
         testSandbox: sinon.SinonSandbox, 
         loggerService: sinon.SinonStubbedInstance<LoggerService>,
-        serviceLocator: sinon.SinonStub<any[], any>;
+        serviceLocator: sinon.SinonStub,
+        processTest: {[key: string]: sinon.SinonStub},
+        shellTest: {[key: string]: sinon.SinonStub};
 
     const TEST_DIR_MESSAGE = `Working dir is testDir`;
     const stateDir = path.resolve(__dirname, '../../../src/state');
 
     before(() => {
-      let { sandbox, loggerServiceStub, serviceLocatorStub } = getTestBed({
+      let {
+        sandbox,
+        loggerServiceStub,
+        serviceLocatorStub,
+        proccesStubs,
+        shellStubs
+      } = getTestBed({
         workDir: 'testDir',
       });
 
       testSandbox = sandbox
       loggerService = loggerServiceStub
       serviceLocator = serviceLocatorStub
+      processTest = proccesStubs
+      shellTest = shellStubs
 
       loggerServiceStub.trace.resetHistory();
       stopState = new StopState();
@@ -70,11 +80,8 @@ describe('StopState tests', () => {
     })
 
     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();
+        const { shellCDStub, shellExecStub }= shellTest;
+        await stopState.onStart();
 
         // loggin messages
         testSandbox.assert.calledWith(loggerService.info, STOP_STATE_ON_START_MESSAGE, StopState.name);
@@ -97,7 +104,7 @@ describe('StopState tests', () => {
         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;
+        expect(processTest.processCWDStub.calledOnce).to.be.true;
     })
 
 });
diff --git a/test/unit/states/startState.spec.ts b/test/unit/states/startState.spec.ts
new file mode 100644
index 0000000..5500a36
--- /dev/null
+++ b/test/unit/states/startState.spec.ts
@@ -0,0 +1,158 @@
+/*-
+ *
+ * Hedera Local Node
+ *
+ * Copyright (C) 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 { SinonSandbox, SinonSpy, SinonStub, SinonStubbedInstance } from 'sinon';
+import shell, { ShellString } from 'shelljs';
+import { LoggerService } from '../../../src/services/LoggerService';
+import { StartState } from '../../../src/state/StartState';
+import { getTestBed } from '../testBed';
+import {
+    START_STATE_INIT_MESSAGE,
+    START_STATE_STARTED_DETECTING,
+    START_STATE_STARTED_MESSAGE,
+    START_STATE_STARTING_MESSAGE
+} from '../../../src/constants';
+import { resolve } from 'path';
+import { DockerService } from '../../../src/services/DockerService';
+import { EventType } from '../../../src/types/EventType';
+import { assert } from 'console';
+import { ConnectionService } from '../../../src/services/ConnectionService';
+import { LocalNodeErrors } from '../../../src/Errors/LocalNodeErrors';
+
+describe('StartState tests', () => {
+    let startState: StartState,
+    testSandbox: SinonSandbox, 
+    loggerService: SinonStubbedInstance<LoggerService>,
+    serviceLocator: SinonStub,
+    dockerService: SinonStubbedInstance<DockerService>,
+    stateDir: string,
+    processTestBed: {[key: string]: SinonStub},
+    shellTestBed: {[key: string]: SinonStub},
+    observerSpy: SinonSpy,
+    connectionService: SinonStubbedInstance<ConnectionService>;
+
+    before(() => {
+        const { 
+            sandbox,
+            loggerServiceStub,
+            serviceLocatorStub,
+            proccesStubs,
+            shellStubs,
+            dockerServiceStub,
+            connectionServiceStub
+        } = getTestBed({
+            workDir: 'testDir',
+        });
+        stateDir = resolve(__dirname, '../../../src/state');
+    
+        testSandbox = sandbox
+        dockerService = dockerServiceStub
+        loggerService = loggerServiceStub
+        serviceLocator = serviceLocatorStub
+        processTestBed = proccesStubs
+        shellTestBed = shellStubs
+        connectionService = connectionServiceStub
+
+        startState = new StartState();
+        observerSpy = testSandbox.spy();
+        const observer = {
+            update: observerSpy
+        }
+        startState.subscribe(observer);
+    });
+
+    afterEach(() => {
+        testSandbox.resetHistory();
+        observerSpy.resetHistory();
+        dockerService.dockerComposeUp.reset()
+    });
+
+    it('should initialize the Start State', async () => {
+        expect(startState).to.be.instanceOf(StartState);
+        testSandbox.assert.calledWith(serviceLocator, 'LoggerService');
+        testSandbox.assert.calledWith(serviceLocator, 'CLIService');
+        testSandbox.assert.calledWith(serviceLocator, 'DockerService');
+        testSandbox.assert.calledWith(serviceLocator, 'ConnectionService');
+        testSandbox.assert.calledOnce(loggerService.trace);
+        testSandbox.assert.calledWith(loggerService.trace, START_STATE_INIT_MESSAGE, StartState.name);
+    })
+
+    it('should have a subscribe method', async () => {
+        expect(startState.subscribe).to.be.a('function');
+    })
+
+    it('should execute onStart properly and send Finish event', async () => {
+        const { shellCDStub }= shellTestBed;
+        dockerService.dockerComposeUp.resolves({code: 0} as ShellString);
+        await startState.onStart();
+
+        // shell commands: 'cd'
+        testSandbox.assert.calledWith(shellCDStub, stateDir);
+        testSandbox.assert.calledWith(shellCDStub, '../../');
+        testSandbox.assert.calledWith(shellCDStub, 'testDir');
+        testSandbox.assert.calledThrice(shellCDStub);
+
+        // loggin messages
+        testSandbox.assert.calledThrice(loggerService.info)
+        testSandbox.assert.calledWith(loggerService.info, START_STATE_STARTING_MESSAGE, StartState.name);
+        testSandbox.assert.calledWith(loggerService.info, START_STATE_STARTED_DETECTING, StartState.name);
+        testSandbox.assert.calledWith(loggerService.info, START_STATE_STARTED_MESSAGE, StartState.name);
+
+        testSandbox.assert.calledOnce(processTestBed.processCWDStub);
+        testSandbox.assert.calledWith(observerSpy, EventType.Finish);
+    })
+
+    it('should execute onStart and send DockerError event (when dockerComposeUp status code eq 1)', async () => {
+        dockerService.dockerComposeUp.resolves({code: 1} as ShellString);
+
+        await startState.onStart();
+
+        testSandbox.assert.calledTwice(dockerService.dockerComposeUp);
+        testSandbox.assert.match(observerSpy.callCount, 2);
+        testSandbox.assert.match(observerSpy.args[0], EventType.DockerError);
+        testSandbox.assert.match(observerSpy.args[1], EventType.Finish);
+    })
+
+    it('should execute onStart and handle connectionService error (LocalNodeError)', async () => {
+        connectionService.waitForFiringUp.throws(new LocalNodeErrors('test error', 'message'));
+        dockerService.dockerComposeUp.resolves({code: 0} as ShellString);
+
+        await startState.onStart();
+
+        testSandbox.assert.calledOnce(dockerService.dockerComposeUp);
+        testSandbox.assert.match(observerSpy.callCount, 1);
+        testSandbox.assert.match(observerSpy.args[0], EventType.UnknownError);
+        testSandbox.assert.calledWith(loggerService.error, 'message', StartState.name);
+    })
+
+    it('should execute onStart and handle connectionService error (generic error)', async () => {
+        connectionService.waitForFiringUp.throws(new Error('test error'));
+        dockerService.dockerComposeUp.resolves({code: 0} as ShellString);
+
+        await startState.onStart();
+
+        testSandbox.assert.calledOnce(dockerService.dockerComposeUp);
+        testSandbox.assert.match(observerSpy.callCount, 1);
+        testSandbox.assert.match(observerSpy.args[0], EventType.UnknownError);
+        testSandbox.assert.notCalled(loggerService.error);
+    })
+
+});
\ No newline at end of file
diff --git a/test/unit/testBed.ts b/test/unit/testBed.ts
index 1fe64cb..9e09bf7 100644
--- a/test/unit/testBed.ts
+++ b/test/unit/testBed.ts
@@ -1,4 +1,5 @@
 import sinon from "sinon";
+import shell from 'shelljs';
 import { LoggerService } from "../../src/services/LoggerService";
 import { CLIService } from "../../src/services/CLIService";
 import { ServiceLocator } from "../../src/services/ServiceLocator";
@@ -9,21 +10,49 @@ export interface LocalNodeTestBed {
     sandbox: sinon.SinonSandbox;
     loggerServiceStub: sinon.SinonStubbedInstance<LoggerService>;
     cliServiceStub: sinon.SinonStubbedInstance<CLIService>;
+    dockerServiceStub: sinon.SinonStubbedInstance<DockerService>;
+    connectionServiceStub: sinon.SinonStubbedInstance<ConnectionService>;
     serviceLocatorStub: sinon.SinonStub;
+    proccesStubs: {
+        processCWDStub: sinon.SinonStub;
+        processPlatformStub: sinon.SinonStub
+    },
+    shellStubs: {
+        shellCDStub: sinon.SinonStub;
+        shellExecStub: sinon.SinonStub;
+    }
 }
 
 let testBead: LocalNodeTestBed
 
 export function getTestBed(cliServiceArgs?: any) {
     if (testBead) {
-        testBead.sandbox.resetHistory();
-        testBead.loggerServiceStub.trace.resetHistory();
+        resetTestBedHistory(testBead)
         return {
             ...testBead
         };
     }
 
     const sandbox = sinon.createSandbox();
+    testBead = {
+        sandbox,
+        ...generateProccessStub(sandbox),
+        ...generateShellStubs(sandbox),
+        ...generateLocalNodeStubs(sandbox, cliServiceArgs),
+    }
+
+    return testBead;
+}
+
+function resetTestBedHistory(testBead: LocalNodeTestBed) {
+    testBead.sandbox.resetHistory();
+    // testBead.loggerServiceStub.trace.resetHistory();
+    // testBead.loggerServiceStub.info.resetHistory();
+    // testBead.proccesStubs.processCWDStub.resetHistory();
+    // testBead.proccesStubs.processPlatformStub.resetHistory();
+}
+
+function generateLocalNodeStubs(sandbox:sinon.SinonSandbox, cliServiceArgs?: any) {
     const dockerServiceStub = sandbox.createStubInstance(DockerService);
     const connectionServiceStub = sandbox.createStubInstance(ConnectionService);
     const loggerServiceStub = sandbox.createStubInstance(LoggerService);
@@ -43,12 +72,33 @@ export function getTestBed(cliServiceArgs?: any) {
         get: getStub
     }));
 
-    testBead = {
-        sandbox,
+    return {
+        connectionServiceStub,
+        dockerServiceStub,
         loggerServiceStub,
         cliServiceStub,
         serviceLocatorStub: getStub
     }
+}
 
-    return testBead;
+function generateProccessStub(sandbox: sinon.SinonSandbox) {
+    const processCWDStub = sandbox.stub(process, 'cwd').returns('testDir');
+    const processPlatformStub = sandbox.stub(process, 'platform').returns('testPlatform');
+
+    return {
+        proccesStubs : {
+            processCWDStub,
+            processPlatformStub
+    }}
+}
+
+function generateShellStubs(sandbox: sinon.SinonSandbox) {
+    const shellCDStub = sandbox.stub(shell, 'cd');
+    const shellExecStub = sandbox.stub(shell, 'exec');
+
+    return {
+        shellStubs :{
+            shellCDStub,
+            shellExecStub
+    }}
 }
\ No newline at end of file