diff --git a/README.md b/README.md
index a7a858f12..97dd322b0 100644
--- a/README.md
+++ b/README.md
@@ -131,6 +131,10 @@ features. We currently support each release for a window of 6 months.
+## Datasets
+
+A dataset of 6400 intrusion traces can be found [here](https://zenodo.org/records/10234379).
+
## Maintainer
diff --git a/simulation-system/libs/csle-common/tests/test_grpc_util.py b/simulation-system/libs/csle-common/tests/test_grpc_util.py
new file mode 100644
index 000000000..b9b885f57
--- /dev/null
+++ b/simulation-system/libs/csle-common/tests/test_grpc_util.py
@@ -0,0 +1,38 @@
+import grpc
+from unittest.mock import patch, MagicMock
+from csle_common.util.grpc_util import GrpcUtil
+
+
+class TestGrpcUtilSuite:
+ """
+ Test suite for grpc_util
+ """
+
+ @patch("grpc.channel_ready_future")
+ def test_grpc_server_on(self, mock_channel_ready_future) -> None:
+ """
+ Test utility function to test if a given gRPC channel is working or not
+
+ :param mock_channel_ready_future: mock_channel_ready_future
+ :return: None
+ """
+ mock_future = MagicMock()
+ mock_channel_ready_future.return_value = mock_future
+ result = GrpcUtil.grpc_server_on(mock_channel_ready_future)
+ mock_future.result.assert_called()
+ assert result
+
+ @patch("grpc.channel_ready_future")
+ def test_grpc_server_on_timeout(self, mock_channel_ready_future) -> None:
+ """
+ Test utility function to test if a given gRPC channel is not working
+
+ :param mock_channel_ready_future: mock_channel_ready_future
+ :return: None
+ """
+ mock_future = MagicMock()
+ mock_future.result.side_effect = grpc.FutureTimeoutError()
+ mock_channel_ready_future.return_value = mock_future
+ result = GrpcUtil.grpc_server_on(mock_channel_ready_future)
+ mock_future.result.assert_called()
+ assert not result
diff --git a/simulation-system/libs/csle-common/tests/test_import_util.py b/simulation-system/libs/csle-common/tests/test_import_util.py
new file mode 100644
index 000000000..0cb158cf9
--- /dev/null
+++ b/simulation-system/libs/csle-common/tests/test_import_util.py
@@ -0,0 +1,64 @@
+from unittest.mock import patch, MagicMock
+from csle_common.util.import_util import ImportUtil
+
+
+class TestImportUtilSuite:
+ """
+ Test suite for import_util
+ """
+
+ @patch("os.path.exists")
+ @patch("csle_common.dao.system_identification.emulation_statistics.EmulationStatistics.from_json_file")
+ @patch("csle_common.metastore.metastore_facade.MetastoreFacade.save_emulation_statistic")
+ def test_import_emulation_statistics_from_disk_json(
+ self, mock_save_emulation_statistic, mock_from_json_file, mock_path_exists
+ ) -> None:
+ """
+ Test the method that imports emulation statistics from disk to the metastore
+
+ :param mock_save_emulation_statistic: mock_save_emulation_statistic
+ :param mock_from_json_file: mock_from_json_file
+ :param mock_path_exists: mock_path_exists
+
+ :return: None
+ """
+ mock_path_exists.return_value = True
+ mock_statistics = MagicMock()
+ mock_from_json_file.return_value = mock_statistics
+ input_file = "file.json"
+ emulation_name = "test_emulation"
+ ImportUtil.import_emulation_statistics_from_disk_json(input_file=input_file, emulation_name=emulation_name)
+
+ mock_path_exists.assert_called()
+ mock_from_json_file.assert_called_once_with(input_file)
+ assert mock_statistics.emulation_name == emulation_name
+ mock_save_emulation_statistic.assert_called()
+
+ @patch("os.path.exists")
+ @patch("csle_common.dao.emulation_config.emulation_trace.EmulationTrace.load_traces_from_disk")
+ @patch("csle_common.metastore.metastore_facade.MetastoreFacade.save_emulation_trace")
+ def test_import_emulation_traces_from_disk_json(
+ self, mock_save_emulation_trace, mock_load_traces_from_disk, mock_path_exists
+ ) -> None:
+ """
+ Test the method that imports emulation traces from disk to the metastore
+
+ :param mock_save_emulation_trace: mock_save_emulation_trac
+ :param mock_load_traces_from_disk: mock_load_traces_from_disk
+ :param mock_path_exists: mock_path_exists
+
+ :return: None
+ """
+ mock_path_exists.return_value = True
+ mock_trace_1 = MagicMock()
+ mock_trace_2 = MagicMock()
+ mock_load_traces_from_disk.return_value = [mock_trace_1, mock_trace_2]
+ input_file = "file.json"
+ emulation_name = "test_emulation"
+ ImportUtil.import_emulation_traces_from_disk_json(input_file=input_file, emulation_name=emulation_name)
+
+ mock_path_exists.assert_called()
+ mock_load_traces_from_disk.assert_called()
+ assert mock_trace_1.emulation_name == emulation_name
+ assert mock_trace_2.emulation_name == emulation_name
+ assert mock_save_emulation_trace.call_count == 2
diff --git a/simulation-system/libs/csle-common/tests/test_management_util.py b/simulation-system/libs/csle-common/tests/test_management_util.py
new file mode 100644
index 000000000..1815e3b62
--- /dev/null
+++ b/simulation-system/libs/csle-common/tests/test_management_util.py
@@ -0,0 +1,80 @@
+import csle_common.constants.constants as constants
+from csle_common.util.management_util import ManagementUtil
+from unittest.mock import patch
+
+class TestManagementUtilSuite:
+ """
+ Test suite for management util
+ """
+
+ @patch("csle_common.metastore.metastore_facade.MetastoreFacade.list_management_users")
+ @patch("bcrypt.gensalt")
+ @patch("bcrypt.hashpw")
+ @patch("csle_common.metastore.metastore_facade.MetastoreFacade.save_management_user")
+ def test_create_default_management_admin_account(
+ self, mock_save_management_user, mock_hashpw, mock_gensalt, mock_list_management_users
+ ) -> None:
+ """
+ Test the method that creates the default management admin account
+
+ :param mock_save_management_user: mock_save_management_user
+ :param mock_hashpw: mock_hashpw
+ :param mock_gensalt: mock_gensalt
+ :param mock_list_management_users: mock_list_management_users
+
+ :return: None
+ """
+ mock_list_management_users.return_value = []
+ mock_salt = b"salt"
+ mock_gensalt.return_value = mock_salt
+ mock_hash = b"hashed_password"
+ mock_hashpw.return_value = mock_hash
+
+ constants.CSLE_ADMIN.MANAGEMENT_USER = "admin"
+ constants.CSLE_ADMIN.MANAGEMENT_PW = "password"
+ constants.CSLE_ADMIN.MANAGEMENT_FIRST_NAME = "first"
+ constants.CSLE_ADMIN.MANAGEMENT_LAST_NAME = "last"
+ constants.CSLE_ADMIN.MANAGEMENT_ORGANIZATION = "organization"
+ constants.CSLE_ADMIN.MANAGEMENT_EMAIL = "admin@email.com"
+
+ ManagementUtil.create_default_management_admin_account()
+ mock_list_management_users.assert_called_once()
+ mock_gensalt.assert_called_once()
+ mock_hashpw.assert_called_once_with(constants.CSLE_ADMIN.MANAGEMENT_PW.encode("utf-8"), mock_salt)
+ mock_save_management_user.assert_called_once()
+
+ @patch("csle_common.metastore.metastore_facade.MetastoreFacade.list_management_users")
+ @patch("bcrypt.gensalt")
+ @patch("bcrypt.hashpw")
+ @patch("csle_common.metastore.metastore_facade.MetastoreFacade.save_management_user")
+ def test_create_default_management_guest_account(
+ self, mock_save_management_user, mock_hashpw, mock_gensalt, mock_list_management_users
+ ) -> None:
+ """
+ Test the method that creates the default management guest account
+
+ :param mock_save_management_user: mock_save_management_user
+ :param mock_hashpw: mock_hashpw
+ :param mock_gensalt: mock_gensalt
+ :param mock_list_management_users: mock_list_management_users
+
+ :return: None
+ """
+ mock_list_management_users.return_value = []
+ mock_salt = b"salt"
+ mock_gensalt.return_value = mock_salt
+ mock_hash = b"hashed_password"
+ mock_hashpw.return_value = mock_hash
+
+ constants.CSLE_GUEST.MANAGEMENT_USER = "user"
+ constants.CSLE_GUEST.MANAGEMENT_PW = "password"
+ constants.CSLE_GUEST.MANAGEMENT_FIRST_NAME = "guest_first"
+ constants.CSLE_GUEST.MANAGEMENT_LAST_NAME = "guest_last"
+ constants.CSLE_GUEST.MANAGEMENT_ORGANIZATION = "guest_organization"
+ constants.CSLE_GUEST.MANAGEMENT_EMAIL = "guest@email.com"
+
+ ManagementUtil.create_default_management_guest_account()
+ mock_list_management_users.assert_called_once()
+ mock_gensalt.assert_called_once()
+ mock_hashpw.assert_called_once_with(constants.CSLE_GUEST.MANAGEMENT_PW.encode("utf-8"), mock_salt)
+ mock_save_management_user.assert_called_once()
diff --git a/simulation-system/libs/csle-common/tests/test_multiprocessing_util.py b/simulation-system/libs/csle-common/tests/test_multiprocessing_util.py
new file mode 100644
index 000000000..09664c081
--- /dev/null
+++ b/simulation-system/libs/csle-common/tests/test_multiprocessing_util.py
@@ -0,0 +1,43 @@
+from unittest.mock import patch
+from csle_common.util.multiprocessing_util import NoDaemonProcess
+from csle_common.util.multiprocessing_util import NoDaemonContext
+from csle_common.util.multiprocessing_util import NestablePool
+
+
+class TestMultiprocessingUtilSuite:
+ """
+ Test suite for multiprocessing util
+ """
+
+ def test_daemon(self) -> None:
+ """
+ Test the process with daemon property set to false
+
+ :return: None
+ """
+ result = NoDaemonProcess(target=lambda: None).daemon
+ assert not result
+
+ def test_no_daemon_context(self) -> None:
+ """
+ Test the NoDaemonContext method
+
+ :return: None
+ """
+ context = NoDaemonContext()
+ process = context.Process(target=lambda: None)
+ assert isinstance(process, NoDaemonProcess)
+ assert not process.daemon
+
+ @patch("multiprocessing.get_context")
+ def test_nestable_pool_initialization(self, mock_get_context) -> None:
+ """
+ Test the method that initializes the pool
+
+ :param mock_get_context: mock_get_context
+
+ :return: None
+ """
+ mock_get_context.return_value = NoDaemonContext()
+ pool = NestablePool()
+ assert pool
diff --git a/simulation-system/libs/csle-common/tests/test_plotting_util.py b/simulation-system/libs/csle-common/tests/test_plotting_util.py
new file mode 100644
index 000000000..d21f0988a
--- /dev/null
+++ b/simulation-system/libs/csle-common/tests/test_plotting_util.py
@@ -0,0 +1,48 @@
+from csle_common.util.plotting_util import PlottingUtil
+from scipy import stats
+import numpy as np
+
+
+class TestPlottingUtilSuite:
+ """
+ Test suite for plotting util
+ """
+
+ def test_running_average(self) -> None:
+ """
+ Test the function used to compute the running average of the last N elements of a vector x
+
+ :return: None
+ """
+ x = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
+ N = 3
+ expected = np.array([1, 2, 3, 3, 4, 5, 6, 7, 8, 9])
+ result = PlottingUtil.running_average(x, N)
+ assert result.any() == expected.any()
+
+ def test_mean_confidence_interval(self) -> None:
+ """
+ Test function that computes confidence intervals
+
+ :return: None
+ """
+ data = np.array([1, 2, 3, 4, 5])
+ mean, h = PlottingUtil.mean_confidence_interval(data=data, confidence=0.95)
+ expected_mean = np.mean(data)
+ expected_se = stats.sem(data)
+ expected_h = expected_se * stats.t.ppf((1 + 0.95) / 2.0, len(data) - 1)
+ assert expected_mean == mean
+ assert expected_h == h
+
+ def test_min_max_norm(self) -> None:
+ """
+ Test function that computes min-max normalization of a vector
+
+ :return: None
+ """
+ vec = np.array([1, 2, 3, 4, 5])
+ min_val = 1
+ max_val = 5
+ expected = np.array([0.0, 0.25, 0.5, 0.75, 1.0])
+ result = PlottingUtil.min_max_norm(vec, max_val, min_val)
+ assert result.any() == expected.any()
diff --git a/simulation-system/libs/csle-common/tests/test_ssh_util.py b/simulation-system/libs/csle-common/tests/test_ssh_util.py
new file mode 100644
index 000000000..2fa9acb91
--- /dev/null
+++ b/simulation-system/libs/csle-common/tests/test_ssh_util.py
@@ -0,0 +1,53 @@
+from csle_common.util.ssh_util import SSHUtil
+from unittest.mock import patch, MagicMock
+import pytest
+
+
+class TestSSHUtilSuite:
+ """
+ Test suite for ssh_util
+ """
+
+ @pytest.fixture(autouse=True)
+ def mock_sleep(self):
+ """
+ Mock time.sleep to avoid delays
+ """
+ with patch("time.sleep", return_value=None):
+ yield
+
+ @patch("csle_common.util.ssh_util.SSHUtil.execute_ssh_cmd")
+ def test_execute_ssh_cmds(self, mock_execute_ssh_cmd) -> None:
+ """
+ Test the method that executes a list of commands over an ssh connection to the emulation
+
+ :param mock_execute_ssh_cmd: mock_execute_ssh_cmd
+
+ :return: None
+ """
+ mock_execute_ssh_cmd.return_value = (b"output", b"error", 1.0)
+ cmds = ["ls", "pwd", "whoami"]
+ conn = MagicMock()
+ results = SSHUtil.execute_ssh_cmds(cmds, conn)
+ mock_execute_ssh_cmd.assert_called()
+ assert results == [(b"output", b"error", 1.0)] * len(cmds)
+
+ def test_execute_ssh_cmd(self) -> None:
+ """
+ Test the method that executes an action on the emulation over a ssh connection
+
+ :return: None
+ """
+ conn = MagicMock()
+ mock_transport = MagicMock()
+ mock_session = MagicMock()
+ mock_session.exit_status_ready.return_value = True
+ mock_session.recv_ready.return_value = True
+ mock_session.recv_stderr_ready.return_value = True
+ mock_session.recv.side_effect = [b"output", b""]
+ mock_session.recv_stderr.side_effect = [b"error", b""]
+ conn.get_transport.return_value = mock_transport
+ mock_transport.open_session.return_value = mock_session
+
+ with pytest.raises(ConnectionError, match="Connection failed"):
+ SSHUtil.execute_ssh_cmd("ls", conn)