diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 4f15b9ab2407e9d56f56ed2acb0abceab1dddf9e..ab31bc6b429207e32c03ee700e536ca6bc77fd5e 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,10 +1,16 @@
 stages:
   - test
   - build
+  - coverage_report
 
 variables:
   BUILD_ENV_DOCKER_IMAGE: docker-registry.ebrains.eu/esd/tmp:latest
 
+default:
+  before_script:
+    - chmod +x dedal/utils/bootstrap.sh
+    - ./dedal/utils/bootstrap.sh
+    - pip install -e .[test]
 
 build-wheel:
   stage: build
@@ -21,17 +27,34 @@ build-wheel:
       - dist/*.tar.gz
     expire_in: 1 week
 
+unit_tests:
+  stage: test
+  tags:
+    - docker-runner
+  image: ubuntu:22.04
+  script:
+    - coverage run -m pytest -s --tb=short --junitxml=test-results.xml ./dedal/tests/unit_tests
+    - mv .coverage .coverage.unit  # Rename to avoid overwriting
+  artifacts:
+    when: always
+    reports:
+      junit: test-results.xml
+    paths:
+      - test-results.xml
+      - .dedal.log
+      - .generate_cache.log
+      - .coverage.unit
+    expire_in: 1 week
 
-testing-pytest:
+integration_tests:
   stage: test
   tags:
     - docker-runner
   image: ubuntu:22.04
   script:
-    - chmod +x dedal/utils/bootstrap.sh
-    - ./dedal/utils/bootstrap.sh
-    - pip install .
-    - pytest ./dedal/tests/ -s --junitxml=test-results.xml
+    - coverage run -m pytest -s --tb=short --junitxml=test-results.xml ./dedal/tests/integration_tests
+    - mv .coverage .coverage.integration  # Rename to avoid overwriting
+  needs: ["unit_tests"]
   artifacts:
     when: always
     reports:
@@ -40,5 +63,27 @@ testing-pytest:
       - test-results.xml
       - .dedal.log
       - .generate_cache.log
+      - .coverage.integration
+    expire_in: 1 week
+
+merge_coverage:
+  stage: coverage_report
+  tags:
+    - docker-runner
+  image: ubuntu:22.04
+  script:
+    - coverage combine .coverage.unit .coverage.integration
+    - coverage report
+    - coverage xml -o coverage.xml
+    - coverage html -d coverage_html
+  artifacts:
+    reports:
+      coverage_report:
+        coverage_format: cobertura
+        path: coverage.xml
+    paths:
+      - coverage.xml
+      - coverage_html
     expire_in: 1 week
+  coverage: '/TOTAL.*?(\d+\%)$/'
 
diff --git a/MANIFEST.ini b/MANIFEST.ini
new file mode 100644
index 0000000000000000000000000000000000000000..e62be46716825eab56feaa6d891b2f5d0bcf314d
--- /dev/null
+++ b/MANIFEST.ini
@@ -0,0 +1,3 @@
+
+include README.md
+recursive-include yashchiki/dedal *.*
\ No newline at end of file
diff --git a/README.md b/README.md
index 733d8ff63a4f2aabb3634707b85c252e4cfb284d..55080aabfee964433f5439b43b3b61f727422a2c 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,5 @@
 # Dedal
+![Coverage Badge](https://gitlab.ebrains.eu/ri/tech-hub/platform/esd/yashchiki/badges/koutakia/coverage.svg)
 
 This repository provides functionalities to easily ```managed spack environments``` and
 ```helpers for the container image build flow```.
@@ -139,4 +140,5 @@ Installs spack packages present in the spack environment defined in configuratio
 
 # Dedal's UML diagram
 
-![screenshot](dedal/docs/resources/dedal_UML.png)
\ No newline at end of file
+![screenshot](dedal/docs/resources/dedal_UML.png)
+
diff --git a/dedal/build_cache/BuildCacheManager.py b/dedal/build_cache/BuildCacheManager.py
index ba1f62a3556d31db0d07763fbb92df86a337a861..62cb9af14bef184c87c4d6ffa81c697a272d7036 100644
--- a/dedal/build_cache/BuildCacheManager.py
+++ b/dedal/build_cache/BuildCacheManager.py
@@ -1,12 +1,12 @@
+import glob
 import os
-import time
+from os.path import join
+from pathlib import Path
 
 import oras.client
-from pathlib import Path
 
 from dedal.build_cache.BuildCacheManagerInterface import BuildCacheManagerInterface
 from dedal.logger.logger_builder import get_logger
-from dedal.utils.utils import clean_up
 
 
 class BuildCacheManager(BuildCacheManagerInterface):
@@ -113,7 +113,49 @@ class BuildCacheManager(BuildCacheManagerInterface):
         if tags is not None:
             try:
                 self._client.delete_tags(self._oci_registry_path, tags)
-                self._logger.info(f"Successfully deleted all artifacts form OCI registry.")
+                self._logger.info("Successfully deleted all artifacts form OCI registry.")
             except RuntimeError as e:
                 self._logger.error(
                     f"Failed to delete artifacts: {e}")
+
+    def __log_warning_if_needed(self, warn_message: str, items: list[str]) -> None:
+        """Logs a warning message if the number of items is greater than 1. (Private function)
+
+            This method logs a warning message using the provided message and items if the list of items has more than one element.
+
+        Args:
+            warn_message (str): The warning message to log.
+            items (list[str]): The list of items to include in the log message.
+        """
+        if len(items) > 1:
+            self._logger.warning(warn_message, items, items[0])
+
+    def get_public_key_from_cache(self, build_cache_dir: str | None) -> str | None:
+        """Retrieves the public key from the build cache.
+
+            This method searches for the public key within the specified build cache directory.
+
+        Args:
+            build_cache_dir (str | None): The path to the build cache directory.
+
+        Returns:
+            str | None: The path to the public key file if found, otherwise None.
+        """
+
+        if not build_cache_dir or not os.path.exists(build_cache_dir):
+            self._logger.warning("Build cache directory does not exist!")
+            return None
+        pgp_folders = glob.glob(f"{build_cache_dir}/**/_pgp", recursive=True)
+        if not pgp_folders:
+            self._logger.warning("No _pgp folder found in the build cache!")
+            return None
+        self.__log_warning_if_needed(
+            "More than one PGP folders found in the build cache: %s, using the first one in the list: %s", pgp_folders)
+        pgp_folder = pgp_folders[0]
+        key_files = glob.glob(join(pgp_folder, "**"))
+        if not key_files:
+            self._logger.warning("No PGP key files found in the build cache!")
+            return None
+        self.__log_warning_if_needed(
+            "More than one PGP key files found in the build cache: %s, using the first one in the list: %s", key_files)
+        return key_files[0]
diff --git a/dedal/commands/__init__.py b/dedal/commands/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..ea9c384e46ebd5602957ff010a7c2b7c2d7d52e6
--- /dev/null
+++ b/dedal/commands/__init__.py
@@ -0,0 +1,8 @@
+#  Copyright (c) 2025
+#  License Information: To be decided!
+#
+#  File: __init__.py
+#  Description: Brief description of the file.
+#  Created by: Murugan, Jithu <j.murugan@fz-juelich.de>
+#  Created on: 2025-02-17
+
diff --git a/dedal/commands/bash_command_executor.py b/dedal/commands/bash_command_executor.py
new file mode 100644
index 0000000000000000000000000000000000000000..aef9c57659070d31936ad71967e5ec21e937bdc6
--- /dev/null
+++ b/dedal/commands/bash_command_executor.py
@@ -0,0 +1,100 @@
+""" Bash Command Executor module. """
+#  Copyright (c) 2025
+#  License Information: To be decided!
+#
+#  File: bash_command_executor.py
+#  Description: Brief description of the file.
+#  Created by: Murugan, Jithu <j.murugan@fz-juelich.de>
+#  Created on: 2025-02-17
+import logging
+import subprocess
+from logging import Logger
+from os import name as os_name
+
+from dedal.commands.command import Command
+from dedal.commands.command_sequence import CommandSequence
+
+
+class BashCommandExecutor:
+    """Executes commands in a Bash shell.
+
+    Manages a sequence of commands and executes them in a single Bash session,
+    handling potential errors during execution.
+    """
+
+    def __init__(self) -> None:
+        self.logger: Logger = logging.getLogger(__name__)
+        self.sequence: CommandSequence = CommandSequence()
+        command_map: dict[str, list[str]] = {
+            "nt": ["wsl", "bash", "-c"],  # Works only if wsl is installed on windows
+            "posix": ["bash", "-c"],
+        }
+        self.bash_command: list[str] = command_map.get(os_name, ["undefined"])
+
+    def add_command(self, command: Command) -> None:
+        """Adds a command to the sequence.
+
+        Appends the given command to the internal sequence of commands to be executed.
+
+        Args:
+            command (Command): The command to add.
+
+        Raises:
+            ValueError: If the command is not an instance of the `Command` class.
+        """
+        if not isinstance(command, Command):
+            raise ValueError("Invalid command type. Use Command.")
+        self.logger.info("Adding command to the sequence: %s", command)
+        self.sequence.add_command(command)
+
+    def execute(self) -> tuple[str | None, str | None]:
+        """Executes all commands in a single Bash session.
+
+        Runs the accumulated commands in a Bash shell and returns the output.
+        Handles various potential errors during execution. The execution is time
+
+        Returns (tuple[str | None, str | None]):
+            A tuple containing the output and error message (if any).
+            output will be None if an error occurred, and the error message will
+            contain details about the error.
+
+        Raises:
+            ValueError: If no commands have been added to the sequence.
+        """
+        if not self.sequence.commands:
+            raise ValueError("No commands to execute.")
+        try:
+            result = subprocess.run(
+                [*self.bash_command, self.sequence.execute()],
+                capture_output=True,
+                text=True,
+                check=True,
+                timeout=172800  # Given a default timeout of 48 hours
+            )
+            self.logger.info("Successfully executed command sequence, output: %s", result.stdout)
+            return result.stdout, None
+        except FileNotFoundError as e:
+            error = f"Error: Bash Command: {self.bash_command} not found: {e}"
+        except subprocess.CalledProcessError as e:
+            error = (f"Error: Command failed with exit code "
+                     f"{e.returncode}, Error Output: {e.stderr}")
+        except PermissionError as e:
+            error = f"Error: Permission denied: {e}"
+        except OSError as e:
+            error = f"Error: OS error occurred: {e}"
+        except ValueError as e:
+            error = f"Error: Invalid argument passed: {e}"
+        except TypeError as e:
+            error = f"Error: Invalid type for arguments: {e}"
+        except subprocess.TimeoutExpired as e:
+            error = f"Error: Command timed out after {e.timeout} seconds"
+        except subprocess.SubprocessError as e:
+            error = f"Subprocess error occurred: {e}"
+        return None, error
+
+    def reset(self) -> None:
+        """Resets the command executor.
+
+        Clears the internal command sequence, preparing the executor for a new set of commands.
+        """
+        self.sequence.clear()
diff --git a/dedal/commands/command.py b/dedal/commands/command.py
new file mode 100644
index 0000000000000000000000000000000000000000..07e9837c142122ff43a5fcd47959a12d7ab0f28f
--- /dev/null
+++ b/dedal/commands/command.py
@@ -0,0 +1,29 @@
+""" Command module. """
+#  Copyright (c) 2025
+#  License Information: To be decided!
+#
+#  File: command.py
+#  Description: Abstract base class for executable commands
+#  Created by: Murugan, Jithu <j.murugan@fz-juelich.de>
+#  Created on: 2025-02-17
+
+from abc import ABC, abstractmethod
+
+
+class Command(ABC):
+    """Abstract base class for executable commands.
+
+    Provides a common interface for defining and executing commands.
+    Subclasses must implement the `execute` method.
+    """
+
+    @abstractmethod
+    def execute(self) -> str:
+        """Executes the command.
+
+        This method must be implemented by subclasses to define the specific
+        behavior of the command.
+
+        Returns:
+            str: The result of the command execution.
+        """
diff --git a/dedal/commands/command_enum.py b/dedal/commands/command_enum.py
new file mode 100644
index 0000000000000000000000000000000000000000..7ef184cbfab3e193c292f7fcbdbedcb7a5783211
--- /dev/null
+++ b/dedal/commands/command_enum.py
@@ -0,0 +1,37 @@
+""" Command Enum module. """
+#  Copyright (c) 2025
+#  License Information: To be decided!
+#
+#  File: command_enum.py
+#  Description: Enumeration of supported commands in command registry
+#  Created by: Murugan, Jithu <j.murugan@fz-juelich.de>
+#  Created on: 2025-02-17
+
+from enum import Enum
+
+
+class CommandEnum(Enum):
+    """Enumeration of supported commands.
+
+    Provides a standardized way to refer to different command types,
+    including Linux commands and Spack commands.
+    """
+    # Linux commands_
+
+    SOURCE = "source"
+    LIST_FILES = "list_files"
+    SHOW_DIRECTORY = "show_directory"
+    FIND_IN_FILE = "find_in_file"
+    ECHO_MESSAGE = "echo_message"
+    CHANGE_DIRECTORY = "change_directory"
+
+    # Spack commands_
+    SPACK_COMPILER_FIND = "spack_compiler_find"
+    SPACK_COMPILERS = "spack_compilers"
+    SPACK_COMPILER_INFO = "spack_compiler_info"
+    SPACK_COMPILER_LIST = "spack_compiler_list"
+    SPACK_ENVIRONMENT_ACTIVATE = "spack_environment_activate"
+    SPACK_FIND = "spack_find"
+    SPACK_INSTALL = "spack_install"
+    SPACK_MIRROR_ADD = "spack_mirror_add"
+    SPACK_GPG_TRUST = "spack_gpg_trust"
diff --git a/dedal/commands/command_registry.py b/dedal/commands/command_registry.py
new file mode 100644
index 0000000000000000000000000000000000000000..adaa3d6eb95119f3e028ea05fe7640f9145cdb5f
--- /dev/null
+++ b/dedal/commands/command_registry.py
@@ -0,0 +1,59 @@
+""" Command Registry module. """
+#  Copyright (c) 2025
+#  License Information: To be decided!
+#
+#  File: command_registry.py
+#  Description: Registry for storing and retrieving shell commands with placeholders
+#  Created by: Murugan, Jithu <j.murugan@fz-juelich.de>
+#  Created on: 2025-02-17
+
+from dedal.commands.command_enum import CommandEnum
+
+
+class CommandRegistry:
+    """Registry for storing and retrieving commands.
+
+    Holds a dictionary of commands, keyed by `CommandEnum` members,
+    allowing easy access to command definitions. The generic shell command factory uses this registry for constructing the shell commands.
+
+    The commands are stored in a dictionary where the keys or command names are `CommandEnum` members, and the values corresponding shell commands stored in a list.
+    The placeholders `{}` in the commands are replaced with the actual values when the command is executed.
+
+    Extend this registry if you want to add more commands and do the necessary mapping in command_runner module.
+    """
+
+    COMMANDS: dict[CommandEnum, list[str]] = {
+        # Linux commands_
+        CommandEnum.LIST_FILES: ["ls", "-l", "{folder_location}"],
+        CommandEnum.SHOW_DIRECTORY: ["pwd"],
+        CommandEnum.FIND_IN_FILE: ["grep", "-i", "{search_term}", "{file_path}"],
+        CommandEnum.ECHO_MESSAGE: ["echo", "{message}"],
+        CommandEnum.SOURCE: ["source", "{file_path}"],
+        CommandEnum.CHANGE_DIRECTORY: ["cd", "{folder_location}"],
+
+        # Spack commands_
+        CommandEnum.SPACK_COMPILER_FIND: ["spack", "compiler", "find", "{compiler_name}"],
+        CommandEnum.SPACK_COMPILERS: ["spack", "compilers"],
+        CommandEnum.SPACK_COMPILER_LIST: ["spack", "compiler", "list"],
+        CommandEnum.SPACK_COMPILER_INFO: ["spack", "compiler", "info", "{compiler_name_with_version}"],
+        CommandEnum.SPACK_ENVIRONMENT_ACTIVATE: ["spack", "env", "activate", "-p", "{env_path}"],
+        CommandEnum.SPACK_FIND: ["spack", "find", "{package_name_with_version}"],
+        CommandEnum.SPACK_INSTALL: ["spack", "install", "{package_name_with_version}"],
+        CommandEnum.SPACK_MIRROR_ADD: ["spack", "mirror", "add", "{autopush}", "{signed}", "{mirror_name}",
+                                       "{mirror_path}"],
+        CommandEnum.SPACK_GPG_TRUST: ["spack", "gpg", "trust", "{public_key_path}"]
+    }
+
+    @classmethod
+    def get_command(cls, command_name: CommandEnum) -> list[str] | None:
+        """Retrieve a command from the registry.
+
+        Returns the command definition associated with the given `CommandEnum` member.
+
+        Args:
+            command_name (CommandEnum): The name of the command to retrieve.
+
+        Returns (list[str]):
+            The shell command in a list format, or None if the command is not found.
+        """
+        return cls.COMMANDS.get(command_name)
diff --git a/dedal/commands/command_runner.py b/dedal/commands/command_runner.py
new file mode 100644
index 0000000000000000000000000000000000000000..88ee46a8248d900a0e721e83721067f71fde1060
--- /dev/null
+++ b/dedal/commands/command_runner.py
@@ -0,0 +1,207 @@
+""" Command Runner module. """
+#  Copyright (c) 2025
+#  License Information: To be decided!
+#
+#  File: command_runner.py
+#  Description: Manages creation, execution, and result handling of command sequences.
+#  Created by: Murugan, Jithu <j.murugan@fz-juelich.de>
+#  Created on: 2025-02-17
+
+from __future__ import annotations
+
+import logging
+from logging import Logger
+from typing import Callable, Any
+
+from dedal.commands.bash_command_executor import BashCommandExecutor
+from dedal.commands.command_enum import CommandEnum
+from dedal.commands.command_sequence import CommandSequence
+from dedal.commands.command_sequence_factory import CommandSequenceFactory
+from dedal.commands.preconfigured_command_enum import PreconfiguredCommandEnum
+from dedal.commands.spack_command_sequence_factory import SpackCommandSequenceFactory
+
+
+class CommandRunner:
+    """This module provides a unified interface for executing both predefined and custom command sequences.
+
+    Functionality
+
+    The module offers two primary methods for running command sequences:
+
+        1. Run Preconfigured Command Sequence: This method allows users to execute preconfigured command sequences using the 'run_preconfigured_command_sequence' method.
+        2. Run Custom Command Sequence: This method enables users to execute dynamically generated custom command sequences using the 'run_custom_command_sequence' method.
+
+    Preconfigured Command Sequences
+        To run a preconfigured command sequence, users can simply call the run_preconfigured_command_sequence method. The module will execute the corresponding command sequence.
+        To create and run a preconfigured command sequence, users must:
+            1. Define a new preconfigured command in the PreconfiguredCommandEnum.
+            2. Add a new entry in CommandRunner.commands_map that maps the preconfigured command to its corresponding function defined in the SpackCommandSequenceFactory class.
+            4. Call the run_preconfigured_command_sequence method to execute the preconfigured command sequence.
+
+    Custom Command Sequences
+        To create and execute a custom command sequence, users must:
+            1. Define the commands as a dictionary, where the key represents the command already registered in the command_registry, and the value contains the corresponding placeholder values expected by the command.
+            2. Invoke the run_custom_command_sequence using generated dictionary to execute the custom command sequence.
+
+    Command Pattern Diagram
+    ------------------------
+
+    The Command pattern is used to encapsulate the request as an object, allowing for more flexibility and extensibility in handling requests.
+
+    ```
+          +---------------+
+          |  Client    |
+          +---------------+
+                  |
+                  |
+                  v
+          +---------------+
+          |  Invoker    |
+          |  (CommandRunner)|
+          +---------------+
+                  |
+                  |
+                  v
+          +---------------+
+          |  Command    |
+          |  (PreconfiguredCommandEnum)|
+          +---------------+
+                  |
+                  |
+                  v
+          +---------------+
+          |  Receiver  |
+          |  (SpackCommandSequenceFactory)|
+          +---------------+
+    ```
+
+    In this diagram:
+
+    *   The Client is the user of the CommandRunner.
+    *   The Invoker is the CommandRunner itself, which receives the Command object and invokes the corresponding action.
+    *   The Command is the PreconfiguredCommandEnum, which represents the request.
+    *   The Receiver is the SpackCommandSequenceFactory, which performs the action requested by the Command object.
+
+    Benefits
+        This module provides a flexible and unified interface for executing both predefined and custom command sequences, allowing users to easily manage and execute complex command workflows.
+
+
+    Attributes
+    ----------
+    logger : Logger
+        The logger instance used for logging purposes.
+    executor : BashCommandExecutor
+        The BashCommandExecutor instance used for executing commands.
+    commands_map : dict[PreconfiguredCommandEnum, Callable[..., CommandSequence]]
+        A dictionary mapping preconfigured commands to their corresponding functions.
+
+    Methods
+    -------
+    run_preconfigured_command_sequence(preconfigured_command: PreconfiguredCommandEnum)
+        Executes a preconfigured command sequence.
+    run_custom_command_sequence(command_sequence: dict)
+        Executes a custom command sequence.
+    """
+
+    def __init__(self) -> None:
+        """Initializes the CommandRunner."""
+        self.logger: Logger = logging.getLogger(__name__)
+        self.executor: BashCommandExecutor = BashCommandExecutor()
+        self.commands_map: dict[PreconfiguredCommandEnum, Callable[..., CommandSequence]] = {
+            PreconfiguredCommandEnum.SPACK_COMPILER_FIND:
+                SpackCommandSequenceFactory.create_spack_compilers_command_sequence,
+            PreconfiguredCommandEnum.SPACK_COMPILER_LIST:
+                SpackCommandSequenceFactory.create_spack_compiler_list_command_sequence,
+            PreconfiguredCommandEnum.SPACK_COMPILERS:
+                SpackCommandSequenceFactory.create_spack_compiler_list_command_sequence,
+            PreconfiguredCommandEnum.SPACK_COMPILER_INFO:
+                SpackCommandSequenceFactory.create_spack_compiler_info_command_sequence,
+            PreconfiguredCommandEnum.SPACK_FIND:
+                SpackCommandSequenceFactory.create_spack_post_install_find_command_sequence,
+            PreconfiguredCommandEnum.SPACK_INSTALL:
+                SpackCommandSequenceFactory.create_spack_install_package_command_sequence,
+            PreconfiguredCommandEnum.SPACK_MIRROR_ADD:
+                SpackCommandSequenceFactory.create_spack_mirror_add_command_sequence,
+            PreconfiguredCommandEnum.SPACK_GPG_TRUST:
+                SpackCommandSequenceFactory.create_spack_gpg_trust_command_sequence,
+        }
+
+    def execute_command(self,
+                        command_sequence: CommandSequence) -> dict[str, str | bool | None]:
+        """Executes a given command sequence.
+
+        Adds the command sequence to the executor and runs it, returning the result as a dictionary.
+
+        Args:
+            command_sequence (CommandSequence): The command sequence to execute.
+
+        Returns (dict[str, str | bool | None]):
+            A dictionary containing the execution result.
+            The dictionary has the following keys:
+            - success (bool): True if the command executed successfully, False otherwise.
+            - output (str | None): The output of the command if successful, None otherwise.
+            - error (str | None): The error message if the command failed, None otherwise.
+        """
+        self.executor.add_command(command_sequence)
+        output, error = self.executor.execute()
+        self.executor.reset()
+        return {
+            "success": error is None,
+            "output": output.strip() if output else None,
+            "error": error
+        }
+
+    def run_preconfigured_command_sequence(self,
+                                           command_name: PreconfiguredCommandEnum,
+                                           *args: Any) -> dict[str, str | bool | None]:
+        """Runs a predefined command sequence.
+
+        Creates and executes a predefined command based on the given name and arguments.
+
+        For example `run_preconfigured_command_sequence(PreconfiguredCommandEnum.SPACK_COMPILER_FIND, 'gcc', '11')`
+        will execute the command `source spack_setup_path && spack find gcc@11`
+
+        Args:
+            command_name (PreconfiguredCommandEnum): The name of the predefined command sequence.
+            args (tuple(Any, ...)): Arguments to pass to the command constructor. The arguments should correspond to the flags, options, and placeholders of the command.
+
+        Returns:
+            A dictionary containing the execution result.
+            The dictionary has the following keys:
+            - success (bool): True if the command executed successfully, False otherwise.
+            - output (str | None): The output of the command if successful, None otherwise.
+            - error (str | None): The error message if the command failed, None otherwise.
+            If command_type is invalid, returns a dictionary with "success" as False and an error message.
+        """
+        if command_name not in self.commands_map:
+            return {"success": False, "error": f"Invalid command name: {command_name}"}
+
+        command_sequence = self.commands_map[command_name](*args)
+        return self.execute_command(command_sequence)
+
+    def run_custom_command_sequence(self,
+                                    command_placeholders_map: dict[CommandEnum, dict[str, str]]) \
+            -> dict[str, str | bool | None]:
+        """Runs a custom command sequence.
+
+        Creates and executes a custom command sequence from a map of command names to placeholder values.
+
+        Args:
+            command_placeholders_map (dict[CommandEnum, dict[str, str]]): A dictionary mapping command name enums to
+                placeholder values. The key is the command name in `CommandEnum` and the value is a dictionary
+                mapping placeholder names to their actual values.
+
+                For example, if the key command is `CommandEnum.FIND_IN_FILE`,
+                 this corresponds to the command `grep -i {search_term} {file_path}` in CommandRegistry.COMMANDS.
+                 Hence the placeholder_map_value should be `{search_term: 'python', file_path: '/path/to/file.txt'}`.
+
+        Returns (dict[str, str | bool | None]):
+            A dictionary containing the execution result.
+            The dictionary has the following keys:
+            - success (bool): True if the command executed successfully, False otherwise.
+            - output (str | None): The output of the command if successful, None otherwise.
+            - error (str | None): The error message if the command failed, None otherwise.
+        """
+        command_sequence = (CommandSequenceFactory
+                            .create_custom_command_sequence(command_placeholders_map))
+        return self.execute_command(command_sequence)
diff --git a/dedal/commands/command_sequence.py b/dedal/commands/command_sequence.py
new file mode 100644
index 0000000000000000000000000000000000000000..6115215a86a5b9d8fef5d3ea5af37f25210652ad
--- /dev/null
+++ b/dedal/commands/command_sequence.py
@@ -0,0 +1,54 @@
+""" Command Sequence module. """
+#  Copyright (c) 2025
+#  License Information: To be decided!
+#
+#  File: command_sequence.py
+#  Description: Command Sequence abstraction
+#  Created by: Murugan, Jithu <j.murugan@fz-juelich.de>
+#  Created on: 2025-02-17
+
+from dedal.commands.command import Command
+
+
+class CommandSequence(Command):
+    """Represents a sequence of executable commands.
+
+    Allows adding commands to a sequence and executing them in order,
+    combining/chaining their execution strings with "&&".
+    """
+
+    def __init__(self) -> None:
+        """Initializes an empty command sequence."""
+        self.commands: list[Command] = []
+
+    def add_command(self, command: Command) -> None:
+        """Adds a command to the sequence.
+
+        Appends the given command to the list of commands.
+
+        Args:
+            command (Command): The command to add.
+
+        Raises:
+            ValueError: If the provided command is not an instance of `Command`.
+        """
+        if not isinstance(command, Command):
+            raise ValueError("Command must be an instance of Command")
+        self.commands.append(command)
+
+    def execute(self) -> str:
+        """Executes the command sequence.
+
+        Executes each command in the sequence and joins their results with "&&".
+
+        Returns:
+            The combined execution string of all commands in the sequence.
+        """
+        return " && ".join(cmd.execute().strip() for cmd in self.commands).strip()
+
+    def clear(self) -> None:
+        """Clears the command sequence.
+
+        Removes all commands from the sequence, making it empty.
+        """
+        self.commands.clear()
diff --git a/dedal/commands/command_sequence_builder.py b/dedal/commands/command_sequence_builder.py
new file mode 100644
index 0000000000000000000000000000000000000000..bd35505772d8d618b17a7e4a81b8d6c7698ef91c
--- /dev/null
+++ b/dedal/commands/command_sequence_builder.py
@@ -0,0 +1,71 @@
+""" Command Sequence Builder module. """
+#  Copyright (c) 2025
+#  License Information: To be decided!
+#
+#  File: command_sequence_builder.py
+#  Description: Command sequence builder module for creating command sequences
+#  Created by: Murugan, Jithu <j.murugan@fz-juelich.de>
+#  Created on: 2025-02-19
+from __future__ import annotations
+
+from dedal.commands.command_enum import CommandEnum
+from dedal.commands.command_registry import CommandRegistry
+from dedal.commands.command_sequence import CommandSequence
+from dedal.commands.shell_command_factory import ShellCommandFactory
+
+
+class CommandSequenceBuilder:
+    """Builds a sequence of commands using the builder pattern.
+
+    Facilitates the creation of CommandSequence objects by adding commands incrementally
+    and then building the final sequence.
+    """
+
+    def __init__(self) -> None:
+        """Initializes a new CommandSequenceBuilder with an empty sequence."""
+        self.sequence: CommandSequence = CommandSequence()
+
+    def add_generic_command(self,
+                            command_name: CommandEnum,
+                            placeholders: dict[str, str]) -> CommandSequenceBuilder:
+        """Adds a generic command to the sequence.
+
+        Retrieves the command definition from the CommandRegistry, replaces placeholders with
+        provided values, creates a ShellCommand, and adds it to the sequence.
+
+        Args:
+            command_name (CommandEnum): The enum representing the command name to add.
+            placeholders (dict[str, str]): A dictionary of placeholder values to substitute in the command.
+
+        Returns:
+            The CommandSequenceBuilder instance (self) for method chaining.
+
+        Raises:
+            ValueError: If the command type is invalid or if the command is unknown.
+        """
+        if not isinstance(command_name, CommandEnum):
+            raise ValueError("Invalid command type. Use CommandEnum.")
+        command = CommandRegistry.get_command(command_name)
+
+        if command is None:
+            raise ValueError(f"Unknown command: {command_name}")
+        full_command = command[:]  # Copy the command list to avoid mutation
+        # Replace placeholders with actual values
+        if placeholders:
+            full_command = [placeholders.get(arg.strip("{}"), arg) for arg in full_command]
+            full_command = list(filter(None, full_command))
+        shell_command = ShellCommandFactory.create_command(*full_command)
+        self.sequence.add_command(shell_command)
+        return self
+
+    def build(self) -> CommandSequence:
+        """Builds and returns the CommandSequence.
+
+        Returns the constructed CommandSequence and resets the builder for creating new sequences.
+
+        Returns:
+            The built CommandSequence.
+        """
+        sequence = self.sequence
+        self.sequence = CommandSequence()  # Reset for next build
+        return sequence
diff --git a/dedal/commands/command_sequence_factory.py b/dedal/commands/command_sequence_factory.py
new file mode 100644
index 0000000000000000000000000000000000000000..1c1872451b4ec41950ce7138246996f16018b6e5
--- /dev/null
+++ b/dedal/commands/command_sequence_factory.py
@@ -0,0 +1,46 @@
+""" Command Sequence Factory module. """
+#  Copyright (c) 2025
+#  License Information: To be decided!
+#
+#  File: command_sequence_factory.py
+#  Description: Brief description of the file.
+#  Created by: Murugan, Jithu <j.murugan@fz-juelich.de>
+#  Created on: 2025-02-17
+
+from dedal.commands.command_enum import CommandEnum
+from dedal.commands.command_sequence import CommandSequence
+from dedal.commands.command_sequence_builder import CommandSequenceBuilder
+
+
+class CommandSequenceFactory:
+    """Factory for creating CommandSequence objects."""
+
+    @staticmethod
+    def create_custom_command_sequence(
+            command_placeholders_map: dict[CommandEnum, dict[str, str]]) -> CommandSequence:
+        """Creates a custom CommandSequence.
+
+        Builds a CommandSequence from a dictionary mapping CommandEnums to placeholder values.
+
+        For example if the key command is `CommandEnum.FIND_IN_FILE` and the value is `{search_term: 'python', file_path: '/path/to/file.txt'}`,
+        this corresponds to the command `grep -i {search_term} {file_path}` in CommandRegistry.COMMANDS. So the user can create a sequence of such commands.
+
+        e.g. command_placeholders_map = {
+            CommandEnum.FIND_IN_FILE: {
+                "search_term": "python",
+                "file_path": "/path/to/file.txt"
+            },
+            CommandEnum.SHOW_DIRECTORY: {},
+            CommandEnum.LIST_FILES: {"folder_location": "/tmp"},
+            CommandEnum.ECHO_MESSAGE: {"message": "Hello, world!"}
+        }
+
+        Args:
+            command_placeholders_map: A dictionary mapping CommandEnum members to dictionaries of placeholder values.
+        Returns:
+            A CommandSequence object representing the custom command sequence.
+        """
+        builder = CommandSequenceBuilder()
+        for command_type, placeholders in command_placeholders_map.items():
+            builder.add_generic_command(command_type, placeholders)
+        return builder.build()
diff --git a/dedal/commands/generic_shell_command.py b/dedal/commands/generic_shell_command.py
new file mode 100644
index 0000000000000000000000000000000000000000..0a02b09537722087a93ab43a211b20e1c82750e9
--- /dev/null
+++ b/dedal/commands/generic_shell_command.py
@@ -0,0 +1,47 @@
+""" Generic Shell Command module. """
+#  Copyright (c) 2025
+#  License Information: To be decided!
+#
+#  File: generic_shell_command.py
+#  Description: Generic shell command implementation
+#  Created by: Murugan, Jithu <j.murugan@fz-juelich.de>
+#  Created on: 2025-02-17
+
+from dedal.commands.command import Command
+
+
+class GenericShellCommand(Command):
+    """Represents a generic shell command.
+
+    Encapsulates a shell command with its name and arguments, providing
+    a way to execute it.
+    """
+
+    def __init__(self, command_name: str, *args: str) -> None:
+        """Initializes a new GenericShellCommand.
+
+        Args:
+            command_name (str): The name of the command.
+            *args (str): The arguments for the command.
+
+        Raises:
+            ValueError: If the command name is empty, not a string, or if any of the arguments are not strings.
+        """
+        if not command_name:
+            raise ValueError("Command name is required!")
+        if not isinstance(command_name, str):
+            raise ValueError("Command name must be a string!")
+        if not all(isinstance(arg, str) for arg in args):
+            raise ValueError("All arguments must be strings!")
+        self.args: tuple[str, ...] = tuple(map(str.strip, args))
+        self.command_name: str = command_name.strip()
+
+    def execute(self) -> str:
+        """Executes the command.
+
+        Constructs and returns the full command string, including the command name and arguments.
+
+        Returns:
+            The full command string.
+        """
+        return f"{self.command_name} {' '.join(self.args)}" if self.args else self.command_name
diff --git a/dedal/commands/preconfigured_command_enum.py b/dedal/commands/preconfigured_command_enum.py
new file mode 100644
index 0000000000000000000000000000000000000000..14b747ada43cf6e5eb31c0ddcbf1753fa2eac485
--- /dev/null
+++ b/dedal/commands/preconfigured_command_enum.py
@@ -0,0 +1,28 @@
+""" Preconfigured Command Enum module. """
+#  Copyright (c) 2025
+#  License Information: To be decided!
+#
+#  File: command_enum.py
+#  Description: Enumeration of supported predefined commands used in command_runner module:
+#  Created by: Murugan, Jithu <j.murugan@fz-juelich.de>
+#  Created on: 2025-02-17
+
+from enum import Enum
+
+
+class PreconfiguredCommandEnum(Enum):
+    """Enumeration of preconfigured composite commands.
+
+    Provides a predefined set of command identifiers for commonly used operations.
+
+    The command_runner module uses these identifiers to construct command sequences and execute them via 'run_preconfigured_command_sequence' method.
+    """
+    SPACK_COMPILER_FIND = "spack_compiler_find"
+    SPACK_COMPILERS = "spack_compilers"
+    SPACK_COMPILER_INFO = "spack_compiler_info"
+    SPACK_COMPILER_LIST = "spack_compiler_list"
+    SPACK_ENVIRONMENT_ACTIVATE = "spack_environment_activate"
+    SPACK_FIND = "spack_find"
+    SPACK_INSTALL = "spack_install"
+    SPACK_MIRROR_ADD = "spack_mirror_add"
+    SPACK_GPG_TRUST = "spack_gpg_trust"
diff --git a/dedal/commands/shell_command_factory.py b/dedal/commands/shell_command_factory.py
new file mode 100644
index 0000000000000000000000000000000000000000..e63a456e201903a8ebf6a4885a4e230c509e97f6
--- /dev/null
+++ b/dedal/commands/shell_command_factory.py
@@ -0,0 +1,33 @@
+""" Shell Command Factory module. """
+#  Copyright (c) 2025
+#  License Information: To be decided!
+#
+#  File: shell_command_factory.py
+#  Description: Shell command factory to create shell command instances
+#  Created by: Murugan, Jithu <j.murugan@fz-juelich.de>
+#  Created on: 2025-02-17
+
+from dedal.commands.command import Command
+from dedal.commands.generic_shell_command import GenericShellCommand
+
+
+class ShellCommandFactory:
+    """Factory for creating shell command instances.
+
+    Provides a static method for creating GenericShellCommand objects.
+    """
+
+    @staticmethod
+    def create_command(command_name: str, *args: str) -> Command:
+        """Creates a generic shell command.
+
+        Instantiates and returns a GenericShellCommand object with the given command name and arguments.
+
+        Args:
+            command_name (str): The name of the command.
+            *args (str): The arguments for the command.
+
+        Returns:
+            A GenericShellCommand object.
+        """
+        return GenericShellCommand(command_name, *args)
diff --git a/dedal/commands/spack_command_sequence_factory.py b/dedal/commands/spack_command_sequence_factory.py
new file mode 100644
index 0000000000000000000000000000000000000000..ce7afac388e152412842ba448a9f17c126f90513
--- /dev/null
+++ b/dedal/commands/spack_command_sequence_factory.py
@@ -0,0 +1,211 @@
+"""Factory for generating predefined spack related command sequences."""
+#  Copyright (c) 2025
+#  License Information: To be decided!
+#
+#  File: spack_command_sequence_factory.py
+#  Description: Factory for generating predefined spack related command sequences
+#  Created by: Murugan, Jithu <j.murugan@fz-juelich.de>
+#  Created on: 2025-02-17
+
+from dedal.commands.command_enum import CommandEnum
+from dedal.commands.command_sequence import CommandSequence
+from dedal.commands.command_sequence_builder import CommandSequenceBuilder
+
+
+def get_name_version(name: str | None, version: str | None) -> str:
+    """Formats a name and version string.
+
+    Returns a string combining the name and version with "@" if both are provided,
+    otherwise returns the name or an empty string.
+
+    Args:
+        name (str): The name.
+        version (str): The version.
+
+    Returns:
+        The formatted name and version string.
+    """
+    return f"{name}@{version}" if name and version else name or ""
+
+
+class SpackCommandSequenceFactory:
+    """Factory for creating Spack command sequences.
+
+    Provides methods for building CommandSequence objects for various Spack operations.
+    """
+
+    @staticmethod
+    def create_spack_enabled_command_sequence_builder(spack_setup_script: str) -> CommandSequenceBuilder:
+        """Creates a CommandSequenceBuilder with Spack setup.
+
+        Initializes a builder with the 'source' command for the given Spack setup script.
+
+        Args:
+            spack_setup_script (str): Path to the Spack setup script.
+
+        Returns:
+            A CommandSequenceBuilder pre-configured with the Spack setup command.
+        """
+        return (CommandSequenceBuilder()
+                .add_generic_command(CommandEnum.SOURCE, {"file_path": spack_setup_script}))
+
+    @staticmethod
+    def create_spack_compilers_command_sequence(spack_setup_script: str) -> CommandSequence:
+        """Creates a command sequence for listing Spack compilers.
+
+        Builds a sequence that sources the Spack setup script and then lists available compilers.
+
+        Args:
+            spack_setup_script (str): Path to the Spack setup script.
+
+        Returns:
+            A CommandSequence for listing Spack compilers.
+        """
+        return (SpackCommandSequenceFactory.create_spack_enabled_command_sequence_builder(spack_setup_script)
+                .add_generic_command(CommandEnum.SPACK_COMPILERS, {})
+                .build())
+
+    @staticmethod
+    def create_spack_compiler_list_command_sequence(spack_setup_script: str) -> CommandSequence:
+        """Creates a command sequence for listing Spack compilers.
+
+        Builds a sequence that sources the Spack setup script and then lists available compilers.
+
+        Args:
+            spack_setup_script (str): Path to the Spack setup script.
+
+        Returns:
+            A CommandSequence for listing Spack compilers.
+        """
+        return (SpackCommandSequenceFactory.create_spack_enabled_command_sequence_builder(spack_setup_script)
+                .add_generic_command(CommandEnum.SPACK_COMPILER_LIST, {})
+                .build())
+
+    @staticmethod
+    def create_spack_install_package_command_sequence(spack_setup_script: str,
+                                                      package_name: str | None,
+                                                      version: str | None) -> CommandSequence:
+        """Creates a command sequence for installing a Spack package.
+
+        Builds a sequence that sources the Spack setup script and then installs the specified package.
+
+        Args:
+            spack_setup_script (str): Path to the Spack setup script.
+            package_name (str | None): The name of the package to install.
+            version (str | None): The version of the package to install.
+
+        Returns:
+            A CommandSequence for installing the Spack package.
+        """
+        return (SpackCommandSequenceFactory.create_spack_enabled_command_sequence_builder(spack_setup_script)
+                .add_generic_command(CommandEnum.SPACK_INSTALL, {
+            "package_name_with_version": get_name_version(package_name, version)
+        }).build())
+
+    @staticmethod
+    def create_spack_mirror_add_command_sequence(spack_setup_script: str,
+                                                 env_name: str,
+                                                 mirror_name: str,
+                                                 mirror_path: str,
+                                                 autopush: bool = False,
+                                                 signed: bool = False) -> CommandSequence:
+        """Creates a command sequence for adding a Spack mirror.
+
+        Builds a sequence that sources the Spack setup script, activates an environment (if specified),
+        and adds the given mirror.
+
+        Args:
+            spack_setup_script (str): Path to the Spack setup script.
+            env_name (str): The name of the environment to activate.
+            mirror_name (str): The name of the mirror.
+            mirror_path (str): The URL or path of the mirror.
+            autopush (bool): Whether to enable autopush for the mirror.
+            signed (bool): Whether to require signed packages from the mirror.
+
+        Returns:
+            A CommandSequence for adding the Spack mirror.
+        """
+        builder = SpackCommandSequenceFactory.create_spack_enabled_command_sequence_builder(spack_setup_script)
+        if env_name:
+            builder = builder.add_generic_command(CommandEnum.SPACK_ENVIRONMENT_ACTIVATE,
+                                                  {"env_path": env_name})
+        place_holders = {
+            "mirror_name": mirror_name,
+            "mirror_path": mirror_path,
+            "autopush": "--autopush" if autopush else "",
+            "signed": "--signed" if signed else ""
+        }
+        builder = builder.add_generic_command(CommandEnum.SPACK_MIRROR_ADD, place_holders)
+        return builder.build()
+
+    @staticmethod
+    def create_spack_compiler_info_command_sequence(spack_setup_script: str,
+                                                    compiler_name: str,
+                                                    compiler_version: str) -> CommandSequence:
+        """Creates a command sequence for getting Spack compiler information.
+
+        Builds a sequence that sources the Spack setup script and retrieves information about the specified compiler.
+
+
+        Args:
+            spack_setup_script (str): Path to the Spack setup script.
+            compiler_name (str): The name of the compiler.
+            compiler_version (str): The version of the compiler.
+
+        Returns:
+            A CommandSequence for getting compiler information.
+        """
+        return (SpackCommandSequenceFactory.create_spack_enabled_command_sequence_builder(spack_setup_script)
+                .add_generic_command(CommandEnum.SPACK_COMPILER_INFO,
+                                     {
+                                         "compiler_name_with_version":
+                                             get_name_version(compiler_name, compiler_version)
+                                     })
+                .build())
+
+    @staticmethod
+    def create_spack_post_install_find_command_sequence(spack_setup_script: str,
+                                                        env_path: str,
+                                                        package_name: str | None,
+                                                        version: str | None) -> CommandSequence:
+        """Creates a command sequence for finding installed Spack packages after installation.
+
+        Builds a sequence that sources the Spack setup script, activates the specified environment,
+        and then searches for the given package.
+
+        Args:
+            spack_setup_script (str): Path to the Spack setup script.
+            env_path (str): The path to the Spack environment.
+            package_name (str | None): The name of the package to find.
+            version (str | None): The version of the package to find.
+
+        Returns:
+            A CommandSequence for finding installed Spack packages.
+        """
+        return (SpackCommandSequenceFactory.create_spack_enabled_command_sequence_builder(spack_setup_script)
+                .add_generic_command(CommandEnum.SPACK_ENVIRONMENT_ACTIVATE, {"env_path": env_path})
+                .add_generic_command(CommandEnum.SPACK_FIND,
+                                     {
+                                         "package_name_with_version":
+                                             get_name_version(package_name, version)
+                                     }).build())
+
+    @staticmethod
+    def create_spack_gpg_trust_command_sequence(spack_setup_script: str,
+                                                public_key_path: str) -> CommandSequence:
+        """Creates a command sequence for trusting a GPG key in Spack.
+
+        Builds a sequence that sources the Spack setup script and then trusts the given GPG key.
+
+        Args:
+            spack_setup_script (str): Path to the Spack setup script.
+            public_key_path (str): Path to the public key file.
+
+        Returns:
+            A CommandSequence for trusting the GPG key.
+        """
+        return (SpackCommandSequenceFactory.create_spack_enabled_command_sequence_builder(spack_setup_script)
+                .add_generic_command(CommandEnum.SPACK_GPG_TRUST,
+                                     {
+                                         "public_key_path": public_key_path
+                                     }).build())
diff --git a/dedal/spack_factory/SpackOperation.py b/dedal/spack_factory/SpackOperation.py
index aebabc0bc816bca5dbf111056251a0b985aad429..ad4f2389edc1c278cfe91eb166be0dc51f60757a 100644
--- a/dedal/spack_factory/SpackOperation.py
+++ b/dedal/spack_factory/SpackOperation.py
@@ -2,13 +2,16 @@ import os
 import re
 import subprocess
 from pathlib import Path
