#![allow(clippy::needless_pass_by_value)]
use super::*;
use serde_json::json;
fn make_type_schema(
type_id: &str,
schema: Value,
parent: Option<Arc<GtsTypeSchema>>,
) -> GtsTypeSchema {
GtsTypeSchema::try_new(GtsTypeId::new(type_id), schema, None, parent).unwrap()
}
const BASE_ID: &str = "gts.acme.core.events.base.v1~";
const DERIVED_ID: &str = "gts.acme.core.events.base.v1~acme.core.events.derived.v1.0~";
const LEAF_ID: &str =
"gts.acme.core.events.base.v1~acme.core.events.derived.v1.0~vendor.x.y.leaf.v1.0~";
#[test]
fn test_type_schema_try_new_extracts_traits() {
let s = make_type_schema(
BASE_ID,
json!({
"title": "User",
"x-gts-traits": { "topicRef": "x" },
"x-gts-traits-schema": { "type": "object" }
}),
None,
);
assert_eq!(s.title.as_deref(), Some("User"));
assert!(s.traits.is_some());
assert!(s.traits_schema.is_some());
assert!(s.parent.is_none());
}
#[test]
fn test_derive_parent_type_id() {
assert!(GtsTypeSchema::derive_parent_type_id(BASE_ID).is_none());
assert_eq!(
GtsTypeSchema::derive_parent_type_id(DERIVED_ID).map(GtsTypeId::into_string),
Some(BASE_ID.to_owned())
);
assert_eq!(
GtsTypeSchema::derive_parent_type_id(LEAF_ID).map(GtsTypeId::into_string),
Some(DERIVED_ID.to_owned())
);
assert!(GtsTypeSchema::derive_parent_type_id("gts.foo.bar.baz.v1~inst").is_none());
}
#[test]
fn test_type_schema_rejects_instance_id() {
let err = GtsTypeSchema::try_new(
GtsTypeId::new("gts.acme.core.events.user.v1~acme.core.instances.u1.v1"),
json!({}),
None,
None,
)
.unwrap_err();
assert!(err.is_invalid_gts_type_id());
}
#[test]
fn test_type_schema_rejects_mismatched_parent() {
let wrong_parent = Arc::new(make_type_schema(
"gts.other.vendor.events.base.v1~",
json!({}),
None,
));
let err = GtsTypeSchema::try_new(
GtsTypeId::new(DERIVED_ID),
json!({}),
None,
Some(wrong_parent),
)
.unwrap_err();
assert!(err.is_invalid_gts_type_id());
}
#[test]
fn test_type_schema_rejects_root_with_parent() {
let stray_parent = Arc::new(make_type_schema(BASE_ID, json!({}), None));
let err = GtsTypeSchema::try_new(GtsTypeId::new(BASE_ID), json!({}), None, Some(stray_parent))
.unwrap_err();
assert!(err.is_invalid_gts_type_id());
}
#[test]
fn test_type_schema_rejects_derived_without_parent() {
let err =
GtsTypeSchema::try_new(GtsTypeId::new(DERIVED_ID), json!({}), None, None).unwrap_err();
assert!(err.is_invalid_gts_type_id());
}
#[test]
fn test_effective_properties_deep_merge() {
let base = Arc::new(make_type_schema(
BASE_ID,
json!({ "type": "object", "properties": { "id": { "type": "string" } } }),
None,
));
let child = make_type_schema(
DERIVED_ID,
json!({
"allOf": [
{ "$ref": format!("gts://{BASE_ID}") },
{ "properties": { "name": { "type": "string" } } }
]
}),
Some(Arc::clone(&base)),
);
let merged = child.effective_properties();
assert!(merged.contains_key("id")); assert!(merged.contains_key("name")); }
#[test]
fn test_effective_properties_child_overrides_parent() {
let base = Arc::new(make_type_schema(
BASE_ID,
json!({ "properties": { "field": { "type": "string", "title": "from-base" } } }),
None,
));
let child = make_type_schema(
DERIVED_ID,
json!({
"allOf": [{ "$ref": format!("gts://{BASE_ID}") }],
"properties": { "field": { "type": "string", "title": "from-child" } }
}),
Some(base),
);
let merged = child.effective_properties();
let field = merged.get("field").unwrap();
assert_eq!(
field.get("title").and_then(|v| v.as_str()),
Some("from-child")
);
}
#[test]
fn test_effective_required_dedup() {
let base = Arc::new(make_type_schema(
BASE_ID,
json!({ "required": ["id"] }),
None,
));
let child = make_type_schema(
DERIVED_ID,
json!({
"allOf": [
{ "$ref": format!("gts://{BASE_ID}") },
{ "required": ["name", "id"] }
]
}),
Some(base),
);
let req = child.effective_required();
assert_eq!(req.iter().filter(|s| *s == "id").count(), 1);
assert!(req.contains(&"name".to_owned()));
}
#[test]
fn test_effective_traits_rightmost_wins() {
let base = Arc::new(make_type_schema(
BASE_ID,
json!({ "x-gts-traits": { "retention": "P30D", "scope": "global" } }),
None,
));
let child = make_type_schema(
DERIVED_ID,
json!({ "x-gts-traits": { "retention": "P90D" } }),
Some(base),
);
let merged = child.effective_traits();
let m = merged.as_object().unwrap();
assert_eq!(m.get("retention").and_then(|v| v.as_str()), Some("P90D"));
assert_eq!(m.get("scope").and_then(|v| v.as_str()), Some("global"));
}
#[test]
fn test_effective_traits_schema_chain_order() {
let base = Arc::new(make_type_schema(
BASE_ID,
json!({ "x-gts-traits-schema": { "type": "object", "properties": { "a": {} } } }),
None,
));
let child = make_type_schema(
DERIVED_ID,
json!({ "x-gts-traits-schema": { "type": "object", "properties": { "b": {} } } }),
Some(base),
);
let chain = child.effective_traits_schema();
assert_eq!(chain.len(), 2);
assert!(chain[0]["properties"]["a"].is_object());
assert!(chain[1]["properties"]["b"].is_object());
}
#[test]
fn test_effective_schema_inlines_refs() {
let base = Arc::new(make_type_schema(
BASE_ID,
json!({ "type": "object", "properties": { "id": { "type": "string" } } }),
None,
));
let child = make_type_schema(
DERIVED_ID,
json!({
"allOf": [
{ "$ref": format!("gts://{BASE_ID}") },
{ "properties": { "name": { "type": "string" } } }
]
}),
Some(base),
);
let merged = child.effective_schema();
let all_of = merged["allOf"].as_array().unwrap();
assert!(all_of[0].get("$ref").is_none());
assert!(all_of[0]["properties"]["id"].is_object());
assert!(all_of[1]["properties"]["name"].is_object());
}
#[test]
fn test_effective_properties_three_level_chain() {
let grand = Arc::new(make_type_schema(
BASE_ID,
json!({ "properties": {
"a": { "type": "string", "title": "from-grand" },
"shared": { "type": "string", "title": "from-grand" }
}}),
None,
));
let parent = Arc::new(make_type_schema(
DERIVED_ID,
json!({
"allOf": [{ "$ref": format!("gts://{BASE_ID}") }],
"properties": {
"b": { "type": "string", "title": "from-parent" },
"shared": { "type": "string", "title": "from-parent" }
}
}),
Some(grand),
));
let child = make_type_schema(
LEAF_ID,
json!({
"allOf": [{ "$ref": format!("gts://{DERIVED_ID}") }],
"properties": {
"c": { "type": "string", "title": "from-child" },
"shared": { "type": "string", "title": "from-child" }
}
}),
Some(parent),
);
let merged = child.effective_properties();
assert!(merged.contains_key("a")); assert!(merged.contains_key("b")); assert!(merged.contains_key("c")); assert_eq!(
merged
.get("shared")
.unwrap()
.get("title")
.and_then(|v| v.as_str()),
Some("from-child")
);
}
#[test]
fn test_effective_properties_root_no_parent() {
let schema = make_type_schema(
BASE_ID,
json!({ "properties": {
"x": { "type": "integer" },
"y": { "type": "string" }
}}),
None,
);
let merged = schema.effective_properties();
assert_eq!(merged.len(), 2);
assert!(merged.contains_key("x"));
assert!(merged.contains_key("y"));
}
#[test]
fn test_effective_properties_from_allof_inline_overlay() {
let base = Arc::new(make_type_schema(
BASE_ID,
json!({ "properties": { "inherited": { "type": "string" } } }),
None,
));
let child = make_type_schema(
DERIVED_ID,
json!({
"allOf": [
{ "$ref": format!("gts://{BASE_ID}") },
{ "properties": { "from_overlay": { "type": "string" } } }
]
}),
Some(base),
);
let merged = child.effective_properties();
assert!(merged.contains_key("inherited"));
assert!(merged.contains_key("from_overlay"));
}
#[test]
fn test_effective_required_three_level_dedup_and_order() {
let grand = Arc::new(make_type_schema(
BASE_ID,
json!({ "required": ["id"] }),
None,
));
let parent = Arc::new(make_type_schema(
DERIVED_ID,
json!({
"allOf": [{ "$ref": format!("gts://{BASE_ID}") }],
"required": ["name", "id"]
}),
Some(grand),
));
let child = make_type_schema(
LEAF_ID,
json!({
"allOf": [{ "$ref": format!("gts://{DERIVED_ID}") }],
"required": ["age", "name"]
}),
Some(parent),
);
let req = child.effective_required();
assert!(req.contains(&"id".to_owned()));
assert!(req.contains(&"name".to_owned()));
assert!(req.contains(&"age".to_owned()));
assert_eq!(req.iter().filter(|s| *s == "id").count(), 1);
assert_eq!(req.iter().filter(|s| *s == "name").count(), 1);
assert_eq!(req.iter().filter(|s| *s == "age").count(), 1);
let pos = |s: &str| req.iter().position(|r| r == s).unwrap();
assert!(pos("age") < pos("name"));
assert!(pos("name") < pos("id"));
}
#[test]
fn test_effective_required_root_no_parent() {
let schema = make_type_schema(BASE_ID, json!({ "required": ["a", "b", "c"] }), None);
let req = schema.effective_required();
assert_eq!(req, vec!["a", "b", "c"]);
}
#[test]
fn test_effective_traits_three_level_rightmost_wins() {
let grand = Arc::new(make_type_schema(
BASE_ID,
json!({ "x-gts-traits": {
"from_grand_only": "g",
"shared": "from-grand"
}}),
None,
));
let parent = Arc::new(make_type_schema(
DERIVED_ID,
json!({ "x-gts-traits": {
"from_parent_only": "p",
"shared": "from-parent"
}}),
Some(grand),
));
let child = make_type_schema(
LEAF_ID,
json!({ "x-gts-traits": {
"from_child_only": "c",
"shared": "from-child"
}}),
Some(parent),
);
let merged = child.effective_traits();
let m = merged.as_object().unwrap();
assert_eq!(m.get("from_grand_only").and_then(|v| v.as_str()), Some("g"));
assert_eq!(
m.get("from_parent_only").and_then(|v| v.as_str()),
Some("p")
);
assert_eq!(m.get("from_child_only").and_then(|v| v.as_str()), Some("c"));
assert_eq!(m.get("shared").and_then(|v| v.as_str()), Some("from-child"));
}
#[test]
fn test_effective_traits_returns_null_when_chain_has_none() {
let base = Arc::new(make_type_schema(BASE_ID, json!({}), None));
let child = make_type_schema(DERIVED_ID, json!({}), Some(base));
assert!(child.effective_traits().is_null());
}
#[test]
fn test_effective_traits_partial_chain_coverage() {
let grand = Arc::new(make_type_schema(BASE_ID, json!({}), None));
let parent = Arc::new(make_type_schema(
DERIVED_ID,
json!({ "x-gts-traits": { "scope": "tenant" } }),
Some(grand),
));
let child = make_type_schema(LEAF_ID, json!({}), Some(parent));
let merged = child.effective_traits();
assert_eq!(
merged
.as_object()
.and_then(|m| m.get("scope"))
.and_then(|v| v.as_str()),
Some("tenant")
);
}
#[test]
fn test_effective_traits_applies_defaults_from_traits_schema() {
let base = Arc::new(make_type_schema(
BASE_ID,
json!({
"x-gts-traits-schema": {
"type": "object",
"properties": {
"allowed_parent_types": { "type": "array", "default": [] },
"idp_provisioning": { "type": "boolean", "default": false }
}
}
}),
None,
));
let child = make_type_schema(DERIVED_ID, json!({}), Some(base));
let merged = child.effective_traits();
let m = merged.as_object().expect("not null");
assert!(
m.get("allowed_parent_types")
.unwrap()
.as_array()
.unwrap()
.is_empty()
);
assert_eq!(m.get("idp_provisioning"), Some(&json!(false)));
}
#[test]
fn test_effective_traits_declared_value_beats_default() {
let base = Arc::new(make_type_schema(
BASE_ID,
json!({
"x-gts-traits-schema": {
"properties": {
"idp_provisioning": { "type": "boolean", "default": false }
}
}
}),
None,
));
let child = make_type_schema(
DERIVED_ID,
json!({ "x-gts-traits": { "idp_provisioning": true } }),
Some(base),
);
let merged = child.effective_traits();
assert_eq!(
merged.get("idp_provisioning").and_then(Value::as_bool),
Some(true)
);
}
#[test]
fn test_effective_traits_default_from_descendant_traits_schema() {
let base = Arc::new(make_type_schema(BASE_ID, json!({}), None));
let child = make_type_schema(
DERIVED_ID,
json!({
"x-gts-traits-schema": {
"properties": {
"retention": { "type": "string", "default": "P30D" }
}
}
}),
Some(base),
);
let merged = child.effective_traits();
assert_eq!(
merged.get("retention").and_then(|v| v.as_str()),
Some("P30D")
);
}
#[test]
fn test_effective_traits_only_defaults_no_declared_values() {
let base = Arc::new(make_type_schema(
BASE_ID,
json!({
"x-gts-traits-schema": {
"properties": {
"scope": { "type": "string", "default": "global" }
}
}
}),
None,
));
let child = make_type_schema(DERIVED_ID, json!({}), Some(base));
let merged = child.effective_traits();
assert_eq!(merged.get("scope").and_then(|v| v.as_str()), Some("global"));
}
#[test]
fn test_effective_traits_returns_null_with_default_less_traits_schema() {
let base = Arc::new(make_type_schema(
BASE_ID,
json!({
"x-gts-traits-schema": {
"properties": {
"scope": { "type": "string" }
}
}
}),
None,
));
let child = make_type_schema(DERIVED_ID, json!({}), Some(base));
assert!(child.effective_traits().is_null());
}
#[test]
fn test_effective_traits_schema_three_level_chain_order() {
let grand = Arc::new(make_type_schema(
BASE_ID,
json!({ "x-gts-traits-schema": { "marker": "grand" } }),
None,
));
let parent = Arc::new(make_type_schema(
DERIVED_ID,
json!({ "x-gts-traits-schema": { "marker": "parent" } }),
Some(grand),
));
let child = make_type_schema(
LEAF_ID,
json!({ "x-gts-traits-schema": { "marker": "child" } }),
Some(parent),
);
let chain = child.effective_traits_schema();
assert_eq!(chain.len(), 3);
assert_eq!(chain[0]["marker"].as_str(), Some("grand"));
assert_eq!(chain[1]["marker"].as_str(), Some("parent"));
assert_eq!(chain[2]["marker"].as_str(), Some("child"));
}
#[test]
fn test_effective_traits_schema_skips_levels_without_block() {
let grand = Arc::new(make_type_schema(
BASE_ID,
json!({ "x-gts-traits-schema": { "marker": "grand" } }),
None,
));
let parent = Arc::new(make_type_schema(DERIVED_ID, json!({}), Some(grand)));
let child = make_type_schema(
LEAF_ID,
json!({ "x-gts-traits-schema": { "marker": "child" } }),
Some(parent),
);
let chain = child.effective_traits_schema();
assert_eq!(chain.len(), 2);
assert_eq!(chain[0]["marker"].as_str(), Some("grand"));
assert_eq!(chain[1]["marker"].as_str(), Some("child"));
}
#[test]
fn test_effective_schema_root_returns_body_unchanged() {
let body = json!({
"type": "object",
"properties": { "id": { "type": "string" } }
});
let schema = make_type_schema(BASE_ID, body.clone(), None);
assert_eq!(schema.effective_schema(), body);
}
#[test]
fn test_effective_schema_strips_id_and_schema_from_inlined_parent() {
let base = Arc::new(make_type_schema(
BASE_ID,
json!({
"$id": format!("gts://{BASE_ID}"),
"$schema": "https://json-schema.org/draft-07/schema#",
"type": "object",
"properties": { "id": { "type": "string" } }
}),
None,
));
let child = make_type_schema(
DERIVED_ID,
json!({
"allOf": [{ "$ref": format!("gts://{BASE_ID}") }]
}),
Some(base),
);
let merged = child.effective_schema();
let inlined = &merged["allOf"][0];
assert!(inlined.get("$id").is_none());
assert!(inlined.get("$schema").is_none());
assert!(inlined["properties"]["id"].is_object());
}
#[test]
fn test_effective_schema_leaves_non_parent_refs_alone() {
let base = Arc::new(make_type_schema(BASE_ID, json!({"type": "object"}), None));
let mixin_id = "gts.other.vendor.events.mixin.v1~";
let child = make_type_schema(
DERIVED_ID,
json!({
"allOf": [
{ "$ref": format!("gts://{BASE_ID}") },
{ "$ref": format!("gts://{mixin_id}") }
]
}),
Some(base),
);
let merged = child.effective_schema();
let all_of = merged["allOf"].as_array().unwrap();
assert!(all_of[0].get("$ref").is_none());
assert_eq!(
all_of[1]["$ref"].as_str(),
Some(format!("gts://{mixin_id}").as_str())
);
}
#[test]
fn test_ancestors_chain_walk() {
let grandparent = Arc::new(make_type_schema(BASE_ID, json!({}), None));
let parent = Arc::new(make_type_schema(
DERIVED_ID,
json!({}),
Some(Arc::clone(&grandparent)),
));
let child = make_type_schema(LEAF_ID, json!({}), Some(parent));
let ids: Vec<String> = child.ancestors().map(|s| s.type_id.to_string()).collect();
assert_eq!(ids, vec![LEAF_ID, DERIVED_ID, BASE_ID]);
}
#[test]
fn test_instance_try_new_validates_chain_match() {
let type_schema = Arc::new(make_type_schema(
"gts.acme.core.events.user.v1~",
json!({}),
None,
));
let inst = GtsInstance::try_new(
GtsInstanceId::new("gts.acme.core.events.user.v1~", "acme.core.instances.u1.v1"),
json!({ "id": "acme.core.instances.u1.v1" }),
None,
type_schema,
)
.unwrap();
assert_eq!(inst.type_id().as_ref(), "gts.acme.core.events.user.v1~");
}
#[test]
fn test_instance_try_new_rejects_mismatched_type_schema() {
let type_schema = Arc::new(make_type_schema(
"gts.acme.other.pkg.type.v1~",
json!({}),
None,
));
let err = GtsInstance::try_new(
GtsInstanceId::new("gts.acme.core.events.user.v1~", "u1"),
json!({}),
None,
type_schema,
)
.unwrap_err();
assert!(err.is_invalid_gts_instance_id());
}
#[test]
fn test_instance_rejects_type_id() {
let type_schema = Arc::new(make_type_schema(
"gts.acme.core.users.user.v1~",
json!({}),
None,
));
let err = GtsInstance::try_new(
GtsInstanceId::new("gts.acme.core.users.user.v1~", ""),
json!({}),
None,
type_schema,
)
.unwrap_err();
assert!(err.is_invalid_gts_instance_id());
}
#[test]
fn test_type_schema_query_builder() {
let empty = TypeSchemaQuery::new();
assert!(empty.is_empty());
let q = TypeSchemaQuery::new().with_pattern("gts.acme.*");
assert!(!q.is_empty());
assert_eq!(q.pattern.as_deref(), Some("gts.acme.*"));
}