Files
wehub-resource-sync 2860fb5d18
Security / Dependency review (push) Has been skipped
Scorecard / Scorecard analysis (push) Failing after 0s
Validate / eval (push) Failing after 0s
Security / Dependency audit (push) Failing after 1s
Security / Secret scan (push) Failing after 1s
Validate / tests (push) Failing after 0s
Validate / mcp-tests (push) Failing after 1s
GitHub Actions Security Analysis with zizmor 🌈 / zizmor (push) Failing after 1s
Security / SAST scan (push) Failing after 13m52s
chore: import upstream snapshot with attribution
2026-07-13 12:05:33 +08:00

663 lines
22 KiB
Python

import json
import os
import subprocess
import sys
from pathlib import Path
from unittest import mock
import pytest
import last30days as cli
from lib import pipeline, planner, reddit_listing, render, rerank, schema
REPO_ROOT = Path(__file__).resolve().parents[1]
def _item(
item_id: str,
source: str,
title: str,
*,
published_at: str = "2026-07-09",
engagement: dict[str, int | float] | None = None,
) -> schema.SourceItem:
return schema.SourceItem(
item_id=item_id,
source=source,
title=title,
body=title,
url=f"https://{source}.example/{item_id}",
published_at=published_at,
engagement=engagement or {},
snippet=f"Evidence about {title}",
)
def _candidate(item: schema.SourceItem) -> schema.Candidate:
return schema.Candidate(
candidate_id=f"candidate-{item.item_id}",
item_id=item.item_id,
source=item.source,
title=item.title,
url=item.url,
snippet=item.snippet,
subquery_labels=["discovery-listings"],
native_ranks={f"discovery-listings:{item.source}": 1},
local_relevance=0.9,
freshness=95,
engagement=100,
source_quality=0.8,
rrf_score=0.1,
sources=[item.source],
source_items=[item],
final_score=80,
)
def test_discovery_plan_reuses_category_peer_mapping():
plan = planner.build_discovery_plan(
"AI agents",
available_sources=["reddit", "hackernews"],
)
assert plan.category == "ai_agent_framework"
assert plan.subreddits == ["LangChain", "LocalLLaMA", "AI_Agents", "MachineLearning"]
assert plan.sources == ["reddit", "hackernews"]
def test_discovery_plan_keeps_keyless_reddit_for_unknown_domains():
plan = planner.build_discovery_plan(
"urban gardening",
available_sources=["reddit", "hackernews"],
)
assert plan.category is None
assert plan.subreddits == ["all"]
assert plan.sources == ["reddit", "hackernews"]
def test_uncategorized_discovery_uses_parseable_r_all_listing_paths():
card = (
'<shreddit-post permalink="/r/gardening/comments/abc123/urban_garden/" '
'post-title="Urban gardening is taking off" score="42" comment-count="7" '
'author="gardener" subreddit-name="gardening" '
'created-timestamp="2026-07-09T12:00:00+00:00">'
)
requested_urls: list[str] = []
def fake_get(url, **_kwargs):
requested_urls.append(url)
return card
with mock.patch.object(reddit_listing.http, "reddit_keyless_get_text", side_effect=fake_get):
result = reddit_listing.fetch_discovery_listings(
["all"], query="urban gardening",
)
assert len(result["items"]) == 1
assert any("/r/all/rising/" in url for url in requested_urls)
assert any("/r/all/top/?t=week" in url for url in requested_urls)
assert all("name=all" not in url for url in requested_urls)
def test_velocity_scoring_favors_a_recent_spike_over_static_bigness():
recent = _item(
"recent",
"reddit",
"Recent spike",
published_at="2026-07-09",
engagement={"score": 100, "num_comments": 10},
)
old = _item(
"old",
"reddit",
"Older large thread",
published_at="2026-06-20",
engagement={"score": 300, "num_comments": 10},
)
assert rerank.engagement_velocity_score(recent, as_of_date="2026-07-10") > (
rerank.engagement_velocity_score(old, as_of_date="2026-07-10")
)
def test_domain_filter_ignores_generic_ai_only_matches():
assert pipeline._matches_discovery_domain(
"AI agents", "An AI agent bankrupted its operator"
)
assert not pipeline._matches_discovery_domain(
"AI agents", "Global dialogue on AI governance"
)
@pytest.mark.parametrize(
("domain", "listing_title"),
[
("城市园艺", "城市园艺技巧与社区花园"),
("גינון עירוני", "מדריך חדש לגינון עירוני"),
],
)
def test_domain_filter_tokenizes_non_latin_domains(domain, listing_title):
assert pipeline._matches_discovery_domain(domain, listing_title)
def test_x_velocity_excludes_views_and_bookmarks():
xquik_item = _item(
"xquik",
"x",
"X backend reach",
engagement={
"likes": 10,
"reposts": 3,
"replies": 2,
"quotes": 1,
"views": 100_000,
"bookmarks": 5_000,
},
)
standard_item = _item(
"standard",
"x",
"X backend interactions",
engagement={"likes": 10, "reposts": 3, "replies": 2, "quotes": 1},
)
assert rerank.discovery_engagement_total(xquik_item) == 16
assert rerank.engagement_velocity_score(
xquik_item, as_of_date="2026-07-10"
) == rerank.engagement_velocity_score(standard_item, as_of_date="2026-07-10")
def test_discovery_topic_name_uses_entities_shared_across_sources():
reddit = _candidate(_item("r1", "reddit", "OpenAI Agent SDK launch details"))
hn = _candidate(_item("h1", "hackernews", "OpenAI Agent SDK reaches developers"))
candidates = {reddit.candidate_id: reddit, hn.candidate_id: hn}
cluster = schema.Cluster(
cluster_id="cluster-1",
title=reddit.title,
candidate_ids=list(candidates),
representative_ids=[reddit.candidate_id],
sources=["hackernews", "reddit"],
score=80,
)
assert pipeline.discovery_topic_name(cluster, candidates, "AI agents") == "OpenAI Agent SDK"
def test_discovery_renderer_snapshot():
report = schema.DiscoveryReport(
domain="AI agents",
range_from="2026-06-10",
range_to="2026-07-10",
generated_at="2026-07-10T00:00:00+00:00",
plan=schema.DiscoveryPlan(
domain="AI agents",
category="ai_agent_framework",
subreddits=["AI_Agents"],
sources=["reddit", "hackernews"],
),
topics=[schema.DiscoveryTopic(
rank=1,
name="Agent memory protocols",
why_spiking="Two independent listing items accelerated this week.",
momentum="new-this-week",
velocity_score=123.45,
sources=["hackernews", "reddit"],
engagement_by_source={
"reddit": {"score": 120, "num_comments": 30},
"hackernews": {"points": 80},
},
command='/last30days "Agent memory protocols"',
)],
)
with mock.patch.object(render, "_render_badge", return_value=["BADGE", ""]):
rendered = render.render_discovery(report)
assert rendered == (
"BADGE\n\n"
"# Trending discovery: AI agents\n\n"
"Window: 2026-06-10 to 2026-07-10\n"
"Feeds: reddit, hackernews\n"
"Communities: r/AI_Agents\n\n"
"## 1. Agent memory protocols\n\n"
"**Momentum:** New this week · velocity 123.45\n\n"
"Two independent listing items accelerated this week.\n\n"
"**Evidence:** Reddit: score 120, num comments 30 · Hacker News: points 80\n\n"
"**Research next:** `/last30days \"Agent memory protocols\"`\n"
)
def test_keyless_discovery_degrades_without_digg():
def fake_fetch(source, plan, *, from_date, to_date, depth, mock, config):
return pipeline._mock_discovery_items(source, plan.domain, to_date), None
with mock.patch.object(pipeline, "available_sources", return_value=["reddit", "hackernews"]), \
mock.patch.object(pipeline, "_fetch_discovery_source", side_effect=fake_fetch):
report = pipeline.run_discover(
domain="AI agents",
config={},
as_of_date="2026-07-10",
)
assert 5 <= len(report.topics) <= 10
assert report.source_status["reddit"].state == "ok"
assert report.source_status["hackernews"].state == "ok"
assert report.source_status["digg"].state == "skipped-unconfigured"
assert report.source_status["x"].state == "skipped-unconfigured"
assert all(topic.command.startswith('/last30days "') for topic in report.topics)
def test_discovery_drops_zero_velocity_clusters():
raw_item = {
"id": "zero-engagement",
"text": "AI agent launch with no interactions",
"url": "https://x.com/example/status/1",
"author_handle": "example",
"date": "2026-07-09",
"engagement": {"likes": 0, "reposts": 0, "replies": 0, "quotes": 0},
"relevance": 0.9,
}
with mock.patch.object(pipeline, "available_sources", return_value=["x"]), \
mock.patch.object(pipeline, "_fetch_discovery_source", return_value=([raw_item], None)):
report = pipeline.run_discover(
domain="AI agents",
config={},
as_of_date="2026-07-10",
)
assert report.topics == []
assert "Fewer than five topic clusters survived this domain sweep." in report.warnings
def test_explicit_unavailable_discovery_source_does_not_widen_to_other_sources():
with mock.patch.object(pipeline, "available_sources", return_value=[]), \
mock.patch.object(pipeline, "_fetch_discovery_source") as fetch:
with pytest.raises(ValueError, match="No listing sources are available"):
pipeline.run_discover(
domain="AI agents",
config={},
requested_sources=["digg"],
as_of_date="2026-07-10",
)
fetch.assert_not_called()
def test_discovery_reads_browser_credentials_and_does_not_schedule_pending_x():
parser = cli.build_parser()
args, extra = parser.parse_known_args(["--discover", "AI agents"])
assert cli._config_policy_for_args(args, "", extra).browser_cookies == "read"
no_cookies_args, extra = parser.parse_known_args(
["--no-browser-cookies", "--discover", "AI agents"]
)
assert cli._config_policy_for_args(no_cookies_args, "", extra).browser_cookies == "off"
fetched_sources: list[str] = []
def fake_available_sources(config, requested_sources, *, x_pending=None, local_only=False):
assert x_pending is False
return ["reddit", "hackernews"] + (["x"] if x_pending is not False else [])
def fake_fetch(source, plan, *, from_date, to_date, depth, mock, config):
fetched_sources.append(source)
return pipeline._mock_discovery_items(source, plan.domain, to_date), None
with mock.patch.object(pipeline, "available_sources", side_effect=fake_available_sources), \
mock.patch.object(pipeline, "_fetch_discovery_source", side_effect=fake_fetch):
report = pipeline.run_discover(
domain="AI agents",
config={"FROM_BROWSER": "firefox", "_BROWSER_COOKIE_MODE": "plan_only"},
as_of_date="2026-07-10",
)
assert "x" not in fetched_sources
assert report.source_status["x"].state == "skipped-unconfigured"
def test_authenticated_x_discovery_uses_available_backend():
plan = planner.build_discovery_plan(
"AI agents",
available_sources=["x"],
)
raw = pipeline._mock_discovery_items("x", plan.domain, "2026-07-10")
with mock.patch.object(pipeline.env, "x_backend_chain", return_value=["bird"]), \
mock.patch.object(pipeline, "_fetch_x_backend", return_value=(raw, "")) as fetch:
items, error = pipeline._fetch_discovery_source(
"x",
plan,
from_date="2026-06-10",
to_date="2026-07-10",
depth="default",
mock=False,
config={"AUTH_TOKEN": "dummy", "CT0": "dummy"},
)
assert error is None
assert len(items) == 6
fetch.assert_called_once()
def test_listing_failure_is_not_reported_as_clean_no_results():
def fake_fetch(source, plan, *, from_date, to_date, depth, mock, config):
if source == "reddit":
return [], "connection timed out"
return pipeline._mock_discovery_items(source, plan.domain, to_date), None
with mock.patch.object(pipeline, "available_sources", return_value=["reddit", "hackernews"]), \
mock.patch.object(pipeline, "_fetch_discovery_source", side_effect=fake_fetch):
report = pipeline.run_discover(
domain="AI agents",
config={},
as_of_date="2026-07-10",
)
assert report.source_status["reddit"].state == "timeout"
assert report.source_status["reddit"].detail == "connection timed out"
def test_reddit_discovery_adapter_preserves_partial_feed_errors():
item = {
"url": "https://reddit.com/r/example/comments/1",
"title": "AI agent launch",
}
with mock.patch.object(
reddit_listing,
"_fetch_one_with_status",
side_effect=[([], "rising timed out"), ([item], None)],
):
result = reddit_listing.fetch_discovery_listings(
["AI_Agents"], query="AI agents",
)
assert result["items"] == [item]
assert result["errors"] == ["r/AI_Agents rising: rising timed out"]
def test_discovery_cli_json_contract_and_mutual_exclusion():
result = subprocess.run(
[
sys.executable,
"skills/last30days/scripts/last30days.py",
"--discover",
"AI agents",
"--mock",
"--emit=json",
],
cwd=REPO_ROOT,
capture_output=True,
text=True,
check=False,
)
assert result.returncode == 0, result.stderr
payload = json.loads(result.stdout)
assert payload["schema_version"] == "1.0"
assert payload["kind"] == "discovery"
assert 5 <= len(payload["results"]) <= 10
assert payload["results"][0]["command"].startswith('/last30days "')
invalid = subprocess.run(
[
sys.executable,
"skills/last30days/scripts/last30days.py",
"topic",
"--discover",
"AI agents",
"--mock",
],
cwd=REPO_ROOT,
capture_output=True,
text=True,
check=False,
)
assert invalid.returncode == 2
assert "cannot be combined with a positional topic" in invalid.stderr
drill_conflict = subprocess.run(
[
sys.executable,
"skills/last30days/scripts/last30days.py",
"--discover",
"AI agents",
"--drill",
"1",
"--mock",
],
cwd=REPO_ROOT,
capture_output=True,
text=True,
check=False,
)
assert drill_conflict.returncode == 2
assert "mutually exclusive" in drill_conflict.stderr
def test_discovery_cli_rejects_historical_as_of():
result = subprocess.run(
[
sys.executable,
"skills/last30days/scripts/last30days.py",
"--discover",
"AI agents",
"--as-of",
"2026-06-01",
"--mock",
],
cwd=REPO_ROOT,
capture_output=True,
text=True,
check=False,
)
assert result.returncode == 2
assert "--as-of cannot be used with --discover" in result.stderr
assert "current live listings" in result.stderr
def test_discovery_filters_incompatible_default_sources_but_rejects_explicit_only():
default_result = subprocess.run(
[
sys.executable,
"skills/last30days/scripts/last30days.py",
"--discover",
"AI agents",
"--mock",
"--emit=json",
],
cwd=REPO_ROOT,
env={**os.environ, "LAST30DAYS_DEFAULT_SEARCH": "reddit,x,youtube,hn"},
capture_output=True,
text=True,
check=False,
)
assert default_result.returncode == 0, default_result.stderr
explicit_result = subprocess.run(
[
sys.executable,
"skills/last30days/scripts/last30days.py",
"--discover",
"AI agents",
"--search=youtube",
"--mock",
],
cwd=REPO_ROOT,
capture_output=True,
text=True,
check=False,
)
assert explicit_result.returncode == 2
assert "unsupported: youtube" in explicit_result.stderr
def test_detect_category_rejects_suffix_false_positives():
from lib import categories
assert categories.detect_category("Dubai agents") is None
assert categories.detect_category("Thai agents real estate") is None
assert categories.detect_category("AI agents") == "ai_agent_framework"
assert categories.detect_category("what's new in ai agent frameworks") == "ai_agent_framework"
def test_discovery_engagement_excludes_rank_metadata():
from lib import pipeline, schema
items = [
schema.SourceItem(
item_id=f"digg-{i}", source="digg", title="t", body="b",
url=f"https://di.gg/{i}", published_at="2026-07-05", snippet="s",
engagement={"postCount": 5, "rank": 100 * (i + 1), "rank_score": 0.5},
)
for i in range(3)
]
totals = pipeline._discovery_engagement(items)
assert totals["digg"]["postCount"] == 15
assert "rank" not in totals["digg"]
assert "rank_score" not in totals["digg"]
def test_domain_matching_preserves_non_plural_anchors():
from lib import pipeline
assert pipeline._matches_discovery_domain("AI bias", "Addressing AI bias in models")
assert pipeline._matches_discovery_domain("supply chain crisis", "The crisis deepens for chip supply")
# Plural matching still works both directions.
assert pipeline._matches_discovery_domain("AI agents", "The best AI agent stacks")
def test_x_fallback_success_is_clean(monkeypatch):
from lib import pipeline, env
calls = []
def fake_fetch(backend, subquery, from_date, to_date, depth, config):
calls.append(backend)
if backend == "bird":
return [], "cookie expired"
return [object()], None
monkeypatch.setattr(pipeline, "_fetch_x_backend", fake_fetch)
monkeypatch.setattr(env, "x_backend_chain", lambda config: ["bird", "xquik"])
plan = pipeline.schema.DiscoveryPlan(
domain="ai agents", category=None, subreddits=[], sources=["x"],
)
items, error = pipeline._fetch_discovery_source(
"x", plan,
from_date="2026-06-11", to_date="2026-07-11", depth="quick",
mock=False, config={},
)
assert error is None
assert len(items) == 1
assert calls == ["bird", "xquik"]
def _digg_envelope(*clusters: dict) -> dict:
return {"results": list(clusters)}
def _digg_cluster(cluster_id: str, title: str, tldr: str = "") -> dict:
return {
"clusterUrlId": cluster_id,
"title": title,
"tldr": tldr,
"rank": 5,
"postCount": 12,
"uniqueAuthors": 8,
}
def test_digg_discovery_drops_off_domain_clusters(monkeypatch):
"""Regression: a crypto sweep surfaced AI stories because the Digg
branch (an AI-only leaderboard feed) applied no domain filter."""
envelope = _digg_envelope(
_digg_cluster("c1", "Bitcoin crypto rally accelerates"),
_digg_cluster("c2", "OpenAI ships a new frontier model"),
)
monkeypatch.setattr(pipeline.digg, "search_digg", lambda *a, **k: envelope)
plan = schema.DiscoveryPlan(
domain="crypto", category=None, subreddits=[], sources=["digg"],
)
items, error = pipeline._fetch_discovery_source(
"digg", plan,
from_date="2026-06-11", to_date="2026-07-11", depth="quick",
mock=False, config={},
)
assert error is None
titles = [item["title"] for item in items]
assert titles == ["Bitcoin crypto rally accelerates"]
def test_digg_discovery_keeps_domain_matching_clusters(monkeypatch):
envelope = _digg_envelope(
_digg_cluster("c1", "AI agents reshape support desks"),
_digg_cluster("c2", "The best AI agent stacks compared"),
)
monkeypatch.setattr(pipeline.digg, "search_digg", lambda *a, **k: envelope)
plan = schema.DiscoveryPlan(
domain="AI agents", category=None, subreddits=[], sources=["digg"],
)
items, error = pipeline._fetch_discovery_source(
"digg", plan,
from_date="2026-06-11", to_date="2026-07-11", depth="quick",
mock=False, config={},
)
assert error is None
assert len(items) == 2
def test_digg_discovery_all_filtered_is_clean_no_results(monkeypatch):
envelope = _digg_envelope(
_digg_cluster("c1", "OpenAI ships a new frontier model"),
_digg_cluster("c2", "Anthropic updates its agent SDK"),
)
monkeypatch.setattr(pipeline.digg, "search_digg", lambda *a, **k: envelope)
plan = schema.DiscoveryPlan(
domain="crypto", category=None, subreddits=[], sources=["digg"],
)
items, error = pipeline._fetch_discovery_source(
"digg", plan,
from_date="2026-06-11", to_date="2026-07-11", depth="quick",
mock=False, config={},
)
assert error is None
assert items == []
def test_x_discovery_preserves_producing_backends_own_error(monkeypatch):
"""A backend that returns items plus its own error is a partial outcome;
only earlier failed-over backends' errors are observability-only."""
monkeypatch.setattr(
pipeline, "_fetch_x_backend",
lambda *a, **k: ([{"id": "x-1", "title": "t"}], "rate limited after page 1"),
)
monkeypatch.setattr(pipeline.env, "x_backend_chain", lambda config: ["bird"])
plan = schema.DiscoveryPlan(
domain="ai agents", category=None, subreddits=[], sources=["x"],
)
items, error = pipeline._fetch_discovery_source(
"x", plan,
from_date="2026-06-11", to_date="2026-07-11", depth="quick",
mock=False, config={},
)
assert len(items) == 1
assert error == "rate limited after page 1"
def test_discovery_exits_when_configured_sources_have_no_discovery_feed(monkeypatch, capsys):
"""A configured source boundary must hold: never silently widen a sweep
to feeds the user filtered out."""
monkeypatch.setattr(
cli.env, "get_config", lambda **_kwargs: {"LAST30DAYS_DEFAULT_SEARCH": "youtube"}
)
monkeypatch.setattr(sys, "argv", ["last30days.py", "--discover", "AI agents", "--mock"])
with mock.patch.object(pipeline, "run_discover") as run:
assert cli.main() == 2
run.assert_not_called()
err = capsys.readouterr().err
assert "no discovery-capable sources" in err
assert "reddit" in err