+
+from dedal.commands.command_runner import CommandRunner
+from dedal.commands.preconfigured_command_enum import PreconfiguredCommandEnum
+from dedal.configuration.SpackConfig import SpackConfig
 from dedal.error_handling.exceptions import BashCommandException, NoSpackEnvironmentException, \
     SpackInstallPackagesException, SpackConcertizeException, SpackMirrorException, SpackGpgException, SpackRepoException
 from dedal.logger.logger_builder import get_logger
-from dedal.configuration.SpackConfig import SpackConfig
 from dedal.tests.testing_variables import SPACK_VERSION
-from dedal.wrapper.spack_wrapper import check_spack_env
 from dedal.utils.utils import run_command, git_clone_repo, log_command, set_bashrc_variable
+from dedal.wrapper.spack_wrapper import check_spack_env
 
 
 class SpackOperation:
@@ -47,6 +50,7 @@ class SpackOperation:
         if self.spack_config.env and spack_config.env.path:
             self.spack_config.env.path = spack_config.env.path
             self.spack_config.env.path.mkdir(parents=True, exist_ok=True)
+        self.command_runner = CommandRunner()
 
     def create_fetch_spack_environment(self):
         if self.spack_config.env.git_path:
@@ -120,9 +124,7 @@ class SpackOperation:
                              check=True,
                              capture_output=True, text=True, logger=self.logger,
                              info_msg=f'Checking if environment {self.spack_config.env.name} exists')
