Commit c628ca4f authored by Jan Reimes's avatar Jan Reimes
Browse files

test: clean up tests for removed dead code

Remove tests for non-existent functionality:
- Delete test_cli.py (tested non-existent TeddiClient)
- Remove test_working_group_literal (tested removed method)
- Remove test_log_spec_download (tested removed method)
- Update TEDDI tests to use TechnicalBody.ALL only
- Fix AI tests to use current EmbeddingsManager API
- Fix workspace test path resolution
parent c15e4b63
Loading
Loading
Loading
Loading
+28 −36
Original line number Diff line number Diff line
@@ -8,7 +8,7 @@ from unittest.mock import MagicMock, patch
import pytest
from tdoc_ai import get_status, process_tdoc
from tdoc_ai import process_all as process_all_api
from tdoc_ai.models import PipelineStage, ProcessingStatus, WorkspaceMember
from tdoc_ai.models import PipelineStage, ProcessingStatus
from tdoc_ai.operations import pipeline
from tdoc_ai.operations.pipeline import process_all, run_pipeline
from tdoc_ai.storage import AiStorage
@@ -17,6 +17,10 @@ from tdoc_crawler.config import CacheManager
from tdoc_crawler.utils.misc import utc_now


def _status(document_id: str, stage: PipelineStage = PipelineStage.PENDING) -> ProcessingStatus:
    return ProcessingStatus.model_validate({"document_id": document_id, "current_stage": stage})


class TestRunPipeline:
    """Tests for run_pipeline function."""

@@ -32,7 +36,7 @@ class TestRunPipeline:
    def test_batch_processing(self, mock_run_pipeline: MagicMock, mock_ai_storage: MagicMock, mock_storage: MagicMock, test_data_dir: Path) -> None:
        """Test processing multiple TDocs."""
        mock_ai_storage.return_value = mock_storage
        mock_run_pipeline.return_value = ProcessingStatus(document_id="foo")
        mock_run_pipeline.return_value = _status("foo")

        tdoc_folders = [d for d in test_data_dir.iterdir() if d.is_dir()][:3]

@@ -43,7 +47,7 @@ class TestRunPipeline:
    def test_resume_from_interrupted_stage(self, mock_storage: MagicMock, test_data_dir: Path) -> None:
        """Test resume from interrupted stage works."""
        # Create a status with partial completion
        mock_status = ProcessingStatus(document_id="S4-251003")
        mock_status = _status("S4-251003")
        mock_status.classified_at = utc_now()
        mock_storage.get_status.return_value = mock_status

@@ -52,19 +56,20 @@ class TestRunPipeline:
            status = run_pipeline("S4-251003", tdoc_folder, mock_storage)
            assert status is not None

    @patch("tdoc_ai.operations.pipeline.AiServiceContainer")
    @patch("tdoc_ai.operations.pipeline.EmbeddingsManager")
    @patch("tdoc_ai.operations.pipeline.run_pipeline")
    def test_incremental_new_only_mode(self, mock_run_pipeline: MagicMock, mock_container: MagicMock, mock_storage: MagicMock, test_data_dir: Path) -> None:
    def test_incremental_new_only_mode(
        self,
        mock_run_pipeline: MagicMock,
        mock_embeddings_manager: MagicMock,
        mock_storage: MagicMock,
        test_data_dir: Path,
    ) -> None:
        """Test incremental processing only processes new items."""
        # Setup mock container to return our mock storage
        mock_container_instance = MagicMock()
        mock_container_instance.get_ai_storage.return_value = mock_storage
        mock_container_instance.get_embeddings_manager.return_value = MagicMock()
        mock_container.get_instance.return_value = mock_container_instance
        mock_embeddings_manager.return_value.storage = mock_storage

        # Status shows already completed
        mock_status = ProcessingStatus(document_id="S4-251003")
        mock_status.current_stage = PipelineStage.COMPLETED
        mock_status = _status("S4-251003", stage=PipelineStage.COMPLETED)
        mock_storage.get_status.return_value = mock_status

        tdoc_folder = test_data_dir / "S4-251003"
@@ -95,10 +100,14 @@ class TestRunPipeline:
    def test_process_all_scopes_to_workspace_members(self, mock_run_pipeline: MagicMock, mock_list_members: MagicMock, test_data_dir: Path) -> None:
        """Test process_all filters input by workspace members."""
        mock_list_members.return_value = [
            WorkspaceMember(workspace_name="test_ws", source_item_id="S4-251003", source_path="/path", source_kind="tdoc", status="included"),
            MagicMock(
                source_item_id="S4-251003",
                is_active=True,
                source_kind="tdoc",
            ),
        ]

        mock_run_pipeline.return_value = ProcessingStatus(document_id="S4-251003", current_stage=PipelineStage.COMPLETED)
        mock_run_pipeline.return_value = _status("S4-251003", stage=PipelineStage.COMPLETED)

        tdoc_ids = ["S4-251003", "26260-j10"]
        # Only S4-251003 should be processed because of the workspace scope
