use assert_cmd::Command;
use predicates::prelude::*;
use std::fs;
use std::path::{Path, PathBuf};
use tempfile::TempDir;
#[path = "spec_validator.rs"]
mod spec_validator;
use spec_validator::SpecValidator;
fn create_test_manifest(temp_dir: &Path, manifest_content: &str) -> PathBuf {
let manifest_path = temp_dir.join(".skill-engine.toml");
fs::write(&manifest_path, manifest_content).expect("Failed to write test manifest");
manifest_path
}
fn simple_manifest() -> &'static str {
r#"
[skills.test-skill]
source = "./test-skill"
runtime = "wasm"
description = "A simple test skill"
"#
}
fn multi_skill_manifest() -> &'static str {
r#"
[skills.kubernetes]
source = "./kubernetes"
runtime = "wasm"
description = "Kubernetes cluster management"
[skills.docker]
source = "./docker"
runtime = "native"
description = "Docker container operations"
[skills.terraform]
source = "./terraform"
runtime = "native"
description = "Infrastructure as Code with Terraform"
"#
}
fn verify_skill_structure(skill_dir: &Path, skill_name: &str, expect_scripts: bool) {
let skill_path = skill_dir.join(skill_name);
assert!(
skill_path.exists(),
"Skill directory {} should exist",
skill_name
);
let skill_md = skill_path.join("SKILL.md");
assert!(skill_md.exists(), "SKILL.md should exist");
assert!(
fs::metadata(&skill_md).unwrap().len() > 0,
"SKILL.md should not be empty"
);
let tools_md = skill_path.join("TOOLS.md");
assert!(tools_md.exists(), "TOOLS.md should exist");
if expect_scripts {
let scripts_dir = skill_path.join("scripts");
assert!(
scripts_dir.exists(),
"scripts/ directory should exist when scripts are enabled"
);
} else {
let scripts_dir = skill_path.join("scripts");
assert!(
!scripts_dir.exists(),
"scripts/ directory should NOT exist when --no-scripts is used"
);
}
}
fn parse_yaml_frontmatter(skill_md_path: &Path) -> serde_yaml::Value {
let content = fs::read_to_string(skill_md_path).expect("Failed to read SKILL.md");
let yaml_start = content.find("---\n").expect("No YAML frontmatter start found");
let yaml_end = content[yaml_start + 4..]
.find("\n---")
.expect("No YAML frontmatter end found")
+ yaml_start
+ 4;
let yaml = &content[yaml_start + 4..yaml_end];
serde_yaml::from_str(yaml).expect("Failed to parse YAML frontmatter")
}
fn validate_yaml_frontmatter(yaml: &serde_yaml::Value) {
assert!(yaml.get("name").is_some(), "name field is required");
assert!(
yaml.get("description").is_some(),
"description field is required"
);
if let Some(name) = yaml["name"].as_str() {
assert!(
name.len() <= 64,
"name must be <= 64 characters, got {}",
name.len()
);
}
if let Some(description) = yaml["description"].as_str() {
assert!(
description.len() <= 1024,
"description must be <= 1024 characters, got {}",
description.len()
);
}
}
#[test]
#[ignore] fn test_generate_all_skills() {
let temp = TempDir::new().unwrap();
create_test_manifest(temp.path(), multi_skill_manifest());
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();
verify_skill_structure(&output_dir, "kubernetes", true);
verify_skill_structure(&output_dir, "docker", true);
verify_skill_structure(&output_dir, "terraform", true);
}
#[test]
#[ignore] fn test_generate_single_skill() {
let temp = TempDir::new().unwrap();
create_test_manifest(temp.path(), multi_skill_manifest());
let output_dir = temp.path().join("skills");
Command::cargo_bin("skill")
.unwrap()
.current_dir(temp.path())
.arg("claude")
.arg("generate")
.arg("--skill")
.arg("kubernetes")
.arg("--output")
.arg(&output_dir)
.assert()
.success();
assert!(output_dir.join("kubernetes").exists());
assert!(!output_dir.join("docker").exists());
assert!(!output_dir.join("terraform").exists());
}
#[test]
#[ignore] fn test_generate_force_overwrite() {
let temp = TempDir::new().unwrap();
create_test_manifest(temp.path(), simple_manifest());
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("test-skill").join("SKILL.md");
fs::write(&skill_md, "MODIFIED CONTENT").unwrap();
Command::cargo_bin("skill")
.unwrap()
.current_dir(temp.path())
.arg("claude")
.arg("generate")
.arg("--output")
.arg(&output_dir)
.assert()
.success();
let content = fs::read_to_string(&skill_md).unwrap();
assert_eq!(
content, "MODIFIED CONTENT",
"File should be preserved without --force"
);
Command::cargo_bin("skill")
.unwrap()
.current_dir(temp.path())
.arg("claude")
.arg("generate")
.arg("--output")
.arg(&output_dir)
.arg("--force")
.assert()
.success();
let content = fs::read_to_string(&skill_md).unwrap();
assert_ne!(
content, "MODIFIED CONTENT",
"File should be overwritten with --force"
);
assert!(content.contains("---"), "Should have YAML frontmatter");
}
#[test]
#[ignore] fn test_generate_no_scripts() {
let temp = TempDir::new().unwrap();
create_test_manifest(temp.path(), simple_manifest());
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)
.arg("--no-scripts")
.assert()
.success();
verify_skill_structure(&output_dir, "test-skill", false);
}
#[test]
#[ignore] fn test_generate_dry_run() {
let temp = TempDir::new().unwrap();
create_test_manifest(temp.path(), simple_manifest());
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)
.arg("--dry-run")
.assert()
.success()
.stdout(predicate::str::contains("Would generate"));
assert!(
!output_dir.exists(),
"No files should be created in dry-run mode"
);
}
#[test]
#[ignore] fn test_validate_yaml_frontmatter() {
let temp = TempDir::new().unwrap();
create_test_manifest(temp.path(), simple_manifest());
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("test-skill").join("SKILL.md");
let yaml = parse_yaml_frontmatter(&skill_md);
validate_yaml_frontmatter(&yaml);
}
#[test]
#[ignore] fn test_validate_skill_md_structure() {
let temp = TempDir::new().unwrap();
create_test_manifest(temp.path(), simple_manifest());
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("test-skill").join("SKILL.md");
let content = fs::read_to_string(&skill_md).unwrap();
assert!(content.contains("---"), "Should have YAML frontmatter");
assert!(
content.contains("# "),
"Should have markdown headings"
);
let content_lower = content.to_lowercase();
assert!(
content_lower.contains("usage") || content_lower.contains("how to use"),
"Should have usage section"
);
}
#[test]
#[ignore] fn test_validate_tools_md_format() {
let temp = TempDir::new().unwrap();
create_test_manifest(temp.path(), simple_manifest());
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 tools_md = output_dir.join("test-skill").join("TOOLS.md");
let content = fs::read_to_string(&tools_md).unwrap();
assert!(
content.contains("# "),
"TOOLS.md should have headings"
);
}
#[test]
#[ignore] fn test_generate_with_invalid_manifest() {
let temp = TempDir::new().unwrap();
fs::write(temp.path().join(".skill-engine.toml"), "invalid { toml [ syntax").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()
.failure();
}
#[test]
#[ignore] fn test_generate_nonexistent_skill() {
let temp = TempDir::new().unwrap();
create_test_manifest(temp.path(), simple_manifest());
let output_dir = temp.path().join("skills");
Command::cargo_bin("skill")
.unwrap()
.current_dir(temp.path())
.arg("claude")
.arg("generate")
.arg("--skill")
.arg("nonexistent-skill")
.arg("--output")
.arg(&output_dir)
.assert()
.failure();
}
#[test]
#[ignore] fn test_generated_skills_are_spec_compliant() {
let temp = TempDir::new().unwrap();
create_test_manifest(temp.path(), multi_skill_manifest());
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 validator = SpecValidator::new();
for entry in fs::read_dir(&output_dir).unwrap() {
let entry = entry.unwrap();
let skill_dir = entry.path();
if !skill_dir.is_dir() {
continue;
}
let skill_name = skill_dir.file_name().unwrap().to_str().unwrap();
println!("Validating skill: {}", skill_name);
let result = validator.validate_skill_directory(&skill_dir);
assert!(
result.is_ok(),
"Skill '{}' failed specification validation:\n{:#?}",
skill_name,
result.err().unwrap()
);
}
}