From 2839b5084aabb5f3d61fdceceba9dc2b07d4cd01 Mon Sep 17 00:00:00 2001 From: Murilo Geraldini Date: Mon, 10 Mar 2025 14:02:56 -0300 Subject: [PATCH] test: hardware listing Closes #981 --- .../unitTests/hardwareDetails_test.py | 213 +++++++++++++ .../kernelCI_app/unitTests/hardware_test.py | 282 ++++++------------ .../unitTests/utils/client/hardwareClient.py | 14 +- .../unitTests/utils/fields/hardware.py | 10 + 4 files changed, 318 insertions(+), 201 deletions(-) create mode 100644 backend/kernelCI_app/unitTests/hardwareDetails_test.py create mode 100644 backend/kernelCI_app/unitTests/utils/fields/hardware.py diff --git a/backend/kernelCI_app/unitTests/hardwareDetails_test.py b/backend/kernelCI_app/unitTests/hardwareDetails_test.py new file mode 100644 index 00000000..36e9bdbc --- /dev/null +++ b/backend/kernelCI_app/unitTests/hardwareDetails_test.py @@ -0,0 +1,213 @@ +from kernelCI_app.unitTests.utils.healthCheck import online +from kernelCI_app.unitTests.utils.client.hardwareClient import HardwareClient +from kernelCI_app.unitTests.utils.asserts import assert_status_code_and_error_response +from kernelCI_app.utils import string_to_json +from http import HTTPStatus +from kernelCI_app.typeModels.hardwareDetails import HardwareDetailsPostBody +import copy + + +client = HardwareClient() + +UNEXISTENT_HARDWARE_ID = { + "id": "no hardware id", + "body": HardwareDetailsPostBody( + origin="", + startTimestampInSeconds=1737487800, + endTimestampInSeconds=1737574200, + selectedCommits={}, + filter={}, + ), +} + +BAD_REQUEST_REQUEST_BODY = { + "id": "google,juniper", + "body": HardwareDetailsPostBody( + origin="", + startTimestampInSeconds="", + endTimestampInSeconds="", + selectedCommits={}, + filter={}, + ), +} + +GOOGLE_JUNIPER_HARDWARE_WITHOUT_FILTERS = { + "id": "google,juniper", + "body": HardwareDetailsPostBody( + startTimestampInSeconds=1740227400, + endTimestampInSeconds=1740659400, + selectedCommits={}, + filter={}, + ), +} + +ARM_JUNO_HARDWARE_WITHOUT_FILTERS = { + "id": "arm,juno", + "body": HardwareDetailsPostBody( + startTimestampInSeconds=1740232800, + endTimestampInSeconds=1740664800, + selectedCommits={}, + filter={}, + ), +} + +GOOGLE_JUNIPER_HARDWARE_WITH_FILTERS = copy.deepcopy( + GOOGLE_JUNIPER_HARDWARE_WITHOUT_FILTERS +) +GOOGLE_JUNIPER_HARDWARE_WITH_FILTERS["body"].filter = { + "filter_architecture": ["arm64"], + "filter_boot.status": ["PASS"], +} + +HARDWARE_WITH_UNEXISTENT_FILTER_VALUE = copy.deepcopy( + GOOGLE_JUNIPER_HARDWARE_WITHOUT_FILTERS +) +HARDWARE_WITH_UNEXISTENT_FILTER_VALUE["body"].filter = { + "filter_architecture": ["invalid"] +} + +HARDWARE_WITH_GLOBAL_FILTER = { + "id": "aaeon-UPN-EHLX4RE-A10-0864", + "body": HardwareDetailsPostBody( + startTimestampInSeconds=1740241800, + endTimestampInSeconds=1740673800, + selectedCommits={}, + filter={ + "filter_architecture": ["i386"], + "filter_config_name": ["defconfig"], + "filter_compiler": ["gcc-12"], + "filter_valid": ["true"], + }, + ), +} + +HARDWARE_WITH_LOCAL_FILTER = { + "id": "google,kevin-rev15", + "body": HardwareDetailsPostBody( + startTimestampInSeconds=1740243600, + endTimestampInSeconds=1740675600, + selectedCommits={}, + filter={ + "filter_boot.issue": ["maestro:e602fca280d85d8e603f7c0aff68363bb0cd7993"], + "filter_boot.status": ["PASS", "MISS"], + "filter_test.status": ["DONE", "FAIL"], + "filter_test.platform": ["rk3399-gru-kevin"], + }, + ), +} + +SUCCESS_EXPECTED_RESPONSE = { + "expected_status": HTTPStatus.OK, + "has_error": False, + "check_emptiness": False, +} + +ERROR_EXPECTED_RESPONSE = { + "expected_status": HTTPStatus.OK, + "has_error": True, + "check_emptiness": False, +} + +BAD_REQUEST_EXPECTED_RESPONSE = { + "expected_status": HTTPStatus.BAD_REQUEST, + "has_error": True, + "check_emptiness": False, +} + +EMPTY_EXPECTED_RESPONSE = { + "expected_status": HTTPStatus.OK, + "has_error": False, + "check_emptiness": True, +} + + +def pytest_generate_tests(metafunc): + base_cases = [ + ((BAD_REQUEST_REQUEST_BODY), BAD_REQUEST_EXPECTED_RESPONSE), + ((GOOGLE_JUNIPER_HARDWARE_WITHOUT_FILTERS), SUCCESS_EXPECTED_RESPONSE), + ((HARDWARE_WITH_UNEXISTENT_FILTER_VALUE), EMPTY_EXPECTED_RESPONSE), + ] + extra_cases = [] + builds_and_boots_cases = [ + ((GOOGLE_JUNIPER_HARDWARE_WITH_FILTERS), SUCCESS_EXPECTED_RESPONSE) + ] + + if metafunc.config.getoption("--run-all"): + extra_cases = [ + ((ARM_JUNO_HARDWARE_WITHOUT_FILTERS), SUCCESS_EXPECTED_RESPONSE), + ((UNEXISTENT_HARDWARE_ID), ERROR_EXPECTED_RESPONSE), + ((HARDWARE_WITH_GLOBAL_FILTER), SUCCESS_EXPECTED_RESPONSE), + ] + + if "test_hardware_boots" in metafunc.fixturenames: + metafunc.parametrize( + "test_hardware_boots,expected_response", + base_cases + extra_cases + builds_and_boots_cases, + ) + + if "test_hardware_builds" in metafunc.fixturenames: + metafunc.parametrize( + "test_hardware_builds,expected_response", + base_cases + extra_cases + builds_and_boots_cases, + ) + + if "test_hardware_tests" in metafunc.fixturenames: + metafunc.parametrize( + "test_hardware_tests,expected_response", base_cases + extra_cases + ) + + +@online +def test_get_hardware_details_boots(test_hardware_boots: dict, expected_response: dict): + expected_status, has_error, check_emptiness = expected_response.values() + hardware_id = test_hardware_boots["id"] + body = test_hardware_boots["body"] + response = client.post_hardware_boots(hardware_id=hardware_id, body=body) + content = string_to_json(response.content.decode()) + assert_status_code_and_error_response( + response=response, + content=content, + status_code=expected_status, + should_error=has_error, + ) + + if check_emptiness: + assert len(content["boots"]) == 0 + + +@online +def test_get_hardware_details_builds( + test_hardware_builds: dict, expected_response: dict +): + expected_status, has_error, check_emptiness = expected_response.values() + hardware_id = test_hardware_builds["id"] + body = test_hardware_builds["body"] + response = client.post_hardware_builds(hardware_id=hardware_id, body=body) + content = string_to_json(response.content.decode()) + assert_status_code_and_error_response( + response=response, + content=content, + status_code=expected_status, + should_error=has_error, + ) + + if check_emptiness: + assert len(content["builds"]) == 0 + + +@online +def test_get_hardware_details_tests(test_hardware_tests: dict, expected_response: dict): + expected_status, has_error, check_emptiness = expected_response.values() + hardware_id = test_hardware_tests["id"] + body = test_hardware_tests["body"] + response = client.post_hardware_tests(hardware_id=hardware_id, body=body) + content = string_to_json(response.content.decode()) + assert_status_code_and_error_response( + response=response, + content=content, + status_code=expected_status, + should_error=has_error, + ) + + if check_emptiness: + assert len(content["tests"]) == 0 diff --git a/backend/kernelCI_app/unitTests/hardware_test.py b/backend/kernelCI_app/unitTests/hardware_test.py index f1df5642..13dc0170 100644 --- a/backend/kernelCI_app/unitTests/hardware_test.py +++ b/backend/kernelCI_app/unitTests/hardware_test.py @@ -1,213 +1,99 @@ +import pytest from kernelCI_app.unitTests.utils.healthCheck import online +from kernelCI_app.typeModels.hardwareListing import HardwareQueryParamsDocumentationOnly from kernelCI_app.unitTests.utils.client.hardwareClient import HardwareClient -from kernelCI_app.unitTests.utils.asserts import assert_status_code_and_error_response -from kernelCI_app.utils import string_to_json -from http import HTTPStatus -from kernelCI_app.typeModels.hardwareDetails import HardwareDetailsPostBody -import copy - - -client = HardwareClient() - -UNEXISTENT_HARDWARE_ID = { - "id": "no hardware id", - "body": HardwareDetailsPostBody( - origin="", - startTimestampInSeconds=1737487800, - endTimestampInSeconds=1737574200, - selectedCommits={}, - filter={}, - ), -} - -BAD_REQUEST_REQUEST_BODY = { - "id": "google,juniper", - "body": HardwareDetailsPostBody( - origin="", - startTimestampInSeconds="", - endTimestampInSeconds="", - selectedCommits={}, - filter={}, - ), -} - -GOOGLE_JUNIPER_HARDWARE_WITHOUT_FILTERS = { - "id": "google,juniper", - "body": HardwareDetailsPostBody( - startTimestampInSeconds=1740227400, - endTimestampInSeconds=1740659400, - selectedCommits={}, - filter={}, - ), -} - -ARM_JUNO_HARDWARE_WITHOUT_FILTERS = { - "id": "arm,juno", - "body": HardwareDetailsPostBody( - startTimestampInSeconds=1740232800, - endTimestampInSeconds=1740664800, - selectedCommits={}, - filter={}, - ), -} - -GOOGLE_JUNIPER_HARDWARE_WITH_FILTERS = copy.deepcopy( - GOOGLE_JUNIPER_HARDWARE_WITHOUT_FILTERS +from kernelCI_app.unitTests.utils.asserts import ( + assert_status_code_and_error_response, + assert_has_fields_in_response_content, ) -GOOGLE_JUNIPER_HARDWARE_WITH_FILTERS["body"].filter = { - "filter_architecture": ["arm64"], - "filter_boot.status": ["PASS"], -} - -HARDWARE_WITH_UNEXISTENT_FILTER_VALUE = copy.deepcopy( - GOOGLE_JUNIPER_HARDWARE_WITHOUT_FILTERS +from kernelCI_app.unitTests.utils.fields.hardware import ( + hardware_listing_fields, + test_status_summary_fields, + build_status_summary_fields, ) -HARDWARE_WITH_UNEXISTENT_FILTER_VALUE["body"].filter = { - "filter_architecture": ["invalid"] -} - -HARDWARE_WITH_GLOBAL_FILTER = { - "id": "aaeon-UPN-EHLX4RE-A10-0864", - "body": HardwareDetailsPostBody( - startTimestampInSeconds=1740241800, - endTimestampInSeconds=1740673800, - selectedCommits={}, - filter={ - "filter_architecture": ["i386"], - "filter_config_name": ["defconfig"], - "filter_compiler": ["gcc-12"], - "filter_valid": ["true"], - }, - ), -} - -HARDWARE_WITH_LOCAL_FILTER = { - "id": "google,kevin-rev15", - "body": HardwareDetailsPostBody( - startTimestampInSeconds=1740243600, - endTimestampInSeconds=1740675600, - selectedCommits={}, - filter={ - "filter_boot.issue": ["maestro:e602fca280d85d8e603f7c0aff68363bb0cd7993"], - "filter_boot.status": ["PASS", "MISS"], - "filter_test.status": ["DONE", "FAIL"], - "filter_test.platform": ["rk3399-gru-kevin"], - }, - ), -} - -SUCCESS_EXPECTED_RESPONSE = { - "expected_status": HTTPStatus.OK, - "has_error": False, - "check_emptiness": False, -} - -ERROR_EXPECTED_RESPONSE = { - "expected_status": HTTPStatus.OK, - "has_error": True, - "check_emptiness": False, -} - -BAD_REQUEST_EXPECTED_RESPONSE = { - "expected_status": HTTPStatus.BAD_REQUEST, - "has_error": True, - "check_emptiness": False, -} - -EMPTY_EXPECTED_RESPONSE = { - "expected_status": HTTPStatus.OK, - "has_error": False, - "check_emptiness": True, -} - - -def pytest_generate_tests(metafunc): - base_cases = [ - ((BAD_REQUEST_REQUEST_BODY), BAD_REQUEST_EXPECTED_RESPONSE), - ((GOOGLE_JUNIPER_HARDWARE_WITHOUT_FILTERS), SUCCESS_EXPECTED_RESPONSE), - ((HARDWARE_WITH_UNEXISTENT_FILTER_VALUE), EMPTY_EXPECTED_RESPONSE), - ] - extra_cases = [] - builds_and_boots_cases = [ - ((GOOGLE_JUNIPER_HARDWARE_WITH_FILTERS), SUCCESS_EXPECTED_RESPONSE) - ] - - if metafunc.config.getoption("--run-all"): - extra_cases = [ - ((ARM_JUNO_HARDWARE_WITHOUT_FILTERS), SUCCESS_EXPECTED_RESPONSE), - ((UNEXISTENT_HARDWARE_ID), ERROR_EXPECTED_RESPONSE), - ((HARDWARE_WITH_GLOBAL_FILTER), SUCCESS_EXPECTED_RESPONSE), - ] - - if "test_hardware_boots" in metafunc.fixturenames: - metafunc.parametrize( - "test_hardware_boots,expected_response", - base_cases + extra_cases + builds_and_boots_cases, - ) - - if "test_hardware_builds" in metafunc.fixturenames: - metafunc.parametrize( - "test_hardware_builds,expected_response", - base_cases + extra_cases + builds_and_boots_cases, - ) - - if "test_hardware_tests" in metafunc.fixturenames: - metafunc.parametrize( - "test_hardware_tests,expected_response", base_cases + extra_cases - ) +from kernelCI_app.utils import string_to_json +from http import HTTPStatus @online -def test_get_hardware_details_boots(test_hardware_boots: dict, expected_response: dict): - expected_status, has_error, check_emptiness = expected_response.values() - hardware_id = test_hardware_boots["id"] - body = test_hardware_boots["body"] - response = client.get_hardware_boots(hardware_id=hardware_id, body=body) +@pytest.mark.parametrize( + "query, has_error_body", + [ + ( + HardwareQueryParamsDocumentationOnly( + origin="maestro", + startTimestampInSeconds="1741192200", + endTimeStampInSeconds="1741624200", + ), + False, + ), + ( + # Up until this point, redhat hasn't sent any hardwares + HardwareQueryParamsDocumentationOnly( + origin="redhat", + startTimestampInSeconds="1741192200", + endTimeStampInSeconds="1741624200", + ), + True, + ), + ( + HardwareQueryParamsDocumentationOnly( + origin="invalid_origin", + startTimestampInSeconds="1741192200", + endTimeStampInSeconds="1741624200", + ), + True, + ), + ], +) +def test_post_hardware_listing( + pytestconfig, query: HardwareQueryParamsDocumentationOnly, has_error_body: bool +) -> None: + client = HardwareClient() + response = client.get_hardware_listing(query=query) content = string_to_json(response.content.decode()) - assert_status_code_and_error_response( - response=response, - content=content, - status_code=expected_status, - should_error=has_error, - ) - - if check_emptiness: - assert len(content["boots"]) == 0 - -@online -def test_get_hardware_details_builds( - test_hardware_builds: dict, expected_response: dict -): - expected_status, has_error, check_emptiness = expected_response.values() - hardware_id = test_hardware_builds["id"] - body = test_hardware_builds["body"] - response = client.get_hardware_builds(hardware_id=hardware_id, body=body) - content = string_to_json(response.content.decode()) assert_status_code_and_error_response( response=response, content=content, - status_code=expected_status, - should_error=has_error, + status_code=HTTPStatus.OK, + should_error=has_error_body, ) - if check_emptiness: - assert len(content["builds"]) == 0 - - -@online -def test_get_hardware_details_tests(test_hardware_tests: dict, expected_response: dict): - expected_status, has_error, check_emptiness = expected_response.values() - hardware_id = test_hardware_tests["id"] - body = test_hardware_tests["body"] - response = client.get_hardware_tests(hardware_id=hardware_id, body=body) - content = string_to_json(response.content.decode()) - assert_status_code_and_error_response( - response=response, - content=content, - status_code=expected_status, - should_error=has_error, - ) + if not has_error_body: + hardware = content["hardware"] + assert_has_fields_in_response_content( + fields=hardware_listing_fields, + response_content=hardware[0], + ) + assert_has_fields_in_response_content( + fields=test_status_summary_fields, + response_content=hardware[0]["test_status_summary"], + ) + assert_has_fields_in_response_content( + fields=test_status_summary_fields, + response_content=hardware[0]["boot_status_summary"], + ) + assert_has_fields_in_response_content( + fields=build_status_summary_fields, + response_content=hardware[0]["build_status_summary"], + ) - if check_emptiness: - assert len(content["tests"]) == 0 + if pytestconfig.getoption("--run-all") and len(content["hardware"]) > 1: + for hardware in content["hardware"][1:]: + assert_has_fields_in_response_content( + fields=hardware_listing_fields, + response_content=hardware, + ) + assert_has_fields_in_response_content( + fields=test_status_summary_fields, + response_content=hardware["test_status_summary"], + ) + assert_has_fields_in_response_content( + fields=test_status_summary_fields, + response_content=hardware["boot_status_summary"], + ) + assert_has_fields_in_response_content( + fields=build_status_summary_fields, + response_content=hardware["build_status_summary"], + ) diff --git a/backend/kernelCI_app/unitTests/utils/client/hardwareClient.py b/backend/kernelCI_app/unitTests/utils/client/hardwareClient.py index a5410566..42618b80 100644 --- a/backend/kernelCI_app/unitTests/utils/client/hardwareClient.py +++ b/backend/kernelCI_app/unitTests/utils/client/hardwareClient.py @@ -2,25 +2,33 @@ from django.urls import reverse from kernelCI_app.unitTests.utils.client.baseClient import BaseClient from kernelCI_app.typeModels.hardwareDetails import HardwareDetailsPostBody +from kernelCI_app.typeModels.hardwareListing import HardwareQueryParamsDocumentationOnly import json class HardwareClient(BaseClient): - def get_hardware_boots( + def get_hardware_listing( + self, *, query: HardwareQueryParamsDocumentationOnly + ) -> requests.Response: + path = reverse("hardware") + url = self.get_endpoint(path=path, query=query.model_dump()) + return requests.get(url) + + def post_hardware_boots( self, *, hardware_id: str, body: HardwareDetailsPostBody ) -> requests.Response: path = reverse("hardwareDetailsBoots", kwargs={"hardware_id": hardware_id}) url = self.get_endpoint(path=path) return requests.post(url=url, data=json.dumps(body.model_dump())) - def get_hardware_builds( + def post_hardware_builds( self, *, hardware_id: str, body: HardwareDetailsPostBody ) -> requests.Response: path = reverse("hardwareDetailsBuilds", kwargs={"hardware_id": hardware_id}) url = self.get_endpoint(path=path) return requests.post(url, data=json.dumps(body.model_dump())) - def get_hardware_tests( + def post_hardware_tests( self, *, hardware_id: str, body: HardwareDetailsPostBody ) -> requests.Response: path = reverse("hardwareDetailsTests", kwargs={"hardware_id": hardware_id}) diff --git a/backend/kernelCI_app/unitTests/utils/fields/hardware.py b/backend/kernelCI_app/unitTests/utils/fields/hardware.py new file mode 100644 index 00000000..41f91cbc --- /dev/null +++ b/backend/kernelCI_app/unitTests/utils/fields/hardware.py @@ -0,0 +1,10 @@ +hardware_listing_fields = [ + "hardware_name", + "platform", + "test_status_summary", + "boot_status_summary", + "build_status_summary", +] + +test_status_summary_fields = ["FAIL", "PASS", "SKIP", "ERROR", "MISS", "NULL", "DONE"] +build_status_summary_fields = ["valid", "invalid", "null"]