"""Tests for the prerequisite reading-path planner. Run: uv run pytest tests/test_planning.py """ from __future__ import annotations import pytest from researchpath.planning import plan_reading_path from researchpath.prerequisites import STATIC_PREREQUISITES def test_target_only_when_no_prereqs(): """A paper with no incoming edges should yield a one-step plan (just itself).""" plan = plan_reading_path("1312.5602") # DQN — no prereqs in the graph assert len(plan.steps) == 1 assert plan.steps[0].paper.arxiv_id == "1312.5602" def test_rainbow_full_chain(): """Rainbow with no prior knowledge requires DQN, Double DQN, PER, Dueling DQN, Rainbow. PER was added to the corpus and is one of Rainbow's six components, so it appears between Double DQN and Dueling DQN (year 2015 vs 2016 topo tie-break). """ plan = plan_reading_path("1710.02298") ids_in_order = [s.paper.arxiv_id for s in plan.steps] assert ids_in_order == ["1312.5602", "1509.06461", "1511.05952", "1511.06581", "1710.02298"] def test_known_set_truncates_chain(): """Knowing DQN should drop it from the Rainbow plan.""" plan = plan_reading_path("1710.02298", known_ids={"1312.5602"}) ids = {s.paper.arxiv_id for s in plan.steps} assert "1312.5602" not in ids assert "1710.02298" in ids # Double DQN, PER, Dueling DQN, Rainbow remain (PER now in corpus as Rainbow component) assert len(plan.steps) == 4 def test_known_target_yields_empty_plan(): """If the target is already 'known', no reading is needed.""" plan = plan_reading_path("1710.02298", known_ids={"1710.02298"}) assert plan.steps == () def test_topological_order_respects_edges(): """For every edge (A -> B), A must appear before B in the plan.""" plan = plan_reading_path("1710.02298") positions = {s.paper.arxiv_id: i for i, s in enumerate(plan.steps)} for edge in STATIC_PREREQUISITES: if edge.from_id in positions and edge.to_id in positions: assert positions[edge.from_id] < positions[edge.to_id], ( f"{edge.from_id} should precede {edge.to_id}" ) def test_unknown_target_raises(): with pytest.raises(ValueError): plan_reading_path("9999.99999") def test_render_text_contains_target_title(): plan = plan_reading_path("1710.02298") text = plan.render_text() assert "Rainbow" in text assert "1710.02298" in text