File size: 2,798 Bytes
20c251e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
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