use color_eyre::eyre;
#[path = "common/chart_instances.rs"]
mod chart_instances;
#[path = "common/schema_roundtrip.rs"]
mod schema_roundtrip;
#[path = "common/values_yaml.rs"]
mod values_yaml;
#[test]
fn traefik_log_root_is_required_by_the_otlp_guard_chain() -> eyre::Result<()> {
let schema = schema_roundtrip::generate_chart_schema_for_path("traefik")?;
let validator = jsonschema::validator_for(&schema)?;
let instance = chart_instances::with_override("traefik", serde_json::json!({ "log": null }))?;
assert!(
!validator.is_valid(&instance),
"deleting log reaches the requirements template's .Values.log.otlp.enabled read"
);
Ok(())
}
#[test]
fn traefik_plugin_validator_holds() -> eyre::Result<()> {
let schema = schema_roundtrip::generate_chart_schema_for_path("traefik")?;
let validator = jsonschema::validator_for(&schema).expect("schema validator");
let plugins = |value: serde_json::Value| {
chart_instances::with_override(
"traefik",
serde_json::json!({ "experimental": { "plugins": value } }),
)
.expect("compose instance")
};
for bad in [
serde_json::json!({ "bad": 7 }),
serde_json::json!({ "bad": { "moduleName": "x" } }),
serde_json::json!({ "bad": { "version": "v1" } }),
] {
assert!(
!validator.is_valid(&plugins(bad.clone())),
"plugins without moduleName+version objects fail rendering: {bad}"
);
}
let complete = plugins(serde_json::json!({
"ok": { "moduleName": "github.com/x/y", "version": "v1.0.0" }
}));
let errors = validator
.iter_errors(&complete)
.map(|error| format!("{}: {error}", error.instance_path()))
.collect::<Vec<_>>();
assert!(
errors.is_empty(),
"a complete plugin renders: {errors:#?}; schema={:#?}",
schema.pointer("/properties/experimental/properties/plugins")
);
assert!(
validator.is_valid(&plugins(serde_json::json!({}))),
"the declared empty map stays valid"
);
Ok(())
}
#[test]
fn traefik_http3_terminal_binds_through_the_services_overlay() -> eyre::Result<()> {
let schema = schema_roundtrip::generate_chart_schema_for_path("traefik")?;
let validator = jsonschema::validator_for(&schema).expect("schema validator");
let bad_port = serde_json::json!({
"http3": { "enabled": true },
"http": { "tls": { "enabled": false } }
});
for (override_, want, label) in [
(
serde_json::json!({}),
true,
"ports without an http3 block skip the terminal",
),
(
serde_json::json!({ "ports": { "websecure": bad_port.clone() } }),
false,
"http3 without tls aborts through the default service",
),
(
serde_json::json!({ "ports": { "websecure": { "http3": { "enabled": true } } } }),
true,
"http3 with the declared tls renders",
),
(
serde_json::json!({
"service": { "enabled": false },
"ports": { "websecure": bad_port.clone() }
}),
true,
"a disabled default service keeps the terminal dormant",
),
] {
let instance =
chart_instances::with_override("traefik", override_).expect("compose instance");
assert!(
validator.is_valid(&instance) == want,
"{label}: instance={instance}"
);
}
Ok(())
}
#[test]
fn traefik_additional_service_members_must_be_mappings() -> eyre::Result<()> {
let schema = schema_roundtrip::generate_chart_schema_for_path("traefik")?;
let validator = jsonschema::validator_for(&schema).expect("schema validator");
for (override_, want, label) in [
(
serde_json::json!({ "service": { "additionalServices": { "audit": false } } }),
false,
"a false member aborts the member navigation",
),
(
serde_json::json!({ "service": { "additionalServices": { "audit": "x" } } }),
false,
"a string member aborts the member navigation",
),
(
serde_json::json!({ "service": { "additionalServices": { "audit": 7 } } }),
false,
"an integer member aborts the member navigation",
),
(
serde_json::json!({ "service": { "additionalServices": { "audit": [] } } }),
false,
"a list member aborts the member navigation",
),
(
serde_json::json!({
"service": { "additionalServices": { "audit": { "type": "ClusterIP" } } },
"ports": { "web": { "expose": { "audit": true } } }
}),
true,
"an object member with an exposed port renders",
),
(
serde_json::json!({ "service": { "additionalServices": {} } }),
true,
"the declared empty map keeps the default service alone",
),
] {
let instance =
chart_instances::with_override("traefik", override_).expect("compose instance");
assert!(
validator.is_valid(&instance) == want,
"{label}: instance={instance}"
);
}
Ok(())
}
#[test]
fn traefik_local_plugin_mount_paths_bind_provider_presence() -> eyre::Result<()> {
let schema = schema_roundtrip::generate_chart_schema_for_path("traefik")?;
let validator = jsonschema::validator_for(&schema).expect("schema validator");
let plugins = |value: serde_json::Value| {
chart_instances::with_override(
"traefik",
serde_json::json!({ "experimental": { "localPlugins": value } }),
)
.expect("compose instance")
};
for (instance, want, label) in [
(
plugins(serde_json::json!({
"p": { "moduleName": "github.com/x/y", "hostPath": "/plugins" }
})),
false,
"a legacy hostPath plugin without mountPath renders a null mount",
),
(
plugins(serde_json::json!({
"p": {
"moduleName": "github.com/x/y",
"hostPath": "/plugins",
"mountPath": "/plugins-storage/p"
}
})),
true,
"a legacy hostPath plugin with mountPath renders",
),
(
plugins(serde_json::json!({
"p": {
"moduleName": "github.com/x/y",
"type": "hostPath",
"hostPath": "/plugins"
}
})),
false,
"a typed plugin without mountPath renders a null mount",
),
(
plugins(serde_json::json!({
"p": {
"moduleName": "github.com/x/y",
"type": "hostPath",
"hostPath": "/plugins",
"mountPath": "/plugins-storage/p"
}
})),
true,
"a typed plugin with mountPath renders",
),
] {
assert!(
validator.is_valid(&instance) == want,
"{label}: instance={instance}"
);
}
Ok(())
}
#[test]
fn traefik_listener_keys_carry_the_provider_section_name_domain() -> eyre::Result<()> {
let schema = schema_roundtrip::generate_chart_schema_for_path("traefik")?;
let validator = jsonschema::validator_for(&schema).expect("schema validator");
let compose = |overrides: serde_json::Value| {
chart_instances::with_override("traefik", overrides).expect("compose traefik instance")
};
let live_listeners = |listeners: serde_json::Value| {
compose(serde_json::json!({
"providers": { "kubernetesGateway": { "enabled": true } },
"gateway": { "enabled": true, "listeners": listeners },
}))
};
let listener = || serde_json::json!({ "port": 8000, "protocol": "TCP" });
for (instance, want, label) in [
(
live_listeners(serde_json::json!({ "Audit": listener() })),
false,
"an uppercase listener key violates the SectionName pattern",
),
(
live_listeners(serde_json::json!({ "audit": listener() })),
true,
"a lowercase listener key renders and validates",
),
(
live_listeners(serde_json::json!({ "a".repeat(254): listener() })),
false,
"a 254-char listener key exceeds the SectionName maxLength",
),
(
live_listeners(serde_json::json!({ "": listener() })),
false,
"an empty listener key violates the SectionName minLength",
),
(
compose(serde_json::json!({
"gateway": { "enabled": true, "listeners": { "Audit": listener() } },
})),
true,
"a dormant kubernetesGateway keeps every key spelling open",
),
(
compose(serde_json::json!({
"providers": { "kubernetesGateway": { "enabled": true } },
"gateway": { "enabled": false, "listeners": { "Audit": listener() } },
})),
true,
"a disabled gateway keeps every key spelling open",
),
] {
assert!(
validator.is_valid(&instance) == want,
"{label}: instance={instance}"
);
}
Ok(())
}