mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-08 22:22:17 +02:00
chore: bumped version to 0.0.31
This commit is contained in:
parent
8df8565e0a
commit
1c9ab207ef
56 changed files with 520 additions and 190 deletions
|
|
@ -562,8 +562,8 @@ class FakeChatLLM(BaseChatModel):
|
|||
|
||||
# Marker unique to a connector subagent's prompt: the main agent must
|
||||
# delegate via ``task``; only the subagent has connector tools registered.
|
||||
in_connector_subagent = (
|
||||
"connected-apps specialist" in _messages_to_text(messages)
|
||||
in_connector_subagent = "connected-apps specialist" in _messages_to_text(
|
||||
messages
|
||||
)
|
||||
|
||||
# Main agent: delegate live-tool connector work to its subagent (which
|
||||
|
|
|
|||
|
|
@ -29,9 +29,7 @@ def _last_ai_text(messages: list) -> str | None:
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_web_query_delegates_to_google_search(
|
||||
db_session, db_user, db_workspace
|
||||
):
|
||||
async def test_web_query_delegates_to_google_search(db_session, db_user, db_workspace):
|
||||
"""A web-search query routes through ``task(google_search)`` and resumes.
|
||||
|
||||
Scripted sequence (the fake model is shared and consumed in order across the
|
||||
|
|
@ -76,9 +74,7 @@ async def test_web_query_delegates_to_google_search(
|
|||
)
|
||||
|
||||
task_tool_messages = [
|
||||
m
|
||||
for m in result["messages"]
|
||||
if isinstance(m, ToolMessage) and m.name == "task"
|
||||
m for m in result["messages"] if isinstance(m, ToolMessage) and m.name == "task"
|
||||
]
|
||||
assert task_tool_messages, "web query did not delegate through the task tool"
|
||||
assert _last_ai_text(result["messages"]) == (
|
||||
|
|
|
|||
|
|
@ -62,13 +62,15 @@ def _tool(name: str, metadata: dict) -> StructuredTool:
|
|||
def test_all_mcp_connectors_route_to_discovery():
|
||||
for connector_type in _MCP_ROUTED:
|
||||
assert (
|
||||
CONNECTOR_TYPE_TO_CONNECTOR_AGENT_MAPS[connector_type]
|
||||
== MCP_DISCOVERY_NAME
|
||||
CONNECTOR_TYPE_TO_CONNECTOR_AGENT_MAPS[connector_type] == MCP_DISCOVERY_NAME
|
||||
), connector_type
|
||||
|
||||
|
||||
def test_file_connectors_keep_native_routes():
|
||||
assert CONNECTOR_TYPE_TO_CONNECTOR_AGENT_MAPS["GOOGLE_DRIVE_CONNECTOR"] == "google_drive"
|
||||
assert (
|
||||
CONNECTOR_TYPE_TO_CONNECTOR_AGENT_MAPS["GOOGLE_DRIVE_CONNECTOR"]
|
||||
== "google_drive"
|
||||
)
|
||||
assert CONNECTOR_TYPE_TO_CONNECTOR_AGENT_MAPS["DROPBOX_CONNECTOR"] == "dropbox"
|
||||
assert CONNECTOR_TYPE_TO_CONNECTOR_AGENT_MAPS["ONEDRIVE_CONNECTOR"] == "onedrive"
|
||||
|
||||
|
|
@ -87,7 +89,9 @@ def test_discovery_gating_is_any_of():
|
|||
|
||||
|
||||
def test_discovery_gating_tokens_match_routed_connectors():
|
||||
assert SUBAGENT_TO_REQUIRED_CONNECTOR_MAP[MCP_DISCOVERY_NAME] == frozenset(_MCP_ROUTED)
|
||||
assert SUBAGENT_TO_REQUIRED_CONNECTOR_MAP[MCP_DISCOVERY_NAME] == frozenset(
|
||||
_MCP_ROUTED
|
||||
)
|
||||
|
||||
|
||||
def test_legacy_aliases_resolve_to_a_live_subagent():
|
||||
|
|
@ -104,7 +108,12 @@ def test_collision_only_prefixes_shared_names():
|
|||
_tool("search", {"mcp_connector_id": 2, "mcp_transport": "http"}),
|
||||
_tool("list_bases", {"mcp_connector_id": 2, "mcp_transport": "http"}),
|
||||
]
|
||||
resolved = {t.name: t for t in resolve_tool_name_collisions(tools, {1: "NOTION_CONNECTOR", 2: "AIRTABLE_CONNECTOR"})}
|
||||
resolved = {
|
||||
t.name: t
|
||||
for t in resolve_tool_name_collisions(
|
||||
tools, {1: "NOTION_CONNECTOR", 2: "AIRTABLE_CONNECTOR"}
|
||||
)
|
||||
}
|
||||
|
||||
# The unique tool keeps its bare name (trusted_tools / history stay valid).
|
||||
assert "list_bases" in resolved
|
||||
|
|
|
|||
|
|
@ -423,8 +423,12 @@ async def test_run_events_replays_buffer_then_finishes(monkeypatch):
|
|||
|
||||
row = _fake_run_row(status="running")
|
||||
raw = str(row.id)
|
||||
run_event_bus.publish(raw, {"type": "run.progress", "phase": "scraping", "current": 1})
|
||||
run_event_bus.publish(raw, {"type": "run.finished", "status": "success", "item_count": 2})
|
||||
run_event_bus.publish(
|
||||
raw, {"type": "run.progress", "phase": "scraping", "current": 1}
|
||||
)
|
||||
run_event_bus.publish(
|
||||
raw, {"type": "run.finished", "status": "success", "item_count": 2}
|
||||
)
|
||||
|
||||
app = _build_app_with_rows(monkeypatch, [row])
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -43,7 +43,9 @@ async def test_maps_urls_to_start_urls_and_wraps_items():
|
|||
assert out.items[0].dataType == "post"
|
||||
|
||||
(actor_input, _limit) = scraper.calls[0]
|
||||
assert [u.url for u in actor_input.startUrls] == ["https://www.reddit.com/r/python/"]
|
||||
assert [u.url for u in actor_input.startUrls] == [
|
||||
"https://www.reddit.com/r/python/"
|
||||
]
|
||||
assert actor_input.searches == []
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -233,7 +233,9 @@ async def test_gate_reserves_worst_case_captcha_when_solving_enabled(monkeypatch
|
|||
|
||||
with pytest.raises(InsufficientCreditsError):
|
||||
await gate_capability(
|
||||
CrawlInput(startUrls=["https://a.com"]), BillingUnit.WEB_CRAWL, _ctx(session)
|
||||
CrawlInput(startUrls=["https://a.com"]),
|
||||
BillingUnit.WEB_CRAWL,
|
||||
_ctx(session),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -64,7 +64,9 @@ def test_preview_is_char_budgeted_and_references_run():
|
|||
per_item = "y" * 500
|
||||
items = [f'{{"i": {i}, "v": "{per_item}"}}' for i in range(500)]
|
||||
body = "\n".join(items)
|
||||
serialized = SerializedOutput(text=body, item_count=len(items), char_count=len(body))
|
||||
serialized = SerializedOutput(
|
||||
text=body, item_count=len(items), char_count=len(body)
|
||||
)
|
||||
|
||||
preview = _build_preview(serialized, run_id="abc")
|
||||
|
||||
|
|
@ -143,8 +145,11 @@ async def test_read_run_paginates(monkeypatch):
|
|||
read_run, _ = _tools()
|
||||
|
||||
out = await read_run.ainvoke(
|
||||
{"ref": "run_" + "0" * 8 + "-0000-0000-0000-000000000000",
|
||||
"offset": 2, "limit": 3}
|
||||
{
|
||||
"ref": "run_" + "0" * 8 + "-0000-0000-0000-000000000000",
|
||||
"offset": 2,
|
||||
"limit": 3,
|
||||
}
|
||||
)
|
||||
assert "item_2" in out and "item_3" in out and "item_4" in out
|
||||
assert "item_0" not in out and "item_5" not in out
|
||||
|
|
@ -184,8 +189,7 @@ async def test_search_run_excerpts_huge_matched_line(monkeypatch):
|
|||
_, search_run = _tools()
|
||||
|
||||
out = await search_run.ainvoke(
|
||||
{"ref": "run_" + "0" * 8 + "-0000-0000-0000-000000000000",
|
||||
"pattern": "NEEDLE"}
|
||||
{"ref": "run_" + "0" * 8 + "-0000-0000-0000-000000000000", "pattern": "NEEDLE"}
|
||||
)
|
||||
assert "NEEDLE" in out
|
||||
assert "match at char 100000" in out
|
||||
|
|
@ -215,8 +219,10 @@ async def test_search_run_matches(monkeypatch):
|
|||
_patch_session(monkeypatch, _BODY, [])
|
||||
_, search_run = _tools()
|
||||
out = await search_run.ainvoke(
|
||||
{"ref": "spill_" + "0" * 8 + "-0000-0000-0000-000000000000",
|
||||
"pattern": "item_7"}
|
||||
{
|
||||
"ref": "spill_" + "0" * 8 + "-0000-0000-0000-000000000000",
|
||||
"pattern": "item_7",
|
||||
}
|
||||
)
|
||||
assert "item_7" in out
|
||||
assert "item_1" not in out.split("item_7")[0]
|
||||
|
|
@ -232,14 +238,30 @@ _CRAWL_BODY = "\n".join(
|
|||
"url": "https://x.com/team/",
|
||||
"status": "success",
|
||||
"links": [
|
||||
{"url": "https://x.com/author/jane/", "text": "Jane Doe",
|
||||
"context": "Jane Doe General Partner", "kind": "internal"},
|
||||
{"url": "https://x.com/author/bob/", "text": "Bob Roe",
|
||||
"context": "Bob Roe Operations", "kind": "internal"},
|
||||
{
|
||||
"url": "https://x.com/author/jane/",
|
||||
"text": "Jane Doe",
|
||||
"context": "Jane Doe General Partner",
|
||||
"kind": "internal",
|
||||
},
|
||||
{
|
||||
"url": "https://x.com/author/bob/",
|
||||
"text": "Bob Roe",
|
||||
"context": "Bob Roe Operations",
|
||||
"kind": "internal",
|
||||
},
|
||||
# Duplicate of Jane (nav + card) — must dedupe.
|
||||
{"url": "https://x.com/author/jane/", "text": "Jane Doe",
|
||||
"context": "Jane Doe General Partner", "kind": "internal"},
|
||||
{"url": "https://x.com/about/", "text": "About", "kind": "internal"},
|
||||
{
|
||||
"url": "https://x.com/author/jane/",
|
||||
"text": "Jane Doe",
|
||||
"context": "Jane Doe General Partner",
|
||||
"kind": "internal",
|
||||
},
|
||||
{
|
||||
"url": "https://x.com/about/",
|
||||
"text": "About",
|
||||
"kind": "internal",
|
||||
},
|
||||
],
|
||||
}
|
||||
),
|
||||
|
|
|
|||
|
|
@ -101,7 +101,11 @@ async def test_aggregated_contacts_carry_provenance_and_site_wide_flag() -> None
|
|||
class _ContactsEngine:
|
||||
async def crawl_url(self, url: str) -> CrawlOutcome:
|
||||
socials = [footer] + ([person] if url.endswith("/about") else [])
|
||||
links = ["https://e.com/about", "https://e.com/blog"] if url == "https://e.com/" else []
|
||||
links = (
|
||||
["https://e.com/about", "https://e.com/blog"]
|
||||
if url == "https://e.com/"
|
||||
else []
|
||||
)
|
||||
return CrawlOutcome(
|
||||
status=_SUCCESS,
|
||||
result={
|
||||
|
|
|
|||
|
|
@ -43,18 +43,20 @@ def test_estimated_units_for_single_url_is_seed_count() -> None:
|
|||
|
||||
|
||||
def test_estimated_units_for_spider_is_max_pages() -> None:
|
||||
model = CrawlInput(
|
||||
startUrls=["https://a.com"], maxCrawlDepth=2, maxCrawlPages=25
|
||||
)
|
||||
model = CrawlInput(startUrls=["https://a.com"], maxCrawlDepth=2, maxCrawlPages=25)
|
||||
assert model.estimated_units == 25
|
||||
|
||||
|
||||
def test_billable_units_counts_only_successes() -> None:
|
||||
out = CrawlOutput(
|
||||
items=[
|
||||
CrawlItem(url="a", status="success", crawl=CrawlMeta(loadedUrl="a", depth=0)),
|
||||
CrawlItem(
|
||||
url="a", status="success", crawl=CrawlMeta(loadedUrl="a", depth=0)
|
||||
),
|
||||
CrawlItem(url="b", status="empty", crawl=CrawlMeta(loadedUrl="b", depth=1)),
|
||||
CrawlItem(url="c", status="failed", crawl=CrawlMeta(loadedUrl="c", depth=1)),
|
||||
CrawlItem(
|
||||
url="c", status="failed", crawl=CrawlMeta(loadedUrl="c", depth=1)
|
||||
),
|
||||
]
|
||||
)
|
||||
assert out.billable_units == 1
|
||||
|
|
|
|||
|
|
@ -274,8 +274,13 @@ def test_parse_serp_extracts_all_blocks():
|
|||
assert prod.prices == ["$24.99", "$30"]
|
||||
|
||||
# Related searches exclude the numeric pagination anchor (a.fl).
|
||||
assert [r.title for r in item.relatedQueries] == ["easy apple pie", "apple pie recipe"]
|
||||
assert item.relatedQueries[0].url == "https://www.google.com/search?q=easy+apple+pie"
|
||||
assert [r.title for r in item.relatedQueries] == [
|
||||
"easy apple pie",
|
||||
"apple pie recipe",
|
||||
]
|
||||
assert (
|
||||
item.relatedQueries[0].url == "https://www.google.com/search?q=easy+apple+pie"
|
||||
)
|
||||
|
||||
# suggestedResults are the related queries re-shaped with type/position.
|
||||
assert [(s.position, s.title, s.type) for s in item.suggestedResults] == [
|
||||
|
|
|
|||
|
|
@ -95,8 +95,15 @@ def test_flatten_comments_counts_replies_and_stops_at_more():
|
|||
"kind": "Listing",
|
||||
"data": {
|
||||
"children": [
|
||||
{"kind": "t1", "data": {"name": "t1_2", "id": "2",
|
||||
"body": "reply", "replies": ""}},
|
||||
{
|
||||
"kind": "t1",
|
||||
"data": {
|
||||
"name": "t1_2",
|
||||
"id": "2",
|
||||
"body": "reply",
|
||||
"replies": "",
|
||||
},
|
||||
},
|
||||
{"kind": "more", "data": {}}, # stub -> ignored
|
||||
]
|
||||
},
|
||||
|
|
@ -112,8 +119,10 @@ def test_flatten_comments_counts_replies_and_stops_at_more():
|
|||
|
||||
def test_flatten_comments_honors_max():
|
||||
tree = [
|
||||
{"kind": "t1", "data": {"name": f"t1_{i}", "id": str(i), "body": "x",
|
||||
"replies": ""}}
|
||||
{
|
||||
"kind": "t1",
|
||||
"data": {"name": f"t1_{i}", "id": str(i), "body": "x", "replies": ""},
|
||||
}
|
||||
for i in range(5)
|
||||
]
|
||||
assert len(flatten_comments(tree, max_comments=2)) == 2
|
||||
|
|
@ -128,9 +137,17 @@ def test_children_and_after():
|
|||
|
||||
|
||||
def test_parse_community_maps_members():
|
||||
thing = {"kind": "t5", "data": {"name": "t5_s", "id": "s",
|
||||
"display_name": "py", "display_name_prefixed": "r/py",
|
||||
"subscribers": 1234, "url": "/r/py/"}}
|
||||
thing = {
|
||||
"kind": "t5",
|
||||
"data": {
|
||||
"name": "t5_s",
|
||||
"id": "s",
|
||||
"display_name": "py",
|
||||
"display_name_prefixed": "r/py",
|
||||
"subscribers": 1234,
|
||||
"url": "/r/py/",
|
||||
},
|
||||
}
|
||||
item = parse_community(thing)
|
||||
assert item["dataType"] == "community"
|
||||
assert item["numberOfMembers"] == 1234
|
||||
|
|
|
|||
|
|
@ -125,9 +125,7 @@ async def test_dedupes_on_canonical_url() -> None:
|
|||
}
|
||||
)
|
||||
|
||||
await crawl_site(
|
||||
engine, ["https://e.com/"], max_crawl_depth=3, max_crawl_pages=10
|
||||
)
|
||||
await crawl_site(engine, ["https://e.com/"], max_crawl_depth=3, max_crawl_pages=10)
|
||||
|
||||
assert engine.calls.count("https://e.com/a") == 1
|
||||
assert engine.calls.count("https://e.com/") == 1
|
||||
|
|
|
|||
|
|
@ -116,7 +116,12 @@ def test_extract_link_records_dedupes_keeping_first_nonempty_text() -> None:
|
|||
html = '<a href="/p"><img src="logo.png"/></a><a href="/p">Pricing</a>'
|
||||
records = extract_link_records(html, "https://example.com/")
|
||||
assert records == [
|
||||
{"url": "https://example.com/p", "text": "Pricing", "rel": "", "kind": "internal"}
|
||||
{
|
||||
"url": "https://example.com/p",
|
||||
"text": "Pricing",
|
||||
"rel": "",
|
||||
"kind": "internal",
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -241,5 +241,3 @@ class TestCheckBalance:
|
|||
session, _user = _make_session(balance_micros=0)
|
||||
await WebCrawlCreditService(session).check_balance(_USER_ID, 0)
|
||||
session.execute.assert_not_called()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -47,9 +47,7 @@ def test_location_stops_at_next_param(monkeypatch: pytest.MonkeyPatch) -> None:
|
|||
def test_no_country_suffix_yields_empty_location(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
monkeypatch.setattr(
|
||||
Config, "PROXY_URL", "http://tok:secret@gw.dataimpulse.com:823"
|
||||
)
|
||||
monkeypatch.setattr(Config, "PROXY_URL", "http://tok:secret@gw.dataimpulse.com:823")
|
||||
assert DataImpulseProvider().get_location() == ""
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue