import json import shutil import subprocess import sys 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["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 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"