//! Integration tests for Permission repositories (PermissionSet and PermissionAssignment) use attune_common::{ models::identity::*, repositories::{ identity::{ CreateIdentityInput, CreatePermissionAssignmentInput, CreatePermissionSetInput, IdentityRepository, PermissionAssignmentRepository, PermissionSetRepository, UpdatePermissionSetInput, }, pack::{CreatePackInput, PackRepository}, Create, Delete, FindById, List, Update, }, }; use serde_json::json; use sqlx::PgPool; use std::sync::atomic::{AtomicU64, Ordering}; mod helpers; use helpers::create_test_pool; static PERMISSION_COUNTER: AtomicU64 = AtomicU64::new(0); /// Test fixture for creating unique permission sets struct PermissionSetFixture { pool: PgPool, id_suffix: String, internal_counter: std::sync::Arc, } impl PermissionSetFixture { fn new(pool: PgPool) -> Self { let counter = PERMISSION_COUNTER.fetch_add(1, Ordering::SeqCst); let timestamp = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_nanos(); // Hash the thread ID to get a unique number let thread_id = std::thread::current().id(); let thread_hash = format!("{:?}", thread_id) .chars() .filter(|c| c.is_numeric()) .collect::() .parse::() .unwrap_or(0); // Add random component for absolute uniqueness use std::collections::hash_map::RandomState; use std::hash::{BuildHasher, Hash, Hasher}; let random_state = RandomState::new(); let mut hasher = random_state.build_hasher(); timestamp.hash(&mut hasher); counter.hash(&mut hasher); thread_hash.hash(&mut hasher); let random_hash = hasher.finish(); // Create a unique lowercase alphanumeric suffix combining all sources of uniqueness let id_suffix = format!("{:x}", random_hash); Self { pool, id_suffix, internal_counter: std::sync::Arc::new(std::sync::atomic::AtomicU64::new(0)), } } fn unique_ref(&self, base: &str) -> String { let seq = self.internal_counter.fetch_add(1, Ordering::SeqCst); format!("test.{}_{}_{}", base, self.id_suffix, seq) } async fn create_pack(&self) -> i64 { let seq = self.internal_counter.fetch_add(1, Ordering::SeqCst); let pack_ref = format!("testpack_{}_{}", self.id_suffix, seq); let input = CreatePackInput { r#ref: pack_ref, version: "1.0.0".to_string(), label: "Test Pack".to_string(), description: Some("Test pack for permissions".to_string()), tags: vec![], conf_schema: json!({}), config: json!({}), meta: json!({}), runtime_deps: vec![], dependencies: vec![], is_standard: false, installers: json!({}), }; PackRepository::create(&self.pool, input) .await .expect("Failed to create pack") .id } async fn create_identity(&self) -> i64 { let seq = self.internal_counter.fetch_add(1, Ordering::SeqCst); let login = format!("testuser_{}_{}", self.id_suffix, seq); let input = CreateIdentityInput { login, display_name: Some("Test User".to_string()), attributes: json!({}), password_hash: None, }; IdentityRepository::create(&self.pool, input) .await .expect("Failed to create identity") .id } async fn create_permission_set( &self, ref_name: &str, pack_id: Option, pack_ref: Option, grants: serde_json::Value, ) -> PermissionSet { let input = CreatePermissionSetInput { r#ref: ref_name.to_string(), pack: pack_id, pack_ref, label: Some("Test Permission Set".to_string()), description: Some("Test description".to_string()), grants, }; PermissionSetRepository::create(&self.pool, input) .await .expect("Failed to create permission set") } async fn create_default(&self) -> PermissionSet { let ref_name = self.unique_ref("permset"); self.create_permission_set(&ref_name, None, None, json!([])) .await } async fn create_with_pack(&self) -> (i64, PermissionSet) { let pack_id = self.create_pack().await; let ref_name = self.unique_ref("permset"); // Get the pack_ref from the last created pack - extract from pack let pack = PackRepository::find_by_id(&self.pool, pack_id) .await .expect("Failed to find pack") .expect("Pack not found"); let pack_ref = pack.r#ref; let permset = self .create_permission_set(&ref_name, Some(pack_id), Some(pack_ref), json!([])) .await; (pack_id, permset) } async fn create_with_grants(&self, grants: serde_json::Value) -> PermissionSet { let ref_name = self.unique_ref("permset"); self.create_permission_set(&ref_name, None, None, grants) .await } async fn create_assignment(&self, identity_id: i64, permset_id: i64) -> PermissionAssignment { let input = CreatePermissionAssignmentInput { identity: identity_id, permset: permset_id, }; PermissionAssignmentRepository::create(&self.pool, input) .await .expect("Failed to create permission assignment") } } // ============================================================================ // PermissionSet Repository Tests // ============================================================================ #[tokio::test] #[ignore = "integration test — requires database"] async fn test_create_permission_set_minimal() { let pool = create_test_pool().await.expect("Failed to create pool"); let fixture = PermissionSetFixture::new(pool.clone()); let ref_name = fixture.unique_ref("minimal"); let input = CreatePermissionSetInput { r#ref: ref_name.clone(), pack: None, pack_ref: None, label: Some("Minimal Permission Set".to_string()), description: None, grants: json!([]), }; let permset = PermissionSetRepository::create(&pool, input) .await .expect("Failed to create permission set"); assert!(permset.id > 0); assert_eq!(permset.r#ref, ref_name); assert_eq!(permset.label, Some("Minimal Permission Set".to_string())); assert!(permset.description.is_none()); assert_eq!(permset.grants, json!([])); assert!(permset.pack.is_none()); assert!(permset.pack_ref.is_none()); } #[tokio::test] #[ignore = "integration test — requires database"] async fn test_create_permission_set_with_pack() { let pool = create_test_pool().await.expect("Failed to create pool"); let fixture = PermissionSetFixture::new(pool.clone()); let pack_id = fixture.create_pack().await; let ref_name = fixture.unique_ref("with_pack"); let pack_ref = format!("testpack_{}", fixture.id_suffix); let input = CreatePermissionSetInput { r#ref: ref_name.clone(), pack: Some(pack_id), pack_ref: Some(pack_ref.clone()), label: Some("Pack Permission Set".to_string()), description: Some("Permission set from pack".to_string()), grants: json!([ {"resource": "actions", "permission": "read"}, {"resource": "actions", "permission": "execute"} ]), }; let permset = PermissionSetRepository::create(&pool, input) .await .expect("Failed to create permission set"); assert_eq!(permset.pack, Some(pack_id)); assert_eq!(permset.pack_ref, Some(pack_ref)); assert!(permset.grants.is_array()); } #[tokio::test] #[ignore = "integration test — requires database"] async fn test_create_permission_set_with_complex_grants() { let pool = create_test_pool().await.expect("Failed to create pool"); let fixture = PermissionSetFixture::new(pool.clone()); let _ref_name = fixture.unique_ref("complex"); let grants = json!([ { "resource": "executions", "permissions": ["read", "write", "delete"], "filters": {"pack": "core"} }, { "resource": "actions", "permissions": ["execute"], "filters": {"tags": ["safe"]} } ]); let permset = fixture.create_with_grants(grants.clone()).await; assert_eq!(permset.grants, grants); } #[tokio::test] #[ignore = "integration test — requires database"] async fn test_permission_set_ref_format_validation() { let pool = create_test_pool().await.expect("Failed to create pool"); let fixture = PermissionSetFixture::new(pool.clone()); // Valid format: pack.name let valid_ref = fixture.unique_ref("valid"); let input = CreatePermissionSetInput { r#ref: valid_ref, pack: None, pack_ref: None, label: None, description: None, grants: json!([]), }; let result = PermissionSetRepository::create(&pool, input).await; assert!(result.is_ok()); // Invalid format: no dot let invalid_ref = format!("nodot_{}", fixture.id_suffix); let input = CreatePermissionSetInput { r#ref: invalid_ref, pack: None, pack_ref: None, label: None, description: None, grants: json!([]), }; let result = PermissionSetRepository::create(&pool, input).await; assert!(result.is_err()); } #[tokio::test] #[ignore = "integration test — requires database"] async fn test_permission_set_ref_lowercase() { let pool = create_test_pool().await.expect("Failed to create pool"); let fixture = PermissionSetFixture::new(pool.clone()); // Create with uppercase - should fail due to CHECK constraint let upper_ref = format!("Test.UPPERCASE_{}", fixture.id_suffix); let input = CreatePermissionSetInput { r#ref: upper_ref, pack: None, pack_ref: None, label: None, description: None, grants: json!([]), }; let result = PermissionSetRepository::create(&pool, input).await; assert!(result.is_err()); } #[tokio::test] #[ignore = "integration test — requires database"] async fn test_permission_set_duplicate_ref() { let pool = create_test_pool().await.expect("Failed to create pool"); let fixture = PermissionSetFixture::new(pool.clone()); let ref_name = fixture.unique_ref("duplicate"); let input = CreatePermissionSetInput { r#ref: ref_name.clone(), pack: None, pack_ref: None, label: None, description: None, grants: json!([]), }; // First create should succeed let result1 = PermissionSetRepository::create(&pool, input.clone()).await; assert!(result1.is_ok()); // Second create with same ref should fail let result2 = PermissionSetRepository::create(&pool, input).await; assert!(result2.is_err()); } #[tokio::test] #[ignore = "integration test — requires database"] async fn test_find_permission_set_by_id() { let pool = create_test_pool().await.expect("Failed to create pool"); let fixture = PermissionSetFixture::new(pool.clone()); let created = fixture.create_default().await; let found = PermissionSetRepository::find_by_id(&pool, created.id) .await .expect("Failed to find permission set") .expect("Permission set not found"); assert_eq!(found.id, created.id); assert_eq!(found.r#ref, created.r#ref); assert_eq!(found.label, created.label); } #[tokio::test] #[ignore = "integration test — requires database"] async fn test_find_permission_set_by_id_not_found() { let pool = create_test_pool().await.expect("Failed to create pool"); let result = PermissionSetRepository::find_by_id(&pool, 999_999_999) .await .expect("Query should succeed"); assert!(result.is_none()); } #[tokio::test] #[ignore = "integration test — requires database"] async fn test_list_permission_sets() { let pool = create_test_pool().await.expect("Failed to create pool"); let fixture = PermissionSetFixture::new(pool.clone()); let p1 = fixture.create_default().await; let p2 = fixture.create_default().await; let p3 = fixture.create_default().await; let permsets = PermissionSetRepository::list(&pool) .await .expect("Failed to list permission sets"); let ids: Vec = permsets.iter().map(|p| p.id).collect(); assert!(ids.contains(&p1.id)); assert!(ids.contains(&p2.id)); assert!(ids.contains(&p3.id)); } #[tokio::test] #[ignore = "integration test — requires database"] async fn test_update_permission_set_label() { let pool = create_test_pool().await.expect("Failed to create pool"); let fixture = PermissionSetFixture::new(pool.clone()); let created = fixture.create_default().await; let update_input = UpdatePermissionSetInput { label: Some("Updated Label".to_string()), description: None, grants: None, }; let updated = PermissionSetRepository::update(&pool, created.id, update_input) .await .expect("Failed to update permission set"); assert_eq!(updated.label, Some("Updated Label".to_string())); assert_eq!(updated.description, created.description); } #[tokio::test] #[ignore = "integration test — requires database"] async fn test_update_permission_set_grants() { let pool = create_test_pool().await.expect("Failed to create pool"); let fixture = PermissionSetFixture::new(pool.clone()); let created = fixture.create_with_grants(json!([])).await; let new_grants = json!([ {"resource": "packs", "permission": "read"}, {"resource": "actions", "permission": "execute"} ]); let update_input = UpdatePermissionSetInput { label: None, description: None, grants: Some(new_grants.clone()), }; let updated = PermissionSetRepository::update(&pool, created.id, update_input) .await .expect("Failed to update permission set"); assert_eq!(updated.grants, new_grants); } #[tokio::test] #[ignore = "integration test — requires database"] async fn test_update_permission_set_all_fields() { let pool = create_test_pool().await.expect("Failed to create pool"); let fixture = PermissionSetFixture::new(pool.clone()); let created = fixture.create_default().await; let new_grants = json!([{"resource": "all", "permission": "admin"}]); let update_input = UpdatePermissionSetInput { label: Some("New Label".to_string()), description: Some("New Description".to_string()), grants: Some(new_grants.clone()), }; let updated = PermissionSetRepository::update(&pool, created.id, update_input) .await .expect("Failed to update permission set"); assert_eq!(updated.label, Some("New Label".to_string())); assert_eq!(updated.description, Some("New Description".to_string())); assert_eq!(updated.grants, new_grants); } #[tokio::test] #[ignore = "integration test — requires database"] async fn test_update_permission_set_no_changes() { let pool = create_test_pool().await.expect("Failed to create pool"); let fixture = PermissionSetFixture::new(pool.clone()); let created = fixture.create_default().await; let update_input = UpdatePermissionSetInput { label: None, description: None, grants: None, }; let updated = PermissionSetRepository::update(&pool, created.id, update_input) .await .expect("Failed to update permission set"); assert_eq!(updated.id, created.id); assert_eq!(updated.r#ref, created.r#ref); } #[tokio::test] #[ignore = "integration test — requires database"] async fn test_update_permission_set_timestamps() { let pool = create_test_pool().await.expect("Failed to create pool"); let fixture = PermissionSetFixture::new(pool.clone()); let created = fixture.create_default().await; let created_timestamp = created.created; let original_updated = created.updated; tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; let update_input = UpdatePermissionSetInput { label: Some("Updated".to_string()), description: None, grants: None, }; let updated = PermissionSetRepository::update(&pool, created.id, update_input) .await .expect("Failed to update permission set"); assert_eq!(updated.created, created_timestamp); assert!(updated.updated > original_updated); } #[tokio::test] #[ignore = "integration test — requires database"] async fn test_delete_permission_set() { let pool = create_test_pool().await.expect("Failed to create pool"); let fixture = PermissionSetFixture::new(pool.clone()); let created = fixture.create_default().await; let deleted = PermissionSetRepository::delete(&pool, created.id) .await .expect("Failed to delete permission set"); assert!(deleted); let found = PermissionSetRepository::find_by_id(&pool, created.id) .await .expect("Query should succeed"); assert!(found.is_none()); } #[tokio::test] #[ignore = "integration test — requires database"] async fn test_delete_permission_set_not_found() { let pool = create_test_pool().await.expect("Failed to create pool"); let deleted = PermissionSetRepository::delete(&pool, 999_999_999) .await .expect("Delete should succeed"); assert!(!deleted); } #[tokio::test] #[ignore = "integration test — requires database"] async fn test_permission_set_cascade_from_pack() { let pool = create_test_pool().await.expect("Failed to create pool"); let fixture = PermissionSetFixture::new(pool.clone()); let (pack_id, permset) = fixture.create_with_pack().await; // Delete pack - permission set should be cascade deleted let deleted = PackRepository::delete(&pool, pack_id) .await .expect("Failed to delete pack"); assert!(deleted); // Permission set should no longer exist let found = PermissionSetRepository::find_by_id(&pool, permset.id) .await .expect("Query should succeed"); assert!(found.is_none()); } #[tokio::test] #[ignore = "integration test — requires database"] async fn test_permission_set_timestamps_auto_set() { let pool = create_test_pool().await.expect("Failed to create pool"); let fixture = PermissionSetFixture::new(pool.clone()); let before = chrono::Utc::now(); let permset = fixture.create_default().await; let after = chrono::Utc::now(); assert!(permset.created >= before); assert!(permset.created <= after); assert!(permset.updated >= before); assert!(permset.updated <= after); } // ============================================================================ // PermissionAssignment Repository Tests // ============================================================================ #[tokio::test] #[ignore = "integration test — requires database"] async fn test_create_permission_assignment() { let pool = create_test_pool().await.expect("Failed to create pool"); let fixture = PermissionSetFixture::new(pool.clone()); let identity_id = fixture.create_identity().await; let permset = fixture.create_default().await; let assignment = fixture.create_assignment(identity_id, permset.id).await; assert!(assignment.id > 0); assert_eq!(assignment.identity, identity_id); assert_eq!(assignment.permset, permset.id); } #[tokio::test] #[ignore = "integration test — requires database"] async fn test_create_permission_assignment_duplicate() { let pool = create_test_pool().await.expect("Failed to create pool"); let fixture = PermissionSetFixture::new(pool.clone()); let identity_id = fixture.create_identity().await; let permset = fixture.create_default().await; // First assignment should succeed let result1 = fixture.create_assignment(identity_id, permset.id).await; assert!(result1.id > 0); // Second assignment with same identity+permset should fail (unique constraint) let input = CreatePermissionAssignmentInput { identity: identity_id, permset: permset.id, }; let result2 = PermissionAssignmentRepository::create(&pool, input).await; assert!(result2.is_err()); } #[tokio::test] #[ignore = "integration test — requires database"] async fn test_create_permission_assignment_invalid_identity() { let pool = create_test_pool().await.expect("Failed to create pool"); let fixture = PermissionSetFixture::new(pool.clone()); let permset = fixture.create_default().await; let input = CreatePermissionAssignmentInput { identity: 999_999_999, permset: permset.id, }; let result = PermissionAssignmentRepository::create(&pool, input).await; assert!(result.is_err()); // Foreign key violation } #[tokio::test] #[ignore = "integration test — requires database"] async fn test_create_permission_assignment_invalid_permset() { let pool = create_test_pool().await.expect("Failed to create pool"); let fixture = PermissionSetFixture::new(pool.clone()); let identity_id = fixture.create_identity().await; let input = CreatePermissionAssignmentInput { identity: identity_id, permset: 999_999_999, }; let result = PermissionAssignmentRepository::create(&pool, input).await; assert!(result.is_err()); // Foreign key violation } #[tokio::test] #[ignore = "integration test — requires database"] async fn test_find_permission_assignment_by_id() { let pool = create_test_pool().await.expect("Failed to create pool"); let fixture = PermissionSetFixture::new(pool.clone()); let identity_id = fixture.create_identity().await; let permset = fixture.create_default().await; let created = fixture.create_assignment(identity_id, permset.id).await; let found = PermissionAssignmentRepository::find_by_id(&pool, created.id) .await .expect("Failed to find assignment") .expect("Assignment not found"); assert_eq!(found.id, created.id); assert_eq!(found.identity, identity_id); assert_eq!(found.permset, permset.id); } #[tokio::test] #[ignore = "integration test — requires database"] async fn test_find_permission_assignment_by_id_not_found() { let pool = create_test_pool().await.expect("Failed to create pool"); let result = PermissionAssignmentRepository::find_by_id(&pool, 999_999_999) .await .expect("Query should succeed"); assert!(result.is_none()); } #[tokio::test] #[ignore = "integration test — requires database"] async fn test_list_permission_assignments() { let pool = create_test_pool().await.expect("Failed to create pool"); let fixture = PermissionSetFixture::new(pool.clone()); let identity_id = fixture.create_identity().await; let p1 = fixture.create_default().await; let p2 = fixture.create_default().await; let a1 = fixture.create_assignment(identity_id, p1.id).await; let a2 = fixture.create_assignment(identity_id, p2.id).await; let assignments = PermissionAssignmentRepository::list(&pool) .await .expect("Failed to list assignments"); let ids: Vec = assignments.iter().map(|a| a.id).collect(); assert!(ids.contains(&a1.id)); assert!(ids.contains(&a2.id)); } #[tokio::test] #[ignore = "integration test — requires database"] async fn test_find_assignments_by_identity() { let pool = create_test_pool().await.expect("Failed to create pool"); let fixture = PermissionSetFixture::new(pool.clone()); let identity1 = fixture.create_identity().await; let identity2 = fixture.create_identity().await; let p1 = fixture.create_default().await; let p2 = fixture.create_default().await; let a1 = fixture.create_assignment(identity1, p1.id).await; let a2 = fixture.create_assignment(identity1, p2.id).await; let _a3 = fixture.create_assignment(identity2, p1.id).await; let assignments = PermissionAssignmentRepository::find_by_identity(&pool, identity1) .await .expect("Failed to find assignments"); assert_eq!(assignments.len(), 2); let ids: Vec = assignments.iter().map(|a| a.id).collect(); assert!(ids.contains(&a1.id)); assert!(ids.contains(&a2.id)); } #[tokio::test] #[ignore = "integration test — requires database"] async fn test_find_assignments_by_identity_empty() { let pool = create_test_pool().await.expect("Failed to create pool"); let fixture = PermissionSetFixture::new(pool.clone()); let identity_id = fixture.create_identity().await; let assignments = PermissionAssignmentRepository::find_by_identity(&pool, identity_id) .await .expect("Failed to find assignments"); assert!(assignments.is_empty()); } #[tokio::test] #[ignore = "integration test — requires database"] async fn test_delete_permission_assignment() { let pool = create_test_pool().await.expect("Failed to create pool"); let fixture = PermissionSetFixture::new(pool.clone()); let identity_id = fixture.create_identity().await; let permset = fixture.create_default().await; let created = fixture.create_assignment(identity_id, permset.id).await; let deleted = PermissionAssignmentRepository::delete(&pool, created.id) .await .expect("Failed to delete assignment"); assert!(deleted); let found = PermissionAssignmentRepository::find_by_id(&pool, created.id) .await .expect("Query should succeed"); assert!(found.is_none()); } #[tokio::test] #[ignore = "integration test — requires database"] async fn test_delete_permission_assignment_not_found() { let pool = create_test_pool().await.expect("Failed to create pool"); let deleted = PermissionAssignmentRepository::delete(&pool, 999_999_999) .await .expect("Delete should succeed"); assert!(!deleted); } #[tokio::test] #[ignore = "integration test — requires database"] async fn test_permission_assignment_cascade_from_identity() { let pool = create_test_pool().await.expect("Failed to create pool"); let fixture = PermissionSetFixture::new(pool.clone()); let identity_id = fixture.create_identity().await; let permset = fixture.create_default().await; let assignment = fixture.create_assignment(identity_id, permset.id).await; // Delete identity - assignment should be cascade deleted let deleted = IdentityRepository::delete(&pool, identity_id) .await .expect("Failed to delete identity"); assert!(deleted); // Assignment should no longer exist let found = PermissionAssignmentRepository::find_by_id(&pool, assignment.id) .await .expect("Query should succeed"); assert!(found.is_none()); } #[tokio::test] #[ignore = "integration test — requires database"] async fn test_permission_assignment_cascade_from_permset() { let pool = create_test_pool().await.expect("Failed to create pool"); let fixture = PermissionSetFixture::new(pool.clone()); let identity_id = fixture.create_identity().await; let permset = fixture.create_default().await; let assignment = fixture.create_assignment(identity_id, permset.id).await; // Delete permission set - assignment should be cascade deleted let deleted = PermissionSetRepository::delete(&pool, permset.id) .await .expect("Failed to delete permission set"); assert!(deleted); // Assignment should no longer exist let found = PermissionAssignmentRepository::find_by_id(&pool, assignment.id) .await .expect("Query should succeed"); assert!(found.is_none()); } #[tokio::test] #[ignore = "integration test — requires database"] async fn test_permission_assignment_timestamp_auto_set() { let pool = create_test_pool().await.expect("Failed to create pool"); let fixture = PermissionSetFixture::new(pool.clone()); let identity_id = fixture.create_identity().await; let permset = fixture.create_default().await; let before = chrono::Utc::now(); let assignment = fixture.create_assignment(identity_id, permset.id).await; let after = chrono::Utc::now(); assert!(assignment.created >= before); assert!(assignment.created <= after); } #[tokio::test] #[ignore = "integration test — requires database"] async fn test_multiple_identities_same_permset() { let pool = create_test_pool().await.expect("Failed to create pool"); let fixture = PermissionSetFixture::new(pool.clone()); let identity1 = fixture.create_identity().await; let identity2 = fixture.create_identity().await; let identity3 = fixture.create_identity().await; let permset = fixture.create_default().await; let a1 = fixture.create_assignment(identity1, permset.id).await; let a2 = fixture.create_assignment(identity2, permset.id).await; let a3 = fixture.create_assignment(identity3, permset.id).await; // All should have same permset assert_eq!(a1.permset, permset.id); assert_eq!(a2.permset, permset.id); assert_eq!(a3.permset, permset.id); // But different identities assert_eq!(a1.identity, identity1); assert_eq!(a2.identity, identity2); assert_eq!(a3.identity, identity3); } #[tokio::test] #[ignore = "integration test — requires database"] async fn test_one_identity_multiple_permsets() { let pool = create_test_pool().await.expect("Failed to create pool"); let fixture = PermissionSetFixture::new(pool.clone()); let identity_id = fixture.create_identity().await; let p1 = fixture.create_default().await; let p2 = fixture.create_default().await; let p3 = fixture.create_default().await; let a1 = fixture.create_assignment(identity_id, p1.id).await; let a2 = fixture.create_assignment(identity_id, p2.id).await; let a3 = fixture.create_assignment(identity_id, p3.id).await; // All should have same identity assert_eq!(a1.identity, identity_id); assert_eq!(a2.identity, identity_id); assert_eq!(a3.identity, identity_id); // But different permsets assert_eq!(a1.permset, p1.id); assert_eq!(a2.permset, p2.id); assert_eq!(a3.permset, p3.id); // Query by identity should return all 3 let assignments = PermissionAssignmentRepository::find_by_identity(&pool, identity_id) .await .expect("Failed to find assignments"); assert_eq!(assignments.len(), 3); } #[tokio::test] #[ignore = "integration test — requires database"] async fn test_permission_set_ordering() { let pool = create_test_pool().await.expect("Failed to create pool"); let fixture = PermissionSetFixture::new(pool.clone()); let ref1 = fixture.unique_ref("aaa"); let ref2 = fixture.unique_ref("bbb"); let ref3 = fixture.unique_ref("ccc"); let _p1 = fixture .create_permission_set(&ref1, None, None, json!([])) .await; let _p2 = fixture .create_permission_set(&ref2, None, None, json!([])) .await; let _p3 = fixture .create_permission_set(&ref3, None, None, json!([])) .await; let permsets = PermissionSetRepository::list(&pool) .await .expect("Failed to list permission sets"); // Should be ordered by ref ASC let our_sets: Vec<&PermissionSet> = permsets .iter() .filter(|p| p.r#ref.starts_with("test.")) .filter(|p| p.r#ref == ref1 || p.r#ref == ref2 || p.r#ref == ref3) .collect(); if our_sets.len() == 3 { let pos1 = permsets.iter().position(|p| p.r#ref == ref1).unwrap(); let pos2 = permsets.iter().position(|p| p.r#ref == ref2).unwrap(); let pos3 = permsets.iter().position(|p| p.r#ref == ref3).unwrap(); assert!(pos1 < pos2); assert!(pos2 < pos3); } } #[tokio::test] #[ignore = "integration test — requires database"] async fn test_permission_assignment_ordering() { let pool = create_test_pool().await.expect("Failed to create pool"); let fixture = PermissionSetFixture::new(pool.clone()); let identity_id = fixture.create_identity().await; let p1 = fixture.create_default().await; let p2 = fixture.create_default().await; let p3 = fixture.create_default().await; let a1 = fixture.create_assignment(identity_id, p1.id).await; tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; let a2 = fixture.create_assignment(identity_id, p2.id).await; tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; let a3 = fixture.create_assignment(identity_id, p3.id).await; let assignments = PermissionAssignmentRepository::list(&pool) .await .expect("Failed to list assignments"); // Should be ordered by created DESC (newest first) let ids: Vec = assignments.iter().map(|a| a.id).collect(); if ids.contains(&a1.id) && ids.contains(&a2.id) && ids.contains(&a3.id) { let pos1 = ids.iter().position(|&id| id == a1.id).unwrap(); let pos2 = ids.iter().position(|&id| id == a2.id).unwrap(); let pos3 = ids.iter().position(|&id| id == a3.id).unwrap(); // Newest (a3) should come before older ones assert!(pos3 < pos2); assert!(pos2 < pos1); } }