197 lines
6.5 KiB
Python
197 lines
6.5 KiB
Python
"""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"
|