From c260c784eb5f7cf575ecd871b495f342816198d3 Mon Sep 17 00:00:00 2001
From: MariusBaldovin <baldovin@google.com>
Date: Thu, 29 Aug 2024 15:36:08 +0100
Subject: [PATCH 01/25] updated delete device endpoint to respond 404 if report
 is not found

---
 framework/python/src/api/api.py      |  8 ++++++++
 framework/python/src/core/testrun.py | 18 ++++++++----------
 testing/api/test_api.py              | 15 +++++++++------
 3 files changed, 25 insertions(+), 16 deletions(-)

diff --git a/framework/python/src/api/api.py b/framework/python/src/api/api.py
index 409884fd1..82b4762b7 100644
--- a/framework/python/src/api/api.py
+++ b/framework/python/src/api/api.py
@@ -465,6 +465,14 @@ async def delete_report(self, request: Request, response: Response):
       response.status_code = 404
       return self._generate_msg(False, "Could not find device")
 
+    # Assign the reports folder path from testrun
+    reports_folder = self._testrun.get_reports_folder(device)
+
+    # Check if reports folder exists
+    if not os.path.exists(reports_folder):
+      response.status_code = 404
+      return self._generate_msg(False, "Report not found")
+
     if self._testrun.delete_report(device, timestamp_formatted):
       return self._generate_msg(True, "Deleted report")
 
diff --git a/framework/python/src/core/testrun.py b/framework/python/src/core/testrun.py
index 8fe325774..977e97242 100644
--- a/framework/python/src/core/testrun.py
+++ b/framework/python/src/core/testrun.py
@@ -230,9 +230,7 @@ def _load_test_reports(self, device):
     device.clear_reports()
 
     # Locate reports folder
-    reports_folder = os.path.join(self._root_dir,
-                                  LOCAL_DEVICES_DIR,
-                                  device.device_folder, 'reports')
+    reports_folder = self.get_reports_folder(device)
 
     # Check if reports folder exists (device may have no reports)
     if not os.path.exists(reports_folder):
@@ -275,18 +273,18 @@ def _load_test_reports(self, device):
         test_report.set_mac_addr(device.mac_addr)
         device.add_report(test_report)
 
+  def get_reports_folder(self, device):
+    """Return the reports folder path for the device"""
+    return os.path.join(self._root_dir,
+                        LOCAL_DEVICES_DIR,
+                        device.device_folder, 'reports')
+
   def delete_report(self, device: Device, timestamp):
     LOGGER.debug(f'Deleting test report for device {device.model} ' +
                  f'at {timestamp}')
 
     # Locate reports folder
-    reports_folder = os.path.join(self._root_dir,
-                                  LOCAL_DEVICES_DIR,
-                                  device.device_folder, 'reports')
-
-    # Check if reports folder exists (device may have no reports)
-    if not os.path.exists(reports_folder):
-      return False
+    reports_folder = self.get_reports_folder(device)
 
     for report_folder in os.listdir(reports_folder):
       if report_folder == timestamp:
diff --git a/testing/api/test_api.py b/testing/api/test_api.py
index 49f894c86..0f5baae05 100644
--- a/testing/api/test_api.py
+++ b/testing/api/test_api.py
@@ -956,8 +956,8 @@ def test_delete_report_invalid_timestamp(empty_devices_dir, testrun, # pylint: d
   # Check if the correct error message returned
   assert "Incorrect timestamp format" in response["error"]
 
-def test_delete_report_no_report(empty_devices_dir, testrun): # pylint: disable=W0613
-  """Test delete report when report does not exist (404)"""
+def test_delete_report_no_device(empty_devices_dir, testrun): # pylint: disable=W0613
+  """Test delete report when device does not exist (404)"""
 
   # Payload to be deleted for a non existing device
   delete_data = {
@@ -977,7 +977,10 @@ def test_delete_report_no_report(empty_devices_dir, testrun): # pylint: disable=
   # Check if "error" in response
   assert "error" in response
 
-def test_delete_report_server_error(empty_devices_dir, testrun, # pylint: disable=W0613
+  # Check if the correct error message returned
+  assert "Could not find device" in response["error"]
+
+def test_delete_report_no_report(empty_devices_dir, testrun, # pylint: disable=W0613
                              add_device, create_report_folder):
   """Test for delete report causing internal server error (500)"""
 
@@ -1004,8 +1007,8 @@ def test_delete_report_server_error(empty_devices_dir, testrun, # pylint: disabl
                       data=json.dumps(delete_data),
                       timeout=5)
 
-  # Check if status code is 500 (Internal Server Error)
-  assert r.status_code == 500
+  # Check if status code is 404 (not found)
+  assert r.status_code == 404
 
   # Parse the JSON response
   response = r.json()
@@ -1014,7 +1017,7 @@ def test_delete_report_server_error(empty_devices_dir, testrun, # pylint: disabl
   assert "error" in response
 
   # Check if the correct error message is returned
-  assert "Error occured whilst deleting report" in response["error"]
+  assert "Report not found" in response["error"]
 
 def test_get_report_success(empty_devices_dir, testrun, # pylint: disable=W0613
                      add_device, create_report_folder):

From 62610dfbc8c9eb4f6a1f1d826a376d3985af1070 Mon Sep 17 00:00:00 2001
From: MariusBaldovin <baldovin@google.com>
Date: Thu, 29 Aug 2024 15:50:22 +0100
Subject: [PATCH 02/25] fix test_delete_report_no_report

---
 testing/api/test_api.py | 16 +++-------------
 1 file changed, 3 insertions(+), 13 deletions(-)

diff --git a/testing/api/test_api.py b/testing/api/test_api.py
index 0f5baae05..e92c645ea 100644
--- a/testing/api/test_api.py
+++ b/testing/api/test_api.py
@@ -980,21 +980,11 @@ def test_delete_report_no_device(empty_devices_dir, testrun): # pylint: disable=
   # Check if the correct error message returned
   assert "Could not find device" in response["error"]
 
-def test_delete_report_no_report(empty_devices_dir, testrun, # pylint: disable=W0613
-                             add_device, create_report_folder):
-  """Test for delete report causing internal server error (500)"""
+def test_delete_report_no_report(empty_devices_dir, testrun, add_device): # pylint: disable=W0613
+  """Test for delete report when report does not exist (404)"""
 
   # Load the device_name and mac_address from add_device fixture
-  device_name, mac_address = add_device()
-
-  # Create the report folder and JSON file
-  create_report_folder(device_name, mac_address, TIMESTAMP)
-
-  # Construct the device report path
-  device_directory = os.path.join(DEVICES_DIRECTORY, device_name)
-
-  # Remove the folder and all its content before delete request
-  shutil.rmtree(device_directory)
+  _, mac_address = add_device()
 
   # Prepare the payload for the DELETE request
   delete_data = {

From 3f530108cf5543767576c4cfe29561a8eb651e53 Mon Sep 17 00:00:00 2001
From: MariusBaldovin <baldovin@google.com>
Date: Tue, 3 Sep 2024 12:55:00 +0100
Subject: [PATCH 03/25] fixed failing tests

---
 framework/python/src/api/api.py   |  12 +-
 testing/api/devices/device_1.json |  55 +++++
 testing/api/devices/device_2.json |  56 +++++
 testing/api/test_api.py           | 326 ++++++++----------------------
 4 files changed, 204 insertions(+), 245 deletions(-)
 create mode 100644 testing/api/devices/device_1.json
 create mode 100644 testing/api/devices/device_2.json

diff --git a/framework/python/src/api/api.py b/framework/python/src/api/api.py
index 22d712f90..84b4d88c0 100644
--- a/framework/python/src/api/api.py
+++ b/framework/python/src/api/api.py
@@ -260,6 +260,12 @@ async def start_testrun(self, request: Request, response: Response):
 
     device = self._session.get_device(body_json["device"]["mac_addr"])
 
+    # Check if requested device is known in the device repository
+    if device is None:
+      response.status_code = status.HTTP_404_NOT_FOUND
+      return self._generate_msg(
+          False, "A device with that MAC address could not be found")
+
     # Check if device is fully configured
     if device.status != "Valid":
       response.status_code = status.HTTP_400_BAD_REQUEST
@@ -277,12 +283,6 @@ async def start_testrun(self, request: Request, response: Response):
           False, "Testrun cannot be started " +
           "whilst a test is running on another device")
 
-    # Check if requested device is known in the device repository
-    if device is None:
-      response.status_code = status.HTTP_404_NOT_FOUND
-      return self._generate_msg(
-          False, "A device with that MAC address could not be found")
-
     device.firmware = body_json["device"]["firmware"]
 
     # Check if config has been updated (device interface not default)
diff --git a/testing/api/devices/device_1.json b/testing/api/devices/device_1.json
new file mode 100644
index 000000000..2de82408b
--- /dev/null
+++ b/testing/api/devices/device_1.json
@@ -0,0 +1,55 @@
+{   
+    "status": "Valid",
+    "mac_addr": "00:1e:42:28:9e:4a",
+    "manufacturer": "Teltonika",
+    "model": "TRB140",
+    "type": "IoT Gateway",
+    "technology": "Hardware - Access Control",
+    "test_pack": "Device Qualification",
+    "additional_info": [
+        {
+          "question": "What type of device is this?",
+          "answer": "IoT Gateway"
+        },
+        {
+          "question": "Please select the technology this device falls into",
+          "answer": "Hardware - Access Control"
+        },
+        {
+            "question": "Does your device process any sensitive information? ",
+            "answer": "Yes"
+        },
+        {
+          "question": "Can all non-essential services be disabled on your device?",
+          "answer": "Yes"
+        },
+        {
+          "question": "Is there a second IP port on the device?",
+          "answer": "Yes"
+        },
+        {
+          "question": "Can the second IP port on your device be disabled?",
+          "answer": "Yes"
+        }
+        ],
+    "test_modules": {
+        "protocol": {
+            "enabled": true
+        },
+        "services": {
+            "enabled": true
+        },
+        "ntp": {
+            "enabled": true
+        },
+        "tls": {
+            "enabled": true
+        },
+        "connection": {
+            "enabled": true
+        },
+        "dns": {
+            "enabled": true
+        }
+    }
+}
\ No newline at end of file
diff --git a/testing/api/devices/device_2.json b/testing/api/devices/device_2.json
new file mode 100644
index 000000000..e813bfc72
--- /dev/null
+++ b/testing/api/devices/device_2.json
@@ -0,0 +1,56 @@
+{
+    "status": "Valid",
+    "mac_addr": "00:1e:42:35:73:c6",
+    "manufacturer": "Google",
+    "model": "First",
+    "type": "IoT Gateway",
+    "technology": "Hardware - Access Control",
+    "test_pack": "Device Qualification",
+    "additional_info": [
+        {
+          "question": "What type of device is this?",
+          "answer": "IoT Gateway"
+        },
+        {
+          "question": "Please select the technology this device falls into",
+          "answer": "Hardware - Access Control"
+        },
+        {
+            "question": "Does your device process any sensitive information? ",
+            "answer": "Yes"
+        },
+        {
+          "question": "Can all non-essential services be disabled on your device?",
+          "answer": "Yes"
+        },
+        {
+          "question": "Is there a second IP port on the device?",
+          "answer": "Yes"
+        },
+        {
+          "question": "Can the second IP port on your device be disabled?",
+          "answer": "Yes"
+        }
+        ],
+     
+    "test_modules": {
+        "protocol": {
+            "enabled": true
+        },
+        "services": {
+            "enabled": false
+        },
+        "ntp": {
+            "enabled": true
+        },
+        "tls": {
+            "enabled": false
+        },
+        "connection": {
+            "enabled": true
+        },
+        "dns": {
+            "enabled": true
+        }
+    }
+}
\ No newline at end of file
diff --git a/testing/api/test_api.py b/testing/api/test_api.py
index e92c645ea..614b0af7f 100644
--- a/testing/api/test_api.py
+++ b/testing/api/test_api.py
@@ -44,6 +44,7 @@
 CERTS_PATH = "testing/api/certificates"
 PROFILES_PATH = "testing/api/profiles"
 REPORTS_PATH = "testing/api/reports"
+DEVICES_PATH = "testing/api/devices"
 
 BASELINE_MAC_ADDR = "02:42:aa:00:01:01"
 ALL_MAC_ADDR = "02:42:aa:00:00:01"
@@ -362,20 +363,23 @@ def test_get_system_config(testrun): # pylint: disable=W0613
       == api_config["network"]["internet_intf"]
   )
 
-def test_start_testrun_success(testing_devices, testrun): # pylint: disable=W0613
+def test_start_testrun_success(empty_devices_dir, testrun, add_device): # pylint: disable=W0613
   """Test for testrun started successfully """
 
+  _, mac_address = add_device()
+
   # Payload with device details
   payload = {"device": {
-             "mac_addr": BASELINE_MAC_ADDR,
-             "firmware": "asd", 
-             "test_modules": {
-                "dns": {"enabled": False},
-                "connection": {"enabled": True},
-                "ntp": {"enabled": False},
-                "baseline": {"enabled": False},
-                "nmap": {"enabled": False}
-            }}}
+    "mac_addr": mac_address,
+    "firmware": "test",
+    "test_modules": {
+        "protocol": { "enabled": True },
+        "services": { "enabled": True },
+        "ntp": { "enabled": True },
+        "tls": { "enabled": True },
+        "connection": { "enabled": True },
+        "dns": { "enabled": True }
+    }}}
 
   # Send the post request
   r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10)
@@ -413,20 +417,23 @@ def test_start_testrun_missing_device(testing_devices, testrun): # pylint: disab
   # Check if 'error' in response
   assert "error" in response
 
-def test_start_testrun_already_started(testing_devices, testrun): # pylint: disable=W0613
+def test_start_testrun_already_started(empty_devices_dir, testrun, add_device): # pylint: disable=W0613
   """Test for testrun already started """
 
+  _, mac_address = add_device()
+
   # Payload with device details
   payload = {"device": {
-             "mac_addr": BASELINE_MAC_ADDR,
-             "firmware": "asd", 
-             "test_modules": {
-                "dns": {"enabled": False},
-                "connection": {"enabled": True},
-                "ntp": {"enabled": False},
-                "baseline": {"enabled": False},
-                "nmap": {"enabled": False}
-            }}}
+    "mac_addr": mac_address,
+    "firmware": "test",
+    "test_modules": {
+        "protocol": { "enabled": True },
+        "services": { "enabled": True },
+        "ntp": { "enabled": True },
+        "tls": { "enabled": True },
+        "connection": { "enabled": True },
+        "dns": { "enabled": True }
+    }}}
 
   # Send the post request (start test)
   r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10)
@@ -446,20 +453,15 @@ def test_start_testrun_already_started(testing_devices, testrun): # pylint: disa
   # Check if 'error' in response
   assert "error" in response
 
-def test_start_testrun_device_not_found(testing_devices, testrun): # pylint: disable=W0613
+def test_start_testrun_device_not_found(empty_devices_dir, testrun): # pylint: disable=W0613
   """Test for start testrun device not found """
 
   # Payload with device details with no mac address assigned
   payload = {"device": {
-             "mac_addr": "",
-             "firmware": "asd", 
-             "test_modules": {
-                "dns": {"enabled": False},
-                "connection": {"enabled": True},
-                "ntp": {"enabled": False},
-                "baseline": {"enabled": False},
-                "nmap": {"enabled": False}
-            }}}
+    "mac_addr": "",
+    "firmware": "test",
+    "test_modules": {}
+    }}
 
   # Send the post request
   r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10)
@@ -692,22 +694,23 @@ def test_system_shutdown(testrun): # pylint: disable=W0613
   # Check if the response status code is 200 (OK)
   assert r.status_code == 200, f"Expected status code 200, got {r.status_code}"
 
-def test_system_shutdown_in_progress(testrun): # pylint: disable=W0613
+def test_system_shutdown_in_progress(empty_devices_dir, testrun, add_device): # pylint: disable=W0613
   """Test system shutdown during an in-progress test"""
+
+  _, mac_address = add_device()
+
   # Payload with device details to start a test
-  payload = {
-      "device": {
-          "mac_addr": BASELINE_MAC_ADDR,
-          "firmware": "asd",
-          "test_modules": {
-              "dns": {"enabled": False},
-              "connection": {"enabled": True},
-              "ntp": {"enabled": False},
-              "baseline": {"enabled": False},
-              "nmap": {"enabled": False}
-          }
-      }
-  }
+  payload = {"device": {
+    "mac_addr": mac_address,
+    "firmware": "test",
+    "test_modules": {
+        "protocol": { "enabled": True },
+        "services": { "enabled": True },
+        "ntp": { "enabled": True },
+        "tls": { "enabled": True },
+        "connection": { "enabled": True },
+        "dns": { "enabled": True }
+    }}}
 
   # Start a test
   r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10)
@@ -794,13 +797,9 @@ def create_and_get_device():
   """Utility method to create a device"""
 
   # Payload with device details
-  device = {
-        "mac_addr": "00:1e:42:28:9e:4a",
-        "manufacturer": "Teltonika",
-        "model": "TRB140"
-    }
+  device = load_json("device_1.json", directory=DEVICES_PATH)
 
-  # Send a POST request to create a new device
+   # Send a POST request to create a new device
   requests.post(f"{API}/device", data=json.dumps(device), timeout=5)
 
   # Send the GET request to retrieve the created device's folder name
@@ -816,8 +815,6 @@ def create_and_get_device():
   # Return only the device name and MAC address
   return device_name, mac_address
 
-
-
 @pytest.fixture()
 def add_device():
   """Fixture to add device during tests"""
@@ -1234,18 +1231,8 @@ def test_status_non_compliant(testing_devices, testrun): # pylint: disable=W0613
   stop_test_device("x123")
 
 def test_create_get_devices(empty_devices_dir, testrun): # pylint: disable=W0613
-  device_1 = {
-      "manufacturer": "Google",
-      "model": "First",
-      "mac_addr": "00:1e:42:35:73:c4",
-      "test_modules": {
-          "dns": {"enabled": True},
-          "connection": {"enabled": True},
-          "ntp": {"enabled": True},
-          "baseline": {"enabled": True},
-          "nmap": {"enabled": True},
-      },
-  }
+
+  device_1 = load_json("device_1.json", directory=DEVICES_PATH)
 
   r = requests.post(f"{API}/device", data=json.dumps(device_1),
                     timeout=5)
@@ -1253,18 +1240,8 @@ def test_create_get_devices(empty_devices_dir, testrun): # pylint: disable=W0613
   assert r.status_code == 201
   assert len(local_get_devices()) == 1
 
-  device_2 = {
-      "manufacturer": "Google",
-      "model": "Second",
-      "mac_addr": "00:1e:42:35:73:c6",
-      "test_modules": {
-          "dns": {"enabled": True},
-          "connection": {"enabled": True},
-          "ntp": {"enabled": True},
-          "baseline": {"enabled": True},
-          "nmap": {"enabled": True},
-      },
-  }
+  device_2 = load_json("device_2.json", directory=DEVICES_PATH)
+
   r = requests.post(f"{API}/device", data=json.dumps(device_2),
                     timeout=5)
   assert r.status_code == 201
@@ -1296,18 +1273,8 @@ def test_create_get_devices(empty_devices_dir, testrun): # pylint: disable=W0613
     )
 
 def test_delete_device_success(empty_devices_dir, testrun): # pylint: disable=W0613
-  device_1 = {
-      "manufacturer": "Google",
-      "model": "First",
-      "mac_addr": "00:1e:42:35:73:c4",
-      "test_modules": {
-          "dns": {"enabled": True},
-          "connection": {"enabled": True},
-          "ntp": {"enabled": True},
-          "baseline": {"enabled": True},
-          "nmap": {"enabled": True},
-      },
-  }
+
+  device_1 = load_json("device_1.json", directory=DEVICES_PATH)
 
   # Send create device request
   r = requests.post(f"{API}/device",
@@ -1319,18 +1286,8 @@ def test_delete_device_success(empty_devices_dir, testrun): # pylint: disable=W0
   assert r.status_code == 201
   assert len(local_get_devices()) == 1
 
-  device_2 = {
-      "manufacturer": "Google",
-      "model": "Second",
-      "mac_addr": "00:1e:42:35:73:c6",
-      "test_modules": {
-          "dns": {"enabled": True},
-          "connection": {"enabled": True},
-          "ntp": {"enabled": True},
-          "baseline": {"enabled": True},
-          "nmap": {"enabled": True},
-      },
-  }
+  device_2 = load_json("device_2.json", directory=DEVICES_PATH)
+
   r = requests.post(f"{API}/device",
                     data=json.dumps(device_2),
                     timeout=5)
@@ -1373,56 +1330,21 @@ def test_delete_device_success(empty_devices_dir, testrun): # pylint: disable=W0
     )
 
 def test_delete_device_not_found(empty_devices_dir, testrun): # pylint: disable=W0613
-  device_1 = {
-      "manufacturer": "Google",
-      "model": "First",
-      "mac_addr": "00:1e:42:35:73:c4",
-      "test_modules": {
-          "dns": {"enabled": True},
-          "connection": {"enabled": True},
-          "ntp": {"enabled": True},
-          "baseline": {"enabled": True},
-          "nmap": {"enabled": True},
-      },
-  }
 
-  # Send create device request
-  r = requests.post(f"{API}/device",
-                    data=json.dumps(device_1),
-                    timeout=5)
-  print(r.text)
-
-  # Check device has been created
-  assert r.status_code == 201
-  assert len(local_get_devices()) == 1
+  payload = {"mac_addr": "non_existing"}
 
   # Test that device_1 deletes
   r = requests.delete(f"{API}/device/",
-                      data=json.dumps(device_1),
+                      data=json.dumps(payload),
                       timeout=5)
-  assert r.status_code == 200
-  assert len(local_get_devices()) == 0
 
-  # Test that device_1 is not found
-  r = requests.delete(f"{API}/device/",
-                      data=json.dumps(device_1),
-                      timeout=5)
   assert r.status_code == 404
+
   assert len(local_get_devices()) == 0
 
 def test_delete_device_no_mac(empty_devices_dir, testrun): # pylint: disable=W0613
