use std::collections::BTreeSet;
use clickhouse_openapi_analyzer::config::clickhouse_cloud_config;
use clickhouse_openapi_analyzer::{
AnalysisInput, analyze, model_fields_with_serde_default, response_tree,
};
const SPEC_JSON: &str = include_str!("../clickhouse_cloud_openapi.json");
const CLIENT_RS: &str = include_str!("../src/client.rs");
const MODELS_RS: &str = include_str!("../src/models.rs");
const META_RS: &str = include_str!("../src/meta.rs");
const LIVE_SPEC_URL: &str = "https://api.clickhouse.cloud/v1";
#[test]
fn vendored_openapi_snapshot_matches_rust_api() {
let config = clickhouse_cloud_config();
let report = analyze_spec(SPEC_JSON, &config);
assert!(!report.has_drift(), "{}", report.render_text());
assert!(
report
.unsupported_enum_constraints
.iter()
.all(|constraint| constraint.acknowledged),
"the vendored snapshot contains an unacknowledged unsupported enum constraint"
);
let reported_pointers = report
.unsupported_enum_constraints
.iter()
.map(|constraint| constraint.spec_pointer.clone())
.collect::<BTreeSet<_>>();
assert_eq!(
reported_pointers, config.acknowledged_unsupported_enum_pointers,
"the snapshot's unsupported enum inventory must exactly match analyzer configuration"
);
}
const ALL_OPTION_EXCEPTIONS: &[(&str, &str)] = &[];
#[test]
fn every_response_tree_field_is_option() {
let tree = response_tree(CLIENT_RS, MODELS_RS).unwrap();
assert!(
tree.types.len() >= 300,
"vacuous test: the response tree collapsed to {} types — did client.rs \
return types or the analyzer's reachability change shape?",
tree.types.len()
);
let strict_fields = tree
.non_option_fields
.iter()
.filter(|(type_name, field)| {
!ALL_OPTION_EXCEPTIONS.contains(&(type_name.as_str(), field.as_str()))
})
.collect::<Vec<_>>();
assert!(
strict_fields.is_empty(),
"response-reachable fields must be Option<T> so a dropped or null field \
cannot fail deserialization: {strict_fields:?}"
);
}
#[test]
fn every_response_tree_option_field_omits_none_when_serialized() {
let tree = response_tree(CLIENT_RS, MODELS_RS).unwrap();
assert!(
!tree.types.is_empty(),
"vacuous test: the response tree is empty"
);
assert!(
tree.option_fields_missing_skip_serializing_if.is_empty(),
"response fields must omit None instead of serializing null; add \
skip_serializing_if to: {:?}",
tree.option_fields_missing_skip_serializing_if
);
}
#[test]
fn models_carry_no_serde_default() {
let offenders = model_fields_with_serde_default(MODELS_RS).unwrap();
assert!(
offenders.is_empty(),
"remove #[serde(default)] from: {offenders:?}"
);
}
#[test]
fn scim_models_are_outside_the_response_tree() {
assert!(
MODELS_RS.matches("\npub struct Scim").count() >= 30,
"vacuous test: the SCIM model family is no longer named `Scim*`"
);
let tree = response_tree(CLIENT_RS, MODELS_RS).unwrap();
let scim_response_types = tree
.types
.iter()
.filter(|name| name.starts_with("Scim"))
.collect::<Vec<_>>();
assert!(
scim_response_types.is_empty(),
"SCIM types became response-reachable and must be split: {scim_response_types:?}"
);
}
#[tokio::test]
#[ignore = "hits the live published ClickHouse OpenAPI spec"]
async fn live_openapi_spec_matches_rust_api() {
let response = reqwest::Client::new()
.get(
std::env::var("CLICKHOUSE_OPENAPI_SPEC_URL")
.unwrap_or_else(|_| LIVE_SPEC_URL.to_string()),
)
.send()
.await
.unwrap()
.error_for_status()
.unwrap();
let live_spec = response.text().await.unwrap();
let config = clickhouse_cloud_config();
let report = analyze_spec(&live_spec, &config);
assert!(!report.has_drift(), "{}", report.render_text());
}
fn analyze_spec(
spec_json: &str,
config: &clickhouse_openapi_analyzer::config::AnalyzerConfig,
) -> clickhouse_openapi_analyzer::report::DriftReport {
analyze(
AnalysisInput {
spec_json,
snapshot_json: SPEC_JSON,
client_rs: CLIENT_RS,
models_rs: MODELS_RS,
meta_rs: META_RS,
},
config,
)
.unwrap()
}