f439f887d0
Backend fixes: - Fix JWT sub claim: encode as str(user.id), decode with int() cast (python-jose requirement) - Replace all deprecated datetime.utcnow() with datetime.now(timezone.utc) - Replace deprecated FastAPI @app.on_event() with modern lifespan context manager - Replace deprecated Pydantic class Config with model_config = ConfigDict(...) - Replace deprecated Pydantic .dict() with .model_dump() - Fix overly broad except (GmailInjectionError, Exception) → except Exception - Remove unused GmailInjectionError import - Fix TokenPayload schema sub field type from int to str Frontend: - Create frontend/src/lib/api.ts — API client module with auth, user, mail accounts, processing runs APIs - Add !frontend/src/lib/ to .gitignore negation Tests: - Add 3 new JWT tests (sub string encoding, access token type, refresh token type) - Update test_token_payload_schema for string sub claim - All 128 tests pass Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com> Agent-Logs-Url: https://github.com/christianlouis/pop_puller_to_gmail/sessions/e0b13eb0-8de7-4f02-81e4-e202cbba4608
278 lines
9.3 KiB
Python
278 lines
9.3 KiB
Python
"""
|
|
Unit tests for Pydantic schema validation.
|
|
"""
|
|
|
|
import pytest
|
|
from pydantic import ValidationError
|
|
from app.models.schemas import (
|
|
UserCreate,
|
|
UserUpdate,
|
|
MailAccountCreate,
|
|
MailAccountUpdate,
|
|
MailAccountTestRequest,
|
|
MailAccountAutoDetectRequest,
|
|
MailProtocol,
|
|
DeliveryMethod,
|
|
SubscriptionTier,
|
|
AccountStatus,
|
|
NotificationChannel,
|
|
Token,
|
|
TokenPayload,
|
|
GoogleAuthRequest,
|
|
NotificationConfigCreate,
|
|
SubscriptionCheckoutRequest,
|
|
ProviderPreset,
|
|
)
|
|
|
|
|
|
class TestEnums:
|
|
"""Test enum values"""
|
|
|
|
def test_subscription_tiers(self):
|
|
"""Test all subscription tier values"""
|
|
assert SubscriptionTier.FREE == "free"
|
|
assert SubscriptionTier.BASIC == "basic"
|
|
assert SubscriptionTier.PRO == "pro"
|
|
assert SubscriptionTier.ENTERPRISE == "enterprise"
|
|
|
|
def test_mail_protocols(self):
|
|
"""Test all mail protocol values"""
|
|
assert MailProtocol.POP3 == "pop3"
|
|
assert MailProtocol.POP3_SSL == "pop3_ssl"
|
|
assert MailProtocol.IMAP == "imap"
|
|
assert MailProtocol.IMAP_SSL == "imap_ssl"
|
|
|
|
def test_account_status(self):
|
|
"""Test all account status values"""
|
|
assert AccountStatus.ACTIVE == "active"
|
|
assert AccountStatus.INACTIVE == "inactive"
|
|
assert AccountStatus.ERROR == "error"
|
|
assert AccountStatus.TESTING == "testing"
|
|
|
|
def test_delivery_method(self):
|
|
"""Test all delivery method values"""
|
|
assert DeliveryMethod.SMTP == "smtp"
|
|
assert DeliveryMethod.GMAIL_API == "gmail_api"
|
|
|
|
def test_notification_channels(self):
|
|
"""Test all notification channel values"""
|
|
assert NotificationChannel.EMAIL == "email"
|
|
assert NotificationChannel.TELEGRAM == "telegram"
|
|
assert NotificationChannel.WEBHOOK == "webhook"
|
|
assert NotificationChannel.SLACK == "slack"
|
|
assert NotificationChannel.DISCORD == "discord"
|
|
|
|
|
|
class TestUserSchemas:
|
|
"""Test user-related schemas"""
|
|
|
|
def test_user_create_with_email(self):
|
|
"""Test UserCreate with valid email"""
|
|
user = UserCreate(email="test@example.com", password="password123")
|
|
assert user.email == "test@example.com"
|
|
assert user.password == "password123"
|
|
|
|
def test_user_create_without_password(self):
|
|
"""Test UserCreate without password (OAuth users)"""
|
|
user = UserCreate(email="test@example.com")
|
|
assert user.password is None
|
|
|
|
def test_user_create_with_full_name(self):
|
|
"""Test UserCreate with full name"""
|
|
user = UserCreate(
|
|
email="test@example.com", full_name="Test User", password="pass"
|
|
)
|
|
assert user.full_name == "Test User"
|
|
|
|
def test_user_create_invalid_email(self):
|
|
"""Test UserCreate rejects invalid email"""
|
|
with pytest.raises(ValidationError):
|
|
UserCreate(email="not-an-email", password="pass")
|
|
|
|
def test_user_update_partial(self):
|
|
"""Test UserUpdate with partial data"""
|
|
update = UserUpdate(full_name="New Name")
|
|
assert update.full_name == "New Name"
|
|
assert update.email is None
|
|
|
|
|
|
class TestTokenSchemas:
|
|
"""Test token schemas"""
|
|
|
|
def test_token_schema(self):
|
|
"""Test Token schema"""
|
|
token = Token(
|
|
access_token="abc123", refresh_token="def456", token_type="bearer"
|
|
)
|
|
assert token.access_token == "abc123"
|
|
assert token.token_type == "bearer"
|
|
|
|
def test_token_payload_schema(self):
|
|
"""Test TokenPayload schema"""
|
|
payload = TokenPayload(sub="42", type="access")
|
|
assert payload.sub == "42"
|
|
assert payload.type == "access"
|
|
|
|
def test_google_auth_request(self):
|
|
"""Test GoogleAuthRequest schema"""
|
|
req = GoogleAuthRequest(
|
|
code="auth-code-123", redirect_uri="http://localhost:3000/callback"
|
|
)
|
|
assert req.code == "auth-code-123"
|
|
|
|
|
|
class TestMailAccountSchemas:
|
|
"""Test mail account schemas"""
|
|
|
|
def test_mail_account_create_valid(self):
|
|
"""Test creating a valid mail account"""
|
|
account = MailAccountCreate(
|
|
name="Test Account",
|
|
email_address="user@example.com",
|
|
host="imap.example.com",
|
|
port=993,
|
|
username="user@example.com",
|
|
password="secret",
|
|
forward_to="me@gmail.com",
|
|
)
|
|
assert account.name == "Test Account"
|
|
assert account.protocol == MailProtocol.POP3_SSL # default
|
|
assert account.use_ssl is True
|
|
|
|
def test_mail_account_create_invalid_port(self):
|
|
"""Test that invalid port is rejected"""
|
|
with pytest.raises(ValidationError):
|
|
MailAccountCreate(
|
|
name="Test",
|
|
email_address="user@example.com",
|
|
host="imap.example.com",
|
|
port=0, # invalid
|
|
username="user@example.com",
|
|
password="secret",
|
|
forward_to="me@gmail.com",
|
|
)
|
|
|
|
def test_mail_account_create_port_too_high(self):
|
|
"""Test that port above 65535 is rejected"""
|
|
with pytest.raises(ValidationError):
|
|
MailAccountCreate(
|
|
name="Test",
|
|
email_address="user@example.com",
|
|
host="imap.example.com",
|
|
port=70000, # invalid
|
|
username="user@example.com",
|
|
password="secret",
|
|
forward_to="me@gmail.com",
|
|
)
|
|
|
|
def test_mail_account_update_partial(self):
|
|
"""Test partial mail account update"""
|
|
update = MailAccountUpdate(is_enabled=False)
|
|
assert update.is_enabled is False
|
|
assert update.name is None
|
|
assert update.password is None
|
|
|
|
def test_mail_account_test_request(self):
|
|
"""Test mail account test connection schema"""
|
|
req = MailAccountTestRequest(
|
|
host="imap.gmail.com",
|
|
port=993,
|
|
protocol=MailProtocol.IMAP_SSL,
|
|
username="user@gmail.com",
|
|
password="app-password",
|
|
)
|
|
assert req.host == "imap.gmail.com"
|
|
|
|
def test_auto_detect_request(self):
|
|
"""Test auto-detect request schema"""
|
|
req = MailAccountAutoDetectRequest(email_address="user@gmail.com")
|
|
assert req.email_address == "user@gmail.com"
|
|
|
|
def test_auto_detect_invalid_email(self):
|
|
"""Test auto-detect rejects invalid email"""
|
|
with pytest.raises(ValidationError):
|
|
MailAccountAutoDetectRequest(email_address="not-email")
|
|
|
|
|
|
class TestNotificationSchemas:
|
|
"""Test notification schemas"""
|
|
|
|
def test_notification_config_create(self):
|
|
"""Test creating notification config"""
|
|
config = NotificationConfigCreate(
|
|
channel=NotificationChannel.TELEGRAM,
|
|
config={"bot_token": "123:abc", "chat_id": "456"},
|
|
)
|
|
assert config.channel == NotificationChannel.TELEGRAM
|
|
assert config.notify_on_errors is True # default
|
|
assert config.notify_on_success is False # default
|
|
|
|
def test_notification_config_threshold_validation(self):
|
|
"""Test notification threshold validation"""
|
|
with pytest.raises(ValidationError):
|
|
NotificationConfigCreate(
|
|
channel=NotificationChannel.EMAIL,
|
|
config={},
|
|
notify_threshold=0, # must be > 0
|
|
)
|
|
|
|
|
|
class TestSubscriptionSchemas:
|
|
"""Test subscription schemas"""
|
|
|
|
def test_subscription_checkout_request_monthly(self):
|
|
"""Test subscription checkout with monthly billing"""
|
|
req = SubscriptionCheckoutRequest(
|
|
tier=SubscriptionTier.PRO,
|
|
billing_period="monthly",
|
|
success_url="https://example.com/success",
|
|
cancel_url="https://example.com/cancel",
|
|
)
|
|
assert req.tier == SubscriptionTier.PRO
|
|
assert req.billing_period == "monthly"
|
|
|
|
def test_subscription_checkout_request_yearly(self):
|
|
"""Test subscription checkout with yearly billing"""
|
|
req = SubscriptionCheckoutRequest(
|
|
tier=SubscriptionTier.BASIC,
|
|
billing_period="yearly",
|
|
success_url="https://example.com/success",
|
|
cancel_url="https://example.com/cancel",
|
|
)
|
|
assert req.billing_period == "yearly"
|
|
|
|
def test_subscription_checkout_invalid_period(self):
|
|
"""Test that invalid billing period is rejected"""
|
|
with pytest.raises(ValidationError):
|
|
SubscriptionCheckoutRequest(
|
|
tier=SubscriptionTier.BASIC,
|
|
billing_period="quarterly", # invalid
|
|
success_url="https://example.com/success",
|
|
cancel_url="https://example.com/cancel",
|
|
)
|
|
|
|
|
|
class TestProviderPresetSchema:
|
|
"""Test provider preset schema"""
|
|
|
|
def test_provider_preset_with_imap(self):
|
|
"""Test provider preset with IMAP config"""
|
|
preset = ProviderPreset(
|
|
id="gmail",
|
|
name="Gmail",
|
|
domains=["gmail.com", "googlemail.com"],
|
|
imap_ssl={"host": "imap.gmail.com", "port": 993},
|
|
)
|
|
assert preset.id == "gmail"
|
|
assert "gmail.com" in preset.domains
|
|
|
|
def test_provider_preset_without_pop3(self):
|
|
"""Test provider preset without POP3 (IMAP only)"""
|
|
preset = ProviderPreset(
|
|
id="posteo",
|
|
name="Posteo",
|
|
domains=["posteo.de"],
|
|
imap_ssl={"host": "posteo.de", "port": 993},
|
|
)
|
|
assert preset.pop3_ssl is None
|