//! Functional tests for `SqliteRelayLabelRepository` implementation. //! //! These tests verify that the SQLite repository correctly implements //! the `RelayLabelRepository` trait using the new infrastructure entities //! and conversion patterns. use sta::{ domain::relay::{ repository::RelayLabelRepository, types::{RelayId, RelayLabel}, }, infrastructure::persistence::{ entities::relay_label_record::RelayLabelRecord, sqlite_repository::SqliteRelayLabelRepository, }, }; /// Test that `get_label` returns None for non-existent relay. #[tokio::test] async fn test_get_label_returns_none_for_non_existent_relay() { let repo = SqliteRelayLabelRepository::in_memory() .await .expect("Failed to create repository"); 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" ); } /// Test that `get_label` retrieves previously saved label. #[tokio::test] async fn test_get_label_retrieves_saved_label() { let repo = SqliteRelayLabelRepository::in_memory() .await .expect("Failed to create repository"); let relay_id = RelayId::new(2).expect("Valid relay ID"); let label = RelayLabel::new("Heater".to_string()).expect("Valid label"); // Save the label repo.save_label(relay_id, label.clone()) .await .expect("save_label should succeed"); // Retrieve the label 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" ); } /// Test that `save_label` successfully saves a label. #[tokio::test] async fn test_save_label_succeeds() { let repo = SqliteRelayLabelRepository::in_memory() .await .expect("Failed to create repository"); 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"); } /// Test that `save_label` overwrites existing label. #[tokio::test] async fn test_save_label_overwrites_existing_label() { let repo = SqliteRelayLabelRepository::in_memory() .await .expect("Failed to create repository"); 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"); // Save first label repo.save_label(relay_id, label1) .await .expect("First save should succeed"); // Overwrite with second label repo.save_label(relay_id, label2) .await .expect("Second save should succeed"); // Verify only the second label is present 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" ); } /// Test that `save_label` works for all valid relay IDs (1-8). #[tokio::test] async fn test_save_label_for_all_valid_relay_ids() { let repo = SqliteRelayLabelRepository::in_memory() .await .expect("Failed to create repository"); 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 ); } // Verify all labels were saved 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"); } /// Test that `save_label` accepts maximum length labels. #[tokio::test] async fn test_save_label_accepts_max_length_labels() { let repo = SqliteRelayLabelRepository::in_memory() .await .expect("Failed to create repository"); 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" ); // Verify it was saved correctly 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" ); } /// Test that `delete_label` succeeds for existing label. #[tokio::test] async fn test_delete_label_succeeds_for_existing_label() { let repo = SqliteRelayLabelRepository::in_memory() .await .expect("Failed to create repository"); let relay_id = RelayId::new(7).expect("Valid relay ID"); let label = RelayLabel::new("ToDelete".to_string()).expect("Valid label"); // Save the label first repo.save_label(relay_id, label) .await .expect("save_label should succeed"); // Delete it let result = repo.delete_label(relay_id).await; assert!(result.is_ok(), "delete_label should succeed"); } /// Test that `delete_label` succeeds for non-existent label. #[tokio::test] async fn test_delete_label_succeeds_for_non_existent_label() { let repo = SqliteRelayLabelRepository::in_memory() .await .expect("Failed to create repository"); let relay_id = RelayId::new(8).expect("Valid relay ID"); // Delete without saving first let result = repo.delete_label(relay_id).await; assert!( result.is_ok(), "delete_label should succeed even if label doesn't exist" ); } /// Test that `delete_label` removes label from repository. #[tokio::test] async fn test_delete_label_removes_label_from_repository() { let repo = SqliteRelayLabelRepository::in_memory() .await .expect("Failed to create repository"); 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"); // Save two labels repo.save_label(relay1, label1) .await .expect("save should succeed"); repo.save_label(relay2, label2) .await .expect("save should succeed"); // Delete one label repo.delete_label(relay2) .await .expect("delete should succeed"); // Verify deleted label is gone let get_result = repo .get_label(relay2) .await .expect("get_label should succeed"); assert!(get_result.is_none(), "Deleted label should not exist"); // Verify other label still exists let other_result = repo .get_label(relay1) .await .expect("get_label should succeed"); assert!(other_result.is_some(), "Other label should still exist"); // Verify get_all_labels only returns the remaining label 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"); } /// Test that `get_all_labels` returns empty vector when no labels exist. #[tokio::test] async fn test_get_all_labels_returns_empty_when_no_labels() { let repo = SqliteRelayLabelRepository::in_memory() .await .expect("Failed to create repository"); 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" ); } /// Test that `get_all_labels` returns all saved labels. #[tokio::test] async fn test_get_all_labels_returns_all_saved_labels() { let repo = SqliteRelayLabelRepository::in_memory() .await .expect("Failed to create repository"); 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"); // Save labels 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"); // Retrieve all labels let result = repo .get_all_labels() .await .expect("get_all_labels should succeed"); assert_eq!(result.len(), 3, "Should return exactly 3 labels"); // Verify the labels are present (order may vary by implementation) 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'"); } /// Test that `get_all_labels` excludes relays without labels. #[tokio::test] async fn test_get_all_labels_excludes_relays_without_labels() { let repo = SqliteRelayLabelRepository::in_memory() .await .expect("Failed to create repository"); 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"); } /// Test that `get_all_labels` excludes deleted labels. #[tokio::test] async fn test_get_all_labels_excludes_deleted_labels() { let repo = SqliteRelayLabelRepository::in_memory() .await .expect("Failed to create repository"); 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"); // Save all three labels 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"); // Delete the middle one repo.delete_label(relay2) .await .expect("delete should succeed"); // Verify get_all_labels only returns the two remaining labels 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"); } /// Test that entity conversion works correctly. #[tokio::test] async fn test_entity_conversion_roundtrip() { let repo = SqliteRelayLabelRepository::in_memory() .await .expect("Failed to create repository"); let relay_id = RelayId::new(1).expect("Valid relay ID"); let relay_label = RelayLabel::new("Test Label".to_string()).expect("Valid label"); // Create record from domain types let _record = RelayLabelRecord::new(relay_id, &relay_label); // Save using repository repo.save_label(relay_id, relay_label.clone()) .await .expect("save_label should succeed"); // Retrieve and verify conversion let retrieved = repo .get_label(relay_id) .await .expect("get_label should succeed"); assert!(retrieved.is_some(), "Label should be retrieved"); assert_eq!(retrieved.unwrap(), relay_label, "Labels should match"); } /// Test that repository handles database errors gracefully. #[tokio::test] async fn test_repository_error_handling() { let _repo = SqliteRelayLabelRepository::in_memory() .await .expect("Failed to create repository"); // Test with invalid relay ID (should be caught by domain validation) let invalid_relay_id = RelayId::new(9); // This will fail validation assert!(invalid_relay_id.is_err(), "Invalid relay ID should fail validation"); // Test with invalid label (should be caught by domain validation) let invalid_label = RelayLabel::new("".to_string()); // Empty label assert!(invalid_label.is_err(), "Empty label should fail validation"); } /// Test that repository operations are thread-safe. #[tokio::test] async fn test_concurrent_operations_are_thread_safe() { let repo = SqliteRelayLabelRepository::in_memory() .await .expect("Failed to create repository"); // Since SqliteRelayLabelRepository doesn't implement Clone, we'll test // sequential operations which still verify the repository handles // multiple operations correctly // Save multiple labels sequentially let relay_id1 = RelayId::new(1).expect("Valid relay ID"); let label1 = RelayLabel::new("Task1".to_string()).expect("Valid label"); repo.save_label(relay_id1, label1) .await .expect("First save should succeed"); let relay_id2 = RelayId::new(2).expect("Valid relay ID"); let label2 = RelayLabel::new("Task2".to_string()).expect("Valid label"); repo.save_label(relay_id2, label2) .await .expect("Second save should succeed"); let relay_id3 = RelayId::new(3).expect("Valid relay ID"); let label3 = RelayLabel::new("Task3".to_string()).expect("Valid label"); repo.save_label(relay_id3, label3) .await .expect("Third save should succeed"); // Verify all labels were saved let all_labels = repo .get_all_labels() .await .expect("get_all_labels should succeed"); assert_eq!(all_labels.len(), 3, "Should have all 3 labels"); }