use amiss_wire::ExitClass;
use amiss_wire::digest::{hb, hj};
use amiss_wire::json::{ErrorKind, MAX_SAFE_INTEGER, Value, canonical, parse};
#[expect(clippy::unwrap_used, reason = "test helper on inputs that must fail")]
fn kind(input: &[u8]) -> ErrorKind {
parse(input).unwrap_err().kind
}
#[test]
fn exit_codes_are_contract() {
assert_eq!(ExitClass::Success.code(), 0);
assert_eq!(ExitClass::BlockingFindings.code(), 1);
assert_eq!(ExitClass::Failure.code(), 2);
}
#[test]
fn accepts_the_restricted_grammar() {
assert_eq!(parse(b"null").unwrap(), Value::Null);
assert_eq!(parse(b" true ").unwrap(), Value::Bool(true));
assert_eq!(parse(b"-1").unwrap(), Value::Integer(-1));
assert_eq!(
parse(b"9007199254740991").unwrap(),
Value::Integer(MAX_SAFE_INTEGER)
);
assert_eq!(
parse(b"-9007199254740991").unwrap(),
Value::Integer(-9_007_199_254_740_991)
);
assert_eq!(
parse(br#""A\/\n""#).unwrap(),
Value::String("A/\n".to_owned())
);
assert_eq!(
parse("\"\u{1f600}\"".as_bytes()).unwrap(),
Value::String("\u{1f600}".to_owned())
);
assert_eq!(
parse(b"[0, {\"a\": []}]").unwrap(),
Value::Array(vec![
Value::Integer(0),
Value::Object(vec![("a".to_owned(), Value::Array(Vec::new()))]),
])
);
}
#[test]
fn rejects_everything_the_contract_names() {
let cases: &[(&[u8], ErrorKind)] = &[
(b"", ErrorKind::UnexpectedEnd),
(b"\xEF\xBB\xBF{}", ErrorKind::ByteOrderMark),
(b"\xff", ErrorKind::InvalidUtf8),
(br#"{"a":1,"a":2}"#, ErrorKind::DuplicateKey),
(br#"{"a":1,"a":2}"#, ErrorKind::DuplicateKey),
(b"-0", ErrorKind::NegativeZero),
(b"1.5", ErrorKind::FractionOrExponent),
(b"1e3", ErrorKind::FractionOrExponent),
(b"0E0", ErrorKind::FractionOrExponent),
(b"9007199254740992", ErrorKind::IntegerOutOfRange),
(b"-9007199254740992", ErrorKind::IntegerOutOfRange),
(b"99999999999999999999", ErrorKind::IntegerOutOfRange),
(b"01", ErrorKind::UnexpectedByte),
(br#""\ud800""#, ErrorKind::LoneSurrogate),
(br#""\udc00""#, ErrorKind::LoneSurrogate),
(br#""\ud83dx""#, ErrorKind::LoneSurrogate),
(br#""\x""#, ErrorKind::InvalidEscape),
(br#""\u00g0""#, ErrorKind::InvalidEscape),
(b"\"\x01\"", ErrorKind::ControlCharacter),
(b"1 2", ErrorKind::TrailingContent),
(b"{} {}", ErrorKind::TrailingContent),
(b"{", ErrorKind::UnexpectedEnd),
(br#"{"a":1"#, ErrorKind::UnexpectedEnd),
(b"[1,]", ErrorKind::UnexpectedByte),
(b"{\"a\":1,}", ErrorKind::UnexpectedByte),
(b"nul", ErrorKind::UnexpectedEnd),
(b"nulL", ErrorKind::UnexpectedByte),
(b"'a'", ErrorKind::UnexpectedByte),
];
for (input, expected) in cases {
assert_eq!(
kind(input),
*expected,
"input {:?}",
String::from_utf8_lossy(input)
);
}
}
#[test]
fn rejects_past_the_depth_limit() {
let mut deep = vec![b'['; 600];
deep.extend(vec![b']'; 600]);
assert_eq!(kind(&deep), ErrorKind::DepthLimit);
}
#[test]
fn error_offsets_point_at_the_defect() {
assert_eq!(parse(b"1 2").unwrap_err().offset, 2);
assert_eq!(parse(br#"{"a":1,"a":2}"#).unwrap_err().offset, 7);
}
#[test]
fn canonical_matches_the_gv003_bytes() {
let value = parse("{ \"z\" : \"\u{e9}\", \"a\" : 1 }".as_bytes()).unwrap();
assert_eq!(canonical(&value), "{\"a\":1,\"z\":\"\u{e9}\"}".as_bytes());
}
#[test]
fn canonical_sorts_keys_by_utf16_code_units() {
let astral = "\u{10000}";
let bmp = "\u{fffd}";
let input = format!("{{\"{bmp}\":2,\"{astral}\":1}}");
let value = parse(input.as_bytes()).unwrap();
let expected = format!("{{\"{astral}\":1,\"{bmp}\":2}}");
assert_eq!(canonical(&value), expected.into_bytes());
}
#[test]
fn canonical_escapes_match_jcs() {
let value = parse(br#"["\u0007\b\/<\">"]"#).unwrap();
assert_eq!(canonical(&value), b"[\"\\u0007\\b/<\\\">\"]");
}
#[test]
fn reproduces_the_normative_seed_vectors() {
let gv001 = parse(br#"{"claim_id":"docs.expr-precedence"}"#).unwrap();
assert_eq!(
hj("assure/claim-key/v1", &gv001).to_string(),
"sha256:f6a22f480cab9ed6e0fc82bcbe67eba85d88f10103f5107008809dec44fb71b0"
);
let gv002 = parse(br#"{"members":[]}"#).unwrap();
assert_eq!(
hj("assure/path-set-projection/v1", &gv002).to_string(),
"sha256:6765a67e22b2efbaaf89509cd34a70682613f002cd82d0ff4e08332e26b76954"
);
let gv003 = parse("{\"z\":\"\u{e9}\",\"a\":1}".as_bytes()).unwrap();
assert_eq!(
hj("assure/test-json/v1", &gv003).to_string(),
"sha256:1a2aab8858a444002cd16e1fa53cc33fd12e5e6ac4568f85e06bef971a28425d"
);
assert_eq!(
hb("assure/text-projection/v1", b"a\nb\n").to_string(),
"sha256:bab154d44fb1340ee8c20af6a1e36b9a903a5e44c584f8ce524237f0289b88c6"
);
assert_eq!(
hb("assure/raw-bytes/v1", b"").to_string(),
"sha256:28031daa5fbb3a297dc947195957fe4a05c1bd2e58c56163013ee62be9368fac"
);
}
#[test]
fn domain_separation_changes_the_digest() {
assert_ne!(hb("amiss/a/v1", b"x"), hb("amiss/b/v1", b"x"));
assert_ne!(hb("amiss/a/v1", b"x"), hb("amiss/a/v1", b"y"));
}