Files
attune/crates/common/tests/key_repository_tests.rs
David Culbreth 87d830f952
Some checks failed
CI / Rustfmt (push) Successful in 23s
CI / Cargo Audit & Deny (push) Successful in 30s
CI / Web Blocking Checks (push) Successful in 48s
CI / Security Blocking Checks (push) Successful in 8s
CI / Clippy (push) Failing after 1m55s
CI / Web Advisory Checks (push) Successful in 35s
CI / Security Advisory Checks (push) Successful in 37s
CI / Tests (push) Successful in 8m5s
[wip] cli capability parity
2026-03-06 16:58:50 -06:00

921 lines
28 KiB
Rust

//! Integration tests for Key repository
//!
//! These tests verify CRUD operations, owner validation, encryption handling,
//! and constraints for the Key repository.
mod helpers;
use attune_common::{
models::enums::OwnerType,
repositories::{
key::{CreateKeyInput, KeyRepository, UpdateKeyInput},
Create, Delete, FindById, List, Update,
},
Error,
};
use helpers::*;
// ============================================================================
// CREATE Tests - System Owner
// ============================================================================
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_create_key_system_owner() {
let pool = create_test_pool().await.unwrap();
let key = KeyFixture::new_system_unique("system_key", "test_value")
.create(&pool)
.await
.unwrap();
assert!(key.id > 0);
assert_eq!(key.owner_type, OwnerType::System);
assert_eq!(key.owner, Some("system".to_string()));
assert_eq!(key.owner_identity, None);
assert_eq!(key.owner_pack, None);
assert_eq!(key.owner_action, None);
assert_eq!(key.owner_sensor, None);
assert!(!key.encrypted);
assert_eq!(key.value, serde_json::json!("test_value"));
assert!(key.created.timestamp() > 0);
assert!(key.updated.timestamp() > 0);
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_create_key_system_encrypted() {
let pool = create_test_pool().await.unwrap();
let key = KeyFixture::new_system_unique("encrypted_key", "encrypted_value")
.with_encrypted(true)
.with_encryption_key_hash("sha256:abc123")
.create(&pool)
.await
.unwrap();
assert!(key.encrypted);
assert_eq!(key.encryption_key_hash, Some("sha256:abc123".to_string()));
}
// ============================================================================
// CREATE Tests - Identity Owner
// ============================================================================
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_create_key_identity_owner() {
let pool = create_test_pool().await.unwrap();
// Create an identity first
let identity = IdentityFixture::new_unique("testuser")
.create(&pool)
.await
.unwrap();
let key = KeyFixture::new_identity_unique(identity.id, "api_key", "secret_token")
.create(&pool)
.await
.unwrap();
assert_eq!(key.owner_type, OwnerType::Identity);
assert_eq!(key.owner, Some(identity.id.to_string()));
assert_eq!(key.owner_identity, Some(identity.id));
assert_eq!(key.owner_pack, None);
assert_eq!(key.value, serde_json::json!("secret_token"));
}
// ============================================================================
// CREATE Tests - Pack Owner
// ============================================================================
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_create_key_pack_owner() {
let pool = create_test_pool().await.unwrap();
let pack = PackFixture::new_unique("testpack")
.create(&pool)
.await
.unwrap();
let key = KeyFixture::new_pack_unique(pack.id, &pack.r#ref, "config_key", "config_value")
.create(&pool)
.await
.unwrap();
assert_eq!(key.owner_type, OwnerType::Pack);
assert_eq!(key.owner, Some(pack.id.to_string()));
assert_eq!(key.owner_pack, Some(pack.id));
assert_eq!(key.owner_pack_ref, Some(pack.r#ref.clone()));
assert_eq!(key.value, serde_json::json!("config_value"));
}
// ============================================================================
// CREATE Tests - Constraints
// ============================================================================
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_create_key_duplicate_ref_fails() {
let pool = create_test_pool().await.unwrap();
let key_ref = format!("duplicate_key_{}", unique_test_id());
// Create first key
let input = CreateKeyInput {
r#ref: key_ref.clone(),
owner_type: OwnerType::System,
owner: Some("system".to_string()),
owner_identity: None,
owner_pack: None,
owner_pack_ref: None,
owner_action: None,
owner_action_ref: None,
owner_sensor: None,
owner_sensor_ref: None,
name: key_ref.clone(),
encrypted: false,
encryption_key_hash: None,
value: serde_json::json!("value1"),
};
KeyRepository::create(&pool, input.clone()).await.unwrap();
// Try to create duplicate
let result = KeyRepository::create(&pool, input).await;
assert!(result.is_err());
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_create_key_system_with_owner_fields_fails() {
let pool = create_test_pool().await.unwrap();
// Create an identity
let identity = IdentityFixture::new_unique("testuser")
.create(&pool)
.await
.unwrap();
// Try to create system key with owner_identity set (should fail)
let input = CreateKeyInput {
r#ref: format!("invalid_key_{}", unique_test_id()),
owner_type: OwnerType::System,
owner: Some("system".to_string()),
owner_identity: Some(identity.id), // This should cause failure
owner_pack: None,
owner_pack_ref: None,
owner_action: None,
owner_action_ref: None,
owner_sensor: None,
owner_sensor_ref: None,
name: "invalid".to_string(),
encrypted: false,
encryption_key_hash: None,
value: serde_json::json!("value"),
};
let result = KeyRepository::create(&pool, input).await;
assert!(result.is_err());
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_create_key_identity_without_owner_id_fails() {
let pool = create_test_pool().await.unwrap();
// Try to create identity key without owner_identity set
let input = CreateKeyInput {
r#ref: format!("invalid_key_{}", unique_test_id()),
owner_type: OwnerType::Identity,
owner: None,
owner_identity: None, // Missing required field
owner_pack: None,
owner_pack_ref: None,
owner_action: None,
owner_action_ref: None,
owner_sensor: None,
owner_sensor_ref: None,
name: "invalid".to_string(),
encrypted: false,
encryption_key_hash: None,
value: serde_json::json!("value"),
};
let result = KeyRepository::create(&pool, input).await;
assert!(result.is_err());
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_create_key_multiple_owners_fails() {
let pool = create_test_pool().await.unwrap();
let identity = IdentityFixture::new_unique("testuser")
.create(&pool)
.await
.unwrap();
let pack = PackFixture::new_unique("testpack")
.create(&pool)
.await
.unwrap();
// Try to create key with both identity and pack owners (should fail)
let input = CreateKeyInput {
r#ref: format!("invalid_key_{}", unique_test_id()),
owner_type: OwnerType::Identity,
owner: None,
owner_identity: Some(identity.id),
owner_pack: Some(pack.id), // Can't have multiple owners
owner_pack_ref: None,
owner_action: None,
owner_action_ref: None,
owner_sensor: None,
owner_sensor_ref: None,
name: "invalid".to_string(),
encrypted: false,
encryption_key_hash: None,
value: serde_json::json!("value"),
};
let result = KeyRepository::create(&pool, input).await;
assert!(result.is_err());
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_create_key_invalid_ref_format_fails() {
let pool = create_test_pool().await.unwrap();
// Try uppercase ref (should fail CHECK constraint)
let input = CreateKeyInput {
r#ref: "UPPERCASE_KEY".to_string(),
owner_type: OwnerType::System,
owner: Some("system".to_string()),
owner_identity: None,
owner_pack: None,
owner_pack_ref: None,
owner_action: None,
owner_action_ref: None,
owner_sensor: None,
owner_sensor_ref: None,
name: "uppercase".to_string(),
encrypted: false,
encryption_key_hash: None,
value: serde_json::json!("value"),
};
let result = KeyRepository::create(&pool, input).await;
assert!(result.is_err());
}
// ============================================================================
// READ Tests
// ============================================================================
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_find_by_id_exists() {
let pool = create_test_pool().await.unwrap();
let key = KeyFixture::new_system_unique("find_key", "value")
.create(&pool)
.await
.unwrap();
let found = KeyRepository::find_by_id(&pool, key.id).await.unwrap();
assert!(found.is_some());
let found = found.unwrap();
assert_eq!(found.id, key.id);
assert_eq!(found.r#ref, key.r#ref);
assert_eq!(found.value, key.value);
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_find_by_id_not_exists() {
let pool = create_test_pool().await.unwrap();
let result = KeyRepository::find_by_id(&pool, 99999).await.unwrap();
assert!(result.is_none());
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_get_by_id_exists() {
let pool = create_test_pool().await.unwrap();
let key = KeyFixture::new_system_unique("get_key", "value")
.create(&pool)
.await
.unwrap();
let found = KeyRepository::get_by_id(&pool, key.id).await.unwrap();
assert_eq!(found.id, key.id);
assert_eq!(found.r#ref, key.r#ref);
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_get_by_id_not_exists_fails() {
let pool = create_test_pool().await.unwrap();
let result = KeyRepository::get_by_id(&pool, 99999).await;
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), Error::NotFound { .. }));
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_find_by_ref_exists() {
let pool = create_test_pool().await.unwrap();
let key = KeyFixture::new_system_unique("ref_key", "value")
.create(&pool)
.await
.unwrap();
let found = KeyRepository::find_by_ref(&pool, &key.r#ref).await.unwrap();
assert!(found.is_some());
let found = found.unwrap();
assert_eq!(found.id, key.id);
assert_eq!(found.r#ref, key.r#ref);
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_find_by_ref_not_exists() {
let pool = create_test_pool().await.unwrap();
let result = KeyRepository::find_by_ref(&pool, "nonexistent_key")
.await
.unwrap();
assert!(result.is_none());
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_list_all_keys() {
let pool = create_test_pool().await.unwrap();
// Create multiple keys
let key1 = KeyFixture::new_system_unique("list_key_a", "value1")
.create(&pool)
.await
.unwrap();
let key2 = KeyFixture::new_system_unique("list_key_b", "value2")
.create(&pool)
.await
.unwrap();
let keys = KeyRepository::list(&pool).await.unwrap();
// Should have at least our 2 keys (may have more from parallel tests)
assert!(keys.len() >= 2);
// Verify our keys are in the list
assert!(keys.iter().any(|k| k.id == key1.id));
assert!(keys.iter().any(|k| k.id == key2.id));
}
// ============================================================================
// UPDATE Tests
// ============================================================================
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_update_value() {
let pool = create_test_pool().await.unwrap();
let key = KeyFixture::new_system_unique("update_key", "original_value")
.create(&pool)
.await
.unwrap();
let original_updated = key.updated;
// Small delay to ensure updated timestamp changes
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
let input = UpdateKeyInput {
value: Some(serde_json::json!("new_value")),
..Default::default()
};
let updated = KeyRepository::update(&pool, key.id, input).await.unwrap();
assert_eq!(updated.value, serde_json::json!("new_value"));
assert!(updated.updated > original_updated);
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_update_name() {
let pool = create_test_pool().await.unwrap();
let key = KeyFixture::new_system_unique("update_name_key", "value")
.create(&pool)
.await
.unwrap();
// Use a unique name to avoid conflicts with parallel tests
let new_name = format!("new_name_{}", unique_test_id());
let input = UpdateKeyInput {
name: Some(new_name.clone()),
..Default::default()
};
let updated = KeyRepository::update(&pool, key.id, input).await.unwrap();
assert_eq!(updated.name, new_name);
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_update_encrypted_status() {
let pool = create_test_pool().await.unwrap();
let key = KeyFixture::new_system_unique("encrypt_key", "plain_value")
.create(&pool)
.await
.unwrap();
assert!(!key.encrypted);
let input = UpdateKeyInput {
encrypted: Some(true),
encryption_key_hash: Some("sha256:xyz789".to_string()),
value: Some(serde_json::json!("encrypted_value")),
..Default::default()
};
let updated = KeyRepository::update(&pool, key.id, input).await.unwrap();
assert!(updated.encrypted);
assert_eq!(
updated.encryption_key_hash,
Some("sha256:xyz789".to_string())
);
assert_eq!(updated.value, serde_json::json!("encrypted_value"));
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_update_multiple_fields() {
let pool = create_test_pool().await.unwrap();
let key = KeyFixture::new_system_unique("multi_update_key", "value")
.create(&pool)
.await
.unwrap();
// Use a unique name to avoid conflicts with parallel tests
let new_name = format!("updated_name_{}", unique_test_id());
let input = UpdateKeyInput {
name: Some(new_name.clone()),
value: Some(serde_json::json!("updated_value")),
encrypted: Some(true),
encryption_key_hash: Some("hash123".to_string()),
};
let updated = KeyRepository::update(&pool, key.id, input).await.unwrap();
assert_eq!(updated.name, new_name);
assert_eq!(updated.value, serde_json::json!("updated_value"));
assert!(updated.encrypted);
assert_eq!(updated.encryption_key_hash, Some("hash123".to_string()));
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_update_no_changes() {
let pool = create_test_pool().await.unwrap();
let key = KeyFixture::new_system_unique("nochange_key", "value")
.create(&pool)
.await
.unwrap();
let original_updated = key.updated;
let input = UpdateKeyInput::default();
let updated = KeyRepository::update(&pool, key.id, input).await.unwrap();
assert_eq!(updated.id, key.id);
assert_eq!(updated.name, key.name);
assert_eq!(updated.value, key.value);
// Updated timestamp should not change when no fields are updated
assert_eq!(updated.updated, original_updated);
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_update_nonexistent_key_fails() {
let pool = create_test_pool().await.unwrap();
let input = UpdateKeyInput {
value: Some(serde_json::json!("new_value")),
..Default::default()
};
let result = KeyRepository::update(&pool, 99999, input).await;
assert!(result.is_err());
}
// ============================================================================
// DELETE Tests
// ============================================================================
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_delete_existing_key() {
let pool = create_test_pool().await.unwrap();
let key = KeyFixture::new_system_unique("delete_key", "value")
.create(&pool)
.await
.unwrap();
let deleted = KeyRepository::delete(&pool, key.id).await.unwrap();
assert!(deleted);
// Verify key is gone
let result = KeyRepository::find_by_id(&pool, key.id).await.unwrap();
assert!(result.is_none());
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_delete_nonexistent_key() {
let pool = create_test_pool().await.unwrap();
let deleted = KeyRepository::delete(&pool, 99999).await.unwrap();
assert!(!deleted);
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_delete_key_when_identity_deleted() {
let pool = create_test_pool().await.unwrap();
let identity = IdentityFixture::new_unique("deleteuser")
.create(&pool)
.await
.unwrap();
let key = KeyFixture::new_identity_unique(identity.id, "user_key", "value")
.create(&pool)
.await
.unwrap();
// Delete the identity - this will fail because key references it
use attune_common::repositories::{identity::IdentityRepository, Delete as _};
let delete_result = IdentityRepository::delete(&pool, identity.id).await;
// Should fail due to foreign key constraint (no CASCADE on key table)
assert!(delete_result.is_err());
// Key should still exist
let result = KeyRepository::find_by_id(&pool, key.id).await.unwrap();
assert!(result.is_some());
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_delete_key_when_pack_deleted() {
let pool = create_test_pool().await.unwrap();
let pack = PackFixture::new_unique("deletepack")
.create(&pool)
.await
.unwrap();
let key = KeyFixture::new_pack_unique(pack.id, &pack.r#ref, "pack_key", "value")
.create(&pool)
.await
.unwrap();
// Delete the pack - this will fail because key references it
use attune_common::repositories::{pack::PackRepository, Delete as _};
let delete_result = PackRepository::delete(&pool, pack.id).await;
// Should fail due to foreign key constraint (no CASCADE on key table)
assert!(delete_result.is_err());
// Key should still exist
let result = KeyRepository::find_by_id(&pool, key.id).await.unwrap();
assert!(result.is_some());
}
// ============================================================================
// Specialized Query Tests
// ============================================================================
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_find_by_owner_type_system() {
let pool = create_test_pool().await.unwrap();
let _key1 = KeyFixture::new_system_unique("sys_key1", "value1")
.create(&pool)
.await
.unwrap();
let _key2 = KeyFixture::new_system_unique("sys_key2", "value2")
.create(&pool)
.await
.unwrap();
let keys = KeyRepository::find_by_owner_type(&pool, OwnerType::System)
.await
.unwrap();
// Should have at least our 2 system keys
assert!(keys.len() >= 2);
assert!(keys.iter().all(|k| k.owner_type == OwnerType::System));
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_find_by_owner_type_identity() {
let pool = create_test_pool().await.unwrap();
let identity1 = IdentityFixture::new_unique("user1")
.create(&pool)
.await
.unwrap();
let identity2 = IdentityFixture::new_unique("user2")
.create(&pool)
.await
.unwrap();
let key1 = KeyFixture::new_identity_unique(identity1.id, "key1", "value1")
.create(&pool)
.await
.unwrap();
let key2 = KeyFixture::new_identity_unique(identity2.id, "key2", "value2")
.create(&pool)
.await
.unwrap();
let keys = KeyRepository::find_by_owner_type(&pool, OwnerType::Identity)
.await
.unwrap();
// Should contain our identity keys
assert!(keys.iter().any(|k| k.id == key1.id));
assert!(keys.iter().any(|k| k.id == key2.id));
assert!(keys.iter().all(|k| k.owner_type == OwnerType::Identity));
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_find_by_owner_type_pack() {
let pool = create_test_pool().await.unwrap();
let pack = PackFixture::new_unique("ownerpack")
.create(&pool)
.await
.unwrap();
let key1 = KeyFixture::new_pack_unique(pack.id, &pack.r#ref, "pack_key1", "value1")
.create(&pool)
.await
.unwrap();
let key2 = KeyFixture::new_pack_unique(pack.id, &pack.r#ref, "pack_key2", "value2")
.create(&pool)
.await
.unwrap();
let keys = KeyRepository::find_by_owner_type(&pool, OwnerType::Pack)
.await
.unwrap();
// Should contain our pack keys
assert!(keys.iter().any(|k| k.id == key1.id));
assert!(keys.iter().any(|k| k.id == key2.id));
assert!(keys.iter().all(|k| k.owner_type == OwnerType::Pack));
}
// ============================================================================
// Timestamp Tests
// ============================================================================
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_created_timestamp_set_automatically() {
let pool = create_test_pool().await.unwrap();
let before = chrono::Utc::now();
let key = KeyFixture::new_system_unique("timestamp_key", "value")
.create(&pool)
.await
.unwrap();
let after = chrono::Utc::now();
assert!(key.created >= before);
assert!(key.created <= after);
assert_eq!(key.created, key.updated); // Should be equal on creation
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_updated_timestamp_changes_on_update() {
let pool = create_test_pool().await.unwrap();
let key = KeyFixture::new_system_unique("update_time_key", "value")
.create(&pool)
.await
.unwrap();
let original_updated = key.updated;
// Small delay to ensure timestamp changes
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
let input = UpdateKeyInput {
value: Some(serde_json::json!("new_value")),
..Default::default()
};
let updated = KeyRepository::update(&pool, key.id, input).await.unwrap();
assert!(updated.updated > original_updated);
assert_eq!(updated.created, key.created); // Created should not change
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_updated_timestamp_unchanged_on_read() {
let pool = create_test_pool().await.unwrap();
let key = KeyFixture::new_system_unique("read_time_key", "value")
.create(&pool)
.await
.unwrap();
let original_updated = key.updated;
// Small delay
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
// Read the key
let found = KeyRepository::find_by_id(&pool, key.id)
.await
.unwrap()
.unwrap();
assert_eq!(found.updated, original_updated); // Should not change
}
// ============================================================================
// Encryption Tests
// ============================================================================
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_key_encrypted_flag() {
let pool = create_test_pool().await.unwrap();
let plain_key = KeyFixture::new_system_unique("plain_key", "plain_value")
.create(&pool)
.await
.unwrap();
let encrypted_key = KeyFixture::new_system_unique("encrypted_key", "cipher_text")
.with_encrypted(true)
.with_encryption_key_hash("sha256:abc")
.create(&pool)
.await
.unwrap();
assert!(!plain_key.encrypted);
assert_eq!(plain_key.encryption_key_hash, None);
assert!(encrypted_key.encrypted);
assert_eq!(
encrypted_key.encryption_key_hash,
Some("sha256:abc".to_string())
);
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_update_encryption_status() {
let pool = create_test_pool().await.unwrap();
// Create plain key
let key = KeyFixture::new_system_unique("to_encrypt", "plain_value")
.create(&pool)
.await
.unwrap();
assert!(!key.encrypted);
// Encrypt it
let input = UpdateKeyInput {
encrypted: Some(true),
encryption_key_hash: Some("sha256:newkey".to_string()),
value: Some(serde_json::json!("encrypted_value")),
..Default::default()
};
let encrypted = KeyRepository::update(&pool, key.id, input).await.unwrap();
assert!(encrypted.encrypted);
assert_eq!(
encrypted.encryption_key_hash,
Some("sha256:newkey".to_string())
);
assert_eq!(encrypted.value, serde_json::json!("encrypted_value"));
// Decrypt it
let input = UpdateKeyInput {
encrypted: Some(false),
encryption_key_hash: None,
value: Some(serde_json::json!("plain_value")),
..Default::default()
};
let decrypted = KeyRepository::update(&pool, key.id, input).await.unwrap();
assert!(!decrypted.encrypted);
assert_eq!(decrypted.value, serde_json::json!("plain_value"));
}
// ============================================================================
// Owner Validation Tests
// ============================================================================
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_multiple_keys_same_pack_different_names() {
let pool = create_test_pool().await.unwrap();
let pack = PackFixture::new_unique("multikey_pack")
.create(&pool)
.await
.unwrap();
let key1 = KeyFixture::new_pack_unique(pack.id, &pack.r#ref, "key1", "value1")
.create(&pool)
.await
.unwrap();
let key2 = KeyFixture::new_pack_unique(pack.id, &pack.r#ref, "key2", "value2")
.create(&pool)
.await
.unwrap();
assert_ne!(key1.id, key2.id);
assert_eq!(key1.owner_pack, Some(pack.id));
assert_eq!(key2.owner_pack, Some(pack.id));
assert_ne!(key1.name, key2.name);
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_same_key_name_different_owners() {
let pool = create_test_pool().await.unwrap();
let pack1 = PackFixture::new_unique("pack1")
.create(&pool)
.await
.unwrap();
let pack2 = PackFixture::new_unique("pack2")
.create(&pool)
.await
.unwrap();
// Same base key name, different owners - should be allowed
// Use same base name so fixture creates keys with same logical name
let base_name = format!("api_key_{}", unique_test_id());
let key1 = KeyFixture::new_pack(pack1.id, &pack1.r#ref, &base_name, "value1")
.create(&pool)
.await
.unwrap();
let key2 = KeyFixture::new_pack(pack2.id, &pack2.r#ref, &base_name, "value2")
.create(&pool)
.await
.unwrap();
assert_ne!(key1.id, key2.id);
assert_eq!(key1.name, key2.name); // Same name
assert_ne!(key1.owner_pack, key2.owner_pack); // Different owners
}