use ic_memory::{
AllocationDeclaration, MemoryAllocationPool, MemoryAllocationPoolError, MemoryAuthority,
MemoryDeclarationError, MemoryRequest, PolicyIdentity, PolicyIdentityError, SchemaMetadata,
};
#[test]
fn declarations_round_trip_checked_key_slot_and_schema() {
let declaration = AllocationDeclaration::memory_manager("app.rows.v1", 100).unwrap();
let json = serde_json::json!({
"stable_key": "app.rows.v1",
"slot": 100,
"schema": { "schema_version": null },
});
assert_eq!(serde_json::to_value(&declaration).unwrap(), json);
assert_eq!(
serde_json::from_value::<AllocationDeclaration>(json).unwrap(),
declaration
);
let expected =
b"\xa3\x6astable_key\x6bapp.rows.v1\x64slot\x18\x64\x66schema\xa1\x6eschema_version\xf6";
let mut bytes = Vec::new();
ciborium::into_writer(&declaration, &mut bytes).unwrap();
assert_eq!(bytes, expected);
assert_eq!(
ciborium::from_reader::<AllocationDeclaration, _>(bytes.as_slice()).unwrap(),
declaration
);
}
#[test]
fn diagnostic_metadata_accepts_printable_ascii_through_the_byte_limit() {
for value in [
" ".to_string(),
(b' '..=b'~').map(char::from).collect(),
"x".repeat(256),
] {
PolicyIdentity::new(value.as_str(), 1).unwrap();
MemoryRequest::new(value.as_str(), "app.rows.v1", SchemaMetadata::default()).unwrap();
let grant = MemoryAuthority::new(value.as_str(), "app.").unwrap();
let json = serde_json::json!({"authority":value,"key_prefix":"app."});
assert_eq!(serde_json::to_value(&grant).unwrap(), json);
assert_eq!(
serde_json::from_value::<MemoryAuthority>(json).unwrap(),
grant
);
let mut bytes = Vec::new();
ciborium::into_writer(&grant, &mut bytes).unwrap();
assert_eq!(
ciborium::from_reader::<MemoryAuthority, _>(bytes.as_slice()).unwrap(),
grant
);
}
}
#[test]
fn diagnostic_metadata_preserves_field_errors_and_rejection_order() {
let cases = [
(
String::new(),
PolicyIdentityError::EmptyName,
"must not be empty",
),
(
format!("{}\0", "x".repeat(256)),
PolicyIdentityError::NameTooLong {
length: 257,
maximum: 256,
},
"must be at most 256 bytes",
),
(
"é\0".to_string(),
PolicyIdentityError::NonAsciiName,
"must be ASCII",
),
(
"printable\u{7f}".to_string(),
PolicyIdentityError::ControlCharacterName,
"must not contain ASCII control characters",
),
];
for (value, policy, reason) in cases {
assert_eq!(PolicyIdentity::new(value.as_str(), 1).unwrap_err(), policy);
assert_eq!(
MemoryRequest::new(value.as_str(), "app.rows.v1", SchemaMetadata::default())
.unwrap_err(),
MemoryDeclarationError::InvalidAuthority { reason }
);
assert_eq!(
MemoryAuthority::new(value.as_str(), "app.").unwrap_err(),
MemoryAllocationPoolError::InvalidAuthority { reason }
);
}
}
#[test]
fn authority_decode_rejects_invalid_metadata_before_pool_assembly() {
for grant in [
serde_json::json!({"authority":"","key_prefix":"app."}),
serde_json::json!({"authority":"app","key_prefix":"ic_memory."}),
serde_json::json!({"authority":"app","key_prefix":"App."}),
serde_json::json!({"authority":"app","key_prefix":"app"}),
serde_json::json!({"authority":"app","key_prefix":"app.","extra":true}),
] {
assert!(serde_json::from_value::<MemoryAuthority>(grant.clone()).is_err());
let mut bytes = Vec::new();
ciborium::into_writer(&grant, &mut bytes).unwrap();
assert!(ciborium::from_reader::<MemoryAuthority, _>(bytes.as_slice()).is_err());
assert!(
serde_json::from_value::<MemoryAllocationPool>(
serde_json::json!({"authorities":[grant],"excluded_ranges":[]})
)
.is_err()
);
}
}
#[test]
fn authority_decode_preserves_current_shape_and_required_fields() {
let grant = MemoryAuthority::new("app", "app.").unwrap();
let json = serde_json::json!({"authority":"app","key_prefix":"app."});
assert_eq!(serde_json::to_value(&grant).unwrap(), json);
for field in ["authority", "key_prefix"] {
let mut incomplete = json.clone();
incomplete.as_object_mut().unwrap().remove(field);
assert!(serde_json::from_value::<MemoryAuthority>(incomplete).is_err());
}
}