@@ -108,18 +117,6 @@ class TestRunPipeline:
        assert "26260-j10" not in results
        mock_list_members.assert_called_once_with("test_ws")

    def test_progress_callback_invocation(self, mock_storage: MagicMock, test_data_dir: Path) -> None:
        """Test progress callback is invoked after each stage."""
        callback_calls = []

        def progress_callback(stage: PipelineStage, tdoc_id: str) -> None:
            callback_calls.append((stage, tdoc_id))

        tdoc_folder = test_data_dir / "S4-251003"
        if tdoc_folder.exists():
            run_pipeline("S4-251003", tdoc_folder, mock_storage, progress_callback=progress_callback)
            assert len(callback_calls) > 0


class TestProcessTdocApi:
    """Tests for public API functions."""
@@ -140,21 +137,16 @@ class TestProcessTdocApi:
        """new_only mode should return only non-completed items."""
        # This test verifies the filtering logic in process_all
        # by mocking the storage.get_status call
        completed = ProcessingStatus(document_id="S4-251003", current_stage=PipelineStage.COMPLETED)
        pending = ProcessingStatus(document_id="S4-260001", current_stage=PipelineStage.PENDING)
        completed = _status("S4-251003", stage=PipelineStage.COMPLETED)
        pending = _status("S4-260001", stage=PipelineStage.PENDING)

        # Create a mock storage that returns completed status for one doc
        mock_storage = MagicMock(spec=AiStorage)
        mock_storage.get_status.side_effect = lambda doc_id, workspace=None: completed if doc_id == "S4-251003" else None

        # Mock the container to return our mock storage
        def mock_get_instance() -> MagicMock:
            container = MagicMock()
            container.get_ai_storage.return_value = mock_storage
            container.get_embeddings_manager.return_value = MagicMock()
            return container

        monkeypatch.setattr("tdoc_ai.operations.pipeline.AiServiceContainer.get_instance", mock_get_instance)
        mock_manager = MagicMock()
        mock_manager.storage = mock_storage
        monkeypatch.setattr("tdoc_ai.operations.pipeline.EmbeddingsManager", lambda _: mock_manager)

        # Create checkout folders so the pipeline doesn't skip them
        (tmp_path / "S4-251003").mkdir()
+1 −12
Original line number Diff line number Diff line
@@ -6,7 +6,6 @@ import json
from unittest.mock import patch

import pytest
from tdoc_ai.container import AiServiceContainer
from tdoc_ai.models import DocumentSummary, LlmConfigError
from tdoc_ai.operations import summarize
from tdoc_ai.operations.summarize import _count_words, _should_skip_summary
@@ -36,6 +35,7 @@ class TestSummarization:
            action_items=["Action 1"],
            decisions=["Decision 1"],
            affected_specs=["TS 26.260"],
            prompt_version="v1",
        )

        assert summary.key_points is not None
@@ -79,17 +79,6 @@ class TestSummarization:
        assert summary.affected_specs == ["TS 26.260"]
        assert mock_client.complete.call_count == 2

    def test_missing_llm_model_config_raises_error(self, monkeypatch: pytest.MonkeyPatch) -> None:
        """Missing/invalid LLM config should raise LlmConfigError."""
        monkeypatch.setattr(
            AiServiceContainer,
            "get_instance",
            lambda: (_ for _ in ()).throw(ValueError("llm_model missing")),
        )

        with pytest.raises(LlmConfigError):
            summarize.summarize_document("S4-260001", "# sample markdown")

    def test_unreachable_llm_endpoint_raises_error(self, ai_storage: AiStorage) -> None:
        """Connection failures must surface as LlmConfigError."""
        with patch("tdoc_ai.operations.summarize._get_llm_client") as get_client:
+5 −9
Original line number Diff line number Diff line
@@ -7,14 +7,10 @@ from tdoc_ai import get_status


class TestWorkspaceContract:
    @patch("tdoc_ai.operations.pipeline.AiStorage")
    def test_get_status_scopes_to_workspace(self, mock_ai_storage: MagicMock, mock_storage: MagicMock) -> None:
    @patch("tdoc_ai.operations.pipeline.EmbeddingsManager")
    def test_get_status_scopes_to_workspace(self, mock_embeddings_manager: MagicMock, mock_storage: MagicMock) -> None:
        """Verify get_status uses workspace scope."""
        mock_ai_storage.return_value = mock_storage
        with patch("tdoc_ai.operations.pipeline.AiServiceContainer") as mock_container:
            mock_container_instance = MagicMock()
            mock_container_instance.get_storage.return_value = mock_storage
            mock_container.get_instance.return_value = mock_container_instance
        mock_embeddings_manager.return_value.storage = mock_storage
        get_status(document_id="S4-251003", workspace="custom_ws")
        mock_storage.get_status.assert_called_once_with("S4-251003", workspace="custom_ws")

