mirror of
https://github.com/Sea-Haven-Industries/apm-wo-analysis.git
synced 2026-09-30 04:13:13 +00:00
Add tests for the Slack interactions endpoint
This commit is contained in:
parent
817e1f6a74
commit
63b6f11bf6
1 changed files with 331 additions and 0 deletions
331
tests/test_interactions.py
Normal file
331
tests/test_interactions.py
Normal file
|
|
@ -0,0 +1,331 @@
|
|||
"""Tests for the Slack interactions Lambda endpoint.
|
||||
|
||||
Covers: signature verification, drill_category modal, drill_site modal,
|
||||
base64-encoded bodies, non-block_actions payloads, and missing-field no-ops.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import json
|
||||
from urllib.parse import urlencode
|
||||
|
||||
|
||||
def _make_event(
|
||||
payload_dict: dict,
|
||||
*,
|
||||
timestamp: str = "1234567890",
|
||||
signature: str = "v0=fakesig",
|
||||
base64_encoded: bool = False,
|
||||
) -> dict:
|
||||
"""Build a realistic API Gateway HTTP API event for Slack interactions."""
|
||||
raw_payload = urlencode({"payload": json.dumps(payload_dict)})
|
||||
body: str
|
||||
if base64_encoded:
|
||||
body = base64.b64encode(raw_payload.encode("utf-8")).decode("utf-8")
|
||||
else:
|
||||
body = raw_payload
|
||||
return {
|
||||
"headers": {
|
||||
"x-slack-request-timestamp": timestamp,
|
||||
"x-slack-signature": signature,
|
||||
"content-type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
"body": body,
|
||||
"isBase64Encoded": base64_encoded,
|
||||
}
|
||||
|
||||
|
||||
def _block_actions_payload(
|
||||
action_id: str,
|
||||
value: str,
|
||||
trigger_id: str = "trigger123",
|
||||
) -> dict:
|
||||
return {
|
||||
"type": "block_actions",
|
||||
"trigger_id": trigger_id,
|
||||
"actions": [
|
||||
{
|
||||
"action_id": action_id,
|
||||
"value": value,
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tests
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_invalid_signature_returns_401_and_no_views_open(monkeypatch):
|
||||
import interactions
|
||||
import slackio
|
||||
|
||||
monkeypatch.setattr(slackio, "verify_signature", lambda body, ts, sig: False)
|
||||
|
||||
views_open_calls = []
|
||||
fake_client = type(
|
||||
"C", (), {"views_open": lambda self, **kw: views_open_calls.append(kw)}
|
||||
)
|
||||
monkeypatch.setattr(slackio, "web_client", lambda: fake_client())
|
||||
|
||||
event = _make_event(
|
||||
_block_actions_payload(
|
||||
"drill_category:Report / Docs Needed", "Report / Docs Needed"
|
||||
)
|
||||
)
|
||||
resp = interactions.handler(event, None)
|
||||
|
||||
assert resp["statusCode"] == 401
|
||||
assert "invalid" in resp["body"].lower()
|
||||
assert views_open_calls == []
|
||||
|
||||
|
||||
def test_drill_category_opens_modal_with_matching_wos(monkeypatch):
|
||||
import interactions
|
||||
import slackio
|
||||
|
||||
monkeypatch.setattr(slackio, "verify_signature", lambda body, ts, sig: True)
|
||||
monkeypatch.setattr(
|
||||
slackio, "get_dashboard_url", lambda: "https://grafana.example.com"
|
||||
)
|
||||
|
||||
details = [
|
||||
{"wo_number": "WO-001", "category": "Report / Docs Needed", "site": "ABQ5"},
|
||||
{"wo_number": "WO-002", "category": "3rd Escalation", "site": "ACY9"},
|
||||
{"wo_number": "WO-003", "category": "Report / Docs Needed", "site": "ABQ5"},
|
||||
]
|
||||
monkeypatch.setattr(slackio, "read_meta_json", lambda dt, name: details)
|
||||
|
||||
views_open_calls = []
|
||||
fake_client = type(
|
||||
"C",
|
||||
(),
|
||||
{"views_open": lambda self, **kw: views_open_calls.append(kw)},
|
||||
)()
|
||||
monkeypatch.setattr(slackio, "web_client", lambda: fake_client)
|
||||
|
||||
event = _make_event(
|
||||
_block_actions_payload(
|
||||
"drill_category:Report / Docs Needed", "Report / Docs Needed"
|
||||
)
|
||||
)
|
||||
resp = interactions.handler(event, None)
|
||||
|
||||
assert resp["statusCode"] == 200
|
||||
assert len(views_open_calls) == 1
|
||||
call = views_open_calls[0]
|
||||
assert call["trigger_id"] == "trigger123"
|
||||
view = call["view"]
|
||||
# Title must reflect the count (2 matching WOs)
|
||||
assert "2" in view["title"]["text"]
|
||||
assert "Report / Docs Needed" in view["title"]["text"]
|
||||
|
||||
|
||||
def test_drill_site_filters_on_site_field(monkeypatch):
|
||||
import interactions
|
||||
import slackio
|
||||
|
||||
monkeypatch.setattr(slackio, "verify_signature", lambda body, ts, sig: True)
|
||||
monkeypatch.setattr(
|
||||
slackio, "get_dashboard_url", lambda: "https://grafana.example.com"
|
||||
)
|
||||
|
||||
details = [
|
||||
{"wo_number": "WO-001", "category": "Report / Docs Needed", "site": "ABQ5"},
|
||||
{"wo_number": "WO-002", "category": "3rd Escalation", "site": "ACY9"},
|
||||
{"wo_number": "WO-003", "category": "Awaiting Scheduling", "site": "ABQ5"},
|
||||
]
|
||||
monkeypatch.setattr(slackio, "read_meta_json", lambda dt, name: details)
|
||||
|
||||
views_open_calls = []
|
||||
fake_client = type(
|
||||
"C",
|
||||
(),
|
||||
{"views_open": lambda self, **kw: views_open_calls.append(kw)},
|
||||
)()
|
||||
monkeypatch.setattr(slackio, "web_client", lambda: fake_client)
|
||||
|
||||
event = _make_event(_block_actions_payload("drill_site:ABQ5", "ABQ5"))
|
||||
resp = interactions.handler(event, None)
|
||||
|
||||
assert resp["statusCode"] == 200
|
||||
assert len(views_open_calls) == 1
|
||||
view = views_open_calls[0]["view"]
|
||||
# 2 WOs match site ABQ5
|
||||
assert "2" in view["title"]["text"]
|
||||
assert "ABQ5" in view["title"]["text"]
|
||||
|
||||
|
||||
def test_base64_encoded_body_is_decoded(monkeypatch):
|
||||
import interactions
|
||||
import slackio
|
||||
|
||||
monkeypatch.setattr(slackio, "verify_signature", lambda body, ts, sig: True)
|
||||
monkeypatch.setattr(
|
||||
slackio, "get_dashboard_url", lambda: "https://grafana.example.com"
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
slackio,
|
||||
"read_meta_json",
|
||||
lambda dt, name: [
|
||||
{"wo_number": "WO-010", "category": "3rd Escalation", "site": "ACY9"}
|
||||
],
|
||||
)
|
||||
|
||||
views_open_calls = []
|
||||
fake_client = type(
|
||||
"C",
|
||||
(),
|
||||
{"views_open": lambda self, **kw: views_open_calls.append(kw)},
|
||||
)()
|
||||
monkeypatch.setattr(slackio, "web_client", lambda: fake_client)
|
||||
|
||||
event = _make_event(
|
||||
_block_actions_payload("drill_category:3rd Escalation", "3rd Escalation"),
|
||||
base64_encoded=True,
|
||||
)
|
||||
resp = interactions.handler(event, None)
|
||||
|
||||
assert resp["statusCode"] == 200
|
||||
assert len(views_open_calls) == 1
|
||||
|
||||
|
||||
def test_non_block_actions_type_returns_200_no_views_open(monkeypatch):
|
||||
import interactions
|
||||
import slackio
|
||||
|
||||
monkeypatch.setattr(slackio, "verify_signature", lambda body, ts, sig: True)
|
||||
|
||||
views_open_calls = []
|
||||
fake_client = type(
|
||||
"C",
|
||||
(),
|
||||
{"views_open": lambda self, **kw: views_open_calls.append(kw)},
|
||||
)()
|
||||
monkeypatch.setattr(slackio, "web_client", lambda: fake_client)
|
||||
|
||||
# "view_submission" is a valid Slack payload type but not block_actions
|
||||
event = _make_event({"type": "view_submission"})
|
||||
resp = interactions.handler(event, None)
|
||||
|
||||
assert resp["statusCode"] == 200
|
||||
assert views_open_calls == []
|
||||
|
||||
|
||||
def test_unknown_action_kind_returns_200_no_views_open(monkeypatch):
|
||||
"""action_id prefix not in _FILTER_FIELD → no-op."""
|
||||
import interactions
|
||||
import slackio
|
||||
|
||||
monkeypatch.setattr(slackio, "verify_signature", lambda body, ts, sig: True)
|
||||
|
||||
views_open_calls = []
|
||||
fake_client = type(
|
||||
"C",
|
||||
(),
|
||||
{"views_open": lambda self, **kw: views_open_calls.append(kw)},
|
||||
)()
|
||||
monkeypatch.setattr(slackio, "web_client", lambda: fake_client)
|
||||
|
||||
payload = {
|
||||
"type": "block_actions",
|
||||
"trigger_id": "tid",
|
||||
"actions": [{"action_id": "open_dashboard", "value": "something"}],
|
||||
}
|
||||
event = _make_event(payload)
|
||||
resp = interactions.handler(event, None)
|
||||
|
||||
assert resp["statusCode"] == 200
|
||||
assert views_open_calls == []
|
||||
|
||||
|
||||
def test_missing_value_returns_200_no_views_open(monkeypatch):
|
||||
"""action has a valid kind but missing value → no-op."""
|
||||
import interactions
|
||||
import slackio
|
||||
|
||||
monkeypatch.setattr(slackio, "verify_signature", lambda body, ts, sig: True)
|
||||
|
||||
views_open_calls = []
|
||||
fake_client = type(
|
||||
"C",
|
||||
(),
|
||||
{"views_open": lambda self, **kw: views_open_calls.append(kw)},
|
||||
)()
|
||||
monkeypatch.setattr(slackio, "web_client", lambda: fake_client)
|
||||
|
||||
payload = {
|
||||
"type": "block_actions",
|
||||
"trigger_id": "tid",
|
||||
"actions": [
|
||||
{"action_id": "drill_category:Report / Docs Needed"}
|
||||
], # no value key
|
||||
}
|
||||
event = _make_event(payload)
|
||||
resp = interactions.handler(event, None)
|
||||
|
||||
assert resp["statusCode"] == 200
|
||||
assert views_open_calls == []
|
||||
|
||||
|
||||
def test_missing_trigger_id_returns_200_no_views_open(monkeypatch):
|
||||
"""Valid action but no trigger_id → no-op."""
|
||||
import interactions
|
||||
import slackio
|
||||
|
||||
monkeypatch.setattr(slackio, "verify_signature", lambda body, ts, sig: True)
|
||||
|
||||
views_open_calls = []
|
||||
fake_client = type(
|
||||
"C",
|
||||
(),
|
||||
{"views_open": lambda self, **kw: views_open_calls.append(kw)},
|
||||
)()
|
||||
monkeypatch.setattr(slackio, "web_client", lambda: fake_client)
|
||||
|
||||
payload = {
|
||||
"type": "block_actions",
|
||||
# trigger_id absent
|
||||
"actions": [
|
||||
{
|
||||
"action_id": "drill_category:Report / Docs Needed",
|
||||
"value": "Report / Docs Needed",
|
||||
}
|
||||
],
|
||||
}
|
||||
event = _make_event(payload)
|
||||
resp = interactions.handler(event, None)
|
||||
|
||||
assert resp["statusCode"] == 200
|
||||
assert views_open_calls == []
|
||||
|
||||
|
||||
def test_empty_details_json_shows_zero_wos_in_modal(monkeypatch):
|
||||
"""Details list is empty → views_open still called, modal title shows 0."""
|
||||
import interactions
|
||||
import slackio
|
||||
|
||||
monkeypatch.setattr(slackio, "verify_signature", lambda body, ts, sig: True)
|
||||
monkeypatch.setattr(
|
||||
slackio, "get_dashboard_url", lambda: "https://grafana.example.com"
|
||||
)
|
||||
monkeypatch.setattr(slackio, "read_meta_json", lambda dt, name: [])
|
||||
|
||||
views_open_calls = []
|
||||
fake_client = type(
|
||||
"C",
|
||||
(),
|
||||
{"views_open": lambda self, **kw: views_open_calls.append(kw)},
|
||||
)()
|
||||
monkeypatch.setattr(slackio, "web_client", lambda: fake_client)
|
||||
|
||||
event = _make_event(
|
||||
_block_actions_payload("drill_category:3rd Escalation", "3rd Escalation")
|
||||
)
|
||||
resp = interactions.handler(event, None)
|
||||
|
||||
assert resp["statusCode"] == 200
|
||||
assert len(views_open_calls) == 1
|
||||
assert "(0)" in views_open_calls[0]["view"]["title"]["text"]
|
||||
Loading…
Add table
Reference in a new issue