package migration import ( "testing" ) // copyMap creates a deep copy of a map[string]interface{} for testing func copyMap(src map[string]interface{}) map[string]interface{} { dst := make(map[string]interface{}) for k, v := range src { dst[k] = v } return dst } // assertPropertyRemoved checks that a property has been removed from the map func assertPropertyRemoved(t *testing.T, obj map[string]interface{}, key string) { if _, exists := obj[key]; exists { t.Errorf("Property %s should have been removed but still exists", key) } } // assertPropertyValue checks that a property has the expected value func assertPropertyValue(t *testing.T, obj map[string]interface{}, key string, expected interface{}) { if actual, exists := obj[key]; !exists { t.Errorf("Property %s should exist but is missing", key) } else if !compareValues(actual, expected) { t.Errorf("Property %s has wrong value. Expected: %v, Got: %v", key, expected, actual) } } // assertPropertiesExist checks that all expected properties exist with correct values func assertPropertiesExist(t *testing.T, obj map[string]interface{}, expected map[string]interface{}) { for key, expectedValue := range expected { assertPropertyValue(t, obj, key, expectedValue) } } // assertPropertiesRemoved checks that all specified properties have been removed func assertPropertiesRemoved(t *testing.T, obj map[string]interface{}, unwantedProps []string) { for _, prop := range unwantedProps { assertPropertyRemoved(t, obj, prop) } } func TestFrontendDefaultsCleanup(t *testing.T) { tests := []struct { name string input map[string]interface{} expected map[string]interface{} }{ { name: "remove_dashboard_id_null", input: map[string]interface{}{ "id": nil, "title": "Test Dashboard", }, expected: map[string]interface{}{ "title": "Test Dashboard", }, }, { name: "remove_version_property", input: map[string]interface{}{ "version": float64(123), "title": "Test Dashboard", }, expected: map[string]interface{}{ "title": "Test Dashboard", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { dashboard := copyMap(tt.input) cleanupDashboardDefaults(dashboard) // Check that expected properties exist for key, expectedValue := range tt.expected { assertPropertyValue(t, dashboard, key, expectedValue) } // Check that version is always removed assertPropertyRemoved(t, dashboard, "version") // Check that id is removed when it was null in input if idValue, exists := tt.input["id"]; exists && idValue == nil { assertPropertyRemoved(t, dashboard, "id") } }) } } func TestCleanupDashboardForSave(t *testing.T) { tests := []struct { name string input map[string]interface{} expected map[string]interface{} }{ { name: "remove_non_persisted_properties", input: map[string]interface{}{ "title": "Test Dashboard", "meta": map[string]interface{}{"canEdit": true}, "events": map[string]interface{}{}, "originalTime": "2023-01-01", "variables": map[string]interface{}{"list": []interface{}{}}, }, expected: map[string]interface{}{ "title": "Test Dashboard", // meta, events, originalTime, variables should be removed }, }, { name: "remove_null_values", input: map[string]interface{}{ "title": "Test Dashboard", "gnetId": nil, }, expected: map[string]interface{}{ "title": "Test Dashboard", // gnetId should be removed }, }, { name: "cleanup_templating_and_panels", input: map[string]interface{}{ "title": "Test Dashboard", "templating": map[string]interface{}{ "list": []interface{}{ map[string]interface{}{ "name": "var1", "index": -1, // Should be removed }, }, }, "panels": []interface{}{ map[string]interface{}{ "type": "timeseries", "title": "", // Default value, should be removed "events": map[string]interface{}{}, // Not persisted, should be removed }, }, }, expected: map[string]interface{}{ "title": "Test Dashboard", "templating": map[string]interface{}{ "list": []interface{}{ map[string]interface{}{ "name": "var1", // index should be removed }, }, }, "panels": []interface{}{ map[string]interface{}{ "type": "timeseries", // title and events should be removed }, }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { dashboard := copyMap(tt.input) cleanupDashboardForSave(dashboard) // Verify expected properties exist assertPropertiesExist(t, dashboard, tt.expected) // Verify unwanted properties are removed unwantedProps := []string{"meta", "events", "originalTime", "variables", "gnetId"} assertPropertiesRemoved(t, dashboard, unwantedProps) }) } } func TestCleanupVariable(t *testing.T) { tests := []struct { name string input map[string]interface{} expected map[string]interface{} }{ { name: "cleanup_query_variable", input: map[string]interface{}{ "type": "query", "name": "var1", "datasource": nil, "index": -1, }, expected: map[string]interface{}{ "type": "query", "name": "var1", "options": []interface{}{}, // datasource and index should be removed }, }, { name: "cleanup_constant_variable", input: map[string]interface{}{ "type": "constant", "name": "var2", "value": "constant_value", "options": []interface{}{"option1"}, "index": -1, }, expected: map[string]interface{}{ "type": "constant", "name": "var2", "value": "constant_value", // options and index should be removed }, }, { name: "cleanup_datasource_variable", input: map[string]interface{}{ "type": "datasource", "name": "var3", "index": -1, }, expected: map[string]interface{}{ "type": "datasource", "name": "var3", "options": []interface{}{}, // index should be removed }, }, { name: "cleanup_custom_variable", input: map[string]interface{}{ "type": "custom", "name": "var4", "options": []interface{}{"option1", "option2"}, "index": -1, }, expected: map[string]interface{}{ "type": "custom", "name": "var4", "options": []interface{}{"option1", "option2"}, // index should be removed }, }, { name: "cleanup_textbox_variable", input: map[string]interface{}{ "type": "textbox", "name": "var5", "value": "text_value", "index": -1, }, expected: map[string]interface{}{ "type": "textbox", "name": "var5", "value": "text_value", // index should be removed }, }, { name: "cleanup_adhoc_variable", input: map[string]interface{}{ "type": "adhoc", "name": "var6", "filters": []interface{}{}, "index": -1, }, expected: map[string]interface{}{ "type": "adhoc", "name": "var6", "filters": []interface{}{}, // index should be removed }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { variable := make(map[string]interface{}) for k, v := range tt.input { variable[k] = v } cleanupVariable(variable) // Verify expected properties exist assertPropertiesExist(t, variable, tt.expected) // Verify unwanted properties are removed assertPropertyRemoved(t, variable, "index") }) } } func TestCleanupPanels(t *testing.T) { tests := []struct { name string input map[string]interface{} expected map[string]interface{} }{ { name: "filter_repeated_panels", input: map[string]interface{}{ "panels": []interface{}{ map[string]interface{}{ "type": "timeseries", "title": "Panel 1", }, map[string]interface{}{ "type": "table", "title": "Panel 2", "repeatPanelId": 1, // Should be filtered out }, map[string]interface{}{ "type": "graph", "title": "Panel 3", "repeatedByRow": "row1", // Should be filtered out }, map[string]interface{}{ "type": "stat", "title": "Panel 4", }, }, }, expected: map[string]interface{}{ "panels": []interface{}{ map[string]interface{}{ "type": "timeseries", "title": "Panel 1", }, map[string]interface{}{ "type": "stat", "title": "Panel 4", }, }, }, }, { name: "cleanup_panel_properties", input: map[string]interface{}{ "panels": []interface{}{ map[string]interface{}{ "type": "timeseries", "title": "", // Default value, should be removed "events": map[string]interface{}{}, // Not persisted, should be removed "scopedVars": map[string]interface{}{"var1": "value1"}, // Should be removed }, }, }, expected: map[string]interface{}{ "panels": []interface{}{ map[string]interface{}{ "type": "timeseries", // title, events, scopedVars should be removed }, }, }, }, { name: "ensure_panels_property_exists", input: map[string]interface{}{ "title": "Test Dashboard", }, expected: map[string]interface{}{ "title": "Test Dashboard", "panels": []interface{}{}, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { dashboard := copyMap(tt.input) cleanupPanels(dashboard) // Verify expected properties exist assertPropertiesExist(t, dashboard, tt.expected) }) } } func TestCleanupRowPanelProperties(t *testing.T) { tests := []struct { name string input map[string]interface{} expected map[string]interface{} }{ { name: "remove_empty_repeat_from_row_panel", input: map[string]interface{}{ "type": "row", "title": "Row Panel", "repeat": "", // Empty string, should be removed "collapsed": false, }, expected: map[string]interface{}{ "type": "row", "title": "Row Panel", "collapsed": false, // Should be preserved // repeat should be removed }, }, { name: "preserve_non_empty_repeat_in_row_panel", input: map[string]interface{}{ "type": "row", "title": "Row Panel", "repeat": "server", // Non-empty, should be preserved "collapsed": false, }, expected: map[string]interface{}{ "type": "row", "title": "Row Panel", "repeat": "server", "collapsed": false, }, }, { name: "no_changes_for_non_row_panel", input: map[string]interface{}{ "type": "timeseries", "title": "Timeseries Panel", "repeat": "", // Should not be removed for non-row panels }, expected: map[string]interface{}{ "type": "timeseries", "title": "Timeseries Panel", "repeat": "", // Should be preserved }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { panel := make(map[string]interface{}) for k, v := range tt.input { panel[k] = v } cleanupRowPanelProperties(panel) // Verify expected properties exist assertPropertiesExist(t, panel, tt.expected) }) } } func TestCleanupFieldConfigDefaults(t *testing.T) { tests := []struct { name string input map[string]interface{} panel map[string]interface{} expected map[string]interface{} }{ { name: "remove_empty_custom_from_migrated_singlestat", input: map[string]interface{}{ "custom": map[string]interface{}{}, "color": map[string]interface{}{ "mode": "fixed", "fixedColor": "red", }, "mappings": []interface{}{}, }, panel: map[string]interface{}{ "autoMigrateFrom": "singlestat", }, expected: map[string]interface{}{ "color": map[string]interface{}{ "mode": "fixed", "fixedColor": "red", }, "mappings": []interface{}{}, // custom should be removed }, }, { name: "preserve_empty_custom_from_non_migrated_panel", input: map[string]interface{}{ "custom": map[string]interface{}{}, "color": map[string]interface{}{ "mode": "fixed", "fixedColor": "red", }, }, panel: map[string]interface{}{ "type": "timeseries", }, expected: map[string]interface{}{ "custom": map[string]interface{}{}, // Should be preserved "color": map[string]interface{}{ "mode": "fixed", "fixedColor": "red", }, }, }, { name: "preserve_non_empty_custom", input: map[string]interface{}{ "custom": map[string]interface{}{ "displayMode": "list", }, "color": map[string]interface{}{ "mode": "fixed", "fixedColor": "red", }, }, panel: map[string]interface{}{ "autoMigrateFrom": "singlestat", }, expected: map[string]interface{}{ "custom": map[string]interface{}{ "displayMode": "list", }, "color": map[string]interface{}{ "mode": "fixed", "fixedColor": "red", }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { defaults := make(map[string]interface{}) for k, v := range tt.input { defaults[k] = v } panel := make(map[string]interface{}) for k, v := range tt.panel { panel[k] = v } cleanupFieldConfigDefaults(defaults, panel) // Verify expected properties exist assertPropertiesExist(t, defaults, tt.expected) }) } } // TestApplyFrontendDefaults tests the core dashboard defaults application logic func TestApplyFrontendDefaults(t *testing.T) { tests := []struct { name string input map[string]interface{} expected map[string]interface{} }{ { name: "apply_dashboard_defaults", input: map[string]interface{}{ "schemaVersion": float64(42), }, expected: map[string]interface{}{ "title": "No Title", "tags": []interface{}{}, "timezone": "", "weekStart": "", "editable": true, "graphTooltip": float64(0), "time": map[string]interface{}{"from": "now-6h", "to": "now"}, "timepicker": map[string]interface{}{}, "schemaVersion": float64(42), // Preserved from input "fiscalYearStartMonth": float64(0), // version is NOT set as default - managed by backend metadata "links": []interface{}{}, }, }, { name: "preserve_existing_values", input: map[string]interface{}{ "title": "Custom Title", "editable": false, "tags": []interface{}{"tag1", "tag2"}, }, expected: map[string]interface{}{ "title": "Custom Title", // Preserved "editable": false, // Preserved "tags": []interface{}{"tag1", "tag2"}, // Preserved "timezone": "", "weekStart": "", "graphTooltip": float64(0), "time": map[string]interface{}{"from": "now-6h", "to": "now"}, "timepicker": map[string]interface{}{}, "schemaVersion": float64(0), "fiscalYearStartMonth": float64(0), // version is NOT set as default - managed by backend metadata "links": []interface{}{}, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { dashboard := copyMap(tt.input) applyFrontendDefaults(dashboard) // Verify expected properties exist for key, expectedValue := range tt.expected { if actualValue, exists := dashboard[key]; !exists { t.Errorf("Property %s should exist but is missing", key) } else if !compareValues(actualValue, expectedValue) { t.Errorf("Property %s has wrong value. Expected: %v (type: %T), Got: %v (type: %T)", key, expectedValue, expectedValue, actualValue, actualValue) } } // Verify that version is NOT set as default (unless it was in input) if _, hadVersionInInput := tt.input["version"]; !hadVersionInInput { if _, hasVersion := dashboard["version"]; hasVersion { t.Errorf("Property version should not be set as default but was found") } } }) } } // TestApplyPanelDefaults tests the core panel defaults application logic func TestApplyPanelDefaults(t *testing.T) { tests := []struct { name string input map[string]interface{} expected map[string]interface{} }{ { name: "apply_panel_defaults", input: map[string]interface{}{ "type": "timeseries", }, expected: map[string]interface{}{ "type": "timeseries", "gridPos": map[string]interface{}{ "x": float64(0), "y": float64(0), "h": float64(3), "w": float64(6), }, "targets": []interface{}{ map[string]interface{}{"refId": "A"}, }, "cachedPluginOptions": map[string]interface{}{}, "transparent": false, "options": map[string]interface{}{}, "links": []interface{}{}, "fieldConfig": map[string]interface{}{ "defaults": map[string]interface{}{}, "overrides": []interface{}{}, }, "title": "", }, }, { name: "preserve_existing_panel_values", input: map[string]interface{}{ "type": "table", "title": "Custom Panel", "transparent": true, "gridPos": map[string]interface{}{ "x": float64(12), "y": float64(0), "h": float64(8), "w": float64(12), }, }, expected: map[string]interface{}{ "type": "table", "title": "Custom Panel", // Preserved "transparent": true, // Preserved "gridPos": map[string]interface{}{ // Preserved "x": float64(12), "y": float64(0), "h": float64(8), "w": float64(12), }, "targets": []interface{}{ map[string]interface{}{"refId": "A"}, }, "cachedPluginOptions": map[string]interface{}{}, "options": map[string]interface{}{}, "links": []interface{}{}, "fieldConfig": map[string]interface{}{ "defaults": map[string]interface{}{}, "overrides": []interface{}{}, }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { panel := make(map[string]interface{}) for k, v := range tt.input { panel[k] = v } applyPanelDefaults(panel) // Verify expected properties exist assertPropertiesExist(t, panel, tt.expected) }) } } // TestCleanupPanelForSave tests the core panel cleanup logic for save model func TestTransformationsArrayContextAwareLogic(t *testing.T) { tests := []struct { name string input map[string]interface{} isNested bool hasOriginal bool expected map[string]interface{} }{ { name: "top_level_panel_removes_empty_transformations", input: map[string]interface{}{ "type": "timeseries", "title": "Top-level Panel", "transformations": []interface{}{}, }, isNested: false, hasOriginal: true, // Had transformations in original input expected: map[string]interface{}{ "type": "timeseries", "title": "Top-level Panel", // transformations removed for top-level panels }, }, { name: "nested_panel_preserves_empty_transformations", input: map[string]interface{}{ "type": "timeseries", "title": "Nested Panel", "transformations": []interface{}{}, }, isNested: true, hasOriginal: true, // Had transformations in original input expected: map[string]interface{}{ "type": "timeseries", "title": "Nested Panel", "transformations": []interface{}{}, // preserved for nested panels }, }, { name: "nested_panel_removes_added_transformations", input: map[string]interface{}{ "type": "table", "title": "Nested Panel Without Original", "transformations": []interface{}{}, }, isNested: true, hasOriginal: false, // Did NOT have transformations in original input expected: map[string]interface{}{ "type": "table", "title": "Nested Panel Without Original", // transformations removed - wasn't in original input }, }, { name: "preserve_non_empty_transformations", input: map[string]interface{}{ "type": "timeseries", "title": "Panel with actual transformations", "transformations": []interface{}{ map[string]interface{}{ "id": "reduce", "options": map[string]interface{}{ "reducers": []interface{}{"mean"}, }, }, }, }, isNested: false, hasOriginal: true, expected: map[string]interface{}{ "type": "timeseries", "title": "Panel with actual transformations", "transformations": []interface{}{ map[string]interface{}{ "id": "reduce", "options": map[string]interface{}{ "reducers": []interface{}{"mean"}, }, }, }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Create a copy of input for testing panel := make(map[string]interface{}) for k, v := range tt.input { panel[k] = v } // Set the original transformations marker if needed if tt.hasOriginal { panel["_originallyHadTransformations"] = true } // Apply the cleanup logic cleanupPanelForSaveWithContext(panel, tt.isNested) // Verify the result if !compareValues(panel, tt.expected) { t.Errorf("Test %s failed.\nExpected: %+v\nGot: %+v", tt.name, tt.expected, panel) } }) } } func TestTrackOriginalTransformations(t *testing.T) { tests := []struct { name string input map[string]interface{} expected map[string]interface{} }{ { name: "track_top_level_panel_with_transformations", input: map[string]interface{}{ "panels": []interface{}{ map[string]interface{}{ "id": 1, "type": "timeseries", "title": "Panel with transformations", "transformations": []interface{}{}, }, map[string]interface{}{ "id": 2, "type": "table", "title": "Panel without transformations", }, }, }, expected: map[string]interface{}{ "panels": []interface{}{ map[string]interface{}{ "id": 1, "type": "timeseries", "title": "Panel with transformations", "transformations": []interface{}{}, "_originallyHadTransformations": true, // marker added }, map[string]interface{}{ "id": 2, "type": "table", "title": "Panel without transformations", // no marker added }, }, }, }, { name: "track_nested_panels_in_row", input: map[string]interface{}{ "panels": []interface{}{ map[string]interface{}{ "id": 1, "type": "row", "title": "Row Panel", "panels": []interface{}{ map[string]interface{}{ "id": 10, "type": "timeseries", "title": "Nested Panel with transformations", "transformations": []interface{}{}, }, map[string]interface{}{ "id": 11, "type": "table", "title": "Nested Panel without transformations", }, }, }, }, }, expected: map[string]interface{}{ "panels": []interface{}{ map[string]interface{}{ "id": 1, "type": "row", "title": "Row Panel", "panels": []interface{}{ map[string]interface{}{ "id": 10, "type": "timeseries", "title": "Nested Panel with transformations", "transformations": []interface{}{}, "_originallyHadTransformations": true, // marker added to nested panel }, map[string]interface{}{ "id": 11, "type": "table", "title": "Nested Panel without transformations", // no marker added }, }, }, }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Create a deep copy of input for testing dashboard := deepCopy(tt.input).(map[string]interface{}) // Apply the tracking logic trackOriginalTransformations(dashboard) // Verify the result if !compareValues(dashboard, tt.expected) { t.Errorf("Test %s failed.\nExpected: %+v\nGot: %+v", tt.name, tt.expected, dashboard) } }) } } func TestCleanupPanelForSave(t *testing.T) { tests := []struct { name string input map[string]interface{} expected map[string]interface{} }{ { name: "remove_not_persisted_properties", input: map[string]interface{}{ "type": "timeseries", "title": "Test Panel", "events": map[string]interface{}{}, "isViewing": true, "cachedPluginOptions": map[string]interface{}{"test": "value"}, "scopedVars": map[string]interface{}{"var1": "value1"}, }, expected: map[string]interface{}{ "type": "timeseries", "title": "Test Panel", // events, isViewing, cachedPluginOptions, scopedVars should be removed }, }, { name: "remove_default_values", input: map[string]interface{}{ "type": "table", "title": "", // Default value "transparent": false, // Default value "options": map[string]interface{}{}, // Default value "links": []interface{}{}, // Default value }, expected: map[string]interface{}{ "type": "table", // title, transparent, options, links should be removed as they match defaults }, }, { name: "preserve_non_default_values", input: map[string]interface{}{ "type": "timeseries", // Use timeseries to avoid auto-migration "title": "Custom Title", // Non-default "transparent": true, // Non-default "options": map[string]interface{}{"custom": "value"}, // Non-default }, expected: map[string]interface{}{ "type": "timeseries", "title": "Custom Title", "transparent": true, "options": map[string]interface{}{"custom": "value"}, }, }, { name: "remove_empty_transformations_array_top_level", input: map[string]interface{}{ "type": "timeseries", "title": "Test Panel", "transformations": []interface{}{}, // Empty array should be removed for top-level panels }, expected: map[string]interface{}{ "type": "timeseries", "title": "Test Panel", // transformations should be removed for top-level panels }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { panel := make(map[string]interface{}) for k, v := range tt.input { panel[k] = v } cleanupPanelForSaveWithContext(panel, false) // Verify expected properties exist assertPropertiesExist(t, panel, tt.expected) // Verify unwanted properties are removed unwantedProps := []string{"events", "isViewing", "cachedPluginOptions", "scopedVars"} assertPropertiesRemoved(t, panel, unwantedProps) }) } } // TestApplyPanelAutoMigration tests the core panel auto-migration logic func TestApplyPanelAutoMigration(t *testing.T) { tests := []struct { name string input map[string]interface{} expected map[string]interface{} }{ { name: "migrate_graph_to_timeseries", input: map[string]interface{}{ "type": "graph", }, expected: map[string]interface{}{ "type": "timeseries", "autoMigrateFrom": "graph", }, }, { name: "migrate_graph_to_barchart", input: map[string]interface{}{ "type": "graph", "xaxis": map[string]interface{}{ "mode": "series", }, }, expected: map[string]interface{}{ "type": "barchart", "autoMigrateFrom": "graph", }, }, { name: "migrate_graph_to_bargauge", input: map[string]interface{}{ "type": "graph", "xaxis": map[string]interface{}{ "mode": "series", }, "legend": map[string]interface{}{ "values": true, }, }, expected: map[string]interface{}{ "type": "bargauge", "autoMigrateFrom": "graph", }, }, { name: "migrate_singlestat_to_stat", input: map[string]interface{}{ "type": "singlestat", }, expected: map[string]interface{}{ "type": "stat", "autoMigrateFrom": "singlestat", }, }, { name: "migrate_table_old_to_table", input: map[string]interface{}{ "type": "table-old", }, expected: map[string]interface{}{ "type": "table", "autoMigrateFrom": "table-old", }, }, { name: "no_migration_for_modern_panels", input: map[string]interface{}{ "type": "timeseries", }, expected: map[string]interface{}{ "type": "timeseries", // No autoMigrateFrom should be added }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { panel := make(map[string]interface{}) for k, v := range tt.input { panel[k] = v } applyPanelAutoMigration(panel) // Verify expected properties exist assertPropertiesExist(t, panel, tt.expected) // Verify no unexpected autoMigrateFrom is added if _, hasAutoMigrate := tt.expected["autoMigrateFrom"]; !hasAutoMigrate { if _, exists := panel["autoMigrateFrom"]; exists { t.Errorf("autoMigrateFrom should not be added for this panel type") } } }) } } // TestRemoveNullValuesRecursively tests the core null value removal logic func TestRemoveNullValuesRecursively(t *testing.T) { tests := []struct { name string input interface{} expected interface{} }{ { name: "remove_null_values_from_map", input: map[string]interface{}{ "title": "Test", "id": nil, "config": map[string]interface{}{ "enabled": true, "value": nil, }, }, expected: map[string]interface{}{ "title": "Test", "config": map[string]interface{}{ "enabled": true, }, }, }, { name: "process_array_elements", input: []interface{}{ "item1", nil, "item2", map[string]interface{}{ "key": "value", "null": nil, }, }, expected: []interface{}{ "item1", nil, // Null values in arrays are NOT removed by the current implementation "item2", map[string]interface{}{ "key": "value", // null key should be removed from nested map }, }, }, { name: "preserve_non_null_values", input: map[string]interface{}{ "string": "value", "number": 42, "bool": true, "array": []interface{}{1, 2, 3}, }, expected: map[string]interface{}{ "string": "value", "number": 42, "bool": true, "array": []interface{}{1, 2, 3}, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Create a copy of the input to avoid modifying the original data := deepCopy(tt.input) removeNullValuesRecursively(data) if !compareValues(data, tt.expected) { t.Errorf("Null value removal failed. Expected: %v, Got: %v", tt.expected, data) } }) } } // TestIsEqual tests the core value equality comparison logic func TestIsEqual(t *testing.T) { tests := []struct { name string a interface{} b interface{} expected bool }{ { name: "equal_strings", a: "test", b: "test", expected: true, }, { name: "different_strings", a: "test1", b: "test2", expected: false, }, { name: "equal_numbers", a: float64(42), b: float64(42), expected: true, }, { name: "equal_booleans", a: true, b: true, expected: true, }, { name: "equal_arrays", a: []interface{}{float64(1), float64(2), float64(3)}, b: []interface{}{float64(1), float64(2), float64(3)}, expected: true, }, { name: "different_arrays", a: []interface{}{float64(1), float64(2), float64(3)}, b: []interface{}{float64(1), float64(2), float64(4)}, expected: false, }, { name: "equal_maps", a: map[string]interface{}{"key": "value"}, b: map[string]interface{}{"key": "value"}, expected: true, }, { name: "different_maps", a: map[string]interface{}{"key": "value1"}, b: map[string]interface{}{"key": "value2"}, expected: false, }, { name: "nil_values", a: nil, b: nil, expected: true, }, { name: "nil_and_value", a: nil, b: "test", expected: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := isEqual(tt.a, tt.b) if result != tt.expected { t.Errorf("isEqual(%v, %v) = %v, expected %v", tt.a, tt.b, result, tt.expected) } }) } } // Helper function to compare values recursively func compareValues(actual, expected interface{}) bool { if actual == nil && expected == nil { return true } if actual == nil || expected == nil { return false } actualMap, actualOk := actual.(map[string]interface{}) expectedMap, expectedOk := expected.(map[string]interface{}) if actualOk && expectedOk { if len(actualMap) != len(expectedMap) { return false } for key, expectedValue := range expectedMap { actualValue, exists := actualMap[key] if !exists || !compareValues(actualValue, expectedValue) { return false } } return true } actualSlice, actualSliceOk := actual.([]interface{}) expectedSlice, expectedSliceOk := expected.([]interface{}) if actualSliceOk && expectedSliceOk { if len(actualSlice) != len(expectedSlice) { return false } for i, expectedValue := range expectedSlice { if !compareValues(actualSlice[i], expectedValue) { return false } } return true } return actual == expected } // Helper function to deep copy interface{} func deepCopy(src interface{}) interface{} { switch v := src.(type) { case map[string]interface{}: dst := make(map[string]interface{}) for k, val := range v { dst[k] = deepCopy(val) } return dst case []interface{}: dst := make([]interface{}, len(v)) for i, val := range v { dst[i] = deepCopy(val) } return dst default: return src } } func TestEnsureTemplatingExists(t *testing.T) { tests := []struct { name string input map[string]interface{} expected map[string]interface{} }{ { name: "create_templating_when_missing", input: map[string]interface{}{ "title": "Test Dashboard", }, expected: map[string]interface{}{ "title": "Test Dashboard", "templating": map[string]interface{}{ "list": []interface{}{}, }, }, }, { name: "add_list_to_existing_templating", input: map[string]interface{}{ "templating": map[string]interface{}{ "enable": true, }, }, expected: map[string]interface{}{ "templating": map[string]interface{}{ "enable": true, "list": []interface{}{}, }, }, }, { name: "preserve_existing_templating", input: map[string]interface{}{ "templating": map[string]interface{}{ "list": []interface{}{ map[string]interface{}{"name": "var1"}, }, }, }, expected: map[string]interface{}{ "templating": map[string]interface{}{ "list": []interface{}{ map[string]interface{}{"name": "var1"}, }, }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { dashboard := copyMap(tt.input) ensureTemplatingExists(dashboard) // Verify expected properties exist assertPropertiesExist(t, dashboard, tt.expected) }) } } func TestEnsureAnnotationsExist(t *testing.T) { tests := []struct { name string input map[string]interface{} expected map[string]interface{} }{ { name: "create_annotations_when_missing", input: map[string]interface{}{ "title": "Test Dashboard", }, expected: map[string]interface{}{ "title": "Test Dashboard", "annotations": map[string]interface{}{ "list": []interface{}{}, }, }, }, { name: "add_list_to_existing_annotations", input: map[string]interface{}{ "annotations": map[string]interface{}{ "enable": true, }, }, expected: map[string]interface{}{ "annotations": map[string]interface{}{ "enable": true, "list": []interface{}{}, }, }, }, { name: "preserve_existing_annotations", input: map[string]interface{}{ "annotations": map[string]interface{}{ "list": []interface{}{ map[string]interface{}{"name": "annotation1"}, }, }, }, expected: map[string]interface{}{ "annotations": map[string]interface{}{ "list": []interface{}{ map[string]interface{}{"name": "annotation1"}, }, }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { dashboard := copyMap(tt.input) ensureAnnotationsExist(dashboard) // Verify expected properties exist assertPropertiesExist(t, dashboard, tt.expected) }) } } func TestEnsurePanelsHaveUniqueIds(t *testing.T) { tests := []struct { name string input map[string]interface{} expected map[string]interface{} }{ { name: "assign_ids_to_panels_without_ids", input: map[string]interface{}{ "panels": []interface{}{ map[string]interface{}{"type": "timeseries"}, map[string]interface{}{"type": "table"}, }, }, expected: map[string]interface{}{ "panels": []interface{}{ map[string]interface{}{"type": "timeseries", "id": float64(1)}, map[string]interface{}{"type": "table", "id": float64(2)}, }, }, }, { name: "fix_duplicate_ids", input: map[string]interface{}{ "panels": []interface{}{ map[string]interface{}{"type": "timeseries", "id": float64(1)}, map[string]interface{}{"type": "table", "id": float64(1)}, // Duplicate map[string]interface{}{"type": "graph", "id": float64(2)}, }, }, expected: map[string]interface{}{ "panels": []interface{}{ map[string]interface{}{"type": "timeseries", "id": float64(1)}, map[string]interface{}{"type": "table", "id": float64(3)}, // Fixed duplicate map[string]interface{}{"type": "graph", "id": float64(2)}, }, }, }, { name: "preserve_valid_ids", input: map[string]interface{}{ "panels": []interface{}{ map[string]interface{}{"type": "timeseries", "id": float64(5)}, map[string]interface{}{"type": "table", "id": float64(10)}, }, }, expected: map[string]interface{}{ "panels": []interface{}{ map[string]interface{}{"type": "timeseries", "id": float64(5)}, map[string]interface{}{"type": "table", "id": float64(10)}, }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { dashboard := copyMap(tt.input) ensurePanelsHaveUniqueIds(dashboard) // Verify expected properties exist assertPropertiesExist(t, dashboard, tt.expected) }) } } func TestEnsureQueryIds(t *testing.T) { tests := []struct { name string input map[string]interface{} expected map[string]interface{} }{ { name: "assign_refIds_to_targets_without_refIds", input: map[string]interface{}{ "targets": []interface{}{ map[string]interface{}{"expr": "up"}, map[string]interface{}{"expr": "rate(up[5m])"}, }, }, expected: map[string]interface{}{ "targets": []interface{}{ map[string]interface{}{"expr": "up", "refId": "A"}, map[string]interface{}{"expr": "rate(up[5m])", "refId": "B"}, }, }, }, { name: "preserve_existing_refIds", input: map[string]interface{}{ "targets": []interface{}{ map[string]interface{}{"expr": "up", "refId": "A"}, map[string]interface{}{"expr": "rate(up[5m])", "refId": "B"}, }, }, expected: map[string]interface{}{ "targets": []interface{}{ map[string]interface{}{"expr": "up", "refId": "A"}, map[string]interface{}{"expr": "rate(up[5m])", "refId": "B"}, }, }, }, { name: "assign_refIds_to_mixed_targets", input: map[string]interface{}{ "targets": []interface{}{ map[string]interface{}{"expr": "up", "refId": "A"}, map[string]interface{}{"expr": "rate(up[5m])"}, // Missing refId map[string]interface{}{"expr": "sum(up)", "refId": "C"}, }, }, expected: map[string]interface{}{ "targets": []interface{}{ map[string]interface{}{"expr": "up", "refId": "A"}, map[string]interface{}{"expr": "rate(up[5m])", "refId": "B"}, // Assigned map[string]interface{}{"expr": "sum(up)", "refId": "C"}, }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { panel := make(map[string]interface{}) for k, v := range tt.input { panel[k] = v } ensureQueryIds(panel) // Verify expected properties exist assertPropertiesExist(t, panel, tt.expected) }) } } func TestAddBuiltInAnnotationQuery(t *testing.T) { tests := []struct { name string input map[string]interface{} expected map[string]interface{} }{ { name: "add_built_in_annotation_when_none_exists", input: map[string]interface{}{ "annotations": map[string]interface{}{ "list": []interface{}{}, }, }, expected: map[string]interface{}{ "annotations": map[string]interface{}{ "list": []interface{}{ map[string]interface{}{ "datasource": map[string]interface{}{ "uid": "-- Grafana --", "type": "grafana", }, "name": "Annotations & Alerts", "type": "dashboard", "iconColor": "rgba(0, 211, 255, 1)", "enable": true, "hide": true, "builtIn": float64(1), }, }, }, }, }, { name: "preserve_existing_built_in_annotation", input: map[string]interface{}{ "annotations": map[string]interface{}{ "list": []interface{}{ map[string]interface{}{ "name": "Annotations & Alerts", "builtIn": float64(1), }, }, }, }, expected: map[string]interface{}{ "annotations": map[string]interface{}{ "list": []interface{}{ map[string]interface{}{ "name": "Annotations & Alerts", "builtIn": float64(1), }, }, }, }, }, { name: "add_built_in_annotation_with_existing_custom_annotations", input: map[string]interface{}{ "annotations": map[string]interface{}{ "list": []interface{}{ map[string]interface{}{ "name": "Custom Annotation", "type": "tags", }, }, }, }, expected: map[string]interface{}{ "annotations": map[string]interface{}{ "list": []interface{}{ map[string]interface{}{ "datasource": map[string]interface{}{ "uid": "-- Grafana --", "type": "grafana", }, "name": "Annotations & Alerts", "type": "dashboard", "iconColor": "rgba(0, 211, 255, 1)", "enable": true, "hide": true, "builtIn": float64(1), }, map[string]interface{}{ "name": "Custom Annotation", "type": "tags", }, }, }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { dashboard := copyMap(tt.input) addBuiltInAnnotationQuery(dashboard) // Verify expected properties exist assertPropertiesExist(t, dashboard, tt.expected) }) } } func TestInitMeta(t *testing.T) { tests := []struct { name string input map[string]interface{} expected map[string]interface{} }{ { name: "init_meta_with_defaults", input: map[string]interface{}{ "editable": true, }, expected: map[string]interface{}{ "editable": true, "meta": map[string]interface{}{ "canShare": true, "canSave": true, "canStar": true, "canEdit": true, "canDelete": true, "showSettings": true, "canMakeEditable": false, "hasUnsavedFolderChange": false, }, }, }, { name: "init_meta_for_non_editable_dashboard", input: map[string]interface{}{ "editable": false, }, expected: map[string]interface{}{ "editable": false, "meta": map[string]interface{}{ "canShare": true, "canSave": false, // Restricted for non-editable "canStar": true, "canEdit": false, // Restricted for non-editable "canDelete": false, // Restricted for non-editable "showSettings": true, // Set before canEdit is restricted (current implementation behavior) "canMakeEditable": true, // Can make editable "hasUnsavedFolderChange": false, }, }, }, { name: "preserve_existing_meta", input: map[string]interface{}{ "editable": true, "meta": map[string]interface{}{ "canShare": false, // Custom value }, }, expected: map[string]interface{}{ "editable": true, "meta": map[string]interface{}{ "canShare": false, // Preserved "canSave": true, "canStar": true, "canEdit": true, "canDelete": true, "showSettings": true, "canMakeEditable": false, "hasUnsavedFolderChange": false, }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { dashboard := copyMap(tt.input) initMeta(dashboard) // Verify expected properties exist assertPropertiesExist(t, dashboard, tt.expected) }) } }