test(kms): pin admin KMS response shapes where they are served (#5626)

The snapshots in crates/kms/src/api_types.rs pinned DeleteKeyResponse,
ListKeysResponse, DescribeKeyResponse and CancelKeyDeletionResponse, none
of which is serialized by any handler: those endpoints answer with
DeleteKmsKeyResponse and siblings in rustfs/src/admin/handlers/kms_keys.rs,
separate types carrying different fields. A breaking change to an admin
response could not fail them. Tag, untag and update-description had the
same gap, where the handler discards the kms-side response and serves its
own KmsKeyMetadataResponse.

Pin the shapes in the crate that produces them, and delete the four kms
mirrors. They were never in the pub use api_types list, had no
constructors and no callers, and only looked live because those snapshots
named them.

Keep the api_types snapshots that pin something real: configure, start,
stop and status are served verbatim by kms_dynamic, and the tag family
are live ObjectEncryptionService return types whose snapshots pin this
crate's public API rather than a wire shape.
This commit is contained in:
Zhengchao An
2026-08-02 19:20:17 +08:00
committed by GitHub
parent b1ddda3bb2
commit 2698a03582
20 changed files with 518 additions and 138 deletions
+25 -90
View File
@@ -21,7 +21,6 @@ use crate::config::{
redacted_secret, redacted_secret_option,
};
use crate::service_manager::KmsServiceStatus;
use crate::types::KeyMetadata;
use serde::{Deserialize, Deserializer, Serialize};
use std::collections::HashMap;
use std::fmt;
@@ -1199,6 +1198,19 @@ mod tests {
}
}
/// The shapes this crate owns, and only those.
///
/// The first four are served verbatim by the dynamic-configuration admin
/// handlers, so pinning them here pins the wire. The last three never
/// reach a socket: `ObjectEncryptionService` returns them and the admin
/// layer answers with its own `KmsKeyMetadataResponse` instead, so what
/// they pin is this crate's public API, not the wire.
///
/// No key-management response belongs in this test. Those endpoints are
/// served from types defined in the `rustfs` crate, and a copy here could
/// only ever agree with them by accident — see
/// `kms_key_admin_responses_have_stable_json_shapes` in
/// `rustfs/src/admin/handlers/kms_keys.rs`.
#[test]
fn kms_management_responses_have_stable_json_shapes() {
insta::assert_json_snapshot!(
@@ -1234,41 +1246,6 @@ mod tests {
config_summary: None,
})
);
insta::assert_json_snapshot!(
"kms_delete_key_response",
stable_json_value(DeleteKeyResponse {
success: true,
message: "key scheduled for deletion".to_string(),
key_id: "key-a".to_string(),
deletion_date: Some("2026-07-01T00:00:00Z".to_string()),
})
);
insta::assert_json_snapshot!(
"kms_list_keys_response",
stable_json_value(ListKeysResponse {
success: true,
message: "keys listed".to_string(),
keys: vec!["key-a".to_string(), "key-b".to_string()],
truncated: true,
next_marker: Some("key-b".to_string()),
})
);
insta::assert_json_snapshot!(
"kms_describe_key_response_missing",
stable_json_value(DescribeKeyResponse {
success: false,
message: "key not found".to_string(),
key_metadata: None,
})
);
insta::assert_json_snapshot!(
"kms_cancel_key_deletion_response",
stable_json_value(CancelKeyDeletionResponse {
success: true,
message: "key deletion canceled".to_string(),
key_id: "key-a".to_string(),
})
);
insta::assert_json_snapshot!(
"kms_update_key_description_response",
stable_json_value(UpdateKeyDescriptionResponse {
@@ -1299,60 +1276,18 @@ mod tests {
// ========================================
// Key Management API Types
// ========================================
/// JSON shape returned by the admin delete-key endpoint.
///
/// The delete *request* shape lives in [`crate::types::DeleteKeyRequest`] —
/// there is deliberately no copy here, because the immediate-deletion gate
/// (`force_immediate` + `confirm_key_id`) must have exactly one definition.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeleteKeyResponse {
/// Success flag
pub success: bool,
/// Status message
pub message: String,
/// Key ID that was deleted or scheduled for deletion
pub key_id: String,
/// Deletion date (if scheduled)
pub deletion_date: Option<String>,
}
/// Response from list keys operation
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListKeysResponse {
/// Success flag
pub success: bool,
/// Status message
pub message: String,
/// List of key IDs
pub keys: Vec<String>,
/// Whether more keys are available
pub truncated: bool,
/// Next marker for pagination
pub next_marker: Option<String>,
}
/// Response from describe key operation
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DescribeKeyResponse {
/// Success flag
pub success: bool,
/// Status message
pub message: String,
/// Key metadata
pub key_metadata: Option<KeyMetadata>,
}
/// Response from cancel key deletion operation
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CancelKeyDeletionResponse {
/// Success flag
pub success: bool,
/// Status message
pub message: String,
/// Key ID
pub key_id: String,
}
//
// What remains here is the key-metadata trio, and nothing else belongs.
// Create, delete, list, describe and cancel-deletion are served from types
// defined in the `rustfs` crate (`rustfs/src/admin/handlers/kms_keys.rs`)
// carrying fields this crate knows nothing about, so a copy here would shadow
// `crate::types` under the same name while agreeing with the wire only by
// accident.
//
// The same holds for `DeleteKeyRequest`: it lives in `crate::types` alone, so
// the immediate-deletion gate (`force_immediate` + `confirm_key_id`) has
// exactly one definition and cannot be silently dropped by deserializing into
// a copy that lacks it.
/// Request to update key description
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -1,9 +0,0 @@
---
source: crates/kms/src/api_types.rs
expression: "serde_json::to_value(CancelKeyDeletionResponse\n{\n success: true, message: \"key deletion canceled\".to_string(), key_id:\n \"key-a\".to_string(),\n}).expect(\"cancel deletion response should serialize\")"
---
{
"key_id": "key-a",
"message": "key deletion canceled",
"success": true
}
@@ -1,10 +0,0 @@
---
source: crates/kms/src/api_types.rs
expression: "serde_json::to_value(DeleteKeyResponse\n{\n success: true, message: \"key scheduled for deletion\".to_string(), key_id:\n \"key-a\".to_string(), deletion_date:\n Some(\"2026-07-01T00:00:00Z\".to_string()),\n}).expect(\"delete key response should serialize\")"
---
{
"deletion_date": "2026-07-01T00:00:00Z",
"key_id": "key-a",
"message": "key scheduled for deletion",
"success": true
}
@@ -1,9 +0,0 @@
---
source: crates/kms/src/api_types.rs
expression: "serde_json::to_value(DescribeKeyResponse\n{\n success: false, message: \"key not found\".to_string(), key_metadata: None,\n}).expect(\"describe key response should serialize\")"
---
{
"key_metadata": null,
"message": "key not found",
"success": false
}
@@ -1,14 +0,0 @@
---
source: crates/kms/src/api_types.rs
expression: "serde_json::to_value(ListKeysResponse\n{\n success: true, message: \"keys listed\".to_string(), keys:\n vec![\"key-a\".to_string(), \"key-b\".to_string()], truncated: true,\n next_marker: Some(\"key-b\".to_string()),\n}).expect(\"list keys response should serialize\")"
---
{
"keys": [
"key-a",
"key-b"
],
"message": "keys listed",
"next_marker": "key-b",
"success": true,
"truncated": true
}