"""Tests for LLMProvider._strip_image_content and _strip_image_content_inplace. Regression test for #4345: image-strip fallback should not leak file paths or produce text that makes the model hallucinate about unseen images. """ from __future__ import annotations from nanobot.providers.base import LLMProvider # --------------------------------------------------------------------------- # _strip_image_content (returns new list) # --------------------------------------------------------------------------- class TestStripImageContent: """Tests for the non-mutating _strip_image_content method.""" def test_replaces_image_url_with_nondescriptive_placeholder(self): messages = [ { "role": "user", "content": [ {"type": "text", "text": "What is this?"}, { "type": "image_url", "image_url": {"url": "data:image/png;base64,abc"}, "_meta": {"path": "/tmp/screenshot.png"}, }, ], } ] result = LLMProvider._strip_image_content(messages) assert result is not None content = result[0]["content"] assert len(content) == 2 assert content[0] == {"type": "text", "text": "What is this?"} assert content[1]["type"] == "text" placeholder = content[1]["text"] # Must NOT leak the file path assert "/tmp/screenshot.png" not in placeholder assert "screenshot" not in placeholder # Must clearly indicate the image was not delivered assert "not delivered" in placeholder.lower() def test_returns_none_when_no_images(self): messages = [ {"role": "user", "content": [{"type": "text", "text": "hello"}]} ] assert LLMProvider._strip_image_content(messages) is None def test_handles_multiple_images(self): messages = [ { "role": "user", "content": [ {"type": "text", "text": "Look at these:"}, { "type": "image_url", "image_url": {"url": "data:image/png;base64,abc"}, "_meta": {"path": "/a.png"}, }, { "type": "image_url", "image_url": {"url": "data:image/png;base64,def"}, "_meta": {"path": "/b.png"}, }, ], } ] result = LLMProvider._strip_image_content(messages) assert result is not None content = result[0]["content"] # text + 2 placeholders assert len(content) == 3 for block in content[1:]: assert block["type"] == "text" assert "/a.png" not in block["text"] assert "/b.png" not in block["text"] def test_handles_image_without_meta(self): messages = [ { "role": "user", "content": [ { "type": "image_url", "image_url": {"url": "data:image/png;base64,abc"}, }, ], } ] result = LLMProvider._strip_image_content(messages) assert result is not None placeholder = result[0]["content"][0]["text"] assert "not delivered" in placeholder.lower() def test_preserves_non_image_content(self): messages = [ {"role": "system", "content": "You are helpful."}, { "role": "user", "content": [ {"type": "text", "text": "Hello"}, { "type": "image_url", "image_url": {"url": "data:image/png;base64,abc"}, "_meta": {"path": "/x.png"}, }, ], }, {"role": "assistant", "content": "Hi there!"}, ] result = LLMProvider._strip_image_content(messages) assert result is not None # system and assistant messages unchanged assert result[0]["content"] == "You are helpful." assert result[2]["content"] == "Hi there!" # user message has image replaced assert result[1]["content"][0]["type"] == "text" # --------------------------------------------------------------------------- # _strip_image_content_inplace (mutates in place) # --------------------------------------------------------------------------- class TestStripImageContentInplace: """Tests for the mutating _strip_image_content_inplace method.""" def test_replaces_image_url_with_nondescriptive_placeholder(self): messages = [ { "role": "user", "content": [ {"type": "text", "text": "What is this?"}, { "type": "image_url", "image_url": {"url": "data:image/png;base64,abc"}, "_meta": {"path": "/tmp/photo.jpg"}, }, ], } ] found = LLMProvider._strip_image_content_inplace(messages) assert found is True content = messages[0]["content"] assert len(content) == 2 placeholder = content[1]["text"] assert "/tmp/photo.jpg" not in placeholder assert "not delivered" in placeholder.lower() def test_returns_false_when_no_images(self): messages = [ {"role": "user", "content": [{"type": "text", "text": "hello"}]} ] assert LLMProvider._strip_image_content_inplace(messages) is False def test_mutates_original_messages(self): original_content = [ {"type": "text", "text": "see this"}, { "type": "image_url", "image_url": {"url": "data:image/png;base64,abc"}, "_meta": {"path": "/img.png"}, }, ] messages = [{"role": "user", "content": original_content}] LLMProvider._strip_image_content_inplace(messages) # The original list was mutated assert original_content[1]["type"] == "text" assert "/img.png" not in original_content[1]["text"]