| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332 |
- from __future__ import annotations
- from uuid import uuid4
- from acquisition.repositories.postgres import PostgresAcquisitionRepository
- class RecordingPostgresRepo(PostgresAcquisitionRepository):
- def __init__(self):
- super().__init__(conn=None)
- self.one_calls = []
- self.one_or_none_calls = []
- self.all_calls = []
- self.one_results = []
- self.one_or_none_results = []
- self.all_results = []
- def _one(self, sql, params):
- self.one_calls.append((sql, params))
- return self.one_results.pop(0)
- def _one_or_none(self, sql, params):
- self.one_or_none_calls.append((sql, params))
- return self.one_or_none_results.pop(0)
- def _all(self, sql, params):
- self.all_calls.append((sql, params))
- return self.all_results.pop(0)
- def test_postgres_summary_uses_formal_run_job_item_tables():
- run_id = uuid4()
- batch_id = uuid4()
- query_id = uuid4()
- repo = RecordingPostgresRepo()
- repo.one_results.append(
- {
- "id": run_id,
- "run_key": "r1",
- "batch_id": batch_id,
- "status": "done",
- "query_count": 1,
- "job_count": 3,
- "candidate_count": 7,
- "creation_hit_count": 2,
- "started_at": None,
- "finished_at": None,
- }
- )
- repo.all_results.append(
- [
- {
- "query_id": query_id,
- "query_text": "脚本 开头",
- "job_count": 3,
- "candidate_count": 7,
- "creation_hit_count": 2,
- "platforms": {"xiaohongshu": {"status": "done"}},
- }
- ]
- )
- summary = repo.get_run_summary(run_id)
- first_sql, first_params = repo.one_calls[0]
- second_sql, second_params = repo.all_calls[0]
- assert "FROM acquisition_runs ar" in first_sql
- assert "LEFT JOIN acquisition_jobs aj" in first_sql
- assert "LEFT JOIN candidate_items ci" in first_sql
- assert "LEFT JOIN item_classifications ic" in first_sql
- assert first_params == (run_id,)
- assert "jsonb_object_agg" in second_sql
- assert "WHERE aj.run_id = %s" in second_sql
- assert second_params == (run_id,)
- assert summary["queries"][0]["query_id"] == query_id
- def test_postgres_repository_lists_only_creation_candidates_for_decode():
- run_id = uuid4()
- item_id = uuid4()
- repo = RecordingPostgresRepo()
- repo.all_results.append(
- [
- {
- "id": item_id,
- "platform": "xiaohongshu",
- "platform_item_id": "x1",
- "unique_key": "xhs:x1",
- "canonical_url": "https://xhs.test/1",
- "status": "candidate",
- }
- ]
- )
- items = repo.list_creation_candidate_items(run_id=run_id, limit=20)
- sql, params = repo.all_calls[0]
- assert "JOIN acquisition_jobs aj ON aj.id = ci.job_id" in sql
- assert "JOIN item_classifications ic ON ic.item_id = ci.id" in sql
- assert "ic.is_creation_knowledge IS TRUE" in sql
- assert "NOT EXISTS" in sql
- assert "FROM decode_results dr" in sql
- assert "LIMIT %s" in sql
- assert params == (run_id, 20)
- assert items[0].id == item_id
- assert items[0].platform == "xiaohongshu"
- def test_postgres_ensure_acquisition_job_merges_metadata_on_conflict():
- job_id = uuid4()
- run_id = uuid4()
- query_id = uuid4()
- repo = RecordingPostgresRepo()
- repo.one_results.append(
- {
- "id": job_id,
- "run_id": run_id,
- "query_id": query_id,
- "platform": "douyin",
- "status": "done",
- "search_limit": 10,
- "display_limit": 5,
- "attempt_count": 1,
- "metadata": {"pagination": {"pages": [{"page_index": 1}]}, "query_text": "q"},
- "error_message": None,
- "started_at": None,
- "finished_at": None,
- "created_at": None,
- "updated_at": None,
- }
- )
- job = repo.ensure_acquisition_job(
- run_id=run_id,
- query_id=query_id,
- platform="douyin",
- search_limit=10,
- display_limit=5,
- status="pending",
- metadata={"query_text": "q"},
- )
- sql, params = repo.one_calls[0]
- assert "metadata = acquisition_jobs.metadata || EXCLUDED.metadata" in sql
- assert params[0] == run_id
- assert params[1] == query_id
- assert job.metadata["pagination"]["pages"][0]["page_index"] == 1
- def test_postgres_upsert_candidate_item_prefers_unique_key_for_existing_rows():
- item_id = uuid4()
- repo = RecordingPostgresRepo()
- repo.one_or_none_results.append({"id": item_id})
- repo.one_results.append(
- {
- "id": item_id,
- "job_id": None,
- "query_id": None,
- "platform": "xiaohongshu",
- "platform_item_id": "x1",
- "unique_key": "xhs:x1",
- "canonical_url": "https://xhs.test/1",
- "content_type": "图文",
- "content_mode": "image_post",
- "title": "更新标题",
- "author_name": None,
- "published_at": None,
- "body_text": "完整正文",
- "raw_summary": None,
- "status": "candidate",
- "source_payload": {},
- "metadata": {},
- "error_message": None,
- "created_at": None,
- "updated_at": None,
- }
- )
- item = repo.upsert_candidate_item(
- platform="xiaohongshu",
- platform_item_id="x1",
- unique_key="xhs:x1",
- canonical_url="https://xhs.test/1",
- content_type="图文",
- content_mode="image_post",
- title="更新标题",
- body_text="完整正文",
- )
- lookup_sql, lookup_params = repo.one_or_none_calls[0]
- update_sql, update_params = repo.one_calls[0]
- assert "WHERE unique_key = %s" in lookup_sql
- assert lookup_params == ("xhs:x1",)
- assert "source_payload =" not in update_sql
- assert "unique_key = COALESCE(%s, unique_key)" in update_sql
- assert "content_mode = %s" in update_sql
- assert "body_text = %s" in update_sql
- assert "INSERT INTO candidate_items" not in update_sql
- assert update_params[2] == "xhs:x1"
- assert update_params[5] == "image_post"
- assert update_params[8] == "完整正文"
- assert item.unique_key == "xhs:x1"
- assert item.content_mode == "image_post"
- assert item.body_text == "完整正文"
- def test_postgres_query_detail_loads_media_and_classifications_only_when_items_exist():
- run_id = uuid4()
- query_id = uuid4()
- item_id = uuid4()
- repo = RecordingPostgresRepo()
- repo.one_results.append({"id": query_id, "query_text": "q", "status": "ready"})
- repo.all_results.extend(
- [
- [{"id": uuid4(), "run_id": run_id, "query_id": query_id, "platform": "weixin"}],
- [{"id": item_id, "platform": "weixin", "query_id": query_id, "status": "candidate"}],
- [{"id": uuid4(), "item_id": item_id, "media_type": "image", "status": "done"}],
- [{"id": uuid4(), "item_id": item_id, "status": "classified"}],
- [{"item_id": item_id, "decode_status": "decoded", "payload_count": 2}],
- ]
- )
- detail = repo.get_query_detail(run_id=run_id, query_id=query_id)
- assert len(repo.all_calls) == 5
- assert "SELECT * FROM media_assets" in repo.all_calls[2][0]
- assert "SELECT * FROM item_classifications" in repo.all_calls[3][0]
- assert "FROM decode_results dr" in repo.all_calls[4][0]
- assert detail["items"][0]["id"] == item_id
- assert detail["media_assets"][0]["item_id"] == item_id
- assert detail["decode_summaries"][0]["payload_count"] == 2
- def test_postgres_latest_query_result_list_uses_lightweight_projection():
- batch_id = uuid4()
- run_id = uuid4()
- query_id = uuid4()
- item_id = uuid4()
- repo = RecordingPostgresRepo()
- repo.one_results.append(
- {"id": query_id, "batch_id": batch_id, "query_text": "q", "status": "ready"}
- )
- repo.one_or_none_results.append({"id": run_id, "batch_id": batch_id, "status": "running"})
- repo.all_results.extend(
- [
- [{"id": uuid4(), "run_id": run_id, "query_id": query_id, "platform": "weixin"}],
- [
- {
- "id": item_id,
- "query_id": query_id,
- "job_id": uuid4(),
- "platform": "weixin",
- "title": "标题",
- "raw_summary": "摘要",
- "status": "candidate",
- "content_mode": "article",
- "metadata": {},
- }
- ],
- [{"id": uuid4(), "item_id": item_id, "media_type": "image", "status": "done"}],
- [
- {
- "id": uuid4(),
- "item_id": item_id,
- "is_creation_knowledge": True,
- "label": "creation",
- "confidence": 0.9,
- "status": "done",
- "error_message": None,
- }
- ],
- [{"item_id": item_id, "decode_status": "decoded", "particle_count": 1, "payload_count": 1}],
- ]
- )
- detail = repo.get_latest_query_result_list(query_id)
- item_sql = repo.all_calls[1][0]
- media_sql = repo.all_calls[2][0]
- classification_sql = repo.all_calls[3][0]
- assert "SELECT ci.*" not in item_sql
- assert "source_payload" not in item_sql
- assert "body_text" not in item_sql
- assert "LEFT(ci.raw_summary, 700) AS raw_summary" in item_sql
- assert "SELECT DISTINCT ON (item_id)" in media_sql
- assert "media_type IN ('cover', 'image', 'frame')" in media_sql
- assert "SELECT DISTINCT ON (item_id)" in classification_sql
- assert "reason" not in classification_sql
- assert detail["run"]["id"] == run_id
- assert detail["items"][0]["raw_summary"] == "摘要"
- assert detail["media_assets"][0]["item_id"] == item_id
- assert detail["decode_summaries"][0]["particle_count"] == 1
- def test_postgres_attach_existing_candidate_updates_query_job_and_metadata():
- item_id = uuid4()
- job_id = uuid4()
- query_id = uuid4()
- repo = RecordingPostgresRepo()
- repo.one_results.append(
- {
- "id": item_id,
- "job_id": job_id,
- "query_id": query_id,
- "platform": "weixin",
- "platform_item_id": "wx1",
- "unique_key": "wx:key",
- "canonical_url": "https://mp.weixin.qq.com/s?...",
- "content_mode": "article",
- "body_text": None,
- "status": "candidate",
- "source_payload": {},
- "metadata": {"acquisition_match_status": "existing"},
- "created_at": None,
- "updated_at": None,
- }
- )
- item = repo.attach_existing_candidate_item(
- item_id,
- job_id=job_id,
- query_id=query_id,
- metadata={"acquisition_match_status": "existing"},
- )
- sql, params = repo.one_calls[0]
- assert "UPDATE candidate_items SET" in sql
- assert "metadata = metadata || %s" in sql
- assert params[0] == job_id
- assert params[1] == query_id
- assert params[3] == item_id
- assert item.metadata["acquisition_match_status"] == "existing"
|