Skip to content

Commit cc9dc30

Browse files
authored
Merge pull request #27 from jethronap/24_test_scraper_agent
24 test scraper agent
2 parents b473c76 + 35ae78d commit cc9dc30

15 files changed

+392
-7
lines changed

local_test_pipeline.sh

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,46 @@ export PYTHONPATH
66
cd "$(dirname "$0")" || exit
77

88
echo "Running base agent tests"
9-
pytest tests/test_base_agent.py
9+
pytest tests/agents/test_base_agent.py
1010
echo "Done..."
1111
echo "==============================================="
1212

1313
echo "Running dummy agent tests"
14-
pytest tests/test_dummy_agent.py
14+
pytest tests/agents/test_dummy_agent.py
15+
echo "Done..."
16+
echo "==============================================="
17+
18+
echo "Running scraper agent tests"
19+
pytest tests/agents/test_scraper_agent.py
1520
echo "Done..."
1621
echo "==============================================="
1722

1823
echo "Running llm wrapper tests"
19-
pytest tests/test_llm_wrapper.py
24+
pytest tests/tools/test_llm_wrapper.py
25+
echo "Done..."
26+
echo "==============================================="
27+
28+
echo "Running ollama client tests"
29+
pytest tests/tools/test_ollama_client.py
2030
echo "Done..."
2131
echo "==============================================="
2232

2333
echo "Running memory store tests"
24-
pytest tests/test_memory_store.py
34+
pytest tests/memory/test_memory_store.py
2535
echo "Done..."
2636
echo "==============================================="
2737

28-
echo "Running ollama client tests"
29-
pytest tests/test_ollama_client.py
38+
echo "Running with_retry decorator tests"
39+
pytest tests/utils/test_retry.py
40+
echo "Done..."
41+
echo "==============================================="
42+
43+
echo "Running db utils tests"
44+
pytest tests/utils/test_db.py
45+
echo "Done..."
46+
echo "==============================================="
47+
48+
echo "Running overpass utils tests"
49+
pytest tests/utils/test_overpass.py
3050
echo "Done..."
3151
echo "==============================================="

tests/agents/__init__.py

Whitespace-only changes.
File renamed without changes.
File renamed without changes.

tests/agents/test_scraper_agent.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import json
2+
3+
import pytest
4+
5+
from src.utils.db import query_hash, payload_hash
6+
from src.agents.scraper_agent import ScraperAgent
7+
8+
9+
PAYLOAD_OK = {"elements": [{"id": 1}, {"id": 2}]}
10+
PAYLOAD_EMPTY = {"elements": []}
11+
12+
13+
def make_agent(mem_fake, run_stub, save_stub):
14+
"""
15+
Helper to build an agent with injected stubs
16+
"""
17+
tools = {"run_query": run_stub, "save_json": save_stub}
18+
return ScraperAgent(name="ScraperAgent", memory=mem_fake, tools=tools)
19+
20+
21+
def test_first_run_saves(mem_fake, tmp_path, monkeypatch):
22+
saved_path = tmp_path / "lund.json"
23+
24+
def run_stub(query):
25+
return PAYLOAD_OK
26+
27+
def save_stub(data, city, overpass_dir):
28+
assert data is PAYLOAD_OK
29+
assert city == "Lund"
30+
return saved_path
31+
32+
agent = make_agent(mem_fake, run_stub, save_stub)
33+
ctx = agent.achieve_goal({"city": "Lund"})
34+
35+
# context flags
36+
assert ctx["cache_hit"] is False
37+
assert ctx["elements_count"] == 2
38+
assert ctx["save_json"] == str(saved_path)
39+
40+
# memory rows contain cache entry
41+
cache_rows = [r for r in mem_fake.rows if r.step == "cache"]
42+
assert len(cache_rows) == 1
43+
qh = query_hash(ctx["query"])
44+
ph = payload_hash(PAYLOAD_OK)
45+
assert f"{qh}|{saved_path}|{ph}" in cache_rows[0].content
46+
47+
48+
def test_second_run_hits_cache(mem_fake, tmp_path, monkeypatch):
49+
cached_path = tmp_path / "lund.json"
50+
cached_path.write_text(json.dumps(PAYLOAD_OK))
51+
52+
# the exact query string we'll pretend the agent will use
53+
q = "FAKE QUERY TEXT"
54+
55+
# pre‑seed cache row
56+
mem_fake.store(
57+
"ScraperAgent",
58+
"cache",
59+
f"{query_hash(q)}|{cached_path}|{payload_hash(PAYLOAD_OK)}",
60+
)
61+
62+
# stub tools that must NOT be called
63+
def run_stub(_):
64+
pytest.fail("run_query should not be called on cache hit")
65+
66+
def save_stub(*_):
67+
pytest.fail("save_json should not be called on cache hit")
68+
69+
# make build_query return the same fake string
70+
monkeypatch.setattr("src.agents.scraper_agent.build_query", lambda *a, **k: q)
71+
72+
agent = make_agent(mem_fake, run_stub, save_stub)
73+
74+
ctx = agent.achieve_goal({"city": "Lund"})
75+
76+
assert ctx["cache_hit"] is True
77+
assert ctx["run_query"] == PAYLOAD_OK
78+
assert ctx["save_json"] == str(cached_path)
79+
80+
81+
def test_empty_result_skips_save(mem_fake, monkeypatch):
82+
def run_stub(query):
83+
return PAYLOAD_EMPTY
84+
85+
def save_stub(*_):
86+
pytest.fail("save_json must not be called for empty result")
87+
88+
agent = make_agent(mem_fake, run_stub, save_stub)
89+
90+
ctx = agent.achieve_goal({"city": "Nowhere"})
91+
92+
assert ctx["elements_count"] == 0
93+
assert ctx["save_json"] == "NO_DATA"
94+
95+
empty_rows = [r for r in mem_fake.rows if r.step == "empty"]
96+
assert empty_rows, "empty marker row should be stored"

