TestSendMaterialJob.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. # Copyright (c) 2018 Ultimaker B.V.
  2. # Copyright (c) 2018 Ultimaker B.V.
  3. # Cura is released under the terms of the LGPLv3 or higher.
  4. import copy
  5. import io
  6. import json
  7. from unittest import TestCase, mock
  8. from unittest.mock import patch, call, MagicMock
  9. from PyQt5.QtCore import QByteArray
  10. from UM.Application import Application
  11. from cura.Machines.MaterialGroup import MaterialGroup
  12. from cura.Machines.MaterialNode import MaterialNode
  13. from plugins.UM3NetworkPrinting.src.SendMaterialJob import SendMaterialJob
  14. _FILES_MAP = {"generic_pla_white": "/materials/generic_pla_white.xml.fdm_material",
  15. "generic_pla_black": "/materials/generic_pla_black.xml.fdm_material",
  16. }
  17. @patch("builtins.open", lambda _, __: io.StringIO("<xml></xml>"))
  18. class TestSendMaterialJob(TestCase):
  19. # version 1
  20. _LOCAL_MATERIAL_WHITE = {"type": "material", "status": "unknown", "id": "generic_pla_white",
  21. "base_file": "generic_pla_white", "setting_version": "5", "name": "White PLA",
  22. "brand": "Generic", "material": "PLA", "color_name": "White",
  23. "GUID": "badb0ee7-87c8-4f3f-9398-938587b67dce", "version": "1", "color_code": "#ffffff",
  24. "description": "Test PLA White", "adhesion_info": "Use glue.", "approximate_diameter": "3",
  25. "properties": {"density": "1.00", "diameter": "2.85", "weight": "750"},
  26. "definition": "fdmprinter", "compatible": True}
  27. # version 2
  28. _LOCAL_MATERIAL_WHITE_NEWER = {"type": "material", "status": "unknown", "id": "generic_pla_white",
  29. "base_file": "generic_pla_white", "setting_version": "5", "name": "White PLA",
  30. "brand": "Generic", "material": "PLA", "color_name": "White",
  31. "GUID": "badb0ee7-87c8-4f3f-9398-938587b67dce", "version": "2",
  32. "color_code": "#ffffff",
  33. "description": "Test PLA White", "adhesion_info": "Use glue.",
  34. "approximate_diameter": "3",
  35. "properties": {"density": "1.00", "diameter": "2.85", "weight": "750"},
  36. "definition": "fdmprinter", "compatible": True}
  37. # invalid version: "one"
  38. _LOCAL_MATERIAL_WHITE_INVALID_VERSION = {"type": "material", "status": "unknown", "id": "generic_pla_white",
  39. "base_file": "generic_pla_white", "setting_version": "5", "name": "White PLA",
  40. "brand": "Generic", "material": "PLA", "color_name": "White",
  41. "GUID": "badb0ee7-87c8-4f3f-9398-938587b67dce", "version": "one",
  42. "color_code": "#ffffff",
  43. "description": "Test PLA White", "adhesion_info": "Use glue.",
  44. "approximate_diameter": "3",
  45. "properties": {"density": "1.00", "diameter": "2.85", "weight": "750"},
  46. "definition": "fdmprinter", "compatible": True}
  47. _LOCAL_MATERIAL_WHITE_ALL_RESULT = {"generic_pla_white": MaterialGroup("generic_pla_white",
  48. MaterialNode(_LOCAL_MATERIAL_WHITE))}
  49. _LOCAL_MATERIAL_WHITE_NEWER_ALL_RESULT = {"generic_pla_white": MaterialGroup("generic_pla_white",
  50. MaterialNode(_LOCAL_MATERIAL_WHITE_NEWER))}
  51. _LOCAL_MATERIAL_WHITE_INVALID_VERSION_ALL_RESULT = {"generic_pla_white": MaterialGroup("generic_pla_white",
  52. MaterialNode(_LOCAL_MATERIAL_WHITE_INVALID_VERSION))}
  53. _LOCAL_MATERIAL_BLACK = {"type": "material", "status": "unknown", "id": "generic_pla_black",
  54. "base_file": "generic_pla_black", "setting_version": "5", "name": "Yellow CPE",
  55. "brand": "Ultimaker", "material": "CPE", "color_name": "Black",
  56. "GUID": "5fbb362a-41f9-4818-bb43-15ea6df34aa4", "version": "1", "color_code": "#000000",
  57. "description": "Test PLA Black", "adhesion_info": "Use glue.", "approximate_diameter": "3",
  58. "properties": {"density": "1.01", "diameter": "2.85", "weight": "750"},
  59. "definition": "fdmprinter", "compatible": True}
  60. _LOCAL_MATERIAL_BLACK_ALL_RESULT = {"generic_pla_black": MaterialGroup("generic_pla_black",
  61. MaterialNode(_LOCAL_MATERIAL_BLACK))}
  62. _REMOTE_MATERIAL_WHITE = {
  63. "guid": "badb0ee7-87c8-4f3f-9398-938587b67dce",
  64. "material": "PLA",
  65. "brand": "Generic",
  66. "version": 1,
  67. "color": "White",
  68. "density": 1.00
  69. }
  70. _REMOTE_MATERIAL_BLACK = {
  71. "guid": "5fbb362a-41f9-4818-bb43-15ea6df34aa4",
  72. "material": "PLA",
  73. "brand": "Generic",
  74. "version": 2,
  75. "color": "Black",
  76. "density": 1.00
  77. }
  78. def test_run(self):
  79. device_mock = MagicMock()
  80. job = SendMaterialJob(device_mock)
  81. job.run()
  82. # We expect the materials endpoint to be called when the job runs.
  83. device_mock.get.assert_called_with("materials/", on_finished = job._onGetRemoteMaterials)
  84. def test__onGetRemoteMaterials_withFailedRequest(self):
  85. reply_mock = MagicMock()
  86. device_mock = MagicMock()
  87. reply_mock.attribute.return_value = 404
  88. job = SendMaterialJob(device_mock)
  89. job._onGetRemoteMaterials(reply_mock)
  90. # We expect the device not to be called for any follow up.
  91. self.assertEqual(0, device_mock.createFormPart.call_count)
  92. def test__onGetRemoteMaterials_withWrongEncoding(self):
  93. reply_mock = MagicMock()
  94. device_mock = MagicMock()
  95. reply_mock.attribute.return_value = 200
  96. reply_mock.readAll.return_value = QByteArray(json.dumps([self._REMOTE_MATERIAL_WHITE]).encode("cp500"))
  97. job = SendMaterialJob(device_mock)
  98. job._onGetRemoteMaterials(reply_mock)
  99. # Given that the parsing fails we do no expect the device to be called for any follow up.
  100. self.assertEqual(0, device_mock.createFormPart.call_count)
  101. def test__onGetRemoteMaterials_withBadJsonAnswer(self):
  102. reply_mock = MagicMock()
  103. device_mock = MagicMock()
  104. reply_mock.attribute.return_value = 200
  105. reply_mock.readAll.return_value = QByteArray(b"Six sick hicks nick six slick bricks with picks and sticks.")
  106. job = SendMaterialJob(device_mock)
  107. job._onGetRemoteMaterials(reply_mock)
  108. # Given that the parsing fails we do no expect the device to be called for any follow up.
  109. self.assertEqual(0, device_mock.createFormPart.call_count)
  110. def test__onGetRemoteMaterials_withMissingGuidInRemoteMaterial(self):
  111. reply_mock = MagicMock()
  112. device_mock = MagicMock()
  113. reply_mock.attribute.return_value = 200
  114. remote_material_without_guid = self._REMOTE_MATERIAL_WHITE.copy()
  115. del remote_material_without_guid["guid"]
  116. reply_mock.readAll.return_value = QByteArray(json.dumps([remote_material_without_guid]).encode("ascii"))
  117. job = SendMaterialJob(device_mock)
  118. job._onGetRemoteMaterials(reply_mock)
  119. # Given that parsing fails we do not expect the device to be called for any follow up.
  120. self.assertEqual(0, device_mock.createFormPart.call_count)
  121. @patch("cura.Machines.MaterialManager.MaterialManager")
  122. @patch("cura.Settings.CuraContainerRegistry")
  123. @patch("UM.Application")
  124. def test__onGetRemoteMaterials_withInvalidVersionInLocalMaterial(self, application_mock, container_registry_mock,
  125. material_manager_mock):
  126. reply_mock = MagicMock()
  127. device_mock = MagicMock()
  128. application_mock.getContainerRegistry.return_value = container_registry_mock
  129. application_mock.getMaterialManager.return_value = material_manager_mock
  130. reply_mock.attribute.return_value = 200
  131. reply_mock.readAll.return_value = QByteArray(json.dumps([self._REMOTE_MATERIAL_WHITE]).encode("ascii"))
  132. material_manager_mock.getAllMaterialGroups.return_value = self._LOCAL_MATERIAL_WHITE_INVALID_VERSION_ALL_RESULT.copy()
  133. with mock.patch.object(Application, "getInstance", new = lambda: application_mock):
  134. job = SendMaterialJob(device_mock)
  135. job._onGetRemoteMaterials(reply_mock)
  136. self.assertEqual(0, device_mock.createFormPart.call_count)
  137. @patch("UM.Application.Application.getInstance")
  138. def test__onGetRemoteMaterials_withNoUpdate(self, application_mock):
  139. reply_mock = MagicMock()
  140. device_mock = MagicMock()
  141. container_registry_mock = application_mock.getContainerRegistry.return_value
  142. material_manager_mock = application_mock.getMaterialManager.return_value
  143. device_mock.createFormPart.return_value = "_xXx_"
  144. material_manager_mock.getAllMaterialGroups.return_value = self._LOCAL_MATERIAL_WHITE_ALL_RESULT.copy()
  145. reply_mock.attribute.return_value = 200
  146. reply_mock.readAll.return_value = QByteArray(json.dumps([self._REMOTE_MATERIAL_WHITE]).encode("ascii"))
  147. with mock.patch.object(Application, "getInstance", new = lambda: application_mock):
  148. job = SendMaterialJob(device_mock)
  149. job._onGetRemoteMaterials(reply_mock)
  150. self.assertEqual(0, device_mock.createFormPart.call_count)
  151. self.assertEqual(0, device_mock.postFormWithParts.call_count)
  152. @patch("UM.Application.Application.getInstance")
  153. def test__onGetRemoteMaterials_withUpdatedMaterial(self, get_instance_mock):
  154. reply_mock = MagicMock()
  155. device_mock = MagicMock()
  156. application_mock = get_instance_mock.return_value
  157. container_registry_mock = application_mock.getContainerRegistry.return_value
  158. material_manager_mock = application_mock.getMaterialManager.return_value
  159. container_registry_mock.getContainerFilePathById = lambda x: _FILES_MAP.get(x)
  160. device_mock.createFormPart.return_value = "_xXx_"
  161. material_manager_mock.getAllMaterialGroups.return_value = self._LOCAL_MATERIAL_WHITE_NEWER_ALL_RESULT.copy()
  162. reply_mock.attribute.return_value = 200
  163. reply_mock.readAll.return_value = QByteArray(json.dumps([self._REMOTE_MATERIAL_WHITE]).encode("ascii"))
  164. job = SendMaterialJob(device_mock)
  165. job._onGetRemoteMaterials(reply_mock)
  166. self.assertEqual(1, device_mock.createFormPart.call_count)
  167. self.assertEqual(1, device_mock.postFormWithParts.call_count)
  168. self.assertEquals(
  169. [call.createFormPart("name=\"file\"; filename=\"generic_pla_white.xml.fdm_material\"", "<xml></xml>"),
  170. call.postFormWithParts(target = "materials/", parts = ["_xXx_"], on_finished = job.sendingFinished)],
  171. device_mock.method_calls)
  172. @patch("UM.Application.Application.getInstance")
  173. def test__onGetRemoteMaterials_withNewMaterial(self, application_mock):
  174. reply_mock = MagicMock()
  175. device_mock = MagicMock()
  176. container_registry_mock = application_mock.getContainerRegistry.return_value
  177. material_manager_mock = application_mock.getMaterialManager.return_value
  178. container_registry_mock.getContainerFilePathById = lambda x: _FILES_MAP.get(x)
  179. device_mock.createFormPart.return_value = "_xXx_"
  180. all_results = self._LOCAL_MATERIAL_WHITE_ALL_RESULT.copy()
  181. for key, value in self._LOCAL_MATERIAL_BLACK_ALL_RESULT.items():
  182. all_results[key] = value
  183. material_manager_mock.getAllMaterialGroups.return_value = all_results
  184. reply_mock.attribute.return_value = 200
  185. reply_mock.readAll.return_value = QByteArray(json.dumps([self._REMOTE_MATERIAL_BLACK]).encode("ascii"))
  186. with mock.patch.object(Application, "getInstance", new = lambda: application_mock):
  187. job = SendMaterialJob(device_mock)
  188. job._onGetRemoteMaterials(reply_mock)
  189. self.assertEqual(1, device_mock.createFormPart.call_count)
  190. self.assertEqual(1, device_mock.postFormWithParts.call_count)
  191. self.assertEquals(
  192. [call.createFormPart("name=\"file\"; filename=\"generic_pla_white.xml.fdm_material\"", "<xml></xml>"),
  193. call.postFormWithParts(target = "materials/", parts = ["_xXx_"], on_finished = job.sendingFinished)],
  194. device_mock.method_calls)