From 2a33977a4e25b956ac27f81d953afe251e8c4eee Mon Sep 17 00:00:00 2001 From: dal Date: Wed, 19 Mar 2025 00:09:43 -0600 Subject: [PATCH] added in some tests and such for dashboard_ymls and metric_handlers --- api/libs/database/src/types/dashboard_yml.rs | 264 ++++++++++++++++++ .../src/metrics/update_metric_handler.rs | 20 ++ .../tests/metrics/update_metric_test.rs | 87 +++++- 3 files changed, 370 insertions(+), 1 deletion(-) diff --git a/api/libs/database/src/types/dashboard_yml.rs b/api/libs/database/src/types/dashboard_yml.rs index 0e23e7e18..47ac5062c 100644 --- a/api/libs/database/src/types/dashboard_yml.rs +++ b/api/libs/database/src/types/dashboard_yml.rs @@ -168,3 +168,267 @@ impl ToSql for DashboardYml { Ok(IsNull::No) } } + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + #[test] + fn test_dashboard_yml_camel_case_serialization() { + // Test case: Verify that DashboardYml serializes to camelCase + // Expected: JSON fields should be in camelCase format + + // Create a dashboard with one row + let dashboard = DashboardYml { + name: "Test Dashboard".to_string(), + description: Some("This is a test dashboard".to_string()), + rows: vec![ + Row { + items: vec![ + RowItem { + id: Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(), + } + ], + row_height: Some(400), + column_sizes: Some(vec![12]), + id: Some(1), + } + ], + }; + + // Serialize to JSON + let json = serde_json::to_value(&dashboard).unwrap(); + + // Verify camelCase field names in the output + assert!(json.get("name").is_some()); + assert!(json.get("description").is_some()); + assert!(json.get("rows").is_some()); + + // Check row fields are in camelCase + let row = &json["rows"][0]; + assert!(row.get("items").is_some()); + assert!(row.get("rowHeight").is_some()); + assert!(row.get("columnSizes").is_some()); + assert!(row.get("id").is_some()); + + // Verify snake_case field names are NOT present + assert!(row.get("row_height").is_none()); + assert!(row.get("column_sizes").is_none()); + } + + #[test] + fn test_dashboard_yml_snake_case_deserialization() { + // Test case: Verify that DashboardYml deserializes from snake_case + // Expected: Both snake_case and camelCase fields should be accepted + + // Create JSON with snake_case fields + let json = json!({ + "name": "Test Dashboard", + "description": "This is a test dashboard", + "rows": [ + { + "items": [ + { + "id": "00000000-0000-0000-0000-000000000001" + } + ], + "row_height": 400, + "column_sizes": [12] + } + ] + }); + + // Convert to YAML and use the new method to assign IDs + let yaml = serde_yaml::to_string(&json).unwrap(); + let dashboard = DashboardYml::new(yaml).unwrap(); + + // Verify fields were properly deserialized + assert_eq!(dashboard.name, "Test Dashboard"); + assert_eq!(dashboard.description, Some("This is a test dashboard".to_string())); + assert_eq!(dashboard.rows.len(), 1); + assert_eq!(dashboard.rows[0].row_height, Some(400)); + assert_eq!(dashboard.rows[0].column_sizes, Some(vec![12])); + + // Check that a row ID was assigned + assert_eq!(dashboard.rows[0].id, Some(1)); + } + + #[test] + fn test_dashboard_yml_camel_case_deserialization() { + // Test case: Verify that DashboardYml deserializes from camelCase + // Expected: camelCase fields should be properly deserialized + + // Create JSON with camelCase fields + let json = json!({ + "name": "Test Dashboard", + "description": "This is a test dashboard", + "rows": [ + { + "items": [ + { + "id": "00000000-0000-0000-0000-000000000001" + } + ], + "rowHeight": 400, + "columnSizes": [12] + } + ] + }); + + // Convert to YAML and use the new method to assign IDs + let yaml = serde_yaml::to_string(&json).unwrap(); + let dashboard = DashboardYml::new(yaml).unwrap(); + + // Verify fields were properly deserialized + assert_eq!(dashboard.name, "Test Dashboard"); + assert_eq!(dashboard.description, Some("This is a test dashboard".to_string())); + assert_eq!(dashboard.rows.len(), 1); + assert_eq!(dashboard.rows[0].row_height, Some(400)); + assert_eq!(dashboard.rows[0].column_sizes, Some(vec![12])); + + // Check that a row ID was assigned + assert_eq!(dashboard.rows[0].id, Some(1)); + } + + #[test] + fn test_row_id_generation() { + // Test case: Verify that row IDs are properly generated + // Expected: Row IDs should increment by 1 for each row + + // Create a dashboard from YAML without row IDs + let yaml = r#" +name: Test Dashboard +description: This is a test dashboard +rows: + - items: + - id: 00000000-0000-0000-0000-000000000001 + rowHeight: 400 + columnSizes: [12] + - items: + - id: 00000000-0000-0000-0000-000000000002 + rowHeight: 320 + columnSizes: [12] + - items: + - id: 00000000-0000-0000-0000-000000000003 + rowHeight: 550 + columnSizes: [12] +"#; + + // Create dashboard using the new method (which should assign row IDs) + let dashboard = DashboardYml::new(yaml.to_string()).unwrap(); + + // Verify that row IDs were assigned in sequence + assert_eq!(dashboard.rows[0].id, Some(1)); + assert_eq!(dashboard.rows[1].id, Some(2)); + assert_eq!(dashboard.rows[2].id, Some(3)); + } + + #[test] + fn test_add_row_method() { + // Test case: Verify that the add_row method assigns the next available ID + // Expected: New rows get the next sequential ID + + // Create a dashboard with one row + let mut dashboard = DashboardYml { + name: "Test Dashboard".to_string(), + description: None, + rows: vec![ + Row { + items: vec![RowItem { id: Uuid::new_v4() }], + row_height: None, + column_sizes: None, + id: Some(1), + } + ], + }; + + // Add a second row using the add_row method + dashboard.add_row( + vec![RowItem { id: Uuid::new_v4() }], + Some(400), + Some(vec![12]), + ); + + // Add a third row + dashboard.add_row( + vec![RowItem { id: Uuid::new_v4() }], + Some(320), + None, + ); + + // Verify that row IDs were assigned in sequence + assert_eq!(dashboard.rows[0].id, Some(1)); + assert_eq!(dashboard.rows[1].id, Some(2)); + assert_eq!(dashboard.rows[2].id, Some(3)); + + // Verify that get_next_row_id returns the expected value + assert_eq!(dashboard.get_next_row_id(), 4); + } + + #[test] + fn test_non_sequential_row_ids() { + // Test case: Verify that get_next_row_id works with non-sequential IDs + // Expected: Next ID should be max(id) + 1 + + // Create a dashboard with rows that have non-sequential IDs + let dashboard = DashboardYml { + name: "Test Dashboard".to_string(), + description: None, + rows: vec![ + Row { + items: vec![RowItem { id: Uuid::new_v4() }], + row_height: None, + column_sizes: None, + id: Some(1), + }, + Row { + items: vec![RowItem { id: Uuid::new_v4() }], + row_height: None, + column_sizes: None, + id: Some(5), // Intentionally out of sequence + }, + Row { + items: vec![RowItem { id: Uuid::new_v4() }], + row_height: None, + column_sizes: None, + id: Some(3), + } + ], + }; + + // Verify that get_next_row_id returns max(id) + 1 + assert_eq!(dashboard.get_next_row_id(), 6); + } + + #[test] + fn test_explicitly_provided_id() { + // Test case: Verify that explicitly provided IDs are preserved during deserialization + // Expected: Row ID should match the provided value + + // Create JSON with an explicit ID field + let json = json!({ + "name": "Test Dashboard", + "description": "This is a test dashboard", + "rows": [ + { + "items": [ + { + "id": "00000000-0000-0000-0000-000000000001" + } + ], + "rowHeight": 400, + "columnSizes": [12], + "id": 42 // Explicitly set ID + } + ] + }); + + // Convert to YAML and use the new method + let yaml = serde_yaml::to_string(&json).unwrap(); + let dashboard = DashboardYml::new(yaml).unwrap(); + + // Verify the explicit ID was preserved + assert_eq!(dashboard.rows[0].id, Some(42)); + } +} diff --git a/api/libs/handlers/src/metrics/update_metric_handler.rs b/api/libs/handlers/src/metrics/update_metric_handler.rs index 407327844..4584187d7 100644 --- a/api/libs/handlers/src/metrics/update_metric_handler.rs +++ b/api/libs/handlers/src/metrics/update_metric_handler.rs @@ -25,6 +25,26 @@ pub struct UpdateMetricRequest { } /// Handler to update a metric by ID +/// +/// This handler updates a metric file in the database and increments its version number. +/// Each time a metric is updated, the previous version is saved in the version history. +/// +/// # Arguments +/// * `metric_id` - The UUID of the metric to update +/// * `user_id` - The UUID of the user making the update +/// * `request` - The update request containing the fields to modify +/// +/// # Returns +/// * `Result` - The updated metric on success, or an error +/// +/// # Versioning +/// The function automatically handles versioning: +/// 1. Retrieves the current metric and extracts its content +/// 2. Updates the content based on the request parameters +/// 3. Increments the version number (based on the number of existing versions) +/// 4. Adds the updated content to the version history with the new version number +/// 5. Saves both the updated content and version history to the database +/// pub async fn update_metric_handler( metric_id: &Uuid, user_id: &Uuid, diff --git a/api/libs/handlers/tests/metrics/update_metric_test.rs b/api/libs/handlers/tests/metrics/update_metric_test.rs index 341539255..4de7fa57e 100644 --- a/api/libs/handlers/tests/metrics/update_metric_test.rs +++ b/api/libs/handlers/tests/metrics/update_metric_test.rs @@ -85,6 +85,18 @@ async fn test_update_metric_integration() -> Result<()> { assert!(db_metric.version_history.0.contains_key(&"1".to_string())); assert!(db_metric.version_history.0.contains_key(&"2".to_string())); + // Get the latest version from the version history + let latest_version = db_metric.version_history.get_latest_version().unwrap(); + assert_eq!(latest_version.version_number, 2); + + // Verify the latest version's content matches the current content + if let database::types::VersionContent::MetricYml(latest_content) = &latest_version.content { + assert_eq!(latest_content.time_frame, "weekly"); + assert_eq!(latest_content.description, Some("Updated test description".to_string())); + } else { + panic!("Expected MetricYml content in version history"); + } + println!("Update metric test passed with ID: {}", metric_id); }, Err(e) => { @@ -133,7 +145,7 @@ async fn test_update_nonexistent_metric() -> Result<()> { Ok(()) } -/// Test updating specific metric fields one at a time +/// Test updating specific metric fields one at a time and verify version increments properly #[tokio::test] async fn test_update_specific_metric_fields() -> Result<()> { // Setup test environment @@ -174,6 +186,25 @@ async fn test_update_specific_metric_fields() -> Result<()> { // Verify other fields were not changed assert_eq!(metric.time_frame, "daily"); assert_eq!(metric.status, Verification::NotRequested); + + // Verify version number was incremented + assert_eq!(metric.version_number, 2); + + // Verify in the database + let mut conn = get_pg_pool().get().await?; + let db_metric = metric_files::table + .filter(metric_files::id.eq(metric_id)) + .first::(&mut conn) + .await?; + + // Check version history has exactly 2 versions + assert_eq!(db_metric.version_history.0.len(), 2); + assert!(db_metric.version_history.0.contains_key(&"1".to_string())); + assert!(db_metric.version_history.0.contains_key(&"2".to_string())); + + // Get the latest version + let latest_version = db_metric.version_history.get_latest_version().unwrap(); + assert_eq!(latest_version.version_number, 2); }, Err(e) => { cleanup_test_data(Some(metric_id), None).await?; @@ -198,6 +229,26 @@ async fn test_update_specific_metric_fields() -> Result<()> { // Verify title remains from previous update assert_eq!(metric.title, "Title Only Update"); + + // Verify version number increased again + assert_eq!(metric.version_number, 3); + + // Verify in the database + let mut conn = get_pg_pool().get().await?; + let db_metric = metric_files::table + .filter(metric_files::id.eq(metric_id)) + .first::(&mut conn) + .await?; + + // Check version history has exactly 3 versions + assert_eq!(db_metric.version_history.0.len(), 3); + assert!(db_metric.version_history.0.contains_key(&"1".to_string())); + assert!(db_metric.version_history.0.contains_key(&"2".to_string())); + assert!(db_metric.version_history.0.contains_key(&"3".to_string())); + + // Get the latest version + let latest_version = db_metric.version_history.get_latest_version().unwrap(); + assert_eq!(latest_version.version_number, 3); }, Err(e) => { cleanup_test_data(Some(metric_id), None).await?; @@ -223,6 +274,40 @@ async fn test_update_specific_metric_fields() -> Result<()> { // Verify other fields remain from previous updates assert_eq!(metric.title, "Title Only Update"); assert_eq!(metric.status, Verification::Verified); + + // Verify version number increased to 4 + assert_eq!(metric.version_number, 4); + + // Verify in the database + let mut conn = get_pg_pool().get().await?; + let db_metric = metric_files::table + .filter(metric_files::id.eq(metric_id)) + .first::(&mut conn) + .await?; + + // Check version history now has 4 versions + assert_eq!(db_metric.version_history.0.len(), 4); + + // Verify all version numbers are present + for i in 1..=4 { + assert!(db_metric.version_history.0.contains_key(&i.to_string()), + "Version {} missing from history", i); + } + + // Get the latest version + let latest_version = db_metric.version_history.get_latest_version().unwrap(); + assert_eq!(latest_version.version_number, 4); + + // Check the content of the latest version + if let database::types::VersionContent::MetricYml(latest_content) = &latest_version.content { + assert_eq!(latest_content.time_frame, "monthly"); + + // The title should be preserved from earlier updates + let yaml = serde_yaml::to_string(latest_content).unwrap(); + assert!(yaml.contains("Title Only Update")); + } else { + panic!("Expected MetricYml content in version history"); + } }, Err(e) => { cleanup_test_data(Some(metric_id), None).await?;