use crate::context::builder::AadContextBuilder;
use crate::context::AadContext;
use crate::error::AadError;
#[test]
fn test_context_new() {
let ctx = AadContext::new("org_abc", "secrets/db", "encryption").unwrap();
assert_eq!(ctx.version(), 1);
assert_eq!(ctx.tenant(), "org_abc");
assert_eq!(ctx.resource(), "secrets/db");
assert_eq!(ctx.purpose(), "encryption");
assert!(ctx.timestamp().is_none());
assert!(ctx.extensions().is_empty());
}
#[test]
fn test_context_with_timestamp() {
let ctx = AadContext::new("org", "res", "test")
.unwrap()
.with_timestamp(1_706_400_000)
.unwrap();
assert_eq!(ctx.timestamp(), Some(1_706_400_000));
}
#[test]
fn test_context_with_extension() {
let ctx = AadContext::new("org", "res", "test")
.unwrap()
.with_string_extension("x_vault_cluster", "us-east-1")
.unwrap();
assert_eq!(ctx.extensions().len(), 1);
}
#[test]
fn test_builder() {
let ctx = AadContextBuilder::new()
.tenant("org_abc")
.resource("secrets/db")
.purpose("encryption")
.timestamp(1_706_400_000)
.extension_string("x_app_field", "value")
.build()
.unwrap();
assert_eq!(ctx.tenant(), "org_abc");
assert_eq!(ctx.timestamp(), Some(1_706_400_000));
assert_eq!(ctx.extensions().len(), 1);
}
#[test]
fn test_builder_missing_required() {
let result = AadContextBuilder::new().tenant("org").resource("res").build();
assert!(matches!(result, Err(AadError::MissingRequiredField { field: "purpose" })));
}
#[test]
fn test_canonicalize_order() {
let ctx = AadContext::new("org_abc", "secrets/db", "encryption").unwrap();
let canonical = ctx.canonicalize_string().unwrap();
assert_eq!(
canonical,
r#"{"purpose":"encryption","resource":"secrets/db","tenant":"org_abc","v":1}"#
);
}
#[test]
fn test_builder_extension_string_nul_byte_surfaces_error() {
let result = AadContextBuilder::new()
.tenant("org")
.resource("res")
.purpose("test")
.extension_string("x_app_key", "val\0ue")
.build();
assert!(matches!(result, Err(AadError::NulByteInValue { .. })));
}
#[test]
fn test_builder_extension_int_out_of_range_surfaces_error() {
let result = AadContextBuilder::new()
.tenant("org")
.resource("res")
.purpose("test")
.extension_int("x_app_key", u64::MAX)
.build();
assert!(matches!(result, Err(AadError::IntegerOutOfRange { .. })));
}
#[test]
fn test_serialize_key_order() {
let ctx = AadContext::new("org_abc", "secrets/db", "encryption")
.unwrap()
.with_timestamp(1_706_400_000)
.unwrap()
.with_string_extension("x_app_env", "prod")
.unwrap()
.with_int_extension("x_app_ver", 42)
.unwrap();
let bytes = ctx.canonicalize().unwrap();
let value: serde_json::Value = serde_json::from_slice(&bytes).unwrap();
let emitted_keys: Vec<&str> = value
.as_object()
.unwrap()
.keys()
.map(String::as_str)
.collect();
let mut sorted = emitted_keys.clone();
sorted.sort_unstable();
assert_eq!(
emitted_keys, sorted,
"canonical output keys must be in lexicographic order; got {emitted_keys:?}"
);
}
#[test]
fn test_with_extension_reserved_keys_return_correct_error() {
let reserved = ["v", "tenant", "resource", "purpose", "ts"];
for key in reserved {
let result = AadContext::new("org", "res", "test")
.unwrap()
.with_string_extension(key, "value");
assert!(
matches!(result, Err(AadError::ReservedKeyAsExtension { .. })),
"expected ReservedKeyAsExtension for key '{key}', got {result:?}"
);
}
}