Files
django-nepali-payments/tests/test_base_service.py
T

118 lines
4.1 KiB
Python

"""Shared base-service and HTTP timeout contract tests.
Verifies the OOP contract: every concrete gateway ``PaymentService`` subclasses
:class:`~nepali_payment.services.base.BasePaymentService`, shares the injected
HTTP client, and the fold/re-raise exception contracts are preserved.
"""
from typing import ClassVar
from nepali_payment.constants import ApiEndpoints
from nepali_payment.enums import PaymentMode
from nepali_payment.http import ApiService
from nepali_payment.models import PaymentResult
from nepali_payment.services.base import BasePaymentService
from nepali_payment.services.connectips import PaymentService as ConnectIpsService
from nepali_payment.services.fonepay import PaymentService as FonepayService
from nepali_payment.services.khalti import PaymentService as KhaltiService
def test_all_gateway_services_subclass_base():
for cls in (KhaltiService, FonepayService, ConnectIpsService):
assert issubclass(cls, BasePaymentService), f"{cls.__name__} must subclass BasePaymentService"
def test_services_share_base_url_and_api():
khalti = KhaltiService("secret", PaymentMode.PRODUCTION)
assert khalti.base_url == ApiEndpoints.KHALTI.BASE_URL
assert isinstance(khalti._api_service, ApiService)
khalti_sb = KhaltiService("secret", PaymentMode.SANDBOX)
assert khalti_sb.base_url == ApiEndpoints.KHALTI.SANDBOX_BASE_URL
def test_as_failed_builds_failed_result():
svc = KhaltiService("secret", PaymentMode.SANDBOX)
result = svc._as_failed(PaymentResult, RuntimeError("boom"))
assert result.success is False
assert "boom" in result.message
def test_run_result_folds_exception():
svc = KhaltiService("secret", PaymentMode.SANDBOX)
def fail():
raise RuntimeError("kaput")
result = svc._run_result(PaymentResult, fail)
assert result.success is False
assert "kaput" in result.message
def test_run_result_passthrough():
svc = KhaltiService("secret", PaymentMode.SANDBOX)
result = svc._run_result(PaymentResult, lambda: PaymentResult(success=True, message="ok"))
assert result.success is True
def test_connectips_service_shares_base_http_client():
"""ConnectIPS inherits the common base interface without a secret key."""
assert issubclass(ConnectIpsService, BasePaymentService)
assert callable(ConnectIpsService.initiate_payment)
assert callable(ConnectIpsService.verify_payment)
def test_api_service_default_and_per_call_timeout():
api = ApiService(timeout=15.0)
assert api._timeout == 15.0
# Explicit per-call timeout, including 0, must take precedence over the default.
class _RecordingResponse:
status_code: ClassVar[int] = 200
headers: ClassVar[dict] = {"Content-Type": "application/json"}
url: ClassVar[str] = ""
text: ClassVar[str] = "{}"
def json(self):
return {}
@property
def ok(self):
return True
def test_timeout_zero_not_swallowed():
"""A per-call ``timeout=0`` must not be replaced by the default."""
captured = {}
class _Session:
def request(self, method, url, headers=None, data=None, json=None, timeout=None):
captured["timeout"] = timeout
return _RecordingResponse()
api = ApiService(session=_Session())
api.get_async_result(object, "http://example.test/x", "POST", timeout=0)
assert captured["timeout"] == 0
def test_manager_caches_service_per_instance():
"""The manager must build its gateway service only once.
Reusing the service avoids re-parsing the ConnectIPS RSA certificate and
keeps one HTTP client across every operation on this manager.
"""
from nepali_payment.enums import PaymentMethod
from nepali_payment.manager import PaymentManager
manager = PaymentManager(PaymentMethod.KHALTI, PaymentMode.SANDBOX, "secret")
assert manager._service() is manager._service()
def test_coerce_handles_non_mapping_data_without_crashing():
"""Coercing a plain string must fall back cleanly, not raise AttributeError."""
from nepali_payment.helpers import _coerce
from nepali_payment.models.khalti import PaymentResponse
assert _coerce(PaymentResponse, "COMPLETE") == "COMPLETE"