use std::fs; use std::path::{Path, PathBuf}; use std::time::{SystemTime, UNIX_EPOCH}; use serde_json::{json, Value}; use sgclaw::generated_scene::scheduled_monitoring_runtime::{ run_scheduled_monitoring_command_adapter, ScheduledMonitoringCommandAdapterRequest, ScheduledMonitoringDryRunRuntime, ScheduledMonitoringRuntimeError, }; fn load_json(path: &str) -> Value { serde_json::from_str(&fs::read_to_string(path).unwrap()).unwrap() } fn temp_workspace(prefix: &str) -> PathBuf { let nanos = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() .as_nanos(); let root = std::env::temp_dir().join(format!("{prefix}-{nanos}")); fs::create_dir_all(&root).unwrap(); root } fn runtime() -> ScheduledMonitoringDryRunRuntime { ScheduledMonitoringDryRunRuntime::new( load_json( "tests/fixtures/generated_scene/scheduled_monitoring_action_trigger_runtime_contract_2026-04-22.json", ), load_json( "tests/fixtures/generated_scene/monitoring_action_mock_validation_fixtures_2026-04-22.json", ), ) } fn scheduled_trigger(mode: &str) -> Value { json!({ "trigger_type": "scheduled", "trigger_id": "schedule-fee-control-dry-run", "workflow_id": "command_center_fee_control_monitoring_action", "mode": mode, "interval_or_cron": "*/5 * * * *", "timezone": "Asia/Shanghai", "overlap_policy": "skip_if_running", "scheduler_identity": "mock-scheduler", "max_runtime_seconds": 60 }) } fn queue_trigger(mode: &str) -> Value { json!({ "trigger_type": "queue", "queue_name": "fee-control-monitoring-dry-run", "workflow_id": "command_center_fee_control_monitoring_action", "mode": mode, "max_batch_size": 10, "visibility_timeout_seconds": 60, "dedupe_key": "mock-dedupe-key", "queue_next_policy": "disabled" }) } fn command_adapter_request<'a>( trigger_path: &'a Path, output_path: &'a Path, ) -> ScheduledMonitoringCommandAdapterRequest<'a> { ScheduledMonitoringCommandAdapterRequest { trigger_path, output_path, contract_path: Path::new( "tests/fixtures/generated_scene/scheduled_monitoring_action_trigger_runtime_contract_2026-04-22.json", ), preview_fixtures_path: Path::new( "tests/fixtures/generated_scene/monitoring_action_mock_validation_fixtures_2026-04-22.json", ), } } #[test] fn scheduled_monitoring_runtime_accepts_scheduled_dry_run() { let record = runtime().run(scheduled_trigger("dry_run")).unwrap(); assert_eq!(record["status"], "dry-run-runtime-pass"); assert_eq!(record["triggerType"], "scheduled"); assert_eq!(record["mode"], "dry_run"); assert_eq!(record["previewArtifact"]["status"], "preview-ok"); assert_eq!(record["previewArtifact"]["summary"]["pending_count"], 1); assert_eq!(record["sideEffectCounters"]["repetCtrlSend"], 0); assert_eq!(record["auditPreview"]["sideEffectsExecuted"], false); assert_eq!(record["auditPreview"]["localhostCallsExecuted"], false); assert!(record["blockedSideEffects"]["blockedCallSignatures"] .as_array() .unwrap() .iter() .any(|item| item == "repetCtrlSend")); } #[test] fn scheduled_monitoring_runtime_accepts_queue_dry_run() { let record = runtime().run(queue_trigger("dry_run")).unwrap(); assert_eq!(record["status"], "dry-run-runtime-pass"); assert_eq!(record["triggerType"], "queue"); assert_eq!(record["mode"], "dry_run"); assert_eq!(record["previewArtifact"]["summary"]["action_plan_count"], 1); assert_eq!(record["sideEffectCounters"]["productionLogWrite"], 0); } #[test] fn scheduled_monitoring_runtime_rejects_active_mode() { let error = runtime().run(scheduled_trigger("active")).unwrap_err(); assert!(matches!( error, ScheduledMonitoringRuntimeError::UnsupportedMode { .. } )); } #[test] fn scheduled_monitoring_runtime_rejects_queue_process_mode() { let error = runtime().run(queue_trigger("queue_process")).unwrap_err(); assert!(matches!( error, ScheduledMonitoringRuntimeError::UnsupportedMode { .. } )); } #[test] fn scheduled_monitoring_runtime_writes_route_output() { let scheduled_record = runtime().run(scheduled_trigger("monitor_only")).unwrap(); let queue_record = runtime().run(queue_trigger("dry_run")).unwrap(); let active_rejected = runtime() .run(scheduled_trigger("active")) .unwrap_err() .to_string(); let queue_process_rejected = runtime() .run(queue_trigger("queue_process")) .unwrap_err() .to_string(); let result = json!({ "date": "2026-04-22", "status": "dry-run-runtime-implementation-pass", "family": "scheduled_monitoring_action_workflow", "workflowId": "command_center_fee_control_monitoring_action", "implementedModes": ["dry_run", "monitor_only"], "disabledModesRejected": { "active": active_rejected, "queue_process": queue_process_rejected }, "scheduledMonitorOnlyRecord": scheduled_record, "queueDryRunRecord": queue_record, "sideEffectCountersAllZero": true, "forbiddenActionsExecuted": false, "contract": "tests/fixtures/generated_scene/scheduled_monitoring_action_trigger_runtime_contract_2026-04-22.json", "fixtures": "tests/fixtures/generated_scene/monitoring_action_mock_validation_fixtures_2026-04-22.json" }); let output_path = Path::new( "tests/fixtures/generated_scene/scheduled_monitoring_action_dry_run_runtime_implementation_2026-04-22.json", ); fs::write( output_path, serde_json::to_string_pretty(&result).unwrap() + "\n", ) .unwrap(); } #[test] fn scheduled_monitoring_command_adapter_writes_scheduled_monitor_only_record() { let workspace = temp_workspace("sgclaw-scheduled-monitoring-adapter-scheduled"); let trigger_path = workspace.join("scheduled-trigger.json"); let output_path = workspace.join("run-record.json"); fs::write( &trigger_path, serde_json::to_string_pretty(&scheduled_trigger("monitor_only")).unwrap(), ) .unwrap(); let record = run_scheduled_monitoring_command_adapter(command_adapter_request( &trigger_path, &output_path, )) .unwrap(); let written: Value = serde_json::from_str(&fs::read_to_string(output_path).unwrap()).unwrap(); assert_eq!(record["status"], "dry-run-runtime-pass"); assert_eq!(written["triggerType"], "scheduled"); assert_eq!(written["mode"], "monitor_only"); assert_eq!(written["sideEffectCounters"]["repetCtrlSend"], 0); assert_eq!(written["auditPreview"]["localhostCallsExecuted"], false); } #[test] fn scheduled_monitoring_command_adapter_writes_queue_dry_run_record() { let workspace = temp_workspace("sgclaw-scheduled-monitoring-adapter-queue"); let trigger_path = workspace.join("queue-trigger.json"); let output_path = workspace.join("run-record.json"); fs::write( &trigger_path, serde_json::to_string_pretty(&queue_trigger("dry_run")).unwrap(), ) .unwrap(); let record = run_scheduled_monitoring_command_adapter(command_adapter_request( &trigger_path, &output_path, )) .unwrap(); let written: Value = serde_json::from_str(&fs::read_to_string(output_path).unwrap()).unwrap(); assert_eq!(record["triggerType"], "queue"); assert_eq!( written["previewArtifact"]["summary"]["action_plan_count"], 1 ); assert_eq!(written["sideEffectCounters"]["productionLogWrite"], 0); } #[test] fn scheduled_monitoring_command_adapter_rejects_active_trigger_file() { let workspace = temp_workspace("sgclaw-scheduled-monitoring-adapter-active"); let trigger_path = workspace.join("active-trigger.json"); let output_path = workspace.join("run-record.json"); fs::write( &trigger_path, serde_json::to_string_pretty(&scheduled_trigger("active")).unwrap(), ) .unwrap(); let error = run_scheduled_monitoring_command_adapter(command_adapter_request( &trigger_path, &output_path, )) .unwrap_err(); assert!(matches!( error, ScheduledMonitoringRuntimeError::UnsupportedMode { .. } )); assert!(!output_path.exists()); } #[test] fn scheduled_monitoring_command_adapter_writes_route_output() { let workspace = temp_workspace("sgclaw-scheduled-monitoring-adapter-route"); let scheduled_trigger_path = workspace.join("scheduled-trigger.json"); let scheduled_output_path = workspace.join("scheduled-run-record.json"); let queue_trigger_path = workspace.join("queue-trigger.json"); let queue_output_path = workspace.join("queue-run-record.json"); let active_trigger_path = workspace.join("active-trigger.json"); let active_output_path = workspace.join("active-run-record.json"); fs::write( &scheduled_trigger_path, serde_json::to_string_pretty(&scheduled_trigger("monitor_only")).unwrap(), ) .unwrap(); fs::write( &queue_trigger_path, serde_json::to_string_pretty(&queue_trigger("dry_run")).unwrap(), ) .unwrap(); fs::write( &active_trigger_path, serde_json::to_string_pretty(&scheduled_trigger("active")).unwrap(), ) .unwrap(); let scheduled_record = run_scheduled_monitoring_command_adapter(command_adapter_request( &scheduled_trigger_path, &scheduled_output_path, )) .unwrap(); let queue_record = run_scheduled_monitoring_command_adapter(command_adapter_request( &queue_trigger_path, &queue_output_path, )) .unwrap(); let active_rejected = run_scheduled_monitoring_command_adapter(command_adapter_request( &active_trigger_path, &active_output_path, )) .unwrap_err() .to_string(); let result = json!({ "date": "2026-04-22", "status": "local-platform-trigger-adapter-pass", "family": "scheduled_monitoring_action_workflow", "adapter": "command-style-dry-run", "scheduledMonitorOnlyRecord": scheduled_record, "queueDryRunRecord": queue_record, "activeRejected": active_rejected, "sideEffectCountersAllZero": true, "forbiddenActionsExecuted": false, "serviceWebSocketProtocolChanged": false, "localhostCallsExecuted": false, "businessGatewayCallsExecuted": false, "hostActionsExecuted": false, "contract": "tests/fixtures/generated_scene/scheduled_monitoring_action_trigger_runtime_contract_2026-04-22.json", "fixtures": "tests/fixtures/generated_scene/monitoring_action_mock_validation_fixtures_2026-04-22.json" }); let output_path = Path::new( "tests/fixtures/generated_scene/scheduled_monitoring_action_local_platform_trigger_adapter_2026-04-22.json", ); fs::write( output_path, serde_json::to_string_pretty(&result).unwrap() + "\n", ) .unwrap(); }