#![forbid(unsafe_code)]
use haematite_cli::serve::schema::{Collection as CollectionSchema, validate};
use serde_json::{Value, json};
type TestResult = Result<(), Box<dyn std::error::Error>>;
#[test]
fn schemaless_collections_accept_every_json_kind_without_transforming_it() -> TestResult {
for bytes in [
"null",
"false",
"123",
"\"text\"",
"[1, 2]",
"{ \"extra\": true }",
] {
let document: Value = serde_json::from_str(bytes)?;
let before = document.clone();
validate(None, &document)?;
assert_eq!(document, before);
}
Ok(())
}
#[test]
fn required_core_allows_extensions_and_preserves_null_and_defaults() -> TestResult {
let shape = CollectionSchema::compile(&json!({
"type": "object", "required": ["name"],
"properties": {"name": {"type": "string"}, "count": {"default": 4}}
}))?;
let document = json!({"name": "Buckley", "extra": {"value": null}});
let before = document.clone();
shape.validate(&document)?;
assert_eq!(document, before);
assert!(document.get("count").is_none());
let Err(error) = shape.validate(&json!({"extra": true})) else {
return Err("missing core field was accepted".into());
};
assert_eq!(error.path, "/name");
Ok(())
}
#[test]
fn nested_array_refusal_names_the_field_with_escaped_json_pointer() -> TestResult {
let shape = CollectionSchema::compile(&json!({
"type": "array", "items": {"type": "object", "required": ["a/b~c"]}
}))?;
let Err(error) = shape.validate(&json!([{}])) else {
return Err("missing nested field was accepted".into());
};
assert_eq!(error.path, "/0/a~1b~0c");
Ok(())
}
#[test]
fn closed_shapes_require_an_explicit_constraint() -> TestResult {
let shape = CollectionSchema::compile(&json!({
"properties": {"name": {"type": ["string", "null"]}},
"additionalProperties": false
}))?;
shape.validate(&json!({"name": null}))?;
assert!(shape.validate(&json!({"extra": 1})).is_err());
Ok(())
}
#[test]
fn numeric_constraints_follow_schema_equality_not_number_representation() -> TestResult {
let shape = CollectionSchema::compile(&json!({"type": "integer", "enum": [1], "minimum": 1}))?;
shape.validate(&json!(1.0))?;
assert!(shape.validate(&json!(1.0001)).is_err());
assert!(shape.validate(&json!(0)).is_err());
Ok(())
}
#[test]
fn documents_do_not_lose_large_integers_to_floating_point_rounding() -> TestResult {
let exact: Value = serde_json::from_str("18446744073709551616001")?;
let different: Value = serde_json::from_str("18446744073709551616002")?;
let shape = CollectionSchema::compile(&json!({"const": exact}))?;
shape.validate(&exact)?;
assert!(shape.validate(&different).is_err());
let huge: Value = serde_json::from_str("1e400")?;
validate(None, &huge)?;
Ok(())
}
#[test]
fn declarations_refuse_unknown_constraints_malformed_shapes_and_external_refs() {
for schema in [
json!({"properties": {"name": {"pattern": "x"}}}),
json!({"required": ["a", "a"]}),
json!({"type": "invented"}),
json!({"$ref": "file:///etc/passwd"}),
json!({"$ref": "http://127.0.0.1:4000/"}),
json!({"$schema": "https://example.invalid/schema"}),
] {
assert!(
CollectionSchema::compile(&schema).is_err(),
"accepted {schema}"
);
}
}
#[test]
fn boolean_shapes_and_nesting_limits_are_enforced() -> TestResult {
CollectionSchema::compile(&json!(true))?.validate(&json!({}))?;
assert!(
CollectionSchema::compile(&json!(false))?
.validate(&json!({}))
.is_err()
);
let mut schema = json!(true);
for _ in 0..66 {
schema = json!({"items": schema});
}
assert!(CollectionSchema::compile(&schema).is_err());
Ok(())
}