async fn test_handle_spec_create_with_epic() {
let temp_dir = TempDir::new().unwrap();
let output_path = temp_dir.path();
let result =
handle_spec_create("Epic Feature", None, Some("EPIC-001"), Some(output_path)).await;
assert!(result.is_ok());
let expected_file = output_path.join("epic-feature.md");
let content = fs::read_to_string(&expected_file).unwrap();
assert!(content.contains("EPIC-001"));
}
#[tokio::test]
async fn test_handle_spec_create_slug_conversion() {
let temp_dir = TempDir::new().unwrap();
let output_path = temp_dir.path();
let result = handle_spec_create(
"Complex Feature Name With Spaces",
None,
None,
Some(output_path),
)
.await;
assert!(result.is_ok());
let expected_file = output_path.join("complex-feature-name-with-spaces.md");
assert!(expected_file.exists());
}
#[tokio::test]
async fn test_handle_spec_create_template_structure() {
let temp_dir = TempDir::new().unwrap();
let output_path = temp_dir.path();
let result =
handle_spec_create("Template Test", None, None, Some(output_path)).await;
assert!(result.is_ok());
let expected_file = output_path.join("template-test.md");
let content = fs::read_to_string(&expected_file).unwrap();
assert!(content.contains("---")); assert!(content.contains("## Executive Summary"));
assert!(content.contains("## Scientific Foundation"));
assert!(content.contains("## Requirements"));
assert!(content.contains("### Functional Requirements"));
assert!(content.contains("### Non-Functional Requirements"));
assert!(content.contains("## Acceptance Criteria"));
assert!(content.contains("## Code Examples"));
assert!(content.contains("## Testing Strategy"));
assert!(content.contains("## References"));
}
#[tokio::test]
async fn test_handle_spec_create_creates_directory() {
let temp_dir = TempDir::new().unwrap();
let nested_path = temp_dir.path().join("nested").join("specs");
let result =
handle_spec_create("Nested Spec", None, None, Some(&nested_path)).await;
assert!(result.is_ok());
assert!(nested_path.exists());
}
#[tokio::test]
async fn test_handle_spec_comply_compliant_spec() {
let temp_dir = TempDir::new().unwrap();
let spec_path = temp_dir.path().join("compliant.md");
let content = create_compliant_spec_content();
fs::write(&spec_path, content).unwrap();
let result =
handle_spec_comply(&spec_path, true, SpecOutputFormat::Text).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_handle_spec_comply_missing_issue_refs() {
let temp_dir = TempDir::new().unwrap();
let spec_path = temp_dir.path().join("no-issues.md");
let content = r#"---
title: No Issues Spec
---
# No Issues Spec
## Requirements
- [ ] AC-001: First
"#;
fs::write(&spec_path, content).unwrap();
let result =
handle_spec_comply(&spec_path, true, SpecOutputFormat::Text).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_handle_spec_comply_missing_code_examples() {
let temp_dir = TempDir::new().unwrap();
let spec_path = temp_dir.path().join("no-code.md");
let content = r##"---
title: No Code Spec
issue_refs: ["#123"]
---
# No Code Spec
## Requirements
- [ ] AC-001: First
"##;
fs::write(&spec_path, content).unwrap();
let result =
handle_spec_comply(&spec_path, true, SpecOutputFormat::Text).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_handle_spec_comply_dry_run_no_changes() {
let temp_dir = TempDir::new().unwrap();
let spec_path = temp_dir.path().join("dryrun.md");
let content = r#"---
title: Dry Run Spec
---
# Dry Run Spec
"#;
fs::write(&spec_path, content).unwrap();
let original_content = fs::read_to_string(&spec_path).unwrap();
let result =
handle_spec_comply(&spec_path, true, SpecOutputFormat::Text).await;
assert!(result.is_ok());
let final_content = fs::read_to_string(&spec_path).unwrap();
assert_eq!(original_content, final_content);
}
#[tokio::test]
async fn test_handle_spec_comply_file_not_found() {
let result = handle_spec_comply(
Path::new("/nonexistent/path/spec.md"),
true,
SpecOutputFormat::Text,
)
.await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_handle_spec_list_empty_directory() {
let temp_dir = TempDir::new().unwrap();
let result = handle_spec_list(
temp_dir.path(),
None,
false,
SpecOutputFormat::Text,
)
.await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_handle_spec_list_with_specs() {
let temp_dir = TempDir::new().unwrap();
let spec1 = temp_dir.path().join("spec1.md");
let spec2 = temp_dir.path().join("spec2.md");
fs::write(&spec1, create_compliant_spec_content()).unwrap();
fs::write(&spec2, "# Simple Spec\n").unwrap();
let result = handle_spec_list(
temp_dir.path(),
None,
false,
SpecOutputFormat::Text,
)
.await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_handle_spec_list_min_score_filter() {
let temp_dir = TempDir::new().unwrap();
let spec1 = temp_dir.path().join("high-score.md");
let spec2 = temp_dir.path().join("low-score.md");
fs::write(&spec1, create_compliant_spec_content()).unwrap();
fs::write(&spec2, "# Low Score\n").unwrap();
let result = handle_spec_list(
temp_dir.path(),
Some(50), false,
SpecOutputFormat::Text,
)
.await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_handle_spec_list_failing_only() {
let temp_dir = TempDir::new().unwrap();
let spec1 = temp_dir.path().join("failing.md");
fs::write(&spec1, "# Failing Spec\n").unwrap();
let result = handle_spec_list(
temp_dir.path(),
None,
true, SpecOutputFormat::Text,
)
.await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_handle_spec_list_json_format() {
let temp_dir = TempDir::new().unwrap();
let spec1 = temp_dir.path().join("spec.md");
fs::write(&spec1, create_compliant_spec_content()).unwrap();
let result = handle_spec_list(
temp_dir.path(),
None,
false,
SpecOutputFormat::Json,
)
.await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_handle_spec_list_markdown_format() {
let temp_dir = TempDir::new().unwrap();
let spec1 = temp_dir.path().join("spec.md");
fs::write(&spec1, create_compliant_spec_content()).unwrap();
let result = handle_spec_list(
temp_dir.path(),
None,
false,
SpecOutputFormat::Markdown,
)
.await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_handle_spec_list_single_file() {
let temp_dir = TempDir::new().unwrap();
let spec_path = temp_dir.path().join("single.md");
fs::write(&spec_path, create_compliant_spec_content()).unwrap();
let result = handle_spec_list(
&spec_path,
None,
false,
SpecOutputFormat::Text,
)
.await;
assert!(result.is_ok());
}
#[test]
fn test_calculate_spec_score_boundary_at_95() {
let mut spec = create_empty_spec();
spec.title = "Test".to_string(); spec.issue_refs = vec!["#1".to_string()]; spec.code_examples = (0..5)
.map(|i| CodeExample {
language: "rust".to_string(),
code: format!("fn f{}() {{}}", i),
line: i,
executable: true,
})
.collect(); spec.acceptance_criteria = (0..10)
.map(|i| AcceptanceCriterion {
text: format!("AC-{}", i),
complete: false,
line: i,
})
.collect(); spec.claims = (0..20)
.map(|i| ValidationClaim {
id: format!("C-{}", i),
text: format!("Claim {}", i),
line: i,
category: ClaimCategory::Implementation,
automatable: false,
validation_cmd: None,
expected_pattern: None,
})
.collect(); spec.test_requirements = (0..5)
.map(|i| TestRequirement {
text: format!("Test {}", i),
test_type: "unit".to_string(),
code_path: None,
})
.collect();
let score = calculate_spec_score(&spec);
assert_eq!(score, 100.0);
assert!(score >= 95.0);
}
#[test]
fn test_calculate_spec_score_just_below_95() {
let mut spec = create_empty_spec();
spec.title = "Test".to_string(); spec.issue_refs = vec!["#1".to_string()]; spec.code_examples = (0..5)
.map(|i| CodeExample {
language: "rust".to_string(),
code: format!("fn f{}() {{}}", i),
line: i,
executable: true,
})
.collect(); spec.acceptance_criteria = (0..10)
.map(|i| AcceptanceCriterion {
text: format!("AC-{}", i),
complete: false,
line: i,
})
.collect(); spec.claims = (0..19)
.map(|i| ValidationClaim {
id: format!("C-{}", i),
text: format!("Claim {}", i),
line: i,
category: ClaimCategory::Implementation,
automatable: false,
validation_cmd: None,
expected_pattern: None,
})
.collect(); spec.test_requirements = (0..5)
.map(|i| TestRequirement {
text: format!("Test {}", i),
test_type: "unit".to_string(),
code_path: None,
})
.collect();
let score = calculate_spec_score(&spec);
assert_eq!(score, 99.0);
}
#[test]
fn test_format_outputs_consistency() {
let spec = create_full_spec();
let score = calculate_spec_score(&spec);
let text = format_spec_score_text(&spec, score, false);
let json_result = format_spec_score_json(&spec, score);
let markdown = format_spec_score_markdown(&spec, score);
assert!(!text.is_empty());
assert!(json_result.is_ok());
assert!(!markdown.is_empty());
assert!(text.contains("PASS"));
let json: serde_json::Value = serde_json::from_str(&json_result.unwrap()).unwrap();
assert_eq!(json["passing"], true);
assert!(markdown.contains("PASS"));
}
#[test]
fn test_format_outputs_failing_consistency() {
let spec = create_empty_spec();
let score = calculate_spec_score(&spec);
let text = format_spec_score_text(&spec, score, false);
let json_result = format_spec_score_json(&spec, score);
let markdown = format_spec_score_markdown(&spec, score);
assert!(text.contains("FAIL"));
let json: serde_json::Value = serde_json::from_str(&json_result.unwrap()).unwrap();
assert_eq!(json["passing"], false);
assert!(markdown.contains("FAIL"));
}
fn create_compliant_spec_content() -> String {
r##"---
title: Compliant Specification
status: Active
issue_refs: ["#123", "#456"]
---
# Compliant Specification
## Executive Summary
This is a compliant spec with [citation1] and [citation2].
## Scientific Foundation
1. Author et al., 2024. Paper Title. [citation3]
2. Another et al., 2024. Another Paper. [citation4]
3. Third et al., 2024. Third Paper. [citation5]
## Requirements
### Functional Requirements
The system MUST provide functionality.
The system SHOULD handle errors gracefully.
The system SHALL be backwards compatible.
### Non-Functional Requirements
Performance MUST be within acceptable limits.
## Acceptance Criteria
### Core Criteria
- [ ] AC-001: First criterion
- [ ] AC-002: Second criterion
- [ ] AC-003: Third criterion
- [ ] AC-004: Fourth criterion
- [ ] AC-005: Fifth criterion
- [ ] AC-006: Sixth criterion
- [ ] AC-007: Seventh criterion
- [ ] AC-008: Eighth criterion
- [ ] AC-009: Ninth criterion
- [ ] AC-010: Tenth criterion
## Code Examples
### Example 1
```rust
fn example_one() {
println!("Example 1");
}
```
### Example 2
```rust
fn example_two() {
println!("Example 2");
}
```
### Example 3
```rust
fn example_three() {
println!("Example 3");
}
```
### Example 4
```rust
fn example_four() {
println!("Example 4");
}
```
### Example 5
```rust
fn example_five() {
println!("Example 5");
}
```
## Testing Strategy
Unit tests must cover 95% of the codebase.
Integration tests must validate all endpoints.
Property tests should verify invariants.
E2e tests should test the full workflow.
Mutation tests should validate test quality.
## References
[citation1] Reference 1
[citation2] Reference 2
[citation3] Reference 3
[citation4] Reference 4
[citation5] Reference 5
"##
.to_string()
}
}