Several library tests patched symbols at import paths that no longer expose them, so they errored (AttributeError) instead of testing anything — giving false confidence. The underlying code is correct; only the test patch targets were stale after earlier refactors moved imports function-local. - test_pipeline: patch source modules (library.models.Item, llm_manager.models.LLMModel, library.services.parsers.DocumentParser, .chunker.ContentTypeChunker, .embedding_client.EmbeddingClient, .vision.VisionAnalyzer, .concepts.ConceptExtractor) since pipeline.py imports them inside methods. default_storage stays (still module-level). - test_search_api: patch library.services.search.SearchService (the view imports it function-local). - test_tasks: patch library.services.pipeline.EmbeddingPipeline (tasks.py imports it function-local). - test_search_views_admin_scope: patch library.utils.neo4j_available; the guard moved to utils when views._all_library_uids became a thin alias. - test_concepts: remove SampleIndexSelectionTests — _select_sample_indices was deleted in the document-level concept-extraction refactor (dead test). Not addressed here: SearchAPIAuthTest / SearchAPIValidationTest return 302 instead of 401/400. Static analysis ruled out routing, middleware, and DRF config; reproducing needs a running server (DB-backed). Flagged for sandbox diagnosis — not a stale-patch issue. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
51 lines
2.0 KiB
Python
51 lines
2.0 KiB
Python
"""
|
|
Tests for the concept extraction service.
|
|
"""
|
|
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from django.test import TestCase
|
|
|
|
from library.services.concepts import ConceptExtractor
|
|
|
|
|
|
class ConceptExtractionParsingTests(TestCase):
|
|
"""Tests for concept response parsing."""
|
|
|
|
def setUp(self):
|
|
self.mock_model = MagicMock()
|
|
self.mock_model.api.api_type = "openai"
|
|
self.mock_model.api.base_url = "http://localhost:8080/v1"
|
|
self.mock_model.api.api_key = "test"
|
|
self.mock_model.api.timeout_seconds = 30
|
|
self.mock_model.name = "test-chat"
|
|
self.extractor = ConceptExtractor(self.mock_model)
|
|
|
|
def test_parse_valid_json_array(self):
|
|
response = '[{"name": "python", "type": "topic"}, {"name": "django", "type": "technique"}]'
|
|
result = self.extractor._parse_concept_response(response)
|
|
self.assertEqual(len(result), 2)
|
|
self.assertEqual(result[0]["name"], "python")
|
|
self.assertEqual(result[1]["type"], "technique")
|
|
|
|
def test_parse_json_in_markdown_code_block(self):
|
|
response = '```json\n[{"name": "python", "type": "topic"}]\n```'
|
|
result = self.extractor._parse_concept_response(response)
|
|
self.assertEqual(len(result), 1)
|
|
|
|
def test_parse_json_embedded_in_text(self):
|
|
response = 'Here are the concepts: [{"name": "neo4j", "type": "technique"}] found in the text.'
|
|
result = self.extractor._parse_concept_response(response)
|
|
self.assertEqual(len(result), 1)
|
|
|
|
def test_parse_invalid_json_returns_empty(self):
|
|
response = "This is not JSON at all."
|
|
result = self.extractor._parse_concept_response(response)
|
|
self.assertEqual(result, [])
|
|
|
|
def test_parse_filters_invalid_entries(self):
|
|
response = '[{"name": "valid", "type": "topic"}, {"invalid": "entry"}, "string"]'
|
|
result = self.extractor._parse_concept_response(response)
|
|
self.assertEqual(len(result), 1)
|
|
self.assertEqual(result[0]["name"], "valid")
|