""" Comprehensive integration tests for the ReadBookMom app. Tests cover: - Module imports and structure - Story loading and text processing - HTML rendering functions (dashboard, library, voices, story text) - Gradio component wiring (inputs/outputs match handler signatures) - State machine transitions (book select → play → pause → ask → resume) - XSS escaping in all HTML renderers - Voice profile integration in player - TTS module chunk splitting - Q&A answer rendering safety """ import os import sys import html as html_module import inspect sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) PASS = 0 FAIL = 0 def check(label, condition, detail=""): global PASS, FAIL if condition: PASS += 1 print(f" [OK] {label}") else: FAIL += 1 print(f" [FAIL] {label} -- {detail}") # ── Module imports ────────────────────────────────────────────── def test_module_imports(): print("\n=== Module imports ===") try: from tts import split_into_chunks, generate_audio_stream check("tts module imports", True) except Exception as e: check("tts module imports", False, str(e)) try: from voice_clone import ( create_voice_profile, synthesize_cloned, synthesize_cloned_preview, synthesize_custom_voice, has_profile, get_model_mode, _trim_reference_audio, _select_dtype, _select_attn_impl, GENERATION_PARAMS, BASE_MODEL_ID, CUSTOM_VOICE_MODEL_ID, ) check("voice_clone module imports", True) except Exception as e: check("voice_clone module imports", False, str(e)) try: from inference import transcribe_audio, answer_story_question check("inference module imports", True) except Exception as e: check("inference module imports", False, str(e)) # ── Story loading ─────────────────────────────────────────────── def test_story_loading(): print("\n=== Story loading ===") from app import load_books_from_stories, load_paragraphs books = load_books_from_stories() check("Stories found", len(books) > 0, f"found {len(books)}") for b in books: required = ["id", "title", "story_path", "cover_url", "voice_name", "percentage"] missing = [k for k in required if k not in b] check(f"Book '{b['title']}' has all keys", len(missing) == 0, f"missing: {missing}") # Test paragraph loading if books: paras = load_paragraphs(books[0]["story_path"]) check(f"Paragraphs loaded from '{books[0]['title']}'", len(paras) > 0, f"got {len(paras)}") # Test non-existent file paras = load_paragraphs("nonexistent_file.txt") check("Non-existent file returns empty list", paras == []) # ── TTS chunk splitting ──────────────────────────────────────── def test_chunk_splitting(): print("\n=== TTS chunk splitting ===") from tts import split_into_chunks chunks = split_into_chunks("Hello world. How are you? I am fine!") check("Splits on sentence boundaries", len(chunks) == 3, f"got {len(chunks)}: {chunks}") chunks = split_into_chunks("Single sentence without ending period") check("Single sentence stays intact", len(chunks) == 1, f"got {len(chunks)}") chunks = split_into_chunks("") check("Empty text returns empty list", len(chunks) == 0) chunks = split_into_chunks(" ") check("Whitespace-only returns empty list", len(chunks) == 0) long_text = "First sentence. Second sentence! Third? Fourth; Fifth." chunks = split_into_chunks(long_text) check("Splits on .!?; delimiters", len(chunks) == 5, f"got {len(chunks)}: {chunks}") # ── HTML rendering ────────────────────────────────────────────── def test_html_rendering(): print("\n=== HTML rendering ===") from app import ( generate_dashboard_html, generate_library_html, render_cloned_voices_html, render_story_text, mock_books, mock_voices, ) # Dashboard dash_html = generate_dashboard_html(mock_books) check("Dashboard renders without error", isinstance(dash_html, str) and len(dash_html) > 100) check("Dashboard contains book titles", any(b["title"] in dash_html for b in mock_books)) # Library lib_html = generate_library_html(mock_books) check("Library renders without error", isinstance(lib_html, str) and len(lib_html) > 100) check("Library contains Tap to Play", "Tap to Play" in lib_html) # Library with search filter if mock_books: first_title = mock_books[0]["title"] filtered = generate_library_html(mock_books, query=first_title) check(f"Library filters to '{first_title}'", first_title in filtered) # Library empty result empty = generate_library_html(mock_books, query="xyznonexistent999") check("Library shows no-match message", "No audiobooks match" in empty) # Cloned voices voices_html = render_cloned_voices_html(mock_voices) check("Voices grid renders", isinstance(voices_html, str) and len(voices_html) > 50) # Story text paras = ["Paragraph one.", "Paragraph two.", "Paragraph three."] story_html = render_story_text(paras, 1) check("Story text highlights current paragraph", "border-left: 3px solid #f5841f" in story_html) check("Story text renders all paragraphs", "Paragraph one" in story_html and "Paragraph three" in story_html) # Empty paragraphs check("Empty paragraphs returns empty string", render_story_text([], 0) == "") # ── XSS escaping ─────────────────────────────────────────────── def test_xss_escaping(): print("\n=== XSS escaping ===") from app import render_story_text, render_cloned_voices_html # Story text with HTML injection xss_paras = ['', 'Normal text'] result = render_story_text(xss_paras, 0) check("Story text escapes