"""Tests for new v1.1 mock tasks: email_classify, cv_tailor, deadline_extract.""" from __future__ import annotations import pytest from llm_gateway.config import GatewayConfig, ProviderConfig, TaskClass from llm_gateway.gateway import Gateway from llm_gateway.mock import get_mock_output, MOCK_OUTPUTS def mock_config(**overrides) -> GatewayConfig: """Build a config in mock mode (no API key).""" primary = ProviderConfig( name="primary", base_url="https://mock.example.com/v1", api_key="", model="glm-5.2", ) defaults = { "primary": primary, "fallback": None, "cheap_model": "glm-5.2", "strong_model": "glm-5.2", "budgets": { "score": 2000, "extract": 4000, "email_classify": 1000, "deadline_extract": 500, "cv_tailor": 6000, "default": 4000, }, "max_retries": 2, } defaults.update(overrides) return GatewayConfig(**defaults) class TestEmailClassifyMock: async def test_email_classify_returns_deterministic(self) -> None: """email_classify mock returns interview_invite classification.""" config = mock_config() gw = Gateway(config) result_a = await gw.run_task("email_classify", "Email from recruiter") result_b = await gw.run_task("email_classify", "Email from recruiter") assert result_a == result_b assert result_a["classification"] == "interview_invite" assert result_a["state_proposal"] == "interviewing" assert "reason" in result_a await gw.aclose() async def test_email_classify_is_cheap(self) -> None: """email_classify should be classified as CHEAP.""" config = mock_config() assert config.get_task_class("email_classify") == TaskClass.CHEAP assert config.get_model("email_classify") == config.cheap_model def test_email_classify_in_mock_outputs(self) -> None: """email_classify should be in MOCK_OUTPUTS.""" assert "email_classify" in MOCK_OUTPUTS output = get_mock_output("email_classify") assert output["classification"] == "interview_invite" assert output["state_proposal"] == "interviewing" class TestCvTailorMock: async def test_cv_tailor_returns_deterministic(self) -> None: """cv_tailor mock returns tailored CV with change_log.""" config = mock_config() gw = Gateway(config) result_a = await gw.run_task("cv_tailor", "Tailor CV for posting") result_b = await gw.run_task("cv_tailor", "Tailor CV for posting") assert result_a == result_b assert "tailored_cv" in result_a assert "change_log" in result_a assert isinstance(result_a["change_log"], list) assert len(result_a["change_log"]) >= 1 # Check change_log entries have action and detail. for entry in result_a["change_log"]: assert "action" in entry assert "detail" in entry await gw.aclose() async def test_cv_tailor_is_strong(self) -> None: """cv_tailor should be classified as STRONG.""" config = mock_config() assert config.get_task_class("cv_tailor") == TaskClass.STRONG assert config.get_model("cv_tailor") == config.strong_model def test_cv_tailor_in_mock_outputs(self) -> None: """cv_tailor should be in MOCK_OUTPUTS.""" assert "cv_tailor" in MOCK_OUTPUTS output = get_mock_output("cv_tailor") assert "tailored_cv" in output assert "change_log" in output # Check it has skills and experience. assert "skills" in output["tailored_cv"] assert "experience" in output["tailored_cv"] class TestDeadlineExtractMock: async def test_deadline_extract_returns_deterministic(self) -> None: """deadline_extract mock returns apply_by (null by default).""" config = mock_config() gw = Gateway(config) result_a = await gw.run_task("deadline_extract", "Extract deadline") result_b = await gw.run_task("deadline_extract", "Extract deadline") assert result_a == result_b assert "apply_by" in result_a # Default mock has null deadline. assert result_a["apply_by"] is None await gw.aclose() async def test_deadline_extract_is_cheap(self) -> None: """deadline_extract should be classified as CHEAP.""" config = mock_config() assert config.get_task_class("deadline_extract") == TaskClass.CHEAP assert config.get_model("deadline_extract") == config.cheap_model def test_deadline_extract_in_mock_outputs(self) -> None: """deadline_extract should be in MOCK_OUTPUTS.""" assert "deadline_extract" in MOCK_OUTPUTS output = get_mock_output("deadline_extract") assert "apply_by" in output assert output["apply_by"] is None