Skip to content

Commit b53db59

Browse files
committed
test: improve unit tes coverage
1 parent 3cc284e commit b53db59

6 files changed

Lines changed: 522 additions & 19 deletions

File tree

rust/operator-binary/src/authorization/opa.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,3 +150,70 @@ impl TrinoOpaConfig {
150150
})
151151
}
152152
}
153+
154+
#[cfg(test)]
155+
mod tests {
156+
use super::*;
157+
158+
fn minimal_opa() -> TrinoOpaConfig {
159+
TrinoOpaConfig {
160+
non_batched_connection_string: "http://opa/allow".to_string(),
161+
batched_connection_string: "http://opa/batch".to_string(),
162+
row_filters_connection_string: None,
163+
batched_column_masking_connection_string: None,
164+
allow_permission_management_operations: false,
165+
tls_secret_class: None,
166+
}
167+
}
168+
169+
#[test]
170+
fn as_config_renders_only_required_keys_when_optionals_are_unset() {
171+
let config = minimal_opa().as_config();
172+
173+
assert_eq!(
174+
config.get("access-control.name").map(String::as_str),
175+
Some("opa")
176+
);
177+
assert_eq!(
178+
config.get("opa.policy.uri").map(String::as_str),
179+
Some("http://opa/allow")
180+
);
181+
assert_eq!(
182+
config.get("opa.policy.batched-uri").map(String::as_str),
183+
Some("http://opa/batch")
184+
);
185+
assert!(!config.contains_key("opa.policy.row-filters-uri"));
186+
assert!(!config.contains_key("opa.policy.batch-column-masking-uri"));
187+
assert!(!config.contains_key("opa.allow-permission-management-operations"));
188+
}
189+
190+
#[test]
191+
fn as_config_renders_optional_keys_when_set() {
192+
let config = TrinoOpaConfig {
193+
row_filters_connection_string: Some("http://opa/rowFilters".to_string()),
194+
batched_column_masking_connection_string: Some(
195+
"http://opa/batchColumnMasks".to_string(),
196+
),
197+
allow_permission_management_operations: true,
198+
..minimal_opa()
199+
}
200+
.as_config();
201+
202+
assert_eq!(
203+
config.get("opa.policy.row-filters-uri").map(String::as_str),
204+
Some("http://opa/rowFilters")
205+
);
206+
assert_eq!(
207+
config
208+
.get("opa.policy.batch-column-masking-uri")
209+
.map(String::as_str),
210+
Some("http://opa/batchColumnMasks")
211+
);
212+
assert_eq!(
213+
config
214+
.get("opa.allow-permission-management-operations")
215+
.map(String::as_str),
216+
Some("true")
217+
);
218+
}
219+
}

rust/operator-binary/src/config/s3.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,47 @@ impl ResolvedS3Config {
130130
Ok(resolved_config)
131131
}
132132
}
133+
134+
#[cfg(test)]
135+
mod tests {
136+
use stackable_operator::commons::tls_verification::Tls;
137+
138+
use super::*;
139+
140+
fn tls_details(verification: Option<TlsVerification>) -> TlsClientDetails {
141+
TlsClientDetails {
142+
tls: verification.map(|verification| Tls { verification }),
143+
}
144+
}
145+
146+
#[test]
147+
fn no_tls_yields_no_truststore_commands() {
148+
assert!(
149+
s3_tls_truststore_commands(&tls_details(None))
150+
.unwrap()
151+
.is_empty()
152+
);
153+
}
154+
155+
#[test]
156+
fn webpki_verification_yields_no_truststore_commands() {
157+
let details = tls_details(Some(TlsVerification::Server(TlsServerVerification {
158+
ca_cert: CaCert::WebPki {},
159+
})));
160+
assert!(s3_tls_truststore_commands(&details).unwrap().is_empty());
161+
}
162+
163+
#[test]
164+
fn secret_class_verification_yields_truststore_commands() {
165+
let details = tls_details(Some(TlsVerification::Server(TlsServerVerification {
166+
ca_cert: CaCert::SecretClass("tls".to_string()),
167+
})));
168+
assert!(!s3_tls_truststore_commands(&details).unwrap().is_empty());
169+
}
170+
171+
#[test]
172+
fn disabled_verification_is_rejected() {
173+
let details = tls_details(Some(TlsVerification::None {}));
174+
assert!(s3_tls_truststore_commands(&details).is_err());
175+
}
176+
}

