124 lines
4.3 KiB
Python
124 lines
4.3 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import date
|
|
|
|
import pytest
|
|
from mousehold_agent.paypal_connector import (
|
|
PayPalAPIError,
|
|
PayPalConfig,
|
|
_paypal_error_message,
|
|
_token_payload_to_auth_check,
|
|
paypal_transaction_to_observation,
|
|
paypal_transaction_to_preview,
|
|
)
|
|
from mousehold_agent.paypal_connector import _validate_date_range as validate_date_range
|
|
|
|
|
|
def test_paypal_negative_transaction_becomes_confirmed_expense() -> None:
|
|
observation = paypal_transaction_to_observation(
|
|
{
|
|
"transaction_info": {
|
|
"transaction_id": "PAYPAL-001",
|
|
"transaction_initiation_date": "2026-06-20T12:30:00Z",
|
|
"transaction_updated_date": "2026-06-20T12:31:00Z",
|
|
"transaction_amount": {"currency_code": "EUR", "value": "-18.99"},
|
|
"transaction_status": "S",
|
|
"transaction_subject": "Payment to Example Merchant",
|
|
},
|
|
"payer_info": {"email_address": "merchant@example.test"},
|
|
},
|
|
household_id="household-1",
|
|
source_account_id="account-1",
|
|
owner_user_id="user-1",
|
|
)
|
|
|
|
assert observation["source_type"] == "paypal_api"
|
|
assert observation["provider_transaction_id"] == "PAYPAL-001"
|
|
assert observation["event_type"] == "expense"
|
|
assert observation["status"] == "confirmed"
|
|
assert observation["amount"] == "18.99"
|
|
assert observation["event_date"] == "2026-06-20"
|
|
|
|
|
|
def test_paypal_positive_transaction_becomes_income() -> None:
|
|
observation = paypal_transaction_to_observation(
|
|
{
|
|
"transaction_info": {
|
|
"transaction_id": "PAYPAL-002",
|
|
"transaction_initiation_date": "2026-06-21T09:10:00+0000",
|
|
"transaction_amount": {"currency_code": "EUR", "value": "42.50"},
|
|
"transaction_status": "S",
|
|
},
|
|
"payer_info": {
|
|
"payer_name": {"given_name": "Alice", "surname": "Example"},
|
|
},
|
|
},
|
|
household_id="household-1",
|
|
source_account_id="account-1",
|
|
owner_user_id="user-1",
|
|
)
|
|
|
|
assert observation["event_type"] == "income"
|
|
assert observation["amount"] == "42.50"
|
|
assert observation["counterparty"] == "Alice Example"
|
|
|
|
|
|
def test_paypal_pending_transaction_preview_is_announced() -> None:
|
|
preview = paypal_transaction_to_preview(
|
|
{
|
|
"transaction_info": {
|
|
"transaction_id": "PAYPAL-003",
|
|
"transaction_initiation_date": "2026-06-22T09:10:00Z",
|
|
"transaction_amount": {"currency_code": "EUR", "value": "-10.00"},
|
|
"transaction_status": "P",
|
|
}
|
|
}
|
|
)
|
|
|
|
assert preview["status"] == "announced"
|
|
assert preview["event_type"] == "expense"
|
|
|
|
|
|
def test_paypal_date_range_is_limited_to_31_days() -> None:
|
|
with pytest.raises(PayPalAPIError):
|
|
validate_date_range(date(2026, 6, 1), date(2026, 7, 2))
|
|
|
|
|
|
def test_paypal_not_authorized_error_mentions_transaction_search_scope() -> None:
|
|
message = _paypal_error_message(
|
|
403,
|
|
'{"name":"NOT_AUTHORIZED","message":"Authorization failed due to insufficient permissions."}',
|
|
)
|
|
|
|
assert "Transaction Search" in message
|
|
assert "https://uri.paypal.com/services/reporting/search/read" in message
|
|
|
|
|
|
def test_paypal_auth_check_reports_missing_transaction_search_scope() -> None:
|
|
check = _token_payload_to_auth_check(
|
|
PayPalConfig(client_id="client", client_secret="secret", environment="sandbox"),
|
|
{
|
|
"scope": "openid https://uri.paypal.com/services/invoicing",
|
|
"token_type": "Bearer",
|
|
"app_id": "APP-123",
|
|
"expires_in": 3600,
|
|
},
|
|
)
|
|
|
|
assert check["app_id"] == "APP-123"
|
|
assert check["environment"] == "sandbox"
|
|
assert check["has_transaction_search"] is False
|
|
|
|
|
|
def test_paypal_auth_check_parses_comma_separated_scope_text() -> None:
|
|
check = _token_payload_to_auth_check(
|
|
PayPalConfig(client_id="client", client_secret="secret", environment="sandbox"),
|
|
{
|
|
"scope": "openid,https://uri.paypal.com/services/reporting/search/read",
|
|
"token_type": "Bearer",
|
|
},
|
|
)
|
|
|
|
assert check["has_transaction_search"] is True
|
|
assert "https://uri.paypal.com/services/reporting/search/read" in check["scopes"]
|