#![allow(clippy::unwrap_used)]
use serde_json::{Value, json};
use super::CompiledSchema;
fn fixture() -> Value {
json!({
"types": [
{"name": "Sale", "sql_source": "v_sale",
"fields": [{"name": "revenue", "field_type": "Float", "nullable": false},
{"name": "tenant_id", "field_type": "String", "nullable": false},
{"name": "author_id", "field_type": "String", "nullable": false}]}
],
"queries": [
{"name": "sales", "return_type": "Sale", "returns_list": true, "sql_source": "v_sale"}
],
"mutations": [],
"fact_tables": {
"tf_sales": {
"table_name": "tf_sales",
"type_name": "Sale",
"measures": [{"name": "revenue", "sql_type": "Decimal", "nullable": false}],
"dimensions": {"name": "data", "paths": []},
"denormalized_filters": [
{"name": "tenant_id", "sql_type": "Text", "indexed": true},
{"name": "author_id", "sql_type": "Text", "indexed": true}
]
}
}
})
}
fn jwt(claim: &str) -> Value {
json!({"source": "jwt", "claim": claim})
}
fn load(doc: &Value) -> crate::error::Result<CompiledSchema> {
CompiledSchema::from_json(&doc.to_string(), false)
}
fn refusal(mutate: impl FnOnce(&mut Value)) -> String {
let mut doc = fixture();
mutate(&mut doc);
load(&doc)
.expect_err("a declaration the runtime cannot honour must not load")
.to_string()
}
fn declare(doc: &mut Value, column: &str, source: Value) {
doc["fact_tables"]["tf_sales"]["inject_params"][column] = source;
}
#[test]
fn a_declaration_on_a_filter_column_loads_and_survives() {
let mut doc = fixture();
declare(&mut doc, "tenant_id", jwt("org"));
let schema = load(&doc).expect("a filter on a filter column loads");
let table = schema.get_fact_table("tf_sales").unwrap();
assert_eq!(
table.inject_params.get("tenant_id"),
Some(&crate::schema::InjectedParamSource::Jwt("org".to_string()))
);
let round_trip = serde_json::to_value(&schema).unwrap();
assert_eq!(round_trip["fact_tables"]["tf_sales"]["inject_params"]["tenant_id"], jwt("org"));
}
#[test]
fn a_declaration_on_a_column_the_table_lacks_is_refused() {
let message = refusal(|d| declare(d, "owner_id", jwt("org")));
for part in [
"fact table 'tf_sales'",
"inject param 'owner_id'",
"not one of its denormalized filter columns",
"'author_id', 'tenant_id'",
] {
assert!(message.contains(part), "{part:?}: {message}");
}
}
#[test]
fn an_optional_claim_is_refused() {
let message =
refusal(|d| declare(d, "tenant_id", json!({"source": "jwt_optional", "claim": "org"})));
for part in [
"fact table 'tf_sales'",
"inject param 'tenant_id'",
"jwt_optional:org",
"jwt:org",
] {
assert!(message.contains(part), "{part:?}: {message}");
}
}
#[test]
fn a_declaration_on_a_relation_scoped_table_is_refused() {
let message = refusal(|d| {
declare(d, "tenant_id", jwt("org"));
d["fact_tables"]["tf_sales"]["row_scope"] = "relation".into();
});
for part in [
"fact table 'tf_sales'",
"row_scope = \"relation\"",
"inject_params",
] {
assert!(message.contains(part), "{part:?}: {message}");
}
}
#[test]
fn a_declaration_contradicting_the_linked_type_is_refused() {
let message = refusal(|d| {
declare(d, "tenant_id", jwt("org"));
d["types"][0]["inject_params"] = json!({"tenant_id": jwt("tid")});
});
for part in [
"fact table 'tf_sales'",
"type 'Sale'",
"'tenant_id'",
"jwt claim 'org'",
"jwt claim 'tid'",
] {
assert!(message.contains(part), "{part:?}: {message}");
}
}
#[test]
fn a_declaration_agreeing_with_the_linked_type_loads() {
let mut doc = fixture();
declare(&mut doc, "tenant_id", jwt("org"));
doc["types"][0]["inject_params"] = json!({"tenant_id": jwt("org")});
load(&doc).expect("the same source in both places loads");
}
#[test]
fn an_inheritance_the_table_cannot_take_still_loads() {
let mut doc = fixture();
doc["types"][0]["inject_params"] = json!({"owner_id": jwt("org")});
doc["types"][0]["fields"]
.as_array_mut()
.unwrap()
.push(json!({"name": "owner_id", "field_type": "String", "nullable": false}));
let schema = load(&doc).expect("no new requirement on a schema that loads today");
let reason = schema
.unenforced_tenant_scope(schema.get_fact_table("tf_sales").unwrap())
.expect("still tenant-scoped, and nothing scopes it");
for part in [
"inherited from type 'Sale'",
"'owner_id' is not one of its denormalized filter columns",
] {
assert!(reason.contains(part), "{part:?}: {reason}");
}
}
#[test]
fn a_scoped_fact_table_is_not_unenforced() {
let mut declared = fixture();
declare(&mut declared, "tenant_id", jwt("org"));
let mut inherited = fixture();
inherited["types"][0]["inject_params"] = json!({"tenant_id": jwt("org")});
for (what, doc) in [("declared", declared), ("inherited", inherited)] {
let schema = load(&doc).unwrap();
assert_eq!(
schema.unenforced_tenant_scope(schema.get_fact_table("tf_sales").unwrap()),
None,
"{what}"
);
assert!(schema.fact_table_scope_violations().is_empty(), "{what}");
}
}
#[test]
fn a_column_injected_only_by_a_query_is_still_unenforced() {
let mut doc = fixture();
doc["queries"][0]["inject_params"] = json!({"tenant_id": jwt("org")});
let schema = load(&doc).unwrap();
assert!(
schema
.unenforced_tenant_scope(schema.get_fact_table("tf_sales").unwrap())
.is_some(),
"scoping is read off a declaration, never off another operation's"
);
}