//! Integration tests: am lab (issue #62) and am plugins (issue #75). mod common; use agent_manager::cli::{Cli, LabArgs, PluginsArgs}; use agent_manager::commands::{lab_cmd, plugins_cmd}; use clap::Parser; use std::path::PathBuf; /// A config with two shell-based fake agents for the lab benchmark. fn lab_config() -> String { concat!( "agents:\n", " - name: bench-ok\n", " installable: false\n", " run: sh\n", " args: [\"-c\", \"echo out: $0\"]\n", " - name: bench-fail\n", " installable: false\n", " run: sh\n", " args: [\"-c\", \"echo boom >&2; exit 3\"]\n", ) .to_string() } #[test] fn lab_runs_a_task_on_every_agent_and_reports() { let dir = std::env::temp_dir().join(format!("am-lab-e2e-{}", std::process::id())); std::fs::create_dir_all(&dir).unwrap(); let cfg = common::write_config(&dir, &lab_config()); let task = dir.join("hello.yaml"); std::fs::write( &task, "name: hello\ndescription: e2e\nargs: [\"hello\"]\ntimeout: 30\n", ) .unwrap(); let cli = Cli::parse_from([ "am", "--config", cfg.to_str().unwrap(), "lab", "--agents", "bench-ok", "--task", "hello.yaml", ]); let mut app = agent_manager::app::App::from_cli(cli).expect("app should build"); common::isolate(&mut app, &dir); let code = lab_cmd::run( &app, &LabArgs { agents: Some("bench-ok, bench-fail".to_string()), task: Some(task.clone()), parallel: false, timeout: None, list: false, }, ) .unwrap(); // One agent fails → the exit code is 1 (CI-friendly). assert_eq!(code, 1); // The benchmark is replayable with the same file. let code2 = lab_cmd::run( &app, &LabArgs { agents: Some("bench-ok".to_string()), task: Some(task), parallel: false, timeout: None, list: false, }, ) .unwrap(); assert_eq!(code2, 0); // Each run was journaled as a lab event. let events = agent_manager::events::read_events(&app.events_dir(), 0); let lab_events = events.iter().filter(|e| e.kind == agent_manager::events::EventKind::Lab); assert!(lab_events.count() >= 3, "expected >= 3 lab events"); let _ = std::fs::remove_dir_all(&dir); } #[test] fn lab_list_shows_the_tasks_of_the_lab_dir() { let dir = std::env::temp_dir().join(format!("am-lab-list-{}", std::process::id())); std::fs::create_dir_all(&dir).unwrap(); let cfg = common::write_config(&dir, "agents: []\n"); let cli = Cli::parse_from(["am", "--config", cfg.to_str().unwrap(), "lab", "--list"]); let mut app = agent_manager::app::App::from_cli(cli).expect("app should build"); common::isolate(&mut app, &dir); std::fs::create_dir_all(app.events_dir().join("lab")).unwrap(); std::fs::write(app.events_dir().join("lab/hello.yaml"), "name: hello\n").unwrap(); let code = lab_cmd::run( &app, &LabArgs { agents: None, task: None, parallel: false, timeout: None, list: true, }, ) .unwrap(); assert_eq!(code, 0); let _ = std::fs::remove_dir_all(&dir); } #[test] fn plugin_runs_on_an_emitted_event() { let dir = std::env::temp_dir().join(format!("am-plugins-e2e-{}", std::process::id())); std::fs::create_dir_all(&dir).unwrap(); let cfg = common::write_config(&dir, "agents: []\n"); let cli = Cli::parse_from(["am", "--config", cfg.to_str().unwrap(), "plugins"]); let mut app = agent_manager::app::App::from_cli(cli).expect("app should build"); common::isolate(&mut app, &dir); // Install an echo plugin: valid JSON on stdout (contract) + a marker // file proving it ran. The absolute path keeps it independent of cwd. let pdir = app.events_dir().join("plugins"); std::fs::create_dir_all(&pdir).unwrap(); let marker = pdir.join("pong.log"); let script = format!( "#!/bin/sh\ncat >/dev/null\necho '{{\"ok\": true, \"message\": \"pong\"}}' | tee '{}'\n", marker.display().to_string().replace('\\', "/") ); std::fs::write(pdir.join("pong.sh"), script).unwrap(); let script_path = pdir.join("pong.sh").display().to_string().replace('\\', "/"); std::fs::write(pdir.join("pong.yaml"), format!("command: \"sh {script_path}\"\n")).unwrap(); // List: the plugin is discovered. let code = plugins_cmd::run(&app, &PluginsArgs { test: None }).unwrap(); assert_eq!(code, 0); // --test: the JSON contract is exercised. let code = plugins_cmd::run(&app, &PluginsArgs { test: Some("pong".to_string()) }).unwrap(); assert_eq!(code, 0); // A real emitted event fires the plugin (acceptance criterion). app.emit(&agent_manager::events::Event::now( agent_manager::events::EventKind::Start, )); assert!(marker.exists(), "the plugin must have run on the emitted event"); let _ = std::fs::remove_dir_all(&dir); } #[test] fn plugins_list_and_test_are_available_in_cli() { let cli = Cli::parse_from(["am", "plugins", "--test", "notify"]); match cli.command { Some(agent_manager::cli::Command::Plugins(args)) => { assert_eq!(args.test.as_deref(), Some("notify")); } other => panic!("unexpected: {other:?}"), } let cli = Cli::parse_from(["am", "lab", "--agents", "a,b", "--task", "t.yaml"]); match cli.command { Some(agent_manager::cli::Command::Lab(args)) => { assert_eq!(args.agents.as_deref(), Some("a,b")); assert_eq!(args.task, Some(PathBuf::from("t.yaml"))); } other => panic!("unexpected: {other:?}"), } }