"""Tests for db.py — SQLite database layer with FSRS integration.""" import os import sys import tempfile from pathlib import Path from unittest.mock import patch import pytest # Add project root to path sys.path.insert(0, str(Path(__file__).parent.parent)) import fsrs @pytest.fixture(autouse=True) def temp_db(tmp_path): """Use a temporary database for each test.""" import db as db_mod db_mod._conn = None db_mod.DB_PATH = tmp_path / "test.db" db_mod.init_db() yield db_mod db_mod.close() def test_init_db_creates_tables(temp_db): """init_db should create all required tables.""" conn = temp_db.get_connection() tables = conn.execute( "SELECT name FROM sqlite_master WHERE type='table'" ).fetchall() table_names = {row["name"] for row in tables} assert "word_progress" in table_names assert "quiz_sessions" in table_names assert "essays" in table_names assert "tutor_sessions" in table_names def test_get_word_progress_nonexistent(temp_db): """Should return None for a word that hasn't been reviewed.""" assert temp_db.get_word_progress("nonexistent") is None def test_update_and_get_word_progress(temp_db): """update_word_progress should create and update progress.""" card = temp_db.update_word_progress("verb_go", fsrs.Rating.Good) assert card is not None assert card.stability is not None progress = temp_db.get_word_progress("verb_go") assert progress is not None assert progress["word_id"] == "verb_go" assert progress["reps"] == 1 assert progress["fsrs_state"] is not None def test_update_word_progress_increments_reps(temp_db): """Reviewing the same word multiple times should increment reps.""" temp_db.update_word_progress("verb_go", fsrs.Rating.Good) temp_db.update_word_progress("verb_go", fsrs.Rating.Easy) progress = temp_db.get_word_progress("verb_go") assert progress["reps"] == 2 def test_get_due_words(temp_db): """get_due_words should return words that are due for review.""" # A newly reviewed word with Rating.Again should be due soon temp_db.update_word_progress("verb_go", fsrs.Rating.Again) # An easy word should have a later due date temp_db.update_word_progress("verb_eat", fsrs.Rating.Easy) # Due words depend on timing; at minimum both should be in the system all_progress = temp_db.get_connection().execute( "SELECT word_id FROM word_progress" ).fetchall() assert len(all_progress) == 2 def test_get_word_counts(temp_db): """get_word_counts should return correct counts.""" counts = temp_db.get_word_counts(total_vocab_size=100) assert counts["total"] == 100 assert counts["seen"] == 0 assert counts["mastered"] == 0 assert counts["due"] == 0 temp_db.update_word_progress("verb_go", fsrs.Rating.Good) counts = temp_db.get_word_counts(total_vocab_size=100) assert counts["seen"] == 1 def test_record_quiz_session(temp_db): """record_quiz_session should insert a quiz record.""" temp_db.record_quiz_session("Common verbs", 10, 7, 120) rows = temp_db.get_connection().execute( "SELECT * FROM quiz_sessions" ).fetchall() assert len(rows) == 1 assert rows[0]["correct"] == 7 assert rows[0]["total_questions"] == 10 def test_save_essay(temp_db): """save_essay should store the essay and feedback.""" temp_db.save_essay("متن آزمایشی", "B1", "Good effort!", "Identity and culture") essays = temp_db.get_recent_essays() assert len(essays) == 1 assert essays[0]["grade"] == "B1" def test_save_tutor_session(temp_db): """save_tutor_session should store the conversation.""" messages = [ {"role": "user", "content": "سلام"}, {"role": "assistant", "content": "سلام! حالت چطوره؟"}, ] temp_db.save_tutor_session("Identity and culture", messages, 300) rows = temp_db.get_connection().execute( "SELECT * FROM tutor_sessions" ).fetchall() assert len(rows) == 1 assert rows[0]["theme"] == "Identity and culture" def test_get_stats(temp_db): """get_stats should return aggregated stats.""" stats = temp_db.get_stats() assert stats["total_reviews"] == 0 assert stats["total_quizzes"] == 0 assert stats["streak"] == 0 assert isinstance(stats["recent_quizzes"], list) def test_close_and_reopen(temp_db): """Closing and reopening should preserve data.""" temp_db.update_word_progress("verb_go", fsrs.Rating.Good) db_path = temp_db.DB_PATH temp_db.close() # Reopen temp_db._conn = None temp_db.DB_PATH = db_path temp_db.init_db() progress = temp_db.get_word_progress("verb_go") assert progress is not None assert progress["reps"] == 1