use a3s_flow::{
FlowEvent, FlowEventEnvelope, NativeRuntimeKind, NativeRuntimeRequest, NativeRuntimeResponse,
RuntimeCommand, WorkflowRunSummary, NATIVE_RUNTIME_PROTOCOL,
};
use chrono::Utc;
use serde_json::json;
use uuid::Uuid;
#[test]
fn native_runtime_request_uses_stable_json_field_names() {
let request = NativeRuntimeRequest::new(
NativeRuntimeKind::Workflow,
"main",
"abc123",
json!({ "run_id": "run-1" }),
);
let encoded = serde_json::to_value(request).unwrap();
assert_eq!(encoded["protocol"], NATIVE_RUNTIME_PROTOCOL);
assert_eq!(encoded["kind"], "workflow");
assert_eq!(encoded["exportName"], "main");
assert_eq!(encoded["sourceHash"], "abc123");
assert_eq!(encoded["payload"]["run_id"], "run-1");
}
#[test]
fn native_runtime_response_decodes_kind_and_error_envelope() {
let response: NativeRuntimeResponse = serde_json::from_value(json!({
"protocol": NATIVE_RUNTIME_PROTOCOL,
"kind": "step",
"ok": false,
"error": "step failed"
}))
.unwrap();
assert_eq!(response.kind, NativeRuntimeKind::Step);
assert_eq!(response.error.as_deref(), Some("step failed"));
}
#[test]
fn native_runtime_operation_commands_accept_omitted_optional_fields() {
let progress: RuntimeCommand = serde_json::from_value(json!({
"type": "record_progress",
"progress": {
"progress_id": "download-1",
"completed": 1
}
}))
.unwrap();
match progress {
RuntimeCommand::RecordProgress { progress } => {
assert_eq!(progress.total, None);
assert_eq!(progress.message, None);
assert_eq!(progress.details, serde_json::Value::Null);
}
command => panic!("unexpected command: {command:?}"),
}
let child: RuntimeCommand = serde_json::from_value(json!({
"type": "link_child_operation",
"child": {
"reference_id": "runtime",
"kind": "runtime.unit",
"operation_id": "runtime-1"
}
}))
.unwrap();
match child {
RuntimeCommand::LinkChildOperation { child } => {
assert_eq!(child.flow_run_id, None);
assert_eq!(child.metadata, serde_json::Value::Null);
}
command => panic!("unexpected command: {command:?}"),
}
}
#[test]
fn run_summary_accepts_payloads_from_before_cancelling_state_was_added() {
let summary: WorkflowRunSummary = serde_json::from_value(json!({
"total_runs": 0,
"pending_runs": 0,
"running_runs": 0,
"suspended_runs": 0,
"completed_runs": 0,
"failed_runs": 0,
"cancelled_runs": 0,
"terminal_runs": 0,
"non_terminal_runs": 0,
"open_waits": 0,
"active_hooks": 0,
"pending_retries": 0
}))
.unwrap();
assert_eq!(summary.cancelling_runs, 0);
}
#[test]
fn flow_event_envelope_uses_stable_native_history_field_names() {
let envelope = FlowEventEnvelope {
run_id: "run-1".to_string(),
sequence: 7,
event_id: Uuid::new_v4(),
timestamp: Utc::now(),
event: FlowEvent::RunCancelled { reason: None },
};
let encoded = serde_json::to_value(envelope).unwrap();
assert_eq!(encoded["run_id"], "run-1");
assert_eq!(encoded["sequence"], 7);
assert!(encoded["event_id"].as_str().is_some());
assert!(encoded["timestamp"].as_str().is_some());
assert_eq!(encoded["event"]["type"], "run_cancelled");
assert_eq!(encoded["event"]["reason"], serde_json::Value::Null);
assert!(
encoded.get("key").is_none(),
"native runtime history envelopes should not include derived event keys"
);
}
#[test]
fn native_ts_authoring_types_track_runtime_protocol_shape() {
let types = include_str!("../examples/native-ts/a3s-flow-runtime.d.ts");
for event_type in [
"run_created",
"run_started",
"run_completed",
"run_failed",
"run_cancellation_requested",
"run_cancelled",
"run_timed_out",
"run_retry_exhausted",
"run_host_shutdown",
"run_progress_recorded",
"child_operation_linked",
"step_created",
"step_started",
"step_completed",
"step_retrying",
"step_failed",
"wait_created",
"wait_completed",
"hook_created",
"hook_received",
"hook_disposed",
] {
assert!(
types.contains(&format!("\"{event_type}\"")),
"missing FlowEvent type {event_type} in native TypeScript authoring contract"
);
}
assert!(types.contains("event_id: string;"));
assert!(!types.contains("key: string;"));
assert!(types.contains("token: string;"));
assert!(types.contains("retry?: RetryPolicy;"));
assert!(types.contains("retry_after: string | null;"));
assert!(types.contains("type: \"record_progress\"; progress: WorkflowProgress"));
assert!(types.contains("type: \"link_child_operation\"; child: ChildOperationReference"));
assert!(types.contains("type: \"cancel\""));
assert!(!types.contains("export function stepOutput"));
}