use super::*;
use std::io::Write as _;
#[test]
fn falsify_doc_001_heading_skip() {
let md = "# Title\n### Skipped H2\n";
let violations = validate_heading_hierarchy(md);
assert!(
!violations.is_empty(),
"heading skip H1->H3 must be detected"
);
assert!(violations.iter().any(|v| v.message.contains("skip")));
}
#[test]
fn falsify_doc_002_duplicate_h1() {
let md = "# Title\n# Another Title\n";
let violations = validate_heading_hierarchy(md);
assert!(!violations.is_empty(), "duplicate H1 must be detected");
assert!(violations
.iter()
.any(|v| v.message.contains("duplicate H1")));
}
#[test]
fn falsify_doc_003_javascript_link() {
let md = "[click](javascript:alert(1))\n";
let violations = validate_links(md);
assert!(!violations.is_empty(), "javascript: link must be flagged");
assert!(violations.iter().any(|v| v.message.contains("javascript:")));
}
#[test]
fn falsify_doc_004_bare_code_fence() {
let md = "```\ncode\n```\n";
let violations = validate_code_fences(md);
assert!(!violations.is_empty(), "bare code fence must be detected");
assert!(violations.iter().any(|v| v.rule == "code-fence-language"));
}
#[test]
fn falsify_doc_005_table_column_mismatch() {
let md = "| A | B |\n|---|---|\n| 1 | 2 | 3 |\n";
let violations = validate_tables(md);
assert!(
!violations.is_empty(),
"table column mismatch must be detected"
);
assert!(violations.iter().any(|v| v.rule == "table-column-parity"));
}
#[test]
fn falsify_doc_006_svg_script_injection() {
let svg = "<svg><script>alert(1)</script></svg>";
let violations = validate_svg(svg);
assert!(!violations.is_empty(), "SVG with <script> must be flagged");
assert!(violations.iter().any(|v| v.message.contains("<script>")));
}
#[test]
fn falsify_doc_007_readme_drift() {
let actual = "# Old content\n";
let generated = "# New content\n";
let result = detect_readme_drift(actual, generated);
assert!(result.stale, "stale README must be detected");
assert!(result.diff_lines > 0);
}
#[test]
fn falsify_doc_008_svg_missing_viewbox() {
let svg = "<svg xmlns='http://www.w3.org/2000/svg'><rect/></svg>";
let violations = validate_svg(svg);
assert!(
violations.iter().any(|v| v.message.contains("viewBox")),
"missing viewBox must be detected"
);
}
#[test]
fn falsify_doc_009_missing_required_section() {
let md = "# Project\n## Usage\nHello\n";
let missing = validate_required_sections(md, &["License"]);
assert!(
!missing.is_empty(),
"missing License section must be detected"
);
assert!(missing.contains(&"License".to_string()));
}
#[test]
fn heading_hierarchy_valid() {
let md = "# Title\n## Section\n### Sub\n## Another\n";
let violations = validate_heading_hierarchy(md);
assert!(
violations.is_empty(),
"valid hierarchy should pass: {:?}",
violations
);
}
#[test]
fn heading_hierarchy_first_not_h1() {
let md = "## Not H1\n### Sub\n";
let violations = validate_heading_hierarchy(md);
assert!(violations
.iter()
.any(|v| v.message.contains("first heading must be H1")));
}
#[test]
fn heading_hierarchy_empty_document() {
let md = "No headings here.\n";
let violations = validate_heading_hierarchy(md);
assert!(violations.is_empty());
}
#[test]
fn link_valid() {
let md = "[text](https://example.com)\n";
let violations = validate_links(md);
assert!(
violations.is_empty(),
"valid link should pass: {:?}",
violations
);
}
#[test]
fn link_empty_url() {
let md = "[text]()\n";
let violations = validate_links(md);
assert!(violations.iter().any(|v| v.message.contains("empty")));
}
#[test]
fn link_unescaped_space() {
let md = "[text](https://example.com/path with spaces)\n";
let violations = validate_links(md);
assert!(violations.iter().any(|v| v.message.contains("space")));
}
#[test]
fn image_link_validated() {
let md = "\n";
let violations = validate_links(md);
assert!(!violations.is_empty(), "image links must also be checked");
}
#[test]
fn code_fence_with_language() {
let md = "```rust\nfn main() {}\n```\n";
let violations = validate_code_fences(md);
assert!(
violations.is_empty(),
"tagged opening fence should produce zero violations: {:?}",
violations
);
}
#[test]
fn code_fence_closing_not_flagged() {
let md = "```rust\nfn main() {}\n```\n";
let violations = validate_code_fences(md);
assert!(violations.is_empty());
}
#[test]
fn heading_inside_code_fence_ignored() {
let md = "# Title\n```bash\n# this is a comment, not a heading\n```\n## Section\n";
let violations = validate_heading_hierarchy(md);
assert!(
violations.is_empty(),
"hash-comments inside code fences must not be treated as headings: {:?}",
violations
);
}
#[test]
fn link_inside_code_fence_ignored() {
let md = "# Title\n```markdown\n[text](javascript:evil)\n```\n";
let violations = validate_links(md);
assert!(
violations.is_empty(),
"links inside code fences must not be validated: {:?}",
violations
);
}
#[test]
fn table_inside_code_fence_ignored() {
let md = "# Title\n```text\n| A | B |\n|---|\n| 1 | 2 | 3 |\n```\n";
let violations = validate_tables(md);
assert!(
violations.is_empty(),
"tables inside code fences must not be validated: {:?}",
violations
);
}
#[test]
fn required_section_inside_fence_not_counted() {
let md = "# Project\n```bash\n# License\n```\n";
let missing = validate_required_sections(md, &["License"]);
assert!(
!missing.is_empty(),
"heading inside code fence must not satisfy required section check"
);
}
#[test]
fn table_valid() {
let md = "| A | B |\n|---|---|\n| 1 | 2 |\n";
let violations = validate_tables(md);
assert!(
violations.is_empty(),
"valid table should pass: {:?}",
violations
);
}
#[test]
fn table_separator_mismatch() {
let md = "| A | B | C |\n|---|---|\n| 1 | 2 | 3 |\n";
let violations = validate_tables(md);
assert!(violations.iter().any(|v| v.rule == "table-column-parity"));
}
#[test]
fn svg_valid() {
let svg = r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><rect/></svg>"#;
let violations = validate_svg(svg);
assert!(
violations.is_empty(),
"valid SVG should pass: {:?}",
violations
);
}
#[test]
fn svg_missing_xmlns() {
let svg = r#"<svg viewBox="0 0 100 100"><rect/></svg>"#;
let violations = validate_svg(svg);
assert!(violations.iter().any(|v| v.message.contains("xmlns")));
}
#[test]
fn svg_no_svg_element() {
let violations = validate_svg("<div>not svg</div>");
assert!(violations.iter().any(|v| v.message.contains("<svg>")));
}
#[test]
fn svg_foreign_object() {
let svg = r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><foreignObject>hi</foreignObject></svg>"#;
let violations = validate_svg(svg);
assert!(violations
.iter()
.any(|v| v.message.contains("foreignObject")));
}
#[test]
fn required_sections_all_present() {
let md = "# Project\n## Installation\n## Usage\n## License\n";
let missing = validate_required_sections(md, &["Installation", "Usage", "License"]);
assert!(missing.is_empty(), "all present: {:?}", missing);
}
#[test]
fn required_sections_case_insensitive() {
let md = "# Project\n## license\n";
let missing = validate_required_sections(md, &["License"]);
assert!(missing.is_empty(), "case-insensitive match should work");
}
#[test]
fn drift_identical() {
let content = "# Title\n\nBody text.\n";
let result = detect_readme_drift(content, content);
assert!(!result.stale);
assert_eq!(result.diff_lines, 0);
}
#[test]
fn drift_trailing_whitespace_normalized() {
let actual = "# Title \nBody.\n";
let generated = "# Title\nBody.\n";
let result = detect_readme_drift(actual, generated);
assert!(!result.stale, "trailing whitespace should be normalized");
}
#[test]
fn drift_different_line_count() {
let actual = "# Title\n";
let generated = "# Title\nExtra line\n";
let result = detect_readme_drift(actual, generated);
assert!(result.stale);
assert_eq!(result.diff_lines, 1);
}
#[test]
fn validate_document_md() {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("test.md");
let mut f = std::fs::File::create(&path).expect("create");
f.write_all(b"# Title\n## Section\n").expect("write");
drop(f);
let violations = validate_document(&path);
assert!(violations.is_empty(), "valid .md: {:?}", violations);
}
#[test]
fn validate_document_svg() {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("test.svg");
let mut f = std::fs::File::create(&path).expect("create");
f.write_all(br#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><rect/></svg>"#)
.expect("write");
drop(f);
let violations = validate_document(&path);
assert!(violations.is_empty(), "valid .svg: {:?}", violations);
}
#[test]
fn validate_document_unsupported() {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("test.txt");
std::fs::File::create(&path).expect("create");
let violations = validate_document(&path);
assert!(violations.iter().any(|v| v.rule == "unsupported-extension"));
}
#[test]
fn validate_document_missing_file() {
let path = std::path::Path::new("/tmp/nonexistent_doc_integrity_test.md");
let violations = validate_document(path);
assert!(violations.iter().any(|v| v.rule == "io-error"));
}