4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
190 lines
5.6 KiB
Python
190 lines
5.6 KiB
Python
"""Tests for integrations.whatsapp.delivery — WhatsApp via Twilio transport."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
import pytest
|
|
|
|
from integrations.whatsapp.delivery import (
|
|
post_whatsapp_message_twilio,
|
|
send_whatsapp_report,
|
|
)
|
|
from platform.notifications.delivery_transport import DeliveryResponse
|
|
|
|
|
|
def _success_response(**kwargs: Any) -> DeliveryResponse:
|
|
defaults: dict[str, Any] = {"ok": True, "status_code": 201, "data": {"sid": "SM123"}}
|
|
defaults.update(kwargs)
|
|
return DeliveryResponse(**defaults)
|
|
|
|
|
|
def _error_response(**kwargs: Any) -> DeliveryResponse:
|
|
defaults: dict[str, Any] = {
|
|
"ok": True,
|
|
"status_code": 400,
|
|
"data": {"message": "Invalid 'From' parameter"},
|
|
"text": "Bad Request",
|
|
}
|
|
defaults.update(kwargs)
|
|
return DeliveryResponse(**defaults)
|
|
|
|
|
|
def test_post_whatsapp_message_twilio_success(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
captured: dict[str, Any] = {}
|
|
|
|
def _fake_post_form(url: str, data: dict[str, str], **kwargs: Any) -> DeliveryResponse:
|
|
captured["url"] = url
|
|
captured["data"] = data
|
|
captured.update(kwargs)
|
|
return _success_response()
|
|
|
|
monkeypatch.setattr("integrations.whatsapp.delivery.post_form", _fake_post_form)
|
|
|
|
success, error, message_id = post_whatsapp_message_twilio(
|
|
to="+1234567890",
|
|
text="hello",
|
|
account_sid="AC123",
|
|
auth_token="secret",
|
|
from_number="whatsapp:+14155238886",
|
|
)
|
|
|
|
assert success is True
|
|
assert error == ""
|
|
assert message_id == "SM123"
|
|
assert captured["url"].endswith("/Accounts/AC123/Messages.json")
|
|
assert captured["data"]["From"] == "whatsapp:+14155238886"
|
|
assert captured["data"]["To"] == "whatsapp:+1234567890"
|
|
assert captured["data"]["Body"] == "hello"
|
|
assert captured["auth"] == ("AC123", "secret")
|
|
|
|
|
|
def test_post_whatsapp_message_twilio_transport_failure(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.setattr(
|
|
"integrations.whatsapp.delivery.post_form",
|
|
lambda *_a, **_kw: DeliveryResponse(
|
|
ok=False, error="auth header tok-leak failed", exc_type="RuntimeError"
|
|
),
|
|
)
|
|
|
|
success, error, message_id = post_whatsapp_message_twilio(
|
|
to="+123",
|
|
text="test",
|
|
account_sid="AC123",
|
|
auth_token="tok-leak",
|
|
from_number="whatsapp:+14155238886",
|
|
)
|
|
|
|
assert success is False
|
|
assert "tok-leak" not in error
|
|
assert "<redacted>" in error
|
|
assert message_id == ""
|
|
|
|
|
|
def test_post_whatsapp_message_twilio_api_error(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.setattr(
|
|
"integrations.whatsapp.delivery.post_form",
|
|
lambda *_a, **_kw: _error_response(),
|
|
)
|
|
|
|
success, error, message_id = post_whatsapp_message_twilio(
|
|
to="+123",
|
|
text="Test",
|
|
account_sid="AC123",
|
|
auth_token="tok-123",
|
|
from_number="whatsapp:bad",
|
|
)
|
|
|
|
assert success is False
|
|
assert "Invalid 'From' parameter" in error
|
|
assert message_id == ""
|
|
|
|
|
|
def test_post_whatsapp_message_twilio_prefixes_whatsapp(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
captured: dict[str, Any] = {}
|
|
|
|
def _fake_post_form(url: str, data: dict[str, str], **kwargs: Any) -> DeliveryResponse:
|
|
captured["data"] = data
|
|
return _success_response()
|
|
|
|
monkeypatch.setattr("integrations.whatsapp.delivery.post_form", _fake_post_form)
|
|
|
|
post_whatsapp_message_twilio(
|
|
to="+1234567890",
|
|
text="hi",
|
|
account_sid="AC123",
|
|
auth_token="tok",
|
|
from_number="+14155238886",
|
|
)
|
|
|
|
assert captured["data"]["To"] == "whatsapp:+1234567890"
|
|
assert captured["data"]["From"] == "whatsapp:+14155238886"
|
|
|
|
|
|
def test_send_whatsapp_report_success(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.setattr(
|
|
"integrations.whatsapp.delivery.post_form",
|
|
lambda *_a, **_kw: _success_response(data={"sid": "SM456"}),
|
|
)
|
|
|
|
success, error = send_whatsapp_report(
|
|
report="Investigation summary",
|
|
whatsapp_ctx={
|
|
"account_sid": "AC123",
|
|
"auth_token": "tok",
|
|
"from_number": "whatsapp:+14155238886",
|
|
"to": "+123",
|
|
},
|
|
)
|
|
|
|
assert success is True
|
|
assert error == ""
|
|
|
|
|
|
def test_send_whatsapp_report_missing_credentials() -> None:
|
|
success, error = send_whatsapp_report(
|
|
report="Test",
|
|
whatsapp_ctx={"account_sid": "AC123"},
|
|
)
|
|
|
|
assert success is False
|
|
assert "Missing" in error
|
|
|
|
|
|
def test_send_whatsapp_report_truncates_long_report(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
captured: dict[str, Any] = {}
|
|
|
|
def _fake_post_form(url: str, data: dict[str, str], **kwargs: Any) -> DeliveryResponse:
|
|
captured["data"] = data
|
|
return _success_response()
|
|
|
|
monkeypatch.setattr("integrations.whatsapp.delivery.post_form", _fake_post_form)
|
|
|
|
send_whatsapp_report(
|
|
report="X" * 5000,
|
|
whatsapp_ctx={
|
|
"account_sid": "AC123",
|
|
"auth_token": "tok",
|
|
"from_number": "whatsapp:+14155238886",
|
|
"to": "+123",
|
|
},
|
|
)
|
|
|
|
assert len(captured["data"]["Body"]) <= 4096
|
|
assert captured["data"]["Body"].endswith("…")
|
|
|
|
|
|
class TestWhatsAppDelegatesToSharedTransport:
|
|
"""WhatsApp delivery must use post_form from delivery_transport,
|
|
not import httpx directly."""
|
|
|
|
def test_module_does_not_import_httpx(self) -> None:
|
|
from integrations.whatsapp import delivery as whatsapp_delivery
|
|
|
|
assert not hasattr(whatsapp_delivery, "httpx"), (
|
|
"whatsapp_delivery should not import httpx directly — "
|
|
"it must go through delivery_transport.post_form"
|
|
)
|