Merge branch 'CURA-7959_add_unit_tests'

This commit is contained in:
Ghostkeeper 2021-05-05 15:41:51 +02:00
commit dfdc7de400
No known key found for this signature in database
GPG key ID: D2A8871EE34EC59A
9 changed files with 291 additions and 5 deletions

View file

@ -2,6 +2,7 @@
# Cura is released under the terms of the LGPLv3 or higher.
import json
import threading
from json import JSONDecodeError
from typing import List, Dict, Any, Callable, Union, Optional
from PyQt5.QtCore import QUrl
@ -43,7 +44,7 @@ class DFFileExportAndUploadManager:
self._library_project_id = library_project_id # type: str
self._library_project_name = library_project_name # type: str
self._file_name = file_name # type: str
self._upload_jobs = [] # type: List[ExportFileJob]
self._formats = formats # type: List[str]
self._api = DigitalFactoryApiClient(application = CuraApplication.getInstance(), on_error = lambda error: Logger.log("e", str(error)))
@ -80,6 +81,8 @@ class DFFileExportAndUploadManager:
)
self._generic_success_message.actionTriggered.connect(self._onMessageActionTriggered)
def _onCuraProjectFileExported(self, job: ExportFileJob) -> None:
"""Handler for when the DF Library workspace file (3MF) has been created locally.
@ -271,7 +274,11 @@ class DFFileExportAndUploadManager:
def extractErrorTitle(reply_body: Optional[str]) -> str:
error_title = ""
if reply_body:
reply_dict = json.loads(reply_body)
try:
reply_dict = json.loads(reply_body)
except JSONDecodeError:
Logger.logException("w", "Unable to extract title from reply body")
return error_title
if "errors" in reply_dict and len(reply_dict["errors"]) >= 1 and "title" in reply_dict["errors"][0]:
error_title = reply_dict["errors"][0]["title"]
return error_title
@ -313,8 +320,13 @@ class DFFileExportAndUploadManager:
QDesktopServices.openUrl(QUrl(project_url))
message.hide()
def start(self) -> None:
for job in self._upload_jobs:
job.start()
def initializeFileUploadJobMetadata(self) -> Dict[str, Any]:
metadata = {}
self._upload_jobs = []
if "3mf" in self._formats and "3mf" in self._file_handlers and self._file_handlers["3mf"]:
filename_3mf = self._file_name + ".3mf"
metadata[filename_3mf] = {
@ -335,7 +347,7 @@ class DFFileExportAndUploadManager:
}
job_3mf = ExportFileJob(self._file_handlers["3mf"], self._nodes, self._file_name, "3mf")
job_3mf.finished.connect(self._onCuraProjectFileExported)
job_3mf.start()
self._upload_jobs.append(job_3mf)
if "ufp" in self._formats and "ufp" in self._file_handlers and self._file_handlers["ufp"]:
filename_ufp = self._file_name + ".ufp"
@ -357,5 +369,5 @@ class DFFileExportAndUploadManager:
}
job_ufp = ExportFileJob(self._file_handlers["ufp"], self._nodes, self._file_name, "ufp")
job_ufp.finished.connect(self._onPrintFileExported)
job_ufp.start()
self._upload_jobs.append(job_ufp)
return metadata

View file

@ -541,6 +541,7 @@ class DigitalFactoryController(QObject):
on_upload_success = self.uploadFileSuccess.emit,
on_upload_finished = self.uploadFileFinished.emit,
on_upload_progress = self.uploadFileProgress.emit)
self.file_upload_manager.start()
# Save the project id to make sure it will be preselected the next time the user opens the save dialog
self._current_workspace_information.setEntryToStore("digital_factory", "library_project_id", library_project_id)

View file

@ -40,6 +40,7 @@ class DigitalFactoryFileModel(ListModel):
def setFiles(self, df_files_in_project: List[DigitalFactoryFileResponse]) -> None:
if self._files == df_files_in_project:
return
self.clear()
self._files = df_files_in_project
self._update()

View file

@ -21,7 +21,7 @@ class DigitalFactoryProjectModel(ListModel):
dfProjectModelChanged = pyqtSignal()
def __init__(self, parent = None):
def __init__(self, parent = None) -> None:
super().__init__(parent)
self.addRoleName(self.DisplayNameRole, "displayName")
self.addRoleName(self.LibraryProjectIdRole, "libraryProjectId")

View file

@ -0,0 +1,48 @@
from unittest.mock import MagicMock, patch
import pytest
from src.DFFileExportAndUploadManager import DFFileExportAndUploadManager
@pytest.fixture
def upload_manager():
file_handler = MagicMock(name = "file_handler")
file_handler.getSupportedFileTypesWrite = MagicMock(return_value = [{
"id": "test",
"extension": ".3mf",
"description": "nope",
"mime_type": "application/vnd.ms-package.3dmanufacturing-3dmodel+xml",
"mode": "binary",
"hide_in_file_dialog": True,
}])
node = MagicMock(name = "SceneNode")
application = MagicMock(name = "CuraApplication")
with patch("cura.CuraApplication.CuraApplication.getInstance", MagicMock(return_value = application)):
return DFFileExportAndUploadManager(file_handlers = {"3mf": file_handler},
nodes = [node],
library_project_id = "test_library_project_id",
library_project_name = "test_library_project_name",
file_name = "file_name",
formats = ["3mf"],
on_upload_error = MagicMock(),
on_upload_success = MagicMock(),
on_upload_finished = MagicMock(),
on_upload_progress = MagicMock())
@pytest.mark.parametrize("input,expected_result",
[("", ""),
("invalid json! {}", ""),
("{\"errors\": [{}]}", ""),
("{\"errors\": [{\"title\": \"some title\"}]}", "some title")])
def test_extractErrorTitle(upload_manager, input, expected_result):
assert upload_manager.extractErrorTitle(input) == expected_result
def test_exportJobError(upload_manager):
mocked_application = MagicMock()
with patch("UM.Application.Application.getInstance", MagicMock(return_value = mocked_application)):
upload_manager._onJobExportError("file_name.3mf")
# Ensure that message was displayed
mocked_application.showMessageSignal.emit.assert_called_once()

View file

@ -0,0 +1,73 @@
from pathlib import Path
from src.DigitalFactoryFileModel import DigitalFactoryFileModel
from src.DigitalFactoryFileResponse import DigitalFactoryFileResponse
file_1 = DigitalFactoryFileResponse(client_id = "client_id_1",
content_type = "zomg",
file_name = "file_1.3mf",
file_id = "file_id_1",
library_project_id = "project_id_1",
status = "test",
user_id = "user_id_1",
username = "username_1",
uploaded_at = "2021-04-07T10:33:25.000Z")
file_2 = DigitalFactoryFileResponse(client_id ="client_id_2",
content_type = "zomg",
file_name = "file_2.3mf",
file_id = "file_id_2",
library_project_id = "project_id_2",
status = "test",
user_id = "user_id_2",
username = "username_2",
uploaded_at = "2021-02-06T09:33:22.000Z")
file_wtf = DigitalFactoryFileResponse(client_id ="client_id_1",
content_type = "zomg",
file_name = "file_3.wtf",
file_id = "file_id_3",
library_project_id = "project_id_1",
status = "test",
user_id = "user_id_1",
username = "username_1",
uploaded_at = "2021-04-06T12:33:25.000Z")
def test_setFiles():
model = DigitalFactoryFileModel()
assert model.count == 0
model.setFiles([file_1, file_2])
assert model.count == 2
assert model.getItem(0)["fileName"] == "file_1.3mf"
assert model.getItem(1)["fileName"] == "file_2.3mf"
def test_clearProjects():
model = DigitalFactoryFileModel()
model.setFiles([file_1, file_2])
model.clearFiles()
assert model.count == 0
def test_setProjectMultipleTimes():
model = DigitalFactoryFileModel()
model.setFiles([file_1, file_2])
model.setFiles([file_2])
assert model.count == 1
assert model.getItem(0)["fileName"] == "file_2.3mf"
def test_setFilter():
model = DigitalFactoryFileModel()
model.setFiles([file_1, file_2, file_wtf])
model.setFilters({"file_name": lambda x: Path(x).suffix[1:].lower() in ["3mf"]})
assert model.count == 2
model.clearFilters()
assert model.count == 3

View file

@ -0,0 +1,55 @@
from src.DigitalFactoryProjectModel import DigitalFactoryProjectModel
from src.DigitalFactoryProjectResponse import DigitalFactoryProjectResponse
project_1 = DigitalFactoryProjectResponse(library_project_id = "omg",
display_name = "zomg",
username = "nope",
organization_shared = True)
project_2 = DigitalFactoryProjectResponse(library_project_id = "omg2",
display_name = "zomg2",
username = "nope",
organization_shared = False)
def test_setProjects():
model = DigitalFactoryProjectModel()
assert model.count == 0
model.setProjects([project_1, project_2])
assert model.count == 2
assert model.getItem(0)["displayName"] == "zomg"
assert model.getItem(1)["displayName"] == "zomg2"
def test_clearProjects():
model = DigitalFactoryProjectModel()
model.setProjects([project_1, project_2])
model.clearProjects()
assert model.count == 0
def test_setProjectMultipleTimes():
model = DigitalFactoryProjectModel()
model.setProjects([project_1, project_2])
model.setProjects([project_2])
assert model.count == 1
assert model.getItem(0)["displayName"] == "zomg2"
def test_extendProjects():
model = DigitalFactoryProjectModel()
assert model.count == 0
model.setProjects([project_1])
assert model.count == 1
model.extendProjects([project_2])
assert model.count == 2
assert model.getItem(0)["displayName"] == "zomg"
assert model.getItem(1)["displayName"] == "zomg2"

View file

@ -0,0 +1,86 @@
from unittest.mock import MagicMock
import pytest
from cura.CuraApplication import CuraApplication
from src.DigitalFactoryApiClient import DigitalFactoryApiClient
from src.PaginationManager import PaginationManager
@pytest.fixture
def application():
app = MagicMock(spec=CuraApplication, name = "Mocked Cura Application")
return app
@pytest.fixture
def pagination_manager():
manager = MagicMock(name = "Mocked Pagination Manager")
return manager
@pytest.fixture
def api_client(application, pagination_manager):
api_client = DigitalFactoryApiClient(application, MagicMock())
api_client._projects_pagination_mgr = pagination_manager
return api_client
def test_getProjectsFirstPage(api_client):
# setup
http_manager = MagicMock()
api_client._http = http_manager
pagination_manager = api_client._projects_pagination_mgr
pagination_manager.limit = 20
finished_callback = MagicMock()
failed_callback = MagicMock()
# Call
api_client.getProjectsFirstPage(on_finished = finished_callback, failed = failed_callback)
# Asserts
pagination_manager.reset.assert_called_once() # Should be called since we asked for new set of projects
http_manager.get.assert_called_once()
args = http_manager.get.call_args_list[0]
# Ensure that it's called with the right limit
assert args[0][0] == "https://api.ultimaker.com/cura/v1/projects?limit=20"
# Change the limit & try again
http_manager.get.reset_mock()
pagination_manager.limit = 80
api_client.getProjectsFirstPage(on_finished = finished_callback, failed = failed_callback)
args = http_manager.get.call_args_list[0]
# Ensure that it's called with the right limit
assert args[0][0] == "https://api.ultimaker.com/cura/v1/projects?limit=80"
def test_getMoreProjects_noNewProjects(api_client):
api_client.hasMoreProjectsToLoad = MagicMock(return_value = False)
http_manager = MagicMock()
api_client._http = http_manager
finished_callback = MagicMock()
failed_callback = MagicMock()
api_client.getMoreProjects(finished_callback, failed_callback)
http_manager.get.assert_not_called()
def test_getMoreProjects_hasNewProjects(api_client):
api_client.hasMoreProjectsToLoad = MagicMock(return_value = True)
http_manager = MagicMock()
api_client._http = http_manager
finished_callback = MagicMock()
failed_callback = MagicMock()
api_client.getMoreProjects(finished_callback, failed_callback)
http_manager.get.assert_called_once()
def test_clear(api_client):
api_client.clear()
api_client._projects_pagination_mgr.reset.assert_called_once()

View file

@ -0,0 +1,10 @@
# Prevents error: "PyCapsule_GetPointer called with incorrect name" with conflicting SIP configurations between Arcus and PyQt: Import custom Sip bindings first!
import Savitar # Dont remove this line
import Arcus # No really. Don't. It needs to be there!
import pynest2d # Really!
# Ensure that the importing for all tests work
import sys
import os
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))