//! Integration tests for Enforcement repository //! //! These tests verify CRUD operations, queries, and constraints //! for the Enforcement repository. mod helpers; use attune_common::{ models::enums::{EnforcementCondition, EnforcementStatus}, repositories::{ event::{CreateEnforcementInput, EnforcementRepository, UpdateEnforcementInput}, Create, Delete, FindById, List, Update, }, Error, }; use helpers::*; use serde_json::json; // ============================================================================ // CREATE Tests // ============================================================================ #[tokio::test] async fn test_create_enforcement_minimal() { let pool = create_test_pool().await.unwrap(); // Create pack, trigger, action, and rule let pack = PackFixture::new_unique("enforcement_pack") .create(&pool) .await .unwrap(); let trigger = TriggerFixture::new_unique(Some(pack.id), Some(pack.r#ref.clone()), "webhook") .create(&pool) .await .unwrap(); let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "action") .create(&pool) .await .unwrap(); use attune_common::repositories::rule::{CreateRuleInput, RuleRepository}; let rule = RuleRepository::create( &pool, CreateRuleInput { r#ref: format!("{}.test_rule", pack.r#ref), pack: pack.id, pack_ref: pack.r#ref.clone(), label: "Test Rule".to_string(), description: "Test".to_string(), action: action.id, action_ref: action.r#ref.clone(), trigger: trigger.id, trigger_ref: trigger.r#ref.clone(), conditions: json!({}), action_params: json!({}), trigger_params: json!({}), enabled: true, is_adhoc: false, }, ) .await .unwrap(); // Create enforcement with minimal fields let input = CreateEnforcementInput { rule: Some(rule.id), rule_ref: rule.r#ref.clone(), trigger_ref: trigger.r#ref.clone(), config: None, event: None, status: EnforcementStatus::Created, payload: json!({}), condition: EnforcementCondition::All, conditions: json!([]), }; let enforcement = EnforcementRepository::create(&pool, input).await.unwrap(); assert!(enforcement.id > 0); assert_eq!(enforcement.rule, Some(rule.id)); assert_eq!(enforcement.rule_ref, rule.r#ref); assert_eq!(enforcement.trigger_ref, trigger.r#ref); assert_eq!(enforcement.config, None); assert_eq!(enforcement.event, None); assert_eq!(enforcement.status, EnforcementStatus::Created); assert_eq!(enforcement.payload, json!({})); assert_eq!(enforcement.condition, EnforcementCondition::All); assert_eq!(enforcement.conditions, json!([])); assert!(enforcement.created.timestamp() > 0); assert!(enforcement.updated.timestamp() > 0); } #[tokio::test] async fn test_create_enforcement_with_event() { let pool = create_test_pool().await.unwrap(); let pack = PackFixture::new_unique("event_enforcement_pack") .create(&pool) .await .unwrap(); let trigger = TriggerFixture::new_unique(Some(pack.id), Some(pack.r#ref.clone()), "webhook") .create(&pool) .await .unwrap(); let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "action") .create(&pool) .await .unwrap(); use attune_common::repositories::rule::{CreateRuleInput, RuleRepository}; let rule = RuleRepository::create( &pool, CreateRuleInput { r#ref: format!("{}.test_rule", pack.r#ref), pack: pack.id, pack_ref: pack.r#ref.clone(), label: "Test Rule".to_string(), description: "Test".to_string(), action: action.id, action_ref: action.r#ref.clone(), trigger: trigger.id, trigger_ref: trigger.r#ref.clone(), conditions: json!({}), action_params: json!({}), trigger_params: json!({}), enabled: true, is_adhoc: false, }, ) .await .unwrap(); // Create an event let event = EventFixture::new_unique(Some(trigger.id), &trigger.r#ref) .with_payload(json!({"event": "data"})) .create(&pool) .await .unwrap(); let input = CreateEnforcementInput { rule: Some(rule.id), rule_ref: rule.r#ref.clone(), trigger_ref: trigger.r#ref.clone(), config: None, event: Some(event.id), status: EnforcementStatus::Created, payload: json!({"from": "event"}), condition: EnforcementCondition::All, conditions: json!([]), }; let enforcement = EnforcementRepository::create(&pool, input).await.unwrap(); assert_eq!(enforcement.event, Some(event.id)); assert_eq!(enforcement.payload, json!({"from": "event"})); } #[tokio::test] async fn test_create_enforcement_with_conditions() { let pool = create_test_pool().await.unwrap(); let pack = PackFixture::new_unique("conditions_pack") .create(&pool) .await .unwrap(); let trigger = TriggerFixture::new_unique(Some(pack.id), Some(pack.r#ref.clone()), "webhook") .create(&pool) .await .unwrap(); let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "action") .create(&pool) .await .unwrap(); use attune_common::repositories::rule::{CreateRuleInput, RuleRepository}; let rule = RuleRepository::create( &pool, CreateRuleInput { r#ref: format!("{}.test_rule", pack.r#ref), pack: pack.id, pack_ref: pack.r#ref.clone(), label: "Test Rule".to_string(), description: "Test".to_string(), action: action.id, action_ref: action.r#ref.clone(), trigger: trigger.id, trigger_ref: trigger.r#ref.clone(), conditions: json!({}), action_params: json!({}), trigger_params: json!({}), enabled: true, is_adhoc: false, }, ) .await .unwrap(); let conditions = json!([ {"equals": {"event.status": "success"}}, {"greater_than": {"event.priority": 5}} ]); let input = CreateEnforcementInput { rule: Some(rule.id), rule_ref: rule.r#ref.clone(), trigger_ref: trigger.r#ref.clone(), config: None, event: None, status: EnforcementStatus::Created, payload: json!({}), condition: EnforcementCondition::All, conditions: conditions.clone(), }; let enforcement = EnforcementRepository::create(&pool, input).await.unwrap(); assert_eq!(enforcement.condition, EnforcementCondition::All); assert_eq!(enforcement.conditions, conditions); } #[tokio::test] async fn test_create_enforcement_with_any_condition() { let pool = create_test_pool().await.unwrap(); let pack = PackFixture::new_unique("any_condition_pack") .create(&pool) .await .unwrap(); let trigger = TriggerFixture::new_unique(Some(pack.id), Some(pack.r#ref.clone()), "webhook") .create(&pool) .await .unwrap(); let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "action") .create(&pool) .await .unwrap(); use attune_common::repositories::rule::{CreateRuleInput, RuleRepository}; let rule = RuleRepository::create( &pool, CreateRuleInput { r#ref: format!("{}.test_rule", pack.r#ref), pack: pack.id, pack_ref: pack.r#ref.clone(), label: "Test Rule".to_string(), description: "Test".to_string(), action: action.id, action_ref: action.r#ref.clone(), trigger: trigger.id, trigger_ref: trigger.r#ref.clone(), conditions: json!({}), action_params: json!({}), trigger_params: json!({}), enabled: true, is_adhoc: false, }, ) .await .unwrap(); let input = CreateEnforcementInput { rule: Some(rule.id), rule_ref: rule.r#ref.clone(), trigger_ref: trigger.r#ref.clone(), config: None, event: None, status: EnforcementStatus::Created, payload: json!({}), condition: EnforcementCondition::Any, conditions: json!([ {"equals": {"event.type": "webhook"}}, {"equals": {"event.type": "timer"}} ]), }; let enforcement = EnforcementRepository::create(&pool, input).await.unwrap(); assert_eq!(enforcement.condition, EnforcementCondition::Any); } #[tokio::test] async fn test_create_enforcement_without_rule_id() { let pool = create_test_pool().await.unwrap(); // Enforcements can be created without a rule ID (rule may have been deleted) let input = CreateEnforcementInput { rule: None, rule_ref: "deleted.rule".to_string(), trigger_ref: "some.trigger".to_string(), config: None, event: None, status: EnforcementStatus::Created, payload: json!({"reason": "rule was deleted"}), condition: EnforcementCondition::All, conditions: json!([]), }; let enforcement = EnforcementRepository::create(&pool, input).await.unwrap(); assert_eq!(enforcement.rule, None); assert_eq!(enforcement.rule_ref, "deleted.rule"); } #[tokio::test] async fn test_create_enforcement_with_invalid_rule_fails() { let pool = create_test_pool().await.unwrap(); // Try to create enforcement with non-existent rule ID let input = CreateEnforcementInput { rule: Some(99999), rule_ref: "nonexistent.rule".to_string(), trigger_ref: "some.trigger".to_string(), config: None, event: None, status: EnforcementStatus::Created, payload: json!({}), condition: EnforcementCondition::All, conditions: json!([]), }; let result = EnforcementRepository::create(&pool, input).await; assert!(result.is_err()); // Foreign key constraint violation } #[tokio::test] async fn test_create_enforcement_with_invalid_event_fails() { let pool = create_test_pool().await.unwrap(); // Try to create enforcement with non-existent event ID let input = CreateEnforcementInput { rule: None, rule_ref: "some.rule".to_string(), trigger_ref: "some.trigger".to_string(), config: None, event: Some(99999), status: EnforcementStatus::Created, payload: json!({}), condition: EnforcementCondition::All, conditions: json!([]), }; let result = EnforcementRepository::create(&pool, input).await; assert!(result.is_err()); // Foreign key constraint violation } // ============================================================================ // READ Tests // ============================================================================ #[tokio::test] async fn test_find_enforcement_by_id() { let pool = create_test_pool().await.unwrap(); let pack = PackFixture::new_unique("find_pack") .create(&pool) .await .unwrap(); let trigger = TriggerFixture::new_unique(Some(pack.id), Some(pack.r#ref.clone()), "webhook") .create(&pool) .await .unwrap(); let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "action") .create(&pool) .await .unwrap(); use attune_common::repositories::rule::{CreateRuleInput, RuleRepository}; let rule = RuleRepository::create( &pool, CreateRuleInput { r#ref: format!("{}.test_rule", pack.r#ref), pack: pack.id, pack_ref: pack.r#ref.clone(), label: "Test Rule".to_string(), description: "Test".to_string(), action: action.id, action_ref: action.r#ref.clone(), trigger: trigger.id, trigger_ref: trigger.r#ref.clone(), conditions: json!({}), action_params: json!({}), trigger_params: json!({}), enabled: true, is_adhoc: false, }, ) .await .unwrap(); let created_enforcement = EnforcementFixture::new_unique(Some(rule.id), &rule.r#ref, &trigger.r#ref) .with_payload(json!({"test": "data"})) .create(&pool) .await .unwrap(); let found = EnforcementRepository::find_by_id(&pool, created_enforcement.id) .await .unwrap(); assert!(found.is_some()); let enforcement = found.unwrap(); assert_eq!(enforcement.id, created_enforcement.id); assert_eq!(enforcement.rule, created_enforcement.rule); assert_eq!(enforcement.rule_ref, created_enforcement.rule_ref); assert_eq!(enforcement.status, created_enforcement.status); } #[tokio::test] async fn test_find_enforcement_by_id_not_found() { let pool = create_test_pool().await.unwrap(); let result = EnforcementRepository::find_by_id(&pool, 99999) .await .unwrap(); assert!(result.is_none()); } #[tokio::test] async fn test_get_enforcement_by_id() { let pool = create_test_pool().await.unwrap(); let pack = PackFixture::new_unique("get_pack") .create(&pool) .await .unwrap(); let trigger = TriggerFixture::new_unique(Some(pack.id), Some(pack.r#ref.clone()), "webhook") .create(&pool) .await .unwrap(); let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "action") .create(&pool) .await .unwrap(); use attune_common::repositories::rule::{CreateRuleInput, RuleRepository}; let rule = RuleRepository::create( &pool, CreateRuleInput { r#ref: format!("{}.test_rule", pack.r#ref), pack: pack.id, pack_ref: pack.r#ref.clone(), label: "Test Rule".to_string(), description: "Test".to_string(), action: action.id, action_ref: action.r#ref.clone(), trigger: trigger.id, trigger_ref: trigger.r#ref.clone(), conditions: json!({}), action_params: json!({}), trigger_params: json!({}), enabled: true, is_adhoc: false, }, ) .await .unwrap(); let created_enforcement = EnforcementFixture::new_unique(Some(rule.id), &rule.r#ref, &trigger.r#ref) .create(&pool) .await .unwrap(); let enforcement = EnforcementRepository::get_by_id(&pool, created_enforcement.id) .await .unwrap(); assert_eq!(enforcement.id, created_enforcement.id); } #[tokio::test] async fn test_get_enforcement_by_id_not_found() { let pool = create_test_pool().await.unwrap(); let result = EnforcementRepository::get_by_id(&pool, 99999).await; assert!(result.is_err()); assert!(matches!(result.unwrap_err(), Error::NotFound { .. })); } // ============================================================================ // LIST Tests // ============================================================================ #[tokio::test] async fn test_list_enforcements_empty() { let pool = create_test_pool().await.unwrap(); let enforcements = EnforcementRepository::list(&pool).await.unwrap(); // May have enforcements from other tests, just verify we can list without error drop(enforcements); } #[tokio::test] async fn test_list_enforcements() { let pool = create_test_pool().await.unwrap(); let pack = PackFixture::new_unique("list_pack") .create(&pool) .await .unwrap(); let trigger = TriggerFixture::new_unique(Some(pack.id), Some(pack.r#ref.clone()), "webhook") .create(&pool) .await .unwrap(); let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "action") .create(&pool) .await .unwrap(); use attune_common::repositories::rule::{CreateRuleInput, RuleRepository}; let rule = RuleRepository::create( &pool, CreateRuleInput { r#ref: format!("{}.test_rule", pack.r#ref), pack: pack.id, pack_ref: pack.r#ref.clone(), label: "Test Rule".to_string(), description: "Test".to_string(), action: action.id, action_ref: action.r#ref.clone(), trigger: trigger.id, trigger_ref: trigger.r#ref.clone(), conditions: json!({}), action_params: json!({}), trigger_params: json!({}), enabled: true, is_adhoc: false, }, ) .await .unwrap(); let before_count = EnforcementRepository::list(&pool).await.unwrap().len(); // Create multiple enforcements let mut created_ids = vec![]; for i in 0..3 { let enforcement = EnforcementFixture::new_unique(Some(rule.id), &rule.r#ref, &trigger.r#ref) .with_payload(json!({"index": i})) .create(&pool) .await .unwrap(); created_ids.push(enforcement.id); } let enforcements = EnforcementRepository::list(&pool).await.unwrap(); assert!(enforcements.len() >= before_count + 3); // Verify our enforcements are in the list let our_enforcements: Vec<_> = enforcements .iter() .filter(|e| created_ids.contains(&e.id)) .collect(); assert_eq!(our_enforcements.len(), 3); } // ============================================================================ // UPDATE Tests // ============================================================================ #[tokio::test] async fn test_update_enforcement_status() { let pool = create_test_pool().await.unwrap(); let pack = PackFixture::new_unique("update_pack") .create(&pool) .await .unwrap(); let trigger = TriggerFixture::new_unique(Some(pack.id), Some(pack.r#ref.clone()), "webhook") .create(&pool) .await .unwrap(); let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "action") .create(&pool) .await .unwrap(); use attune_common::repositories::rule::{CreateRuleInput, RuleRepository}; let rule = RuleRepository::create( &pool, CreateRuleInput { r#ref: format!("{}.test_rule", pack.r#ref), pack: pack.id, pack_ref: pack.r#ref.clone(), label: "Test Rule".to_string(), description: "Test".to_string(), action: action.id, action_ref: action.r#ref.clone(), trigger: trigger.id, trigger_ref: trigger.r#ref.clone(), conditions: json!({}), action_params: json!({}), trigger_params: json!({}), enabled: true, is_adhoc: false, }, ) .await .unwrap(); let enforcement = EnforcementFixture::new_unique(Some(rule.id), &rule.r#ref, &trigger.r#ref) .with_status(EnforcementStatus::Created) .create(&pool) .await .unwrap(); let input = UpdateEnforcementInput { status: Some(EnforcementStatus::Processed), payload: None, }; let updated = EnforcementRepository::update(&pool, enforcement.id, input) .await .unwrap(); assert_eq!(updated.id, enforcement.id); assert_eq!(updated.status, EnforcementStatus::Processed); assert!(updated.updated > enforcement.updated); } #[tokio::test] async fn test_update_enforcement_status_transitions() { let pool = create_test_pool().await.unwrap(); let pack = PackFixture::new_unique("status_pack") .create(&pool) .await .unwrap(); let trigger = TriggerFixture::new_unique(Some(pack.id), Some(pack.r#ref.clone()), "webhook") .create(&pool) .await .unwrap(); let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "action") .create(&pool) .await .unwrap(); use attune_common::repositories::rule::{CreateRuleInput, RuleRepository}; let rule = RuleRepository::create( &pool, CreateRuleInput { r#ref: format!("{}.test_rule", pack.r#ref), pack: pack.id, pack_ref: pack.r#ref.clone(), label: "Test Rule".to_string(), description: "Test".to_string(), action: action.id, action_ref: action.r#ref.clone(), trigger: trigger.id, trigger_ref: trigger.r#ref.clone(), conditions: json!({}), action_params: json!({}), trigger_params: json!({}), enabled: true, is_adhoc: false, }, ) .await .unwrap(); let enforcement = EnforcementFixture::new_unique(Some(rule.id), &rule.r#ref, &trigger.r#ref) .create(&pool) .await .unwrap(); // Test status transitions: Created -> Succeeded let updated = EnforcementRepository::update( &pool, enforcement.id, UpdateEnforcementInput { status: Some(EnforcementStatus::Processed), payload: None, }, ) .await .unwrap(); assert_eq!(updated.status, EnforcementStatus::Processed); // Test status transition: Succeeded -> Failed (although unusual) let updated = EnforcementRepository::update( &pool, enforcement.id, UpdateEnforcementInput { status: Some(EnforcementStatus::Disabled), payload: None, }, ) .await .unwrap(); assert_eq!(updated.status, EnforcementStatus::Disabled); } #[tokio::test] async fn test_update_enforcement_payload() { let pool = create_test_pool().await.unwrap(); let pack = PackFixture::new_unique("payload_pack") .create(&pool) .await .unwrap(); let trigger = TriggerFixture::new_unique(Some(pack.id), Some(pack.r#ref.clone()), "webhook") .create(&pool) .await .unwrap(); let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "action") .create(&pool) .await .unwrap(); use attune_common::repositories::rule::{CreateRuleInput, RuleRepository}; let rule = RuleRepository::create( &pool, CreateRuleInput { r#ref: format!("{}.test_rule", pack.r#ref), pack: pack.id, pack_ref: pack.r#ref.clone(), label: "Test Rule".to_string(), description: "Test".to_string(), action: action.id, action_ref: action.r#ref.clone(), trigger: trigger.id, trigger_ref: trigger.r#ref.clone(), conditions: json!({}), action_params: json!({}), trigger_params: json!({}), enabled: true, is_adhoc: false, }, ) .await .unwrap(); let enforcement = EnforcementFixture::new_unique(Some(rule.id), &rule.r#ref, &trigger.r#ref) .with_payload(json!({"initial": "data"})) .create(&pool) .await .unwrap(); let new_payload = json!({"updated": "data", "version": 2}); let input = UpdateEnforcementInput { status: None, payload: Some(new_payload.clone()), }; let updated = EnforcementRepository::update(&pool, enforcement.id, input) .await .unwrap(); assert_eq!(updated.payload, new_payload); } #[tokio::test] async fn test_update_enforcement_both_fields() { let pool = create_test_pool().await.unwrap(); let pack = PackFixture::new_unique("both_pack") .create(&pool) .await .unwrap(); let trigger = TriggerFixture::new_unique(Some(pack.id), Some(pack.r#ref.clone()), "webhook") .create(&pool) .await .unwrap(); let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "action") .create(&pool) .await .unwrap(); use attune_common::repositories::rule::{CreateRuleInput, RuleRepository}; let rule = RuleRepository::create( &pool, CreateRuleInput { r#ref: format!("{}.test_rule", pack.r#ref), pack: pack.id, pack_ref: pack.r#ref.clone(), label: "Test Rule".to_string(), description: "Test".to_string(), action: action.id, action_ref: action.r#ref.clone(), trigger: trigger.id, trigger_ref: trigger.r#ref.clone(), conditions: json!({}), action_params: json!({}), trigger_params: json!({}), enabled: true, is_adhoc: false, }, ) .await .unwrap(); let enforcement = EnforcementFixture::new_unique(Some(rule.id), &rule.r#ref, &trigger.r#ref) .create(&pool) .await .unwrap(); let new_payload = json!({"result": "success"}); let input = UpdateEnforcementInput { status: Some(EnforcementStatus::Processed), payload: Some(new_payload.clone()), }; let updated = EnforcementRepository::update(&pool, enforcement.id, input) .await .unwrap(); assert_eq!(updated.status, EnforcementStatus::Processed); assert_eq!(updated.payload, new_payload); } #[tokio::test] async fn test_update_enforcement_no_changes() { let pool = create_test_pool().await.unwrap(); let pack = PackFixture::new_unique("nochange_pack") .create(&pool) .await .unwrap(); let trigger = TriggerFixture::new_unique(Some(pack.id), Some(pack.r#ref.clone()), "webhook") .create(&pool) .await .unwrap(); let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "action") .create(&pool) .await .unwrap(); use attune_common::repositories::rule::{CreateRuleInput, RuleRepository}; let rule = RuleRepository::create( &pool, CreateRuleInput { r#ref: format!("{}.test_rule", pack.r#ref), pack: pack.id, pack_ref: pack.r#ref.clone(), label: "Test Rule".to_string(), description: "Test".to_string(), action: action.id, action_ref: action.r#ref.clone(), trigger: trigger.id, trigger_ref: trigger.r#ref.clone(), conditions: json!({}), action_params: json!({}), trigger_params: json!({}), enabled: true, is_adhoc: false, }, ) .await .unwrap(); let enforcement = EnforcementFixture::new_unique(Some(rule.id), &rule.r#ref, &trigger.r#ref) .with_payload(json!({"test": "data"})) .create(&pool) .await .unwrap(); let input = UpdateEnforcementInput { status: None, payload: None, }; let result = EnforcementRepository::update(&pool, enforcement.id, input) .await .unwrap(); // Should return existing enforcement without updating assert_eq!(result.id, enforcement.id); assert_eq!(result.status, enforcement.status); } #[tokio::test] async fn test_update_enforcement_not_found() { let pool = create_test_pool().await.unwrap(); let input = UpdateEnforcementInput { status: Some(EnforcementStatus::Processed), payload: None, }; let result = EnforcementRepository::update(&pool, 99999, input).await; // When updating non-existent entity with changes, SQLx returns RowNotFound error assert!(result.is_err()); } // ============================================================================ // DELETE Tests // ============================================================================ #[tokio::test] async fn test_delete_enforcement() { let pool = create_test_pool().await.unwrap(); let pack = PackFixture::new_unique("delete_pack") .create(&pool) .await .unwrap(); let trigger = TriggerFixture::new_unique(Some(pack.id), Some(pack.r#ref.clone()), "webhook") .create(&pool) .await .unwrap(); let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "action") .create(&pool) .await .unwrap(); use attune_common::repositories::rule::{CreateRuleInput, RuleRepository}; let rule = RuleRepository::create( &pool, CreateRuleInput { r#ref: format!("{}.test_rule", pack.r#ref), pack: pack.id, pack_ref: pack.r#ref.clone(), label: "Test Rule".to_string(), description: "Test".to_string(), action: action.id, action_ref: action.r#ref.clone(), trigger: trigger.id, trigger_ref: trigger.r#ref.clone(), conditions: json!({}), action_params: json!({}), trigger_params: json!({}), enabled: true, is_adhoc: false, }, ) .await .unwrap(); let enforcement = EnforcementFixture::new_unique(Some(rule.id), &rule.r#ref, &trigger.r#ref) .create(&pool) .await .unwrap(); let deleted = EnforcementRepository::delete(&pool, enforcement.id) .await .unwrap(); assert!(deleted); // Verify it's gone let found = EnforcementRepository::find_by_id(&pool, enforcement.id) .await .unwrap(); assert!(found.is_none()); } #[tokio::test] async fn test_delete_enforcement_not_found() { let pool = create_test_pool().await.unwrap(); let deleted = EnforcementRepository::delete(&pool, 99999).await.unwrap(); assert!(!deleted); } // ============================================================================ // SPECIALIZED QUERY Tests // ============================================================================ #[tokio::test] async fn test_find_enforcements_by_rule() { let pool = create_test_pool().await.unwrap(); let pack = PackFixture::new_unique("rule_query_pack") .create(&pool) .await .unwrap(); let trigger = TriggerFixture::new_unique(Some(pack.id), Some(pack.r#ref.clone()), "webhook") .create(&pool) .await .unwrap(); let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "action") .create(&pool) .await .unwrap(); use attune_common::repositories::rule::{CreateRuleInput, RuleRepository}; let rule1 = RuleRepository::create( &pool, CreateRuleInput { r#ref: format!("{}.rule1", pack.r#ref), pack: pack.id, pack_ref: pack.r#ref.clone(), label: "Rule 1".to_string(), description: "Test".to_string(), action: action.id, action_ref: action.r#ref.clone(), trigger: trigger.id, trigger_ref: trigger.r#ref.clone(), conditions: json!({}), action_params: json!({}), trigger_params: json!({}), enabled: true, is_adhoc: false, }, ) .await .unwrap(); let rule2 = RuleRepository::create( &pool, CreateRuleInput { r#ref: format!("{}.rule2", pack.r#ref), pack: pack.id, pack_ref: pack.r#ref.clone(), label: "Rule 2".to_string(), description: "Test".to_string(), action: action.id, action_ref: action.r#ref.clone(), trigger: trigger.id, trigger_ref: trigger.r#ref.clone(), conditions: json!({}), action_params: json!({}), trigger_params: json!({}), enabled: true, is_adhoc: false, }, ) .await .unwrap(); // Create enforcements for rule1 for i in 0..3 { EnforcementFixture::new_unique(Some(rule1.id), &rule1.r#ref, &trigger.r#ref) .with_payload(json!({"rule": 1, "index": i})) .create(&pool) .await .unwrap(); } // Create enforcements for rule2 for i in 0..2 { EnforcementFixture::new_unique(Some(rule2.id), &rule2.r#ref, &trigger.r#ref) .with_payload(json!({"rule": 2, "index": i})) .create(&pool) .await .unwrap(); } let enforcements = EnforcementRepository::find_by_rule(&pool, rule1.id) .await .unwrap(); assert_eq!(enforcements.len(), 3); for enforcement in &enforcements { assert_eq!(enforcement.rule, Some(rule1.id)); } } #[tokio::test] async fn test_find_enforcements_by_status() { let pool = create_test_pool().await.unwrap(); let pack = PackFixture::new_unique("status_query_pack") .create(&pool) .await .unwrap(); let trigger = TriggerFixture::new_unique(Some(pack.id), Some(pack.r#ref.clone()), "webhook") .create(&pool) .await .unwrap(); let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "action") .create(&pool) .await .unwrap(); use attune_common::repositories::rule::{CreateRuleInput, RuleRepository}; let rule = RuleRepository::create( &pool, CreateRuleInput { r#ref: format!("{}.test_rule", pack.r#ref), pack: pack.id, pack_ref: pack.r#ref.clone(), label: "Test Rule".to_string(), description: "Test".to_string(), action: action.id, action_ref: action.r#ref.clone(), trigger: trigger.id, trigger_ref: trigger.r#ref.clone(), conditions: json!({}), action_params: json!({}), trigger_params: json!({}), enabled: true, is_adhoc: false, }, ) .await .unwrap(); // Create enforcements with different statuses let enf1 = EnforcementFixture::new_unique(Some(rule.id), &rule.r#ref, &trigger.r#ref) .with_status(EnforcementStatus::Created) .create(&pool) .await .unwrap(); let enf2 = EnforcementFixture::new_unique(Some(rule.id), &rule.r#ref, &trigger.r#ref) .with_status(EnforcementStatus::Processed) .create(&pool) .await .unwrap(); let enf3 = EnforcementFixture::new_unique(Some(rule.id), &rule.r#ref, &trigger.r#ref) .with_status(EnforcementStatus::Processed) .create(&pool) .await .unwrap(); let processed_enforcements = EnforcementRepository::find_by_status(&pool, EnforcementStatus::Processed) .await .unwrap(); // Filter to only our test enforcements let our_processed: Vec<_> = processed_enforcements .iter() .filter(|e| e.id == enf2.id || e.id == enf3.id) .collect(); assert_eq!(our_processed.len(), 2); for enforcement in &our_processed { assert_eq!(enforcement.status, EnforcementStatus::Processed); } let created_enforcements = EnforcementRepository::find_by_status(&pool, EnforcementStatus::Created) .await .unwrap(); // Verify our created enforcement is in the list let our_created: Vec<_> = created_enforcements .iter() .filter(|e| e.id == enf1.id) .collect(); assert_eq!(our_created.len(), 1); } #[tokio::test] async fn test_find_enforcements_by_event() { let pool = create_test_pool().await.unwrap(); let pack = PackFixture::new_unique("event_query_pack") .create(&pool) .await .unwrap(); let trigger = TriggerFixture::new_unique(Some(pack.id), Some(pack.r#ref.clone()), "webhook") .create(&pool) .await .unwrap(); let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "action") .create(&pool) .await .unwrap(); use attune_common::repositories::rule::{CreateRuleInput, RuleRepository}; let rule = RuleRepository::create( &pool, CreateRuleInput { r#ref: format!("{}.test_rule", pack.r#ref), pack: pack.id, pack_ref: pack.r#ref.clone(), label: "Test Rule".to_string(), description: "Test".to_string(), action: action.id, action_ref: action.r#ref.clone(), trigger: trigger.id, trigger_ref: trigger.r#ref.clone(), conditions: json!({}), action_params: json!({}), trigger_params: json!({}), enabled: true, is_adhoc: false, }, ) .await .unwrap(); // Create events let event1 = EventFixture::new_unique(Some(trigger.id), &trigger.r#ref) .create(&pool) .await .unwrap(); let event2 = EventFixture::new_unique(Some(trigger.id), &trigger.r#ref) .create(&pool) .await .unwrap(); // Create enforcements for event1 for i in 0..3 { EnforcementFixture::new_unique(Some(rule.id), &rule.r#ref, &trigger.r#ref) .with_event(event1.id) .with_payload(json!({"event": 1, "index": i})) .create(&pool) .await .unwrap(); } // Create enforcement for event2 EnforcementFixture::new_unique(Some(rule.id), &rule.r#ref, &trigger.r#ref) .with_event(event2.id) .create(&pool) .await .unwrap(); let enforcements = EnforcementRepository::find_by_event(&pool, event1.id) .await .unwrap(); assert_eq!(enforcements.len(), 3); for enforcement in &enforcements { assert_eq!(enforcement.event, Some(event1.id)); } } // ============================================================================ // CASCADE & RELATIONSHIP Tests // ============================================================================ #[tokio::test] async fn test_delete_rule_sets_enforcement_rule_to_null() { let pool = create_test_pool().await.unwrap(); let pack = PackFixture::new_unique("cascade_rule_pack") .create(&pool) .await .unwrap(); let trigger = TriggerFixture::new_unique(Some(pack.id), Some(pack.r#ref.clone()), "webhook") .create(&pool) .await .unwrap(); let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "action") .create(&pool) .await .unwrap(); use attune_common::repositories::rule::{CreateRuleInput, RuleRepository}; let rule = RuleRepository::create( &pool, CreateRuleInput { r#ref: format!("{}.test_rule", pack.r#ref), pack: pack.id, pack_ref: pack.r#ref.clone(), label: "Test Rule".to_string(), description: "Test".to_string(), action: action.id, action_ref: action.r#ref.clone(), trigger: trigger.id, trigger_ref: trigger.r#ref.clone(), conditions: json!({}), action_params: json!({}), trigger_params: json!({}), enabled: true, is_adhoc: false, }, ) .await .unwrap(); let enforcement = EnforcementFixture::new(Some(rule.id), &rule.r#ref, &trigger.r#ref) .create(&pool) .await .unwrap(); // Delete the rule use attune_common::repositories::Delete; RuleRepository::delete(&pool, rule.id).await.unwrap(); // Enforcement should still exist but with NULL rule (ON DELETE SET NULL) let found_enforcement = EnforcementRepository::find_by_id(&pool, enforcement.id) .await .unwrap() .unwrap(); assert_eq!(found_enforcement.rule, None); assert_eq!(found_enforcement.rule_ref, rule.r#ref); // rule_ref preserved } // ============================================================================ // TIMESTAMP Tests // ============================================================================ #[tokio::test] async fn test_enforcement_timestamps_auto_managed() { let pool = create_test_pool().await.unwrap(); let pack = PackFixture::new_unique("timestamp_pack") .create(&pool) .await .unwrap(); let trigger = TriggerFixture::new_unique(Some(pack.id), Some(pack.r#ref.clone()), "webhook") .create(&pool) .await .unwrap(); let action = ActionFixture::new_unique(pack.id, &pack.r#ref, "action") .create(&pool) .await .unwrap(); use attune_common::repositories::rule::{CreateRuleInput, RuleRepository}; let rule = RuleRepository::create( &pool, CreateRuleInput { r#ref: format!("{}.test_rule", pack.r#ref), pack: pack.id, pack_ref: pack.r#ref.clone(), label: "Test Rule".to_string(), description: "Test".to_string(), action: action.id, action_ref: action.r#ref.clone(), trigger: trigger.id, trigger_ref: trigger.r#ref.clone(), conditions: json!({}), action_params: json!({}), trigger_params: json!({}), enabled: true, is_adhoc: false, }, ) .await .unwrap(); let enforcement = EnforcementFixture::new_unique(Some(rule.id), &rule.r#ref, &trigger.r#ref) .create(&pool) .await .unwrap(); let created_time = enforcement.created; let updated_time = enforcement.updated; assert!(created_time.timestamp() > 0); assert_eq!(created_time, updated_time); // Update and verify timestamp changed tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; let input = UpdateEnforcementInput { status: Some(EnforcementStatus::Processed), payload: None, }; let updated = EnforcementRepository::update(&pool, enforcement.id, input) .await .unwrap(); assert_eq!(updated.created, created_time); // created unchanged assert!(updated.updated > updated_time); // updated changed }