use assert_cmd::Command;
use predicates::prelude::*;
use std::fs;
use std::path::Path;
use tempfile::TempDir;
#[test]
#[ignore] fn test_error_missing_manifest() {
let temp = TempDir::new().unwrap();
Command::cargo_bin("skill")
.unwrap()
.current_dir(temp.path())
.arg("claude")
.arg("generate")
.assert()
.failure()
.stderr(predicate::str::contains("manifest").or(predicate::str::contains("not found")));
}
#[test]
#[ignore] fn test_error_invalid_toml_manifest() {
let temp = TempDir::new().unwrap();
let manifest_path = temp.path().join(".skill-engine.toml");
fs::write(&manifest_path, "invalid { toml [ syntax").unwrap();
Command::cargo_bin("skill")
.unwrap()
.current_dir(temp.path())
.arg("claude")
.arg("generate")
.assert()
.failure()
.stderr(
predicate::str::contains("TOML")
.or(predicate::str::contains("parse"))
.or(predicate::str::contains("invalid")),
);
}
#[test]
#[ignore] fn test_error_missing_required_manifest_fields() {
let temp = TempDir::new().unwrap();
let manifest_path = temp.path().join(".skill-engine.toml");
fs::write(
&manifest_path,
r#"
[skills.incomplete]
# Missing description and other required fields
source = "./incomplete"
"#,
)
.unwrap();
Command::cargo_bin("skill")
.unwrap()
.current_dir(temp.path())
.arg("claude")
.arg("generate")
.assert()
.failure()
.stderr(
predicate::str::contains("required")
.or(predicate::str::contains("missing"))
.or(predicate::str::contains("description")),
);
}
#[test]
#[ignore] fn test_error_invalid_skill_name() {
let temp = TempDir::new().unwrap();
let manifest_path = temp.path().join(".skill-engine.toml");
fs::write(
&manifest_path,
r#"
[skills.valid-skill]
source = "./valid"
runtime = "wasm"
description = "A valid skill"
"#,
)
.unwrap();
Command::cargo_bin("skill")
.unwrap()
.current_dir(temp.path())
.arg("claude")
.arg("generate")
.arg("--skill")
.arg("nonexistent-skill")
.assert()
.failure()
.stderr(
predicate::str::contains("not found")
.and(predicate::str::contains("nonexistent-skill")),
);
}
#[test]
#[ignore] fn test_error_no_skills_in_manifest() {
let temp = TempDir::new().unwrap();
let manifest_path = temp.path().join(".skill-engine.toml");
fs::write(&manifest_path, "# Empty manifest\n").unwrap();
Command::cargo_bin("skill")
.unwrap()
.current_dir(temp.path())
.arg("claude")
.arg("generate")
.assert()
.failure()
.stderr(
predicate::str::contains("No skills")
.or(predicate::str::contains("empty"))
.or(predicate::str::contains("found")),
);
}
#[test]
#[ignore] #[cfg(unix)]
fn test_error_output_dir_not_writable() {
use std::os::unix::fs::PermissionsExt;
let temp = TempDir::new().unwrap();
let manifest_path = temp.path().join(".skill-engine.toml");
fs::write(
&manifest_path,
r#"
[skills.test-skill]
source = "./test"
runtime = "wasm"
description = "Test skill"
"#,
)
.unwrap();
let output_dir = temp.path().join("readonly");
fs::create_dir(&output_dir).unwrap();
let mut perms = fs::metadata(&output_dir).unwrap().permissions();
perms.set_mode(0o444); fs::set_permissions(&output_dir, perms).unwrap();
Command::cargo_bin("skill")
.unwrap()
.current_dir(temp.path())
.arg("claude")
.arg("generate")
.arg("--output")
.arg(&output_dir)
.assert()
.failure()
.stderr(
predicate::str::contains("permission")
.or(predicate::str::contains("Permission"))
.or(predicate::str::contains("denied")),
);
let mut perms = fs::metadata(&output_dir).unwrap().permissions();
perms.set_mode(0o755);
fs::set_permissions(&output_dir, perms).unwrap();
}
#[test]
#[ignore] fn test_error_script_generation_failure() {
let temp = TempDir::new().unwrap();
let manifest_path = temp.path().join(".skill-engine.toml");
fs::write(
&manifest_path,
r#"
[skills.test-skill]
source = "./test"
runtime = "wasm"
description = "Test skill"
"#,
)
.unwrap();
let output_dir = temp.path().join("skills");
fs::create_dir_all(&output_dir).unwrap();
let skill_dir = output_dir.join("test-skill");
fs::create_dir(&skill_dir).unwrap();
let scripts_path = skill_dir.join("scripts");
fs::write(&scripts_path, "I am a file, not a directory").unwrap();
Command::cargo_bin("skill")
.unwrap()
.current_dir(temp.path())
.arg("claude")
.arg("generate")
.arg("--output")
.arg(&output_dir)
.arg("--force")
.assert()
.failure();
}
#[test]
#[ignore] fn test_concurrent_generation_safety() {
use std::sync::Arc;
use std::thread;
let temp = Arc::new(TempDir::new().unwrap());
let manifest_path = temp.path().join(".skill-engine.toml");
fs::write(
&manifest_path,
r#"
[skills.test-skill]
source = "./test"
runtime = "wasm"
description = "Test skill for concurrency"
"#,
)
.unwrap();
let output_dir = temp.path().join("skills");
fs::create_dir(&output_dir).unwrap();
let mut handles = vec![];
for i in 0..3 {
let temp_path = temp.path().to_path_buf();
let output = output_dir.clone();
let handle = thread::spawn(move || {
println!("Thread {} starting generation", i);
Command::cargo_bin("skill")
.unwrap()
.current_dir(&temp_path)
.arg("claude")
.arg("generate")
.arg("--output")
.arg(&output)
.arg("--force")
.output()
.expect("Failed to execute command");
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
let skill_md = output_dir.join("test-skill").join("SKILL.md");
assert!(
skill_md.exists(),
"SKILL.md should exist after concurrent generation"
);
let content = fs::read_to_string(skill_md).unwrap();
assert!(
content.contains("---"),
"SKILL.md should have valid YAML frontmatter"
);
assert!(
content.contains("name:"),
"SKILL.md should have name field"
);
}
#[test]
#[ignore] fn test_partial_failure_continues_generation() {
let temp = TempDir::new().unwrap();
let manifest_path = temp.path().join(".skill-engine.toml");
fs::write(
&manifest_path,
r#"
[skills.valid-skill]
source = "./valid"
runtime = "wasm"
description = "A valid skill"
[skills.test-skill-2]
source = "./test2"
runtime = "native"
description = "Another valid skill"
"#,
)
.unwrap();
let output_dir = temp.path().join("skills");
let result = Command::cargo_bin("skill")
.unwrap()
.current_dir(temp.path())
.arg("claude")
.arg("generate")
.arg("--output")
.arg(&output_dir)
.output()
.unwrap();
if output_dir.exists() {
let entries: Vec<_> = fs::read_dir(&output_dir)
.unwrap()
.filter_map(|e| e.ok())
.collect();
if !entries.is_empty() {
println!(
"Generated {} skill(s) even with potential issues",
entries.len()
);
}
}
let stderr = String::from_utf8_lossy(&result.stderr);
println!("stderr: {}", stderr);
}
#[test]
#[ignore] fn test_path_traversal_prevention() {
let temp = TempDir::new().unwrap();
let manifest_path = temp.path().join(".skill-engine.toml");
fs::write(
&manifest_path,
r#"
[skills.test]
source = "./test"
runtime = "wasm"
description = "Test"
"#,
)
.unwrap();
let traversal_path = temp.path().join("..").join("..").join("..").join("etc");
let result = Command::cargo_bin("skill")
.unwrap()
.current_dir(temp.path())
.arg("claude")
.arg("generate")
.arg("--output")
.arg(&traversal_path)
.output()
.unwrap();
assert!(
!result.status.success() || !Path::new("/etc/skills").exists(),
"Should not write to /etc via path traversal"
);
}
#[test]
#[ignore] fn test_special_characters_in_paths() {
let temp = TempDir::new().unwrap();
let manifest_path = temp.path().join(".skill-engine.toml");
fs::write(
&manifest_path,
r#"
[skills.test]
source = "./test"
runtime = "wasm"
description = "Test"
"#,
)
.unwrap();
let output_dir = temp.path().join("skills with spaces");
Command::cargo_bin("skill")
.unwrap()
.current_dir(temp.path())
.arg("claude")
.arg("generate")
.arg("--output")
.arg(&output_dir)
.assert();
if output_dir.exists() {
println!("Successfully handled spaces in path");
}
}
#[test]
#[ignore] fn test_error_message_quality() {
let temp = TempDir::new().unwrap();
let manifest_path = temp.path().join(".skill-engine.toml");
fs::write(
&manifest_path,
r#"
[skills.existing]
source = "./existing"
runtime = "wasm"
description = "An existing skill"
"#,
)
.unwrap();
let result = Command::cargo_bin("skill")
.unwrap()
.current_dir(temp.path())
.arg("claude")
.arg("generate")
.arg("--skill")
.arg("nonexistent")
.output()
.unwrap();
let stderr = String::from_utf8_lossy(&result.stderr);
assert!(
stderr.contains("not found") || stderr.contains("does not exist"),
"Error should explain skill was not found"
);
assert!(
stderr.contains("nonexistent"),
"Error should mention the requested skill name"
);
let has_stack_trace = stderr.contains("panicked at")
|| stderr.contains("stack backtrace:")
|| stderr.contains("thread 'main'");
if has_stack_trace {
println!("Warning: Error message contains stack trace");
println!("stderr: {}", stderr);
}
let line_count = stderr.lines().count();
assert!(
line_count < 50,
"Error message should be concise (got {} lines)",
line_count
);
}
#[test]
#[ignore] fn test_helpful_error_suggestions() {
let temp = TempDir::new().unwrap();
let result = Command::cargo_bin("skill")
.unwrap()
.current_dir(temp.path())
.arg("claude")
.arg("generate")
.output()
.unwrap();
let stderr = String::from_utf8_lossy(&result.stderr);
let has_helpful_info = stderr.contains("create")
|| stderr.contains("initialize")
|| stderr.contains(".skill-engine.toml")
|| stderr.contains("manifest");
assert!(
has_helpful_info,
"Error should provide helpful suggestions: {}",
stderr
);
}
#[test]
#[ignore] fn test_extreme_values() {
let temp = TempDir::new().unwrap();
let manifest_path = temp.path().join(".skill-engine.toml");
let long_name = "a".repeat(64);
let long_desc = "d".repeat(1024);
fs::write(
&manifest_path,
format!(
r#"
[skills.{}]
source = "./test"
runtime = "wasm"
description = "{}"
"#,
long_name, long_desc
),
)
.unwrap();
let output_dir = temp.path().join("skills");
Command::cargo_bin("skill")
.unwrap()
.current_dir(temp.path())
.arg("claude")
.arg("generate")
.arg("--output")
.arg(&output_dir)
.assert();
}
#[test]
#[ignore] fn test_unicode_in_descriptions() {
let temp = TempDir::new().unwrap();
let manifest_path = temp.path().join(".skill-engine.toml");
fs::write(
&manifest_path,
r#"
[skills.unicode-test]
source = "./test"
runtime = "wasm"
description = "Kubernetes 集群管理 🚀 Deploy to cloud"
"#,
)
.unwrap();
let output_dir = temp.path().join("skills");
Command::cargo_bin("skill")
.unwrap()
.current_dir(temp.path())
.arg("claude")
.arg("generate")
.arg("--output")
.arg(&output_dir)
.assert()
.success();
let skill_md = output_dir.join("unicode-test").join("SKILL.md");
if skill_md.exists() {
let content = fs::read_to_string(skill_md).unwrap();
assert!(
content.contains("集群") && content.contains("🚀"),
"Unicode characters should be preserved"
);
}
}