155 lines
5.4 KiB
Python
155 lines
5.4 KiB
Python
"""Tests for data module validation."""
|
|
|
|
import os
|
|
import sys
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
|
|
|
|
from data import (
|
|
get_categories,
|
|
get_verse_by_id,
|
|
get_verses_by_category,
|
|
get_verses_by_max_difficulty,
|
|
load_verses,
|
|
normalize_text,
|
|
)
|
|
|
|
|
|
class TestLoadVerses:
|
|
"""Tests for verse data loading."""
|
|
|
|
def test_load_returns_list(self) -> None:
|
|
"""Verses load as a list."""
|
|
verses = load_verses()
|
|
assert isinstance(verses, list)
|
|
|
|
def test_minimum_verses(self) -> None:
|
|
"""At least 12 verses loaded."""
|
|
verses = load_verses()
|
|
assert len(verses) >= 12
|
|
|
|
def test_verse_structure(self) -> None:
|
|
"""Each verse has required fields: id, text, category, difficulty, reference."""
|
|
verses = load_verses()
|
|
required_fields = {"id", "text", "category", "difficulty", "reference"}
|
|
for verse in verses:
|
|
assert required_fields.issubset(verse.keys()), (
|
|
f"Missing fields in {verse.get('id', 'unknown')}"
|
|
)
|
|
|
|
def test_unique_ids(self) -> None:
|
|
"""All verse IDs are unique."""
|
|
verses = load_verses()
|
|
ids = [v["id"] for v in verses]
|
|
assert len(ids) == len(set(ids))
|
|
|
|
def test_categories_present(self) -> None:
|
|
"""All four categories represented: Binding, Healing, Exorcism, Protection, Judgment."""
|
|
verses = load_verses()
|
|
categories = get_categories(verses)
|
|
expected = {"Binding", "Healing", "Exorcism", "Protection", "Judgment"}
|
|
assert expected.issubset(set(categories))
|
|
|
|
def test_difficulty_range(self) -> None:
|
|
"""All difficulties are between 1 and 5."""
|
|
verses = load_verses()
|
|
for verse in verses:
|
|
assert 1 <= verse["difficulty"] <= 5
|
|
|
|
def test_easy_verses_count(self) -> None:
|
|
"""At least 4 easy (difficulty 1) verses for starting players."""
|
|
verses = load_verses()
|
|
easy = get_verses_by_max_difficulty(verses, 1)
|
|
assert len(easy) >= 4
|
|
|
|
|
|
class TestNormalizeText:
|
|
"""Tests for text normalization used in verse matching."""
|
|
|
|
def test_removes_punctuation(self) -> None:
|
|
"""Punctuation is stripped from text."""
|
|
result = normalize_text("Hello, world! How are you?")
|
|
assert result == "hello world how are you"
|
|
|
|
def test_lowercases(self) -> None:
|
|
"""Text is converted to lowercase."""
|
|
result = normalize_text("HELLO WORLD")
|
|
assert result == "hello world"
|
|
|
|
def test_collapses_whitespace(self) -> None:
|
|
"""Multiple spaces are collapsed to single space."""
|
|
result = normalize_text("Hello world")
|
|
assert result == "hello world"
|
|
|
|
def test_combined(self) -> None:
|
|
"""Combined punctuation, case, and whitespace normalization."""
|
|
result = normalize_text(" Hello, World! ")
|
|
assert result == "hello world"
|
|
|
|
def test_empty_string(self) -> None:
|
|
"""Empty string returns empty string."""
|
|
assert normalize_text("") == ""
|
|
|
|
def test_identical_verses_match(self) -> None:
|
|
"""Same verse text normalizes to identical strings."""
|
|
verse = "I have given you authority to trample on snakes and scorpions."
|
|
typed = "I have given you authority to trample on snakes and scorpions."
|
|
assert normalize_text(verse) == normalize_text(typed)
|
|
|
|
def test_punctuation_difference_ignored(self) -> None:
|
|
"""Punctuation differences are ignored in normalization."""
|
|
verse = "He heals the brokenhearted and binds up their wounds."
|
|
typed = "He heals the brokenhearted, and binds up their wounds!"
|
|
assert normalize_text(verse) == normalize_text(typed)
|
|
|
|
|
|
class TestGetVerseById:
|
|
"""Tests for verse lookup by ID."""
|
|
|
|
def test_finds_existing_verse(self) -> None:
|
|
"""Existing verse ID returns the verse."""
|
|
verses = load_verses()
|
|
verse = get_verse_by_id(verses, "binding_001")
|
|
assert verse is not None
|
|
assert verse["id"] == "binding_001"
|
|
|
|
def test_returns_none_for_missing(self) -> None:
|
|
"""Non-existent ID returns None."""
|
|
verses = load_verses()
|
|
assert get_verse_by_id(verses, "nonexistent_999") is None
|
|
|
|
|
|
class TestGetVersesByCategory:
|
|
"""Tests for filtering verses by category."""
|
|
|
|
def test_filter_by_category(self) -> None:
|
|
"""Returns only verses matching the category."""
|
|
verses = load_verses()
|
|
binding = get_verses_by_category(verses, "Binding")
|
|
assert all(v["category"] == "Binding" for v in binding)
|
|
assert len(binding) >= 3
|
|
|
|
def test_case_insensitive(self) -> None:
|
|
"""Category filtering is case-insensitive."""
|
|
verses = load_verses()
|
|
result1 = get_verses_by_category(verses, "Binding")
|
|
result2 = get_verses_by_category(verses, "binding")
|
|
assert result1 == result2
|
|
|
|
|
|
class TestGetVersesByMaxDifficulty:
|
|
"""Tests for filtering verses by maximum difficulty."""
|
|
|
|
def test_filters_correctly(self) -> None:
|
|
"""Returns only verses at or below max difficulty."""
|
|
verses = load_verses()
|
|
easy = get_verses_by_max_difficulty(verses, 1)
|
|
assert all(v["difficulty"] <= 1 for v in easy)
|
|
|
|
def test_includes_all_at_level(self) -> None:
|
|
"""All verses up to max difficulty are included."""
|
|
verses = load_verses()
|
|
all_verses = load_verses()
|
|
result = get_verses_by_max_difficulty(verses, 5)
|
|
assert len(result) == len(all_verses)
|