Compare commits
1 Commits
feat/admin
...
fix/stale-
| Author | SHA1 | Date | |
|---|---|---|---|
| 539d9b6c34 |
@@ -48,30 +48,3 @@ class ConceptExtractionParsingTests(TestCase):
|
|||||||
result = self.extractor._parse_concept_response(response)
|
result = self.extractor._parse_concept_response(response)
|
||||||
self.assertEqual(len(result), 1)
|
self.assertEqual(len(result), 1)
|
||||||
self.assertEqual(result[0]["name"], "valid")
|
self.assertEqual(result[0]["name"], "valid")
|
||||||
|
|
||||||
|
|
||||||
class SampleIndexSelectionTests(TestCase):
|
|
||||||
"""Tests for sample index selection."""
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.extractor = ConceptExtractor(MagicMock())
|
|
||||||
|
|
||||||
def test_small_total_returns_all(self):
|
|
||||||
indices = self.extractor._select_sample_indices(5, max_samples=10)
|
|
||||||
self.assertEqual(indices, [0, 1, 2, 3, 4])
|
|
||||||
|
|
||||||
def test_equal_total_returns_all(self):
|
|
||||||
indices = self.extractor._select_sample_indices(10, max_samples=10)
|
|
||||||
self.assertEqual(indices, list(range(10)))
|
|
||||||
|
|
||||||
def test_large_total_returns_max_samples(self):
|
|
||||||
indices = self.extractor._select_sample_indices(100, max_samples=10)
|
|
||||||
self.assertEqual(len(indices), 10)
|
|
||||||
# Should be evenly spaced
|
|
||||||
self.assertEqual(indices[0], 0)
|
|
||||||
self.assertEqual(indices[-1], 90)
|
|
||||||
|
|
||||||
def test_returns_integers(self):
|
|
||||||
indices = self.extractor._select_sample_indices(50, max_samples=7)
|
|
||||||
for idx in indices:
|
|
||||||
self.assertIsInstance(idx, int)
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class EmbeddingPipelineInitTests(TestCase):
|
|||||||
class PipelineItemNotFoundTests(TestCase):
|
class PipelineItemNotFoundTests(TestCase):
|
||||||
"""Tests for handling missing items."""
|
"""Tests for handling missing items."""
|
||||||
|
|
||||||
@patch("library.services.pipeline.Item")
|
@patch("library.models.Item")
|
||||||
def test_process_nonexistent_item_raises(self, mock_item_cls):
|
def test_process_nonexistent_item_raises(self, mock_item_cls):
|
||||||
mock_item_cls.nodes.get.side_effect = Exception("Not found")
|
mock_item_cls.nodes.get.side_effect = Exception("Not found")
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ class PipelineItemNotFoundTests(TestCase):
|
|||||||
pipeline.process_item("nonexistent-uid")
|
pipeline.process_item("nonexistent-uid")
|
||||||
self.assertIn("Item not found", str(ctx.exception))
|
self.assertIn("Item not found", str(ctx.exception))
|
||||||
|
|
||||||
@patch("library.services.pipeline.Item")
|
@patch("library.models.Item")
|
||||||
def test_reprocess_nonexistent_item_raises(self, mock_item_cls):
|
def test_reprocess_nonexistent_item_raises(self, mock_item_cls):
|
||||||
mock_item_cls.nodes.get.side_effect = Exception("Not found")
|
mock_item_cls.nodes.get.side_effect = Exception("Not found")
|
||||||
|
|
||||||
@@ -69,9 +69,9 @@ class PipelineItemNotFoundTests(TestCase):
|
|||||||
class PipelineNoEmbeddingModelTests(TestCase):
|
class PipelineNoEmbeddingModelTests(TestCase):
|
||||||
"""Tests for handling missing system embedding model."""
|
"""Tests for handling missing system embedding model."""
|
||||||
|
|
||||||
@patch("library.services.pipeline.LLMModel")
|
@patch("llm_manager.models.LLMModel")
|
||||||
@patch("library.services.pipeline.default_storage")
|
@patch("library.services.pipeline.default_storage")
|
||||||
@patch("library.services.pipeline.DocumentParser")
|
@patch("library.services.parsers.DocumentParser")
|
||||||
def test_no_embedding_model_raises(self, mock_parser, mock_storage, mock_llm):
|
def test_no_embedding_model_raises(self, mock_parser, mock_storage, mock_llm):
|
||||||
"""Pipeline raises ValueError if no system embedding model is configured."""
|
"""Pipeline raises ValueError if no system embedding model is configured."""
|
||||||
mock_llm.get_system_embedding_model.return_value = None
|
mock_llm.get_system_embedding_model.return_value = None
|
||||||
@@ -86,7 +86,7 @@ class PipelineNoEmbeddingModelTests(TestCase):
|
|||||||
mock_item.chunks.all.return_value = []
|
mock_item.chunks.all.return_value = []
|
||||||
mock_item.images.all.return_value = []
|
mock_item.images.all.return_value = []
|
||||||
|
|
||||||
with patch("library.services.pipeline.Item") as mock_item_cls:
|
with patch("library.models.Item") as mock_item_cls:
|
||||||
mock_item_cls.nodes.get.return_value = mock_item
|
mock_item_cls.nodes.get.return_value = mock_item
|
||||||
|
|
||||||
# Mock S3 read
|
# Mock S3 read
|
||||||
@@ -166,11 +166,11 @@ class PipelineVisionStageTests(TestCase):
|
|||||||
item.images.all.return_value = []
|
item.images.all.return_value = []
|
||||||
return item
|
return item
|
||||||
|
|
||||||
@patch("library.services.pipeline.ConceptExtractor")
|
@patch("library.services.concepts.ConceptExtractor")
|
||||||
@patch("library.services.pipeline.EmbeddingClient")
|
@patch("library.services.embedding_client.EmbeddingClient")
|
||||||
@patch("library.services.pipeline.ContentTypeChunker")
|
@patch("library.services.chunker.ContentTypeChunker")
|
||||||
@patch("library.services.pipeline.DocumentParser")
|
@patch("library.services.parsers.DocumentParser")
|
||||||
@patch("library.services.pipeline.LLMModel")
|
@patch("llm_manager.models.LLMModel")
|
||||||
@patch("library.services.pipeline.default_storage")
|
@patch("library.services.pipeline.default_storage")
|
||||||
def test_no_vision_model_marks_images_skipped(
|
def test_no_vision_model_marks_images_skipped(
|
||||||
self, mock_storage, mock_llm, mock_parser_cls,
|
self, mock_storage, mock_llm, mock_parser_cls,
|
||||||
@@ -227,12 +227,12 @@ class PipelineVisionStageTests(TestCase):
|
|||||||
img_node.save.assert_called()
|
img_node.save.assert_called()
|
||||||
self.assertEqual(result["images_analyzed"], 0)
|
self.assertEqual(result["images_analyzed"], 0)
|
||||||
|
|
||||||
@patch("library.services.pipeline.VisionAnalyzer")
|
@patch("library.services.vision.VisionAnalyzer")
|
||||||
@patch("library.services.pipeline.ConceptExtractor")
|
@patch("library.services.concepts.ConceptExtractor")
|
||||||
@patch("library.services.pipeline.EmbeddingClient")
|
@patch("library.services.embedding_client.EmbeddingClient")
|
||||||
@patch("library.services.pipeline.ContentTypeChunker")
|
@patch("library.services.chunker.ContentTypeChunker")
|
||||||
@patch("library.services.pipeline.DocumentParser")
|
@patch("library.services.parsers.DocumentParser")
|
||||||
@patch("library.services.pipeline.LLMModel")
|
@patch("llm_manager.models.LLMModel")
|
||||||
@patch("library.services.pipeline.default_storage")
|
@patch("library.services.pipeline.default_storage")
|
||||||
def test_vision_model_triggers_analysis(
|
def test_vision_model_triggers_analysis(
|
||||||
self, mock_storage, mock_llm, mock_parser_cls,
|
self, mock_storage, mock_llm, mock_parser_cls,
|
||||||
@@ -287,7 +287,7 @@ class PipelineVisionStageTests(TestCase):
|
|||||||
mock_vision_cls.assert_called_once_with(mock_vision_model, user=None)
|
mock_vision_cls.assert_called_once_with(mock_vision_model, user=None)
|
||||||
mock_analyzer.analyze_images.assert_called_once()
|
mock_analyzer.analyze_images.assert_called_once()
|
||||||
|
|
||||||
@patch("library.services.pipeline.LLMModel")
|
@patch("llm_manager.models.LLMModel")
|
||||||
def test_no_images_skips_vision_entirely(self, mock_llm):
|
def test_no_images_skips_vision_entirely(self, mock_llm):
|
||||||
"""When there are no images, vision stage is a no-op regardless of model."""
|
"""When there are no images, vision stage is a no-op regardless of model."""
|
||||||
mock_vision_model = MagicMock()
|
mock_vision_model = MagicMock()
|
||||||
@@ -309,10 +309,10 @@ class PipelineVisionStageTests(TestCase):
|
|||||||
patch.object(pipeline, "_store_chunks", return_value=[]), \
|
patch.object(pipeline, "_store_chunks", return_value=[]), \
|
||||||
patch.object(pipeline, "_store_images", return_value=[]), \
|
patch.object(pipeline, "_store_images", return_value=[]), \
|
||||||
patch.object(pipeline, "_associate_images_with_chunks"), \
|
patch.object(pipeline, "_associate_images_with_chunks"), \
|
||||||
patch("library.services.pipeline.DocumentParser") as mock_parser_cls, \
|
patch("library.services.parsers.DocumentParser") as mock_parser_cls, \
|
||||||
patch("library.services.pipeline.ContentTypeChunker") as mock_chunker_cls, \
|
patch("library.services.chunker.ContentTypeChunker") as mock_chunker_cls, \
|
||||||
patch("library.services.pipeline.EmbeddingClient"), \
|
patch("library.services.embedding_client.EmbeddingClient"), \
|
||||||
patch("library.services.pipeline.VisionAnalyzer") as mock_vision_cls:
|
patch("library.services.vision.VisionAnalyzer") as mock_vision_cls:
|
||||||
|
|
||||||
mock_parser = MagicMock()
|
mock_parser = MagicMock()
|
||||||
mock_parser.parse_bytes.return_value = MagicMock(images=[], text_blocks=[])
|
mock_parser.parse_bytes.return_value = MagicMock(images=[], text_blocks=[])
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ class SearchAPIResponseTest(TestCase):
|
|||||||
self.client = APIClient()
|
self.client = APIClient()
|
||||||
self.client.force_authenticate(user=self.user)
|
self.client.force_authenticate(user=self.user)
|
||||||
|
|
||||||
@patch("library.api.views.SearchService")
|
@patch("library.services.search.SearchService")
|
||||||
def test_successful_search_response_format(self, MockService):
|
def test_successful_search_response_format(self, MockService):
|
||||||
"""Successful search returns expected JSON structure."""
|
"""Successful search returns expected JSON structure."""
|
||||||
mock_response = SearchResponse(
|
mock_response = SearchResponse(
|
||||||
@@ -159,7 +159,7 @@ class SearchAPIResponseTest(TestCase):
|
|||||||
self.assertEqual(image["image_uid"], "img1")
|
self.assertEqual(image["image_uid"], "img1")
|
||||||
self.assertEqual(image["image_type"], "diagram")
|
self.assertEqual(image["image_type"], "diagram")
|
||||||
|
|
||||||
@patch("library.api.views.SearchService")
|
@patch("library.services.search.SearchService")
|
||||||
def test_vector_only_endpoint(self, MockService):
|
def test_vector_only_endpoint(self, MockService):
|
||||||
"""Vector-only endpoint sets correct search types."""
|
"""Vector-only endpoint sets correct search types."""
|
||||||
mock_response = SearchResponse(
|
mock_response = SearchResponse(
|
||||||
@@ -184,7 +184,7 @@ class SearchAPIResponseTest(TestCase):
|
|||||||
self.assertEqual(call_args.search_types, ["vector"])
|
self.assertEqual(call_args.search_types, ["vector"])
|
||||||
self.assertFalse(call_args.rerank)
|
self.assertFalse(call_args.rerank)
|
||||||
|
|
||||||
@patch("library.api.views.SearchService")
|
@patch("library.services.search.SearchService")
|
||||||
def test_fulltext_only_endpoint(self, MockService):
|
def test_fulltext_only_endpoint(self, MockService):
|
||||||
"""Fulltext-only endpoint sets correct search types."""
|
"""Fulltext-only endpoint sets correct search types."""
|
||||||
mock_response = SearchResponse(
|
mock_response = SearchResponse(
|
||||||
@@ -208,7 +208,7 @@ class SearchAPIResponseTest(TestCase):
|
|||||||
self.assertEqual(call_args.search_types, ["fulltext"])
|
self.assertEqual(call_args.search_types, ["fulltext"])
|
||||||
self.assertFalse(call_args.rerank)
|
self.assertFalse(call_args.rerank)
|
||||||
|
|
||||||
@patch("library.api.views.SearchService")
|
@patch("library.services.search.SearchService")
|
||||||
def test_reranker_skip_reason_surfaced_in_json(self, MockService):
|
def test_reranker_skip_reason_surfaced_in_json(self, MockService):
|
||||||
"""``reranker_skip_reason`` propagates through the JSON API."""
|
"""``reranker_skip_reason`` propagates through the JSON API."""
|
||||||
mock_response = SearchResponse(
|
mock_response = SearchResponse(
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class AllLibraryUidsHelperTests(TestCase):
|
|||||||
|
|
||||||
def test_returns_empty_when_neo4j_unavailable(self):
|
def test_returns_empty_when_neo4j_unavailable(self):
|
||||||
"""Helper must not touch ``Library.nodes`` if Neo4j is down."""
|
"""Helper must not touch ``Library.nodes`` if Neo4j is down."""
|
||||||
with patch("library.views.neo4j_available", return_value=False):
|
with patch("library.utils.neo4j_available", return_value=False):
|
||||||
self.assertEqual(views._all_library_uids(), [])
|
self.assertEqual(views._all_library_uids(), [])
|
||||||
|
|
||||||
def test_returns_every_library_uid(self):
|
def test_returns_every_library_uid(self):
|
||||||
@@ -62,7 +62,7 @@ class AllLibraryUidsHelperTests(TestCase):
|
|||||||
fake_nodes.all.return_value = fake_libs
|
fake_nodes.all.return_value = fake_libs
|
||||||
fake_library_cls = SimpleNamespace(nodes=fake_nodes)
|
fake_library_cls = SimpleNamespace(nodes=fake_nodes)
|
||||||
|
|
||||||
with patch("library.views.neo4j_available", return_value=True), \
|
with patch("library.utils.neo4j_available", return_value=True), \
|
||||||
patch.dict("sys.modules", {"library.models": SimpleNamespace(Library=fake_library_cls)}):
|
patch.dict("sys.modules", {"library.models": SimpleNamespace(Library=fake_library_cls)}):
|
||||||
result = views._all_library_uids()
|
result = views._all_library_uids()
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ class AllLibraryUidsHelperTests(TestCase):
|
|||||||
fake_nodes.all.return_value = fake_libs
|
fake_nodes.all.return_value = fake_libs
|
||||||
fake_library_cls = SimpleNamespace(nodes=fake_nodes)
|
fake_library_cls = SimpleNamespace(nodes=fake_nodes)
|
||||||
|
|
||||||
with patch("library.views.neo4j_available", return_value=True), \
|
with patch("library.utils.neo4j_available", return_value=True), \
|
||||||
patch.dict("sys.modules", {"library.models": SimpleNamespace(Library=fake_library_cls)}):
|
patch.dict("sys.modules", {"library.models": SimpleNamespace(Library=fake_library_cls)}):
|
||||||
result = views._all_library_uids()
|
result = views._all_library_uids()
|
||||||
|
|
||||||
@@ -95,7 +95,7 @@ class AllLibraryUidsHelperTests(TestCase):
|
|||||||
fake_nodes.all.side_effect = RuntimeError("neo4j blew up")
|
fake_nodes.all.side_effect = RuntimeError("neo4j blew up")
|
||||||
fake_library_cls = SimpleNamespace(nodes=fake_nodes)
|
fake_library_cls = SimpleNamespace(nodes=fake_nodes)
|
||||||
|
|
||||||
with patch("library.views.neo4j_available", return_value=True), \
|
with patch("library.utils.neo4j_available", return_value=True), \
|
||||||
patch.dict("sys.modules", {"library.models": SimpleNamespace(Library=fake_library_cls)}):
|
patch.dict("sys.modules", {"library.models": SimpleNamespace(Library=fake_library_cls)}):
|
||||||
self.assertEqual(views._all_library_uids(), [])
|
self.assertEqual(views._all_library_uids(), [])
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from django.test import TestCase, override_settings
|
|||||||
class EmbedItemTaskTests(TestCase):
|
class EmbedItemTaskTests(TestCase):
|
||||||
"""Tests for the embed_item task."""
|
"""Tests for the embed_item task."""
|
||||||
|
|
||||||
@patch("library.tasks.EmbeddingPipeline")
|
@patch("library.services.pipeline.EmbeddingPipeline")
|
||||||
def test_embed_item_success(self, mock_pipeline_cls):
|
def test_embed_item_success(self, mock_pipeline_cls):
|
||||||
from library.tasks import embed_item
|
from library.tasks import embed_item
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ class EmbedItemTaskTests(TestCase):
|
|||||||
self.assertEqual(result["item_uid"], "test-uid-123")
|
self.assertEqual(result["item_uid"], "test-uid-123")
|
||||||
mock_pipeline.process_item.assert_called_once()
|
mock_pipeline.process_item.assert_called_once()
|
||||||
|
|
||||||
@patch("library.tasks.EmbeddingPipeline")
|
@patch("library.services.pipeline.EmbeddingPipeline")
|
||||||
def test_embed_item_failure(self, mock_pipeline_cls):
|
def test_embed_item_failure(self, mock_pipeline_cls):
|
||||||
from library.tasks import embed_item
|
from library.tasks import embed_item
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ class EmbedItemTaskTests(TestCase):
|
|||||||
class ReembedItemTaskTests(TestCase):
|
class ReembedItemTaskTests(TestCase):
|
||||||
"""Tests for the reembed_item task."""
|
"""Tests for the reembed_item task."""
|
||||||
|
|
||||||
@patch("library.tasks.EmbeddingPipeline")
|
@patch("library.services.pipeline.EmbeddingPipeline")
|
||||||
def test_reembed_item_success(self, mock_pipeline_cls):
|
def test_reembed_item_success(self, mock_pipeline_cls):
|
||||||
from library.tasks import reembed_item
|
from library.tasks import reembed_item
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user