rust/operator-binary/src/controller/build/graceful_shutdown.rs

Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,13 @@ mod tests {
145145
use stackable_operator::shared::time::Duration;
146146

147147
use super::*;
148-
use crate::controller::build::properties::test_support::validated_cluster_from_yaml;
148+
use crate::{
149+
config::fault_tolerant_execution::ResolvedFaultTolerantExecutionConfig,
150+
controller::build::properties::test_support::{
151+
MINIMAL_TRINO_YAML, empty_derefs, validated_cluster_from_yaml,
152+
validated_cluster_from_yaml_with_derefs,
153+
},
154+
};
149155

150156
/// A worker role group without an explicit `gracefulShutdownTimeout` falls back to the
151157
/// product default.
@@ -255,4 +261,107 @@ mod tests {
255261
Duration::from_minutes_unchecked(5)
256262
);
257263
}
264+
265+
fn fte_derefs() -> crate::controller::dereference::DereferencedObjects {
266+
let mut derefs = empty_derefs();
267+
derefs.resolved_fte_config = Some(ResolvedFaultTolerantExecutionConfig {
268+
config_properties: BTreeMap::new(),
269+
exchange_manager_properties: BTreeMap::new(),
270+
volumes: Vec::new(),
271+
volume_mounts: Vec::new(),
272+
init_container_extra_start_commands: Vec::new(),
273+
});
274+
derefs
275+
}
276+
277+
#[test]
278+
fn coordinator_props_set_query_max_execution_time_without_fte() {
279+
let cluster = validated_cluster_from_yaml(MINIMAL_TRINO_YAML);
280+
let props = graceful_shutdown_config_properties(&cluster, TrinoRole::Coordinator);
281+
// The default worker graceful-shutdown timeout is 60 minutes (3600s).
282+
assert_eq!(
283+
props.get("query.max-execution-time").map(String::as_str),
284+
Some("3600s")
285+
);
286+
}
287+
288+
#[test]
289+
fn coordinator_props_empty_with_fault_tolerant_execution() {
290+
let cluster = validated_cluster_from_yaml_with_derefs(MINIMAL_TRINO_YAML, fte_derefs());
291+
let props = graceful_shutdown_config_properties(&cluster, TrinoRole::Coordinator);
292+
// With fault-tolerant execution, queries may be retried, so no max-execution-time is set.
293+
assert!(props.is_empty());
294+
}
295+
296+
#[test]
297+
fn worker_props_set_shutdown_grace_period() {
298+
let cluster = validated_cluster_from_yaml(MINIMAL_TRINO_YAML);
299+
let props = graceful_shutdown_config_properties(&cluster, TrinoRole::Worker);
300+
assert_eq!(
301+
props.get("shutdown.grace-period").map(String::as_str),
302+
Some("30s")
303+
);
304+
}
305+
306+
#[test]
307+
fn worker_termination_grace_period_adds_overhead_and_sets_pre_stop() {
308+
let cluster = validated_cluster_from_yaml(MINIMAL_TRINO_YAML);
309+
let merged = &cluster.role_group_configs[&TrinoRole::Worker]
310+
.values()
311+
.next()
312+
.unwrap()
313+
.config;
314+
let mut pod_builder = PodBuilder::new();
315+
let mut trino_builder = ContainerBuilder::new("trino").unwrap();
316+
add_graceful_shutdown_config(
317+
&cluster,
318+
&TrinoRole::Worker,
319+
merged,
320+
&mut pod_builder,
321+
&mut trino_builder,
322+
)
323+
.unwrap();
324+
325+
// Default worker timeout 3600s + 2 * 30s grace + 10s safety = 3670s.
326+
let spec = pod_builder.build_template().spec.unwrap();
327+
assert_eq!(spec.termination_grace_period_seconds, Some(3670));
328+
329+
let command = trino_builder
330+
.build()
331+
.lifecycle
332+
.unwrap()
333+
.pre_stop
334+
.unwrap()
335+
.exec
336+
.unwrap()
337+
.command
338+
.unwrap();
339+
assert!(command.iter().any(|arg| arg.contains("sleep 3670")));
340+
}
341+
342+
#[test]
343+
fn coordinator_termination_grace_period_has_no_overhead_or_pre_stop() {
344+
let cluster = validated_cluster_from_yaml(MINIMAL_TRINO_YAML);
345+
let merged = &cluster.role_group_configs[&TrinoRole::Coordinator]
346+
.values()
347+
.next()
348+
.unwrap()
349+
.config;
350+
let mut pod_builder = PodBuilder::new();
351+
let mut trino_builder = ContainerBuilder::new("trino").unwrap();
352+
add_graceful_shutdown_config(
353+
&cluster,
354+
&TrinoRole::Coordinator,
355+
merged,
356+
&mut pod_builder,
357+
&mut trino_builder,
358+
)
359+
.unwrap();
360+
361+
// The coordinator default timeout (900s) is used verbatim, with no overhead.
362+
let spec = pod_builder.build_template().spec.unwrap();
363+
assert_eq!(spec.termination_grace_period_seconds, Some(900));
364+
// Coordinators do not get a graceful-shutdown pre-stop hook.
365+
assert!(trino_builder.build().lifecycle.is_none());
366+
}
258367
}

rust/operator-binary/src/controller/build/properties/mod.rs

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,25 +42,40 @@ pub(crate) mod test_support {
4242
crd::{authentication::ResolvedAuthenticationClassRef, v1alpha1},
4343
};
4444

