"""Unit tests for database schema verification. Tests that the database schema supports all fields that were previously stored in file-based storage (key/data files). Ref: Task 1 - Verify Database Schema Supports All File-Based Data """ from __future__ import annotations import pytest from sqlalchemy import create_engine, select from sqlalchemy.orm import Session, sessionmaker from src.server.database.base import Base from src.server.database.models import AnimeSeries, Episode @pytest.fixture def db_engine(): """Create in-memory SQLite database engine for testing.""" engine = create_engine("sqlite:///:memory:", echo=False) Base.metadata.create_all(engine) return engine @pytest.fixture def db_session(db_engine): """Create database session for testing.""" SessionLocal = sessionmaker(bind=db_engine) session = SessionLocal() yield session session.close() class TestAnimeSeriesHasAllRequiredFields: """Verify AnimeSeries model has all Serie properties.""" def test_anime_series_has_id_column(self, db_session: Session): """Test that AnimeSeries has an id primary key column.""" series = AnimeSeries( key="test-key", name="Test Series", site="https://example.com", folder="/anime/test", ) db_session.add(series) db_session.commit() result = db_session.execute(select(AnimeSeries.id).where(AnimeSeries.key == "test-key")) assert result.scalar_one_or_none() is not None def test_anime_series_has_key_column(self, db_session: Session): """Test that AnimeSeries has a key column for provider identifier.""" series = AnimeSeries( key="unique-provider-key", name="Test Series", site="https://example.com", folder="/anime/test", ) db_session.add(series) db_session.commit() result = db_session.execute(select(AnimeSeries.key).where(AnimeSeries.key == "unique-provider-key")) assert result.scalar_one_or_none() == "unique-provider-key" def test_anime_series_has_name_column(self, db_session: Session): """Test that AnimeSeries has a name column.""" series = AnimeSeries( key="name-test", name="My Custom Name", site="https://example.com", folder="/anime/test", ) db_session.add(series) db_session.commit() result = db_session.execute(select(AnimeSeries.name).where(AnimeSeries.key == "name-test")) assert result.scalar_one_or_none() == "My Custom Name" def test_anime_series_has_site_column(self, db_session: Session): """Test that AnimeSeries has a site column.""" series = AnimeSeries( key="site-test", name="Test Series", site="https://aniworld.to/watch/series", folder="/anime/test", ) db_session.add(series) db_session.commit() result = db_session.execute(select(AnimeSeries.site).where(AnimeSeries.key == "site-test")) assert result.scalar_one_or_none() == "https://aniworld.to/watch/series" def test_anime_series_has_folder_column(self, db_session: Session): """Test that AnimeSeries has a folder column.""" series = AnimeSeries( key="folder-test", name="Test Series", site="https://example.com", folder="/anime/My Series Folder (2024)", ) db_session.add(series) db_session.commit() result = db_session.execute(select(AnimeSeries.folder).where(AnimeSeries.key == "folder-test")) assert result.scalar_one_or_none() == "/anime/My Series Folder (2024)" def test_anime_series_has_year_column(self, db_session: Session): """Test that AnimeSeries has an optional year column.""" series = AnimeSeries( key="year-test", name="Test Series", site="https://example.com", folder="/anime/test", year=2024, ) db_session.add(series) db_session.commit() result = db_session.execute(select(AnimeSeries.year).where(AnimeSeries.key == "year-test")) assert result.scalar_one_or_none() == 2024 def test_anime_series_year_is_nullable(self, db_session: Session): """Test that year column is optional (nullable).""" series = AnimeSeries( key="no-year-test", name="Test Series", site="https://example.com", folder="/anime/test", ) db_session.add(series) db_session.commit() result = db_session.execute(select(AnimeSeries.year).where(AnimeSeries.key == "no-year-test")) assert result.scalar_one_or_none() is None def test_anime_series_has_nfo_path_column(self, db_session: Session): """Test that AnimeSeries has an optional nfo_path column.""" series = AnimeSeries( key="nfo-path-test", name="Test Series", site="https://example.com", folder="/anime/test", nfo_path="/anime/test/tvshow.nfo", ) db_session.add(series) db_session.commit() result = db_session.execute(select(AnimeSeries.nfo_path).where(AnimeSeries.key == "nfo-path-test")) assert result.scalar_one_or_none() == "/anime/test/tvshow.nfo" def test_anime_series_nfo_path_is_nullable(self, db_session: Session): """Test that nfo_path column is optional (nullable).""" series = AnimeSeries( key="no-nfo-path-test", name="Test Series", site="https://example.com", folder="/anime/test", ) db_session.add(series) db_session.commit() result = db_session.execute(select(AnimeSeries.nfo_path).where(AnimeSeries.key == "no-nfo-path-test")) assert result.scalar_one_or_none() is None def test_anime_series_has_timestamps(self, db_session: Session): """Test that AnimeSeries has created_at and updated_at timestamps.""" series = AnimeSeries( key="timestamps-test", name="Test Series", site="https://example.com", folder="/anime/test", ) db_session.add(series) db_session.commit() assert series.created_at is not None assert series.updated_at is not None class TestEpisodeModelTracksMissingEpisodes: """Verify Episode model can store missing episodes.""" def test_episode_has_season_column(self, db_session: Session): """Test that Episode has a season column.""" series = AnimeSeries( key="episode-season-test", name="Test Series", site="https://example.com", folder="/anime/test", ) db_session.add(series) db_session.commit() episode = Episode( series_id=series.id, season=2, episode_number=5, ) db_session.add(episode) db_session.commit() result = db_session.execute(select(Episode.season).where(Episode.id == episode.id)) assert result.scalar_one_or_none() == 2 def test_episode_has_episode_number_column(self, db_session: Session): """Test that Episode has an episode_number column.""" series = AnimeSeries( key="episode-num-test", name="Test Series", site="https://example.com", folder="/anime/test", ) db_session.add(series) db_session.commit() episode = Episode( series_id=series.id, season=1, episode_number=12, ) db_session.add(episode) db_session.commit() result = db_session.execute(select(Episode.episode_number).where(Episode.id == episode.id)) assert result.scalar_one_or_none() == 12 def test_episode_has_is_downloaded_column(self, db_session: Session): """Test that Episode has an is_downloaded column.""" series = AnimeSeries( key="downloaded-test", name="Test Series", site="https://example.com", folder="/anime/test", ) db_session.add(series) db_session.commit() episode = Episode( series_id=series.id, season=1, episode_number=1, is_downloaded=True, ) db_session.add(episode) db_session.commit() result = db_session.execute(select(Episode.is_downloaded).where(Episode.id == episode.id)) assert result.scalar_one_or_none() is True def test_episode_is_downloaded_defaults_false(self, db_session: Session): """Test that is_downloaded defaults to False.""" series = AnimeSeries( key="default-downloaded-test", name="Test Series", site="https://example.com", folder="/anime/test", ) db_session.add(series) db_session.commit() episode = Episode( series_id=series.id, season=1, episode_number=1, ) db_session.add(episode) db_session.commit() result = db_session.execute(select(Episode.is_downloaded).where(Episode.id == episode.id)) assert result.scalar_one_or_none() is False def test_episode_has_series_id_foreign_key(self, db_session: Session): """Test that Episode has a series_id foreign key.""" series = AnimeSeries( key="fk-test", name="Test Series", site="https://example.com", folder="/anime/test", ) db_session.add(series) db_session.commit() episode = Episode( series_id=series.id, season=1, episode_number=1, ) db_session.add(episode) db_session.commit() result = db_session.execute(select(Episode.series_id).where(Episode.id == episode.id)) assert result.scalar_one_or_none() == series.id class TestEpisodeRelationshipFromSeries: """Verify Series.episodes relationship works.""" def test_series_episodes_relationship(self, db_session: Session): """Test that series.episodes returns all episodes.""" series = AnimeSeries( key="episodes-rel-test", name="Test Series", site="https://example.com", folder="/anime/test", ) db_session.add(series) db_session.commit() episode1 = Episode( series_id=series.id, season=1, episode_number=1, title="First Episode", ) episode2 = Episode( series_id=series.id, season=1, episode_number=2, title="Second Episode", ) episode3 = Episode( series_id=series.id, season=2, episode_number=1, title="Season 2 Premiere", ) db_session.add_all([episode1, episode2, episode3]) db_session.commit() assert len(series.episodes) == 3 episode_titles = [ep.title for ep in series.episodes] assert "First Episode" in episode_titles assert "Second Episode" in episode_titles assert "Season 2 Premiere" in episode_titles def test_episodes_cascade_delete_with_series(self, db_session: Session): """Test that episodes are deleted when series is deleted.""" series = AnimeSeries( key="cascade-delete-test", name="Test Series", site="https://example.com", folder="/anime/test", ) db_session.add(series) db_session.commit() episode = Episode( series_id=series.id, season=1, episode_number=1, ) db_session.add(episode) db_session.commit() series_id = series.id episode_id = episode.id db_session.delete(series) db_session.commit() result = db_session.execute(select(Episode).where(Episode.id == episode_id)) assert result.scalar_one_or_none() is None def test_series_episodes_filtered_by_season(self, db_session: Session): """Test that episodes relationship returns all seasons.""" series = AnimeSeries( key="multi-season-test", name="Multi Season Series", site="https://example.com", folder="/anime/test", ) db_session.add(series) db_session.commit() for season in range(1, 4): for ep_num in range(1, 4): episode = Episode( series_id=series.id, season=season, episode_number=ep_num, ) db_session.add(episode) db_session.commit() assert len(series.episodes) == 9 seasons = {ep.season for ep in series.episodes} assert seasons == {1, 2, 3}