Files
ewoooc/tests/test_competitor_intel_cache.py
OoO e57793829c
All checks were successful
CD Pipeline / deploy (push) Successful in 1m5s
[V10.360] 關閉預設瀏覽器 smoke 並優化 PChome 熱路徑
2026-05-21 12:07:25 +08:00

144 lines
6.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
def test_competitor_intel_cache_reuses_memory_and_shared_file(tmp_path, monkeypatch):
from services import competitor_intel_repository as repo
monkeypatch.setattr(repo, "_CACHE_FILE", Path(tmp_path) / "competitor_intel_cache.pkl")
repo._MEM_CACHE.clear()
calls = {"count": 0}
def producer():
calls["count"] += 1
return {"valid_matches": 7, "match_rate": 0.1}
first = repo._cached_payload("coverage:test", producer, ttl_seconds=60)
second = repo._cached_payload("coverage:test", producer, ttl_seconds=60)
repo._MEM_CACHE.clear()
third = repo._cached_payload("coverage:test", producer, ttl_seconds=60)
assert first == second == third == {"valid_matches": 7, "match_rate": 0.1}
assert calls["count"] == 1
def test_clear_competitor_intel_cache_removes_shared_file(tmp_path, monkeypatch):
from services import competitor_intel_repository as repo
cache_file = Path(tmp_path) / "competitor_intel_cache.pkl"
monkeypatch.setattr(repo, "_CACHE_FILE", cache_file)
repo._MEM_CACHE["x"] = {"time": 1, "value": {"ok": True}}
cache_file.write_bytes(b"stale")
repo.clear_competitor_intel_cache()
assert repo._MEM_CACHE == {}
assert not cache_file.exists()
def test_competitor_ppt_results_keep_pending_diagnostics_in_export():
source = (ROOT / "services" / "competitor_intel_repository.py").read_text(encoding="utf-8")
assert "LEFT JOIN valid_competitor" in source
assert "\"found\": found" in source
assert "\"match_status\"" in source
assert "\"candidate_count\"" in source
assert "\"unit_comparison\"" in source
assert "build_unit_price_comparison" in source
assert "(vc.pchome_price IS NULL)" in source
assert "(lm.momo_price - vc.pchome_price) AS price_diff" in source
assert "((lm.momo_price - vc.pchome_price) / vc.pchome_price * 100) AS price_diff_pct" in source
def test_competitor_ppt_results_use_history_for_dated_reports():
source = (ROOT / "services" / "competitor_intel_repository.py").read_text(encoding="utf-8")
route_source = (ROOT / "routes" / "openclaw_bot_routes.py").read_text(encoding="utf-8")
assert "requested_historical_prices" in source
assert "use_history_prices" in source
assert "FROM competitor_price_history cph" in source
assert "cph.crawled_at >= DATE(:start_date)" in source
assert "cph.crawled_at < DATE(:end_date) + INTERVAL '1 day'" in source
assert "pr.timestamp < DATE(:end_date) + INTERVAL '1 day'" in source
assert "'competitor_price_history' AS competitor_source" in source
assert "\"competitor_source\"" in source
assert "\"pc_crawled_at\"" in source
assert "指定期間 competitor_price_history" in route_source
def test_competitor_coverage_counts_only_active_product_intersection():
source = (ROOT / "services" / "competitor_intel_repository.py").read_text(encoding="utf-8")
coverage_source = source.split("def _fetch_competitor_coverage_uncached", 1)[1].split(
"def _fetch_manual_review_summary", 1
)[0]
assert "coverage:v4" in source
assert "(SELECT COUNT(*) FROM valid_competitor) AS valid_matches" not in coverage_source
assert "FROM latest_momo lm\n JOIN valid_competitor vc ON vc.sku = lm.sku" in coverage_source
assert "FROM products p\n JOIN LATERAL" in coverage_source
assert "WHERE p.status = 'ACTIVE'" in coverage_source
def test_competitor_ppt_and_ai_use_momo_minus_pchome_gap_direction():
ppt_source = (ROOT / "services" / "ppt_generator.py").read_text(encoding="utf-8")
route_source = (ROOT / "routes" / "openclaw_bot_routes.py").read_text(encoding="utf-8")
assert 'pc_wins = [r for r in found if r.get("price_diff", 0) > 10]' in ppt_source
assert 'mo_wins = [r for r in found if r.get("price_diff", 0) < -10]' in ppt_source
assert "平均價差 {avg_pct:+.1f}%momo - PChome" in ppt_source
assert "price_diff = MOMO售價 PChome售價" in route_source
assert "正值=MOMO較貴、PChome低價壓力" in route_source
def test_competitor_review_queue_is_canonical_unit_price_handoff():
source = (ROOT / "services" / "competitor_intel_repository.py").read_text(encoding="utf-8")
daily_template = (ROOT / "templates" / "daily_sales.html").read_text(encoding="utf-8")
growth_template = (ROOT / "templates" / "growth_analysis.html").read_text(encoding="utf-8")
assert "def fetch_competitor_review_queue" in source
assert "\"review_queue\": fetch_competitor_review_queue" in source
assert "\"unit_comparable_count\"" in source
assert "manual_review_summary" in source
assert "manual_accept_count" in source
assert "manual_reject_count" in source
assert "manual_unit_price_count" in source
assert "competitor_match_reviews" in source
assert "\"status_label\"" in source
assert "\"action_label\"" in source
assert "build_unit_price_comparison" in source
assert "需單位價覆核" in daily_template
assert "人工採用" in daily_template
assert "人工否決" in daily_template
assert "人工單位價" in daily_template
assert "competitor_intel.review_queue" in daily_template
assert "coverage.unit_comparable_count" in growth_template
assert "coverage.manual_accept_count" in growth_template
assert "coverage.manual_reject_count" in growth_template
assert "coverage.manual_unit_price_count" in growth_template
def test_competitor_ppt_prompt_uses_neutral_ewooc_viewpoint():
source = (ROOT / "routes" / "openclaw_bot_routes.py").read_text(encoding="utf-8")
assert "EwoooC 商品營運視角" in source
assert "待補資料不可當成成功配對" in source
assert "高信心比對" in source
assert "待補身份/價格" in source
assert "需單位價比較" in source
assert "單位價覆核樣本" in source
assert "我方 = PChome" not in source
assert "請以 PChome 視角" not in source
def test_top_competitor_risks_reads_latest_momo_price_after_valid_competitor_filter():
source = (ROOT / "services" / "competitor_intel_repository.py").read_text(encoding="utf-8")
risk_source = source.split("def _fetch_top_competitor_risks_uncached", 1)[1].split("def fetch_competitor_review_queue", 1)[0]
assert "FROM valid_competitor vc" in risk_source
assert "JOIN LATERAL" in risk_source
assert "WHERE pr.product_id = p.id" in risk_source
assert "ROW_NUMBER() OVER (PARTITION BY p.id" not in risk_source