use kube::api::{Api, Patch, PatchParams};
use kube::Resource;
use serde::{de::DeserializeOwned, Serialize};
use serde_json::json;
use std::fmt::Debug;
#[must_use]
pub fn merge_status_body<S: Serialize + ?Sized>(status: &S) -> serde_json::Value {
json!({ "status": status })
}
pub async fn merge_status<K, S>(api: &Api<K>, name: &str, status: &S) -> Result<K, kube::Error>
where
K: Resource + DeserializeOwned + Clone + Debug,
K::DynamicType: Default,
S: Serialize + ?Sized,
{
let body = merge_status_body(status);
api.patch_status(name, &PatchParams::default(), &Patch::Merge(&body))
.await
}
pub async fn merge<K, B>(api: &Api<K>, name: &str, body: &B) -> Result<K, kube::Error>
where
K: Resource + DeserializeOwned + Clone + Debug,
K::DynamicType: Default,
B: Serialize + Debug + ?Sized,
{
api.patch(name, &PatchParams::default(), &Patch::Merge(body))
.await
}
#[must_use]
pub fn apply_patch_params(field_manager: &str) -> PatchParams {
PatchParams::apply(field_manager).force()
}
#[must_use]
pub fn spec_suspended_body(suspended: bool) -> serde_json::Value {
json!({ "spec": { "suspended": suspended } })
}
#[cfg(test)]
mod tests {
use super::*;
use serde::Serialize;
use serde_json::json;
#[test]
fn merge_status_body_wraps_typed_status_under_top_level_status_slot() {
#[derive(Serialize)]
struct S {
phase: &'static str,
reason: &'static str,
}
let body = merge_status_body(&S {
phase: "Bound",
reason: "member allocated",
});
assert_eq!(
body,
json!({ "status": { "phase": "Bound", "reason": "member allocated" } }),
);
}
#[test]
fn merge_status_body_top_level_key_is_exactly_status_lowercase() {
let body = merge_status_body(&json!({"phase": "Running"}));
let obj = body.as_object().expect("top-level must be a JSON object");
assert_eq!(obj.len(), 1, "wrap adds exactly ONE top-level slot");
assert!(
obj.contains_key("status"),
"top-level slot must be exactly `status` (lowercase)"
);
}
#[test]
fn merge_status_body_accepts_pre_serialized_json_value_verbatim() {
let pre = json!({"phase": "Attested", "phaseSince": "2026-05-01T00:00:00Z"});
let body = merge_status_body(&pre);
assert_eq!(body, json!({"status": pre}));
}
#[test]
fn merge_status_body_wraps_scalar_status_without_object_promotion() {
let body = merge_status_body(&"Attested");
assert_eq!(body, json!({"status": "Attested"}));
}
#[test]
fn merge_status_body_preserves_struct_update_composition_bytewise() {
#[derive(Serialize)]
struct Base {
phase: &'static str,
phase_since: &'static str,
extra: Option<&'static str>,
}
fn base() -> Base {
Base {
phase: "Queued",
phase_since: "2026-05-01T00:00:00Z",
extra: None,
}
}
let struct_update = Base {
extra: Some("pool matched"),
..base()
};
let spelled_out = Base {
phase: "Queued",
phase_since: "2026-05-01T00:00:00Z",
extra: Some("pool matched"),
};
assert_eq!(
merge_status_body(&struct_update),
merge_status_body(&spelled_out),
"struct-update composition serializes byte-identically to the fully-spelled struct literal",
);
}
#[test]
fn merge_status_delegates_wire_body_construction_to_merge_status_body() {
let body_via_helper = merge_status_body(&json!({"phase": "Running"}));
assert_eq!(body_via_helper["status"]["phase"], "Running");
}
#[test]
fn apply_patch_params_binds_field_manager_pass_through_slot_verbatim() {
let pp = apply_patch_params("tatara-reconciler");
assert_eq!(pp.field_manager.as_deref(), Some("tatara-reconciler"));
let pp = apply_patch_params("tatara-export-worker");
assert_eq!(pp.field_manager.as_deref(), Some("tatara-export-worker"));
let pp = apply_patch_params("per-shard-manager-42");
assert_eq!(pp.field_manager.as_deref(), Some("per-shard-manager-42"));
}
#[test]
fn apply_patch_params_stamps_force_true() {
let pp = apply_patch_params("tatara-reconciler");
assert!(pp.force);
}
#[test]
fn apply_patch_params_defaults_dry_run_and_field_validation_off() {
let pp = apply_patch_params("tatara-reconciler");
assert!(!pp.dry_run);
assert!(pp.field_validation.is_none());
}
#[test]
fn apply_patch_params_matches_pre_lift_hand_authored_chain_bytewise() {
for mgr in [
"tatara-reconciler",
"tatara-export-worker",
"per-shard-manager-42",
] {
let pre_lift = PatchParams::apply(mgr).force();
let lifted = apply_patch_params(mgr);
assert_eq!(lifted.field_manager, pre_lift.field_manager);
assert_eq!(lifted.force, pre_lift.force);
assert_eq!(lifted.dry_run, pre_lift.dry_run);
assert_eq!(
lifted.field_validation.is_none(),
pre_lift.field_validation.is_none()
);
}
}
#[test]
fn merge_uses_default_patch_params_posture_no_field_manager_no_dry_run_no_force() {
let pp = PatchParams::default();
assert!(pp.field_manager.is_none(), "default has no field_manager");
assert!(!pp.dry_run, "default has dry_run false");
assert!(!pp.force, "default has force false");
assert!(
pp.field_validation.is_none(),
"default has no field_validation"
);
}
#[test]
fn merge_selects_patch_merge_strategy_not_apply_or_strategic() {
let body = json!({"spec": {"suspended": true}});
let patch: Patch<&serde_json::Value> = Patch::Merge(&body);
assert!(
matches!(patch, Patch::Merge(_)),
"merge primitive dispatches through Patch::Merge, not Apply/Strategic/Json"
);
}
#[test]
fn merge_dispatches_body_verbatim_no_wrap_or_re_encode() {
let spec_body = json!({"spec": {"suspended": true}});
let meta_body = json!({
"metadata": {"finalizers": ["tatara.pleme.io/process-finalizer"]},
});
let strip_body = json!({
"metadata": {"annotations": {"tatara.pleme.io/signal": serde_json::Value::Null}},
});
let data_body = json!({"data": {"receipt.json": "{...}"}});
let spec_next_body = json!({"spec": {"nextSequence": 42}});
for body in [spec_body, meta_body, strip_body, data_body, spec_next_body] {
let round_trip = serde_json::to_value(&body).unwrap();
assert_eq!(round_trip, body, "body serializes to itself verbatim");
let obj = body.as_object().expect("pre-lift bodies are JSON objects");
assert_eq!(
obj.len(),
1,
"each pre-lift consumer composed exactly ONE top-level slot"
);
}
}
#[test]
fn merge_body_composition_matches_pre_lift_signals_and_finalizer_shapes_bytewise() {
let strip = json!({
"metadata": {
"annotations": { "tatara.pleme.io/signal": serde_json::Value::Null }
}
});
assert_eq!(
strip["metadata"]["annotations"]["tatara.pleme.io/signal"],
serde_json::Value::Null,
"strip stamps JSON null to trigger merge-patch key removal"
);
let suspend = json!({ "spec": { "suspended": true } });
assert_eq!(suspend["spec"]["suspended"], serde_json::Value::Bool(true));
let resume = json!({ "spec": { "suspended": false } });
assert_eq!(resume["spec"]["suspended"], serde_json::Value::Bool(false));
}
#[test]
fn spec_suspended_body_wraps_true_under_spec_suspended_slot() {
let body = spec_suspended_body(true);
assert_eq!(body, json!({ "spec": { "suspended": true } }));
}
#[test]
fn spec_suspended_body_wraps_false_under_spec_suspended_slot() {
let body = spec_suspended_body(false);
assert_eq!(body, json!({ "spec": { "suspended": false } }));
}
#[test]
fn spec_suspended_body_top_level_slot_is_exactly_spec_lowercase() {
for value in [true, false] {
let body = spec_suspended_body(value);
let obj = body.as_object().expect("top-level must be a JSON object");
assert_eq!(obj.len(), 1, "wrap adds exactly ONE top-level slot");
assert!(
obj.contains_key("spec"),
"top-level slot must be exactly `spec` (lowercase)"
);
}
}
#[test]
fn spec_suspended_body_inner_slot_is_exactly_suspended_lowercase() {
for value in [true, false] {
let body = spec_suspended_body(value);
let spec = body["spec"]
.as_object()
.expect("inner `spec` must be a JSON object");
assert_eq!(
spec.len(),
1,
"inner spec carries exactly ONE slot (`suspended`)"
);
assert!(
spec.contains_key("suspended"),
"inner slot must be exactly `suspended` (lowercase)"
);
}
}
#[test]
fn spec_suspended_body_inner_value_is_json_bool_not_string() {
assert_eq!(
spec_suspended_body(true)["spec"]["suspended"],
serde_json::Value::Bool(true),
);
assert_eq!(
spec_suspended_body(false)["spec"]["suspended"],
serde_json::Value::Bool(false),
);
}
#[test]
fn spec_suspended_body_matches_pre_lift_hand_authored_shape_bytewise() {
for value in [true, false] {
let composed = spec_suspended_body(value);
let hand_authored = json!({ "spec": { "suspended": value } });
assert_eq!(
composed, hand_authored,
"spec_suspended_body({value}) must be byte-identical to the pre-lift `json!` block",
);
}
}
}