oncall-engine/engine/apps/slack/tests/test_slack_renderer.py
Vadim Stepanov d1373b58d2
Fix orphaned messages in Slack (#2023)
# What this PR does
Reworks Slack handlers for buttons and select menus for AG Slack
messages.

<img width="602" alt="Screenshot 2023-05-31 at 19 34 05"
src="https://github.com/grafana/oncall/assets/20116910/857bf096-7bdd-427b-94b6-15aad873a8ac">


## Current implementation

- It's possible to end up with orphaned Slack messages that are posted
to Slack but have no `SlackMessage` instance in the DB. For such
messages, clicking buttons will result in an exception and HTTP 500. See
private repo
[issue](https://github.com/grafana/oncall-private/issues/1841) for more
info.
- Bug in authorization system, which effectively bypasses any permission
checks. For example, it's possible to resolve an alert group while being
a Viewer.
- No tests covering most buttons.

## Changes in this PR

- Make the system more robust, don't use `SlackMessage` model to figure
out the alert group being interacted on, instead embed `alert_group_pk`
to every button and use it when receiving interaction requests from
Slack.
- Existing orphaned Slack messages will be repaired. Clicking buttons
under orphaned messages will work (and missing `SlackMessage` instance
will be created on interaction). This is possible because some buttons
already have `alert_group_pk` embedded, and it's possible to get this
data on button clicks (even if the clicked button itself doesn't have
`alert_group_pk` embedded).
- Fix authorization. Show warning window when unauthorized:
<img width="511" alt="Screenshot 2023-05-31 at 19 40 02"
src="https://github.com/grafana/oncall/assets/20116910/5abeeaa7-1b61-4a47-b3af-0e21d5cd1907">

- Added tests for all the buttons under AG message. Add tests checking
authorization, actual execution of scenario steps, orphan message
repairing, backward compatibility, etc. Also add tests on
`AlertGroupSlackRenderer` checking that correct data is embedded into
buttons.
- Cosmetic changes such as renaming `incident`  to `Alert Group`.

## Which issue(s) this PR fixes
Related to https://github.com/grafana/oncall-private/issues/1841

## Checklist

- [x] Unit, integration, and e2e (if applicable) tests updated
- [x] Documentation added (or `pr:no public docs` PR label added if not
required)
- [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not
required)
2023-06-01 10:21:30 +00:00

213 lines
8.9 KiB
Python

import json
import pytest
from apps.alerts.incident_appearance.renderers.slack_renderer import AlertGroupSlackRenderer
from apps.alerts.models import AlertGroup
@pytest.mark.django_db
def test_slack_renderer_acknowledge_button(make_organization, make_alert_receive_channel, make_alert_group, make_alert):
organization = make_organization()
alert_receive_channel = make_alert_receive_channel(organization)
alert_group = make_alert_group(alert_receive_channel)
make_alert(alert_group=alert_group, raw_request_data={})
elements = AlertGroupSlackRenderer(alert_group).render_alert_group_attachments()[0]["blocks"][0]["elements"]
button = elements[0]
assert button["text"]["text"] == "Acknowledge"
assert json.loads(button["value"]) == {"organization_id": organization.pk, "alert_group_pk": alert_group.pk}
@pytest.mark.django_db
def test_slack_renderer_unacknowledge_button(
make_organization, make_alert_receive_channel, make_alert_group, make_alert
):
organization = make_organization()
alert_receive_channel = make_alert_receive_channel(organization)
alert_group = make_alert_group(alert_receive_channel, acknowledged=True)
make_alert(alert_group=alert_group, raw_request_data={})
elements = AlertGroupSlackRenderer(alert_group).render_alert_group_attachments()[0]["blocks"][0]["elements"]
button = elements[0]
assert button["text"]["text"] == "Unacknowledge"
assert json.loads(button["value"]) == {"organization_id": organization.pk, "alert_group_pk": alert_group.pk}
@pytest.mark.django_db
def test_slack_renderer_resolve_button(make_organization, make_alert_receive_channel, make_alert_group, make_alert):
organization = make_organization()
alert_receive_channel = make_alert_receive_channel(organization)
alert_group = make_alert_group(alert_receive_channel)
make_alert(alert_group=alert_group, raw_request_data={})
elements = AlertGroupSlackRenderer(alert_group).render_alert_group_attachments()[0]["blocks"][0]["elements"]
button = elements[1]
assert button["text"]["text"] == "Resolve"
assert json.loads(button["value"]) == {"organization_id": organization.pk, "alert_group_pk": alert_group.pk}
@pytest.mark.django_db
def test_slack_renderer_unresolve_button(make_organization, make_alert_receive_channel, make_alert_group, make_alert):
organization = make_organization()
alert_receive_channel = make_alert_receive_channel(organization)
alert_group = make_alert_group(alert_receive_channel, resolved=True)
make_alert(alert_group=alert_group, raw_request_data={})
elements = AlertGroupSlackRenderer(alert_group).render_alert_group_attachments()[0]["blocks"][0]["elements"]
button = elements[0]
assert button["text"]["text"] == "Unresolve"
assert json.loads(button["value"]) == {"organization_id": organization.pk, "alert_group_pk": alert_group.pk}
@pytest.mark.django_db
def test_slack_renderer_invite_action(
make_organization, make_user, make_alert_receive_channel, make_alert_group, make_alert
):
organization = make_organization()
user = make_user(organization=organization)
alert_receive_channel = make_alert_receive_channel(organization)
alert_group = make_alert_group(alert_receive_channel)
make_alert(alert_group=alert_group, raw_request_data={})
elements = AlertGroupSlackRenderer(alert_group).render_alert_group_attachments()[0]["blocks"][0]["elements"]
ack_button = elements[2]
assert ack_button["placeholder"]["text"] == "Invite..."
# Check only user_id is passed. Otherwise, if there are a lot of users, the payload could be unnecessarily large.
assert json.loads(ack_button["options"][0]["value"]) == {"user_id": user.pk}
@pytest.mark.django_db
def test_slack_renderer_stop_invite_button(
make_organization, make_user, make_alert_receive_channel, make_alert_group, make_alert, make_invitation
):
organization = make_organization()
user = make_user(organization=organization)
alert_receive_channel = make_alert_receive_channel(organization)
alert_group = make_alert_group(alert_receive_channel)
make_alert(alert_group=alert_group, raw_request_data={})
invitation = make_invitation(alert_group, user, user)
action = AlertGroupSlackRenderer(alert_group).render_alert_group_attachments()[1]["actions"][0]
assert action["text"] == f"Stop inviting {user.username}"
assert json.loads(action["value"]) == {
"organization_id": organization.pk,
"alert_group_pk": alert_group.pk,
"invitation_id": invitation.pk,
}
@pytest.mark.django_db
def test_slack_renderer_silence_button(make_organization, make_alert_receive_channel, make_alert_group, make_alert):
organization = make_organization()
alert_receive_channel = make_alert_receive_channel(organization)
alert_group = make_alert_group(alert_receive_channel)
make_alert(alert_group=alert_group, raw_request_data={})
elements = AlertGroupSlackRenderer(alert_group).render_alert_group_attachments()[0]["blocks"][0]["elements"]
button = elements[3]
assert button["placeholder"]["text"] == "Silence"
values = [json.loads(option["value"]) for option in button["options"]]
assert values == [
{"organization_id": organization.pk, "alert_group_pk": alert_group.pk, "delay": delay}
for delay, _ in AlertGroup.SILENCE_DELAY_OPTIONS
]
@pytest.mark.django_db
def test_slack_renderer_unsilence_button(make_organization, make_alert_receive_channel, make_alert_group, make_alert):
organization = make_organization()
alert_receive_channel = make_alert_receive_channel(organization)
alert_group = make_alert_group(alert_receive_channel, silenced=True)
make_alert(alert_group=alert_group, raw_request_data={})
elements = AlertGroupSlackRenderer(alert_group).render_alert_group_attachments()[0]["blocks"][0]["elements"]
button = elements[3]
assert button["text"]["text"] == "Unsilence"
assert json.loads(button["value"]) == {
"organization_id": organization.pk,
"alert_group_pk": alert_group.pk,
}
@pytest.mark.django_db
def test_slack_renderer_attach_button(make_organization, make_alert_receive_channel, make_alert_group, make_alert):
organization = make_organization()
alert_receive_channel = make_alert_receive_channel(organization)
alert_group = make_alert_group(alert_receive_channel, silenced=True)
make_alert(alert_group=alert_group, raw_request_data={})
elements = AlertGroupSlackRenderer(alert_group).render_alert_group_attachments()[0]["blocks"][0]["elements"]
button = elements[4]
assert button["text"]["text"] == "Attach to ..."
assert json.loads(button["value"]) == {
"organization_id": organization.pk,
"alert_group_pk": alert_group.pk,
}
@pytest.mark.django_db
def test_slack_renderer_unattach_button(make_organization, make_alert_receive_channel, make_alert_group, make_alert):
organization = make_organization()
alert_receive_channel = make_alert_receive_channel(organization)
root_alert_group = make_alert_group(alert_receive_channel)
make_alert(alert_group=root_alert_group, raw_request_data={})
alert_group = make_alert_group(alert_receive_channel, root_alert_group=root_alert_group)
make_alert(alert_group=alert_group, raw_request_data={})
action = AlertGroupSlackRenderer(alert_group).render_alert_group_attachments()[0]["actions"][0]
assert action["text"] == "Unattach"
assert json.loads(action["value"]) == {
"organization_id": organization.pk,
"alert_group_pk": alert_group.pk,
}
@pytest.mark.django_db
def test_slack_renderer_format_alert_button(
make_organization, make_alert_receive_channel, make_alert_group, make_alert
):
organization = make_organization()
alert_receive_channel = make_alert_receive_channel(organization)
alert_group = make_alert_group(alert_receive_channel)
make_alert(alert_group=alert_group, raw_request_data={})
elements = AlertGroupSlackRenderer(alert_group).render_alert_group_attachments()[0]["blocks"][0]["elements"]
button = elements[5]
assert button["text"]["text"] == ":mag: Format Alert"
assert json.loads(button["value"]) == {"organization_id": organization.pk, "alert_group_pk": alert_group.pk}
@pytest.mark.django_db
def test_slack_renderer_resolution_notes_button(
make_organization, make_alert_receive_channel, make_alert_group, make_alert
):
organization = make_organization()
alert_receive_channel = make_alert_receive_channel(organization)
alert_group = make_alert_group(alert_receive_channel)
make_alert(alert_group=alert_group, raw_request_data={})
elements = AlertGroupSlackRenderer(alert_group).render_alert_group_attachments()[0]["blocks"][0]["elements"]
button = elements[6]
assert button["text"]["text"] == "Add Resolution notes"
assert json.loads(button["value"]) == {
"organization_id": organization.pk,
"alert_group_pk": alert_group.pk,
"resolution_note_window_action": "edit",
}