""" Behavioral tests for findings/topic module. Tests Topic and TopicGraph dataclasses with source management and graph logic. """ class TestTopicInit: """Tests for Topic dataclass initialization.""" def test_basic_construction(self): from local_deep_research.advanced_search_system.findings.topic import ( Topic, ) t = Topic( id="t1", title="Climate Change", lead_source={"url": "http://example.com"}, ) assert t.id == "t1" assert t.title == "Climate Change" assert t.lead_source == {"url": "http://example.com"} def test_supporting_sources_default_empty(self): from local_deep_research.advanced_search_system.findings.topic import ( Topic, ) t = Topic(id="t1", title="t", lead_source={"url": "u"}) assert t.supporting_sources == [] def test_rejected_sources_default_empty(self): from local_deep_research.advanced_search_system.findings.topic import ( Topic, ) t = Topic(id="t1", title="t", lead_source={"url": "u"}) assert t.rejected_sources == [] def test_related_topic_ids_default_empty(self): from local_deep_research.advanced_search_system.findings.topic import ( Topic, ) t = Topic(id="t1", title="t", lead_source={"url": "u"}) assert t.related_topic_ids == [] def test_parent_topic_id_default_none(self): from local_deep_research.advanced_search_system.findings.topic import ( Topic, ) t = Topic(id="t1", title="t", lead_source={"url": "u"}) assert t.parent_topic_id is None def test_child_topic_ids_default_empty(self): from local_deep_research.advanced_search_system.findings.topic import ( Topic, ) t = Topic(id="t1", title="t", lead_source={"url": "u"}) assert t.child_topic_ids == [] class TestTopicAddSupportingSource: """Tests for Topic.add_supporting_source() method.""" def test_add_one_source(self): from local_deep_research.advanced_search_system.findings.topic import ( Topic, ) t = Topic(id="t1", title="t", lead_source={"url": "u"}) t.add_supporting_source({"url": "http://support.com"}) assert len(t.supporting_sources) == 1 def test_no_duplicate_sources(self): from local_deep_research.advanced_search_system.findings.topic import ( Topic, ) t = Topic(id="t1", title="t", lead_source={"url": "u"}) source = {"url": "http://support.com"} t.add_supporting_source(source) t.add_supporting_source(source) assert len(t.supporting_sources) == 1 class TestTopicRejectSource: """Tests for Topic.reject_source() method.""" def test_reject_supporting_source(self): from local_deep_research.advanced_search_system.findings.topic import ( Topic, ) t = Topic(id="t1", title="t", lead_source={"url": "u"}) source = {"url": "http://bad.com"} t.add_supporting_source(source) t.reject_source(source) assert source not in t.supporting_sources assert source in t.rejected_sources def test_reject_non_supporting_source(self): from local_deep_research.advanced_search_system.findings.topic import ( Topic, ) t = Topic(id="t1", title="t", lead_source={"url": "u"}) source = {"url": "http://other.com"} t.reject_source(source) assert source in t.rejected_sources def test_no_duplicate_rejected(self): from local_deep_research.advanced_search_system.findings.topic import ( Topic, ) t = Topic(id="t1", title="t", lead_source={"url": "u"}) source = {"url": "http://bad.com"} t.reject_source(source) t.reject_source(source) assert t.rejected_sources.count(source) == 1 class TestTopicUpdateLeadSource: """Tests for Topic.update_lead_source() method.""" def test_old_lead_becomes_supporting(self): from local_deep_research.advanced_search_system.findings.topic import ( Topic, ) old_lead = {"url": "http://old.com"} new_lead = {"url": "http://new.com"} t = Topic(id="t1", title="t", lead_source=old_lead) t.update_lead_source(new_lead) assert t.lead_source == new_lead assert old_lead in t.supporting_sources def test_new_lead_removed_from_supporting(self): from local_deep_research.advanced_search_system.findings.topic import ( Topic, ) lead = {"url": "http://lead.com"} candidate = {"url": "http://candidate.com"} t = Topic(id="t1", title="t", lead_source=lead) t.add_supporting_source(candidate) t.update_lead_source(candidate) assert t.lead_source == candidate # candidate should not be in supporting after being promoted assert candidate not in t.supporting_sources class TestTopicGetAllSources: """Tests for Topic.get_all_sources() method.""" def test_lead_only(self): from local_deep_research.advanced_search_system.findings.topic import ( Topic, ) lead = {"url": "http://lead.com"} t = Topic(id="t1", title="t", lead_source=lead) all_sources = t.get_all_sources() assert len(all_sources) == 1 assert all_sources[0] == lead def test_lead_plus_supporting(self): from local_deep_research.advanced_search_system.findings.topic import ( Topic, ) lead = {"url": "http://lead.com"} t = Topic(id="t1", title="t", lead_source=lead) t.add_supporting_source({"url": "http://s1.com"}) t.add_supporting_source({"url": "http://s2.com"}) assert len(t.get_all_sources()) == 3 class TestTopicToDict: """Tests for Topic.to_dict() method.""" def test_contains_all_fields(self): from local_deep_research.advanced_search_system.findings.topic import ( Topic, ) t = Topic(id="t1", title="Climate", lead_source={"url": "u"}) d = t.to_dict() assert d["id"] == "t1" assert d["title"] == "Climate" assert d["lead_source"] == {"url": "u"} assert "supporting_sources" in d assert "rejected_sources" in d assert "created_at" in d assert "related_topic_ids" in d assert "parent_topic_id" in d assert "child_topic_ids" in d def test_created_at_is_isoformat(self): from local_deep_research.advanced_search_system.findings.topic import ( Topic, ) t = Topic(id="t1", title="t", lead_source={"url": "u"}) d = t.to_dict() assert isinstance(d["created_at"], str) assert "T" in d["created_at"] class TestTopicGraphInit: """Tests for TopicGraph initialization.""" def test_empty_graph(self): from local_deep_research.advanced_search_system.findings.topic import ( TopicGraph, ) g = TopicGraph() assert g.topics == {} class TestTopicGraphAddAndGet: """Tests for TopicGraph.add_topic() and get_topic().""" def test_add_and_retrieve(self): from local_deep_research.advanced_search_system.findings.topic import ( Topic, TopicGraph, ) g = TopicGraph() t = Topic(id="t1", title="Climate", lead_source={"url": "u"}) g.add_topic(t) assert g.get_topic("t1") is t def test_get_nonexistent_returns_none(self): from local_deep_research.advanced_search_system.findings.topic import ( TopicGraph, ) g = TopicGraph() assert g.get_topic("nonexistent") is None class TestTopicGraphLinkTopics: """Tests for TopicGraph.link_topics() method.""" def test_bidirectional_link(self): from local_deep_research.advanced_search_system.findings.topic import ( Topic, TopicGraph, ) g = TopicGraph() t1 = Topic(id="t1", title="A", lead_source={"url": "u1"}) t2 = Topic(id="t2", title="B", lead_source={"url": "u2"}) g.add_topic(t1) g.add_topic(t2) g.link_topics("t1", "t2") assert "t2" in t1.related_topic_ids assert "t1" in t2.related_topic_ids def test_no_duplicate_links(self): from local_deep_research.advanced_search_system.findings.topic import ( Topic, TopicGraph, ) g = TopicGraph() t1 = Topic(id="t1", title="A", lead_source={"url": "u1"}) t2 = Topic(id="t2", title="B", lead_source={"url": "u2"}) g.add_topic(t1) g.add_topic(t2) g.link_topics("t1", "t2") g.link_topics("t1", "t2") assert t1.related_topic_ids.count("t2") == 1 def test_link_nonexistent_topic_no_error(self): from local_deep_research.advanced_search_system.findings.topic import ( Topic, TopicGraph, ) g = TopicGraph() t1 = Topic(id="t1", title="A", lead_source={"url": "u1"}) g.add_topic(t1) g.link_topics("t1", "nonexistent") assert t1.related_topic_ids == [] class TestTopicGraphParentChild: """Tests for TopicGraph.set_parent_child() method.""" def test_set_parent_child(self): from local_deep_research.advanced_search_system.findings.topic import ( Topic, TopicGraph, ) g = TopicGraph() parent = Topic(id="p", title="Parent", lead_source={"url": "u"}) child = Topic(id="c", title="Child", lead_source={"url": "u"}) g.add_topic(parent) g.add_topic(child) g.set_parent_child("p", "c") assert "c" in parent.child_topic_ids assert child.parent_topic_id == "p" class TestTopicGraphGetRootTopics: """Tests for TopicGraph.get_root_topics() method.""" def test_all_roots_when_no_parents(self): from local_deep_research.advanced_search_system.findings.topic import ( Topic, TopicGraph, ) g = TopicGraph() g.add_topic(Topic(id="t1", title="A", lead_source={"url": "u"})) g.add_topic(Topic(id="t2", title="B", lead_source={"url": "u"})) roots = g.get_root_topics() assert len(roots) == 2 def test_excludes_children(self): from local_deep_research.advanced_search_system.findings.topic import ( Topic, TopicGraph, ) g = TopicGraph() parent = Topic(id="p", title="P", lead_source={"url": "u"}) child = Topic(id="c", title="C", lead_source={"url": "u"}) g.add_topic(parent) g.add_topic(child) g.set_parent_child("p", "c") roots = g.get_root_topics() assert len(roots) == 1 assert roots[0].id == "p" class TestTopicGraphGetRelatedTopics: """Tests for TopicGraph.get_related_topics() method.""" def test_returns_related(self): from local_deep_research.advanced_search_system.findings.topic import ( Topic, TopicGraph, ) g = TopicGraph() t1 = Topic(id="t1", title="A", lead_source={"url": "u"}) t2 = Topic(id="t2", title="B", lead_source={"url": "u"}) g.add_topic(t1) g.add_topic(t2) g.link_topics("t1", "t2") related = g.get_related_topics("t1") assert len(related) == 1 assert related[0].id == "t2" def test_nonexistent_topic_returns_empty(self): from local_deep_research.advanced_search_system.findings.topic import ( TopicGraph, ) g = TopicGraph() assert g.get_related_topics("nonexistent") == [] class TestTopicGraphMergeTopics: """Tests for TopicGraph.merge_topics() method.""" def test_merge_combines_sources(self): from local_deep_research.advanced_search_system.findings.topic import ( Topic, TopicGraph, ) g = TopicGraph() t1 = Topic(id="t1", title="A", lead_source={"url": "u1"}) t2 = Topic(id="t2", title="B", lead_source={"url": "u2"}) t2.add_supporting_source({"url": "u3"}) g.add_topic(t1) g.add_topic(t2) merged = g.merge_topics("t1", "t2") assert merged is not None # t2's supporting source should be in t1's supporting assert {"url": "u3"} in merged.supporting_sources def test_merge_removes_second_topic(self): from local_deep_research.advanced_search_system.findings.topic import ( Topic, TopicGraph, ) g = TopicGraph() t1 = Topic(id="t1", title="A", lead_source={"url": "u1"}) t2 = Topic(id="t2", title="B", lead_source={"url": "u2"}) g.add_topic(t1) g.add_topic(t2) g.merge_topics("t1", "t2") assert g.get_topic("t2") is None def test_merge_updates_title(self): from local_deep_research.advanced_search_system.findings.topic import ( Topic, TopicGraph, ) g = TopicGraph() t1 = Topic(id="t1", title="A", lead_source={"url": "u1"}) t2 = Topic(id="t2", title="B", lead_source={"url": "u2"}) g.add_topic(t1) g.add_topic(t2) merged = g.merge_topics("t1", "t2", new_title="Combined") assert merged.title == "Combined" def test_merge_nonexistent_returns_none(self): from local_deep_research.advanced_search_system.findings.topic import ( Topic, TopicGraph, ) g = TopicGraph() t1 = Topic(id="t1", title="A", lead_source={"url": "u1"}) g.add_topic(t1) assert g.merge_topics("t1", "nonexistent") is None def test_merge_updates_references_in_other_topics(self): from local_deep_research.advanced_search_system.findings.topic import ( Topic, TopicGraph, ) g = TopicGraph() t1 = Topic(id="t1", title="A", lead_source={"url": "u1"}) t2 = Topic(id="t2", title="B", lead_source={"url": "u2"}) t3 = Topic(id="t3", title="C", lead_source={"url": "u3"}) g.add_topic(t1) g.add_topic(t2) g.add_topic(t3) g.link_topics("t2", "t3") g.merge_topics("t1", "t2") # t3 should now reference t1 instead of t2 assert "t1" in t3.related_topic_ids assert "t2" not in t3.related_topic_ids class TestTopicGraphToDict: """Tests for TopicGraph.to_dict() method.""" def test_empty_graph_to_dict(self): from local_deep_research.advanced_search_system.findings.topic import ( TopicGraph, ) g = TopicGraph() assert g.to_dict() == {} def test_graph_with_topics(self): from local_deep_research.advanced_search_system.findings.topic import ( Topic, TopicGraph, ) g = TopicGraph() g.add_topic(Topic(id="t1", title="A", lead_source={"url": "u"})) d = g.to_dict() assert "t1" in d assert d["t1"]["title"] == "A"