Skip to content

Commit 6393a54

Browse files
committed
test: add comprehensive tests for DocumentActionTools async methods and file validation
1 parent bf37673 commit 6393a54

1 file changed

Lines changed: 293 additions & 0 deletions

File tree

tests/unit/test_document_action_tools.py

Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -643,3 +643,296 @@ def test_serialize_for_json_pil_image(self):
643643
assert "data" in result
644644
assert result["format"] == "PNG"
645645

646+
647+
class TestDocumentActionToolsFileValidation:
648+
"""Test file validation methods."""
649+
650+
@pytest.mark.asyncio
651+
async def test_validate_document_file_valid(self, tmp_path):
652+
"""Test _validate_document_file with valid file."""
653+
tools = DocumentActionTools()
654+
655+
# Create a test PDF file
656+
test_file = tmp_path / "test.pdf"
657+
test_file.write_bytes(b"fake pdf content")
658+
659+
result = await tools._validate_document_file(str(test_file))
660+
661+
assert result["valid"] is True
662+
assert result["file_type"] == "pdf"
663+
assert result["file_size"] > 0
664+
665+
@pytest.mark.asyncio
666+
async def test_validate_document_file_not_exists(self):
667+
"""Test _validate_document_file with non-existent file."""
668+
tools = DocumentActionTools()
669+
670+
result = await tools._validate_document_file("/nonexistent/file.pdf")
671+
672+
assert result["valid"] is False
673+
assert "does not exist" in result["error"]
674+
675+
@pytest.mark.asyncio
676+
async def test_validate_document_file_too_large(self, tmp_path):
677+
"""Test _validate_document_file with file exceeding size limit."""
678+
tools = DocumentActionTools()
679+
tools.max_file_size = 10 # Very small limit for testing
680+
681+
# Create a large file
682+
test_file = tmp_path / "large.pdf"
683+
test_file.write_bytes(b"x" * 100)
684+
685+
result = await tools._validate_document_file(str(test_file))
686+
687+
assert result["valid"] is False
688+
assert "exceeds" in result["error"]
689+
690+
@pytest.mark.asyncio
691+
async def test_validate_document_file_unsupported_type(self, tmp_path):
692+
"""Test _validate_document_file with unsupported file type."""
693+
tools = DocumentActionTools()
694+
695+
test_file = tmp_path / "test.xyz"
696+
test_file.write_bytes(b"content")
697+
698+
result = await tools._validate_document_file(str(test_file))
699+
700+
assert result["valid"] is False
701+
assert "Unsupported file type" in result["error"]
702+
703+
704+
class TestDocumentActionToolsQualityScore:
705+
"""Test quality score creation methods."""
706+
707+
def test_create_quality_score_from_validation_dict(self):
708+
"""Test _create_quality_score_from_validation with dictionary."""
709+
tools = DocumentActionTools()
710+
711+
validation_data = {
712+
"overall_score": 0.85,
713+
"completeness_score": 0.90,
714+
"accuracy_score": 0.80,
715+
"compliance_score": 0.75,
716+
"quality_score": 0.85,
717+
"decision": "APPROVED",
718+
"reasoning": "Good quality document",
719+
"issues_found": [],
720+
"confidence": 0.95,
721+
}
722+
723+
result = tools._create_quality_score_from_validation(validation_data)
724+
725+
assert result.overall_score == 0.85
726+
assert result.completeness_score == 0.90
727+
assert result.accuracy_score == 0.80
728+
assert result.compliance_score == 0.75
729+
assert result.quality_score == 0.85
730+
assert result.decision.value == "APPROVED"
731+
assert isinstance(result.reasoning, dict)
732+
assert result.confidence == 0.95
733+
assert result.judge_model == tools.MODEL_LARGE_JUDGE
734+
735+
def test_create_quality_score_from_validation_dict_string_reasoning(self):
736+
"""Test _create_quality_score_from_validation with string reasoning."""
737+
tools = DocumentActionTools()
738+
739+
validation_data = {
740+
"overall_score": 0.75,
741+
"reasoning": "String reasoning text",
742+
}
743+
744+
result = tools._create_quality_score_from_validation(validation_data)
745+
746+
assert isinstance(result.reasoning, dict)
747+
assert result.reasoning["summary"] == "String reasoning text"
748+
assert result.reasoning["details"] == "String reasoning text"
749+
750+
def test_create_quality_score_from_validation_object(self):
751+
"""Test _create_quality_score_from_validation with object."""
752+
tools = DocumentActionTools()
753+
754+
class ValidationObj:
755+
def __init__(self):
756+
self.overall_score = 0.85
757+
self.completeness_score = 0.90
758+
self.accuracy_score = 0.80
759+
self.compliance_score = 0.75
760+
self.quality_score = 0.85
761+
self.decision = "APPROVED"
762+
self.reasoning = "Object reasoning"
763+
self.issues_found = []
764+
self.confidence = 0.95
765+
766+
obj = ValidationObj()
767+
result = tools._create_quality_score_from_validation(obj)
768+
769+
assert result.overall_score == 0.85
770+
assert result.decision.value == "APPROVED"
771+
assert isinstance(result.reasoning, dict)
772+
assert result.reasoning["summary"] == "Object reasoning"
773+
774+
@pytest.mark.asyncio
775+
async def test_extract_quality_from_extraction_data_success(self):
776+
"""Test _extract_quality_from_extraction_data with valid data."""
777+
tools = DocumentActionTools()
778+
779+
with patch.object(tools, "_get_extraction_data", return_value={"quality_score": 0.85}):
780+
result = await tools._extract_quality_from_extraction_data("doc-1")
781+
assert result == 0.85
782+
783+
@pytest.mark.asyncio
784+
async def test_extract_quality_from_extraction_data_no_score(self):
785+
"""Test _extract_quality_from_extraction_data with no quality score."""
786+
tools = DocumentActionTools()
787+
788+
with patch.object(tools, "_get_extraction_data", return_value={}):
789+
result = await tools._extract_quality_from_extraction_data("doc-1")
790+
assert result == 0.0
791+
792+
@pytest.mark.asyncio
793+
async def test_extract_quality_from_extraction_data_error(self):
794+
"""Test _extract_quality_from_extraction_data handles errors gracefully."""
795+
tools = DocumentActionTools()
796+
797+
with patch.object(tools, "_get_extraction_data", side_effect=Exception("Test error")):
798+
result = await tools._extract_quality_from_extraction_data("doc-1")
799+
assert result == 0.0
800+
801+
802+
class TestDocumentActionToolsMockData:
803+
"""Test mock data generation."""
804+
805+
def test_get_mock_extraction_data(self):
806+
"""Test _get_mock_extraction_data generates valid structure."""
807+
tools = DocumentActionTools()
808+
809+
result = tools._get_mock_extraction_data()
810+
811+
assert isinstance(result, dict)
812+
assert "extraction_results" in result
813+
assert "confidence_scores" in result
814+
assert "stages" in result
815+
assert "quality_score" in result
816+
assert "routing_decision" in result
817+
assert len(result["extraction_results"]) > 0
818+
819+
820+
class TestDocumentActionToolsDocumentOperations:
821+
"""Test main document operation methods."""
822+
823+
@pytest.mark.asyncio
824+
async def test_upload_document_validation_failure(self, tmp_path):
825+
"""Test upload_document with validation failure."""
826+
tools = DocumentActionTools()
827+
828+
# File doesn't exist
829+
result = await tools.upload_document(
830+
file_path="/nonexistent/file.pdf",
831+
document_type="invoice"
832+
)
833+
834+
assert result["success"] is False
835+
assert "validation failed" in result["message"].lower()
836+
837+
@pytest.mark.asyncio
838+
async def test_upload_document_success(self, tmp_path):
839+
"""Test upload_document with valid file."""
840+
tools = DocumentActionTools()
841+
842+
# Create test file
843+
test_file = tmp_path / "test.pdf"
844+
test_file.write_bytes(b"fake pdf content")
845+
846+
with patch.object(tools, "_start_document_processing", return_value={"processing_started": True}), \
847+
patch.object(tools, "_save_status_data"):
848+
result = await tools.upload_document(
849+
file_path=str(test_file),
850+
document_type="invoice"
851+
)
852+
853+
assert result["success"] is True
854+
assert "document_id" in result
855+
assert result["status"] == "processing_started"
856+
857+
@pytest.mark.asyncio
858+
async def test_upload_document_with_custom_id(self, tmp_path):
859+
"""Test upload_document with custom document ID."""
860+
tools = DocumentActionTools()
861+
862+
test_file = tmp_path / "test.pdf"
863+
test_file.write_bytes(b"fake pdf content")
864+
custom_id = "custom-doc-123"
865+
866+
with patch.object(tools, "_start_document_processing", return_value={"processing_started": True}), \
867+
patch.object(tools, "_save_status_data"):
868+
result = await tools.upload_document(
869+
file_path=str(test_file),
870+
document_type="invoice",
871+
document_id=custom_id
872+
)
873+
874+
assert result["success"] is True
875+
assert result["document_id"] == custom_id
876+
877+
@pytest.mark.asyncio
878+
async def test_get_document_status_not_found(self):
879+
"""Test get_document_status with non-existent document."""
880+
tools = DocumentActionTools()
881+
882+
with patch.object(tools, "_get_processing_status", return_value=None):
883+
result = await tools.get_document_status("nonexistent-doc")
884+
885+
assert result["success"] is True # Returns success with unknown status
886+
assert result["status"] == "unknown"
887+
888+
@pytest.mark.asyncio
889+
async def test_get_document_status_found(self):
890+
"""Test get_document_status with existing document."""
891+
tools = DocumentActionTools()
892+
893+
doc_id = "test-doc-123"
894+
tools.document_statuses[doc_id] = {
895+
"status": "processing",
896+
"current_stage": "OCR Extraction",
897+
"progress": 50,
898+
"stages": [{"name": "preprocessing", "status": "completed"}],
899+
"estimated_completion": datetime.now().timestamp() + 60,
900+
}
901+
902+
with patch.object(tools, "_get_processing_status") as mock_get:
903+
mock_get.return_value = {
904+
"status": "processing",
905+
"current_stage": "OCR Extraction",
906+
"progress": 50,
907+
"stages": [{"name": "preprocessing", "status": "completed"}],
908+
"estimated_completion": datetime.now().timestamp() + 60,
909+
}
910+
911+
result = await tools.get_document_status(doc_id)
912+
913+
assert result["success"] is True
914+
assert result["status"] == "processing"
915+
assert result["progress"] == 50
916+
917+
@pytest.mark.asyncio
918+
async def test_extract_document_data_not_found(self):
919+
"""Test extract_document_data with non-existent document."""
920+
tools = DocumentActionTools()
921+
922+
with patch.object(tools, "_get_extraction_data", return_value=None):
923+
result = await tools.extract_document_data("nonexistent-doc")
924+
925+
assert result["success"] is False
926+
assert "not found" in result["message"].lower()
927+
928+
@pytest.mark.asyncio
929+
async def test_start_document_processing(self):
930+
"""Test _start_document_processing returns processing info."""
931+
tools = DocumentActionTools()
932+
933+
result = await tools._start_document_processing()
934+
935+
assert result["processing_started"] is True
936+
assert "pipeline_id" in result
937+
assert "estimated_completion" in result
938+

0 commit comments

Comments
 (0)