-        if result is None:
-            return False
-        return True
+        return result is not None
 
     @check_spack_env
     def add_spack_repo(self, repo_path: Path, repo_name: str):
@@ -187,26 +189,104 @@ class SpackOperation:
         else:
             raise SpackGpgException('No GPG configuration was defined is spack configuration')
 
-    def add_mirror(self, mirror_name: str, mirror_path: Path, signed=False, autopush=False, global_mirror=False):
-        autopush = '--autopush' if autopush else ''
-        signed = '--signed' if signed else ''
-        if global_mirror:
-            run_command("bash", "-c",
-                        f'{self.spack_setup_script} spack mirror add {autopush} {signed} {mirror_name} {mirror_path}',
-                        check=True,
-                        logger=self.logger,
-                        info_msg=f'Added mirror {mirror_name}',
-                        exception_msg=f'Failed to add mirror {mirror_name}',
-                        exception=SpackMirrorException)
-        else:
-            check_spack_env(
-                run_command("bash", "-c",
-                            f'{self.spack_command_on_env} && spack mirror add {autopush} {signed} {mirror_name} {mirror_path}',
-                            check=True,
-                            logger=self.logger,
-                            info_msg=f'Added mirror {mirror_name}',
-                            exception_msg=f'Failed to add mirror {mirror_name}',
-                            exception=SpackMirrorException))
+    def add_mirror(self,
+                   mirror_name: str,
+                   mirror_path: str,
+                   signed=False,
+                   autopush=False,
+                   global_mirror=False) -> bool:
+        """Adds a Spack mirror.
+
+        Adds a new mirror to the Spack configuration, either globally or to a specific environment.
+
+        Args:
+            mirror_name (str): The name of the mirror.
+            mirror_path (str): The path or URL of the mirror.
+            signed (bool): Whether to require signed packages from the mirror.
+            autopush (bool): Whether to enable autopush for the mirror.
+            global_mirror (bool): Whether to add the mirror globally (True) or to the current environment (False).
+
+        Returns:
+            True if the mirror was added successfully, False otherwise.
+
+        Raises:
+            ValueError: If mirror_name or mirror_path are empty.
+            NoSpackEnvironmentException: If global_mirror is False and no environment is defined.
+        """
+        if not mirror_name or not mirror_path:
+            raise ValueError("mirror_name and mirror_path are required")
+        if not global_mirror and not self.env_path:
+            raise NoSpackEnvironmentException('No spack environment defined')
+        result = self.command_runner.run_preconfigured_command_sequence(
+            PreconfiguredCommandEnum.SPACK_MIRROR_ADD,
+            self.spack_setup_script,
+            "" if global_mirror else str(self.env_path),
+            mirror_name,
+            mirror_path,
+            autopush,
+            signed)
+        return self.handle_result(
+            result,
+            "Failed to add mirror %s, reason: %s, output: %s",
+            (mirror_name,),
+            "Added mirror %s",
+            (mirror_name,)
+        )
+
+    def trust_gpg_key(self, public_key_path: str):
+        """Adds a GPG public key to the trusted keyring.
+
+        This method attempts to add the provided GPG public key to the
+        Spack trusted keyring.
+
+        Args:
+            public_key_path (str): Path to the GPG public key file.
+
+        Returns:
+            bool: True if the key was added successfully, False otherwise.
+
+        Raises:
+            ValueError: If public_key_path is empty.
+        """
+        if not public_key_path:
+            raise ValueError("public_key_path is required")
+        result = self.command_runner.run_preconfigured_command_sequence(
+            PreconfiguredCommandEnum.SPACK_GPG_TRUST,
+            self.spack_setup_script,
+            public_key_path)
+        return self.handle_result(
+            result,
+            "Failed to add public key %s as trusted, reason: %s, output: %s",
+            (public_key_path,),
+            "Added public key %s as trusted",
+            (public_key_path,),
+        )
+
+    def handle_result(self,
+                      result: dict[str, str | bool | None],
+                      error_msg: str,
+                      error_msg_args: tuple[str, ...],
+                      info_msg: str,
+                      info_msg_args: tuple[str, ...]):
+        """Handles the result of a command execution.
+
+        Checks the success status of the result and logs either an error or an info message accordingly.
+
+        Args:
+            result (dict[str, str | bool | None]): A dictionary containing the result of the command execution.
+            error_msg (str): The error message to log if the command failed.
+            error_msg_args (tuple[str, ...]): Arguments to format the error message.
+            info_msg (str): The info message to log if the command succeeded.
+            info_msg_args (tuple[str, ...]): Arguments to format the info message.
+
+        Returns:
+            bool: True if the command succeeded, False otherwise.
+        """
+        if not result["success"]:
+            self.logger.error(error_msg, *error_msg_args, result['error'], result['output'])
+            return False
+        self.logger.info(info_msg, *info_msg_args)
+        return True
 
     def remove_mirror(self, mirror_name: str):
         run_command("bash", "-c",
diff --git a/dedal/spack_factory/SpackOperationCreateCache.py b/dedal/spack_factory/SpackOperationCreateCache.py
index 41cfc8454136fd0096f6dcf78c0df62de004c6e6..8d6125fb3ebc3d505509ac29841b0975a0b8e768 100644
--- a/dedal/spack_factory/SpackOperationCreateCache.py
+++ b/dedal/spack_factory/SpackOperationCreateCache.py
@@ -42,7 +42,10 @@ class SpackOperationCreateCache(SpackOperation):
         if self.spack_config.gpg:
             signed = True
             self.create_gpg_keys()
-        self.add_mirror('local_cache', self.spack_config.buildcache_dir, signed=signed, autopush=signed,
+        self.add_mirror('local_cache',
+                        str(self.spack_config.buildcache_dir),
+                        signed=signed,
+                        autopush=signed,
                         global_mirror=False)
         self.logger.info(f'Added mirror for {self.spack_config.env.name}')
         super().install_packages(jobs=jobs, signed=signed, debug=debug, fresh=True)
diff --git a/dedal/spack_factory/SpackOperationUseCache.py b/dedal/spack_factory/SpackOperationUseCache.py
index 2bb6f76ae6bf0cc56a1cec842684f6a1e22f7ca5..16312e7fd3af19e4ded58eb606f2b1861dedde09 100644
--- a/dedal/spack_factory/SpackOperationUseCache.py
+++ b/dedal/spack_factory/SpackOperationUseCache.py
@@ -3,11 +3,12 @@ import subprocess
 from pathlib import Path
 
 from dedal.build_cache.BuildCacheManager import BuildCacheManager
+from dedal.configuration.SpackConfig import SpackConfig
+from dedal.error_handling.exceptions import NoSpackEnvironmentException
 from dedal.error_handling.exceptions import SpackInstallPackagesException
 from dedal.logger.logger_builder import get_logger
 from dedal.spack_factory.SpackOperation import SpackOperation
-from dedal.configuration.SpackConfig import SpackConfig
-from dedal.utils.utils import file_exists_and_not_empty, run_command, log_command, copy_to_tmp, copy_file
+from dedal.utils.utils import file_exists_and_not_empty, run_command, log_command, copy_file
 from dedal.wrapper.spack_wrapper import check_spack_env
 
 
@@ -29,9 +30,33 @@ class SpackOperationUseCache(SpackOperation):
                                              os.environ.get('BUILDCACHE_OCI_PASSWORD'),
                                              cache_version=spack_config.cache_version_build)
 
-    def setup_spack_env(self):
+    def setup_spack_env(self) -> None:
+        """Set up the spack environment for using the cache.
+
+        Downloads the build cache, adds the public key to trusted keys,
+        and adds the build cache mirror.
+
+        Raises:
+            ValueError: If there is an issue with the build cache setup (mirror_name/mirror_path are empty).
+            NoSpackEnvironmentException: If the spack environment is not set up.
+        """
         super().setup_spack_env()
-        # todo add buildcache to the spack environment
+        try:
+            # Download build cache from OCI Registry and add public key to trusted keys
+            self.build_cache.download(self.spack_config.buildcache_dir)
+            cached_public_key = self.build_cache.get_public_key_from_cache(str(self.spack_config.buildcache_dir))
+            signed = cached_public_key is not None and self.trust_gpg_key(cached_public_key)
+            if not signed:
+                self.logger.warning("Public key not found in cache or failed to trust pgp keys!")
+            # Add build cache mirror
+            self.add_mirror('local_cache',
+                            str(self.spack_config.buildcache_dir),
+                            signed=signed,
+                            autopush=True,
+                            global_mirror=False)
+        except (ValueError, NoSpackEnvironmentException) as e:
+            self.logger.error("Error adding buildcache mirror: %s", e)
+            raise
 
     @check_spack_env
     def concretize_spack_env(self):
diff --git a/dedal/tests/spack_from_scratch_test.py b/dedal/tests/spack_from_scratch_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..2fec80f743d72190d0b175191660250981f98255
--- /dev/null
+++ b/dedal/tests/spack_from_scratch_test.py
@@ -0,0 +1,204 @@
+from pathlib import Path
+import pytest
+from dedal.configuration.SpackConfig import SpackConfig
+from dedal.error_handling.exceptions import BashCommandException, NoSpackEnvironmentException
+from dedal.spack_factory.SpackOperationCreator import SpackOperationCreator
+from dedal.model.SpackDescriptor import SpackDescriptor
+from dedal.tests.testing_variables import test_spack_env_git, ebrains_spack_builds_git
+from dedal.utils.utils import file_exists_and_not_empty
+
+
+def test_spack_repo_exists_1():
+    spack_operation = SpackOperationCreator.get_spack_operator()
+    spack_operation.install_spack()
+    assert spack_operation.spack_repo_exists('ebrains-spack-builds') == False
+
+
+def test_spack_repo_exists_2(tmp_path):
+    install_dir = tmp_path
+    env = SpackDescriptor('ebrains-spack-builds', install_dir)
+    config = SpackConfig(env=env, install_dir=install_dir)
+    spack_operation = SpackOperationCreator.get_spack_operator(config)
+    spack_operation.install_spack()
+    with pytest.raises(NoSpackEnvironmentException):
+        spack_operation.spack_repo_exists(env.env_name)
+
+
+def test_spack_repo_exists_3(tmp_path):
+    install_dir = tmp_path
+    env = SpackDescriptor('ebrains-spack-builds', install_dir)
+    config = SpackConfig(env=env, install_dir=install_dir)
+    spack_operation = SpackOperationCreator.get_spack_operator(config)
+    spack_operation.install_spack()
+    print(spack_operation.get_spack_installed_version())
+    spack_operation.setup_spack_env()
+    assert spack_operation.spack_repo_exists(env.env_name) == False
+
+
+def test_spack_from_scratch_setup_1(tmp_path):
+    install_dir = tmp_path
+    env = SpackDescriptor('ebrains-spack-builds', install_dir, ebrains_spack_builds_git)
+    config = SpackConfig(env=env, system_name='ebrainslab', install_dir=install_dir)
+    spack_operation = SpackOperationCreator.get_spack_operator(config)
+    spack_operation.install_spack()
+    spack_operation.setup_spack_env()
+    assert spack_operation.spack_repo_exists(env.env_name) == False
+
+
+def test_spack_from_scratch_setup_2(tmp_path):
+    install_dir = tmp_path
+    env = SpackDescriptor('ebrains-spack-builds', install_dir, ebrains_spack_builds_git)
+    repo = env
+    config = SpackConfig(env=env, system_name='ebrainslab', install_dir=install_dir)
+    config.add_repo(repo)
+    config.add_repo(repo)
+    spack_operation = SpackOperationCreator.get_spack_operator(config)
+    spack_operation.install_spack()
+    spack_operation.setup_spack_env()
+    assert spack_operation.spack_repo_exists(env.env_name) == True
+
+
+def test_spack_from_scratch_setup_3(tmp_path):
+    install_dir = tmp_path
+    env = SpackDescriptor('new_env1', install_dir)
+    repo = env
+    config = SpackConfig(env=env, system_name='ebrainslab', install_dir=install_dir)
+    config.add_repo(repo)
+    config.add_repo(repo)
+    spack_operation = SpackOperationCreator.get_spack_operator(config)
+    spack_operation.install_spack()
+    with pytest.raises(BashCommandException):
+        spack_operation.setup_spack_env()
+
+
+def test_spack_from_scratch_setup_4(tmp_path):
+    install_dir = tmp_path
+    env = SpackDescriptor('new_env2', install_dir)
+    config = SpackConfig(env=env, install_dir=install_dir)
+    spack_operation = SpackOperationCreator.get_spack_operator(config)
+    spack_operation.install_spack()
+    spack_operation.setup_spack_env()
+    assert spack_operation.spack_env_exists() == True
+
+
+def test_spack_not_a_valid_repo():
+    env = SpackDescriptor('ebrains-spack-builds', Path(), None)
+    repo = env
+    config = SpackConfig(env=env, system_name='ebrainslab')
+    config.add_repo(repo)
+    spack_operation = SpackOperationCreator.get_spack_operator(config)
+    with pytest.raises(BashCommandException):
+        spack_operation.add_spack_repo(repo.path, repo.env_name)
+
+
+def test_spack_from_scratch_concretize_1(tmp_path):
+    install_dir = tmp_path
+    env = SpackDescriptor('ebrains-spack-builds', install_dir, ebrains_spack_builds_git)
+    repo = env
+    config = SpackConfig(env=env, system_name='ebrainslab', install_dir=install_dir)
+    config.add_repo(repo)
+    config.add_repo(repo)
+    spack_operation = SpackOperationCreator.get_spack_operator(config)
+    spack_operation.install_spack()
+    spack_operation.install_spack()
+    spack_operation.setup_spack_env()
+    spack_operation.concretize_spack_env(force=True)
+    concretization_file_path = spack_operation.env_path / 'spack.lock'
+    assert file_exists_and_not_empty(concretization_file_path) == True
+
+
+def test_spack_from_scratch_concretize_2(tmp_path):
+    install_dir = tmp_path
+    env = SpackDescriptor('ebrains-spack-builds', install_dir, ebrains_spack_builds_git)
+    repo = env
+    config = SpackConfig(env=env, system_name='ebrainslab', install_dir=install_dir)
+    config.add_repo(repo)
+    config.add_repo(repo)
+    spack_operation = SpackOperationCreator.get_spack_operator(config)
+    spack_operation.install_spack()
+    spack_operation.setup_spack_env()
+    spack_operation.concretize_spack_env(force=False)
+    concretization_file_path = spack_operation.env_path / 'spack.lock'
+    assert file_exists_and_not_empty(concretization_file_path) == True
+
+
+def test_spack_from_scratch_concretize_3(tmp_path):
+    install_dir = tmp_path
+    env = SpackDescriptor('ebrains-spack-builds', install_dir, ebrains_spack_builds_git)
+    repo = env
+    config = SpackConfig(env=env, system_name='ebrainslab', install_dir=install_dir)
+    config.add_repo(repo)
+    config.add_repo(repo)
+    spack_operation = SpackOperationCreator.get_spack_operator(config)
+    spack_operation.install_spack()
+    spack_operation.setup_spack_env()
+    concretization_file_path = spack_operation.env_path / 'spack.lock'
+    assert file_exists_and_not_empty(concretization_file_path) == False
+
+
+def test_spack_from_scratch_concretize_4(tmp_path):
+    install_dir = tmp_path
+    env = SpackDescriptor('test-spack-env', install_dir, test_spack_env_git)
+    config = SpackConfig(env=env, install_dir=install_dir)
+    spack_operation = SpackOperationCreator.get_spack_operator(config)
+    spack_operation.install_spack()
+    spack_operation.setup_spack_env()
+    spack_operation.concretize_spack_env(force=False)
+    concretization_file_path = spack_operation.env_path / 'spack.lock'
+    assert file_exists_and_not_empty(concretization_file_path) == True
+
+
+def test_spack_from_scratch_concretize_5(tmp_path):
+    install_dir = tmp_path
+    env = SpackDescriptor('test-spack-env', install_dir, test_spack_env_git)
+    config = SpackConfig(env=env, install_dir=install_dir)
+    spack_operation = SpackOperationCreator.get_spack_operator(config)
+    spack_operation.install_spack()
+    spack_operation.setup_spack_env()
+    spack_operation.concretize_spack_env(force=True)
+    concretization_file_path = spack_operation.env_path / 'spack.lock'
+    assert file_exists_and_not_empty(concretization_file_path) == True
+
+
+def test_spack_from_scratch_concretize_6(tmp_path):
+    install_dir = tmp_path
+    env = SpackDescriptor('test-spack-env', install_dir, test_spack_env_git)
+    repo = SpackDescriptor('ebrains-spack-builds', install_dir, ebrains_spack_builds_git)
+    config = SpackConfig(env=env, install_dir=install_dir)
+    config.add_repo(repo)
+    spack_operation = SpackOperationCreator.get_spack_operator(config)
+    spack_operation.install_spack()
+    spack_operation.setup_spack_env()
+    spack_operation.concretize_spack_env(force=False)
+    concretization_file_path = spack_operation.env_path / 'spack.lock'
+    assert file_exists_and_not_empty(concretization_file_path) == True
+
+
+def test_spack_from_scratch_concretize_7(tmp_path):
+    install_dir = tmp_path
+    env = SpackDescriptor('test-spack-env', install_dir, test_spack_env_git)
+    repo = SpackDescriptor('ebrains-spack-builds', install_dir, ebrains_spack_builds_git)
+    config = SpackConfig(env=env)
+    config.add_repo(repo)
+    spack_operation = SpackOperationCreator.get_spack_operator(config)
+    spack_operation.install_spack()
+    spack_operation.setup_spack_env()
+    spack_operation.concretize_spack_env(force=True)
+    concretization_file_path = spack_operation.env_path / 'spack.lock'
+    assert file_exists_and_not_empty(concretization_file_path) == True
+
+
+def test_spack_from_scratch_install(tmp_path):
+    install_dir = tmp_path
+    env = SpackDescriptor('test-spack-env', install_dir, test_spack_env_git)
+    repo = SpackDescriptor('ebrains-spack-builds', install_dir, ebrains_spack_builds_git)
+    config = SpackConfig(env=env)
+    config.add_repo(repo)
+    spack_operation = SpackOperationCreator.get_spack_operator(config)
+    spack_operation.install_spack()
+    spack_operation.setup_spack_env()
+    spack_operation.concretize_spack_env(force=True)
+    concretization_file_path = spack_operation.env_path / 'spack.lock'
+    assert file_exists_and_not_empty(concretization_file_path) == True
+    install_result = spack_operation.install_packages(jobs=2, signed=False, fresh=True, debug=False)
+    assert install_result.returncode == 0
diff --git a/dedal/tests/spack_install_test.py b/dedal/tests/spack_install_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..564d5c6aa2138e815cd7d092215a4f2eee8816f6
--- /dev/null
+++ b/dedal/tests/spack_install_test.py
@@ -0,0 +1,12 @@
+import pytest
+from dedal.spack_factory.SpackOperation import SpackOperation
+from dedal.tests.testing_variables import SPACK_VERSION
+
+
+# run this test first so that spack is installed only once for all the tests
+@pytest.mark.run(order=1)
+def test_spack_install_scratch():
+    spack_operation = SpackOperation()
+    spack_operation.install_spack(spack_version=f'v{SPACK_VERSION}')
+    installed_spack_version = spack_operation.get_spack_installed_version()
+    assert SPACK_VERSION == installed_spack_version
diff --git a/dedal/tests/unit_tests/test_bash_command_executor.py b/dedal/tests/unit_tests/test_bash_command_executor.py
new file mode 100644
index 0000000000000000000000000000000000000000..e216cc0f85dd22841d3c956b1714e8f444f46c7f
--- /dev/null
+++ b/dedal/tests/unit_tests/test_bash_command_executor.py
@@ -0,0 +1,236 @@
+#  Copyright (c) 2025
+#  License Information: To be decided!
+#
+#  File: test_bash_command_executor.py
+#  Description: Brief description of the file.
+#  Created by: Murugan, Jithu <j.murugan@fz-juelich.de>
+#  Created on: 2025-02-18
+import os
+import subprocess
+from unittest.mock import patch
+
+import pytest
+
+from dedal.commands.bash_command_executor import BashCommandExecutor
+from dedal.commands.command import Command
+
+
+class MockCommand(Command):
+    def __init__(self, cmd: str):
+        self._cmd = cmd
+
+    def execute(self) -> str:
+        return self._cmd
+
+
+class TestBashCommandExecutor:
+    @pytest.mark.parametrize(
+        "test_id, os_name, expected_bash_command",
+        [
+            ("posix_system", "posix", ["bash", "-c"]),
+            ("nt_system", "nt", ["wsl", "bash", "-c"]),
+        ],
+    )
+    def test_init_success_path(self, mocker, test_id, os_name, expected_bash_command):
+        # Arrange
+        mock_get_logger = mocker.patch("dedal.commands.bash_command_executor.logging.getLogger")
+        mocker.patch("dedal.commands.bash_command_executor.os_name", os_name)
+
+        # Act
+        executor = BashCommandExecutor()
+
+        # Assert
+        assert executor.bash_command == expected_bash_command
+        mock_get_logger.assert_called_once_with('dedal.commands.bash_command_executor')
+
+    @pytest.mark.parametrize(
+        "test_id, num_commands", [(1, 1), (5, 5), (0, 0)]
+    )
+    def test_add_command_success_path(self, mocker, test_id: int, num_commands: int):
+        # Arrange
+        executor = BashCommandExecutor()
+        executor.logger = mocker.MagicMock()
+        commands = [MockCommand("some_command") for _ in range(num_commands)]
+
+        # Act
+        for command in commands:
+            executor.add_command(command)
+
+        # Assert
+        assert len(executor.sequence.commands) == num_commands
+        executor.logger.info.assert_has_calls(
+            [mocker.call("Adding command to the sequence: %s", command) for command in commands])
+
+    def test_add_command_invalid_type(self):
+        # Arrange
+        executor = BashCommandExecutor()
+        invalid_command = "not a command object"  # type: ignore
+
+        # Act
+        with pytest.raises(ValueError) as except_info:
+            executor.add_command(invalid_command)
+
+        # Assert
+        assert str(except_info.value) == "Invalid command type. Use Command."
+
+    @patch("dedal.commands.bash_command_executor.os_name", "unknown")
+    def test_init_unknown_os(self):
+
+        # Act
+        executor = BashCommandExecutor()
+
+        # Assert
+        assert executor.bash_command == ["undefined"]
+
+    @pytest.mark.parametrize(
+        "test_id, commands, expected_output",
+        [
+            ("single_command", [MockCommand("echo hello")], "hello\n"),
+            ("multiple_commands", [MockCommand("echo hello"), MockCommand("echo world")], "hello\nworld\n"),
+            ("command_with_pipe", [MockCommand("echo hello | grep hello")], "hello\n"),
+
+        ],
+    )
+    @patch("dedal.commands.bash_command_executor.subprocess.run")
+    def test_execute_success_path_posix(self, mock_subprocess_run, test_id, commands, expected_output, mocker):
+        # Arrange
+        executor = BashCommandExecutor()
+        executor.logger = mocker.MagicMock()
+        for cmd in commands:
+            executor.add_command(cmd)
+        mock_subprocess_run.return_value.stdout = expected_output
+
+        # Act
+        stdout, err = executor.execute()
+
+        # Assert
+        assert stdout == expected_output
+        assert err is None
+        executor.logger.info.assert_has_calls(
+            [mocker.call('Adding command to the sequence: %s', cmd) for cmd in commands] +
+            [mocker.call("Successfully executed command sequence, output: %s",
+                         mock_subprocess_run.return_value.stdout)])
+
+    @patch("dedal.commands.bash_command_executor.subprocess.run",
+           side_effect=FileNotFoundError("Mock file not found error"))
+    def test_execute_file_not_found_error(self, mock_subprocess_run):
+        # Arrange
+        executor = BashCommandExecutor()
+        executor.bash_command = ["bash", "-c"]
+        executor.add_command(MockCommand("some_command"))
+
+        # Act
+        stdout, err = executor.execute()
+
+        # Assert
+        assert stdout is None
+        assert err == "Error: Bash Command: ['bash', '-c'] not found: Mock file not found error"
+        mock_subprocess_run.assert_called_once_with(['bash', '-c', 'some_command'], capture_output=True, text=True,
+                                                    check=True, timeout=172800)
+
+    @patch("dedal.commands.bash_command_executor.subprocess.run",
+           side_effect=subprocess.CalledProcessError(1, "some_command", stderr="Mock stderr"))
+    def test_execute_called_process_error(self, mock_subprocess_run, mocker):
+        # Arrange
+        mocker.patch("dedal.commands.bash_command_executor.os_name", "nt")
+        executor = BashCommandExecutor()
+        executor.add_command(MockCommand("failing_command"))
+
+        # Act
+        stdout, err = executor.execute()
+
+        # Assert
+        assert stdout is None
+        assert err == "Error: Command failed with exit code 1, Error Output: Mock stderr"
+        mock_subprocess_run.assert_called_once_with(['wsl', 'bash', '-c', 'failing_command'], capture_output=True,
+                                                    text=True, check=True, timeout=172800)
+
+    @pytest.mark.parametrize(
+        "test_id, exception, expected_error_message",
+        [
+            ("permission_error", PermissionError("Mock permission denied"),
+             "Error: Permission denied: Mock permission denied"),
+            ("os_error", OSError("Mock OS error"), "Error: OS error occurred: Mock OS error"),
+            ("value_error", ValueError("Mock invalid argument"),
+             "Error: Invalid argument passed: Mock invalid argument"),
+            ("type_error", TypeError("Mock invalid type"), "Error: Invalid type for arguments: Mock invalid type"),
+            ("timeout_expired", subprocess.TimeoutExpired("some_command", 10),
+             "Error: Command timed out after 10 seconds"),
+            ("subprocess_error", subprocess.SubprocessError("Mock subprocess error"),
+             "Subprocess error occurred: Mock subprocess error"),
+        ],
+    )
+    def test_execute_other_errors(self, test_id, exception, expected_error_message, mocker):
+        # Arrange
+        mocker.patch("dedal.commands.bash_command_executor.os_name", "nt")
+        with patch("dedal.commands.bash_command_executor.subprocess.run", side_effect=exception) as mock_subprocess_run:
+            executor = BashCommandExecutor()
+            executor.add_command(MockCommand("some_command"))
+
+            # Act
+            stdout, err = executor.execute()
+
+            # Assert
+            assert stdout is None
+            assert err == expected_error_message
+            mock_subprocess_run.assert_called_once_with(['wsl', 'bash', '-c', 'some_command'], capture_output=True,
+                                                        text=True, check=True, timeout=172800)
+
+    def test_execute_no_commands(self):
+        # Arrange
+        executor = BashCommandExecutor()
+
+        # Act
+        with pytest.raises(ValueError) as except_info:
+            executor.execute()
+
+        # Assert
+        assert str(except_info.value) == "No commands to execute."
+
+    @patch("dedal.commands.bash_command_executor.subprocess.run")
+    def test_execute_happy_path_nt(self, mock_subprocess_run, mocker):
+        # Arrange
+        mocker.patch("dedal.commands.bash_command_executor.os_name", "nt")
+        executor = BashCommandExecutor()
+        executor.add_command(MockCommand("echo hello"))
+        mock_subprocess_run.return_value.stdout = "hello\n"
+
+        # Act
+        stdout, err = executor.execute()
+
+        # Assert
+        assert stdout == "hello\n"
+        assert err is None
+        assert executor.bash_command == ['wsl', 'bash', '-c']
+        mock_subprocess_run.assert_called_once_with(['wsl', 'bash', '-c', 'echo hello'], capture_output=True, text=True,
+                                                    check=True, timeout=172800)
+
+    def test_execute_unknown_os(self, mocker):
+        # Arrange
+        errors = {
+            "posix": "Error: Bash Command: ['undefined'] not found: [Errno 2] No such file or directory: 'undefined'",
+            "nt": "Error: Bash Command: ['undefined'] not found: [WinError 2] The system cannot find the file "
+                  'specified'
+        }
+        original_os = os.name
+        expected_error = errors.get(original_os, "Error: Unknown OS")
+        mocker.patch("dedal.commands.bash_command_executor.os_name")
+        executor = BashCommandExecutor()
+        executor.add_command(MockCommand("echo hello"))
+
+        # Act
+        assert executor.execute() == (None, expected_error)
+
+    def test_reset(self, mocker):
+        # Test ID: reset
+
+        # Arrange
+        executor = BashCommandExecutor()
+        mock_sequence = mocker.MagicMock()
+        executor.sequence = mock_sequence
+
+        # Act
+        executor.reset()
+
+        # Assert
+        mock_sequence.clear.assert_called_once()
diff --git a/dedal/tests/unit_tests/test_build_cache_manager.py b/dedal/tests/unit_tests/test_build_cache_manager.py
new file mode 100644
index 0000000000000000000000000000000000000000..af5690ebb2c0bc94ef9701e4bad6dde7bde61dd5
--- /dev/null
+++ b/dedal/tests/unit_tests/test_build_cache_manager.py
@@ -0,0 +1,161 @@
+#  Copyright (c) 2025
+#  License Information: To be decided!
+#
+#  File: test_build_cache_manager.py
+#  Description: Brief description of the file.
+#  Created by: Murugan, Jithu <j.murugan@fz-juelich.de>
+#  Created on: 2025-02-20
+import pytest
+from _pytest.fixtures import fixture
+
+from dedal.build_cache.BuildCacheManager import BuildCacheManager
+
+
+class TestBuildCacheManager:
+
+    @fixture(scope="function")
+    def mock_build_cache_manager(self, mocker):
+        mocker.patch("dedal.build_cache.BuildCacheManager.get_logger")
+        return BuildCacheManager("TEST_HOST", "TEST_PROJECT", "TEST_USERNAME", "TEST_PASSWORD", "TEST_VERSION")
+
+    def test_get_public_key_from_cache_success_path(self, mock_build_cache_manager, tmp_path):
+
+        # Arrange
+        build_cache_dir = tmp_path / "build_cache"
+        pgp_folder = build_cache_dir / "project" / "_pgp"
+        pgp_folder.mkdir(parents=True)
+        key_file = pgp_folder / "key.pub"
+        key_file.write_text("public key content")
+
+        # Act
+        result = mock_build_cache_manager.get_public_key_from_cache(str(build_cache_dir))
+
+        # Assert
+        assert result == str(key_file)
+
+    @pytest.mark.parametrize("test_id, num_pgp_folders, num_key_files, expected_log_message", [
+        ("more_than_one_gpg_folder", 2, 1,
+         "More than one PGP folders found in the build cache: %s, using the first one in the list: %s"),
+        ("more_than_one_key_file", 1, 2,
+         "More than one PGP key files found in the build cache: %s, using the first one in the list: %s"),
+    ])
+    def test_get_public_key_from_cache_multiple_files_or_folders(self, mock_build_cache_manager, test_id,
+                                                                 tmp_path, num_pgp_folders,
+                                                                 num_key_files, expected_log_message):
+
+        # Arrange
+        pgp_folders = []
+        key_files = []
+        build_cache_dir = tmp_path / "build_cache"
+        for i in range(num_pgp_folders):
+            pgp_folder = build_cache_dir / f"project{i}" / "_pgp"
+            pgp_folders.append(str(pgp_folder))
+            pgp_folder.mkdir(parents=True)
+            for j in range(num_key_files):
+                key_file = pgp_folder / f"key{j}.pub"
+                key_files.append(str(key_file))
+                key_file.write_text(f"public key {j} content")
+
+        # Act
+        result = mock_build_cache_manager.get_public_key_from_cache(str(build_cache_dir))
+
+        # Assert
+        # Cannot assure the order in which the OS returns the files,
+        # hence check if the result is in the expected list
+        assert result in [str(build_cache_dir / "project0" / "_pgp" / "key0.pub"),
+                          str(build_cache_dir / "project0" / "_pgp" / "key1.pub"),
+                          str(build_cache_dir / "project1" / "_pgp" / "key0.pub")]
+        assert mock_build_cache_manager._logger.warning.call_args[0][0] == expected_log_message
+        assert set(mock_build_cache_manager._logger.warning.call_args[0][1]) == set(
+            pgp_folders) if test_id == "more_than_one_gpg_folder" else set(key_files)
+        assert mock_build_cache_manager._logger.warning.call_args[0][
+                   2] in pgp_folders if test_id == "more_than_one_gpg_folder" else key_files
+
+    @pytest.mark.parametrize("build_cache_dir, expected_log_message", [
+        (None, 'Build cache directory does not exist!'),
+        ("non_existent_dir", 'Build cache directory does not exist!'),
+    ])
+    def test_get_public_key_from_cache_no_build_cache(self, mock_build_cache_manager, build_cache_dir,
+                                                      expected_log_message, tmp_path):
+
+        # Arrange
+        build_cache_dir = str(tmp_path / build_cache_dir) if build_cache_dir else None
+
+        # Act
+        result = mock_build_cache_manager.get_public_key_from_cache(build_cache_dir)
+
+        # Assert
+        assert result is None
+        mock_build_cache_manager._logger.warning.assert_called_once_with(expected_log_message)
+
+        # Assert
+        assert result is None
+        mock_build_cache_manager._logger.warning.assert_called_once_with(expected_log_message)
+
+    @pytest.mark.parametrize("build_cache_dir, expected_log_message", [
+        ("non_existent_dir", "No _pgp folder found in the build cache!"),
+    ])
+    def test_get_public_key_from_cache_no_pgp_folder(self, mock_build_cache_manager, build_cache_dir,
+                                                     expected_log_message, tmp_path):
+
+        # Arrange
+        if build_cache_dir == "non_existent_dir":
+            build_cache_dir = tmp_path / build_cache_dir
+            build_cache_dir.mkdir(parents=True)
+
+        # Act
+        result = mock_build_cache_manager.get_public_key_from_cache(build_cache_dir)
+
+        # Assert
+        assert result is None
+        mock_build_cache_manager._logger.warning.assert_called_once_with(expected_log_message)
+
+        # Assert
+        assert result is None
+        mock_build_cache_manager._logger.warning.assert_called_once_with(expected_log_message)
+
+    def test_get_public_key_from_cache_empty_pgp_folder(self, mock_build_cache_manager, tmp_path):
+
+        # Arrange
+        build_cache_dir = tmp_path / "build_cache"
+        pgp_folder = build_cache_dir / "project" / "_pgp"
+        pgp_folder.mkdir(parents=True)
+
+        # Act
+        result = mock_build_cache_manager.get_public_key_from_cache(str(build_cache_dir))
+
+        # Assert
+        assert result is None
+        mock_build_cache_manager._logger.warning.assert_called_once_with("No PGP key files found in the build cache!")
+
+    @pytest.mark.parametrize("items, expected_log_message", [
+        (["item1", "item2"], "test message item1 item2 item1"),
+        (["item1", "item2", "item3"], "test message item1 item2 item3 item1"),
+    ])
+    def test_log_warning_if_needed_multiple_items(self, mock_build_cache_manager, items, expected_log_message):
+        # Test ID: multiple_items
+
+        # Arrange
+        warn_message = "test message"
+
+        # Act
+        mock_build_cache_manager._BuildCacheManager__log_warning_if_needed(warn_message, items)
+
+        # Assert
+        mock_build_cache_manager._logger.warning.assert_called_once_with(warn_message, items, items[0])
+
+    @pytest.mark.parametrize("items", [
+        [],
+        ["item1"],
+    ])
+    def test_log_warning_if_needed_no_warning(self, mock_build_cache_manager, items):
+        # Test ID: no_warning
+
+        # Arrange
+        warn_message = "test message"
+
+        # Act
+        mock_build_cache_manager._BuildCacheManager__log_warning_if_needed(warn_message, items)
+
+        # Assert
+        mock_build_cache_manager._logger.warning.assert_not_called()
diff --git a/dedal/tests/unit_tests/test_command.py b/dedal/tests/unit_tests/test_command.py
new file mode 100644
index 0000000000000000000000000000000000000000..3c86404199262dec907c983620f7c95a7adf0bc6
--- /dev/null
+++ b/dedal/tests/unit_tests/test_command.py
@@ -0,0 +1,45 @@
+#  Copyright (c) 2025
+#  License Information: To be decided!
+#
+#  File: test_command.py
+#  Description: Brief description of the file.
+#  Created by: Murugan, Jithu <j.murugan@fz-juelich.de>
+#  Created on: 2025-02-18
+
+import pytest
+
+from dedal.commands.command import Command
+
+class ConcreteCommand(Command):
+    def __init__(self, return_value: str):
+        self._return_value = return_value
+
+    def execute(self) -> str:
+        return self._return_value
+
+
+class TestCommand:
+    def test_execute_abstract_method(self):
+        # Act
+        with pytest.raises(TypeError):
+            Command()  # type: ignore
+
+
+    @pytest.mark.parametrize(
+        "test_id, return_value",
+        [
+            ("empty_string", ""),
+            ("non_empty_string", "some_command"),
+            ("string_with_spaces", "command with spaces"),
+            ("non_ascii_chars", "αβγδ"),
+        ],
+    )
+    def test_execute_concrete_command(self, test_id, return_value):
+        # Arrange
+        command = ConcreteCommand(return_value)
+
+        # Act
+        result = command.execute()
+
+        # Assert
+        assert result == return_value
diff --git a/dedal/tests/unit_tests/test_command_enum.py b/dedal/tests/unit_tests/test_command_enum.py
new file mode 100644
index 0000000000000000000000000000000000000000..f29e2b4c707a318100fa3d90c9f62d54d8b64ebb
--- /dev/null
+++ b/dedal/tests/unit_tests/test_command_enum.py
@@ -0,0 +1,38 @@
+#  Copyright (c) 2025
+#  License Information: To be decided!
+#
+#  File: test_command_enum.py
+#  Description: Brief description of the file.
+#  Created by: Murugan, Jithu <j.murugan@fz-juelich.de>
+#  Created on: 2025-02-18
+
+import pytest
+
+from dedal.commands.command_enum import CommandEnum
+
+class TestCommandEnum:
+
+    @pytest.mark.parametrize(
+        "test_id, command_name, expected_value",
+        [
+            ("source", "SOURCE", "source"),
+            ("list_files", "LIST_FILES", "list_files"),
+            ("spack_install", "SPACK_INSTALL", "spack_install"),
+        ],
+    )
+    def test_command_enum_values(self, test_id, command_name, expected_value):
+
+        # Act
+        command = CommandEnum[command_name]
+
+        # Assert
+        assert command.value == expected_value
+        assert command.name == command_name
+
+
+    def test_command_enum_invalid_name(self):
+
+        # Act
+        with pytest.raises(KeyError):
+            CommandEnum["INVALID_COMMAND"] # type: ignore
+
diff --git a/dedal/tests/unit_tests/test_command_runner.py b/dedal/tests/unit_tests/test_command_runner.py
new file mode 100644
index 0000000000000000000000000000000000000000..ac30fa083b82a1dc8684c33dfcf91c19ea0d2a3e
--- /dev/null
+++ b/dedal/tests/unit_tests/test_command_runner.py
@@ -0,0 +1,125 @@
+#  Copyright (c) 2025
+#  License Information: To be decided!
+#
+#  File: test_command_runner.py
+#  Description: Brief description of the file.
+#  Created by: Murugan, Jithu <j.murugan@fz-juelich.de>
+#  Created on: 2025-02-18
+
+import pytest
+
+from dedal.commands.command_enum import CommandEnum
+from dedal.commands.command_runner import CommandRunner
+from dedal.commands.command_sequence import CommandSequence
+from dedal.commands.preconfigured_command_enum import PreconfiguredCommandEnum
+
+
+class MockCommandSequence(CommandSequence):
+    def __init__(self, cmd_str: str):
+        self._cmd_str = cmd_str
+
+    def execute(self) -> str:
+        return self._cmd_str
+
+
+class TestCommandRunner:
+    @pytest.fixture(scope="function")
+    def mock_command_runner(self, mocker):
+        mocker.patch("dedal.commands.command_runner.BashCommandExecutor")
+        mocker.patch("dedal.commands.command_runner.logging.getLogger")
+        return CommandRunner()
+
+    @pytest.mark.parametrize(
+        "test_id, command_str, mock_output, expected_output",
+        [
+            ("simple_command", "echo hello", "hello\n", {"success": True, "output": "hello", "error": None}),
+            ("complex_command", "ls -l /tmp", "mock_output\n",
+             {"success": True, "output": "mock_output", "error": None}),
+            ("empty_output", "ls -l /tmp", "", {"success": True, "output": None, "error": None}),
+            ("command_with_error", "failing_command", "", {"success": False, "output": None, "error": "mock_error"}),
+
+        ],
+    )
+    def test_execute_command(self, mock_command_runner, test_id, command_str, mock_output, expected_output):
+        # Arrange
+        mock_command_runner.executor.execute.return_value = (
+            mock_output, "mock_error" if "failing" in command_str else None)
+        command_sequence = MockCommandSequence(command_str)
+
+        # Act
+        result = mock_command_runner.execute_command(command_sequence)
+
+        # Assert
+        assert result == expected_output
+        mock_command_runner.executor.execute.assert_called_once()
+        mock_command_runner.executor.reset.assert_called_once()
+
+    @pytest.mark.parametrize(
+        "test_id, command_type, args, expected_result",
+        [
+            (
+                    "valid_command",
+                    PreconfiguredCommandEnum.SPACK_COMPILER_FIND,
+                    ["gcc"],
+                    {"success": True, "output": "mock_output", "error": None},
+            ),
+        ],
+    )
+    def test_run_predefined_command_success_path(self, mock_command_runner, test_id, command_type, args,
+                                                 expected_result):
+        # Arrange
+        mock_command_runner.executor.execute.return_value = ("mock_output\n", None)
+
+        # Act
+        result = mock_command_runner.run_preconfigured_command_sequence(command_type, *args)
+
+        # Assert
+        assert result == expected_result
+
+    def test_run_predefined_command_invalid_type(self, mock_command_runner):
+        # Arrange
+        invalid_command_type = "INVALID_COMMAND"  # type: ignore
+
+        # Act
+        result = mock_command_runner.run_preconfigured_command_sequence(invalid_command_type)
+
+        # Assert
+        assert result == {"success": False, "error": "Invalid command name: INVALID_COMMAND"}
+
+    @pytest.mark.parametrize(
+        "test_id, command_placeholders_map, expected_result",
+        [
+            (
+                    "single_command",
+                    {CommandEnum.LIST_FILES: {"folder_location": "/tmp"}},
+                    {"success": True, "output": "mock_output", "error": None},
+            ),
+            (
+                    "multiple_commands",
+                    {
+                        CommandEnum.LIST_FILES: {"folder_location": "/tmp"},
+                        CommandEnum.SHOW_DIRECTORY: {},
+                    },
+                    {"success": True, "output": "mock_output", "error": None},
+            ),
+            (
+                    "empty_placeholders",
+                    {CommandEnum.SHOW_DIRECTORY: {}},
+                    {"success": True, "output": "mock_output", "error": None},
+            ),
+        ],
+    )
+    def test_run_custom_command(self, mocker, mock_command_runner, test_id, command_placeholders_map, expected_result):
+        # Arrange
+        mock_command_runner.execute_command = mocker.MagicMock(return_value=expected_result)
+        mock_create_custom_command_sequence = mocker.patch(
+            "dedal.commands.command_runner.CommandSequenceFactory.create_custom_command_sequence")
+        mock_create_custom_command_sequence.return_value = MockCommandSequence("mock_command")
+
+        # Act
+        result = mock_command_runner.run_custom_command_sequence(command_placeholders_map)
+
+        # Assert
+        assert result == expected_result
+        mock_create_custom_command_sequence.assert_called_once_with(command_placeholders_map)
+        mock_command_runner.execute_command.assert_called_once_with(mock_create_custom_command_sequence.return_value)
diff --git a/dedal/tests/unit_tests/test_command_sequence.py b/dedal/tests/unit_tests/test_command_sequence.py
new file mode 100644
index 0000000000000000000000000000000000000000..e663c06b57e32630666510e0b6ed55313012fe26
--- /dev/null
+++ b/dedal/tests/unit_tests/test_command_sequence.py
@@ -0,0 +1,108 @@
+#  Copyright (c) 2025
+#  License Information: To be decided!
+#
+#  File: test_command_sequence.py
+#  Description: Brief description of the file.
+#  Created by: Murugan, Jithu <j.murugan@fz-juelich.de>
+#  Created on: 2025-02-19
+from unittest.mock import Mock
+
+import pytest
+
+from dedal.commands.command import Command
+from dedal.commands.command_sequence import CommandSequence
+
+
+class MockCommand(Command):
+    def __init__(self, cmd_str: str):
+        self._cmd_str = cmd_str
+
+    def execute(self) -> str:
+        return self._cmd_str
+
+
+class TestCommandSequence:
+
+    @pytest.mark.parametrize(
+        "test_id, commands, expected_output",
+        [
+            ("single_command", ["echo hello"], "echo hello"),
+            ("single_command_with_spaces at_beginning", [" pwd"], "pwd"),
+            ("single_command_with_spaces at end", [" pwd "], "pwd"),
+            ("multiple_commands_with_spaces", ["       echo hello", "  ls -l /tmp  "], "echo hello && ls -l /tmp"),
+            ("multiple_commands", ["echo hello", "ls -l /tmp"], "echo hello && ls -l /tmp"),
+            ("multiple_commands_with_spaces_in_between_and_end", ["echo   hello   ", "ls -l    /tmp  "],
+             "echo   hello && ls -l    /tmp"),
+            ("empty_command", [""], ""),
+            ("commands_with_spaces", ["command with spaces", "another command"],
+             "command with spaces && another command"),
+        ],
+    )
+    def test_execute_success_path(self, test_id, commands, expected_output):
+        # Arrange
+        sequence = CommandSequence()
+        for cmd in commands:
+            sequence.add_command(MockCommand(cmd))
+
+        # Act
+        result = sequence.execute()
+
+        # Assert
+        assert result == expected_output
+
+    def test_execute_no_commands(self):
+        # Arrange
+        sequence = CommandSequence()
+
+        # Act
+        result = sequence.execute()
+
+        # Assert
+        assert result == ""
+
+    @pytest.mark.parametrize(
+        "test_id, num_commands",
+        [
+            ("one_command", 1),
+            ("multiple_commands", 5),
+            ("no_commands", 0),
+        ],
+    )
+    def test_add_command_success_path(self, test_id, num_commands):
+        # Arrange
+        sequence = CommandSequence()
+        commands = [MockCommand(f"command_{i}") for i in range(num_commands)]
+
+        # Act
+        for command in commands:
+            sequence.add_command(command)
+
+        # Assert
+        assert len(sequence.commands) == num_commands
+
+    def test_add_command_invalid_type(self):
+        # Arrange
+        sequence = CommandSequence()
+        invalid_command = "not a command object"  # type: ignore
+
+        # Act
+        with pytest.raises(ValueError) as except_info:
+            sequence.add_command(invalid_command)
+
+        # Assert
+        assert str(except_info.value) == "Command must be an instance of Command"
+
+    @pytest.mark.parametrize("initial_commands", [[], [Mock(spec=Command), Mock(spec=Command)]])
+    def test_clear(self, mocker, initial_commands):
+        # Test ID: clear
+
+        # Arrange
+        sequence = CommandSequence()
+        for command in initial_commands:
+            sequence.add_command(command)
+
+        # Act
+        sequence.clear()
+
+        # Assert
+        assert len(sequence.commands) == 0
diff --git a/dedal/tests/unit_tests/test_command_sequence_builder.py b/dedal/tests/unit_tests/test_command_sequence_builder.py
new file mode 100644
index 0000000000000000000000000000000000000000..e4004255128836a4643665bf6acf338cd9497155
--- /dev/null
+++ b/dedal/tests/unit_tests/test_command_sequence_builder.py
@@ -0,0 +1,95 @@
+#  Copyright (c) 2025
+#  License Information: To be decided!
+#
+#  File: test_command_sequence_builder.py
+#  Description: Brief description of the file.
+#  Created by: Murugan, Jithu <j.murugan@fz-juelich.de>
+#  Created on: 2025-02-19
+
+from unittest.mock import Mock, patch
+
+import pytest
+
+from dedal.commands.command import Command
+from dedal.commands.command_enum import CommandEnum
+from dedal.commands.command_registry import CommandRegistry
+from dedal.commands.command_sequence import CommandSequence
+from dedal.commands.command_sequence_builder import CommandSequenceBuilder
+from dedal.commands.shell_command_factory import ShellCommandFactory
+
+
+class TestCommandSequenceBuilder:
+
+    @pytest.mark.parametrize(
+        "test_id, command_name, placeholders, expected_command",
+        [
+            ("no_placeholders", CommandEnum.SHOW_DIRECTORY, {}, ["pwd"]),
+            ("with_placeholders", CommandEnum.LIST_FILES, {"folder_location": "/tmp  "}, ["ls", "-l", "/tmp"]),
+            ("missing_placeholders", CommandEnum.FIND_IN_FILE, {"search_term": "error"},
+             ["grep", "-i", "error", "{file_path}"]),
+            ("missing_placeholders_values_1", CommandEnum.SPACK_MIRROR_ADD, {"autopush": ""},
+             ["spack", "mirror", "add", "{signed}", "{mirror_name}","{mirror_path}"]),
+            ("missing_placeholders_values_2", CommandEnum.SPACK_MIRROR_ADD, {"autopush": "", "signed": ""},
+             ["spack", "mirror", "add", "{mirror_name}", "{mirror_path}"]),
+            ("missing_placeholders_values_3", CommandEnum.SPACK_MIRROR_ADD, {"autopush": "", "signed": "", "mirror_name": "test", "mirror_path": "test_path"},
+             ["spack", "mirror", "add", "test", "test_path"]),
+            ("extra_placeholders", CommandEnum.ECHO_MESSAGE, {"message": "hello", "extra": "world"}, ["echo", "hello"]),
+        ],
+    )
+    def test_add_generic_command_success_path(self, mocker, test_id, command_name,
+                                            placeholders, expected_command):
+        # Arrange
+        mock_get_command = mocker.patch.object(CommandRegistry, "get_command")
+        mock_create_command = mocker.patch.object(ShellCommandFactory, "create_command")
+        builder = CommandSequenceBuilder()
+        mock_get_command.return_value = expected_command
+        mock_create_command.return_value = Mock(spec=Command,
+                                                execute=lambda: " ".join(expected_command) if expected_command else "")
+
+        # Act
+        builder.add_generic_command(command_name, placeholders)
+
+        # Assert
+        mock_get_command.assert_called_once_with(command_name)
+        mock_create_command.assert_called_once_with(*expected_command)
+        assert len(builder.sequence.commands) == 1
+        assert builder.sequence.commands[0].execute() == " ".join(expected_command) if expected_command else ""
+
+    def test_add_generic_command_invalid_command_type(self):
+        # Arrange
+        builder = CommandSequenceBuilder()
+        invalid_command_name = "invalid"  # type: ignore
+
+        # Act
+        with pytest.raises(ValueError) as except_info:
+            builder.add_generic_command(invalid_command_name, {})
+
+        # Assert
+        assert str(except_info.value) == "Invalid command type. Use CommandEnum."
+
+    def test_add_generic_command_unknown_command(self, mocker):
+        # Arrange
+        mock_get_command = mocker.patch.object(CommandRegistry, "get_command")
+        builder = CommandSequenceBuilder()
+        mock_get_command.return_value = None
+
+        # Act
+        with pytest.raises(ValueError) as except_info:
+            builder.add_generic_command(CommandEnum.LIST_FILES, {})
+
+        # Assert
+        assert str(except_info.value) == "Unknown command: CommandEnum.LIST_FILES"
+
+    def test_build(self):
+        # Arrange
+        builder = CommandSequenceBuilder()
+        builder.add_generic_command(CommandEnum.SHOW_DIRECTORY, {})
+
+        # Act
+        sequence = builder.build()
+
+        # Assert
+        assert isinstance(sequence, CommandSequence)
+        assert sequence.execute() == "pwd"
+        assert len(sequence.commands) == 1
+        assert len(builder.sequence.commands) == 0  # Check if the builder's sequence is reset
diff --git a/dedal/tests/unit_tests/test_command_sequence_factory.py b/dedal/tests/unit_tests/test_command_sequence_factory.py
new file mode 100644
index 0000000000000000000000000000000000000000..7048690aac222786ae5b8794c07969e8e8e91544
--- /dev/null
+++ b/dedal/tests/unit_tests/test_command_sequence_factory.py
@@ -0,0 +1,49 @@
+#  Copyright (c) 2025
+#  License Information: To be decided!
+#
+#  File: test_command_sequence_factory.py
+#  Description: Brief description of the file.
+#  Created by: Murugan, Jithu <j.murugan@fz-juelich.de>
+#  Created on: 2025-02-19
+
+from unittest.mock import Mock, patch
+
+import pytest
+
+from dedal.commands.command_enum import CommandEnum
+from dedal.commands.command_sequence import CommandSequence
+from dedal.commands.command_sequence_builder import CommandSequenceBuilder
+from dedal.commands.command_sequence_factory import CommandSequenceFactory
+
+
+class TestCommandSequenceFactory:
+
+    @pytest.mark.parametrize(
+        "test_id, command_placeholders_map, expected_calls",
+        [
+            ("single_command", {CommandEnum.SHOW_DIRECTORY: {}}, [(CommandEnum.SHOW_DIRECTORY, {})]),
+            (
+                    "multiple_commands",
+                    {CommandEnum.LIST_FILES: {"folder_location": "/tmp"}, CommandEnum.SHOW_DIRECTORY: {}},
+                    [(CommandEnum.LIST_FILES, {"folder_location": "/tmp"}), (CommandEnum.SHOW_DIRECTORY, {})],
+            ),
+            ("no_commands", {}, []),
+        ],
+    )
+    def test_create_custom_command_sequence(self, mocker, test_id, command_placeholders_map, expected_calls):
+        # Arrange
+        mock_add_generic_command = mocker.patch.object(CommandSequenceBuilder, "add_generic_command")
+        mock_builder = Mock(spec=CommandSequenceBuilder, build=Mock(return_value=CommandSequence()))
+        mock_builder.add_generic_command = mock_add_generic_command
+        mock_add_generic_command.return_value = mock_builder
+
+        with patch("dedal.commands.command_sequence_factory.CommandSequenceBuilder", return_value=mock_builder):
+            # Act
+            CommandSequenceFactory.create_custom_command_sequence(command_placeholders_map)
+
+        # Assert
+        assert mock_add_generic_command.call_count == len(expected_calls)
+        mock_add_generic_command.assert_has_calls(
+            [mocker.call(args, kwargs) for args, kwargs in expected_calls], any_order=True
+        )
+        mock_builder.build.assert_called_once()
diff --git a/dedal/tests/unit_tests/test_generic_shell_command.py b/dedal/tests/unit_tests/test_generic_shell_command.py
new file mode 100644
index 0000000000000000000000000000000000000000..7ed779aee2faf531e2472d9219551d1792aa7bf1
--- /dev/null
+++ b/dedal/tests/unit_tests/test_generic_shell_command.py
@@ -0,0 +1,63 @@
+#  Copyright (c) 2025
+#  License Information: To be decided!
+#
+#  File: test_generic_shell_command.py
+#  Description: Brief description of the file.
+#  Created by: Murugan, Jithu <j.murugan@fz-juelich.de>
+#  Created on: 2025-02-19
+
+import pytest
+
+from dedal.commands.command import Command
+from dedal.commands.generic_shell_command import GenericShellCommand
+
+class TestGenericShellCommand:
+
+    @pytest.mark.parametrize(
+        "test_id, command_name, args, expected_output",
+        [
+            ("no_args", "ls", [], "ls"),
+            ("single_arg", "echo", ["hello"], "echo hello"),
+            ("multiple_args", "grep", ["-i", "error", "file.txt"], "grep -i error file.txt"),
+            ("command_with_spaces", "  ls  ", ["-l", "/tmp"], "ls -l /tmp"),
+            ("args_with_spaces", "ls", [" -l ", " /tmp "], "ls -l /tmp"),
+            ("command_and_args_with_spaces", "  ls  ", [" -l ", " /tmp "], "ls -l /tmp"),
+            ("empty_args", "ls", [""], "ls "), # Empty arguments are preserved, but stripped
+        ],
+    )
+    def test_execute_success_path(self, test_id, command_name, args, expected_output):
+
+        # Act
+        command = GenericShellCommand(command_name, *args)
+        result = command.execute()
+
+        # Assert
+        assert result == expected_output
+
+
+    @pytest.mark.parametrize(
+        "test_id, command_name, args, expected_error",
+        [
+            ("empty_command_name", "", [], ValueError),
+            ("none_command_name", None, [], ValueError), # type: ignore
+            ("int_command_name", 123, [], ValueError), # type: ignore
+            ("invalid_arg_type", "ls", [123], ValueError), # type: ignore
+            ("mixed_arg_types", "ls", ["valid", 456], ValueError), # type: ignore
+        ],
+    )
+    def test_init_error_cases(self, test_id, command_name, args, expected_error):
+
+        # Act
+        with pytest.raises(expected_error) as except_info:
+            GenericShellCommand(command_name, *args)
+
+        # Assert
+        if expected_error is ValueError:
+            if not command_name:
+                assert str(except_info.value) == "Command name is required!"
+            elif not isinstance(command_name, str):
+                assert str(except_info.value) == "Command name must be a string!"
+            else:
+                assert str(except_info.value) == "All arguments must be strings!"
+
+
diff --git a/dedal/tests/unit_tests/test_preconfigured_command_enum.py b/dedal/tests/unit_tests/test_preconfigured_command_enum.py
new file mode 100644
index 0000000000000000000000000000000000000000..820c4d4175903e12c4cd125cf327371fa08145f4
--- /dev/null
+++ b/dedal/tests/unit_tests/test_preconfigured_command_enum.py
@@ -0,0 +1,37 @@
+#  Copyright (c) 2025
+#  License Information: To be decided!
+#
+#  File: test_preconfigured_command_enum.py
+#  Description: Brief description of the file.
+#  Created by: Murugan, Jithu <j.murugan@fz-juelich.de>
+#  Created on: 2025-02-19
+
+import pytest
+
+from dedal.commands.preconfigured_command_enum import PreconfiguredCommandEnum
+
+class TestPreconfiguredCommandEnum:
+
+    @pytest.mark.parametrize(
+        "test_id, command_name, expected_value",
+        [
+            ("spack_compiler_find", "SPACK_COMPILER_FIND", "spack_compiler_find"),
+            ("spack_compilers", "SPACK_COMPILERS", "spack_compilers"),
+            ("spack_install", "SPACK_INSTALL", "spack_install"),
+        ],
+    )
+    def test_preconfigured_command_enum_values(self, test_id, command_name, expected_value):
+
+        # Act
+        command = PreconfiguredCommandEnum[command_name]
+
+        # Assert
+        assert command.value == expected_value
+        assert command.name == command_name
+
+
+    def test_preconfigured_command_enum_invalid_name(self):
+
+        # Act
+        with pytest.raises(KeyError):
+            PreconfiguredCommandEnum["INVALID_COMMAND"] # type: ignore
diff --git a/dedal/tests/unit_tests/test_shell_command_factory.py b/dedal/tests/unit_tests/test_shell_command_factory.py
new file mode 100644
index 0000000000000000000000000000000000000000..bfd2b2db3f98d45419cf2f6e3246cf5d2d4f4c00
--- /dev/null
+++ b/dedal/tests/unit_tests/test_shell_command_factory.py
@@ -0,0 +1,61 @@
+#  Copyright (c) 2025
+#  License Information: To be decided!
+#
+#  File: test_shell_command_factory.py
+#  Description: Brief description of the file.
+#  Created by: Murugan, Jithu <j.murugan@fz-juelich.de>
+#  Created on: 2025-02-19
+
+import pytest
+
+from dedal.commands.generic_shell_command import GenericShellCommand
+from dedal.commands.shell_command_factory import ShellCommandFactory
+
+
+class TestShellCommandFactory:
+
+    @pytest.mark.parametrize(
+        "test_id, command_name, args",
+        [
+            ("no_args", "ls", []),
+            ("single_arg", "echo", ["hello"]),
+            ("multiple_args", "grep", ["-i", "error", "file.txt"]),
+            ("command_with_spaces", "  ls  ", ["-l", "/tmp"]),
+            ("args_with_spaces", "ls", [" -l ", " /tmp "]),
+            ("command_and_args_with_spaces", "  ls  ", [" -l ", " /tmp "]),
+        ],
+    )
+    def test_create_command_happy_path(self, test_id, command_name, args):
+
+        # Act
+        command = ShellCommandFactory.create_command(command_name, *args)
+
+        # Assert
+        assert isinstance(command, GenericShellCommand)
+        assert command.command_name == command_name.strip()
+        assert command.args == tuple(map(str.strip, args))
+
+    @pytest.mark.parametrize(
+        "test_id, command_name, args, expected_error",
+        [
+            ("empty_command_name", "", [], ValueError),
+            ("none_command_name", None, [], ValueError),  # type: ignore
+            ("int_command_name", 123, [], ValueError),  # type: ignore
+            ("invalid_arg_type", "ls", [123], ValueError),  # type: ignore
+            ("mixed_arg_types", "ls", ["valid", 456], ValueError),  # type: ignore
+        ],
+    )
+    def test_create_command_error_cases(self, test_id, command_name, args, expected_error):
+
+        # Act
+        with pytest.raises(expected_error) as except_info:
+            ShellCommandFactory.create_command(command_name, *args)
+
+        # Assert
+        if expected_error is ValueError:
+            if not command_name:
+                assert str(except_info.value) == "Command name is required!"
+            elif not isinstance(command_name, str):
+                assert str(except_info.value) == "Command name must be a string!"
+            else:
+                assert str(except_info.value) == "All arguments must be strings!"
diff --git a/dedal/tests/unit_tests/test_spack_command_sequence_factory.py b/dedal/tests/unit_tests/test_spack_command_sequence_factory.py
new file mode 100644
index 0000000000000000000000000000000000000000..0ffebcf3034236b5940ffbd529d5cb23ed9dc3c4
--- /dev/null
+++ b/dedal/tests/unit_tests/test_spack_command_sequence_factory.py
@@ -0,0 +1,159 @@
+#  Copyright (c) 2025
+#  License Information: To be decided!
+#
+#  File: test_spack_command_sequence_factory.py
+#  Description: Brief description of the file.
+#  Created by: Murugan, Jithu <j.murugan@fz-juelich.de>
+#  Created on: 2025-02-19
+
+import pytest
+
+from dedal.commands.command_enum import CommandEnum
+from dedal.commands.command_sequence import CommandSequence
+from dedal.commands.command_sequence_builder import CommandSequenceBuilder
+from dedal.commands.spack_command_sequence_factory import SpackCommandSequenceFactory
+
+
+class TestSpackCommandSequenceFactory:
+
+    @pytest.mark.parametrize(
+        "test_id, spack_setup_script",
+        [
+            ("with_setup_script", "/path/to/setup.sh"),
+            ("empty_setup_script", "")],
+    )
+    def test_create_spack_enabled_command_sequence_builder(self, test_id, spack_setup_script):
+        # Act
+        builder = SpackCommandSequenceFactory.create_spack_enabled_command_sequence_builder(spack_setup_script)
+
+        # Assert
+        assert isinstance(builder, CommandSequenceBuilder)
+        assert len(builder.sequence.commands) == 1
+        assert builder.sequence.commands[0].command_name == "source"
+        if spack_setup_script:
+            assert builder.sequence.commands[0].args == ("/path/to/setup.sh",)
+        else:
+            assert len(builder.sequence.commands[0].args) == 0
+
+    @pytest.mark.parametrize(
+        "test_id, method_name, args, kwargs, expected_command, expected_args",
+        [
+            (
+                    "spack_compilers", "create_spack_compilers_command_sequence", ["/path/to/setup.sh"], {}, "spack",
+                    ["compilers"]
+            ),
+            (
+                    "spack_compiler_list", "create_spack_compiler_list_command_sequence", ["/path/to/setup.sh"], {},
+                    "spack", ["compiler", "list"]
+            ),
+            (
+                    "spack_install_with_version", "create_spack_install_package_command_sequence",
+                    ["/path/to/setup.sh", "package", "1.0"], {}, "spack", ["install", "package@1.0"]
+            ),
+            (
+                    "spack_install_no_version", "create_spack_install_package_command_sequence",
+                    ["/path/to/setup.sh", "package", None], {}, "spack", ["install", "package"]
+            ),
+            (
+                    "spack_install_no_package", "create_spack_install_package_command_sequence",
+                    ["/path/to/setup.sh", None, "1.0"], {}, "spack", ["install"]
+            ),
+            (
+                    "spack_compiler_info", "create_spack_compiler_info_command_sequence",
+                    ["/path/to/setup.sh", "gcc", "10.2"], {}, "spack", ["compiler", "info", "gcc@10.2"]
+            ),
+        ],
+    )
+    def test_create_command_sequence(self, test_id, method_name, args, kwargs, expected_command, expected_args):
+        # Arrange
+        method = getattr(SpackCommandSequenceFactory, method_name)
+
+        # Act
+        sequence = method(*args, **kwargs)
+
+        # Assert
+        assert isinstance(sequence, CommandSequence)
+        assert len(sequence.commands) == 2
+        assert sequence.commands[1].command_name == expected_command
+        assert sequence.commands[1].args == tuple(expected_args)
+
+    @pytest.mark.parametrize(
+        "test_id, env_name, mirror_name, mirror_path, autopush, signed, expected_length, expected_autopush, expected_signed, expected_output",
+        [
+            ("no_env", "", "mymirror", "/path/to/mirror", False, False, 2, "", "",
+             ("mirror", "add", "mymirror", "/path/to/mirror")),
+            ("with_env", "myenv", "mymirror", "/path/to/mirror", True, True, 3, "--autopush", "--signed",
+             ("mirror", "add", "--autopush", "--signed", "mymirror", "/path/to/mirror")),
+        ],
+    )
+    def test_create_spack_mirror_add_command_sequence(self, test_id, env_name, mirror_name, mirror_path, autopush,
+                                                      signed, expected_length, expected_autopush, expected_signed,
+                                                      expected_output):
+        # Arrange
+        spack_setup_script = "/path/to/setup.sh"
+
+        # Act
+        sequence = SpackCommandSequenceFactory.create_spack_mirror_add_command_sequence(
+            spack_setup_script, env_name, mirror_name, mirror_path, autopush, signed
+        )
+
+        # Assert
+        assert isinstance(sequence, CommandSequence)
+        assert len(sequence.commands) == expected_length
+        assert sequence.commands[-1].command_name == "spack"
+        assert sequence.commands[-1].args == expected_output
+
+    @pytest.mark.parametrize(
+        "test_id, package_name, version, expected_package_arg",
+        [
+            ("with_package_and_version", "mypackage", "1.2.3", "mypackage@1.2.3"),
+            ("only_package_name", "mypackage", None, "mypackage"),
+            ("no_package", None, "1.2.3", ""),
+        ],
+    )
+    def test_create_spack_post_install_find_command_sequence(self, test_id, package_name, version,
+                                                             expected_package_arg):
+        # Arrange
+        spack_setup_script = "/path/to/setup.sh"
+        env_path = "/path/to/env"
+
+        # Act
+        sequence = SpackCommandSequenceFactory.create_spack_post_install_find_command_sequence(
+            spack_setup_script, env_path, package_name, version
+        )
+
+        # Assert
+        assert isinstance(sequence, CommandSequence)
+        assert len(sequence.commands) == 3
+        assert sequence.commands[1].command_name == "spack"
+        assert sequence.commands[1].args == ("env", "activate", "-p", env_path)
+        assert sequence.commands[2].command_name == "spack"
+        assert sequence.commands[2].args == ("find", expected_package_arg) if expected_package_arg else ("find",)
+
+    @pytest.mark.parametrize("spack_setup_script, public_key_path", [
+        ("/path/to/setup-env.sh", "key.gpg"),
+        ("./setup-env.sh", "path/to/key.gpg"),
+    ])
+    def test_create_spack_gpg_trust_command_sequence(self, mocker, spack_setup_script, public_key_path):
+        # Test ID: create_spack_gpg_trust_command_sequence
+
+        # Arrange
+        mock_command_sequence_builder = mocker.MagicMock()
+        mocker.patch.object(SpackCommandSequenceFactory, "create_spack_enabled_command_sequence_builder",
+                            return_value=mock_command_sequence_builder)
+
+        mock_command_sequence = mocker.MagicMock()
+        mock_command_sequence_builder.add_generic_command.return_value = mock_command_sequence_builder
+        mock_command_sequence_builder.build.return_value = mock_command_sequence
+
+        # Act
+        result = SpackCommandSequenceFactory.create_spack_gpg_trust_command_sequence(spack_setup_script,
+                                                                                     public_key_path)
+
+        # Assert
+        assert result == mock_command_sequence
+        SpackCommandSequenceFactory.create_spack_enabled_command_sequence_builder.assert_called_once_with(
+            spack_setup_script)
+        mock_command_sequence_builder.add_generic_command.assert_called_once_with(CommandEnum.SPACK_GPG_TRUST,
+                                                                                  {"public_key_path": public_key_path})
+        mock_command_sequence_builder.build.assert_called_once()
diff --git a/dedal/tests/unit_tests/test_spack_operation.py b/dedal/tests/unit_tests/test_spack_operation.py
new file mode 100644
index 0000000000000000000000000000000000000000..f053459c04e2f8f10206d570cbf3857f1ec1de3c
--- /dev/null
+++ b/dedal/tests/unit_tests/test_spack_operation.py
@@ -0,0 +1,209 @@
+#  Copyright (c) 2025
+#  License Information: To be decided!
+#
+#  File: test_spack_operation.py
+#  Description: Brief description of the file.
+#  Created by: Murugan, Jithu <j.murugan@fz-juelich.de>
+#  Created on: 2025-02-19
+
+import pytest
+from _pytest.fixtures import fixture
+
+from dedal.build_cache.BuildCacheManager import BuildCacheManager
+from dedal.commands.preconfigured_command_enum import PreconfiguredCommandEnum
+from dedal.error_handling.exceptions import NoSpackEnvironmentException
+from dedal.spack_factory.SpackOperation import SpackOperation
+
+
+class TestSpackOperationAddMirrorWithComposite:
+
+    @fixture
+    def mock_spack_operation(self, mocker):
+        mocker.resetall()
+        mocker.patch("dedal.spack_factory.SpackOperation.CommandRunner")
+        mocker.patch("dedal.spack_factory.SpackOperation.get_logger")
+        mock_object = SpackOperation()
+        mock_object.logger = mocker.MagicMock()
+        return mock_object
+
+    @pytest.mark.parametrize(
+        "test_id, global_mirror, env_path, mirror_name, mirror_path, autopush, signed, expected_result, expected_env_arg",
+        [
+            ("global_mirror", True, None, "global_mirror", "/path/to/global/mirror", False, False, True, ""),
+            ("local_mirror", False, "/path/to/env", "local_mirror", "/path/to/local/mirror", True, True, True,
+             "/path/to/env"),
+        ],
+    )
+    def test_add_mirror_with_composite_success_path(self, mock_spack_operation, test_id, global_mirror, env_path,
+                                                    mirror_name, mirror_path, autopush, signed, expected_result,
+                                                    expected_env_arg):
+        # Arrange
+        mock_spack_operation.spack_setup_script = "setup.sh"
+        mock_spack_operation.env_path = env_path
+        mock_spack_operation.command_runner.run_preconfigured_command_sequence.return_value = {"success": True,
+                                                                                               "error": None,
+                                                                                               "output": None}
+
+        # Act
+        result = mock_spack_operation.add_mirror(mirror_name, mirror_path, signed, autopush,
+                                                 global_mirror)
+
+        # Assert
+        assert result == expected_result
+        mock_spack_operation.command_runner.run_preconfigured_command_sequence.assert_called_once_with(
+            PreconfiguredCommandEnum.SPACK_MIRROR_ADD,
+            "setup.sh",
+            expected_env_arg,
+            mirror_name,
+            mirror_path,
+            autopush,
+            signed,
+        )
+        mock_spack_operation.logger.info.assert_called_once_with("Added mirror %s", mirror_name)
+
+    @pytest.mark.parametrize(
+        "test_id, mirror_name, mirror_path, expected_error",
+        [
+            ("missing_mirror_name", "", "/path/to/mirror", ValueError),
+            ("missing_mirror_path", "mirror_name", "", ValueError),
+            ("missing_both", "", "", ValueError),
+        ],
+    )
+    def test_add_mirror_with_composite_missing_args(self, mock_spack_operation, test_id, mirror_name, mirror_path,
+                                                    expected_error):
+        # Arrange
+        mock_spack_operation.spack_setup_script = "setup.sh"
+
+        # Act
+        with pytest.raises(expected_error) as except_info:
+            mock_spack_operation.add_mirror(mirror_name, mirror_path)
+
+        # Assert
+        assert str(except_info.value) == "mirror_name and mirror_path are required"
+
+    def test_add_mirror_with_composite_no_env(self, mock_spack_operation):
+        # Arrange
+        mock_spack_operation.spack_setup_script = "setup.sh"
+        mock_spack_operation.env_path = None
+
+        # Act
+        with pytest.raises(NoSpackEnvironmentException) as except_info:
+            mock_spack_operation.add_mirror("mirror_name", "/path/to/mirror")
+
+        # Assert
+        assert str(except_info.value) == "No spack environment defined"
+
+    def test_add_mirror_with_composite_command_failure(self, mock_spack_operation):
+        # Arrange
+        mock_spack_operation.spack_setup_script = "setup.sh"
+        mock_spack_operation.env_path = "/path/to/env"
+        mock_spack_operation.command_runner.run_preconfigured_command_sequence.return_value = {"success": False,
+                                                                                               "error": "Error: Command failed with exit code 1, Error Output: ==> Error: Mirror with name mirror_name already exists.",
+                                                                                               "output": None}
+
+        # Act
+        result = mock_spack_operation.add_mirror("mirror_name", "/path/to/mirror")
+
+        # Assert
+        assert result is False
+        mock_spack_operation.command_runner.run_preconfigured_command_sequence.assert_called_once_with(
+            PreconfiguredCommandEnum.SPACK_MIRROR_ADD,
+            "setup.sh",
+            "/path/to/env",
+            "mirror_name",
+            "/path/to/mirror",
+            False,
+            False
+        )
+        mock_spack_operation.logger.error.assert_called_once_with('Failed to add mirror %s, reason: %s, output: %s',
+                                                                  'mirror_name',
+                                                                  'Error: Command failed with exit code 1, Error Output: ==> Error: Mirror with '
+                                                                  'name mirror_name already exists.',
+                                                                  None)
+
+    @pytest.mark.parametrize("result, expected_log_message, expected_return_value", [
+        ({"success": True, "output": "test output"}, "test info message", True),
+        ({"success": True, "error": "test error", "output": "test output"}, "test info message", True),
+    ])
+    def test_handle_result_success(self, mock_spack_operation, result, expected_log_message, expected_return_value):
+        # Test ID: success
+
+        # Arrange
+        error_msg = "test error message"
+        error_msg_args = ("arg1", "arg2")
+        info_msg = "test info message"
+        info_msg_args = ("arg3", "arg4")
+
+        # Act
+        return_value = mock_spack_operation.handle_result(result, error_msg, error_msg_args, info_msg, info_msg_args)
+
+        # Assert
+        assert return_value == expected_return_value
+        mock_spack_operation.logger.info.assert_called_once_with(info_msg, *info_msg_args)
+        mock_spack_operation.logger.error.assert_not_called()
+
+    @pytest.mark.parametrize("result, expected_log_message", [
+        ({"success": False, "error": "test error", "output": "test output"},
+         "test error message arg1 arg2 test error test output"),
+        ({"success": False, "error": None, "output": None}, "test error message arg1 arg2 None None"),
+    ])
+    def test_handle_result_failure(self, mock_spack_operation, result, expected_log_message):
+        # Test ID: failure
+
+        # Arrange
+        error_msg = "test error message"
+        error_msg_args = ("arg1", "arg2")
+        info_msg = "test info message"
+        info_msg_args = ("arg3", "arg4")
+
+        # Act
+        return_value = mock_spack_operation.handle_result(result, error_msg, error_msg_args, info_msg, info_msg_args)
+
+        # Assert
+        assert return_value is False
+        mock_spack_operation.logger.error.assert_called_once_with(error_msg, *error_msg_args, result["error"],
+                                                                  result["output"])
+        mock_spack_operation.logger.info.assert_not_called()
+
+    @pytest.mark.parametrize("public_key_path, result_success, expected_log_message", [
+        ("test_key.gpg", True, ('Added public key %s as trusted', 'test_key.gpg')),
+        ("test_key.gpg", False,
+         ('Failed to add public key %s as trusted, reason: %s, output: %s',
+          'test_key.gpg',
+          'test_error',
+          'test_output')),
+    ])
+    def test_trust_gpg_key(self, mock_spack_operation, public_key_path, result_success, expected_log_message):
+        # Test ID: trust_gpg_key
+
+        # Arrange
+        mock_result = {"success": result_success, "error": "test_error", "output": "test_output"}
+        mock_spack_operation.command_runner.run_preconfigured_command_sequence.return_value = mock_result
+
+        # Act
+        if result_success:
+            result = mock_spack_operation.trust_gpg_key(public_key_path)
+        else:
+            result = mock_spack_operation.trust_gpg_key(public_key_path)
+
+        # Assert
+        if result_success:
+            assert result is True
+            mock_spack_operation.logger.info.assert_called_once_with(*expected_log_message)
+        else:
+            assert result is False
+            mock_spack_operation.logger.error.assert_called_once_with(*expected_log_message)
+
+        mock_spack_operation.command_runner.run_preconfigured_command_sequence.assert_called_once_with(
+            PreconfiguredCommandEnum.SPACK_GPG_TRUST,
+            mock_spack_operation.spack_setup_script,
+            public_key_path
+        )
+
+    def test_trust_gpg_key_empty_path(self, mock_spack_operation):
+        # Test ID: empty_path
+
+        # Act & Assert
+        with pytest.raises(ValueError) as except_info:
+            mock_spack_operation.trust_gpg_key("")
+        assert str(except_info.value) == "public_key_path is required"
diff --git a/dedal/tests/unit_tests/test_spack_operation_use_cache.py b/dedal/tests/unit_tests/test_spack_operation_use_cache.py
new file mode 100644
index 0000000000000000000000000000000000000000..fe5d9da3d9831990266b4b380837b246724d2489
--- /dev/null
+++ b/dedal/tests/unit_tests/test_spack_operation_use_cache.py
@@ -0,0 +1,89 @@
+#  Copyright (c) 2025
+#  License Information: To be decided!
+#
+#  File: test_spack_operation_use_cache.py
+#  Description: Brief description of the file.
+#  Created by: Murugan, Jithu <j.murugan@fz-juelich.de>
+#  Created on: 2025-02-20
+from pathlib import Path
+
+import pytest
+
+from dedal.error_handling.exceptions import NoSpackEnvironmentException
+from dedal.spack_factory.SpackOperationUseCache import SpackOperationUseCache
+
+
+@pytest.fixture
+def spack_operation_use_cache_mock(mocker):
+    super_mock = mocker.patch("dedal.spack_factory.SpackOperationUseCache.super")
+    super_mock.return_value.setup_spack_env = mocker.MagicMock()
+    mocker.patch("dedal.spack_factory.SpackOperationUseCache.BuildCacheManager")
+    mock_spack_operation_use_cache = SpackOperationUseCache()
+    mock_spack_operation_use_cache.build_cache = mocker.MagicMock()
+    mock_spack_operation_use_cache.spack_config = mocker.MagicMock()
+    mock_spack_operation_use_cache.spack_config.buildcache_dir = Path("path/to/buildcache")
+    mock_spack_operation_use_cache.logger = mocker.MagicMock()
+    return mock_spack_operation_use_cache
+
+
+class TestSpackOperationUseCache:
+
+    @pytest.mark.parametrize("test_id, signed, key_path", [
+        ("key_path_exists", True, "path/to/key.gpg"),
+        ("key_path_does_not_exist", False, None)])
+    def test_setup_spack_env(self, mocker, spack_operation_use_cache_mock, test_id, signed, key_path):
+        # Test ID: setup_spack_env_success
+        super_mock = mocker.patch("dedal.spack_factory.SpackOperationUseCache.super")
+        spack_operation_use_cache_mock.trust_gpg_key = mocker.MagicMock()
+        spack_operation_use_cache_mock.add_mirror = mocker.MagicMock()
+
+        # Arrange
+        spack_operation_use_cache_mock.build_cache.get_public_key_from_cache.return_value = key_path
+        spack_operation_use_cache_mock.trust_gpg_key.return_value = signed
+        spack_operation_use_cache_mock.add_mirror.return_value = None
+
+        # Act
+        spack_operation_use_cache_mock.setup_spack_env()
+
+        # Assert
+        spack_operation_use_cache_mock.build_cache.download.assert_called_once_with(
+            spack_operation_use_cache_mock.spack_config.buildcache_dir)
+        spack_operation_use_cache_mock.build_cache.get_public_key_from_cache.assert_called_once_with(
+            str(spack_operation_use_cache_mock.spack_config.buildcache_dir))
+
+        if key_path:
+            spack_operation_use_cache_mock.trust_gpg_key.assert_called_once_with(key_path)
+        else:
+            spack_operation_use_cache_mock.trust_gpg_key.assert_not_called()
+
+        if not signed:
+            spack_operation_use_cache_mock.logger.warning.assert_called_once_with(
+                "Public key not found in cache or failed to trust pgp keys!")
+
+        spack_operation_use_cache_mock.add_mirror.assert_called_once_with(
+            'local_cache',
+            str(spack_operation_use_cache_mock.spack_config.buildcache_dir),
+            signed=signed,
+            autopush=True,
+            global_mirror=False
+        )
+        super_mock.return_value.setup_spack_env.assert_called_once()  # call original method
+
+    @pytest.mark.parametrize("exception_type", [ValueError, NoSpackEnvironmentException])
+    def test_setup_spack_env_exceptions(self, mocker, spack_operation_use_cache_mock, exception_type):
+        # Test ID: setup_spack_env_exceptions
+        spack_operation_use_cache_mock.trust_gpg_key = mocker.MagicMock()
+        spack_operation_use_cache_mock.add_mirror = mocker.MagicMock()
+
+        # Arrange
+        spack_operation_use_cache_mock.build_cache.get_public_key_from_cache.return_value = "path/to/key.gpg"
+        spack_operation_use_cache_mock.trust_gpg_key.return_value = True
+        exception = exception_type("test exception")
+        spack_operation_use_cache_mock.add_mirror.side_effect = exception
+
+        # Act & Assert
+        with pytest.raises(exception_type):
+            spack_operation_use_cache_mock.setup_spack_env()
+
+        spack_operation_use_cache_mock.logger.error.assert_called_once_with("Error adding buildcache mirror: %s",
+                                                                            exception)
diff --git a/dedal/utils/bootstrap.sh b/dedal/utils/bootstrap.sh
index d103e44055db47f958114bc8092c7cee09f262c2..9cd2e1e11b4ab82fd301cc86179de7ba373f5d03 100644
--- a/dedal/utils/bootstrap.sh
+++ b/dedal/utils/bootstrap.sh
@@ -1,4 +1,4 @@
-# Minimal prerequisites for installing the dedal library
+# Minimal prerequisites for installing the esd_library
 # pip must be installed on the OS
 echo "Bootstrapping..."
 set -euo pipefail
diff --git a/pyproject.toml b/pyproject.toml
index aad18fa55c29b67f30ba7464f2b180a8256d479d..c7ea27622c32e267ecf0559f1f457656a2f60216 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -6,19 +6,17 @@ build-backend = "setuptools.build_meta"
 name = "dedal"
 version = "0.1.0"
 authors = [
-    {name = "Eric Müller", email = "mueller@kip.uni-heidelberg.de"},
-    {name = "Adrian Ciu", email = "adrian.ciu@codemart.ro"},
+    { name = "Eric Müller", email = "mueller@kip.uni-heidelberg.de" },
+    { name = "Adrian Ciu", email = "adrian.ciu@codemart.ro" },
+    { name = "Jithu Murugan", email = "j.murugan@fz-juelich.de" }
 ]
-description = "This package provides all the necessary tools to create an Ebrains Software Distribution environment"
+description = "This package includes all the essential tools required to set up an EBRAINS Software Distribution environment."
 readme = "README.md"
 requires-python = ">=3.10"
 dependencies = [
     "oras",
     "spack",
     "ruamel.yaml",
-    "pytest",
-    "pytest-mock",
-    "pytest-ordering",
     "click",
     "jsonpickle",
 ]
@@ -27,4 +25,8 @@ dependencies = [
 dedal = "dedal.cli.spack_manager_api:cli"
 
 [tool.setuptools.data-files]
-"dedal" = ["dedal/logger/logging.conf"]
\ No newline at end of file
+"dedal" = ["dedal/logger/logging.conf"]
+
+[project.optional-dependencies]
+test = ["pytest", "pytest-mock", "pytest-ordering", "coverage"]
+dev = ["mypy", "pylint", "black", "flake8"]
\ No newline at end of file