315 lines
11 KiB
Rust
315 lines
11 KiB
Rust
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();
|
|
}
|