#[serial_test::serial]
#[tokio::test]
async fn mechanic_json_repair_mode_creates_default_layout() {
let home = tempfile::tempdir().unwrap();
let _home_guard = EnvGuard::set("HOME", home.path().to_str().unwrap());
let roboticus_dir = home.path().join(".roboticus");
let logs_dir = roboticus_dir.join("logs");
std::fs::create_dir_all(&logs_dir).unwrap();
std::fs::write(
logs_dir.join("roboticus.log"),
"Telegram API error\",\"status\":\"404 Not Found\"\n\
Telegram API error\",\"status\":\"404 Not Found\"\n\
Telegram API error\",\"status\":\"404 Not Found\"\n\
unknown action: unknown\nunknown action: unknown\nunknown action: unknown\n",
)
.unwrap();
let state_db = roboticus_dir.join("state.db");
let conn = rusqlite::Connection::open(&state_db).unwrap();
conn.execute_batch(
"CREATE TABLE sub_agents (role TEXT, skills_json TEXT);
INSERT INTO sub_agents (role, skills_json) VALUES ('specialist', NULL);",
)
.unwrap();
drop(conn);
let wallet = roboticus_dir.join("wallet.json");
std::fs::write(&wallet, "{}").unwrap();
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = std::fs::metadata(&wallet).unwrap().permissions();
perms.set_mode(0o644);
std::fs::set_permissions(&wallet, perms).unwrap();
}
cmd_mechanic_json("http://127.0.0.1:9", true, &[])
.await
.expect("mechanic should complete with unreachable gateway");
let conn = rusqlite::Connection::open(&state_db).unwrap();
let role: String = conn
.query_row("SELECT role FROM sub_agents LIMIT 1", [], |r| r.get(0))
.unwrap();
assert_eq!(role, "subagent");
}
#[serial_test::serial]
#[test]
fn cmd_reset_yes_removes_state_and_preserves_wallet() {
let home = tempfile::tempdir().unwrap();
let _home_guard = EnvGuard::set("HOME", home.path().to_str().unwrap());
let roboticus_dir = home.path().join(".roboticus");
let logs_dir = roboticus_dir.join("logs");
std::fs::create_dir_all(&logs_dir).unwrap();
std::fs::write(roboticus_dir.join("state.db"), "db").unwrap();
std::fs::write(
roboticus_dir.join("roboticus.toml"),
"[agent]\nname='x'\nid='x'\n",
)
.unwrap();
std::fs::write(roboticus_dir.join("wallet.json"), "{}").unwrap();
cmd_reset(true).unwrap();
assert!(!roboticus_dir.join("state.db").exists());
assert!(!roboticus_dir.join("roboticus.toml").exists());
assert!(!logs_dir.exists());
assert!(roboticus_dir.join("wallet.json").exists());
}
#[serial_test::serial]
#[test]
fn cmd_uninstall_purge_removes_data_dir() {
let home = tempfile::tempdir().unwrap();
let _home_guard = EnvGuard::set("HOME", home.path().to_str().unwrap());
let roboticus_dir = home.path().join(".roboticus");
std::fs::create_dir_all(&roboticus_dir).unwrap();
std::fs::write(roboticus_dir.join("state.db"), "db").unwrap();
cmd_uninstall(true, None).unwrap();
assert!(!roboticus_dir.exists());
}
#[test]
fn cmd_completion_bash_succeeds() {
cmd_completion("bash").unwrap();
}
#[test]
fn cmd_completion_zsh_succeeds() {
cmd_completion("zsh").unwrap();
}
#[test]
fn cmd_completion_fish_succeeds() {
cmd_completion("fish").unwrap();
}
#[test]
fn cmd_completion_unknown_shell_succeeds() {
cmd_completion("powershell").unwrap();
}
#[test]
fn count_occurrences_empty_haystack() {
assert_eq!(count_occurrences("", "needle"), 0);
}
#[test]
fn count_occurrences_empty_needle() {
assert!(count_occurrences("abc", "") >= 3);
}
#[test]
fn count_occurrences_no_match() {
assert_eq!(count_occurrences("hello world", "xyz"), 0);
}
#[test]
fn count_occurrences_overlapping_needles() {
assert_eq!(count_occurrences("aaa", "aa"), 1);
}
#[test]
fn recent_log_snapshot_empty_dir() {
let dir = tempfile::tempdir().unwrap();
assert!(recent_log_snapshot(dir.path(), 1024).is_none());
}
#[test]
fn recent_log_snapshot_nonexistent_dir() {
assert!(recent_log_snapshot(Path::new("/nonexistent/path"), 1024).is_none());
}
#[test]
fn recent_log_snapshot_ignores_non_log_files() {
let dir = tempfile::tempdir().unwrap();
std::fs::write(dir.path().join("readme.txt"), "not a log").unwrap();
assert!(recent_log_snapshot(dir.path(), 1024).is_none());
}
#[test]
fn recent_log_snapshot_max_bytes_truncates() {
let dir = tempfile::tempdir().unwrap();
std::fs::write(dir.path().join("roboticus.log"), "a]".repeat(500)).unwrap();
let snap = recent_log_snapshot(dir.path(), 10).unwrap();
assert!(snap.len() <= 20); }
#[test]
fn go_bin_candidates_with_gopath() {
let candidates = go_bin_candidates_with(Some("/custom/go/path"));
assert!(candidates.contains(&PathBuf::from("/custom/go/path/bin")));
}
#[test]
fn go_bin_candidates_without_gopath() {
let candidates = go_bin_candidates_with(None);
assert!(!candidates.is_empty() || std::env::var("HOME").is_err());
}
#[test]
fn find_gosh_in_go_bins_with_no_gosh() {
let dir = tempfile::tempdir().unwrap();
let bin = dir.path().join("bin");
std::fs::create_dir_all(&bin).unwrap();
let temp_gosh_path = bin.join("gosh");
assert!(!temp_gosh_path.is_file());
if let Some(found) = find_gosh_in_go_bins_with(dir.path().to_str()) {
assert!(
!found.starts_with(dir.path()),
"found gosh in temp dir, but we didn't put one there"
);
}
}
#[test]
fn path_contains_dir_in_empty_path_var() {
let path_var = std::ffi::OsString::from("");
let probe = if cfg!(windows) { r"C:\Windows" } else { "/usr/bin" };
assert!(!path_contains_dir_in(Path::new(probe), &path_var));
}
#[test]
fn path_contains_dir_in_multiple_entries() {
if cfg!(windows) {
let path_var =
std::ffi::OsString::from(r"C:\Windows;C:\Users\bin;C:\opt");
assert!(path_contains_dir_in(Path::new(r"C:\Users\bin"), &path_var));
assert!(!path_contains_dir_in(Path::new(r"C:\Users"), &path_var));
} else {
let path_var = std::ffi::OsString::from("/usr/bin:/usr/local/bin:/opt/bin");
assert!(path_contains_dir_in(Path::new("/usr/local/bin"), &path_var));
assert!(!path_contains_dir_in(Path::new("/usr/local"), &path_var));
}
}
#[test]
fn normalize_schema_safe_nonexistent_db() {
assert!(!normalize_schema_safe(Path::new("/nonexistent/path.db")).unwrap());
}
#[test]
fn normalize_schema_safe_idempotent() {
let dir = tempfile::tempdir().unwrap();
let db_path = dir.path().join("state.db");
let conn = rusqlite::Connection::open(&db_path).unwrap();
conn.execute_batch(
"CREATE TABLE sub_agents (role TEXT, skills_json TEXT);
INSERT INTO sub_agents (role, skills_json) VALUES ('subagent', '[]');",
)
.unwrap();
drop(conn);
assert!(!normalize_schema_safe(&db_path).unwrap());
let conn = rusqlite::Connection::open(&db_path).unwrap();
let role: String = conn
.query_row("SELECT role FROM sub_agents LIMIT 1", [], |r| r.get(0))
.unwrap();
assert_eq!(role, "subagent");
}
#[test]
fn finding_builder_fields_all_populated() {
let f = finding(
"test-id",
"low",
0.5,
"A summary",
"Detailed explanation",
"Plan description",
vec!["cmd1".into(), "cmd2".into()],
false,
true,
);
assert_eq!(f.id, "test-id");
assert_eq!(f.severity, "low");
assert!((f.confidence - 0.5).abs() < f64::EPSILON);
assert_eq!(f.summary, "A summary");
assert_eq!(f.details, "Detailed explanation");
assert!(!f.repair_plan.safe_auto_repair);
assert!(f.repair_plan.requires_human_approval);
assert_eq!(f.repair_plan.commands.len(), 2);
assert!(!f.auto_repaired);
}
#[test]
fn cmd_security_audit_warns_on_plaintext_api_keys() {
let cfg_dir = tempfile::tempdir().unwrap();
let cfg_path = cfg_dir.path().join("roboticus.toml");
std::fs::write(
&cfg_path,
r#"[agent]
name = "Test"
id = "test"
api_key = "sk-1234567890"
[server]
bind = "0.0.0.0"
port = 18789
[database]
path = ":memory:"
[models]
primary = "ollama/qwen3:8b"
[cors]
allowed_origins = ["*"]
"#,
)
.unwrap();
cmd_security_audit(cfg_path.to_str().unwrap(), false).unwrap();
}
#[test]
fn cmd_security_audit_nonexistent_config() {
cmd_security_audit("/nonexistent/path/roboticus.toml", false).unwrap();
}
#[serial_test::serial]
#[test]
fn cmd_security_audit_runs_against_local_config() {
let home = tempfile::tempdir().unwrap();
let _home_guard = EnvGuard::set("HOME", home.path().to_str().unwrap());
let cfg_dir = tempfile::tempdir().unwrap();
let cfg_path = cfg_dir.path().join("roboticus.toml");
std::fs::write(
&cfg_path,
r#"[agent]
name = "Test"
id = "test"
[server]
bind = "127.0.0.1"
port = 18789
[database]
path = ":memory:"
[models]
primary = "ollama/qwen3:8b"
"#,
)
.unwrap();
cmd_security_audit(cfg_path.to_str().unwrap(), false).unwrap();
}
#[serial_test::serial]
#[test]
fn resolve_security_audit_config_path_falls_back_to_home_default() {
let home = tempfile::tempdir().unwrap();
let _home_guard = EnvGuard::set("HOME", home.path().to_str().unwrap());
let roboticus_dir = home.path().join(".roboticus");
std::fs::create_dir_all(&roboticus_dir).unwrap();
let home_cfg = roboticus_dir.join("roboticus.toml");
std::fs::write(&home_cfg, "[server]\nport = 18789\n").unwrap();
let resolved = resolve_security_audit_config_path("roboticus.toml");
assert_eq!(resolved, home_cfg);
}