+1 −1
Original line number Diff line number Diff line
@@ -11,7 +11,7 @@ from tdoc_ai.storage import AiStorage


def _load_workspaces_module() -> ModuleType:
    file_path = Path(__file__).resolve().parents[2] / "tdoc-ai" / "tdoc_ai" / "operations" / "workspaces.py"
    file_path = Path(__file__).resolve().parents[2] / "src" / "tdoc-ai" / "tdoc_ai" / "operations" / "workspaces.py"
    spec = importlib.util.spec_from_file_location("workspace_ops", file_path)
    if spec is None or spec.loader is None:
        raise RuntimeError("Failed to load workspace operations module")

tests/teddi_mcp/test_cli.py

deleted100644 → 0
+0 −97
Original line number Diff line number Diff line
"""Tests for teddi-mcp CLI output formats."""

from __future__ import annotations

import json
import sys
import types

import pytest
from teddi_mcp.cli import app
from teddi_mcp.models import DocumentRef, SearchRequest, SearchResponse, TermResult
from typer.testing import CliRunner

runner = CliRunner()


class _FakeTeddiClient:
    """Simple async client stub for CLI tests."""

    async def __aenter__(self) -> _FakeTeddiClient:
        return self

    async def __aexit__(self, exc_type: object, exc: object, tb: object) -> None:
        return None

    async def search_terms(self, request: SearchRequest) -> SearchResponse:
        result = TermResult(
            term="QoS",
            description="Quality of Service",
            documents=[
                DocumentRef(
                    technical_body="3GPP",
                    specification="TS 24.008",
                    url="https://example.com/spec",
                )
            ],
        )
        return SearchResponse(query=request, results=[result], total_count=1)


@pytest.fixture
def patch_client(monkeypatch: pytest.MonkeyPatch) -> None:
    """Patch TeddiClient with deterministic async stub."""
    monkeypatch.setattr("teddi_mcp.cli.TeddiClient", _FakeTeddiClient)


def test_search_term_json_output(patch_client: None) -> None:
    """CLI emits valid JSON for --output json."""
    result = runner.invoke(app, ["search", "term", "QoS", "--output", "json"])

    assert result.exit_code == 0
    parsed = json.loads(result.stdout)
    assert parsed["query"]["term"] == "QoS"
    assert parsed["results"][0]["term"] == "QoS"


def test_search_term_ison_output(patch_client: None, monkeypatch: pytest.MonkeyPatch) -> None:
    """CLI uses ison serializer for --output ison."""
    fake_ison_module = types.ModuleType("ison_parser")
    fake_ison_module.from_dict = lambda payload: payload  # type: ignore[attr-defined]
    fake_ison_module.dumps = lambda payload: f"ISON:{payload['query']['term']}"  # type: ignore[attr-defined]
    monkeypatch.setitem(sys.modules, "ison_parser", fake_ison_module)

    result = runner.invoke(app, ["search", "term", "QoS", "--output", "ison"])

    assert result.exit_code == 0
    assert "ISON:QoS" in result.stdout


def test_search_term_toon_output(patch_client: None, monkeypatch: pytest.MonkeyPatch) -> None:
    """CLI uses toon serializer for --output toon."""
    fake_toon_module = types.ModuleType("toon_format")
    fake_toon_module.encode = lambda payload: f"TOON:{payload['query']['term']}"  # type: ignore[attr-defined]
    monkeypatch.setitem(sys.modules, "toon_format", fake_toon_module)

    result = runner.invoke(app, ["search", "term", "QoS", "--output", "toon"])

    assert result.exit_code == 0
    assert "TOON:QoS" in result.stdout


def test_search_term_table_output(patch_client: None) -> None:
    """CLI renders Rich table for --output table."""
    result = runner.invoke(app, ["search", "term", "QoS", "--output", "table"])

    assert result.exit_code == 0
    assert "TEDDI Search Results" in result.stdout
    assert "QoS" in result.stdout


def test_search_term_rejects_invalid_output(patch_client: None) -> None:
    """CLI rejects unsupported output values."""
    result = runner.invoke(app, ["search", "term", "QoS", "--output", "xml"])

    assert result.exit_code != 0
    combined_output = result.stdout + result.stderr
    assert "Invalid value for '--output'" in combined_output
Loading