#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_header() {
let header = "# TICKET-PMAT-5011: Ticket Management System";
let (id, title) = parse_header(header).unwrap();
assert_eq!(id, "TICKET-PMAT-5011");
assert_eq!(title, "Ticket Management System");
}
#[test]
fn test_parse_header_invalid_format() {
assert!(parse_header("## TICKET-PMAT-0001: Not hash one").is_err());
}
#[test]
fn test_parse_header_missing_title() {
assert!(parse_header("# TICKET-PMAT-0001 no colon separator").is_err());
}
#[test]
fn test_extract_metadata_missing_field() {
let lines: Vec<&str> = vec!["**Status**: GREEN", "**Priority**: P0"];
let result = extract_metadata(&lines, "**Sprint**");
assert!(result.is_err());
}
#[test]
fn test_extract_section_missing_header() {
let lines: Vec<&str> = vec!["## Something Else", "body", "## Another"];
let result = extract_section(&lines, "## Objective");
assert!(result.is_err());
}
#[test]
fn test_parse_priority_p2_variant() {
assert_eq!(parse_priority("P2").unwrap(), Priority::P2);
}
#[test]
fn test_parse_status_refactor_variant() {
assert_eq!(parse_status("REFACTOR").unwrap(), TicketStatus::Refactor);
}
#[test]
fn test_parse_status() {
assert_eq!(parse_status("RED").unwrap(), TicketStatus::Red);
assert_eq!(parse_status("Green").unwrap(), TicketStatus::Green);
assert_eq!(parse_status("COMPLETE").unwrap(), TicketStatus::Complete);
assert!(parse_status("INVALID").is_err());
}
#[test]
fn test_parse_priority() {
assert_eq!(parse_priority("P0").unwrap(), Priority::P0);
assert_eq!(parse_priority("p1").unwrap(), Priority::P1);
assert!(parse_priority("P3").is_err());
}
#[test]
fn test_parse_complexity() {
assert_eq!(parse_complexity("8").unwrap(), 8);
assert!(parse_complexity("invalid").is_err());
}
#[test]
fn test_parse_dependencies() {
let deps = parse_dependencies("TICKET-PMAT-5010, TICKET-PMAT-5009");
assert_eq!(deps.len(), 2);
assert_eq!(deps[0], "TICKET-PMAT-5010");
let no_deps = parse_dependencies("None");
assert_eq!(no_deps.len(), 0);
}
#[test]
fn test_validate_ticket_valid() {
let ticket = TicketFile {
id: "TICKET-PMAT-5011".into(),
title: "Test".into(),
status: TicketStatus::Red,
priority: Priority::P0,
complexity: 8,
estimated_time: "4 hours".into(),
dependencies: vec![],
sprint: "Sprint 17".into(),
objective: "Test objective".into(),
success_criteria: vec!["Criterion 1".into()],
file_path: PathBuf::new(),
};
assert!(ticket.validate().is_ok());
}
#[test]
fn test_parse_content_success() {
let content = "# TICKET-PMAT-9001: Coverage Sample\n\n\
**Status**: RED\n\
**Priority**: P1\n\
**Complexity**: 5\n\
**Estimated Time**: 2 hours\n\
**Dependencies**: None\n\
**Sprint**: Sprint 42\n\n\
## Objective\n\n\
Cover parse_content success path.\n\n\
## Success Criteria\n\n\
- [ ] First criterion\n\
- [ ] Second criterion\n";
let ticket = TicketFile::parse_content(content).unwrap();
assert_eq!(ticket.id, "TICKET-PMAT-9001");
assert_eq!(ticket.title, "Coverage Sample");
assert_eq!(ticket.status, TicketStatus::Red);
assert_eq!(ticket.priority, Priority::P1);
assert_eq!(ticket.complexity, 5);
assert_eq!(ticket.estimated_time, "2 hours");
assert!(ticket.dependencies.is_empty());
assert_eq!(ticket.sprint, "Sprint 42");
assert!(!ticket.objective.is_empty());
assert_eq!(ticket.success_criteria.len(), 2);
}
#[test]
fn test_parse_content_empty_errors() {
let result = TicketFile::parse_content("");
assert!(result.is_err());
}
#[test]
fn test_ticket_file_from_file_roundtrip() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("TICKET-PMAT-9002.md");
let content = "# TICKET-PMAT-9002: File Roundtrip\n\n\
**Status**: GREEN\n\
**Priority**: P0\n\
**Complexity**: 3\n\
**Estimated Time**: 1 hour\n\
**Dependencies**: None\n\
**Sprint**: Sprint 42\n\n\
## Objective\n\n\
Cover from_file path.\n\n\
## Success Criteria\n\n\
- [ ] Only criterion\n";
std::fs::write(&path, content).unwrap();
let ticket = TicketFile::from_file(&path).unwrap();
assert_eq!(ticket.id, "TICKET-PMAT-9002");
assert_eq!(ticket.file_path, path);
assert!(ticket.validate().is_ok());
}
#[test]
fn test_validate_ticket_invalid_complexity() {
let ticket = TicketFile {
id: "TICKET-PMAT-5011".into(),
title: "Test".into(),
status: TicketStatus::Red,
priority: Priority::P0,
complexity: 15,
estimated_time: "4 hours".into(),
dependencies: vec![],
sprint: "Sprint 17".into(),
objective: "Test objective".into(),
success_criteria: vec!["Criterion 1".into()],
file_path: PathBuf::new(),
};
assert!(ticket.validate().is_err());
}
#[test]
fn test_validate_ticket_bad_id_prefix() {
let ticket = TicketFile {
id: "BAD-PREFIX-0001".into(),
title: "Test".into(),
status: TicketStatus::Red,
priority: Priority::P0,
complexity: 5,
estimated_time: "1 hour".into(),
dependencies: vec![],
sprint: "Sprint 1".into(),
objective: "Objective".into(),
success_criteria: vec!["Criterion".into()],
file_path: PathBuf::new(),
};
assert!(ticket.validate().is_err());
}
#[test]
fn test_validate_ticket_empty_objective() {
let ticket = TicketFile {
id: "TICKET-PMAT-0001".into(),
title: "Test".into(),
status: TicketStatus::Red,
priority: Priority::P0,
complexity: 5,
estimated_time: "1 hour".into(),
dependencies: vec![],
sprint: "Sprint 1".into(),
objective: " ".into(), success_criteria: vec!["Criterion".into()],
file_path: PathBuf::new(),
};
assert!(ticket.validate().is_err());
}
#[test]
fn test_validate_ticket_empty_success_criteria() {
let ticket = TicketFile {
id: "TICKET-PMAT-0001".into(),
title: "Test".into(),
status: TicketStatus::Red,
priority: Priority::P0,
complexity: 5,
estimated_time: "1 hour".into(),
dependencies: vec![],
sprint: "Sprint 1".into(),
objective: "Objective".into(),
success_criteria: vec![],
file_path: PathBuf::new(),
};
assert!(ticket.validate().is_err());
}
#[test]
fn test_list_tickets_empty_dir() {
let dir = tempfile::tempdir().unwrap();
let tickets = list_tickets(dir.path()).unwrap();
assert!(tickets.is_empty());
}
#[test]
fn test_list_tickets_skips_non_ticket_files() {
let dir = tempfile::tempdir().unwrap();
std::fs::write(dir.path().join("readme.txt"), "not markdown").unwrap();
std::fs::write(dir.path().join("notes.md"), "# Just notes").unwrap();
let content = "# TICKET-PMAT-9100: Sample\n\n\
**Status**: RED\n\
**Priority**: P0\n\
**Complexity**: 3\n\
**Estimated Time**: 1 hour\n\
**Dependencies**: None\n\
**Sprint**: Sprint 1\n\n\
## Objective\n\n\
Sample.\n\n\
## Success Criteria\n\n\
- [ ] Crit\n";
std::fs::write(dir.path().join("TICKET-PMAT-9100.md"), content).unwrap();
let tickets = list_tickets(dir.path()).unwrap();
assert_eq!(tickets.len(), 1);
assert_eq!(tickets[0].id, "TICKET-PMAT-9100");
}
#[test]
fn test_ticket_exists_with_tempdir() {
let dir = tempfile::tempdir().unwrap();
std::fs::write(dir.path().join("TICKET-PMAT-7777.md"), "# content").unwrap();
assert!(ticket_exists(dir.path(), "TICKET-PMAT-7777"));
assert!(!ticket_exists(dir.path(), "TICKET-PMAT-8888"));
}
#[test]
fn integration_parse_ticket_5010() {
let ticket_path =
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("docs/tickets/TICKET-PMAT-5010.md");
if !ticket_path.exists() {
eprintln!("Skipping: ticket file not found at {:?}", ticket_path);
return;
}
let ticket = TicketFile::from_file(&ticket_path).unwrap();
assert_eq!(ticket.id, "TICKET-PMAT-5010");
assert_eq!(ticket.title, "Roadmap Parsing and Validation");
assert_eq!(ticket.priority, Priority::P0);
assert!(ticket.complexity <= 10);
assert!(!ticket.objective.is_empty());
assert!(!ticket.success_criteria.is_empty());
assert!(ticket.validate().is_ok());
}
#[test]
fn integration_list_all_tickets() {
let tickets_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("docs/tickets");
if !tickets_dir.exists() {
eprintln!("Skipping: tickets dir not found at {:?}", tickets_dir);
return;
}
let tickets = list_tickets(&tickets_dir).unwrap();
assert!(!tickets.is_empty());
assert!(
tickets.len() >= 5,
"Expected at least 5 tickets, found {}",
tickets.len()
);
for ticket in &tickets {
assert!(
ticket.validate().is_ok(),
"Ticket {} failed validation",
ticket.id
);
}
}
#[test]
fn test_ticket_exists() {
let tickets_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("docs/tickets");
if !tickets_dir.exists() {
eprintln!("Skipping: tickets dir not found at {:?}", tickets_dir);
return;
}
assert!(ticket_exists(&tickets_dir, "TICKET-PMAT-5010"));
assert!(!ticket_exists(&tickets_dir, "TICKET-PMAT-9999"));
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod property_tests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn prop_valid_complexity_range(complexity in 1u8..=10) {
let ticket = TicketFile {
id: "TICKET-PMAT-0001".into(),
title: "Test".into(),
status: TicketStatus::Red,
priority: Priority::P0,
complexity,
estimated_time: "1 hour".into(),
dependencies: vec![],
sprint: "Sprint 1".into(),
objective: "Test".into(),
success_criteria: vec!["Test".into()],
file_path: PathBuf::new(),
};
prop_assert!(ticket.validate().is_ok());
}
#[test]
fn prop_invalid_complexity_rejected(complexity in 11u8..=255) {
let ticket = TicketFile {
id: "TICKET-PMAT-0001".into(),
title: "Test".into(),
status: TicketStatus::Red,
priority: Priority::P0,
complexity,
estimated_time: "1 hour".into(),
dependencies: vec![],
sprint: "Sprint 1".into(),
objective: "Test".into(),
success_criteria: vec!["Test".into()],
file_path: PathBuf::new(),
};
prop_assert!(ticket.validate().is_err());
}
}
}