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"]