import json import shutil import subprocess import sys from datetime import datetime, timezone import pytest def test_pixelrag_assessment_starts_visual_evidence_fallback_not_price_write(): from services.pixelrag_crawler_integration_service import ( RESULT_PHASE1_READY, build_pixelrag_crawler_integration_assessment, ) payload = build_pixelrag_crawler_integration_assessment() assert payload["success"] is True assert payload["result"] == RESULT_PHASE1_READY assert payload["feasible"] is True assert payload["can_start_now"] is True assert payload["recommended_mode"] == "pixelrag_inspired_visual_evidence_fallback" assert payload["full_pixelrag_ready"] is False assert payload["controlled_apply"] == { "network_call": False, "db_write": False, "secret_read": False, "github_dependency": False, "production_price_write": False, "rollback": "Disable the visual fallback selector or ignore generated artifacts.", } phase_ids = {phase["id"]: phase for phase in payload["phases"]} assert phase_ids["PXR-1"]["status"] == "ready_to_start" assert phase_ids["PXR-2"]["writes"] == ["artifact_file_only"] assert phase_ids["PXR-3"]["status"] == "deferred_model_not_verified" assert phase_ids["PXR-5"]["writes"] == ["review_diagnostics_only"] assert any( guard["code"] == "no_price_write_from_pixels" for guard in payload["safety_guards"] ) def test_pixelrag_assessment_blocks_when_visual_capture_is_not_ready(): from services.pixelrag_crawler_integration_service import ( RESULT_BLOCKED_NO_CAPTURE, build_pixelrag_crawler_integration_assessment, ) payload = build_pixelrag_crawler_integration_assessment( capabilities={"playwright_artifact_pipeline": False} ) assert payload["result"] == RESULT_BLOCKED_NO_CAPTURE assert payload["feasible"] is False assert payload["can_start_now"] is False assert payload["recommended_mode"] == "prepare_visual_capture_prerequisites" assert payload["phases"][0]["status"] == "blocked_prerequisite" def test_pixelrag_assessment_keeps_github_and_hosted_embedding_out_of_runtime(): from services.pixelrag_crawler_integration_service import ( build_pixelrag_crawler_integration_assessment, ) payload = build_pixelrag_crawler_integration_assessment( capabilities={ "ollama_multimodal_embedding_ready": True, "pgvector_visual_index_ready": True, } ) findings = {finding["code"]: finding for finding in payload["source_findings"]} guards = {guard["code"]: guard for guard in payload["safety_guards"]} assert payload["full_pixelrag_ready"] is True assert "Ollama" in findings["qwen3_vl_embedding_optional"]["adoption"] assert guards["no_external_embedding_api"]["status"] == "enforced" assert guards["no_github_runtime_dependency"]["status"] == "enforced" assert payload["controlled_apply"]["github_dependency"] is False def test_pixelrag_report_cli_outputs_machine_readable_json(): completed = subprocess.run( [ sys.executable, "scripts/ops/report_pixelrag_crawler_integration.py", "--platform", "momo", ], capture_output=True, check=False, text=True, ) assert completed.returncode == 0 payload = json.loads(completed.stdout) assert payload["success"] is True assert payload["target_platforms"] == ["momo"] assert payload["next_actions"][0]["action"] == "add_visual_evidence_manifest_lane" def test_visual_fallback_selector_routes_parser_empty_and_low_confidence_cases(): from services.pixelrag_crawler_integration_service import ( should_emit_visual_evidence_fallback, ) parser_empty = should_emit_visual_evidence_fallback( parser_success=False, parsed_item_count=0, missing_fields=["price"], failure_reason="HTML selector did not find rendered price block", ) clean_parser = should_emit_visual_evidence_fallback( parser_success=True, parsed_item_count=8, confidence_band="high", ) assert parser_empty["should_emit"] is True assert "parser_failed" in parser_empty["triggers"] assert "critical_field_missing" in parser_empty["triggers"] assert clean_parser["should_emit"] is False assert clean_parser["fallback_reason"] == "structured_evidence_sufficient" def test_visual_evidence_manifest_builds_tile_plan_without_runtime_writes(): from services.pixelrag_crawler_integration_service import ( RESULT_MANIFEST_READY, build_pixelrag_visual_evidence_manifest, ) payload = build_pixelrag_visual_evidence_manifest( url="https://m.momoshop.com.tw/search.momo?searchKeyword=test", platform="momo", crawler="MomoCrawler.search_products", trigger_reason="parser_empty", viewport={"name": "desktop-1440", "width": 1440, "height": 950}, page_size={"width": 1440, "height": 1900}, tile_size={"width": 512, "height": 512}, ) assert payload["success"] is True assert payload["result"] == RESULT_MANIFEST_READY assert payload["status"] == "capture_requested" assert payload["tile_plan"] == { "tiles_x": 3, "tiles_y": 4, "tile_count": 12, "overlap_px": 0, } assert payload["artifact"]["writes"] == ["artifact_file_only"] assert payload["artifact"]["suggested_path"].startswith( "runtime_artifacts/pixelrag_visual_evidence/momo/" ) assert payload["controlled_apply"]["db_write"] is False assert payload["controlled_apply"]["network_call"] is False assert payload["controlled_apply"]["production_price_write"] is False def test_visual_evidence_manifest_rejects_urls_with_credentials(): from services.pixelrag_crawler_integration_service import ( RESULT_MANIFEST_REJECTED, build_pixelrag_visual_evidence_manifest, ) payload = build_pixelrag_visual_evidence_manifest( url="https://user:pass@example.test/product", platform="momo", crawler="MomoCrawler", trigger_reason="parser_empty", ) assert payload["success"] is False assert payload["result"] == RESULT_MANIFEST_REJECTED assert "URL credentials are not allowed" in payload["errors"][0] def test_pixelrag_report_cli_outputs_visual_manifest(): completed = subprocess.run( [ sys.executable, "scripts/ops/report_pixelrag_crawler_integration.py", "--platform", "pchome", "--manifest-url", "https://24h.pchome.com.tw/prod/TEST-000000001", "--crawler", "PChomeCrawler.search_products", "--trigger-reason", "parser_empty", "--page-size", "page=1024x1024", "--tile-size", "tile=512x512", ], capture_output=True, check=False, text=True, ) assert completed.returncode == 0 payload = json.loads(completed.stdout) assert payload["result"] == "PIXELRAG_VISUAL_EVIDENCE_MANIFEST_READY" assert payload["capture_target"]["platform"] == "pchome" assert payload["tile_plan"]["tile_count"] == 4 def test_pixelrag_ai_automation_route_returns_assessment_and_manifest(): from flask import Flask from routes import system_public_routes as routes app = Flask(__name__) with app.test_request_context( "/api/ai-automation/pixelrag-crawler-integration?platform=momo" ): assessment_response = routes.ai_automation_pixelrag_crawler_integration_api.__wrapped__() assessment = assessment_response.get_json() with app.test_request_context( "/api/ai-automation/pixelrag-crawler-integration" "?platform=pchome" "&manifest_url=https://24h.pchome.com.tw/prod/TEST-000000001" "&crawler=PChomeCrawler.search_products" "&trigger_reason=parser_empty" ): manifest_response = routes.ai_automation_pixelrag_crawler_integration_api.__wrapped__() manifest = manifest_response.get_json() assert assessment["policy"] == "read_only_pixelrag_crawler_integration_assessment_v1" assert assessment["target_platforms"] == ["momo"] assert assessment["controlled_apply"]["db_write"] is False assert manifest["policy"] == "read_only_pixelrag_visual_evidence_manifest_v1" assert manifest["result"] == "PIXELRAG_VISUAL_EVIDENCE_MANIFEST_READY" assert manifest["capture_target"]["platform"] == "pchome" def _write_capture_receipt(root, *, platform="pchome", manifest_id="manifest-1"): target_dir = root / platform / manifest_id tiles_dir = target_dir / "tiles" tiles_dir.mkdir(parents=True) screenshot = target_dir / "fullpage.png" tile_0 = tiles_dir / "tile_000.png" tile_1 = tiles_dir / "tile_001.png" screenshot.write_bytes(b"png") tile_0.write_bytes(b"png") tile_1.write_bytes(b"png") receipt = { "success": True, "policy": "read_only_pixelrag_visual_capture_artifact_v1", "status": "captured", "generated_at": datetime.now(timezone.utc).isoformat(), "manifest_id": manifest_id, "capture_target": { "platform": platform, "crawler": "PChomeCrawler.visual_fallback", "trigger_reason": "parser_empty", "url": "https://24h.pchome.com.tw/", }, "http_status": 200, "page_metrics": { "final_url": "https://24h.pchome.com.tw/", "title": "PChome 24h購物", }, "tile_plan": { "planned_tile_count": 6, "emitted_tile_count": 2, }, "files": [ {"kind": "fullpage_screenshot", "path": str(screenshot)}, {"kind": "tile", "path": str(tile_0)}, {"kind": "tile", "path": str(tile_1)}, ], } (target_dir / "capture_receipt.json").write_text( json.dumps(receipt, ensure_ascii=False), encoding="utf-8", ) return target_dir / "capture_receipt.json" def test_pixelrag_visual_evidence_readback_reports_latest_receipt(tmp_path): from services.pixelrag_crawler_integration_service import ( READBACK_POLICY, build_pixelrag_visual_evidence_readback, ) receipt_path = _write_capture_receipt(tmp_path) payload = build_pixelrag_visual_evidence_readback( artifact_root=tmp_path, platform="pchome", manifest_id="manifest-1", ) assert payload["policy"] == READBACK_POLICY assert payload["status"] == "ok" assert payload["summary"]["receipt_count"] == 1 assert payload["summary"]["tile_file_count"] == 2 assert payload["summary"]["missing_file_count"] == 0 assert payload["summary"]["http_status"] == 200 assert payload["latest_receipt"]["path"] == str(receipt_path) assert payload["next_machine_action"] == "keep_pixelrag_visual_evidence_receipt" assert payload["controlled_apply"]["db_write"] is False def test_pixelrag_visual_evidence_readback_warns_when_missing(tmp_path): from services.pixelrag_crawler_integration_service import build_pixelrag_visual_evidence_readback payload = build_pixelrag_visual_evidence_readback(artifact_root=tmp_path, platform="momo") assert payload["success"] is False assert payload["status"] == "warning" assert payload["summary"]["receipt_count"] == 0 assert payload["next_machine_action"] == "run_pixelrag_visual_capture_worker" def test_pixelrag_visual_evidence_readback_route_returns_payload(tmp_path, monkeypatch): from flask import Flask from routes import system_public_routes as routes from services import pixelrag_crawler_integration_service as service _write_capture_receipt(tmp_path, platform="pchome", manifest_id="route-manifest") monkeypatch.setattr(service, "DEFAULT_ARTIFACT_ROOT", str(tmp_path)) app = Flask(__name__) with app.test_request_context( "/api/ai-automation/pixelrag-visual-evidence-readback" "?platform=pchome&manifest_id=route-manifest&max_age_hours=999" ): response = routes.ai_automation_pixelrag_visual_evidence_readback_api.__wrapped__() payload = response.get_json() assert payload["policy"] == "read_only_pixelrag_visual_evidence_readback_v1" assert payload["status"] == "ok" assert payload["manifest_id"] == "route-manifest" assert payload["summary"]["tile_file_count"] == 2 def test_pixelrag_capture_worker_dry_run_plans_artifact_outputs(tmp_path): if not shutil.which("node"): pytest.skip("node is required for the dry-run capture worker test") from services.pixelrag_crawler_integration_service import ( build_pixelrag_visual_evidence_manifest, ) manifest = build_pixelrag_visual_evidence_manifest( url="https://m.momoshop.com.tw/search.momo?searchKeyword=test", platform="momo", crawler="MomoCrawler.search_products", trigger_reason="parser_empty", page_size={"width": 1024, "height": 1024}, tile_size={"width": 512, "height": 512}, ) completed = subprocess.run( [ "node", "scripts/ops/capture_pixelrag_visual_evidence.js", "--manifest-json", json.dumps(manifest), "--output-dir", str(tmp_path), "--dry-run", ], capture_output=True, check=False, text=True, ) assert completed.returncode == 0 payload = json.loads(completed.stdout) assert payload["policy"] == "read_only_pixelrag_visual_capture_artifact_v1" assert payload["status"] == "dry_run_ready" assert payload["controlled_apply"]["artifact_write"] is False assert payload["tile_plan"]["planned_tile_count"] == 4 assert payload["planned_output"]["screenshot"].endswith("fullpage.png") def test_pixelrag_capture_worker_rejects_wrong_platform_host(tmp_path): if not shutil.which("node"): pytest.skip("node is required for the dry-run capture worker test") from services.pixelrag_crawler_integration_service import ( build_pixelrag_visual_evidence_manifest, ) manifest = build_pixelrag_visual_evidence_manifest( url="https://example.test/product", platform="momo", crawler="MomoCrawler.search_products", trigger_reason="parser_empty", ) completed = subprocess.run( [ "node", "scripts/ops/capture_pixelrag_visual_evidence.js", "--manifest-json", json.dumps(manifest), "--output-dir", str(tmp_path), "--dry-run", ], capture_output=True, check=False, text=True, ) assert completed.returncode == 1 payload = json.loads(completed.stdout) assert payload["status"] == "rejected" assert any("not allowed for platform momo" in error for error in payload["errors"]) def test_pixelrag_python_capture_worker_dry_run_plans_artifact_outputs(tmp_path): from services.pixelrag_crawler_integration_service import ( build_pixelrag_visual_evidence_manifest, ) manifest = build_pixelrag_visual_evidence_manifest( url="https://24h.pchome.com.tw/", platform="pchome", crawler="PChomeCrawler.visual_fallback", trigger_reason="parser_empty", page_size={"width": 1024, "height": 1024}, tile_size={"width": 512, "height": 512}, ) completed = subprocess.run( [ sys.executable, "scripts/ops/capture_pixelrag_visual_evidence.py", "--manifest-json", json.dumps(manifest), "--output-dir", str(tmp_path), "--dry-run", ], capture_output=True, check=False, text=True, ) assert completed.returncode == 0 payload = json.loads(completed.stdout) assert payload["policy"] == "read_only_pixelrag_visual_capture_artifact_v1" assert payload["status"] == "dry_run_ready" assert payload["controlled_apply"]["artifact_write"] is False assert payload["tile_plan"]["planned_tile_count"] == 4 assert payload["capture_target"]["platform"] == "pchome"