Repository: hashgraph/hedera-local-node
Pull request #528 (2024-01-30 12:08:25)

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

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

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


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

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


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=528) **Quality Gate failed**  
Failed conditions

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



Patch:
diff --git a/src/constants.ts b/src/constants.ts
index c4ecfc1..ae890e2 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -46,7 +46,9 @@ export const OPTIONAL_PORTS = [7546, 8080, 6379, 3000];
 export const EVM_ADDRESSES_BLOCKLIST_FILE_RELATIVE_PATH = '../../compose-network/network-node'
 export const RELATIVE_TMP_DIR_PATH = 'services/record-parser/temp';
 export const RELATIVE_RECORDS_DIR_PATH = 'network-logs/node/recordStreams/record0.0.3';
-export const APPLICATION_YML_RELATIVE_PATH = '../../compose-network/mirror-node/application.yml';
+export const RECORD_PARSER_SOURCE_REL_PATH = '../../src/services/record-parser';
+export const NETWORK_NODE_CONFIG_DIR_PATH = 'compose-network/network-node/data/config';
+export const APPLICATION_YML_RELATIVE_PATH = 'compose-network/mirror-node/application.yml';
 export const MIN_MEMORY_SINGLE_MODE = 4;
 export const MIN_MEMORY_MULTI_MODE = 14;
 export const RECOMMENDED_MEMORY_SINGLE_MODE = 8;
@@ -96,4 +98,15 @@ export const NETWORK_PREP_STATE_STARTING_MESSAGE = 'Starting Network Preparation
 export const NETWORK_PREP_STATE_IMPORT_FEES_START = 'Starting Fees import...';
 export const NETWORK_PREP_STATE_IMPORT_FEES_END = 'Imported fees successfully';
 export const NETWORK_PREP_STATE_WAITING_TOPIC_CREATION = 'Waiting for topic creation...';
