deer-flow/backend/tests/test_ddg_image_search_urls.py
Chaoqi d06fc14be9
fix(image-search): keep the source page URL in DuckDuckGo and Serper results (#6915)
* test(image-search): expect source_url in DuckDuckGo and Serper results

* fix(image-search): keep the source page URL in DuckDuckGo and Serper results

The DuckDuckGo and Serper image_search tools returned only title,
image_url and thumbnail_url and dropped the page each image comes from
(DDGS `url`, Serper `link`). Brave already returns it as `source_url`,
and the workspace UI links every image thumbnail through `source_url`,
so DuckDuckGo (the default provider) and Serper results rendered as
"Unsafe link omitted" and the agent lost the image's source page.

Return `source_url` from both providers, screened with
`safe_public_result_url` like the image and thumbnail URLs.
2026-10-15 14:56:46 +08:00

53 lines
2.5 KiB
Python

import json
from unittest.mock import patch
import pytest
from deerflow.community.image_search import tools
from deerflow.community.url_safety import safe_public_result_url
@pytest.mark.asyncio
async def test_image_search_drops_unsafe_result_urls() -> None:
rows = [
{"title": "safe", "image": "https://images.example/full.jpg", "thumbnail": "https://images.example/thumb.jpg", "url": "https://images.example/page"},
{"title": "unsafe original", "image": "http://127.0.0.1/private", "thumbnail": "https://images.example/thumb2.jpg", "url": "http://10.0.0.1/page"},
{"title": "unsafe thumbnail", "image": "https://images.example/full3.jpg", "thumbnail": "javascript:alert(1)"},
{"title": "both unsafe", "image": "http://2130706433/private", "thumbnail": "http://localhost/thumb"},
]
with patch.object(tools, "_search_images", return_value=rows), patch.object(tools, "get_app_config") as config:
config.return_value.get_tool_config.return_value = None
result = json.loads(await tools.image_search_tool.ainvoke({"query": "reference"}))
assert result["total_results"] == 3
assert result["results"] == [
{"title": "safe", "image_url": "https://images.example/full.jpg", "thumbnail_url": "https://images.example/thumb.jpg", "source_url": "https://images.example/page"},
{"title": "unsafe original", "image_url": "", "thumbnail_url": "https://images.example/thumb2.jpg", "source_url": ""},
{"title": "unsafe thumbnail", "image_url": "https://images.example/full3.jpg", "thumbnail_url": "", "source_url": ""},
]
@pytest.mark.asyncio
async def test_image_search_reports_when_all_urls_are_unsafe() -> None:
with patch.object(tools, "_search_images", return_value=[{"title": "local", "image": "http://[::1]/image"}]), patch.object(tools, "get_app_config") as config:
config.return_value.get_tool_config.return_value = None
result = json.loads(await tools.image_search_tool.ainvoke({"query": "reference"}))
assert result == {"error": "No safe image URLs found", "query": "reference"}
@pytest.mark.parametrize(
"url",
[
"file:///etc/passwd",
"http://metadata.google.internal/computeMetadata/v1/",
"http://127.0.0.1/image",
"http://0177.0.0.1/image",
"http://0x7f000001/image",
"http://[::ffff:127.0.0.1]/image",
"http://[64:ff9b::7f00:1]/image",
],
)
def test_image_search_rejects_local_result_url_spellings(url: str) -> None:
assert safe_public_result_url(url) == ""