mock_service.py 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. import os
  2. import shutil
  3. from collections import defaultdict
  4. from fixtures.integrations import FIXTURE_DIRECTORY
  5. from fixtures.integrations.stub_service import StubService
  6. from sentry.utils import json
  7. from sentry.utils.numbers import base32_encode
  8. class MockService(StubService):
  9. """
  10. A mock is a service that replicates the functionality of a real software
  11. system by implementing the same interface with simplified business logic.
  12. For example, a mocked random dice_roll function might return `hash(time()) % 6`.
  13. Like stubs, mocks can make tests simpler and more reliable.
  14. """
  15. def __init__(self, mode="memory"):
  16. """
  17. Initialize the mock instance. Wipe the previous instance's data if it exists.
  18. """
  19. super().__init__()
  20. self.mode = mode
  21. self._next_error_code = None
  22. self._next_ids = defaultdict(lambda: 0)
  23. if self.mode == "file":
  24. path = os.path.join(FIXTURE_DIRECTORY, self.service_name, "data")
  25. if os.path.exists(path):
  26. shutil.rmtree(path)
  27. os.makedirs(path)
  28. else:
  29. self._memory = defaultdict(dict)
  30. def add_project(self, project):
  31. """
  32. Create a new, empty project.
  33. :param project: String name of project
  34. :return: void
  35. """
  36. self._next_ids.get(project) # touch
  37. if self.mode == "file":
  38. self._get_project_path(project)
  39. def remove_project(self, project):
  40. """
  41. Totally wipe out a project.
  42. :param project: String name of project
  43. :return: void
  44. """
  45. del self._next_ids[project]
  46. if self.mode == "file":
  47. path = self._get_project_path(project)
  48. shutil.rmtree(path)
  49. def break_next_api_call(self, error_code=500):
  50. """
  51. Simulate an outage for a single API call.
  52. """
  53. self._next_error_code = error_code
  54. def _throw_if_broken(self, message_option=None):
  55. """
  56. See break_next_api_call.
  57. :param message_option: What should the message be if this raises?
  58. :raises: Generic Exception
  59. """
  60. if self._next_error_code:
  61. self._next_error_code = None
  62. message = message_option or f"{self.service_name} is down"
  63. raise Exception(f"{self._next_error_code}: {message}")
  64. def _get_project_names(self):
  65. return self._next_ids.keys()
  66. def _get_new_ticket_name(self, project):
  67. counter = self._next_ids[project]
  68. self._next_ids[project] = counter + 1
  69. return f"{project}-{base32_encode(counter)}"
  70. def _get_project_path(self, project):
  71. path = os.path.join(FIXTURE_DIRECTORY, self.service_name, "data", project)
  72. if not os.path.exists(path):
  73. os.makedirs(path)
  74. return path
  75. def _set_data(self, project, name, data):
  76. if self.mode == "memory":
  77. if not self._memory[project]:
  78. self._memory[project] = defaultdict()
  79. self._memory[project][name] = data
  80. return
  81. path = os.path.join(self._get_project_path(project), f"{name}.json")
  82. with open(path, "w") as f:
  83. f.write(json.dumps(data, sort_keys=True, indent=4))
  84. def _get_data(self, project, name):
  85. if self.mode == "memory":
  86. if not self._memory[project]:
  87. self._memory[project] = defaultdict()
  88. return self._memory[project].get(name)
  89. path = os.path.join(self._get_project_path(project), f"{name}.json")
  90. if not os.path.exists(path):
  91. return None
  92. with open(path) as f:
  93. return json.loads(f.read())