Skip to main content

Unit Tests

All unit tests are in tests/test_unit.py and run with pytest.

How to Run

# from the project root
pytest tests/test_unit.py -v

Overview

Unit tests verify individual functions and service layer logic in isolation. No real server is started. Database-touching tests use the test SQLite instance initialized by init_db(). File I/O tests use tmp_path fixtures that are automatically cleaned up after each run.

Test runtime files stay inside repo-local temporary folders and are cleaned after the run, so pytest should not leave random tmp_* project folders behind.


Video Timestamp Helpers

TestWhat it checksFails if
test_time_to_seconds_mmss"1:30"90returns a value other than 90, or crashes on valid input
test_time_to_seconds_hhmmss"1:00:00"3600hours are ignored or parsed incorrectly
test_time_to_seconds_bad_input"bad"0raises an exception instead of returning 0
test_time_to_seconds_noneNone raises AttributeErrorsilently returns a value instead of raising
test_time_to_seconds_seconds_only"45"45single-segment input is misread as minutes
test_time_to_seconds_hhmmss_full"2:30:15"9015hours or seconds component is dropped

Text Normalization

TestWhat it checksFails if
test_normalize_text_removes_stopwords"the big dog""big dog"stopwords are kept, causing fuzzy match to underperform
test_normalize_text_maps_synonyms"scared""afraid"synonym is not mapped, so a correct-but-different-word answer is marked wrong
test_normalize_text_emptyempty string → empty stringreturns None or raises on empty input

Segment Builder

TestWhat it checksFails if
test_build_segments_standard180s at 60s intervals → 4 segmentswrong segment count means questions appear at wrong times
test_build_segments_shorter_lastlast segment shorter than intervallast segment is dropped or padded to full interval
test_build_segments_singlesingle 60s segmentreturns empty list or two segments for a short video

Video Assignment (Many-to-Many)

TestWhat it checksFails if
test_add_assignmentadding a video-expert pair persists correctlyassignment is not saved to DB
test_two_experts_same_videotwo parents can claim the same videosecond claim overwrites the first
test_remove_assignmentremoving a pair deletes only that rowother assignments are also deleted
test_claim_is_idempotentclaiming the same video twice does not duplicateduplicate rows are inserted
test_claim_video_calls_add_assignmentclaim flow calls correct service functionwrong service function is called
test_unclaim_video_calls_remove_assignmentunclaim flow calls correct service functionunclaim silently does nothing

Child Management

TestWhat it checksFails if
test_generate_child_id_is_6_digitchild IDs are always 6 numeric digitsshorter IDs or non-numeric IDs are generated
test_create_and_list_childcreated child appears in listchild is saved but not retrievable
test_same_name_different_experts_allowedsame name under different parents is validchild creation is incorrectly rejected
test_duplicate_name_same_expert_now_allowedduplicate names allowed under same parentcreation is incorrectly blocked
test_invalid_icon_rejectedunknown icon key raises ValueErrorinvalid icon is silently accepted and stored
test_update_and_deactivate_childupdating fields and deactivating work correctlyfields do not persist or child remains active after deactivation
test_new_icon_keys_are_validall current icon keys pass validationa valid icon key is rejected after being added
test_bad_icon_still_rejectedinvalid icon key still fails after new icons addedvalidation is too loose and accepts anything
test_normalize_child_id_strips_whitespacewhitespace trimmed from child IDID with spaces fails lookup even though it should match
test_normalize_child_id_emptyempty string returns emptyraises instead of returning empty
test_normalize_name_collapses_spacesextra spaces collapsed in namename stored with inconsistent spacing
test_normalize_name_emptyempty name returns emptyraises on empty name input
test_normalize_icon_key_lowercasesicon key lowercased on save"Pig" and "pig" treated as different icons
test_delete_child_removes_recorddeleted child no longer in DBchild record remains after delete
test_delete_child_nonexistent_returns_falsedeleting unknown child returns falseraises an exception instead of returning false

Password Hashing

TestWhat it checksFails if
test_hash_password_is_not_plaintextstored hash differs from raw passwordpassword is stored in plain text (security failure)
test_verify_password_correctcorrect password verifies successfullyvalid login is rejected
test_verify_password_wrongwrong password fails verificationincorrect password is accepted (authentication bypass)

Database Schema

These tests use a temporary in-memory SQLite instance that is created fresh for each run and wiped clean after. No real data is stored and nothing carries over between test runs - it is safe and isolated.

