use serde_json::json;
use crate::{Audience, Disclosure};
use super::filter::filter_by_audience_in_scope;
use super::policy::{DocumentScope, ProductGroupAccessPolicy};
#[test]
fn two_definitions_declaring_one_leaf_are_classified_apart() {
let policy = policy_from(&json!({
"properties": {
"anodeMaterial": {
"type": "array",
"items": { "$ref": "#/definitions/materialComposition" }
},
"criticalRawMaterials": {
"type": "array",
"items": { "$ref": "#/definitions/criticalRawMaterial" }
}
},
"definitions": {
"materialComposition": {
"properties": { "name": { "x-disclosure": "restricted" } }
},
"criticalRawMaterial": {
"properties": { "name": { "x-disclosure": "public" } }
}
}
}));
assert_eq!(
policy.disclosure_for_path(&["anodeMaterial", "name"], DocumentScope::ProductGroupData),
Disclosure::Restricted,
"the restricted definition governs the path that refers to it"
);
assert_eq!(
policy.disclosure_for_path(
&["criticalRawMaterials", "name"],
DocumentScope::ProductGroupData
),
Disclosure::Public,
"and its twin is not dragged along with it"
);
}
#[test]
fn a_definition_lands_on_every_path_that_refers_to_it() {
let policy = policy_from(&json!({
"properties": {
"anodeMaterial": { "items": { "$ref": "#/definitions/material" } },
"cathodeMaterial": { "items": { "$ref": "#/definitions/material" } },
"electrolyteMaterial": { "items": { "$ref": "#/definitions/material" } }
},
"definitions": {
"material": {
"properties": { "weightPct": { "x-disclosure": "restricted" } }
}
}
}));
for parent in ["anodeMaterial", "cathodeMaterial", "electrolyteMaterial"] {
assert!(
policy
.field_disclosure
.contains_key(&format!("{parent}.weightPct")),
"{parent} refers to the definition, so its class must be keyed under \
that path and not only as a bare leaf: {:?}",
policy.field_disclosure.keys().collect::<Vec<_>>()
);
assert_eq!(
policy.disclosure_for_path(&[parent, "weightPct"], DocumentScope::ProductGroupData),
Disclosure::Restricted,
"{parent} refers to the definition and must carry its class"
);
}
assert!(
policy.field_disclosure.contains_key("weightPct"),
"and the floor is kept alongside them"
);
}
#[test]
fn an_unreached_position_still_gets_the_definition_class() {
let policy = policy_from(&json!({
"properties": {
"anodeMaterial": { "items": { "$ref": "#/definitions/material" } },
"elsewhere": {
"patternProperties": {
"^x-": { "$ref": "#/definitions/material" }
}
}
},
"definitions": {
"material": {
"properties": { "weightPct": { "x-disclosure": "restricted" } }
}
}
}));
assert_eq!(
policy.disclosure_for_path(
&["elsewhere", "xCustom", "weightPct"],
DocumentScope::ProductGroupData
),
Disclosure::Restricted,
"a reference through a construct the walk does not descend must not \
lose the class — the bare leaf is what covers it"
);
}
#[test]
fn a_named_path_still_beats_the_floor() {
let policy = policy_from(&json!({
"properties": {
"restrictedHolder": { "items": { "$ref": "#/definitions/secret" } },
"publicHolder": { "items": { "$ref": "#/definitions/open" } }
},
"definitions": {
"secret": { "properties": { "value": { "x-disclosure": "restricted" } } },
"open": { "properties": { "value": { "x-disclosure": "public" } } }
}
}));
assert_eq!(
policy.disclosure_for_path(&["value"], DocumentScope::ProductGroupData),
Disclosure::Restricted,
"an under-specified query gets the conservative answer"
);
assert_eq!(
policy.disclosure_for_path(&["publicHolder", "value"], DocumentScope::ProductGroupData),
Disclosure::Public
);
}
#[test]
fn the_battery_material_definition_is_positioned() {
let policy = ProductGroupAccessPolicy::for_schema_version("battery", "2.6.0")
.expect("battery 2.6.0 is registered");
for parent in ["anodeMaterial", "cathodeMaterial", "electrolyteMaterial"] {
assert!(
policy
.field_disclosure
.contains_key(&format!("{parent}.weightPct")),
"{parent}.weightPct should be recorded as its own key, not only as \
bare weightPct: {:?}",
policy.field_disclosure.keys().collect::<Vec<_>>()
);
assert_eq!(
policy.disclosure_for_path(&[parent, "weightPct"], DocumentScope::ProductGroupData),
Disclosure::Restricted
);
}
assert_eq!(
policy.disclosure_for_path(
&["criticalRawMaterials", "casNumber"],
DocumentScope::ProductGroupData
),
Disclosure::Public,
"Annex XIII point 1(b) puts critical raw materials on the public tier"
);
}
#[test]
fn the_filter_redacts_by_path_through_a_ref() {
let policy = policy_from(&json!({
"properties": {
"anodeMaterial": {
"type": "array",
"items": { "$ref": "#/definitions/materialComposition" }
},
"criticalRawMaterials": {
"type": "array",
"items": { "$ref": "#/definitions/criticalRawMaterial" }
}
},
"definitions": {
"materialComposition": {
"properties": {
"name": { "x-disclosure": "public" },
"weightPct": { "x-disclosure": "restricted" },
"casNumber": { "x-disclosure": "restricted" }
}
},
"criticalRawMaterial": {
"properties": {
"name": { "x-disclosure": "public" },
"casNumber": { "x-disclosure": "public" }
}
}
}
}));
let document = json!({
"anodeMaterial": [{ "name": "graphite", "weightPct": 42.5, "casNumber": "7782-42-5" }],
"criticalRawMaterials": [{ "name": "cobalt", "casNumber": "7440-48-4" }]
});
let public = filter_by_audience_in_scope(
&document,
&policy,
Audience::Public,
DocumentScope::ProductGroupData,
)
.filtered_data;
let anode = &public["anodeMaterial"][0];
assert!(
anode.get("weightPct").is_none() && anode.get("casNumber").is_none(),
"the restricted definition's fields must not reach an anonymous \
reader: {anode}"
);
assert!(
anode.get("name").is_some(),
"its Public sibling in the same definition survives: {anode}"
);
let crm = &public["criticalRawMaterials"][0];
assert!(
crm.get("casNumber").is_some(),
"and the same leaf name under the public definition is not dragged under \
with it — the whole point, and unexpressible before the ref was \
followed: {crm}"
);
}
fn policy_from(schema: &serde_json::Value) -> ProductGroupAccessPolicy {
ProductGroupAccessPolicy::from_schema("test", "1.0.0", &schema.to_string())
.expect("schema has a root properties map")
}