"""Unit tests for platform API credential registry and providers.""" from unittest.mock import AsyncMock, MagicMock, patch import pytest from app.core.crypto.credential_crypto import decrypt_secret, encrypt_secret, mask_secret from app.services.platform_api_registry import ( EffectiveCredential, build_credential_views, fetch_price_via_credential, resolve_platform_credential, ) class TestCredentialCrypto: def test_encrypt_decrypt_roundtrip(self): raw = "sk-test-api-key-12345" token = encrypt_secret(raw) assert decrypt_secret(token) == raw assert raw not in token def test_mask_secret(self): assert mask_secret("abcd") == "••••" assert mask_secret("my-long-api-key-9xyz") == "••••9xyz" class TestBuildCredentialViews: def test_no_row_no_env_shows_none_source(self): views = build_credential_views([]) amazon = next(v for v in views if v.platform_id == "amazon") assert amazon.source == "none" assert amazon.api_key_hint is None assert "rainforest" in amazon.available_providers def test_env_credential_shows_environment_source(self): with patch("app.services.platform_api_registry.get_settings") as gs: gs.return_value = MagicMock(RAINFOREST_API_KEY="rf-env-key-9999") views = build_credential_views([]) amazon = next(v for v in views if v.platform_id == "amazon") assert amazon.source == "environment" assert amazon.api_key_hint == "••••9999" assert amazon.is_enabled is True @pytest.mark.asyncio class TestResolvePlatformCredential: async def test_superadmin_db_takes_priority_over_env(self): row = MagicMock() row.platform_id = "amazon" row.provider = "rainforest" row.api_key_encrypted = encrypt_secret("db-key-wins") row.api_secret_encrypted = None row.is_enabled = True db = AsyncMock() repo = MagicMock() repo.get_enabled = AsyncMock(return_value=row) with patch( "app.services.platform_api_registry.PlatformApiCredentialRepository", return_value=repo, ), patch("app.services.platform_api_registry._env_credential") as env: env.return_value = EffectiveCredential( platform_id="amazon", provider="rainforest", api_key="env-key", api_secret=None, source="environment", ) cred = await resolve_platform_credential("amazon", db=db, redis=None) assert cred is not None assert cred.source == "superadmin" assert cred.api_key == "db-key-wins" async def test_falls_back_to_environment(self): db = AsyncMock() repo = MagicMock() repo.get_enabled = AsyncMock(return_value=None) with patch( "app.services.platform_api_registry.PlatformApiCredentialRepository", return_value=repo, ), patch("app.services.platform_api_registry._env_credential") as env: env.return_value = EffectiveCredential( platform_id="google_books", provider="google_books", api_key="gbooks-env", api_secret=None, source="environment", ) cred = await resolve_platform_credential("google_books", db=db, redis=None) assert cred is not None assert cred.source == "environment" @pytest.mark.asyncio class TestFetchPriceViaCredential: async def test_rainforest_parses_buybox_price(self): client = AsyncMock() response = MagicMock(status_code=200) response.json.return_value = { "product": { "buybox_winner": { "price": {"value": 15.99, "currency": "USD"}, }, }, } client.get.return_value = response cred = EffectiveCredential( platform_id="amazon", provider="rainforest", api_key="rf-key", api_secret=None, source="superadmin", ) hit = await fetch_price_via_credential( client, cred, "amazon", "https://www.amazon.com/dp/B012345678", ) assert hit is not None assert hit.formatted == "$15.99" assert hit.source == "api:rainforest" async def test_scrapingbee_delegates_to_html_parser(self): client = AsyncMock() response = MagicMock( status_code=200, text=('{"price": "12.50", "title": "Test Book"}' + " " * 600), ) client.get.return_value = response cred = EffectiveCredential( platform_id="barnes_noble", provider="scrapingbee", api_key="sb-key", api_secret=None, source="superadmin", ) hit = await fetch_price_via_credential( client, cred, "barnes_noble", "https://www.barnesandnoble.com/w/book", ) assert hit is not None assert hit.formatted == "$12.50" assert hit.source == "api:scrapingbee"