def test_new_processing_issue(self, mock_func): """ Test that a Slack message is sent with the expected payload when an issue is held back in reprocessing """ notification = NewProcessingIssuesActivityNotification( Activity( project=self.project, user=self.user, type=ActivityType.NEW_PROCESSING_ISSUES, data={ "issues": get_issues_data(), "reprocessing_active": True, }, )) with self.tasks(): notification.send() attachment, text = get_attachment() assert ( text == f"Processing issues on <{self.project.slug}|http://testserver/settings/{self.organization.slug}/projects/{self.project.slug}/processing-issues/" ) assert ( attachment["text"] == f"Some events failed to process in your project {self.project.slug}" ) assert ( attachment["footer"] == f"{self.project.slug} | <http://testserver/settings/account/notifications/workflow/?referrer=new-processing-issues-activity-slack-user|Notification Settings>" )
def _get_activity(self, request, group, num): activity_items = set() activity = [] activity_qs = (Activity.objects.filter( group=group).order_by("-datetime").select_related("user")) # we select excess so we can filter dupes for item in activity_qs[:num * 2]: sig = (item.type, item.ident, item.user_id) # TODO: we could just generate a signature (hash(text)) for notes # so there's no special casing if item.type == Activity.NOTE: activity.append(item) elif sig not in activity_items: activity_items.add(sig) activity.append(item) activity.append( Activity( id=0, project=group.project, group=group, type=Activity.FIRST_SEEN, datetime=group.first_seen, )) return activity[:num]
def test_no_committers(self): release, deploy = self.another_release("b") email = ReleaseActivityEmail( Activity( project=self.project, user=self.user1, type=Activity.RELEASE, data={"version": release.version, "deploy_id": deploy.id}, ) ) # only user3 is included because they opted into all deploy emails assert len(email.get_participants()) == 1 assert email.get_participants() == {self.user3: GroupSubscriptionReason.deploy_setting} context = email.get_context() assert context["environment"] == "production" assert context["repos"] == [] user_context = email.get_user_context(self.user1) # make sure this only includes projects user has access to assert len(user_context["projects"]) == 1 assert user_context["projects"][0][0] == self.project with self.tasks(): email.send() assert len(mail.outbox) == 1 sent_email_addresses = {msg.to[0] for msg in mail.outbox} assert sent_email_addresses == {self.user3.email}
def test_simple(self): email = ReleaseActivityEmail( Activity( project=self.project, user=self.user, type=Activity.RELEASE, data={ 'version': self.release.version, 'deploy_id': self.deploy.id, }, )) with self.feature('workflow:release-emails'): assert email.get_participants() == { self.user: GroupSubscriptionReason.committed, } context = email.get_context() assert context['environment'] == 'production' assert context['repos'][0]['commits'] == [ (self.commit, self.user), (self.commit2, self.user2), ] user_context = email.get_user_context(self.user) # make sure this only includes projects user has access to assert len(user_context['projects']) == 1 assert user_context['projects'][0][0] == self.project with self.tasks(): email.send() assert len(mail.outbox) == 1 msg = mail.outbox[-1] assert msg.to == [self.user.email]
def test_resolved(self, mock_func): """ Test that a Slack message is sent with the expected payload when an issue is resolved """ notification = ResolvedActivityNotification( Activity( project=self.project, group=self.group, user=self.user, type=ActivityType.SET_RESOLVED, data={"assignee": ""}, ) ) with self.tasks(): notification.send() attachment, text = get_attachment() assert ( text == f"{self.name} marked <http://testserver/organizations/{self.organization.slug}/issues/{self.group.id}/?referrer=activity_notification|{self.short_id}> as resolved" ) assert ( attachment["footer"] == f"{self.project.slug} | <http://testserver/settings/account/notifications/workflow/?referrer=resolved-activity-slack-user|Notification Settings>" )
def test_get_participants(self): organization = self.create_organization(owner=self.create_user()) team = self.create_team(organization=organization) project = self.create_project(organization=organization, team=team) group = self.create_group(project=project) actor = self.create_user() other = self.create_user() for user in (actor, other): self.create_member([team], user=user, organization=organization) GroupSubscription.objects.subscribe(group, user) email = ActivityEmail( Activity( project=group.project, group=group, user=actor, ) ) assert email.get_participants() == set([other]) UserOption.objects.set_value( user=actor, project=None, key='self_notifications', value='1' ) assert email.get_participants() == set([actor, other])
def test_resolved_in_release(self, mock_func): """ Test that a Slack message is sent with the expected payload when an issue is resolved in a release """ notification = ResolvedInReleaseActivityNotification( Activity( project=self.project, group=self.group, user=self.user, type=ActivityType.SET_RESOLVED_IN_RELEASE, data={"version": "meow"}, )) with self.tasks(): notification.send() attachment = get_attachment() release_name = notification.activity.data["version"] assert attachment["title"] == "Resolved Issue" assert ( attachment["text"] == f"{self.name} marked {self.short_id} as resolved in {release_name}" ) assert ( attachment["footer"] == f"<http://testserver/organizations/{self.organization.slug}/issues/{self.group.id}/?referrer=ResolvedInReleaseActivitySlack|{self.short_id}> via <http://testserver/settings/account/notifications/?referrer=ResolvedInReleaseActivitySlack|Notification Settings>" )
def test_note(self, mock_func): """ Test that a Slack message is sent with the expected payload when a comment is made on an issue """ notification = NoteActivityNotification( Activity( project=self.project, group=self.group, user=self.user, type=ActivityType.NOTE, data={ "text": "text", "mentions": [] }, )) with self.tasks(): notification.send() attachment = get_attachment() assert attachment["title"] == f"New comment by {self.name}" assert attachment["text"] == notification.activity.data["text"] assert ( attachment["footer"] == f"<http://testserver/organizations/{self.organization.slug}/issues/{self.group.id}/?referrer=NoteActivitySlack|{self.short_id}> via <http://testserver/settings/account/notifications/?referrer=NoteActivitySlack|Notification Settings>" )
def get(self, request): org = Organization(id=1, slug="organization", name="My Company") project = Project(id=1, organization=org, slug="project", name="My Project") group = next(make_group_generator(get_random(request), project)) data = dict(load_data("python")) data["message"] = group.message data.pop("logentry", None) event_manager = EventManager(data) event_manager.normalize() data = event_manager.get_data() event_type = event_manager.get_event_type() event = eventstore.create_event( event_id="a" * 32, group_id=group.id, project_id=project.id, data=data.data ) group.message = event.search_message group.data = {"type": event_type.key, "metadata": event_type.get_metadata(data)} activity = Activity(group=group, project=event.project, **self.get_activity(request, event)) return render_to_response( "sentry/debug/mail/preview.html", context={ "preview": ActivityMailPreview(request, activity), "format": request.GET.get("format"), }, )
def test_uses_default(self): user6 = self.create_user() self.create_member(user=user6, organization=self.org, teams=[self.team]) UserOption.objects.set_value(user=user6, organization=None, key="deploy-emails", value=UserOptionValue.all_deploys) release = Release.objects.create( version="b" * 40, organization_id=self.project.organization_id, date_released=timezone.now(), ) release.add_project(self.project) release.add_project(self.project2) deploy = Deploy.objects.create(release=release, organization_id=self.org.id, environment_id=self.environment.id) email = ReleaseActivityEmail( Activity( project=self.project, user=self.user, type=Activity.RELEASE, data={ "version": release.version, "deploy_id": deploy.id }, )) # user3 and user 6 are included because they oped into all deploy emails # (one on an org level, one as their default) assert len(email.get_participants()) == 2 assert email.get_participants() == { user6: GroupSubscriptionReason.deploy_setting, self.user3: GroupSubscriptionReason.deploy_setting, } context = email.get_context() assert context["environment"] == "production" assert context["repos"] == [] user_context = email.get_user_context(user6) # make sure this only includes projects user has access to assert len(user_context["projects"]) == 1 assert user_context["projects"][0][0] == self.project with self.tasks(): email.send() assert len(mail.outbox) == 2 sent_email_addresses = {msg.to[0] for msg in mail.outbox} assert sent_email_addresses == {self.user3.email, user6.email}
def test_get_participants_without_actor(self): group, (user, ) = self.get_fixture_data(1) email = ActivityEmail(Activity( project=group.project, group=group, )) assert set(email.get_participants()) == set([user])
def test_get_participants(self): group, (actor, other) = self.get_fixture_data(2) email = ActivityEmail(Activity(project=group.project, group=group, user=actor)) assert set(email.get_participants()) == set([other]) UserOption.objects.set_value(user=actor, key="self_notifications", value="1") assert set(email.get_participants()) == set([actor, other])
def test_new_processing_issue(self, mock_func): """ Test that a Slack message is sent with the expected payload when an issue is held back in reprocessing """ data = [ { "data": { "image_arch": "arm64", "image_path": "/var/containers/Bundle/Application/FB14D416-DE4E-4224-9789-6B88E9C42601/CrashProbeiOS.app/CrashProbeiOS", "image_uuid": "a2df1794-e0c7-371c-baa4-93eac340a78a", }, "object": "dsym:a2df1794-e0c7-371c-baa4-93eac340a78a", "scope": "native", "type": "native_missing_dsym", }, { "data": { "image_arch": "arm64", "image_path": "/var/containers/Bundle/Application/FB14D416-DE4E-4224-9789-6B88E9C42601/CrashProbeiOS.app/libCrashProbeiOS", "image_uuid": "12dc1b4c-a01b-463f-ae88-5cf0c31ae680", }, "object": "dsym:12dc1b4c-a01b-463f-ae88-5cf0c31ae680", "scope": "native", "type": "native_bad_dsym", }, ] notification = NewProcessingIssuesActivityNotification( Activity( project=self.project, group=self.group, user=self.user, type=ActivityType.NEW_PROCESSING_ISSUES, data={ "issues": data, "reprocessing_active": True, }, )) with self.tasks(): notification.send() attachment, text = get_attachment() assert ( text == f"Processing issues on <{self.project.slug}|http://testserver/settings/{self.organization.slug}/projects/{self.project.slug}/processing-issues/" ) assert ( attachment["text"] == f"Some events failed to process in your project {self.project.slug}" ) assert ( attachment["footer"] == f"{self.project.slug} | <http://testserver/settings/account/notifications/workflow/?referrer=NewProcessingIssuesActivitySlack|Notification Settings>" )
def new_note(request): org = Organization( id=1, slug='example', name='Example', ) team = Team( id=1, slug='example', name='Example', organization=org, ) project = Project( id=1, slug='example', name='Example', team=team, organization=org, ) group = Group( id=1, project=project, message='This is an example event.', ) event = Event( id=1, project=project, group=group, message=group.message, data=load_data('python'), ) note = Activity( group=event.group, event=event, project=event.project, type=Activity.NOTE, user=request.user, data={'text': 'This is an example note!'}, ) preview = MailPreview( html_template='sentry/emails/activity/note.html', text_template='sentry/emails/activity/note.txt', context={ 'data': note.data, 'author': note.user, 'date': note.datetime, 'group': group, 'link': group.get_absolute_url(), }, ) return render_to_response('sentry/debug/mail/preview.html', { 'preview': preview, })
def get(self, request): org = Organization( id=1, slug='organization', name='My Company', ) project = Project( id=1, organization=org, slug='project', name='My Project', ) group = next(make_group_generator( get_random(request), project, ), ) data = dict(load_data('python')) data['message'] = group.message data.pop('logentry', None) event_manager = EventManager(data) event_manager.normalize() data = event_manager.get_data() event_type = event_manager.get_event_type() group.message = event_manager.get_search_message() group.data = { 'type': event_type.key, 'metadata': event_type.get_metadata(data), } event = Event(id=1, project=project, message=event_manager.get_search_message(), group=group, datetime=datetime(2016, 6, 13, 3, 8, 24, tzinfo=timezone.utc), data=event_manager.get_data()) activity = Activity(group=event.group, project=event.project, **self.get_activity(request, event)) return render_to_response( 'sentry/debug/mail/preview.html', { 'preview': ActivityMailPreview(request, activity), 'format': request.GET.get('format'), })
def test_multiple_identities(self, mock_func): """ Test that we notify a user with multiple Identities in each place """ integration2 = Integration.objects.create( provider="slack", name="Team B", external_id="TXXXXXXX2", metadata={ "access_token": "xoxp-xxxxxxxxx-xxxxxxxxxx-xxxxxxxxxxxx", "installation_type": "born_as_bot", }, ) integration2.add_organization(self.organization, self.user) idp2 = IdentityProvider.objects.create(type="slack", external_id="TXXXXXXX2", config={}) identity2 = Identity.objects.create( external_id="UXXXXXXX2", idp=idp2, user=self.user, status=IdentityStatus.VALID, scopes=[], ) # create a second response responses.add( method=responses.POST, url="https://slack.com/api/chat.postMessage", body='{"ok": true}', status=200, content_type="application/json", ) notification = AssignedActivityNotification( Activity( project=self.project, group=self.group, user=self.user, type=ActivityType.ASSIGNED, data={"assignee": self.user.id}, )) with self.tasks(): notification.send() assert len(responses.calls) >= 2 data = parse_qs(responses.calls[0].request.body) assert "channel" in data channel = data["channel"][0] assert channel == self.identity.external_id data = parse_qs(responses.calls[1].request.body) assert "channel" in data channel = data["channel"][0] assert channel == identity2.external_id
def assigned(request): org = Organization( id=1, slug='example', name='Example', ) team = Team( id=1, slug='example', name='Example', organization=org, ) project = Project( id=1, slug='example', name='Example', team=team, organization=org, ) group = Group( id=1, project=project, message='This is an example event.', ) event = Event( id=1, project=project, group=group, message=group.message, data=load_data('python'), ) assigned = Activity( group=event.group, project=event.project, type=Activity.ASSIGNED, user=request.user, data={ 'text': 'This is an example note!', 'assignee': '*****@*****.**' }, ) return MailPreview( html_template='sentry/emails/activity/assigned.html', text_template='sentry/emails/activity/assigned.txt', context={ 'data': assigned.data, 'author': assigned.user, 'date': assigned.datetime, 'group': group, 'link': group.get_absolute_url(), }, ).render()
def test_doesnt_generate_on_no_release(self): email = ReleaseActivityEmail( Activity( project=self.project, user=self.user, type=Activity.RELEASE, data={'version': 'a'}, )) assert email.release is None assert not email.should_email()
def test_does_not_generate_on_no_release(self): email = ReleaseActivityEmail( Activity( project=self.project, user=self.user1, type=Activity.RELEASE, data={"version": "a", "deploy_id": 5}, ) ) assert email.release is None assert not email.should_email()
def test_get_subject(self): group, (user, ) = self.get_fixture_data(1) email = ActivityEmail(Activity(project=group.project, group=group)) with mock.patch( "sentry.models.ProjectOption.objects.get_value") as get_value: get_value.side_effect = ( lambda project, key, default=None: "[Example prefix] " if key == "mail:subject_prefix" else default) assert email.get_subject_with_prefix().startswith( "[Example prefix] ")
def test_uses_default(self): user6 = self.create_user() self.create_member(user=user6, organization=self.org, teams=[self.team]) NotificationSetting.objects.update_settings( ExternalProviders.EMAIL, NotificationSettingTypes.DEPLOY, NotificationSettingOptionValues.ALWAYS, user=user6, ) release, deploy = self.another_release("b") email = ReleaseActivityNotification( Activity( project=self.project, user=self.user1, type=Activity.RELEASE, data={ "version": release.version, "deploy_id": deploy.id }, )) # user3 and user 6 are included because they oped into all deploy emails # (one on an org level, one as their default) participants = email.get_participants_with_group_subscription_reason()[ ExternalProviders.EMAIL] assert len(participants) == 2 assert participants == { user6: GroupSubscriptionReason.deploy_setting, self.user3: GroupSubscriptionReason.deploy_setting, } context = email.get_context() assert context["environment"] == "production" assert context["repos"] == [] user_context = email.get_recipient_context(user6, {}) # make sure this only includes projects user has access to assert len(user_context["projects"]) == 1 assert user_context["projects"][0][0] == self.project with self.tasks(): email.send() assert len(mail.outbox) == 2 sent_email_addresses = {msg.to[0] for msg in mail.outbox} assert sent_email_addresses == {self.user3.email, user6.email}
def setUp(self): super().setUp() self.email = NoteActivityNotification( Activity( project=self.project, group=self.group, user=self.user, type=ActivityType.NOTE, data={ "text": "text", "mentions": [] }, ))
def test_multiple_orgs(self, mock_func): """ Test that if a user is in 2 orgs with Slack and has an Identity linked in each, we're only going to notify them for the relevant org """ org2 = self.create_organization(owner=self.user) integration2 = Integration.objects.create( provider="slack", name="Team B", external_id="TXXXXXXX2", metadata={ "access_token": "xoxp-xxxxxxxxx-xxxxxxxxxx-xxxxxxxxxxxx", "installation_type": "born_as_bot", }, ) integration2.add_organization(org2, self.user) idp2 = IdentityProvider.objects.create(type="slack", external_id="TXXXXXXX2", config={}) Identity.objects.create( external_id="UXXXXXXX2", idp=idp2, user=self.user, status=IdentityStatus.VALID, scopes=[], ) # create a second response that won't actually be used, but here to make sure it's not a false positive responses.add( method=responses.POST, url="https://slack.com/api/chat.postMessage", body='{"ok": true}', status=200, content_type="application/json", ) notification = AssignedActivityNotification( Activity( project=self.project, group=self.group, user=self.user, type=ActivityType.ASSIGNED, data={"assignee": self.user.id}, )) with self.tasks(): notification.send() assert len(responses.calls) == 1 data = parse_qs(responses.calls[0].request.body) assert "channel" in data channel = data["channel"][0] assert channel == self.identity.external_id
def test_simple(self): email = ReleaseActivityNotification( Activity( project=self.project, user=self.user1, type=Activity.RELEASE, data={ "version": self.release.version, "deploy_id": self.deploy.id }, )) # user1 is included because they committed # user2 committed but isn't in a team associated with the project. # user3 is included because they oped into all deploy emails # user4 committed but isn't included because they opted out of all deploy emails # for that org -- also tests to make sure org overrides default preference # user5 committed with another email address and is still included. participants = email.get_participants_with_group_subscription_reason()[ ExternalProviders.EMAIL] assert len(participants) == 3 assert participants == { self.user1: GroupSubscriptionReason.committed, self.user3: GroupSubscriptionReason.deploy_setting, self.user5: GroupSubscriptionReason.committed, } context = email.get_context() assert context["environment"] == "production" assert context["repos"][0]["commits"] == [ (self.commit1, self.user1), (self.commit2, self.user2), (self.commit3, self.user4), (self.commit4, self.user5), ] user_context = email.get_recipient_context(self.user1, {}) # make sure this only includes projects user has access to assert len(user_context["projects"]) == 1 assert user_context["projects"][0][0] == self.project with self.tasks(): email.send() assert len(mail.outbox) == 3 sent_email_addresses = {msg.to[0] for msg in mail.outbox} assert sent_email_addresses == { self.user1.email, self.user3.email, self.user5.email }
def test_simple(self): email = ReleaseActivityEmail( Activity( project=self.project, user=self.user, type=Activity.RELEASE, data={ 'version': self.release.version, 'deploy_id': self.deploy.id, }, )) # user is included because they committed # user2 committed but isn't in a team associated with the project. # user3 is included because they oped into all deploy emails # user4 committed but isn't included because they opted out of all deploy emails # for that org -- also tests to make sure org overrides default preference # user5 committed with another email address and is still included. assert len(email.get_participants()) == 3 assert email.get_participants() == { self.user: GroupSubscriptionReason.committed, self.user3: GroupSubscriptionReason.deploy_setting, self.user5: GroupSubscriptionReason.committed, } context = email.get_context() assert context['environment'] == 'production' assert context['repos'][0]['commits'] == [ (self.commit, self.user), (self.commit2, self.user2), (self.commit3, self.user4), (self.commit4, self.user5), ] user_context = email.get_user_context(self.user) # make sure this only includes projects user has access to assert len(user_context['projects']) == 1 assert user_context['projects'][0][0] == self.project with self.tasks(): email.send() assert len(mail.outbox) == 3 sent_email_addresses = {msg.to[0] for msg in mail.outbox} assert sent_email_addresses == { self.user.email, self.user3.email, self.user5.email }
def test_no_committers(self): release = Release.objects.create( version='b' * 40, organization_id=self.project.organization_id, date_released=timezone.now(), ) release.add_project(self.project) release.add_project(self.project2) deploy = Deploy.objects.create( release=release, organization_id=self.org.id, environment_id=self.environment.id, ) email = ReleaseActivityEmail( Activity( project=self.project, user=self.user, type=Activity.RELEASE, data={ 'version': release.version, 'deploy_id': deploy.id, }, )) # only user3 is included because they oped into all deploy emails assert len(email.get_participants()) == 1 assert email.get_participants() == { self.user3: GroupSubscriptionReason.deploy_setting, } context = email.get_context() assert context['environment'] == 'production' assert context['repos'] == [] user_context = email.get_user_context(self.user) # make sure this only includes projects user has access to assert len(user_context['projects']) == 1 assert user_context['projects'][0][0] == self.project with self.tasks(): email.send() assert len(mail.outbox) == 1 sent_email_addresses = {msg.to[0] for msg in mail.outbox} assert sent_email_addresses == {self.user3.email}
def test_deploy(self, mock_func): """ Test that a Slack message is sent with the expected payload when a deploy happens """ release = Release.objects.create( version="meow" * 10, organization_id=self.project.organization_id, date_released=timezone.now(), ) project2 = self.create_project(name="battlesnake") release.add_project(self.project) release.add_project(project2) deploy = Deploy.objects.create( release=release, organization_id=self.organization.id, environment_id=self.environment.id, ) notification = ReleaseActivityNotification( Activity( project=self.project, user=self.user, type=Activity.RELEASE, data={ "version": release.version, "deploy_id": deploy.id }, )) with self.tasks(): notification.send() attachment, text = get_attachment() assert ( text == f"Release {release.version[:12]} deployed to {self.environment.name} for these projects" ) assert attachment["actions"][0]["text"] == self.project.slug assert ( attachment["actions"][0]["url"] == f"http://testserver/organizations/{self.organization.slug}/releases/{release.version}/?project={self.project.id}&unselectedSeries=Healthy/" ) assert attachment["actions"][1]["text"] == project2.slug assert ( attachment["actions"][1]["url"] == f"http://testserver/organizations/{self.organization.slug}/releases/{release.version}/?project={project2.id}&unselectedSeries=Healthy/" ) assert ( attachment["footer"] == f"{self.project.slug} | <http://testserver/settings/account/notifications/deploy/?referrer=ReleaseActivitySlack|Notification Settings>" )
def test_simple(self): email = ReleaseActivityEmail( Activity( project=self.project, user=self.user, type=Activity.RELEASE, data={ 'version': self.release.version, 'deploy_id': self.deploy.id, }, )) # user is included because they committed # user2 committed but isn't in a team associated with the project. # user3 is included because they oped into all deploy emails # user4 commited but isn't included because they opted out of all deploy emails assert len(email.get_participants()) == 2 assert email.get_participants() == { self.user: GroupSubscriptionReason.committed, self.user3: GroupSubscriptionReason.deploy_setting, } context = email.get_context() assert context['environment'] == 'production' assert context['repos'][0]['commits'] == [ (self.commit, self.user), (self.commit2, self.user2), (self.commit3, self.user4), ] user_context = email.get_user_context(self.user) # make sure this only includes projects user has access to assert len(user_context['projects']) == 1 assert user_context['projects'][0][0] == self.project with self.tasks(): email.send() assert len(mail.outbox) == 2 msg = mail.outbox[0] assert msg.to == [self.user.email] or msg.to == [self.user3.email] msg2 = mail.outbox[1] assert msg2.to == [self.user.email] or msg2.to == [self.user3.email] assert msg.to != msg2.to
def get(self, request): org = Organization( id=1, slug='organization', name='My Company', ) team = Team( id=1, slug='team', name='My Team', organization=org, ) project = Project( id=1, organization=org, team=team, slug='project', name='My Project', ) group = next( make_group_generator( get_random(request), project, ), ) event = Event( id=1, project=project, group=group, message=group.message, data=load_data('python'), datetime=datetime(2016, 6, 13, 3, 8, 24, tzinfo=timezone.utc), ) activity = Activity( group=event.group, project=event.project, **self.get_activity(request, event) ) return render_to_response('sentry/debug/mail/preview.html', { 'preview': ActivityMailPreview(activity), 'format': request.GET.get('format'), })
def test_assignment(self, mock_func): """ Test that a Slack message is sent with the expected payload when an issue is assigned """ notification = AssignedActivityNotification( Activity( project=self.project, group=self.group, user=self.user, type=ActivityType.ASSIGNED, data={"assignee": self.user.id}, )) with self.tasks(): notification.send() attachment, text = get_attachment() assert text == f"Issue assigned to {self.name} by themselves" assert attachment["title"] == self.group.title assert ( attachment["footer"] == f"{self.project.slug} | <http://testserver/settings/account/notifications/workflow/?referrer=AssignedActivitySlackUser|Notification Settings>" )