NetworkManagerMock.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. # Copyright (c) 2018 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. import json
  4. from typing import Dict, Tuple, Union, Optional, Any
  5. from unittest.mock import MagicMock
  6. from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest
  7. from UM.Logger import Logger
  8. from UM.Signal import Signal
  9. class FakeSignal:
  10. def __init__(self):
  11. self._callbacks = []
  12. def connect(self, callback):
  13. self._callbacks.append(callback)
  14. def disconnect(self, callback):
  15. self._callbacks.remove(callback)
  16. def emit(self, *args, **kwargs):
  17. for callback in self._callbacks:
  18. callback(*args, **kwargs)
  19. ## This class can be used to mock the QNetworkManager class and test the code using it.
  20. # After patching the QNetworkManager class, requests are prepared before they can be executed.
  21. # Any requests not prepared beforehand will cause KeyErrors.
  22. class NetworkManagerMock:
  23. # An enumeration of the supported operations and their code for the network access manager.
  24. _OPERATIONS = {
  25. "GET": QNetworkAccessManager.GetOperation,
  26. "POST": QNetworkAccessManager.PostOperation,
  27. "PUT": QNetworkAccessManager.PutOperation,
  28. "DELETE": QNetworkAccessManager.DeleteOperation,
  29. "HEAD": QNetworkAccessManager.HeadOperation,
  30. } # type: Dict[str, int]
  31. ## Initializes the network manager mock.
  32. def __init__(self) -> None:
  33. # A dict with the prepared replies, using the format {(http_method, url): reply}
  34. self.replies = {} # type: Dict[Tuple[str, str], MagicMock]
  35. self.request_bodies = {} # type: Dict[Tuple[str, str], bytes]
  36. # Signals used in the network manager.
  37. self.finished = Signal()
  38. self.authenticationRequired = Signal()
  39. ## Mock implementation of the get, post, put, delete and head methods from the network manager.
  40. # Since the methods are very simple and the same it didn't make sense to repeat the code.
  41. # \param method: The method being called.
  42. # \return The mocked function, if the method name is known. Defaults to the standard getattr function.
  43. def __getattr__(self, method: str) -> Any:
  44. ## This mock implementation will simply return the reply from the prepared ones.
  45. # it raises a KeyError if requests are done without being prepared.
  46. def doRequest(request: QNetworkRequest, body: Optional[bytes] = None, *_):
  47. key = method.upper(), request.url().toString()
  48. if body:
  49. self.request_bodies[key] = body
  50. return self.replies[key]
  51. operation = self._OPERATIONS.get(method.upper())
  52. if operation:
  53. return doRequest
  54. # the attribute is not one of the implemented methods, default to the standard implementation.
  55. return getattr(super(), method)
  56. ## Prepares a server reply for the given parameters.
  57. # \param method: The HTTP method.
  58. # \param url: The URL being requested.
  59. # \param status_code: The HTTP status code for the response.
  60. # \param response: The response body from the server (generally json-encoded).
  61. def prepareReply(self, method: str, url: str, status_code: int, response: Union[bytes, dict]) -> None:
  62. reply_mock = MagicMock()
  63. reply_mock.url().toString.return_value = url
  64. reply_mock.operation.return_value = self._OPERATIONS[method]
  65. reply_mock.attribute.return_value = status_code
  66. reply_mock.finished = FakeSignal()
  67. reply_mock.isFinished.return_value = False
  68. reply_mock.readAll.return_value = response if isinstance(response, bytes) else json.dumps(response).encode()
  69. self.replies[method, url] = reply_mock
  70. Logger.log("i", "Prepared mock {}-response to {} {}", status_code, method, url)
  71. ## Gets the request that was sent to the network manager for the given method and URL.
  72. # \param method: The HTTP method.
  73. # \param url: The URL.
  74. def getRequestBody(self, method: str, url: str) -> Optional[bytes]:
  75. return self.request_bodies.get((method.upper(), url))
  76. ## Emits the signal that the reply is ready to all prepared replies.
  77. def flushReplies(self) -> None:
  78. for key, reply in self.replies.items():
  79. Logger.log("i", "Flushing reply to {} {}", *key)
  80. reply.isFinished.return_value = True
  81. reply.finished.emit()
  82. self.finished.emit(reply)
  83. self.reset()
  84. ## Deletes all prepared replies
  85. def reset(self) -> None:
  86. self.replies.clear()