Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion novelforge/agents/chapter/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,11 @@ def run_chapter_rhythm_classifier(
"chapter_num": chapter_num,
"failure_summary": failure_summary,
})
return {PASS_FAILURE_KEY: failure_summary}
return {
PASS_FAILURE_KEY: failure_summary,
"recommended_shape_for_this_chapter": "",
"recommendation_reason": "",
}


# ---------------------------------------------------------------------------
Expand Down
8 changes: 8 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,14 @@ def _canned_llm_response(messages, *, action="", json_mode=False):
]
})

# Chapter rhythm classifier (returns JSON with a named rhythm shape)
if "classifying" in act or ("rhythm" in act and "chapter" in act):
return json.dumps({
"detected_patterns": [],
"recommended_shape_for_this_chapter": "lyrical-interlude",
"recommendation_reason": "Default rhythm for test purposes.",
})

if "revision" in act:
return "The revised chapter content with improvements."

Expand Down
13 changes: 11 additions & 2 deletions tests/test_chapter_agent_failures.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,10 +180,19 @@ def test_rhythm_classifier_returns_dict_with_pass_failure_key(self, monkeypatch)
assert "boom" in result[PASS_FAILURE_KEY]

def test_rhythm_classifier_fallback_is_otherwise_empty(self, monkeypatch):
"""The fallback dict must not carry false business data (only PASS_FAILURE_KEY)."""
"""The fallback dict must carry PASS_FAILURE_KEY and safe-default business keys."""
_patch_chapter_call_llm(monkeypatch, _raise_runtime("x"))
result = run_chapter_rhythm_classifier(**_CHAPTER_KWARGS_STR, title="Novel")
assert set(result.keys()) == {PASS_FAILURE_KEY}
assert PASS_FAILURE_KEY in result
# Minimal safe defaults must be present so callers can .get() without KeyError
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment says the defaults are present so callers can .get() without KeyError, but dict.get() never raises KeyError even when a key is missing. Consider rewording to reflect the real motivation (e.g., allowing direct indexing / ensuring typed keys exist for downstream code).

Suggested change
# Minimal safe defaults must be present so callers can .get() without KeyError
# Minimal safe defaults must be present so downstream code can rely on the expected keys.

Copilot uses AI. Check for mistakes.
assert "recommended_shape_for_this_chapter" in result
assert "recommendation_reason" in result
# No spurious keys beyond the three above
assert set(result.keys()) == {
PASS_FAILURE_KEY,
"recommended_shape_for_this_chapter",
"recommendation_reason",
}


# ---------------------------------------------------------------------------
Expand Down
11 changes: 7 additions & 4 deletions tests/test_illustration_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,14 +160,17 @@ def test_warning_logged_when_novel_progress_entry_missing(
# Patch progress_manager.update so that the first call (linking
# illustration_token onto the novel entry) raises KeyError, simulating
# a deleted entry.
original_update = pm.update
# NOTE: patch the *class* method, not the instance, to avoid leaving a
# stale instance attribute on the singleton after monkeypatch teardown
# (which would bypass class-level spies installed by other tests).
original_class_update = type(pm).update

def _raise_on_novel_token(tok, data):
def _raise_on_novel_token(self, tok, data):
if "illustration_token" in data:
raise KeyError(tok)
return original_update(tok, data)
return original_class_update(self, tok, data)

monkeypatch.setattr(pm, "update", _raise_on_novel_token)
monkeypatch.setattr(type(pm), "update", _raise_on_novel_token)

token = "deleted-novel-link"
_done_novel(token)
Expand Down
Loading