From 84887eb0b3d328ec0ccf52b0a8d5414bea4192ee Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Mon, 26 Oct 2020 14:57:55 -0700 Subject: [PATCH] Fix issue #75; Fix task tests --- .../controllers/import_controller.py | 11 +++- backend/ibutsu_server/openapi/openapi.yaml | 3 + backend/ibutsu_server/tasks/importers.py | 16 +++-- backend/ibutsu_server/test/__init__.py | 28 ++++++++- .../test/res/empty-testsuite.xml | 9 +++ backend/ibutsu_server/test/test_tasks.py | 60 ++++++++++++------- 6 files changed, 96 insertions(+), 31 deletions(-) create mode 100644 backend/ibutsu_server/test/res/empty-testsuite.xml diff --git a/backend/ibutsu_server/controllers/import_controller.py b/backend/ibutsu_server/controllers/import_controller.py index 5a586108..78045acf 100644 --- a/backend/ibutsu_server/controllers/import_controller.py +++ b/backend/ibutsu_server/controllers/import_controller.py @@ -1,8 +1,10 @@ +import connexion from ibutsu_server.db.base import session from ibutsu_server.db.models import Import from ibutsu_server.db.models import ImportFile from ibutsu_server.tasks.importers import run_archive_import from ibutsu_server.tasks.importers import run_junit_import +from ibutsu_server.util.projects import get_project_id def get_import(id_): @@ -17,7 +19,7 @@ def get_import(id_): return import_.to_dict() -def add_import(import_file=None, *args, **kwargs): +def add_import(import_file=None, project=None, *args, **kwargs): """Imports a JUnit XML file and creates a test run and results from it. :param import_file: file to upload @@ -27,8 +29,13 @@ def add_import(import_file=None, *args, **kwargs): """ if not import_file: return "Bad request, no file uploaded", 400 + data = {} + if connexion.request.form.get("project"): + project = connexion.request.form["project"] + if project: + data["project_id"] = get_project_id(project) new_import = Import.from_dict( - **{"status": "pending", "filename": import_file.filename, "format": "", "data": {}} + **{"status": "pending", "filename": import_file.filename, "format": "", "data": data} ) session.add(new_import) session.commit() diff --git a/backend/ibutsu_server/openapi/openapi.yaml b/backend/ibutsu_server/openapi/openapi.yaml index 360ced52..102d4a5f 100644 --- a/backend/ibutsu_server/openapi/openapi.yaml +++ b/backend/ibutsu_server/openapi/openapi.yaml @@ -1154,6 +1154,9 @@ paths: description: The file to import format: binary type: string + project: + description: The project associated with this import + type: string required: - importFile type: object diff --git a/backend/ibutsu_server/tasks/importers.py b/backend/ibutsu_server/tasks/importers.py index 66272751..5e61194c 100644 --- a/backend/ibutsu_server/tasks/importers.py +++ b/backend/ibutsu_server/tasks/importers.py @@ -18,7 +18,7 @@ from lxml import objectify -def _create_result(tar, run_id, result, artifacts): +def _create_result(tar, run_id, result, artifacts, project_id=None): """Create a result with artifacts, used in the archive importer""" old_id = None result_id = convert_objectid_to_uuid(result.get("id")) @@ -33,6 +33,8 @@ def _create_result(tar, run_id, result, artifacts): if "id" in result: result.pop("id") result["run_id"] = run_id + if project_id: + result["project_id"] = project_id result_record = Result.from_dict(**result) session.add(result_record) session.commit() @@ -85,6 +87,8 @@ def run_junit_import(import_): "tests": testsuite.get("tests"), }, } + if import_record.data.get("project_id"): + run_dict["project_id"] = import_record.data["project_id"] # Insert the run, and then update the import with the run id run = Run.from_dict(**run_dict) session.add(run) @@ -92,7 +96,7 @@ def run_junit_import(import_): run_dict = run.to_dict() import_record.data["run_id"].append(run.id) # Import the contents of the XML file - for testcase in testsuite.testcase: + for testcase in testsuite.iterchildren(tag="testcase"): test_name = testcase.get("name").split(".")[-1] if testcase.get("classname"): test_name = testcase.get("classname").split(".")[-1] + "." + test_name @@ -109,6 +113,8 @@ def run_junit_import(import_): "params": {}, "source": testsuite.get("name"), } + if import_record.data.get("project_id"): + result_dict["project_id"] = import_record.data["project_id"] skip_reason, traceback = None, None if testcase.find("failure"): result_dict["result"] = "failed" @@ -231,7 +237,9 @@ def run_archive_import(import_): elif not run_dict.get("created") and not run_dict.get("start_time"): run_dict["created"] = start_time run_dict["start_time"] = start_time - if run_dict.get("metadata", {}).get("project"): + if import_record.data.get("project_id"): + run_dict["project_id"] = import_record.data["project_id"] + elif run_dict.get("metadata", {}).get("project"): run_dict["project_id"] = get_project_id(run_dict["metadata"]["project"]) # If this run has a valid ObjectId, check if this run exists if is_uuid(run_dict.get("id")): @@ -246,7 +254,7 @@ def run_archive_import(import_): # Now loop through all the results, and create or update them for result in results: artifacts = result_artifacts.get(result["id"], []) - _create_result(tar, run.id, result, artifacts) + _create_result(tar, run.id, result, artifacts, import_record.data.get("project_id")) # Update the import record _update_import_status(import_record, "done") if run: diff --git a/backend/ibutsu_server/test/__init__.py b/backend/ibutsu_server/test/__init__.py index 53fd1ac8..562818e9 100644 --- a/backend/ibutsu_server/test/__init__.py +++ b/backend/ibutsu_server/test/__init__.py @@ -1,6 +1,6 @@ import logging from copy import copy -from unittest.mock import MagicMock +from inspect import isfunction import ibutsu_server.tasks from flask_testing import TestCase @@ -9,6 +9,27 @@ from ibutsu_server.util import merge_dicts +def mock_task(*args, **kwargs): + if args and isfunction(args[0]): + func = args[0] + + def wrap(*args, **kwargs): + return func(*args, **kwargs) + + wrap._orig_func = func + return wrap + else: + + def decorate(func): + def _wrapped(*args, **kwargs): + return func(*args, **kwargs) + + _wrapped._orig_func = func + return _wrapped + + return decorate + + class BaseTestCase(TestCase): def create_app(self): logging.getLogger("connexion.operation").setLevel("ERROR") @@ -19,6 +40,9 @@ def create_app(self): } app = get_app(**extra_config) create_celery_app(app.app) + + if ibutsu_server.tasks.task is None: + ibutsu_server.tasks.task = mock_task return app.app def assert_201(self, response, message=None): @@ -142,4 +166,4 @@ class MockRun(MockModel): # Mock out the task decorator -ibutsu_server.tasks.task = MagicMock() +ibutsu_server.tasks.task = mock_task diff --git a/backend/ibutsu_server/test/res/empty-testsuite.xml b/backend/ibutsu_server/test/res/empty-testsuite.xml new file mode 100644 index 00000000..adf260d3 --- /dev/null +++ b/backend/ibutsu_server/test/res/empty-testsuite.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/backend/ibutsu_server/test/test_tasks.py b/backend/ibutsu_server/test/test_tasks.py index c93728b8..b755a26b 100644 --- a/backend/ibutsu_server/test/test_tasks.py +++ b/backend/ibutsu_server/test/test_tasks.py @@ -1,49 +1,63 @@ import time import uuid from datetime import datetime +from unittest.mock import MagicMock +from unittest.mock import patch -import pytest from ibutsu_server.test import BaseTestCase MOCK_RUN_ID = str(uuid.uuid4()) -MOCK_RUN = {"id": MOCK_RUN_ID} +MOCK_RUN = MagicMock(**{"id": MOCK_RUN_ID, "data": {}}) MOCK_PROJECT = str(uuid.uuid4()) MOCK_TIME = time.time() MOCK_RESULTS = [ - { - "result": "passed", + MagicMock( + **{ + "result": "passed", + "duration": 1.532734345, + "data": {"component": "login", "env": "qa", "project": MOCK_PROJECT}, + "starttime": MOCK_TIME, + } + ) +] +UPDATED_RUN = MagicMock( + **{ + "id": MOCK_RUN_ID, "duration": 1.532734345, - "metadata": {"component": "login", "env": "qa", "project": MOCK_PROJECT}, - "starttime": MOCK_TIME, + "summary": { + "errors": 0, + "failures": 0, + "skips": 0, + "xfailures": 0, + "xpasses": 0, + "tests": 1, + }, + "data": {"component": "login", "env": "qa", "project": MOCK_PROJECT}, + "start_time": MOCK_TIME, + "created": datetime.fromtimestamp(MOCK_TIME).isoformat(), } -] -UPDATED_RUN = { - "id": MOCK_RUN_ID, - "duration": 1.532734345, - "summary": {"errors": 0, "failures": 0, "skips": 0, "xfailures": 0, "xpasses": 0, "tests": 1}, - "metadata": {"component": "login", "env": "qa", "project": MOCK_PROJECT}, - "start_time": MOCK_TIME, - "created": datetime.fromtimestamp(MOCK_TIME).isoformat(), -} +) -@pytest.mark.skip("Currently breaking collection") class TestRunTasks(BaseTestCase): - def test_update_run(self, mocker): + @patch("ibutsu_server.tasks.runs.Result") + @patch("ibutsu_server.tasks.runs.Run") + @patch("ibutsu_server.tasks.runs.session") + @patch("ibutsu_server.tasks.runs.lock") + def test_update_run(self, mocked_lock, mocked_session, mocked_run, mocked_result): """Test updating the run""" from ibutsu_server.tasks.runs import update_run - mocker.patch("ibutsu_server.tasks.runs.lock") - mocked_session = mocker.patch("ibutsu_server.tasks.runs.session") - mocked_run = mocker.patch("ibutsu_server.tasks.runs.Run") + mocked_lock.return_value.__enter__.return_value = None mocked_run.query.get.return_value = MOCK_RUN - mocked_result = mocker.patch("ibutsu_server.tasks.runs.Result") mocked_result.query.return_value.filter.return_value.all.return_value = MOCK_RESULTS + update_run = update_run._orig_func update_run(MOCK_RUN_ID) + mocked_lock.assert_called_once() mocked_run.query.get.assert_called_once_with(MOCK_RUN_ID) - mocked_result.query.return_value.filter.assert_called_once() - mocked_result.query.return_value.filter.return_value.all.assert_called_once() + mocked_result.query.filter.assert_called_once() + mocked_result.query.filter.return_value.order_by.return_value.all.assert_called_once() mocked_session.add.assert_called_once() mocked_session.commit.assert_called_once()