123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117 |
- from __future__ import annotations
- import os
- import shutil
- from collections import defaultdict
- from typing import Any
- import orjson
- from fixtures.integrations import FIXTURE_DIRECTORY
- from fixtures.integrations.stub_service import StubService
- from sentry.utils.numbers import base32_encode
- class MockService(StubService):
- """
- A mock is a service that replicates the functionality of a real software
- system by implementing the same interface with simplified business logic.
- For example, a mocked random dice_roll function might return `hash(time()) % 6`.
- Like stubs, mocks can make tests simpler and more reliable.
- """
- def __init__(self, mode="memory"):
- """
- Initialize the mock instance. Wipe the previous instance's data if it exists.
- """
- super().__init__()
- self.mode = mode
- self._next_error_code = None
- self._next_ids = defaultdict(int)
- if self.mode == "file":
- path = os.path.join(FIXTURE_DIRECTORY, self.service_name, "data")
- if os.path.exists(path):
- shutil.rmtree(path)
- os.makedirs(path)
- else:
- self._memory: dict[str, dict[str, Any]] = defaultdict(dict)
- def add_project(self, project):
- """
- Create a new, empty project.
- :param project: String name of project
- :return: void
- """
- self._next_ids.get(project) # touch
- if self.mode == "file":
- self._get_project_path(project)
- def remove_project(self, project):
- """
- Totally wipe out a project.
- :param project: String name of project
- :return: void
- """
- del self._next_ids[project]
- if self.mode == "file":
- path = self._get_project_path(project)
- shutil.rmtree(path)
- def break_next_api_call(self, error_code=500):
- """
- Simulate an outage for a single API call.
- """
- self._next_error_code = error_code
- def _throw_if_broken(self, message_option=None):
- """
- See break_next_api_call.
- :param message_option: What should the message be if this raises?
- :raises: Generic Exception
- """
- if self._next_error_code:
- self._next_error_code = None
- message = message_option or f"{self.service_name} is down"
- raise Exception(f"{self._next_error_code}: {message}")
- def _get_project_names(self):
- return self._next_ids.keys()
- def _get_new_ticket_name(self, project):
- counter = self._next_ids[project]
- self._next_ids[project] = counter + 1
- return f"{project}-{base32_encode(counter)}"
- def _get_project_path(self, project):
- path = os.path.join(FIXTURE_DIRECTORY, self.service_name, "data", project)
- os.makedirs(path, exist_ok=True)
- return path
- def _set_data(self, project, name, data):
- if self.mode == "memory":
- if not self._memory[project]:
- self._memory[project] = defaultdict()
- self._memory[project][name] = data
- return
- path = os.path.join(self._get_project_path(project), f"{name}.json")
- with open(path, "wb") as f:
- f.write(orjson.dumps(data))
- def _get_data(self, project, name):
- if self.mode == "memory":
- if not self._memory[project]:
- self._memory[project] = defaultdict()
- return self._memory[project].get(name)
- path = os.path.join(self._get_project_path(project), f"{name}.json")
- if not os.path.exists(path):
- return None
- with open(path, "rb") as f:
- return orjson.loads(f.read())
|