-export const NETWORK_PREP_STATE_TOPIC_CREATED = 'Topic was created!';
\ No newline at end of file
+export const NETWORK_PREP_STATE_TOPIC_CREATED = 'Topic was created!';
+
+// Init state
+export const INIT_STATE_INIT_MESSAGE = 'Initialization State Initialized!';
+export const INIT_STATE_STARTING_MESSAGE = "Initialization State Starting...";
+export const INIT_STATE_START_DOCKER_CHECK = "Making sure that Docker is started and it's correct version...";
+export const INIT_STATE_CONFIGURING_ENV_VARIABLES_FINISH = 'Needed environment variables were set for this configuration.';
+export const INIT_STATE_RELAY_LIMITS_DISABLED = 'Hedera JSON-RPC Relay rate limits were disabled.';
+export const INIT_STATE_BOOTSTRAPPED_PROP_SET = 'Needed bootsrap properties were set for this configuration.';
+export const INIT_STATE_MIRROR_PROP_SET = 'Needed mirror node properties were set for this configuration.';
+export const INIT_STATE_NO_NODE_CONF_NEEDED = 'No additional node configuration needed.';
+export const INIT_STATE_NO_ENV_VAR_CONFIGURED = 'No new environment variables were configured.';
diff --git a/src/data/ConfigurationData.ts b/src/data/ConfigurationData.ts
index d6bc8a9..b3de2f6 100644
--- a/src/data/ConfigurationData.ts
+++ b/src/data/ConfigurationData.ts
@@ -34,18 +34,18 @@ export class ConfigurationData {
      * @param {NetworkType} network - The type of the network.
      * @returns {NetworkConfiguration} The configuration data for the selected network.
      */
-    public getSelectedConfigurationData(network: NetworkType): NetworkConfiguration {
+    public static getSelectedConfigurationData(network: NetworkType): NetworkConfiguration {
         switch (network) {
             case NetworkType.LOCAL:
-                return this.getNetworkConfiguration(local as any);
+                return ConfigurationData.getNetworkConfiguration(local as any);
             case NetworkType.MAINNET:
-                return this.getNetworkConfiguration(mainnet as any);
+                return ConfigurationData.getNetworkConfiguration(mainnet as any);
             case NetworkType.TESTNET:
-                return this.getNetworkConfiguration(testnet as any);
+                return ConfigurationData.getNetworkConfiguration(testnet as any);
             case NetworkType.PREVIEWNET:
-                return this.getNetworkConfiguration(previewnet as any);
+                return ConfigurationData.getNetworkConfiguration(previewnet as any);
             default:
-                return this.getNetworkConfiguration(local as any);
+                return ConfigurationData.getNetworkConfiguration(local as any);
         }
     }
 
@@ -54,7 +54,7 @@ export class ConfigurationData {
      * @param {NetworkConfiguration} jsonConfiguration - The JSON configuration.
      * @returns The network configuration.
      */
-    private getNetworkConfiguration(jsonConfiguration: NetworkConfiguration): NetworkConfiguration {
+    private static getNetworkConfiguration(jsonConfiguration: NetworkConfiguration): NetworkConfiguration {
         const relayConfiguration = jsonConfiguration?.envConfiguration ?? undefined;
         const nodeProperties = jsonConfiguration?.nodeConfiguration!.properties ?? undefined;
         const configuration: NetworkConfiguration = {
diff --git a/src/state/InitState.ts b/src/state/InitState.ts
index b70ab94..0283d86 100644
--- a/src/state/InitState.ts
+++ b/src/state/InitState.ts
@@ -34,7 +34,22 @@ import { ConfigurationData } from '../data/ConfigurationData';
 import { Configuration } from '../types/NetworkConfiguration';
 import originalNodeConfiguration from '../configuration/originalNodeConfiguration.json';
 import { DockerService } from '../services/DockerService';
-import { APPLICATION_YML_RELATIVE_PATH, NECESSARY_PORTS, OPTIONAL_PORTS } from '../constants';
+import {
+    APPLICATION_YML_RELATIVE_PATH,
+    INIT_STATE_BOOTSTRAPPED_PROP_SET,
+    INIT_STATE_CONFIGURING_ENV_VARIABLES_FINISH,
+    INIT_STATE_INIT_MESSAGE,
+    INIT_STATE_MIRROR_PROP_SET,
+    INIT_STATE_NO_ENV_VAR_CONFIGURED,
+    INIT_STATE_NO_NODE_CONF_NEEDED,
+    INIT_STATE_RELAY_LIMITS_DISABLED,
+    INIT_STATE_STARTING_MESSAGE,
+    INIT_STATE_START_DOCKER_CHECK,
+    NECESSARY_PORTS,
+    NETWORK_NODE_CONFIG_DIR_PATH,
+    OPTIONAL_PORTS,
+    RECORD_PARSER_SOURCE_REL_PATH
+} from '../constants';
 
 configDotenv({ path: path.resolve(__dirname, '../../.env') });
 
@@ -77,7 +92,7 @@ export class InitState implements IState{
         this.logger = ServiceLocator.Current.get<LoggerService>(LoggerService.name);
         this.cliOptions = ServiceLocator.Current.get<CLIService>(CLIService.name).getCurrentArgv();
         this.dockerService = ServiceLocator.Current.get<DockerService>(DockerService.name);
-        this.logger.trace('Initialization State Initialized!', this.stateName);
+        this.logger.trace(INIT_STATE_INIT_MESSAGE, this.stateName);
     }
 
     /**
@@ -94,13 +109,13 @@ export class InitState implements IState{
      * @returns {Promise<void>} A promise that resolves when the state has started.
      */
     public async onStart(): Promise<void> {
-        this.logger.trace('Initialization State Starting...', this.stateName);
-        const configurationData = new ConfigurationData().getSelectedConfigurationData(this.cliOptions.network);
-        this.logger.info("Making sure that Docker is started and it's correct version...", this.stateName);
+        this.logger.trace(INIT_STATE_STARTING_MESSAGE, this.stateName);
+        const configurationData = ConfigurationData.getSelectedConfigurationData(this.cliOptions.network);
+
         // Check if docker is running and it's the correct version
+        this.logger.info(INIT_STATE_START_DOCKER_CHECK, this.stateName);
         const isCorrectDockerComposeVersion = await this.dockerService.isCorrectDockerComposeVersion();
         const isDockerStarted = await this.dockerService.checkDocker();
-
         const dockerHasEnoughResources = await this.dockerService.checkDockerResources(this.cliOptions.multiNode);
 
         if (!(isCorrectDockerComposeVersion && isDockerStarted && dockerHasEnoughResources)) {
@@ -140,13 +155,13 @@ export class InitState implements IState{
     private prepareWorkDirectory() {
         this.logger.info(`Local Node Working directory set to ${this.cliOptions.workDir}`, this.stateName);
         FileSystemUtils.createEphemeralDirectories(this.cliOptions.workDir);
-        const configDirSource = join(__dirname, '../../compose-network/network-node/data/config/');
-        const configPathMirrorNodeSource = join(__dirname, APPLICATION_YML_RELATIVE_PATH);
-        const recordParserSource = join(__dirname,'../../src/services/record-parser');
+        const configDirSource = join(__dirname, `../../${NETWORK_NODE_CONFIG_DIR_PATH}`);
+        const configPathMirrorNodeSource = join(__dirname, `../../${APPLICATION_YML_RELATIVE_PATH}`);
+        const recordParserSource = join(__dirname, RECORD_PARSER_SOURCE_REL_PATH);
 
         const configFiles = {
-            [configDirSource]: `${this.cliOptions.workDir}/compose-network/network-node/data/config`,
-            [configPathMirrorNodeSource]: `${this.cliOptions.workDir}/compose-network/mirror-node/application.yml`,
+            [configDirSource]: `${this.cliOptions.workDir}/${NETWORK_NODE_CONFIG_DIR_PATH}`,
+            [configPathMirrorNodeSource]: `${this.cliOptions.workDir}/${APPLICATION_YML_RELATIVE_PATH}`,
             [recordParserSource]: `${this.cliOptions.workDir}/services/record-parser`
         };
         FileSystemUtils.copyPaths(configFiles);
@@ -159,22 +174,13 @@ export class InitState implements IState{
      */
     private configureEnvVariables(imageTagConfiguration: Array<Configuration>, envConfiguration: Array<Configuration> | undefined): void {
         imageTagConfiguration.forEach(variable => {
-            const node = variable.key;
-            let tag = variable.value;
-            if (this.cliOptions.networkTag && (node === "NETWORK_NODE_IMAGE_TAG" || node === "HAVEGED_IMAGE_TAG")) {
-                tag = this.cliOptions.networkTag;
-            } else if(this.cliOptions.mirrorTag && node === "MIRROR_IMAGE_TAG") {
-                tag = this.cliOptions.mirrorTag;
-            } else if(this.cliOptions.relayTag && node === "RELAY_IMAGE_TAG") {
-                tag = this.cliOptions.relayTag;
-            }
-
+            const { tag, node } = this.extractImageTag(variable);
             this.logger.trace(`Environment variable ${node} will be set to ${tag}.`, this.stateName);
             process.env[node] = tag;
         });
 
         if (!envConfiguration) {
-            this.logger.trace('No new environment variables were configured.', this.stateName);
+            this.logger.trace(INIT_STATE_NO_ENV_VAR_CONFIGURED, this.stateName);
             return;
         }
 
@@ -188,9 +194,36 @@ export class InitState implements IState{
             process.env.RELAY_HBAR_RATE_LIMIT_TINYBAR = '0';
             process.env.RELAY_HBAR_RATE_LIMIT_DURATION = '0';
             process.env.RELAY_RATE_LIMIT_DISABLED = `${relayLimitsDisabled}`;
-            this.logger.info('Hedera JSON-RPC Relay rate limits were disabled.', this.stateName);
+            this.logger.info(INIT_STATE_RELAY_LIMITS_DISABLED, this.stateName);
+        }
+        this.logger.info(INIT_STATE_CONFIGURING_ENV_VARIABLES_FINISH, this.stateName);
+    }
+
+    /**
+     * Extracts the image tag from the configuration.
+     * 
+     * @private
+     * @param {Configuration} variable - The configuration.
+     * @returns {string} The extracted image tag.
+     */
+    private extractImageTag(variable: Configuration): {
+        tag: string,
+        node: string
+    } {
+        const node = variable.key;
+        let tag = variable.value;
+        if (this.cliOptions.networkTag && (node === "NETWORK_NODE_IMAGE_TAG" || node === "HAVEGED_IMAGE_TAG")) {
+            tag = this.cliOptions.networkTag;
+        } else if(this.cliOptions.mirrorTag && node === "MIRROR_IMAGE_TAG") {
+            tag = this.cliOptions.mirrorTag;
+        } else if(this.cliOptions.relayTag && node === "RELAY_IMAGE_TAG") {
+            tag = this.cliOptions.relayTag;
+        }
+
+        return { 
+            tag,
+            node
         }
-        this.logger.info('Needed environment variables were set for this configuration.', this.stateName);
     }
 
     /**
@@ -198,7 +231,7 @@ export class InitState implements IState{
      * @param {Array<Configuration> | undefined} nodeConfiguration - The node configuration.
      */
     private configureNodeProperties(nodeConfiguration: Array<Configuration> | undefined): void {
-        const propertiesFilePath = join(this.cliOptions.workDir, 'compose-network/network-node/data/config/bootstrap.properties');
+        const propertiesFilePath = join(this.cliOptions.workDir, NETWORK_NODE_CONFIG_DIR_PATH, 'bootstrap.properties');
 
         let newProperties = '';
         originalNodeConfiguration.bootsrapProperties.forEach(property => {
@@ -206,7 +239,7 @@ export class InitState implements IState{
         });
 
         if (!nodeConfiguration) {
-            this.logger.trace('No additional node configuration needed.', this.stateName);
+            this.logger.trace(INIT_STATE_NO_NODE_CONF_NEEDED, this.stateName);
             return;
         }
         nodeConfiguration!.forEach(property => {
@@ -216,7 +249,7 @@ export class InitState implements IState{
 
         writeFileSync(propertiesFilePath, newProperties, { flag: 'w' });
 
-        this.logger.info('Needed bootsrap properties were set for this configuration.', this.stateName);
+        this.logger.info(INIT_STATE_BOOTSTRAPPED_PROP_SET, this.stateName);
     }
 
     /**
@@ -229,7 +262,6 @@ export class InitState implements IState{
         this.logger.trace('Configuring required mirror node properties, depending on selected configuration...', this.stateName);
         const turboMode = !this.cliOptions.fullMode;
         const debugMode = this.cliOptions.enableDebug;
-
         const multiNode = this.cliOptions.multiNode;
 
         const propertiesFilePath = join(this.cliOptions.workDir, 'compose-network/mirror-node/application.yml');
@@ -250,6 +282,6 @@ export class InitState implements IState{
         }
 
         writeFileSync(propertiesFilePath, yaml.dump(application, { lineWidth: 256 }));
-        this.logger.info('Needed mirror node properties were set for this configuration.', this.stateName);
+        this.logger.info(INIT_STATE_MIRROR_PROP_SET, this.stateName);
     }
 }


Test patch:
diff --git a/test/unit/states/InitState.spec.ts b/test/unit/states/InitState.spec.ts
new file mode 100644
index 0000000..71bbce7
--- /dev/null
+++ b/test/unit/states/InitState.spec.ts
@@ -0,0 +1,347 @@
+/*-
+ *
+ * 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 fs from 'fs';
+import yaml from 'js-yaml';
+import { SinonSandbox, SinonSpy, SinonStub, SinonStubbedInstance } from 'sinon';
+import { LoggerService } from '../../../src/services/LoggerService';
+import { InitState } from '../../../src/state/InitState';
+import { getTestBed } from '../testBed';
+import {
+    APPLICATION_YML_RELATIVE_PATH,
+    INIT_STATE_BOOTSTRAPPED_PROP_SET,
+    INIT_STATE_CONFIGURING_ENV_VARIABLES_FINISH,
+    INIT_STATE_INIT_MESSAGE,
+    INIT_STATE_MIRROR_PROP_SET,
+    INIT_STATE_NO_ENV_VAR_CONFIGURED,
+    INIT_STATE_NO_NODE_CONF_NEEDED,
+    INIT_STATE_RELAY_LIMITS_DISABLED,
+    INIT_STATE_STARTING_MESSAGE,
+    INIT_STATE_START_DOCKER_CHECK,
+    NECESSARY_PORTS,
+    NETWORK_NODE_CONFIG_DIR_PATH,
+    OPTIONAL_PORTS,
+    RECORD_PARSER_SOURCE_REL_PATH,
+} from '../../../src/constants';
+import { DockerService } from '../../../src/services/DockerService';
+import { EventType } from '../../../src/types/EventType';
+import { CLIService } from '../../../src/services/CLIService';
+import { ConfigurationData } from '../../../src/data/ConfigurationData';
+import { FileSystemUtils } from '../../../src/utils/FileSystemUtils';
+import { join } from 'path';
+
+describe('InitState tests', () => {
+    let initState: InitState,
+        testSandbox: SinonSandbox, 
+        loggerService: SinonStubbedInstance<LoggerService>,
+        serviceLocator: SinonStub,
+        dockerService: SinonStubbedInstance<DockerService>,
+        observerSpy: SinonSpy,
+        configurationData: SinonStub,
+        prepareWorkDirectoryStub: SinonStub,
+        configureEnvVariablesStub: SinonStub,
+        configureNodePropertiesStub: SinonStub,
+        configureMirrorNodePropertiesStub: SinonStub,
+        extractImageTagStub: SinonStub;
+
+    const TEST_CONFIGURATION = {
+        imageTagConfiguration: [
+            { key: "TEST_VAR_1", value: 'test'}
+        ],
+        envConfiguration: [
+            { key: "TEST_VAR_2", value: 'test'}
+        ],
+        nodeConfiguration: {
+            properties: [
+                { key: "TEST_VAR_3", value: 'test'}
+            ],
+        },
+    }
+
+    const stubPrivateFunctions = () => {
+        prepareWorkDirectoryStub = testSandbox.stub(initState as any, 'prepareWorkDirectory')
+        configureEnvVariablesStub = testSandbox.stub(initState as any, 'configureEnvVariables')
+        configureNodePropertiesStub = testSandbox.stub(initState as any, 'configureNodeProperties')
+        configureMirrorNodePropertiesStub = testSandbox.stub(initState as any, 'configureMirrorNodeProperties')
+        extractImageTagStub = testSandbox.stub(initState as any, 'extractImageTag').returns({
+            node: TEST_CONFIGURATION.imageTagConfiguration[0].key,
+            tag: TEST_CONFIGURATION.imageTagConfiguration[0].value,
+        })
+    }
+
+    before(() => {
+        const { 
+            sandbox,
+            loggerServiceStub,
+            serviceLocatorStub,
+            dockerServiceStub,
+        } = getTestBed({
+            workDir: 'testDir',
+            fullMode: true,
+            enableDebug: true,
+            multiNode: true,
+            networkTag: 'test-network-tag',
+            mirrorTag: 'test-mirror-tag',
+            relayTag: 'test-relay-tag',
+        });
+    
+        testSandbox = sandbox
+        dockerService = dockerServiceStub
+        loggerService = loggerServiceStub
+        serviceLocator = serviceLocatorStub
+
+        initState = new InitState();
+        observerSpy = testSandbox.spy();
+        const observer = {
+            update: observerSpy
+        }
+        initState.subscribe(observer);
+        configurationData = testSandbox.stub(ConfigurationData, 'getSelectedConfigurationData').callsFake(() => TEST_CONFIGURATION)
+        stubPrivateFunctions()
+    });
+
+    afterEach(() => {
+        testSandbox.resetHistory();
+        observerSpy.resetHistory();
+        dockerService.isCorrectDockerComposeVersion.reset()
+        dockerService.checkDocker.reset()
+        dockerService.checkDockerResources.reset()
+    });
+
+    it('should initialize the Init State', async () => {
+        expect(initState).to.be.instanceOf(InitState);
+        testSandbox.assert.calledWith(serviceLocator, LoggerService.name);
+        testSandbox.assert.calledWith(serviceLocator, DockerService.name);
+        testSandbox.assert.calledWith(serviceLocator, CLIService.name);
+        testSandbox.assert.calledOnceWithExactly(loggerService.trace, INIT_STATE_INIT_MESSAGE, InitState.name);
+    })
+
+    it('should have a subscribe method', async () => {
+        expect(initState.subscribe).to.be.a('function');
+    })
+
+    it('should execute onStart', async () => {
+        dockerService.isCorrectDockerComposeVersion.resolves(true)
+        dockerService.checkDocker.resolves(true)
+        dockerService.checkDockerResources.resolves(true)
+
+        await initState.onStart();
+
+        // loggin messages
+        testSandbox.assert.calledWithExactly(loggerService.trace, INIT_STATE_STARTING_MESSAGE, InitState.name);
+        testSandbox.assert.calledOnce(configurationData)
+        testSandbox.assert.calledWithExactly(loggerService.info, INIT_STATE_START_DOCKER_CHECK, InitState.name);
+        testSandbox.assert.calledOnce(dockerService.isCorrectDockerComposeVersion);
+        testSandbox.assert.calledOnce(dockerService.checkDocker);
+        testSandbox.assert.calledOnce(dockerService.checkDockerResources);
+        
+        testSandbox.assert.calledOnceWithExactly(dockerService.isPortInUse, NECESSARY_PORTS.concat(OPTIONAL_PORTS));
+        testSandbox.assert.calledWith(observerSpy, EventType.Finish);
+        testSandbox.assert.calledOnce(prepareWorkDirectoryStub);
+        testSandbox.assert.calledOnce(configureEnvVariablesStub);
+        testSandbox.assert.calledOnce(configureNodePropertiesStub);
+        testSandbox.assert.calledOnce(configureMirrorNodePropertiesStub);
+    })
+
+    it('should execute onStart and finish with UnresolvableError on docker checks (Compose Version, Docker Running, Resources)', async () => {
+        dockerService.isCorrectDockerComposeVersion.resolves(false);
+        await initState.onStart();
+
+        // loggin messages
+        testSandbox.assert.calledOnceWithExactly(loggerService.trace, INIT_STATE_STARTING_MESSAGE, InitState.name);
+        testSandbox.assert.calledOnce(configurationData)
+        testSandbox.assert.calledOnceWithExactly(loggerService.info, INIT_STATE_START_DOCKER_CHECK, InitState.name);
+        testSandbox.assert.calledOnce(dockerService.isCorrectDockerComposeVersion);
+        testSandbox.assert.calledOnce(dockerService.checkDocker);
+        testSandbox.assert.calledOnce(dockerService.checkDockerResources);
+
+        testSandbox.assert.calledWith(observerSpy, EventType.UnresolvableError);
+        
+        testSandbox.assert.notCalled(dockerService.isPortInUse);
+    })
+
+    describe('private functions', () => {
+        let createEphemeralDirectoriesStub: SinonStub,
+            copyPathsStub: SinonStub,
+            onStartStub: SinonStub,
+            fsWriteFileSync: SinonStub,
+            fsReadFileSync: SinonStub,
+            ymlLoad: SinonStub,
+            ymlDump: SinonStub;
+        const rootDirSource = join(__dirname, `../`);
+
+        const configDirSource = join(rootDirSource, `../../${NETWORK_NODE_CONFIG_DIR_PATH}`);
+        const configPathMirrorNodeSource = join(rootDirSource, `../../${APPLICATION_YML_RELATIVE_PATH}`);
+        const recordParserSource = join(rootDirSource, RECORD_PARSER_SOURCE_REL_PATH);
+        const configFiles = {
+            [configDirSource]: `testDir/${NETWORK_NODE_CONFIG_DIR_PATH}`,
+            [configPathMirrorNodeSource]: `testDir/${APPLICATION_YML_RELATIVE_PATH}`,
+            [recordParserSource]: `testDir/services/record-parser`
+        };
+
+        before(() => {
+            createEphemeralDirectoriesStub = testSandbox.stub(FileSystemUtils, "createEphemeralDirectories").callsFake(() => {})
+            copyPathsStub = testSandbox.stub(FileSystemUtils, "copyPaths").callsFake(() => {})
+            fsWriteFileSync = testSandbox.stub(fs, 'writeFileSync');
+            fsReadFileSync = testSandbox.stub(fs, 'readFileSync').returns('test');
+            ymlLoad = testSandbox.stub(yaml, 'load').returns({
+                hedera: {
+                    mirror: {
+                        importer: {
+                            dataPath: {
+                            },
+                            downloader: {
+                                local: {}
+                            }
+                        },
+                        monitor: {
+                            nodes: {}
+                        }
+                    }
+                }
+            });
+            ymlDump = testSandbox.stub(yaml, 'dump');
+        })
+
+        afterEach(() => {
+            onStartStub.restore()
+        })
+        
+        it('should execute "prepareWorkDirectory" as expected', async () => {
+            prepareWorkDirectoryStub.restore()
+
+            onStartStub = testSandbox.stub(initState, 'onStart')
+                .callsFake(() => (initState  as any).prepareWorkDirectory())
+            await initState.onStart();
+            testSandbox.assert.calledWithExactly(loggerService.info, "Local Node Working directory set to testDir", InitState.name);
+            testSandbox.assert.calledOnceWithExactly(createEphemeralDirectoriesStub, "testDir");
+            testSandbox.assert.calledOnceWithExactly(copyPathsStub, configFiles);
+            
+            prepareWorkDirectoryStub = testSandbox.stub(initState as any, 'prepareWorkDirectory')
+        })
+
+        it('should execute "configureEnvVariables" as expected', async () => {
+            configureEnvVariablesStub.restore()
+
+            onStartStub = testSandbox.stub(initState, 'onStart')
+                .callsFake(() => (initState  as any).configureEnvVariables(
+                    TEST_CONFIGURATION.imageTagConfiguration,
+                    TEST_CONFIGURATION.envConfiguration,
+                ))
+            await initState.onStart();
+
+            testSandbox.assert.match(process.env.TEST_VAR_1, 'test');
+            testSandbox.assert.match(process.env.TEST_VAR_2, 'test');
+            testSandbox.assert.called(loggerService.trace);
+            testSandbox.assert.called(extractImageTagStub);
+            testSandbox.assert.calledWithExactly(loggerService.info, INIT_STATE_CONFIGURING_ENV_VARIABLES_FINISH, InitState.name);
+            testSandbox.assert.calledWithExactly(loggerService.info, INIT_STATE_RELAY_LIMITS_DISABLED, InitState.name);
+
+            configureEnvVariablesStub = testSandbox.stub(initState as any, 'configureEnvVariables')
+        })
+
+        it('should execute "configureEnvVariables" as expected (NO ENV configured)', async () => {
+            configureEnvVariablesStub.restore()
+
+            onStartStub = testSandbox.stub(initState, 'onStart')
+                .callsFake(() => (initState  as any).configureEnvVariables(
+                    TEST_CONFIGURATION.imageTagConfiguration,
+                ))
+            await initState.onStart();
+
+            testSandbox.assert.calledWithExactly(loggerService.trace, INIT_STATE_NO_ENV_VAR_CONFIGURED, InitState.name);
+
+            configureEnvVariablesStub = testSandbox.stub(initState as any, 'configureEnvVariables')
+        })
+
+        it('should execute "extractImageTag" as expected and return "NETWORK_NODE_IMAGE_TAG"', async () => {
+            extractImageTagStub.restore()
+
+            const {tag, node} = (initState as any).extractImageTag({
+                key: "NETWORK_NODE_IMAGE_TAG",
+                value: 'test'
+            })
+            expect(tag).to.be.equal('test-network-tag');
+            expect(node).to.be.equal('NETWORK_NODE_IMAGE_TAG');
+        })
+
+        it('should execute "extractImageTag" as expected and return "MIRROR_IMAGE_TAG"', async () => {
+            extractImageTagStub.restore()
+
+            const {tag, node} = (initState as any).extractImageTag({
+                key: "MIRROR_IMAGE_TAG",
+                value: 'test'
+            })
+            expect(tag).to.be.equal('test-mirror-tag');
+            expect(node).to.be.equal('MIRROR_IMAGE_TAG');
+        })
+
+        it('should execute "extractImageTag" as expected and return "RELAY_IMAGE_TAG"', async () => {
+            extractImageTagStub.restore()
+
+            const {tag, node} = (initState as any).extractImageTag({
+                key: "RELAY_IMAGE_TAG",
+                value: 'test'
+            })
+            expect(tag).to.be.equal('test-relay-tag');
+            expect(node).to.be.equal('RELAY_IMAGE_TAG');
+        })
+
+        it('should execute "configureNodeProperties" as write to file', async () => {
+            configureNodePropertiesStub.restore()
+            onStartStub = testSandbox.stub(initState, 'onStart')
+                .callsFake(() => (initState  as any).configureNodeProperties(
+                    TEST_CONFIGURATION.nodeConfiguration.properties
+                ))
+            await initState.onStart();
+            testSandbox.assert.calledWithExactly(loggerService.info, INIT_STATE_BOOTSTRAPPED_PROP_SET, InitState.name);
+            testSandbox.assert.called(fsWriteFileSync);
+
+            configureNodePropertiesStub = testSandbox.stub(initState as any, 'configureNodeProperties')
+        })
+
+        it('should execute "configureNodeProperties" as write to file (No additional node configuration needed)', async () => {
+            configureNodePropertiesStub.restore()
+            onStartStub = testSandbox.stub(initState, 'onStart')
+                .callsFake(() => (initState  as any).configureNodeProperties())
+            await initState.onStart();
+            testSandbox.assert.calledWithExactly(loggerService.trace, INIT_STATE_NO_NODE_CONF_NEEDED, InitState.name);
+            testSandbox.assert.notCalled(fsWriteFileSync);
+
+            configureNodePropertiesStub = testSandbox.stub(initState as any, 'configureNodeProperties')
+        })
+
+        it('should execute "configureMirrorNodeProperties" as write to file', async () => {
+            configureMirrorNodePropertiesStub.restore()
+
+            onStartStub = testSandbox.stub(initState, 'onStart')
+            .callsFake(() => (initState  as any).configureMirrorNodeProperties())
+            await initState.onStart();
+            testSandbox.assert.calledOnceWithExactly(loggerService.info, INIT_STATE_MIRROR_PROP_SET, InitState.name);
+            testSandbox.assert.called(fsReadFileSync);
+            testSandbox.assert.called(fsWriteFileSync);
+            testSandbox.assert.called(ymlLoad);
+            testSandbox.assert.called(ymlDump);
+
+            configureMirrorNodePropertiesStub = testSandbox.stub(initState as any, 'configureMirrorNodeProperties')
+        })
+    })
+});