#[cfg(test)]
mod owners_tests {
use k8s_openapi::api::apps::v1::Deployment;
use k8s_openapi::api::core::v1::ConfigMap;
use kube::Resource;
use serde_json::json;
use crate::owners::{controller_ref, owner_ref, set_owner_refs};
fn configmap_with_uid(name: &str, namespace: &str, uid: &str) -> ConfigMap {
serde_json::from_value(json!({
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": { "name": name, "namespace": namespace, "uid": uid, "resourceVersion": "1" }
}))
.unwrap()
}
fn configmap_no_uid(name: &str) -> ConfigMap {
serde_json::from_value(json!({
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": { "name": name, "resourceVersion": "1" }
}))
.unwrap()
}
fn configmap_no_name() -> ConfigMap {
serde_json::from_value(json!({
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": { "uid": "abc-123", "resourceVersion": "1" }
}))
.unwrap()
}
fn empty_deployment() -> Deployment {
serde_json::from_value(json!({
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": { "name": "child", "namespace": "default", "resourceVersion": "1" }
}))
.unwrap()
}
#[test]
fn owner_ref_builds_correct_fields() {
let cm = configmap_with_uid("my-cm", "default", "uid-abc");
let oref = owner_ref(&cm).unwrap();
assert_eq!(oref.name, "my-cm");
assert_eq!(oref.uid, "uid-abc");
assert_eq!(oref.kind, "ConfigMap");
assert_eq!(oref.api_version, ConfigMap::api_version(&()).as_ref());
}
#[test]
fn owner_ref_sets_controller_false() {
let cm = configmap_with_uid("my-cm", "default", "uid-abc");
let oref = owner_ref(&cm).unwrap();
assert_eq!(oref.controller, Some(false));
assert_eq!(oref.block_owner_deletion, Some(false));
}
#[test]
fn owner_ref_errors_on_missing_uid() {
let cm = configmap_no_uid("my-cm");
let result = owner_ref(&cm);
assert!(result.is_err());
let msg = result.unwrap_err().to_string();
assert!(msg.contains("uid"), "expected 'uid' in error: {msg}");
}
#[test]
fn owner_ref_errors_on_missing_name() {
let cm = configmap_no_name();
let result = owner_ref(&cm);
assert!(result.is_err());
let msg = result.unwrap_err().to_string();
assert!(msg.contains("name"), "expected 'name' in error: {msg}");
}
#[test]
fn controller_ref_sets_controller_true() {
let cm = configmap_with_uid("my-cm", "default", "uid-xyz");
let oref = controller_ref(&cm).unwrap();
assert_eq!(oref.controller, Some(true));
assert_eq!(oref.block_owner_deletion, Some(true));
}
#[test]
fn controller_ref_builds_correct_fields() {
let cm = configmap_with_uid("my-cm", "default", "uid-xyz");
let oref = controller_ref(&cm).unwrap();
assert_eq!(oref.name, "my-cm");
assert_eq!(oref.uid, "uid-xyz");
assert_eq!(oref.kind, "ConfigMap");
}
#[test]
fn set_owner_refs_writes_owner_references_to_child() {
let cm = configmap_with_uid("parent", "default", "uid-parent");
let oref = controller_ref(&cm).unwrap();
let mut child = empty_deployment();
set_owner_refs(&mut child, vec![oref.clone()]);
let refs = child.meta().owner_references.as_ref().unwrap();
assert_eq!(refs.len(), 1);
assert_eq!(refs[0].name, "parent");
assert_eq!(refs[0].uid, "uid-parent");
assert_eq!(refs[0].controller, Some(true));
}
#[test]
fn set_owner_refs_replaces_existing_references() {
let cm1 = configmap_with_uid("parent-1", "default", "uid-1");
let cm2 = configmap_with_uid("parent-2", "default", "uid-2");
let mut child = empty_deployment();
set_owner_refs(&mut child, vec![owner_ref(&cm1).unwrap()]);
set_owner_refs(&mut child, vec![controller_ref(&cm2).unwrap()]);
let refs = child.meta().owner_references.as_ref().unwrap();
assert_eq!(refs.len(), 1);
assert_eq!(refs[0].name, "parent-2");
}
#[test]
fn set_owner_refs_accepts_multiple_owners() {
let cm1 = configmap_with_uid("p1", "default", "uid-1");
let cm2 = configmap_with_uid("p2", "default", "uid-2");
let mut child = empty_deployment();
set_owner_refs(
&mut child,
vec![owner_ref(&cm1).unwrap(), owner_ref(&cm2).unwrap()],
);
let refs = child.meta().owner_references.as_ref().unwrap();
assert_eq!(refs.len(), 2);
}
fn cm_with_owner_label(name: &str, namespace: &str, label_key: &str, owner: &str) -> ConfigMap {
serde_json::from_value(json!({
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": {
"name": name,
"namespace": namespace,
"resourceVersion": "1",
"labels": { label_key: owner }
}
}))
.unwrap()
}
#[test]
fn owner_label_mapper_returns_object_ref_with_correct_name_and_namespace() {
let mapper = owner_label_mapper::<ConfigMap, ConfigMap>("my-op/owner");
let cm = cm_with_owner_label("my-cm", "my-ns", "my-op/owner", "my-cr");
let result = mapper(cm);
assert!(
result.is_some(),
"expected Some when label and namespace are present"
);
let obj_ref = result.unwrap();
assert_eq!(
obj_ref.name, "my-cr",
"ObjectRef name should be the label value"
);
assert_eq!(
obj_ref.namespace.as_deref(),
Some("my-ns"),
"ObjectRef namespace should match trigger resource namespace"
);
}
#[test]
fn owner_label_mapper_returns_none_when_label_absent() {
let mapper = owner_label_mapper::<ConfigMap, ConfigMap>("my-op/owner");
let cm: ConfigMap = serde_json::from_value(json!({
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": { "name": "cm", "namespace": "ns", "resourceVersion": "1" }
}))
.unwrap();
assert!(
mapper(cm).is_none(),
"expected None when owner label is absent"
);
}
#[test]
fn owner_label_mapper_returns_none_when_label_value_is_empty() {
let mapper = owner_label_mapper::<ConfigMap, ConfigMap>("my-op/owner");
let cm = cm_with_owner_label("cm", "ns", "my-op/owner", "");
assert!(
mapper(cm).is_none(),
"expected None when owner label value is empty"
);
}
#[test]
fn owner_label_mapper_returns_none_when_namespace_is_absent() {
use k8s_openapi::api::core::v1::Node;
let mapper = owner_label_mapper::<Node, ConfigMap>("my-op/owner");
let node: Node = serde_json::from_value(json!({
"apiVersion": "v1",
"kind": "Node",
"metadata": {
"name": "my-node",
"resourceVersion": "1",
"labels": { "my-op/owner": "my-cr" }
}
}))
.unwrap();
assert!(
mapper(node).is_none(),
"expected None for cluster-scoped trigger with no namespace"
);
}
#[test]
fn owner_label_mapper_uses_trigger_namespace_as_cr_namespace() {
let mapper = owner_label_mapper::<ConfigMap, ConfigMap>("koprs/owner");
let cm = cm_with_owner_label("cm", "staging", "koprs/owner", "my-cr");
let obj_ref = mapper(cm).unwrap();
assert_eq!(obj_ref.namespace.as_deref(), Some("staging"));
}
#[test]
fn owner_label_mapper_ignores_unrelated_labels() {
let mapper = owner_label_mapper::<ConfigMap, ConfigMap>("my-op/owner");
let cm: ConfigMap = serde_json::from_value(json!({
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": {
"name": "cm",
"namespace": "ns",
"resourceVersion": "1",
"labels": { "some-other-label": "some-value" }
}
}))
.unwrap();
assert!(
mapper(cm).is_none(),
"expected None — wrong label key present"
);
}
use http::{Request, Response, StatusCode};
use kube::client::Body;
use tower_test::mock;
type MockHandle = mock::Handle<Request<Body>, Response<Body>>;
fn mock_client() -> (kube::Client, MockHandle) {
let (svc, handle) = mock::pair::<Request<Body>, Response<Body>>();
(kube::Client::new(svc, "default"), handle)
}
fn json_response(body: serde_json::Value) -> Response<Body> {
let bytes = serde_json::to_vec(&body).unwrap();
Response::builder()
.status(StatusCode::OK)
.header("Content-Type", "application/json")
.body(Body::from(bytes))
.unwrap()
}
fn configmap_list(names: &[&str], namespace: &str) -> serde_json::Value {
let items: Vec<_> = names
.iter()
.map(|n| {
json!({
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": { "name": n, "namespace": namespace, "resourceVersion": "1" }
})
})
.collect();
json!({
"apiVersion": "v1",
"kind": "ConfigMapList",
"metadata": { "resourceVersion": "1" },
"items": items
})
}
fn node_list(names: &[&str]) -> serde_json::Value {
let items: Vec<_> = names
.iter()
.map(|n| {
json!({
"apiVersion": "v1",
"kind": "Node",
"metadata": { "name": n, "resourceVersion": "1" }
})
})
.collect();
json!({
"apiVersion": "v1",
"kind": "NodeList",
"metadata": { "resourceVersion": "1" },
"items": items
})
}
use crate::owners::{make_object_ref_mapper, make_object_refs, owner_label_mapper};
use crate::scope::{Cluster, Namespaced};
#[tokio::test]
async fn make_object_refs_namespaced_returns_one_ref_per_resource() {
let (client, mut handle) = mock_client();
let server = tokio::spawn(async move {
let (req, send) = handle.next_request().await.unwrap();
assert!(
req.uri()
.to_string()
.contains("/namespaces/my-ns/configmaps")
);
send.send_response(json_response(configmap_list(&["cm1", "cm2"], "my-ns")));
});
let refs = make_object_refs::<ConfigMap, _>(client, Namespaced("my-ns"))
.await
.unwrap();
assert_eq!(refs.len(), 2);
server.await.unwrap();
}
#[tokio::test]
async fn make_object_refs_cluster_returns_one_ref_per_node() {
let (client, mut handle) = mock_client();
let server = tokio::spawn(async move {
let (req, send) = handle.next_request().await.unwrap();
let uri = req.uri().to_string();
assert!(uri.contains("/api/v1/nodes"), "uri={uri}");
assert!(!uri.contains("namespaces"), "uri={uri}");
send.send_response(json_response(node_list(&["n1", "n2", "n3"])));
});
use k8s_openapi::api::core::v1::Node;
let refs = make_object_refs::<Node, _>(client, Cluster).await.unwrap();
assert_eq!(refs.len(), 3);
server.await.unwrap();
}
#[tokio::test]
async fn make_object_refs_generic_namespaced_scopes_to_namespace() {
let (client, mut handle) = mock_client();
let server = tokio::spawn(async move {
let (req, send) = handle.next_request().await.unwrap();
assert!(
req.uri()
.to_string()
.contains("/namespaces/prod/configmaps")
);
send.send_response(json_response(configmap_list(&["cm-prod"], "prod")));
});
let refs = make_object_refs::<ConfigMap, _>(client, Namespaced("prod"))
.await
.unwrap();
assert_eq!(refs.len(), 1);
server.await.unwrap();
}
#[tokio::test]
async fn make_object_refs_returns_empty_vec_when_no_resources() {
let (client, mut handle) = mock_client();
let server = tokio::spawn(async move {
let (_req, send) = handle.next_request().await.unwrap();
send.send_response(json_response(configmap_list(&[], "ns")));
});
let refs = make_object_refs::<ConfigMap, _>(client, Cluster)
.await
.unwrap();
assert!(refs.is_empty());
server.await.unwrap();
}
#[test]
fn make_object_ref_mapper_returns_fixed_refs_for_any_trigger() {
use k8s_openapi::api::core::v1::Node;
use kube_runtime::reflector::ObjectRef;
use std::sync::Arc;
let ref1 = ObjectRef::<ConfigMap>::new("cm1");
let ref2 = ObjectRef::<ConfigMap>::new("cm2");
let refs = Arc::new(vec![ref1, ref2]);
let mapper = make_object_ref_mapper::<Node, ConfigMap>(refs.clone());
let node = serde_json::from_value::<Node>(json!({
"apiVersion": "v1",
"kind": "Node",
"metadata": { "name": "n1", "resourceVersion": "1" }
}))
.unwrap();
let result = mapper(node);
assert_eq!(result.len(), 2);
}
#[test]
fn make_object_ref_mapper_returns_independent_clones() {
use k8s_openapi::api::core::v1::Node;
use kube_runtime::reflector::ObjectRef;
use std::sync::Arc;
let refs = Arc::new(vec![ObjectRef::<ConfigMap>::new("cm1")]);
let mapper = make_object_ref_mapper::<Node, ConfigMap>(refs);
let node1 = serde_json::from_value::<Node>(json!({
"apiVersion": "v1", "kind": "Node",
"metadata": { "name": "n1", "resourceVersion": "1" }
}))
.unwrap();
let node2 = serde_json::from_value::<Node>(json!({
"apiVersion": "v1", "kind": "Node",
"metadata": { "name": "n2", "resourceVersion": "1" }
}))
.unwrap();
let r1 = mapper(node1);
let r2 = mapper(node2);
assert_eq!(r1.len(), 1);
assert_eq!(r2.len(), 1);
}
}