Add documentation for retry configuration in planning mode: - Document retry settings in .g3.toml under [agent] section - Note RetryConfig implementation in g3-core/src/retry.rs - Clarify hardcoded vs config-based retry values Verify existing retry loop and coach feedback parsing: - Confirm execute_with_retry() handles recoverable errors - Document feedback extraction source priority order - Provide manual verification steps for testing
209 lines
6.2 KiB
Rust
209 lines
6.2 KiB
Rust
//! Integration tests for retry logic and feedback extraction in planning mode
|
|
//!
|
|
//! These tests verify that the retry infrastructure and coach feedback extraction
|
|
//! work correctly together, without requiring actual API calls.
|
|
|
|
use g3_core::feedback_extraction::{ExtractedFeedback, FeedbackExtractionConfig, FeedbackSource};
|
|
use g3_core::retry::RetryConfig;
|
|
|
|
#[test]
|
|
fn test_retry_config_for_planning_player() {
|
|
let config = RetryConfig::planning("player");
|
|
assert_eq!(config.max_retries, 3);
|
|
assert!(config.is_autonomous);
|
|
assert_eq!(config.role_name, "player");
|
|
}
|
|
|
|
#[test]
|
|
fn test_retry_config_for_planning_coach() {
|
|
let config = RetryConfig::planning("coach");
|
|
assert_eq!(config.max_retries, 3);
|
|
assert!(config.is_autonomous);
|
|
assert_eq!(config.role_name, "coach");
|
|
}
|
|
|
|
#[test]
|
|
fn test_retry_config_with_custom_max_retries() {
|
|
let config = RetryConfig::planning("player").with_max_retries(6);
|
|
assert_eq!(config.max_retries, 6);
|
|
assert!(config.is_autonomous);
|
|
assert_eq!(config.role_name, "player");
|
|
}
|
|
|
|
#[test]
|
|
fn test_retry_config_default() {
|
|
let config = RetryConfig::default();
|
|
assert_eq!(config.max_retries, 3);
|
|
assert!(!config.is_autonomous);
|
|
assert_eq!(config.role_name, "agent");
|
|
}
|
|
|
|
#[test]
|
|
fn test_retry_config_player_preset() {
|
|
let config = RetryConfig::player();
|
|
assert_eq!(config.max_retries, 3);
|
|
assert!(config.is_autonomous);
|
|
assert_eq!(config.role_name, "player");
|
|
}
|
|
|
|
#[test]
|
|
fn test_retry_config_coach_preset() {
|
|
let config = RetryConfig::coach();
|
|
assert_eq!(config.max_retries, 3);
|
|
assert!(config.is_autonomous);
|
|
assert_eq!(config.role_name, "coach");
|
|
}
|
|
|
|
#[test]
|
|
fn test_extracted_feedback_approval_detection() {
|
|
let approved = ExtractedFeedback::new(
|
|
"Great work! IMPLEMENTATION_APPROVED".to_string(),
|
|
FeedbackSource::NativeToolCall,
|
|
);
|
|
assert!(approved.is_approved());
|
|
assert!(!approved.is_fallback());
|
|
|
|
let not_approved = ExtractedFeedback::new(
|
|
"Please fix the issues".to_string(),
|
|
FeedbackSource::NativeToolCall,
|
|
);
|
|
assert!(!not_approved.is_approved());
|
|
assert!(!not_approved.is_fallback());
|
|
|
|
let fallback = ExtractedFeedback::new(
|
|
"Default feedback".to_string(),
|
|
FeedbackSource::DefaultFallback,
|
|
);
|
|
assert!(!fallback.is_approved());
|
|
assert!(fallback.is_fallback());
|
|
}
|
|
|
|
#[test]
|
|
fn test_feedback_extraction_config_default() {
|
|
let config = FeedbackExtractionConfig::default();
|
|
assert!(!config.verbose);
|
|
assert!(config.logs_dir.is_none());
|
|
assert!(config.default_feedback.contains("review"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_feedback_extraction_config_custom() {
|
|
let config = FeedbackExtractionConfig {
|
|
verbose: true,
|
|
logs_dir: Some(std::path::PathBuf::from("/tmp/test_logs")),
|
|
default_feedback: "Custom fallback message for testing".to_string(),
|
|
};
|
|
assert!(config.verbose);
|
|
assert_eq!(
|
|
config.logs_dir,
|
|
Some(std::path::PathBuf::from("/tmp/test_logs"))
|
|
);
|
|
assert!(config.default_feedback.contains("Custom fallback"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_feedback_source_variants() {
|
|
// Verify all feedback sources are distinguishable
|
|
let sources = vec![
|
|
FeedbackSource::SessionLog,
|
|
FeedbackSource::NativeToolCall,
|
|
FeedbackSource::ConversationHistory,
|
|
FeedbackSource::TaskResultResponse,
|
|
FeedbackSource::DefaultFallback,
|
|
];
|
|
|
|
for (i, source1) in sources.iter().enumerate() {
|
|
for (j, source2) in sources.iter().enumerate() {
|
|
if i == j {
|
|
assert_eq!(source1, source2);
|
|
} else {
|
|
assert_ne!(source1, source2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_retry_configs_for_planning_mode_are_autonomous() {
|
|
// Both player and coach should be marked as autonomous for planning mode
|
|
let player = RetryConfig::planning("player");
|
|
let coach = RetryConfig::planning("coach");
|
|
|
|
assert!(
|
|
player.is_autonomous,
|
|
"Player should be autonomous in planning mode"
|
|
);
|
|
assert!(
|
|
coach.is_autonomous,
|
|
"Coach should be autonomous in planning mode"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_extracted_feedback_new() {
|
|
let feedback = ExtractedFeedback::new(
|
|
"Test content".to_string(),
|
|
FeedbackSource::SessionLog,
|
|
);
|
|
assert_eq!(feedback.content, "Test content");
|
|
assert_eq!(feedback.source, FeedbackSource::SessionLog);
|
|
}
|
|
|
|
#[test]
|
|
fn test_extracted_feedback_approval_variations() {
|
|
// Test various approval message formats
|
|
let cases = vec![
|
|
("IMPLEMENTATION_APPROVED", true),
|
|
("IMPLEMENTATION_APPROVED - great work!", true),
|
|
("All done. IMPLEMENTATION_APPROVED", true),
|
|
("implementation_approved", false), // Case sensitive
|
|
("APPROVED", false), // Must be exact phrase
|
|
("Please fix these issues", false),
|
|
("", false),
|
|
];
|
|
|
|
for (content, expected_approved) in cases {
|
|
let feedback = ExtractedFeedback::new(content.to_string(), FeedbackSource::SessionLog);
|
|
assert_eq!(
|
|
feedback.is_approved(),
|
|
expected_approved,
|
|
"Failed for content: '{}'",
|
|
content
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_feedback_source_fallback_detection() {
|
|
// Only DefaultFallback should be detected as fallback
|
|
let sources_and_expected = vec![
|
|
(FeedbackSource::SessionLog, false),
|
|
(FeedbackSource::NativeToolCall, false),
|
|
(FeedbackSource::ConversationHistory, false),
|
|
(FeedbackSource::TaskResultResponse, false),
|
|
(FeedbackSource::DefaultFallback, true),
|
|
];
|
|
|
|
for (source, expected_is_fallback) in sources_and_expected {
|
|
let feedback = ExtractedFeedback::new("Test".to_string(), source.clone());
|
|
assert_eq!(
|
|
feedback.is_fallback(),
|
|
expected_is_fallback,
|
|
"Failed for source: {:?}",
|
|
source
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_retry_config_chaining() {
|
|
// Test that with_max_retries can be chained
|
|
let config = RetryConfig::planning("player")
|
|
.with_max_retries(10)
|
|
.with_max_retries(5);
|
|
|
|
assert_eq!(config.max_retries, 5);
|
|
assert!(config.is_autonomous);
|
|
assert_eq!(config.role_name, "player");
|
|
}
|