From fa05b3df5e150be34a868caaa7d85b9ae70d4417 Mon Sep 17 00:00:00 2001 From: Scott Werner Date: Thu, 13 Aug 2026 10:44:58 -0400 Subject: [PATCH] Prove workflow-version ID JSON parity with the OpenAPI schema The round-trip fixtures only used empty workflow_dependencies, so no WorkflowVersionId value ever appeared on the wire in a fabro-api assertion and CreateWorkflowVersionResponse had no coverage at all. Put a real 64-hex id in the fixture, round-trip the response type, and pin serialization to the schema's ^[0-9a-f]{64}$ pattern including lowercase normalization of case-insensitive input. Co-Authored-By: Claude Fable 5 --- .../tests/workflow_version_round_trip.rs | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/lib/foundation/fabro-api/tests/workflow_version_round_trip.rs b/lib/foundation/fabro-api/tests/workflow_version_round_trip.rs index 894c1c910..3bf0c8835 100644 --- a/lib/foundation/fabro-api/tests/workflow_version_round_trip.rs +++ b/lib/foundation/fabro-api/tests/workflow_version_round_trip.rs @@ -1,12 +1,14 @@ use std::any::{TypeId, type_name}; use fabro_api::types::{ - WorkflowPath as ApiWorkflowPath, WorkflowVersion as ApiWorkflowVersion, - WorkflowVersionId as ApiWorkflowVersionId, + CreateWorkflowVersionResponse, WorkflowPath as ApiWorkflowPath, + WorkflowVersion as ApiWorkflowVersion, WorkflowVersionId as ApiWorkflowVersionId, }; use fabro_types::{WorkflowPath, WorkflowVersion, WorkflowVersionId}; use serde_json::json; +const DEPENDENCY_ID: &str = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; + #[test] fn workflow_version_schemas_reuse_domain_types() { assert_same_type::(); @@ -22,13 +24,38 @@ fn workflow_version_round_trips_exact_wire_shape() { "prompts/goal.md": "Ship it", "workflow.fabro": "digraph W { start [shape=Mdiamond] exit [shape=Msquare] start -> exit }" }, - "workflow_dependencies": {} + "workflow_dependencies": { "children/check.fabro": DEPENDENCY_ID } }); let version: ApiWorkflowVersion = serde_json::from_value(value.clone()).unwrap(); assert_eq!(serde_json::to_value(version).unwrap(), value); } +#[test] +fn create_workflow_version_response_round_trips_exact_wire_shape() { + let value = json!({ "workflow_version_id": DEPENDENCY_ID }); + + let response: CreateWorkflowVersionResponse = serde_json::from_value(value.clone()).unwrap(); + assert_eq!(serde_json::to_value(&response).unwrap(), value); +} + +#[test] +fn workflow_version_id_emits_the_documented_lowercase_pattern() { + // Input is accepted case-insensitively, but serialization must match the + // OpenAPI schema pattern `^[0-9a-f]{64}$`. + let id = serde_json::from_value::(json!(DEPENDENCY_ID.to_uppercase())) + .unwrap(); + let emitted = serde_json::to_value(id).unwrap(); + assert_eq!(emitted, json!(DEPENDENCY_ID)); + + let text = emitted.as_str().unwrap(); + assert_eq!(text.len(), 64); + assert!( + text.bytes() + .all(|byte| matches!(byte, b'0'..=b'9' | b'a'..=b'f')) + ); +} + #[test] fn workflow_version_replacement_rejects_unknown_fields() { let value = json!({