Files
2024-02-15 17:01:50 +00:00

352 lines
15 KiB
Python

from unittest.mock import MagicMock, Mock, patch
from uuid import uuid4
import pytest
from sentry.models.relocation import Relocation
from sentry.testutils.cases import TestCase
from sentry.utils.relocation import (
OrderedTask,
fail_relocation,
retry_task_or_fail_relocation,
start_relocation_task,
)
class RelocationUtilsTestCase(TestCase):
def setUp(self):
super().setUp()
self.owner = self.create_user(
email="owner", is_superuser=False, is_staff=False, is_active=True
)
self.superuser = self.create_user(
"superuser", is_superuser=True, is_staff=True, is_active=True
)
self.relocation: Relocation = Relocation.objects.create(
creator_id=self.superuser.id,
owner_id=self.owner.id,
want_org_slugs=["testing"],
step=Relocation.Step.UPLOADING.value,
)
self.uuid = self.relocation.uuid
def mock_message_builder(self, fake_message_builder: Mock):
fake_message_builder.return_value.send_async.return_value = MagicMock()
@patch("sentry.utils.relocation.MessageBuilder")
class RelocationStartTestCase(RelocationUtilsTestCase):
def test_bad_relocation_not_found(self, fake_message_builder: Mock):
self.mock_message_builder(fake_message_builder)
uuid = uuid4().hex
(rel, attempts_left) = start_relocation_task(uuid, OrderedTask.UPLOADING_COMPLETE, 3)
assert fake_message_builder.call_count == 0
assert rel is None
assert not attempts_left
def test_bad_relocation_completed(self, fake_message_builder: Mock):
self.mock_message_builder(fake_message_builder)
self.relocation.status = Relocation.Status.FAILURE.value
self.relocation.save()
(rel, attempts_left) = start_relocation_task(self.uuid, OrderedTask.UPLOADING_COMPLETE, 3)
assert fake_message_builder.call_count == 0
assert rel is None
assert not attempts_left
assert Relocation.objects.get(uuid=self.uuid).status == Relocation.Status.FAILURE.value
def test_bad_unknown_task(self, fake_message_builder: Mock):
self.mock_message_builder(fake_message_builder)
(rel, attempts_left) = start_relocation_task(self.uuid, OrderedTask.NONE, 3)
assert fake_message_builder.call_count == 1
assert fake_message_builder.call_args.kwargs["type"] == "relocation.failed"
fake_message_builder.return_value.send_async.assert_called_once_with(
to=[self.owner.email, self.superuser.email]
)
assert rel is None
assert not attempts_left
assert Relocation.objects.get(uuid=self.uuid).status == Relocation.Status.FAILURE.value
def test_bad_task_out_of_order(self, fake_message_builder: Mock):
self.mock_message_builder(fake_message_builder)
self.relocation.latest_task = OrderedTask.PREPROCESSING_SCAN.name
self.relocation.save()
(rel, attempts_left) = start_relocation_task(self.uuid, OrderedTask.UPLOADING_COMPLETE, 3)
assert fake_message_builder.call_count == 1
assert fake_message_builder.call_args.kwargs["type"] == "relocation.failed"
fake_message_builder.return_value.send_async.assert_called_once_with(
to=[self.owner.email, self.superuser.email]
)
assert rel is None
assert not attempts_left
assert Relocation.objects.get(uuid=self.uuid).status == Relocation.Status.FAILURE.value
def test_good_first_task(self, fake_message_builder: Mock):
self.mock_message_builder(fake_message_builder)
(rel, attempts_left) = start_relocation_task(self.uuid, OrderedTask.UPLOADING_COMPLETE, 3)
assert fake_message_builder.call_count == 0
assert attempts_left == 2
relocation: Relocation = Relocation.objects.get(uuid=self.uuid)
assert relocation.step == Relocation.Step.UPLOADING.value
assert relocation.status != Relocation.Status.FAILURE.value
def test_good_next_task(self, fake_message_builder: Mock):
self.mock_message_builder(fake_message_builder)
self.relocation.latest_task = OrderedTask.UPLOADING_COMPLETE.name
self.relocation.save()
(rel, attempts_left) = start_relocation_task(self.uuid, OrderedTask.PREPROCESSING_SCAN, 3)
assert fake_message_builder.call_count == 0
assert attempts_left == 2
relocation: Relocation = Relocation.objects.get(uuid=self.uuid)
assert relocation.step == Relocation.Step.PREPROCESSING.value
assert relocation.status != Relocation.Status.FAILURE.value
def test_good_pause_at_scheduled_pause(self, fake_message_builder: Mock):
self.mock_message_builder(fake_message_builder)
self.relocation.latest_task = OrderedTask.UPLOADING_COMPLETE.name
self.relocation.scheduled_pause_at_step = Relocation.Step.PREPROCESSING.value
self.relocation.save()
(rel, attempts_left) = start_relocation_task(self.uuid, OrderedTask.PREPROCESSING_SCAN, 3)
assert fake_message_builder.call_count == 0
assert attempts_left == 0
relocation: Relocation = Relocation.objects.get(uuid=self.uuid)
assert relocation.step == Relocation.Step.PREPROCESSING.value
assert relocation.latest_task == OrderedTask.PREPROCESSING_SCAN.name
assert relocation.status == Relocation.Status.PAUSE.value
assert relocation.scheduled_pause_at_step is None
def test_good_already_paused(self, fake_message_builder: Mock):
self.mock_message_builder(fake_message_builder)
self.relocation.latest_task = OrderedTask.UPLOADING_COMPLETE.name
self.relocation.status = Relocation.Status.PAUSE.value
self.relocation.save()
(rel, attempts_left) = start_relocation_task(self.uuid, OrderedTask.UPLOADING_COMPLETE, 3)
assert fake_message_builder.call_count == 0
assert attempts_left == 0
relocation: Relocation = Relocation.objects.get(uuid=self.uuid)
assert relocation.step == Relocation.Step.UPLOADING.value
assert relocation.latest_task == OrderedTask.UPLOADING_COMPLETE.name
assert relocation.status == Relocation.Status.PAUSE.value
assert relocation.scheduled_pause_at_step is None
def test_good_cancel_at_scheduled_cancel(self, fake_message_builder: Mock):
self.mock_message_builder(fake_message_builder)
self.relocation.latest_task = OrderedTask.UPLOADING_COMPLETE.name
self.relocation.scheduled_cancel_at_step = Relocation.Step.PREPROCESSING.value
self.relocation.save()
(_, attempts_left) = start_relocation_task(self.uuid, OrderedTask.PREPROCESSING_SCAN, 3)
assert fake_message_builder.call_count == 0
assert attempts_left == 0
relocation: Relocation = Relocation.objects.get(uuid=self.uuid)
assert relocation.step == Relocation.Step.PREPROCESSING.value
assert relocation.latest_task == OrderedTask.PREPROCESSING_SCAN.name
assert relocation.status == Relocation.Status.FAILURE.value
assert relocation.scheduled_cancel_at_step is None
assert relocation.failure_reason == "This relocation was cancelled by an administrator."
def test_good_already_cancelled(self, fake_message_builder: Mock):
self.mock_message_builder(fake_message_builder)
self.relocation.step = Relocation.Step.POSTPROCESSING.value
self.relocation.latest_task = OrderedTask.POSTPROCESSING.name
self.relocation.status = Relocation.Status.FAILURE.value
self.relocation.failure_reason = "Cancelled"
self.relocation.save()
(_, attempts_left) = start_relocation_task(self.uuid, OrderedTask.POSTPROCESSING, 3)
assert fake_message_builder.call_count == 0
assert attempts_left == 0
relocation: Relocation = Relocation.objects.get(uuid=self.uuid)
assert relocation.step == Relocation.Step.POSTPROCESSING.value
assert relocation.latest_task == OrderedTask.POSTPROCESSING.name
assert relocation.status == Relocation.Status.FAILURE.value
assert relocation.scheduled_cancel_at_step is None
assert self.relocation.failure_reason == "Cancelled"
def test_good_cancel_before_pause(self, fake_message_builder: Mock):
self.mock_message_builder(fake_message_builder)
self.relocation.latest_task = OrderedTask.UPLOADING_COMPLETE.name
self.relocation.scheduled_cancel_at_step = Relocation.Step.PREPROCESSING.value
self.relocation.scheduled_pause_at_step = Relocation.Step.PREPROCESSING.value
self.relocation.save()
(_, attempts_left) = start_relocation_task(self.uuid, OrderedTask.PREPROCESSING_SCAN, 3)
assert fake_message_builder.call_count == 0
assert attempts_left == 0
relocation: Relocation = Relocation.objects.get(uuid=self.uuid)
assert relocation.step == Relocation.Step.PREPROCESSING.value
assert relocation.latest_task == OrderedTask.PREPROCESSING_SCAN.name
assert relocation.status == Relocation.Status.FAILURE.value
assert relocation.scheduled_pause_at_step is None
assert relocation.scheduled_cancel_at_step is None
assert relocation.failure_reason == "This relocation was cancelled by an administrator."
def test_good_pause_before_cancel(self, fake_message_builder: Mock):
self.mock_message_builder(fake_message_builder)
self.relocation.latest_task = OrderedTask.UPLOADING_COMPLETE.name
self.relocation.scheduled_cancel_at_step = Relocation.Step.POSTPROCESSING.value
self.relocation.scheduled_pause_at_step = Relocation.Step.PREPROCESSING.value
self.relocation.save()
(_, attempts_left) = start_relocation_task(self.uuid, OrderedTask.PREPROCESSING_SCAN, 3)
assert fake_message_builder.call_count == 0
assert attempts_left == 0
relocation: Relocation = Relocation.objects.get(uuid=self.uuid)
assert relocation.step == Relocation.Step.PREPROCESSING.value
assert relocation.latest_task == OrderedTask.PREPROCESSING_SCAN.name
assert relocation.status == Relocation.Status.PAUSE.value
assert relocation.scheduled_pause_at_step is None
assert relocation.scheduled_cancel_at_step == Relocation.Step.POSTPROCESSING.value
assert relocation.failure_reason is None
@patch("sentry.utils.relocation.MessageBuilder")
class RelocationFailTestCase(RelocationUtilsTestCase):
def test_no_reason(self, fake_message_builder: Mock):
self.mock_message_builder(fake_message_builder)
fail_relocation(self.relocation, OrderedTask.UPLOADING_COMPLETE)
assert fake_message_builder.call_count == 1
assert fake_message_builder.call_args.kwargs["type"] == "relocation.failed"
fake_message_builder.return_value.send_async.assert_called_once_with(
to=[self.owner.email, self.superuser.email]
)
relocation: Relocation = Relocation.objects.get(uuid=self.uuid)
assert relocation.status == Relocation.Status.FAILURE.value
assert relocation.latest_notified == Relocation.EmailKind.FAILED.value
assert not relocation.failure_reason
def test_with_reason(self, fake_message_builder: Mock):
self.mock_message_builder(fake_message_builder)
fail_relocation(self.relocation, OrderedTask.UPLOADING_COMPLETE, "foo")
assert fake_message_builder.call_count == 1
assert fake_message_builder.call_args.kwargs["type"] == "relocation.failed"
fake_message_builder.return_value.send_async.assert_called_once_with(
to=[self.owner.email, self.superuser.email]
)
relocation: Relocation = Relocation.objects.get(uuid=self.uuid)
assert relocation.status == Relocation.Status.FAILURE.value
assert relocation.latest_notified == Relocation.EmailKind.FAILED.value
assert relocation.failure_reason == "foo"
@patch("sentry.utils.relocation.MessageBuilder")
class RelocationRetryOrFailTestCase(RelocationUtilsTestCase):
def test_no_reason_attempts_left(self, fake_message_builder: Mock):
self.mock_message_builder(fake_message_builder)
with pytest.raises(ValueError):
with retry_task_or_fail_relocation(self.relocation, OrderedTask.UPLOADING_COMPLETE, 3):
raise ValueError("Some sort of failure")
assert fake_message_builder.call_count == 0
relocation: Relocation = Relocation.objects.get(uuid=self.uuid)
assert relocation.status == Relocation.Status.IN_PROGRESS.value
assert relocation.latest_notified != Relocation.EmailKind.FAILED.value
assert not relocation.failure_reason
def test_no_reason_last_attempt(self, fake_message_builder: Mock):
self.mock_message_builder(fake_message_builder)
# Wrap in `try/except` to make mypy happy.
try:
with retry_task_or_fail_relocation(self.relocation, OrderedTask.UPLOADING_COMPLETE, 0):
raise ValueError("Some sort of failure")
except Exception:
pass
assert fake_message_builder.call_count == 1
assert fake_message_builder.call_args.kwargs["type"] == "relocation.failed"
fake_message_builder.return_value.send_async.assert_called_once_with(
to=[self.owner.email, self.superuser.email]
)
relocation: Relocation = Relocation.objects.get(uuid=self.uuid)
assert relocation.status == Relocation.Status.FAILURE.value
assert relocation.latest_notified == Relocation.EmailKind.FAILED.value
def test_with_reason_attempts_left(self, fake_message_builder: Mock):
self.mock_message_builder(fake_message_builder)
with pytest.raises(ValueError):
with retry_task_or_fail_relocation(
self.relocation, OrderedTask.UPLOADING_COMPLETE, 3, "foo"
):
raise ValueError("Some sort of failure")
assert fake_message_builder.call_count == 0
relocation: Relocation = Relocation.objects.get(uuid=self.uuid)
assert relocation.status == Relocation.Status.IN_PROGRESS.value
assert relocation.latest_notified != Relocation.EmailKind.FAILED.value
assert not relocation.failure_reason
def test_with_reason_last_attempt(self, fake_message_builder: Mock):
self.mock_message_builder(fake_message_builder)
# Wrap in `try/except` to make mypy happy.
try:
with retry_task_or_fail_relocation(
self.relocation, OrderedTask.UPLOADING_COMPLETE, 0, "foo"
):
raise ValueError("Some sort of failure")
except Exception:
pass
assert fake_message_builder.call_count == 1
assert fake_message_builder.call_args.kwargs["type"] == "relocation.failed"
fake_message_builder.return_value.send_async.assert_called_once_with(
to=[self.owner.email, self.superuser.email]
)
relocation: Relocation = Relocation.objects.get(uuid=self.uuid)
assert relocation.status == Relocation.Status.FAILURE.value
assert relocation.latest_notified == Relocation.EmailKind.FAILED.value
assert relocation.failure_reason == "foo"