mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-08-08 06:23:59 -06:00
Merge branch 'CURA-7959_add_unit_tests'
This commit is contained in:
commit
dfdc7de400
9 changed files with 291 additions and 5 deletions
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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()
|
73
plugins/DigitalLibrary/tests/TestDigitalFactoryFileModel.py
Normal file
73
plugins/DigitalLibrary/tests/TestDigitalFactoryFileModel.py
Normal 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
|
|
@ -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"
|
86
plugins/DigitalLibrary/tests/TestDigitalLibraryApiClient.py
Normal file
86
plugins/DigitalLibrary/tests/TestDigitalLibraryApiClient.py
Normal 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()
|
10
plugins/DigitalLibrary/tests/conftest.py
Normal file
10
plugins/DigitalLibrary/tests/conftest.py
Normal 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__)), ".."))
|
Loading…
Add table
Add a link
Reference in a new issue