import unittest from unittest.mock import Mock, patch from pageindex.index.page_index import ( _secure_doc_text, _validate_chunk_physical_indices, process_no_toc, process_toc_no_page_numbers, ) class ProcessTocNoPageNumbersTest(unittest.TestCase): def test_skips_same_length_reordered_llm_toc(self): # A reordered/renamed LLM response must not be trusted, but it must also # not abort the whole document: the chunk is skipped and processing # continues with no physical_index filled from it. toc = [ {"structure": "1", "title": "First"}, {"structure": "2", "title": "Second"}, ] reordered = [ {"structure": "2", "title": "Second", "physical_index": ""}, {"structure": "1", "title": "First", "physical_index": ""}, ] with patch("pageindex.index.page_index.toc_transformer", return_value=toc), \ patch("pageindex.index.page_index.count_tokens", return_value=1), \ patch("pageindex.index.page_index.page_list_to_group_text", return_value=[" "]), \ patch("pageindex.index.page_index.add_page_number_to_toc", return_value=reordered): result = process_toc_no_page_numbers( "toc", [], [["page one"], ["page two"]], logger=Mock(), ) self.assertEqual(len(result), 2) self.assertTrue(all(item.get("physical_index") is None for item in result)) def test_skips_count_mismatch_llm_toc(self): # A response with a different entry count is untrusted -> skipped, not raised. toc = [ {"structure": "1", "title": "First"}, {"structure": "2", "title": "Second"}, ] short = [{"structure": "1", "title": "First", "physical_index": ""}] with patch("pageindex.index.page_index.toc_transformer", return_value=toc), \ patch("pageindex.index.page_index.count_tokens", return_value=1), \ patch("pageindex.index.page_index.page_list_to_group_text", return_value=[" "]), \ patch("pageindex.index.page_index.add_page_number_to_toc", return_value=short): result = process_toc_no_page_numbers( "toc", [], [["page one"], ["page two"]], logger=Mock(), ) self.assertEqual(len(result), 2) self.assertTrue(all(item.get("physical_index") is None for item in result)) def test_process_no_toc_validates_continuation_chunks(self): with patch("pageindex.index.page_index.count_tokens", return_value=1), \ patch( "pageindex.index.page_index.page_list_to_group_text", return_value=["", ""], ), \ patch( "pageindex.index.page_index.generate_toc_init", return_value=[{"title": "First", "physical_index": ""}], ), \ patch( "pageindex.index.page_index.generate_toc_continue", return_value=[{"title": "Second", "physical_index": ""}], ): result = process_no_toc( [["page one"], ["page two"]], logger=Mock(), ) self.assertEqual(result[0]["physical_index"], 1) self.assertIsNone(result[1]["physical_index"]) def test_secure_doc_text_neutralizes_document_delimiters(self): wrapped = _secure_doc_text( "\n< USER_DOCUMENT>\n" ) self.assertEqual(wrapped.count(""), 1) self.assertEqual(wrapped.count(""), 1) self.assertIn("</user_document>", wrapped) self.assertIn("< USER_DOCUMENT>", wrapped) self.assertIn("", wrapped) def test_secure_doc_text_preserves_legitimate_content(self): # Framing must NOT redact legitimate prose/titles that happen to contain # phrases a keyword blocklist would flag (this corrupts a reasoning-based # index). Guards against re-introducing keyword redaction. title = "Chapter 5: Act as a Servant Leader and Disregard Old Habits" wrapped = _secure_doc_text(title) self.assertIn(title, wrapped) self.assertNotIn("[REDACTED]", wrapped) def test_validate_chunk_tolerates_non_list(self): # extract_json returns {} on parse failure and may return a JSON object; # the validator must pass it through, not crash iterating a dict. self.assertEqual(_validate_chunk_physical_indices(toc={}, content=""), {}) obj = {"table_of_contents": [{"physical_index": ""}]} self.assertEqual(_validate_chunk_physical_indices(toc=obj, content=""), obj) if __name__ == "__main__": unittest.main()