|
| 1 | +from __future__ import annotations |
| 2 | + |
| 3 | +import json |
| 4 | +from typing import get_type_hints |
| 5 | + |
| 6 | +import pytest |
| 7 | +import respx |
| 8 | +from httpx import Response |
| 9 | + |
| 10 | +from lettermint import AsyncLettermint, HttpRequestError, Lettermint |
| 11 | +from lettermint import types as lm_types |
| 12 | + |
| 13 | + |
| 14 | +@pytest.mark.parametrize( |
| 15 | + "state", |
| 16 | + [{}, {"basic_auth": {"username": " fixture user ", "password": ""}}, {"basic_auth": None}], |
| 17 | +) |
| 18 | +@pytest.mark.parametrize("asynchronous", [False, True]) |
| 19 | +@respx.mock |
| 20 | +@pytest.mark.asyncio |
| 21 | +async def test_webhook_credential_states_keep_bearer_auth(state: dict, asynchronous: bool) -> None: |
| 22 | + create = respx.post("https://api.lettermint.co/v1/webhooks").mock( |
| 23 | + return_value=Response(201, json={"data": {"has_basic_auth": True}}) |
| 24 | + ) |
| 25 | + update = respx.put("https://api.lettermint.co/v1/webhooks/webhook-id").mock( |
| 26 | + return_value=Response(200, json={"data": {"has_basic_auth": True}}) |
| 27 | + ) |
| 28 | + payload = { |
| 29 | + "name": "Fixture", |
| 30 | + "url": "https://example.test/hook", |
| 31 | + "events": ["message.sent"], |
| 32 | + **state, |
| 33 | + } |
| 34 | + if asynchronous: |
| 35 | + async with AsyncLettermint.api("fixture-token") as api: |
| 36 | + assert (await api.webhooks.create(payload))["data"]["has_basic_auth"] is True |
| 37 | + assert (await api.webhooks.update("webhook-id", state))["data"][ |
| 38 | + "has_basic_auth" |
| 39 | + ] is True |
| 40 | + else: |
| 41 | + with Lettermint.api("fixture-token") as sync_api: |
| 42 | + assert sync_api.webhooks.create(payload)["data"]["has_basic_auth"] is True |
| 43 | + assert sync_api.webhooks.update("webhook-id", state)["data"]["has_basic_auth"] is True |
| 44 | + for route, expected in [(create, payload), (update, state)]: |
| 45 | + request = route.calls.last.request |
| 46 | + assert json.loads(request.content) == expected |
| 47 | + assert request.headers["authorization"] == "Bearer fixture-token" |
| 48 | + assert "x-lettermint-token" not in request.headers |
| 49 | + |
| 50 | + |
| 51 | +def test_webhook_types_expose_required_read_flag_and_optional_nullable_credentials() -> None: |
| 52 | + for model in [lm_types.WebhookData, lm_types.WebhookListData, lm_types.WebhookSecretData]: |
| 53 | + assert "Required[bool]" in str(get_type_hints(model, include_extras=True)["has_basic_auth"]) |
| 54 | + for model in [lm_types.StoreWebhookData, lm_types.UpdateWebhookData]: |
| 55 | + assert "NotRequired" in str(get_type_hints(model, include_extras=True)["basic_auth"]) |
| 56 | + assert "has_basic_auth" not in get_type_hints(model, include_extras=True) |
| 57 | + credentials: lm_types.WebhookBasicAuthData = {"username": "fixture", "password": ""} |
| 58 | + assert credentials["password"] == "" |
| 59 | + |
| 60 | + |
| 61 | +@respx.mock |
| 62 | +def test_free_plan_sandbox_keeps_403_response() -> None: |
| 63 | + body = { |
| 64 | + "error": { |
| 65 | + "code": "FEATURE_NOT_AVAILABLE", |
| 66 | + "message": "Sandbox mode is available only on paid plans.", |
| 67 | + } |
| 68 | + } |
| 69 | + respx.post("https://api.lettermint.co/v1/send").mock(return_value=Response(403, json=body)) |
| 70 | + with Lettermint.email("fixture-token") as email, pytest.raises(HttpRequestError) as caught: |
| 71 | + email.from_("from@example.test").to("to@example.test").subject("Fixture").send() |
| 72 | + assert caught.value.status_code == 403 |
| 73 | + assert caught.value.response_body == body |
0 commit comments