use crate::{canonicalize_object, parse_default, validate_object, AadError};
use rstest::rstest;
#[rstest]
#[case(
r#"{"v":1,"tenant":"org","resource":"res","purpose":"test","ts":9007199254740992}"#,
"ts one above MAX_SAFE_INTEGER"
)]
fn test_negative_integer_above_max_safe(#[case] input: &str, #[case] _label: &str) {
let result = parse_default(input);
assert!(matches!(result, Err(AadError::IntegerOutOfRange { .. })));
}
#[rstest]
#[case(r#"{"v":1,"tenant":"org","resource":"res","purpose":"test","ts":-1}"#, "ts negative")]
fn test_negative_integer_value(#[case] input: &str, #[case] _label: &str) {
let result = parse_default(input);
assert!(matches!(result, Err(AadError::NegativeInteger { .. })));
}
#[rstest]
#[case(r#"{"v":1,"tenant":"","resource":"res","purpose":"test"}"#, "tenant")]
fn test_negative_empty_tenant(#[case] input: &str, #[case] field: &'static str) {
let result = parse_default(input);
assert!(matches!(result, Err(AadError::FieldTooShort { field: f, .. }) if f == field));
}
#[rstest]
#[case("tenant")]
fn test_negative_nul_byte_in_tenant(#[case] _field: &str) {
let input = r#"{"v":1,"tenant":"org\u0000abc","resource":"res","purpose":"test"}"#;
let result = parse_default(input);
assert!(matches!(result, Err(AadError::NulByteInValue { field: "tenant" })));
}
#[rstest]
#[case(
r#"{"v":1,"tenant":"org","resource":"res","purpose":"test","unknown":"value"}"#,
"non-extension unknown field"
)]
#[case(
r#"{"v":1,"tenant":"org","resource":"res","purpose":"test","unknown":"value"}"#,
"unknown field non-extension"
)]
fn test_negative_unknown_field(#[case] input: &str, #[case] _label: &str) {
let result = parse_default(input);
assert!(matches!(result, Err(AadError::UnknownField { .. })));
}
#[rstest]
#[case(r#"{"v":1,"tenant":"org","resource":"res"}"#, "purpose")]
fn test_negative_missing_required_field(#[case] input: &str, #[case] field: &'static str) {
let result = parse_default(input);
assert!(matches!(result, Err(AadError::MissingRequiredField { field: f }) if f == field));
}
#[rstest]
#[case(
r#"{"v":1,"tenant":"org","tenant":"other","resource":"res","purpose":"test"}"#,
"duplicate tenant"
)]
fn test_negative_duplicate_key(#[case] input: &str, #[case] _label: &str) {
let result = parse_default(input);
assert!(matches!(result, Err(AadError::DuplicateKey { .. })));
}
#[test]
fn test_duplicate_key_exact_key_extracted() {
let input = r#"{"v":1,"tenant":"org","resource":"res","purpose":"test","x_a_b":1,"x_a_b":2}"#;
match parse_default(input) {
Err(AadError::DuplicateKey { key }) => {
assert_eq!(key, "x_a_b", "expected duplicate key 'x_a_b', got: {key:?}");
}
other => panic!("expected DuplicateKey, got: {other:?}"),
}
}
#[rstest]
#[case(r#"{"v":1,"v":2,"tenant":"org","resource":"res","purpose":"test"}"#, "v", "first field")]
#[case(
r#"{"v":1,"tenant":"org","resource":"res","resource":"other","purpose":"test"}"#,
"resource",
"middle field"
)]
#[case(
r#"{"v":1,"tenant":"org","resource":"res","purpose":"test","purpose":"other"}"#,
"purpose",
"last field"
)]
fn test_negative_duplicate_key_position(
#[case] input: &str,
#[case] expected_key: &str,
#[case] _position: &str,
) {
match parse_default(input) {
Err(AadError::DuplicateKey { ref key }) => {
assert_eq!(
key, expected_key,
"expected duplicate key '{expected_key}' ({_position}), got {key:?}"
);
}
other => panic!("expected DuplicateKey ({_position}), got: {other:?}"),
}
}
#[rstest]
#[case(
r#"{"v":1,"tenant":"org","resource":"res","purpose":"test","x_foo":"value"}"#,
"x_foo missing second segment"
)]
#[case(
r#"{"v":1,"tenant":"org","resource":"res","purpose":"test","x__field":"value"}"#,
"x__field empty app segment"
)]
#[case(
r#"{"v":1,"tenant":"org","resource":"res","purpose":"test","x_app_":"value"}"#,
"x_app_ empty field part"
)]
#[case(
r#"{"v":1,"tenant":"org","resource":"res","purpose":"test","x_foo":"bad"}"#,
"x_foo no second segment (duplicate label variant)"
)]
#[case(
r#"{"v":1,"tenant":"org","resource":"res","purpose":"test","x__bar":"bad"}"#,
"x__bar empty app segment (duplicate label variant)"
)]
fn test_negative_invalid_extension_key_format(#[case] input: &str, #[case] _label: &str) {
let result = parse_default(input);
assert!(
matches!(result, Err(AadError::InvalidExtensionKeyFormat { .. })),
"{_label}: must return InvalidExtensionKeyFormat; got {result:?}"
);
}
#[test]
fn test_negative_extension_key_x_bare() {
let result = validate_object(r#"{"v":1,"x_":"bad"}"#);
let result2 =
parse_default(r#"{"v":1,"tenant":"org","resource":"res","purpose":"test","x_":"bad"}"#);
assert!(
matches!(result2, Err(AadError::InvalidExtensionKeyFormat { .. })),
"bare x_ key must return InvalidExtensionKeyFormat; got {result2:?}"
);
let _ = result;
}
#[rstest]
#[case(r#"{"v":2,"tenant":"org","resource":"res","purpose":"test"}"#, 2_u64)]
#[case(r#"{"v":0,"tenant":"org","resource":"res","purpose":"test"}"#, 0_u64)]
fn test_negative_unsupported_version(#[case] input: &str, #[case] version: u64) {
let result = parse_default(input);
assert!(
matches!(result, Err(AadError::UnsupportedVersion { version: v }) if v == version),
"v={version} must return UnsupportedVersion; got {result:?}"
);
}
#[rstest]
#[case(r#"{"v":"1","tenant":"org","resource":"res","purpose":"test"}"#, "v", "string version")]
#[case(r#"{"v":1,"tenant":123,"resource":"res","purpose":"test"}"#, "tenant", "integer tenant")]
#[case(
r#"{"v":1,"tenant":"org","resource":"res","purpose":"test","ts":"1706400000"}"#,
"ts",
"string ts"
)]
fn test_negative_wrong_field_type(
#[case] input: &str,
#[case] field: &'static str,
#[case] _label: &str,
) {
let result = parse_default(input);
assert!(
matches!(result, Err(AadError::WrongFieldType { field: f, .. }) if f == field),
"{_label}: must return WrongFieldType for field '{field}'; got {result:?}"
);
}
#[rstest]
#[case(r#"{"v":1,"tenant":"org","resource":"res","purpose":"test""#, "truncated JSON")]
#[case(r#"["v", 1, "tenant", "org"]"#, "JSON array")]
fn test_negative_invalid_json(#[case] input: &str, #[case] _label: &str) {
let result = parse_default(input);
assert!(
matches!(result, Err(AadError::InvalidJson { .. })),
"{_label}: must return InvalidJson; got {result:?}"
);
}
#[test]
fn test_invalid_field_key_reason_contains_character() {
let input = r#"{"v":1,"tenant":"org","resource":"res","purpose":"test","X_app_foo":"val"}"#;
let result = parse_default(input);
match result {
Err(AadError::InvalidFieldKey { reason, .. }) => {
assert!(
reason.contains("'X'"),
"expected reason to identify character 'X', got: {reason}"
);
}
other => panic!("expected InvalidFieldKey, got: {other:?}"),
}
}
#[rstest]
#[case(r#""hello""#, "JSON string")]
#[case("42", "JSON number")]
#[case("true", "JSON boolean true")]
#[case("false", "JSON boolean false")]
#[case("null", "JSON null")]
fn test_negative_generic_rejects_non_object(#[case] input: &str, #[case] label: &str) {
let result = canonicalize_object(input);
assert!(
matches!(result, Err(AadError::InvalidJson { .. })),
"{label} must return InvalidJson; got {result:?}"
);
}
#[test]
fn test_negative_duplicate_ts() {
let input = r#"{"v":1,"tenant":"org","resource":"res","purpose":"test","ts":1,"ts":2}"#;
assert!(
matches!(parse_default(input), Err(AadError::DuplicateKey { ref key }) if key == "ts"),
"duplicate ts must return DuplicateKey"
);
}
#[rstest]
#[case(r#"{"v":1,"tenant":["a","b"],"resource":"res","purpose":"test"}"#, "array as tenant")]
#[case(r#"{"v":1,"tenant":{"sub":"obj"},"resource":"res","purpose":"test"}"#, "object as tenant")]
#[case(r#"{"v":1,"tenant":true,"resource":"res","purpose":"test"}"#, "bool as tenant")]
#[case(
r#"{"v":1,"tenant":"org","resource":"res","purpose":"test","x_app_flag":true}"#,
"bool as extension"
)]
#[case(
r#"{"v":1,"tenant":"org","resource":"res","purpose":"test","x_app_data":[]}"#,
"array as extension"
)]
fn test_negative_invalid_field_value_type(#[case] input: &str, #[case] _label: &str) {
assert!(
matches!(parse_default(input), Err(AadError::WrongFieldType { .. })),
"{_label} must return WrongFieldType"
);
}
#[rstest]
#[case(
r#"{"v":1,"tenant":"org","resource":"res","purpose":"test","x-region":"us"}"#,
"hyphen in key"
)]
#[case(r#"{"v":1,"tenant":"org","resource":"res","purpose":"test","x.region":"us"}"#, "dot in key")]
fn test_negative_invalid_key_characters(#[case] input: &str, #[case] _label: &str) {
assert!(
matches!(parse_default(input), Err(AadError::InvalidFieldKey { .. })),
"{_label} must return InvalidFieldKey"
);
}
#[test]
fn test_negative_uppercase_x_prefix() {
let input = r#"{"v":1,"tenant":"org","resource":"res","purpose":"test","X_region":"us"}"#;
assert!(
matches!(parse_default(input), Err(AadError::InvalidFieldKey { .. })),
"X_region must return InvalidFieldKey"
);
}