mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-31 09:18:28 +00:00
test: expand snapshot coverage (#4024)
This commit is contained in:
Generated
+4
@@ -9063,6 +9063,7 @@ dependencies = [
|
|||||||
"http-body-util",
|
"http-body-util",
|
||||||
"hyper",
|
"hyper",
|
||||||
"hyper-util",
|
"hyper-util",
|
||||||
|
"insta",
|
||||||
"jemalloc_pprof",
|
"jemalloc_pprof",
|
||||||
"jiff",
|
"jiff",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -9213,6 +9214,7 @@ dependencies = [
|
|||||||
name = "rustfs-concurrency"
|
name = "rustfs-concurrency"
|
||||||
version = "1.0.0-beta.8"
|
version = "1.0.0-beta.8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"insta",
|
||||||
"rustfs-io-core",
|
"rustfs-io-core",
|
||||||
"rustfs-io-metrics",
|
"rustfs-io-metrics",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -9612,6 +9614,7 @@ dependencies = [
|
|||||||
"async-trait",
|
"async-trait",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"chacha20poly1305",
|
"chacha20poly1305",
|
||||||
|
"insta",
|
||||||
"jiff",
|
"jiff",
|
||||||
"md5",
|
"md5",
|
||||||
"moka",
|
"moka",
|
||||||
@@ -10048,6 +10051,7 @@ name = "rustfs-storage-api"
|
|||||||
version = "1.0.0-beta.8"
|
version = "1.0.0-beta.8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
"insta",
|
||||||
"rustfs-filemeta",
|
"rustfs-filemeta",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ thiserror = { workspace = true }
|
|||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
insta = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
tokio = { workspace = true, features = ["test-util","macros","rt-multi-thread"] }
|
tokio = { workspace = true, features = ["test-util","macros","rt-multi-thread"] }
|
||||||
|
|
||||||
|
|||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
source: crates/concurrency/src/workload.rs
|
||||||
|
expression: "serde_json::to_value(®istry).expect(\"workload admission registry should serialize\")"
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"active": null,
|
||||||
|
"class": "scanner",
|
||||||
|
"limit": null,
|
||||||
|
"queued": null,
|
||||||
|
"reason": null,
|
||||||
|
"state": "unknown"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"active": 3,
|
||||||
|
"class": "foreground_read",
|
||||||
|
"limit": 8,
|
||||||
|
"queued": 1,
|
||||||
|
"reason": null,
|
||||||
|
"state": "open"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
source: crates/concurrency/src/workload.rs
|
||||||
|
expression: encoded
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"active": 0,
|
||||||
|
"class": "foreground_write",
|
||||||
|
"limit": 0,
|
||||||
|
"queued": 0,
|
||||||
|
"reason": "foreground write admission not exposed",
|
||||||
|
"state": "disabled"
|
||||||
|
}
|
||||||
@@ -181,7 +181,25 @@ pub trait WorkloadAdmissionSnapshotProvider {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use serde_json::json;
|
use serde_json::{Value, json};
|
||||||
|
|
||||||
|
fn sorted_json_value(value: Value) -> Value {
|
||||||
|
match value {
|
||||||
|
Value::Array(values) => Value::Array(values.into_iter().map(sorted_json_value).collect()),
|
||||||
|
Value::Object(object) => {
|
||||||
|
let mut entries: Vec<_> = object.into_iter().collect();
|
||||||
|
entries.sort_by(|(left, _), (right, _)| left.cmp(right));
|
||||||
|
|
||||||
|
Value::Object(
|
||||||
|
entries
|
||||||
|
.into_iter()
|
||||||
|
.map(|(key, value)| (key, sorted_json_value(value)))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
value => value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn workload_contract_covers_required_classes() {
|
fn workload_contract_covers_required_classes() {
|
||||||
@@ -211,6 +229,10 @@ mod tests {
|
|||||||
);
|
);
|
||||||
let registry = WorkloadAdmissionRegistrySnapshot::new(vec![unknown.clone(), open.clone()]);
|
let registry = WorkloadAdmissionRegistrySnapshot::new(vec![unknown.clone(), open.clone()]);
|
||||||
|
|
||||||
|
insta::assert_json_snapshot!(
|
||||||
|
"workload_admission_registry_snapshot",
|
||||||
|
sorted_json_value(serde_json::to_value(®istry).expect("workload admission registry should serialize"))
|
||||||
|
);
|
||||||
assert_eq!(registry.entries(), &[unknown, open]);
|
assert_eq!(registry.entries(), &[unknown, open]);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
registry.get(WorkloadClass::Scanner).map(|snapshot| snapshot.state),
|
registry.get(WorkloadClass::Scanner).map(|snapshot| snapshot.state),
|
||||||
@@ -262,8 +284,9 @@ mod tests {
|
|||||||
let snapshot = WorkloadAdmissionSnapshot::new(WorkloadClass::ForegroundWrite, AdmissionState::Disabled)
|
let snapshot = WorkloadAdmissionSnapshot::new(WorkloadClass::ForegroundWrite, AdmissionState::Disabled)
|
||||||
.with_counts(Some(0), Some(0), Some(0))
|
.with_counts(Some(0), Some(0), Some(0))
|
||||||
.with_reason("foreground write admission not exposed");
|
.with_reason("foreground write admission not exposed");
|
||||||
let encoded = serde_json::to_value(&snapshot).expect("serialize workload admission snapshot");
|
let encoded = sorted_json_value(serde_json::to_value(&snapshot).expect("serialize workload admission snapshot"));
|
||||||
|
|
||||||
|
insta::assert_json_snapshot!("workload_admission_snapshot", encoded);
|
||||||
assert_eq!(encoded["class"], json!("foreground_write"));
|
assert_eq!(encoded["class"], json!("foreground_write"));
|
||||||
assert_eq!(encoded["state"], json!("disabled"));
|
assert_eq!(encoded["state"], json!("disabled"));
|
||||||
|
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ reqwest = { workspace = true }
|
|||||||
vaultrs = { workspace = true }
|
vaultrs = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
insta = { workspace = true }
|
||||||
tempfile = { workspace = true }
|
tempfile = { workspace = true }
|
||||||
temp-env = { workspace = true }
|
temp-env = { workspace = true }
|
||||||
|
|
||||||
|
|||||||
@@ -487,6 +487,29 @@ impl ConfigureKmsRequest {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::config::REDACTED_SECRET;
|
use crate::config::REDACTED_SECRET;
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
fn stable_json_value(value: impl serde::Serialize) -> Value {
|
||||||
|
sorted_json_value(serde_json::to_value(value).expect("KMS snapshot value should serialize"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sorted_json_value(value: Value) -> Value {
|
||||||
|
match value {
|
||||||
|
Value::Array(values) => Value::Array(values.into_iter().map(sorted_json_value).collect()),
|
||||||
|
Value::Object(object) => {
|
||||||
|
let mut entries: Vec<_> = object.into_iter().collect();
|
||||||
|
entries.sort_by(|(left, _), (right, _)| left.cmp(right));
|
||||||
|
|
||||||
|
Value::Object(
|
||||||
|
entries
|
||||||
|
.into_iter()
|
||||||
|
.map(|(key, value)| (key, sorted_json_value(value)))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
value => value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_deserialize_vault_kv2_configure_request_accepts_type_aliases() {
|
fn test_deserialize_vault_kv2_configure_request_accepts_type_aliases() {
|
||||||
@@ -650,6 +673,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let summary = KmsConfigSummary::from(&config);
|
let summary = KmsConfigSummary::from(&config);
|
||||||
|
insta::assert_json_snapshot!("kms_vault_transit_config_summary", stable_json_value(&summary));
|
||||||
assert_eq!(summary.backend_type, KmsBackend::VaultTransit);
|
assert_eq!(summary.backend_type, KmsBackend::VaultTransit);
|
||||||
assert_eq!(summary.timeout_seconds, 30);
|
assert_eq!(summary.timeout_seconds, 30);
|
||||||
assert_eq!(summary.retry_attempts, 3);
|
assert_eq!(summary.retry_attempts, 3);
|
||||||
@@ -777,6 +801,102 @@ mod tests {
|
|||||||
assert!(rendered.contains("has_master_key") || rendered.contains("has_stored_credentials"));
|
assert!(rendered.contains("has_master_key") || rendered.contains("has_stored_credentials"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn kms_management_responses_have_stable_json_shapes() {
|
||||||
|
insta::assert_json_snapshot!(
|
||||||
|
"kms_configure_response",
|
||||||
|
stable_json_value(ConfigureKmsResponse {
|
||||||
|
success: true,
|
||||||
|
message: "kms configured".to_string(),
|
||||||
|
status: KmsServiceStatus::Configured,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
insta::assert_json_snapshot!(
|
||||||
|
"kms_start_response",
|
||||||
|
stable_json_value(StartKmsResponse {
|
||||||
|
success: true,
|
||||||
|
message: "kms started".to_string(),
|
||||||
|
status: KmsServiceStatus::Running,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
insta::assert_json_snapshot!(
|
||||||
|
"kms_stop_response",
|
||||||
|
stable_json_value(StopKmsResponse {
|
||||||
|
success: true,
|
||||||
|
message: "kms stopped".to_string(),
|
||||||
|
status: KmsServiceStatus::Configured,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
insta::assert_json_snapshot!(
|
||||||
|
"kms_status_response",
|
||||||
|
stable_json_value(KmsStatusResponse {
|
||||||
|
status: KmsServiceStatus::Running,
|
||||||
|
backend_type: Some(KmsBackend::VaultTransit),
|
||||||
|
healthy: Some(true),
|
||||||
|
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 {
|
||||||
|
success: true,
|
||||||
|
message: "key description updated".to_string(),
|
||||||
|
key_id: "key-a".to_string(),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
insta::assert_json_snapshot!(
|
||||||
|
"kms_tag_key_response",
|
||||||
|
stable_json_value(TagKeyResponse {
|
||||||
|
success: true,
|
||||||
|
message: "key tags updated".to_string(),
|
||||||
|
key_id: "key-a".to_string(),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
insta::assert_json_snapshot!(
|
||||||
|
"kms_untag_key_response",
|
||||||
|
stable_json_value(UntagKeyResponse {
|
||||||
|
success: true,
|
||||||
|
message: "key tags removed".to_string(),
|
||||||
|
key_id: "key-a".to_string(),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
|
|||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
source: crates/kms/src/api_types.rs
|
||||||
|
expression: "serde_json::to_value(ConfigureKmsResponse\n{\n success: true, message: \"kms configured\".to_string(), status:\n KmsServiceStatus::Configured,\n}).expect(\"configure response should serialize\")"
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"message": "kms configured",
|
||||||
|
"status": "Configured",
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
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
|
||||||
|
}
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
source: crates/kms/src/api_types.rs
|
||||||
|
expression: "serde_json::to_value(StartKmsResponse\n{\n success: true, message: \"kms started\".to_string(), status:\n KmsServiceStatus::Running,\n}).expect(\"start response should serialize\")"
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"message": "kms started",
|
||||||
|
"status": "Running",
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
source: crates/kms/src/api_types.rs
|
||||||
|
expression: "serde_json::to_value(KmsStatusResponse\n{\n status: KmsServiceStatus::Running, backend_type:\n Some(KmsBackend::VaultTransit), healthy: Some(true), config_summary: None,\n}).expect(\"status response should serialize\")"
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"backend_type": "VaultTransit",
|
||||||
|
"config_summary": null,
|
||||||
|
"healthy": true,
|
||||||
|
"status": "Running"
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
source: crates/kms/src/api_types.rs
|
||||||
|
expression: "serde_json::to_value(StopKmsResponse\n{\n success: true, message: \"kms stopped\".to_string(), status:\n KmsServiceStatus::Configured,\n}).expect(\"stop response should serialize\")"
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"message": "kms stopped",
|
||||||
|
"status": "Configured",
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
source: crates/kms/src/api_types.rs
|
||||||
|
expression: "serde_json::to_value(TagKeyResponse\n{\n success: true, message: \"key tags updated\".to_string(), key_id:\n \"key-a\".to_string(),\n}).expect(\"tag key response should serialize\")"
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"key_id": "key-a",
|
||||||
|
"message": "key tags updated",
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
source: crates/kms/src/api_types.rs
|
||||||
|
expression: "serde_json::to_value(UntagKeyResponse\n{\n success: true, message: \"key tags removed\".to_string(), key_id:\n \"key-a\".to_string(),\n}).expect(\"untag key response should serialize\")"
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"key_id": "key-a",
|
||||||
|
"message": "key tags removed",
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
source: crates/kms/src/api_types.rs
|
||||||
|
expression: "serde_json::to_value(UpdateKeyDescriptionResponse\n{\n success: true, message: \"key description updated\".to_string(), key_id:\n \"key-a\".to_string(),\n}).expect(\"update description response should serialize\")"
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"key_id": "key-a",
|
||||||
|
"message": "key description updated",
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
source: crates/kms/src/api_types.rs
|
||||||
|
expression: "serde_json::to_value(&summary).expect(\"KMS config summary should serialize\")"
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"backend_summary": {
|
||||||
|
"address": "http://127.0.0.1:8200",
|
||||||
|
"auth_method_type": "token",
|
||||||
|
"backend_type": "vault-transit",
|
||||||
|
"has_stored_credentials": true,
|
||||||
|
"mount_path": "transit",
|
||||||
|
"namespace": "tenant-a",
|
||||||
|
"skip_tls_verify": false
|
||||||
|
},
|
||||||
|
"backend_type": "VaultTransit",
|
||||||
|
"cache_summary": {
|
||||||
|
"enable_metrics": true,
|
||||||
|
"max_keys": 1000,
|
||||||
|
"ttl_seconds": 3600
|
||||||
|
},
|
||||||
|
"cache_ttl_seconds": 3600,
|
||||||
|
"default_key_id": "rustfs-master-key",
|
||||||
|
"enable_cache": true,
|
||||||
|
"max_cached_keys": 1000,
|
||||||
|
"retry_attempts": 3,
|
||||||
|
"timeout_seconds": 30
|
||||||
|
}
|
||||||
@@ -38,6 +38,7 @@ time.workspace = true
|
|||||||
uuid.workspace = true
|
uuid.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
insta = { workspace = true }
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
tokio = { workspace = true, features = ["macros", "rt"] }
|
tokio = { workspace = true, features = ["macros", "rt"] }
|
||||||
|
|
||||||
|
|||||||
@@ -95,13 +95,36 @@ impl std::error::Error for CapabilitySnapshotError {}
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
fn sorted_json_value(value: Value) -> Value {
|
||||||
|
match value {
|
||||||
|
Value::Array(values) => Value::Array(values.into_iter().map(sorted_json_value).collect()),
|
||||||
|
Value::Object(object) => {
|
||||||
|
let mut entries: Vec<_> = object.into_iter().collect();
|
||||||
|
entries.sort_by(|(left, _), (right, _)| left.cmp(right));
|
||||||
|
|
||||||
|
Value::Object(
|
||||||
|
entries
|
||||||
|
.into_iter()
|
||||||
|
.map(|(key, value)| (key, sorted_json_value(value)))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
value => value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn capability_status_serializes_unknown_and_unsupported_states() {
|
fn capability_status_serializes_unknown_and_unsupported_states() {
|
||||||
let unknown = CapabilityStatus::unknown();
|
let unknown = CapabilityStatus::unknown();
|
||||||
let unsupported = CapabilityStatus::unsupported().with_reason("target does not expose profiler");
|
let unsupported = CapabilityStatus::unsupported().with_reason("target does not expose profiler");
|
||||||
|
|
||||||
let encoded = serde_json::to_string(&(unknown, unsupported)).expect("serialize capability statuses");
|
let encoded = serde_json::to_string(&(&unknown, &unsupported)).expect("serialize capability statuses");
|
||||||
|
insta::assert_json_snapshot!(
|
||||||
|
"capability_status_contract",
|
||||||
|
sorted_json_value(serde_json::to_value((&unknown, &unsupported)).expect("capability statuses should serialize"))
|
||||||
|
);
|
||||||
let decoded: (CapabilityStatus, CapabilityStatus) =
|
let decoded: (CapabilityStatus, CapabilityStatus) =
|
||||||
serde_json::from_str(&encoded).expect("deserialize capability statuses");
|
serde_json::from_str(&encoded).expect("deserialize capability statuses");
|
||||||
|
|
||||||
|
|||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
source: crates/storage-api/src/capability.rs
|
||||||
|
expression: "serde_json::to_value((&unknown,\n&unsupported)).expect(\"capability statuses should serialize\")"
|
||||||
|
---
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"state": "unknown"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"reason": "target does not expose profiler",
|
||||||
|
"state": "unsupported"
|
||||||
|
}
|
||||||
|
]
|
||||||
+83
@@ -0,0 +1,83 @@
|
|||||||
|
---
|
||||||
|
source: crates/storage-api/src/topology.rs
|
||||||
|
expression: "serde_json::to_value(&snapshot).expect(\"topology snapshot should serialize\")"
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"capabilities": {
|
||||||
|
"failure_domain_labels": {
|
||||||
|
"state": "supported"
|
||||||
|
},
|
||||||
|
"media_labels": {
|
||||||
|
"state": "supported"
|
||||||
|
},
|
||||||
|
"numa": {
|
||||||
|
"state": "unknown"
|
||||||
|
},
|
||||||
|
"profiling": {
|
||||||
|
"state": "supported"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pools": [
|
||||||
|
{
|
||||||
|
"labels": {
|
||||||
|
"additional": {
|
||||||
|
"room": "a"
|
||||||
|
},
|
||||||
|
"media": null,
|
||||||
|
"node": null,
|
||||||
|
"numa_node": null,
|
||||||
|
"rack": null,
|
||||||
|
"zone": null
|
||||||
|
},
|
||||||
|
"pool_id": null,
|
||||||
|
"pool_index": 0,
|
||||||
|
"sets": [
|
||||||
|
{
|
||||||
|
"disks": [
|
||||||
|
{
|
||||||
|
"capabilities": {
|
||||||
|
"failure_domain": {
|
||||||
|
"state": "unknown"
|
||||||
|
},
|
||||||
|
"media_type": {
|
||||||
|
"state": "supported"
|
||||||
|
},
|
||||||
|
"numa": {
|
||||||
|
"reason": "not reported",
|
||||||
|
"state": "unsupported"
|
||||||
|
},
|
||||||
|
"profiling": {
|
||||||
|
"state": "disabled"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"disk_id": "disk-2",
|
||||||
|
"disk_index": 2,
|
||||||
|
"labels": {
|
||||||
|
"additional": {
|
||||||
|
"slot": "nvme0"
|
||||||
|
},
|
||||||
|
"media": "ssd",
|
||||||
|
"node": null,
|
||||||
|
"numa_node": null,
|
||||||
|
"rack": null,
|
||||||
|
"zone": null
|
||||||
|
},
|
||||||
|
"pool_index": 0,
|
||||||
|
"set_index": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"labels": {
|
||||||
|
"media": null,
|
||||||
|
"node": null,
|
||||||
|
"numa_node": null,
|
||||||
|
"rack": null,
|
||||||
|
"zone": null
|
||||||
|
},
|
||||||
|
"pool_index": 0,
|
||||||
|
"set_id": null,
|
||||||
|
"set_index": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -95,6 +95,25 @@ pub trait TopologySnapshotProvider: Send + Sync + Debug {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::CapabilityState;
|
use crate::CapabilityState;
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
fn sorted_json_value(value: Value) -> Value {
|
||||||
|
match value {
|
||||||
|
Value::Array(values) => Value::Array(values.into_iter().map(sorted_json_value).collect()),
|
||||||
|
Value::Object(object) => {
|
||||||
|
let mut entries: Vec<_> = object.into_iter().collect();
|
||||||
|
entries.sort_by(|(left, _), (right, _)| left.cmp(right));
|
||||||
|
|
||||||
|
Value::Object(
|
||||||
|
entries
|
||||||
|
.into_iter()
|
||||||
|
.map(|(key, value)| (key, sorted_json_value(value)))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
value => value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn topology_snapshot_allows_missing_and_extra_labels() {
|
fn topology_snapshot_allows_missing_and_extra_labels() {
|
||||||
@@ -141,6 +160,10 @@ mod tests {
|
|||||||
}"#;
|
}"#;
|
||||||
|
|
||||||
let snapshot: TopologySnapshot = serde_json::from_str(raw).expect("deserialize topology snapshot");
|
let snapshot: TopologySnapshot = serde_json::from_str(raw).expect("deserialize topology snapshot");
|
||||||
|
insta::assert_json_snapshot!(
|
||||||
|
"topology_snapshot_contract",
|
||||||
|
sorted_json_value(serde_json::to_value(&snapshot).expect("topology snapshot should serialize"))
|
||||||
|
);
|
||||||
let disk = &snapshot.pools[0].sets[0].disks[0];
|
let disk = &snapshot.pools[0].sets[0].disks[0];
|
||||||
|
|
||||||
assert_eq!(snapshot.pools[0].labels.zone, None);
|
assert_eq!(snapshot.pools[0].labels.zone, None);
|
||||||
|
|||||||
@@ -210,6 +210,7 @@ serial_test = { workspace = true }
|
|||||||
tempfile = { workspace = true }
|
tempfile = { workspace = true }
|
||||||
aws-config = { workspace = true }
|
aws-config = { workspace = true }
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
|
insta = { workspace = true }
|
||||||
proptest = "1"
|
proptest = "1"
|
||||||
tokio = { workspace = true, features = ["test-util"] }
|
tokio = { workspace = true, features = ["test-util"] }
|
||||||
temp-env = { workspace = true, features = ["async_closure"] }
|
temp-env = { workspace = true, features = ["async_closure"] }
|
||||||
|
|||||||
@@ -382,9 +382,26 @@ mod tests {
|
|||||||
PluginInstanceDetail, PluginInstanceDiagnostic, PluginInstanceDiagnosticCode, PluginInstanceDiagnosticCount,
|
PluginInstanceDetail, PluginInstanceDiagnostic, PluginInstanceDiagnosticCode, PluginInstanceDiagnosticCount,
|
||||||
PluginInstanceEntry, PluginInstanceSource, PluginInstancesResponse, PluginRuntimeContract, PluginRuntimeTransport,
|
PluginInstanceEntry, PluginInstanceSource, PluginInstancesResponse, PluginRuntimeContract, PluginRuntimeTransport,
|
||||||
};
|
};
|
||||||
use serde_json::json;
|
use serde_json::{Value, json};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
fn sorted_json_value(value: Value) -> Value {
|
||||||
|
match value {
|
||||||
|
Value::Array(values) => Value::Array(values.into_iter().map(sorted_json_value).collect()),
|
||||||
|
Value::Object(map) => {
|
||||||
|
let mut entries = map.into_iter().collect::<Vec<_>>();
|
||||||
|
entries.sort_by(|(left, _), (right, _)| left.cmp(right));
|
||||||
|
Value::Object(
|
||||||
|
entries
|
||||||
|
.into_iter()
|
||||||
|
.map(|(key, value)| (key, sorted_json_value(value)))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
value => value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn plugin_catalog_contract_serializes_stable_json_shape() {
|
fn plugin_catalog_contract_serializes_stable_json_shape() {
|
||||||
let response = PluginCatalogResponse {
|
let response = PluginCatalogResponse {
|
||||||
@@ -418,7 +435,8 @@ mod tests {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let value = serde_json::to_value(response).expect("catalog response should serialize");
|
let value = sorted_json_value(serde_json::to_value(response).expect("catalog response should serialize"));
|
||||||
|
insta::assert_json_snapshot!("plugin_catalog_contract", value);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
value,
|
value,
|
||||||
json!({
|
json!({
|
||||||
@@ -481,7 +499,8 @@ mod tests {
|
|||||||
next_marker: None,
|
next_marker: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let value = serde_json::to_value(response).expect("instance response should serialize");
|
let value = sorted_json_value(serde_json::to_value(response).expect("instance response should serialize"));
|
||||||
|
insta::assert_json_snapshot!("plugin_instance_contract", value);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
value,
|
value,
|
||||||
json!({
|
json!({
|
||||||
@@ -534,7 +553,8 @@ mod tests {
|
|||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
|
|
||||||
let value = serde_json::to_value(detail).expect("instance detail should serialize");
|
let value = sorted_json_value(serde_json::to_value(detail).expect("instance detail should serialize"));
|
||||||
|
insta::assert_json_snapshot!("plugin_instance_detail_contract", value);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
value,
|
value,
|
||||||
json!({
|
json!({
|
||||||
@@ -591,7 +611,8 @@ mod tests {
|
|||||||
installation: None,
|
installation: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let value = serde_json::to_value(entry).expect("catalog entry should serialize");
|
let value = sorted_json_value(serde_json::to_value(entry).expect("catalog entry should serialize"));
|
||||||
|
insta::assert_json_snapshot!("plugin_catalog_distribution_contract", value);
|
||||||
assert_eq!(value["distribution"]["artifacts"][0]["artifact_id"], "sidecar-linux-amd64");
|
assert_eq!(value["distribution"]["artifacts"][0]["artifact_id"], "sidecar-linux-amd64");
|
||||||
assert_eq!(value["distribution"]["artifacts"][0]["target_triple"], "x86_64-unknown-linux-gnu");
|
assert_eq!(value["distribution"]["artifacts"][0]["target_triple"], "x86_64-unknown-linux-gnu");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
+45
@@ -0,0 +1,45 @@
|
|||||||
|
---
|
||||||
|
source: rustfs/src/admin/plugin_contract.rs
|
||||||
|
expression: value
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"admin_discovery": {
|
||||||
|
"clusterSnapshot": "/rustfs/admin/v4/cluster/snapshot",
|
||||||
|
"extensionsCatalog": "/rustfs/admin/v4/extensions/catalog",
|
||||||
|
"runtimeCapabilities": "/rustfs/admin/v4/runtime/capabilities"
|
||||||
|
},
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"api_compatibility_version": "rustfs.target-plugin.v1",
|
||||||
|
"display_name": "Webhook",
|
||||||
|
"distribution": null,
|
||||||
|
"domain_configs": [
|
||||||
|
{
|
||||||
|
"domain": "notify",
|
||||||
|
"subsystem": "notify_webhook",
|
||||||
|
"valid_fields": [
|
||||||
|
"endpoint",
|
||||||
|
"auth_token"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"entrypoint_kind": "builtin",
|
||||||
|
"packaging": "builtin",
|
||||||
|
"plugin_id": "builtin:webhook",
|
||||||
|
"provider": "rustfs",
|
||||||
|
"runtime_contract": {
|
||||||
|
"protocol_version": "rustfs.target-runtime.v1",
|
||||||
|
"transport": "in_process"
|
||||||
|
},
|
||||||
|
"secret_fields": [
|
||||||
|
"auth_token"
|
||||||
|
],
|
||||||
|
"supported_domains": [
|
||||||
|
"audit",
|
||||||
|
"notify"
|
||||||
|
],
|
||||||
|
"target_type": "webhook",
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
---
|
||||||
|
source: rustfs/src/admin/plugin_contract.rs
|
||||||
|
expression: value
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"api_compatibility_version": "rustfs.target-plugin.v1",
|
||||||
|
"display_name": "Webhook+",
|
||||||
|
"distribution": {
|
||||||
|
"artifacts": [
|
||||||
|
{
|
||||||
|
"artifact_id": "sidecar-linux-amd64",
|
||||||
|
"digest_sha256": "0123456789abcdef",
|
||||||
|
"download_uri": "https://plugins.example.test/webhook.tar.zst",
|
||||||
|
"provenance_uri": "https://plugins.example.test/webhook.tar.zst.intoto.jsonl",
|
||||||
|
"signature_uri": "https://plugins.example.test/webhook.tar.zst.sig",
|
||||||
|
"size_bytes": 4096,
|
||||||
|
"target_triple": "x86_64-unknown-linux-gnu"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"domain_configs": [],
|
||||||
|
"entrypoint_kind": "sidecar",
|
||||||
|
"packaging": "builtin",
|
||||||
|
"plugin_id": "external:webhook",
|
||||||
|
"provider": "example",
|
||||||
|
"runtime_contract": {
|
||||||
|
"protocol_version": "rustfs.target-runtime.v1",
|
||||||
|
"transport": "grpc"
|
||||||
|
},
|
||||||
|
"secret_fields": [],
|
||||||
|
"supported_domains": [
|
||||||
|
"notify"
|
||||||
|
],
|
||||||
|
"target_type": "webhook",
|
||||||
|
"version": "1.2.3"
|
||||||
|
}
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
source: rustfs/src/admin/plugin_contract.rs
|
||||||
|
expression: value
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"diagnostic_counts": [
|
||||||
|
{
|
||||||
|
"code": "not_loaded_in_runtime",
|
||||||
|
"count": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"instances": [
|
||||||
|
{
|
||||||
|
"account_id": "primary",
|
||||||
|
"config": {
|
||||||
|
"enable": "on",
|
||||||
|
"endpoint": "https://example.com/hook"
|
||||||
|
},
|
||||||
|
"diagnostic_codes": [
|
||||||
|
"not_loaded_in_runtime"
|
||||||
|
],
|
||||||
|
"domain": "notify",
|
||||||
|
"enabled": true,
|
||||||
|
"id": "builtin:webhook:notify:primary",
|
||||||
|
"plugin_id": "builtin:webhook",
|
||||||
|
"service": "webhook",
|
||||||
|
"source": "config",
|
||||||
|
"status": "offline",
|
||||||
|
"subsystem": "notify_webhook"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"next_marker": null,
|
||||||
|
"truncated": false
|
||||||
|
}
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
source: rustfs/src/admin/plugin_contract.rs
|
||||||
|
expression: value
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"account_id": "primary",
|
||||||
|
"config": {
|
||||||
|
"endpoint": "https://example.com/hook"
|
||||||
|
},
|
||||||
|
"diagnostic_codes": [
|
||||||
|
"not_loaded_in_runtime"
|
||||||
|
],
|
||||||
|
"diagnostics": [
|
||||||
|
{
|
||||||
|
"code": "not_loaded_in_runtime",
|
||||||
|
"message": "plugin instance is enabled in config but not currently loaded in runtime"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"domain": "notify",
|
||||||
|
"enabled": true,
|
||||||
|
"id": "builtin:webhook:notify:primary",
|
||||||
|
"plugin_id": "builtin:webhook",
|
||||||
|
"service": "webhook",
|
||||||
|
"source": "config",
|
||||||
|
"status": "offline",
|
||||||
|
"subsystem": "notify_webhook"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user