mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-09-16 13:20:39 -07:00
The suite hadn't run green since the routes refactor: - test_profile_duplicate_names.py imported the pre-refactor module layout and broke collection; now imports backend.services.profiles - tests/conftest.py puts the repo root and backend dir on sys.path so files collect standalone instead of depending on run order - test_cors.py tested a hand-copied mirror of the origin list that had drifted from app.py (missing http://tauri.localhost); it now builds the app via the real create_app() factory - test_progress.py simulated a 1KB download, below the tracker's 1MB reporting threshold; simulation raised to 5MB - slow/timeout markers registered in pyproject Ruff: ~900 violations auto-fixed (typing modernization, import sorting, unused imports, whitespace). The remaining rules are baselined in pyproject.toml with per-rule counts to burn down, plus per-file carve-outs for deliberate env-before-import ordering. ruff check is now clean; suite is 134 passed, 2 skipped.
215 lines
6.6 KiB
Python
215 lines
6.6 KiB
Python
"""
|
|
Tests for profile duplicate name validation.
|
|
|
|
This test suite verifies that the application correctly handles
|
|
duplicate profile names and provides user-friendly error messages.
|
|
"""
|
|
|
|
import shutil
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from sqlalchemy import create_engine
|
|
from sqlalchemy.orm import sessionmaker
|
|
|
|
from backend.database import Base
|
|
from backend.models import VoiceProfileCreate
|
|
from backend.services.profiles import create_profile, update_profile
|
|
|
|
|
|
@pytest.fixture
|
|
def test_db():
|
|
"""Create a temporary test database."""
|
|
# Create temporary directory for test database
|
|
temp_dir = tempfile.mkdtemp()
|
|
db_path = Path(temp_dir) / "test.db"
|
|
|
|
# Create engine and session
|
|
engine = create_engine(f"sqlite:///{db_path}")
|
|
Base.metadata.create_all(bind=engine)
|
|
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
|
|
|
db = SessionLocal()
|
|
|
|
yield db
|
|
|
|
# Cleanup
|
|
db.close()
|
|
shutil.rmtree(temp_dir)
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_profiles_dir(monkeypatch, tmp_path):
|
|
"""Mock the profiles directory to use a temporary path."""
|
|
from backend import config
|
|
monkeypatch.setattr(config, 'get_profiles_dir', lambda: tmp_path)
|
|
return tmp_path
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_profile_duplicate_name_raises_error(test_db, mock_profiles_dir):
|
|
"""Test that creating a profile with a duplicate name raises a ValueError."""
|
|
# Create first profile
|
|
profile_data_1 = VoiceProfileCreate(
|
|
name="Test Profile",
|
|
description="First profile",
|
|
language="en"
|
|
)
|
|
|
|
profile_1 = await create_profile(profile_data_1, test_db)
|
|
assert profile_1.name == "Test Profile"
|
|
|
|
# Try to create second profile with same name
|
|
profile_data_2 = VoiceProfileCreate(
|
|
name="Test Profile",
|
|
description="Second profile",
|
|
language="en"
|
|
)
|
|
|
|
with pytest.raises(ValueError) as exc_info:
|
|
await create_profile(profile_data_2, test_db)
|
|
|
|
# Verify error message is user-friendly
|
|
assert "already exists" in str(exc_info.value)
|
|
assert "Test Profile" in str(exc_info.value)
|
|
assert "choose a different name" in str(exc_info.value).lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_profile_different_names_succeeds(test_db, mock_profiles_dir):
|
|
"""Test that creating profiles with different names succeeds."""
|
|
# Create first profile
|
|
profile_data_1 = VoiceProfileCreate(
|
|
name="Profile One",
|
|
description="First profile",
|
|
language="en"
|
|
)
|
|
|
|
profile_1 = await create_profile(profile_data_1, test_db)
|
|
assert profile_1.name == "Profile One"
|
|
|
|
# Create second profile with different name
|
|
profile_data_2 = VoiceProfileCreate(
|
|
name="Profile Two",
|
|
description="Second profile",
|
|
language="en"
|
|
)
|
|
|
|
profile_2 = await create_profile(profile_data_2, test_db)
|
|
assert profile_2.name == "Profile Two"
|
|
|
|
# Verify both profiles exist
|
|
assert profile_1.id != profile_2.id
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_update_profile_to_duplicate_name_raises_error(test_db, mock_profiles_dir):
|
|
"""Test that updating a profile to a duplicate name raises a ValueError."""
|
|
# Create two profiles with different names
|
|
profile_data_1 = VoiceProfileCreate(
|
|
name="Profile A",
|
|
description="First profile",
|
|
language="en"
|
|
)
|
|
profile_1 = await create_profile(profile_data_1, test_db)
|
|
|
|
profile_data_2 = VoiceProfileCreate(
|
|
name="Profile B",
|
|
description="Second profile",
|
|
language="en"
|
|
)
|
|
profile_2 = await create_profile(profile_data_2, test_db)
|
|
|
|
# Try to update profile_2 to use profile_1's name
|
|
update_data = VoiceProfileCreate(
|
|
name="Profile A", # Duplicate name
|
|
description="Updated description",
|
|
language="en"
|
|
)
|
|
|
|
with pytest.raises(ValueError) as exc_info:
|
|
await update_profile(profile_2.id, update_data, test_db)
|
|
|
|
# Verify error message is user-friendly
|
|
assert "already exists" in str(exc_info.value)
|
|
assert "Profile A" in str(exc_info.value)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_update_profile_keep_same_name_succeeds(test_db, mock_profiles_dir):
|
|
"""Test that updating a profile while keeping the same name succeeds."""
|
|
# Create profile
|
|
profile_data = VoiceProfileCreate(
|
|
name="My Profile",
|
|
description="Original description",
|
|
language="en"
|
|
)
|
|
profile = await create_profile(profile_data, test_db)
|
|
|
|
# Update profile with same name but different description
|
|
update_data = VoiceProfileCreate(
|
|
name="My Profile", # Same name
|
|
description="Updated description",
|
|
language="en"
|
|
)
|
|
|
|
updated_profile = await update_profile(profile.id, update_data, test_db)
|
|
|
|
# Verify update succeeded
|
|
assert updated_profile is not None
|
|
assert updated_profile.id == profile.id
|
|
assert updated_profile.name == "My Profile"
|
|
assert updated_profile.description == "Updated description"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_update_profile_to_new_unique_name_succeeds(test_db, mock_profiles_dir):
|
|
"""Test that updating a profile to a new unique name succeeds."""
|
|
# Create profile
|
|
profile_data = VoiceProfileCreate(
|
|
name="Original Name",
|
|
description="Profile description",
|
|
language="en"
|
|
)
|
|
profile = await create_profile(profile_data, test_db)
|
|
|
|
# Update profile with new unique name
|
|
update_data = VoiceProfileCreate(
|
|
name="New Unique Name",
|
|
description="Updated description",
|
|
language="en"
|
|
)
|
|
|
|
updated_profile = await update_profile(profile.id, update_data, test_db)
|
|
|
|
# Verify update succeeded
|
|
assert updated_profile is not None
|
|
assert updated_profile.id == profile.id
|
|
assert updated_profile.name == "New Unique Name"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_case_sensitive_names_allowed(test_db, mock_profiles_dir):
|
|
"""Test that profile names are case-sensitive (e.g., 'Test' and 'test' are different)."""
|
|
# Create profile with lowercase name
|
|
profile_data_1 = VoiceProfileCreate(
|
|
name="test profile",
|
|
description="Lowercase",
|
|
language="en"
|
|
)
|
|
profile_1 = await create_profile(profile_data_1, test_db)
|
|
|
|
# Create profile with different case
|
|
profile_data_2 = VoiceProfileCreate(
|
|
name="Test Profile",
|
|
description="Title case",
|
|
language="en"
|
|
)
|
|
profile_2 = await create_profile(profile_data_2, test_db)
|
|
|
|
# Both should succeed since SQLite unique constraint is case-sensitive by default
|
|
assert profile_1.name == "test profile"
|
|
assert profile_2.name == "Test Profile"
|
|
assert profile_1.id != profile_2.id
|