mirror of
https://github.com/Sea-Haven-Industries/apm-wo-analysis.git
synced 2026-09-30 15:53:15 +00:00
331 lines
9.7 KiB
Python
331 lines
9.7 KiB
Python
"""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"]
|