2024-05-09 15:40:36 +02:00
|
|
|
|
# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
|
|
|
|
|
|
#
|
|
|
|
|
|
# SPDX-License-Identifier: Apache-2.0
|
2024-05-22 13:14:39 +02:00
|
|
|
|
|
2023-09-20 11:55:18 +02:00
|
|
|
|
import os
|
2023-09-06 17:31:42 +02:00
|
|
|
|
from unittest.mock import Mock, patch
|
|
|
|
|
|
|
|
|
|
|
|
import pytest
|
2025-07-03 09:49:09 +02:00
|
|
|
|
from requests import HTTPError, RequestException, Timeout
|
2023-09-06 17:31:42 +02:00
|
|
|
|
|
2023-11-24 14:48:43 +01:00
|
|
|
|
from haystack import Document
|
2025-07-03 09:49:09 +02:00
|
|
|
|
from haystack.components.websearch.serper_dev import SerperDevError, SerperDevWebSearch
|
|
|
|
|
|
from haystack.utils.auth import Secret
|
2023-09-06 17:31:42 +02:00
|
|
|
|
|
|
|
|
|
|
EXAMPLE_SERPERDEV_RESPONSE = {
|
|
|
|
|
|
"searchParameters": {
|
|
|
|
|
|
"q": "Who is the boyfriend of Olivia Wilde?",
|
|
|
|
|
|
"gl": "us",
|
|
|
|
|
|
"hl": "en",
|
|
|
|
|
|
"autocorrect": True,
|
|
|
|
|
|
"type": "search",
|
|
|
|
|
|
},
|
|
|
|
|
|
"organic": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"title": "Olivia Wilde embraces Jason Sudeikis amid custody battle, Harry Styles split - Page Six",
|
|
|
|
|
|
"link": "https://pagesix.com/2023/01/29/olivia-wilde-hugs-it-out-with-jason-sudeikis-after-harry-styles-split/",
|
2025-07-03 09:49:09 +02:00
|
|
|
|
"snippet": "Looks like Olivia Wilde and Jason Sudeikis are starting 2023 on good terms. Amid their highly "
|
|
|
|
|
|
"publicized custody battle – and the actress' ...",
|
2023-09-06 17:31:42 +02:00
|
|
|
|
"date": "Jan 29, 2023",
|
|
|
|
|
|
"position": 1,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"title": "Olivia Wilde Is 'Quietly Dating' Again Following Harry Styles Split: 'He Makes Her Happy'",
|
|
|
|
|
|
"link": "https://www.yahoo.com/now/olivia-wilde-quietly-dating-again-183844364.html",
|
2025-07-03 09:49:09 +02:00
|
|
|
|
"snippet": "Olivia Wilde is “quietly dating again” following her November 2022 split from Harry Styles, "
|
|
|
|
|
|
"a source exclusively tells Life & Style.",
|
2023-09-06 17:31:42 +02:00
|
|
|
|
"date": "Feb 10, 2023",
|
|
|
|
|
|
"position": 2,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"title": "Olivia Wilde and Harry Styles' Relationship Timeline: The Way They Were - Us Weekly",
|
|
|
|
|
|
"link": "https://www.usmagazine.com/celebrity-news/pictures/olivia-wilde-and-harry-styles-relationship-timeline/",
|
2025-07-03 09:49:09 +02:00
|
|
|
|
"snippet": "Olivia Wilde started dating Harry Styles after ending her years-long engagement to Jason "
|
|
|
|
|
|
"Sudeikis — see their relationship timeline.",
|
2023-09-06 17:31:42 +02:00
|
|
|
|
"date": "Mar 10, 2023",
|
|
|
|
|
|
"imageUrl": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSgTcalNFvptTbYBiDXX55s8yCGfn6F1qbed9DAN16LvynTr9GayK5SPmY&s",
|
|
|
|
|
|
"position": 3,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"title": "Olivia Wilde Is 'Ready to Date Again' After Harry Styles Split - Us Weekly",
|
|
|
|
|
|
"link": "https://www.usmagazine.com/celebrity-news/news/olivia-wilde-is-ready-to-date-again-after-harry-styles-split/",
|
2025-07-03 09:49:09 +02:00
|
|
|
|
"snippet": "Ready for love! Olivia Wilde is officially back on the dating scene following her split from "
|
|
|
|
|
|
"her ex-boyfriend, Harry Styles.",
|
2023-09-06 17:31:42 +02:00
|
|
|
|
"date": "Mar 1, 2023",
|
|
|
|
|
|
"imageUrl": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRCRAeRy5sVE631ZctzbzuOF70xkIOHaTvh2K7dYvdiVBwALiKrIjpscok&s",
|
|
|
|
|
|
"position": 4,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"title": "Harry Styles and Olivia Wilde's Definitive Relationship Timeline - Harper's Bazaar",
|
|
|
|
|
|
"link": "https://www.harpersbazaar.com/celebrity/latest/a35172115/harry-styles-olivia-wilde-relationship-timeline/",
|
2025-07-03 09:49:09 +02:00
|
|
|
|
"snippet": "November 2020: News breaks about Olivia splitting from fiancé Jason Sudeikis. ... "
|
|
|
|
|
|
"In mid-November, news breaks of Olivia Wilde's split from Jason ...",
|
2023-09-06 17:31:42 +02:00
|
|
|
|
"date": "Feb 23, 2023",
|
|
|
|
|
|
"imageUrl": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRRqw3fvZOIGHEepxCc7yFAWYsS_v_1H6X-4nxyFJxdfRuFQw_BrI6JVzI&s",
|
|
|
|
|
|
"position": 5,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"title": "Harry Styles and Olivia Wilde's Relationship Timeline - People",
|
|
|
|
|
|
"link": "https://people.com/music/harry-styles-olivia-wilde-relationship-timeline/",
|
2025-07-03 09:49:09 +02:00
|
|
|
|
"snippet": "Harry Styles and Olivia Wilde first met on the set of Don't Worry Darling and stepped out as "
|
|
|
|
|
|
"a couple in January 2021. Relive all their biggest relationship ...",
|
2023-09-06 17:31:42 +02:00
|
|
|
|
"position": 6,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"title": "Jason Sudeikis and Olivia Wilde's Relationship Timeline - People",
|
|
|
|
|
|
"link": "https://people.com/movies/jason-sudeikis-olivia-wilde-relationship-timeline/",
|
2025-07-03 09:49:09 +02:00
|
|
|
|
"snippet": "Jason Sudeikis and Olivia Wilde ended their engagement of seven years in 2020. Here's a "
|
|
|
|
|
|
"complete timeline of their relationship.",
|
2023-09-06 17:31:42 +02:00
|
|
|
|
"date": "Mar 24, 2023",
|
|
|
|
|
|
"imageUrl": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSleZoXusQyJJe2WMgIuck_cVaJ8AE0_hU2QxsXzYvKANi55UQlv82yAVI&s",
|
|
|
|
|
|
"position": 7,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
2025-07-03 09:49:09 +02:00
|
|
|
|
"title": "Olivia Wilde's anger at ex-boyfriend Harry Styles: She resents him and thinks he was using her "
|
|
|
|
|
|
"| Marca",
|
2023-09-06 17:31:42 +02:00
|
|
|
|
"link": "https://www.marca.com/en/lifestyle/celebrities/2023/02/23/63f779a4e2704e8d988b4624.html",
|
2025-07-03 09:49:09 +02:00
|
|
|
|
"snippet": "The two started dating after Wilde split up with actor Jason Sudeikisin 2020. However, their "
|
|
|
|
|
|
"relationship came to an end last November.",
|
2023-09-06 17:31:42 +02:00
|
|
|
|
"date": "Feb 23, 2023",
|
|
|
|
|
|
"imageUrl": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQBgJF2mSnIWCvPrqUqM4WTI9xPNWPyLvHuune85swpB1yE_G8cy_7KRh0&s",
|
|
|
|
|
|
"position": 8,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"title": "Olivia Wilde's dating history: Who has the actress dated? | The US Sun",
|
|
|
|
|
|
"link": "https://www.the-sun.com/entertainment/5221040/olivia-wildes-dating-history/",
|
2025-07-03 09:49:09 +02:00
|
|
|
|
"snippet": "AMERICAN actress Olivia Wilde started dating Harry Styles in January 2021 after breaking off "
|
|
|
|
|
|
"her engagement the year prior.",
|
2023-09-06 17:31:42 +02:00
|
|
|
|
"date": "Nov 19, 2022",
|
|
|
|
|
|
"imageUrl": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTpm8BToVFHJoH6yRggg0fLocLT9mt6lwsnRxFFDNdDGhDydzQiSKZ9__g&s",
|
|
|
|
|
|
"position": 9,
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
"relatedSearches": [
|
|
|
|
|
|
{"query": "Harry Styles girlfriends in order"},
|
|
|
|
|
|
{"query": "Harry Styles and Olivia Wilde engaged"},
|
|
|
|
|
|
{"query": "Harry Styles and Olivia Wilde wedding"},
|
|
|
|
|
|
{"query": "Who is Harry Styles married to"},
|
|
|
|
|
|
{"query": "Jason Sudeikis Olivia Wilde relationship"},
|
|
|
|
|
|
{"query": "Olivia Wilde and Jason Sudeikis kids"},
|
|
|
|
|
|
{"query": "Olivia Wilde children"},
|
|
|
|
|
|
{"query": "Harry Styles and Olivia Wilde age difference"},
|
|
|
|
|
|
{"query": "Jason Sudeikis Olivia Wilde, Harry Styles"},
|
|
|
|
|
|
],
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
|
def mock_serper_dev_search_result():
|
2023-11-24 14:48:43 +01:00
|
|
|
|
with patch("haystack.components.websearch.serper_dev.requests") as mock_run:
|
2023-09-06 17:31:42 +02:00
|
|
|
|
mock_run.post.return_value = Mock(status_code=200, json=lambda: EXAMPLE_SERPERDEV_RESPONSE)
|
|
|
|
|
|
yield mock_run
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-05-22 13:14:39 +02:00
|
|
|
|
@pytest.fixture
|
|
|
|
|
|
def mock_serper_dev_search_result_no_snippet():
|
|
|
|
|
|
resp = {**EXAMPLE_SERPERDEV_RESPONSE}
|
|
|
|
|
|
del resp["organic"][0]["snippet"]
|
|
|
|
|
|
with patch("haystack.components.websearch.serper_dev.requests") as mock_run:
|
|
|
|
|
|
mock_run.post.return_value = Mock(status_code=200, json=lambda: resp)
|
|
|
|
|
|
yield mock_run
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-09-06 17:31:42 +02:00
|
|
|
|
class TestSerperDevSearchAPI:
|
2023-10-23 15:56:23 +05:30
|
|
|
|
def test_init_fail_wo_api_key(self, monkeypatch):
|
|
|
|
|
|
monkeypatch.delenv("SERPERDEV_API_KEY", raising=False)
|
2024-02-05 13:17:01 +01:00
|
|
|
|
with pytest.raises(ValueError, match="None of the .* environment variables are set"):
|
2023-10-23 15:56:23 +05:30
|
|
|
|
SerperDevWebSearch()
|
|
|
|
|
|
|
2024-02-05 13:17:01 +01:00
|
|
|
|
def test_to_dict(self, monkeypatch):
|
|
|
|
|
|
monkeypatch.setenv("SERPERDEV_API_KEY", "test-api-key")
|
|
|
|
|
|
component = SerperDevWebSearch(top_k=10, allowed_domains=["test.com"], search_params={"param": "test"})
|
2023-09-06 17:31:42 +02:00
|
|
|
|
data = component.to_dict()
|
|
|
|
|
|
assert data == {
|
2023-11-24 14:48:43 +01:00
|
|
|
|
"type": "haystack.components.websearch.serper_dev.SerperDevWebSearch",
|
2024-02-05 13:17:01 +01:00
|
|
|
|
"init_parameters": {
|
|
|
|
|
|
"api_key": {"env_vars": ["SERPERDEV_API_KEY"], "strict": True, "type": "env_var"},
|
|
|
|
|
|
"top_k": 10,
|
|
|
|
|
|
"allowed_domains": ["test.com"],
|
2025-08-29 15:52:02 +05:30
|
|
|
|
"exclude_subdomains": False,
|
2024-02-05 13:17:01 +01:00
|
|
|
|
"search_params": {"param": "test"},
|
|
|
|
|
|
},
|
2023-09-06 17:31:42 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize("top_k", [1, 5, 7])
|
|
|
|
|
|
def test_web_search_top_k(self, mock_serper_dev_search_result, top_k: int):
|
2024-02-05 13:17:01 +01:00
|
|
|
|
ws = SerperDevWebSearch(api_key=Secret.from_token("test-api-key"), top_k=top_k)
|
2023-09-25 10:03:01 +02:00
|
|
|
|
results = ws.run(query="Who is the boyfriend of Olivia Wilde?")
|
|
|
|
|
|
documents = results["documents"]
|
|
|
|
|
|
links = results["links"]
|
|
|
|
|
|
assert len(documents) == len(links) == top_k
|
|
|
|
|
|
assert all(isinstance(doc, Document) for doc in documents)
|
|
|
|
|
|
assert all(isinstance(link, str) for link in links)
|
|
|
|
|
|
assert all(link.startswith("http") for link in links)
|
2023-09-06 17:31:42 +02:00
|
|
|
|
|
2024-05-22 13:14:39 +02:00
|
|
|
|
def test_no_snippet(self, mock_serper_dev_search_result_no_snippet):
|
|
|
|
|
|
ws = SerperDevWebSearch(api_key=Secret.from_token("test-api-key"), top_k=1)
|
|
|
|
|
|
ws.run(query="Who is the boyfriend of Olivia Wilde?")
|
|
|
|
|
|
|
2023-09-06 17:31:42 +02:00
|
|
|
|
@patch("requests.post")
|
|
|
|
|
|
def test_timeout_error(self, mock_post):
|
|
|
|
|
|
mock_post.side_effect = Timeout
|
2024-02-05 13:17:01 +01:00
|
|
|
|
ws = SerperDevWebSearch(api_key=Secret.from_token("test-api-key"))
|
2023-09-06 17:31:42 +02:00
|
|
|
|
|
|
|
|
|
|
with pytest.raises(TimeoutError):
|
|
|
|
|
|
ws.run(query="Who is the boyfriend of Olivia Wilde?")
|
|
|
|
|
|
|
|
|
|
|
|
@patch("requests.post")
|
|
|
|
|
|
def test_request_exception(self, mock_post):
|
|
|
|
|
|
mock_post.side_effect = RequestException
|
2024-02-05 13:17:01 +01:00
|
|
|
|
ws = SerperDevWebSearch(api_key=Secret.from_token("test-api-key"))
|
2023-09-06 17:31:42 +02:00
|
|
|
|
|
|
|
|
|
|
with pytest.raises(SerperDevError):
|
|
|
|
|
|
ws.run(query="Who is the boyfriend of Olivia Wilde?")
|
|
|
|
|
|
|
|
|
|
|
|
@patch("requests.post")
|
|
|
|
|
|
def test_bad_response_code(self, mock_post):
|
|
|
|
|
|
mock_response = mock_post.return_value
|
|
|
|
|
|
mock_response.status_code = 404
|
|
|
|
|
|
mock_response.raise_for_status.side_effect = HTTPError
|
2024-02-05 13:17:01 +01:00
|
|
|
|
ws = SerperDevWebSearch(api_key=Secret.from_token("test-api-key"))
|
2023-09-06 17:31:42 +02:00
|
|
|
|
|
|
|
|
|
|
with pytest.raises(SerperDevError):
|
|
|
|
|
|
ws.run(query="Who is the boyfriend of Olivia Wilde?")
|
2023-09-20 11:55:18 +02:00
|
|
|
|
|
|
|
|
|
|
@pytest.mark.skipif(
|
|
|
|
|
|
not os.environ.get("SERPERDEV_API_KEY", None),
|
|
|
|
|
|
reason="Export an env var called SERPERDEV_API_KEY containing the SerperDev API key to run this test.",
|
|
|
|
|
|
)
|
|
|
|
|
|
@pytest.mark.integration
|
2023-09-25 10:03:01 +02:00
|
|
|
|
def test_web_search(self):
|
2024-02-05 13:17:01 +01:00
|
|
|
|
ws = SerperDevWebSearch(top_k=10)
|
2023-09-25 10:03:01 +02:00
|
|
|
|
results = ws.run(query="Who is the boyfriend of Olivia Wilde?")
|
|
|
|
|
|
documents = results["documents"]
|
2024-02-28 16:43:08 +01:00
|
|
|
|
links = results["links"]
|
2023-09-25 10:03:01 +02:00
|
|
|
|
assert len(documents) == len(links) == 10
|
2025-08-29 15:52:02 +05:30
|
|
|
|
assert all(isinstance(doc, Document) for doc in documents)
|
2023-09-25 10:03:01 +02:00
|
|
|
|
assert all(isinstance(link, str) for link in links)
|
|
|
|
|
|
assert all(link.startswith("http") for link in links)
|
2025-08-29 15:52:02 +05:30
|
|
|
|
|
|
|
|
|
|
def test_exclude_subdomains_filtering(self, monkeypatch):
|
|
|
|
|
|
"""Test that exclude_subdomains parameter properly filters results."""
|
|
|
|
|
|
monkeypatch.setenv("SERPERDEV_API_KEY", "test-api-key")
|
|
|
|
|
|
|
|
|
|
|
|
# Mock response with mixed domains and subdomains
|
|
|
|
|
|
mock_response = {
|
|
|
|
|
|
"organic": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"title": "Main domain result",
|
|
|
|
|
|
"link": "https://example.com/page1",
|
|
|
|
|
|
"snippet": "Content from main domain",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"title": "Subdomain result 1",
|
|
|
|
|
|
"link": "https://blog.example.com/post1",
|
|
|
|
|
|
"snippet": "Content from blog subdomain",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"title": "Subdomain result 2",
|
|
|
|
|
|
"link": "https://shop.example.com/product1",
|
|
|
|
|
|
"snippet": "Content from shop subdomain",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"title": "Different domain result",
|
|
|
|
|
|
"link": "https://other.com/page1",
|
|
|
|
|
|
"snippet": "Content from different domain",
|
|
|
|
|
|
},
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
with patch("haystack.components.websearch.serper_dev.requests") as mock_requests:
|
|
|
|
|
|
mock_requests.post.return_value = Mock(status_code=200, json=lambda: mock_response)
|
|
|
|
|
|
|
|
|
|
|
|
# Test with exclude_subdomains=False (default behavior)
|
|
|
|
|
|
ws_include = SerperDevWebSearch(
|
|
|
|
|
|
api_key=Secret.from_token("test-api-key"), allowed_domains=["example.com"], exclude_subdomains=False
|
|
|
|
|
|
)
|
|
|
|
|
|
results_include = ws_include.run(query="test query")
|
|
|
|
|
|
|
|
|
|
|
|
# Should include main domain and subdomains but exclude other domains
|
|
|
|
|
|
assert len(results_include["documents"]) == 3 # example.com + 2 subdomains
|
|
|
|
|
|
assert len(results_include["links"]) == 3
|
|
|
|
|
|
|
|
|
|
|
|
included_links = results_include["links"]
|
|
|
|
|
|
assert "https://example.com/page1" in included_links
|
|
|
|
|
|
assert "https://blog.example.com/post1" in included_links
|
|
|
|
|
|
assert "https://shop.example.com/product1" in included_links
|
|
|
|
|
|
assert "https://other.com/page1" not in included_links
|
|
|
|
|
|
|
|
|
|
|
|
# Test with exclude_subdomains=True
|
|
|
|
|
|
ws_exclude = SerperDevWebSearch(
|
|
|
|
|
|
api_key=Secret.from_token("test-api-key"), allowed_domains=["example.com"], exclude_subdomains=True
|
|
|
|
|
|
)
|
|
|
|
|
|
results_exclude = ws_exclude.run(query="test query")
|
|
|
|
|
|
|
|
|
|
|
|
# Should only include main domain, exclude subdomains and other domains
|
|
|
|
|
|
assert len(results_exclude["documents"]) == 1 # only example.com
|
|
|
|
|
|
assert len(results_exclude["links"]) == 1
|
|
|
|
|
|
|
|
|
|
|
|
excluded_links = results_exclude["links"]
|
|
|
|
|
|
assert "https://example.com/page1" in excluded_links
|
|
|
|
|
|
assert "https://blog.example.com/post1" not in excluded_links
|
|
|
|
|
|
assert "https://shop.example.com/product1" not in excluded_links
|
|
|
|
|
|
assert "https://other.com/page1" not in excluded_links
|
|
|
|
|
|
|
|
|
|
|
|
def test_is_domain_allowed_helper_method(self, monkeypatch):
|
|
|
|
|
|
"""Test the _is_domain_allowed helper method directly."""
|
|
|
|
|
|
monkeypatch.setenv("SERPERDEV_API_KEY", "test-api-key")
|
|
|
|
|
|
|
|
|
|
|
|
# Test with exclude_subdomains=False
|
|
|
|
|
|
ws_include = SerperDevWebSearch(
|
|
|
|
|
|
api_key=Secret.from_token("test-api-key"),
|
|
|
|
|
|
allowed_domains=["example.com", "test.org"],
|
|
|
|
|
|
exclude_subdomains=False,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# Should allow main domains and subdomains
|
|
|
|
|
|
assert ws_include._is_domain_allowed("https://example.com/page") == True
|
|
|
|
|
|
assert ws_include._is_domain_allowed("https://blog.example.com/post") == True
|
|
|
|
|
|
assert ws_include._is_domain_allowed("https://shop.example.com/product") == True
|
|
|
|
|
|
assert ws_include._is_domain_allowed("https://test.org/page") == True
|
|
|
|
|
|
assert ws_include._is_domain_allowed("https://sub.test.org/page") == True
|
|
|
|
|
|
assert ws_include._is_domain_allowed("https://other.com/page") == False
|
|
|
|
|
|
|
|
|
|
|
|
# Test with exclude_subdomains=True
|
|
|
|
|
|
ws_exclude = SerperDevWebSearch(
|
|
|
|
|
|
api_key=Secret.from_token("test-api-key"),
|
|
|
|
|
|
allowed_domains=["example.com", "test.org"],
|
|
|
|
|
|
exclude_subdomains=True,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# Should only allow exact domain matches
|
|
|
|
|
|
assert ws_exclude._is_domain_allowed("https://example.com/page") == True
|
|
|
|
|
|
assert ws_exclude._is_domain_allowed("https://blog.example.com/post") == False
|
|
|
|
|
|
assert ws_exclude._is_domain_allowed("https://shop.example.com/product") == False
|
|
|
|
|
|
assert ws_exclude._is_domain_allowed("https://test.org/page") == True
|
|
|
|
|
|
assert ws_exclude._is_domain_allowed("https://sub.test.org/page") == False
|
|
|
|
|
|
assert ws_exclude._is_domain_allowed("https://other.com/page") == False
|
|
|
|
|
|
|
|
|
|
|
|
# Test with no allowed_domains (should allow all)
|
|
|
|
|
|
ws_no_filter = SerperDevWebSearch(api_key=Secret.from_token("test-api-key"), allowed_domains=None)
|
|
|
|
|
|
assert ws_no_filter._is_domain_allowed("https://any.domain.com/page") == True
|