From c260c784eb5f7cf575ecd871b495f342816198d3 Mon Sep 17 00:00:00 2001 From: MariusBaldovin 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 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 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 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 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 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 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 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 }} - 🚀 + +

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">
- - + + aria-label="This device will be tested for the Pilot program."> {{ device.manufacturer }} @@ -54,14 +56,16 @@ class="button-edit" type="button">
- - + + aria-label="This device will be tested for the Pilot program.">

{{ device.manufacturer }}

{ 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; + 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: ` `, + 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 @@

{{ data.title }}

- - + Device Qualification @@ -137,8 +138,9 @@

{{ data.title }}

- - + Pilot Assessment @@ -212,6 +214,7 @@

Summary

class="device-qualification-form-summary">
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 @@ + + + + + + + + + + + + + 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 @@ + + + + + + From f6478bb33107ce3f89c1f1b62431bde3cce8ea8e Mon Sep 17 00:00:00 2001 From: Sofia Kurilova 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 @@ + +Risk Assessment Profile Completed +

+ It has been saved as "{{ data.profile.name }}" and can now be attached to + reports. +

+

+ Preliminary risk estimation based on your answers is + + {{ data.profile.risk }} risk + + +
This means that your device might be eligible for most networks. Final + estimation will be available in the Zip file. +

+ + + 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; + 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, + @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 { ); }); - saveProfile = this.effect(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 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 @@

Testrun

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 -
+
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 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); - 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); - 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 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 @@ 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 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 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 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 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 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 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 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 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 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 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 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