1+ """Tests for the VLM provider Strategy Pattern (issue #592)."""
2+ from unittest .mock import MagicMock , patch
3+ import pytest
4+
5+ from app .vision .base import BaseVisionProvider
6+ from app .vision .registry import _REGISTRY , get_vision_provider , register_provider
7+
8+
9+ class TestBaseVisionProvider :
10+ def test_cannot_instantiate_abstract_class (self ):
11+ with pytest .raises (TypeError ):
12+ BaseVisionProvider ()
13+
14+ def test_concrete_subclass_works (self ):
15+ class Dummy (BaseVisionProvider ):
16+ def caption (self , image_bytes : bytes ) -> str :
17+ return "dummy"
18+ assert Dummy ().caption (b"x" ) == "dummy"
19+
20+
21+ class TestRegistry :
22+ def setup_method (self ):
23+ self ._original = dict (_REGISTRY )
24+
25+ def teardown_method (self ):
26+ _REGISTRY .clear ()
27+ _REGISTRY .update (self ._original )
28+
29+ def test_register_and_retrieve (self ):
30+ class FakeProvider (BaseVisionProvider ):
31+ def caption (self , image_bytes : bytes ) -> str :
32+ return "fake"
33+ register_provider ("fake" , FakeProvider )
34+ assert get_vision_provider ("fake" ) is not None
35+
36+ def test_case_insensitive (self ):
37+ class P (BaseVisionProvider ):
38+ def caption (self , image_bytes : bytes ) -> str :
39+ return ""
40+ register_provider ("UPPER" , P )
41+ assert get_vision_provider ("upper" ) is not None
42+
43+ def test_unknown_returns_none (self ):
44+ assert get_vision_provider ("doesnotexist" ) is None
45+
46+ def test_none_returns_none (self ):
47+ assert get_vision_provider (None ) is None
48+
49+ def test_broken_init_returns_none (self ):
50+ class Broken (BaseVisionProvider ):
51+ def __init__ (self ): raise RuntimeError ("fail" )
52+ def caption (self , image_bytes : bytes ) -> str : return ""
53+ register_provider ("broken" , Broken )
54+ assert get_vision_provider ("broken" ) is None
55+
56+
57+ class TestCaptionImage :
58+ def test_uses_provider_when_configured (self ):
59+ from app .rag .vision import caption_image
60+
61+ class StubProvider (BaseVisionProvider ):
62+ def caption (self , image_bytes : bytes ) -> str :
63+ return "stub caption"
64+
65+ with patch ("app.rag.vision.get_vision_provider" , return_value = StubProvider ()):
66+ assert caption_image (b"img" , page = 1 ) == "stub caption"
67+
68+ def test_falls_back_to_ocr (self ):
69+ from app .rag .vision import caption_image
70+
71+ class EmptyProvider (BaseVisionProvider ):
72+ def caption (self , image_bytes : bytes ) -> str :
73+ return ""
74+
75+ with patch ("app.rag.vision.get_vision_provider" , return_value = EmptyProvider ()):
76+ with patch ("app.rag.vision._ocr_caption" , return_value = "ocr text" ):
77+ assert caption_image (b"img" , page = 1 ) == "ocr text"
78+
79+ def test_falls_back_to_placeholder (self ):
80+ from app .rag .vision import caption_image
81+
82+ with patch ("app.rag.vision.get_vision_provider" , return_value = None ):
83+ with patch ("app.rag.vision._ocr_caption" , return_value = "" ):
84+ result = caption_image (b"img" , page = 3 )
85+ assert "page 3" in result
86+
87+ def test_batch_mode (self ):
88+ from app .rag .vision import caption_image
89+
90+ with patch ("app.rag.vision.get_vision_provider" , return_value = None ):
91+ with patch ("app.rag.vision._ocr_caption" , return_value = "" ):
92+ results = caption_image ([b"img1" , b"img2" ], page = [1 , 2 ])
93+ assert isinstance (results , list ) and len (results ) == 2
0 commit comments