diff --git a/novelforge/names.py b/novelforge/names.py index 60faf99..24b0991 100644 --- a/novelforge/names.py +++ b/novelforge/names.py @@ -51,10 +51,11 @@ # Validate that every allowed genre has a name-pool mapping. _missing_name_genres = ALLOWED_GENRES - _GENRE_GROUP.keys() -assert not _missing_name_genres, ( - f"Genres missing from _GENRE_GROUP in names.py: {sorted(_missing_name_genres)}. " - f"Add a style-group mapping for each." -) +if _missing_name_genres: + raise ValueError( + f"Genres missing from _GENRE_GROUP in names.py: {sorted(_missing_name_genres)}. " + f"Add a style-group mapping for each." + ) # --------------------------------------------------------------------------- # Name pool data diff --git a/novelforge/voice.py b/novelforge/voice.py index 391aa5f..09e9213 100644 --- a/novelforge/voice.py +++ b/novelforge/voice.py @@ -241,10 +241,11 @@ # Validate that every allowed genre has voice weights. _missing_voice_genres = ALLOWED_GENRES - _GENRE_VOICE_WEIGHTS.keys() -assert not _missing_voice_genres, ( - f"Genres missing from _GENRE_VOICE_WEIGHTS in voice.py: {sorted(_missing_voice_genres)}. " - f"Add a voice weight mapping for each." -) +if _missing_voice_genres: + raise ValueError( + f"Genres missing from _GENRE_VOICE_WEIGHTS in voice.py: {sorted(_missing_voice_genres)}. " + f"Add a voice weight mapping for each." + ) def select_voice_seed(genre: str = "", premise: str = "") -> dict[str, str]: diff --git a/tests/test_names.py b/tests/test_names.py index 1eb9f3a..bed284f 100644 --- a/tests/test_names.py +++ b/tests/test_names.py @@ -1,7 +1,11 @@ """Tests for the novelforge.names module (genre-aware name pools).""" +import importlib + import pytest +import novelforge.names +import novelforge.validation from novelforge.names import ( _GENRE_GROUP, _NAMES, @@ -167,3 +171,22 @@ def test_output_mentions_male_female_last(self): assert "male first names" in result.lower() assert "female first names" in result.lower() assert "last names" in result.lower() + + +# --------------------------------------------------------------------------- +# Genre coverage validation raises ValueError (not silenced by -O flag) +# --------------------------------------------------------------------------- + +class TestNameGenreValidation: + """names.py must raise ValueError for missing genre mappings even under -O.""" + + def test_raises_value_error_when_genre_missing_from_genre_group(self, monkeypatch): + """Reloading names.py with an extra ALLOWED_GENRES entry raises ValueError.""" + extended = novelforge.validation.ALLOWED_GENRES | {"FakeGenreForTest"} + monkeypatch.setattr(novelforge.validation, "ALLOWED_GENRES", extended) + with pytest.raises(ValueError, match="FakeGenreForTest"): + importlib.reload(novelforge.names) + + def test_no_error_when_all_genres_covered(self): + """Reloading names.py with the standard ALLOWED_GENRES does not raise.""" + importlib.reload(novelforge.names) diff --git a/tests/test_voice.py b/tests/test_voice.py index 674f714..5049a92 100644 --- a/tests/test_voice.py +++ b/tests/test_voice.py @@ -2,10 +2,13 @@ Tests for novelforge/voice.py — voice seed selection and formatting. """ +import importlib import random import pytest +import novelforge.validation +import novelforge.voice from novelforge.voice import ( _PREMISE_KEYWORD_BOOST, _PREMISE_KEYWORDS, @@ -212,3 +215,23 @@ def test_embeds_seed_content(self): assert seed["prose_style"] in prompt assert seed["emotional_register"] in prompt assert seed["sensory_preference"] in prompt + + + +# --------------------------------------------------------------------------- +# Genre coverage validation raises ValueError (not silenced by -O flag) +# --------------------------------------------------------------------------- + +class TestVoiceGenreValidation: + """voice.py must raise ValueError for missing genre mappings even under -O.""" + + def test_raises_value_error_when_genre_missing_from_voice_weights(self, monkeypatch): + """Reloading voice.py with an extra ALLOWED_GENRES entry raises ValueError.""" + extended = novelforge.validation.ALLOWED_GENRES | {"FakeGenreForTest"} + monkeypatch.setattr(novelforge.validation, "ALLOWED_GENRES", extended) + with pytest.raises(ValueError, match="FakeGenreForTest"): + importlib.reload(novelforge.voice) + + def test_no_error_when_all_genres_covered(self): + """Reloading voice.py with the standard ALLOWED_GENRES does not raise.""" + importlib.reload(novelforge.voice)