tests/conftest.py

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1+
import json
2+
from types import SimpleNamespace
3+
14
import pytest
2-
from src.config.settings import OllamaSettings, DatabaseSettings
5+
import requests
6+
7+
from src.config.settings import OllamaSettings, DatabaseSettings, OverpassSettings
38

49

510
@pytest.fixture
@@ -31,3 +36,86 @@ def __init__(self, settings):
3136

3237
def __call__(self, prompt, **kwargs):
3338
return self._response
39+
40+
41+
@pytest.fixture
42+
def fake_sleep(monkeypatch):
43+
"""
44+
Replace time.sleep so tests run instantly and record delays.
45+
"""
46+
delays = []
47+
48+
def _fake_sleep(seconds: float):
49+
delays.append(seconds)
50+
51+
monkeypatch.setattr("time.sleep", _fake_sleep)
52+
return delays
53+
54+
55+
def http_error(status: int) -> requests.HTTPError:
56+
r = requests.Response()
57+
r.status_code = status
58+
return requests.HTTPError(response=r)
59+
60+
61+
@pytest.fixture
62+
def ovp_settings(tmp_path):
63+
return OverpassSettings(
64+
endpoint="https://overpass.test/api",
65+
headers={"User-Agent": "pytest"},
66+
query_timeout=10,
67+
timeout=2,
68+
dir=str(tmp_path / "ovp"),
69+
max_attempts=2,
70+
base_delay=0.0,
71+
)
72+
73+
74+
class _DummyResp(SimpleNamespace):
75+
def raise_for_status(self):
76+
if 400 <= self.status_code:
77+
raise requests.HTTPError(response=self)
78+
79+
def json(self):
80+
return json.loads(self._text) if isinstance(self._text, str) else self._text
81+
82+
text = property(lambda self: self._text)
83+
84+
85+
@pytest.fixture
86+
def patch_requests(monkeypatch):
87+
"""
88+
Fixture that lets each test prepare responses:
89+
90+
store["get"] = _DummyResp(...)
91+
store["post"] = _DummyResp(...)
92+
"""
93+
store = {"get": None, "post": None}
94+
95+
def fake_get(*_, **__):
96+
return store["get"]
97+
98+
def fake_post(*_, **__):
99+
return store["post"]
100+
101+
monkeypatch.setattr(requests, "get", fake_get)
102+
monkeypatch.setattr(requests, "post", fake_post)
103+
return store
104+
105+
106+
class MemoryStoreFake:
107+
def __init__(self):
108+
self.rows = []
109+
110+
def store(self, agent_id: str, step: str, content: str):
111+
row = SimpleNamespace(agent_id=agent_id, step=step, content=content)
112+
self.rows.append(row)
113+
return row
114+
115+
def load(self, agent_id: str):
116+
return [r for r in self.rows if r.agent_id == agent_id]
117+
118+
119+
@pytest.fixture
120+
def mem_fake():
121+
return MemoryStoreFake()

tests/memory/__init__.py

Whitespace-only changes.
File renamed without changes.

tests/tools/__init__.py

Whitespace-only changes.
File renamed without changes.

0 commit comments

Comments
 (0)