Files
Portabase.Agent/src/tests/services/api_models_tests.rs
T
charles-gauthereau ca294e968c fix
2026-08-13 22:02:17 +02:00

246 lines
8.2 KiB
Rust

use serde_json::json;
use crate::services::api::models::agent::backup::{BackupResponse, BackupUploadResponse};
use crate::services::api::models::agent::restore::ResultRestoreResponse;
use crate::services::api::models::agent::status::{DatabaseStatus, PingResult};
#[test]
fn backup_response_deserializes_nested_backup_id() {
let response: BackupResponse = serde_json::from_value(json!({
"message": "created",
"backup": {
"id": "backup-123"
}
}))
.unwrap();
assert_eq!(response.message, "created");
assert_eq!(response.backup.id, "backup-123");
}
#[test]
fn backup_upload_response_deserializes_storage_payload() {
let response: BackupUploadResponse = serde_json::from_value(json!({
"message": "uploaded",
"backupStorage": {
"id": "storage-456"
}
}))
.unwrap();
assert_eq!(response.message, "uploaded");
assert_eq!(response.backup_storage.id, "storage-456");
}
#[test]
fn restore_response_deserializes_status() {
let response: ResultRestoreResponse = serde_json::from_value(json!({
"message": "ok",
"status": true
}))
.unwrap();
assert_eq!(response.message, "ok");
assert!(response.status);
}
#[test]
fn ping_result_deserializes_and_normalizes_storage_config_keys() {
let payload = json!({
"agent": {
"id": "agent-1",
"lastContact": "2026-03-22T10:00:00Z"
},
"databases": [{
"dbms": "postgres",
"generatedId": "db-1",
"storages": [{
"id": "storage-1",
"provider": "s3",
"config": {
"bucketName": "agent-backups",
"nestedConfig": {
"regionName": "eu-west-3"
},
"allowedRegions": [
{ "regionCode": "eu-west-3" }
]
}
}],
"encrypt": true,
"data": {
"backup": {
"action": true,
"cron": "*/5 * * * *"
},
"restore": {
"action": false,
"file": null,
"metaFile": null
}
}
}]
});
let result: PingResult = serde_json::from_value(payload).unwrap();
let storage = &result.databases[0].storages[0];
assert_eq!(result.agent.id, "agent-1");
assert_eq!(result.agent.last_contact, "2026-03-22T10:00:00Z");
assert_eq!(result.databases[0].generated_id, "db-1");
assert_eq!(storage.provider, "s3");
assert_eq!(
storage.config["bucket_name"].as_str(),
Some("agent-backups")
);
assert_eq!(
storage.config["nested_config"]["region_name"].as_str(),
Some("eu-west-3")
);
assert_eq!(
storage.config["allowed_regions"][0]["region_code"].as_str(),
Some("eu-west-3")
);
assert_eq!(
result.databases[0].data.backup.cron.as_deref(),
Some("*/5 * * * *")
);
assert!(result.databases[0].data.backup.action);
assert!(!result.databases[0].data.restore.action);
assert!(result.databases[0].data.restore.file.is_none());
assert!(result.databases[0].data.restore.meta_file.is_none());
}
#[test]
fn database_status_legacy_plaintext_storages() {
let status: DatabaseStatus = serde_json::from_value(json!({
"dbms": "postgres",
"generatedId": "gen-1",
"storages": [ { "id": "s1", "config": { "bucket": "b" }, "provider": "s3" } ],
"encrypt": true,
"data": {
"backup": { "action": false, "cron": null },
"restore": { "action": false, "file": null, "metaFile": null, "size": null }
}
})).unwrap();
assert_eq!(status.storages.len(), 1);
assert_eq!(status.storages_encrypted, None);
assert!(status.storages_ciphertext.is_none());
}
#[test]
fn database_status_encrypted_envelope() {
let status: DatabaseStatus = serde_json::from_value(json!({
"dbms": "postgres",
"generatedId": "gen-1",
"storages": [],
"storages_encrypted": true,
"storages_ciphertext": "AQIDBA==",
"encrypt": true,
"data": {
"backup": { "action": true, "cron": null },
"restore": { "action": false, "file": null, "metaFile": null, "size": null }
}
})).unwrap();
assert!(status.storages.is_empty());
assert_eq!(status.storages_encrypted, Some(true));
assert_eq!(status.storages_ciphertext.as_deref(), Some("AQIDBA=="));
}
#[test]
fn database_status_defaults_config_fields_absent() {
let json = r#"{
"dbms": "postgresql",
"generatedId": "16678159-ff7e-4c97-8c83-0adeff214681",
"encrypt": false,
"data": { "backup": { "action": false, "cron": null },
"restore": { "action": false, "file": null, "metaFile": null, "size": null } }
}"#;
let status: crate::services::api::models::agent::status::DatabaseStatus =
serde_json::from_str(json).unwrap();
assert_eq!(status.config_encrypted, None);
assert!(status.config_ciphertext.is_none());
assert!(status.resolved_config.is_none());
}
#[test]
fn resolve_dashboard_config_decrypts_full_entry() {
use crate::services::status::resolve_dashboard_config;
use base64::{engine::general_purpose, Engine};
// 32-byte master key, base64 STANDARD (matches decrypt_json_gcm).
let master_key_b64 = general_purpose::STANDARD.encode([7u8; 32]);
// Full agent-entry shape the dashboard encrypts.
let entry = r#"{
"name": "Dashboard PG",
"type": "postgresql",
"database": "app",
"username": "postgres",
"password": "s3cret",
"port": 5432,
"host": "10.0.0.10",
"generated_id": "16678159-ff7e-4c97-8c83-0adeff214681"
}"#;
let ciphertext = encrypt_json_gcm(entry.as_bytes(), &master_key_b64);
let mut status: crate::services::api::models::agent::status::DatabaseStatus =
serde_json::from_str(
r#"{
"dbms": "postgresql",
"generatedId": "16678159-ff7e-4c97-8c83-0adeff214681",
"encrypt": false,
"config_encrypted": true,
"config_ciphertext": "PLACEHOLDER",
"data": { "backup": { "action": false, "cron": null },
"restore": { "action": false, "file": null, "metaFile": null, "size": null } }
}"#,
)
.unwrap();
status.config_ciphertext = Some(ciphertext);
resolve_dashboard_config(&mut status, &master_key_b64).unwrap();
let cfg = status.resolved_config.expect("resolved");
assert_eq!(cfg.name, "Dashboard PG");
assert_eq!(cfg.password, "s3cret");
assert_eq!(cfg.host, "10.0.0.10");
assert_eq!(cfg.db_type.as_str(), "postgresql");
}
#[test]
fn resolve_dashboard_config_noop_when_not_encrypted() {
use crate::services::status::resolve_dashboard_config;
let mut status: crate::services::api::models::agent::status::DatabaseStatus =
serde_json::from_str(
r#"{
"dbms": "postgresql",
"generatedId": "16678159-ff7e-4c97-8c83-0adeff214681",
"encrypt": false,
"data": { "backup": { "action": false, "cron": null },
"restore": { "action": false, "file": null, "metaFile": null, "size": null } }
}"#,
)
.unwrap();
resolve_dashboard_config(&mut status, "unused").unwrap();
assert!(status.resolved_config.is_none());
}
fn encrypt_json_gcm(plaintext: &[u8], master_key_b64: &str) -> String {
use aes_gcm::aead::{Aead, KeyInit};
use aes_gcm::{Aes256Gcm, Key, Nonce};
use base64::{engine::general_purpose, Engine};
let key_bytes = general_purpose::STANDARD.decode(master_key_b64).unwrap();
let key = Key::<Aes256Gcm>::try_from(key_bytes.as_slice()).unwrap();
let cipher = Aes256Gcm::new(&key);
let nonce_bytes = [0u8; 12];
let nonce = Nonce::try_from(&nonce_bytes[..]).unwrap();
let ct = cipher.encrypt(&nonce, plaintext).unwrap();
let mut data = nonce_bytes.to_vec();
data.extend_from_slice(&ct);
general_purpose::STANDARD.encode(data)
}