chore: Remove blocking read strings

This commit is contained in:
Jayson Reis 2026-02-18 15:52:07 +00:00 committed by Chummy
parent bc0be9a3c1
commit b9af601943
26 changed files with 331 additions and 243 deletions

View file

@ -626,8 +626,8 @@ mod tests {
assert!(!token_set.is_expiring_within(Duration::from_secs(1)));
}
#[test]
fn store_roundtrip_with_encryption() {
#[tokio::test]
async fn store_roundtrip_with_encryption() {
let tmp = TempDir::new().unwrap();
let store = AuthProfilesStore::new(tmp.path(), true);
@ -661,14 +661,14 @@ mod tests {
Some("refresh-123")
);
let raw = fs::read_to_string(store.path()).unwrap();
let raw = tokio::fs::read_to_string(store.path()).await.unwrap();
assert!(raw.contains("enc2:"));
assert!(!raw.contains("refresh-123"));
assert!(!raw.contains("access-123"));
}
#[test]
fn atomic_write_replaces_file() {
#[tokio::test]
async fn atomic_write_replaces_file() {
let tmp = TempDir::new().unwrap();
let store = AuthProfilesStore::new(tmp.path(), false);
@ -678,7 +678,7 @@ mod tests {
let path = store.path().to_path_buf();
assert!(path.exists());
let contents = fs::read_to_string(path).unwrap();
let contents = tokio::fs::read_to_string(path).await.unwrap();
assert!(contents.contains("\"schema_version\": 1"));
}
}

View file

@ -3385,8 +3385,8 @@ tool_dispatcher = "xml"
assert_eq!(parsed.agent.tool_dispatcher, "xml");
}
#[test]
fn config_save_and_load_tmpdir() {
#[tokio::test]
async fn config_save_and_load_tmpdir() {
let dir = std::env::temp_dir().join("zeroclaw_test_config");
let _ = fs::remove_dir_all(&dir);
fs::create_dir_all(&dir).unwrap();
@ -3431,7 +3431,7 @@ tool_dispatcher = "xml"
config.save().unwrap();
assert!(config_path.exists());
let contents = fs::read_to_string(&config_path).unwrap();
let contents = tokio::fs::read_to_string(&config_path).await.unwrap();
let loaded: Config = toml::from_str(&contents).unwrap();
assert!(loaded
.api_key
@ -3446,8 +3446,8 @@ tool_dispatcher = "xml"
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn config_save_encrypts_nested_credentials() {
#[tokio::test]
async fn config_save_encrypts_nested_credentials() {
let dir = std::env::temp_dir().join(format!(
"zeroclaw_test_nested_credentials_{}",
uuid::Uuid::new_v4()
@ -3477,7 +3477,9 @@ tool_dispatcher = "xml"
config.save().unwrap();
let contents = fs::read_to_string(config.config_path.clone()).unwrap();
let contents = tokio::fs::read_to_string(config.config_path.clone())
.await
.unwrap();
let stored: Config = toml::from_str(&contents).unwrap();
let store = crate::security::SecretStore::new(&dir, true);
@ -3527,8 +3529,8 @@ tool_dispatcher = "xml"
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn config_save_atomic_cleanup() {
#[tokio::test]
async fn config_save_atomic_cleanup() {
let dir =
std::env::temp_dir().join(format!("zeroclaw_test_config_{}", uuid::Uuid::new_v4()));
fs::create_dir_all(&dir).unwrap();
@ -3545,7 +3547,7 @@ tool_dispatcher = "xml"
config.default_model = Some("model-b".into());
config.save().unwrap();
let contents = fs::read_to_string(&config_path).unwrap();
let contents = tokio::fs::read_to_string(&config_path).await.unwrap();
assert!(contents.contains("model-b"));
let names: Vec<String> = fs::read_dir(&dir)

View file

@ -475,13 +475,13 @@ mod tests {
use chrono::{Duration as ChronoDuration, Utc};
use tempfile::TempDir;
fn test_config(tmp: &TempDir) -> Config {
async fn test_config(tmp: &TempDir) -> Config {
let config = Config {
workspace_dir: tmp.path().join("workspace"),
config_path: tmp.path().join("config.toml"),
..Config::default()
};
std::fs::create_dir_all(&config.workspace_dir).unwrap();
tokio::fs::create_dir_all(&config.workspace_dir).await.unwrap();
config
}
@ -513,7 +513,7 @@ mod tests {
#[tokio::test]
async fn run_job_command_success() {
let tmp = TempDir::new().unwrap();
let config = test_config(&tmp);
let config = test_config(&tmp).await;
let job = test_job("echo scheduler-ok");
let security = SecurityPolicy::from_config(&config.autonomy, &config.workspace_dir);
@ -526,7 +526,7 @@ mod tests {
#[tokio::test]
async fn run_job_command_failure() {
let tmp = TempDir::new().unwrap();
let config = test_config(&tmp);
let config = test_config(&tmp).await;
let job = test_job("ls definitely_missing_file_for_scheduler_test");
let security = SecurityPolicy::from_config(&config.autonomy, &config.workspace_dir);
@ -539,7 +539,7 @@ mod tests {
#[tokio::test]
async fn run_job_command_times_out() {
let tmp = TempDir::new().unwrap();
let mut config = test_config(&tmp);
let mut config = test_config(&tmp).await;
config.autonomy.allowed_commands = vec!["sleep".into()];
let job = test_job("sleep 1");
let security = SecurityPolicy::from_config(&config.autonomy, &config.workspace_dir);
@ -553,7 +553,7 @@ mod tests {
#[tokio::test]
async fn run_job_command_blocks_disallowed_command() {
let tmp = TempDir::new().unwrap();
let mut config = test_config(&tmp);
let mut config = test_config(&tmp).await;
config.autonomy.allowed_commands = vec!["echo".into()];
let job = test_job("curl https://evil.example");
let security = SecurityPolicy::from_config(&config.autonomy, &config.workspace_dir);
@ -567,7 +567,7 @@ mod tests {
#[tokio::test]
async fn run_job_command_blocks_forbidden_path_argument() {
let tmp = TempDir::new().unwrap();
let mut config = test_config(&tmp);
let mut config = test_config(&tmp).await;
config.autonomy.allowed_commands = vec!["cat".into()];
let job = test_job("cat /etc/passwd");
let security = SecurityPolicy::from_config(&config.autonomy, &config.workspace_dir);
@ -582,7 +582,7 @@ mod tests {
#[tokio::test]
async fn run_job_command_blocks_readonly_mode() {
let tmp = TempDir::new().unwrap();
let mut config = test_config(&tmp);
let mut config = test_config(&tmp).await;
config.autonomy.level = crate::security::AutonomyLevel::ReadOnly;
let job = test_job("echo should-not-run");
let security = SecurityPolicy::from_config(&config.autonomy, &config.workspace_dir);
@ -596,7 +596,7 @@ mod tests {
#[tokio::test]
async fn run_job_command_blocks_rate_limited() {
let tmp = TempDir::new().unwrap();
let mut config = test_config(&tmp);
let mut config = test_config(&tmp).await;
config.autonomy.max_actions_per_hour = 0;
let job = test_job("echo should-not-run");
let security = SecurityPolicy::from_config(&config.autonomy, &config.workspace_dir);
@ -610,16 +610,17 @@ mod tests {
#[tokio::test]
async fn execute_job_with_retry_recovers_after_first_failure() {
let tmp = TempDir::new().unwrap();
let mut config = test_config(&tmp);
let mut config = test_config(&tmp).await;
config.reliability.scheduler_retries = 1;
config.reliability.provider_backoff_ms = 1;
config.autonomy.allowed_commands = vec!["sh".into()];
let security = SecurityPolicy::from_config(&config.autonomy, &config.workspace_dir);
std::fs::write(
tokio::fs::write(
config.workspace_dir.join("retry-once.sh"),
"#!/bin/sh\nif [ -f retry-ok.flag ]; then\n echo recovered\n exit 0\nfi\ntouch retry-ok.flag\nexit 1\n",
)
.await
.unwrap();
let job = test_job("sh ./retry-once.sh");
@ -631,7 +632,7 @@ mod tests {
#[tokio::test]
async fn execute_job_with_retry_exhausts_attempts() {
let tmp = TempDir::new().unwrap();
let mut config = test_config(&tmp);
let mut config = test_config(&tmp).await;
config.reliability.scheduler_retries = 1;
config.reliability.provider_backoff_ms = 1;
let security = SecurityPolicy::from_config(&config.autonomy, &config.workspace_dir);
@ -646,7 +647,7 @@ mod tests {
#[tokio::test]
async fn run_agent_job_returns_error_without_provider_key() {
let tmp = TempDir::new().unwrap();
let config = test_config(&tmp);
let config = test_config(&tmp).await;
let mut job = test_job("");
job.job_type = JobType::Agent;
job.prompt = Some("Say hello".into());
@ -662,7 +663,7 @@ mod tests {
#[tokio::test]
async fn persist_job_result_records_run_and_reschedules_shell_job() {
let tmp = TempDir::new().unwrap();
let config = test_config(&tmp);
let config = test_config(&tmp).await;
let job = cron::add_job(&config, "*/5 * * * *", "echo ok").unwrap();
let started = Utc::now();
let finished = started + ChronoDuration::milliseconds(10);
@ -679,7 +680,7 @@ mod tests {
#[tokio::test]
async fn persist_job_result_success_deletes_one_shot() {
let tmp = TempDir::new().unwrap();
let config = test_config(&tmp);
let config = test_config(&tmp).await;
let at = Utc::now() + ChronoDuration::minutes(10);
let job = cron::add_agent_job(
&config,
@ -704,7 +705,7 @@ mod tests {
#[tokio::test]
async fn persist_job_result_failure_disables_one_shot() {
let tmp = TempDir::new().unwrap();
let config = test_config(&tmp);
let config = test_config(&tmp).await;
let at = Utc::now() + ChronoDuration::minutes(10);
let job = cron::add_agent_job(
&config,
@ -730,7 +731,7 @@ mod tests {
#[tokio::test]
async fn deliver_if_configured_handles_none_and_invalid_channel() {
let tmp = TempDir::new().unwrap();
let config = test_config(&tmp);
let config = test_config(&tmp).await;
let mut job = test_job("echo ok");
assert!(deliver_if_configured(&config, &job, "x").await.is_ok());

View file

@ -1381,8 +1381,8 @@ mod tests {
assert_eq!(normalize_max_keys(1, 10_000), 1);
}
#[test]
fn persist_pairing_tokens_writes_config_tokens() {
#[tokio::test]
async fn persist_pairing_tokens_writes_config_tokens() {
let temp = tempfile::tempdir().unwrap();
let config_path = temp.path().join("config.toml");
let workspace_path = temp.path().join("workspace");
@ -1400,7 +1400,7 @@ mod tests {
let shared_config = Arc::new(Mutex::new(config));
persist_pairing_tokens(&shared_config, &guard).unwrap();
let saved = std::fs::read_to_string(config_path).unwrap();
let saved = tokio::fs::read_to_string(config_path).await.unwrap();
let parsed: Config = toml::from_str(&saved).unwrap();
assert_eq!(parsed.gateway.paired_tokens.len(), 1);
let persisted = &parsed.gateway.paired_tokens[0];

View file

@ -608,7 +608,7 @@ exit 1
.iter()
.any(|e| e.content.contains("Rust should stay local-first")));
let context_calls = fs::read_to_string(&marker).unwrap_or_default();
let context_calls = tokio::fs::read_to_string(&marker).await.unwrap_or_default();
assert!(
context_calls.trim().is_empty(),
"Expected local-hit short-circuit; got calls: {context_calls}"
@ -669,7 +669,7 @@ exit 1
assert!(first.is_empty());
assert!(second.is_empty());
let calls = fs::read_to_string(&marker).unwrap_or_default();
let calls = tokio::fs::read_to_string(&marker).await.unwrap_or_default();
assert_eq!(calls.lines().count(), 1);
}
}

View file

@ -229,7 +229,6 @@ impl Memory for MarkdownMemory {
#[cfg(test)]
mod tests {
use super::*;
use std::fs as sync_fs;
use tempfile::TempDir;
fn temp_workspace() -> (TempDir, MarkdownMemory) {
@ -256,7 +255,7 @@ mod tests {
mem.store("pref", "User likes Rust", MemoryCategory::Core, None)
.await
.unwrap();
let content = sync_fs::read_to_string(mem.core_path()).unwrap();
let content = fs::read_to_string(mem.core_path()).await.unwrap();
assert!(content.contains("User likes Rust"));
}
@ -267,7 +266,7 @@ mod tests {
.await
.unwrap();
let path = mem.daily_path();
let content = sync_fs::read_to_string(path).unwrap();
let content = fs::read_to_string(path).await.unwrap();
assert!(content.contains("Finished tests"));
}

View file

@ -4537,8 +4537,8 @@ mod tests {
// ── scaffold_workspace: personalization ─────────────────────
#[test]
fn scaffold_bakes_user_name_into_files() {
#[tokio::test]
async fn scaffold_bakes_user_name_into_files() {
let tmp = TempDir::new().unwrap();
let ctx = ProjectContext {
user_name: "Alice".into(),
@ -4546,21 +4546,25 @@ mod tests {
};
scaffold_workspace(tmp.path(), &ctx).unwrap();
let user_md = fs::read_to_string(tmp.path().join("USER.md")).unwrap();
let user_md = tokio::fs::read_to_string(tmp.path().join("USER.md"))
.await
.unwrap();
assert!(
user_md.contains("**Name:** Alice"),
"USER.md should contain user name"
);
let bootstrap = fs::read_to_string(tmp.path().join("BOOTSTRAP.md")).unwrap();
let bootstrap = tokio::fs::read_to_string(tmp.path().join("BOOTSTRAP.md"))
.await
.unwrap();
assert!(
bootstrap.contains("**Alice**"),
"BOOTSTRAP.md should contain user name"
);
}
#[test]
fn scaffold_bakes_timezone_into_files() {
#[tokio::test]
async fn scaffold_bakes_timezone_into_files() {
let tmp = TempDir::new().unwrap();
let ctx = ProjectContext {
timezone: "US/Pacific".into(),
@ -4568,21 +4572,25 @@ mod tests {
};
scaffold_workspace(tmp.path(), &ctx).unwrap();
let user_md = fs::read_to_string(tmp.path().join("USER.md")).unwrap();
let user_md = tokio::fs::read_to_string(tmp.path().join("USER.md"))
.await
.unwrap();
assert!(
user_md.contains("**Timezone:** US/Pacific"),
"USER.md should contain timezone"
);
let bootstrap = fs::read_to_string(tmp.path().join("BOOTSTRAP.md")).unwrap();
let bootstrap = tokio::fs::read_to_string(tmp.path().join("BOOTSTRAP.md"))
.await
.unwrap();
assert!(
bootstrap.contains("US/Pacific"),
"BOOTSTRAP.md should contain timezone"
);
}
#[test]
fn scaffold_bakes_agent_name_into_files() {
#[tokio::test]
async fn scaffold_bakes_agent_name_into_files() {
let tmp = TempDir::new().unwrap();
let ctx = ProjectContext {
agent_name: "Crabby".into(),
@ -4590,39 +4598,49 @@ mod tests {
};
scaffold_workspace(tmp.path(), &ctx).unwrap();
let identity = fs::read_to_string(tmp.path().join("IDENTITY.md")).unwrap();
let identity = tokio::fs::read_to_string(tmp.path().join("IDENTITY.md"))
.await
.unwrap();
assert!(
identity.contains("**Name:** Crabby"),
"IDENTITY.md should contain agent name"
);
let soul = fs::read_to_string(tmp.path().join("SOUL.md")).unwrap();
let soul = tokio::fs::read_to_string(tmp.path().join("SOUL.md"))
.await
.unwrap();
assert!(
soul.contains("You are **Crabby**"),
"SOUL.md should contain agent name"
);
let agents = fs::read_to_string(tmp.path().join("AGENTS.md")).unwrap();
let agents = tokio::fs::read_to_string(tmp.path().join("AGENTS.md"))
.await
.unwrap();
assert!(
agents.contains("Crabby Personal Assistant"),
"AGENTS.md should contain agent name"
);
let heartbeat = fs::read_to_string(tmp.path().join("HEARTBEAT.md")).unwrap();
let heartbeat = tokio::fs::read_to_string(tmp.path().join("HEARTBEAT.md"))
.await
.unwrap();
assert!(
heartbeat.contains("Crabby"),
"HEARTBEAT.md should contain agent name"
);
let bootstrap = fs::read_to_string(tmp.path().join("BOOTSTRAP.md")).unwrap();
let bootstrap = tokio::fs::read_to_string(tmp.path().join("BOOTSTRAP.md"))
.await
.unwrap();
assert!(
bootstrap.contains("Introduce yourself as Crabby"),
"BOOTSTRAP.md should contain agent name"
);
}
#[test]
fn scaffold_bakes_communication_style() {
#[tokio::test]
async fn scaffold_bakes_communication_style() {
let tmp = TempDir::new().unwrap();
let ctx = ProjectContext {
communication_style: "Be technical and detailed.".into(),
@ -4630,19 +4648,25 @@ mod tests {
};
scaffold_workspace(tmp.path(), &ctx).unwrap();
let soul = fs::read_to_string(tmp.path().join("SOUL.md")).unwrap();
let soul = tokio::fs::read_to_string(tmp.path().join("SOUL.md"))
.await
.unwrap();
assert!(
soul.contains("Be technical and detailed."),
"SOUL.md should contain communication style"
);
let user_md = fs::read_to_string(tmp.path().join("USER.md")).unwrap();
let user_md = tokio::fs::read_to_string(tmp.path().join("USER.md"))
.await
.unwrap();
assert!(
user_md.contains("Be technical and detailed."),
"USER.md should contain communication style"
);
let bootstrap = fs::read_to_string(tmp.path().join("BOOTSTRAP.md")).unwrap();
let bootstrap = tokio::fs::read_to_string(tmp.path().join("BOOTSTRAP.md"))
.await
.unwrap();
assert!(
bootstrap.contains("Be technical and detailed."),
"BOOTSTRAP.md should contain communication style"
@ -4651,19 +4675,23 @@ mod tests {
// ── scaffold_workspace: defaults when context is empty ──────
#[test]
fn scaffold_uses_defaults_for_empty_context() {
#[tokio::test]
async fn scaffold_uses_defaults_for_empty_context() {
let tmp = TempDir::new().unwrap();
let ctx = ProjectContext::default(); // all empty
scaffold_workspace(tmp.path(), &ctx).unwrap();
let identity = fs::read_to_string(tmp.path().join("IDENTITY.md")).unwrap();
let identity = tokio::fs::read_to_string(tmp.path().join("IDENTITY.md"))
.await
.unwrap();
assert!(
identity.contains("**Name:** ZeroClaw"),
"should default agent name to ZeroClaw"
);
let user_md = fs::read_to_string(tmp.path().join("USER.md")).unwrap();
let user_md = tokio::fs::read_to_string(tmp.path().join("USER.md"))
.await
.unwrap();
assert!(
user_md.contains("**Name:** User"),
"should default user name to User"
@ -4673,7 +4701,9 @@ mod tests {
"should default timezone to UTC"
);
let soul = fs::read_to_string(tmp.path().join("SOUL.md")).unwrap();
let soul = tokio::fs::read_to_string(tmp.path().join("SOUL.md"))
.await
.unwrap();
assert!(
soul.contains("Be warm, natural, and clear."),
"should default communication style"
@ -4682,8 +4712,8 @@ mod tests {
// ── scaffold_workspace: skip existing files ─────────────────
#[test]
fn scaffold_does_not_overwrite_existing_files() {
#[tokio::test]
async fn scaffold_does_not_overwrite_existing_files() {
let tmp = TempDir::new().unwrap();
let ctx = ProjectContext {
user_name: "Bob".into(),
@ -4697,7 +4727,7 @@ mod tests {
scaffold_workspace(tmp.path(), &ctx).unwrap();
// SOUL.md should be untouched
let soul = fs::read_to_string(&soul_path).unwrap();
let soul = tokio::fs::read_to_string(&soul_path).await.unwrap();
assert!(
soul.contains("Do not overwrite me"),
"existing files should not be overwritten"
@ -4708,14 +4738,16 @@ mod tests {
);
// But USER.md should be created fresh
let user_md = fs::read_to_string(tmp.path().join("USER.md")).unwrap();
let user_md = tokio::fs::read_to_string(tmp.path().join("USER.md"))
.await
.unwrap();
assert!(user_md.contains("**Name:** Bob"));
}
// ── scaffold_workspace: idempotent ──────────────────────────
#[test]
fn scaffold_is_idempotent() {
#[tokio::test]
async fn scaffold_is_idempotent() {
let tmp = TempDir::new().unwrap();
let ctx = ProjectContext {
user_name: "Eve".into(),
@ -4724,19 +4756,23 @@ mod tests {
};
scaffold_workspace(tmp.path(), &ctx).unwrap();
let soul_v1 = fs::read_to_string(tmp.path().join("SOUL.md")).unwrap();
let soul_v1 = tokio::fs::read_to_string(tmp.path().join("SOUL.md"))
.await
.unwrap();
// Run again — should not change anything
scaffold_workspace(tmp.path(), &ctx).unwrap();
let soul_v2 = fs::read_to_string(tmp.path().join("SOUL.md")).unwrap();
let soul_v2 = tokio::fs::read_to_string(tmp.path().join("SOUL.md"))
.await
.unwrap();
assert_eq!(soul_v1, soul_v2, "scaffold should be idempotent");
}
// ── scaffold_workspace: all files are non-empty ─────────────
#[test]
fn scaffold_files_are_non_empty() {
#[tokio::test]
async fn scaffold_files_are_non_empty() {
let tmp = TempDir::new().unwrap();
let ctx = ProjectContext::default();
scaffold_workspace(tmp.path(), &ctx).unwrap();
@ -4751,20 +4787,22 @@ mod tests {
"BOOTSTRAP.md",
"MEMORY.md",
] {
let content = fs::read_to_string(tmp.path().join(f)).unwrap();
let content = tokio::fs::read_to_string(tmp.path().join(f)).await.unwrap();
assert!(!content.trim().is_empty(), "{f} should not be empty");
}
}
// ── scaffold_workspace: AGENTS.md references on-demand memory
#[test]
fn agents_md_references_on_demand_memory() {
#[tokio::test]
async fn agents_md_references_on_demand_memory() {
let tmp = TempDir::new().unwrap();
let ctx = ProjectContext::default();
scaffold_workspace(tmp.path(), &ctx).unwrap();
let agents = fs::read_to_string(tmp.path().join("AGENTS.md")).unwrap();
let agents = tokio::fs::read_to_string(tmp.path().join("AGENTS.md"))
.await
.unwrap();
assert!(
agents.contains("memory_recall"),
"AGENTS.md should reference memory_recall for on-demand access"
@ -4777,13 +4815,15 @@ mod tests {
// ── scaffold_workspace: MEMORY.md warns about token cost ────
#[test]
fn memory_md_warns_about_token_cost() {
#[tokio::test]
async fn memory_md_warns_about_token_cost() {
let tmp = TempDir::new().unwrap();
let ctx = ProjectContext::default();
scaffold_workspace(tmp.path(), &ctx).unwrap();
let memory = fs::read_to_string(tmp.path().join("MEMORY.md")).unwrap();
let memory = tokio::fs::read_to_string(tmp.path().join("MEMORY.md"))
.await
.unwrap();
assert!(
memory.contains("costs tokens"),
"MEMORY.md should warn about token cost"
@ -4796,13 +4836,15 @@ mod tests {
// ── scaffold_workspace: TOOLS.md lists memory_forget ────────
#[test]
fn tools_md_lists_all_builtin_tools() {
#[tokio::test]
async fn tools_md_lists_all_builtin_tools() {
let tmp = TempDir::new().unwrap();
let ctx = ProjectContext::default();
scaffold_workspace(tmp.path(), &ctx).unwrap();
let tools = fs::read_to_string(tmp.path().join("TOOLS.md")).unwrap();
let tools = tokio::fs::read_to_string(tmp.path().join("TOOLS.md"))
.await
.unwrap();
for tool in &[
"shell",
"file_read",
@ -4826,13 +4868,15 @@ mod tests {
);
}
#[test]
fn soul_md_includes_emoji_awareness_guidance() {
#[tokio::test]
async fn soul_md_includes_emoji_awareness_guidance() {
let tmp = TempDir::new().unwrap();
let ctx = ProjectContext::default();
scaffold_workspace(tmp.path(), &ctx).unwrap();
let soul = fs::read_to_string(tmp.path().join("SOUL.md")).unwrap();
let soul = tokio::fs::read_to_string(tmp.path().join("SOUL.md"))
.await
.unwrap();
assert!(
soul.contains("Use emojis naturally (0-2 max"),
"SOUL.md should include emoji usage guidance"
@ -4845,8 +4889,8 @@ mod tests {
// ── scaffold_workspace: special characters in names ─────────
#[test]
fn scaffold_handles_special_characters_in_names() {
#[tokio::test]
async fn scaffold_handles_special_characters_in_names() {
let tmp = TempDir::new().unwrap();
let ctx = ProjectContext {
user_name: "José María".into(),
@ -4856,17 +4900,21 @@ mod tests {
};
scaffold_workspace(tmp.path(), &ctx).unwrap();
let user_md = fs::read_to_string(tmp.path().join("USER.md")).unwrap();
let user_md = tokio::fs::read_to_string(tmp.path().join("USER.md"))
.await
.unwrap();
assert!(user_md.contains("José María"));
let soul = fs::read_to_string(tmp.path().join("SOUL.md")).unwrap();
let soul = tokio::fs::read_to_string(tmp.path().join("SOUL.md"))
.await
.unwrap();
assert!(soul.contains("ZeroClaw-v2"));
}
// ── scaffold_workspace: full personalization round-trip ─────
#[test]
fn scaffold_full_personalization() {
#[tokio::test]
async fn scaffold_full_personalization() {
let tmp = TempDir::new().unwrap();
let ctx = ProjectContext {
user_name: "Argenis".into(),
@ -4879,27 +4927,39 @@ mod tests {
scaffold_workspace(tmp.path(), &ctx).unwrap();
// Verify every file got personalized
let identity = fs::read_to_string(tmp.path().join("IDENTITY.md")).unwrap();
let identity = tokio::fs::read_to_string(tmp.path().join("IDENTITY.md"))
.await
.unwrap();
assert!(identity.contains("**Name:** Claw"));
let soul = fs::read_to_string(tmp.path().join("SOUL.md")).unwrap();
let soul = tokio::fs::read_to_string(tmp.path().join("SOUL.md"))
.await
.unwrap();
assert!(soul.contains("You are **Claw**"));
assert!(soul.contains("Be friendly, human, and conversational"));
let user_md = fs::read_to_string(tmp.path().join("USER.md")).unwrap();
let user_md = tokio::fs::read_to_string(tmp.path().join("USER.md"))
.await
.unwrap();
assert!(user_md.contains("**Name:** Argenis"));
assert!(user_md.contains("**Timezone:** US/Eastern"));
assert!(user_md.contains("Be friendly, human, and conversational"));
let agents = fs::read_to_string(tmp.path().join("AGENTS.md")).unwrap();
let agents = tokio::fs::read_to_string(tmp.path().join("AGENTS.md"))
.await
.unwrap();
assert!(agents.contains("Claw Personal Assistant"));
let bootstrap = fs::read_to_string(tmp.path().join("BOOTSTRAP.md")).unwrap();
let bootstrap = tokio::fs::read_to_string(tmp.path().join("BOOTSTRAP.md"))
.await
.unwrap();
assert!(bootstrap.contains("**Argenis**"));
assert!(bootstrap.contains("US/Eastern"));
assert!(bootstrap.contains("Introduce yourself as Claw"));
let heartbeat = fs::read_to_string(tmp.path().join("HEARTBEAT.md")).unwrap();
let heartbeat = tokio::fs::read_to_string(tmp.path().join("HEARTBEAT.md"))
.await
.unwrap();
assert!(heartbeat.contains("Claw"));
}

View file

@ -75,7 +75,7 @@ impl Tool for ArduinoUploadTool {
let sketch_dir = temp_dir.join(sketch_name);
let ino_path = sketch_dir.join(format!("{}.ino", sketch_name));
if let Err(e) = std::fs::create_dir_all(&sketch_dir) {
if let Err(e) = tokio::fs::create_dir_all(&sketch_dir).await {
return Ok(ToolResult {
success: false,
output: format!("Failed to create sketch dir: {}", e),
@ -83,8 +83,8 @@ impl Tool for ArduinoUploadTool {
});
}
if let Err(e) = std::fs::write(&ino_path, code) {
let _ = std::fs::remove_dir_all(&temp_dir);
if let Err(e) = tokio::fs::write(&ino_path, code).await {
let _ = tokio::fs::remove_dir_all(&temp_dir).await;
return Ok(ToolResult {
success: false,
output: format!("Failed to write sketch: {}", e),
@ -103,7 +103,7 @@ impl Tool for ArduinoUploadTool {
let compile_output = match compile {
Ok(o) => o,
Err(e) => {
let _ = std::fs::remove_dir_all(&temp_dir);
let _ = tokio::fs::remove_dir_all(&temp_dir).await;
return Ok(ToolResult {
success: false,
output: format!("arduino-cli compile failed: {}", e),
@ -114,7 +114,7 @@ impl Tool for ArduinoUploadTool {
if !compile_output.status.success() {
let stderr = String::from_utf8_lossy(&compile_output.stderr);
let _ = std::fs::remove_dir_all(&temp_dir);
let _ = tokio::fs::remove_dir_all(&temp_dir).await;
return Ok(ToolResult {
success: false,
output: format!("Compile failed:\n{}", stderr),
@ -130,7 +130,7 @@ impl Tool for ArduinoUploadTool {
let upload_output = match upload {
Ok(o) => o,
Err(e) => {
let _ = std::fs::remove_dir_all(&temp_dir);
let _ = tokio::fs::remove_dir_all(&temp_dir).await;
return Ok(ToolResult {
success: false,
output: format!("arduino-cli upload failed: {}", e),
@ -139,7 +139,7 @@ impl Tool for ArduinoUploadTool {
}
};
let _ = std::fs::remove_dir_all(&temp_dir);
let _ = tokio::fs::remove_dir_all(&temp_dir).await;
if !upload_output.status.success() {
let stderr = String::from_utf8_lossy(&upload_output.stderr);

View file

@ -1054,7 +1054,10 @@ impl Provider for OpenAiCompatibleProvider {
let url = self.chat_completions_url();
let response = self
.apply_auth_header(self.http_client().post(&url).json(&native_request), credential)
.apply_auth_header(
self.http_client().post(&url).json(&native_request),
credential,
)
.send()
.await?;

View file

@ -45,14 +45,12 @@ fn is_non_retryable(err: &anyhow::Error) -> bool {
return true;
}
let model_catalog_mismatch = msg_lower.contains("model")
msg_lower.contains("model")
&& (msg_lower.contains("not found")
|| msg_lower.contains("unknown")
|| msg_lower.contains("unsupported")
|| msg_lower.contains("does not exist")
|| msg_lower.contains("invalid"));
model_catalog_mismatch
|| msg_lower.contains("invalid"))
}
/// Check if an error is a rate-limit (429) error.

View file

@ -335,8 +335,8 @@ mod tests {
// ── §8.1 Log rotation tests ─────────────────────────────
#[test]
fn audit_logger_writes_event_when_enabled() -> Result<()> {
#[tokio::test]
async fn audit_logger_writes_event_when_enabled() -> Result<()> {
let tmp = TempDir::new()?;
let config = AuditConfig {
enabled: true,
@ -353,7 +353,7 @@ mod tests {
let log_path = tmp.path().join("audit.log");
assert!(log_path.exists(), "audit log file must be created");
let content = std::fs::read_to_string(&log_path)?;
let content = tokio::fs::read_to_string(&log_path).await?;
assert!(!content.is_empty(), "audit log must not be empty");
let parsed: AuditEvent = serde_json::from_str(content.trim())?;
@ -361,8 +361,8 @@ mod tests {
Ok(())
}
#[test]
fn audit_log_command_event_writes_structured_entry() -> Result<()> {
#[tokio::test]
async fn audit_log_command_event_writes_structured_entry() -> Result<()> {
let tmp = TempDir::new()?;
let config = AuditConfig {
enabled: true,
@ -382,7 +382,7 @@ mod tests {
})?;
let log_path = tmp.path().join("audit.log");
let content = std::fs::read_to_string(&log_path)?;
let content = tokio::fs::read_to_string(&log_path).await?;
let parsed: AuditEvent = serde_json::from_str(content.trim())?;
let action = parsed.action.unwrap();

View file

@ -334,8 +334,8 @@ mod tests {
assert!(!SecretStore::is_encrypted(""));
}
#[test]
fn key_file_created_on_first_encrypt() {
#[tokio::test]
async fn key_file_created_on_first_encrypt() {
let tmp = TempDir::new().unwrap();
let store = SecretStore::new(tmp.path(), true);
assert!(!store.key_path.exists());
@ -343,7 +343,7 @@ mod tests {
store.encrypt("test").unwrap();
assert!(store.key_path.exists(), "Key file should be created");
let key_hex = fs::read_to_string(&store.key_path).unwrap();
let key_hex = tokio::fs::read_to_string(&store.key_path).await.unwrap();
assert_eq!(
key_hex.len(),
KEY_LEN * 2,

View file

@ -191,8 +191,8 @@ mod tests {
}
}
#[test]
fn integrate_creates_files() {
#[tokio::test]
async fn integrate_creates_files() {
let tmp = std::env::temp_dir().join("zeroclaw-test-integrate");
let _ = fs::remove_dir_all(&tmp);
@ -203,11 +203,15 @@ mod tests {
assert!(path.join("SKILL.toml").exists());
assert!(path.join("SKILL.md").exists());
let toml = fs::read_to_string(path.join("SKILL.toml")).unwrap();
let toml = tokio::fs::read_to_string(path.join("SKILL.toml"))
.await
.unwrap();
assert!(toml.contains("name = \"test-skill\""));
assert!(toml.contains("stars = 42"));
let md = fs::read_to_string(path.join("SKILL.md")).unwrap();
let md = tokio::fs::read_to_string(path.join("SKILL.md"))
.await
.unwrap();
assert!(md.contains("# test-skill"));
assert!(md.contains("A test skill for unit tests"));

View file

@ -4,21 +4,21 @@ mod tests {
use std::path::Path;
use tempfile::TempDir;
#[test]
fn test_skills_symlink_unix_edge_cases() {
#[tokio::test]
async fn test_skills_symlink_unix_edge_cases() {
let tmp = TempDir::new().unwrap();
let workspace_dir = tmp.path().join("workspace");
std::fs::create_dir_all(&workspace_dir).unwrap();
tokio::fs::create_dir_all(&workspace_dir).await.unwrap();
let skills_path = skills_dir(&workspace_dir);
std::fs::create_dir_all(&skills_path).unwrap();
tokio::fs::create_dir_all(&skills_path).await.unwrap();
// Test case 1: Valid symlink creation on Unix
#[cfg(unix)]
{
let source_dir = tmp.path().join("source_skill");
std::fs::create_dir_all(&source_dir).unwrap();
std::fs::write(source_dir.join("SKILL.md"), "# Test Skill\nContent").unwrap();
tokio::fs::create_dir_all(&source_dir).await.unwrap();
tokio::fs::write(source_dir.join("SKILL.md"), "# Test Skill\nContent").await.unwrap();
let dest_link = skills_path.join("linked_skill");
@ -31,7 +31,7 @@ mod tests {
assert!(dest_link.is_symlink());
// Verify we can read through symlink
let content = std::fs::read_to_string(dest_link.join("SKILL.md"));
let content = tokio::fs::read_to_string(dest_link.join("SKILL.md")).await;
assert!(content.is_ok());
assert!(content.unwrap().contains("Test Skill"));
@ -45,7 +45,7 @@ mod tests {
);
// But reading through it should fail
let content = std::fs::read_to_string(broken_link.join("SKILL.md"));
let content = tokio::fs::read_to_string(broken_link.join("SKILL.md")).await;
assert!(content.is_err());
}
@ -53,7 +53,7 @@ mod tests {
#[cfg(windows)]
{
let source_dir = tmp.path().join("source_skill");
std::fs::create_dir_all(&source_dir).unwrap();
tokio::fs::create_dir_all(&source_dir).await.unwrap();
let dest_link = skills_path.join("linked_skill");
@ -64,7 +64,7 @@ mod tests {
assert!(!dest_link.exists());
} else {
// Clean up if it succeeded
let _ = std::fs::remove_dir(&dest_link);
let _ = tokio::fs::remove_dir(&dest_link).await;
}
}
@ -80,21 +80,21 @@ mod tests {
assert!(!empty_skills_path.exists());
}
#[test]
fn test_skills_symlink_permissions_and_safety() {
#[tokio::test]
async fn test_skills_symlink_permissions_and_safety() {
let tmp = TempDir::new().unwrap();
let workspace_dir = tmp.path().join("workspace");
std::fs::create_dir_all(&workspace_dir).unwrap();
tokio::fs::create_dir_all(&workspace_dir).await.unwrap();
let skills_path = skills_dir(&workspace_dir);
std::fs::create_dir_all(&skills_path).unwrap();
tokio::fs::create_dir_all(&skills_path).await.unwrap();
#[cfg(unix)]
{
// Test case: Symlink outside workspace should be allowed (user responsibility)
let outside_dir = tmp.path().join("outside_skill");
std::fs::create_dir_all(&outside_dir).unwrap();
std::fs::write(outside_dir.join("SKILL.md"), "# Outside Skill\nContent").unwrap();
tokio::fs::create_dir_all(&outside_dir).await.unwrap();
tokio::fs::write(outside_dir.join("SKILL.md"), "# Outside Skill\nContent").await.unwrap();
let dest_link = skills_path.join("outside_skill");
let result = std::os::unix::fs::symlink(&outside_dir, &dest_link);
@ -104,7 +104,7 @@ mod tests {
);
// Should still be readable
let content = std::fs::read_to_string(dest_link.join("SKILL.md"));
let content = tokio::fs::read_to_string(dest_link.join("SKILL.md")).await;
assert!(content.is_ok());
assert!(content.unwrap().contains("Outside Skill"));
}

View file

@ -1225,8 +1225,9 @@ mod native_backend {
});
if let Some(path_str) = path {
std::fs::write(&path_str, &png)
.with_context(|| format!("Failed to write screenshot to {path_str}"))?;
tokio::fs::write(&path_str, &png).await.with_context(|| {
format!("Failed to write screenshot to {path_str}")
})?;
payload["path"] = Value::String(path_str);
} else {
payload["png_base64"] =

View file

@ -217,13 +217,13 @@ mod tests {
use crate::security::AutonomyLevel;
use tempfile::TempDir;
fn test_config(tmp: &TempDir) -> Arc<Config> {
async fn test_config(tmp: &TempDir) -> Arc<Config> {
let config = Config {
workspace_dir: tmp.path().join("workspace"),
config_path: tmp.path().join("config.toml"),
..Config::default()
};
std::fs::create_dir_all(&config.workspace_dir).unwrap();
tokio::fs::create_dir_all(&config.workspace_dir).await.unwrap();
Arc::new(config)
}
@ -237,7 +237,7 @@ mod tests {
#[tokio::test]
async fn adds_shell_job() {
let tmp = TempDir::new().unwrap();
let cfg = test_config(&tmp);
let cfg = test_config(&tmp).await;
let tool = CronAddTool::new(cfg.clone(), test_security(&cfg));
let result = tool
.execute(json!({
@ -262,7 +262,7 @@ mod tests {
};
config.autonomy.allowed_commands = vec!["echo".into()];
config.autonomy.level = AutonomyLevel::Supervised;
std::fs::create_dir_all(&config.workspace_dir).unwrap();
tokio::fs::create_dir_all(&config.workspace_dir).await.unwrap();
let cfg = Arc::new(config);
let tool = CronAddTool::new(cfg.clone(), test_security(&cfg));
@ -285,7 +285,7 @@ mod tests {
#[tokio::test]
async fn rejects_invalid_schedule() {
let tmp = TempDir::new().unwrap();
let cfg = test_config(&tmp);
let cfg = test_config(&tmp).await;
let tool = CronAddTool::new(cfg.clone(), test_security(&cfg));
let result = tool
@ -307,7 +307,7 @@ mod tests {
#[tokio::test]
async fn agent_job_requires_prompt() {
let tmp = TempDir::new().unwrap();
let cfg = test_config(&tmp);
let cfg = test_config(&tmp).await;
let tool = CronAddTool::new(cfg.clone(), test_security(&cfg));
let result = tool

View file

@ -63,20 +63,20 @@ mod tests {
use crate::config::Config;
use tempfile::TempDir;
fn test_config(tmp: &TempDir) -> Arc<Config> {
async fn test_config(tmp: &TempDir) -> Arc<Config> {
let config = Config {
workspace_dir: tmp.path().join("workspace"),
config_path: tmp.path().join("config.toml"),
..Config::default()
};
std::fs::create_dir_all(&config.workspace_dir).unwrap();
tokio::fs::create_dir_all(&config.workspace_dir).await.unwrap();
Arc::new(config)
}
#[tokio::test]
async fn returns_empty_list_when_no_jobs() {
let tmp = TempDir::new().unwrap();
let cfg = test_config(&tmp);
let cfg = test_config(&tmp).await;
let tool = CronListTool::new(cfg);
let result = tool.execute(json!({})).await.unwrap();
@ -87,7 +87,7 @@ mod tests {
#[tokio::test]
async fn errors_when_cron_disabled() {
let tmp = TempDir::new().unwrap();
let mut cfg = (*test_config(&tmp)).clone();
let mut cfg = (*test_config(&tmp).await).clone();
cfg.cron.enabled = false;
let tool = CronListTool::new(Arc::new(cfg));

View file

@ -76,20 +76,20 @@ mod tests {
use crate::config::Config;
use tempfile::TempDir;
fn test_config(tmp: &TempDir) -> Arc<Config> {
async fn test_config(tmp: &TempDir) -> Arc<Config> {
let config = Config {
workspace_dir: tmp.path().join("workspace"),
config_path: tmp.path().join("config.toml"),
..Config::default()
};
std::fs::create_dir_all(&config.workspace_dir).unwrap();
tokio::fs::create_dir_all(&config.workspace_dir).await.unwrap();
Arc::new(config)
}
#[tokio::test]
async fn removes_existing_job() {
let tmp = TempDir::new().unwrap();
let cfg = test_config(&tmp);
let cfg = test_config(&tmp).await;
let job = cron::add_job(&cfg, "*/5 * * * *", "echo ok").unwrap();
let tool = CronRemoveTool::new(cfg.clone());
@ -101,7 +101,7 @@ mod tests {
#[tokio::test]
async fn errors_when_job_id_missing() {
let tmp = TempDir::new().unwrap();
let cfg = test_config(&tmp);
let cfg = test_config(&tmp).await;
let tool = CronRemoveTool::new(cfg);
let result = tool.execute(json!({})).await.unwrap();

View file

@ -107,20 +107,20 @@ mod tests {
use crate::config::Config;
use tempfile::TempDir;
fn test_config(tmp: &TempDir) -> Arc<Config> {
async fn test_config(tmp: &TempDir) -> Arc<Config> {
let config = Config {
workspace_dir: tmp.path().join("workspace"),
config_path: tmp.path().join("config.toml"),
..Config::default()
};
std::fs::create_dir_all(&config.workspace_dir).unwrap();
tokio::fs::create_dir_all(&config.workspace_dir).await.unwrap();
Arc::new(config)
}
#[tokio::test]
async fn force_runs_job_and_records_history() {
let tmp = TempDir::new().unwrap();
let cfg = test_config(&tmp);
let cfg = test_config(&tmp).await;
let job = cron::add_job(&cfg, "*/5 * * * *", "echo run-now").unwrap();
let tool = CronRunTool::new(cfg.clone());
@ -134,7 +134,7 @@ mod tests {
#[tokio::test]
async fn errors_for_missing_job() {
let tmp = TempDir::new().unwrap();
let cfg = test_config(&tmp);
let cfg = test_config(&tmp).await;
let tool = CronRunTool::new(cfg);
let result = tool

View file

@ -121,20 +121,20 @@ mod tests {
use chrono::{Duration as ChronoDuration, Utc};
use tempfile::TempDir;
fn test_config(tmp: &TempDir) -> Arc<Config> {
async fn test_config(tmp: &TempDir) -> Arc<Config> {
let config = Config {
workspace_dir: tmp.path().join("workspace"),
config_path: tmp.path().join("config.toml"),
..Config::default()
};
std::fs::create_dir_all(&config.workspace_dir).unwrap();
tokio::fs::create_dir_all(&config.workspace_dir).await.unwrap();
Arc::new(config)
}
#[tokio::test]
async fn lists_runs_with_truncation() {
let tmp = TempDir::new().unwrap();
let cfg = test_config(&tmp);
let cfg = test_config(&tmp).await;
let job = cron::add_job(&cfg, "*/5 * * * *", "echo ok").unwrap();
let long_output = "x".repeat(1000);
@ -163,7 +163,7 @@ mod tests {
#[tokio::test]
async fn errors_when_job_id_missing() {
let tmp = TempDir::new().unwrap();
let cfg = test_config(&tmp);
let cfg = test_config(&tmp).await;
let tool = CronRunsTool::new(cfg);
let result = tool.execute(json!({})).await.unwrap();
assert!(!result.success);

View file

@ -111,13 +111,13 @@ mod tests {
use crate::config::Config;
use tempfile::TempDir;
fn test_config(tmp: &TempDir) -> Arc<Config> {
async fn test_config(tmp: &TempDir) -> Arc<Config> {
let config = Config {
workspace_dir: tmp.path().join("workspace"),
config_path: tmp.path().join("config.toml"),
..Config::default()
};
std::fs::create_dir_all(&config.workspace_dir).unwrap();
tokio::fs::create_dir_all(&config.workspace_dir).await.unwrap();
Arc::new(config)
}
@ -131,7 +131,7 @@ mod tests {
#[tokio::test]
async fn updates_enabled_flag() {
let tmp = TempDir::new().unwrap();
let cfg = test_config(&tmp);
let cfg = test_config(&tmp).await;
let job = cron::add_job(&cfg, "*/5 * * * *", "echo ok").unwrap();
let tool = CronUpdateTool::new(cfg.clone(), test_security(&cfg));
@ -156,7 +156,7 @@ mod tests {
..Config::default()
};
config.autonomy.allowed_commands = vec!["echo".into()];
std::fs::create_dir_all(&config.workspace_dir).unwrap();
tokio::fs::create_dir_all(&config.workspace_dir).await.unwrap();
let cfg = Arc::new(config);
let job = cron::add_job(&cfg, "*/5 * * * *", "echo ok").unwrap();
let tool = CronUpdateTool::new(cfg.clone(), test_security(&cfg));

View file

@ -428,7 +428,7 @@ mod tests {
async fn execute_real_file() {
// Create a minimal valid PNG
let dir = std::env::temp_dir().join("zeroclaw_image_info_test");
let _ = std::fs::create_dir_all(&dir);
let _ = tokio::fs::create_dir_all(&dir).await;
let png_path = dir.join("test.png");
// Minimal 1x1 red PNG (67 bytes)
@ -448,7 +448,7 @@ mod tests {
0x49, 0x45, 0x4E, 0x44, // IEND
0xAE, 0x42, 0x60, 0x82, // CRC
];
std::fs::write(&png_path, &png_bytes).unwrap();
tokio::fs::write(&png_path, &png_bytes).await.unwrap();
let tool = ImageInfoTool::new(test_security());
let result = tool
@ -461,13 +461,13 @@ mod tests {
assert!(!result.output.contains("data:"));
// Clean up
let _ = std::fs::remove_dir_all(&dir);
let _ = tokio::fs::remove_dir_all(&dir).await;
}
#[tokio::test]
async fn execute_with_base64() {
let dir = std::env::temp_dir().join("zeroclaw_image_info_b64");
let _ = std::fs::create_dir_all(&dir);
let _ = tokio::fs::create_dir_all(&dir).await;
let png_path = dir.join("test_b64.png");
// Minimal 1x1 PNG
@ -478,7 +478,7 @@ mod tests {
0xD7, 0x63, 0xF8, 0xCF, 0xC0, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0xE2, 0x21, 0xBC,
0x33, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82,
];
std::fs::write(&png_path, &png_bytes).unwrap();
tokio::fs::write(&png_path, &png_bytes).await.unwrap();
let tool = ImageInfoTool::new(test_security());
let result = tool
@ -488,6 +488,6 @@ mod tests {
assert!(result.success);
assert!(result.output.contains("data:image/png;base64,"));
let _ = std::fs::remove_dir_all(&dir);
let _ = tokio::fs::remove_dir_all(&dir).await;
}
}

View file

@ -41,9 +41,10 @@ impl PushoverTool {
)
}
fn get_credentials(&self) -> anyhow::Result<(String, String)> {
async fn get_credentials(&self) -> anyhow::Result<(String, String)> {
let env_path = self.workspace_dir.join(".env");
let content = std::fs::read_to_string(&env_path)
let content = tokio::fs::read_to_string(&env_path)
.await
.map_err(|e| anyhow::anyhow!("Failed to read {}: {}", env_path.display(), e))?;
let mut token = None;
@ -153,7 +154,7 @@ impl Tool for PushoverTool {
let sound = args.get("sound").and_then(|v| v.as_str()).map(String::from);
let (token, user_key) = self.get_credentials()?;
let (token, user_key) = self.get_credentials().await?;
let mut form = reqwest::multipart::Form::new()
.text("token", token)
@ -269,8 +270,8 @@ mod tests {
assert!(required.contains(&serde_json::Value::String("message".to_string())));
}
#[test]
fn credentials_parsed_from_env_file() {
#[tokio::test]
async fn credentials_parsed_from_env_file() {
let tmp = TempDir::new().unwrap();
let env_path = tmp.path().join(".env");
fs::write(
@ -283,7 +284,7 @@ mod tests {
test_security(AutonomyLevel::Full, 100),
tmp.path().to_path_buf(),
);
let result = tool.get_credentials();
let result = tool.get_credentials().await;
assert!(result.is_ok());
let (token, user_key) = result.unwrap();
@ -291,20 +292,20 @@ mod tests {
assert_eq!(user_key, "userkey456");
}
#[test]
fn credentials_fail_without_env_file() {
#[tokio::test]
async fn credentials_fail_without_env_file() {
let tmp = TempDir::new().unwrap();
let tool = PushoverTool::new(
test_security(AutonomyLevel::Full, 100),
tmp.path().to_path_buf(),
);
let result = tool.get_credentials();
let result = tool.get_credentials().await;
assert!(result.is_err());
}
#[test]
fn credentials_fail_without_token() {
#[tokio::test]
async fn credentials_fail_without_token() {
let tmp = TempDir::new().unwrap();
let env_path = tmp.path().join(".env");
fs::write(&env_path, "PUSHOVER_USER_KEY=userkey456\n").unwrap();
@ -313,13 +314,13 @@ mod tests {
test_security(AutonomyLevel::Full, 100),
tmp.path().to_path_buf(),
);
let result = tool.get_credentials();
let result = tool.get_credentials().await;
assert!(result.is_err());
}
#[test]
fn credentials_fail_without_user_key() {
#[tokio::test]
async fn credentials_fail_without_user_key() {
let tmp = TempDir::new().unwrap();
let env_path = tmp.path().join(".env");
fs::write(&env_path, "PUSHOVER_TOKEN=testtoken123\n").unwrap();
@ -328,13 +329,13 @@ mod tests {
test_security(AutonomyLevel::Full, 100),
tmp.path().to_path_buf(),
);
let result = tool.get_credentials();
let result = tool.get_credentials().await;
assert!(result.is_err());
}
#[test]
fn credentials_ignore_comments() {
#[tokio::test]
async fn credentials_ignore_comments() {
let tmp = TempDir::new().unwrap();
let env_path = tmp.path().join(".env");
fs::write(&env_path, "# This is a comment\nPUSHOVER_TOKEN=realtoken\n# Another comment\nPUSHOVER_USER_KEY=realuser\n").unwrap();
@ -343,7 +344,7 @@ mod tests {
test_security(AutonomyLevel::Full, 100),
tmp.path().to_path_buf(),
);
let result = tool.get_credentials();
let result = tool.get_credentials().await;
assert!(result.is_ok());
let (token, user_key) = result.unwrap();
@ -371,8 +372,8 @@ mod tests {
assert!(schema["properties"].get("sound").is_some());
}
#[test]
fn credentials_support_export_and_quoted_values() {
#[tokio::test]
async fn credentials_support_export_and_quoted_values() {
let tmp = TempDir::new().unwrap();
let env_path = tmp.path().join(".env");
fs::write(
@ -385,7 +386,7 @@ mod tests {
test_security(AutonomyLevel::Full, 100),
tmp.path().to_path_buf(),
);
let result = tool.get_credentials();
let result = tool.get_credentials().await;
assert!(result.is_ok());
let (token, user_key) = result.unwrap();

View file

@ -368,14 +368,14 @@ mod tests {
use crate::security::AutonomyLevel;
use tempfile::TempDir;
fn test_setup() -> (TempDir, Config, Arc<SecurityPolicy>) {
async fn test_setup() -> (TempDir, Config, Arc<SecurityPolicy>) {
let tmp = TempDir::new().unwrap();
let config = Config {
workspace_dir: tmp.path().join("workspace"),
config_path: tmp.path().join("config.toml"),
..Config::default()
};
std::fs::create_dir_all(&config.workspace_dir).unwrap();
tokio::fs::create_dir_all(&config.workspace_dir).await.unwrap();
let security = Arc::new(SecurityPolicy::from_config(
&config.autonomy,
&config.workspace_dir,
@ -383,9 +383,9 @@ mod tests {
(tmp, config, security)
}
#[test]
fn tool_name_and_schema() {
let (_tmp, config, security) = test_setup();
#[tokio::test]
async fn tool_name_and_schema() {
let (_tmp, config, security) = test_setup().await;
let tool = ScheduleTool::new(security, config);
assert_eq!(tool.name(), "schedule");
let schema = tool.parameters_schema();
@ -394,7 +394,7 @@ mod tests {
#[tokio::test]
async fn list_empty() {
let (_tmp, config, security) = test_setup();
let (_tmp, config, security) = test_setup().await;
let tool = ScheduleTool::new(security, config);
let result = tool.execute(json!({"action": "list"})).await.unwrap();
@ -404,7 +404,7 @@ mod tests {
#[tokio::test]
async fn create_get_and_cancel_roundtrip() {
let (_tmp, config, security) = test_setup();
let (_tmp, config, security) = test_setup().await;
let tool = ScheduleTool::new(security, config);
let create = tool
@ -440,7 +440,7 @@ mod tests {
#[tokio::test]
async fn once_and_pause_resume_aliases_work() {
let (_tmp, config, security) = test_setup();
let (_tmp, config, security) = test_setup().await;
let tool = ScheduleTool::new(security, config);
let once = tool
@ -489,7 +489,7 @@ mod tests {
},
..Config::default()
};
std::fs::create_dir_all(&config.workspace_dir).unwrap();
tokio::fs::create_dir_all(&config.workspace_dir).await.unwrap();
let security = Arc::new(SecurityPolicy::from_config(
&config.autonomy,
&config.workspace_dir,
@ -514,7 +514,7 @@ mod tests {
#[tokio::test]
async fn unknown_action_returns_failure() {
let (_tmp, config, security) = test_setup();
let (_tmp, config, security) = test_setup().await;
let tool = ScheduleTool::new(security, config);
let result = tool.execute(json!({"action": "explode"})).await.unwrap();

View file

@ -363,7 +363,7 @@ mod tests {
.unwrap();
assert!(allowed.success);
let _ = std::fs::remove_file(std::env::temp_dir().join("zeroclaw_shell_approval_test"));
let _ = tokio::fs::remove_file(std::env::temp_dir().join("zeroclaw_shell_approval_test")).await;
}
// ── §5.2 Shell timeout enforcement tests ─────────────────

View file

@ -6,7 +6,6 @@
//! 3. All build-essential paths are NOT excluded
//! 4. Pattern syntax is valid
use std::fs;
use std::path::Path;
/// Paths that MUST be excluded from Docker build context (security/performance)
@ -96,8 +95,8 @@ fn is_excluded(patterns: &[String], path: &str) -> bool {
excluded
}
#[test]
fn dockerignore_file_exists() {
#[tokio::test]
async fn dockerignore_file_exists() {
let path = Path::new(env!("CARGO_MANIFEST_DIR")).join(".dockerignore");
assert!(
path.exists(),
@ -105,10 +104,12 @@ fn dockerignore_file_exists() {
);
}
#[test]
fn dockerignore_excludes_security_critical_paths() {
#[tokio::test]
async fn dockerignore_excludes_security_critical_paths() {
let path = Path::new(env!("CARGO_MANIFEST_DIR")).join(".dockerignore");
let content = fs::read_to_string(&path).expect("Failed to read .dockerignore");
let content = tokio::fs::read_to_string(&path)
.await
.expect("Failed to read .dockerignore");
let patterns = parse_dockerignore(&content);
for must_exclude in MUST_EXCLUDE {
@ -129,10 +130,12 @@ fn dockerignore_excludes_security_critical_paths() {
}
}
#[test]
fn dockerignore_does_not_exclude_build_essentials() {
#[tokio::test]
async fn dockerignore_does_not_exclude_build_essentials() {
let path = Path::new(env!("CARGO_MANIFEST_DIR")).join(".dockerignore");
let content = fs::read_to_string(&path).expect("Failed to read .dockerignore");
let content = tokio::fs::read_to_string(&path)
.await
.expect("Failed to read .dockerignore");
let patterns = parse_dockerignore(&content);
for must_include in MUST_INCLUDE {
@ -144,10 +147,12 @@ fn dockerignore_does_not_exclude_build_essentials() {
}
}
#[test]
fn dockerignore_excludes_git_directory() {
#[tokio::test]
async fn dockerignore_excludes_git_directory() {
let path = Path::new(env!("CARGO_MANIFEST_DIR")).join(".dockerignore");
let content = fs::read_to_string(&path).expect("Failed to read .dockerignore");
let content = tokio::fs::read_to_string(&path)
.await
.expect("Failed to read .dockerignore");
let patterns = parse_dockerignore(&content);
// .git directory and its contents must be excluded
@ -162,10 +167,12 @@ fn dockerignore_excludes_git_directory() {
);
}
#[test]
fn dockerignore_excludes_target_directory() {
#[tokio::test]
async fn dockerignore_excludes_target_directory() {
let path = Path::new(env!("CARGO_MANIFEST_DIR")).join(".dockerignore");
let content = fs::read_to_string(&path).expect("Failed to read .dockerignore");
let content = tokio::fs::read_to_string(&path)
.await
.expect("Failed to read .dockerignore");
let patterns = parse_dockerignore(&content);
assert!(is_excluded(&patterns, "target"), "target must be excluded");
@ -179,10 +186,12 @@ fn dockerignore_excludes_target_directory() {
);
}
#[test]
fn dockerignore_excludes_database_files() {
#[tokio::test]
async fn dockerignore_excludes_database_files() {
let path = Path::new(env!("CARGO_MANIFEST_DIR")).join(".dockerignore");
let content = fs::read_to_string(&path).expect("Failed to read .dockerignore");
let content = tokio::fs::read_to_string(&path)
.await
.expect("Failed to read .dockerignore");
let patterns = parse_dockerignore(&content);
assert!(
@ -199,10 +208,12 @@ fn dockerignore_excludes_database_files() {
);
}
#[test]
fn dockerignore_excludes_markdown_files() {
#[tokio::test]
async fn dockerignore_excludes_markdown_files() {
let path = Path::new(env!("CARGO_MANIFEST_DIR")).join(".dockerignore");
let content = fs::read_to_string(&path).expect("Failed to read .dockerignore");
let content = tokio::fs::read_to_string(&path)
.await
.expect("Failed to read .dockerignore");
let patterns = parse_dockerignore(&content);
assert!(
@ -219,10 +230,12 @@ fn dockerignore_excludes_markdown_files() {
);
}
#[test]
fn dockerignore_excludes_image_files() {
#[tokio::test]
async fn dockerignore_excludes_image_files() {
let path = Path::new(env!("CARGO_MANIFEST_DIR")).join(".dockerignore");
let content = fs::read_to_string(&path).expect("Failed to read .dockerignore");
let content = tokio::fs::read_to_string(&path)
.await
.expect("Failed to read .dockerignore");
let patterns = parse_dockerignore(&content);
assert!(
@ -235,10 +248,12 @@ fn dockerignore_excludes_image_files() {
);
}
#[test]
fn dockerignore_excludes_env_files() {
#[tokio::test]
async fn dockerignore_excludes_env_files() {
let path = Path::new(env!("CARGO_MANIFEST_DIR")).join(".dockerignore");
let content = fs::read_to_string(&path).expect("Failed to read .dockerignore");
let content = tokio::fs::read_to_string(&path)
.await
.expect("Failed to read .dockerignore");
let patterns = parse_dockerignore(&content);
assert!(
@ -247,10 +262,12 @@ fn dockerignore_excludes_env_files() {
);
}
#[test]
fn dockerignore_excludes_ci_configs() {
#[tokio::test]
async fn dockerignore_excludes_ci_configs() {
let path = Path::new(env!("CARGO_MANIFEST_DIR")).join(".dockerignore");
let content = fs::read_to_string(&path).expect("Failed to read .dockerignore");
let content = tokio::fs::read_to_string(&path)
.await
.expect("Failed to read .dockerignore");
let patterns = parse_dockerignore(&content);
assert!(
@ -263,10 +280,12 @@ fn dockerignore_excludes_ci_configs() {
);
}
#[test]
fn dockerignore_has_valid_syntax() {
#[tokio::test]
async fn dockerignore_has_valid_syntax() {
let path = Path::new(env!("CARGO_MANIFEST_DIR")).join(".dockerignore");
let content = fs::read_to_string(&path).expect("Failed to read .dockerignore");
let content = tokio::fs::read_to_string(&path)
.await
.expect("Failed to read .dockerignore");
for (line_num, line) in content.lines().enumerate() {
let trimmed = line.trim();
@ -294,8 +313,8 @@ fn dockerignore_has_valid_syntax() {
}
}
#[test]
fn dockerignore_pattern_matching_edge_cases() {
#[tokio::test]
async fn dockerignore_pattern_matching_edge_cases() {
// Test the pattern matching logic itself
let patterns = vec![
".git".to_string(),