package validator import ( "testing" ) func TestIsVariableReference(t *testing.T) { tests := []struct { name string input string expected bool }{ {"dollar brace", "${prometheus}", true}, {"dollar simple", "$datasource", true}, {"double bracket", "[[prometheus]]", true}, {"concrete uid", "abcd1234", false}, {"empty string", "", false}, {"dollar only", "$", false}, {"empty braces", "${}", false}, {"number start", "$123", true}, // Changed: Grafana ACCEPTS digits (per \w+ regex) {"all digits", "$999", true}, // New: All digits are valid per \w+ {"special chars dash", "$ds-name", false}, // Changed: Grafana REJECTS dashes (not in \w) {"underscore", "$DS_PROMETHEUS", true}, {"complex variable", "${DS_PROMETHEUS}", true}, {"simple letter", "$p", true}, {"with fieldpath", "${var.field}", true}, // New: Test fieldPath syntax {"with format", "[[var:text]]", true}, // New: Test format syntax {"brace with format", "${var:json}", true}, // New: Test brace format syntax {"digit in brackets", "[[123]]", true}, // New: Digits allowed in all patterns {"empty brackets", "[[]]", false}, // New: Empty brackets rejected } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := isVariableReference(tt.input) if result != tt.expected { t.Errorf("isVariableReference(%q) = %v, want %v", tt.input, result, tt.expected) } }) } } func TestExtractVariableName(t *testing.T) { tests := []struct { name string input string expected string }{ {"dollar brace", "${prometheus}", "prometheus"}, {"dollar simple", "$datasource", "datasource"}, {"double bracket", "[[prometheus]]", "prometheus"}, {"not variable", "concrete-uid", ""}, {"empty", "", ""}, {"complex name", "${DS_PROMETHEUS}", "DS_PROMETHEUS"}, {"with underscore", "$DS_NAME", "DS_NAME"}, {"digit variable", "$123", "123"}, // New: Digits are valid {"with fieldpath", "${var.field}", "var"}, // Changed: Extract only name, not fieldPath {"with format brace", "${var:json}", "var"}, // Changed: Extract only name, not format {"with format bracket", "[[var:text]]", "var"}, // Changed: Extract only name, not format {"fieldpath and format", "${var.field:json}", "var"}, // New: Extract only name from complex syntax } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := extractVariableName(tt.input) if result != tt.expected { t.Errorf("extractVariableName(%q) = %q, want %q", tt.input, result, tt.expected) } }) } } func TestIsPrometheusVariable(t *testing.T) { // Dashboard with Prometheus __inputs dashboardWithPrometheus := map[string]interface{}{ "__inputs": []interface{}{ map[string]interface{}{ "name": "DS_PROMETHEUS", "type": "datasource", "pluginId": "prometheus", }, }, } // Dashboard with MySQL __inputs dashboardWithMySQL := map[string]interface{}{ "__inputs": []interface{}{ map[string]interface{}{ "name": "DS_MYSQL", "type": "datasource", "pluginId": "mysql", }, }, } // Dashboard without __inputs dashboardWithoutInputs := map[string]interface{}{ "title": "Test Dashboard", } tests := []struct { name string varRef string dashboard map[string]interface{} expected bool }{ {"prometheus variable with inputs", "${DS_PROMETHEUS}", dashboardWithPrometheus, true}, {"prometheus simple var", "$DS_PROMETHEUS", dashboardWithPrometheus, true}, {"mysql variable", "${DS_MYSQL}", dashboardWithMySQL, false}, {"not variable", "concrete-uid", dashboardWithPrometheus, false}, {"variable without inputs", "${prometheus}", dashboardWithoutInputs, true}, // Fallback to true for MVP {"wrong variable name", "${OTHER}", dashboardWithPrometheus, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := isPrometheusVariable(tt.varRef, tt.dashboard) if result != tt.expected { t.Errorf("isPrometheusVariable(%q, dashboard) = %v, want %v", tt.varRef, result, tt.expected) } }) } } func TestResolveDatasourceUID(t *testing.T) { singleUID := "prom-uid-123" dashboardWithPrometheus := map[string]interface{}{ "__inputs": []interface{}{ map[string]interface{}{ "name": "DS_PROMETHEUS", "type": "datasource", "pluginId": "prometheus", }, }, } dashboardWithMySQL := map[string]interface{}{ "__inputs": []interface{}{ map[string]interface{}{ "name": "DS_MYSQL", "type": "datasource", "pluginId": "mysql", }, }, } tests := []struct { name string uid string dashboard map[string]interface{} expectedUID string description string }{ {"concrete uid", "concrete-123", dashboardWithPrometheus, "concrete-123", "should return concrete UID as-is"}, {"prometheus variable", "${DS_PROMETHEUS}", dashboardWithPrometheus, singleUID, "should resolve to single datasource UID"}, {"prometheus simple var", "$DS_PROMETHEUS", dashboardWithPrometheus, singleUID, "should resolve simple $ syntax"}, {"mysql variable", "${DS_MYSQL}", dashboardWithMySQL, "${DS_MYSQL}", "should return non-Prometheus variable as-is"}, {"empty uid", "", dashboardWithPrometheus, "", "should return empty string as-is"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := resolveDatasourceUID(tt.uid, singleUID, tt.dashboard) if result != tt.expectedUID { t.Errorf("resolveDatasourceUID(%q, %q, dashboard) = %q, want %q (%s)", tt.uid, singleUID, result, tt.expectedUID, tt.description) } }) } }