TestWhat it checksFails if
test_parents_table_existsparents table created on inittable is missing and parent login crashes
test_parents_table_has_correct_columnsall expected columns presenta missing column causes runtime errors on insert or select
test_children_has_parent_id_columnchildren table has parent_id FKparent-child linking is silently broken
test_parents_table_has_login_code_columnlogin_code plain text column existsaccess code lookup fails at runtime
test_upsert_login_code_stores_plain_and_hashaccess code stored in both plain and hashed formplain code is missing so parent login with custom code fails

Report Service

TestWhat it checksFails if
test_compute_top_categories_correct_scorescorrect answer scores 100% for its categorycorrect answer is underscored, unfairly lowering the report
test_compute_top_categories_almost_is_half_pointalmost answer scores 50% for its categoryalmost is treated as fully correct or fully wrong
test_compute_top_categories_wrong_yields_zerowrong answer scores 0% for its categorywrong answer contributes to score, inflating the report
test_report_empty_when_no_attemptschild with no history returns zeroed-out reportreport crashes or returns None for a new child

Downloader Logic

TestWhat it checksFails if
test_select_auth_profile_prefers_windows_browser_orderWindows downloader tries Chrome, Firefox, then Edgewrong browser order causes unnecessary auth failures
test_select_auth_profile_mac_order_skips_safarimacOS downloader prefers Chrome and Firefox, not SafariSafari is tried first and fails on protected videos
test_metadata_and_download_opts_share_browser_authmetadata and download requests use the same browser authauth mismatch causes metadata to succeed but download to fail
test_select_auth_profile_falls_back_to_cookiefilecookie file is used when browser probing failsdownloader gives up instead of trying the cookie fallback
test_classify_auth_error_returns_stable_code_and_hintprotected-download auth failures map to stable API error fieldsfrontend receives a raw crash message instead of a useful error
test_subtitle_opts_reuse_used_player_clientsubtitle fetches reuse the successful player clientsubtitle fetch uses a different client and fails on protected videos
test_preferred_download_format_has_broad_ffmpeg_fallbackFFmpeg-enabled format selection keeps a broader fallbackdownload fails when the preferred format is unavailable
test_download_with_format_fallback_keeps_player_client_firstformat fallback does not drop the chosen protected-video client too earlyclient is dropped prematurely and protected video download fails
test_apply_runtime_options_enables_remote_ejs_componentsdownloader enables remote EJS components for YouTube JS challengesYouTube JS challenge breaks the download silently
test_resolve_ffmpeg_path_uses_winget_linkWindows Winget FFmpeg install can be discovered automaticallyFFmpeg is not found and video processing fails on Windows
test_repair_invalid_mp4_replaces_fileinvalid HLS .mp4 downloads are repaired into valid MP4 filescorrupted file is kept and video playback fails

Quiz Scoring Service

TestWhat it checksFails if
test_save_quiz_result_creates_filesaving a quiz result creates the JSON fileresult is lost and never written to disk
test_save_quiz_result_appends_attemptstwo sessions for the same child both appear in historysecond attempt overwrites the first
test_save_quiz_result_stores_manual_pausesmanual pause count is saved with the attemptpause count is always 0 in the report
test_get_child_scores_no_filereturns failure when no score file existscrashes instead of returning a clean error
test_save_quiz_result_checkpoint_only_updates_watch_timecheckpoint save adds watch time and updates pause count without overwriting quiz scorescheckpoint overwrites correct/wrong counts mid-session

Video Files

TestWhat it checksFails if
test_find_primary_video_file_finds_mp4.mp4 file in a directory is found correctlyvideo does not appear in the library
test_find_primary_video_file_skips_audio_onlyaudio-only .m4a file is not returned as a videoaudio file is treated as a video and playback fails
test_find_primary_video_file_prefers_merged_over_fragmentmerged mp4 is preferred over fragmented downloadfragment without audio is served instead of the full video
test_find_primary_video_file_empty_dirempty directory returns Nonecrashes instead of returning nothing
test_find_primary_video_file_nonexistent_dirnonexistent path returns Nonecrashes on missing directory
test_list_question_json_files_returns_filesquestion JSON files are found and listed correctlyquestions are not available for review or playback
test_list_question_json_files_emptydirectory with no questions returns empty listcrashes or returns garbage on a fresh install
test_list_question_json_files_no_dirmissing downloads directory returns empty listcrashes on first run before any videos are downloaded