Skip to content

Commit 59105f3

Browse files
Copilotjamesadevine
andcommitted
Add missing test coverage for compile/common.rs, standalone.rs, and tools/result.rs
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
1 parent a628f20 commit 59105f3

4 files changed

Lines changed: 287 additions & 1 deletion

File tree

agentic-pipelines/src/compile/common.rs

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,145 @@ pub fn generate_pipeline_path(output_path: &std::path::Path) -> String {
484484
#[cfg(test)]
485485
mod tests {
486486
use super::*;
487+
use crate::compile::types::{McpConfig, McpOptions, Repository};
488+
489+
/// Helper: create a minimal FrontMatter by parsing YAML
490+
fn minimal_front_matter() -> FrontMatter {
491+
let (fm, _) = parse_markdown("---\nname: test-agent\ndescription: test\n---\n").unwrap();
492+
fm
493+
}
494+
495+
// ─── compute_effective_workspace ─────────────────────────────────────────
496+
497+
#[test]
498+
fn test_workspace_explicit_root() {
499+
let ws = compute_effective_workspace(&Some("root".to_string()), &[], "agent");
500+
assert_eq!(ws, "root");
501+
}
502+
503+
#[test]
504+
fn test_workspace_explicit_repo_with_checkouts() {
505+
let checkouts = vec!["other-repo".to_string()];
506+
let ws = compute_effective_workspace(&Some("repo".to_string()), &checkouts, "agent");
507+
assert_eq!(ws, "repo");
508+
}
509+
510+
#[test]
511+
fn test_workspace_implicit_root_no_checkouts() {
512+
let ws = compute_effective_workspace(&None, &[], "agent");
513+
assert_eq!(ws, "root");
514+
}
515+
516+
#[test]
517+
fn test_workspace_implicit_repo_with_checkouts() {
518+
let checkouts = vec!["other-repo".to_string()];
519+
let ws = compute_effective_workspace(&None, &checkouts, "agent");
520+
assert_eq!(ws, "repo");
521+
}
522+
523+
#[test]
524+
fn test_workspace_explicit_repo_no_checkouts_still_returns_repo() {
525+
// Emits a warning but still returns "repo"
526+
let ws = compute_effective_workspace(&Some("repo".to_string()), &[], "agent");
527+
assert_eq!(ws, "repo");
528+
}
529+
530+
// ─── validate_checkout_list ───────────────────────────────────────────────
531+
532+
#[test]
533+
fn test_validate_checkout_list_empty_is_ok() {
534+
let result = validate_checkout_list(&[], &[]);
535+
assert!(result.is_ok());
536+
}
537+
538+
#[test]
539+
fn test_validate_checkout_list_valid_alias_passes() {
540+
let repos = vec![Repository {
541+
repository: "my-repo".to_string(),
542+
repo_type: "git".to_string(),
543+
name: "org/my-repo".to_string(),
544+
repo_ref: "refs/heads/main".to_string(),
545+
}];
546+
let checkout = vec!["my-repo".to_string()];
547+
let result = validate_checkout_list(&repos, &checkout);
548+
assert!(result.is_ok());
549+
}
550+
551+
#[test]
552+
fn test_validate_checkout_list_unknown_alias_fails() {
553+
let repos = vec![Repository {
554+
repository: "my-repo".to_string(),
555+
repo_type: "git".to_string(),
556+
name: "org/my-repo".to_string(),
557+
repo_ref: "refs/heads/main".to_string(),
558+
}];
559+
let checkout = vec!["unknown-alias".to_string()];
560+
let result = validate_checkout_list(&repos, &checkout);
561+
assert!(result.is_err());
562+
assert!(result.unwrap_err().to_string().contains("unknown-alias"));
563+
}
564+
565+
#[test]
566+
fn test_validate_checkout_list_empty_checkout_of_nonempty_repos_ok() {
567+
let repos = vec![Repository {
568+
repository: "my-repo".to_string(),
569+
repo_type: "git".to_string(),
570+
name: "org/my-repo".to_string(),
571+
repo_ref: "refs/heads/main".to_string(),
572+
}];
573+
let result = validate_checkout_list(&repos, &[]);
574+
assert!(result.is_ok());
575+
}
576+
577+
// ─── generate_copilot_params ──────────────────────────────────────────────
578+
579+
#[test]
580+
fn test_copilot_params_bash_wildcard() {
581+
let mut fm = minimal_front_matter();
582+
fm.tools = Some(crate::compile::types::ToolsConfig {
583+
bash: Some(vec![":*".to_string()]),
584+
edit: None,
585+
});
586+
let params = generate_copilot_params(&fm);
587+
assert!(params.contains("--allow-tool \"shell(:*)\""));
588+
}
589+
590+
#[test]
591+
fn test_copilot_params_bash_disabled() {
592+
let mut fm = minimal_front_matter();
593+
fm.tools = Some(crate::compile::types::ToolsConfig {
594+
bash: Some(vec![]),
595+
edit: None,
596+
});
597+
let params = generate_copilot_params(&fm);
598+
assert!(!params.contains("shell("));
599+
}
600+
601+
#[test]
602+
fn test_copilot_params_custom_mcp_not_added_with_mcp_flag() {
603+
let mut fm = minimal_front_matter();
604+
fm.mcp_servers.insert(
605+
"my-tool".to_string(),
606+
McpConfig::WithOptions(McpOptions {
607+
command: Some("node".to_string()),
608+
..Default::default()
609+
}),
610+
);
611+
let params = generate_copilot_params(&fm);
612+
// Custom MCPs (with command) should NOT appear as --mcp flags
613+
assert!(!params.contains("--mcp my-tool"));
614+
}
615+
616+
#[test]
617+
fn test_copilot_params_builtin_mcp_added_with_mcp_flag() {
618+
let mut fm = minimal_front_matter();
619+
fm.mcp_servers
620+
.insert("ado".to_string(), McpConfig::Enabled(true));
621+
let params = generate_copilot_params(&fm);
622+
assert!(params.contains("--mcp ado"));
623+
}
624+
625+
// ─── sanitize_filename ────────────────────────────────────────────────────
487626

488627
#[test]
489628
fn test_sanitize_filename_basic() {

agentic-pipelines/src/compile/standalone.rs

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,4 +498,107 @@ fn generate_memory_prompt() -> String {
498498
.to_string()
499499
}
500500

501+
#[cfg(test)]
502+
mod tests {
503+
use super::*;
504+
use crate::compile::common::parse_markdown;
505+
use crate::compile::types::{McpConfig, McpOptions};
506+
507+
fn minimal_front_matter() -> FrontMatter {
508+
let (fm, _) = parse_markdown("---\nname: test-agent\ndescription: test\n---\n").unwrap();
509+
fm
510+
}
511+
512+
#[test]
513+
fn test_generate_firewall_config_builtin_simple_enabled() {
514+
let mut fm = minimal_front_matter();
515+
fm.mcp_servers
516+
.insert("ado".to_string(), McpConfig::Enabled(true));
517+
let config = generate_firewall_config(&fm);
518+
let upstream = config.upstreams.get("ado").unwrap();
519+
assert_eq!(upstream.command, "agency");
520+
assert_eq!(upstream.args, vec!["mcp", "ado"]);
521+
assert_eq!(upstream.allowed, vec!["*"]);
522+
}
523+
524+
#[test]
525+
fn test_generate_firewall_config_builtin_with_allowed_list() {
526+
let mut fm = minimal_front_matter();
527+
fm.mcp_servers.insert(
528+
"icm".to_string(),
529+
McpConfig::WithOptions(McpOptions {
530+
allowed: vec!["create_incident".to_string(), "get_incident".to_string()],
531+
..Default::default()
532+
}),
533+
);
534+
let config = generate_firewall_config(&fm);
535+
let upstream = config.upstreams.get("icm").unwrap();
536+
assert_eq!(upstream.command, "agency");
537+
assert_eq!(upstream.args, vec!["mcp", "icm"]);
538+
assert_eq!(
539+
upstream.allowed,
540+
vec!["create_incident".to_string(), "get_incident".to_string()]
541+
);
542+
}
543+
544+
#[test]
545+
fn test_generate_firewall_config_custom_mcp() {
546+
let mut fm = minimal_front_matter();
547+
fm.mcp_servers.insert(
548+
"my-tool".to_string(),
549+
McpConfig::WithOptions(McpOptions {
550+
command: Some("node".to_string()),
551+
args: vec!["server.js".to_string()],
552+
allowed: vec!["do_thing".to_string()],
553+
..Default::default()
554+
}),
555+
);
556+
let config = generate_firewall_config(&fm);
557+
let upstream = config.upstreams.get("my-tool").unwrap();
558+
assert_eq!(upstream.command, "node");
559+
assert_eq!(upstream.args, vec!["server.js"]);
560+
assert_eq!(upstream.allowed, vec!["do_thing"]);
561+
}
501562

563+
#[test]
564+
fn test_generate_firewall_config_custom_mcp_empty_allowed_defaults_to_wildcard() {
565+
let mut fm = minimal_front_matter();
566+
fm.mcp_servers.insert(
567+
"my-tool".to_string(),
568+
McpConfig::WithOptions(McpOptions {
569+
command: Some("python".to_string()),
570+
allowed: vec![],
571+
..Default::default()
572+
}),
573+
);
574+
let config = generate_firewall_config(&fm);
575+
let upstream = config.upstreams.get("my-tool").unwrap();
576+
assert_eq!(upstream.allowed, vec!["*"]);
577+
}
578+
579+
#[test]
580+
fn test_generate_firewall_config_unknown_non_builtin_skipped() {
581+
// An MCP that is neither built-in nor has a command should be skipped
582+
let mut fm = minimal_front_matter();
583+
fm.mcp_servers
584+
.insert("phantom".to_string(), McpConfig::Enabled(true));
585+
let config = generate_firewall_config(&fm);
586+
assert!(!config.upstreams.contains_key("phantom"));
587+
}
588+
589+
#[test]
590+
fn test_generate_firewall_config_disabled_mcp_skipped() {
591+
let mut fm = minimal_front_matter();
592+
fm.mcp_servers
593+
.insert("ado".to_string(), McpConfig::Enabled(false));
594+
let config = generate_firewall_config(&fm);
595+
assert!(!config.upstreams.contains_key("ado"));
596+
}
597+
598+
#[test]
599+
fn test_generate_firewall_config_empty_mcp_servers() {
600+
let fm = minimal_front_matter();
601+
let config = generate_firewall_config(&fm);
602+
assert!(config.upstreams.is_empty());
603+
}
604+
}

agentic-pipelines/src/compile/types.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ pub enum McpConfig {
313313
}
314314

315315
/// Detailed MCP options
316-
#[derive(Debug, Deserialize, Clone)]
316+
#[derive(Debug, Deserialize, Clone, Default)]
317317
pub struct McpOptions {
318318
/// Custom command (if present, it's a custom MCP - standalone only)
319319
#[serde(default)]

agentic-pipelines/src/tools/result.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,3 +228,47 @@ macro_rules! tool_result {
228228
}
229229
};
230230
}
231+
232+
#[cfg(test)]
233+
mod tests {
234+
use super::*;
235+
236+
#[test]
237+
fn test_execution_result_success() {
238+
let r = ExecutionResult::success("all good");
239+
assert!(r.success);
240+
assert_eq!(r.message, "all good");
241+
assert!(r.data.is_none());
242+
}
243+
244+
#[test]
245+
fn test_execution_result_success_with_data() {
246+
let data = serde_json::json!({"id": 42});
247+
let r = ExecutionResult::success_with_data("created", data.clone());
248+
assert!(r.success);
249+
assert_eq!(r.message, "created");
250+
assert_eq!(r.data, Some(data));
251+
}
252+
253+
#[test]
254+
fn test_execution_result_failure() {
255+
let r = ExecutionResult::failure("something broke");
256+
assert!(!r.success);
257+
assert_eq!(r.message, "something broke");
258+
assert!(r.data.is_none());
259+
}
260+
261+
#[test]
262+
fn test_anyhow_to_mcp_error_preserves_message() {
263+
let err = anyhow::anyhow!("test error message");
264+
let mcp_err = anyhow_to_mcp_error(err);
265+
assert!(mcp_err.message.contains("test error message"));
266+
}
267+
268+
#[test]
269+
fn test_anyhow_to_mcp_error_uses_invalid_params_code() {
270+
let err = anyhow::anyhow!("some error");
271+
let mcp_err = anyhow_to_mcp_error(err);
272+
assert_eq!(mcp_err.code, rmcp::model::ErrorCode::INVALID_PARAMS);
273+
}
274+
}

0 commit comments

Comments
 (0)