//! Comprehensive tests for `RelayLabelRepository` trait contract. //! //! This module provides a reusable test suite that verifies any implementation //! of the `RelayLabelRepository` trait meets the expected contract. These tests //! can be run against different implementations (mock, SQLite, PostgreSQL, etc.) //! to ensure they all behave correctly. //! //! **T035**: Write tests for RelayLabelRepository trait //! - Test: `get_label(RelayId(1)) → Option` //! - Test: `save_label(RelayId(1), label) → Result<(), RepositoryError>` //! - Test: `delete_label(RelayId(1)) → Result<(), RepositoryError>` #[cfg(test)] mod relay_label_repository_contract_tests { use crate::{ domain::relay::{ repository::RelayLabelRepository, types::{RelayId, RelayLabel}, }, infrastructure::persistence::label_repository::MockRelayLabelRepository, }; #[tokio::test] pub async fn test_get_label_returns_none_for_non_existent_relay() { let repo = MockRelayLabelRepository::new(); let relay_id = RelayId::new(1).expect("Valid relay ID"); let result = repo.get_label(relay_id).await; assert!(result.is_ok(), "get_label should succeed"); assert!( result.unwrap().is_none(), "get_label should return None for non-existent relay" ); } #[tokio::test] pub async fn test_get_label_retrieves_saved_label() { let repo = MockRelayLabelRepository::new(); let relay_id = RelayId::new(2).expect("Valid relay ID"); let label = RelayLabel::new("Heater".to_string()).expect("Valid label"); repo.save_label(relay_id, label.clone()) .await .expect("save_label should succeed"); let result = repo.get_label(relay_id).await; assert!(result.is_ok(), "get_label should succeed"); let retrieved = result.unwrap(); assert!(retrieved.is_some(), "get_label should return Some"); assert_eq!( retrieved.unwrap().as_str(), "Heater", "Retrieved label should match saved label" ); } #[tokio::test] pub async fn test_get_label_returns_none_after_delete() { let repo = MockRelayLabelRepository::new(); let relay_id = RelayId::new(3).expect("Valid relay ID"); let label = RelayLabel::new("ToBeDeleted".to_string()).expect("Valid label"); repo.save_label(relay_id, label) .await .expect("save_label should succeed"); repo.delete_label(relay_id) .await .expect("delete_label should succeed"); let result = repo.get_label(relay_id).await; assert!(result.is_ok(), "get_label should succeed"); assert!( result.unwrap().is_none(), "get_label should return None after delete" ); } #[tokio::test] pub async fn test_save_label_succeeds() { let repo = MockRelayLabelRepository::new(); let relay_id = RelayId::new(1).expect("Valid relay ID"); let label = RelayLabel::new("Pump".to_string()).expect("Valid label"); let result = repo.save_label(relay_id, label).await; assert!(result.is_ok(), "save_label should succeed"); } #[tokio::test] pub async fn test_save_label_overwrites_existing_label() { let repo = MockRelayLabelRepository::new(); let relay_id = RelayId::new(4).expect("Valid relay ID"); let label1 = RelayLabel::new("First".to_string()).expect("Valid label"); let label2 = RelayLabel::new("Second".to_string()).expect("Valid label"); repo.save_label(relay_id, label1) .await .expect("First save should succeed"); repo.save_label(relay_id, label2) .await .expect("Second save should succeed"); let result = repo .get_label(relay_id) .await .expect("get_label should succeed"); assert!(result.is_some(), "Label should exist"); assert_eq!( result.unwrap().as_str(), "Second", "Label should be updated to second value" ); } #[tokio::test] pub async fn test_save_label_for_all_valid_relay_ids() { let repo = MockRelayLabelRepository::new(); for id in 1..=8 { let relay_id = RelayId::new(id).expect("Valid relay ID"); let label = RelayLabel::new(format!("Relay {id}")).expect("Valid label"); let result = repo.save_label(relay_id, label).await; assert!( result.is_ok(), "save_label should succeed for relay ID {id}" ); } let all_labels = repo .get_all_labels() .await .expect("get_all_labels should succeed"); assert_eq!(all_labels.len(), 8, "Should have all 8 relay labels"); } #[tokio::test] pub async fn test_save_label_accepts_max_length_labels() { let repo = MockRelayLabelRepository::new(); let relay_id = RelayId::new(5).expect("Valid relay ID"); let max_label = RelayLabel::new("A".repeat(50)).expect("Valid max-length label"); let result = repo.save_label(relay_id, max_label).await; assert!( result.is_ok(), "save_label should succeed with max-length label" ); let retrieved = repo .get_label(relay_id) .await .expect("get_label should succeed"); assert!(retrieved.is_some(), "Label should be saved"); assert_eq!( retrieved.unwrap().as_str().len(), 50, "Label should have correct length" ); } #[tokio::test] pub async fn test_save_label_accepts_min_length_labels() { let repo = MockRelayLabelRepository::new(); let relay_id = RelayId::new(6).expect("Valid relay ID"); let min_label = RelayLabel::new("X".to_string()).expect("Valid min-length label"); let result = repo.save_label(relay_id, min_label).await; assert!( result.is_ok(), "save_label should succeed with min-length label" ); let retrieved = repo .get_label(relay_id) .await .expect("get_label should succeed"); assert!(retrieved.is_some(), "Label should be saved"); assert_eq!(retrieved.unwrap().as_str(), "X", "Label should match"); } #[tokio::test] pub async fn test_delete_label_succeeds_for_existing_label() { let repo = MockRelayLabelRepository::new(); let relay_id = RelayId::new(7).expect("Valid relay ID"); let label = RelayLabel::new("ToDelete".to_string()).expect("Valid label"); repo.save_label(relay_id, label) .await .expect("save_label should succeed"); let result = repo.delete_label(relay_id).await; assert!(result.is_ok(), "delete_label should succeed"); } #[tokio::test] pub async fn test_delete_label_succeeds_for_non_existent_label() { let repo = MockRelayLabelRepository::new(); let relay_id = RelayId::new(8).expect("Valid relay ID"); let result = repo.delete_label(relay_id).await; assert!( result.is_ok(), "delete_label should succeed even if label doesn't exist" ); } #[tokio::test] pub async fn test_delete_label_removes_label_from_repository() { let repo = MockRelayLabelRepository::new(); let relay1 = RelayId::new(1).expect("Valid relay ID"); let relay2 = RelayId::new(2).expect("Valid relay ID"); let label1 = RelayLabel::new("Keep".to_string()).expect("Valid label"); let label2 = RelayLabel::new("Remove".to_string()).expect("Valid label"); repo.save_label(relay1, label1) .await .expect("save should succeed"); repo.save_label(relay2, label2) .await .expect("save should succeed"); repo.delete_label(relay2) .await .expect("delete should succeed"); let get_result = repo .get_label(relay2) .await .expect("get_label should succeed"); assert!(get_result.is_none(), "Deleted label should not exist"); let other_result = repo .get_label(relay1) .await .expect("get_label should succeed"); assert!(other_result.is_some(), "Other label should still exist"); let all_labels = repo .get_all_labels() .await .expect("get_all_labels should succeed"); assert_eq!(all_labels.len(), 1, "Should only have one label remaining"); assert_eq!(all_labels[0].0.as_u8(), 1, "Should be relay 1"); } #[tokio::test] pub async fn test_delete_label_is_idempotent() { let repo = MockRelayLabelRepository::new(); let relay_id = RelayId::new(3).expect("Valid relay ID"); let label = RelayLabel::new("Idempotent".to_string()).expect("Valid label"); repo.save_label(relay_id, label) .await .expect("save should succeed"); repo.delete_label(relay_id) .await .expect("First delete should succeed"); let second_delete = repo.delete_label(relay_id).await; assert!( second_delete.is_ok(), "Second delete should succeed (idempotent)" ); } #[tokio::test] pub async fn test_get_all_labels_returns_empty_when_no_labels() { let repo = MockRelayLabelRepository::new(); let result = repo.get_all_labels().await; assert!(result.is_ok(), "get_all_labels should succeed"); assert!( result.unwrap().is_empty(), "get_all_labels should return empty vector" ); } #[tokio::test] pub async fn test_get_all_labels_returns_all_saved_labels() { let repo = MockRelayLabelRepository::new(); let relay1 = RelayId::new(1).expect("Valid relay ID"); let relay3 = RelayId::new(3).expect("Valid relay ID"); let relay5 = RelayId::new(5).expect("Valid relay ID"); let label1 = RelayLabel::new("Pump".to_string()).expect("Valid label"); let label3 = RelayLabel::new("Heater".to_string()).expect("Valid label"); let label5 = RelayLabel::new("Fan".to_string()).expect("Valid label"); repo.save_label(relay1, label1.clone()) .await .expect("Save should succeed"); repo.save_label(relay3, label3.clone()) .await .expect("Save should succeed"); repo.save_label(relay5, label5.clone()) .await .expect("Save should succeed"); let result = repo .get_all_labels() .await .expect("get_all_labels should succeed"); assert_eq!(result.len(), 3, "Should return exactly 3 labels"); let has_relay1 = result .iter() .any(|(id, label)| id.as_u8() == 1 && label.as_str() == "Pump"); let has_relay3 = result .iter() .any(|(id, label)| id.as_u8() == 3 && label.as_str() == "Heater"); let has_relay5 = result .iter() .any(|(id, label)| id.as_u8() == 5 && label.as_str() == "Fan"); assert!(has_relay1, "Should contain relay 1 with label 'Pump'"); assert!(has_relay3, "Should contain relay 3 with label 'Heater'"); assert!(has_relay5, "Should contain relay 5 with label 'Fan'"); } #[tokio::test] pub async fn test_get_all_labels_excludes_relays_without_labels() { let repo = MockRelayLabelRepository::new(); let relay2 = RelayId::new(2).expect("Valid relay ID"); let label2 = RelayLabel::new("Only This One".to_string()).expect("Valid label"); repo.save_label(relay2, label2) .await .expect("Save should succeed"); let result = repo .get_all_labels() .await .expect("get_all_labels should succeed"); assert_eq!( result.len(), 1, "Should return only the one relay with a label" ); assert_eq!(result[0].0.as_u8(), 2, "Should be relay 2"); } #[tokio::test] pub async fn test_get_all_labels_excludes_deleted_labels() { let repo = MockRelayLabelRepository::new(); let relay1 = RelayId::new(1).expect("Valid relay ID"); let relay2 = RelayId::new(2).expect("Valid relay ID"); let relay3 = RelayId::new(3).expect("Valid relay ID"); let label1 = RelayLabel::new("Keep1".to_string()).expect("Valid label"); let label2 = RelayLabel::new("Delete".to_string()).expect("Valid label"); let label3 = RelayLabel::new("Keep2".to_string()).expect("Valid label"); repo.save_label(relay1, label1) .await .expect("save should succeed"); repo.save_label(relay2, label2) .await .expect("save should succeed"); repo.save_label(relay3, label3) .await .expect("save should succeed"); repo.delete_label(relay2) .await .expect("delete should succeed"); let result = repo .get_all_labels() .await .expect("get_all_labels should succeed"); assert_eq!(result.len(), 2, "Should have 2 labels after deletion"); let has_relay1 = result.iter().any(|(id, _)| id.as_u8() == 1); let has_relay2 = result.iter().any(|(id, _)| id.as_u8() == 2); let has_relay3 = result.iter().any(|(id, _)| id.as_u8() == 3); assert!(has_relay1, "Relay 1 should be present"); assert!(!has_relay2, "Relay 2 should NOT be present (deleted)"); assert!(has_relay3, "Relay 3 should be present"); } }