use super::{
MarkerSyntax, ensure_generated_header, marker_comment_style, marker_header_syntax, provenance_header_for_path,
};
use crate::core::hash;
use std::path::Path;
const HASH: &str = "0ce4d753fdb4854e44358639dcbaebee3449a4afa142dbc4f0a72aa72c214648";
const HASH_HEADER: &str = "# This file is auto-generated by alef — DO NOT EDIT.\n\
# To regenerate: alef generate\n\
# To verify freshness: alef verify\n";
const SLASH_HEADER: &str = "// This file is auto-generated by alef — DO NOT EDIT.\n\
// To regenerate: alef generate\n\
// To verify freshness: alef verify\n";
const HTML_HEADER: &str = "<!-- This file is auto-generated by alef — DO NOT EDIT. -->\n\
<!-- To regenerate: alef generate -->\n\
<!-- To verify freshness: alef verify -->\n";
fn emit_and_stamp(path: &str, body: &str) -> (String, String) {
let stamped = ensure_generated_header(Path::new(path), body);
let hashed = hash::inject_hash_line(&stamped, HASH);
(stamped, hashed)
}
fn assert_read_side_agrees(stamped: &str, hashed: &str) {
assert!(
hash::content_has_alef_marker(stamped),
"content_has_alef_marker must find the emitted marker in:\n{stamped}"
);
assert_eq!(
hash::extract_hash(hashed),
Some(HASH.to_owned()),
"extract_hash must recover the injected hash from:\n{hashed}"
);
assert_eq!(
hash::strip_hash_line(hashed),
stamped,
"strip_hash_line must reproduce the pre-stamp bytes exactly"
);
}
#[test]
fn should_stamp_cmake_config_with_hash_comment() {
let (stamped, hashed) = emit_and_stamp("crates/foo-ffi/cmake/foo-ffi-config.cmake", "if(TARGET foo::foo)\n");
assert_eq!(stamped, format!("{HASH_HEADER}\nif(TARGET foo::foo)\n"));
assert_eq!(
hashed,
format!(
"# This file is auto-generated by alef — DO NOT EDIT.\n\
# alef:hash:{HASH}\n\
# To regenerate: alef generate\n\
# To verify freshness: alef verify\n\
\n\
if(TARGET foo::foo)\n"
)
);
assert_read_side_agrees(&stamped, &hashed);
}
#[test]
fn should_stamp_xml_after_the_declaration_never_before_it() {
let (stamped, hashed) = emit_and_stamp(
"test_apps/php/phpunit.xml",
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit/>\n",
);
assert_eq!(
stamped,
format!("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n{HTML_HEADER}\n<phpunit/>\n")
);
assert_eq!(
stamped.lines().next(),
Some("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"),
"the XML declaration must remain the very first bytes of the document"
);
assert_eq!(
stamped.lines().nth(1),
Some("<!-- This file is auto-generated by alef — DO NOT EDIT. -->"),
"the marker belongs on line 1, immediately after the declaration"
);
assert_eq!(
hashed,
format!(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
<!-- This file is auto-generated by alef — DO NOT EDIT. -->\n\
<!-- alef:hash:{HASH} -->\n\
<!-- To regenerate: alef generate -->\n\
<!-- To verify freshness: alef verify -->\n\
\n\
<phpunit/>\n"
)
);
assert_read_side_agrees(&stamped, &hashed);
}
#[test]
fn should_stamp_xml_whose_declaration_has_no_trailing_newline() {
let (stamped, hashed) = emit_and_stamp("packages/java/pom.xml", "<?xml version=\"1.0\"?><project/>\n");
assert_eq!(stamped, format!("<?xml version=\"1.0\"?>\n{HTML_HEADER}\n<project/>\n"));
assert_read_side_agrees(&stamped, &hashed);
}
#[test]
fn should_stamp_csproj_without_declaration_at_line_zero() {
let (stamped, hashed) = emit_and_stamp(
"e2e/csharp/Foo.E2eTests.csproj",
"<Project Sdk=\"Microsoft.NET.Sdk\">\n</Project>\n",
);
assert_eq!(
stamped,
format!("{HTML_HEADER}\n<Project Sdk=\"Microsoft.NET.Sdk\">\n</Project>\n")
);
assert_read_side_agrees(&stamped, &hashed);
}
#[test]
fn should_stamp_makefile_with_hash_comment() {
let (stamped, hashed) = emit_and_stamp("e2e/c/Makefile", "all:\n\t$(CC) main.c\n");
assert_eq!(stamped, format!("{HASH_HEADER}\nall:\n\t$(CC) main.c\n"));
assert_read_side_agrees(&stamped, &hashed);
}
#[test]
fn should_stamp_gemspec_with_ruby_hash_comment() {
let (stamped, hashed) = emit_and_stamp("packages/ruby/foo.gemspec", "Gem::Specification.new do |s|\nend\n");
assert_eq!(stamped, format!("{HASH_HEADER}\nGem::Specification.new do |s|\nend\n"));
assert_read_side_agrees(&stamped, &hashed);
}
#[test]
fn should_stamp_go_mod_with_double_slash_comment() {
let (stamped, hashed) = emit_and_stamp("e2e/go/go.mod", "module example.com/e2e\n\ngo 1.24\n");
assert_eq!(stamped, format!("{SLASH_HEADER}\nmodule example.com/e2e\n\ngo 1.24\n"));
assert_read_side_agrees(&stamped, &hashed);
}
#[test]
fn should_stamp_zon_with_zig_double_slash_comment() {
let (stamped, hashed) = emit_and_stamp("packages/zig/build.zig.zon", ".{\n .name = \"foo\",\n}\n");
assert_eq!(stamped, format!("{SLASH_HEADER}\n.{{\n .name = \"foo\",\n}}\n"));
assert_read_side_agrees(&stamped, &hashed);
}
#[test]
fn should_stamp_clang_format_with_hash_comment() {
let (stamped, hashed) = emit_and_stamp(".clang-format", "---\nBasedOnStyle: LLVM\n");
assert_eq!(stamped, format!("{HASH_HEADER}\n---\nBasedOnStyle: LLVM\n"));
assert_read_side_agrees(&stamped, &hashed);
}
#[test]
fn should_leave_json_untouched_because_it_has_no_comment_syntax() {
let body = "{\n \"name\": \"foo\"\n}\n";
assert_eq!(
ensure_generated_header(Path::new("packages/node/package.json"), body),
body
);
assert_eq!(marker_header_syntax(Path::new("packages/node/package.json")), None);
}
#[test]
fn should_leave_lockfiles_untouched() {
let body = "# This file is automatically @generated by Cargo.\nversion = 4\n";
assert_eq!(ensure_generated_header(Path::new("Cargo.lock"), body), body);
assert_eq!(marker_header_syntax(Path::new("e2e/php/composer.lock")), None);
}
#[test]
fn provenance_header_for_path_matches_what_ensure_generated_header_would_prepend() {
assert_eq!(
provenance_header_for_path(Path::new("src/lib.rs")),
Some(SLASH_HEADER.to_owned())
);
assert_eq!(
provenance_header_for_path(Path::new("packages/java/pom.xml")),
Some(HTML_HEADER.to_owned())
);
}
#[test]
fn provenance_header_for_path_returns_none_for_an_unmarkable_extension() {
assert_eq!(
provenance_header_for_path(Path::new("packages/node/package.json")),
None
);
}
#[test]
fn should_not_promote_newly_emitted_types_onto_the_ownership_table() {
for path in [
"crates/foo-ffi/cmake/foo-ffi-config.cmake",
"test_apps/php/phpunit.xml",
"e2e/csharp/Foo.E2eTests.csproj",
"e2e/c/Makefile",
"packages/ruby/foo.gemspec",
"e2e/go/go.mod",
"packages/zig/build.zig.zon",
".clang-format",
] {
assert!(
marker_header_syntax(Path::new(path)).is_some(),
"{path} must be stamped with a marker"
);
assert_eq!(
marker_comment_style(Path::new(path)),
None,
"{path} must stay off the ownership table until markers have propagated \
to consumer repos, otherwise existing unmarked copies freeze permanently"
);
}
}
#[test]
fn should_preserve_existing_markable_extension_behaviour() {
assert_eq!(
marker_header_syntax(Path::new("src/lib.rs")),
Some(MarkerSyntax::Comment(hash::CommentStyle::DoubleSlash))
);
assert_eq!(
marker_header_syntax(Path::new("foo.h")),
Some(MarkerSyntax::Comment(hash::CommentStyle::Block))
);
let (stamped, hashed) = emit_and_stamp("scripts/run.sh", "#!/usr/bin/env bash\nset -e\n");
assert_eq!(stamped, format!("#!/usr/bin/env bash\n{HASH_HEADER}\nset -e\n"));
assert_read_side_agrees(&stamped, &hashed);
let (php_stamped, php_hashed) = emit_and_stamp("src/Foo.php", "<?php\nclass Foo {}\n");
assert_eq!(php_stamped, format!("<?php\n{SLASH_HEADER}\nclass Foo {{}}\n"));
assert_read_side_agrees(&php_stamped, &php_hashed);
}
#[test]
fn should_not_double_stamp_content_that_already_carries_a_marker() {
let already = format!("{HTML_HEADER}\n<project/>\n");
assert_eq!(
ensure_generated_header(Path::new("packages/java/pom.xml"), &already),
already
);
}
#[test]
fn should_round_trip_a_generic_stamp_through_an_xml_declaration_prologue() {
let stamped = ensure_generated_header(
Path::new("test_apps/php/phpunit.xml"),
"<?xml version=\"1.0\"?>\n<phpunit/>\n",
);
let with_stamp = hash::inject_stamp_line(&stamped, hash::HANDLE_ABI_STAMP_KEY, "2");
assert_eq!(
hash::extract_stamp(&with_stamp, hash::HANDLE_ABI_STAMP_KEY),
Some("2".to_owned())
);
}