|
@@ -3,10 +3,12 @@ from __future__ import annotations
|
|
|
import httpx
|
|
import httpx
|
|
|
|
|
|
|
|
import acquisition.classify as classify_module
|
|
import acquisition.classify as classify_module
|
|
|
|
|
+import acquisition.creation_search as creation_search_module
|
|
|
from acquisition import store
|
|
from acquisition import store
|
|
|
-from acquisition.classify import _providers, classify_video
|
|
|
|
|
|
|
+from acquisition.classify import _providers, classify_imgtext, classify_video
|
|
|
from acquisition.creation_search import Candidate, PlatformRateLimiter, run_platform_query
|
|
from acquisition.creation_search import Candidate, PlatformRateLimiter, run_platform_query
|
|
|
from acquisition.crawler import RateLimiter
|
|
from acquisition.crawler import RateLimiter
|
|
|
|
|
+from core.models import Post
|
|
|
from core.config import PgConfig, Settings
|
|
from core.config import PgConfig, Settings
|
|
|
from scripts import run_creation_search as run_cli
|
|
from scripts import run_creation_search as run_cli
|
|
|
|
|
|
|
@@ -129,6 +131,27 @@ def test_classify_video_accepts_cdn_url(monkeypatch):
|
|
|
assert seen["url"] == "https://cdn.test/video.mp4"
|
|
assert seen["url"] == "https://cdn.test/video.mp4"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+def test_classify_imgtext_maps_public_data_to_settings_data_dir(tmp_path, monkeypatch):
|
|
|
|
|
+ legacy = tmp_path / "legacy_data"
|
|
|
|
|
+ img = legacy / "media" / "x.jpg"
|
|
|
|
|
+ img.parent.mkdir(parents=True)
|
|
|
|
|
+ img.write_bytes(b"\xff\xd8fake-jpeg")
|
|
|
|
|
+ settings = _settings()
|
|
|
|
|
+ settings.data_dir = str(legacy)
|
|
|
|
|
+ seen = {}
|
|
|
|
|
+
|
|
|
|
|
+ def fake_judge(messages, settings, timeout):
|
|
|
|
|
+ seen["parts"] = messages[1]["content"]
|
|
|
|
|
+ return 1, "ok", "knowledge", ""
|
|
|
|
|
+
|
|
|
|
|
+ monkeypatch.setattr("acquisition.classify._judge", fake_judge)
|
|
|
|
|
+ res = classify_imgtext({"images": ["/data/media/x.jpg"]}, settings)
|
|
|
|
|
+
|
|
|
|
|
+ assert res == (1, "ok", "knowledge", "")
|
|
|
|
|
+ image_parts = [p for p in seen["parts"] if p["type"] == "image_url"]
|
|
|
|
|
+ assert image_parts and image_parts[0]["image_url"]["url"].startswith("data:image/jpeg;base64,")
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
def test_qwen_provider_uses_dashscope_credentials(monkeypatch):
|
|
def test_qwen_provider_uses_dashscope_credentials(monkeypatch):
|
|
|
monkeypatch.setenv("CLASSIFY_PROVIDER", "qwen")
|
|
monkeypatch.setenv("CLASSIFY_PROVIDER", "qwen")
|
|
|
monkeypatch.setenv("CLASSIFY_MODEL", "qwen3.7-plus")
|
|
monkeypatch.setenv("CLASSIFY_MODEL", "qwen3.7-plus")
|
|
@@ -228,6 +251,30 @@ def test_run_platform_query_skips_bad_items_and_fills_display_limit(tmp_path, mo
|
|
|
assert len(detail["platforms"]["weixin"]["items"]) == 5
|
|
assert len(detail["platforms"]["weixin"]["items"]) == 5
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+def test_xhs_media_saved_under_settings_data_dir(tmp_path, monkeypatch):
|
|
|
|
|
+ settings = _settings()
|
|
|
|
|
+ settings.data_dir = str(tmp_path / "legacy_data")
|
|
|
|
|
+ post = Post(
|
|
|
|
|
+ id="xhs_1", platform="xiaohongshu", url="https://xhs.test/1",
|
|
|
|
|
+ content_id="cid1", title="t", body_text="b", image_urls=["https://img.test/a.jpg"],
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ monkeypatch.setattr(creation_search_module, "fetch_post_detail", lambda *a, **k: post)
|
|
|
|
|
+ out = creation_search_module._process_xhs(
|
|
|
|
|
+ Candidate(rank=1, source_id="cid1"),
|
|
|
|
|
+ query_hash="qh",
|
|
|
|
|
+ settings=settings,
|
|
|
|
|
+ rate_limiter=PlatformRateLimiter("xiaohongshu", delegate=RateLimiter(
|
|
|
|
|
+ min_interval_seconds=0, max_interval_seconds=0, sleep_fn=lambda _: None,
|
|
|
|
|
+ )),
|
|
|
|
|
+ downloader=lambda url, platform: b"\xff\xd8fake-jpeg",
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ expected = tmp_path / "legacy_data" / "media" / "xiaohongshu" / "qh" / "cid1" / "image_1.jpg"
|
|
|
|
|
+ assert expected.exists()
|
|
|
|
|
+ assert out["image_urls"] == ["/data/media/xiaohongshu/qh/cid1/image_1.jpg"]
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
def test_creation_job_done_and_classification_queue(tmp_path):
|
|
def test_creation_job_done_and_classification_queue(tmp_path):
|
|
|
c = store.connect(tmp_path / "app.db")
|
|
c = store.connect(tmp_path / "app.db")
|
|
|
store.create_creation_run(c, "r1", total_queries=1, ts=1)
|
|
store.create_creation_run(c, "r1", total_queries=1, ts=1)
|
|
@@ -311,3 +358,31 @@ def test_run_workers_skip_done_does_not_rerun(tmp_path, monkeypatch):
|
|
|
)
|
|
)
|
|
|
assert failed == 0
|
|
assert failed == 0
|
|
|
assert calls == []
|
|
assert calls == []
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def test_run_workers_resolves_env_db_path(tmp_path, monkeypatch):
|
|
|
|
|
+ db = tmp_path / "legacy_data" / "app.db"
|
|
|
|
|
+ c = store.connect(db)
|
|
|
|
|
+ store.create_creation_run(c, "r1", total_queries=1, ts=1)
|
|
|
|
|
+ store.ensure_creation_job(c, "r1", "q", "weixin", ts=1)
|
|
|
|
|
+ c.close()
|
|
|
|
|
+ monkeypatch.setenv("CK_SQLITE_PATH", str(db))
|
|
|
|
|
+
|
|
|
|
|
+ def fake_run(conn, *, platform, run_id, query, display_limit, **kwargs):
|
|
|
|
|
+ store.update_creation_job(
|
|
|
|
|
+ conn, run_id, query, platform, status="done",
|
|
|
|
|
+ searched_count=10, display_count=display_limit, ts=2,
|
|
|
|
|
+ )
|
|
|
|
|
+ return {"platform": platform, "status": "done", "display_count": display_limit, "error": ""}
|
|
|
|
|
+
|
|
|
|
|
+ monkeypatch.setattr(run_cli, "run_platform_query", fake_run)
|
|
|
|
|
+ failed = run_cli.run_platform_workers(
|
|
|
|
|
+ run_id="r1", queries=["q"], platforms=["weixin"],
|
|
|
|
|
+ settings=_settings(), search_limit=10, display_limit=5,
|
|
|
|
|
+ classify=False, skip_done=False,
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ c = store.connect(db)
|
|
|
|
|
+ summary = store.creation_search_summary(c, run_id="r1")["queries"]["q"]["platforms"]
|
|
|
|
|
+ assert failed == 0
|
|
|
|
|
+ assert summary["weixin"]["status"] == "done"
|