"""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"]