#[cfg(test)]
mod tests {
use super::super::loader::Loader;
use super::super::transformer::Transformer;
use super::super::validator::Validator;
use super::super::renderer::Renderer;
use super::super::script_gen::ScriptGenerator;
use super::super::types::{RawSkill, RawTool, RawToolParameter, SkillRuntimeType, ValidatedSkill};
use std::fs;
use tempfile::TempDir;
use tokio::task::JoinSet;
fn create_large_manifest(num_skills: usize) -> String {
let mut manifest = String::from("");
for i in 0..num_skills {
manifest.push_str(&format!(
r#"
[skills.skill-{}]
source = "./skill-{}"
runtime = "wasm"
description = "Test skill number {}"
"#,
i, i, i
));
}
manifest
}
fn create_skill_with_many_tools(num_tools: usize) -> RawSkill {
let tools: Vec<RawTool> = (0..num_tools)
.map(|i| RawTool {
name: format!("tool_{}", i),
description: format!("Tool number {}", i),
parameters: vec![],
streaming: false,
})
.collect();
RawSkill {
name: "test-skill".to_string(),
description: Some("Test skill with many tools".to_string()),
source: "./test-skill".to_string(),
runtime: SkillRuntimeType::Wasm,
tools,
skill_md_content: None,
}
}
#[tokio::test]
async fn test_concurrent_skill_loading() {
let temp = TempDir::new().unwrap();
let manifest_content = r#"
[skills.skill-one]
source = "./skill-one"
runtime = "wasm"
description = "First skill"
[skills.skill-two]
source = "./skill-two"
runtime = "native"
description = "Second skill"
[skills.skill-three]
source = "./skill-three"
runtime = "docker"
description = "Third skill"
"#;
let manifest_path = temp.path().join(".skill-engine.toml");
fs::write(&manifest_path, manifest_content).unwrap();
let mut join_set = JoinSet::new();
for _ in 0..10 {
let path = manifest_path.clone();
join_set.spawn(async move {
let loader = Loader::new(Some(&path)).unwrap();
loader.load_all_skills().await
});
}
let mut results = Vec::new();
while let Some(result) = join_set.join_next().await {
let skills = result.unwrap().unwrap();
results.push(skills);
}
assert_eq!(results.len(), 10);
for skills in results {
assert_eq!(skills.len(), 3);
}
}
#[tokio::test]
#[ignore] async fn test_large_manifest() {
let temp = TempDir::new().unwrap();
let manifest_content = create_large_manifest(150);
let manifest_path = temp.path().join(".skill-engine.toml");
fs::write(&manifest_path, manifest_content).unwrap();
let loader = Loader::new(Some(&manifest_path)).unwrap();
let skills = loader.load_all_skills().await.unwrap();
assert_eq!(skills.len(), 150);
assert!(skills.iter().any(|s| s.name == "skill-0"));
assert!(skills.iter().any(|s| s.name == "skill-149"));
}
#[test]
fn test_invalid_utf8_handling() {
let validator = Validator::new();
let test_cases = vec![
("skill\0name", "Skill with null byte"),
("skill\u{FEFF}name", "Skill with BOM"),
("skill\u{200B}name", "Skill with zero-width space"),
("skill\u{202E}name", "Skill with RTL override"),
];
for (name, desc) in test_cases {
let skill = RawSkill {
name: name.to_string(),
description: Some(desc.to_string()),
source: "./test".to_string(),
runtime: SkillRuntimeType::Wasm,
tools: vec![],
skill_md_content: None,
};
let result = validator.validate(&skill);
assert!(result.is_ok() || result.is_err(), "Should not panic");
}
}
#[test]
fn test_empty_inputs() {
let lenient_validator = Validator::new();
let strict_validator = Validator::strict();
let empty_name = RawSkill {
name: "".to_string(),
description: Some("Valid description".to_string()),
source: "./test".to_string(),
runtime: SkillRuntimeType::Wasm,
tools: vec![],
skill_md_content: None,
};
let lenient_result = lenient_validator.validate(&empty_name);
let strict_result = strict_validator.validate(&empty_name);
assert!(lenient_result.is_ok() || strict_result.is_err());
let empty_desc = RawSkill {
name: "valid-name".to_string(),
description: Some("".to_string()),
source: "./test".to_string(),
runtime: SkillRuntimeType::Wasm,
tools: vec![],
skill_md_content: None,
};
assert!(lenient_validator.validate(&empty_desc).is_ok());
let none_desc = RawSkill {
name: "valid-name".to_string(),
description: None,
source: "./test".to_string(),
runtime: SkillRuntimeType::Wasm,
tools: vec![],
skill_md_content: None,
};
assert!(lenient_validator.validate(&none_desc).is_ok());
}
#[test]
fn test_maximum_field_lengths() {
let validator = Validator::new();
let long_name = "a".repeat(1000);
let skill = RawSkill {
name: long_name.clone(),
description: Some("Test".to_string()),
source: "./test".to_string(),
runtime: SkillRuntimeType::Wasm,
tools: vec![],
skill_md_content: None,
};
let result = validator.validate(&skill);
assert!(result.is_ok() || result.is_err());
let long_desc = "a".repeat(10000);
let skill = RawSkill {
name: "valid-name".to_string(),
description: Some(long_desc),
source: "./test".to_string(),
runtime: SkillRuntimeType::Wasm,
tools: vec![],
skill_md_content: None,
};
let result = validator.validate(&skill);
assert!(result.is_ok() || result.is_err());
}
#[test]
fn test_unicode_edge_cases() {
let validator = Validator::new();
let test_cases = vec![
("emoji-skill-🚀", "Skill with emoji"),
("skill-العربية", "Skill with Arabic RTL text"),
("skill-with-é-ñ-ü", "Skill with accented characters"),
("skill-👨👩👧👦", "Skill with family emoji"),
("skill-\u{0301}combined", "Skill with combining diacritic"),
];
for (name, desc) in test_cases {
let skill = RawSkill {
name: name.to_string(),
description: Some(desc.to_string()),
source: "./test".to_string(),
runtime: SkillRuntimeType::Wasm,
tools: vec![],
skill_md_content: None,
};
let result = validator.validate(&skill);
assert!(result.is_ok() || result.is_err(), "Should not panic on {}", name);
}
}
#[test]
fn test_readonly_filesystem() {
let temp = TempDir::new().unwrap();
let output_dir = temp.path().join("readonly");
fs::create_dir(&output_dir).unwrap();
let validator = Validator::new();
let transformer = Transformer::new();
let raw_skill = create_skill_with_many_tools(5);
let validated = validator.validate(&raw_skill).unwrap();
let skill = transformer.transform(validated).unwrap();
let renderer = Renderer::new(&output_dir).unwrap();
let result = renderer.render(&skill);
assert!(result.is_ok());
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(&output_dir).unwrap().permissions();
perms.set_mode(0o444); fs::set_permissions(&output_dir, perms).unwrap();
let readonly_dir = temp.path().join("readonly2");
fs::create_dir(&readonly_dir).unwrap();
let mut perms = fs::metadata(&readonly_dir).unwrap().permissions();
perms.set_mode(0o444);
fs::set_permissions(&readonly_dir, perms).unwrap();
let result2 = Renderer::new(&readonly_dir);
if let Ok(r) = result2 {
let result3 = r.render(&skill);
assert!(result3.is_err(), "Should error on read-only filesystem");
}
let mut perms = fs::metadata(&output_dir).unwrap().permissions();
perms.set_mode(0o755);
fs::set_permissions(&output_dir, perms).unwrap();
let mut perms = fs::metadata(&readonly_dir).unwrap().permissions();
perms.set_mode(0o755);
fs::set_permissions(&readonly_dir, perms).unwrap();
}
}
#[test]
#[ignore] fn test_skill_with_many_tools() {
let temp = TempDir::new().unwrap();
let validator = Validator::new();
let transformer = Transformer::new();
let raw_skill = create_skill_with_many_tools(500);
let validated = validator.validate(&raw_skill).unwrap();
let skill = transformer.transform(validated).unwrap();
let renderer = Renderer::new(temp.path()).unwrap();
let result = renderer.render(&skill);
assert!(result.is_ok());
let skill_md_path = temp.path().join("test-skill").join("SKILL.md");
assert!(skill_md_path.exists());
let content = fs::read_to_string(&skill_md_path).unwrap();
assert!(content.contains("tool_0"));
assert!(content.contains("tool_499"));
}
#[tokio::test]
async fn test_concurrent_transformations() {
let validator = Validator::new();
let transformer = Transformer::new();
let raw_skill = create_skill_with_many_tools(10);
let mut join_set = JoinSet::new();
for _ in 0..20 {
let v = Validator::new();
let t = Transformer::new();
let s = raw_skill.clone();
join_set.spawn(async move {
let validated = v.validate(&s)?;
t.transform(validated)
});
}
let mut results = Vec::new();
while let Some(result) = join_set.join_next().await {
let transformed: anyhow::Result<_> = result.unwrap();
results.push(transformed.unwrap());
}
assert_eq!(results.len(), 20);
for result in &results {
assert_eq!(result.name, "test-skill");
assert_eq!(result.tools.len(), 10);
}
}
#[test]
fn test_malformed_toml() {
let temp = TempDir::new().unwrap();
let malformed_cases = vec![
"invalid { toml [[ }",
"[skills.test]\nsource = ", "[skills.test]\nruntime = \"invalid_runtime\"",
"[skills.test]\n[skills.test]", ];
for (i, malformed) in malformed_cases.iter().enumerate() {
let manifest_path = temp.path().join(format!("manifest_{}.toml", i));
fs::write(&manifest_path, malformed).unwrap();
let result = Loader::new(Some(&manifest_path));
assert!(result.is_err(), "Should error on malformed TOML case {}", i);
}
}
#[test]
fn test_script_gen_edge_cases() {
let temp = TempDir::new().unwrap();
let validator = Validator::new();
let transformer = Transformer::new();
let generator = ScriptGenerator::new("test-skill");
let problematic_tools = vec![
"tool-with-dashes",
"tool_with_underscores",
"tool.with.dots",
"UPPERCASE",
"123numeric",
];
for tool_name in problematic_tools {
let raw_skill = RawSkill {
name: "test-skill".to_string(),
description: Some("Test".to_string()),
source: "./test".to_string(),
runtime: SkillRuntimeType::Wasm,
tools: vec![RawTool {
name: tool_name.to_string(),
description: "Test tool".to_string(),
parameters: vec![],
streaming: false,
}],
skill_md_content: None,
};
let validated = validator.validate(&raw_skill).unwrap();
let skill = transformer.transform(validated).unwrap();
let result = generator.generate(&skill, temp.path());
assert!(result.is_ok() || result.is_err(),
"Should not panic on tool name: {}", tool_name);
}
}
#[test]
fn test_tool_with_many_parameters() {
let validator = Validator::new();
let transformer = Transformer::new();
let generator = ScriptGenerator::new("test-skill");
let params: Vec<RawToolParameter> = (0..50)
.map(|i| RawToolParameter {
name: format!("param_{}", i),
param_type: "string".to_string(),
description: format!("Parameter {}", i),
required: i % 2 == 0, default_value: if i % 2 == 0 { None } else { Some("default".to_string()) },
})
.collect();
let raw_skill = RawSkill {
name: "test-skill".to_string(),
description: Some("Test".to_string()),
source: "./test".to_string(),
runtime: SkillRuntimeType::Wasm,
tools: vec![RawTool {
name: "complex_tool".to_string(),
description: "Tool with many params".to_string(),
parameters: params,
streaming: false,
}],
skill_md_content: None,
};
let validated = validator.validate(&raw_skill).unwrap();
let skill = transformer.transform(validated).unwrap();
let temp = TempDir::new().unwrap();
let result = generator.generate(&skill, temp.path());
assert!(result.is_ok());
let script_path = temp.path().join("test-skill/scripts/complex_tool.sh");
assert!(script_path.exists());
let content = fs::read_to_string(&script_path).unwrap();
assert!(content.contains("param_0"));
assert!(content.contains("param_49"));
}
#[test]
fn test_deeply_nested_output() {
let temp = TempDir::new().unwrap();
let deep_path = temp.path()
.join("level1")
.join("level2")
.join("level3")
.join("level4");
let validator = Validator::new();
let transformer = Transformer::new();
let raw_skill = create_skill_with_many_tools(1);
let validated = validator.validate(&raw_skill).unwrap();
let skill = transformer.transform(validated).unwrap();
let renderer = Renderer::new(&deep_path).unwrap();
let result = renderer.render(&skill);
assert!(result.is_ok());
let skill_md = deep_path.join("test-skill/SKILL.md");
assert!(skill_md.exists());
}
#[test]
fn test_validator_strict_mode_edge_cases() {
let strict_validator = Validator::strict();
let skill = RawSkill {
name: "test".to_string(),
description: Some("Test description".to_string()),
source: "./test".to_string(),
runtime: SkillRuntimeType::Wasm,
tools: vec![],
skill_md_content: None,
};
let result = strict_validator.validate(&skill);
assert!(result.is_ok() || result.is_err());
}
#[test]
fn test_renderer_yaml_escaping_edge_cases() {
let validator = Validator::new();
let transformer = Transformer::new();
let raw_skill = RawSkill {
name: "test-skill".to_string(),
description: Some("Description with: colons, [brackets], {braces}, 'quotes', \"double quotes\", & ampersands".to_string()),
source: "./test".to_string(),
runtime: SkillRuntimeType::Wasm,
tools: vec![],
skill_md_content: None,
};
let validated = validator.validate(&raw_skill).unwrap();
let skill = transformer.transform(validated).unwrap();
let temp = TempDir::new().unwrap();
let renderer = Renderer::new(temp.path()).unwrap();
let result = renderer.render(&skill);
assert!(result.is_ok());
let skill_md_path = temp.path().join("test-skill/SKILL.md");
let content = fs::read_to_string(&skill_md_path).unwrap();
assert!(content.contains("description:"));
}
#[test]
fn test_empty_collections() {
let validator = Validator::new();
let no_tools = RawSkill {
name: "test".to_string(),
description: Some("Test".to_string()),
source: "./test".to_string(),
runtime: SkillRuntimeType::Wasm,
tools: vec![],
skill_md_content: None,
};
assert!(validator.validate(&no_tools).is_ok());
let no_params = RawSkill {
name: "test".to_string(),
description: Some("Test".to_string()),
source: "./test".to_string(),
runtime: SkillRuntimeType::Wasm,
tools: vec![RawTool {
name: "empty_tool".to_string(),
description: "Tool with no params".to_string(),
parameters: vec![],
streaming: false,
}],
skill_md_content: None,
};
assert!(validator.validate(&no_params).is_ok());
}
#[test]
fn test_transformer_consistency() {
let validator = Validator::new();
let transformer = Transformer::new();
let raw_skill = create_skill_with_many_tools(5);
let validated1 = validator.validate(&raw_skill).unwrap();
let validated2 = validator.validate(&raw_skill).unwrap();
let validated3 = validator.validate(&raw_skill).unwrap();
let result1 = transformer.transform(validated1);
let result2 = transformer.transform(validated2);
let result3 = transformer.transform(validated3);
assert!(result1.is_ok());
assert!(result2.is_ok());
assert!(result3.is_ok());
let r1 = result1.unwrap();
let r2 = result2.unwrap();
let r3 = result3.unwrap();
assert_eq!(r1.name, r2.name);
assert_eq!(r2.name, r3.name);
assert_eq!(r1.tools.len(), r2.tools.len());
assert_eq!(r2.tools.len(), r3.tools.len());
}
}