from __future__ import annotations import logging import pytest from dovla_cil.vlm.client import VLMClient, VLMParseError def test_mock_mode_returns_deterministic_json(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setenv("OPENCLAUDE_MOCK", "1") monkeypatch.setenv("OPENCLAUDE_MODEL", "mock-model") client = VLMClient(api_key="test-secret") first = client.chat_json("system", "user", schema_hint={"answer": "string"}) second = client.chat_json("system", "user", schema_hint={"answer": "string"}) assert first == second assert first["mock"] is True assert first["model"] == "mock-model" assert first["schema_keys"] == ["answer"] def test_chat_json_parses_fenced_json() -> None: class StaticTextClient(VLMClient): def chat_text(self, system: str, user: str) -> str: del system, user return '```json\n{"answer": 7, "ok": true}\n```' client = StaticTextClient(api_key="test-secret", model="model") assert client.chat_json("system", "user") == {"answer": 7, "ok": True} def test_chat_json_extracts_first_json_object() -> None: class StaticTextClient(VLMClient): def chat_text(self, system: str, user: str) -> str: del system, user return 'Here is the result: {"answer": {"nested": true}}. Thanks.' client = StaticTextClient(api_key="test-secret", model="model") assert client.chat_json("system", "user") == {"answer": {"nested": True}} def test_parse_error_redacts_secret() -> None: class BadTextClient(VLMClient): def chat_text(self, system: str, user: str) -> str: del system, user return "not json test-secret" client = BadTextClient(api_key="test-secret", model="model") with pytest.raises(VLMParseError) as exc_info: client.chat_json("system", "user") assert "test-secret" not in str(exc_info.value) assert "***REDACTED***" in str(exc_info.value) def test_repr_and_retry_logs_redact_secret( caplog: pytest.LogCaptureFixture, monkeypatch: pytest.MonkeyPatch ) -> None: class FailingClient(VLMClient): def _request_once(self, *, messages, response_format): del messages, response_format raise RuntimeError(f"boom {self.api_key}") monkeypatch.setattr("dovla_cil.vlm.client.time.sleep", lambda _seconds: None) client = FailingClient(api_key="test-secret", model="model", max_retries=1) assert "test-secret" not in repr(client) with caplog.at_level(logging.WARNING, logger="dovla_cil.vlm.client"): with pytest.raises(Exception) as exc_info: client.chat_text("system", "user") assert "test-secret" not in caplog.text assert "test-secret" not in str(exc_info.value) assert "***REDACTED***" in caplog.text