"""Tests for notification payload builder."""
from datetime import datetime, UTC
from preloop.services.push_notifications.notification_payloads import (
NotificationPayloadBuilder,
)
class TestNewApprovalRequestPayload:
"""Test new_approval_request payload generation."""
def test_basic_payload_structure(self):
"""Test basic payload structure for new approval request."""
payload = NotificationPayloadBuilder.new_approval_request(
request_id="123e4567-e89b-12d3-a456-426614174000",
tool_name="create_issue",
priority="medium",
)
assert "aps" in payload
assert "alert" in payload["aps"]
assert "title" in payload["aps"]["alert"]
assert "subtitle" in payload["aps"]["alert"]
assert "body" in payload["aps"]["alert"]
assert payload["aps"]["sound"] == "default"
assert payload["aps"]["badge"] == 1
assert payload["aps"]["category"] == "APPROVAL_REQUEST"
assert payload["aps"]["thread-id"] == "approval-requests"
# Custom data
assert payload["type"] == "new_approval_request"
assert payload["approval_request_id"] == "123e4567-e89b-12d3-a456-426614174000"
assert payload["tool_name"] == "create_issue"
assert payload["priority"] == "medium"
def test_urgent_priority_notification(self):
"""Test urgent priority produces correct alert and sound."""
payload = NotificationPayloadBuilder.new_approval_request(
request_id="test-id", tool_name="delete_database", priority="urgent"
)
assert payload["aps"]["alert"]["title"] == "🚨 URGENT: Approval Needed"
assert payload["aps"]["sound"] == "critical.caf"
assert payload["aps"]["interruption-level"] == "critical"
assert payload["aps"]["relevance-score"] == 1.0
def test_high_priority_notification(self):
"""Test high priority produces correct alert."""
payload = NotificationPayloadBuilder.new_approval_request(
request_id="test-id", tool_name="modify_production", priority="high"
)
assert payload["aps"]["alert"]["title"] == "⚠️ High Priority Approval"
assert payload["aps"]["sound"] == "default"
assert payload["aps"]["interruption-level"] == "time-sensitive"
assert payload["aps"]["relevance-score"] == 1.0
def test_medium_priority_notification(self):
"""Test medium priority produces correct alert."""
payload = NotificationPayloadBuilder.new_approval_request(
request_id="test-id", tool_name="create_issue", priority="medium"
)
assert payload["aps"]["alert"]["title"] == "New Approval Request"
assert payload["aps"]["sound"] == "default"
assert payload["aps"]["interruption-level"] == "active"
assert payload["aps"]["relevance-score"] == 0.5
def test_low_priority_notification(self):
"""Test low priority produces correct alert."""
payload = NotificationPayloadBuilder.new_approval_request(
request_id="test-id", tool_name="add_comment", priority="low"
)
assert payload["aps"]["alert"]["title"] == "New Approval Request"
assert payload["aps"]["interruption-level"] == "active"
assert payload["aps"]["relevance-score"] == 0.5
def test_tool_name_formatting(self):
"""Test tool name is formatted nicely in subtitle and body."""
payload = NotificationPayloadBuilder.new_approval_request(
request_id="test-id", tool_name="create_github_issue", priority="medium"
)
# Tool name should be formatted: underscores -> spaces, title case
assert payload["aps"]["alert"]["subtitle"] == "Create Github Issue"
assert "Create Github Issue" in payload["aps"]["alert"]["body"]
def test_with_agent_reasoning(self):
"""Test payload includes agent reasoning in body."""
reasoning = "This issue needs to be created to track the bug"
payload = NotificationPayloadBuilder.new_approval_request(
request_id="test-id",
tool_name="create_issue",
priority="medium",
agent_reasoning=reasoning,
)
assert reasoning in payload["aps"]["alert"]["body"]
def test_agent_reasoning_truncation(self):
"""Test agent reasoning is truncated to 100 chars."""
long_reasoning = "A" * 150
payload = NotificationPayloadBuilder.new_approval_request(
request_id="test-id",
tool_name="create_issue",
priority="medium",
agent_reasoning=long_reasoning,
)
# Should be truncated with ellipsis
body = payload["aps"]["alert"]["body"]
assert len(body) < len(long_reasoning) + 50 # Accounting for prefix text
assert "..." in body
def test_with_expires_at(self):
"""Test payload includes expiration timestamp."""
expires_at = datetime(2025, 1, 1, 12, 0, 0, tzinfo=UTC)
payload = NotificationPayloadBuilder.new_approval_request(
request_id="test-id",
tool_name="create_issue",
priority="medium",
expires_at=expires_at,
)
assert "expires_at" in payload
assert payload["expires_at"] == "2025-01-01T12:00:00+00:00"
def test_without_expires_at(self):
"""Test payload works without expiration timestamp."""
payload = NotificationPayloadBuilder.new_approval_request(
request_id="test-id", tool_name="create_issue", priority="medium"
)
assert "expires_at" not in payload
class TestRequestExpiringPayload:
"""Test request_expiring_soon payload generation."""
def test_basic_structure(self):
"""Test basic structure of expiring request payload."""
payload = NotificationPayloadBuilder.request_expiring_soon(
request_id="test-id", tool_name="create_issue", minutes_remaining=15
)
assert payload["aps"]["alert"]["title"] == "⏰ Request Expiring Soon"
assert "Create Issue" in payload["aps"]["alert"]["subtitle"]
assert "expires in 15 minutes" in payload["aps"]["alert"]["body"]
assert payload["aps"]["sound"] == "default"
assert payload["aps"]["badge"] == 1
assert payload["aps"]["category"] == "APPROVAL_REQUEST"
assert payload["aps"]["interruption-level"] == "time-sensitive"
assert payload["type"] == "request_expiring_soon"
assert payload["approval_request_id"] == "test-id"
assert payload["tool_name"] == "create_issue"
assert payload["minutes_remaining"] == 15
def test_minutes_remaining_formatting(self):
"""Test different minute values are formatted correctly."""
payload_1 = NotificationPayloadBuilder.request_expiring_soon(
request_id="test-id", tool_name="create_issue", minutes_remaining=1
)
assert "expires in 1 minutes" in payload_1["aps"]["alert"]["body"]
payload_30 = NotificationPayloadBuilder.request_expiring_soon(
request_id="test-id", tool_name="create_issue", minutes_remaining=30
)
assert "expires in 30 minutes" in payload_30["aps"]["alert"]["body"]
class TestRequestExpiredPayload:
"""Test request_expired payload generation."""
def test_basic_structure(self):
"""Test basic structure of expired request payload."""
payload = NotificationPayloadBuilder.request_expired(
request_id="test-id", tool_name="create_issue"
)
assert payload["aps"]["alert"]["title"] == "Request Expired"
assert "Create Issue" in payload["aps"]["alert"]["subtitle"]
assert payload["aps"]["alert"]["body"] == "An approval request has expired"
assert payload["aps"]["sound"] == "default"
assert payload["aps"]["badge"] == 0 # Clear badge
assert payload["type"] == "request_expired"
assert payload["approval_request_id"] == "test-id"
assert payload["tool_name"] == "create_issue"
def test_badge_cleared(self):
"""Test that badge is cleared (set to 0) for expired requests."""
payload = NotificationPayloadBuilder.request_expired(
request_id="test-id", tool_name="create_issue"
)
assert payload["aps"]["badge"] == 0
class TestRequestResolvedPayload:
"""Test request_resolved payload generation."""
def test_approved_notification(self):
"""Test resolved payload for approved request."""
payload = NotificationPayloadBuilder.request_resolved(
request_id="test-id",
tool_name="create_issue",
resolved_by="john@example.com",
decision="approved",
)
assert payload["aps"]["alert"]["title"] == "Request Resolved"
assert "Create Issue" in payload["aps"]["alert"]["subtitle"]
assert (
"john@example.com approved this request" in payload["aps"]["alert"]["body"]
)
assert payload["aps"]["sound"] == "default"
assert payload["aps"]["badge"] == 0
assert payload["type"] == "request_resolved"
assert payload["approval_request_id"] == "test-id"
assert payload["tool_name"] == "create_issue"
assert payload["resolved_by"] == "john@example.com"
assert payload["decision"] == "approved"
def test_declined_notification(self):
"""Test resolved payload for declined request."""
payload = NotificationPayloadBuilder.request_resolved(
request_id="test-id",
tool_name="delete_database",
resolved_by="admin@example.com",
decision="declined",
)
assert (
"admin@example.com declined this request" in payload["aps"]["alert"]["body"]
)
assert payload["decision"] == "declined"
def test_badge_cleared_on_resolution(self):
"""Test that badge is cleared when request is resolved."""
payload = NotificationPayloadBuilder.request_resolved(
request_id="test-id",
tool_name="create_issue",
resolved_by="user@example.com",
decision="approved",
)
assert payload["aps"]["badge"] == 0