use super::{ManifestFacts, read_manifest_facts, shape, strict_text};
use crate::dependent_version_content::PolicyIncomplete;
const PATH: &[u8] = b"package/module/a/package.json";
fn facts(text: &str) -> ManifestFacts {
return read_manifest_facts(PATH, text).unwrap_or_else(|error: PolicyIncomplete| {
panic!("{text} should parse: {error:?}");
});
}
fn failure(text: &str) -> PolicyIncomplete {
return read_manifest_facts(PATH, text)
.err()
.unwrap_or_else(|| panic!("{text} should fail"));
}
fn syntax(detail: &str) -> PolicyIncomplete {
return PolicyIncomplete::ManifestSyntax {
path: PATH.to_vec(),
detail: String::from(detail),
};
}
fn units(text: &str) -> Vec<u16> {
return text.encode_utf16().collect();
}
fn names(values: &[&str]) -> Vec<String> {
return values
.iter()
.map(|value: &&str| return String::from(*value))
.collect();
}
#[test]
fn decodes_strict_utf8_without_one_byte_order_mark() {
assert_eq!(strict_text(PATH, b"{}"), Ok("{}"));
assert_eq!(strict_text(PATH, b"\xef\xbb\xbf{}"), Ok("{}"));
assert_eq!(
strict_text(PATH, b"\xef\xbb\xbf\xef\xbb\xbf{}"),
Ok("\u{feff}{}")
);
assert_eq!(
strict_text(PATH, b"{\"name\":\"\xff\"}"),
Err(PolicyIncomplete::NotUtf8 {
path: PATH.to_vec()
})
);
}
#[test]
fn reads_name_version_runtime_fields_in_order_and_development_names() {
assert_eq!(
facts(
r#"{"name":"@scope/a","version":"1.0.0","dependencies":{"@scope/b":"workspace:*"},"peerDependencies":{"@scope/c":"*"},"optionalDependencies":{"@scope/d":"*"},"devDependencies":{"@scope/e":"workspace:*"}}"#
),
ManifestFacts {
name: String::from("@scope/a"),
version: Some(units("1.0.0")),
runtime_dependency_names: names(&["@scope/b", "@scope/c", "@scope/d"]),
dev_dependency_names: names(&["@scope/e"]),
}
);
}
#[test]
fn omits_an_absent_version_and_reads_non_object_fields_as_empty() {
assert_eq!(
facts(r#"{"name":"a","dependencies":["b"]}"#),
ManifestFacts {
name: String::from("a"),
version: None,
runtime_dependency_names: Vec::new(),
dev_dependency_names: Vec::new(),
}
);
}
#[test]
fn rejects_the_incumbent_shapes() {
assert_eq!(failure("[]"), shape(PATH, "is not a JSON object"));
assert_eq!(
failure(r#"{"version":"1.0.0"}"#),
shape(PATH, "has no string \"name\"")
);
assert_eq!(
failure(r#"{"name":"a","version":1}"#),
shape(PATH, "has a non-string \"version\"")
);
assert_eq!(
failure(r#"{"name":"a","version":null}"#),
shape(PATH, "has a non-string \"version\"")
);
assert_eq!(
failure(r#"{"name":1}"#),
shape(PATH, "has no string \"name\"")
);
assert_eq!(
shape(PATH, "is not a JSON object"),
PolicyIncomplete::ManifestShape {
path: PATH.to_vec(),
problem: String::from("package/module/a/package.json is not a JSON object"),
}
);
}
#[test]
fn reads_a_scalar_root_as_a_shape_problem() {
assert_eq!(failure("\"x\""), shape(PATH, "is not a JSON object"));
assert_eq!(failure(" 1 "), shape(PATH, "is not a JSON object"));
assert_eq!(failure("null"), shape(PATH, "is not a JSON object"));
}
#[test]
fn refuses_comments_and_trailing_commas() {
assert_eq!(
failure("{\"name\":\"a\"} // x"),
syntax("JSON allows no comments")
);
assert_eq!(
failure("/* x */ {\"name\":\"a\"}"),
syntax("JSON allows no comments")
);
assert_eq!(
failure("{\"name\":\"a\",}"),
syntax("JSON allows no trailing commas")
);
assert_eq!(
failure("{\"name\":\"a\", \r\n\t}"),
syntax("JSON allows no trailing commas")
);
assert_eq!(
failure("{\"name\":\"a\",\"d\":[1,]}"),
syntax("JSON allows no trailing commas")
);
assert_eq!(
failure("{\"name\":\"a\"},"),
syntax("JSON allows no trailing commas")
);
}
#[test]
fn reads_slashes_and_commas_inside_strings_as_text() {
assert_eq!(facts(r#"{"name":"a/b,}"}"#).name, "a/b,}");
assert_eq!(facts(r#"{"name":"a\"/,]"}"#).name, "a\"/,]");
assert_eq!(facts(r#"{"name":"a\\","x":"/"}"#).name, "a\\");
assert_eq!(facts(r#"{"x":[1, 2],"name":"a"}"#).name, "a");
}
#[test]
fn refuses_text_that_is_not_one_value() {
assert_eq!(
failure(""),
syntax("the text is not exactly one JSON value")
);
assert_eq!(
failure("1, 2"),
syntax("the text is not exactly one JSON value")
);
assert!(matches!(
failure("{\"name\":}"),
PolicyIncomplete::ManifestSyntax { .. }
));
assert!(matches!(
failure("{} {}"),
PolicyIncomplete::ManifestSyntax { .. }
));
assert!(matches!(
failure("\u{feff}{}"),
PolicyIncomplete::ManifestSyntax { .. }
));
}
#[test]
fn takes_the_last_duplicate_key() {
let read: ManifestFacts = facts(
r#"{"name":"first","version":"1.0.0","dependencies":{"x":"1"},"name":"second","version":"2.0.0","dependencies":{"y":"1"}}"#,
);
assert_eq!(read.name, "second");
assert_eq!(read.version, Some(units("2.0.0")));
assert_eq!(read.runtime_dependency_names, names(&["y"]));
assert_eq!(
failure(r#"{"name":"a","name":1}"#),
shape(PATH, "has no string \"name\"")
);
}
#[test]
fn keeps_dependency_names_unique_within_a_field() {
let read: ManifestFacts = facts(
r#"{"name":"a","dependencies":{"b":"1","c":"1","b":"2"},"peerDependencies":{"b":"1"},"devDependencies":{"d":"1","d":"1"}}"#,
);
assert_eq!(read.runtime_dependency_names, names(&["b", "c", "b"]));
assert_eq!(read.dev_dependency_names, names(&["d"]));
}
#[test]
fn handles_unpaired_surrogates_by_field() {
assert_eq!(
failure(r#"{"name":"a\ud800"}"#),
shape(PATH, "has a \"name\" holding an unpaired UTF-16 surrogate")
);
let read: ManifestFacts =
facts(r#"{"name":"a😀","version":"1.0.\udc00","dependencies":{"\ud800":"1","b":"1"}}"#);
assert_eq!(read.name, "a\u{1f600}");
assert_eq!(read.version, Some(vec![0x31, 0x2e, 0x30, 0x2e, 0xdc00]));
assert_eq!(read.runtime_dependency_names, names(&["b"]));
}