File size: 2,808 Bytes
94eb0cb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
77
78
79
"""Sprint A14-S54 — garde-fou MRO BaseVLMAdapter (fix audit #6).

Avant S54, l'ordre des parents dans :

    class AnthropicVLMAdapter(BaseVLMAdapter, AnthropicAdapter)

était critique mais non vérifié.  Un swap accidentel à
``(AnthropicAdapter, BaseVLMAdapter)`` aurait silencieusement donné
output_types = {CORRECTED_TEXT} (depuis LLM) au lieu de {RAW_TEXT}
(depuis VLM) — l'erreur ne se serait manifestée qu'au runtime sur
une jonction de type incompatible.

S54 ajoute ``__init_subclass__`` qui lève ``TypeError`` à la
définition de la classe si l'ordre est incorrect.
"""

from __future__ import annotations

import pytest

from picarones.adapters.llm.anthropic_adapter import AnthropicAdapter
from picarones.adapters.llm.openai_adapter import OpenAIAdapter
from picarones.adapters.vlm import (
    AnthropicVLMAdapter,
    BaseVLMAdapter,
    OpenAIVLMAdapter,
)
from picarones.domain.artifacts import ArtifactType


class TestExistingAdaptersStillValid:
    """Les 4 VLM adapters concrets définis correctement passent."""

    def test_anthropic_vlm_defined(self) -> None:
        # Si l'ordre était mauvais, l'import aurait planté.
        adapter = AnthropicVLMAdapter()
        assert adapter.input_types == frozenset({ArtifactType.IMAGE})
        assert adapter.output_types == frozenset({ArtifactType.RAW_TEXT})

    def test_openai_vlm_defined(self) -> None:
        adapter = OpenAIVLMAdapter()
        assert adapter.input_types == frozenset({ArtifactType.IMAGE})


class TestWrongOrderRejected:
    def test_llm_first_then_vlm_rejected(self) -> None:
        """Définir une classe avec LLM avant VLM doit lever TypeError."""
        with pytest.raises(TypeError, match="ordre MRO"):
            # Définition dynamique d'une classe avec mauvais ordre.
            type(
                "BadOrderVLM",
                (AnthropicAdapter, BaseVLMAdapter),
                {"name": property(lambda self: "bad")},
            )

    def test_correct_order_accepted(self) -> None:
        """L'ordre correct (VLM en premier) est accepté."""
        # Test propriété : aucun TypeError levé.
        type(
            "GoodOrderVLM",
            (BaseVLMAdapter, OpenAIAdapter),
            {"name": property(lambda self: "good")},
        )


class TestErrorMessageHelpful:
    def test_message_explains_the_fix(self) -> None:
        with pytest.raises(TypeError) as exc_info:
            type(
                "BadVLM",
                (AnthropicAdapter, BaseVLMAdapter),
                {"name": property(lambda self: "x")},
            )
        msg = str(exc_info.value)
        # Le message doit suggérer la correction concrète.
        assert "BaseVLMAdapter" in msg
        assert "AnthropicAdapter" in msg
        assert "Corrigez" in msg or "correct" in msg.lower()