use kcode_k1_transaction_id::TxId;
use kcode_k1_web_code_document::{CodeDocument, Language};
use kcode_k1_web_package::{AuthorityId, SourceFile, WebFamily};
use semver::Version;
fn family() -> WebFamily {
WebFamily::new(AuthorityId::new(TxId::from_bytes([1; 12])), "alpha").unwrap()
}
#[test]
fn templates_are_exact_two_file_authored_trees() {
for (language, path) in [
(Language::JavaScript, "index.js"),
(Language::Html, "index.html"),
(Language::Css, "index.css"),
] {
let document = CodeDocument::template(family(), language).unwrap();
assert_eq!(
document
.files()
.iter()
.map(|file| file.path())
.collect::<Vec<_>>(),
vec!["Documentation.md", path]
);
assert!(document.file("k1-web.json").is_none());
assert!(
document
.files()
.iter()
.all(|file| !file.path().starts_with("Code."))
);
let package = document.to_source_package(Version::new(1, 2, 3)).unwrap();
assert_eq!(package.id().version(), &Version::new(1, 2, 3));
assert!(
package
.files()
.iter()
.any(|file| file.path() == "k1-web.json")
);
if language == Language::JavaScript {
assert_eq!((package.entry(), package.tests()), ("index.js", "index.js"));
} else {
assert_eq!(
(package.entry(), package.tests()),
("k1-entry.js", "k1-tests.js")
);
let adapter = package
.files()
.iter()
.find(|file| file.path() == "k1-entry.js")
.unwrap();
assert!(std::str::from_utf8(adapter.bytes()).unwrap().contains(path));
}
}
}
#[test]
fn full_trees_and_text_mutations_preserve_exact_bytes() {
let original = CodeDocument::new(
family(),
vec![
SourceFile::new("src/unusual.data", b"old".to_vec()),
SourceFile::new("assets/icon.png", vec![0, 159, 255]),
],
)
.unwrap();
let created = original
.create_text("src/new.txt", "created\n".into())
.unwrap();
assert!(original.file("src/new.txt").is_none());
assert_eq!(created.file("src/new.txt").unwrap().bytes(), b"created\n");
let replaced = created
.replace_text("src/unusual.data", "new".into())
.unwrap();
assert_eq!(replaced.file("src/unusual.data").unwrap().bytes(), b"new");
assert_eq!(created.file("src/unusual.data").unwrap().bytes(), b"old");
let error = replaced
.replace_text("assets/icon.png", "forbidden".into())
.unwrap_err();
assert_eq!(
error.message(),
"this file is a non-text object and cannot be modified"
);
let renamed = replaced
.rename("assets/icon.png", "assets/logo.png")
.unwrap();
assert!(renamed.file("assets/icon.png").is_none());
assert_eq!(
renamed.file("assets/logo.png").unwrap().bytes(),
[0, 159, 255]
);
assert!(
renamed
.delete("assets/logo.png")
.unwrap()
.file("assets/logo.png")
.is_none()
);
}
#[test]
fn explicit_manifest_projection_changes_only_the_version_token() {
let manifest = b"{\n \"name\":\"alpha\", \"version\" : \"0.0.0\",\n \"entry\":\"index.js\",\"tests\":\"index.js\",\"dependencies\":[]\n}\n";
let document = CodeDocument::new(
family(),
vec![
SourceFile::new("Documentation.md", b"docs\r\n".to_vec()),
SourceFile::new(
"index.js",
b"export async function runTests() {}\n".to_vec(),
),
SourceFile::new("k1-web.json", manifest.to_vec()),
SourceFile::new("assets/blob.bin", vec![0, 255, 1]),
],
)
.unwrap();
let package = document.to_source_package(Version::new(2, 3, 4)).unwrap();
let projected = package
.files()
.iter()
.find(|file| file.path() == "k1-web.json")
.unwrap();
assert_eq!(
std::str::from_utf8(projected.bytes()).unwrap(),
std::str::from_utf8(manifest)
.unwrap()
.replace("\"0.0.0\"", "\"2.3.4\"")
);
assert_eq!(
package
.files()
.iter()
.find(|file| file.path() == "assets/blob.bin")
.unwrap()
.bytes(),
[0, 255, 1]
);
let recovered = CodeDocument::from_source_package(&package).unwrap();
assert_eq!(recovered.files(), package.files());
}
#[test]
fn tree_shape_is_validated_while_package_validity_is_deferred() {
let temporary = CodeDocument::new(
family(),
vec![SourceFile::new("nested/readme.odd", b"draft".to_vec())],
)
.unwrap();
assert!(temporary.to_source_package(Version::new(1, 0, 0)).is_err());
for files in [
vec![
SourceFile::new("same", Vec::new()),
SourceFile::new("same", Vec::new()),
],
vec![SourceFile::new("../outside", Vec::new())],
vec![
SourceFile::new("assets", Vec::new()),
SourceFile::new("assets/icon.png", Vec::new()),
],
] {
assert!(CodeDocument::new(family(), files).is_err());
}
}
#[test]
fn mutations_reject_missing_colliding_and_ambiguous_paths() {
let document = CodeDocument::template(family(), Language::JavaScript).unwrap();
assert!(
document
.create_text("index.js", "duplicate".into())
.is_err()
);
assert!(document.rename("index.js", "Documentation.md").is_err());
assert!(document.rename("index.js", "index.js").is_err());
assert!(document.rename("missing", "other").is_err());
assert!(document.delete("missing").is_err());
let ambiguous = CodeDocument::new(
family(),
vec![
SourceFile::new("Documentation.md", b"docs".to_vec()),
SourceFile::new("index.js", b"export function runTests() {}".to_vec()),
SourceFile::new("index.html", b"<p>x</p>".to_vec()),
],
)
.unwrap();
assert!(ambiguous.to_source_package(Version::new(1, 0, 0)).is_err());
}