use kcode_k1_transaction_id::TxId;
use kcode_k1_web_package::{
AuthorityId, DependencySelector, SourceFile, SourcePackage, WebFamily, WebId,
};
use semver::Version;
fn authority(byte: u8) -> AuthorityId {
AuthorityId::new(TxId::from_bytes([byte; 12]))
}
fn package_id() -> WebId {
WebId::new(
WebFamily::new(authority(1), "alpha").unwrap(),
Version::new(1, 2, 3),
)
.unwrap()
}
fn manifest(name: &str, version: &str, entry: &str, tests: &str, dependencies: &str) -> String {
format!(
r#"{{"name":"{name}","version":"{version}","entry":"{entry}","tests":"{tests}","dependencies":{dependencies}}}"#
)
}
fn complete_files(manifest: String) -> Vec<SourceFile> {
vec![
SourceFile::new("tests.js", b"export function runTests() {}".to_vec()),
SourceFile::new("k1-web.json", manifest.into_bytes()),
SourceFile::new("index.js", b"export {};".to_vec()),
SourceFile::new("Documentation.md", b"contract".to_vec()),
]
}
fn standard_manifest(dependencies: &str) -> String {
manifest("alpha", "1.2.3", "index.js", "tests.js", dependencies)
}
fn dependency(authority: &str, name: &str, selector: &str) -> String {
format!(r#"{{"authority":"{authority}","name":"{name}","selector":"{selector}"}}"#)
}
fn dependency_list(dependencies: &[String]) -> String {
format!("[{}]", dependencies.join(","))
}
#[test]
fn validates_names_versions_and_selectors() {
let family = WebFamily::new(authority(1), "a-b2").unwrap();
assert_eq!(
(family.authority(), family.logical_name()),
(authority(1), "a-b2")
);
for name in ["", "-a", "a-", "a--b", "A", "a_b", "é"] {
assert!(WebFamily::new(authority(1), name).is_err(), "{name}");
}
assert!(WebFamily::new(authority(1), "a".repeat(37)).is_err());
for version in ["1.2.3-pre", "1.2.3+build"] {
assert!(WebId::new(family.clone(), Version::parse(version).unwrap()).is_err());
}
for text in ["*", "0", "0.2", "0.2.3"] {
assert_eq!(DependencySelector::parse(text).unwrap().to_string(), text);
}
assert!(
DependencySelector::parse("0")
.unwrap()
.matches(&Version::new(0, 8, 9))
);
for text in ["", "01", "1.02", "1.2.03", "1.2.3.4", "v1", "1.*"] {
assert!(DependencySelector::parse(text).is_err(), "{text}");
}
assert!(
!DependencySelector::parse("*")
.unwrap()
.matches(&Version::parse("1.2.3-pre").unwrap())
);
}
#[test]
fn validates_paths_utf8_and_arbitrary_assets() {
for path in ["", "/a", "a//b", "a/./b", "a/../b", "a\\b", "a:b"] {
let mut files = complete_files(standard_manifest("[]"));
files.push(SourceFile::new(path, Vec::new()));
assert!(SourcePackage::new(package_id(), files).is_err(), "{path}");
}
let mut duplicate = complete_files(standard_manifest("[]"));
duplicate.push(SourceFile::new("index.js", Vec::new()));
assert!(SourcePackage::new(package_id(), duplicate).is_err());
let mut collision = complete_files(standard_manifest("[]"));
collision.push(SourceFile::new("assets", Vec::new()));
collision.push(SourceFile::new("assets/icon.bin", Vec::new()));
assert!(SourcePackage::new(package_id(), collision).is_err());
for path in ["k1-web.json", "Documentation.md", "index.js", "tests.js"] {
let mut files = complete_files(standard_manifest("[]"));
*files.iter_mut().find(|file| file.path() == path).unwrap() =
SourceFile::new(path, vec![0xff]);
assert!(SourcePackage::new(package_id(), files).is_err(), "{path}");
}
let bytes = vec![0, 159, 255];
let mut files = complete_files(standard_manifest("[]"));
files.push(SourceFile::new("z/asset.bin", bytes.clone()));
let package = SourcePackage::new(package_id(), files).unwrap();
assert!(package.files().windows(2).all(|pair| pair[0] < pair[1]));
assert_eq!(package.files().last().unwrap().bytes(), bytes);
}
#[test]
fn enforces_exact_manifest_and_javascript_targets() {
let valid = standard_manifest("[]");
let invalid = [
valid.replace("\"dependencies\":[]", "\"dependencies\":[],\"extra\":true"),
valid.replace(",\"tests\":\"tests.js\"", ""),
manifest("other", "1.2.3", "index.js", "tests.js", "[]"),
manifest("alpha", "1.2.4", "index.js", "tests.js", "[]"),
manifest("alpha", "01.2.3", "index.js", "tests.js", "[]"),
manifest("alpha", "1.2.3", "index.txt", "tests.js", "[]"),
manifest("alpha", "1.2.3", "index.js", "missing.mjs", "[]"),
];
for manifest in invalid {
assert!(SourcePackage::new(package_id(), complete_files(manifest)).is_err());
}
let package = SourcePackage::new(package_id(), complete_files(valid)).unwrap();
assert_eq!((package.entry(), package.tests()), ("index.js", "tests.js"));
}
#[test]
fn validates_deduplicates_and_sorts_dependencies() {
let first = authority(0xab).to_string();
let second = authority(0xcd).to_string();
let declarations = dependency_list(&[
dependency(&second, "alpha", "*"),
dependency(&first, "zeta", "2"),
dependency(&first, "alpha", "1.2.3"),
dependency(&first, "alpha", "1"),
dependency(&first, "alpha", "0"),
dependency(&first, "alpha", "*"),
]);
let package = SourcePackage::new(
package_id(),
complete_files(standard_manifest(&declarations)),
)
.unwrap();
let dependencies = package.dependencies();
let selectors = dependencies
.iter()
.map(|item| item.selector().to_string())
.collect::<Vec<_>>();
assert_eq!(selectors, ["*", "0", "1", "1.2.3", "2", "*"]);
assert_eq!(dependencies[0].authority().to_string(), first);
assert_eq!(dependencies[4].name(), "zeta");
assert_eq!(dependencies[5].authority().to_string(), second);
let duplicate = dependency(&first, "alpha", "*");
let unknown = duplicate.replace('}', ",\"extra\":true}");
for declarations in [
dependency_list(&[duplicate.clone(), duplicate]),
dependency_list(&[dependency(&first.to_uppercase(), "alpha", "*")]),
dependency_list(&[unknown]),
] {
let files = complete_files(standard_manifest(&declarations));
assert!(SourcePackage::new(package_id(), files).is_err());
}
}