-  device_1 = {
-      "manufacturer": "Google",
-      "model": "First",
-      "mac_addr": "00:1e:42:35:73:c4",
-      "test_modules": {
-          "dns": {"enabled": True},
-          "connection": {"enabled": True},
-          "ntp": {"enabled": True},
-          "baseline": {"enabled": True},
-          "nmap": {"enabled": True},
-      },
-  }
+
+  device_1 = load_json("device_1.json", directory=DEVICES_PATH)
 
   # Send create device request
   r = requests.post(f"{API}/device",
@@ -1485,18 +1407,8 @@ def test_delete_device_testrun_running(testing_devices, testrun): # pylint: disa
 def test_start_system_not_configured_correctly(
     empty_devices_dir, # pylint: disable=W0613
     testrun): # pylint: disable=W0613
-  device_1 = {
-      "manufacturer": "Google",
-      "model": "First",
-      "mac_addr": "00:1e:42:35:73:c4",
-      "test_modules": {
-          "dns": {"enabled": True},
-          "connection": {"enabled": True},
-          "ntp": {"enabled": True},
-          "baseline": {"enabled": True},
-          "nmap": {"enabled": True},
-      },
-  }
+
+  device_1 = load_json("device_1.json", directory=DEVICES_PATH)
 
   # Send create device request
   r = requests.post(f"{API}/device",
@@ -1504,25 +1416,17 @@ def test_start_system_not_configured_correctly(
                     timeout=5)
 
   payload = {"device": {"mac_addr": None, "firmware": "asd"}}
+
   r = requests.post(f"{API}/system/start",
                     data=json.dumps(payload),
                     timeout=10)
+
   assert r.status_code == 500
 
 def test_start_device_not_found(empty_devices_dir, # pylint: disable=W0613
                                 testrun): # pylint: disable=W0613
-  device_1 = {
-      "manufacturer": "Google",
-      "model": "First",
-      "mac_addr": "00:1e:42:35:73:c4",
-      "test_modules": {
-          "dns": {"enabled": True},
-          "connection": {"enabled": True},
-          "ntp": {"enabled": True},
-          "baseline": {"enabled": True},
-          "nmap": {"enabled": True},
-      },
-  }
+
+  device_1 = load_json("device_1.json", directory=DEVICES_PATH)
 
   # Send create device request
   r = requests.post(f"{API}/device",
@@ -1543,18 +1447,8 @@ def test_start_device_not_found(empty_devices_dir, # pylint: disable=W0613
 def test_start_missing_device_information(
     empty_devices_dir, # pylint: disable=W0613
     testrun): # pylint: disable=W0613
-  device_1 = {
-      "manufacturer": "Google",
-      "model": "First",
-      "mac_addr": "00:1e:42:35:73:c4",
-      "test_modules": {
-          "dns": {"enabled": True},
-          "connection": {"enabled": True},
-          "ntp": {"enabled": True},
-          "baseline": {"enabled": True},
-          "nmap": {"enabled": True},
-      },
-  }
+
+  device_1 = load_json("device_1.json", directory=DEVICES_PATH)
 
   # Send create device request
   r = requests.post(f"{API}/device",
@@ -1570,18 +1464,8 @@ def test_start_missing_device_information(
 def test_create_device_already_exists(
     empty_devices_dir, # pylint: disable=W0613
     testrun): # pylint: disable=W0613
-  device_1 = {
-      "manufacturer": "Google",
-      "model": "First",
-      "mac_addr": "00:1e:42:35:73:c4",
-      "test_modules": {
-          "dns": {"enabled": True},
-          "connection": {"enabled": True},
-          "ntp": {"enabled": True},
-          "baseline": {"enabled": True},
-          "nmap": {"enabled": True},
-      },
-  }
+
+  device_1 = load_json("device_1.json", directory=DEVICES_PATH)
 
   r = requests.post(f"{API}/device",
                     data=json.dumps(device_1),
@@ -1597,8 +1481,8 @@ def test_create_device_already_exists(
 def test_create_device_invalid_json(
     empty_devices_dir, # pylint: disable=W0613
     testrun): # pylint: disable=W0613
-  device_1 = {
-  }
+
+  device_1 = {}
 
   r = requests.post(f"{API}/device",
                     data=json.dumps(device_1),
@@ -1665,18 +1549,8 @@ def test_device_edit_device(
 def test_device_edit_device_not_found(
     empty_devices_dir, # pylint: disable=W0613
     testrun): # pylint: disable=W0613
-  device_1 = {
-      "manufacturer": "Google",
-      "model": "First",
-      "mac_addr": "00:1e:42:35:73:c4",
-      "test_modules": {
-          "dns": {"enabled": True},
-          "connection": {"enabled": True},
-          "ntp": {"enabled": True},
-          "baseline": {"enabled": True},
-          "nmap": {"enabled": True},
-      },
-  }
+
+  device_1 = load_json("device_1.json", directory=DEVICES_PATH)
 
   r = requests.post(f"{API}/device",
                     data=json.dumps(device_1),
@@ -1701,18 +1575,8 @@ def test_device_edit_device_not_found(
 def test_device_edit_device_incorrect_json_format(
     empty_devices_dir, # pylint: disable=W0613
     testrun): # pylint: disable=W0613
-  device_1 = {
-      "manufacturer": "Google",
-      "model": "First",
-      "mac_addr": "00:1e:42:35:73:c4",
-      "test_modules": {
-          "dns": {"enabled": True},
-          "connection": {"enabled": True},
-          "ntp": {"enabled": True},
-          "baseline": {"enabled": True},
-          "nmap": {"enabled": True},
-      },
-  }
+
+  device_1 = load_json("device_1.json", directory=DEVICES_PATH)
 
   r = requests.post(f"{API}/device",
                     data=json.dumps(device_1),
@@ -1732,47 +1596,31 @@ def test_device_edit_device_incorrect_json_format(
 def test_device_edit_device_with_mac_already_exists(
     empty_devices_dir, # pylint: disable=W0613
     testrun): # pylint: disable=W0613
-  device_1 = {
-      "manufacturer": "Google",
-      "model": "First",
-      "mac_addr": "00:1e:42:35:73:c4",
-      "test_modules": {
-          "dns": {"enabled": True},
-          "connection": {"enabled": True},
-          "ntp": {"enabled": True},
-          "baseline": {"enabled": True},
-          "nmap": {"enabled": True},
-      },
-  }
+
+  device_1 = load_json("device_1.json", directory=DEVICES_PATH)
 
   r = requests.post(f"{API}/device",
                     data=json.dumps(device_1),
                     timeout=5)
+
   assert r.status_code == 201
+
   assert len(local_get_devices()) == 1
 
-  device_2 = {
-      "manufacturer": "Google",
-      "model": "Second",
-      "mac_addr": "00:1e:42:35:73:c6",
-      "test_modules": {
-          "dns": {"enabled": True},
-          "connection": {"enabled": True},
-          "ntp": {"enabled": True},
-          "baseline": {"enabled": True},
-          "nmap": {"enabled": True},
-      },
-  }
+  device_2 = load_json("device_2.json", directory=DEVICES_PATH)
+
   r = requests.post(f"{API}/device",
                     data=json.dumps(device_2),
                     timeout=5)
+
   assert r.status_code == 201
+
   assert len(local_get_devices()) == 2
 
   updated_device = copy.deepcopy(device_1)
 
   updated_device_payload = {}
-  updated_device_payload = {}
+
   updated_device_payload["device"] = updated_device
   updated_device_payload["mac_addr"] = "00:1e:42:35:73:c6"
   updated_device_payload["model"] = "Alphabet"

From db7782635586bfe21c8f7760785a8e10df836d87 Mon Sep 17 00:00:00 2001
From: MariusBaldovin <baldovin@google.com>
Date: Tue, 3 Sep 2024 15:17:37 +0100
Subject: [PATCH 04/25] fixed json formatting

---
 testing/api/devices/device_1.json | 108 +++++++++++++++---------------
 testing/api/devices/device_2.json | 107 +++++++++++++++--------------
 2 files changed, 107 insertions(+), 108 deletions(-)

diff --git a/testing/api/devices/device_1.json b/testing/api/devices/device_1.json
index 2de82408b..f76f5d2e0 100644
--- a/testing/api/devices/device_1.json
+++ b/testing/api/devices/device_1.json
@@ -1,55 +1,55 @@
-{   
-    "status": "Valid",
-    "mac_addr": "00:1e:42:28:9e:4a",
-    "manufacturer": "Teltonika",
-    "model": "TRB140",
-    "type": "IoT Gateway",
-    "technology": "Hardware - Access Control",
-    "test_pack": "Device Qualification",
-    "additional_info": [
-        {
-          "question": "What type of device is this?",
-          "answer": "IoT Gateway"
-        },
-        {
-          "question": "Please select the technology this device falls into",
-          "answer": "Hardware - Access Control"
-        },
-        {
-            "question": "Does your device process any sensitive information? ",
-            "answer": "Yes"
-        },
-        {
-          "question": "Can all non-essential services be disabled on your device?",
-          "answer": "Yes"
-        },
-        {
-          "question": "Is there a second IP port on the device?",
-          "answer": "Yes"
-        },
-        {
-          "question": "Can the second IP port on your device be disabled?",
-          "answer": "Yes"
-        }
-        ],
-    "test_modules": {
-        "protocol": {
-            "enabled": true
-        },
-        "services": {
-            "enabled": true
-        },
-        "ntp": {
-            "enabled": true
-        },
-        "tls": {
-            "enabled": true
-        },
-        "connection": {
-            "enabled": true
-        },
-        "dns": {
-            "enabled": true
-        }
+{
+  "status": "Valid",
+  "mac_addr": "00:1e:42:28:9e:4a",
+  "manufacturer": "Teltonika",
+  "model": "TRB140",
+  "type": "IoT Gateway",
+  "technology": "Hardware - Access Control",
+  "test_pack": "Device Qualification",
+  "additional_info": [
+    {
+      "question": "What type of device is this?",
+      "answer": "IoT Gateway"
+    },
+    {
+      "question": "Please select the technology this device falls into",
+      "answer": "Hardware - Access Control"
+    },
+    {
+      "question": "Does your device process any sensitive information?",
+      "answer": "Yes"
+    },
+    {
+      "question": "Can all non-essential services be disabled on your device?",
+      "answer": "Yes"
+    },
+    {
+      "question": "Is there a second IP port on the device?",
+      "answer": "Yes"
+    },
+    {
+      "question": "Can the second IP port on your device be disabled?",
+      "answer": "Yes"
     }
-}
\ No newline at end of file
+  ],
+  "test_modules": {
+    "protocol": {
+      "enabled": true
+    },
+    "services": {
+      "enabled": false
+    },
+    "ntp": {
+      "enabled": true
+    },
+    "tls": {
+      "enabled": false
+    },
+    "connection": {
+      "enabled": true
+    },
+    "dns": {
+      "enabled": true
+    }
+  }
+}
diff --git a/testing/api/devices/device_2.json b/testing/api/devices/device_2.json
index e813bfc72..df8a418fb 100644
--- a/testing/api/devices/device_2.json
+++ b/testing/api/devices/device_2.json
@@ -1,56 +1,55 @@
 {
-    "status": "Valid",
-    "mac_addr": "00:1e:42:35:73:c6",
-    "manufacturer": "Google",
-    "model": "First",
-    "type": "IoT Gateway",
-    "technology": "Hardware - Access Control",
-    "test_pack": "Device Qualification",
-    "additional_info": [
-        {
-          "question": "What type of device is this?",
-          "answer": "IoT Gateway"
-        },
-        {
-          "question": "Please select the technology this device falls into",
-          "answer": "Hardware - Access Control"
-        },
-        {
-            "question": "Does your device process any sensitive information? ",
-            "answer": "Yes"
-        },
-        {
-          "question": "Can all non-essential services be disabled on your device?",
-          "answer": "Yes"
-        },
-        {
-          "question": "Is there a second IP port on the device?",
-          "answer": "Yes"
-        },
-        {
-          "question": "Can the second IP port on your device be disabled?",
-          "answer": "Yes"
-        }
-        ],
-     
-    "test_modules": {
-        "protocol": {
-            "enabled": true
-        },
-        "services": {
-            "enabled": false
-        },
-        "ntp": {
-            "enabled": true
-        },
-        "tls": {
-            "enabled": false
-        },
-        "connection": {
-            "enabled": true
-        },
-        "dns": {
-            "enabled": true
-        }
+  "status": "Valid",
+  "mac_addr": "00:1e:42:35:73:c6",
+  "manufacturer": "Google",
+  "model": "First",
+  "type": "IoT Gateway",
+  "technology": "Hardware - Access Control",
+  "test_pack": "Device Qualification",
+  "additional_info": [
+    {
+      "question": "What type of device is this?",
+      "answer": "IoT Gateway"
+    },
+    {
+      "question": "Please select the technology this device falls into",
+      "answer": "Hardware - Access Control"
+    },
+    {
+      "question": "Does your device process any sensitive information?",
+      "answer": "Yes"
+    },
+    {
+      "question": "Can all non-essential services be disabled on your device?",
+      "answer": "Yes"
+    },
+    {
+      "question": "Is there a second IP port on the device?",
+      "answer": "Yes"
+    },
+    {
+      "question": "Can the second IP port on your device be disabled?",
+      "answer": "Yes"
     }
-}
\ No newline at end of file
+  ],
+  "test_modules": {
+    "protocol": {
+      "enabled": true
+    },
+    "services": {
+      "enabled": false
+    },
+    "ntp": {
+      "enabled": true
+    },
+    "tls": {
+      "enabled": false
+    },
+    "connection": {
+      "enabled": true
+    },
+    "dns": {
+      "enabled": true
+    }
+  }
+}

From fb5382d478ab4d0f26c0f5bc423e9f733b6a2f4f Mon Sep 17 00:00:00 2001
From: MariusBaldovin <baldovin@google.com>
Date: Thu, 5 Sep 2024 10:53:34 +0100
Subject: [PATCH 05/25] updated the profile endpoints

---
 testing/api/profiles/new_profile.json     |  44 ----
 testing/api/profiles/new_profile_1.json   |  53 +++++
 testing/api/profiles/new_profile_2.json   |  69 +++---
 testing/api/profiles/updated_profile.json |  41 ----
 testing/api/test_api.py                   | 259 +++++++++++++---------
 5 files changed, 238 insertions(+), 228 deletions(-)
 delete mode 100644 testing/api/profiles/new_profile.json
 create mode 100644 testing/api/profiles/new_profile_1.json
 delete mode 100644 testing/api/profiles/updated_profile.json

diff --git a/testing/api/profiles/new_profile.json b/testing/api/profiles/new_profile.json
deleted file mode 100644
index e2e46f3ab..000000000
--- a/testing/api/profiles/new_profile.json
+++ /dev/null
@@ -1,44 +0,0 @@
-{
-    "name": "New Profile",
-    "status": "Valid",
-    "questions": [
-      {
-        "question": "How will this device be used at Google?",
-        "answer": "Monitoring"
-      },
-      {
-        "question": "Is this device going to be managed by Google or a third party?",
-        "answer": "Google"
-      },
-      {
-        "question": "Will the third-party device administrator be able to grant access to authorized Google personnel upon request?",
-        "answer": "N/A"
-      },
-      {
-        "question": "Which of the following statements are true about this device?",
-        "answer": [
-          0
-      ]
-      },
-      {
-        "question": "Does the network protocol assure server-to-client identity verification?",
-        "answer": "Yes"
-      },
-      {
-        "question": "Click the statements that best describe the characteristics of this device.",
-        "answer": [
-          0
-      ]
-      },
-      {
-        "question": "Are any of the following statements true about this device?",
-        "answer": [
-          0
-        ]
-      },
-      {
-        "question": "Comments",
-        "answer": ""
-      }
-    ]
-  }
\ No newline at end of file
diff --git a/testing/api/profiles/new_profile_1.json b/testing/api/profiles/new_profile_1.json
new file mode 100644
index 000000000..7f915ab34
--- /dev/null
+++ b/testing/api/profiles/new_profile_1.json
@@ -0,0 +1,53 @@
+{
+  "name": "new_profile_1",
+  "version": "1.4",
+  "created": "2024-09-03",
+  "status": "Valid",
+  "risk": "High",
+  "questions": [
+    {
+      "question": "How will this device be used at Google?",
+      "answer": "Monitoring"
+    },
+    {
+      "question": "Is this device going to be managed by Google or a third party?",
+      "answer": "Google",
+      "risk": "Limited"
+    },
+    {
+      "question": "Will the third-party device administrator be able to grant access to authorized Google personnel upon request?",
+      "answer": "N/A",
+      "risk": "Limited"
+    },
+    {
+      "question": "Which of the following statements are true about this device?",
+      "answer": [
+        0
+      ],
+      "risk": "High"
+    },
+    {
+      "question": "Does the network protocol assure server-to-client identity verification?",
+      "answer": "Yes",
+      "risk": "Limited"
+    },
+    {
+      "question": "Click the statements that best describe the characteristics of this device.",
+      "answer": [
+        0
+      ],
+      "risk": "High"
+    },
+    {
+      "question": "Are any of the following statements true about this device?",
+      "answer": [
+        0
+      ],
+      "risk": "High"  
+    },
+    {
+      "question": "Comments",
+      "answer": ""
+    }
+  ]
+}
\ No newline at end of file
diff --git a/testing/api/profiles/new_profile_2.json b/testing/api/profiles/new_profile_2.json
index 963265fe0..910fedd52 100644
--- a/testing/api/profiles/new_profile_2.json
+++ b/testing/api/profiles/new_profile_2.json
@@ -1,36 +1,37 @@
 {
-    "name": "New Profile 2",
-    "status": "Draft",
-    "questions": [
-    {
-      "question": "How will this device be used at Google?",
-      "answer": "Monitoring"
-    },
-    {
-      "question": "Is this device going to be managed by Google or a third party?",
-      "answer": "Google"
-    },
-    {
-      "question": "Will the third-party device administrator be able to grant access to authorized Google personnel upon request?",
-      "answer": "N/A"
-    },
-    {
-      "question": "Which of the following statements are true about this device?",
-      "answer": []
-    },
-    {
-      "question": "Does the network protocol assure server-to-client identity verification?",
-      "answer": "Yes"
-    },
-    {
-      "question": "Click the statements that best describe the characteristics of this device.",
-      "answer": []
-    },
-    {
-      "question": "Are any of the following statements true about this device?",
-      "answer": [
-            0
-        ]
-    }
-    ]
+  "name": "new_profile_2",
+  "version": "1.4",
+  "created": "2024-09-03",
+  "status": "Draft",
+  "risk": null,
+  "questions": [
+  {
+    "question": "How will this device be used at Google?",
+    "answer": "Monitoring"
+  },
+  {
+    "question": "Is this device going to be managed by Google or a third party?",
+    "answer": "Google"
+  },
+  {
+    "question": "Will the third-party device administrator be able to grant access to authorized Google personnel upon request?",
+    "answer": "N/A"
+  },
+  {
+    "question": "Which of the following statements are true about this device?",
+    "answer": []
+  },
+  {
+    "question": "Does the network protocol assure server-to-client identity verification?",
+    "answer": "Yes"
+  },
+  {
+    "question": "Click the statements that best describe the characteristics of this device.",
+    "answer": []
+  },
+  {
+    "question": "Are any of the following statements true about this device?",
+    "answer": [0]
+  }
+  ]
 }
\ No newline at end of file
diff --git a/testing/api/profiles/updated_profile.json b/testing/api/profiles/updated_profile.json
deleted file mode 100644
index 9184cd07d..000000000
--- a/testing/api/profiles/updated_profile.json
+++ /dev/null
@@ -1,41 +0,0 @@
-{
-    "name": "New Profile",
-    "rename": "Updated Profile",
-    "status": "Draft",
-    "questions": [
-      {
-        "question": "How will this device be used at Google?",
-        "answer": "Monitoring"
-      },
-      {
-        "question": "Is this device going to be managed by Google or a third party?",
-        "answer": "Google"
-      },
-      {
-        "question": "Will the third-party device administrator be able to grant access to authorized Google personnel upon request?",
-        "answer": "N/A"
-      },
-      {
-        "question": "Which of the following statements are true about this device?",
-        "answer": []
-      },
-      {
-        "question": "Does the network protocol assure server-to-client identity verification?",
-        "answer": "Yes"
-      },
-      {
-        "question": "Click the statements that best describe the characteristics of this device.",
-        "answer": []
-      },
-      {
-        "question": "Are any of the following statements true about this device?",
-        "answer": [
-          0
-        ]
-      },
-      {
-        "question": "Comments",
-        "answer": ""
-      }
-    ]
-}
\ No newline at end of file
diff --git a/testing/api/test_api.py b/testing/api/test_api.py
index 614b0af7f..0afdbb8de 100644
--- a/testing/api/test_api.py
+++ b/testing/api/test_api.py
@@ -29,7 +29,6 @@
 import pytest
 import requests
 
-ALL_DEVICES = "*"
 API = "http://127.0.0.1:8000"
 LOG_PATH = "/tmp/testrun.log"
 TEST_SITE_DIR = ".."
@@ -105,31 +104,97 @@ def docker_logs(device_name):
 
 def load_json(file_name, directory):
   """Utility method to load json files' """
+
   # Construct the base path relative to the main folder
   base_path = os.path.abspath(os.path.join(__file__, "../../.."))
+
   # Construct the full file path
   file_path = os.path.join(base_path, directory, file_name)
 
   # Open the file in read mode
   with open(file_path, "r", encoding="utf-8") as file:
+
     # Return the file content
     return json.load(file)
 
+def local_delete_devices():
+  """ Deletes all devices from local/devices directory"""
+
+  try:
+
+    # Check if the profile_path (local/devices) exists and is a folder
+    if os.path.exists(DEVICES_DIRECTORY) and os.path.isdir(DEVICES_DIRECTORY):
+
+      # Iterate over all devices from devices folder
+      for item in os.listdir(DEVICES_DIRECTORY):
+
+        # Create the full path
+        item_path = os.path.join(DEVICES_DIRECTORY, item)
+
+        # Check if item is a file
+        if os.path.isfile(item_path):
+
+          #If True remove file
+          os.unlink(item_path)
+
+        else:
+
+          # If item is a folder remove it
+          shutil.rmtree(item_path)
+
+  except PermissionError:
+
+    # Permission related issues
+    print(f"Permission Denied: {item}")
+
+  except OSError as err:
+
+    # System related issues
+    print(f"Error removing {item}: {err}")
+
+def local_get_devices():
+  """ Returns path to 'device_config.json' for all devices from local/devices"""
+
+  # List to store the paths of all 'device_config.json' files
+  device_configs = []
+
+  # Loop through each file/folder from 'local/devices'.
+  for device_folder in os.listdir(DEVICES_DIRECTORY):
+
+    # Construct the full path for the file/folder
+    device_path = os.path.join(DEVICES_DIRECTORY, device_folder)
+
+    # Check if the current path is a folder
+    if os.path.isdir(device_path):
+
+      # Construct the full path to 'device_config.json' inside the folder.
+      config_path = os.path.join(device_path, "device_config.json")
+
+      # Check if 'device_config.json' exists in the path.
+      if os.path.exists(config_path):
+
+        # Append the file path to the list.
+        device_configs.append(config_path)
+
+  # Return all the device_config.json paths
+  return device_configs
+
 @pytest.fixture
 def empty_devices_dir():
   """ Fixture to empty devices directory """
+
   # Empty the directory before the test
-  local_delete_devices(ALL_DEVICES)
+  local_delete_devices()
 
   yield
 
   # Empty the directory after the test
-  local_delete_devices(ALL_DEVICES)
+  local_delete_devices()
 
 @pytest.fixture
 def testing_devices():
   """ Use devices from the testing/device_configs directory """
-  local_delete_devices(ALL_DEVICES)
+  local_delete_devices()
   shutil.copytree(
       os.path.join(os.path.dirname(__file__), TESTING_DEVICES),
       os.path.join(DEVICES_DIRECTORY),
@@ -223,23 +288,6 @@ def get_network_interfaces():
       ifaces.append(i.stem)
   return ifaces
 
-def local_delete_devices(path):
-  """ Deletes all local devices 
-  """
-  for thing in Path(DEVICES_DIRECTORY).glob(path):
-    if thing.is_file():
-      thing.unlink()
-    else:
-      shutil.rmtree(thing)
-
-def local_get_devices():
-  """ Returns path to device configs of devices in local/devices directory"""
-  return sorted(
-      Path(DEVICES_DIRECTORY).glob(
-          "*/device_config.json"
-      )
-  )
-
 # Tests for system endpoints
 
 @pytest.fixture()
@@ -1147,12 +1195,13 @@ def test_export_report_not_found(empty_devices_dir, testrun, add_device): # pyli
   # Check if the correct error message is returned
   assert "Report could not be found" in response["error"]
 
-def test_export_report_with_profile(empty_devices_dir, testrun, add_device, # pylint: disable=W0613
-                        create_report_folder, reset_profiles, add_profile): # pylint: disable=W0613
+def test_export_report_with_profile(empty_devices_dir, empty_profiles_dir,  # pylint: disable=W0613
+                                      add_one_profile, testrun, add_device, # pylint: disable=W0613
+                        create_report_folder): # pylint: disable=W0613
   """Test export results with existing profile when report exists (200)"""
 
-  # Create a profile using the add_profile fixture
-  profile = add_profile("new_profile.json")
+  # Load the profile using load_json utility method
+  profile = load_json("new_profile_1.json", directory=PROFILES_PATH)
 
   # Load the device_name and mac_address from add_device fixture
   device_name, mac_address = add_device()
@@ -2058,27 +2107,53 @@ def test_delete_certificate_not_found(testrun, reset_certs): # pylint: disable=W
 
 # Tests for profile endpoints
 
-def delete_all_profiles():
-  """Utility method to delete all profiles from risk_profiles folder"""
+@pytest.fixture()
+def add_one_profile():
+  """Fixture to create one profile during tests"""
+
+  # Construct full path of the profile from 'testing/api/profiles' folder
+  source_path = os.path.join(PROFILES_PATH, "new_profile_1.json")
+
+  # Construct full path where the profile will be copied
+  target_path = os.path.join(PROFILES_DIRECTORY, "new_profile_1.json")
+
+  # Copy the profile from 'testing/api/profiles' to 'local/risk_profiles'
+  shutil.copy(source_path, target_path)
+
+@pytest.fixture()
+def add_two_profiles():
+  """Fixture to create two profiles during tests"""
+
+  # Iterate over the files from 'testing/api/profiles' folder
+  for file in os.listdir(PROFILES_PATH):
+
+    # Construct full path of the file_name from 'testing/api/profiles' folder
+    source_path = os.path.join(PROFILES_PATH, file)
 
-  # Assign the profiles directory
-  profiles_path = os.path.join(PROFILES_DIRECTORY)
+    # Construct full path where the file_name will be copied
+    target_path = os.path.join(PROFILES_DIRECTORY, file)
+
+    # Copy the file_name from 'testing/api/profiles' to 'local/risk_profiles'
+    shutil.copy(source_path, target_path)
+
+def delete_all_profiles():
+  """Utility method to delete all profiles from local/risk_profiles"""
 
   try:
 
     # Check if the profile_path (local/risk_profiles) exists and is a folder
-    if os.path.exists(profiles_path) and os.path.isdir(profiles_path):
+    if os.path.exists(PROFILES_DIRECTORY) and os.path.isdir(PROFILES_DIRECTORY):
 
       # Iterate over all profiles from risk_profiles folder
-      for item in os.listdir(profiles_path):
+      for item in os.listdir(PROFILES_DIRECTORY):
 
         # Create the full path
-        item_path = os.path.join(profiles_path, item)
+        item_path = os.path.join(PROFILES_DIRECTORY, item)
 
         # Check if item is a file
         if os.path.isfile(item_path):
 
-          #If True remove file
+          # Remove file
           os.unlink(item_path)
 
         else:
@@ -2096,31 +2171,8 @@ def delete_all_profiles():
     # System related issues
     print(f"Error removing {item}: {err}")
 
-def create_profile(file_name):
-  """Utility method to create the profile"""
-
-  # Load the profile
-  new_profile = load_json(file_name, directory=PROFILES_PATH)
-
-  # Assign the profile name to profile_name
-  profile_name = new_profile["name"]
-
-  # Exception if the profile already exists
-  if profile_exists(profile_name):
-    raise ValueError(f"Profile: {profile_name} exists")
-
-  # Send the post request
-  r = requests.post(f"{API}/profiles", data=json.dumps(new_profile), timeout=5)
-
-  # Exception if status code is not 201
-  if r.status_code != 201:
-    raise ValueError(f"API request failed with code: {r.status_code}")
-
-  # Return the profile
-  return new_profile
-
 @pytest.fixture()
-def reset_profiles():
+def empty_profiles_dir():
   """Delete the profiles before and after each test"""
 
   # Delete before the test
@@ -2131,13 +2183,6 @@ def reset_profiles():
   # Delete after the test
   delete_all_profiles()
 
-@pytest.fixture()
-def add_profile():
-  """Fixture to create profiles during tests"""
-
-  # Returning the reference to create_profile
-  return create_profile
-
 def profile_exists(profile_name):
   """Utility method to check if profile exists"""
 
@@ -2174,10 +2219,8 @@ def test_get_profiles_format(testrun): # pylint: disable=W0613
     assert "question" in item
     assert "type" in item
 
-def test_get_profiles(testrun, reset_profiles, add_profile): # pylint: disable=W0613
-  """Test for get profiles (no profile, one profile, two profiles)"""
-
-  # Test for no profile
+def test_get_profiles_no_profiles(empty_profiles_dir, testrun): # pylint: disable=W0613
+  """Test for get profiles when no profiles created (200)"""
 
   # Send the get request to "/profiles" endpoint
   r = requests.get(f"{API}/profiles", timeout=5)
@@ -2194,10 +2237,8 @@ def test_get_profiles(testrun, reset_profiles, add_profile): # pylint: disable=W
   # Check if the list is empty
   assert len(response) == 0
 
-  # Test for one profile
-
-  # Load the profile using add_profile fixture
-  add_profile("new_profile.json")
+def test_get_profiles_one_profile(empty_profiles_dir, add_one_profile, testrun): # pylint: disable=W0613
+  """Test for get profiles when one profile is created (200)"""
 
   # Send get request to the "/profiles" endpoint
   r = requests.get(f"{API}/profiles", timeout=5)
@@ -2237,10 +2278,9 @@ def test_get_profiles(testrun, reset_profiles, add_profile): # pylint: disable=W
       # Check if "asnswer" key is in dict element
       assert "answer" in element
 
-  # Test for two profiles
-
-  # Load the profile using add_profile fixture
-  add_profile("new_profile_2.json")
+def test_get_profiles_two_profiles(empty_profiles_dir, add_two_profiles, # pylint: disable=W0613
+                                   testrun): # pylint: disable=W0613
+  """Test for get profiles when two profiles are created (200)"""
 
   # Send the get request to "/profiles" endpoint
   r = requests.get(f"{API}/profiles", timeout=5)
@@ -2257,11 +2297,11 @@ def test_get_profiles(testrun, reset_profiles, add_profile): # pylint: disable=W
   # Check if response contains two profiles
   assert len(response) == 2
 
-def test_create_profile(testrun, reset_profiles): # pylint: disable=W0613
-  """Test for create profile when profile does not exist"""
+def test_create_profile(testrun): # pylint: disable=W0613
+  """Test for create profile when profile does not exist (201)"""
 
   # Load the profile
-  new_profile = load_json("new_profile.json", directory=PROFILES_PATH)
+  new_profile = load_json("new_profile_1.json", directory=PROFILES_PATH)
 
   # Assign the profile name to profile_name
   profile_name = new_profile["name"]
@@ -2299,25 +2339,28 @@ def test_create_profile(testrun, reset_profiles): # pylint: disable=W0613
   # Check if profile was created
   assert created_profile is not None
 
-def test_update_profile(testrun, reset_profiles, add_profile): # pylint: disable=W0613
-  """Test for update profile when profile already exists"""
-
-  # Load the new profile using add_profile fixture
-  new_profile = add_profile("new_profile.json")
+def test_update_profile(empty_profiles_dir, add_one_profile, testrun): # pylint: disable=W0613
+  """Test for update profile when profile already exists (200)"""
 
-  # Load the updated profile using load_json utility method
-  updated_profile = load_json("updated_profile.json",
-                              directory=PROFILES_PATH)
+  # Load the new profile using load_json fixture
+  new_profile = load_json("new_profile_1.json", directory=PROFILES_PATH)
 
   # Assign the new_profile name
   profile_name = new_profile["name"]
 
   # Assign the updated_profile name
-  updated_profile_name = updated_profile["rename"]
+  updated_profile_name = "updated_profile_1"
 
-  # Exception if the profile does't exists
+  # Payload with the updated device name
+  updated_profile = {
+    "name": profile_name,
+    "rename" : updated_profile_name,
+    "questions": new_profile["questions"]   
+    }
+
+  # Exception if the profile does not exists
   if not profile_exists(profile_name):
-    raise ValueError(f"Profile: {profile_name} exists")
+    raise ValueError(f"Profile: {profile_name} does not exists")
 
   # Send the post request to update the profile
   r = requests.post(
@@ -2351,11 +2394,9 @@ def test_update_profile(testrun, reset_profiles, add_profile): # pylint: disable
   # Check if profile was updated
   assert updated_profile_check is not None
 
-def test_update_profile_invalid_json(testrun, reset_profiles, add_profile): # pylint: disable=W0613
-  """Test for update profile invalid JSON payload (no 'name')"""
-
-  # Load the new profile using add_profile fixture
-  add_profile("new_profile.json")
+def test_update_profile_invalid_json(empty_profiles_dir, add_one_profile, # pylint: disable=W0613
+                                     testrun): # pylint: disable=W0613
+  """Test for update profile invalid JSON payload (400)"""
 
   # Invalid JSON
   updated_profile = {}
@@ -2375,8 +2416,8 @@ def test_update_profile_invalid_json(testrun, reset_profiles, add_profile): # py
   # Check if "error" key in response
   assert "error" in response
 
-def test_create_profile_invalid_json(testrun, reset_profiles): # pylint: disable=W0613
-  """Test for create profile invalid JSON payload """
+def test_create_profile_invalid_json(empty_profiles_dir, testrun): # pylint: disable=W0613
+  """Test for create profile invalid JSON payload (400) """
 
   # Invalid JSON
   new_profile = {}
@@ -2396,11 +2437,11 @@ def test_create_profile_invalid_json(testrun, reset_profiles): # pylint: disable
   # Check if "error" key in response
   assert "error" in response
 
-def test_delete_profile(testrun, reset_profiles, add_profile): # pylint: disable=W0613
-  """Test for delete profile"""
+def test_delete_profile(empty_profiles_dir, add_one_profile, testrun): # pylint: disable=W0613
+  """Test for successfully delete profile (200)"""
 
-  # Assign the profile from the fixture
-  profile_to_delete = add_profile("new_profile.json")
+  # Load the profile using load_json utility method
+  profile_to_delete = load_json("new_profile_1.json", directory=PROFILES_PATH)
 
   # Assign the profile name
   profile_name = profile_to_delete["name"]
@@ -2438,8 +2479,8 @@ def test_delete_profile(testrun, reset_profiles, add_profile): # pylint: disable
   # Check if profile was deleted
   assert deleted_profile is None
 
-def test_delete_profile_no_profile(testrun, reset_profiles): # pylint: disable=W0613
-  """Test delete profile if the profile does not exists"""
+def test_delete_profile_no_profile(empty_profiles_dir, testrun): # pylint: disable=W0613
+  """Test delete profile if the profile does not exists (404)"""
 
   # Assign the profile to delete
   profile_to_delete = {"name": "New Profile"}
@@ -2453,8 +2494,8 @@ def test_delete_profile_no_profile(testrun, reset_profiles): # pylint: disable=W
   # Check if status code is 404 (Profile does not exist)
   assert r.status_code == 404
 
-def test_delete_profile_invalid_json(testrun, reset_profiles): # pylint: disable=W0613
-  """Test for delete profile wrong JSON payload"""
+def test_delete_profile_invalid_json(empty_profiles_dir, testrun): # pylint: disable=W0613
+  """Test for delete profile invalid JSON payload (400)"""
 
   # Invalid payload
   profile_to_delete = {}
@@ -2492,12 +2533,12 @@ def test_delete_profile_invalid_json(testrun, reset_profiles): # pylint: disable
   # Check if "error" key in response
   assert "error" in response
 
-def test_delete_profile_server_error(testrun, reset_profiles, # pylint: disable=W0613
-                                     add_profile):
-  """Test for delete profile causing internal server error"""
+def test_delete_profile_server_error(empty_profiles_dir, add_one_profile, # pylint: disable=W0613
+                                     testrun): # pylint: disable=W0613
+  """Test for delete profile causing internal server error (500)"""
 
   # Assign the profile from the fixture
-  profile_to_delete = add_profile("new_profile.json")
+  profile_to_delete = load_json("new_profile_1.json", directory=PROFILES_PATH)
 
   # Assign the profile name to profile_name
   profile_name = profile_to_delete["name"]

From 666bea4d34c35e2753504dd3231920fe84a4385b Mon Sep 17 00:00:00 2001
From: MariusBaldovin <baldovin@google.com>
Date: Thu, 5 Sep 2024 15:39:51 +0100
Subject: [PATCH 06/25] updates

---
 .../device_config.json}                       |   0
 .../device_config.json}                       |   0
 testing/api/test_api.py                       | 462 +++++++++++-------
 3 files changed, 292 insertions(+), 170 deletions(-)
 rename testing/api/devices/{device_1.json => device_1/device_config.json} (100%)
 rename testing/api/devices/{device_2.json => device_2/device_config.json} (100%)

diff --git a/testing/api/devices/device_1.json b/testing/api/devices/device_1/device_config.json
similarity index 100%
rename from testing/api/devices/device_1.json
rename to testing/api/devices/device_1/device_config.json
diff --git a/testing/api/devices/device_2.json b/testing/api/devices/device_2/device_config.json
similarity index 100%
rename from testing/api/devices/device_2.json
rename to testing/api/devices/device_2/device_config.json
diff --git a/testing/api/test_api.py b/testing/api/test_api.py
index 0afdbb8de..788dea445 100644
--- a/testing/api/test_api.py
+++ b/testing/api/test_api.py
@@ -44,6 +44,8 @@
 PROFILES_PATH = "testing/api/profiles"
 REPORTS_PATH = "testing/api/reports"
 DEVICES_PATH = "testing/api/devices"
+DEVICE_1_PATH = "testing/api/devices/device_1"
+DEVICE_2_PATH = "testing/api/devices/device_2"
 
 BASELINE_MAC_ADDR = "02:42:aa:00:01:01"
 ALL_MAC_ADDR = "02:42:aa:00:00:01"
@@ -117,12 +119,12 @@ def load_json(file_name, directory):
     # Return the file content
     return json.load(file)
 
-def local_delete_devices():
-  """ Deletes all devices from local/devices directory"""
+def delete_all_devices():
+  """Utility method to delete all devices from local/devices"""
 
   try:
 
-    # Check if the profile_path (local/devices) exists and is a folder
+    # Check if the device_path (local/devices) exists and is a folder
     if os.path.exists(DEVICES_DIRECTORY) and os.path.isdir(DEVICES_DIRECTORY):
 
       # Iterate over all devices from devices folder
@@ -134,7 +136,7 @@ def local_delete_devices():
         # Check if item is a file
         if os.path.isfile(item_path):
 
-          #If True remove file
+          # Remove file
           os.unlink(item_path)
 
         else:
@@ -184,17 +186,17 @@ def empty_devices_dir():
   """ Fixture to empty devices directory """
 
   # Empty the directory before the test
-  local_delete_devices()
+  delete_all_devices()
 
   yield
 
   # Empty the directory after the test
-  local_delete_devices()
+  delete_all_devices()
 
 @pytest.fixture
 def testing_devices():
   """ Use devices from the testing/device_configs directory """
-  local_delete_devices()
+  delete_all_devices()
   shutil.copytree(
       os.path.join(os.path.dirname(__file__), TESTING_DEVICES),
       os.path.join(DEVICES_DIRECTORY),
@@ -316,6 +318,7 @@ def test_get_system_interfaces(testrun): # pylint: disable=W0613
 
   # Check if the key are in the response
   assert set(response.keys()) == set(local_interfaces)
+
   # Ensure that all values in the response are strings
   assert all(isinstance(x, str) for x in response)
 
@@ -411,23 +414,26 @@ def test_get_system_config(testrun): # pylint: disable=W0613
       == api_config["network"]["internet_intf"]
   )
 
-def test_start_testrun_success(empty_devices_dir, testrun, add_device): # pylint: disable=W0613
-  """Test for testrun started successfully """
+def test_start_testrun_success(empty_devices_dir, add_one_device, testrun): # pylint: disable=W0613
+  """Test for testrun started successfully (200) """
+
+  # Load the device using load_json utility method
+  device = load_json("device_config.json", directory=DEVICE_1_PATH)
+
+  # Assign the device mac address
+  mac_address = device["mac_addr"]
 
-  _, mac_address = add_device()
+  # Assign device modules
+  device_modules = device["test_modules"]
 
   # Payload with device details
-  payload = {"device": {
-    "mac_addr": mac_address,
-    "firmware": "test",
-    "test_modules": {
-        "protocol": { "enabled": True },
-        "services": { "enabled": True },
-        "ntp": { "enabled": True },
-        "tls": { "enabled": True },
-        "connection": { "enabled": True },
-        "dns": { "enabled": True }
-    }}}
+  payload = {
+    "device": {
+      "mac_addr": mac_address,
+      "firmware": "test",
+      "test_modules": device_modules
+    }
+  }
 
   # Send the post request
   r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10)
@@ -465,23 +471,27 @@ def test_start_testrun_missing_device(testing_devices, testrun): # pylint: disab
   # Check if 'error' in response
   assert "error" in response
 
-def test_start_testrun_already_started(empty_devices_dir, testrun, add_device): # pylint: disable=W0613
-  """Test for testrun already started """
+def test_start_testrun_already_started(empty_devices_dir, add_one_device, # pylint: disable=W0613
+                                                                testrun): # pylint: disable=W0613
+  """Test for testrun already started (409) """
+
+  # Load the device using load_json utility method
+  device = load_json("device_config.json", directory=DEVICE_1_PATH)
 
-  _, mac_address = add_device()
+  # Assign the device mac address
+  mac_address = device["mac_addr"]
+
+  # Assign the test modules
+  test_modules = device["test_modules"]
 
   # Payload with device details
-  payload = {"device": {
-    "mac_addr": mac_address,
-    "firmware": "test",
-    "test_modules": {
-        "protocol": { "enabled": True },
-        "services": { "enabled": True },
-        "ntp": { "enabled": True },
-        "tls": { "enabled": True },
-        "connection": { "enabled": True },
-        "dns": { "enabled": True }
-    }}}
+  payload = {
+    "device": {
+      "mac_addr": mac_address,
+      "firmware": "test",
+      "test_modules": test_modules
+    }
+  }
 
   # Send the post request (start test)
   r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10)
@@ -742,23 +752,27 @@ def test_system_shutdown(testrun): # pylint: disable=W0613
   # Check if the response status code is 200 (OK)
   assert r.status_code == 200, f"Expected status code 200, got {r.status_code}"
 
-def test_system_shutdown_in_progress(empty_devices_dir, testrun, add_device): # pylint: disable=W0613
+def test_system_shutdown_in_progress(empty_devices_dir, add_one_device, # pylint: disable=W0613
+                                     testrun): # pylint: disable=W0613
   """Test system shutdown during an in-progress test"""
 
-  _, mac_address = add_device()
+  # Load the device using load_json utility method
+  device = load_json("device_config.json", directory=DEVICE_1_PATH)
 
-  # Payload with device details to start a test
-  payload = {"device": {
-    "mac_addr": mac_address,
-    "firmware": "test",
-    "test_modules": {
-        "protocol": { "enabled": True },
-        "services": { "enabled": True },
-        "ntp": { "enabled": True },
-        "tls": { "enabled": True },
-        "connection": { "enabled": True },
-        "dns": { "enabled": True }
-    }}}
+  # Assign the device mac address
+  mac_address = device["mac_addr"]
+
+  # Assign the test modules
+  test_modules = device["test_modules"]
+
+  # Payload with device details
+  payload = {
+    "device": {
+      "mac_addr": mac_address,
+      "firmware": "test",
+      "test_modules": test_modules
+    }
+  }
 
   # Start a test
   r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10)
@@ -841,34 +855,6 @@ def _create_report_folder(device_name, mac_address, timestamp):
 
   return _create_report_folder
 
-def create_and_get_device():
-  """Utility method to create a device"""
-
-  # Payload with device details
-  device = load_json("device_1.json", directory=DEVICES_PATH)
-
-   # Send a POST request to create a new device
-  requests.post(f"{API}/device", data=json.dumps(device), timeout=5)
-
-  # Send the GET request to retrieve the created device's folder name
-  r = requests.get(f"{API}/devices", timeout=5)
-
-  # Parse the json response
-  response = r.json()
-
-  # Extract the device name and MAC address from response
-  device_name = f'{response[0]["manufacturer"]} {response[0]["model"]}'
-  mac_address = response[0]["mac_addr"]
-
-  # Return only the device name and MAC address
-  return device_name, mac_address
-
-@pytest.fixture()
-def add_device():
-  """Fixture to add device during tests"""
-  # Returning the reference to create_device
-  return create_and_get_device
-
 def test_get_reports_no_reports(testrun): # pylint: disable=W0613
   """Test get reports when no reports exist"""
 
@@ -887,14 +873,20 @@ def test_get_reports_no_reports(testrun): # pylint: disable=W0613
   # Check if the response is an empty list
   assert response == []
 
-def test_delete_report_success(empty_devices_dir, testrun, # pylint: disable=W0613
-                        add_device, create_report_folder):
+def test_delete_report_success(empty_devices_dir, add_one_device, # pylint: disable=W0613
+                                  create_report_folder, testrun): # pylint: disable=W0613
   """Test for succesfully delete a report (200)"""
 
   r = requests.get(f"{API}/devices", timeout=5)
 
-  # Load the device_name and mac_address from add_device fixture
-  device_name, mac_address = add_device()
+  # Load the device using load_json utility method
+  device = load_json("device_config.json", directory=DEVICE_1_PATH)
+
+  # Assign the device mac address
+  mac_address = device["mac_addr"]
+
+  # Assign the device name
+  device_name = f'{device["manufacturer"]} {device["model"]}'
 
   # Create the report directory
   report_folder = create_report_folder(device_name, mac_address, TIMESTAMP)
@@ -920,8 +912,8 @@ def test_delete_report_success(empty_devices_dir, testrun, # pylint: disable=W06
   # Check if report folder has been deleted
   assert not os.path.exists(report_folder)
 
-def test_delete_report_no_payload(empty_devices_dir, testrun, # pylint: disable=W0613
-                           add_device, create_report_folder): # pylint: disable=W0613
+def test_delete_report_no_payload(empty_devices_dir, add_one_device, # pylint: disable=W0613
+                           create_report_folder, testrun): # pylint: disable=W0613
   """Test delete report bad request when the payload is missing (400)"""
 
   # Send a DELETE request to remove the report without the payload
@@ -939,12 +931,18 @@ def test_delete_report_no_payload(empty_devices_dir, testrun, # pylint: disable=
   # Check if the correct error message returned
   assert "Invalid request received, missing body" in response["error"]
 
-def test_delete_report_invalid_payload(empty_devices_dir, testrun, # pylint: disable=W0613
-                                add_device, create_report_folder):
+def test_delete_report_invalid_payload(empty_devices_dir, add_one_device, # pylint: disable=W0613
+                                          create_report_folder, testrun): # pylint: disable=W0613
   """Test delete report bad request, mac addr and timestamp are missing (400)"""
 
-  # Load the device_name and mac_address from add_device fixture
-  device_name, mac_address = add_device()
+  # Load the device using load_json utility method
+  device = load_json("device_config.json", directory=DEVICE_1_PATH)
+
+  # Assign the device mac address
+  mac_address = device["mac_addr"]
+
+  # Assign the device name
+  device_name = f'{device["manufacturer"]} {device["model"]}'
 
   # Create the report directory
   create_report_folder(device_name, mac_address, TIMESTAMP)
@@ -967,12 +965,18 @@ def test_delete_report_invalid_payload(empty_devices_dir, testrun, # pylint: dis
   # Check if the correct error message returned
   assert "Missing mac address or timestamp" in response["error"]
 
-def test_delete_report_invalid_timestamp(empty_devices_dir, testrun, # pylint: disable=W0613
-                                  add_device, create_report_folder):
+def test_delete_report_invalid_timestamp(empty_devices_dir, add_one_device, # pylint: disable=W0613
+                                            create_report_folder, testrun): # pylint: disable=W0613
   """Test delete report bad request when timestamp format is not valid (400)"""
 
-  # Load the device_name and mac_address from add_device fixture
-  device_name, mac_address = add_device()
+  # Load the device using load_json utility method
+  device = load_json("device_config.json", directory=DEVICE_1_PATH)
+
+  # Assign the device mac address
+  mac_address = device["mac_addr"]
+
+  # Assign the device name
+  device_name = f'{device["manufacturer"]} {device["model"]}'
 
   # Assign the incorrect timestamp format
   timestamp = "2024-01-01 invalid"
@@ -1025,11 +1029,14 @@ def test_delete_report_no_device(empty_devices_dir, testrun): # pylint: disable=
   # Check if the correct error message returned
   assert "Could not find device" in response["error"]
 
-def test_delete_report_no_report(empty_devices_dir, testrun, add_device): # pylint: disable=W0613
+def test_delete_report_no_report(empty_devices_dir, testrun, add_one_device): # pylint: disable=W0613
   """Test for delete report when report does not exist (404)"""
 
-  # Load the device_name and mac_address from add_device fixture
-  _, mac_address = add_device()
+  # Load the device using load_json utility method
+  device = load_json("device_config.json", directory=DEVICE_1_PATH)
+
+  # Assign the device mac address
+  mac_address = device["mac_addr"]
 
   # Prepare the payload for the DELETE request
   delete_data = {
@@ -1055,11 +1062,17 @@ def test_delete_report_no_report(empty_devices_dir, testrun, add_device): # pyli
   assert "Report not found" in response["error"]
 
 def test_get_report_success(empty_devices_dir, testrun, # pylint: disable=W0613
-                     add_device, create_report_folder):
+                     add_one_device, create_report_folder):
   """Test get report when report exists (200)"""
 
-  # Load the device_name and mac_address from add_device fixture
-  device_name, mac_address = add_device()
+  # Load the device using load_json utility method
+  device = load_json("device_config.json", directory=DEVICE_1_PATH)
+
+  # Assign the device mac address
+  mac_address = device["mac_addr"]
+
+  # Assign the device name
+  device_name = f'{device["manufacturer"]} {device["model"]}'
 
   # Assign the timestamp and change the format
   timestamp = TIMESTAMP.replace(" ", "T")
@@ -1076,11 +1089,14 @@ def test_get_report_success(empty_devices_dir, testrun, # pylint: disable=W0613
   # Check if the response is a PDF
   assert r.headers["Content-Type"] == "application/pdf"
 
-def test_get_report_not_found(empty_devices_dir, testrun, add_device): # pylint: disable=W0613
+def test_get_report_not_found(empty_devices_dir, testrun, add_one_device): # pylint: disable=W0613
   """Test get report when report doesn't exist (404)"""
 
-  # Load the device_name and ignore mac_address from add_device fixture
-  device_name, _ = add_device()
+  # Load the device using load_json utility method
+  device = load_json("device_config.json", directory=DEVICE_1_PATH)
+
+  # Assign the device name
+  device_name = f'{device["manufacturer"]} {device["model"]}'
 
   # Send the get request
   r = requests.get(f"{API}/report/{device_name}/{TIMESTAMP}", timeout=5)
@@ -1145,11 +1161,17 @@ def test_export_report_device_not_found(empty_devices_dir, testrun, # pylint: di
   assert "A device with that name could not be found" in response["error"]
 
 def test_export_report_profile_not_found(empty_devices_dir, testrun, # pylint: disable=W0613
-                            add_device, create_report_folder):
+                            add_one_device, create_report_folder):
   """Test for export report result when the profile is not found"""
 
-  # Load the device_name and mac_address from add_device fixture
-  device_name, mac_address = add_device()
+  # Load the device using load_json utility method
+  device = load_json("device_config.json", directory=DEVICE_1_PATH)
+
+  # Assign the device mac address
+  mac_address = device["mac_addr"]
+
+  # Assign the device name
+  device_name = f'{device["manufacturer"]} {device["model"]}'
 
   # Create the report for the device
   create_report_folder(device_name, mac_address, TIMESTAMP)
@@ -1174,11 +1196,14 @@ def test_export_report_profile_not_found(empty_devices_dir, testrun, # pylint: d
   # Check if the correct error message returned
   assert "A profile with that name could not be found" in response["error"]
 
-def test_export_report_not_found(empty_devices_dir, testrun, add_device): # pylint: disable=W0613
+def test_export_report_not_found(empty_devices_dir, testrun, add_one_device): # pylint: disable=W0613
   """Test for export the report result when the report could not be found"""
 
-  # Load the device_name and ignore mac_address from add_device fixture
-  device_name, _ = add_device()
+  # Load the device using load_json utility method
+  device = load_json("device_config.json", directory=DEVICE_1_PATH)
+
+  # Assign the device name
+  device_name = f'{device["manufacturer"]} {device["model"]}'
 
   # Send the post request to trigger the zipping process
   r = requests.post(f"{API}/export/{device_name}/{TIMESTAMP}", timeout=10)
@@ -1195,16 +1220,22 @@ def test_export_report_not_found(empty_devices_dir, testrun, add_device): # pyli
   # Check if the correct error message is returned
   assert "Report could not be found" in response["error"]
 
-def test_export_report_with_profile(empty_devices_dir, empty_profiles_dir,  # pylint: disable=W0613
-                                      add_one_profile, testrun, add_device, # pylint: disable=W0613
-                        create_report_folder): # pylint: disable=W0613
+def test_export_report_with_profile(empty_devices_dir, add_one_device, # pylint: disable=W0613
+                                  empty_profiles_dir, add_one_profile, # pylint: disable=W0613
+                                       create_report_folder, testrun): # pylint: disable=W0613
   """Test export results with existing profile when report exists (200)"""
 
   # Load the profile using load_json utility method
-  profile = load_json("new_profile_1.json", directory=PROFILES_PATH)
+  profile = load_json("profile_1.json", directory=PROFILES_PATH)
+
+  # Load the device using load_json utility method
+  device = load_json("device_config.json", directory=DEVICE_1_PATH)
+
+  # Assign the device mac address
+  mac_address = device["mac_addr"]
 
-  # Load the device_name and mac_address from add_device fixture
-  device_name, mac_address = add_device()
+  # Assign the device name
+  device_name = f'{device["manufacturer"]} {device["model"]}'
 
   # Assign the timestamp and change the format
   timestamp = TIMESTAMP.replace(" ", "T")
@@ -1223,12 +1254,18 @@ def test_export_report_with_profile(empty_devices_dir, empty_profiles_dir,  # py
   # Check if the response is a zip file
   assert r.headers["Content-Type"] == "application/zip"
 
-def test_export_results_with_no_profile(empty_devices_dir, testrun, # pylint: disable=W0613
-                                        add_device, create_report_folder):
+def test_export_results_with_no_profile(empty_devices_dir, add_one_device, # pylint: disable=W0613
+                                         create_report_folder, testrun): # pylint: disable=W0613
   """Test export results with no profile when report exists (200)"""
 
-  # Load the device_name and mac_address from add_device fixture
-  device_name, mac_address = add_device()
+  # Load the device using load_json utility method
+  device = load_json("device_config.json", directory=DEVICE_1_PATH)
+
+  # Assign the device name
+  device_name = f'{device["manufacturer"]} {device["model"]}'
+
+  # Assign the device mac address
+  mac_address = device["mac_addr"]
 
   # Assign the timestamp and change the format
   timestamp = TIMESTAMP.replace(" ", "T")
@@ -1247,6 +1284,55 @@ def test_export_results_with_no_profile(empty_devices_dir, testrun, # pylint: di
 
 # Tests for device endpoints
 
+@pytest.fixture()
+def add_one_device():
+  """Fixture to create one device during tests"""
+
+  # Load the device configurations using load_json utility method
+  device = load_json("device_config.json", directory=DEVICE_1_PATH)
+
+  # Assign the device name
+  device_name = f'{device["manufacturer"]} {device["model"]}'
+
+  # Construct full path of the device from 'testing/api/devices/device_1'
+  source_path = os.path.join(DEVICE_1_PATH, "device_config.json")
+
+  # Construct full path where the device will be copied
+  target_path = os.path.join(DEVICES_DIRECTORY, device_name)
+
+  # Create the target directory if it doesn't exist
+  os.makedirs(target_path, exist_ok=True)
+
+  # Copy device_config from 'testing/api/devices/device_1' to 'local/devices'
+  shutil.copy(source_path, target_path)
+
+@pytest.fixture()
+def add_two_devices():
+  """Fixture to create two devices during tests"""
+
+  # List of device folders from 'testing/api/devices'
+  devices = ["device_1", "device_2"]
+
+  for device in devices:
+
+    # Construct the full path for the device_config.json
+    device_path = os.path.join(DEVICES_PATH, device)
+
+    # Load the device configurations using load_json utility method
+    device = load_json("device_config.json", directory=device_path)
+
+    # Assign the device name
+    device_name = f'{device["manufacturer"]} {device["model"]}'
+
+    # Construct the source path of the device config file
+    source_path = os.path.join(device_path, "device_config.json")
+
+    # Construct the target path where the profile will be copied
+    target_path = os.path.join(DEVICES_DIRECTORY, device_name)
+
+    # Copy the profile from source to target
+    shutil.copy(source_path, target_path)
+
 @pytest.mark.skip()
 def test_status_non_compliant(testing_devices, testrun): # pylint: disable=W0613
 
@@ -1279,51 +1365,93 @@ def test_status_non_compliant(testing_devices, testrun): # pylint: disable=W0613
 
   stop_test_device("x123")
 
-def test_create_get_devices(empty_devices_dir, testrun): # pylint: disable=W0613
+def test_get_devices_no_devices(empty_devices_dir, testrun): # pylint: disable=W0613
+  """Test for get devices endpoint when no devices are available (200)"""
 
-  device_1 = load_json("device_1.json", directory=DEVICES_PATH)
+  # Check if there are no devices in local/devices
+  if len(local_get_devices()) != 0:
+    raise Exception("Expected no devices in local/devices")
 
-  r = requests.post(f"{API}/device", data=json.dumps(device_1),
-                    timeout=5)
-  print(r.text)
-  assert r.status_code == 201
-  assert len(local_get_devices()) == 1
+  # Send the get request to retrieve all devices
+  r = requests.get(f"{API}/devices", timeout=5)
 
-  device_2 = load_json("device_2.json", directory=DEVICES_PATH)
+  # Check if status code is 200 (Ok)
+  assert r.status_code == 200
 
-  r = requests.post(f"{API}/device", data=json.dumps(device_2),
-                    timeout=5)
-  assert r.status_code == 201
-  assert len(local_get_devices()) == 2
+  # Parse the json response
+  response = r.json()
 
-  # Test that returned devices API endpoint matches expected structure
+  # Check if response is a list
+  assert isinstance(response, list)
+
+  # Check if the list is empty
+  assert len(response) == 0
+
+  # Check if there are no devices in local/devices
+  assert len(local_get_devices()) == 0
+
+def test_get_devices_one_device(empty_devices_dir, add_one_device, testrun): # pylint: disable=W0613
+  """Test for get devices endpoint when one device is created (200)"""
+
+  # Check if there is one device in local/devices
+  if len(local_get_devices()) != 1:
+    raise Exception("Expected one device in local/devices")
+
+  # Send get request to the "/devices" endpoint
   r = requests.get(f"{API}/devices", timeout=5)
-  all_devices = r.json()
-  pretty_print(all_devices)
 
-  with open(
-      os.path.join(os.path.dirname(__file__), "mockito/get_devices.json"),
-      encoding="utf-8"
-  ) as f:
-    mockito = json.load(f)
+  # Check if status code is 200 (OK)
+  assert r.status_code == 200
 
-  print(mockito)
+  # Parse the json response (devices)
+  response = r.json()
 
-  # Validate structure
-  assert all(isinstance(x, dict) for x in all_devices)
+  # Check if response contains one device
+  assert len(response) == 1
 
-  # TOOO uncomment when is done
-  # assert set(dict_paths(mockito[0])) == set(dict_paths(all_devices[0]))
+def test_get_devices_two_devices(empty_devices_dir, add_two_devices, testrun): # pylint: disable=W0613
+  """Test for get devices endpoint when two devices are created (200)"""
 
-  # Validate contents of given keys matches
-  for key in ["mac_addr", "manufacturer", "model"]:
-    assert set([all_devices[0][key], all_devices[1][key]]) == set(
-        [device_1[key], device_2[key]]
-    )
+  # Check if there are two devices in local/devices
+  if len(local_get_devices()) != 2:
+    raise Exception("Expected two devices in local/devices")
+
+  # Send get request to the "/devices" endpoint
+  r = requests.get(f"{API}/devices", timeout=5)
+
+  # Check if status code is 200 (OK)
+  assert r.status_code == 200
+
+  # Parse the response (devices)
+  response = r.json()
+
+  # Check if response contains one device
+  assert len(response) == 2
+
+  # Assign the expected fields from device
+  expected_fields = [
+    "status",
+    "mac_addr",
+    "manufacturer",
+    "model",
+    "type",
+    "technology",
+    "test_pack",
+    "test_modules",
+  ]
+
+  # Iterate over all devices
+  for device in response:
+
+    # Iterate over all expected fields list
+    for field in expected_fields:
+
+      # Check if the device has all the expected fields
+      assert field in device
 
 def test_delete_device_success(empty_devices_dir, testrun): # pylint: disable=W0613
 
-  device_1 = load_json("device_1.json", directory=DEVICES_PATH)
+  device_1 = load_json("device_config.json", directory=DEVICE_1_PATH)
 
   # Send create device request
   r = requests.post(f"{API}/device",
@@ -1335,7 +1463,7 @@ def test_delete_device_success(empty_devices_dir, testrun): # pylint: disable=W0
   assert r.status_code == 201
   assert len(local_get_devices()) == 1
 
-  device_2 = load_json("device_2.json", directory=DEVICES_PATH)
+  device_2 = load_json("device_config.json", directory=DEVICE_2_PATH)
 
   r = requests.post(f"{API}/device",
                     data=json.dumps(device_2),
@@ -1393,7 +1521,7 @@ def test_delete_device_not_found(empty_devices_dir, testrun): # pylint: disable=
 
 def test_delete_device_no_mac(empty_devices_dir, testrun): # pylint: disable=W0613
 
-  device_1 = load_json("device_1.json", directory=DEVICES_PATH)
+  device_1 = load_json("device_config.json", directory=DEVICE_1_PATH)
 
   # Send create device request
   r = requests.post(f"{API}/device",
@@ -1457,7 +1585,7 @@ def test_start_system_not_configured_correctly(
     empty_devices_dir, # pylint: disable=W0613
     testrun): # pylint: disable=W0613
 
-  device_1 = load_json("device_1.json", directory=DEVICES_PATH)
+  device_1 = load_json("device_config.json", directory=DEVICE_1_PATH)
 
   # Send create device request
   r = requests.post(f"{API}/device",
@@ -1475,7 +1603,7 @@ def test_start_system_not_configured_correctly(
 def test_start_device_not_found(empty_devices_dir, # pylint: disable=W0613
                                 testrun): # pylint: disable=W0613
 
-  device_1 = load_json("device_1.json", directory=DEVICES_PATH)
+  device_1 = load_json("device_config.json", directory=DEVICE_1_PATH)
 
   # Send create device request
   r = requests.post(f"{API}/device",
@@ -1497,7 +1625,7 @@ def test_start_missing_device_information(
     empty_devices_dir, # pylint: disable=W0613
     testrun): # pylint: disable=W0613
 
-  device_1 = load_json("device_1.json", directory=DEVICES_PATH)
+  device_1 = load_json("device_config.json", directory=DEVICE_1_PATH)
 
   # Send create device request
   r = requests.post(f"{API}/device",
@@ -1514,7 +1642,7 @@ def test_create_device_already_exists(
     empty_devices_dir, # pylint: disable=W0613
     testrun): # pylint: disable=W0613
 
-  device_1 = load_json("device_1.json", directory=DEVICES_PATH)
+  device_1 = load_json("device_config.json", directory=DEVICE_1_PATH)
 
   r = requests.post(f"{API}/device",
                     data=json.dumps(device_1),
@@ -1599,7 +1727,7 @@ def test_device_edit_device_not_found(
     empty_devices_dir, # pylint: disable=W0613
     testrun): # pylint: disable=W0613
 
-  device_1 = load_json("device_1.json", directory=DEVICES_PATH)
+  device_1 = load_json("device_config.json", directory=DEVICE_1_PATH)
 
   r = requests.post(f"{API}/device",
                     data=json.dumps(device_1),
@@ -1625,7 +1753,7 @@ def test_device_edit_device_incorrect_json_format(
     empty_devices_dir, # pylint: disable=W0613
     testrun): # pylint: disable=W0613
 
-  device_1 = load_json("device_1.json", directory=DEVICES_PATH)
+  device_1 = load_json("device_config.json", directory=DEVICE_1_PATH)
 
   r = requests.post(f"{API}/device",
                     data=json.dumps(device_1),
@@ -1646,7 +1774,7 @@ def test_device_edit_device_with_mac_already_exists(
     empty_devices_dir, # pylint: disable=W0613
     testrun): # pylint: disable=W0613
 
-  device_1 = load_json("device_1.json", directory=DEVICES_PATH)
+  device_1 = load_json("device_config.json", directory=DEVICE_1_PATH)
 
   r = requests.post(f"{API}/device",
                     data=json.dumps(device_1),
@@ -1656,7 +1784,7 @@ def test_device_edit_device_with_mac_already_exists(
 
   assert len(local_get_devices()) == 1
 
-  device_2 = load_json("device_2.json", directory=DEVICES_PATH)
+  device_2 = load_json("device_config.json", directory=DEVICE_2_PATH)
 
   r = requests.post(f"{API}/device",
                     data=json.dumps(device_2),
@@ -1695,7 +1823,7 @@ def test_invalid_path_get(testrun): # pylint: disable=W0613
   assert set(dict_paths(mockito)) == set(dict_paths(response))
 
 def test_create_invalid_chars(empty_devices_dir, testrun): # pylint: disable=W0613
-  # local_delete_devices(ALL_DEVICES)
+  # delete_all_devices(ALL_DEVICES)
   # We must start test run with no devices in local/devices for this test
   # to function as expected
   assert len(local_get_devices()) == 0
@@ -2114,27 +2242,21 @@ def add_one_profile():
   # Construct full path of the profile from 'testing/api/profiles' folder
   source_path = os.path.join(PROFILES_PATH, "new_profile_1.json")
 
-  # Construct full path where the profile will be copied
-  target_path = os.path.join(PROFILES_DIRECTORY, "new_profile_1.json")
-
   # Copy the profile from 'testing/api/profiles' to 'local/risk_profiles'
-  shutil.copy(source_path, target_path)
+  shutil.copy(source_path, PROFILES_DIRECTORY)
 
 @pytest.fixture()
 def add_two_profiles():
   """Fixture to create two profiles during tests"""
 
   # Iterate over the files from 'testing/api/profiles' folder
-  for file in os.listdir(PROFILES_PATH):
-
-    # Construct full path of the file_name from 'testing/api/profiles' folder
-    source_path = os.path.join(PROFILES_PATH, file)
+  for profile in os.listdir(PROFILES_PATH):
 
-    # Construct full path where the file_name will be copied
-    target_path = os.path.join(PROFILES_DIRECTORY, file)
+    # Construct full path of the file from 'testing/api/profiles' folder
+    source_path = os.path.join(PROFILES_PATH, profile)
 
     # Copy the file_name from 'testing/api/profiles' to 'local/risk_profiles'
-    shutil.copy(source_path, target_path)
+    shutil.copy(source_path, PROFILES_DIRECTORY)
 
 def delete_all_profiles():
   """Utility method to delete all profiles from local/risk_profiles"""
@@ -2342,7 +2464,7 @@ def test_create_profile(testrun): # pylint: disable=W0613
 def test_update_profile(empty_profiles_dir, add_one_profile, testrun): # pylint: disable=W0613
   """Test for update profile when profile already exists (200)"""
 
-  # Load the new profile using load_json fixture
+  # Load the profile using load_json utility method
   new_profile = load_json("new_profile_1.json", directory=PROFILES_PATH)
 
   # Assign the new_profile name

From 386af90db9a719d55e28212bdcdc84482c443a61 Mon Sep 17 00:00:00 2001
From: MariusBaldovin <baldovin@google.com>
Date: Thu, 5 Sep 2024 15:47:11 +0100
Subject: [PATCH 07/25] fixed device json

---
 testing/api/devices/device_1.json | 1 -
 testing/api/devices/device_2.json | 1 -
 2 files changed, 2 deletions(-)

diff --git a/testing/api/devices/device_1.json b/testing/api/devices/device_1.json
index f76f5d2e0..3be69a082 100644
--- a/testing/api/devices/device_1.json
+++ b/testing/api/devices/device_1.json
@@ -1,5 +1,4 @@
 {
-  "status": "Valid",
   "mac_addr": "00:1e:42:28:9e:4a",
   "manufacturer": "Teltonika",
   "model": "TRB140",
diff --git a/testing/api/devices/device_2.json b/testing/api/devices/device_2.json
index df8a418fb..177ee23e6 100644
--- a/testing/api/devices/device_2.json
+++ b/testing/api/devices/device_2.json
@@ -1,5 +1,4 @@
 {
-  "status": "Valid",
   "mac_addr": "00:1e:42:35:73:c6",
   "manufacturer": "Google",
   "model": "First",

From 014324cb5830e1da6be9d89a54d39b3062e7eef5 Mon Sep 17 00:00:00 2001
From: Olga Mardvilko <mardvilka@google.com>
Date: Wed, 4 Sep 2024 15:13:44 +0300
Subject: [PATCH 08/25] 362646053: (feat) add program icons and change borders
 in tiles (#745)

---
 modules/ui/src/app/app.component.ts           | 10 ++++
 .../components/callout/callout.component.html |  6 +--
 .../components/callout/callout.component.scss | 10 ++++
 .../callout/callout.component.spec.ts         |  3 +-
 .../components/callout/callout.component.ts   | 16 +++++--
 .../device-item/device-item.component.html    | 20 ++++----
 .../device-item/device-item.component.scss    | 10 ++--
 .../device-item/device-item.component.spec.ts |  6 ++-
 .../device-item/device-item.component.ts      |  8 ++--
 .../pilot-icon/pilot-icon.component.ts        | 10 ----
 .../program-type-con.component.spec.ts        | 46 +++++++++++++++++++
 .../program-type-icon.component.ts            | 39 ++++++++++++++++
 .../qualification-icon.component.ts           | 10 ----
 .../consent-dialog.component.spec.ts          |  8 +++-
 .../version/version.component.spec.ts         |  3 +-
 modules/ui/src/app/model/program-type.ts      | 19 ++++++++
 .../device-qualification-from.component.html  | 11 +++--
 .../device-qualification-from.component.scss  |  4 ++
 .../device-qualification-from.component.ts    |  8 ++--
 modules/ui/src/assets/icons/pilot.svg         | 13 ++++++
 modules/ui/src/assets/icons/qualification.svg |  6 +++
 21 files changed, 209 insertions(+), 57 deletions(-)
 delete mode 100644 modules/ui/src/app/components/pilot-icon/pilot-icon.component.ts
 create mode 100644 modules/ui/src/app/components/program-type-icon/program-type-con.component.spec.ts
 create mode 100644 modules/ui/src/app/components/program-type-icon/program-type-icon.component.ts
 delete mode 100644 modules/ui/src/app/components/qualification-icon/qualification-icon.component.ts
 create mode 100644 modules/ui/src/app/model/program-type.ts
 create mode 100644 modules/ui/src/assets/icons/pilot.svg
 create mode 100644 modules/ui/src/assets/icons/qualification.svg

diff --git a/modules/ui/src/app/app.component.ts b/modules/ui/src/app/app.component.ts
index d2559c926..05dc9e001 100644
--- a/modules/ui/src/app/app.component.ts
+++ b/modules/ui/src/app/app.component.ts
@@ -44,6 +44,8 @@ const TESTRUN_LOGO_URL = '/assets/icons/testrun_logo_small.svg';
 const TESTRUN_LOGO_COLOR_URL = '/assets/icons/testrun_logo_color.svg';
 const CLOSE_URL = '/assets/icons/close.svg';
 const DRAFT_URL = '/assets/icons/draft.svg';
+const PILOT_URL = '/assets/icons/pilot.svg';
+const QUALIFICATION_URL = '/assets/icons/qualification.svg';
 
 @Component({
   selector: 'app-root',
@@ -115,6 +117,14 @@ export class AppComponent {
       'draft',
       this.domSanitizer.bypassSecurityTrustResourceUrl(DRAFT_URL)
     );
+    this.matIconRegistry.addSvgIcon(
+      'pilot',
+      this.domSanitizer.bypassSecurityTrustResourceUrl(PILOT_URL)
+    );
+    this.matIconRegistry.addSvgIcon(
+      'qualification',
+      this.domSanitizer.bypassSecurityTrustResourceUrl(QUALIFICATION_URL)
+    );
   }
 
   get isRiskAssessmentRoute(): boolean {
diff --git a/modules/ui/src/app/components/callout/callout.component.html b/modules/ui/src/app/components/callout/callout.component.html
index 5d8e10d3f..3afe188ed 100644
--- a/modules/ui/src/app/components/callout/callout.component.html
+++ b/modules/ui/src/app/components/callout/callout.component.html
@@ -21,9 +21,9 @@
     color="primary">
     {{ type }}
   </mat-icon>
-  <span *ngIf="type === CalloutType.InfoPilot" class="callout-icon info-pilot"
-    >🚀</span
-  >
+  <span *ngIf="type === CalloutType.InfoPilot" class="callout-icon info-pilot">
+    <app-program-type-icon [type]="ProgramType.Pilot"></app-program-type-icon
+  ></span>
   <p class="callout-context">
     <ng-content></ng-content>
   </p>
diff --git a/modules/ui/src/app/components/callout/callout.component.scss b/modules/ui/src/app/components/callout/callout.component.scss
index 411f49d1f..efce8b8a1 100644
--- a/modules/ui/src/app/components/callout/callout.component.scss
+++ b/modules/ui/src/app/components/callout/callout.component.scss
@@ -31,6 +31,16 @@
   top: 60px;
 }
 
+:host .info-pilot ::ng-deep app-program-type-icon {
+  padding-rigt: 1px;
+
+  .icon {
+    width: 18px;
+    height: 18px;
+    line-height: 18px;
+  }
+}
+
 :host.hidden {
   display: none;
 }
diff --git a/modules/ui/src/app/components/callout/callout.component.spec.ts b/modules/ui/src/app/components/callout/callout.component.spec.ts
index 9d8605275..fab52c949 100644
--- a/modules/ui/src/app/components/callout/callout.component.spec.ts
+++ b/modules/ui/src/app/components/callout/callout.component.spec.ts
@@ -16,6 +16,7 @@
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 
 import { CalloutComponent } from './callout.component';
+import { MatIconTestingModule } from '@angular/material/icon/testing';
 
 describe('CalloutComponent', () => {
   let component: CalloutComponent;
@@ -24,7 +25,7 @@ describe('CalloutComponent', () => {
 
   beforeEach(() => {
     TestBed.configureTestingModule({
-      imports: [CalloutComponent],
+      imports: [CalloutComponent, MatIconTestingModule],
     }).compileComponents();
     fixture = TestBed.createComponent(CalloutComponent);
     component = fixture.componentInstance;
diff --git a/modules/ui/src/app/components/callout/callout.component.ts b/modules/ui/src/app/components/callout/callout.component.ts
index e4be32f45..9067d9fa5 100644
--- a/modules/ui/src/app/components/callout/callout.component.ts
+++ b/modules/ui/src/app/components/callout/callout.component.ts
@@ -25,17 +25,25 @@ import { CommonModule } from '@angular/common';
 import { MatIconModule } from '@angular/material/icon';
 import { MatButtonModule } from '@angular/material/button';
 import { CalloutType } from '../../model/callout-type';
+import { ProgramType } from '../../model/program-type';
+import { ProgramTypeIconComponent } from '../program-type-icon/program-type-icon.component';
 
 @Component({
+  changeDetection: ChangeDetectionStrategy.OnPush,
+  imports: [
+    CommonModule,
+    MatIconModule,
+    MatButtonModule,
+    ProgramTypeIconComponent,
+  ],
   selector: 'app-callout',
   standalone: true,
-  imports: [CommonModule, MatIconModule, MatButtonModule],
-  templateUrl: './callout.component.html',
   styleUrls: ['./callout.component.scss'],
-  changeDetection: ChangeDetectionStrategy.OnPush,
+  templateUrl: './callout.component.html',
 })
 export class CalloutComponent {
-  public readonly CalloutType = CalloutType;
+  readonly CalloutType = CalloutType;
+  readonly ProgramType = ProgramType;
   @HostBinding('class.hidden') @Input() closed: boolean = false;
   @Input() id: string | null = null;
   @Input() type = '';
diff --git a/modules/ui/src/app/components/device-item/device-item.component.html b/modules/ui/src/app/components/device-item/device-item.component.html
index 8d69596d8..bfed0a3d4 100644
--- a/modules/ui/src/app/components/device-item/device-item.component.html
+++ b/modules/ui/src/app/components/device-item/device-item.component.html
@@ -22,14 +22,16 @@
   class="device-item"
   type="button">
   <div class="item-manufacturer">
-    <app-qualification-icon
+    <app-program-type-icon
       *ngIf="device.test_pack === TestingType.Qualification"
+      [type]="ProgramType.Qualification"
       matTooltip="This device will be tested for Qualification."
-      aria-label="This device will be tested for Qualification."></app-qualification-icon>
-    <app-pilot-icon
+      aria-label="This device will be tested for Qualification."></app-program-type-icon>
+    <app-program-type-icon
       *ngIf="device.test_pack === TestingType.Pilot"
+      [type]="ProgramType.Pilot"
       matTooltip="This device will be tested for the Pilot program."
-      aria-label="This device will be tested for the Pilot program."></app-pilot-icon>
+      aria-label="This device will be tested for the Pilot program."></app-program-type-icon>
     <span [attr.aria-label]="device.manufacturer">{{
       device.manufacturer
     }}</span>
@@ -54,14 +56,16 @@
     class="button-edit"
     type="button">
     <div [attr.aria-label]="device.manufacturer" class="item-manufacturer">
-      <app-qualification-icon
+      <app-program-type-icon
         *ngIf="device.test_pack === TestingType.Qualification"
+        [type]="ProgramType.Qualification"
         matTooltip="This device will be tested for Qualification."
-        aria-label="This device will be tested for Qualification."></app-qualification-icon>
-      <app-pilot-icon
+        aria-label="This device will be tested for Qualification."></app-program-type-icon>
+      <app-program-type-icon
         *ngIf="device.test_pack === TestingType.Pilot"
+        [type]="ProgramType.Pilot"
         matTooltip="This device will be tested for the Pilot program."
-        aria-label="This device will be tested for the Pilot program."></app-pilot-icon>
+        aria-label="This device will be tested for the Pilot program."></app-program-type-icon>
       <p class="item-manufacturer-text">{{ device.manufacturer }}</p>
       <mat-icon
         class="item-manufacturer-icon"
diff --git a/modules/ui/src/app/components/device-item/device-item.component.scss b/modules/ui/src/app/components/device-item/device-item.component.scss
index fcef089dd..2c1551ea8 100644
--- a/modules/ui/src/app/components/device-item/device-item.component.scss
+++ b/modules/ui/src/app/components/device-item/device-item.component.scss
@@ -25,7 +25,7 @@ $border-radius: 12px;
   width: $device-item-width;
   height: 80px;
   border-radius: $border-radius;
-  border: 1px solid #c4c7c5;
+  border: 1px solid $blue-300;
   background: $white;
   box-sizing: border-box;
   grid-template-columns: 1fr 1fr;
@@ -53,7 +53,7 @@ $border-radius: 12px;
   width: $device-item-width;
   min-height: calc($icon-width - 2px);
   border-radius: $border-radius;
-  border: 1px solid #c4c7c5;
+  border: 1px solid $blue-300;
   background: $white;
   box-sizing: border-box;
   grid-template-columns: 1fr $icon-width;
@@ -87,7 +87,7 @@ $border-radius: 12px;
     cursor: pointer;
 
     .item-manufacturer-text {
-      max-width: 106px;
+      max-width: 82px;
     }
 
     .item-manufacturer-icon {
@@ -137,7 +137,7 @@ $border-radius: 12px;
     text-overflow: ellipsis;
     white-space: nowrap;
     overflow: hidden;
-    max-width: 130px;
+    max-width: 106px;
   }
 
   .item-manufacturer-icon {
@@ -198,7 +198,7 @@ $border-radius: 12px;
   }
   &:disabled {
     pointer-events: none;
-    opacity: 0.5;
+    background: rgba(60, 64, 67, 0.12);
   }
 }
 
diff --git a/modules/ui/src/app/components/device-item/device-item.component.spec.ts b/modules/ui/src/app/components/device-item/device-item.component.spec.ts
index 87f886b66..df7204975 100644
--- a/modules/ui/src/app/components/device-item/device-item.component.spec.ts
+++ b/modules/ui/src/app/components/device-item/device-item.component.spec.ts
@@ -80,17 +80,19 @@ describe('DeviceItemComponent', () => {
     it('should have qualification icon if testing type is qualification', () => {
       component.device.test_pack = TestingType.Qualification;
       fixture.detectChanges();
-      const icon = compiled.querySelector('app-qualification-icon');
+      const icon = compiled.querySelector('app-program-type-icon');
 
       expect(icon).toBeTruthy();
+      expect(icon?.getAttribute('ng-reflect-type')).toEqual('qualification');
     });
 
     it('should have pilot icon if testing type is pilot', () => {
       component.device.test_pack = TestingType.Pilot;
       fixture.detectChanges();
-      const icon = compiled.querySelector('app-pilot-icon');
+      const icon = compiled.querySelector('app-program-type-icon');
 
       expect(icon).toBeTruthy();
+      expect(icon?.getAttribute('ng-reflect-type')).toEqual('pilot');
     });
 
     it('should emit mac address', () => {
diff --git a/modules/ui/src/app/components/device-item/device-item.component.ts b/modules/ui/src/app/components/device-item/device-item.component.ts
index 3c499ba03..2a62a5f20 100644
--- a/modules/ui/src/app/components/device-item/device-item.component.ts
+++ b/modules/ui/src/app/components/device-item/device-item.component.ts
@@ -24,8 +24,8 @@ import { CommonModule } from '@angular/common';
 import { MatButtonModule } from '@angular/material/button';
 import { MatIconModule } from '@angular/material/icon';
 import { MatTooltipModule } from '@angular/material/tooltip';
-import { QualificationIconComponent } from '../qualification-icon/qualification-icon.component';
-import { PilotIconComponent } from '../pilot-icon/pilot-icon.component';
+import { ProgramTypeIconComponent } from '../program-type-icon/program-type-icon.component';
+import { ProgramType } from '../../model/program-type';
 
 @Component({
   selector: 'app-device-item',
@@ -37,13 +37,13 @@ import { PilotIconComponent } from '../pilot-icon/pilot-icon.component';
     MatButtonModule,
     MatIconModule,
     MatTooltipModule,
-    QualificationIconComponent,
-    PilotIconComponent,
+    ProgramTypeIconComponent,
   ],
 })
 export class DeviceItemComponent {
   readonly DeviceStatus = DeviceStatus;
   readonly TestingType = TestingType;
+  readonly ProgramType = ProgramType;
   @Input() device!: Device;
   @Input() tabIndex = 0;
   @Input() deviceView!: string;
diff --git a/modules/ui/src/app/components/pilot-icon/pilot-icon.component.ts b/modules/ui/src/app/components/pilot-icon/pilot-icon.component.ts
deleted file mode 100644
index 82c786180..000000000
--- a/modules/ui/src/app/components/pilot-icon/pilot-icon.component.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { Component } from '@angular/core';
-
-@Component({
-  selector: 'app-pilot-icon',
-  standalone: true,
-  imports: [],
-  template: '🚀',
-  styles: ':host { display: inline; padding-right: 4px;}',
-})
-export class PilotIconComponent {}
diff --git a/modules/ui/src/app/components/program-type-icon/program-type-con.component.spec.ts b/modules/ui/src/app/components/program-type-icon/program-type-con.component.spec.ts
new file mode 100644
index 000000000..7a984eff0
--- /dev/null
+++ b/modules/ui/src/app/components/program-type-icon/program-type-con.component.spec.ts
@@ -0,0 +1,46 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ProgramTypeIconComponent } from './program-type-icon.component';
+import { MatIconTestingModule } from '@angular/material/icon/testing';
+
+describe('ProgramTypeIconComponent', () => {
+  let component: ProgramTypeIconComponent;
+  let fixture: ComponentFixture<ProgramTypeIconComponent>;
+  let compiled: HTMLElement;
+
+  beforeEach(() => {
+    TestBed.configureTestingModule({
+      imports: [ProgramTypeIconComponent, MatIconTestingModule],
+    }).compileComponents();
+    fixture = TestBed.createComponent(ProgramTypeIconComponent);
+    component = fixture.componentInstance;
+    component.type = 'pilot';
+    compiled = fixture.nativeElement as HTMLElement;
+    fixture.detectChanges();
+  });
+
+  it('should create component', () => {
+    expect(component).toBeTruthy();
+  });
+
+  it('should have svgIcon provided from type', () => {
+    const iconEl = compiled.querySelector('.icon');
+
+    expect(iconEl?.getAttribute('ng-reflect-svg-icon')).toEqual('pilot');
+  });
+});
diff --git a/modules/ui/src/app/components/program-type-icon/program-type-icon.component.ts b/modules/ui/src/app/components/program-type-icon/program-type-icon.component.ts
new file mode 100644
index 000000000..ccf5b3335
--- /dev/null
+++ b/modules/ui/src/app/components/program-type-icon/program-type-icon.component.ts
@@ -0,0 +1,39 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { Component, Input } from '@angular/core';
+import { MatIcon } from '@angular/material/icon';
+
+@Component({
+  selector: 'app-program-type-icon',
+  standalone: true,
+  imports: [MatIcon],
+  template: ` <mat-icon [svgIcon]="type" class="icon"></mat-icon> `,
+  styles: `
+    :host {
+      display: inline-flex;
+      align-items: center;
+      padding-right: 4px;
+    }
+    .icon {
+      width: 16px;
+      height: 16px;
+      line-height: 16px;
+    }
+  `,
+})
+export class ProgramTypeIconComponent {
+  @Input() type = '';
+}
diff --git a/modules/ui/src/app/components/qualification-icon/qualification-icon.component.ts b/modules/ui/src/app/components/qualification-icon/qualification-icon.component.ts
deleted file mode 100644
index 328254e12..000000000
--- a/modules/ui/src/app/components/qualification-icon/qualification-icon.component.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { Component } from '@angular/core';
-
-@Component({
-  selector: 'app-qualification-icon',
-  standalone: true,
-  imports: [],
-  template: '🛡',
-  styles: ':host { display: inline; padding-right: 4px; }',
-})
-export class QualificationIconComponent {}
diff --git a/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.spec.ts b/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.spec.ts
index 56fd7b246..af768f9fe 100644
--- a/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.spec.ts
+++ b/modules/ui/src/app/components/version/consent-dialog/consent-dialog.component.spec.ts
@@ -24,6 +24,7 @@ import {
 import { MatButtonModule } from '@angular/material/button';
 import { of } from 'rxjs';
 import { NEW_VERSION, VERSION } from '../../../mocks/version.mock';
+import { MatIconTestingModule } from '@angular/material/icon/testing';
 
 describe('ConsentDialogComponent', () => {
   let component: ConsentDialogComponent;
@@ -32,7 +33,12 @@ describe('ConsentDialogComponent', () => {
 
   beforeEach(() => {
     TestBed.configureTestingModule({
-      imports: [ConsentDialogComponent, MatDialogModule, MatButtonModule],
+      imports: [
+        ConsentDialogComponent,
+        MatDialogModule,
+        MatButtonModule,
+        MatIconTestingModule,
+      ],
       providers: [
         {
           provide: MatDialogRef,
diff --git a/modules/ui/src/app/components/version/version.component.spec.ts b/modules/ui/src/app/components/version/version.component.spec.ts
index 2985d4bd9..b879aa26d 100644
--- a/modules/ui/src/app/components/version/version.component.spec.ts
+++ b/modules/ui/src/app/components/version/version.component.spec.ts
@@ -27,6 +27,7 @@ import { NEW_VERSION, VERSION } from '../../mocks/version.mock';
 import { of } from 'rxjs';
 import { MatDialogRef } from '@angular/material/dialog';
 import { ConsentDialogComponent } from './consent-dialog/consent-dialog.component';
+import { MatIconTestingModule } from '@angular/material/icon/testing';
 
 describe('VersionComponent', () => {
   let component: VersionComponent;
@@ -42,7 +43,7 @@ describe('VersionComponent', () => {
     mockService = jasmine.createSpyObj(['getVersion', 'fetchVersion']);
     mockService.getVersion.and.returnValue(versionBehaviorSubject$);
     TestBed.configureTestingModule({
-      imports: [VersionComponent],
+      imports: [VersionComponent, MatIconTestingModule],
       providers: [{ provide: TestRunService, useValue: mockService }],
     });
     fixture = TestBed.createComponent(VersionComponent);
diff --git a/modules/ui/src/app/model/program-type.ts b/modules/ui/src/app/model/program-type.ts
new file mode 100644
index 000000000..6c6d8b9f5
--- /dev/null
+++ b/modules/ui/src/app/model/program-type.ts
@@ -0,0 +1,19 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export enum ProgramType {
+  Pilot = 'pilot',
+  Qualification = 'qualification',
+}
diff --git a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.html b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.html
index 1db4dad1d..d0d32ebfa 100644
--- a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.html
+++ b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.html
@@ -127,8 +127,9 @@ <h2 class="device-qualification-form-header-title">{{ data.title }}</h2>
           <mat-radio-button
             [value]="TestingType.Qualification"
             class="device-qualification-form-journey-button">
-            <span>
-              <app-qualification-icon></app-qualification-icon
+            <span class="device-qualification-form-journey-button-info">
+              <app-program-type-icon
+                [type]="ProgramType.Qualification"></app-program-type-icon
               ><span class="device-qualification-form-journey-button-label"
                 >Device Qualification</span
               >
@@ -137,8 +138,9 @@ <h2 class="device-qualification-form-header-title">{{ data.title }}</h2>
           <mat-radio-button
             [value]="TestingType.Pilot"
             class="device-qualification-form-journey-button">
-            <span>
-              <app-pilot-icon></app-pilot-icon
+            <span class="device-qualification-form-journey-button-info">
+              <app-program-type-icon
+                [type]="ProgramType.Pilot"></app-program-type-icon
               ><span class="device-qualification-form-journey-button-label"
                 >Pilot Assessment</span
               >
@@ -212,6 +214,7 @@ <h3 class="device-qualification-form-step-title">Summary</h3>
               class="device-qualification-form-summary">
               <app-device-item
                 *ngIf="device"
+                [tabIndex]="-1"
                 [deviceView]="DeviceView.Basic"
                 [device]="device"></app-device-item>
               <div class="device-qualification-form-summary-info">
diff --git a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.scss b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.scss
index 3b995a4c1..da55327a8 100644
--- a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.scss
+++ b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.scss
@@ -115,6 +115,10 @@ $form-height: 993px;
   padding: 0 18px 0 24px;
 }
 
+.device-qualification-form-journey-button-info {
+  display: flex;
+}
+
 .device-qualification-form-journey-button-label {
   font-family: $font-secondary;
   font-style: normal;
diff --git a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.ts b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.ts
index 9d0535492..00bddbf4f 100644
--- a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.ts
+++ b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.ts
@@ -63,10 +63,10 @@ import { DynamicFormComponent } from '../../../../components/dynamic-form/dynami
 import { filter, skip, Subject, takeUntil, timer } from 'rxjs';
 import { FormAction, FormResponse } from '../../devices.component';
 import { DeviceItemComponent } from '../../../../components/device-item/device-item.component';
-import { QualificationIconComponent } from '../../../../components/qualification-icon/qualification-icon.component';
-import { PilotIconComponent } from '../../../../components/pilot-icon/pilot-icon.component';
+import { ProgramTypeIconComponent } from '../../../../components/program-type-icon/program-type-icon.component';
 import { Question } from '../../../../model/profile';
 import { FormControlType } from '../../../../model/question';
+import { ProgramType } from '../../../../model/program-type';
 import { FocusManagerService } from '../../../../services/focus-manager.service';
 
 const MAC_ADDRESS_PATTERN =
@@ -106,8 +106,7 @@ interface DialogData {
     MatRadioButton,
     DynamicFormComponent,
     DeviceItemComponent,
-    QualificationIconComponent,
-    PilotIconComponent,
+    ProgramTypeIconComponent,
   ],
   providers: [provideNgxMask(), DevicesStore],
   templateUrl: './device-qualification-from.component.html',
@@ -118,6 +117,7 @@ export class DeviceQualificationFromComponent
 {
   readonly TestingType = TestingType;
   readonly DeviceView = DeviceView;
+  readonly ProgramType = ProgramType;
   @ViewChild('stepper') public stepper!: StepperComponent;
   testModules: TestModule[] = [];
   deviceQualificationForm: FormGroup = this.fb.group({});
diff --git a/modules/ui/src/assets/icons/pilot.svg b/modules/ui/src/assets/icons/pilot.svg
new file mode 100644
index 000000000..0ce8298a3
--- /dev/null
+++ b/modules/ui/src/assets/icons/pilot.svg
@@ -0,0 +1,13 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_5420_29128)">
+<path d="M2.86987 6.37396L4.42439 7.03164C4.6104 6.65962 4.80305 6.30089 5.00235 5.95544C5.20165 5.60999 5.42087 5.26454 5.66003 4.9191L4.54397 4.69987L2.86987 6.37396ZM5.69989 8.02813L7.97187 10.2802C8.5299 10.0676 9.12779 9.74208 9.76554 9.30363C10.4033 8.86517 11.0012 8.36693 11.5592 7.8089C12.4893 6.87885 13.2134 5.84915 13.7316 4.7198C14.263 3.57716 14.4955 2.52753 14.4291 1.57091C13.4725 1.50448 12.4228 1.73699 11.2802 2.26845C10.1376 2.78662 9.10122 3.51073 8.17117 4.44078C7.61314 4.99882 7.1149 5.59671 6.67644 6.23446C6.23799 6.8722 5.91247 7.4701 5.69989 8.02813ZM9.24737 6.7327C8.94178 6.42711 8.78899 6.05509 8.78899 5.61663C8.78899 5.1649 8.94178 4.78623 9.24737 4.48064C9.55296 4.17506 9.93162 4.02226 10.3834 4.02226C10.8351 4.02226 11.2138 4.17506 11.5194 4.48064C11.8249 4.78623 11.9777 5.1649 11.9777 5.61663C11.9777 6.05509 11.8249 6.42711 11.5194 6.7327C11.2138 7.03829 10.8351 7.19108 10.3834 7.19108C9.93162 7.19108 9.55296 7.03829 9.24737 6.7327ZM9.62604 13.1301L11.3001 11.456L11.0809 10.34C10.7355 10.5791 10.39 10.7984 10.0446 10.9976C9.69911 11.1837 9.34038 11.3697 8.96836 11.5557L9.62604 13.1301ZM15.864 0.116042C16.1165 1.7237 15.957 3.29151 15.3857 4.81945C14.8277 6.3341 13.8578 7.78233 12.476 9.16412L12.8746 11.1372C12.9277 11.4029 12.9144 11.662 12.8347 11.9144C12.755 12.1669 12.6221 12.3861 12.4361 12.5721L9.08793 15.9203L7.41384 11.9941L4.00587 8.58616L0.0797186 6.91206L3.40797 3.56388C3.59399 3.37787 3.81321 3.245 4.06565 3.16528C4.33138 3.08557 4.59711 3.07228 4.86284 3.12542L6.83588 3.52402C8.21767 2.14223 9.6659 1.16567 11.1806 0.594355C12.6952 0.0230371 14.2564 -0.136401 15.864 0.116042ZM1.49473 11.1172C1.95975 10.6522 2.52443 10.4197 3.18875 10.4197C3.86636 10.4064 4.43768 10.6323 4.9027 11.0973C5.36773 11.5623 5.5936 12.1336 5.58031 12.8113C5.58031 13.4756 5.3478 14.0402 4.88277 14.5053C4.55061 14.8374 3.99258 15.1231 3.20868 15.3623C2.43806 15.6014 1.3685 15.814 0 16C0.18601 14.6315 0.398594 13.5619 0.63775 12.7913C0.876906 12.0074 1.16256 11.4494 1.49473 11.1172ZM2.63072 12.2333C2.49785 12.3662 2.36499 12.612 2.23212 12.9707C2.09926 13.3161 2.00625 13.6682 1.95311 14.027C2.31184 13.9738 2.66393 13.8875 3.00938 13.7679C3.36812 13.635 3.61392 13.5021 3.74678 13.3693C3.90622 13.2098 3.99258 13.0172 4.00587 12.7913C4.01915 12.5655 3.94608 12.3728 3.78664 12.2134C3.6272 12.0539 3.43455 11.9808 3.20868 11.9941C2.98281 11.9941 2.79016 12.0739 2.63072 12.2333Z" fill="#188038"/>
+<path d="M2.87023 6.37418L4.42475 7.03186L4.00622 8.58637L7.4142 11.9943L8.96872 11.5559L9.6264 13.1303L11.3005 11.4562L11.0813 10.3402L12.4763 9.16433L12.8749 11.1374C12.9281 11.4031 12.9148 11.6622 12.8351 11.9146C12.7554 12.1671 12.6225 12.3863 12.4365 12.5723L9.08829 15.9205L7.4142 11.9943L4.00622 8.58637L0.0800781 6.91228L3.40833 3.56409C3.59435 3.37808 3.81357 3.24522 4.06601 3.1655C4.33174 3.08578 4.59747 3.07249 4.8632 3.12564L6.83624 3.52423L5.66039 4.91931L4.54433 4.70008L2.87023 6.37418Z" fill="#1967D2"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M4.42438 7.03164C4.61039 6.65962 4.80305 6.30089 5.00234 5.95544C5.20164 5.60999 5.42087 5.26454 5.66002 4.9191L6.83587 3.52402C8.21767 2.14223 9.66589 1.16567 11.1805 0.594355C12.6952 0.0230371 14.2564 -0.1364 15.864 0.116042C16.1165 1.7237 15.957 3.29151 15.3857 4.81945C14.8277 6.3341 13.8578 7.78233 12.476 9.16412L11.0809 10.34C10.7354 10.5791 10.39 10.7984 10.0446 10.9976C9.6991 11.1837 9.34037 11.3697 8.96835 11.5557L7.41384 11.9941L4.00586 8.58616L4.42438 7.03164ZM7.97187 10.2802L5.69988 8.02813C5.91247 7.4701 6.23798 6.8722 6.67644 6.23446C7.11489 5.59671 7.61313 4.99882 8.17116 4.44078C9.10121 3.51073 10.1376 2.78662 11.2802 2.26845C12.4228 1.73699 13.4725 1.50448 14.4291 1.57091C14.4955 2.52753 14.263 3.57716 13.7315 4.7198C13.2134 5.84915 12.4893 6.87885 11.5592 7.8089C11.0012 8.36693 10.4033 8.86517 9.76554 9.30363C9.12779 9.74208 8.5299 10.0676 7.97187 10.2802Z" fill="#188038"/>
+<path d="M9.24745 6.7329C8.94186 6.42731 8.78906 6.05529 8.78906 5.61684C8.78906 5.1651 8.94186 4.78643 9.24745 4.48084C9.55303 4.17526 9.9317 4.02246 10.3834 4.02246C10.8352 4.02246 11.2138 4.17526 11.5194 4.48084C11.825 4.78643 11.9778 5.1651 11.9778 5.61684C11.9778 6.05529 11.825 6.42731 11.5194 6.7329C11.2138 7.03849 10.8352 7.19128 10.3834 7.19128C9.9317 7.19128 9.55303 7.03849 9.24745 6.7329Z" fill="white"/>
+</g>
+<defs>
+<clipPath id="clip0_5420_29128">
+<rect width="16" height="16" fill="white"/>
+</clipPath>
+</defs>
+</svg>
diff --git a/modules/ui/src/assets/icons/qualification.svg b/modules/ui/src/assets/icons/qualification.svg
new file mode 100644
index 000000000..732e417cb
--- /dev/null
+++ b/modules/ui/src/assets/icons/qualification.svg
@@ -0,0 +1,6 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g id="Frame 424">
+<path id="icon" d="M14.4712 4.13139C14.5625 3.90314 14.6081 3.66729 14.6081 3.42383H12.7822L7.53251 8.67347L4.93052 6.0943L3.65234 7.3953L6.23152 9.97447C6.58149 10.3244 7.00755 10.4994 7.50969 10.4994C8.02705 10.4994 8.46071 10.3244 8.81069 9.97447L14.0832 4.702C14.2505 4.53462 14.3799 4.34442 14.4712 4.13139Z" fill="#188038"/>
+<path id="icon_2" d="M2.12268 13.786C3.5378 15.262 5.27247 16 7.32668 16C9.36567 16 11.0851 15.2924 12.485 13.8773C13.9001 12.447 14.6077 10.7123 14.6077 8.67332H12.7817C12.7817 10.195 12.2492 11.4883 11.184 12.5535C10.1341 13.6186 8.84831 14.1512 7.32668 14.1512C5.78982 14.1512 4.48883 13.6034 3.42368 12.5078C2.35854 11.3971 1.82596 10.058 1.82596 8.49073V1.82596H12.7817H14.6077C14.6077 1.32382 14.4251 0.897764 14.0599 0.547788C13.7099 0.182596 13.2839 0 12.7817 0H1.82596C1.32382 0 0.890157 0.182596 0.524965 0.547788C0.174988 0.897764 0 1.32382 0 1.82596V8.42225C0 10.5069 0.70756 12.2948 2.12268 13.786Z" fill="#1967D2"/>
+</g>
+</svg>

From f6478bb33107ce3f89c1f1b62431bde3cce8ea8e Mon Sep 17 00:00:00 2001
From: Sofia Kurilova <kurilova@google.com>
Date: Wed, 4 Sep 2024 14:29:22 +0200
Subject: [PATCH 09/25] Adds dialog if profile is successfully saved (#746)

* Adds dialog if profile is successfully saved
---
 .../success-dialog.component.html             | 44 +++++++++++
 .../success-dialog.component.scss             | 73 +++++++++++++++++++
 .../success-dialog.component.spec.ts          | 70 ++++++++++++++++++
 .../success-dialog.component.ts               | 55 ++++++++++++++
 .../risk-assessment.component.spec.ts         | 12 ++-
 .../risk-assessment.component.ts              | 21 +++++-
 .../risk-assessment.store.spec.ts             | 16 +++-
 .../risk-assessment/risk-assessment.store.ts  | 30 ++++++--
 8 files changed, 305 insertions(+), 16 deletions(-)
 create mode 100644 modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.html
 create mode 100644 modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.scss
 create mode 100644 modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.spec.ts
 create mode 100644 modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.ts

diff --git a/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.html b/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.html
new file mode 100644
index 000000000..7e616388c
--- /dev/null
+++ b/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.html
@@ -0,0 +1,44 @@
+<!--
+Copyright 2023 Google LLC
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<span class="simple-dialog-title">Risk Assessment Profile Completed</span>
+<p class="simple-dialog-content">
+  It has been saved as "{{ data.profile.name }}" and can now be attached to
+  reports.
+</p>
+<p class="simple-dialog-content">
+  <span class="simple-dialog-content-risk"
+    >Preliminary risk estimation based on your answers is
+    <span
+      *ngIf="data.profile.risk"
+      class="profile-item-risk"
+      [ngClass]="getRiskClass(data.profile.risk!)">
+      {{ data.profile.risk }} risk
+    </span></span
+  >
+
+  <br />This means that your device might be eligible for most networks. Final
+  estimation will be available in the Zip file.
+</p>
+<mat-dialog-actions align="end" class="delete-form-actions">
+  <button
+    (click)="confirm()"
+    class="confirm-button"
+    color="primary"
+    mat-button
+    type="button">
+    Confirm
+  </button>
+</mat-dialog-actions>
diff --git a/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.scss b/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.scss
new file mode 100644
index 000000000..23badf7a4
--- /dev/null
+++ b/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.scss
@@ -0,0 +1,73 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@import '../../../../../theming/colors';
+@import '../../../../../theming/variables';
+
+:host {
+  display: grid;
+  overflow: hidden;
+  width: 570px;
+  padding: 24px 0 8px 0;
+  > * {
+    padding: 0 16px 0 24px;
+  }
+}
+
+.simple-dialog-title {
+  font-family: $font-primary;
+  font-size: 18px;
+  font-weight: 400;
+  line-height: 24px;
+  text-align: left;
+}
+
+.simple-dialog-title + .simple-dialog-content {
+  margin-top: 0;
+  padding-top: 0;
+  border-bottom: 1px solid $lighter-grey;
+}
+
+.simple-dialog-content {
+  font-family: Roboto, sans-serif;
+  font-size: 14px;
+  line-height: 20px;
+  letter-spacing: 0.2px;
+  color: $grey-800;
+  padding: 16px 16px 16px 24px;
+  margin: 0;
+}
+
+.simple-dialog-actions {
+  padding: 0;
+  min-height: 30px;
+}
+
+.simple-dialog-content-risk {
+  font-weight: bold;
+  display: inline-flex;
+  align-items: center;
+}
+
+.profile-item-risk {
+  display: inline-flex;
+  align-items: center;
+  height: 20px;
+  margin-left: 2px;
+  font-family: $font-secondary;
+  font-size: 12px;
+  font-weight: 400;
+  letter-spacing: 0.3px;
+}
diff --git a/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.spec.ts b/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.spec.ts
new file mode 100644
index 000000000..e497af997
--- /dev/null
+++ b/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.spec.ts
@@ -0,0 +1,70 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { SuccessDialogComponent } from './success-dialog.component';
+import { TestRunService } from '../../../../services/test-run.service';
+import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
+import { of } from 'rxjs';
+import { PROFILE_MOCK } from '../../../../mocks/profile.mock';
+
+describe('SuccessDialogComponent', () => {
+  let component: SuccessDialogComponent;
+  let fixture: ComponentFixture<SuccessDialogComponent>;
+  const testRunServiceMock = jasmine.createSpyObj(['getRiskClass']);
+  let compiled: HTMLElement;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [SuccessDialogComponent],
+      providers: [
+        { provide: TestRunService, useValue: testRunServiceMock },
+        {
+          provide: MatDialogRef,
+          useValue: {
+            keydownEvents: () => of(new KeyboardEvent('keydown', { code: '' })),
+            close: () => ({}),
+          },
+        },
+        { provide: MAT_DIALOG_DATA, useValue: {} },
+      ],
+    }).compileComponents();
+    fixture = TestBed.createComponent(SuccessDialogComponent);
+    component = fixture.componentInstance;
+    component.data = {
+      profile: PROFILE_MOCK,
+    };
+    compiled = fixture.nativeElement as HTMLElement;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+
+  it('should close dialog on "cancel" click', () => {
+    const closeSpy = spyOn(component.dialogRef, 'close');
+    const confirmButton = compiled.querySelector(
+      '.confirm-button'
+    ) as HTMLButtonElement;
+
+    confirmButton?.click();
+
+    expect(closeSpy).toHaveBeenCalled();
+
+    closeSpy.calls.reset();
+  });
+});
diff --git a/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.ts b/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.ts
new file mode 100644
index 000000000..6c3fb58d9
--- /dev/null
+++ b/modules/ui/src/app/pages/risk-assessment/components/success-dialog/success-dialog.component.ts
@@ -0,0 +1,55 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { Component, Inject } from '@angular/core';
+import {
+  MAT_DIALOG_DATA,
+  MatDialogModule,
+  MatDialogRef,
+} from '@angular/material/dialog';
+import { MatButtonModule } from '@angular/material/button';
+import { EscapableDialogComponent } from '../../../../components/escapable-dialog/escapable-dialog.component';
+import { Profile, RiskResultClassName } from '../../../../model/profile';
+import { TestRunService } from '../../../../services/test-run.service';
+import { CommonModule } from '@angular/common';
+
+interface DialogData {
+  profile: Profile;
+}
+
+@Component({
+  selector: 'app-success-dialog',
+  templateUrl: './success-dialog.component.html',
+  styleUrls: ['./success-dialog.component.scss'],
+  standalone: true,
+  imports: [MatDialogModule, MatButtonModule, CommonModule],
+})
+export class SuccessDialogComponent extends EscapableDialogComponent {
+  constructor(
+    private readonly testRunService: TestRunService,
+    public override dialogRef: MatDialogRef<SuccessDialogComponent>,
+    @Inject(MAT_DIALOG_DATA) public data: DialogData
+  ) {
+    super(dialogRef);
+  }
+
+  confirm() {
+    this.dialogRef.close();
+  }
+
+  public getRiskClass(riskResult: string): RiskResultClassName {
+    return this.testRunService.getRiskClass(riskResult);
+  }
+}
diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.spec.ts b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.spec.ts
index 8e792ff83..7b65ca7e6 100644
--- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.spec.ts
+++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.spec.ts
@@ -236,10 +236,13 @@ describe('RiskAssessmentComponent', () => {
         });
 
         it('should call store saveProfile when it is new profile', () => {
-          expect(mockRiskAssessmentStore.saveProfile).toHaveBeenCalledWith({
+          const args = mockRiskAssessmentStore.saveProfile.calls.argsFor(0);
+          // @ts-expect-error config is in object
+          expect(args[0].profile).toEqual({
             name: 'test',
             questions: [],
           });
+          expect(mockRiskAssessmentStore.saveProfile).toHaveBeenCalled();
         });
 
         it('should close the form', () => {
@@ -301,9 +304,10 @@ describe('RiskAssessmentComponent', () => {
 
           tick();
 
-          expect(mockRiskAssessmentStore.saveProfile).toHaveBeenCalledWith(
-            NEW_PROFILE_MOCK
-          );
+          const args = mockRiskAssessmentStore.saveProfile.calls.argsFor(0);
+          // @ts-expect-error config is in object
+          expect(args[0].profile).toEqual(NEW_PROFILE_MOCK);
+          expect(mockRiskAssessmentStore.saveProfile).toHaveBeenCalled();
         }));
 
         it('should close the form', fakeAsync(() => {
diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.ts b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.ts
index dd3d33d9d..a756806a5 100644
--- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.ts
+++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.component.ts
@@ -27,6 +27,7 @@ import { LiveAnnouncer } from '@angular/cdk/a11y';
 import { Profile, ProfileStatus } from '../../model/profile';
 import { Observable } from 'rxjs/internal/Observable';
 import { DeviceValidators } from '../devices/components/device-form/device.validators';
+import { SuccessDialogComponent } from './components/success-dialog/success-dialog.component';
 
 @Component({
   selector: 'app-risk-assessment',
@@ -158,7 +159,12 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy {
   }
 
   private saveProfile(profile: Profile) {
-    this.store.saveProfile(profile);
+    this.store.saveProfile({
+      profile,
+      onSave: (profile: Profile) => {
+        this.openSuccessDialog(profile);
+      },
+    });
     this.isOpenProfileForm = false;
   }
 
@@ -191,4 +197,17 @@ export class RiskAssessmentComponent implements OnInit, OnDestroy {
 
     return dialogRef?.afterClosed();
   }
+
+  private openSuccessDialog(profile: Profile): void {
+    this.dialog.open(SuccessDialogComponent, {
+      ariaLabel: 'Risk Assessment Profile Completed',
+      data: {
+        profile,
+      },
+      autoFocus: true,
+      hasBackdrop: true,
+      disableClose: true,
+      panelClass: 'simple-dialog',
+    });
+  }
 }
diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.spec.ts b/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.spec.ts
index 3bc123929..b010a9ea6 100644
--- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.spec.ts
+++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.spec.ts
@@ -29,7 +29,7 @@ import {
 import { FocusManagerService } from '../../services/focus-manager.service';
 import { AppState } from '../../store/state';
 import { selectRiskProfiles } from '../../store/selectors';
-import { fetchRiskProfiles, setRiskProfiles } from '../../store/actions';
+import { setRiskProfiles } from '../../store/actions';
 
 describe('RiskAssessmentStore', () => {
   let riskAssessmentStore: RiskAssessmentStore;
@@ -230,10 +230,18 @@ describe('RiskAssessmentStore', () => {
     });
 
     describe('saveProfile', () => {
-      it('should dispatch fetchRiskProfiles', () => {
-        riskAssessmentStore.saveProfile(NEW_PROFILE_MOCK);
+      it('should dispatch setRiskProfiles', () => {
+        const onSave = jasmine.createSpy('onSave');
+        mockService.fetchProfiles.and.returnValue(of([NEW_PROFILE_MOCK]));
+        riskAssessmentStore.saveProfile({
+          profile: NEW_PROFILE_MOCK,
+          onSave,
+        });
 
-        expect(store.dispatch).toHaveBeenCalledWith(fetchRiskProfiles());
+        expect(store.dispatch).toHaveBeenCalledWith(
+          setRiskProfiles({ riskProfiles: [NEW_PROFILE_MOCK] })
+        );
+        expect(onSave).toHaveBeenCalled();
       });
     });
   });
diff --git a/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.ts b/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.ts
index 93e89b434..2ed9a01c1 100644
--- a/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.ts
+++ b/modules/ui/src/app/pages/risk-assessment/risk-assessment.store.ts
@@ -17,14 +17,14 @@
 import { Injectable } from '@angular/core';
 import { ComponentStore } from '@ngrx/component-store';
 import { tap, withLatestFrom } from 'rxjs/operators';
-import { delay, exhaustMap } from 'rxjs';
+import { catchError, delay, EMPTY, exhaustMap, throwError } from 'rxjs';
 import { TestRunService } from '../../services/test-run.service';
 import { Profile, ProfileFormat } from '../../model/profile';
 import { FocusManagerService } from '../../services/focus-manager.service';
 import { Store } from '@ngrx/store';
 import { AppState } from '../../store/state';
 import { selectRiskProfiles } from '../../store/selectors';
-import { fetchRiskProfiles, setRiskProfiles } from '../../store/actions';
+import { setRiskProfiles } from '../../store/actions';
 
 export interface AppComponentState {
   selectedProfile: Profile | null;
@@ -131,14 +131,30 @@ export class RiskAssessmentStore extends ComponentStore<AppComponentState> {
     );
   });
 
-  saveProfile = this.effect<Profile>(trigger$ => {
+  saveProfile = this.effect<{
+    profile: Profile;
+    onSave: ((profile: Profile) => void) | undefined;
+  }>(trigger$ => {
     return trigger$.pipe(
-      exhaustMap((name: Profile) => {
-        return this.testRunService.saveProfile(name).pipe(
-          tap(saved => {
+      exhaustMap(({ profile, onSave }) => {
+        return this.testRunService.saveProfile(profile).pipe(
+          exhaustMap(saved => {
             if (saved) {
-              this.store.dispatch(fetchRiskProfiles());
+              return this.testRunService.fetchProfiles();
             }
+            return throwError('Failed to upload profile');
+          }),
+          tap(newProfiles => {
+            this.store.dispatch(setRiskProfiles({ riskProfiles: newProfiles }));
+            const uploadedProfile = newProfiles.find(
+              p => p.name === profile.name || p.name === profile.rename
+            );
+            if (onSave && uploadedProfile) {
+              onSave(uploadedProfile);
+            }
+          }),
+          catchError(() => {
+            return EMPTY;
           })
         );
       })

From 2f6a2bdd9067185e8a24c658be2f6adc17eb8494 Mon Sep 17 00:00:00 2001
From: Olga Mardvilko <mardvilka@google.com>
Date: Thu, 5 Sep 2024 11:53:12 +0300
Subject: [PATCH 10/25] 364545807: (fix) changes to prevent the second callout
 is hidden (#748)

---
 modules/ui/src/app/app.component.html         |  6 +-
 .../components/callout/callout.component.html |  2 +-
 .../components/callout/callout.component.scss |  4 -
 .../components/callout/callout.component.ts   |  2 -
 modules/ui/src/styles.scss                    | 81 ++-----------------
 5 files changed, 13 insertions(+), 82 deletions(-)

diff --git a/modules/ui/src/app/app.component.html b/modules/ui/src/app/app.component.html
index d7d51f8ef..62f93ff79 100644
--- a/modules/ui/src/app/app.component.html
+++ b/modules/ui/src/app/app.component.html
@@ -241,11 +241,13 @@ <h1 class="main-heading">Testrun</h1>
           id="outdated_devices_callout"
           role="alert"
           aria-live="assertive"
-          [closed]="!!vm.calloutState.get('outdated_devices_callout')"
           [closable]="true"
           [type]="CalloutType.Error"
           (calloutClosed)="calloutClosed($event)"
-          *ngIf="vm.hasExpiredDevices">
+          *ngIf="
+            vm.hasExpiredDevices &&
+            !vm.calloutState.get('outdated_devices_callout')
+          ">
           Further information is required in your device configurations. Please
           update your
           <a
diff --git a/modules/ui/src/app/components/callout/callout.component.html b/modules/ui/src/app/components/callout/callout.component.html
index 3afe188ed..c2493e0a2 100644
--- a/modules/ui/src/app/components/callout/callout.component.html
+++ b/modules/ui/src/app/components/callout/callout.component.html
@@ -13,7 +13,7 @@
 See the License for the specific language governing permissions and
 limitations under the License.
 -->
-<div *ngIf="!closed" class="callout-container" [ngClass]="type">
+<div class="callout-container" [ngClass]="type">
   <mat-icon
     *ngIf="type !== CalloutType.InfoPilot"
     class="callout-icon"
diff --git a/modules/ui/src/app/components/callout/callout.component.scss b/modules/ui/src/app/components/callout/callout.component.scss
index efce8b8a1..2e3ced624 100644
--- a/modules/ui/src/app/components/callout/callout.component.scss
+++ b/modules/ui/src/app/components/callout/callout.component.scss
@@ -41,10 +41,6 @@
   }
 }
 
-:host.hidden {
-  display: none;
-}
-
 @media (width < 742px) {
   :host + ::ng-deep app-callout {
     top: 80px;
diff --git a/modules/ui/src/app/components/callout/callout.component.ts b/modules/ui/src/app/components/callout/callout.component.ts
index 9067d9fa5..8fa46d9f0 100644
--- a/modules/ui/src/app/components/callout/callout.component.ts
+++ b/modules/ui/src/app/components/callout/callout.component.ts
@@ -17,7 +17,6 @@ import {
   ChangeDetectionStrategy,
   Component,
   EventEmitter,
-  HostBinding,
   Input,
   Output,
 } from '@angular/core';
@@ -44,7 +43,6 @@ import { ProgramTypeIconComponent } from '../program-type-icon/program-type-icon
 export class CalloutComponent {
   readonly CalloutType = CalloutType;
   readonly ProgramType = ProgramType;
-  @HostBinding('class.hidden') @Input() closed: boolean = false;
   @Input() id: string | null = null;
   @Input() type = '';
   @Input() closable = false;
diff --git a/modules/ui/src/styles.scss b/modules/ui/src/styles.scss
index 46bc22278..0e04a118e 100644
--- a/modules/ui/src/styles.scss
+++ b/modules/ui/src/styles.scss
@@ -261,90 +261,25 @@ body:has(.filter-dialog-content)
 }
 
 body
-  #main:has(app-callout .info):not(:has(app-callout .error))
+  #main:has(app-callout)
   app-device-repository:not(:has(.device-repository-content-empty)),
-body
-  #main:has(app-callout .info):not(:has(app-callout .error))
-  app-history:not(:has(.results-content-empty)),
-body
-  #main:has(app-callout .info):not(:has(app-callout .error))
-  app-progress:not(:has(.progress-content-empty)),
-body
-  #main:has(app-callout .info):not(:has(app-callout .error))
-  app-risk-assessment
-  .risk-assessment-container,
-body
-  #main:has(app-callout .check_circle):not(:has(app-callout .error))
-  app-device-repository:not(:has(.device-repository-content-empty)),
-body
-  #main:has(app-callout .check_circle):not(:has(app-callout .error))
-  app-history:not(:has(.results-content-empty)),
-body
-  #main:has(app-callout .check_circle):not(:has(app-callout .error))
-  app-progress:not(:has(.progress-content-empty)),
-body
-  #main:has(app-callout .check_circle):not(:has(app-callout .error))
-  app-risk-assessment
-  .risk-assessment-container,
-body
-  #main:has(app-callout .error):not(:has(app-callout .info))
-  app-device-repository:not(:has(.device-repository-content-empty)),
-body
-  #main:has(app-callout .error):not(:has(app-callout .info))
-  app-history:not(:has(.results-content-empty)),
-body
-  #main:has(app-callout .error):not(:has(app-callout .info))
-  app-progress:not(:has(.progress-content-empty)),
-body
-  #main:has(app-callout .error):not(:has(app-callout .info))
-  app-risk-assessment
-  .risk-assessment-container {
+body #main:has(app-callout) app-history:not(:has(.results-content-empty)),
+body #main:has(app-callout) app-progress:not(:has(.progress-content-empty)),
+body #main:has(app-callout) app-risk-assessment .risk-assessment-container {
   margin-top: 96px;
 }
 
 body
-  #main:has(app-callout .info):has(app-callout .error)
-  app-device-repository:not(:has(.device-repository-content-empty)),
-body
-  #main:has(app-callout .info):has(app-callout .error)
-  app-history:not(:has(.results-content-empty)),
-body
-  #main:has(app-callout .info):has(app-callout .error)
-  app-progress:not(:has(.progress-content-empty)),
-body
-  #main:has(app-callout .info):has(app-callout .error)
-  app-risk-assessment
-  .risk-assessment-container {
-  margin-top: 156px;
-}
-
-body
-  #main:has(app-callout .error app-callout .error)
-  app-device-repository:not(:has(.device-repository-content-empty)),
-body
-  #main:has(app-callout .error app-callout .error)
-  app-history:not(:has(.results-content-empty)),
-body
-  #main:has(app-callout .error app-callout .error)
-  app-progress:not(:has(.progress-content-empty)),
-body
-  #main:has(app-callout .error app-callout .error)
-  app-risk-assessment
-  .risk-assessment-container {
-  margin-top: 156px;
-}
-
-body
-  #main:has(app-callout .check_circle):has(app-callout .error)
+  #main:has(app-callout + app-callout)
   app-device-repository:not(:has(.device-repository-content-empty)),
 body
-  #main:has(app-callout .check_circle):has(app-callout .error)
+  #main:has(app-callout + app-callout)
   app-history:not(:has(.results-content-empty)),
 body
-  #main:has(app-callout .check_circle):has(app-callout .error)
+  #main:has(app-callout + app-callout)
   app-progress:not(:has(.progress-content-empty)),
 body
-  #main:has(app-callout .check_circle):has(app-callout .error)
+  #main:has(app-callout + app-callout)
   app-risk-assessment
   .risk-assessment-container {
   margin-top: 156px;

From 99be694b1c22e3be2a39d2c78d56ffa0b60856a8 Mon Sep 17 00:00:00 2001
From: Sofia Kurilova <kurilova@google.com>
Date: Thu, 5 Sep 2024 14:07:01 +0200
Subject: [PATCH 11/25] Change dialog height on resize (#749)

---
 .../device-qualification-from.component.scss     |  1 -
 .../device-qualification-from.component.ts       | 16 ++++++++++++++++
 2 files changed, 16 insertions(+), 1 deletion(-)

diff --git a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.scss b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.scss
index da55327a8..9a5231a29 100644
--- a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.scss
+++ b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.scss
@@ -18,7 +18,6 @@
 @import 'src/theming/variables';
 
 $form-min-width: 732px;
-$form-height: 993px;
 
 :host {
   container-type: size;
diff --git a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.ts b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.ts
index 00bddbf4f..fd14688f7 100644
--- a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.ts
+++ b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.ts
@@ -17,6 +17,7 @@ import {
   AfterViewInit,
   Component,
   ElementRef,
+  HostListener,
   Inject,
   OnDestroy,
   OnInit,
@@ -115,6 +116,7 @@ interface DialogData {
 export class DeviceQualificationFromComponent
   implements OnInit, AfterViewInit, OnDestroy
 {
+  readonly FORM_HEIGHT = 993;
   readonly TestingType = TestingType;
   readonly DeviceView = DeviceView;
   readonly ProgramType = ProgramType;
@@ -179,6 +181,11 @@ export class DeviceQualificationFromComponent
     return device1 && device2 && this.compareDevices(device1, device2);
   }
 
+  @HostListener('window:resize', ['$event'])
+  onResize() {
+    this.setDialogHeight();
+  }
+
   constructor(
     private fb: FormBuilder,
     private deviceValidators: DeviceValidators,
@@ -556,4 +563,13 @@ export class DeviceQualificationFromComponent
     }
     return true;
   }
+
+  private setDialogHeight(): void {
+    const windowHeight = window.innerHeight;
+    if (windowHeight < this.FORM_HEIGHT) {
+      this.element.nativeElement.style.height = '100vh';
+    } else {
+      this.element.nativeElement.style.height = this.FORM_HEIGHT + 'px';
+    }
+  }
 }

From cc53334c6b451d25c094b8ae366220e5564b8390 Mon Sep 17 00:00:00 2001
From: Sofia Kurilova <kurilova@google.com>
Date: Thu, 5 Sep 2024 14:48:07 +0200
Subject: [PATCH 12/25] When canceling deletion, Create device modal opens
 (#751)

* Open Edit device window when delete device is cancelled
---
 .../device-qualification-from.component.ts          | 12 +++++-------
 .../src/app/pages/devices/devices.component.spec.ts |  6 +++---
 .../ui/src/app/pages/devices/devices.component.ts   | 13 +++++++++++--
 3 files changed, 19 insertions(+), 12 deletions(-)

diff --git a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.ts b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.ts
index fd14688f7..b36b5c2b0 100644
--- a/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.ts
+++ b/modules/ui/src/app/pages/devices/components/device-qualification-from/device-qualification-from.component.ts
@@ -165,12 +165,6 @@ export class DeviceQualificationFromComponent
     return this.getStep(0).controls['test_modules'] as FormArray;
   }
 
-  get formPristine() {
-    return (
-      this.deviceQualificationForm.get('steps') as FormArray
-    ).controls.every(control => (control as FormGroup).pristine);
-  }
-
   get formValid() {
     return (
       this.deviceQualificationForm.get('steps') as FormArray
@@ -265,7 +259,11 @@ export class DeviceQualificationFromComponent
   }
 
   delete(): void {
-    this.dialogRef.close({ action: FormAction.Delete } as FormResponse);
+    this.dialogRef.close({
+      action: FormAction.Delete,
+      device: this.createDeviceFromForm(),
+      index: this.stepper.selectedIndex,
+    } as FormResponse);
   }
 
   closeForm(): void {
diff --git a/modules/ui/src/app/pages/devices/devices.component.spec.ts b/modules/ui/src/app/pages/devices/devices.component.spec.ts
index 4df5fed2b..c7bc533b7 100644
--- a/modules/ui/src/app/pages/devices/devices.component.spec.ts
+++ b/modules/ui/src/app/pages/devices/devices.component.spec.ts
@@ -289,7 +289,7 @@ describe('DevicesComponent', () => {
         afterClosed: () => of(true),
       } as MatDialogRef<typeof SimpleDialogComponent>);
 
-      component.openDeleteDialog([device], MOCK_TEST_MODULES, device);
+      component.openDeleteDialog([device], MOCK_TEST_MODULES, device, device);
 
       const args = mockDevicesStore.deleteDevice.calls.argsFor(0);
       // @ts-expect-error config is in object
@@ -303,13 +303,13 @@ describe('DevicesComponent', () => {
         afterClosed: () => of(null),
       } as MatDialogRef<typeof SimpleDialogComponent>);
 
-      component.openDeleteDialog([device], MOCK_TEST_MODULES, device);
+      component.openDeleteDialog([device], MOCK_TEST_MODULES, device, device);
 
       expect(openDeviceDialogSpy).toHaveBeenCalledWith(
         [device],
         MOCK_TEST_MODULES,
         device,
-        undefined,
+        device,
         false,
         0
       );
diff --git a/modules/ui/src/app/pages/devices/devices.component.ts b/modules/ui/src/app/pages/devices/devices.component.ts
index b62468593..1411ced7a 100644
--- a/modules/ui/src/app/pages/devices/devices.component.ts
+++ b/modules/ui/src/app/pages/devices/devices.component.ts
@@ -172,7 +172,16 @@ export class DevicesComponent implements OnInit, OnDestroy {
         }
         if (response.action === FormAction.Delete && initialDevice) {
           this.devicesStore.selectDevice(initialDevice);
-          this.openDeleteDialog(devices, testModules, initialDevice);
+          if (response.device) {
+            this.openDeleteDialog(
+              devices,
+              testModules,
+              initialDevice,
+              response.device,
+              isEditDevice,
+              response.index
+            );
+          }
         }
       });
   }
@@ -219,7 +228,7 @@ export class DevicesComponent implements OnInit, OnDestroy {
     devices: Device[],
     testModules: TestModule[],
     initialDevice: Device,
-    device?: Device,
+    device: Device,
     isEditDevice = false,
     index = 0
   ) {

From 725292d9f27e83a8872917be77ea6efcda240a0a Mon Sep 17 00:00:00 2001
From: Olga Mardvilko <mardvilka@google.com>
Date: Thu, 5 Sep 2024 16:24:12 +0300
Subject: [PATCH 13/25] 364828484: (fix) [GAR 1.1] change program icon
 aria-label (#752)

---
 .../device-item/device-item.component.html    | 22 +++++++++----------
 .../device-item/device-item.component.ts      |  5 ++++-
 2 files changed, 14 insertions(+), 13 deletions(-)

diff --git a/modules/ui/src/app/components/device-item/device-item.component.html b/modules/ui/src/app/components/device-item/device-item.component.html
index bfed0a3d4..b3c3e5591 100644
--- a/modules/ui/src/app/components/device-item/device-item.component.html
+++ b/modules/ui/src/app/components/device-item/device-item.component.html
@@ -16,12 +16,15 @@
 <button
   *ngIf="deviceView === DeviceView.Basic"
   [tabIndex]="tabIndex"
+  [attr.role]="tabIndex === 0 ? 'button' : 'presentation'"
   (click)="itemClick()"
   [attr.aria-label]="label"
   [ngClass]="{ 'non-interective': tabIndex !== 0 }"
   class="device-item"
   type="button">
-  <div class="item-manufacturer">
+  <div
+    class="item-manufacturer"
+    attr.aria-label="{{ device.test_pack }} {{ device.manufacturer }}">
     <app-program-type-icon
       *ngIf="device.test_pack === TestingType.Qualification"
       [type]="ProgramType.Qualification"
@@ -32,9 +35,7 @@
       [type]="ProgramType.Pilot"
       matTooltip="This device will be tested for the Pilot program."
       aria-label="This device will be tested for the Pilot program."></app-program-type-icon>
-    <span [attr.aria-label]="device.manufacturer">{{
-      device.manufacturer
-    }}</span>
+    <span>{{ device.manufacturer }}</span>
   </div>
   <div [attr.aria-label]="device.model" class="item-name">
     {{ device.model }}
@@ -55,7 +56,7 @@
     attr.aria-label="Edit device {{ label }}"
     class="button-edit"
     type="button">
-    <div [attr.aria-label]="device.manufacturer" class="item-manufacturer">
+    <div class="item-manufacturer">
       <app-program-type-icon
         *ngIf="device.test_pack === TestingType.Qualification"
         [type]="ProgramType.Qualification"
@@ -73,16 +74,13 @@
         edit_square</mat-icon
       >
     </div>
-    <div
-      *ngIf="device.status === DeviceStatus.INVALID"
-      [attr.aria-label]="device.status"
-      class="item-status">
-      Outdated
+    <div *ngIf="device.status === DeviceStatus.INVALID" class="item-status">
+      {{ INVALID_DEVICE }}
     </div>
-    <div [attr.aria-label]="device.model" class="item-name">
+    <div class="item-name">
       {{ device.model }}
     </div>
-    <div [attr.aria-label]="device.mac_addr" class="item-mac-address">
+    <div class="item-mac-address">
       {{ device.mac_addr }}
     </div>
   </button>
diff --git a/modules/ui/src/app/components/device-item/device-item.component.ts b/modules/ui/src/app/components/device-item/device-item.component.ts
index 2a62a5f20..72690210f 100644
--- a/modules/ui/src/app/components/device-item/device-item.component.ts
+++ b/modules/ui/src/app/components/device-item/device-item.component.ts
@@ -44,6 +44,7 @@ export class DeviceItemComponent {
   readonly DeviceStatus = DeviceStatus;
   readonly TestingType = TestingType;
   readonly ProgramType = ProgramType;
+  readonly INVALID_DEVICE = 'Outdated';
   @Input() device!: Device;
   @Input() tabIndex = 0;
   @Input() deviceView!: string;
@@ -60,6 +61,8 @@ export class DeviceItemComponent {
   }
 
   get label() {
-    return `${this.device.manufacturer} ${this.device.model} ${this.device.mac_addr}`;
+    const deviceStatus =
+      this.device.status === DeviceStatus.INVALID ? this.INVALID_DEVICE : '';
+    return `${this.device.test_pack} ${this.device.manufacturer} ${this.device.model} ${deviceStatus} ${this.device.mac_addr}`;
   }
 }

From c9f75ea5868f22aac47d0e1baee51bea68abf4e4 Mon Sep 17 00:00:00 2001
From: MariusBaldovin <baldovin@google.com>
Date: Thu, 5 Sep 2024 15:47:11 +0100
Subject: [PATCH 14/25] fixed device json

---
 testing/api/devices/device_1/device_config.json | 1 -
 testing/api/devices/device_2/device_config.json | 1 -
 2 files changed, 2 deletions(-)

diff --git a/testing/api/devices/device_1/device_config.json b/testing/api/devices/device_1/device_config.json
index f76f5d2e0..3be69a082 100644
--- a/testing/api/devices/device_1/device_config.json
+++ b/testing/api/devices/device_1/device_config.json
@@ -1,5 +1,4 @@
 {
-  "status": "Valid",
   "mac_addr": "00:1e:42:28:9e:4a",
   "manufacturer": "Teltonika",
   "model": "TRB140",
diff --git a/testing/api/devices/device_2/device_config.json b/testing/api/devices/device_2/device_config.json
index df8a418fb..177ee23e6 100644
--- a/testing/api/devices/device_2/device_config.json
+++ b/testing/api/devices/device_2/device_config.json
@@ -1,5 +1,4 @@
 {
-  "status": "Valid",
   "mac_addr": "00:1e:42:35:73:c6",
   "manufacturer": "Google",
   "model": "First",

From ab90b753eebdf3ce6359787c5ccd4324de94310e Mon Sep 17 00:00:00 2001
From: MariusBaldovin <baldovin@google.com>
Date: Thu, 5 Sep 2024 16:36:18 +0100
Subject: [PATCH 15/25] fixed failing tests

---
 testing/api/test_api.py | 23 +++++++++++++----------
 1 file changed, 13 insertions(+), 10 deletions(-)

diff --git a/testing/api/test_api.py b/testing/api/test_api.py
index 788dea445..dcc3e0853 100644
--- a/testing/api/test_api.py
+++ b/testing/api/test_api.py
@@ -1029,7 +1029,7 @@ def test_delete_report_no_device(empty_devices_dir, testrun): # pylint: disable=
   # Check if the correct error message returned
   assert "Could not find device" in response["error"]
 
-def test_delete_report_no_report(empty_devices_dir, testrun, add_one_device): # pylint: disable=W0613
+def test_delete_report_no_report(empty_devices_dir, add_one_device, testrun): # pylint: disable=W0613
   """Test for delete report when report does not exist (404)"""
 
   # Load the device using load_json utility method
@@ -1061,8 +1061,8 @@ def test_delete_report_no_report(empty_devices_dir, testrun, add_one_device): #
   # Check if the correct error message is returned
   assert "Report not found" in response["error"]
 
-def test_get_report_success(empty_devices_dir, testrun, # pylint: disable=W0613
-                     add_one_device, create_report_folder):
+def test_get_report_success(empty_devices_dir, add_one_device, # pylint: disable=W0613
+                               create_report_folder, testrun): # pylint: disable=W0613
   """Test get report when report exists (200)"""
 
   # Load the device using load_json utility method
@@ -1089,7 +1089,7 @@ def test_get_report_success(empty_devices_dir, testrun, # pylint: disable=W0613
   # Check if the response is a PDF
   assert r.headers["Content-Type"] == "application/pdf"
 
-def test_get_report_not_found(empty_devices_dir, testrun, add_one_device): # pylint: disable=W0613
+def test_get_report_not_found(empty_devices_dir, add_one_device, testrun): # pylint: disable=W0613
   """Test get report when report doesn't exist (404)"""
 
   # Load the device using load_json utility method
@@ -1160,8 +1160,8 @@ def test_export_report_device_not_found(empty_devices_dir, testrun, # pylint: di
   # Check if the correct error message returned
   assert "A device with that name could not be found" in response["error"]
 
-def test_export_report_profile_not_found(empty_devices_dir, testrun, # pylint: disable=W0613
-                            add_one_device, create_report_folder):
+def test_export_report_profile_not_found(empty_devices_dir, add_one_device, # pylint: disable=W0613
+                                            create_report_folder, testrun): # pylint: disable=W0613
   """Test for export report result when the profile is not found"""
 
   # Load the device using load_json utility method
@@ -1196,7 +1196,7 @@ def test_export_report_profile_not_found(empty_devices_dir, testrun, # pylint: d
   # Check if the correct error message returned
   assert "A profile with that name could not be found" in response["error"]
 
-def test_export_report_not_found(empty_devices_dir, testrun, add_one_device): # pylint: disable=W0613
+def test_export_report_not_found(empty_devices_dir, add_one_device, testrun): # pylint: disable=W0613
   """Test for export the report result when the report could not be found"""
 
   # Load the device using load_json utility method
@@ -1226,7 +1226,7 @@ def test_export_report_with_profile(empty_devices_dir, add_one_device, # pylint:
   """Test export results with existing profile when report exists (200)"""
 
   # Load the profile using load_json utility method
-  profile = load_json("profile_1.json", directory=PROFILES_PATH)
+  profile = load_json("new_profile_1.json", directory=PROFILES_PATH)
 
   # Load the device using load_json utility method
   device = load_json("device_config.json", directory=DEVICE_1_PATH)
@@ -1313,10 +1313,10 @@ def add_two_devices():
   # List of device folders from 'testing/api/devices'
   devices = ["device_1", "device_2"]
 
-  for device in devices:
+  for file in devices:
 
     # Construct the full path for the device_config.json
-    device_path = os.path.join(DEVICES_PATH, device)
+    device_path = os.path.join(DEVICES_PATH, file)
 
     # Load the device configurations using load_json utility method
     device = load_json("device_config.json", directory=device_path)
@@ -1330,6 +1330,9 @@ def add_two_devices():
     # Construct the target path where the profile will be copied
     target_path = os.path.join(DEVICES_DIRECTORY, device_name)
 
+    # Create the target directory if it doesn't exist
+    os.makedirs(target_path, exist_ok=True)
+
     # Copy the profile from source to target
     shutil.copy(source_path, target_path)
 

From 1bd98a2c3b8db8de01ebbdd6c25a916d56d09868 Mon Sep 17 00:00:00 2001
From: MariusBaldovin <baldovin@google.com>
Date: Thu, 5 Sep 2024 16:43:00 +0100
Subject: [PATCH 16/25] fixed errors

---
 testing/api/test_api.py | 23 -----------------------
 1 file changed, 23 deletions(-)

diff --git a/testing/api/test_api.py b/testing/api/test_api.py
index 7fa15607f..dcc3e0853 100644
--- a/testing/api/test_api.py
+++ b/testing/api/test_api.py
@@ -511,7 +511,6 @@ def test_start_testrun_already_started(empty_devices_dir, add_one_device, # pyli
   # Check if 'error' in response
   assert "error" in response
 
-def test_start_testrun_device_not_found(empty_devices_dir, testrun): # pylint: disable=W0613
 def test_start_testrun_device_not_found(empty_devices_dir, testrun): # pylint: disable=W0613
   """Test for start testrun device not found """
 
@@ -521,10 +520,6 @@ def test_start_testrun_device_not_found(empty_devices_dir, testrun): # pylint: d
     "firmware": "test",
     "test_modules": {}
     }}
-    "mac_addr": "",
-    "firmware": "test",
-    "test_modules": {}
-    }}
 
   # Send the post request
   r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10)
@@ -1010,8 +1005,6 @@ def test_delete_report_invalid_timestamp(empty_devices_dir, add_one_device, # py
   # Check if the correct error message returned
   assert "Incorrect timestamp format" in response["error"]
 
-def test_delete_report_no_device(empty_devices_dir, testrun): # pylint: disable=W0613
-  """Test delete report when device does not exist (404)"""
 def test_delete_report_no_device(empty_devices_dir, testrun): # pylint: disable=W0613
   """Test delete report when device does not exist (404)"""
 
@@ -1058,8 +1051,6 @@ def test_delete_report_no_report(empty_devices_dir, add_one_device, testrun): #
 
   # Check if status code is 404 (not found)
   assert r.status_code == 404
-  # Check if status code is 404 (not found)
-  assert r.status_code == 404
 
   # Parse the JSON response
   response = r.json()
@@ -1069,7 +1060,6 @@ def test_delete_report_no_report(empty_devices_dir, add_one_device, testrun): #
 
   # Check if the correct error message is returned
   assert "Report not found" in response["error"]
-  assert "Report not found" in response["error"]
 
 def test_get_report_success(empty_devices_dir, add_one_device, # pylint: disable=W0613
                                create_report_folder, testrun): # pylint: disable=W0613
@@ -1523,17 +1513,13 @@ def test_delete_device_not_found(empty_devices_dir, testrun): # pylint: disable=
 
   payload = {"mac_addr": "non_existing"}
 
-  payload = {"mac_addr": "non_existing"}
-
   # Test that device_1 deletes
   r = requests.delete(f"{API}/device/",
-                      data=json.dumps(payload),
                       data=json.dumps(payload),
                       timeout=5)
 
   assert r.status_code == 404
 
-
   assert len(local_get_devices()) == 0
 
 def test_delete_device_no_mac(empty_devices_dir, testrun): # pylint: disable=W0613
@@ -1611,12 +1597,10 @@ def test_start_system_not_configured_correctly(
 
   payload = {"device": {"mac_addr": None, "firmware": "asd"}}
 
-
   r = requests.post(f"{API}/system/start",
                     data=json.dumps(payload),
                     timeout=10)
 
-
   assert r.status_code == 500
 
 def test_start_device_not_found(empty_devices_dir, # pylint: disable=W0613
@@ -1680,8 +1664,6 @@ def test_create_device_invalid_json(
 
   device_1 = {}
 
-  device_1 = {}
-
   r = requests.post(f"{API}/device",
                     data=json.dumps(device_1),
                     timeout=5)
@@ -1801,10 +1783,8 @@ def test_device_edit_device_with_mac_already_exists(
                     data=json.dumps(device_1),
                     timeout=5)
 
-
   assert r.status_code == 201
 
-
   assert len(local_get_devices()) == 1
 
   device_2 = load_json("device_config.json", directory=DEVICE_2_PATH)
@@ -1813,17 +1793,14 @@ def test_device_edit_device_with_mac_already_exists(
                     data=json.dumps(device_2),
                     timeout=5)
 
-
   assert r.status_code == 201
 
-
   assert len(local_get_devices()) == 2
 
   updated_device = copy.deepcopy(device_1)
 
   updated_device_payload = {}
 
-
   updated_device_payload["device"] = updated_device
   updated_device_payload["mac_addr"] = "00:1e:42:35:73:c6"
   updated_device_payload["model"] = "Alphabet"

From 21d900f342c8f80ff22a69db870fd2842d12a1a8 Mon Sep 17 00:00:00 2001
From: MariusBaldovin <baldovin@google.com>
Date: Thu, 5 Sep 2024 16:43:45 +0100
Subject: [PATCH 17/25] fixed device json files

---
 testing/api/devices/device_1.json | 54 -------------------------------
 testing/api/devices/device_2.json | 54 -------------------------------
 2 files changed, 108 deletions(-)
 delete mode 100644 testing/api/devices/device_1.json
 delete mode 100644 testing/api/devices/device_2.json

diff --git a/testing/api/devices/device_1.json b/testing/api/devices/device_1.json
deleted file mode 100644
index 3be69a082..000000000
--- a/testing/api/devices/device_1.json
+++ /dev/null
@@ -1,54 +0,0 @@
-{
-  "mac_addr": "00:1e:42:28:9e:4a",
-  "manufacturer": "Teltonika",
-  "model": "TRB140",
-  "type": "IoT Gateway",
-  "technology": "Hardware - Access Control",
-  "test_pack": "Device Qualification",
-  "additional_info": [
-    {
-      "question": "What type of device is this?",
-      "answer": "IoT Gateway"
-    },
-    {
-      "question": "Please select the technology this device falls into",
-      "answer": "Hardware - Access Control"
-    },
-    {
-      "question": "Does your device process any sensitive information?",
-      "answer": "Yes"
-    },
-    {
-      "question": "Can all non-essential services be disabled on your device?",
-      "answer": "Yes"
-    },
-    {
-      "question": "Is there a second IP port on the device?",
-      "answer": "Yes"
-    },
-    {
-      "question": "Can the second IP port on your device be disabled?",
-      "answer": "Yes"
-    }
-  ],
-  "test_modules": {
-    "protocol": {
-      "enabled": true
-    },
-    "services": {
-      "enabled": false
-    },
-    "ntp": {
-      "enabled": true
-    },
-    "tls": {
-      "enabled": false
-    },
-    "connection": {
-      "enabled": true
-    },
-    "dns": {
-      "enabled": true
-    }
-  }
-}
diff --git a/testing/api/devices/device_2.json b/testing/api/devices/device_2.json
deleted file mode 100644
index 177ee23e6..000000000
--- a/testing/api/devices/device_2.json
+++ /dev/null
@@ -1,54 +0,0 @@
-{
-  "mac_addr": "00:1e:42:35:73:c6",
-  "manufacturer": "Google",
-  "model": "First",
-  "type": "IoT Gateway",
-  "technology": "Hardware - Access Control",
-  "test_pack": "Device Qualification",
-  "additional_info": [
-    {
-      "question": "What type of device is this?",
-      "answer": "IoT Gateway"
-    },
-    {
-      "question": "Please select the technology this device falls into",
-      "answer": "Hardware - Access Control"
-    },
-    {
-      "question": "Does your device process any sensitive information?",
-      "answer": "Yes"
-    },
-    {
-      "question": "Can all non-essential services be disabled on your device?",
-      "answer": "Yes"
-    },
-    {
-      "question": "Is there a second IP port on the device?",
-      "answer": "Yes"
-    },
-    {
-      "question": "Can the second IP port on your device be disabled?",
-      "answer": "Yes"
-    }
-  ],
-  "test_modules": {
-    "protocol": {
-      "enabled": true
-    },
-    "services": {
-      "enabled": false
-    },
-    "ntp": {
-      "enabled": true
-    },
-    "tls": {
-      "enabled": false
-    },
-    "connection": {
-      "enabled": true
-    },
-    "dns": {
-      "enabled": true
-    }
-  }
-}

From 42eef84a77d52ac64b390d37e1dfbca808bc90e6 Mon Sep 17 00:00:00 2001
From: MariusBaldovin <baldovin@google.com>
Date: Fri, 6 Sep 2024 16:49:30 +0100
Subject: [PATCH 18/25] updates on the code

---
 testing/api/{ => sys_config}/system.json   |    0
 testing/api/sys_config/updated_system.json |    7 +
 testing/api/test_api.py                    | 1710 ++++++++++++--------
 3 files changed, 1029 insertions(+), 688 deletions(-)
 rename testing/api/{ => sys_config}/system.json (100%)
 create mode 100644 testing/api/sys_config/updated_system.json

diff --git a/testing/api/system.json b/testing/api/sys_config/system.json
similarity index 100%
rename from testing/api/system.json
rename to testing/api/sys_config/system.json
diff --git a/testing/api/sys_config/updated_system.json b/testing/api/sys_config/updated_system.json
new file mode 100644
index 000000000..95c10642c
--- /dev/null
+++ b/testing/api/sys_config/updated_system.json
@@ -0,0 +1,7 @@
+{
+  "network": {
+    "device_intf": "updated_endev0a",
+    "internet_intf": "updated_dummynet"
+  },
+  "log_level": "DEBUG"
+}
\ No newline at end of file
diff --git a/testing/api/test_api.py b/testing/api/test_api.py
index dcc3e0853..c8e6888d2 100644
--- a/testing/api/test_api.py
+++ b/testing/api/test_api.py
@@ -19,13 +19,11 @@
 import copy
 import json
 import os
-from pathlib import Path
 import re
 import shutil
 import signal
 import subprocess
 import time
-from typing import Iterator
 import pytest
 import requests
 
@@ -34,12 +32,12 @@
 TEST_SITE_DIR = ".."
 
 DEVICES_DIRECTORY = "local/devices"
-TESTING_DEVICES = "../device_configs"
+TESTING_DEVICES = "../devices"
 PROFILES_DIRECTORY = "local/risk_profiles"
-SYSTEM_CONFIG_PATH = "local/system.json"
+SYS_CONFIG_FILE = "local/system.json"
 CERTS_DIRECTORY = "local/root_certs"
 
-SYSTEM_CONFIG_RESTORE_PATH = "testing/api/system.json"
+SYS_CONFIG_PATH = "testing/api/sys_config"
 CERTS_PATH = "testing/api/certificates"
 PROFILES_PATH = "testing/api/profiles"
 REPORTS_PATH = "testing/api/reports"
@@ -47,6 +45,7 @@
 DEVICE_1_PATH = "testing/api/devices/device_1"
 DEVICE_2_PATH = "testing/api/devices/device_2"
 
+
 BASELINE_MAC_ADDR = "02:42:aa:00:01:01"
 ALL_MAC_ADDR = "02:42:aa:00:00:01"
 
@@ -58,13 +57,13 @@ def pretty_print(dictionary: dict):
   print(json.dumps(dictionary, indent=4))
 
 def query_system_status() -> str:
-  """Query system status from API and returns this"""
+  """ Query system status from API and returns this """
   r = requests.get(f"{API}/system/status", timeout=5)
   response = r.json()
   return response["status"]
 
 def query_test_count() -> int:
-  """Queries status and returns number of test results"""
+  """ Queries status and returns number of test results """
   r = requests.get(f"{API}/system/status", timeout=5)
   response = r.json()
   return len(response["tests"]["results"])
@@ -105,7 +104,7 @@ def docker_logs(device_name):
   print(cmd.stdout)
 
 def load_json(file_name, directory):
-  """Utility method to load json files' """
+  """ Utility method to load json files """
 
   # Construct the base path relative to the main folder
   base_path = os.path.abspath(os.path.join(__file__, "../../.."))
@@ -119,94 +118,9 @@ def load_json(file_name, directory):
     # Return the file content
     return json.load(file)
 
-def delete_all_devices():
-  """Utility method to delete all devices from local/devices"""
-
-  try:
-
-    # Check if the device_path (local/devices) exists and is a folder
-    if os.path.exists(DEVICES_DIRECTORY) and os.path.isdir(DEVICES_DIRECTORY):
-
-      # Iterate over all devices from devices folder
-      for item in os.listdir(DEVICES_DIRECTORY):
-
-        # Create the full path
-        item_path = os.path.join(DEVICES_DIRECTORY, item)
-
-        # Check if item is a file
-        if os.path.isfile(item_path):
-
-          # Remove file
-          os.unlink(item_path)
-
-        else:
-
-          # If item is a folder remove it
-          shutil.rmtree(item_path)
-
-  except PermissionError:
-
-    # Permission related issues
-    print(f"Permission Denied: {item}")
-
-  except OSError as err:
-
-    # System related issues
-    print(f"Error removing {item}: {err}")
-
-def local_get_devices():
-  """ Returns path to 'device_config.json' for all devices from local/devices"""
-
-  # List to store the paths of all 'device_config.json' files
-  device_configs = []
-
-  # Loop through each file/folder from 'local/devices'.
-  for device_folder in os.listdir(DEVICES_DIRECTORY):
-
-    # Construct the full path for the file/folder
-    device_path = os.path.join(DEVICES_DIRECTORY, device_folder)
-
-    # Check if the current path is a folder
-    if os.path.isdir(device_path):
-
-      # Construct the full path to 'device_config.json' inside the folder.
-      config_path = os.path.join(device_path, "device_config.json")
-
-      # Check if 'device_config.json' exists in the path.
-      if os.path.exists(config_path):
-
-        # Append the file path to the list.
-        device_configs.append(config_path)
-
-  # Return all the device_config.json paths
-  return device_configs
-
-@pytest.fixture
-def empty_devices_dir():
-  """ Fixture to empty devices directory """
-
-  # Empty the directory before the test
-  delete_all_devices()
-
-  yield
-
-  # Empty the directory after the test
-  delete_all_devices()
-
-@pytest.fixture
-def testing_devices():
-  """ Use devices from the testing/device_configs directory """
-  delete_all_devices()
-  shutil.copytree(
-      os.path.join(os.path.dirname(__file__), TESTING_DEVICES),
-      os.path.join(DEVICES_DIRECTORY),
-      dirs_exist_ok=True,
-  )
-  return local_get_devices()
-
 @pytest.fixture
 def testrun(request): # pylint: disable=W0613
-  """ Start intstance of testrun """
+  """ Start instance of testrun """
   # pylint: disable=W1509
   with subprocess.Popen(
       "bin/testrun",
@@ -267,8 +181,8 @@ def until_true(func: Callable, message: str, timeout: int):
     time.sleep(1)
   raise TimeoutError(f"Timed out waiting {timeout}s for {message}")
 
-def dict_paths(thing: dict, stem: str = "") -> Iterator[str]:
-  """Returns json paths (in dot notation) from a given dictionary"""
+def dict_paths(thing: dict, stem: str = ""):
+  """ Returns json paths (in dot notation) from a given dictionary """
   for k, v in thing.items():
     path = f"{stem}.{k}" if stem else k
     if isinstance(v, dict):
@@ -277,32 +191,72 @@ def dict_paths(thing: dict, stem: str = "") -> Iterator[str]:
       yield path
 
 def get_network_interfaces():
-  """return list of network interfaces on machine
+  """ Return list of network interfaces on machine
 
-  uses /sys/class/net rather than inetfaces as test-run uses the latter
+  Uses /sys/class/net rather than interfaces as testrun uses the latter
   """
+  # Initialise empty list
   ifaces = []
-  path = Path("/sys/class/net")
-  for i in path.iterdir():
-    if not i.is_dir():
+
+  # Path to the directory containing network interfaces
+  path = "/sys/class/net"
+
+  # Iterate over the items in the directory
+  for item in os.listdir(path):
+
+    # Construct the full path
+    full_path = os.path.join(path, item)
+
+    # Skip if the item is not a directory
+    if not os.path.isdir(full_path):
       continue
-    if i.stem.startswith("en") or i.stem.startswith("eth"):
-      ifaces.append(i.stem)
+
+    # Check if the interface name starts with 'en' or 'eth'
+    if item.startswith("en") or item.startswith("eth"):
+      ifaces.append(item)
+
+  # Return the list of network interfaces
   return ifaces
 
+def test_invalid_api_path(testrun): # pylint: disable=W0613
+  """ Test for invalid API path (404)"""
+
+  # Send the get request to the invalid path
+  r = requests.get(f"{API}/non-existing", timeout=5)
+
+  # Check that the response status code is 404 (Not Found)
+  assert r.status_code == 404
+
 # Tests for system endpoints
 
 @pytest.fixture()
-def restore_config():
-  """Restore the original configuration (system.json) after the test"""
+def restore_sys_config():
+  """ Restore the original system configuration (system.json) after the test """
+
   yield
 
-  # Restore system.json from 'testing/api/' after the test
-  if os.path.exists(SYSTEM_CONFIG_RESTORE_PATH):
-    shutil.copy(SYSTEM_CONFIG_RESTORE_PATH, SYSTEM_CONFIG_PATH)
+  # Construct the full path for 'system.json'
+  sys_config = os.path.join(SYS_CONFIG_PATH, "system.json")
+
+  # Restore system.json from 'testing/api/sys_config' after the test
+  if os.path.exists(sys_config):
+
+    shutil.copy(sys_config, SYS_CONFIG_FILE)
+
+@pytest.fixture()
+def update_sys_config():
+  """ Update the system configuration (system.json) before the test """
+
+  # Construct the full path for 'updated_system.json'
+  updated_sys_config = os.path.join(SYS_CONFIG_PATH, "updated_system.json")
+
+  # Restore system.json from 'testing/api/sys_config' after the test
+  if os.path.exists(updated_sys_config):
+
+    shutil.copy(updated_sys_config, SYS_CONFIG_FILE)
 
-def test_get_system_interfaces(testrun): # pylint: disable=W0613
-  """Tests API system interfaces against actual local interfaces"""
+def test_get_sys_interfaces(testrun): # pylint: disable=W0613
+  """ Tests API system interfaces against actual local interfaces (200) """
 
   # Send a GET request to the API to retrieve system interfaces
   r = requests.get(f"{API}/system/interfaces", timeout=5)
@@ -322,57 +276,43 @@ def test_get_system_interfaces(testrun): # pylint: disable=W0613
   # Ensure that all values in the response are strings
   assert all(isinstance(x, str) for x in response)
 
-def test_update_system_config(testrun, restore_config): # pylint: disable=W0613
-  """Test update system configuration endpoint ('/system/config')"""
+def test_update_sys_config(testrun, restore_sys_config): # pylint: disable=W0613
+  """ Test update system configuration endpoint (200) """
 
-  # Configuration data to update
-  updated_system_config = {
-      "network": {
-          "device_intf": "updated_endev0a",
-          "internet_intf": "updated_wlan1"
-      },
-      "log_level": "DEBUG"
-  }
+  # Load the updated system configuration
+  updated_sys_config = load_json("updated_system.json",
+                                 directory=SYS_CONFIG_PATH)
+
+  # Assign the values of 'device_intf' and 'internet_intf' from payload
+  updated_device_intf = updated_sys_config["network"]["device_intf"]
+  updated_internet_intf = updated_sys_config["network"]["internet_intf"]
 
   # Send the post request to update the system configuration
   r = requests.post(f"{API}/system/config",
-                    data=json.dumps(updated_system_config),
+                    data=json.dumps(updated_sys_config),
                     timeout=5)
 
   # Check if status code is 200 (OK)
   assert r.status_code == 200
 
-  # Parse the JSON response
-  response = r.json()
+  # Load 'system.json' from 'local' folder
+  local_sys_config = load_json("system.json", directory="local")
 
-  # Check if the response["network"]["device_intf"] has been updated
-  assert (
-    response["network"]["device_intf"]
-    == updated_system_config["network"]["device_intf"]
-  )
+  # Assign 'device_intf' and 'internet_intf' values from 'local/system.json'
+  local_device_intf = local_sys_config["network"]["device_intf"]
+  local_internet_intf = local_sys_config["network"]["internet_intf"]
 
-  # Check if the response["network"]["internet_intf"] has been updated
-  assert (
-        response["network"]["internet_intf"]
-        == updated_system_config["network"]["internet_intf"]
-  )
+  # Check if 'device_intf' has been updated
+  assert updated_device_intf == local_device_intf
 
-  # Check if the response["log_level"] has been updated
-  assert (
-    response["log_level"]
-    == updated_system_config["log_level"]
-  )
+  # Check if 'internet_intf' has been updated
+  assert updated_internet_intf == local_internet_intf
 
-def test_update_system_config_invalid_config(testrun, restore_config): # pylint: disable=W0613
-  """Test invalid configuration file for update system configuration"""
+def test_update_sys_config_invalid_payload(testrun): # pylint: disable=W0613
+  """ Test invalid payload for update system configuration (400) """
 
-  # Configuration data to update with missing "log_level" field
-  updated_system_config = {
-      "network": {
-          "device_intf": "updated_endev0a",
-          "internet_intf": "updated_wlan1"
-      }
-  }
+  # Empty payload
+  updated_system_config = {}
 
   # Send the post request to update the system configuration
   r = requests.post(f"{API}/system/config",
@@ -382,40 +322,58 @@ def test_update_system_config_invalid_config(testrun, restore_config): # pylint:
   # Check if status code is 400 (Invalid config)
   assert r.status_code == 400
 
-def test_get_system_config(testrun): # pylint: disable=W0613
-  """Tests get system configuration endpoint ('/system/config')"""
+def test_get_sys_config(testrun): # pylint: disable=W0613
+  """ Tests get system configuration endpoint (200) """
 
   # Send a GET request to the API to retrieve system configuration
   r = requests.get(f"{API}/system/config", timeout=5)
 
-  # Load system configuration file
-  local_config = load_json("system.json", directory="local")
+  # Check if status code is 200 (OK)
+  assert r.status_code == 200
 
   # Parse the JSON response
-  api_config = r.json()
+  api_sys_config = r.json()
 
-  # Check if status code is 200 (OK)
-  assert r.status_code == 200
+  # Assign the json response keys and expected types
+  expected_keys = {
+    "network": dict,
+    "log_level": str, 
+    "startup_timeout": int, 
+    "monitor_period": int,
+    "max_device_reports": int,
+    "api_url": str,
+    "api_port": int,
+    "org_name": str,
+  }
 
-  # Validate structure
-  assert set(dict_paths(api_config)) | set(dict_paths(local_config)) == set(
-      dict_paths(api_config)
-  )
+  # Iterate over the dict keys and values
+  for key, key_type in expected_keys.items():
+
+    # Check if the key is in the JSON response
+    assert key in api_sys_config
+
+    # Check if the key has the expected data type
+    assert isinstance(api_sys_config[key], key_type)
+
+  # Load the local system configuration file 'local/system.json'
+  local_sys_config = load_json("system.json", directory="local")
+
+  # Assign 'device_intf' and 'internet_intf' values from 'local/system.json'
+  local_device_intf = local_sys_config["network"]["device_intf"]
+  local_internet_intf = local_sys_config["network"]["internet_intf"]
+
+  # Assign 'device_intf' and 'internet_intf' values from the api response
+  api_device_intf = api_sys_config["network"]["device_intf"]
+  api_internet_intf = api_sys_config["network"]["internet_intf"]
 
   # Check if the device interface in the local config matches the API config
-  assert (
-      local_config["network"]["device_intf"]
-      == api_config["network"]["device_intf"]
-  )
+  assert api_device_intf == local_device_intf
 
   # Check if the internet interface in the local config matches the API config
-  assert (
-      local_config["network"]["internet_intf"]
-      == api_config["network"]["internet_intf"]
-  )
+  assert api_internet_intf == local_internet_intf
 
 def test_start_testrun_success(empty_devices_dir, add_one_device, testrun): # pylint: disable=W0613
-  """Test for testrun started successfully (200) """
+  """ Test for testrun started successfully (200) """
 
   # Load the device using load_json utility method
   device = load_json("device_config.json", directory=DEVICE_1_PATH)
@@ -424,14 +382,14 @@ def test_start_testrun_success(empty_devices_dir, add_one_device, testrun): # py
   mac_address = device["mac_addr"]
 
   # Assign device modules
-  device_modules = device["test_modules"]
+  test_modules = device["test_modules"]
 
   # Payload with device details
   payload = {
     "device": {
       "mac_addr": mac_address,
       "firmware": "test",
-      "test_modules": device_modules
+      "test_modules": test_modules
     }
   }
 
@@ -447,14 +405,27 @@ def test_start_testrun_success(empty_devices_dir, add_one_device, testrun): # py
   # Check that device is in response
   assert "device" in response
 
-  # Check that mac_addr in response
-  assert "mac_addr" in response["device"]
+  # Assign the json response keys and expected types
+  expected_keys = {
+    "mac_addr": str,
+    "firmware": str,
+    "test_modules": dict
+  }
+
+  # Assign the device properties
+  device = response["device"]
 
-  # Check that firmware in response
-  assert "firmware" in response["device"]
+  # Iterate over the 'expected_keys' dict keys and values
+  for key, key_type in expected_keys.items():
+
+    # Check if the key is in the device
+    assert key in device
+
+    # Check if the key has the expected data type
+    assert isinstance(device[key], key_type)
 
-def test_start_testrun_missing_device(testing_devices, testrun): # pylint: disable=W0613
-  """Test for missing device when testrun is started """
+def test_start_testrun_invalid_json(testrun): # pylint: disable=W0613
+  """ Test for invalid JSON payload when testrun is started (400) """
 
   # Payload empty dict (no device)
   payload = {}
@@ -473,7 +444,7 @@ def test_start_testrun_missing_device(testing_devices, testrun): # pylint: disab
 
 def test_start_testrun_already_started(empty_devices_dir, add_one_device, # pylint: disable=W0613
                                                                 testrun): # pylint: disable=W0613
-  """Test for testrun already started (409) """
+  """ Test for testrun already started (409) """
 
   # Load the device using load_json utility method
   device = load_json("device_config.json", directory=DEVICE_1_PATH)
@@ -512,7 +483,7 @@ def test_start_testrun_already_started(empty_devices_dir, add_one_device, # pyli
   assert "error" in response
 
 def test_start_testrun_device_not_found(empty_devices_dir, testrun): # pylint: disable=W0613
-  """Test for start testrun device not found """
+  """ Test for start testrun when device is not found (404) """
 
   # Payload with device details with no mac address assigned
   payload = {"device": {
@@ -533,228 +504,121 @@ def test_start_testrun_device_not_found(empty_devices_dir, testrun): # pylint: d
   # Check if 'error' in response
   assert "error" in response
 
-# Currently not working due to blocking during monitoring period
-@pytest.mark.skip()
-def test_status_in_progress(testing_devices, testrun): # pylint: disable=W0613
+def test_start_testrun_error(empty_devices_dir, add_one_device, # pylint: disable=W0613
+               update_sys_config, testrun, restore_sys_config): # pylint: disable=W0613
+  """ Test for start testrun internal server error (500) """
 
-  payload = {"device": {"mac_addr": BASELINE_MAC_ADDR, "firmware": "asd"}}
-  r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10)
-  assert r.status_code == 200
+  # Load the device using load_json utility method
+  device = load_json("device_config.json", directory=DEVICE_1_PATH)
 
-  until_true(
-      lambda: query_system_status().lower() == "waiting for device",
-      "system status is `waiting for device`",
-      30,
-  )
+  # Assign the mac address
+  mac_address = device["mac_addr"]
 
-  start_test_device("x123", BASELINE_MAC_ADDR)
+  # Assign the test modules
+  test_modules = device["test_modules"]
 
-  until_true(
-      lambda: query_system_status().lower() == "in progress",
-      "system status is `in progress`",
-      600,
-  )
+  # Payload with device details
+  payload = { "device":
+                {
+                "mac_addr": mac_address,
+                "firmware": "test",
+                "test_modules": test_modules
+                }
+            }
 
-# Currently not working due to blocking during monitoring period
-@pytest.mark.skip()
-def test_start_testrun_already_in_progress(
-  testing_devices, # pylint: disable=W0613
-  testrun): # pylint: disable=W0613
-  payload = {"device": {"mac_addr": BASELINE_MAC_ADDR, "firmware": "asd"}}
+  # Send the post request
   r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10)
 
-  until_true(
-      lambda: query_system_status().lower() == "waiting for device",
-      "system status is `waiting for device`",
-      30,
-  )
+  # Parse the json response
+  response = r.json()
 
-  start_test_device("x123", BASELINE_MAC_ADDR)
+  # Check if the response status code is 500
+  assert r.status_code == 500
 
-  until_true(
-      lambda: query_system_status().lower() == "in progress",
-      "system status is `in progress`",
-      600,
-  )
-  r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10)
-  assert r.status_code == 409
+  # Check if 'error' in response
+  assert "error" in response
 
-@pytest.mark.skip()
-def test_trigger_run(testing_devices, testrun): # pylint: disable=W0613
-  payload = {"device": {"mac_addr": BASELINE_MAC_ADDR, "firmware": "asd"}}
+def test_stop_running_testrun(empty_devices_dir, add_one_device, testrun): # pylint: disable=W0613
+  """ Test for successfully stop testrun when test is running (200) """
+
+  # Load the device and mac address using add_device utility method
+  device = load_json("device_config.json", directory=DEVICE_1_PATH)
+
+  mac_address = device["mac_addr"]
+
+  test_modules = device["test_modules"]
+
+  # Payload with device details
+  payload = {
+            "device": {
+              "mac_addr": mac_address,
+              "firmware": "test",
+              "test_modules": test_modules
+              }
+            }
+
+  # Send the post request
   r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10)
+
+  # Check if the response status code is 200 (OK)
   assert r.status_code == 200
 
-  until_true(
-      lambda: query_system_status().lower() == "waiting for device",
-      "system status is `waiting for device`",
-      30,
-  )
+  # Send the post request to stop the test
+  r = requests.post(f"{API}/system/stop", timeout=10)
 
-  start_test_device("x123", BASELINE_MAC_ADDR)
+  # Parse the json response
+  response = r.json()
 
-  until_true(
-      lambda: query_system_status().lower() == "compliant",
-      "system status is `complete`",
-      600,
-  )
+  # Check if status code is 200 (ok)
+  assert r.status_code == 200
 
-  stop_test_device("x123")
+  # Check if error in response
+  assert "success" in response
 
-  # Validate response
+  # Validate system status
+
+  # Send the get request to retrieve system status
   r = requests.get(f"{API}/system/status", timeout=5)
+
+  # Parse the json response
   response = r.json()
-  pretty_print(response)
 
-  # Validate results
-  results = {x["name"]: x for x in response["tests"]["results"]}
-  print(results)
-  # there are only 3 baseline tests
-  assert len(results) == 3
+  # Check if status is 'Cancelled'
+  assert response["status"] == "Cancelled"
 
-  # Validate structure
-  with open(
-      os.path.join(
-          os.path.dirname(__file__), "mockito/running_system_status.json"
-      ), encoding="utf-8"
-  ) as f:
-    mockito = json.load(f)
+def test_stop_testrun_not_running(testrun): # pylint: disable=W0613
+  """Test for stop testrun when is not running"""
 
-  # validate structure
-  assert set(dict_paths(mockito)).issubset(set(dict_paths(response)))
+  # Send the post request to stop the test
+  r = requests.post(f"{API}/system/stop", timeout=10)
 
-  # Validate results structure
-  assert set(dict_paths(mockito["tests"]["results"][0])).issubset(
-      set(dict_paths(response["tests"]["results"][0]))
-  )
+  # Parse the json response
+  response = r.json()
 
-  # Validate a result
-  assert results["baseline.compliant"]["result"] == "Compliant"
+  # Check if status code is 404 (not found)
+  assert r.status_code == 404
 
-@pytest.mark.skip()
-def test_stop_running_test(testing_devices, testrun): # pylint: disable=W0613
-  payload = {"device": {"mac_addr": ALL_MAC_ADDR, "firmware": "asd"}}
-  r = requests.post(f"{API}/system/start", data=json.dumps(payload),
-                    timeout=10)
-  assert r.status_code == 200
+  # Check if error in response
+  assert "error" in response
 
-  until_true(
-      lambda: query_system_status().lower() == "waiting for device",
-      "system status is `waiting for device`",
-      30,
-  )
-
-  start_test_device("x12345", ALL_MAC_ADDR)
-
-  until_true(
-      lambda: query_test_count() > 1,
-      "system status is `complete`",
-      1000,
-  )
-
-  stop_test_device("x12345")
-
-  # Validate response
-  r = requests.post(f"{API}/system/stop", timeout=5)
-  response = r.json()
-  pretty_print(response)
-  assert response == {"success": "Testrun stopped"}
-  time.sleep(1)
-
-  # Validate response
-  r = requests.get(f"{API}/system/status", timeout=5)
-  response = r.json()
-  pretty_print(response)
+def test_system_shutdown(testrun): # pylint: disable=W0613
+  """Test for testrun shutdown endpoint (200)"""
 
-  assert response["status"] == "Cancelled"
+  # Send a POST request to initiate the system shutdown
+  r = requests.post(f"{API}/system/shutdown", timeout=5)
 
-def test_stop_running_not_running(testrun): # pylint: disable=W0613
-  # Validate response
-  r = requests.post(f"{API}/system/stop",
-                    timeout=10)
+  # Parse the json response
   response = r.json()
-  pretty_print(response)
-
-  assert r.status_code == 404
-  assert response["error"] == "Testrun is not currently running"
 
-@pytest.mark.skip()
-def test_multiple_runs(testing_devices, testrun): # pylint: disable=W0613
-  payload = {"device": {"mac_addr": BASELINE_MAC_ADDR, "firmware": "asd"}}
-  r = requests.post(f"{API}/system/start", data=json.dumps(payload),
-                    timeout=10)
+  # Check if the response status code is 200 (OK)
   assert r.status_code == 200
-  print(r.text)
-
-  until_true(
-      lambda: query_system_status().lower() == "waiting for device",
-      "system status is `waiting for device`",
-      30,
-  )
-
-  start_test_device("x123", BASELINE_MAC_ADDR)
-
-  until_true(
-      lambda: query_system_status().lower() == "compliant",
-      "system status is `complete`",
-      900,
-  )
-
-  stop_test_device("x123")
-
-  # Validate response
-  r = requests.get(f"{API}/system/status", timeout=5)
-  response = r.json()
-  pretty_print(response)
-
-  # Validate results
-  results = {x["name"]: x for x in response["tests"]["results"]}
-  print(results)
-  # there are only 3 baseline tests
-  assert len(results) == 3
-
-  payload = {"device": {"mac_addr": BASELINE_MAC_ADDR, "firmware": "asd"}}
-  r = requests.post(f"{API}/system/start", data=json.dumps(payload),
-                    timeout=10)
-  # assert r.status_code == 200
-  # returns 409
-  print(r.text)
-
-  until_true(
-      lambda: query_system_status().lower() == "waiting for device",
-      "system status is `waiting for device`",
-      30,
-  )
-
-  start_test_device("x123", BASELINE_MAC_ADDR)
-
-  until_true(
-      lambda: query_system_status().lower() == "compliant",
-      "system status is `complete`",
-      900,
-  )
-
-  stop_test_device("x123")
-
-def test_status_idle(testrun): # pylint: disable=W0613
-  """Test system status 'idle' endpoint (/system/status)"""
-  until_true(
-      lambda: query_system_status().lower() == "idle",
-      "system status is `idle`",
-      30,
-  )
-
-def test_system_shutdown(testrun): # pylint: disable=W0613
-  """Test the shutdown system endpoint"""
-  # Send a POST request to initiate the system shutdown
-  r = requests.post(f"{API}/system/shutdown", timeout=5)
 
-  # Check if the response status code is 200 (OK)
-  assert r.status_code == 200, f"Expected status code 200, got {r.status_code}"
+  # Check if null in response
+  assert response is None
 
 def test_system_shutdown_in_progress(empty_devices_dir, add_one_device, # pylint: disable=W0613
                                      testrun): # pylint: disable=W0613
-  """Test system shutdown during an in-progress test"""
+  """ Test system shutdown during an in-progress test (400) """
 
   # Load the device using load_json utility method
   device = load_json("device_config.json", directory=DEVICE_1_PATH)
@@ -787,6 +651,20 @@ def test_system_shutdown_in_progress(empty_devices_dir, add_one_device, # pylint
   # Check if the response status code is 400 (test in progress)
   assert r.status_code == 400
 
+  # Parse the json response
+  response = r.json()
+
+  # Check if 'error' in response
+  assert "error" in response
+
+def test_status_idle(testrun): # pylint: disable=W0613
+  """Test system status 'idle' endpoint (/system/status)"""
+  until_true(
+      lambda: query_system_status().lower() == "idle",
+      "system status is `idle`",
+      30,
+  )
+
 def test_system_version(testrun): # pylint: disable=W0613
   """Test for testrun version endpoint"""
 
@@ -799,12 +677,12 @@ def test_system_version(testrun): # pylint: disable=W0613
   # Parse the response
   response = r.json()
 
-  # Assign the json response keys and expected types
+  # Assign the expected json response keys and expected types
   expected_keys = {
-    "installed_version":str,
-    "update_available":bool, 
-    "latest_version":str, 
-    "latest_version_url":str
+    "installed_version": str,
+    "update_available": bool, 
+    "latest_version": str, 
+    "latest_version_url": str
   }
 
   # Iterate over the dict keys and values
@@ -816,11 +694,27 @@ def test_system_version(testrun): # pylint: disable=W0613
     # Check if the key has the expected data type
     assert isinstance(response[key], key_type)
 
+def test_get_test_modules(testrun): # pylint: disable=W0613
+  """ Test the /system/modules endpoint to get the test modules (200) """
+
+  # Send a GET request to the API endpoint
+  r = requests.get(f"{API}/system/modules", timeout=5)
+
+  # Check if status code is 200 (OK)
+  assert r.status_code == 200
+
+  # Parse the JSON response
+  response = r.json()
+
+  # Check if the response is a list
+  assert isinstance(response, list)
+
 # Tests for reports endpoints
 
 @pytest.fixture
 def create_report_folder(): # pylint: disable=W0613
   """Fixture to create the reports folder in local/devices"""
+
   def _create_report_folder(device_name, mac_address, timestamp):
 
     # Create the device folder path
@@ -1336,43 +1230,112 @@ def add_two_devices():
     # Copy the profile from source to target
     shutil.copy(source_path, target_path)
 
-@pytest.mark.skip()
-def test_status_non_compliant(testing_devices, testrun): # pylint: disable=W0613
+def delete_all_devices():
+  """Utility method to delete all devices from local/devices"""
+
+  try:
+
+    # Check if the device_path (local/devices) exists and is a folder
+    if os.path.exists(DEVICES_DIRECTORY) and os.path.isdir(DEVICES_DIRECTORY):
+
+      # Iterate over all devices from devices folder
+      for item in os.listdir(DEVICES_DIRECTORY):
+
+        # Create the full path
+        item_path = os.path.join(DEVICES_DIRECTORY, item)
+
+        # Check if item is a file
+        if os.path.isfile(item_path):
+
+          # Remove file
+          os.unlink(item_path)
+
+        else:
+
+          # If item is a folder remove it
+          shutil.rmtree(item_path)
+
+  except PermissionError:
+
+    # Permission related issues
+    print(f"Permission Denied: {item}")
+
+  except OSError as err:
+
+    # System related issues
+    print(f"Error removing {item}: {err}")
+
+@pytest.fixture
+def empty_devices_dir():
+  """Delete all devices before and after test"""
+
+  # Empty the directory before the test
+  delete_all_devices()
+
+  yield
+
+  # Empty the directory after the test
+  delete_all_devices()
+
+def get_all_devices():
+  """ Returns list with paths to all devices from local/devices"""
+
+  # List to store the paths of all 'device_config.json' files
+  devices = []
+
+  # Loop through each file/folder from 'local/devices'.
+  for device_folder in os.listdir(DEVICES_DIRECTORY):
+
+    # Construct the full path for the file/folder
+    device_path = os.path.join(DEVICES_DIRECTORY, device_folder)
+
+    # Check if the current path is a folder
+    if os.path.isdir(device_path):
 
+      # Construct the full path to 'device_config.json' inside the folder.
+      config_path = os.path.join(device_path, "device_config.json")
+
+      # Check if 'device_config.json' exists in the path.
+      if os.path.exists(config_path):
+
+        # Append the file path to the list.
+        devices.append(config_path)
+
+  # Return all the device_config.json paths
+  return devices
+
+def device_exists(device_mac):
+  """Utility method to check if device exists"""
+
+  # Send the get request
   r = requests.get(f"{API}/devices", timeout=5)
-  all_devices = r.json()
-  payload = {
-    "device": {
-      "mac_addr": all_devices[0]["mac_addr"],
-      "firmware": "asd"
-    }
-  }
-  r = requests.post(f"{API}/system/start", data=json.dumps(payload),
-                    timeout=10)
-  assert r.status_code == 200
-  print(r.text)
 
-  until_true(
-      lambda: query_system_status().lower() == "waiting for device",
-      "system status is `waiting for device`",
-      30,
-  )
+  # Check if status code is not 200 (OK)
+  if r.status_code != 200:
+    raise ValueError(f"Api request failed with code: {r.status_code}")
 
-  start_test_device("x123", all_devices[0]["mac_addr"])
+  # Parse the JSON response to get the list of devices
+  devices = r.json()
 
-  until_true(
-      lambda: query_system_status().lower() == "non-compliant",
-      "system status is `complete",
-      600,
-  )
+  # Return if mac address is in the list of devices
+  return any(p["mac_addr"] == device_mac for p in devices)
 
-  stop_test_device("x123")
+@pytest.fixture
+def testing_devices():
+  """ Use devices from the testing/devices directory """
+  delete_all_devices()
+  shutil.copytree(
+      os.path.join(os.path.dirname(__file__), TESTING_DEVICES),
+      os.path.join(DEVICES_DIRECTORY),
+      dirs_exist_ok=True,
+  )
+  return get_all_devices()
 
 def test_get_devices_no_devices(empty_devices_dir, testrun): # pylint: disable=W0613
-  """Test for get devices endpoint when no devices are available (200)"""
+  """ Test for get devices endpoint when no devices are available (200) """
 
   # Check if there are no devices in local/devices
-  if len(local_get_devices()) != 0:
+  if len(get_all_devices()) != 0:
     raise Exception("Expected no devices in local/devices")
 
   # Send the get request to retrieve all devices
@@ -1391,13 +1354,13 @@ def test_get_devices_no_devices(empty_devices_dir, testrun): # pylint: disable=W
   assert len(response) == 0
 
   # Check if there are no devices in local/devices
-  assert len(local_get_devices()) == 0
+  assert len(get_all_devices()) == 0
 
 def test_get_devices_one_device(empty_devices_dir, add_one_device, testrun): # pylint: disable=W0613
-  """Test for get devices endpoint when one device is created (200)"""
+  """ Test for get devices endpoint when one device is created (200) """
 
   # Check if there is one device in local/devices
-  if len(local_get_devices()) != 1:
+  if len(get_all_devices()) != 1:
     raise Exception("Expected one device in local/devices")
 
   # Send get request to the "/devices" endpoint
@@ -1413,10 +1376,10 @@ def test_get_devices_one_device(empty_devices_dir, add_one_device, testrun): # p
   assert len(response) == 1
 
 def test_get_devices_two_devices(empty_devices_dir, add_two_devices, testrun): # pylint: disable=W0613
-  """Test for get devices endpoint when two devices are created (200)"""
+  """ Test for get devices endpoint when two devices are created (200) """
 
   # Check if there are two devices in local/devices
-  if len(local_get_devices()) != 2:
+  if len(get_all_devices()) != 2:
     raise Exception("Expected two devices in local/devices")
 
   # Send get request to the "/devices" endpoint
@@ -1452,194 +1415,75 @@ def test_get_devices_two_devices(empty_devices_dir, add_two_devices, testrun): #
       # Check if the device has all the expected fields
       assert field in device
 
-def test_delete_device_success(empty_devices_dir, testrun): # pylint: disable=W0613
+def test_create_device(empty_devices_dir, testrun): # pylint: disable=W0613
+  """ Test for successfully create device endpoint (201) """
 
+  # Load the first device using load_json utility method
   device_1 = load_json("device_config.json", directory=DEVICE_1_PATH)
 
-  # Send create device request
+  # Send the post request to the '/device' endpoint
   r = requests.post(f"{API}/device",
                     data=json.dumps(device_1),
                     timeout=5)
-  print(r.text)
 
-  # Check device has been created
+  # Check if status code is 201 (Created)
   assert r.status_code == 201
-  assert len(local_get_devices()) == 1
 
+  # Check if there is one device in 'local/devices'
+  assert len(get_all_devices()) == 1
+
+  # Load the first device using load_json utility method
   device_2 = load_json("device_config.json", directory=DEVICE_2_PATH)
 
+  # Send the post request to the '/device' endpoint
   r = requests.post(f"{API}/device",
                     data=json.dumps(device_2),
                     timeout=5)
-  assert r.status_code == 201
-  assert len(local_get_devices()) == 2
 
+  # Check if status code is 201 (Created)
+  assert r.status_code == 201
 
-  # Test that device_1 deletes
-  r = requests.delete(f"{API}/device/",
-                      data=json.dumps(device_1),
-                      timeout=5)
-  assert r.status_code == 200
-  assert len(local_get_devices()) == 1
-
+  # Check if there are two devices in 'local/devices'
+  assert len(get_all_devices()) == 2
 
-  # Test that returned devices API endpoint matches expected structure
+  # Send a get request to retrieve created devices
   r = requests.get(f"{API}/devices", timeout=5)
-  all_devices = r.json()
-  pretty_print(all_devices)
 
-  with open(
-      os.path.join(os.path.dirname(__file__),
-                   "mockito/get_devices.json"),
-                   encoding="utf-8"
-  ) as f:
-    mockito = json.load(f)
+  # Parse the json response (devices)
+  response = r.json()
 
-  print(mockito)
-
-  # Validate structure
-  assert all(isinstance(x, dict) for x in all_devices)
-
-  # TOOO uncomment when is done
-  # assert set(dict_paths(mockito[0])) == set(dict_paths(all_devices[0]))
-
-  # Validate contents of given keys matches
-  for key in ["mac_addr", "manufacturer", "model"]:
-    assert set([all_devices[0][key]]) == set(
-        [device_2[key]]
-    )
-
-def test_delete_device_not_found(empty_devices_dir, testrun): # pylint: disable=W0613
-
-  payload = {"mac_addr": "non_existing"}
-
-  # Test that device_1 deletes
-  r = requests.delete(f"{API}/device/",
-                      data=json.dumps(payload),
-                      timeout=5)
-
-  assert r.status_code == 404
-
-  assert len(local_get_devices()) == 0
-
-def test_delete_device_no_mac(empty_devices_dir, testrun): # pylint: disable=W0613
-
-  device_1 = load_json("device_config.json", directory=DEVICE_1_PATH)
-
-  # Send create device request
-  r = requests.post(f"{API}/device",
-                    data=json.dumps(device_1),
-                    timeout=5)
-  print(r.text)
-
-  # Check device has been created
-  assert r.status_code == 201
-  assert len(local_get_devices()) == 1
-
-  device_1.pop("mac_addr")
-
-  # Test that device_1 can't delete with no mac address
-  r = requests.delete(f"{API}/device/",
-                      data=json.dumps(device_1),
-                      timeout=5)
-  assert r.status_code == 400
-  assert len(local_get_devices()) == 1
-
-# Currently not working due to blocking during monitoring period
-@pytest.mark.skip()
-def test_delete_device_testrun_running(testing_devices, testrun): # pylint: disable=W0613
-
-  payload = {"device": {"mac_addr": BASELINE_MAC_ADDR, "firmware": "asd"}}
-  r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10)
-  assert r.status_code == 200
-
-  until_true(
-      lambda: query_system_status().lower() == "waiting for device",
-      "system status is `waiting for device`",
-      30,
-  )
-
-  start_test_device("x123", BASELINE_MAC_ADDR)
-
-  until_true(
-      lambda: query_system_status().lower() == "in progress",
-      "system status is `in progress`",
-      600,
-  )
-
-  device_1 = {
-        "manufacturer": "Google",
-        "model": "First",
-        "mac_addr": BASELINE_MAC_ADDR,
-        "test_modules": {
-            "dns": {"enabled": True},
-            "connection": {"enabled": True},
-            "ntp": {"enabled": True},
-            "baseline": {"enabled": True},
-            "nmap": {"enabled": True},
-        },
-    }
-  r = requests.delete(f"{API}/device/",
-                      data=json.dumps(device_1),
-                      timeout=5)
-  assert r.status_code == 403
-
-def test_start_system_not_configured_correctly(
-    empty_devices_dir, # pylint: disable=W0613
-    testrun): # pylint: disable=W0613
-
-  device_1 = load_json("device_config.json", directory=DEVICE_1_PATH)
-
-  # Send create device request
-  r = requests.post(f"{API}/device",
-                    data=json.dumps(device_1),
-                    timeout=5)
-
-  payload = {"device": {"mac_addr": None, "firmware": "asd"}}
-
-  r = requests.post(f"{API}/system/start",
-                    data=json.dumps(payload),
-                    timeout=10)
-
-  assert r.status_code == 500
-
-def test_start_device_not_found(empty_devices_dir, # pylint: disable=W0613
-                                testrun): # pylint: disable=W0613
-
-  device_1 = load_json("device_config.json", directory=DEVICE_1_PATH)
-
-  # Send create device request
-  r = requests.post(f"{API}/device",
-                    data=json.dumps(device_1),
-                    timeout=5)
-
-  r = requests.delete(f"{API}/device/",
-                      data=json.dumps(device_1),
-                      timeout=5)
-  assert r.status_code == 200
+  # Assign the expected fields from device
+  expected_fields = [
+    "mac_addr",
+    "manufacturer",
+    "model",
+    "type",
+    "technology",
+    "test_pack",
+    "additional_info",
+    "test_modules",
+  ]
 
-  payload = {"device": {"mac_addr": device_1["mac_addr"], "firmware": "asd"}}
-  r = requests.post(f"{API}/system/start",
-                    data=json.dumps(payload),
-                    timeout=10)
-  assert r.status_code == 404
+  # Iterate over all expected_fields list
+  for field in expected_fields:
 
-def test_start_missing_device_information(
-    empty_devices_dir, # pylint: disable=W0613
-    testrun): # pylint: disable=W0613
+    # If the field is 'additional_info' compare lists
+    if field == "additional_info":
+      assert response[0][field] == device_1[field]
+      assert response[1][field] == device_2[field]
 
-  device_1 = load_json("device_config.json", directory=DEVICE_1_PATH)
+    # If the field is 'test_modules' compare dictionaries
+    elif field == "test_modules":
+      assert response[0][field] == device_1[field]
+      assert response[1][field] == device_2[field]
 
-  # Send create device request
-  r = requests.post(f"{API}/device",
-                    data=json.dumps(device_1),
-                    timeout=5)
+    # For the other fields check if the created device has the given values
+    else:
+      assert set([response[0][field], response[1][field]]) == set(
+        [device_1[field], device_2[field]])
 
-  payload = {}
-  r = requests.post(f"{API}/system/start",
-                    data=json.dumps(payload),
-                    timeout=10)
-  assert r.status_code == 400
+  # Check if 'status' in api response
+  assert all("status" in r for r in response)
 
 def test_create_device_already_exists(
     empty_devices_dir, # pylint: disable=W0613
@@ -1651,7 +1495,7 @@ def test_create_device_already_exists(
                     data=json.dumps(device_1),
                     timeout=5)
   assert r.status_code == 201
-  assert len(local_get_devices()) == 1
+  assert len(get_all_devices()) == 1
 
   r = requests.post(f"{API}/device",
                     data=json.dumps(device_1),
@@ -1726,7 +1570,7 @@ def test_device_edit_device(
   assert updated_device_api["model"] == new_model
   assert updated_device_api["test_modules"] == new_test_modules
 
-def test_device_edit_device_not_found(
+def test_edit_device_not_found(
     empty_devices_dir, # pylint: disable=W0613
     testrun): # pylint: disable=W0613
 
@@ -1736,7 +1580,7 @@ def test_device_edit_device_not_found(
                     data=json.dumps(device_1),
                     timeout=5)
   assert r.status_code == 201
-  assert len(local_get_devices()) == 1
+  assert len(get_all_devices()) == 1
 
   updated_device = copy.deepcopy(device_1)
 
@@ -1752,7 +1596,7 @@ def test_device_edit_device_not_found(
 
   assert r.status_code == 404
 
-def test_device_edit_device_incorrect_json_format(
+def test_edit_device_incorrect_json_format(
     empty_devices_dir, # pylint: disable=W0613
     testrun): # pylint: disable=W0613
 
@@ -1762,7 +1606,7 @@ def test_device_edit_device_incorrect_json_format(
                     data=json.dumps(device_1),
                     timeout=5)
   assert r.status_code == 201
-  assert len(local_get_devices()) == 1
+  assert len(get_all_devices()) == 1
 
   updated_device_payload = {}
 
@@ -1773,7 +1617,7 @@ def test_device_edit_device_incorrect_json_format(
 
   assert r.status_code == 400
 
-def test_device_edit_device_with_mac_already_exists(
+def test_edit_device_with_mac_already_exists(
     empty_devices_dir, # pylint: disable=W0613
     testrun): # pylint: disable=W0613
 
@@ -1785,7 +1629,7 @@ def test_device_edit_device_with_mac_already_exists(
 
   assert r.status_code == 201
 
-  assert len(local_get_devices()) == 1
+  assert len(get_all_devices()) == 1
 
   device_2 = load_json("device_config.json", directory=DEVICE_2_PATH)
 
@@ -1795,7 +1639,7 @@ def test_device_edit_device_with_mac_already_exists(
 
   assert r.status_code == 201
 
-  assert len(local_get_devices()) == 2
+  assert len(get_all_devices()) == 2
 
   updated_device = copy.deepcopy(device_1)
 
@@ -1812,168 +1656,377 @@ def test_device_edit_device_with_mac_already_exists(
 
   assert r.status_code == 409
 
-def test_invalid_path_get(testrun): # pylint: disable=W0613
-  r = requests.get(f"{API}/blah/blah", timeout=5)
-  response = r.json()
-  assert r.status_code == 404
-  with open(
-      os.path.join(os.path.dirname(__file__), "mockito/invalid_request.json"),
-      encoding="utf-8"
-  ) as f:
-    mockito = json.load(f)
-
-  # validate structure
-  assert set(dict_paths(mockito)) == set(dict_paths(response))
+def test_edit_device_invalid_manufacturer(empty_devices_dir, add_one_device, # pylint: disable=W0613
+                                                                   testrun): # pylint: disable=W0613
+  """ Test for edit device invalid chars in 'manufacturer' field (400) """
 
-def test_create_invalid_chars(empty_devices_dir, testrun): # pylint: disable=W0613
-  # delete_all_devices(ALL_DEVICES)
-  # We must start test run with no devices in local/devices for this test
-  # to function as expected
-  assert len(local_get_devices()) == 0
+  # Load the device
+  device = load_json("device_config.json", directory=DEVICE_1_PATH)
 
-  # Test adding device
-  device_1 = {
-      "manufacturer": "/'disallowed characters///",
-      "model": "First",
-      "mac_addr": BASELINE_MAC_ADDR,
-      "test_modules": {
-          "dns": {"enabled": False},
-          "connection": {"enabled": True},
-          "ntp": {"enabled": True},
-          "baseline": {"enabled": True},
-          "nmap": {"enabled": True},
-      },
-  }
+  # Modify the "manufacturer" field value with the invalid characters
+  device["manufacturer"] = "/';disallowed characters"
 
-  r = requests.post(f"{API}/device", data=json.dumps(device_1),
+  # Send the post request to update the device
+  r = requests.post(f"{API}/device", data=json.dumps(device),
                     timeout=5)
-  print(r.text)
-  print(r.status_code)
 
-def test_get_test_modules(testrun): # pylint: disable=W0613
-  """Test the /system/modules endpoint to get the test modules"""
-
-  # Send a GET request to the API endpoint
-  r = requests.get(f"{API}/system/modules", timeout=5)
-
-  # Check if status code is 200 (OK)
-  assert r.status_code == 200
+  # Check if the status code is 400 (bad request)
+  assert r.status_code == 400
 
-  # Parse the JSON response
+  # Parse the json response
   response = r.json()
 
-  # Check if the response is a list
-  assert isinstance(response, list)
+  # Check if 'error' in response
+  assert "error" in response
 
-# Tests for certificates endpoints
+def test_edit_device_invalid_model(empty_devices_dir, add_one_device, testrun): # pylint: disable=W0613
+  """ Test for edit device invalid chars in 'model' field (400) """
 
-def delete_all_certs():
-  """Utility method to delete all certificates from root_certs folder"""
+  # Load the device
+  device = load_json("device_config.json", directory=DEVICE_1_PATH)
 
-  try:
+  # Modify the "model" field value with the invalid characters
+  device["model"] = "/';disallowed characters"
 
-    # Check if the profile_path (local/root_certs) exists and is a folder
-    if os.path.exists(CERTS_DIRECTORY) and os.path.isdir(CERTS_DIRECTORY):
+  # Send the post request to update the device
+  r = requests.post(f"{API}/device", data=json.dumps(device),
+                    timeout=5)
 
-       # Iterate over all certificates from root_certs folder
-      for item in os.listdir(CERTS_DIRECTORY):
+  # Check if the status code is 400 (bad request)
+  assert r.status_code == 400
 
-        # Combine the directory path with the item name to create the full path
-        item_path = os.path.join(CERTS_DIRECTORY, item)
+  # Parse the json response
+  response = r.json()
 
-        # Check if item is a file
-        if os.path.isfile(item_path):
+  # Check if 'error' in response
+  assert "error" in response
 
-          #If True remove file
-          os.unlink(item_path)
+def test_edit_long_chars(empty_devices_dir, testrun): # pylint: disable=W0613
+  """ Test for edit a device with model over 28 chars (400) """
 
-        else:
+  # Load the device
+  device = load_json("device_config.json", directory=DEVICE_1_PATH)
 
-          # If item is a folder remove it
-          shutil.rmtree(item_path)
+  # Modify the "model" field value with 29 chars
+  device["model"] = "a" * 29
 
-  except PermissionError:
+  # Send the post request to edit the device
+  r = requests.post(f"{API}/device", data=json.dumps(device),
+                    timeout=5)
 
-    # Permission related issues
-    print(f"Permission Denied: {item}")
+  # Check if the status code is 400 (bad request)
+  assert r.status_code == 400
 
-  except OSError as err:
+  # Parse the json response
+  response = r.json()
 
-    # System related issues
-    print(f"Error removing {item}: {err}")
+  # Check if 'error' in response
+  assert "error" in response
 
-def load_certificate_file(cert_filename):
-  """Utility method to load a certificate file in binary read mode."""
+def test_delete_device(empty_devices_dir, add_one_device, testrun): # pylint: disable=W0613
+  """ Test for succesfully delete device endpoint (200) """
 
-  # Construct the full file path
-  cert_path = os.path.join(CERTS_PATH, cert_filename)
+  device = load_json("device_config.json", directory=DEVICE_1_PATH)
 
-  # Open the certificate file in binary read mode
-  with open(cert_path, "rb") as cert_file:
+  # Assign the mac address
+  mac_address = device["mac_addr"]
 
-    # Return the certificate file
-    return cert_file.read()
+  # Assign the payload with device to be deleted mac address
+  payload = { "mac_addr": mac_address }
 
-def upload_cert(filename):
-  """Utility method to upload a certificate"""
+  # Test that device_1 deletes
+  r = requests.delete(f"{API}/device/",
+              data=json.dumps(payload),
+                            timeout=5)
 
-  # Load the certificate using the utility method
-  cert_file = load_certificate_file(filename)
+  # Check if status code is 200 (OK)
+  assert r.status_code == 200
 
-  # Send a POST request to the API endpoint to upload the certificate
-  response = requests.post(
-      f"{API}/system/config/certs",
-      files={"file": (filename, cert_file, "application/x-x509-ca-cert")},
-      timeout=5)
+  # Parse the JSON response
+  response = r.json()
 
-  # Return the response
-  return response
+  # Check if the response contains "success" key
+  assert "success" in response
 
-@pytest.fixture()
-def reset_certs():
-  """Delete the certificates before and after each test"""
+  # Send the get request to check if the device has been deleted
+  r = requests.get(f"{API}/devices", timeout=5)
 
-  # Delete before the test
-  delete_all_certs()
+  # Exception if status code is not 200
+  if r.status_code != 200:
+    raise ValueError(f"API request failed with code: {r.status_code}")
 
-  yield
+  # Parse the JSON response (device)
+  device = r.json()
 
-  # Delete after the test
-  delete_all_certs()
+  # Iterate through the devices to find the device based on the 'mac address'
+  deleted_device = next(
+      (d for d in device if d["mac_addr"] == mac_address),
+      None
+  )
 
-@pytest.fixture()
-def add_cert():
-  """Fixture to upload certificates during tests."""
+  # Check if device was deleted
+  assert deleted_device is None
 
-  # Returning the reference to upload_certificate
-  return upload_cert
+def test_delete_device_not_found(empty_devices_dir, testrun): # pylint: disable=W0613
+  """ Test for delete device when the device doesn't exist (404) """  
 
-def test_get_certificates_no_certificates(testrun, reset_certs): # pylint: disable=W0613
-  """Test for get certificates when no certificates have been uploaded"""
+  # Assign the payload with non existing device mac address
+  payload = {"mac_addr" : "non-existing"}
 
-  # Send the get request to "/system/config/certs" endpoint
-  r = requests.get(f"{API}/system/config/certs", timeout=5)
+  # Test that device_1 is not found
+  r = requests.delete(f"{API}/device/",
+                      data=json.dumps(payload),
+                      timeout=5)
 
-  # Check if status code is 200 (OK)
-  assert r.status_code == 200
+  # Check if status code is 404 (not found)
+  assert r.status_code == 404
 
-  # Parse the response (certificates)
+  # Parse the JSON response
   response = r.json()
 
-  # Check if response is a list
-  assert isinstance(response, list)
+  # Check if error in response
+  assert "error" in response
 
-  # Check if the list is empty
-  assert len(response) == 0
+def test_delete_device_no_mac(empty_devices_dir, add_one_device, testrun): # pylint: disable=W0613
+  """ Test for delete device when no mac address in payload (400) """
 
-def test_get_certificates(testrun, reset_certs, add_cert): # pylint: disable=W0613
-  """Test for get certificates when two certificates have been uploaded"""
+  # Assign an empty payload (no mac address)
+  payload = {}
 
-  # Use add_cert fixture to upload the first certificate
-  add_cert("crt.pem")
+  # Send the delete request
+  r = requests.delete(f"{API}/device/",
+                      data=json.dumps(payload),
+                      timeout=5)
 
-  # Send the get request to "/system/config/certs" endpoint
-  r = requests.get(f"{API}/system/config/certs", timeout=5)
+  # Check if status code is 400 (bad request)
+  assert r.status_code == 400
+
+  # Parse the JSON response
+  response = r.json()
+
+  # Check if 'error' in response
+  assert "error" in response
+
+  # Check that device wasn't deleted from 'local/devices'
+  assert len(get_all_devices()) == 1
+
+def test_delete_device_testrun_in_progress(empty_devices_dir, add_one_device, # pylint: disable=W0613
+                                                                    testrun): # pylint: disable=W0613
+  """ Test for delete device when testrun is in progress (403) """
+
+  # Load the device details
+  device = load_json("device_config.json", directory=DEVICE_1_PATH)
+
+  # Assign the mac address
+  mac_address = device["mac_addr"]
+
+  # Assign the test modules
+  test_modules = device["test_modules"]
+
+  # Payload with device details
+  payload = {
+    "device": {
+      "mac_addr": mac_address,
+      "firmware": "test",
+      "test_modules": test_modules
+    }
+  }
+
+  # Send the post request to start a test
+  requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10)
+
+  # Assign the payload with device to be deleted mac address
+  payload = { "mac_addr": mac_address }
+
+  # Send the delete request
+  r = requests.delete(f"{API}/device/",
+              data=json.dumps(payload),
+                            timeout=5)
+
+  # Check if status code is 403 (Forbidden)
+  assert r.status_code == 403
+
+  # Parse the JSON response
+  response = r.json()
+
+  # Check if the response contains "success" key
+  assert "error" in response
+
+def test_create_invalid_manufacturer(empty_devices_dir, testrun): # pylint: disable=W0613
+  """ Test for create device invalid chars in 'manufacturer' field (400) """
+
+  # Load the device
+  device = load_json("device_config.json", directory=DEVICE_1_PATH)
+
+  # Modify the "manufacturer" field value with the invalid characters
+  device["manufacturer"] = "/';disallowed characters"
+
+  # Send the post request to create the device
+  r = requests.post(f"{API}/device", data=json.dumps(device),
+                    timeout=5)
+
+  # Check if the status code is 400 (bad request)
+  assert r.status_code == 400
+
+  # Parse the json response
+  response = r.json()
+
+  # Check if 'error' in response
+  assert "error" in response
+
+def test_create_invalid_model(empty_devices_dir, testrun): # pylint: disable=W0613
+  """ Test for create device invalid chars in 'model' field (400) """
+
+  # Load the device
+  device = load_json("device_config.json", directory=DEVICE_1_PATH)
+
+  # Modify the "model" field value with the invalid characters
+  device["model"] = "/';disallowed characters"
+
+  # Send the post request to create the device
+  r = requests.post(f"{API}/device", data=json.dumps(device),
+                    timeout=5)
+
+  # Check if the status code is 400 (bad request)
+  assert r.status_code == 400
+
+  # Parse the json response
+  response = r.json()
+
+  # Check if 'error' in response
+  assert "error" in response
+
+def test_create_long_chars(empty_devices_dir, testrun): # pylint: disable=W0613
+  """ Test for create a device with model over 28 chars (400) """
+
+  # Load the device
+  device = load_json("device_config.json", directory=DEVICE_1_PATH)
+
+  # Modify the "model" field value with 29 chars
+  device["model"] = "a" * 29
+
+  # Send the post request to create the device
+  r = requests.post(f"{API}/device", data=json.dumps(device),
+                    timeout=5)
+
+  # Check if the status code is 400 (bad request)
+  assert r.status_code == 400
+
+  # Parse the json response
+  response = r.json()
+
+  # Check if 'error' in response
+  assert "error" in response
+
+# Tests for certificates endpoints
+
+def delete_all_certs():
+  """Utility method to delete all certificates from root_certs folder"""
+
+  try:
+
+    # Check if the profile_path (local/root_certs) exists and is a folder
+    if os.path.exists(CERTS_DIRECTORY) and os.path.isdir(CERTS_DIRECTORY):
+
+       # Iterate over all certificates from root_certs folder
+      for item in os.listdir(CERTS_DIRECTORY):
+
+        # Combine the directory path with the item name to create the full path
+        item_path = os.path.join(CERTS_DIRECTORY, item)
+
+        # Check if item is a file
+        if os.path.isfile(item_path):
+
+          #If True remove file
+          os.unlink(item_path)
+
+        else:
+
+          # If item is a folder remove it
+          shutil.rmtree(item_path)
+
+  except PermissionError:
+
+    # Permission related issues
+    print(f"Permission Denied: {item}")
+
+  except OSError as err:
+
+    # System related issues
+    print(f"Error removing {item}: {err}")
+
+def load_certificate_file(cert_filename):
+  """Utility method to load a certificate file in binary read mode."""
+
+  # Construct the full file path
+  cert_path = os.path.join(CERTS_PATH, cert_filename)
+
+  # Open the certificate file in binary read mode
+  with open(cert_path, "rb") as cert_file:
+
+    # Return the certificate file
+    return cert_file.read()
+
+def upload_cert(filename):
+  """Utility method to upload a certificate"""
+
+  # Load the certificate using the utility method
+  cert_file = load_certificate_file(filename)
+
+  # Send a POST request to the API endpoint to upload the certificate
+  response = requests.post(
+      f"{API}/system/config/certs",
+      files={"file": (filename, cert_file, "application/x-x509-ca-cert")},
+      timeout=5)
+
+  # Return the response
+  return response
+
+@pytest.fixture()
+def reset_certs():
+  """Delete the certificates before and after each test"""
+
+  # Delete before the test
+  delete_all_certs()
+
+  yield
+
+  # Delete after the test
+  delete_all_certs()
+
+@pytest.fixture()
+def add_cert():
+  """Fixture to upload certificates during tests."""
+
+  # Returning the reference to upload_certificate
+  return upload_cert
+
+def test_get_certificates_no_certificates(testrun, reset_certs): # pylint: disable=W0613
+  """Test for get certificates when no certificates have been uploaded"""
+
+  # Send the get request to "/system/config/certs" endpoint
+  r = requests.get(f"{API}/system/config/certs", timeout=5)
+
+  # Check if status code is 200 (OK)
+  assert r.status_code == 200
+
+  # Parse the response (certificates)
+  response = r.json()
+
+  # Check if response is a list
+  assert isinstance(response, list)
+
+  # Check if the list is empty
+  assert len(response) == 0
+
+def test_get_certificates(testrun, reset_certs, add_cert): # pylint: disable=W0613
+  """Test for get certificates when two certificates have been uploaded"""
+
+  # Use add_cert fixture to upload the first certificate
+  add_cert("crt.pem")
+
+  # Send the get request to "/system/config/certs" endpoint
+  r = requests.get(f"{API}/system/config/certs", timeout=5)
 
   # Check if status code is 200 (OK)
   assert r.status_code == 200
@@ -2688,3 +2741,284 @@ def test_delete_profile_server_error(empty_profiles_dir, add_one_profile, # pyli
 
   # Check if error in response
   assert "error" in response
+
+# Skipped tests currently not working due to blocking during monitoring period
+
+@pytest.mark.skip()
+def test_delete_device_testrun_running(testing_devices, testrun): # pylint: disable=W0613
+
+  payload = {"device": {"mac_addr": BASELINE_MAC_ADDR, "firmware": "asd"}}
+  r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10)
+  assert r.status_code == 200
+
+  until_true(
+      lambda: query_system_status().lower() == "waiting for device",
+      "system status is `waiting for device`",
+      30,
+  )
+
+  start_test_device("x123", BASELINE_MAC_ADDR)
+
+  until_true(
+      lambda: query_system_status().lower() == "in progress",
+      "system status is `in progress`",
+      600,
+  )
+
+  device_1 = {
+        "manufacturer": "Google",
+        "model": "First",
+        "mac_addr": BASELINE_MAC_ADDR,
+        "test_modules": {
+            "dns": {"enabled": True},
+            "connection": {"enabled": True},
+            "ntp": {"enabled": True},
+            "baseline": {"enabled": True},
+            "nmap": {"enabled": True},
+        },
+    }
+  r = requests.delete(f"{API}/device/",
+                      data=json.dumps(device_1),
+                      timeout=5)
+  assert r.status_code == 403
+
+@pytest.mark.skip()
+def test_stop_running_test(empty_devices_dir, add_one_device, testrun): # pylint: disable=W0613
+  """ Test for successfully stop testrun when test is running (200) """
+
+  # Load the device and mac address using add_device utility method
+  device = load_json("device_config.json", directory=DEVICE_1_PATH)
+
+  mac_address = device["mac_addr"]
+
+  test_modules = device["test_modules"]
+
+  # Payload with device details
+  payload = {
+            "device": {
+              "mac_addr": mac_address,
+              "firmware": "test",
+              "test_modules": test_modules
+              }
+            }
+
+  r = requests.post(f"{API}/system/start", data=json.dumps(payload),
+                    timeout=10)
+
+  assert r.status_code == 200
+
+  until_true(
+      lambda: query_system_status().lower() == "waiting for device",
+      "system status is `waiting for device`",
+      30,
+  )
+
+  start_test_device("x12345", ALL_MAC_ADDR)
+
+  until_true(
+      lambda: query_test_count() > 1,
+      "system status is `complete`",
+      1000,
+  )
+
+  stop_test_device("x12345")
+
+  # Validate response
+  r = requests.post(f"{API}/system/stop", timeout=5)
+  response = r.json()
+  pretty_print(response)
+  assert response == {"success": "Testrun stopped"}
+  time.sleep(1)
+
+  # Validate response
+  r = requests.get(f"{API}/system/status", timeout=5)
+  response = r.json()
+  pretty_print(response)
+
+  assert response["status"] == "Cancelled"
+
+@pytest.mark.skip()
+def test_status_in_progress(testing_devices, testrun): # pylint: disable=W0613
+
+  payload = {"device": {"mac_addr": BASELINE_MAC_ADDR, "firmware": "asd"}}
+  r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10)
+  assert r.status_code == 200
+
+  until_true(
+      lambda: query_system_status().lower() == "waiting for device",
+      "system status is `waiting for device`",
+      30,
+  )
+
+  start_test_device("x123", BASELINE_MAC_ADDR)
+
+  until_true(
+      lambda: query_system_status().lower() == "in progress",
+      "system status is `in progress`",
+      600,
+  )
+
+@pytest.mark.skip()
+def test_start_testrun_already_in_progress(
+  testing_devices, # pylint: disable=W0613
+  testrun): # pylint: disable=W0613
+  payload = {"device": {"mac_addr": BASELINE_MAC_ADDR, "firmware": "asd"}}
+  r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10)
+
+  until_true(
+      lambda: query_system_status().lower() == "waiting for device",
+      "system status is `waiting for device`",
+      30,
+  )
+
+  start_test_device("x123", BASELINE_MAC_ADDR)
+
+  until_true(
+      lambda: query_system_status().lower() == "in progress",
+      "system status is `in progress`",
+      600,
+  )
+  r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10)
+  assert r.status_code == 409
+
+@pytest.mark.skip()
+def test_trigger_run(testing_devices, testrun): # pylint: disable=W0613
+  payload = {"device": {"mac_addr": BASELINE_MAC_ADDR, "firmware": "asd"}}
+  r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10)
+  assert r.status_code == 200
+
+  until_true(
+      lambda: query_system_status().lower() == "waiting for device",
+      "system status is `waiting for device`",
+      30,
+  )
+
+  start_test_device("x123", BASELINE_MAC_ADDR)
+
+  until_true(
+      lambda: query_system_status().lower() == "compliant",
+      "system status is `complete`",
+      600,
+  )
+
+  stop_test_device("x123")
+
+  # Validate response
+  r = requests.get(f"{API}/system/status", timeout=5)
+  response = r.json()
+  pretty_print(response)
+
+  # Validate results
+  results = {x["name"]: x for x in response["tests"]["results"]}
+  print(results)
+  # there are only 3 baseline tests
+  assert len(results) == 3
+
+  # Validate structure
+  with open(
+      os.path.join(
+          os.path.dirname(__file__), "mockito/running_system_status.json"
+      ), encoding="utf-8"
+  ) as f:
+    mockito = json.load(f)
+
+  # validate structure
+  assert set(dict_paths(mockito)).issubset(set(dict_paths(response)))
+
+  # Validate results structure
+  assert set(dict_paths(mockito["tests"]["results"][0])).issubset(
+      set(dict_paths(response["tests"]["results"][0]))
+  )
+
+  # Validate a result
+  assert results["baseline.compliant"]["result"] == "Compliant"
+
+@pytest.mark.skip()
+def test_multiple_runs(testing_devices, testrun): # pylint: disable=W0613
+  payload = {"device": {"mac_addr": BASELINE_MAC_ADDR, "firmware": "asd"}}
+  r = requests.post(f"{API}/system/start", data=json.dumps(payload),
+                    timeout=10)
+  assert r.status_code == 200
+  print(r.text)
+
+  until_true(
+      lambda: query_system_status().lower() == "waiting for device",
+      "system status is `waiting for device`",
+      30,
+  )
+
+  start_test_device("x123", BASELINE_MAC_ADDR)
+
+  until_true(
+      lambda: query_system_status().lower() == "compliant",
+      "system status is `complete`",
+      900,
+  )
+
+  stop_test_device("x123")
+
+  # Validate response
+  r = requests.get(f"{API}/system/status", timeout=5)
+  response = r.json()
+  pretty_print(response)
+
+  # Validate results
+  results = {x["name"]: x for x in response["tests"]["results"]}
+  print(results)
+  # there are only 3 baseline tests
+  assert len(results) == 3
+
+  payload = {"device": {"mac_addr": BASELINE_MAC_ADDR, "firmware": "asd"}}
+  r = requests.post(f"{API}/system/start", data=json.dumps(payload),
+                    timeout=10)
+  # assert r.status_code == 200
+  # returns 409
+  print(r.text)
+
+  until_true(
+      lambda: query_system_status().lower() == "waiting for device",
+      "system status is `waiting for device`",
+      30,
+  )
+
+  start_test_device("x123", BASELINE_MAC_ADDR)
+
+  until_true(
+      lambda: query_system_status().lower() == "compliant",
+      "system status is `complete`",
+      900,
+  )
+
+  stop_test_device("x123")
+
+@pytest.mark.skip()
+def test_status_non_compliant(testing_devices, testrun): # pylint: disable=W0613
+
+  r = requests.get(f"{API}/devices", timeout=5)
+  all_devices = r.json()
+  payload = {
+    "device": {
+      "mac_addr": all_devices[0]["mac_addr"],
+      "firmware": "asd"
+    }
+  }
+  r = requests.post(f"{API}/system/start", data=json.dumps(payload),
+                    timeout=10)
+  assert r.status_code == 200
+  print(r.text)
+
+  until_true(
+      lambda: query_system_status().lower() == "waiting for device",
+      "system status is `waiting for device`",
+      30,
+  )
+
+  start_test_device("x123", all_devices[0]["mac_addr"])
+
+  until_true(
+      lambda: query_system_status().lower() == "non-compliant",
+      "system status is `complete",
+      600,
+  )
+
+  stop_test_device("x123")

From 46c95fc805e1ef0fd197a2542f9ca49dc09ed318 Mon Sep 17 00:00:00 2001
From: MariusBaldovin <baldovin@google.com>
Date: Mon, 9 Sep 2024 14:00:58 +0100
Subject: [PATCH 19/25] updates on devices tests

---
 testing/api/test_api    |   2 +-
 testing/api/test_api.py | 717 +++++++++++++++++++++++-----------------
 2 files changed, 415 insertions(+), 304 deletions(-)

diff --git a/testing/api/test_api b/testing/api/test_api
index c1d3c19f7..095123a3b 100755
--- a/testing/api/test_api
+++ b/testing/api/test_api
@@ -37,7 +37,7 @@ sudo docker build ./testing/docker/ci_test_device1 -t test-run/ci_device_1 -f  .
 sudo chown -R $USER local
 
 # Copy configuration to testrun
-sudo cp testing/api/system.json local/system.json
+sudo cp testing/api/sys_config/system.json local/system.json
 
 # Needs to be sudo because this invokes bin/testrun 
 sudo venv/bin/python3 -m pytest -v testing/api/test_api.py
diff --git a/testing/api/test_api.py b/testing/api/test_api.py
index c8e6888d2..cbdbb11d6 100644
--- a/testing/api/test_api.py
+++ b/testing/api/test_api.py
@@ -16,7 +16,6 @@
 # pylint: disable=redefined-outer-name
 
 from collections.abc import Callable
-import copy
 import json
 import os
 import re
@@ -56,10 +55,16 @@ def pretty_print(dictionary: dict):
   """ Pretty print dictionary """
   print(json.dumps(dictionary, indent=4))
 
-def query_system_status() -> str:
-  """ Query system status from API and returns this """
+def query_system_status():
+  """ Query system/status endpoint and returns 'status' value """
+
+  # Send the get request
   r = requests.get(f"{API}/system/status", timeout=5)
+
+  # Parse the json response
   response = r.json()
+
+  # return the system status
   return response["status"]
 
 def query_test_count() -> int:
@@ -68,12 +73,23 @@ def query_test_count() -> int:
   response = r.json()
   return len(response["tests"]["results"])
 
+@pytest.fixture
+def testing_devices():
+  """ Use devices from the testing/devices directory """
+  delete_all_devices()
+  shutil.copytree(
+      os.path.join(os.path.dirname(__file__), TESTING_DEVICES),
+      os.path.join(DEVICES_DIRECTORY),
+      dirs_exist_ok=True,
+  )
+  return get_all_devices()
+
 def start_test_device(
-    device_name, mac_address, image_name="test-run/ci_device_1", args=""
+    device_name, mac_addr, image_name="test-run/ci_device_1", args=""
 ):
   """ Start test device container with given name """
   cmd = subprocess.run(
-      f"docker run -d --network=endev0 --mac-address={mac_address}"
+      f"docker run -d --network=endev0 --mac-address={mac_addr}"
       f" --cap-add=NET_ADMIN -v /tmp:/out --privileged --name={device_name}"
       f" {image_name} {args}",
       shell=True,
@@ -308,7 +324,7 @@ def test_update_sys_config(testrun, restore_sys_config): # pylint: disable=W0613
   # Check if 'internet_intf' has been updated
   assert updated_internet_intf == local_internet_intf
 
-def test_update_sys_config_invalid_payload(testrun): # pylint: disable=W0613
+def test_update_sys_config_invalid_json(testrun): # pylint: disable=W0613
   """ Test invalid payload for update system configuration (400) """
 
   # Empty payload
@@ -372,6 +388,50 @@ def test_get_sys_config(testrun): # pylint: disable=W0613
   # Check if the internet interface in the local config matches the API config
   assert api_internet_intf == local_internet_intf
 
+@pytest.fixture()
+def start_test():
+  """ Starts a testrun test """  
+
+  # Load the device (payload) using load_json utility method
+  device = load_json("device_config.json", directory=DEVICE_1_PATH)
+
+  # Assign the mac address
+  mac_addr = device["mac_addr"]
+
+  # Assign the test modules
+  test_modules = device["test_modules"]
+
+  # Payload with device details
+  payload = {
+    "device": {
+      "mac_addr": mac_addr,
+      "firmware": "test",
+      "test_modules": test_modules
+    }
+  }
+
+  # Send the post request (start test)
+  r = requests.post(f"{API}/system/start",
+                    data=json.dumps(payload),
+                    timeout=10)
+
+  # Exception if status code is not 200
+  if r.status_code != 200:
+    raise ValueError(f"API request failed with code: {r.status_code}")
+
+@pytest.fixture()
+def stop_test():
+  """ Stops a testrun test """ 
+
+  # Send the post request to stop the test
+  r = requests.post(f"{API}/system/stop", timeout=10)
+
+  # Exception if status code is not 200
+  if r.status_code != 200:
+    raise ValueError(f"API request failed with code: {r.status_code}")
+
+  # Validate system status
+
 def test_start_testrun_success(empty_devices_dir, add_one_device, testrun): # pylint: disable=W0613
   """ Test for testrun started successfully (200) """
 
@@ -379,7 +439,7 @@ def test_start_testrun_success(empty_devices_dir, add_one_device, testrun): # py
   device = load_json("device_config.json", directory=DEVICE_1_PATH)
 
   # Assign the device mac address
-  mac_address = device["mac_addr"]
+  mac_addr = device["mac_addr"]
 
   # Assign device modules
   test_modules = device["test_modules"]
@@ -387,7 +447,7 @@ def test_start_testrun_success(empty_devices_dir, add_one_device, testrun): # py
   # Payload with device details
   payload = {
     "device": {
-      "mac_addr": mac_address,
+      "mac_addr": mac_addr,
       "firmware": "test",
       "test_modules": test_modules
     }
@@ -443,14 +503,14 @@ def test_start_testrun_invalid_json(testrun): # pylint: disable=W0613
   assert "error" in response
 
 def test_start_testrun_already_started(empty_devices_dir, add_one_device, # pylint: disable=W0613
-                                                                testrun): # pylint: disable=W0613
+                                                    testrun, start_test): # pylint: disable=W0613
   """ Test for testrun already started (409) """
 
   # Load the device using load_json utility method
   device = load_json("device_config.json", directory=DEVICE_1_PATH)
 
   # Assign the device mac address
-  mac_address = device["mac_addr"]
+  mac_addr = device["mac_addr"]
 
   # Assign the test modules
   test_modules = device["test_modules"]
@@ -458,7 +518,7 @@ def test_start_testrun_already_started(empty_devices_dir, add_one_device, # pyli
   # Payload with device details
   payload = {
     "device": {
-      "mac_addr": mac_address,
+      "mac_addr": mac_addr,
       "firmware": "test",
       "test_modules": test_modules
     }
@@ -467,12 +527,6 @@ def test_start_testrun_already_started(empty_devices_dir, add_one_device, # pyli
   # Send the post request (start test)
   r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10)
 
-  # Check if the response status code is 200 (OK)
-  assert r.status_code == 200
-
-  # Send the second post request (start test again)
-  r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10)
-
   # Parse the json response
   response = r.json()
 
@@ -512,7 +566,7 @@ def test_start_testrun_error(empty_devices_dir, add_one_device, # pylint: disabl
   device = load_json("device_config.json", directory=DEVICE_1_PATH)
 
   # Assign the mac address
-  mac_address = device["mac_addr"]
+  mac_addr = device["mac_addr"]
 
   # Assign the test modules
   test_modules = device["test_modules"]
@@ -520,7 +574,7 @@ def test_start_testrun_error(empty_devices_dir, add_one_device, # pylint: disabl
   # Payload with device details
   payload = { "device":
                 {
-                "mac_addr": mac_address,
+                "mac_addr": mac_addr,
                 "firmware": "test",
                 "test_modules": test_modules
                 }
@@ -538,31 +592,10 @@ def test_start_testrun_error(empty_devices_dir, add_one_device, # pylint: disabl
   # Check if 'error' in response
   assert "error" in response
 
-def test_stop_running_testrun(empty_devices_dir, add_one_device, testrun): # pylint: disable=W0613
+def test_stop_running_testrun(empty_devices_dir, add_one_device, # pylint: disable=W0613
+                                           testrun, start_test): # pylint: disable=W0613
   """ Test for successfully stop testrun when test is running (200) """
 
-  # Load the device and mac address using add_device utility method
-  device = load_json("device_config.json", directory=DEVICE_1_PATH)
-
-  mac_address = device["mac_addr"]
-
-  test_modules = device["test_modules"]
-
-  # Payload with device details
-  payload = {
-            "device": {
-              "mac_addr": mac_address,
-              "firmware": "test",
-              "test_modules": test_modules
-              }
-            }
-
-  # Send the post request
-  r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10)
-
-  # Check if the response status code is 200 (OK)
-  assert r.status_code == 200
-
   # Send the post request to stop the test
   r = requests.post(f"{API}/system/stop", timeout=10)
 
@@ -575,19 +608,8 @@ def test_stop_running_testrun(empty_devices_dir, add_one_device, testrun): # pyl
   # Check if error in response
   assert "success" in response
 
-  # Validate system status
-
-  # Send the get request to retrieve system status
-  r = requests.get(f"{API}/system/status", timeout=5)
-
-  # Parse the json response
-  response = r.json()
-
-  # Check if status is 'Cancelled'
-  assert response["status"] == "Cancelled"
-
 def test_stop_testrun_not_running(testrun): # pylint: disable=W0613
-  """Test for stop testrun when is not running"""
+  """ Test for stop testrun when is not running (404) """
 
   # Send the post request to stop the test
   r = requests.post(f"{API}/system/stop", timeout=10)
@@ -601,8 +623,8 @@ def test_stop_testrun_not_running(testrun): # pylint: disable=W0613
   # Check if error in response
   assert "error" in response
 
-def test_system_shutdown(testrun): # pylint: disable=W0613
-  """Test for testrun shutdown endpoint (200)"""
+def test_sys_shutdown(testrun): # pylint: disable=W0613
+  """ Test for testrun shutdown endpoint (200) """
 
   # Send a POST request to initiate the system shutdown
   r = requests.post(f"{API}/system/shutdown", timeout=5)
@@ -616,35 +638,10 @@ def test_system_shutdown(testrun): # pylint: disable=W0613
   # Check if null in response
   assert response is None
 
-def test_system_shutdown_in_progress(empty_devices_dir, add_one_device, # pylint: disable=W0613
-                                     testrun): # pylint: disable=W0613
+def test_sys_shutdown_in_progress(empty_devices_dir, add_one_device, # pylint: disable=W0613
+                                     testrun, start_test): # pylint: disable=W0613
   """ Test system shutdown during an in-progress test (400) """
 
-  # Load the device using load_json utility method
-  device = load_json("device_config.json", directory=DEVICE_1_PATH)
-
-  # Assign the device mac address
-  mac_address = device["mac_addr"]
-
-  # Assign the test modules
-  test_modules = device["test_modules"]
-
-  # Payload with device details
-  payload = {
-    "device": {
-      "mac_addr": mac_address,
-      "firmware": "test",
-      "test_modules": test_modules
-    }
-  }
-
-  # Start a test
-  r = requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10)
-
-  # Check if status code is not 200 (OK)
-  if r.status_code != 200:
-    raise ValueError(f"Api request failed with code: {r.status_code}")
-
   # Attempt to shutdown while the test is running
   r = requests.post(f"{API}/system/shutdown", timeout=5)
 
@@ -657,13 +654,49 @@ def test_system_shutdown_in_progress(empty_devices_dir, add_one_device, # pylint
   # Check if 'error' in response
   assert "error" in response
 
-def test_status_idle(testrun): # pylint: disable=W0613
-  """Test system status 'idle' endpoint (/system/status)"""
-  until_true(
-      lambda: query_system_status().lower() == "idle",
-      "system status is `idle`",
-      30,
-  )
+def test_sys_status_idle(testrun): # pylint: disable=W0613
+  """ Test for system status 'Idle' (200) """
+
+  # Send the get request
+  r = requests.get(f"{API}/system/status", timeout=5)
+
+  # Check if the response status code is 200 (OK)
+  assert r.status_code == 200
+
+  # Parse the json response
+  response = r.json()
+
+  # Check if system status is 'Idle'
+  assert response["status"] == "Idle"
+
+def test_sys_status_cancelled(empty_devices_dir, add_one_device, # pylint: disable=W0613
+                                testrun, start_test, stop_test): # pylint: disable=W0613
+  """ Test for system status 'cancelled' (200) """
+
+  # Send the get request to retrieve system status
+  r = requests.get(f"{API}/system/status", timeout=5)
+
+  # Parse the json response
+  response = r.json()
+
+  # Check if status is 'Cancelled'
+  assert response["status"] == "Cancelled"
+
+def test_sys_status_waiting(empty_devices_dir, add_one_device, # pylint: disable=W0613
+                                         testrun, start_test): # pylint: disable=W0613
+  """ Test for system status 'Waiting for Device' (200) """
+
+  # Send the get request
+  r = requests.get(f"{API}/system/status", timeout=5)
+
+  # Check if the response status code is 200 (OK)
+  assert r.status_code == 200
+
+  # Parse the json response
+  response = r.json()
+
+  # Check if system status is 'Waiting for Device'
+  assert response["status"] == "Waiting for Device"
 
 def test_system_version(testrun): # pylint: disable=W0613
   """Test for testrun version endpoint"""
@@ -715,20 +748,20 @@ def test_get_test_modules(testrun): # pylint: disable=W0613
 def create_report_folder(): # pylint: disable=W0613
   """Fixture to create the reports folder in local/devices"""
 
-  def _create_report_folder(device_name, mac_address, timestamp):
+  def _create_report_folder(device_name, mac_addr, timestamp):
 
     # Create the device folder path
     main_folder = os.path.join(DEVICES_DIRECTORY, device_name)
 
     # Remove the ":" from mac address for the folder structure
-    mac_address = mac_address.replace(":", "")
+    mac_addr = mac_addr.replace(":", "")
 
     # Change the timestamp format for the folder structure
     timestamp = timestamp.replace(" ", "T")
 
     # Create the report folder path
     report_folder = os.path.join(main_folder, "reports", timestamp,
-                                 "test", mac_address)
+                                 "test", mac_addr)
 
     # Ensure the report folder exists
     os.makedirs(report_folder, exist_ok=True)
@@ -777,17 +810,17 @@ def test_delete_report_success(empty_devices_dir, add_one_device, # pylint: disa
   device = load_json("device_config.json", directory=DEVICE_1_PATH)
 
   # Assign the device mac address
-  mac_address = device["mac_addr"]
+  mac_addr = device["mac_addr"]
 
   # Assign the device name
   device_name = f'{device["manufacturer"]} {device["model"]}'
 
   # Create the report directory
-  report_folder = create_report_folder(device_name, mac_address, TIMESTAMP)
+  report_folder = create_report_folder(device_name, mac_addr, TIMESTAMP)
 
   # Payload
   delete_data = {
-    "mac_addr": mac_address,
+    "mac_addr": mac_addr,
     "timestamp": TIMESTAMP
   }
 
@@ -833,13 +866,13 @@ def test_delete_report_invalid_payload(empty_devices_dir, add_one_device, # pyli
   device = load_json("device_config.json", directory=DEVICE_1_PATH)
 
   # Assign the device mac address
-  mac_address = device["mac_addr"]
+  mac_addr = device["mac_addr"]
 
   # Assign the device name
   device_name = f'{device["manufacturer"]} {device["model"]}'
 
   # Create the report directory
-  create_report_folder(device_name, mac_address, TIMESTAMP)
+  create_report_folder(device_name, mac_addr, TIMESTAMP)
 
   # Empty payload
   delete_data = {}
@@ -867,7 +900,7 @@ def test_delete_report_invalid_timestamp(empty_devices_dir, add_one_device, # py
   device = load_json("device_config.json", directory=DEVICE_1_PATH)
 
   # Assign the device mac address
-  mac_address = device["mac_addr"]
+  mac_addr = device["mac_addr"]
 
   # Assign the device name
   device_name = f'{device["manufacturer"]} {device["model"]}'
@@ -876,11 +909,11 @@ def test_delete_report_invalid_timestamp(empty_devices_dir, add_one_device, # py
   timestamp = "2024-01-01 invalid"
 
   # Create the report.json
-  create_report_folder(device_name, mac_address, timestamp)
+  create_report_folder(device_name, mac_addr, timestamp)
 
   # Payload
   delete_data = {
-    "mac_addr": mac_address,
+    "mac_addr": mac_addr,
     "timestamp": timestamp
   }
 
@@ -930,11 +963,11 @@ def test_delete_report_no_report(empty_devices_dir, add_one_device, testrun): #
   device = load_json("device_config.json", directory=DEVICE_1_PATH)
 
   # Assign the device mac address
-  mac_address = device["mac_addr"]
+  mac_addr = device["mac_addr"]
 
   # Prepare the payload for the DELETE request
   delete_data = {
-      "mac_addr": mac_address,
+      "mac_addr": mac_addr,
       "timestamp": TIMESTAMP
   }
 
@@ -963,7 +996,7 @@ def test_get_report_success(empty_devices_dir, add_one_device, # pylint: disable
   device = load_json("device_config.json", directory=DEVICE_1_PATH)
 
   # Assign the device mac address
-  mac_address = device["mac_addr"]
+  mac_addr = device["mac_addr"]
 
   # Assign the device name
   device_name = f'{device["manufacturer"]} {device["model"]}'
@@ -972,7 +1005,7 @@ def test_get_report_success(empty_devices_dir, add_one_device, # pylint: disable
   timestamp = TIMESTAMP.replace(" ", "T")
 
   # Create the report for the device
-  create_report_folder(device_name, mac_address, timestamp)
+  create_report_folder(device_name, mac_addr, timestamp)
 
   # Send the get request
   r = requests.get(f"{API}/report/{device_name}/{timestamp}", timeout=5)
@@ -1032,12 +1065,12 @@ def test_export_report_device_not_found(empty_devices_dir, testrun, # pylint: di
                                  create_report_folder):
   """Test for export the report result when the device could not be found"""
 
-  # Assign the non-existing device name, mac_address
+  # Assign the non-existing device name, mac_addr
   device_name = "non existing device"
-  mac_address = "00:1e:42:35:73:c4"
+  mac_addr = "00:1e:42:35:73:c4"
 
   # Create the report for the non-existing device
-  create_report_folder(device_name, mac_address, TIMESTAMP)
+  create_report_folder(device_name, mac_addr, TIMESTAMP)
 
   # Send the post request
   r = requests.post(f"{API}/export/{device_name}/{TIMESTAMP}", timeout=5)
@@ -1062,13 +1095,13 @@ def test_export_report_profile_not_found(empty_devices_dir, add_one_device, # py
   device = load_json("device_config.json", directory=DEVICE_1_PATH)
 
   # Assign the device mac address
-  mac_address = device["mac_addr"]
+  mac_addr = device["mac_addr"]
 
   # Assign the device name
   device_name = f'{device["manufacturer"]} {device["model"]}'
 
   # Create the report for the device
-  create_report_folder(device_name, mac_address, TIMESTAMP)
+  create_report_folder(device_name, mac_addr, TIMESTAMP)
 
   # Add a non existing profile into the payload
   payload = {"profile": "non_existent_profile"}
@@ -1126,7 +1159,7 @@ def test_export_report_with_profile(empty_devices_dir, add_one_device, # pylint:
   device = load_json("device_config.json", directory=DEVICE_1_PATH)
 
   # Assign the device mac address
-  mac_address = device["mac_addr"]
+  mac_addr = device["mac_addr"]
 
   # Assign the device name
   device_name = f'{device["manufacturer"]} {device["model"]}'
@@ -1135,7 +1168,7 @@ def test_export_report_with_profile(empty_devices_dir, add_one_device, # pylint:
   timestamp = TIMESTAMP.replace(" ", "T")
 
   # Create the report for the device
-  create_report_folder(device_name, mac_address, timestamp)
+  create_report_folder(device_name, mac_addr, timestamp)
 
   # Send the post request
   r = requests.post(f"{API}/export/{device_name}/{timestamp}",
@@ -1159,13 +1192,13 @@ def test_export_results_with_no_profile(empty_devices_dir, add_one_device, # pyl
   device_name = f'{device["manufacturer"]} {device["model"]}'
 
   # Assign the device mac address
-  mac_address = device["mac_addr"]
+  mac_addr = device["mac_addr"]
 
   # Assign the timestamp and change the format
   timestamp = TIMESTAMP.replace(" ", "T")
 
   # Create the report for the device
-  create_report_folder(device_name, mac_address, timestamp)
+  create_report_folder(device_name, mac_addr, timestamp)
 
   # Send the post request
   r = requests.post(f"{API}/export/{device_name}/{timestamp}", timeout=5)
@@ -1320,21 +1353,10 @@ def device_exists(device_mac):
   # Return if mac address is in the list of devices
   return any(p["mac_addr"] == device_mac for p in devices)
 
-@pytest.fixture
-def testing_devices():
-  """ Use devices from the testing/devices directory """
-  delete_all_devices()
-  shutil.copytree(
-      os.path.join(os.path.dirname(__file__), TESTING_DEVICES),
-      os.path.join(DEVICES_DIRECTORY),
-      dirs_exist_ok=True,
-  )
-  return get_all_devices()
-
 def test_get_devices_no_devices(empty_devices_dir, testrun): # pylint: disable=W0613
   """ Test for get devices endpoint when no devices are available (200) """
 
-  # Check if there are no devices in local/devices
+  # Error handling if there are devices in local/devices
   if len(get_all_devices()) != 0:
     raise Exception("Expected no devices in local/devices")
 
@@ -1359,7 +1381,7 @@ def test_get_devices_no_devices(empty_devices_dir, testrun): # pylint: disable=W
 def test_get_devices_one_device(empty_devices_dir, add_one_device, testrun): # pylint: disable=W0613
   """ Test for get devices endpoint when one device is created (200) """
 
-  # Check if there is one device in local/devices
+  # Error handling if there is not one device in local/devices
   if len(get_all_devices()) != 1:
     raise Exception("Expected one device in local/devices")
 
@@ -1378,7 +1400,7 @@ def test_get_devices_one_device(empty_devices_dir, add_one_device, testrun): # p
 def test_get_devices_two_devices(empty_devices_dir, add_two_devices, testrun): # pylint: disable=W0613
   """ Test for get devices endpoint when two devices are created (200) """
 
-  # Check if there are two devices in local/devices
+  # Error handling if there are not two devices in local/devices
   if len(get_all_devices()) != 2:
     raise Exception("Expected two devices in local/devices")
 
@@ -1406,14 +1428,11 @@ def test_get_devices_two_devices(empty_devices_dir, add_two_devices, testrun): #
     "test_modules",
   ]
 
-  # Iterate over all devices
-  for device in response:
-
-    # Iterate over all expected fields list
-    for field in expected_fields:
+  # Iterate over all expected_fields list
+  for field in expected_fields:
 
-      # Check if the device has all the expected fields
-      assert field in device
+    # Check if both devices have the expected fields
+    assert all(field in r for r in response)
 
 def test_create_device(empty_devices_dir, testrun): # pylint: disable=W0613
   """ Test for successfully create device endpoint (201) """
@@ -1421,10 +1440,11 @@ def test_create_device(empty_devices_dir, testrun): # pylint: disable=W0613
   # Load the first device using load_json utility method
   device_1 = load_json("device_config.json", directory=DEVICE_1_PATH)
 
+  # Assign the mac address for the first device
+  mac_addr_1 = device_1["mac_addr"]
+
   # Send the post request to the '/device' endpoint
-  r = requests.post(f"{API}/device",
-                    data=json.dumps(device_1),
-                    timeout=5)
+  r = requests.post(f"{API}/device", data=json.dumps(device_1), timeout=5)
 
   # Check if status code is 201 (Created)
   assert r.status_code == 201
@@ -1432,13 +1452,14 @@ def test_create_device(empty_devices_dir, testrun): # pylint: disable=W0613
   # Check if there is one device in 'local/devices'
   assert len(get_all_devices()) == 1
 
-  # Load the first device using load_json utility method
+  # Load the second device using load_json utility method
   device_2 = load_json("device_config.json", directory=DEVICE_2_PATH)
 
+  # Assign the mac address for the second device
+  mac_addr_2 = device_2["mac_addr"]
+
   # Send the post request to the '/device' endpoint
-  r = requests.post(f"{API}/device",
-                    data=json.dumps(device_2),
-                    timeout=5)
+  r = requests.post(f"{API}/device", data=json.dumps(device_2), timeout=5)
 
   # Check if status code is 201 (Created)
   assert r.status_code == 201
@@ -1452,209 +1473,313 @@ def test_create_device(empty_devices_dir, testrun): # pylint: disable=W0613
   # Parse the json response (devices)
   response = r.json()
 
-  # Assign the expected fields from device
-  expected_fields = [
-    "mac_addr",
-    "manufacturer",
-    "model",
-    "type",
-    "technology",
-    "test_pack",
-    "additional_info",
-    "test_modules",
+  # Iterate through all the devices to find the device based on the "mac_addr"
+  created_devices = [
+    d for d in response
+    if d["mac_addr"] in {mac_addr_1, mac_addr_2}
   ]
 
-  # Iterate over all expected_fields list
-  for field in expected_fields:
+  # Check if both devices have been found
+  assert len(created_devices) == 2
 
-    # If the field is 'additional_info' compare lists
-    if field == "additional_info":
-      assert response[0][field] == device_1[field]
-      assert response[1][field] == device_2[field]
+def test_create_device_already_exists(empty_devices_dir, add_one_device, # pylint: disable=W0613
+                                                               testrun): # pylint: disable=W0613
+  """ Test for crete device when device already exists (409) """
 
-    # If the field is 'test_modules' compare dictionaries
-    elif field == "test_modules":
-      assert response[0][field] == device_1[field]
-      assert response[1][field] == device_2[field]
+  # Error handling if there is not one devices in local/devices
+  if len(get_all_devices()) != 1:
+    raise Exception("Expected one device in local/devices")
 
-    # For the other fields check if the created device has the given values
-    else:
-      assert set([response[0][field], response[1][field]]) == set(
-        [device_1[field], device_2[field]])
+  # Load the device (payload) using load_json utility method
+  device = load_json("device_config.json", directory=DEVICE_1_PATH)
 
-  # Check if 'status' in api response
-  assert all("status" in r for r in response)
+  # Send the post request to create the device
+  r = requests.post(f"{API}/device", data=json.dumps(device), timeout=5)
 
-def test_create_device_already_exists(
-    empty_devices_dir, # pylint: disable=W0613
-    testrun): # pylint: disable=W0613
+  # Check if status code is 409 (conflict)
+  assert r.status_code == 409
 
-  device_1 = load_json("device_config.json", directory=DEVICE_1_PATH)
+  # Parse the json response (devices)
+  response = r.json()
 
-  r = requests.post(f"{API}/device",
-                    data=json.dumps(device_1),
-                    timeout=5)
-  assert r.status_code == 201
+  # Check if 'error' in response
+  assert "error" in response
+
+  # Check if 'local/device' has only one device
   assert len(get_all_devices()) == 1
 
-  r = requests.post(f"{API}/device",
-                    data=json.dumps(device_1),
-                    timeout=5)
-  assert r.status_code == 409
+def test_create_device_invalid_json(empty_devices_dir, testrun): # pylint: disable=W0613
+  """ Test for create device invalid json payload """
 
-def test_create_device_invalid_json(
-    empty_devices_dir, # pylint: disable=W0613
-    testrun): # pylint: disable=W0613
+  # Error handling if there are devices in local/devices
+  if len(get_all_devices()) != 0:
+    raise Exception("Expected no device in local/devices")
 
-  device_1 = {}
+  # Empty payload
+  device = {}
 
-  r = requests.post(f"{API}/device",
-                    data=json.dumps(device_1),
-                    timeout=5)
+  # Send the post request
+  r = requests.post(f"{API}/device", data=json.dumps(device), timeout=5)
+
+  # Check if status code is 400 (bad request)
   assert r.status_code == 400
 
-def test_create_device_invalid_request(
-    empty_devices_dir, # pylint: disable=W0613
-    testrun): # pylint: disable=W0613
+  # Parse the json response (devices)
+  response = r.json()
 
-  r = requests.post(f"{API}/device",
-                    data=None,
-                    timeout=5)
+  # Check if 'error' in response
+  assert "error" in response
+
+  # Check if 'local/device' has no devices
+  assert len(get_all_devices()) == 0
+
+def test_create_device_invalid_request(empty_devices_dir, testrun): # pylint: disable=W0613
+  """ Test for create device when no payload is added """
+
+  # Send the post request with no payload
+  r = requests.post(f"{API}/device", data=None, timeout=5)
+
+  # Check if status code is 400 (bad request)
   assert r.status_code == 400
 
-def test_device_edit_device(
-    testing_devices, # pylint: disable=W0613
-    testrun): # pylint: disable=W0613
-  with open(
-      testing_devices[1], encoding="utf-8"
-  ) as f:
-    local_device = json.load(f)
+  # Parse the json response (devices)
+  response = r.json()
+
+  # Check if 'error' in response
+  assert "error" in response
 
-  mac_addr = local_device["mac_addr"]
-  new_model = "Alphabet"
+  # Check if 'local/device' has no devices
+  assert len(get_all_devices()) == 0
 
-  r = requests.get(f"{API}/devices", timeout=5)
-  all_devices = r.json()
+def test_edit_device(empty_devices_dir, add_one_device,  # pylint: disable=W0613
+                                              testrun): # pylint: disable=W0613
+  """ Test for successfully edit device (200) """
 
-  api_device = next(x for x in all_devices if x["mac_addr"] == mac_addr)
+  # Error handling if there is not one devices in local/devices
+  if len(get_all_devices()) != 1:
+    raise Exception("Expected one device in local/devices")
 
-  updated_device = copy.deepcopy(api_device)
-  updated_device["model"] = new_model
+  # Load the device (payload) using load_json utility method
+  device = load_json("device_config.json", directory=DEVICE_1_PATH)
 
-  new_test_modules = {
-      k: {"enabled": not v["enabled"]}
-      for k, v in updated_device["test_modules"].items()
-  }
-  updated_device["test_modules"] = new_test_modules
+  # Assign the mac address
+  mac_addr = device["mac_addr"]
 
-  updated_device_payload = {}
-  updated_device_payload["device"] = updated_device
-  updated_device_payload["mac_addr"] = mac_addr
+  # Update the manufacturer and model values
+  device["manufacturer"] = "Updated Manufacturer"
+  device["model"] = "Updated Model"
+
+  # Payload with the updated device name
+  updated_device = {
+    "mac_addr": mac_addr,
+    "device": device 
+    }
 
-  print("updated_device")
-  pretty_print(updated_device)
-  print("api_device")
-  pretty_print(api_device)
+  # Exception if the device is not found
+  if not device_exists(mac_addr):
+    raise ValueError(f"Device with mac address:{mac_addr} not found")
 
-  # update device
-  r = requests.post(f"{API}/device/edit",
-                    data=json.dumps(updated_device_payload),
-                    timeout=5)
+  # Send the post request to update the device
+  r = requests.post(
+      f"{API}/device/edit",
+      data=json.dumps(updated_device),
+      timeout=5)
 
+  # Check if status code is 200 (OK)
   assert r.status_code == 200
 
+  # Check if 'local/device' still has only one device
+  assert len(get_all_devices()) == 1
+
+  # Send a get request to verify device update
   r = requests.get(f"{API}/devices", timeout=5)
-  all_devices = r.json()
-  updated_device_api = next(x for x in all_devices if x["mac_addr"] == mac_addr)
 
-  assert updated_device_api["model"] == new_model
-  assert updated_device_api["test_modules"] == new_test_modules
+  # Check if status code is 200 (OK)
+  assert r.status_code == 200
 
-def test_edit_device_not_found(
-    empty_devices_dir, # pylint: disable=W0613
-    testrun): # pylint: disable=W0613
+  # Parse the response (devices list)
+  response = r.json()
 
-  device_1 = load_json("device_config.json", directory=DEVICE_1_PATH)
+  # Iterate through the devices to find the device based on "mac_addr"
+  updated_device = next(
+    (d for d in response if d["mac_addr"] == mac_addr),
+    None
+  )
 
-  r = requests.post(f"{API}/device",
-                    data=json.dumps(device_1),
-                    timeout=5)
-  assert r.status_code == 201
-  assert len(get_all_devices()) == 1
+  # Error handling if the device is not being found
+  if updated_device is None:
+    raise Exception("The device could not be found")
 
-  updated_device = copy.deepcopy(device_1)
+  # Check if device "manufacturer" was updated
+  assert device["manufacturer"] == updated_device["manufacturer"]
 
-  updated_device_payload = {}
-  updated_device_payload["device"] = updated_device
-  updated_device_payload["mac_addr"] = "00:1e:42:35:73:c6"
-  updated_device_payload["model"] = "Alphabet"
+  # Check if device "manufacturer" was updated
+  assert device["model"] == updated_device["model"]
 
+def test_edit_device_not_found(empty_devices_dir, testrun): # pylint: disable=W0613
 
-  r = requests.post(f"{API}/device/edit",
-                      data=json.dumps(updated_device_payload),
-                      timeout=5)
+  """ Test for edit device when device is not found (404) """
+
+  # Error handling if there are devices in local/devices
+  if len(get_all_devices()) != 0:
+    raise Exception("Expected no device in local/devices")
+
+  # Load the device (payload) using load_json utility method
+  device = load_json("device_config.json", directory=DEVICE_1_PATH)
+
+  # Assign the mac address
+  mac_addr = device["mac_addr"]
+
+  # Update the manufacturer and model values
+  device["manufacturer"] = "Updated manufacturer"
+  device["model"] = "Updated model"
+
+  # Payload with the updated device name
+  updated_device = {
+    "mac_addr": mac_addr,
+    "device": device 
+    }
+
+  # Exception if the device is found
+  if device_exists(mac_addr):
+    raise ValueError(f"Device with mac address:{mac_addr} found")
 
+  # Send the post request to update the device
+  r = requests.post(
+      f"{API}/device/edit",
+      data=json.dumps(updated_device),
+      timeout=5)
+
+  # Check if status code is 404 (not found)
   assert r.status_code == 404
 
-def test_edit_device_incorrect_json_format(
-    empty_devices_dir, # pylint: disable=W0613
-    testrun): # pylint: disable=W0613
+  # Parse the json response (devices)
+  response = r.json()
 
-  device_1 = load_json("device_config.json", directory=DEVICE_1_PATH)
+  # Check if 'error' in response
+  assert "error" in response
 
-  r = requests.post(f"{API}/device",
-                    data=json.dumps(device_1),
-                    timeout=5)
-  assert r.status_code == 201
-  assert len(get_all_devices()) == 1
+  # Check if 'local/device' still has no devices
+  assert len(get_all_devices()) == 0
 
-  updated_device_payload = {}
+def test_edit_device_invalid_json(empty_devices_dir, testrun): # pylint: disable=W0613
+  """ Test for edit device invalid json (400) """
 
+  # Empty payload
+  payload = {}
 
+  # Send the post request to update the device
   r = requests.post(f"{API}/device/edit",
-                      data=json.dumps(updated_device_payload),
+                      data=json.dumps(payload),
                       timeout=5)
 
+  # Check if status code is 400 (bad request)
   assert r.status_code == 400
 
-def test_edit_device_with_mac_already_exists(
-    empty_devices_dir, # pylint: disable=W0613
-    testrun): # pylint: disable=W0613
+  # Parse the json response (devices)
+  response = r.json()
 
-  device_1 = load_json("device_config.json", directory=DEVICE_1_PATH)
+  # Check if 'error' in response
+  assert "error" in response
 
-  r = requests.post(f"{API}/device",
-                    data=json.dumps(device_1),
-                    timeout=5)
+def test_edit_device_mac_already_exists( empty_devices_dir, add_two_devices, # pylint: disable=W0613
+                                                                  testrun): # pylint: disable=W0613
+  """ Test for edit device when the mac address already exists (409) """
 
-  assert r.status_code == 201
+  # Load the first device (payload) using load_json utility method
+  device_1 = load_json("device_config.json", directory=DEVICE_1_PATH)
 
-  assert len(get_all_devices()) == 1
+  # Assign the device_1 initial mac address
+  mac_addr_1 = device_1["mac_addr"]
 
+  # Load the second device using load_json utility method
   device_2 = load_json("device_config.json", directory=DEVICE_2_PATH)
 
-  r = requests.post(f"{API}/device",
-                    data=json.dumps(device_2),
+  # Update the device_1 mac address with device_2 mac address
+  device_1["mac_addr"] = device_2["mac_addr"]
+
+  # Payload with the updated device mac address
+  updated_device = {
+    "mac_addr": mac_addr_1,
+    "device": device_1 
+    }
+
+  # Exception if the device is not found
+  if not device_exists(mac_addr_1):
+    raise ValueError(f"Device with mac address:{mac_addr_1} not found")
+
+  # Send the post request to update the device
+  r = requests.post(f"{API}/device/edit",
+                    data=json.dumps(updated_device),
                     timeout=5)
 
-  assert r.status_code == 201
+  # Check if status code is 409 (conflict)
+  assert r.status_code == 409
 
-  assert len(get_all_devices()) == 2
+  # Parse the json response (devices)
+  response = r.json()
 
-  updated_device = copy.deepcopy(device_1)
+  # Check if 'error' in response
+  assert "error" in response
 
-  updated_device_payload = {}
+def test_edit_device_test_in_progress(empty_devices_dir, add_one_device,  # pylint: disable=W0613
+                                                   testrun, start_test): # pylint: disable=W0613
+  """ Test for edit device when a test is in progress (403) """
 
-  updated_device_payload["device"] = updated_device
-  updated_device_payload["mac_addr"] = "00:1e:42:35:73:c6"
-  updated_device_payload["model"] = "Alphabet"
+  # Load the device (payload) using load_json utility method
+  device = load_json("device_config.json", directory=DEVICE_1_PATH)
 
+  # Assign the mac address
+  mac_addr = device["mac_addr"]
 
-  r = requests.post(f"{API}/device/edit",
-                      data=json.dumps(updated_device_payload),
-                      timeout=5)
+  # Update the manufacturer and model values
+  device["manufacturer"] = "Updated Manufacturer"
+  device["model"] = "Updated Model"
 
-  assert r.status_code == 409
+  # Payload with the updated device name
+  updated_device = {
+    "mac_addr": mac_addr,
+    "device": device 
+    }
+
+  # Exception if the device is not found
+  if not device_exists(mac_addr):
+    raise ValueError(f"Device with mac address:{mac_addr} not found")
+
+  # Send the post request to update the device
+  r = requests.post(
+      f"{API}/device/edit",
+      data=json.dumps(updated_device),
+      timeout=5)
+
+  # Check if status code is 403 (forbidden)
+  assert r.status_code == 403
+
+  # Send a get request to verify that device was not updated
+  r = requests.get(f"{API}/devices", timeout=5)
+
+  # Exception if status code is not 200
+  if r.status_code != 200:
+    raise ValueError(f"API request failed with code: {r.status_code}")
+
+  # Parse the response (devices list)
+  response = r.json()
+
+  # Iterate through the devices to find the device based on "mac_addr"
+  updated_device = next(
+    (d for d in response if d["mac_addr"] == mac_addr),
+    None
+  )
+
+  # Error handling if the device is not being found
+  if updated_device is None:
+    raise Exception("The device could not be found")
+
+  # Check that device "manufacturer" was not updated
+  assert device["manufacturer"] != updated_device["manufacturer"]
+
+  # Check that device "manufacturer" was not updated
+  assert device["model"] != updated_device["model"]
 
 def test_edit_device_invalid_manufacturer(empty_devices_dir, add_one_device, # pylint: disable=W0613
                                                                    testrun): # pylint: disable=W0613
@@ -1726,15 +1851,16 @@ def test_edit_long_chars(empty_devices_dir, testrun): # pylint: disable=W0613
 def test_delete_device(empty_devices_dir, add_one_device, testrun): # pylint: disable=W0613
   """ Test for succesfully delete device endpoint (200) """
 
+  # Load the device
   device = load_json("device_config.json", directory=DEVICE_1_PATH)
 
   # Assign the mac address
-  mac_address = device["mac_addr"]
+  mac_addr = device["mac_addr"]
 
-  # Assign the payload with device to be deleted mac address
-  payload = { "mac_addr": mac_address }
+  # Assign the payload with device to be deleted
+  payload = { "mac_addr": mac_addr }
 
-  # Test that device_1 deletes
+  # Send the delete request
   r = requests.delete(f"{API}/device/",
               data=json.dumps(payload),
                             timeout=5)
@@ -1760,7 +1886,7 @@ def test_delete_device(empty_devices_dir, add_one_device, testrun): # pylint: di
 
   # Iterate through the devices to find the device based on the 'mac address'
   deleted_device = next(
-      (d for d in device if d["mac_addr"] == mac_address),
+      (d for d in device if d["mac_addr"] == mac_addr),
       None
   )
 
@@ -1811,39 +1937,24 @@ def test_delete_device_no_mac(empty_devices_dir, add_one_device, testrun): # pyl
   assert len(get_all_devices()) == 1
 
 def test_delete_device_testrun_in_progress(empty_devices_dir, add_one_device, # pylint: disable=W0613
-                                                                    testrun): # pylint: disable=W0613
+                                                        testrun, start_test): # pylint: disable=W0613
   """ Test for delete device when testrun is in progress (403) """
 
   # Load the device details
   device = load_json("device_config.json", directory=DEVICE_1_PATH)
 
   # Assign the mac address
-  mac_address = device["mac_addr"]
-
-  # Assign the test modules
-  test_modules = device["test_modules"]
-
-  # Payload with device details
-  payload = {
-    "device": {
-      "mac_addr": mac_address,
-      "firmware": "test",
-      "test_modules": test_modules
-    }
-  }
-
-  # Send the post request to start a test
-  requests.post(f"{API}/system/start", data=json.dumps(payload), timeout=10)
+  mac_addr = device["mac_addr"]
 
   # Assign the payload with device to be deleted mac address
-  payload = { "mac_addr": mac_address }
+  payload = { "mac_addr": mac_addr }
 
   # Send the delete request
   r = requests.delete(f"{API}/device/",
               data=json.dumps(payload),
                             timeout=5)
 
-  # Check if status code is 403 (Forbidden)
+  # Check if status code is 403 (forbidden)
   assert r.status_code == 403
 
   # Parse the JSON response
@@ -2789,14 +2900,14 @@ def test_stop_running_test(empty_devices_dir, add_one_device, testrun): # pylint
   # Load the device and mac address using add_device utility method
   device = load_json("device_config.json", directory=DEVICE_1_PATH)
 
-  mac_address = device["mac_addr"]
+  mac_addr = device["mac_addr"]
 
   test_modules = device["test_modules"]
 
   # Payload with device details
   payload = {
             "device": {
-              "mac_addr": mac_address,
+              "mac_addr": mac_addr,
               "firmware": "test",
               "test_modules": test_modules
               }

From 5d9693dedf02fabdc973a14b8bf4ddaaf4135caf Mon Sep 17 00:00:00 2001
From: Marius <86727846+MariusBaldovin@users.noreply.github.com>
Date: Tue, 10 Sep 2024 09:24:25 +0100
Subject: [PATCH 20/25] Update testing.yml

Signed-off-by: Marius <86727846+MariusBaldovin@users.noreply.github.com>
---
 .github/workflows/testing.yml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml
index 2f2115892..7917f1299 100644
--- a/.github/workflows/testing.yml
+++ b/.github/workflows/testing.yml
@@ -2,6 +2,7 @@ name: Testrun test suite
 permissions: {}
 
 on:
+  push:
   pull_request:
   schedule:
     - cron: '0 13 * * *'

From d79fc7f52a4ab1def79b397a0ae4367020b05ba7 Mon Sep 17 00:00:00 2001
From: MariusBaldovin <baldovin@google.com>
Date: Tue, 10 Sep 2024 09:39:32 +0100
Subject: [PATCH 21/25] added comments to testrun fixture

---
 testing/api/test_api.py | 27 +++++++++++++++++++++++++--
 1 file changed, 25 insertions(+), 2 deletions(-)

diff --git a/testing/api/test_api.py b/testing/api/test_api.py
index cbdbb11d6..d9de29d8f 100644
--- a/testing/api/test_api.py
+++ b/testing/api/test_api.py
@@ -137,6 +137,8 @@ def load_json(file_name, directory):
 @pytest.fixture
 def testrun(request): # pylint: disable=W0613
   """ Start instance of testrun """
+
+  # Launch the Testrun in a new process group
   # pylint: disable=W1509
   with subprocess.Popen(
       "bin/testrun",
@@ -146,42 +148,63 @@ def testrun(request): # pylint: disable=W0613
       preexec_fn=os.setsid
   ) as proc:
 
+    # Wait until the API is ready to accept requests or timeout
     while True:
+
       try:
+
+        # Capture the process output
         outs = proc.communicate(timeout=1)[0]
+
       except subprocess.TimeoutExpired as e:
+
+        # If output is captured during timeout, decode and check
         if e.output is not None:
           output = e.output.decode("utf-8")
+        
           if re.search("API waiting for requests", output):
             break
+
       except Exception:
-        pytest.fail("testrun terminated")
+        # Fail if the Testrun process unexpectedly terminates
+        pytest.fail("Testrun terminated")
 
+    # Wait for two of seconds before yielding
     time.sleep(2)
 
     yield
 
+    # Terminate the Testrun process group
     os.killpg(os.getpgid(proc.pid), signal.SIGTERM)
     try:
+
+      # Wait up to 60 seconds for clean termination of the Testrun process
       outs = proc.communicate(timeout=60)[0]
+
+    # If termination exceeds the timeout, force to kill the process
     except subprocess.TimeoutExpired as e:
       print(e.output)
       os.killpg(os.getpgid(proc.pid), signal.SIGKILL)
       pytest.exit(
-          "waited 60s but Testrun did not cleanly exit .. terminating all tests"
+          "Waited 60s but Testrun did not cleanly exit .. terminating all tests"
       )
 
   print(outs)
 
+  # Stop any left Docker containers after the test
   cmd = subprocess.run(
       "docker stop $(docker ps -a -q)", shell=True,
       capture_output=True, check=False
   )
+
   print(cmd.stdout)
+
+  # Remove the stopped Docker containers 
   cmd = subprocess.run(
       "docker rm  $(docker ps -a -q)", shell=True,
       capture_output=True, check=False
   )
+
   print(cmd.stdout)
 
 def until_true(func: Callable, message: str, timeout: int):

From cfdd3850143e3f11363d76be445df81f0fbdbb3a Mon Sep 17 00:00:00 2001
From: MariusBaldovin <baldovin@google.com>
Date: Tue, 10 Sep 2024 09:55:01 +0100
Subject: [PATCH 22/25] fixed pylint

---
 testing/api/test_api.py | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/testing/api/test_api.py b/testing/api/test_api.py
index d9de29d8f..c312328aa 100644
--- a/testing/api/test_api.py
+++ b/testing/api/test_api.py
@@ -161,7 +161,8 @@ def testrun(request): # pylint: disable=W0613
         # If output is captured during timeout, decode and check
         if e.output is not None:
           output = e.output.decode("utf-8")
-        
+
+          # Check if the output contains the message indicating the API is ready
           if re.search("API waiting for requests", output):
             break
 
@@ -199,7 +200,7 @@ def testrun(request): # pylint: disable=W0613
 
   print(cmd.stdout)
 
-  # Remove the stopped Docker containers 
+  # Remove the stopped Docker containers
   cmd = subprocess.run(
       "docker rm  $(docker ps -a -q)", shell=True,
       capture_output=True, check=False

From e21fb0c53a5909982f6f26094d6c6962ba05d6a8 Mon Sep 17 00:00:00 2001
From: MariusBaldovin <baldovin@google.com>
Date: Tue, 10 Sep 2024 10:01:10 +0100
Subject: [PATCH 23/25] reverted testing.yml back to original state

---
 .github/workflows/testing.yml | 1 -
 1 file changed, 1 deletion(-)

diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml
index 7917f1299..2f2115892 100644
--- a/.github/workflows/testing.yml
+++ b/.github/workflows/testing.yml
@@ -2,7 +2,6 @@ name: Testrun test suite
 permissions: {}
 
 on:
-  push:
   pull_request:
   schedule:
     - cron: '0 13 * * *'

From cf3035044f9305ad6b470e18cfe5883dc307e455 Mon Sep 17 00:00:00 2001
From: MariusBaldovin <baldovin@google.com>
Date: Tue, 10 Sep 2024 12:05:25 +0100
Subject: [PATCH 24/25] updated profiles directory, small updates on code

---
 testing/api/profiles/draft_profile.json       |  35 ++++
 testing/api/profiles/new_profile_2.json       |  37 ----
 ...{new_profile_1.json => valid_profile.json} |  28 +--
 testing/api/test_api.py                       | 165 +++++++++---------
 4 files changed, 128 insertions(+), 137 deletions(-)
 create mode 100644 testing/api/profiles/draft_profile.json
 delete mode 100644 testing/api/profiles/new_profile_2.json
 rename testing/api/profiles/{new_profile_1.json => valid_profile.json} (69%)

diff --git a/testing/api/profiles/draft_profile.json b/testing/api/profiles/draft_profile.json
new file mode 100644
index 000000000..0f580fb98
--- /dev/null
+++ b/testing/api/profiles/draft_profile.json
@@ -0,0 +1,35 @@
+{
+  "name": "draft_profile",
+  "version": "1.4",
+  "created": "2024-09-03",
+  "questions": [
+    {
+      "question": "How will this device be used at Google?",
+      "answer": "Monitoring"
+    },
+    {
+      "question": "Is this device going to be managed by Google or a third party?",
+      "answer": "Google"
+    },
+    {
+      "question": "Will the third-party device administrator be able to grant access to authorized Google personnel upon request?",
+      "answer": ""
+    },
+    {
+      "question": "Which of the following statements are true about this device?",
+      "answer": []
+    },
+    {
+      "question": "Does the network protocol assure server-to-client identity verification?",
+      "answer": "Yes"
+    },
+    {
+      "question": "Click the statements that best describe the characteristics of this device.",
+      "answer": []
+    },
+    {
+      "question": "Are any of the following statements true about this device?",
+      "answer": []
+    }
+    ]
+}
\ No newline at end of file
diff --git a/testing/api/profiles/new_profile_2.json b/testing/api/profiles/new_profile_2.json
deleted file mode 100644
index 910fedd52..000000000
--- a/testing/api/profiles/new_profile_2.json
+++ /dev/null
@@ -1,37 +0,0 @@
-{
-  "name": "new_profile_2",
-  "version": "1.4",
-  "created": "2024-09-03",
-  "status": "Draft",
-  "risk": null,
-  "questions": [
-  {
-    "question": "How will this device be used at Google?",
-    "answer": "Monitoring"
-  },
-  {
-    "question": "Is this device going to be managed by Google or a third party?",
-    "answer": "Google"
-  },
-  {
-    "question": "Will the third-party device administrator be able to grant access to authorized Google personnel upon request?",
-    "answer": "N/A"
-  },
-  {
-    "question": "Which of the following statements are true about this device?",
-    "answer": []
-  },
-  {
-    "question": "Does the network protocol assure server-to-client identity verification?",
-    "answer": "Yes"
-  },
-  {
-    "question": "Click the statements that best describe the characteristics of this device.",
-    "answer": []
-  },
-  {
-    "question": "Are any of the following statements true about this device?",
-    "answer": [0]
-  }
-  ]
-}
\ No newline at end of file
diff --git a/testing/api/profiles/new_profile_1.json b/testing/api/profiles/valid_profile.json
similarity index 69%
rename from testing/api/profiles/new_profile_1.json
rename to testing/api/profiles/valid_profile.json
index 7f915ab34..207929f8d 100644
--- a/testing/api/profiles/new_profile_1.json
+++ b/testing/api/profiles/valid_profile.json
@@ -1,9 +1,7 @@
 {
-  "name": "new_profile_1",
+  "name": "valid_profile",
   "version": "1.4",
   "created": "2024-09-03",
-  "status": "Valid",
-  "risk": "High",
   "questions": [
     {
       "question": "How will this device be used at Google?",
@@ -11,39 +9,27 @@
     },
     {
       "question": "Is this device going to be managed by Google or a third party?",
-      "answer": "Google",
-      "risk": "Limited"
+      "answer": "Google"
     },
     {
       "question": "Will the third-party device administrator be able to grant access to authorized Google personnel upon request?",
-      "answer": "N/A",
-      "risk": "Limited"
+      "answer": "N/A"
     },
     {
       "question": "Which of the following statements are true about this device?",
-      "answer": [
-        0
-      ],
-      "risk": "High"
+      "answer": [0]
     },
     {
       "question": "Does the network protocol assure server-to-client identity verification?",
-      "answer": "Yes",
-      "risk": "Limited"
+      "answer": "Yes"
     },
     {
       "question": "Click the statements that best describe the characteristics of this device.",
-      "answer": [
-        0
-      ],
-      "risk": "High"
+      "answer": [0]
     },
     {
       "question": "Are any of the following statements true about this device?",
-      "answer": [
-        0
-      ],
-      "risk": "High"  
+      "answer": [0]
     },
     {
       "question": "Comments",
diff --git a/testing/api/test_api.py b/testing/api/test_api.py
index c312328aa..207f5151b 100644
--- a/testing/api/test_api.py
+++ b/testing/api/test_api.py
@@ -414,7 +414,7 @@ def test_get_sys_config(testrun): # pylint: disable=W0613
 
 @pytest.fixture()
 def start_test():
-  """ Starts a testrun test """  
+  """ Starts a testrun test using the API """  
 
   # Load the device (payload) using load_json utility method
   device = load_json("device_config.json", directory=DEVICE_1_PATH)
@@ -445,7 +445,7 @@ def start_test():
 
 @pytest.fixture()
 def stop_test():
-  """ Stops a testrun test """ 
+  """ Stops a testrun test using the API """ 
 
   # Send the post request to stop the test
   r = requests.post(f"{API}/system/stop", timeout=10)
@@ -770,7 +770,7 @@ def test_get_test_modules(testrun): # pylint: disable=W0613
 
 @pytest.fixture
 def create_report_folder(): # pylint: disable=W0613
-  """Fixture to create the reports folder in local/devices"""
+  """ Fixture to create the device reports folder in local/devices """
 
   def _create_report_folder(device_name, mac_addr, timestamp):
 
@@ -824,12 +824,10 @@ def test_get_reports_no_reports(testrun): # pylint: disable=W0613
   # Check if the response is an empty list
   assert response == []
 
-def test_delete_report_success(empty_devices_dir, add_one_device, # pylint: disable=W0613
-                                  create_report_folder, testrun): # pylint: disable=W0613
+def test_delete_report(empty_devices_dir, add_one_device, # pylint: disable=W0613
+                          create_report_folder, testrun): # pylint: disable=W0613
   """Test for succesfully delete a report (200)"""
 
-  r = requests.get(f"{API}/devices", timeout=5)
-
   # Load the device using load_json utility method
   device = load_json("device_config.json", directory=DEVICE_1_PATH)
 
@@ -1014,7 +1012,7 @@ def test_delete_report_no_report(empty_devices_dir, add_one_device, testrun): #
 
 def test_get_report_success(empty_devices_dir, add_one_device, # pylint: disable=W0613
                                create_report_folder, testrun): # pylint: disable=W0613
-  """Test get report when report exists (200)"""
+  """Test for successfully get report when report exists (200)"""
 
   # Load the device using load_json utility method
   device = load_json("device_config.json", directory=DEVICE_1_PATH)
@@ -1177,7 +1175,7 @@ def test_export_report_with_profile(empty_devices_dir, add_one_device, # pylint:
   """Test export results with existing profile when report exists (200)"""
 
   # Load the profile using load_json utility method
-  profile = load_json("new_profile_1.json", directory=PROFILES_PATH)
+  profile = load_json("valid_profile.json", directory=PROFILES_PATH)
 
   # Load the device using load_json utility method
   device = load_json("device_config.json", directory=DEVICE_1_PATH)
@@ -2056,7 +2054,7 @@ def test_create_long_chars(empty_devices_dir, testrun): # pylint: disable=W0613
 # Tests for certificates endpoints
 
 def delete_all_certs():
-  """Utility method to delete all certificates from root_certs folder"""
+  """ Delete all certificates from root_certs folder """
 
   try:
 
@@ -2091,7 +2089,7 @@ def delete_all_certs():
     print(f"Error removing {item}: {err}")
 
 def load_certificate_file(cert_filename):
-  """Utility method to load a certificate file in binary read mode."""
+  """ Utility method to load a certificate file in binary read mode """
 
   # Construct the full file path
   cert_path = os.path.join(CERTS_PATH, cert_filename)
@@ -2102,24 +2100,9 @@ def load_certificate_file(cert_filename):
     # Return the certificate file
     return cert_file.read()
 
-def upload_cert(filename):
-  """Utility method to upload a certificate"""
-
-  # Load the certificate using the utility method
-  cert_file = load_certificate_file(filename)
-
-  # Send a POST request to the API endpoint to upload the certificate
-  response = requests.post(
-      f"{API}/system/config/certs",
-      files={"file": (filename, cert_file, "application/x-x509-ca-cert")},
-      timeout=5)
-
-  # Return the response
-  return response
-
 @pytest.fixture()
 def reset_certs():
-  """Delete the certificates before and after each test"""
+  """ Delete the certificates before and after each test """
 
   # Delete before the test
   delete_all_certs()
@@ -2131,13 +2114,28 @@ def reset_certs():
 
 @pytest.fixture()
 def add_cert():
-  """Fixture to upload certificates during tests."""
+  """ Upload certificates during tests """
+
+  # Utility method to upload a certificate
+  def _upload_cert(filename):
+
+    # Load the certificate using the utility method
+    cert_file = load_certificate_file(filename)
+
+    # Send a POST request to the API endpoint to upload the certificate
+    response = requests.post(
+        f"{API}/system/config/certs",
+        files={"file": (filename, cert_file, "application/x-x509-ca-cert")},
+        timeout=5)
+
+    # Return the response
+    return response
 
   # Returning the reference to upload_certificate
-  return upload_cert
+  return _upload_cert
 
-def test_get_certificates_no_certificates(testrun, reset_certs): # pylint: disable=W0613
-  """Test for get certificates when no certificates have been uploaded"""
+def test_get_certs_no_certs(reset_certs, testrun): # pylint: disable=W0613
+  """ Test for get certificate when no certificates are available (200) """
 
   # Send the get request to "/system/config/certs" endpoint
   r = requests.get(f"{API}/system/config/certs", timeout=5)
@@ -2154,8 +2152,8 @@ def test_get_certificates_no_certificates(testrun, reset_certs): # pylint: disab
   # Check if the list is empty
   assert len(response) == 0
 
-def test_get_certificates(testrun, reset_certs, add_cert): # pylint: disable=W0613
-  """Test for get certificates when two certificates have been uploaded"""
+def test_get_certs(testrun, reset_certs, add_cert): # pylint: disable=W0613
+  """ Test for get certificates (one and two certificates) (200) """
 
   # Use add_cert fixture to upload the first certificate
   add_cert("crt.pem")
@@ -2193,8 +2191,8 @@ def test_get_certificates(testrun, reset_certs, add_cert): # pylint: disable=W06
   # Check if response contains two certificates
   assert len(response) == 2
 
-def test_upload_certificate(testrun, reset_certs): # pylint: disable=W0613
-  """Test for upload certificate successfully"""
+def test_upload_cert(testrun, reset_certs): # pylint: disable=W0613
+  """ Test for upload certificate successfully (200) """
 
   # Load the first certificate file content using the utility method
   cert_file = load_certificate_file("crt.pem")
@@ -2252,8 +2250,8 @@ def test_upload_certificate(testrun, reset_certs): # pylint: disable=W0613
   # Check if "WR2.pem" exists
   assert any(cert["filename"] == "WR2.pem" for cert in response)
 
-def test_upload_invalid_certificate_format(testrun, reset_certs): # pylint: disable=W0613
-  """Test for upload an invalid certificate format """
+def test_upload_invalid_cert_format(testrun, reset_certs): # pylint: disable=W0613
+  """ Test for upload an invalid certificate format (400) """
 
   # Load the first certificate file content using the utility method
   cert_file = load_certificate_file("invalid.pem")
@@ -2274,8 +2272,8 @@ def test_upload_invalid_certificate_format(testrun, reset_certs): # pylint: disa
   # Check if "error" key is in response
   assert "error" in response
 
-def test_upload_invalid_certificate_name(testrun, reset_certs): # pylint: disable=W0613
-  """Test for upload a valid certificate with invalid filename"""
+def test_upload_invalid_cert_name(testrun, reset_certs): # pylint: disable=W0613
+  """ Test for upload a valid certificate with invalid filename (400) """
 
   # Assign the invalid certificate name to a variable
   cert_name = "invalidname1234567891234.pem"
@@ -2299,8 +2297,8 @@ def test_upload_invalid_certificate_name(testrun, reset_certs): # pylint: disabl
   # Check if "error" key is in response
   assert "error" in response
 
-def test_upload_existing_certificate(testrun, reset_certs): # pylint: disable=W0613
-  """Test for upload an existing certificate"""
+def test_upload_existing_cert(testrun, reset_certs): # pylint: disable=W0613
+  """ Test for upload an existing certificate (409) """
 
   # Load the first certificate file content using the utility method
   cert_file = load_certificate_file("crt.pem")
@@ -2343,8 +2341,8 @@ def test_upload_existing_certificate(testrun, reset_certs): # pylint: disable=W0
   # Check if "error" key is in response
   assert "error" in response
 
-def test_delete_certificate_success(testrun, reset_certs, add_cert): # pylint: disable=W0613
-  """Test for successfully deleting an existing certificate"""
+def test_delete_cert(testrun, reset_certs, add_cert): # pylint: disable=W0613
+  """ Test for successfully deleting an existing certificate (200) """
 
   # Use the add_cert fixture to upload the first certificate
   add_cert("crt.pem")
@@ -2381,10 +2379,10 @@ def test_delete_certificate_success(testrun, reset_certs, add_cert): # pylint: d
   # Check that the certificate is no longer listed
   assert not any(cert["filename"] == "crt.pem" for cert in response)
 
-def test_delete_certificate_bad_request(testrun, reset_certs, add_cert): # pylint: disable=W0613
-  """Test for delete a certificate without providing the name"""
+def test_delete_cert_bad_request(testrun, reset_certs, add_cert): # pylint: disable=W0613
+  """ Test for delete a certificate without providing the name (400)"""
 
-  # Use the add_cert fixture to upload the first certificate
+  # Use the add_cert fixture to upload the certificate
   add_cert("crt.pem")
 
    # Empty payload
@@ -2404,11 +2402,11 @@ def test_delete_certificate_bad_request(testrun, reset_certs, add_cert): # pylin
   # Check if error in response
   assert "error" in response
 
-def test_delete_certificate_not_found(testrun, reset_certs): # pylint: disable=W0613
-  """Test for delete certificate when does not exist"""
+def test_delete_cert_not_found(testrun, reset_certs): # pylint: disable=W0613
+  """ Test for delete certificate when does not exist (404) """
 
   # Attempt to delete a certificate with a name that doesn't exist
-  delete_payload = {"name": "non_existing"}
+  delete_payload = {"name": "non existing"}
 
   # Send the delete request
   r = requests.delete(f"{API}/system/config/certs",
@@ -2428,17 +2426,17 @@ def test_delete_certificate_not_found(testrun, reset_certs): # pylint: disable=W
 
 @pytest.fixture()
 def add_one_profile():
-  """Fixture to create one profile during tests"""
+  """ Create one profile during tests """
 
   # Construct full path of the profile from 'testing/api/profiles' folder
-  source_path = os.path.join(PROFILES_PATH, "new_profile_1.json")
+  source_path = os.path.join(PROFILES_PATH, "valid_profile.json")
 
   # Copy the profile from 'testing/api/profiles' to 'local/risk_profiles'
   shutil.copy(source_path, PROFILES_DIRECTORY)
 
 @pytest.fixture()
 def add_two_profiles():
-  """Fixture to create two profiles during tests"""
+  """ Create two profiles during tests """
 
   # Iterate over the files from 'testing/api/profiles' folder
   for profile in os.listdir(PROFILES_PATH):
@@ -2486,7 +2484,7 @@ def delete_all_profiles():
 
 @pytest.fixture()
 def empty_profiles_dir():
-  """Delete the profiles before and after each test"""
+  """ Delete all the profiles before and after test """
 
   # Delete before the test
   delete_all_profiles()
@@ -2497,7 +2495,7 @@ def empty_profiles_dir():
   delete_all_profiles()
 
 def profile_exists(profile_name):
-  """Utility method to check if profile exists"""
+  """ Utility method to check if profile exists """
 
   # Send the get request
   r = requests.get(f"{API}/profiles", timeout=5)
@@ -2513,7 +2511,7 @@ def profile_exists(profile_name):
   return any(p["name"] == profile_name for p in profiles)
 
 def test_get_profiles_format(testrun): # pylint: disable=W0613
-  """Test profiles format"""
+  """ Test for profiles format (200) """
 
   # Send the get request
   r = requests.get(f"{API}/profiles/format", timeout=5)
@@ -2533,7 +2531,7 @@ def test_get_profiles_format(testrun): # pylint: disable=W0613
     assert "type" in item
 
 def test_get_profiles_no_profiles(empty_profiles_dir, testrun): # pylint: disable=W0613
-  """Test for get profiles when no profiles created (200)"""
+  """ Test for get profiles when no profiles created (200) """
 
   # Send the get request to "/profiles" endpoint
   r = requests.get(f"{API}/profiles", timeout=5)
@@ -2551,7 +2549,7 @@ def test_get_profiles_no_profiles(empty_profiles_dir, testrun): # pylint: disabl
   assert len(response) == 0
 
 def test_get_profiles_one_profile(empty_profiles_dir, add_one_profile, testrun): # pylint: disable=W0613
-  """Test for get profiles when one profile is created (200)"""
+  """ Test for get profiles when one profile is created (200) """
 
   # Send get request to the "/profiles" endpoint
   r = requests.get(f"{API}/profiles", timeout=5)
@@ -2593,7 +2591,7 @@ def test_get_profiles_one_profile(empty_profiles_dir, add_one_profile, testrun):
 
 def test_get_profiles_two_profiles(empty_profiles_dir, add_two_profiles, # pylint: disable=W0613
                                    testrun): # pylint: disable=W0613
-  """Test for get profiles when two profiles are created (200)"""
+  """ Test for get profiles when two profiles are created (200) """
 
   # Send the get request to "/profiles" endpoint
   r = requests.get(f"{API}/profiles", timeout=5)
@@ -2611,10 +2609,10 @@ def test_get_profiles_two_profiles(empty_profiles_dir, add_two_profiles, # pylin
   assert len(response) == 2
 
 def test_create_profile(testrun): # pylint: disable=W0613
-  """Test for create profile when profile does not exist (201)"""
+  """ Test for create profile when profile does not exist (201) """
 
   # Load the profile
-  new_profile = load_json("new_profile_1.json", directory=PROFILES_PATH)
+  new_profile = load_json("valid_profile.json", directory=PROFILES_PATH)
 
   # Assign the profile name to profile_name
   profile_name = new_profile["name"]
@@ -2653,22 +2651,25 @@ def test_create_profile(testrun): # pylint: disable=W0613
   assert created_profile is not None
 
 def test_update_profile(empty_profiles_dir, add_one_profile, testrun): # pylint: disable=W0613
-  """Test for update profile when profile already exists (200)"""
+  """ Test for update profile when profile already exists (200) """
 
   # Load the profile using load_json utility method
-  new_profile = load_json("new_profile_1.json", directory=PROFILES_PATH)
+  new_profile = load_json("valid_profile.json", directory=PROFILES_PATH)
 
   # Assign the new_profile name
   profile_name = new_profile["name"]
 
+  # Assign the profile questions
+  profile_questions = new_profile["questions"]
+
   # Assign the updated_profile name
-  updated_profile_name = "updated_profile_1"
+  updated_profile_name = "updated_valid_profile"
 
   # Payload with the updated device name
   updated_profile = {
     "name": profile_name,
     "rename" : updated_profile_name,
-    "questions": new_profile["questions"]   
+    "questions": profile_questions    
     }
 
   # Exception if the profile does not exists
@@ -2709,7 +2710,7 @@ def test_update_profile(empty_profiles_dir, add_one_profile, testrun): # pylint:
 
 def test_update_profile_invalid_json(empty_profiles_dir, add_one_profile, # pylint: disable=W0613
                                      testrun): # pylint: disable=W0613
-  """Test for update profile invalid JSON payload (400)"""
+  """ Test for update profile invalid JSON payload (400) """
 
   # Invalid JSON
   updated_profile = {}
@@ -2730,7 +2731,7 @@ def test_update_profile_invalid_json(empty_profiles_dir, add_one_profile, # pyli
   assert "error" in response
 
 def test_create_profile_invalid_json(empty_profiles_dir, testrun): # pylint: disable=W0613
-  """Test for create profile invalid JSON payload (400) """
+  """ Test for create profile invalid JSON payload (400) """
 
   # Invalid JSON
   new_profile = {}
@@ -2751,10 +2752,10 @@ def test_create_profile_invalid_json(empty_profiles_dir, testrun): # pylint: dis
   assert "error" in response
 
 def test_delete_profile(empty_profiles_dir, add_one_profile, testrun): # pylint: disable=W0613
-  """Test for successfully delete profile (200)"""
+  """ Test for successfully delete profile (200) """
 
   # Load the profile using load_json utility method
-  profile_to_delete = load_json("new_profile_1.json", directory=PROFILES_PATH)
+  profile_to_delete = load_json("valid_profile.json", directory=PROFILES_PATH)
 
   # Assign the profile name
   profile_name = profile_to_delete["name"]
@@ -2793,10 +2794,10 @@ def test_delete_profile(empty_profiles_dir, add_one_profile, testrun): # pylint:
   assert deleted_profile is None
 
 def test_delete_profile_no_profile(empty_profiles_dir, testrun): # pylint: disable=W0613
-  """Test delete profile if the profile does not exists (404)"""
+  """ Test delete profile if the profile does not exists (404) """
 
   # Assign the profile to delete
-  profile_to_delete = {"name": "New Profile"}
+  profile_to_delete = {"name": "non existing"}
 
   # Delete the profile
   r = requests.delete(
@@ -2807,8 +2808,14 @@ def test_delete_profile_no_profile(empty_profiles_dir, testrun): # pylint: disab
   # Check if status code is 404 (Profile does not exist)
   assert r.status_code == 404
 
+  # Parse the response
+  response = r.json()
+
+  # Check if "error" key in response
+  assert "error" in response
+
 def test_delete_profile_invalid_json(empty_profiles_dir, testrun): # pylint: disable=W0613
-  """Test for delete profile invalid JSON payload (400)"""
+  """ Test for delete profile invalid JSON payload (400) """
 
   # Invalid payload
   profile_to_delete = {}
@@ -2819,12 +2826,12 @@ def test_delete_profile_invalid_json(empty_profiles_dir, testrun): # pylint: dis
       data=json.dumps(profile_to_delete),
       timeout=5)
 
-  # Parse the response
-  response = r.json()
-
   # Check if status code is 400 (bad request)
   assert r.status_code == 400
 
+  # Parse the response
+  response = r.json()
+
   # Check if "error" key in response
   assert "error" in response
 
@@ -2837,21 +2844,21 @@ def test_delete_profile_invalid_json(empty_profiles_dir, testrun): # pylint: dis
       data=json.dumps(profile_to_delete_2),
       timeout=5)
 
-  # Parse the response
-  response = r.json()
-
   # Check if status code is 400 (bad request)
   assert r.status_code == 400
 
+  # Parse the response
+  response = r.json()
+
   # Check if "error" key in response
   assert "error" in response
 
 def test_delete_profile_server_error(empty_profiles_dir, add_one_profile, # pylint: disable=W0613
                                      testrun): # pylint: disable=W0613
-  """Test for delete profile causing internal server error (500)"""
+  """ Test for delete profile causing internal server error (500) """
 
   # Assign the profile from the fixture
-  profile_to_delete = load_json("new_profile_1.json", directory=PROFILES_PATH)
+  profile_to_delete = load_json("valid_profile.json", directory=PROFILES_PATH)
 
   # Assign the profile name to profile_name
   profile_name = profile_to_delete["name"]

From f43578847e67326c98ab8574e626aab3f5d312d7 Mon Sep 17 00:00:00 2001
From: MariusBaldovin <baldovin@google.com>
Date: Fri, 13 Sep 2024 09:17:51 +0100
Subject: [PATCH 25/25] updates

---
 testing/api/test_api.py | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/testing/api/test_api.py b/testing/api/test_api.py
index a9ff5f288..bdabe09de 100644
--- a/testing/api/test_api.py
+++ b/testing/api/test_api.py
@@ -64,7 +64,7 @@ def query_system_status():
   # Parse the json response
   response = r.json()
 
-  # return the system status
+  # Return the system status
   return response["status"]
 
 def query_test_count() -> int:
@@ -170,7 +170,7 @@ def testrun(request): # pylint: disable=W0613
         # Fail if the Testrun process unexpectedly terminates
         pytest.fail("Testrun terminated")
 
-    # Wait for two of seconds before yielding
+    # Wait for two seconds before yielding
     time.sleep(2)
 
     yield
@@ -192,7 +192,7 @@ def testrun(request): # pylint: disable=W0613
 
   print(outs)
 
-  # Stop any left Docker containers after the test
+  # Stop any remaining Docker containers after the test
   cmd = subprocess.run(
       "docker stop $(docker ps -a -q)", shell=True,
       capture_output=True, check=False
@@ -230,7 +230,7 @@ def dict_paths(thing: dict, stem: str = ""):
     else:
       yield path
 
-def get_network_interfaces():
+def get_network_interfaces() -> str:
   """ Return list of network interfaces on machine
 
   Uses /sys/class/net rather than interfaces as testrun uses the latter