45+
/// Dereferenced objects with no external resources resolved.
46+
pub fn empty_derefs() -> DereferencedObjects {
47+
DereferencedObjects {
48+
resolved_authentication_classes: Vec::new(),
49+
catalog_definitions: Vec::new(),
50+
catalogs: Vec::new(),
51+
trino_opa_config: None,
52+
resolved_fte_config: None,
53+
resolved_client_protocol_config: None,
54+
}
55+
}
56+
4557
pub fn validated_cluster_from_yaml(yaml: &str) -> ValidatedCluster {
46-
validated_cluster_from_yaml_with_auth(yaml, Vec::new())
58+
validated_cluster_from_yaml_with_derefs(yaml, empty_derefs())
4759
}
4860

4961
/// Like [`validated_cluster_from_yaml`], but injects already-resolved AuthenticationClasses so
5062
/// tests can exercise authentication-dependent branches.
5163
pub fn validated_cluster_from_yaml_with_auth(
5264
yaml: &str,
5365
resolved_authentication_classes: Vec<ResolvedAuthenticationClassRef>,
66+
) -> ValidatedCluster {
67+
let mut derefs = empty_derefs();
68+
derefs.resolved_authentication_classes = resolved_authentication_classes;
69+
validated_cluster_from_yaml_with_derefs(yaml, derefs)
70+
}
71+
72+
/// Validates `yaml` against the given (test-supplied) dereferenced objects, letting tests
73+
/// exercise branches that depend on resolved inputs (e.g. fault-tolerant execution).
74+
pub fn validated_cluster_from_yaml_with_derefs(
75+
yaml: &str,
76+
derefs: DereferencedObjects,
5477
) -> ValidatedCluster {
5578
let trino: v1alpha1::TrinoCluster = serde_yaml::from_str(yaml).expect("invalid test YAML");
56-
let derefs = DereferencedObjects {
57-
resolved_authentication_classes,
58-
catalog_definitions: Vec::new(),
59-
catalogs: Vec::new(),
60-
trino_opa_config: None,
61-
resolved_fte_config: None,
62-
resolved_client_protocol_config: None,
63-
};
6479
let operator_env = OperatorEnvironmentOptions {
6580
operator_namespace: "stackable-operators".to_string(),
6681
operator_service_name: "trino-operator".to_string(),

rust/operator-binary/src/controller/build/properties/security_properties.rs

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,20 @@ mod tests {
4444
crd::TrinoRole,
4545
};
4646

47-
#[test]
48-
fn default_renders_networkaddress_cache_settings() {
49-
let cluster = validated_cluster_from_yaml(MINIMAL_TRINO_YAML);
50-
let rg = cluster.role_group_configs[&TrinoRole::Coordinator]
47+
fn coordinator_rg(
48+
cluster: &crate::controller::ValidatedCluster,
49+
) -> crate::controller::TrinoRoleGroupConfig {
50+
cluster.role_group_configs[&TrinoRole::Coordinator]
5151
.values()
5252
.next()
5353
.unwrap()
54-
.clone();
55-
let props = build(&rg);
54+
.clone()
55+
}
56+
57+
#[test]
58+
fn default_renders_networkaddress_cache_settings() {
59+
let cluster = validated_cluster_from_yaml(MINIMAL_TRINO_YAML);
60+
let props = build(&coordinator_rg(&cluster));
5661
assert_eq!(
5762
props.get("networkaddress.cache.ttl").map(String::as_str),
5863
Some("30")
@@ -64,4 +69,54 @@ mod tests {
6469
Some("0")
6570
);
6671
}
72+
73+
#[test]
74+
fn user_override_wins_and_extra_key_is_added() {
75+
let cluster = validated_cluster_from_yaml(
76+
r#"
77+
apiVersion: trino.stackable.tech/v1alpha1
78+
kind: TrinoCluster
79+
metadata:
80+
name: simple-trino
81+
namespace: default
82+
uid: "e6ac237d-a6d4-43a1-8135-f36506110912"
83+
spec:
84+
image:
85+
productVersion: "479"
86+
clusterConfig:
87+
catalogLabelSelector: {}
88+
coordinators:
89+
roleGroups:
90+
default:
91+
replicas: 1
92+
configOverrides:
93+
security.properties:
94+
networkaddress.cache.ttl: "99"
95+
custom.extra.key: "myvalue"
96+
workers:
97+
roleGroups:
98+
default:
99+
replicas: 1
100+
"#,
101+
);
102+
let props = build(&coordinator_rg(&cluster));
103+
104+
// User override wins over the default.
105+
assert_eq!(
106+
props.get("networkaddress.cache.ttl").map(String::as_str),
107+
Some("99")
108+
);
109+
// Extra (non-default) override key is added.
110+
assert_eq!(
111+
props.get("custom.extra.key").map(String::as_str),
112+
Some("myvalue")
113+
);
114+
// Untouched default remains.
115+
assert_eq!(
116+
props
117+
.get("networkaddress.cache.negative.ttl")
118+
.map(String::as_str),
119+
Some("0")
120+
);
121+
}
67122
}

0 commit comments

Comments
 (0)