"""Tests for Pydantic data models (backend/models.py).""" import pytest from pydantic import ValidationError from models import ( UserCreate, UserUpdate, EncryptionMetadata, JournalEntryCreate, JournalEntryUpdate, MoodEnum, ) # --------------------------------------------------------------------------- # UserCreate # --------------------------------------------------------------------------- class TestUserCreate: def test_requires_email(self): with pytest.raises(ValidationError): UserCreate() def test_valid_email_only(self): user = UserCreate(email="test@example.com") assert user.email == "test@example.com" def test_display_name_is_optional(self): user = UserCreate(email="test@example.com") assert user.displayName is None def test_photo_url_is_optional(self): user = UserCreate(email="test@example.com") assert user.photoURL is None def test_all_fields(self): user = UserCreate( email="test@example.com", displayName="Alice", photoURL="https://example.com/pic.jpg", ) assert user.displayName == "Alice" assert user.photoURL == "https://example.com/pic.jpg" # --------------------------------------------------------------------------- # UserUpdate # --------------------------------------------------------------------------- class TestUserUpdate: def test_all_fields_optional(self): update = UserUpdate() assert update.displayName is None assert update.photoURL is None assert update.theme is None def test_update_only_theme(self): update = UserUpdate(theme="dark") assert update.theme == "dark" assert update.displayName is None def test_update_only_display_name(self): update = UserUpdate(displayName="New Name") assert update.displayName == "New Name" assert update.theme is None def test_model_dump_excludes_unset(self): update = UserUpdate(theme="dark") dumped = update.model_dump(exclude_unset=True) assert "theme" in dumped assert "displayName" not in dumped # --------------------------------------------------------------------------- # EncryptionMetadata # --------------------------------------------------------------------------- class TestEncryptionMetadata: def test_requires_ciphertext(self): with pytest.raises(ValidationError): EncryptionMetadata(nonce="abc") def test_requires_nonce(self): with pytest.raises(ValidationError): EncryptionMetadata(ciphertext="abc") def test_requires_both_ciphertext_and_nonce(self): with pytest.raises(ValidationError): EncryptionMetadata() def test_default_algorithm_is_xsalsa20(self): meta = EncryptionMetadata(ciphertext="abc", nonce="xyz") assert meta.algorithm == "XSalsa20-Poly1305" def test_default_encrypted_is_true(self): meta = EncryptionMetadata(ciphertext="abc", nonce="xyz") assert meta.encrypted is True def test_valid_full_metadata(self): meta = EncryptionMetadata( encrypted=True, ciphertext="dGVzdA==", nonce="bm9uY2U=", algorithm="XSalsa20-Poly1305", ) assert meta.ciphertext == "dGVzdA==" assert meta.nonce == "bm9uY2U=" def test_custom_algorithm_accepted(self): meta = EncryptionMetadata(ciphertext="abc", nonce="xyz", algorithm="AES-256-GCM") assert meta.algorithm == "AES-256-GCM" # --------------------------------------------------------------------------- # JournalEntryCreate # --------------------------------------------------------------------------- class TestJournalEntryCreate: def test_all_fields_optional(self): entry = JournalEntryCreate() assert entry.title is None assert entry.content is None assert entry.encryption is None assert entry.mood is None def test_encrypted_entry_has_no_plaintext(self): """Encrypted entries legitimately have no title or content.""" entry = JournalEntryCreate( encryption=EncryptionMetadata(ciphertext="abc", nonce="xyz") ) assert entry.title is None assert entry.content is None assert entry.encryption is not None def test_valid_mood_values(self): for mood in ("happy", "sad", "neutral", "anxious", "grateful"): entry = JournalEntryCreate(mood=mood) assert entry.mood == mood def test_invalid_mood_raises_validation_error(self): with pytest.raises(ValidationError): JournalEntryCreate(mood="ecstatic") def test_default_is_public_is_false(self): entry = JournalEntryCreate() assert entry.isPublic is False def test_tags_default_is_none(self): entry = JournalEntryCreate() assert entry.tags is None def test_tags_list_accepted(self): entry = JournalEntryCreate(tags=["family", "work", "health"]) assert entry.tags == ["family", "work", "health"] # --------------------------------------------------------------------------- # JournalEntryUpdate # --------------------------------------------------------------------------- class TestJournalEntryUpdate: def test_all_fields_optional(self): update = JournalEntryUpdate() assert update.title is None assert update.mood is None def test_update_mood_only(self): update = JournalEntryUpdate(mood="happy") dumped = update.model_dump(exclude_unset=True) assert dumped == {"mood": MoodEnum.happy} def test_invalid_mood_raises_error(self): with pytest.raises(ValidationError): JournalEntryUpdate(mood="angry") def test_update_encryption(self): update = JournalEntryUpdate( encryption=EncryptionMetadata(ciphertext="new_ct", nonce="new_nonce") ) assert update.encryption.ciphertext == "new_ct" # --------------------------------------------------------------------------- # MoodEnum # --------------------------------------------------------------------------- class TestMoodEnum: def test_all_enum_values(self): assert MoodEnum.happy == "happy" assert MoodEnum.sad == "sad" assert MoodEnum.neutral == "neutral" assert MoodEnum.anxious == "anxious" assert MoodEnum.grateful == "grateful" def test_enum_used_in_entry_create(self): entry = JournalEntryCreate(mood=MoodEnum.grateful) assert entry.mood == "grateful"