From bfee8040e971ccc9ffbdd9a4d8e44c820b9efc92 Mon Sep 17 00:00:00 2001 From: Jochen Date: Wed, 19 Nov 2025 11:32:14 +1100 Subject: [PATCH] regression tests added --- crates/g3-core/src/lib.rs | 3 + .../cache_control_error_regression_test.rs | 131 ++++++++++++++ .../tests/cache_control_integration_test.rs | 164 ++++++++++++++++++ 3 files changed, 298 insertions(+) create mode 100644 crates/g3-providers/tests/cache_control_error_regression_test.rs create mode 100644 crates/g3-providers/tests/cache_control_integration_test.rs diff --git a/crates/g3-core/src/lib.rs b/crates/g3-core/src/lib.rs index 0ee85b2..ee9b314 100644 --- a/crates/g3-core/src/lib.rs +++ b/crates/g3-core/src/lib.rs @@ -3,6 +3,8 @@ pub mod error_handling; pub mod project; pub mod task_result; pub mod ui_writer; + +use std::process::exit; pub use task_result::TaskResult; #[cfg(test)] @@ -1528,6 +1530,7 @@ If you can complete it with 1-2 tool calls, skip TODO. Message::new(MessageRole::System, system_prompt) } }; + self.context_window.add_message(system_message); } diff --git a/crates/g3-providers/tests/cache_control_error_regression_test.rs b/crates/g3-providers/tests/cache_control_error_regression_test.rs new file mode 100644 index 0000000..533c943 --- /dev/null +++ b/crates/g3-providers/tests/cache_control_error_regression_test.rs @@ -0,0 +1,131 @@ +//! Regression test for cache_control serialization bug +//! +//! This test verifies that cache_control is NOT serialized in the wrong format. +//! The bug was that it serialized as: +//! - `system.0.cache_control.ephemeral.ttl` (WRONG) +//! +//! It should serialize as: +//! - `"cache_control": {"type": "ephemeral"}` for ephemeral +//! - `"cache_control": {"type": "ephemeral", "ttl": "5m"}` for 5minute +//! - `"cache_control": {"type": "ephemeral", "ttl": "1h"}` for 1hour + +use g3_providers::{CacheControl, Message, MessageRole}; + +#[test] +fn test_no_wrong_serialization_format() { + // Test ephemeral + let msg = Message::with_cache_control( + MessageRole::System, + "Test".to_string(), + CacheControl::ephemeral(), + ); + let json = serde_json::to_string(&msg).unwrap(); + + println!("Ephemeral message JSON: {}", json); + + // Should NOT contain the wrong format + assert!(!json.contains("system.0.cache_control"), + "JSON should not contain 'system.0.cache_control' path"); + assert!(!json.contains("cache_control.ephemeral"), + "JSON should not contain 'cache_control.ephemeral' path"); + + // Should contain the correct format + assert!(json.contains(r#""cache_control":{"type":"ephemeral"}"#), + "JSON should contain correct cache_control format"); +} + +#[test] +fn test_five_minute_no_wrong_format() { + let msg = Message::with_cache_control( + MessageRole::System, + "Test".to_string(), + CacheControl::five_minute(), + ); + let json = serde_json::to_string(&msg).unwrap(); + + println!("5-minute message JSON: {}", json); + + // Should NOT contain the wrong format + assert!(!json.contains("system.0.cache_control"), + "JSON should not contain 'system.0.cache_control' path"); + assert!(!json.contains("cache_control.ephemeral.ttl"), + "JSON should not contain 'cache_control.ephemeral.ttl' path"); + + // Should contain the correct format with ttl as a direct field + assert!(json.contains(r#""type":"ephemeral""#), + "JSON should contain type field"); + assert!(json.contains(r#""ttl":"5m""#), + "JSON should contain ttl field with value 5m"); +} + +#[test] +fn test_one_hour_no_wrong_format() { + let msg = Message::with_cache_control( + MessageRole::System, + "Test".to_string(), + CacheControl::one_hour(), + ); + let json = serde_json::to_string(&msg).unwrap(); + + println!("1-hour message JSON: {}", json); + + // Should NOT contain the wrong format + assert!(!json.contains("system.0.cache_control"), + "JSON should not contain 'system.0.cache_control' path"); + assert!(!json.contains("cache_control.ephemeral.ttl"), + "JSON should not contain 'cache_control.ephemeral.ttl' path"); + + // Should contain the correct format with ttl as a direct field + assert!(json.contains(r#""type":"ephemeral""#), + "JSON should contain type field"); + assert!(json.contains(r#""ttl":"1h""#), + "JSON should contain ttl field with value 1h"); +} + +#[test] +fn test_cache_control_structure_is_flat() { + // Verify that the cache_control object has a flat structure + // with 'type' and optional 'ttl' at the same level + + let cache_control = CacheControl::five_minute(); + let json_value = serde_json::to_value(&cache_control).unwrap(); + + println!("Cache control as JSON value: {}", serde_json::to_string_pretty(&json_value).unwrap()); + + let obj = json_value.as_object().expect("Should be an object"); + + // Should have exactly 2 keys at the top level + assert_eq!(obj.len(), 2, "Cache control should have exactly 2 top-level fields"); + + // Both 'type' and 'ttl' should be at the same level + assert!(obj.contains_key("type"), "Should have 'type' field"); + assert!(obj.contains_key("ttl"), "Should have 'ttl' field"); + + // 'type' should be a string, not an object + assert!(obj["type"].is_string(), "'type' should be a string value"); + + // 'ttl' should be a string, not nested + assert!(obj["ttl"].is_string(), "'ttl' should be a string value"); +} + +#[test] +fn test_ephemeral_cache_control_structure() { + let cache_control = CacheControl::ephemeral(); + let json_value = serde_json::to_value(&cache_control).unwrap(); + + println!("Ephemeral cache control as JSON value: {}", serde_json::to_string_pretty(&json_value).unwrap()); + + let obj = json_value.as_object().expect("Should be an object"); + + // Should have exactly 1 key (only 'type', no 'ttl') + assert_eq!(obj.len(), 1, "Ephemeral cache control should have exactly 1 top-level field"); + + // Should have 'type' field + assert!(obj.contains_key("type"), "Should have 'type' field"); + + // Should NOT have 'ttl' field + assert!(!obj.contains_key("ttl"), "Ephemeral should not have 'ttl' field"); + + // 'type' should be a string with value "ephemeral" + assert_eq!(obj["type"].as_str().unwrap(), "ephemeral"); +} diff --git a/crates/g3-providers/tests/cache_control_integration_test.rs b/crates/g3-providers/tests/cache_control_integration_test.rs new file mode 100644 index 0000000..5ec365c --- /dev/null +++ b/crates/g3-providers/tests/cache_control_integration_test.rs @@ -0,0 +1,164 @@ +//! Integration tests for cache_control feature +//! +//! These tests verify that cache_control is correctly serialized in messages +//! for both Anthropic and Databricks providers. + +use g3_providers::{CacheControl, Message, MessageRole}; +use serde_json::json; + +#[test] +fn test_ephemeral_cache_control_serialization() { + let cache_control = CacheControl::ephemeral(); + let json = serde_json::to_value(&cache_control).unwrap(); + + println!("Ephemeral cache_control JSON: {}", serde_json::to_string(&json).unwrap()); + + assert_eq!(json, json!({ + "type": "ephemeral" + })); + + // Verify no ttl field is present + assert!(!json.as_object().unwrap().contains_key("ttl")); +} + +#[test] +fn test_five_minute_cache_control_serialization() { + let cache_control = CacheControl::five_minute(); + let json = serde_json::to_value(&cache_control).unwrap(); + + println!("5-minute cache_control JSON: {}", serde_json::to_string(&json).unwrap()); + + assert_eq!(json, json!({ + "type": "ephemeral", + "ttl": "5m" + })); +} + +#[test] +fn test_one_hour_cache_control_serialization() { + let cache_control = CacheControl::one_hour(); + let json = serde_json::to_value(&cache_control).unwrap(); + + println!("1-hour cache_control JSON: {}", serde_json::to_string(&json).unwrap()); + + assert_eq!(json, json!({ + "type": "ephemeral", + "ttl": "1h" + })); +} + +#[test] +fn test_message_with_ephemeral_cache_control() { + let msg = Message::with_cache_control( + MessageRole::System, + "System prompt".to_string(), + CacheControl::ephemeral(), + ); + + let json = serde_json::to_value(&msg).unwrap(); + println!("Message with ephemeral cache_control: {}", serde_json::to_string(&json).unwrap()); + + let cache_control = json.get("cache_control").expect("cache_control field should exist"); + assert_eq!(cache_control.get("type").unwrap(), "ephemeral"); + assert!(!cache_control.as_object().unwrap().contains_key("ttl")); +} + +#[test] +fn test_message_with_five_minute_cache_control() { + let msg = Message::with_cache_control( + MessageRole::System, + "System prompt".to_string(), + CacheControl::five_minute(), + ); + + let json = serde_json::to_value(&msg).unwrap(); + println!("Message with 5-minute cache_control: {}", serde_json::to_string(&json).unwrap()); + + let cache_control = json.get("cache_control").expect("cache_control field should exist"); + assert_eq!(cache_control.get("type").unwrap(), "ephemeral"); + assert_eq!(cache_control.get("ttl").unwrap(), "5m"); +} + +#[test] +fn test_message_with_one_hour_cache_control() { + let msg = Message::with_cache_control( + MessageRole::System, + "System prompt".to_string(), + CacheControl::one_hour(), + ); + + let json = serde_json::to_value(&msg).unwrap(); + println!("Message with 1-hour cache_control: {}", serde_json::to_string(&json).unwrap()); + + let cache_control = json.get("cache_control").expect("cache_control field should exist"); + assert_eq!(cache_control.get("type").unwrap(), "ephemeral"); + assert_eq!(cache_control.get("ttl").unwrap(), "1h"); +} + +#[test] +fn test_message_without_cache_control() { + let msg = Message::new(MessageRole::User, "Hello".to_string()); + + let json = serde_json::to_value(&msg).unwrap(); + println!("Message without cache_control: {}", serde_json::to_string(&json).unwrap()); + + // cache_control field should not be present when not set + assert!(!json.as_object().unwrap().contains_key("cache_control")); +} + +#[test] +fn test_cache_control_json_format_ephemeral() { + let cache_control = CacheControl::ephemeral(); + let json_str = serde_json::to_string(&cache_control).unwrap(); + + println!("Ephemeral JSON string: {}", json_str); + + // Verify exact JSON format + assert_eq!(json_str, r#"{"type":"ephemeral"}"#); +} + +#[test] +fn test_cache_control_json_format_five_minute() { + let cache_control = CacheControl::five_minute(); + let json_str = serde_json::to_string(&cache_control).unwrap(); + + println!("5-minute JSON string: {}", json_str); + + // Verify exact JSON format + assert_eq!(json_str, r#"{"type":"ephemeral","ttl":"5m"}"#); +} + +#[test] +fn test_cache_control_json_format_one_hour() { + let cache_control = CacheControl::one_hour(); + let json_str = serde_json::to_string(&cache_control).unwrap(); + + println!("1-hour JSON string: {}", json_str); + + // Verify exact JSON format + assert_eq!(json_str, r#"{"type":"ephemeral","ttl":"1h"}"#); +} + +#[test] +fn test_deserialization_ephemeral() { + let json_str = r#"{"type":"ephemeral"}"#; + let cache_control: CacheControl = serde_json::from_str(json_str).unwrap(); + + assert_eq!(cache_control.ttl, None); +} + +#[test] +fn test_deserialization_five_minute() { + let json_str = r#"{"type":"ephemeral","ttl":"5m"}"#; + let cache_control: CacheControl = serde_json::from_str(json_str).unwrap(); + + assert_eq!(cache_control.ttl, Some("5m".to_string())); +} + +#[test] +fn test_deserialization_one_hour() { + let json_str = r#"{"type":"ephemeral","ttl":"1h"}"#; + let cache_control: CacheControl = serde_json::from_str(json_str).unwrap(); + + assert_eq!(cache_control.ttl, Some("1h".to_string())); +}