"""Author RAG — Email Integrations Admin Page Tests.""" import pytest from unittest.mock import AsyncMock, patch, MagicMock from fastapi.testclient import TestClient # ── ConvertKit sync ──────────────────────────────────────────────────────── @pytest.mark.asyncio async def test_sync_subscriber_convertkit_success(): from app.api.email_integrations import sync_subscriber, _configs _configs["test-author"] = { "provider": "convertkit", "api_key": "ck_test_key_1234", "list_id": "12345", "sync_on_buy_intent": True, "sync_on_link_click": False, } with patch("httpx.AsyncClient") as MockClient: instance = AsyncMock() MockClient.return_value.__aenter__ = AsyncMock(return_value=instance) MockClient.return_value.__aexit__ = AsyncMock(return_value=False) instance.post = AsyncMock(return_value=MagicMock(status_code=200)) await sync_subscriber("test-author", "reader@example.com", "Jane") instance.post.assert_called_once() call_kwargs = instance.post.call_args assert "subscribe" in str(call_kwargs) @pytest.mark.asyncio async def test_sync_subscriber_no_config(): """Should silently no-op if author has no email integration configured.""" from app.api.email_integrations import sync_subscriber, _configs _configs.pop("no-config-author", None) # Should not raise await sync_subscriber("no-config-author", "reader@example.com") # ── GDPR ────────────────────────────────────────────────────────────────── @pytest.mark.asyncio async def test_gdpr_deletion_request_idempotent(): from app.api.gdpr import _deletion_requests _deletion_requests["dup-author"] = { "author_id": "dup-author", "scheduled_at": "2024-01-01T00:00:00Z", "deletion_date": "2024-01-31T00:00:00Z", "status": "pending", } # Re-requesting should return already_requested assert _deletion_requests["dup-author"]["status"] == "pending" def test_gdpr_delete_status_none(): from app.api.gdpr import _deletion_requests _deletion_requests.pop("clean-author", None) assert "clean-author" not in _deletion_requests # ── Agency ──────────────────────────────────────────────────────────────── def test_agency_member_role_validation(): from app.models.agency import AgencyMember m = AgencyMember.__new__(AgencyMember) m.role = "author" assert m.role in ("author", "admin") # ── Rate Limiter ────────────────────────────────────────────────────────── def test_rate_limit_rule_login(): from app.middleware.rate_limit import _get_rule limit, window = _get_rule("/api/auth/login") assert limit == 10 assert window == 900 def test_rate_limit_rule_forgot(): from app.middleware.rate_limit import _get_rule limit, window = _get_rule("/api/auth/forgot-password") assert limit == 3 assert window == 3600 def test_rate_limit_rule_default(): from app.middleware.rate_limit import _get_rule limit, window = _get_rule("/api/admin/some-author/books") assert limit == 300 assert window == 60 # ── Stripe Connect ──────────────────────────────────────────────────────── @pytest.mark.asyncio async def test_connect_status_no_account(mock_db, mock_author): mock_author.stripe_connect_account_id = None from app.api.stripe_connect import connect_status result = await connect_status( slug=mock_author.id, db=mock_db, author=mock_author, ) assert result["connected"] is False