TestSendMaterialJob.py 13 KB

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