chore: turn clippy on for test code (#4817)

This commit is contained in:
Jack Amadeo
2025-09-26 00:06:07 -04:00
committed by GitHub
parent f5877a9996
commit 757ceb6109
38 changed files with 660 additions and 849 deletions
+1 -1
View File
@@ -192,7 +192,7 @@ response:
let url = result.unwrap();
assert!(url.starts_with("goose://recipe?config="));
let encoded_part = url.strip_prefix("goose://recipe?config=").unwrap();
assert!(encoded_part.len() > 0);
assert!(!encoded_part.is_empty());
}
#[test]
+6 -6
View File
@@ -248,7 +248,7 @@ mod tests {
);
// Create a proper temporary directory that will be automatically cleaned up
let _temp_dir = TempDir::with_prefix(&format!("goose_test_{}_", test_id)).unwrap();
let _temp_dir = TempDir::with_prefix(format!("goose_test_{}_", test_id)).unwrap();
let test_dir = _temp_dir.path();
// Set up environment
@@ -275,7 +275,7 @@ mod tests {
if log_dir.exists() {
for entry in std::fs::read_dir(&log_dir).unwrap() {
let entry = entry.unwrap();
if entry.path().extension().map_or(false, |ext| ext == "log") {
if entry.path().extension().is_some_and(|ext| ext == "log") {
std::fs::remove_file(entry.path()).unwrap();
}
}
@@ -315,7 +315,7 @@ mod tests {
let log_count = all_files
.iter()
.filter(|e| e.path().extension().map_or(false, |ext| ext == "log"))
.filter(|e| e.path().extension().is_some_and(|ext| ext == "log"))
.count();
println!("Found {} log files in directory", log_count);
@@ -358,8 +358,8 @@ mod tests {
.iter()
.filter(|e| {
let path = e.path();
let matches = path.extension().map_or(false, |ext| ext == "log")
&& path.file_name().map_or(false, |name| {
let matches = path.extension().is_some_and(|ext| ext == "log")
&& path.file_name().is_some_and(|name| {
let name = name.to_string_lossy();
if let Some(session) = session_name {
name.ends_with(&format!("{}.log", session))
@@ -411,7 +411,7 @@ mod tests {
})
.collect();
log_files.sort_by(|a, b| a.file_name().cmp(&b.file_name()));
log_files.sort_by_key(|a| a.file_name());
assert_eq!(log_files.len(), 1, "Expected exactly one matching log file");
let log_file_name = log_files[0].file_name().to_string_lossy().into_owned();
@@ -14,9 +14,11 @@ use std::collections::HashMap;
use tokio::sync::mpsc::{self, Receiver};
use tokio_util::sync::CancellationToken;
type Handler = Box<dyn Fn(&Value) -> Result<Vec<Content>, ErrorData> + Send + Sync>;
pub struct MockClient {
tools: HashMap<String, Tool>,
handlers: HashMap<String, Box<dyn Fn(&Value) -> Result<Vec<Content>, ErrorData> + Send + Sync>>,
handlers: HashMap<String, Handler>,
}
impl MockClient {
@@ -180,7 +180,7 @@ where
let original_env = setup_environment(config)?;
let inner_provider = create(&factory_name, ModelConfig::new(&config.model_name)?)?;
let inner_provider = create(&factory_name, ModelConfig::new(config.model_name)?)?;
let test_provider = Arc::new(TestProvider::new_recording(inner_provider, &file_path));
(
+6 -10
View File
@@ -443,18 +443,14 @@ mod tests {
let mut cache = CompletionCache::new();
// Add some test prompts
let mut extension1_prompts = Vec::new();
extension1_prompts.push("test_prompt1".to_string());
extension1_prompts.push("test_prompt2".to_string());
cache
.prompts
.insert("extension1".to_string(), extension1_prompts);
cache.prompts.insert(
"extension1".to_string(),
vec!["test_prompt1".to_string(), "test_prompt2".to_string()],
);
let mut extension2_prompts = Vec::new();
extension2_prompts.push("other_prompt".to_string());
cache
.prompts
.insert("extension2".to_string(), extension2_prompts);
.insert("extension2".to_string(), vec!["other_prompt".to_string()]);
// Add prompt info with arguments
let test_prompt1_args = vec![
@@ -519,7 +515,7 @@ mod tests {
let (pos, candidates) = completer.complete_slash_commands("/e").unwrap();
assert_eq!(pos, 0);
// There might be multiple commands starting with "e" like "/exit" and "/extension"
assert!(candidates.len() >= 1);
assert!(!candidates.is_empty());
// Test multiple matches
let (pos, candidates) = completer.complete_slash_commands("/").unwrap();
@@ -34,7 +34,7 @@ fn test_format_result_data_for_display() {
assert_eq!(format_result_data_for_display(&json!(true)), "true");
assert_eq!(format_result_data_for_display(&json!(false)), "false");
assert_eq!(format_result_data_for_display(&json!(42)), "42");
assert_eq!(format_result_data_for_display(&json!(3.14)), "3.14");
assert_eq!(format_result_data_for_display(&json!(3.41)), "3.41");
assert_eq!(format_result_data_for_display(&json!(null)), "null");
let partial_obj = json!({
@@ -134,7 +134,7 @@ fn test_symlink_handling() {
#[cfg(unix)]
{
use std::os::unix::fs::symlink;
let _ = symlink(&dir_path.join("target.rs"), dir_path.join("link.rs"));
let _ = symlink(dir_path.join("target.rs"), dir_path.join("link.rs"));
let _ = symlink(&target_dir, dir_path.join("link_dir"));
}
@@ -2970,7 +2970,7 @@ mod tests {
let temp_path = temp_dir.path();
// Set the current directory before creating the server
std::env::set_current_dir(&temp_path).unwrap();
std::env::set_current_dir(temp_path).unwrap();
// Create some test files and directories
fs::create_dir(temp_path.join("subdir1")).unwrap();
@@ -3029,7 +3029,7 @@ mod tests {
let temp_path = temp_dir.path();
// Set the current directory before creating the server
std::env::set_current_dir(&temp_path).unwrap();
std::env::set_current_dir(temp_path).unwrap();
// Create more than 50 files to test the limit
for i in 0..60 {
@@ -3085,7 +3085,7 @@ mod tests {
let temp_path = temp_dir.path();
// Set the current directory before creating the server
std::env::set_current_dir(&temp_path).unwrap();
std::env::set_current_dir(temp_path).unwrap();
let server = create_test_server();
@@ -3666,7 +3666,7 @@ Additional instructions here.
// Cancel the command
let cancel_params = CancelledNotificationParam {
request_id: request_id,
request_id,
reason: Some("test cancellation".to_string()),
};
@@ -3748,7 +3748,7 @@ Additional instructions here.
// Cancel the command
let cancel_params = CancelledNotificationParam {
request_id: request_id,
request_id,
reason: Some("test cancellation".to_string()),
};
-2
View File
@@ -391,7 +391,6 @@ pub fn routes(state: Arc<AppState>) -> Router {
mod tests {
use super::*;
use axum::{body::Body, http::Request};
use serde_json::json;
use tower::ServiceExt;
#[tokio::test(flavor = "multi_thread")]
@@ -424,7 +423,6 @@ mod tests {
let state = AppState::new().await.unwrap();
let app = routes(state);
let large_data = "a".repeat(30 * 1024 * 1024); // 30MB
let request = Request::builder()
.uri("/audio/transcribe")
.method("POST")
+6 -12
View File
@@ -631,25 +631,19 @@ mod tests {
#[test]
fn test_make_full() {
assert_eq!(
make_full_cmd("uvx", &vec!["mcp_slack".to_string()]),
make_full_cmd("uvx", &["mcp_slack".to_string()]),
"uvx mcp_slack"
);
assert_eq!(
make_full_cmd("uvx", &vec!["mcp_slack ".to_string()]),
make_full_cmd("uvx", &["mcp_slack ".to_string()]),
"uvx mcp_slack"
);
assert_eq!(
make_full_cmd(
"uvx",
&vec!["mcp_slack".to_string(), "--verbose".to_string()]
),
make_full_cmd("uvx", &["mcp_slack".to_string(), "--verbose".to_string()]),
"uvx mcp_slack --verbose"
);
assert_eq!(
make_full_cmd(
"uvx",
&vec!["mcp_slack".to_string(), " --verbose".to_string()]
),
make_full_cmd("uvx", &["mcp_slack".to_string(), " --verbose".to_string()]),
"uvx mcp_slack --verbose"
);
}
@@ -1093,14 +1087,14 @@ mod tests {
// With bypass enabled, any command should be allowed regardless of allowlist
assert!(is_command_allowed(
"uvx",
&vec!["unauthorized_command".to_string()]
&["unauthorized_command".to_string()]
));
// Test case insensitivity
env::set_var("GOOSE_ALLOWLIST_BYPASS", "TRUE");
assert!(is_command_allowed(
"uvx",
&vec!["unauthorized_command".to_string()]
&["unauthorized_command".to_string()]
));
// Clean up
@@ -129,6 +129,6 @@ recipe: recipe_content
let result = RecipeManifestMetadata::from_yaml_file(&file_path).unwrap();
assert_eq!(result.name, "Test Recipe");
assert_eq!(result.is_global, true);
assert!(result.is_global);
}
}
-43
View File
@@ -557,58 +557,15 @@ pub fn routes(state: Arc<AppState>) -> Router {
#[cfg(test)]
mod tests {
use super::*;
use goose::conversation::message::Message;
use goose::{
model::ModelConfig,
providers::{
base::{Provider, ProviderUsage, Usage},
errors::ProviderError,
},
};
#[derive(Clone)]
struct MockProvider {
model_config: ModelConfig,
}
#[async_trait::async_trait]
impl Provider for MockProvider {
fn metadata() -> goose::providers::base::ProviderMetadata {
goose::providers::base::ProviderMetadata::empty()
}
async fn complete_with_model(
&self,
_model_config: &ModelConfig,
_system: &str,
_messages: &[Message],
_tools: &[rmcp::model::Tool],
) -> anyhow::Result<(Message, ProviderUsage), ProviderError> {
Ok((
Message::assistant().with_text("Mock response"),
ProviderUsage::new("mock".to_string(), Usage::default()),
))
}
fn get_model_config(&self) -> ModelConfig {
self.model_config.clone()
}
}
mod integration_tests {
use super::*;
use axum::{body::Body, http::Request};
use goose::conversation::message::Message;
use serde_json::json;
use tower::ServiceExt;
#[tokio::test(flavor = "multi_thread")]
async fn test_reply_endpoint() {
let mock_model_config = ModelConfig::new("test-model").unwrap();
let mock_provider = MockProvider {
model_config: mock_model_config,
};
let state = AppState::new().await.unwrap();
let app = routes(state);
+2 -2
View File
@@ -31,9 +31,9 @@ async fn main() -> Result<()> {
let mut usage = Usage::default();
while let Some(Ok((msg, usage_part))) = stream.next().await {
dbg!(msg);
usage_part.map(|u| {
if let Some(u) = usage_part {
usage += u.usage;
});
}
}
println!("\nToken Usage:");
println!("------------");
@@ -6,14 +6,13 @@ use serde_json::json;
use crate::agents::recipe_tools::param_utils::prepare_command_params;
fn setup_default_sub_recipe() -> SubRecipe {
let sub_recipe = SubRecipe {
SubRecipe {
name: "test_sub_recipe".to_string(),
path: "test_sub_recipe.yaml".to_string(),
values: Some(HashMap::from([("key1".to_string(), "value1".to_string())])),
sequential_when_repeated: true,
description: Some("Test subrecipe".to_string()),
};
sub_recipe
}
}
mod prepare_command_params_tests {
@@ -1,51 +1,48 @@
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use std::collections::HashMap;
use crate::recipe::SubRecipe;
use serde_json::json;
use serde_json::Value;
use tempfile::TempDir;
use crate::recipe::SubRecipe;
use serde_json::json;
use serde_json::Value;
use tempfile::TempDir;
fn setup_default_sub_recipe() -> SubRecipe {
let sub_recipe = SubRecipe {
name: "test_sub_recipe".to_string(),
path: "test_sub_recipe.yaml".to_string(),
values: Some(HashMap::from([("key1".to_string(), "value1".to_string())])),
sequential_when_repeated: true,
description: Some("Test subrecipe".to_string()),
};
sub_recipe
fn setup_default_sub_recipe() -> SubRecipe {
SubRecipe {
name: "test_sub_recipe".to_string(),
path: "test_sub_recipe.yaml".to_string(),
values: Some(HashMap::from([("key1".to_string(), "value1".to_string())])),
sequential_when_repeated: true,
description: Some("Test subrecipe".to_string()),
}
}
mod get_input_schema {
use super::*;
use crate::agents::recipe_tools::sub_recipe_tools::get_input_schema;
fn prepare_sub_recipe(sub_recipe_file_content: &str) -> (SubRecipe, TempDir) {
let mut sub_recipe = setup_default_sub_recipe();
let temp_dir = tempfile::tempdir().unwrap();
let temp_file = temp_dir.path().join(sub_recipe.path.clone());
std::fs::write(&temp_file, sub_recipe_file_content).unwrap();
sub_recipe.path = temp_file.to_string_lossy().to_string();
(sub_recipe, temp_dir)
}
mod get_input_schema {
use super::*;
use crate::agents::recipe_tools::sub_recipe_tools::get_input_schema;
fn verify_task_parameters(result: Value, expected_task_parameters_items: Value) {
let task_parameters = result
.get("properties")
.unwrap()
.as_object()
.unwrap()
.get("task_parameters")
.unwrap()
.as_object()
.unwrap();
let task_parameters_items = task_parameters.get("items").unwrap();
assert_eq!(&expected_task_parameters_items, task_parameters_items);
}
fn prepare_sub_recipe(sub_recipe_file_content: &str) -> (SubRecipe, TempDir) {
let mut sub_recipe = setup_default_sub_recipe();
let temp_dir = tempfile::tempdir().unwrap();
let temp_file = temp_dir.path().join(sub_recipe.path.clone());
std::fs::write(&temp_file, sub_recipe_file_content).unwrap();
sub_recipe.path = temp_file.to_string_lossy().to_string();
(sub_recipe, temp_dir)
}
fn verify_task_parameters(result: Value, expected_task_parameters_items: Value) {
let task_parameters = result
.get("properties")
.unwrap()
.as_object()
.unwrap()
.get("task_parameters")
.unwrap()
.as_object()
.unwrap();
let task_parameters_items = task_parameters.get("items").unwrap();
assert_eq!(&expected_task_parameters_items, task_parameters_items);
}
const SUB_RECIPE_FILE_CONTENT_WITH_TWO_PARAMS: &str = r#"{
const SUB_RECIPE_FILE_CONTENT_WITH_TWO_PARAMS: &str = r#"{
"version": "1.0.0",
"title": "Test Recipe",
"description": "A test recipe",
@@ -66,67 +63,66 @@ mod tests {
]
}"#;
#[test]
fn test_with_one_param_in_tool_input() {
let (mut sub_recipe, _temp_dir) =
prepare_sub_recipe(SUB_RECIPE_FILE_CONTENT_WITH_TWO_PARAMS);
sub_recipe.values = Some(HashMap::from([("key1".to_string(), "value1".to_string())]));
#[test]
fn test_with_one_param_in_tool_input() {
let (mut sub_recipe, _temp_dir) =
prepare_sub_recipe(SUB_RECIPE_FILE_CONTENT_WITH_TWO_PARAMS);
sub_recipe.values = Some(HashMap::from([("key1".to_string(), "value1".to_string())]));
let result = get_input_schema(&sub_recipe).unwrap();
let result = get_input_schema(&sub_recipe).unwrap();
verify_task_parameters(
result,
json!({
"type": "object",
"properties": {
"key2": { "type": "number", "description": "An optional parameter" }
},
"required": []
}),
);
}
verify_task_parameters(
result,
json!({
"type": "object",
"properties": {
"key2": { "type": "number", "description": "An optional parameter" }
},
"required": []
}),
);
}
#[test]
fn test_without_param_in_tool_input() {
let (mut sub_recipe, _temp_dir) =
prepare_sub_recipe(SUB_RECIPE_FILE_CONTENT_WITH_TWO_PARAMS);
sub_recipe.values = Some(HashMap::from([
("key1".to_string(), "value1".to_string()),
("key2".to_string(), "value2".to_string()),
]));
#[test]
fn test_without_param_in_tool_input() {
let (mut sub_recipe, _temp_dir) =
prepare_sub_recipe(SUB_RECIPE_FILE_CONTENT_WITH_TWO_PARAMS);
sub_recipe.values = Some(HashMap::from([
("key1".to_string(), "value1".to_string()),
("key2".to_string(), "value2".to_string()),
]));
let result = get_input_schema(&sub_recipe).unwrap();
let result = get_input_schema(&sub_recipe).unwrap();
assert_eq!(
None,
result
.get("properties")
.unwrap()
.as_object()
.unwrap()
.get("task_parameters")
);
}
assert_eq!(
None,
result
.get("properties")
.unwrap()
.as_object()
.unwrap()
.get("task_parameters")
);
}
#[test]
fn test_with_all_params_in_tool_input() {
let (mut sub_recipe, _temp_dir) =
prepare_sub_recipe(SUB_RECIPE_FILE_CONTENT_WITH_TWO_PARAMS);
sub_recipe.values = None;
#[test]
fn test_with_all_params_in_tool_input() {
let (mut sub_recipe, _temp_dir) =
prepare_sub_recipe(SUB_RECIPE_FILE_CONTENT_WITH_TWO_PARAMS);
sub_recipe.values = None;
let result = get_input_schema(&sub_recipe).unwrap();
let result = get_input_schema(&sub_recipe).unwrap();
verify_task_parameters(
result,
json!({
"type": "object",
"properties": {
"key1": { "type": "string", "description": "A test parameter" },
"key2": { "type": "number", "description": "An optional parameter" }
},
"required": ["key1"]
}),
);
}
verify_task_parameters(
result,
json!({
"type": "object",
"properties": {
"key1": { "type": "string", "description": "A test parameter" },
"key2": { "type": "number", "description": "An optional parameter" }
},
"required": ["key1"]
}),
);
}
}
+7 -7
View File
@@ -1024,7 +1024,7 @@ mod tests {
let key = format!("key{}", i);
let value = format!("value{}", i);
assert!(
final_values.get(&key).is_some(),
final_values.contains_key(&key),
"Missing key {} in final values",
key
);
@@ -1075,7 +1075,7 @@ mod tests {
// Should have recovered the data
assert!(
recovered_values.len() >= 1,
!recovered_values.is_empty(),
"Should have recovered at least one key"
);
@@ -1169,7 +1169,7 @@ mod tests {
// Should have recovered the data from backup
assert!(
recovered_values.len() >= 1,
!recovered_values.is_empty(),
"Should have recovered data from backup"
);
@@ -1260,10 +1260,10 @@ mod tests {
assert_eq!(value, Value::Number((-123).into()));
// Test floats
let value = Config::parse_env_value("3.14")?;
let value = Config::parse_env_value("3.41")?;
assert!(matches!(value, Value::Number(_)));
if let Value::Number(n) = value {
assert_eq!(n.as_f64().unwrap(), 3.14);
assert_eq!(n.as_f64().unwrap(), 3.41);
}
let value = Config::parse_env_value("0.01")?;
@@ -1421,7 +1421,7 @@ mod tests {
// Test boolean environment variable
std::env::set_var("ENABLED", "true");
let value: bool = config.get_param("enabled")?;
assert_eq!(value, true);
assert!(value);
// Test JSON object environment variable
std::env::set_var("CONFIG", "{\"debug\": true, \"level\": 5}");
@@ -1431,7 +1431,7 @@ mod tests {
level: i32,
}
let value: TestConfig = config.get_param("config")?;
assert_eq!(value.debug, true);
assert!(value.debug);
assert_eq!(value.level, 5);
// Clean up
@@ -1,68 +1,65 @@
#[cfg(test)]
mod tests {
use crate::config::signup_openrouter::PkceAuthFlow;
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
use sha2::{Digest, Sha256};
use crate::config::signup_openrouter::PkceAuthFlow;
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
use sha2::{Digest, Sha256};
#[test]
fn test_pkce_flow_creation() {
let flow = PkceAuthFlow::new().expect("Failed to create PKCE flow");
#[test]
fn test_pkce_flow_creation() {
let flow = PkceAuthFlow::new().expect("Failed to create PKCE flow");
// Verify code_verifier is 128 characters
assert_eq!(flow.code_verifier.len(), 128);
// Verify code_verifier is 128 characters
assert_eq!(flow.code_verifier.len(), 128);
// Verify code_challenge is base64url encoded (no padding)
assert!(!flow.code_challenge.contains('='));
assert!(!flow.code_challenge.contains('+'));
assert!(!flow.code_challenge.contains('/'));
// Verify code_challenge is base64url encoded (no padding)
assert!(!flow.code_challenge.contains('='));
assert!(!flow.code_challenge.contains('+'));
assert!(!flow.code_challenge.contains('/'));
// Verify auth URL is properly formatted
let auth_url = flow.get_auth_url();
assert!(auth_url.starts_with("https://openrouter.ai/auth"));
assert!(auth_url.contains("callback_url=http%3A%2F%2Flocalhost%3A3000"));
assert!(auth_url.contains(&format!("code_challenge={}", flow.code_challenge)));
assert!(auth_url.contains("code_challenge_method=S256"));
}
#[test]
fn test_different_flows_have_different_verifiers() {
let flow1 = PkceAuthFlow::new().expect("Failed to create PKCE flow 1");
let flow2 = PkceAuthFlow::new().expect("Failed to create PKCE flow 2");
// Verify that different flows have different verifiers and challenges
assert_ne!(flow1.code_verifier, flow2.code_verifier);
assert_ne!(flow1.code_challenge, flow2.code_challenge);
}
#[test]
fn test_code_verifier_is_alphanumeric() {
let flow = PkceAuthFlow::new().expect("Failed to create PKCE flow");
// Verify all characters in code_verifier are alphanumeric
assert!(flow.code_verifier.chars().all(|c| c.is_alphanumeric()));
}
#[test]
fn test_code_challenge_matches_verifier() {
let flow = PkceAuthFlow::new().expect("Failed to create PKCE flow");
// Manually compute the expected challenge
let mut hasher = Sha256::new();
hasher.update(&flow.code_verifier);
let hash = hasher.finalize();
let expected_challenge = URL_SAFE_NO_PAD.encode(hash);
// Verify the challenge matches
assert_eq!(flow.code_challenge, expected_challenge);
}
#[test]
fn test_pkce_verifier_length_bounds() {
// PKCE spec requires verifier to be 43-128 characters
// Our implementation uses 128 characters
let flow = PkceAuthFlow::new().expect("Failed to create PKCE flow");
assert!(flow.code_verifier.len() >= 43);
assert!(flow.code_verifier.len() <= 128);
}
// Verify auth URL is properly formatted
let auth_url = flow.get_auth_url();
assert!(auth_url.starts_with("https://openrouter.ai/auth"));
assert!(auth_url.contains("callback_url=http%3A%2F%2Flocalhost%3A3000"));
assert!(auth_url.contains(&format!("code_challenge={}", flow.code_challenge)));
assert!(auth_url.contains("code_challenge_method=S256"));
}
#[test]
fn test_different_flows_have_different_verifiers() {
let flow1 = PkceAuthFlow::new().expect("Failed to create PKCE flow 1");
let flow2 = PkceAuthFlow::new().expect("Failed to create PKCE flow 2");
// Verify that different flows have different verifiers and challenges
assert_ne!(flow1.code_verifier, flow2.code_verifier);
assert_ne!(flow1.code_challenge, flow2.code_challenge);
}
#[test]
fn test_code_verifier_is_alphanumeric() {
let flow = PkceAuthFlow::new().expect("Failed to create PKCE flow");
// Verify all characters in code_verifier are alphanumeric
assert!(flow.code_verifier.chars().all(|c| c.is_alphanumeric()));
}
#[test]
fn test_code_challenge_matches_verifier() {
let flow = PkceAuthFlow::new().expect("Failed to create PKCE flow");
// Manually compute the expected challenge
let mut hasher = Sha256::new();
hasher.update(&flow.code_verifier);
let hash = hasher.finalize();
let expected_challenge = URL_SAFE_NO_PAD.encode(hash);
// Verify the challenge matches
assert_eq!(flow.code_challenge, expected_challenge);
}
#[test]
fn test_pkce_verifier_length_bounds() {
// PKCE spec requires verifier to be 43-128 characters
// Our implementation uses 128 characters
let flow = PkceAuthFlow::new().expect("Failed to create PKCE flow");
assert!(flow.code_verifier.len() >= 43);
assert!(flow.code_verifier.len() <= 128);
}
+15 -6
View File
@@ -558,11 +558,14 @@ mod tests {
];
// Create session metadata with specific token counts
#[allow(clippy::field_reassign_with_default)]
let mut session_metadata = SessionMetadata::default();
session_metadata.total_tokens = Some(8000); // High token count to trigger compaction
session_metadata.accumulated_total_tokens = Some(15000); // Even higher accumulated count
session_metadata.input_tokens = Some(5000);
session_metadata.output_tokens = Some(3000);
{
session_metadata.total_tokens = Some(8000); // High token count to trigger compaction
session_metadata.accumulated_total_tokens = Some(15000); // Even higher accumulated count
session_metadata.input_tokens = Some(5000);
session_metadata.output_tokens = Some(3000);
}
// Test with session metadata - should use total_tokens for compaction (not accumulated)
let result_with_metadata = check_compaction_needed(
@@ -594,7 +597,10 @@ mod tests {
// Test with metadata that has only accumulated tokens (no total_tokens)
let mut session_metadata_no_total = SessionMetadata::default();
session_metadata_no_total.accumulated_total_tokens = Some(7500);
#[allow(clippy::field_reassign_with_default)]
{
session_metadata_no_total.accumulated_total_tokens = Some(7500);
}
let result_with_no_total = check_compaction_needed(
&agent,
@@ -650,7 +656,10 @@ mod tests {
// Create session metadata with high token count to trigger compaction
let mut session_metadata = SessionMetadata::default();
session_metadata.total_tokens = Some(9000); // High enough to trigger compaction
#[allow(clippy::field_reassign_with_default)]
{
session_metadata.total_tokens = Some(9000); // High enough to trigger compaction
}
// Test full compaction flow with session metadata
let result = check_and_compact_messages(
+5 -5
View File
@@ -506,7 +506,7 @@ mod tests {
let context_limit = 25;
let result = truncate_messages(
&messages.messages(),
messages.messages(),
&token_counts,
context_limit,
&OldestFirstTruncation,
@@ -590,7 +590,7 @@ mod tests {
let context_limit = 100; // Exactly matches total tokens
let result = truncate_messages(
&messages.messages(),
messages.messages(),
&token_counts,
context_limit,
&OldestFirstTruncation,
@@ -605,7 +605,7 @@ mod tests {
token_counts.push(1);
let result = truncate_messages(
&messages.messages(),
messages.messages(),
&token_counts,
context_limit,
&OldestFirstTruncation,
@@ -698,7 +698,7 @@ mod tests {
let (messages, token_counts) = result;
// Verify the conversation still makes sense
assert!(messages.len() >= 1);
assert!(!messages.is_empty());
assert!(messages.last().unwrap().role == Role::User);
assert!(token_counts.iter().sum::<usize>() <= context_limit);
@@ -710,7 +710,7 @@ mod tests {
// Test impossibly small context window
let (messages, token_counts) = create_messages_with_counts(1, 10, false);
let result = truncate_messages(
&messages.messages(),
messages.messages(),
&token_counts,
5, // Impossibly small context
&OldestFirstTruncation,
+1 -1
View File
@@ -379,7 +379,7 @@ mod tests {
0,
"Fixed conversation should have no issues, but found: {:?}\n\n{}",
issues_with_fixed,
debug_conversation_fix(&messages, &fixed.messages(), &issues)
debug_conversation_fix(&messages, fixed.messages(), &issues)
);
(fixed.messages().clone(), issues)
}
@@ -337,7 +337,7 @@ mod tests {
assert!(tool
.description
.as_ref()
.map_or(false, |desc| desc.contains("read-only operation")));
.is_some_and(|desc| desc.contains("read-only operation")));
}
#[test]
+1 -66
View File
@@ -30,11 +30,6 @@ use crate::model::ModelConfig;
use anyhow::Result;
use once_cell::sync::Lazy;
#[cfg(test)]
use super::errors::ProviderError;
#[cfg(test)]
use rmcp::model::Tool;
const DEFAULT_LEAD_TURNS: usize = 3;
const DEFAULT_FAILURE_THRESHOLD: usize = 2;
const DEFAULT_FALLBACK_TURNS: usize = 2;
@@ -174,63 +169,8 @@ fn create_worker_model_config(default_model: &ModelConfig) -> Result<ModelConfig
#[cfg(test)]
mod tests {
use super::*;
use crate::conversation::message::{Message, MessageContent};
use crate::providers::base::{ProviderMetadata, ProviderUsage, Usage};
use chrono::Utc;
use rmcp::model::{AnnotateAble, RawTextContent, Role};
use std::env;
#[derive(Clone)]
struct MockTestProvider {
name: String,
model_config: ModelConfig,
}
#[async_trait::async_trait]
impl Provider for MockTestProvider {
fn metadata() -> ProviderMetadata {
ProviderMetadata::new(
"mock_test",
"Mock Test Provider",
"A mock provider for testing",
"mock-model",
vec!["mock-model"],
"",
vec![],
)
}
fn get_model_config(&self) -> ModelConfig {
self.model_config.clone()
}
async fn complete_with_model(
&self,
_model_config: &ModelConfig,
_system: &str,
_messages: &[Message],
_tools: &[Tool],
) -> Result<(Message, ProviderUsage), ProviderError> {
Ok((
Message::new(
Role::Assistant,
Utc::now().timestamp(),
vec![MessageContent::Text(
RawTextContent {
text: format!(
"Response from {} with model {}",
self.name, self.model_config.model_name
),
meta: None,
}
.no_annotation(),
)],
),
ProviderUsage::new(self.model_config.model_name.clone(), Usage::default()),
))
}
}
struct EnvVarGuard {
vars: Vec<(String, Option<String>)>,
}
@@ -355,17 +295,12 @@ mod tests {
let default_model =
ModelConfig::new_or_fail("gpt-3.5-turbo").with_context_limit(Some(16_000));
let result = create_lead_worker_from_env("openai", &default_model, "gpt-4o");
let _result = create_lead_worker_from_env("openai", &default_model, "gpt-4o");
_guard.set("GOOSE_WORKER_CONTEXT_LIMIT", "32000");
let _result = create_lead_worker_from_env("openai", &default_model, "gpt-4o");
_guard.set("GOOSE_CONTEXT_LIMIT", "64000");
let _result = create_lead_worker_from_env("openai", &default_model, "gpt-4o");
match result {
Ok(_) => {}
Err(_) => {}
}
}
}
+4 -4
View File
@@ -545,7 +545,7 @@ mod tests {
0x0D, 0x0A, 0x1A, 0x0A, // PNG header
0x00, 0x00, 0x00, 0x0D, // Rest of fake PNG data
];
std::fs::write(&png_path, &png_data).unwrap();
std::fs::write(&png_path, png_data).unwrap();
let png_path_str = png_path.to_str().unwrap();
// Create a fake PNG (wrong magic numbers)
@@ -583,7 +583,7 @@ mod tests {
0x0D, 0x0A, 0x1A, 0x0A, // PNG header
0x00, 0x00, 0x00, 0x0D, // Rest of fake PNG data
];
std::fs::write(&png_path, &png_data).unwrap();
std::fs::write(&png_path, png_data).unwrap();
let png_path_str = png_path.to_str().unwrap();
// Create a fake PNG (wrong magic numbers)
@@ -613,7 +613,7 @@ mod tests {
let gif_path = temp_dir.path().join("test.gif");
// Minimal GIF89a header
let gif_data = [0x47, 0x49, 0x46, 0x38, 0x39, 0x61];
std::fs::write(&gif_path, &gif_data).unwrap();
std::fs::write(&gif_path, gif_data).unwrap();
let gif_path_str = gif_path.to_str().unwrap();
// Test loading unsupported GIF format
@@ -965,7 +965,7 @@ mod tests {
// Make request to mock server
let client = reqwest::Client::new();
let response = client
.get(&format!("{}/test", &mock_server.uri()))
.get(format!("{}/test", &mock_server.uri()))
.send()
.await
.unwrap();
+337 -343
View File
@@ -1,97 +1,96 @@
#[cfg(test)]
mod tests {
use crate::recipe::build_recipe::{
build_recipe_from_template, resolve_sub_recipe_path, RecipeError,
};
use crate::recipe::read_recipe_file_content::RecipeFile;
use crate::recipe::{RecipeParameterInputType, RecipeParameterRequirement};
use tempfile::TempDir;
use crate::recipe::build_recipe::{
build_recipe_from_template, resolve_sub_recipe_path, RecipeError,
};
use crate::recipe::read_recipe_file_content::RecipeFile;
use crate::recipe::{RecipeParameterInputType, RecipeParameterRequirement};
use tempfile::TempDir;
const NO_USER_PROMPT: Option<fn(&str, &str) -> Result<String, anyhow::Error>> = None;
#[allow(clippy::type_complexity)]
const NO_USER_PROMPT: Option<fn(&str, &str) -> Result<String, anyhow::Error>> = None;
fn setup_recipe_file(instructions_and_parameters: &str) -> (TempDir, RecipeFile) {
let recipe_content = format!(
r#"{{
fn setup_recipe_file(instructions_and_parameters: &str) -> (TempDir, RecipeFile) {
let recipe_content = format!(
r#"{{
"version": "1.0.0",
"title": "Test Recipe",
"description": "A test recipe",
{}
}}"#,
instructions_and_parameters
);
let temp_dir = tempfile::tempdir().unwrap();
let recipe_path = temp_dir.path().join("test_recipe.json");
instructions_and_parameters
);
let temp_dir = tempfile::tempdir().unwrap();
let recipe_path = temp_dir.path().join("test_recipe.json");
std::fs::write(&recipe_path, recipe_content).unwrap();
std::fs::write(&recipe_path, recipe_content).unwrap();
let recipe_file = RecipeFile {
content: std::fs::read_to_string(&recipe_path).unwrap(),
parent_dir: temp_dir.path().to_path_buf(),
file_path: recipe_path,
};
let recipe_file = RecipeFile {
content: std::fs::read_to_string(&recipe_path).unwrap(),
parent_dir: temp_dir.path().to_path_buf(),
file_path: recipe_path,
};
(temp_dir, recipe_file)
}
(temp_dir, recipe_file)
}
fn setup_test_file(temp_dir: &TempDir, filename: &str, content: &str) -> std::path::PathBuf {
let file_path = temp_dir.path().join(filename);
std::fs::write(&file_path, content).unwrap();
file_path
}
fn setup_test_file(temp_dir: &TempDir, filename: &str, content: &str) -> std::path::PathBuf {
let file_path = temp_dir.path().join(filename);
std::fs::write(&file_path, content).unwrap();
file_path
}
fn setup_yaml_recipe_file(instructions_and_parameters: &str) -> (TempDir, RecipeFile) {
let recipe_content = format!(
r#"version: "1.0.0"
fn setup_yaml_recipe_file(instructions_and_parameters: &str) -> (TempDir, RecipeFile) {
let recipe_content = format!(
r#"version: "1.0.0"
title: "Test Recipe"
description: "A test recipe"
{}"#,
instructions_and_parameters
);
let temp_dir = tempfile::tempdir().unwrap();
let recipe_path = temp_dir.path().join("test_recipe.yaml");
instructions_and_parameters
);
let temp_dir = tempfile::tempdir().unwrap();
let recipe_path = temp_dir.path().join("test_recipe.yaml");
std::fs::write(&recipe_path, recipe_content).unwrap();
std::fs::write(&recipe_path, recipe_content).unwrap();
let recipe_file = RecipeFile {
content: std::fs::read_to_string(&recipe_path).unwrap(),
parent_dir: temp_dir.path().to_path_buf(),
file_path: recipe_path,
};
let recipe_file = RecipeFile {
content: std::fs::read_to_string(&recipe_path).unwrap(),
parent_dir: temp_dir.path().to_path_buf(),
file_path: recipe_path,
};
(temp_dir, recipe_file)
}
(temp_dir, recipe_file)
}
fn setup_yaml_recipe_files(
parent_content: &str,
child_content: &str,
) -> (TempDir, RecipeFile, RecipeFile) {
let temp_dir = tempfile::tempdir().unwrap();
let temp_path = temp_dir.path();
fn setup_yaml_recipe_files(
parent_content: &str,
child_content: &str,
) -> (TempDir, RecipeFile, RecipeFile) {
let temp_dir = tempfile::tempdir().unwrap();
let temp_path = temp_dir.path();
let parent_path = temp_path.join("parent.yaml");
std::fs::write(&parent_path, parent_content).unwrap();
let parent_path = temp_path.join("parent.yaml");
std::fs::write(&parent_path, parent_content).unwrap();
let child_path = temp_path.join("child.yaml");
std::fs::write(&child_path, child_content).unwrap();
let child_path = temp_path.join("child.yaml");
std::fs::write(&child_path, child_content).unwrap();
let parent_recipe_file = RecipeFile {
content: std::fs::read_to_string(&parent_path).unwrap(),
parent_dir: temp_path.to_path_buf(),
file_path: parent_path,
};
let parent_recipe_file = RecipeFile {
content: std::fs::read_to_string(&parent_path).unwrap(),
parent_dir: temp_path.to_path_buf(),
file_path: parent_path,
};
let child_recipe_file = RecipeFile {
content: std::fs::read_to_string(&child_path).unwrap(),
parent_dir: temp_path.to_path_buf(),
file_path: child_path,
};
let child_recipe_file = RecipeFile {
content: std::fs::read_to_string(&child_path).unwrap(),
parent_dir: temp_path.to_path_buf(),
file_path: child_path,
};
(temp_dir, parent_recipe_file, child_recipe_file)
}
(temp_dir, parent_recipe_file, child_recipe_file)
}
#[test]
fn test_build_recipe_from_template_success() {
let instructions_and_parameters = r#"
#[test]
fn test_build_recipe_from_template_success() {
let instructions_and_parameters = r#"
"instructions": "Test instructions with {{ my_name }}",
"parameters": [
{
@@ -102,28 +101,28 @@ description: "A test recipe"
}
]"#;
let (_temp_dir, recipe_file) = setup_recipe_file(instructions_and_parameters);
let (_temp_dir, recipe_file) = setup_recipe_file(instructions_and_parameters);
let params = vec![("my_name".to_string(), "value".to_string())];
let recipe = build_recipe_from_template(recipe_file, params, NO_USER_PROMPT).unwrap();
let params = vec![("my_name".to_string(), "value".to_string())];
let recipe = build_recipe_from_template(recipe_file, params, NO_USER_PROMPT).unwrap();
assert_eq!(recipe.title, "Test Recipe");
assert_eq!(recipe.description, "A test recipe");
assert_eq!(recipe.instructions.unwrap(), "Test instructions with value");
assert_eq!(recipe.parameters.as_ref().unwrap().len(), 1);
let param = &recipe.parameters.as_ref().unwrap()[0];
assert_eq!(param.key, "my_name");
assert!(matches!(param.input_type, RecipeParameterInputType::String));
assert!(matches!(
param.requirement,
RecipeParameterRequirement::Required
));
assert_eq!(param.description, "A test parameter");
}
assert_eq!(recipe.title, "Test Recipe");
assert_eq!(recipe.description, "A test recipe");
assert_eq!(recipe.instructions.unwrap(), "Test instructions with value");
assert_eq!(recipe.parameters.as_ref().unwrap().len(), 1);
let param = &recipe.parameters.as_ref().unwrap()[0];
assert_eq!(param.key, "my_name");
assert!(matches!(param.input_type, RecipeParameterInputType::String));
assert!(matches!(
param.requirement,
RecipeParameterRequirement::Required
));
assert_eq!(param.description, "A test parameter");
}
#[test]
fn test_build_recipe_from_template_success_variable_in_prompt() {
let instructions_and_parameters = r#"
#[test]
fn test_build_recipe_from_template_success_variable_in_prompt() {
let instructions_and_parameters = r#"
"instructions": "Test instructions",
"prompt": "My prompt {{ my_name }}",
"parameters": [
@@ -135,28 +134,28 @@ description: "A test recipe"
}
]"#;
let (_temp_dir, recipe_file) = setup_recipe_file(instructions_and_parameters);
let (_temp_dir, recipe_file) = setup_recipe_file(instructions_and_parameters);
let params = vec![("my_name".to_string(), "value".to_string())];
let recipe = build_recipe_from_template(recipe_file, params, NO_USER_PROMPT).unwrap();
let params = vec![("my_name".to_string(), "value".to_string())];
let recipe = build_recipe_from_template(recipe_file, params, NO_USER_PROMPT).unwrap();
assert_eq!(recipe.title, "Test Recipe");
assert_eq!(recipe.description, "A test recipe");
assert_eq!(recipe.instructions.unwrap(), "Test instructions");
assert_eq!(recipe.prompt.unwrap(), "My prompt value");
let param = &recipe.parameters.as_ref().unwrap()[0];
assert_eq!(param.key, "my_name");
assert!(matches!(param.input_type, RecipeParameterInputType::String));
assert!(matches!(
param.requirement,
RecipeParameterRequirement::Required
));
assert_eq!(param.description, "A test parameter");
}
assert_eq!(recipe.title, "Test Recipe");
assert_eq!(recipe.description, "A test recipe");
assert_eq!(recipe.instructions.unwrap(), "Test instructions");
assert_eq!(recipe.prompt.unwrap(), "My prompt value");
let param = &recipe.parameters.as_ref().unwrap()[0];
assert_eq!(param.key, "my_name");
assert!(matches!(param.input_type, RecipeParameterInputType::String));
assert!(matches!(
param.requirement,
RecipeParameterRequirement::Required
));
assert_eq!(param.description, "A test parameter");
}
#[test]
fn test_build_recipe_from_template_wrong_parameters_in_recipe_file() {
let instructions_and_parameters = r#"
#[test]
fn test_build_recipe_from_template_wrong_parameters_in_recipe_file() {
let instructions_and_parameters = r#"
"instructions": "Test instructions with {{ expected_param1 }} {{ expected_param2 }}",
"parameters": [
{
@@ -166,29 +165,28 @@ description: "A test recipe"
"description": "A test parameter"
}
]"#;
let (_temp_dir, recipe_file) = setup_recipe_file(instructions_and_parameters);
let (_temp_dir, recipe_file) = setup_recipe_file(instructions_and_parameters);
let build_recipe_result =
build_recipe_from_template(recipe_file, Vec::new(), NO_USER_PROMPT);
assert!(build_recipe_result.is_err());
let err = build_recipe_result.unwrap_err();
println!("{}", err.to_string());
let build_recipe_result = build_recipe_from_template(recipe_file, Vec::new(), NO_USER_PROMPT);
assert!(build_recipe_result.is_err());
let err = build_recipe_result.unwrap_err();
println!("{}", err);
match err {
RecipeError::TemplateRendering { source } => {
let err_str = source.to_string();
assert!(err_str.contains("Unnecessary parameter definitions: wrong_param_key."));
assert!(err_str.contains("Missing definitions for parameters in the recipe file:"));
assert!(err_str.contains("expected_param1"));
assert!(err_str.contains("expected_param2"));
}
_ => panic!("Expected TemplateRendering error"),
match err {
RecipeError::TemplateRendering { source } => {
let err_str = source.to_string();
assert!(err_str.contains("Unnecessary parameter definitions: wrong_param_key."));
assert!(err_str.contains("Missing definitions for parameters in the recipe file:"));
assert!(err_str.contains("expected_param1"));
assert!(err_str.contains("expected_param2"));
}
_ => panic!("Expected TemplateRendering error"),
}
}
#[test]
fn test_build_recipe_from_template_with_default_values_in_recipe_file() {
let instructions_and_parameters = r#"
#[test]
fn test_build_recipe_from_template_with_default_values_in_recipe_file() {
let instructions_and_parameters = r#"
"instructions": "Test instructions with {{ param_with_default }} {{ param_without_default }}",
"parameters": [
{
@@ -205,23 +203,22 @@ description: "A test recipe"
"description": "A test parameter"
}
]"#;
let (_temp_dir, recipe_file) = setup_recipe_file(instructions_and_parameters);
let params = vec![("param_without_default".to_string(), "value1".to_string())];
let (_temp_dir, recipe_file) = setup_recipe_file(instructions_and_parameters);
let params = vec![("param_without_default".to_string(), "value1".to_string())];
let recipe = build_recipe_from_template(recipe_file, params, NO_USER_PROMPT).unwrap();
let recipe = build_recipe_from_template(recipe_file, params, NO_USER_PROMPT).unwrap();
assert_eq!(recipe.title, "Test Recipe");
assert_eq!(recipe.description, "A test recipe");
assert_eq!(
recipe.instructions.unwrap(),
"Test instructions with my_default_value value1"
);
}
assert_eq!(recipe.title, "Test Recipe");
assert_eq!(recipe.description, "A test recipe");
assert_eq!(
recipe.instructions.unwrap(),
"Test instructions with my_default_value value1"
);
}
#[test]
fn test_build_recipe_from_template_optional_parameters_with_empty_default_values_in_recipe_file(
) {
let instructions_and_parameters = r#"
#[test]
fn test_build_recipe_from_template_optional_parameters_with_empty_default_values_in_recipe_file() {
let instructions_and_parameters = r#"
"instructions": "Test instructions with {{ optional_param }}",
"parameters": [
{
@@ -232,17 +229,17 @@ description: "A test recipe"
"default": ""
}
]"#;
let (_temp_dir, recipe_file) = setup_recipe_file(instructions_and_parameters);
let (_temp_dir, recipe_file) = setup_recipe_file(instructions_and_parameters);
let recipe = build_recipe_from_template(recipe_file, Vec::new(), NO_USER_PROMPT).unwrap();
assert_eq!(recipe.title, "Test Recipe");
assert_eq!(recipe.description, "A test recipe");
assert_eq!(recipe.instructions.unwrap(), "Test instructions with ");
}
let recipe = build_recipe_from_template(recipe_file, Vec::new(), NO_USER_PROMPT).unwrap();
assert_eq!(recipe.title, "Test Recipe");
assert_eq!(recipe.description, "A test recipe");
assert_eq!(recipe.instructions.unwrap(), "Test instructions with ");
}
#[test]
fn test_build_recipe_from_template_optional_parameters_without_default_values_in_recipe_file() {
let instructions_and_parameters = r#"
#[test]
fn test_build_recipe_from_template_optional_parameters_without_default_values_in_recipe_file() {
let instructions_and_parameters = r#"
"instructions": "Test instructions with {{ optional_param }}",
"parameters": [
{
@@ -252,24 +249,23 @@ description: "A test recipe"
"description": "A test parameter"
}
]"#;
let (_temp_dir, recipe_file) = setup_recipe_file(instructions_and_parameters);
let (_temp_dir, recipe_file) = setup_recipe_file(instructions_and_parameters);
let build_recipe_result =
build_recipe_from_template(recipe_file, Vec::new(), NO_USER_PROMPT);
assert!(build_recipe_result.is_err());
let err = build_recipe_result.unwrap_err();
println!("{}", err.to_string());
match err {
RecipeError::TemplateRendering { source } => {
assert!(source.to_string().to_lowercase().contains("missing"));
}
_ => panic!("Expected TemplateRendering error"),
let build_recipe_result = build_recipe_from_template(recipe_file, Vec::new(), NO_USER_PROMPT);
assert!(build_recipe_result.is_err());
let err = build_recipe_result.unwrap_err();
println!("{}", err);
match err {
RecipeError::TemplateRendering { source } => {
assert!(source.to_string().to_lowercase().contains("missing"));
}
_ => panic!("Expected TemplateRendering error"),
}
}
#[test]
fn test_build_recipe_from_template_wrong_input_type_in_recipe_file() {
let instructions_and_parameters = r#"
#[test]
fn test_build_recipe_from_template_wrong_input_type_in_recipe_file() {
let instructions_and_parameters = r#"
"instructions": "Test instructions with {{ param }}",
"parameters": [
{
@@ -279,37 +275,37 @@ description: "A test recipe"
"description": "A test parameter"
}
]"#;
let params = vec![("param".to_string(), "value".to_string())];
let (_temp_dir, recipe_file) = setup_recipe_file(instructions_and_parameters);
let params = vec![("param".to_string(), "value".to_string())];
let (_temp_dir, recipe_file) = setup_recipe_file(instructions_and_parameters);
let build_recipe_result = build_recipe_from_template(recipe_file, params, NO_USER_PROMPT);
assert!(build_recipe_result.is_err());
let err = build_recipe_result.unwrap_err();
match err {
RecipeError::TemplateRendering { source } => {
let err_msg = source.to_string();
eprint!("Error: {}", err_msg);
assert!(err_msg.contains("unknown variant `some_invalid_type`"));
}
_ => panic!("Expected TemplateRendering error, got: {:?}", err),
let build_recipe_result = build_recipe_from_template(recipe_file, params, NO_USER_PROMPT);
assert!(build_recipe_result.is_err());
let err = build_recipe_result.unwrap_err();
match err {
RecipeError::TemplateRendering { source } => {
let err_msg = source.to_string();
eprint!("Error: {}", err_msg);
assert!(err_msg.contains("unknown variant `some_invalid_type`"));
}
_ => panic!("Expected TemplateRendering error, got: {:?}", err),
}
}
#[test]
fn test_build_recipe_from_template_success_without_parameters() {
let instructions_and_parameters = r#"
#[test]
fn test_build_recipe_from_template_success_without_parameters() {
let instructions_and_parameters = r#"
"instructions": "Test instructions"
"#;
let (_temp_dir, recipe_file) = setup_recipe_file(instructions_and_parameters);
let (_temp_dir, recipe_file) = setup_recipe_file(instructions_and_parameters);
let recipe = build_recipe_from_template(recipe_file, Vec::new(), NO_USER_PROMPT).unwrap();
assert_eq!(recipe.instructions.unwrap(), "Test instructions");
assert!(recipe.parameters.is_none());
}
let recipe = build_recipe_from_template(recipe_file, Vec::new(), NO_USER_PROMPT).unwrap();
assert_eq!(recipe.instructions.unwrap(), "Test instructions");
assert!(recipe.parameters.is_none());
}
#[test]
fn test_template_inheritance() {
let parent_content = r#"
#[test]
fn test_template_inheritance() {
let parent_content = r#"
version: 1.0.0
title: Parent
description: Parent recipe
@@ -334,102 +330,102 @@ description: "A test recipe"
description: whether the feature is enabled
"#;
let child_content = r#"
let child_content = r#"
{% extends "parent.yaml" -%}
{% block prompt -%}
What is the capital of Germany?
{%- endblock %}
"#;
let (_temp_dir, parent_recipe_file, child_recipe_file) =
setup_yaml_recipe_files(parent_content, child_content);
let (_temp_dir, parent_recipe_file, child_recipe_file) =
setup_yaml_recipe_files(parent_content, child_content);
let params = vec![
("date".to_string(), "today".to_string()),
("is_enabled".to_string(), "true".to_string()),
];
let params = vec![
("date".to_string(), "today".to_string()),
("is_enabled".to_string(), "true".to_string()),
];
let parent_recipe =
build_recipe_from_template(parent_recipe_file, params.clone(), NO_USER_PROMPT).unwrap();
assert_eq!(parent_recipe.description, "Parent recipe");
assert_eq!(
let parent_recipe =
build_recipe_from_template(parent_recipe_file, params.clone(), NO_USER_PROMPT).unwrap();
assert_eq!(parent_recipe.description, "Parent recipe");
assert_eq!(
parent_recipe.prompt.unwrap(),
"show me the news for day: today\nWhat is the capital of France?\n\n Feature is enabled.\n"
);
assert_eq!(parent_recipe.parameters.as_ref().unwrap().len(), 2);
assert_eq!(parent_recipe.parameters.as_ref().unwrap()[0].key, "date");
assert_eq!(
parent_recipe.parameters.as_ref().unwrap()[1].key,
"is_enabled"
);
assert_eq!(parent_recipe.parameters.as_ref().unwrap().len(), 2);
assert_eq!(parent_recipe.parameters.as_ref().unwrap()[0].key, "date");
assert_eq!(
parent_recipe.parameters.as_ref().unwrap()[1].key,
"is_enabled"
);
let child_recipe =
build_recipe_from_template(child_recipe_file, params, NO_USER_PROMPT).unwrap();
assert_eq!(child_recipe.title, "Parent");
assert_eq!(child_recipe.description, "Parent recipe");
assert_eq!(
let child_recipe =
build_recipe_from_template(child_recipe_file, params, NO_USER_PROMPT).unwrap();
assert_eq!(child_recipe.title, "Parent");
assert_eq!(child_recipe.description, "Parent recipe");
assert_eq!(
child_recipe.prompt.unwrap().trim(),
"show me the news for day: today\nWhat is the capital of Germany?\n\n Feature is enabled."
);
assert_eq!(child_recipe.parameters.as_ref().unwrap().len(), 2);
assert_eq!(child_recipe.parameters.as_ref().unwrap()[0].key, "date");
assert_eq!(
child_recipe.parameters.as_ref().unwrap()[1].key,
"is_enabled"
);
assert_eq!(child_recipe.parameters.as_ref().unwrap().len(), 2);
assert_eq!(child_recipe.parameters.as_ref().unwrap()[0].key, "date");
assert_eq!(
child_recipe.parameters.as_ref().unwrap()[1].key,
"is_enabled"
);
}
mod sub_recipe_path_resolution {
use super::*;
fn create_recipe_file(
temp_path: &std::path::Path,
recipe_folder: &str,
recipe_file_name: &str,
content: &str,
) -> std::path::PathBuf {
let recipes_dir = temp_path.join(recipe_folder);
std::fs::create_dir_all(&recipes_dir).unwrap();
let recipe_path = recipes_dir.join(recipe_file_name);
std::fs::write(&recipe_path, content).unwrap();
recipe_path
}
mod sub_recipe_path_resolution {
use super::*;
#[test]
fn test_resolve_sub_recipe_path_relative() {
let temp_dir = tempfile::tempdir().unwrap();
let parent_dir = temp_dir.path();
fn create_recipe_file(
temp_path: &std::path::Path,
recipe_folder: &str,
recipe_file_name: &str,
content: &str,
) -> std::path::PathBuf {
let recipes_dir = temp_path.join(recipe_folder);
std::fs::create_dir_all(&recipes_dir).unwrap();
let recipe_path = recipes_dir.join(recipe_file_name);
std::fs::write(&recipe_path, content).unwrap();
recipe_path
}
let result = resolve_sub_recipe_path("./sub-recipes/child.yaml", parent_dir);
assert!(result.is_ok());
#[test]
fn test_resolve_sub_recipe_path_relative() {
let temp_dir = tempfile::tempdir().unwrap();
let parent_dir = temp_dir.path();
let expected_path = parent_dir.join("./sub-recipes/child.yaml");
assert_eq!(result.unwrap(), expected_path.to_str().unwrap());
}
let result = resolve_sub_recipe_path("./sub-recipes/child.yaml", parent_dir);
assert!(result.is_ok());
#[test]
fn test_resolve_sub_recipe_path_absolute() {
let temp_dir = tempfile::tempdir().unwrap();
let parent_dir = temp_dir.path();
let absolute_path = "/absolute/path/to/recipe.yaml";
let expected_path = parent_dir.join("./sub-recipes/child.yaml");
assert_eq!(result.unwrap(), expected_path.to_str().unwrap());
}
let result = resolve_sub_recipe_path(absolute_path, parent_dir);
assert!(result.is_ok());
assert_eq!(result.unwrap(), absolute_path);
}
#[test]
fn test_resolve_sub_recipe_path_absolute() {
let temp_dir = tempfile::tempdir().unwrap();
let parent_dir = temp_dir.path();
let absolute_path = "/absolute/path/to/recipe.yaml";
let result = resolve_sub_recipe_path(absolute_path, parent_dir);
assert!(result.is_ok());
assert_eq!(result.unwrap(), absolute_path);
}
#[test]
fn test_build_recipe_with_relative_sub_recipe_path() {
let temp_dir = tempfile::tempdir().unwrap();
let temp_path = temp_dir.path();
let sub_recipe_content = r#"
#[test]
fn test_build_recipe_with_relative_sub_recipe_path() {
let temp_dir = tempfile::tempdir().unwrap();
let temp_path = temp_dir.path();
let sub_recipe_content = r#"
version: 1.0.0
title: Child Recipe
description: A child recipe
instructions: Child instructions
"#;
create_recipe_file(temp_path, "sub-recipes", "child.yaml", sub_recipe_content);
let main_recipe_content = r#"{
create_recipe_file(temp_path, "sub-recipes", "child.yaml", sub_recipe_content);
let main_recipe_content = r#"{
"version": "1.0.0",
"title": "Main Recipe",
"description": "Main recipe with sub-recipe",
@@ -441,92 +437,91 @@ instructions: Child instructions
}
]
}"#;
let main_recipe_path =
create_recipe_file(temp_path, "main", "main.json", main_recipe_content);
let main_recipe_path =
create_recipe_file(temp_path, "main", "main.json", main_recipe_content);
let recipe_file = RecipeFile {
content: main_recipe_content.to_string(),
parent_dir: temp_path.to_path_buf(),
file_path: main_recipe_path,
};
let recipe_file = RecipeFile {
content: main_recipe_content.to_string(),
parent_dir: temp_path.to_path_buf(),
file_path: main_recipe_path,
};
let recipe =
build_recipe_from_template(recipe_file, Vec::new(), NO_USER_PROMPT).unwrap();
let recipe = build_recipe_from_template(recipe_file, Vec::new(), NO_USER_PROMPT).unwrap();
assert_eq!(recipe.title, "Main Recipe");
assert!(recipe.sub_recipes.is_some());
assert_eq!(recipe.title, "Main Recipe");
assert!(recipe.sub_recipes.is_some());
let sub_recipes = recipe.sub_recipes.unwrap();
assert_eq!(sub_recipes.len(), 1);
assert_eq!(sub_recipes[0].name, "child");
let sub_recipes = recipe.sub_recipes.unwrap();
assert_eq!(sub_recipes.len(), 1);
assert_eq!(sub_recipes[0].name, "child");
let expected_absolute_path = temp_path.join("./sub-recipes/child.yaml");
assert_eq!(
sub_recipes[0].path,
expected_absolute_path.to_str().unwrap()
);
let expected_absolute_path = temp_path.join("./sub-recipes/child.yaml");
assert_eq!(
sub_recipes[0].path,
expected_absolute_path.to_str().unwrap()
);
}
}
mod file_parameter_tests {
use super::*;
#[test]
fn test_build_recipe_file_parameter_valid_paths() {
let instructions_and_parameters = r#"instructions: "Test file content: {{ FILE_PARAM }}"
parameters:
- key: FILE_PARAM
input_type: file
requirement: required
description: A file parameter"#;
let (temp_dir, recipe_file) = setup_yaml_recipe_file(instructions_and_parameters);
let test_content = "Hello from file!\nThis is line 2\n Indented line 3";
let test_file_path = setup_test_file(&temp_dir, "test_file.txt", test_content);
let params = vec![(
"FILE_PARAM".to_string(),
test_file_path.to_string_lossy().to_string(),
)];
let result = build_recipe_from_template(recipe_file, params, NO_USER_PROMPT);
assert!(result.is_ok());
let recipe = result.unwrap();
let instructions = recipe.instructions.as_ref().unwrap();
assert!(instructions.contains("Hello from file!"));
assert!(instructions.contains("Test file content:"));
}
#[test]
fn test_build_recipe_file_parameter_nonexistent_file() {
let instructions_and_parameters = r#"instructions: "Test file content: {{ FILE_PARAM }}"
parameters:
- key: FILE_PARAM
input_type: file
requirement: required
description: A file parameter"#;
let (_temp_dir, recipe_file) = setup_yaml_recipe_file(instructions_and_parameters);
let params = vec![(
"FILE_PARAM".to_string(),
"/nonexistent/path/file.txt".to_string(),
)];
let result = build_recipe_from_template(recipe_file, params, NO_USER_PROMPT);
assert!(result.is_err());
if let Err(RecipeError::TemplateRendering { source }) = result {
assert!(source.to_string().contains("Failed to read parameter file"));
} else {
panic!("Expected TemplateRendering error");
}
}
mod file_parameter_tests {
use super::*;
#[test]
fn test_build_recipe_file_parameter_valid_paths() {
let instructions_and_parameters = r#"instructions: "Test file content: {{ FILE_PARAM }}"
parameters:
- key: FILE_PARAM
input_type: file
requirement: required
description: A file parameter"#;
let (temp_dir, recipe_file) = setup_yaml_recipe_file(instructions_and_parameters);
let test_content = "Hello from file!\nThis is line 2\n Indented line 3";
let test_file_path = setup_test_file(&temp_dir, "test_file.txt", test_content);
let params = vec![(
"FILE_PARAM".to_string(),
test_file_path.to_string_lossy().to_string(),
)];
let result = build_recipe_from_template(recipe_file, params, NO_USER_PROMPT);
assert!(result.is_ok());
let recipe = result.unwrap();
let instructions = recipe.instructions.as_ref().unwrap();
assert!(instructions.contains("Hello from file!"));
assert!(instructions.contains("Test file content:"));
}
#[test]
fn test_build_recipe_file_parameter_nonexistent_file() {
let instructions_and_parameters = r#"instructions: "Test file content: {{ FILE_PARAM }}"
parameters:
- key: FILE_PARAM
input_type: file
requirement: required
description: A file parameter"#;
let (_temp_dir, recipe_file) = setup_yaml_recipe_file(instructions_and_parameters);
let params = vec![(
"FILE_PARAM".to_string(),
"/nonexistent/path/file.txt".to_string(),
)];
let result = build_recipe_from_template(recipe_file, params, NO_USER_PROMPT);
assert!(result.is_err());
if let Err(RecipeError::TemplateRendering { source }) = result {
assert!(source.to_string().contains("Failed to read parameter file"));
} else {
panic!("Expected TemplateRendering error");
}
}
#[test]
fn test_build_recipe_file_parameter_with_default_rejected() {
let instructions_and_parameters = r#"instructions: "Test file content: {{ FILE_PARAM }}"
#[test]
fn test_build_recipe_file_parameter_with_default_rejected() {
let instructions_and_parameters = r#"instructions: "Test file content: {{ FILE_PARAM }}"
parameters:
- key: FILE_PARAM
input_type: file
@@ -534,19 +529,18 @@ parameters:
description: A file parameter
default: "/etc/passwd""#;
let (_temp_dir, recipe_file) = setup_yaml_recipe_file(instructions_and_parameters);
let (_temp_dir, recipe_file) = setup_yaml_recipe_file(instructions_and_parameters);
let params = vec![];
let result = build_recipe_from_template(recipe_file, params, NO_USER_PROMPT);
let params = vec![];
let result = build_recipe_from_template(recipe_file, params, NO_USER_PROMPT);
assert!(result.is_err());
if let Err(RecipeError::TemplateRendering { source }) = result {
assert!(source
.to_string()
.contains("File parameters cannot have default values"));
} else {
panic!("Expected TemplateRendering error for file parameter with default");
}
assert!(result.is_err());
if let Err(RecipeError::TemplateRendering { source }) = result {
assert!(source
.to_string()
.contains("File parameters cannot have default values"));
} else {
panic!("Expected TemplateRendering error for file parameter with default");
}
}
}
+54 -54
View File
@@ -1348,6 +1348,60 @@ async fn run_scheduled_job_internal(
Ok(session_id_for_return)
}
#[async_trait]
impl SchedulerTrait for Scheduler {
async fn add_scheduled_job(&self, job: ScheduledJob) -> Result<(), SchedulerError> {
self.add_scheduled_job(job).await
}
async fn list_scheduled_jobs(&self) -> Result<Vec<ScheduledJob>, SchedulerError> {
Ok(self.list_scheduled_jobs().await)
}
async fn remove_scheduled_job(&self, id: &str) -> Result<(), SchedulerError> {
self.remove_scheduled_job(id).await
}
async fn pause_schedule(&self, id: &str) -> Result<(), SchedulerError> {
self.pause_schedule(id).await
}
async fn unpause_schedule(&self, id: &str) -> Result<(), SchedulerError> {
self.unpause_schedule(id).await
}
async fn run_now(&self, id: &str) -> Result<String, SchedulerError> {
self.run_now(id).await
}
async fn sessions(
&self,
sched_id: &str,
limit: usize,
) -> Result<Vec<(String, SessionMetadata)>, SchedulerError> {
self.sessions(sched_id, limit).await
}
async fn update_schedule(
&self,
sched_id: &str,
new_cron: String,
) -> Result<(), SchedulerError> {
self.update_schedule(sched_id, new_cron).await
}
async fn kill_running_job(&self, sched_id: &str) -> Result<(), SchedulerError> {
self.kill_running_job(sched_id).await
}
async fn get_running_job_info(
&self,
sched_id: &str,
) -> Result<Option<(String, DateTime<Utc>)>, SchedulerError> {
self.get_running_job_info(sched_id).await
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -1527,57 +1581,3 @@ mod tests {
Ok(())
}
}
#[async_trait]
impl SchedulerTrait for Scheduler {
async fn add_scheduled_job(&self, job: ScheduledJob) -> Result<(), SchedulerError> {
self.add_scheduled_job(job).await
}
async fn list_scheduled_jobs(&self) -> Result<Vec<ScheduledJob>, SchedulerError> {
Ok(self.list_scheduled_jobs().await)
}
async fn remove_scheduled_job(&self, id: &str) -> Result<(), SchedulerError> {
self.remove_scheduled_job(id).await
}
async fn pause_schedule(&self, id: &str) -> Result<(), SchedulerError> {
self.pause_schedule(id).await
}
async fn unpause_schedule(&self, id: &str) -> Result<(), SchedulerError> {
self.unpause_schedule(id).await
}
async fn run_now(&self, id: &str) -> Result<String, SchedulerError> {
self.run_now(id).await
}
async fn sessions(
&self,
sched_id: &str,
limit: usize,
) -> Result<Vec<(String, SessionMetadata)>, SchedulerError> {
self.sessions(sched_id, limit).await
}
async fn update_schedule(
&self,
sched_id: &str,
new_cron: String,
) -> Result<(), SchedulerError> {
self.update_schedule(sched_id, new_cron).await
}
async fn kill_running_job(&self, sched_id: &str) -> Result<(), SchedulerError> {
self.kill_running_job(sched_id).await
}
async fn get_running_job_info(
&self,
sched_id: &str,
) -> Result<Option<(String, DateTime<Utc>)>, SchedulerError> {
self.get_running_job_info(sched_id).await
}
}
@@ -130,7 +130,7 @@ mod tests {
if inspector.is_enabled() {
// If security is enabled, should detect the dangerous command
assert!(
results.len() >= 1,
!results.is_empty(),
"Security inspector should detect dangerous command when enabled"
);
if !results.is_empty() {
-38
View File
@@ -97,41 +97,3 @@ pub fn get_valid_sorted_sessions(sort_order: SortOrder) -> Result<Vec<SessionInf
Ok(session_infos)
}
#[cfg(test)]
mod tests {
use crate::session::SessionMetadata;
use std::fs;
use tempfile::tempdir;
#[test]
fn test_get_valid_sorted_sessions_with_corrupted_files() {
let temp_dir = tempdir().unwrap();
let session_dir = temp_dir.path().join("sessions");
fs::create_dir_all(&session_dir).unwrap();
// Create a valid session file
let valid_session = session_dir.join("valid_session.jsonl");
let metadata = SessionMetadata::default();
let metadata_json = serde_json::to_string(&metadata).unwrap();
fs::write(&valid_session, format!("{}\n", metadata_json)).unwrap();
// Create a corrupted session file (invalid JSON)
let corrupted_session = session_dir.join("corrupted_session.jsonl");
fs::write(&corrupted_session, "invalid json content").unwrap();
// Create another valid session file
let valid_session2 = session_dir.join("valid_session2.jsonl");
fs::write(&valid_session2, format!("{}\n", metadata_json)).unwrap();
// Mock the session directory by temporarily setting it
// Note: This is a simplified test - in practice, we'd need to mock the session::list_sessions function
// For now, we'll just verify that the function handles errors gracefully
// The key improvement is that get_valid_sorted_sessions should not fail completely
// when encountering corrupted sessions, but should skip them and continue with valid ones
// This test verifies the logic changes we made to handle corrupted sessions gracefully
assert!(true, "Test passes - the function now handles corrupted sessions gracefully by skipping them instead of failing completely");
}
}
+6 -3
View File
@@ -1364,7 +1364,7 @@ mod tests {
#[test]
fn test_corruption_recovery() -> Result<()> {
let test_cases = vec![
let test_cases = [
// Case 1: Unclosed quotes
(
r#"{"role":"user","content":[{"type":"text","text":"Hello there}]"#,
@@ -1665,7 +1665,10 @@ mod tests {
let file_path = dir.path().join("metadata.jsonl");
let mut metadata = SessionMetadata::default();
metadata.description = "Description with\nnewline and \"quotes\" and 🦆".to_string();
#[allow(clippy::field_reassign_with_default)]
{
metadata.description = "Description with\nnewline and \"quotes\" and 🦆".to_string();
}
let messages = Conversation::new_unvalidated(vec![Message::user().with_text("test")]);
@@ -1702,7 +1705,7 @@ mod tests {
let mut lines: Vec<String> = contents.lines().map(String::from).collect();
lines[0] = lines[0].replace(
&get_home_dir().to_string_lossy().into_owned(),
&invalid_dir.to_string_lossy().into_owned(),
&invalid_dir.to_string_lossy(),
);
fs::write(&file_path, lines.join("\n"))?;
-24
View File
@@ -274,30 +274,6 @@ mod tests {
use mcp_core::ToolCall;
use serde_json::json;
struct MockInspector {
name: &'static str,
results: Vec<InspectionResult>,
}
#[async_trait]
impl ToolInspector for MockInspector {
fn name(&self) -> &'static str {
self.name
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
async fn inspect(
&self,
_tool_requests: &[ToolRequest],
_messages: &[Message],
) -> Result<Vec<InspectionResult>> {
Ok(self.results.clone())
}
}
#[test]
fn test_apply_inspection_results() {
let tool_request = ToolRequest {
@@ -344,9 +344,10 @@ mod tests {
use tokio::sync::mpsc;
use tracing::dispatcher;
type Events = Arc<Mutex<Vec<(String, Value)>>>;
struct TestFixture {
original_subscriber: Option<dispatcher::Dispatch>,
events: Option<Arc<Mutex<Vec<(String, Value)>>>>,
events: Option<Events>,
}
impl TestFixture {
+3 -4
View File
@@ -20,6 +20,7 @@ use goose::providers::{
enum ProviderType {
Azure,
OpenAi,
#[allow(dead_code)]
Anthropic,
Bedrock,
Databricks,
@@ -540,9 +541,7 @@ mod schedule_tool_tests {
mod final_output_tool_tests {
use super::*;
use futures::stream;
use goose::agents::final_output_tool::{
FINAL_OUTPUT_CONTINUATION_MESSAGE, FINAL_OUTPUT_TOOL_NAME,
};
use goose::agents::final_output_tool::FINAL_OUTPUT_TOOL_NAME;
use goose::conversation::Conversation;
use goose::providers::base::MessageStream;
use goose::recipe::Response;
@@ -1151,7 +1150,7 @@ mod max_turns_tests {
}
assert!(
responses.len() >= 1,
!responses.is_empty(),
"Expected at least 1 response, got {}",
responses.len()
);
@@ -431,7 +431,7 @@ mod tests {
assert!(recipe.extensions.is_some());
let extensions = recipe.extensions.unwrap();
// At minimum we should get the full config (stdio), shortname may not resolve
assert!(extensions.len() >= 1 && extensions.len() <= 2);
assert!(!extensions.is_empty() && extensions.len() <= 2);
// The last one should always be the stdio config we provided
if let Some(last) = extensions.last() {
match last {
+1 -3
View File
@@ -57,9 +57,7 @@ mod execution_tests {
async fn test_session_limit() {
let manager = AgentManager::new(Some(3)).await.unwrap();
let sessions: Vec<_> = (0..3)
.map(|i| String::from(format!("session-{}", i)))
.collect();
let sessions: Vec<_> = (0..3).map(|i| format!("session-{}", i)).collect();
for session in &sessions {
manager
@@ -156,6 +156,7 @@ async fn test_replayed_session(
let extension_manager = ExtensionManager::new();
#[allow(clippy::redundant_closure_call)]
let result = (async || -> Result<(), Box<dyn std::error::Error>> {
extension_manager.add_extension(extension_config).await?;
+13 -30
View File
@@ -29,10 +29,17 @@ pub struct ConfigurableMockScheduler {
running_jobs: Arc<Mutex<HashSet<String>>>,
call_log: Arc<Mutex<Vec<String>>>,
behaviors: Arc<Mutex<HashMap<String, MockBehavior>>>,
#[allow(clippy::type_complexity)]
sessions_data: Arc<Mutex<HashMap<String, Vec<(String, SessionMetadata)>>>>,
}
#[allow(dead_code)]
impl Default for ConfigurableMockScheduler {
fn default() -> Self {
Self::new()
}
}
impl ConfigurableMockScheduler {
pub fn new() -> Self {
Self {
@@ -44,36 +51,6 @@ impl ConfigurableMockScheduler {
}
}
pub async fn with_behavior(self, method: &str, behavior: MockBehavior) -> Self {
self.behaviors
.lock()
.await
.insert(method.to_string(), behavior);
self
}
pub async fn with_existing_job(self, job: ScheduledJob) -> Self {
self.jobs.lock().await.insert(job.id.clone(), job);
self
}
pub async fn with_running_job(self, job_id: &str) -> Self {
self.running_jobs.lock().await.insert(job_id.to_string());
self
}
pub async fn with_sessions_data(
self,
job_id: &str,
sessions: Vec<(String, SessionMetadata)>,
) -> Self {
self.sessions_data
.lock()
.await
.insert(job_id.to_string(), sessions);
self
}
pub async fn get_calls(&self) -> Vec<String> {
self.call_log.lock().await.clone()
}
@@ -337,6 +314,12 @@ pub struct ScheduleToolTestBuilder {
scheduler: Arc<ConfigurableMockScheduler>,
}
impl Default for ScheduleToolTestBuilder {
fn default() -> Self {
Self::new()
}
}
impl ScheduleToolTestBuilder {
pub fn new() -> Self {
Self {
@@ -11,7 +11,6 @@ use goose::session::storage::SessionMetadata;
use rmcp::model::Tool;
use std::sync::Arc;
use tempfile::TempDir;
use tokio;
use uuid::Uuid;
// Mock provider implementation for testing
@@ -298,7 +297,7 @@ async fn test_todo_clear_removes_from_session() {
.unwrap();
// Consume the stream
while let Some(_) = stream.next().await {}
while (stream.next().await).is_some() {}
// With mock provider, the TODO won't actually be cleared via tool calls
// but we can verify the structure is correct
@@ -463,9 +462,12 @@ async fn test_todo_update_preserves_other_metadata() {
// Set initial metadata with various fields
let mut metadata = SessionMetadata::default();
metadata.message_count = 5;
metadata.description = "Test session".to_string();
metadata.total_tokens = Some(1000);
#[allow(clippy::field_reassign_with_default)]
{
metadata.message_count = 5;
metadata.description = "Test session".to_string();
metadata.total_tokens = Some(1000);
}
let todo_state = TodoState::new("Initial TODO".to_string());
todo_state
.to_extension_data(&mut metadata.extension_data)
+2 -2
View File
@@ -14,11 +14,11 @@ echo "🔍 Running all clippy checks..."
# Run standard clippy with strict warnings
echo " → Standard clippy rules (strict)"
cargo clippy --jobs 2 -- -D warnings
cargo clippy --all-targets --jobs 2 -- -D warnings
# Run baseline rules check
echo ""
check_all_baseline_rules
echo ""
echo "✅ All lint checks passed!"
echo "✅ All lint checks passed!"
@@ -13,7 +13,16 @@ function getStdioConfig(
timeout: number
) {
// Validate that the command is one of the allowed commands
const allowedCommands = ['cu', 'docker', 'jbang', 'npx', 'uvx', 'goosed', 'npx.cmd', 'i-ching-mcp-server'];
const allowedCommands = [
'cu',
'docker',
'jbang',
'npx',
'uvx',
'goosed',
'npx.cmd',
'i-ching-mcp-server',
];
if (!allowedCommands.includes(cmd)) {
toastService.handleError(
'Invalid Command',