This commit is contained in:
@@ -484,6 +484,10 @@ def test_market_intel_preview_template_uses_safe_fetch_false_endpoint():
|
||||
assert "data-market-intel-opportunity-alert-sequence" in template
|
||||
assert "data-market-intel-migration" in template
|
||||
assert "data-market-intel-migration-tables" in template
|
||||
assert "data-market-intel-migration-drill" in template
|
||||
assert "data-market-intel-migration-drill-checks" in template
|
||||
assert "data-market-intel-migration-drill-preapply" in template
|
||||
assert "data-market-intel-migration-drill-rollback" in template
|
||||
assert "data-market-intel-approval" in template
|
||||
assert "data-market-intel-approval-gates" in template
|
||||
assert "data-market-intel-deploy" in template
|
||||
@@ -506,6 +510,7 @@ def test_market_intel_preview_template_uses_safe_fetch_false_endpoint():
|
||||
assert "market_intel.market_intel_opportunity_evidence_plan" in template
|
||||
assert "market_intel.market_intel_opportunity_alert_plan" in template
|
||||
assert "market_intel.market_intel_migration_blueprint" in template
|
||||
assert "market_intel.market_intel_migration_apply_drill" in template
|
||||
assert "market_intel.market_intel_write_approval_runbook" in template
|
||||
assert "market_intel.market_intel_deployment_readiness" in template
|
||||
assert "required_manual_steps" in template
|
||||
@@ -530,7 +535,7 @@ def test_legacy_source_bridge_default_is_planned_only():
|
||||
bridge = MarketIntelService().build_legacy_source_bridge()
|
||||
|
||||
assert bridge["mode"] == "legacy_source_bridge_planned"
|
||||
assert bridge["phase"] == "phase_42_alert_review_queue_migration_blueprint"
|
||||
assert bridge["phase"] == "phase_43_migration_apply_drill_preview"
|
||||
assert bridge["execute_requested"] is False
|
||||
assert bridge["read_only_query_executed"] is False
|
||||
assert bridge["database_connection_opened"] is False
|
||||
@@ -688,7 +693,7 @@ def test_mcp_tool_contract_preview_is_read_only_and_whitelisted():
|
||||
contract = MarketIntelService().build_mcp_tool_contract()
|
||||
|
||||
assert contract["mode"] == "mcp_tool_contract_preview"
|
||||
assert contract["phase"] == "phase_42_alert_review_queue_migration_blueprint"
|
||||
assert contract["phase"] == "phase_43_migration_apply_drill_preview"
|
||||
assert contract["caller"] == "market_intel"
|
||||
assert contract["contract_ready"] is True
|
||||
assert contract["blocked_reasons"] == []
|
||||
@@ -821,7 +826,7 @@ def test_mcp_activation_runbook_route_is_preview_only():
|
||||
|
||||
assert response.status_code == 200
|
||||
assert data["mode"] == "mcp_activation_runbook_preview"
|
||||
assert data["phase"] == "phase_42_alert_review_queue_migration_blueprint"
|
||||
assert data["phase"] == "phase_43_migration_apply_drill_preview"
|
||||
assert data["deployment_actions_executed"] is False
|
||||
assert data["docker_command_executed"] is False
|
||||
assert data["ssh_command_executed"] is False
|
||||
@@ -834,7 +839,7 @@ def test_mcp_fetch_gate_default_blocks_external_fetch():
|
||||
gate = MarketIntelService().build_mcp_fetch_gate(fetch_requested=True)
|
||||
|
||||
assert gate["mode"] == "mcp_fetch_gate_planned"
|
||||
assert gate["phase"] == "phase_42_alert_review_queue_migration_blueprint"
|
||||
assert gate["phase"] == "phase_43_migration_apply_drill_preview"
|
||||
assert gate["fetch_requested"] is True
|
||||
assert gate["manual_fetch_gate_open"] is False
|
||||
assert gate["network_request_allowed"] is False
|
||||
@@ -904,7 +909,7 @@ def test_mcp_fetch_gate_route_is_preview_only():
|
||||
|
||||
assert response.status_code == 200
|
||||
assert data["mode"] == "mcp_fetch_gate_planned"
|
||||
assert data["phase"] == "phase_42_alert_review_queue_migration_blueprint"
|
||||
assert data["phase"] == "phase_43_migration_apply_drill_preview"
|
||||
assert data["fetch_requested"] is False
|
||||
assert data["network_request_allowed"] is False
|
||||
assert data["external_network_executed"] is False
|
||||
@@ -916,7 +921,7 @@ def test_scheduler_plan_preview_blocks_job_attachment():
|
||||
plan = MarketIntelService().build_scheduler_plan()
|
||||
|
||||
assert plan["mode"] == "scheduler_attach_plan_preview"
|
||||
assert plan["phase"] == "phase_42_alert_review_queue_migration_blueprint"
|
||||
assert plan["phase"] == "phase_43_migration_apply_drill_preview"
|
||||
assert plan["ready_to_attach_scheduler"] is False
|
||||
assert plan["scheduler_attached"] is False
|
||||
assert plan["scheduler_registration_executed"] is False
|
||||
@@ -954,7 +959,7 @@ def test_scheduler_plan_route_is_preview_only():
|
||||
|
||||
assert response.status_code == 200
|
||||
assert data["mode"] == "scheduler_attach_plan_preview"
|
||||
assert data["phase"] == "phase_42_alert_review_queue_migration_blueprint"
|
||||
assert data["phase"] == "phase_43_migration_apply_drill_preview"
|
||||
assert data["scheduler_registration_executed"] is False
|
||||
assert data["crawler_job_started"] is False
|
||||
assert data["external_network_executed"] is False
|
||||
@@ -965,7 +970,7 @@ def test_match_review_plan_preview_blocks_auto_confirm():
|
||||
plan = MarketIntelService().build_match_review_plan()
|
||||
|
||||
assert plan["mode"] == "match_review_plan_preview"
|
||||
assert plan["phase"] == "phase_42_alert_review_queue_migration_blueprint"
|
||||
assert plan["phase"] == "phase_43_migration_apply_drill_preview"
|
||||
assert plan["ready_for_review_queue"] is False
|
||||
assert plan["review_queue_created"] is False
|
||||
assert plan["auto_match_executed"] is False
|
||||
@@ -1001,7 +1006,7 @@ def test_match_review_plan_route_is_preview_only():
|
||||
|
||||
assert response.status_code == 200
|
||||
assert data["mode"] == "match_review_plan_preview"
|
||||
assert data["phase"] == "phase_42_alert_review_queue_migration_blueprint"
|
||||
assert data["phase"] == "phase_43_migration_apply_drill_preview"
|
||||
assert data["review_queue_created"] is False
|
||||
assert data["auto_confirm_executed"] is False
|
||||
assert data["external_network_executed"] is False
|
||||
@@ -1012,7 +1017,7 @@ def test_opportunity_plan_preview_blocks_alerts_and_ai_summary():
|
||||
plan = MarketIntelService().build_opportunity_plan()
|
||||
|
||||
assert plan["mode"] == "opportunity_plan_preview"
|
||||
assert plan["phase"] == "phase_42_alert_review_queue_migration_blueprint"
|
||||
assert plan["phase"] == "phase_43_migration_apply_drill_preview"
|
||||
assert plan["ready_for_opportunity_queue"] is False
|
||||
assert plan["opportunity_queue_created"] is False
|
||||
assert plan["threat_alert_dispatched"] is False
|
||||
@@ -1053,7 +1058,7 @@ def test_opportunity_plan_route_is_preview_only():
|
||||
|
||||
assert response.status_code == 200
|
||||
assert data["mode"] == "opportunity_plan_preview"
|
||||
assert data["phase"] == "phase_42_alert_review_queue_migration_blueprint"
|
||||
assert data["phase"] == "phase_43_migration_apply_drill_preview"
|
||||
assert data["opportunity_queue_created"] is False
|
||||
assert data["threat_alert_dispatched"] is False
|
||||
assert data["ai_summary_generated"] is False
|
||||
@@ -1064,7 +1069,7 @@ def test_opportunity_scoring_plan_preview_blocks_scoring_and_alerts():
|
||||
plan = MarketIntelService().build_opportunity_scoring_plan()
|
||||
|
||||
assert plan["mode"] == "opportunity_scoring_plan_preview"
|
||||
assert plan["phase"] == "phase_42_alert_review_queue_migration_blueprint"
|
||||
assert plan["phase"] == "phase_43_migration_apply_drill_preview"
|
||||
assert plan["ready_for_scoring_job"] is False
|
||||
assert plan["scoring_job_created"] is False
|
||||
assert plan["score_calculation_executed"] is False
|
||||
@@ -1112,7 +1117,7 @@ def test_opportunity_scoring_plan_route_is_preview_only():
|
||||
|
||||
assert response.status_code == 200
|
||||
assert data["mode"] == "opportunity_scoring_plan_preview"
|
||||
assert data["phase"] == "phase_42_alert_review_queue_migration_blueprint"
|
||||
assert data["phase"] == "phase_43_migration_apply_drill_preview"
|
||||
assert data["scoring_job_created"] is False
|
||||
assert data["score_calculation_executed"] is False
|
||||
assert data["sample_scores_generated"] is False
|
||||
@@ -1124,7 +1129,7 @@ def test_opportunity_evidence_plan_preview_blocks_queries_and_alerts():
|
||||
plan = MarketIntelService().build_opportunity_evidence_plan()
|
||||
|
||||
assert plan["mode"] == "opportunity_evidence_plan_preview"
|
||||
assert plan["phase"] == "phase_42_alert_review_queue_migration_blueprint"
|
||||
assert plan["phase"] == "phase_43_migration_apply_drill_preview"
|
||||
assert plan["ready_for_evidence_bundle"] is False
|
||||
assert plan["evidence_bundle_created"] is False
|
||||
assert plan["evidence_query_executed"] is False
|
||||
@@ -1170,7 +1175,7 @@ def test_opportunity_evidence_plan_route_is_preview_only():
|
||||
|
||||
assert response.status_code == 200
|
||||
assert data["mode"] == "opportunity_evidence_plan_preview"
|
||||
assert data["phase"] == "phase_42_alert_review_queue_migration_blueprint"
|
||||
assert data["phase"] == "phase_43_migration_apply_drill_preview"
|
||||
assert data["evidence_bundle_created"] is False
|
||||
assert data["evidence_query_executed"] is False
|
||||
assert data["sample_evidence_generated"] is False
|
||||
@@ -1183,7 +1188,7 @@ def test_opportunity_alert_plan_preview_blocks_dispatch_and_llm_calls():
|
||||
plan = MarketIntelService().build_opportunity_alert_plan()
|
||||
|
||||
assert plan["mode"] == "opportunity_alert_plan_preview"
|
||||
assert plan["phase"] == "phase_42_alert_review_queue_migration_blueprint"
|
||||
assert plan["phase"] == "phase_43_migration_apply_drill_preview"
|
||||
assert plan["ready_for_alert_candidates"] is False
|
||||
assert plan["alert_candidate_created"] is False
|
||||
assert plan["alert_queue_created"] is False
|
||||
@@ -1268,7 +1273,7 @@ def test_opportunity_alert_plan_route_is_preview_only():
|
||||
|
||||
assert response.status_code == 200
|
||||
assert data["mode"] == "opportunity_alert_plan_preview"
|
||||
assert data["phase"] == "phase_42_alert_review_queue_migration_blueprint"
|
||||
assert data["phase"] == "phase_43_migration_apply_drill_preview"
|
||||
assert data["alert_candidate_created"] is False
|
||||
assert data["alert_queue_created"] is False
|
||||
assert data["review_queue_created"] is False
|
||||
@@ -1346,7 +1351,7 @@ def test_mcp_deploy_preflight_route_is_preview_only():
|
||||
|
||||
assert response.status_code == 200
|
||||
assert data["mode"] == "mcp_external_deploy_preflight_preview"
|
||||
assert data["phase"] == "phase_42_alert_review_queue_migration_blueprint"
|
||||
assert data["phase"] == "phase_43_migration_apply_drill_preview"
|
||||
assert data["deployment_actions_executed"] is False
|
||||
assert data["docker_command_executed"] is False
|
||||
assert data["ssh_command_executed"] is False
|
||||
@@ -1361,7 +1366,7 @@ def test_mcp_readiness_default_is_planned_only(monkeypatch):
|
||||
readiness = MarketIntelService().build_mcp_readiness()
|
||||
|
||||
assert readiness["mode"] == "mcp_readiness_planned"
|
||||
assert readiness["phase"] == "phase_42_alert_review_queue_migration_blueprint"
|
||||
assert readiness["phase"] == "phase_43_migration_apply_drill_preview"
|
||||
assert readiness["execute_requested"] is False
|
||||
assert readiness["router_enabled"] is False
|
||||
assert readiness["external_mcp_complete"] is False
|
||||
@@ -1754,6 +1759,7 @@ def test_deployment_readiness_reports_app_only_release_gate():
|
||||
assert readiness["checks"]["opportunity_scoring_plan_preview_safe"] is True
|
||||
assert readiness["checks"]["opportunity_evidence_plan_preview_safe"] is True
|
||||
assert readiness["checks"]["opportunity_alert_plan_preview_safe"] is True
|
||||
assert readiness["checks"]["migration_apply_drill_preview_safe"] is True
|
||||
assert readiness["checks"]["writer_plan_dry_run_only"] is True
|
||||
assert readiness["writer_plan_summary"]["writes_executed"] is False
|
||||
assert "readiness_checks_not_all_passed" not in readiness["blocked_reasons"]
|
||||
@@ -1780,11 +1786,17 @@ def test_deployment_readiness_reports_app_only_release_gate():
|
||||
assert "/api/market_intel/opportunity_scoring_plan" in readiness["production_smoke_targets"]
|
||||
assert "/api/market_intel/opportunity_evidence_plan" in readiness["production_smoke_targets"]
|
||||
assert "/api/market_intel/opportunity_alert_plan" in readiness["production_smoke_targets"]
|
||||
assert "/api/market_intel/migration_apply_drill" in readiness["production_smoke_targets"]
|
||||
assert readiness["write_approval_runbook"]["ready_for_real_write"] is False
|
||||
assert readiness["write_approval_runbook"]["writes_executed"] is False
|
||||
assert readiness["migration_blueprint"]["migration_executed"] is False
|
||||
assert readiness["migration_blueprint"]["file_created"] is True
|
||||
assert readiness["migration_blueprint"]["file_matches_blueprint"] is True
|
||||
assert readiness["migration_apply_drill"]["mode"] == "migration_apply_drill_preview"
|
||||
assert readiness["migration_apply_drill"]["migration_executed"] is False
|
||||
assert readiness["migration_apply_drill"]["rollback_executed"] is False
|
||||
assert readiness["migration_apply_drill"]["database_write_executed"] is False
|
||||
assert readiness["migration_apply_drill"]["api_executes_migration"] is False
|
||||
assert readiness["schema_db_probe"]["read_only_query_executed"] is False
|
||||
assert readiness["platform_seed_db_diff"]["read_only_query_executed"] is False
|
||||
assert readiness["legacy_source_bridge"]["read_only_query_executed"] is False
|
||||
@@ -1898,6 +1910,133 @@ def test_migration_blueprint_is_additive_preview_only():
|
||||
assert "DROP TABLE IF EXISTS market_alert_review_queue" in blueprint["rollback_sql"]
|
||||
|
||||
|
||||
def test_migration_apply_drill_planned_is_safe_and_manual_only():
|
||||
drill = MarketIntelService().build_migration_apply_drill()
|
||||
|
||||
assert drill["mode"] == "migration_apply_drill_preview"
|
||||
assert drill["phase"] == "phase_43_migration_apply_drill_preview"
|
||||
assert drill["execute_requested"] is False
|
||||
assert drill["schema_state"] == "planned_no_db_probe"
|
||||
assert drill["drill_ready_for_operator_review"] is True
|
||||
assert drill["ready_for_manual_apply_review"] is False
|
||||
assert drill["ready_to_apply_migration"] is False
|
||||
assert drill["read_only_query_executed"] is False
|
||||
assert drill["database_connection_opened"] is False
|
||||
assert drill["database_session_created"] is False
|
||||
assert drill["database_write_executed"] is False
|
||||
assert drill["database_commit_executed"] is False
|
||||
assert drill["migration_executed"] is False
|
||||
assert drill["rollback_executed"] is False
|
||||
assert drill["api_executes_migration"] is False
|
||||
assert drill["api_executes_rollback"] is False
|
||||
assert drill["checks"]["migration_file_matches_blueprint"] is True
|
||||
assert drill["checks"]["forward_sql_additive_only"] is True
|
||||
assert "read_only_db_probe_not_executed" in drill["blocked_reasons"]
|
||||
assert "api_never_runs_migration" in drill["blocked_reasons"]
|
||||
assert drill["rollback_drill"]["rollback_executed"] is False
|
||||
assert drill["rollback_drill"]["requires_manual_approval"] is True
|
||||
assert "market_alert_review_queue" in drill["rollback_drill"]["rollback_table_order"]
|
||||
assert len(drill["pre_apply_checklist"]) >= 6
|
||||
assert len(drill["post_apply_verification"]) >= 4
|
||||
assert len(drill["risk_register"]) >= 4
|
||||
|
||||
|
||||
def test_migration_apply_drill_sqlite_read_only_reports_already_applied():
|
||||
service = MarketIntelService()
|
||||
seed_rows = service.build_platform_seed_plan()["seeds"]
|
||||
engine = create_engine("sqlite:///:memory:")
|
||||
with engine.begin() as conn:
|
||||
conn.execute(
|
||||
text(
|
||||
"""
|
||||
CREATE TABLE market_platforms (
|
||||
code TEXT PRIMARY KEY,
|
||||
name TEXT,
|
||||
base_url TEXT,
|
||||
enabled BOOLEAN,
|
||||
crawl_policy_json TEXT
|
||||
)
|
||||
"""
|
||||
)
|
||||
)
|
||||
for table_name in MARKET_INTEL_TABLES:
|
||||
if table_name == "market_platforms":
|
||||
continue
|
||||
conn.execute(text(f"CREATE TABLE {table_name} (id INTEGER PRIMARY KEY)"))
|
||||
for seed in seed_rows:
|
||||
conn.execute(
|
||||
text(
|
||||
"""
|
||||
INSERT INTO market_platforms
|
||||
(code, name, base_url, enabled, crawl_policy_json)
|
||||
VALUES
|
||||
(:code, :name, :base_url, :enabled, :crawl_policy_json)
|
||||
"""
|
||||
),
|
||||
{
|
||||
"code": seed["code"],
|
||||
"name": seed["name"],
|
||||
"base_url": seed["base_url"],
|
||||
"enabled": seed["enabled"],
|
||||
"crawl_policy_json": json.dumps(
|
||||
seed["crawl_policy_json"],
|
||||
ensure_ascii=False,
|
||||
sort_keys=True,
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
drill = service.build_migration_apply_drill(
|
||||
execute_requested=True,
|
||||
schema_engine=engine,
|
||||
seed_diff_engine=engine,
|
||||
database_type="sqlite",
|
||||
)
|
||||
|
||||
assert drill["mode"] == "migration_apply_drill_preview"
|
||||
assert drill["execute_requested"] is True
|
||||
assert drill["read_only_query_executed"] is True
|
||||
assert drill["database_connection_opened"] is True
|
||||
assert drill["schema_state"] == "already_applied"
|
||||
assert drill["schema_db_probe_summary"]["schema_tables_exist"] is True
|
||||
assert drill["schema_db_probe_summary"]["missing_tables"] == []
|
||||
assert drill["platform_seed_db_diff_summary"]["seed_rows_ready"] is True
|
||||
assert set(drill["platform_seed_db_diff_summary"]["matching_codes"]) == {
|
||||
"momo",
|
||||
"pchome",
|
||||
"coupang",
|
||||
"shopee",
|
||||
}
|
||||
assert drill["database_session_created"] is False
|
||||
assert drill["database_write_executed"] is False
|
||||
assert drill["database_commit_executed"] is False
|
||||
assert drill["migration_executed"] is False
|
||||
assert "market_schema_already_present_apply_not_required" in drill["blocked_reasons"]
|
||||
|
||||
|
||||
def test_migration_apply_drill_route_is_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
with client.session_transaction() as session:
|
||||
session["logged_in"] = True
|
||||
|
||||
response = client.get("/api/market_intel/migration_apply_drill?execute=false")
|
||||
data = response.get_json()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert data["mode"] == "migration_apply_drill_preview"
|
||||
assert data["phase"] == "phase_43_migration_apply_drill_preview"
|
||||
assert data["execute_requested"] is False
|
||||
assert data["migration_executed"] is False
|
||||
assert data["rollback_executed"] is False
|
||||
assert data["database_write_executed"] is False
|
||||
assert data["api_executes_migration"] is False
|
||||
|
||||
|
||||
def test_seed_writer_cli_status_blocks_real_write():
|
||||
status = MarketIntelService().build_seed_writer_cli_status(
|
||||
platform_code="all",
|
||||
|
||||
Reference in New Issue
Block a user