123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994 |
- from datetime import datetime
- import pytz
- from django.contrib.auth.models import AnonymousUser
- from django.core import mail
- from django.db.models import F
- from django.utils import timezone
- from exam import fixture
- from sentry.api.serializers import UserReportWithGroupSerializer, serialize
- from sentry.digests.notifications import build_digest, event_to_record
- from sentry.event_manager import EventManager, get_event_type
- from sentry.mail import mail_adapter
- from sentry.mail.adapter import ActionTargetType
- from sentry.models import (
- Activity,
- NotificationSetting,
- Organization,
- OrganizationMember,
- OrganizationMemberTeam,
- ProjectOption,
- ProjectOwnership,
- Repository,
- Rule,
- User,
- UserReport,
- )
- from sentry.notifications.types import NotificationSettingOptionValues, NotificationSettingTypes
- from sentry.ownership import grammar
- from sentry.ownership.grammar import Matcher, Owner, dump_schema
- from sentry.plugins.base import Notification
- from sentry.rules.processor import RuleFuture
- from sentry.testutils import TestCase
- from sentry.testutils.helpers.datetime import before_now, iso_format
- from sentry.types.integrations import ExternalProviders
- from sentry.utils.compat import mock
- from sentry.utils.email import MessageBuilder
- class BaseMailAdapterTest:
- @fixture
- def adapter(self):
- return mail_adapter
- def make_event_data(self, filename, url="http://example.com"):
- mgr = EventManager(
- {
- "tags": [("level", "error")],
- "stacktrace": {"frames": [{"lineno": 1, "filename": filename}]},
- "request": {"url": url},
- }
- )
- mgr.normalize()
- data = mgr.get_data()
- event_type = get_event_type(data)
- data["type"] = event_type.key
- data["metadata"] = event_type.get_metadata(data)
- return data
- class MailAdapterGetSendToTest(BaseMailAdapterTest, TestCase):
- def setUp(self):
- self.user2 = self.create_user(email="baz@example.com", is_active=True)
- self.create_member(user=self.user2, organization=self.organization, teams=[self.team])
- ProjectOwnership.objects.create(
- project_id=self.project.id,
- schema=dump_schema(
- [
- grammar.Rule(Matcher("path", "*.py"), [Owner("team", self.team.slug)]),
- grammar.Rule(Matcher("path", "*.jx"), [Owner("user", self.user2.email)]),
- grammar.Rule(
- Matcher("path", "*.cbl"),
- [Owner("user", self.user.email), Owner("user", self.user2.email)],
- ),
- ]
- ),
- fallthrough=True,
- )
- def test_get_send_to_with_team_owners(self):
- event = self.store_event(data=self.make_event_data("foo.py"), project_id=self.project.id)
- assert sorted({self.user.pk, self.user2.pk}) == sorted(
- self.adapter.get_send_to(self.project, ActionTargetType.ISSUE_OWNERS, event=event.data)[
- ExternalProviders.EMAIL
- ]
- )
- # Make sure that disabling mail alerts works as expected
- NotificationSetting.objects.update_settings(
- ExternalProviders.EMAIL,
- NotificationSettingTypes.ISSUE_ALERTS,
- NotificationSettingOptionValues.NEVER,
- user=self.user2,
- project=self.project,
- )
- assert {self.user.pk} == self.adapter.get_send_to(
- self.project, ActionTargetType.ISSUE_OWNERS, event=event.data
- )[ExternalProviders.EMAIL]
- def test_get_send_to_with_user_owners(self):
- event = self.store_event(data=self.make_event_data("foo.cbl"), project_id=self.project.id)
- assert sorted({self.user.pk, self.user2.pk}) == sorted(
- self.adapter.get_send_to(self.project, ActionTargetType.ISSUE_OWNERS, event=event.data)[
- ExternalProviders.EMAIL
- ]
- )
- # Make sure that disabling mail alerts works as expected
- NotificationSetting.objects.update_settings(
- ExternalProviders.EMAIL,
- NotificationSettingTypes.ISSUE_ALERTS,
- NotificationSettingOptionValues.NEVER,
- user=self.user2,
- project=self.project,
- )
- assert {self.user.pk} == self.adapter.get_send_to(
- self.project, ActionTargetType.ISSUE_OWNERS, event=event.data
- )[ExternalProviders.EMAIL]
- def test_get_send_to_with_user_owner(self):
- event = self.store_event(data=self.make_event_data("foo.jx"), project_id=self.project.id)
- assert {self.user2.pk} == self.adapter.get_send_to(
- self.project, ActionTargetType.ISSUE_OWNERS, event=event.data
- )[ExternalProviders.EMAIL]
- def test_get_send_to_with_fallthrough(self):
- event = self.store_event(data=self.make_event_data("foo.cpp"), project_id=self.project.id)
- assert {self.user.pk, self.user2.pk} == self.adapter.get_send_to(
- self.project, ActionTargetType.ISSUE_OWNERS, event=event.data
- )[ExternalProviders.EMAIL]
- def test_get_send_to_without_fallthrough(self):
- ProjectOwnership.objects.get(project_id=self.project.id).update(fallthrough=False)
- event = self.store_event(data=self.make_event_data("foo.cpp"), project_id=self.project.id)
- assert set() == set(
- self.adapter.get_send_to(self.project, ActionTargetType.ISSUE_OWNERS, event=event.data)
- )
- class MailAdapterGetSendableUsersTest(BaseMailAdapterTest, TestCase):
- def test_get_sendable_user_objects(self):
- user = self.create_user(email="foo@example.com", is_active=True)
- user2 = self.create_user(email="baz@example.com", is_active=True)
- self.create_user(email="baz2@example.com", is_active=True)
- # user with inactive account
- self.create_user(email="bar@example.com", is_active=False)
- # user not in any groups
- self.create_user(email="bar2@example.com", is_active=True)
- organization = self.create_organization(owner=user)
- team = self.create_team(organization=organization)
- project = self.create_project(name="Test", teams=[team])
- OrganizationMemberTeam.objects.create(
- organizationmember=OrganizationMember.objects.get(user=user, organization=organization),
- team=team,
- )
- self.create_member(user=user2, organization=organization, teams=[team])
- # all members
- users = self.adapter.get_sendable_user_objects(project)
- assert sorted({user.id, user2.id}) == sorted([user.id for user in users])
- # disabled user2
- NotificationSetting.objects.update_settings(
- ExternalProviders.EMAIL,
- NotificationSettingTypes.ISSUE_ALERTS,
- NotificationSettingOptionValues.NEVER,
- user=user2,
- project=project,
- )
- assert user2 not in self.adapter.get_sendable_user_objects(project)
- user4 = User.objects.create(username="baz4", email="bar@example.com", is_active=True)
- self.create_member(user=user4, organization=organization, teams=[team])
- assert user4 in self.adapter.get_sendable_user_objects(project)
- # disabled by default user4
- NotificationSetting.objects.update_settings(
- ExternalProviders.EMAIL,
- NotificationSettingTypes.ISSUE_ALERTS,
- NotificationSettingOptionValues.NEVER,
- user=user4,
- )
- assert user4 not in self.adapter.get_sendable_user_objects(project)
- NotificationSetting.objects.remove_settings(
- ExternalProviders.EMAIL,
- NotificationSettingTypes.ISSUE_ALERTS,
- user=user4,
- )
- NotificationSetting.objects.update_settings(
- ExternalProviders.EMAIL,
- NotificationSettingTypes.ISSUE_ALERTS,
- NotificationSettingOptionValues.NEVER,
- user=user4,
- )
- assert user4 not in self.adapter.get_sendable_user_objects(project)
- class MailAdapterBuildSubjectPrefixTest(BaseMailAdapterTest, TestCase):
- def test_default_prefix(self):
- assert self.adapter._build_subject_prefix(self.project) == "[Sentry] "
- def test_project_level_prefix(self):
- prefix = "[Example prefix] "
- ProjectOption.objects.set_value(
- project=self.project, key="mail:subject_prefix", value=prefix
- )
- assert self.adapter._build_subject_prefix(self.project) == prefix
- class MailAdapterBuildMessageTest(BaseMailAdapterTest, TestCase):
- def test(self):
- subject = "hello"
- assert self.adapter._build_message(self.project, subject) is None
- def test_specify_send_to(self):
- subject = "hello"
- send_to_user = self.create_user("hello@timecube.com")
- msg = self.adapter._build_message(self.project, subject, send_to=[send_to_user.id])
- assert msg._send_to == {send_to_user.email}
- assert msg.subject.endswith(subject)
- class MailAdapterSendMailTest(BaseMailAdapterTest, TestCase):
- def test(self):
- subject = "hello"
- with self.tasks():
- self.adapter._send_mail(self.project, subject, body="hi", send_to=[self.user.id])
- msg = mail.outbox[0]
- assert msg.subject.endswith(subject)
- assert msg.recipients() == [self.user.email]
- class MailAdapterNotifyTest(BaseMailAdapterTest, TestCase):
- def test_simple_notification(self):
- event = self.store_event(
- data={"message": "Hello world", "level": "error"}, project_id=self.project.id
- )
- rule = Rule.objects.create(project=self.project, label="my rule")
- notification = Notification(event=event, rule=rule)
- with self.options({"system.url-prefix": "http://example.com"}), self.tasks():
- self.adapter.notify(notification, ActionTargetType.ISSUE_OWNERS)
- msg = mail.outbox[0]
- assert msg.subject == "[Sentry] BAR-1 - Hello world"
- assert "my rule" in msg.alternatives[0][0]
- @mock.patch("sentry.interfaces.stacktrace.Stacktrace.get_title")
- @mock.patch("sentry.interfaces.stacktrace.Stacktrace.to_email_html")
- def test_notify_users_renders_interfaces_with_utf8(self, _to_email_html, _get_title):
- _to_email_html.return_value = "רונית מגן"
- _get_title.return_value = "Stacktrace"
- event = self.store_event(
- data={"message": "Soubor ji\xc5\xbe existuje", "stacktrace": {"frames": [{}]}},
- project_id=self.project.id,
- )
- notification = Notification(event=event)
- with self.options({"system.url-prefix": "http://example.com"}):
- self.adapter.notify(notification, ActionTargetType.ISSUE_OWNERS)
- _get_title.assert_called_once_with()
- _to_email_html.assert_called_once_with(event)
- @mock.patch("sentry.mail.mail_adapter._send_mail")
- def test_notify_users_does_email(self, _send_mail):
- event_manager = EventManager({"message": "hello world", "level": "error"})
- event_manager.normalize()
- event_data = event_manager.get_data()
- event_type = get_event_type(event_data)
- event_data["type"] = event_type.key
- event_data["metadata"] = event_type.get_metadata(event_data)
- event = event_manager.save(self.project.id)
- group = event.group
- notification = Notification(event=event)
- with self.options({"system.url-prefix": "http://example.com"}):
- self.adapter.notify(notification, ActionTargetType.ISSUE_OWNERS)
- assert _send_mail.call_count == 1
- args, kwargs = _send_mail.call_args
- self.assertEquals(kwargs.get("project"), self.project)
- self.assertEquals(kwargs.get("reference"), group)
- assert kwargs.get("subject") == "BAR-1 - hello world"
- @mock.patch("sentry.mail.mail_adapter._send_mail")
- def test_multiline_error(self, _send_mail):
- event_manager = EventManager({"message": "hello world\nfoo bar", "level": "error"})
- event_manager.normalize()
- event_data = event_manager.get_data()
- event_type = get_event_type(event_data)
- event_data["type"] = event_type.key
- event_data["metadata"] = event_type.get_metadata(event_data)
- event = event_manager.save(self.project.id)
- notification = Notification(event=event)
- with self.options({"system.url-prefix": "http://example.com"}):
- self.adapter.notify(notification, ActionTargetType.ISSUE_OWNERS)
- assert _send_mail.call_count == 1
- args, kwargs = _send_mail.call_args
- assert kwargs.get("subject") == "BAR-1 - hello world"
- def test_notify_users_with_utf8_subject(self):
- event = self.store_event(
- data={"message": "רונית מגן", "level": "error"}, project_id=self.project.id
- )
- notification = Notification(event=event)
- with self.options({"system.url-prefix": "http://example.com"}), self.tasks():
- self.adapter.notify(notification, ActionTargetType.ISSUE_OWNERS)
- assert len(mail.outbox) == 1
- msg = mail.outbox[0]
- assert msg.subject == "[Sentry] BAR-1 - רונית מגן"
- def test_notify_with_suspect_commits(self):
- repo = Repository.objects.create(
- organization_id=self.organization.id, name=self.organization.id
- )
- release = self.create_release(project=self.project, version="v12")
- release.set_commits(
- [
- {
- "id": "a" * 40,
- "repository": repo.name,
- "author_email": "bob@example.com",
- "author_name": "Bob",
- "message": "i fixed a bug",
- "patch_set": [{"path": "src/sentry/models/release.py", "type": "M"}],
- }
- ]
- )
- event = self.store_event(
- data={
- "message": "Kaboom!",
- "platform": "python",
- "timestamp": iso_format(before_now(seconds=1)),
- "stacktrace": {
- "frames": [
- {
- "function": "handle_set_commits",
- "abs_path": "/usr/src/sentry/src/sentry/tasks.py",
- "module": "sentry.tasks",
- "in_app": True,
- "lineno": 30,
- "filename": "sentry/tasks.py",
- },
- {
- "function": "set_commits",
- "abs_path": "/usr/src/sentry/src/sentry/models/release.py",
- "module": "sentry.models.release",
- "in_app": True,
- "lineno": 39,
- "filename": "sentry/models/release.py",
- },
- ]
- },
- "tags": {"sentry:release": release.version},
- },
- project_id=self.project.id,
- )
- with self.tasks():
- notification = Notification(event=event)
- self.adapter.notify(notification, ActionTargetType.ISSUE_OWNERS)
- assert len(mail.outbox) >= 1
- msg = mail.outbox[-1]
- assert "Suspect Commits" in msg.body
- def assert_notify(
- self,
- event,
- emails_sent_to,
- target_type=ActionTargetType.ISSUE_OWNERS,
- target_identifier=None,
- ):
- mail.outbox = []
- with self.options({"system.url-prefix": "http://example.com"}), self.tasks():
- self.adapter.notify(Notification(event=event), target_type, target_identifier)
- assert sorted(email.to[0] for email in mail.outbox) == sorted(emails_sent_to)
- def test_notify_users_with_owners(self):
- user = self.create_user(email="foo@example.com", is_active=True)
- user2 = self.create_user(email="baz@example.com", is_active=True)
- organization = self.create_organization(owner=user)
- team = self.create_team(organization=organization)
- project = self.create_project(name="Test", teams=[team])
- OrganizationMemberTeam.objects.create(
- organizationmember=OrganizationMember.objects.get(user=user, organization=organization),
- team=team,
- )
- self.create_member(user=user2, organization=organization, teams=[team])
- self.group = self.create_group(
- first_seen=timezone.now(),
- last_seen=timezone.now(),
- project=project,
- message="hello world",
- logger="root",
- )
- ProjectOwnership.objects.create(
- project_id=project.id,
- schema=dump_schema(
- [
- grammar.Rule(Matcher("path", "*.py"), [Owner("team", team.slug)]),
- grammar.Rule(Matcher("path", "*.jx"), [Owner("user", user2.email)]),
- grammar.Rule(
- Matcher("path", "*.cbl"),
- [Owner("user", user.email), Owner("user", user2.email)],
- ),
- ]
- ),
- fallthrough=True,
- )
- event_all_users = self.store_event(
- data=self.make_event_data("foo.cbl"), project_id=project.id
- )
- self.assert_notify(event_all_users, [user.email, user2.email])
- event_team = self.store_event(data=self.make_event_data("foo.py"), project_id=project.id)
- self.assert_notify(event_team, [user.email, user2.email])
- event_single_user = self.store_event(
- data=self.make_event_data("foo.jx"), project_id=project.id
- )
- self.assert_notify(event_single_user, [user2.email])
- # Make sure that disabling mail alerts works as expected
- NotificationSetting.objects.update_settings(
- ExternalProviders.EMAIL,
- NotificationSettingTypes.ISSUE_ALERTS,
- NotificationSettingOptionValues.NEVER,
- user=user2,
- project=project,
- )
- event_all_users = self.store_event(
- data=self.make_event_data("foo.cbl"), project_id=project.id
- )
- self.assert_notify(event_all_users, [user.email])
- def test_notify_team(self):
- user = self.create_user(email="foo@example.com", is_active=True)
- user2 = self.create_user(email="baz@example.com", is_active=True)
- team = self.create_team(organization=self.organization, members=[user, user2])
- project = self.create_project(teams=[team])
- event = self.store_event(data=self.make_event_data("foo.py"), project_id=project.id)
- self.assert_notify(event, [user.email, user2.email], ActionTargetType.TEAM, str(team.id))
- def test_notify_user(self):
- user = self.create_user(email="foo@example.com", is_active=True)
- self.create_team(organization=self.organization, members=[user])
- event = self.store_event(data=self.make_event_data("foo.py"), project_id=self.project.id)
- self.assert_notify(event, [user.email], ActionTargetType.MEMBER, str(user.id))
- class MailAdapterGetDigestSubjectTest(BaseMailAdapterTest, TestCase):
- def test_get_digest_subject(self):
- assert (
- self.adapter.get_digest_subject(
- mock.Mock(qualified_short_id="BAR-1"),
- {mock.sentinel.group: 3},
- datetime(2016, 9, 19, 1, 2, 3, tzinfo=pytz.utc),
- )
- == "BAR-1 - 1 new alert since Sept. 19, 2016, 1:02 a.m. UTC"
- )
- class MailAdapterNotifyDigestTest(BaseMailAdapterTest, TestCase):
- @mock.patch.object(mail_adapter, "notify", side_effect=mail_adapter.notify, autospec=True)
- def test_notify_digest(self, notify):
- project = self.project
- event = self.store_event(
- data={"timestamp": iso_format(before_now(minutes=1)), "fingerprint": ["group-1"]},
- project_id=project.id,
- )
- event2 = self.store_event(
- data={"timestamp": iso_format(before_now(minutes=1)), "fingerprint": ["group-2"]},
- project_id=project.id,
- )
- rule = project.rule_set.all()[0]
- digest = build_digest(
- project, (event_to_record(event, (rule,)), event_to_record(event2, (rule,)))
- )
- with self.tasks():
- self.adapter.notify_digest(project, digest, ActionTargetType.ISSUE_OWNERS)
- assert notify.call_count == 0
- assert len(mail.outbox) == 1
- message = mail.outbox[0]
- assert "List-ID" in message.message()
- @mock.patch.object(mail_adapter, "notify", side_effect=mail_adapter.notify, autospec=True)
- @mock.patch.object(MessageBuilder, "send_async", autospec=True)
- def test_notify_digest_single_record(self, send_async, notify):
- event = self.store_event(data={}, project_id=self.project.id)
- rule = self.project.rule_set.all()[0]
- digest = build_digest(self.project, (event_to_record(event, (rule,)),))
- self.adapter.notify_digest(self.project, digest, ActionTargetType.ISSUE_OWNERS)
- assert send_async.call_count == 1
- assert notify.call_count == 1
- def test_notify_digest_subject_prefix(self):
- ProjectOption.objects.set_value(
- project=self.project, key="mail:subject_prefix", value="[Example prefix] "
- )
- event = self.store_event(
- data={"timestamp": iso_format(before_now(minutes=1)), "fingerprint": ["group-1"]},
- project_id=self.project.id,
- )
- event2 = self.store_event(
- data={"timestamp": iso_format(before_now(minutes=1)), "fingerprint": ["group-2"]},
- project_id=self.project.id,
- )
- rule = self.project.rule_set.all()[0]
- digest = build_digest(
- self.project, (event_to_record(event, (rule,)), event_to_record(event2, (rule,)))
- )
- with self.tasks():
- self.adapter.notify_digest(self.project, digest, ActionTargetType.ISSUE_OWNERS)
- assert len(mail.outbox) == 1
- msg = mail.outbox[0]
- assert msg.subject.startswith("[Example prefix]")
- @mock.patch.object(mail_adapter, "notify", side_effect=mail_adapter.notify, autospec=True)
- def test_notify_digest_user_does_not_exist(self, notify):
- """Test that in the event a rule has been created with an action to send to a user who
- no longer exists, we don't blow up when getting users in get_send_to
- """
- project = self.project
- event = self.store_event(
- data={"timestamp": iso_format(before_now(minutes=1)), "fingerprint": ["group-1"]},
- project_id=project.id,
- )
- event2 = self.store_event(
- data={"timestamp": iso_format(before_now(minutes=1)), "fingerprint": ["group-2"]},
- project_id=project.id,
- )
- action_data = {
- "id": "sentry.mail.actions.NotifyEmailAction",
- "targetType": "Member",
- "targetIdentifier": str(444),
- }
- rule = Rule.objects.create(
- project=self.project,
- label="a rule",
- data={
- "match": "all",
- "actions": [action_data],
- },
- )
- digest = build_digest(
- project, (event_to_record(event, (rule,)), event_to_record(event2, (rule,)))
- )
- with self.tasks():
- self.adapter.notify_digest(project, digest, ActionTargetType.MEMBER, 444)
- assert notify.call_count == 0
- assert len(mail.outbox) == 0
- class MailAdapterRuleNotifyTest(BaseMailAdapterTest, TestCase):
- def test_normal(self):
- event = self.store_event(data={}, project_id=self.project.id)
- rule = Rule.objects.create(project=self.project, label="my rule")
- futures = [RuleFuture(rule, {})]
- with mock.patch.object(self.adapter, "notify") as notify:
- self.adapter.rule_notify(event, futures, ActionTargetType.ISSUE_OWNERS)
- notify.call_count == 1
- @mock.patch("sentry.mail.adapter.digests")
- def test_digest(self, digests):
- digests.enabled.return_value = True
- event = self.store_event(data={}, project_id=self.project.id)
- rule = Rule.objects.create(project=self.project, label="my rule")
- futures = [RuleFuture(rule, {})]
- self.adapter.rule_notify(event, futures, ActionTargetType.ISSUE_OWNERS)
- digests.add.call_count == 1
- class MailAdapterShouldNotifyTest(BaseMailAdapterTest, TestCase):
- def test_should_notify(self):
- assert self.adapter.should_notify(ActionTargetType.ISSUE_OWNERS, self.group)
- assert self.adapter.should_notify(ActionTargetType.MEMBER, self.group)
- def test_should_not_notify_no_users(self):
- NotificationSetting.objects.update_settings(
- ExternalProviders.EMAIL,
- NotificationSettingTypes.ISSUE_ALERTS,
- NotificationSettingOptionValues.NEVER,
- user=self.user,
- project=self.project,
- )
- assert not self.adapter.should_notify(ActionTargetType.ISSUE_OWNERS, self.group)
- def test_should_always_notify_target_member(self):
- NotificationSetting.objects.update_settings(
- ExternalProviders.EMAIL,
- NotificationSettingTypes.ISSUE_ALERTS,
- NotificationSettingOptionValues.NEVER,
- user=self.user,
- project=self.project,
- )
- assert self.adapter.should_notify(ActionTargetType.MEMBER, self.group)
- class MailAdapterGetSendToOwnersTest(BaseMailAdapterTest, TestCase):
- def setUp(self):
- self.user = self.create_user(email="foo@example.com", is_active=True)
- self.user2 = self.create_user(email="baz@example.com", is_active=True)
- self.user3 = self.create_user(email="bar@example.com", is_active=True)
- self.organization = self.create_organization(owner=self.user)
- self.team = self.create_team(
- organization=self.organization, members=[self.user2, self.user3]
- )
- self.team2 = self.create_team(organization=self.organization, members=[self.user])
- self.project = self.create_project(name="Test", teams=[self.team, self.team2])
- self.group = self.create_group(
- first_seen=timezone.now(),
- last_seen=timezone.now(),
- project=self.project,
- message="hello world",
- logger="root",
- )
- ProjectOwnership.objects.create(
- project_id=self.project.id,
- schema=dump_schema(
- [
- grammar.Rule(Matcher("path", "*.py"), [Owner("team", self.team.slug)]),
- grammar.Rule(Matcher("path", "*.jx"), [Owner("user", self.user2.email)]),
- grammar.Rule(
- Matcher("path", "*.cbl"),
- [
- Owner("user", self.user.email),
- Owner("user", self.user2.email),
- Owner("user", self.user3.email),
- ],
- ),
- ]
- ),
- fallthrough=True,
- )
- def test_all_users(self):
- event_all_users = self.store_event(
- data=self.make_event_data("foo.cbl"), project_id=self.project.id
- )
- assert self.adapter.get_send_to_owners(event_all_users, self.project)[
- ExternalProviders.EMAIL
- ] == {
- self.user.id,
- self.user2.id,
- self.user3.id,
- }
- def test_team(self):
- event_team = self.store_event(
- data=self.make_event_data("foo.py"), project_id=self.project.id
- )
- assert self.adapter.get_send_to_owners(event_team, self.project)[
- ExternalProviders.EMAIL
- ] == {
- self.user2.id,
- self.user3.id,
- }
- def test_single_user(self):
- event_single_user = self.store_event(
- data=self.make_event_data("foo.jx"), project_id=self.project.id
- )
- assert self.adapter.get_send_to_owners(event_single_user, self.project)[
- ExternalProviders.EMAIL
- ] == {self.user2.id}
- def test_disable_alerts_user_scope(self):
- event_all_users = self.store_event(
- data=self.make_event_data("foo.cbl"), project_id=self.project.id
- )
- NotificationSetting.objects.update_settings(
- ExternalProviders.EMAIL,
- NotificationSettingTypes.ISSUE_ALERTS,
- NotificationSettingOptionValues.NEVER,
- user=self.user2,
- )
- assert self.user2.id not in self.adapter.get_send_to_owners(event_all_users, self.project)
- def test_disable_alerts_project_scope(self):
- event_all_users = self.store_event(
- data=self.make_event_data("foo.cbl"), project_id=self.project.id
- )
- NotificationSetting.objects.update_settings(
- ExternalProviders.EMAIL,
- NotificationSettingTypes.ISSUE_ALERTS,
- NotificationSettingOptionValues.NEVER,
- user=self.user2,
- project=self.project,
- )
- assert self.user2.id not in self.adapter.get_send_to_owners(event_all_users, self.project)
- def test_disable_alerts_multiple_scopes(self):
- event_all_users = self.store_event(
- data=self.make_event_data("foo.cbl"), project_id=self.project.id
- )
- # Project-independent setting.
- NotificationSetting.objects.update_settings(
- ExternalProviders.EMAIL,
- NotificationSettingTypes.ISSUE_ALERTS,
- NotificationSettingOptionValues.ALWAYS,
- user=self.user2,
- )
- # Per-project setting.
- NotificationSetting.objects.update_settings(
- ExternalProviders.EMAIL,
- NotificationSettingTypes.ISSUE_ALERTS,
- NotificationSettingOptionValues.NEVER,
- user=self.user2,
- project=self.project,
- )
- assert self.user2.id not in self.adapter.get_send_to_owners(event_all_users, self.project)
- class MailAdapterGetSendToTeamTest(BaseMailAdapterTest, TestCase):
- def test_send_to_team(self):
- assert {self.user.id} == self.adapter.get_send_to_team(self.project, str(self.team.id))
- def test_send_disabled(self):
- NotificationSetting.objects.update_settings(
- ExternalProviders.EMAIL,
- NotificationSettingTypes.ISSUE_ALERTS,
- NotificationSettingOptionValues.NEVER,
- user=self.user,
- project=self.project,
- )
- assert set() == self.adapter.get_send_to_team(self.project, str(self.team.id))
- def test_invalid_team(self):
- assert set() == self.adapter.get_send_to_team(self.project, "900001")
- def test_other_project_team(self):
- user_2 = self.create_user()
- team_2 = self.create_team(self.organization, members=[user_2])
- project_2 = self.create_project(organization=self.organization, teams=[team_2])
- assert {user_2.id} == self.adapter.get_send_to_team(project_2, str(team_2.id))
- assert set() == self.adapter.get_send_to_team(self.project, str(team_2.id))
- def test_other_org_team(self):
- org_2 = self.create_organization()
- user_2 = self.create_user()
- team_2 = self.create_team(org_2, members=[user_2])
- project_2 = self.create_project(organization=org_2, teams=[team_2])
- assert {user_2.id} == self.adapter.get_send_to_team(project_2, str(team_2.id))
- assert set() == self.adapter.get_send_to_team(self.project, str(team_2.id))
- class MailAdapterGetSendToMemberTest(BaseMailAdapterTest, TestCase):
- def test_send_to_user(self):
- assert {self.user.id} == self.adapter.get_send_to_member(self.project, str(self.user.id))
- def test_send_disabled_still_sends(self):
- NotificationSetting.objects.update_settings(
- ExternalProviders.EMAIL,
- NotificationSettingTypes.ISSUE_ALERTS,
- NotificationSettingOptionValues.NEVER,
- user=self.user,
- project=self.project,
- )
- assert {self.user.id} == self.adapter.get_send_to_member(self.project, str(self.user.id))
- def test_invalid_user(self):
- assert set() == self.adapter.get_send_to_member(self.project, "900001")
- def test_other_org_user(self):
- org_2 = self.create_organization()
- user_2 = self.create_user()
- team_2 = self.create_team(org_2, members=[user_2])
- team_3 = self.create_team(org_2, members=[user_2])
- project_2 = self.create_project(organization=org_2, teams=[team_2, team_3])
- assert {user_2.id} == self.adapter.get_send_to_member(project_2, str(user_2.id))
- assert set() == self.adapter.get_send_to_member(self.project, str(user_2.id))
- def test_no_project_access(self):
- org_2 = self.create_organization()
- user_2 = self.create_user()
- team_2 = self.create_team(org_2, members=[user_2])
- user_3 = self.create_user()
- self.create_team(org_2, members=[user_3])
- project_2 = self.create_project(organization=org_2, teams=[team_2])
- assert {user_2.id} == self.adapter.get_send_to_member(project_2, str(user_2.id))
- assert set() == self.adapter.get_send_to_member(self.project, str(user_3.id))
- class MailAdapterNotifyAboutActivityTest(BaseMailAdapterTest, TestCase):
- def test_assignment(self):
- NotificationSetting.objects.update_settings(
- ExternalProviders.EMAIL,
- NotificationSettingTypes.WORKFLOW,
- NotificationSettingOptionValues.ALWAYS,
- user=self.user,
- )
- activity = Activity.objects.create(
- project=self.project,
- group=self.group,
- type=Activity.ASSIGNED,
- user=self.create_user("foo@example.com"),
- data={"assignee": str(self.user.id), "assigneeType": "user"},
- )
- with self.tasks():
- self.adapter.notify_about_activity(activity)
- assert len(mail.outbox) == 1
- msg = mail.outbox[0]
- assert msg.subject == "Re: [Sentry] BAR-1 - こんにちは"
- assert msg.to == [self.user.email]
- def test_assignment_team(self):
- NotificationSetting.objects.update_settings(
- ExternalProviders.EMAIL,
- NotificationSettingTypes.WORKFLOW,
- NotificationSettingOptionValues.ALWAYS,
- user=self.user,
- )
- activity = Activity.objects.create(
- project=self.project,
- group=self.group,
- type=Activity.ASSIGNED,
- user=self.create_user("foo@example.com"),
- data={"assignee": str(self.project.teams.first().id), "assigneeType": "team"},
- )
- with self.tasks():
- self.adapter.notify_about_activity(activity)
- assert len(mail.outbox) == 1
- msg = mail.outbox[0]
- assert msg.subject == "Re: [Sentry] BAR-1 - こんにちは"
- assert msg.to == [self.user.email]
- def test_note(self):
- user_foo = self.create_user("foo@example.com")
- NotificationSetting.objects.update_settings(
- ExternalProviders.EMAIL,
- NotificationSettingTypes.WORKFLOW,
- NotificationSettingOptionValues.ALWAYS,
- user=self.user,
- )
- activity = Activity.objects.create(
- project=self.project,
- group=self.group,
- type=Activity.NOTE,
- user=user_foo,
- data={"text": "sup guise"},
- )
- self.project.teams.first().organization.member_set.create(user=user_foo)
- with self.tasks():
- self.adapter.notify_about_activity(activity)
- assert len(mail.outbox) >= 1
- msg = mail.outbox[-1]
- assert msg.subject == "Re: [Sentry] BAR-1 - こんにちは"
- assert msg.to == [self.user.email]
- class MailAdapterHandleSignalTest(BaseMailAdapterTest, TestCase):
- def create_report(self):
- user_foo = self.create_user("foo@example.com")
- self.project.teams.first().organization.member_set.create(user=user_foo)
- return UserReport.objects.create(
- project_id=self.project.id,
- group_id=self.group.id,
- name="Homer Simpson",
- email="homer.simpson@example.com",
- )
- def test_user_feedback(self):
- report = self.create_report()
- NotificationSetting.objects.update_settings(
- ExternalProviders.EMAIL,
- NotificationSettingTypes.WORKFLOW,
- NotificationSettingOptionValues.ALWAYS,
- user=self.user,
- )
- with self.tasks():
- self.adapter.handle_signal(
- name="user-reports.created",
- project=self.project,
- payload={
- "report": serialize(report, AnonymousUser(), UserReportWithGroupSerializer())
- },
- )
- assert len(mail.outbox) == 1
- msg = mail.outbox[0]
- # email includes issue metadata
- assert "group-header" in msg.alternatives[0][0]
- assert "enhanced privacy" not in msg.body
- assert (
- msg.subject
- == f"[Sentry] {self.group.qualified_short_id} - New Feedback from Homer Simpson"
- )
- assert msg.to == [self.user.email]
- def test_user_feedback__enhanced_privacy(self):
- self.organization.update(flags=F("flags").bitor(Organization.flags.enhanced_privacy))
- assert self.organization.flags.enhanced_privacy.is_set is True
- NotificationSetting.objects.update_settings(
- ExternalProviders.EMAIL,
- NotificationSettingTypes.WORKFLOW,
- NotificationSettingOptionValues.ALWAYS,
- user=self.user,
- )
- report = self.create_report()
- with self.tasks():
- self.adapter.handle_signal(
- name="user-reports.created",
- project=self.project,
- payload={
- "report": serialize(report, AnonymousUser(), UserReportWithGroupSerializer())
- },
- )
- assert len(mail.outbox) == 1
- msg = mail.outbox[0]
- # email does not include issue metadata
- assert "group-header" not in msg.alternatives[0][0]
- assert "enhanced privacy" in msg.body
- assert (
- msg.subject
- == f"[Sentry] {self.group.qualified_short_id} - New Feedback from Homer Simpson"
- )
- assert msg.to == [self.user.email]
|