github.com/cycloidio/terraform@v1.1.10-0.20220513142504-76d5c768dc63/plugin/convert/diagnostics_test.go (about) 1 package convert 2 3 import ( 4 "errors" 5 "testing" 6 7 "github.com/google/go-cmp/cmp" 8 "github.com/google/go-cmp/cmp/cmpopts" 9 "github.com/hashicorp/hcl/v2" 10 "github.com/hashicorp/hcl/v2/hclsyntax" 11 "github.com/cycloidio/terraform/tfdiags" 12 proto "github.com/cycloidio/terraform/tfplugin5" 13 "github.com/zclconf/go-cty/cty" 14 ) 15 16 var ignoreUnexported = cmpopts.IgnoreUnexported( 17 proto.Diagnostic{}, 18 proto.Schema_Block{}, 19 proto.Schema_NestedBlock{}, 20 proto.Schema_Attribute{}, 21 ) 22 23 func TestProtoDiagnostics(t *testing.T) { 24 diags := WarnsAndErrsToProto( 25 []string{ 26 "warning 1", 27 "warning 2", 28 }, 29 []error{ 30 errors.New("error 1"), 31 errors.New("error 2"), 32 }, 33 ) 34 35 expected := []*proto.Diagnostic{ 36 { 37 Severity: proto.Diagnostic_WARNING, 38 Summary: "warning 1", 39 }, 40 { 41 Severity: proto.Diagnostic_WARNING, 42 Summary: "warning 2", 43 }, 44 { 45 Severity: proto.Diagnostic_ERROR, 46 Summary: "error 1", 47 }, 48 { 49 Severity: proto.Diagnostic_ERROR, 50 Summary: "error 2", 51 }, 52 } 53 54 if !cmp.Equal(expected, diags, ignoreUnexported) { 55 t.Fatal(cmp.Diff(expected, diags, ignoreUnexported)) 56 } 57 } 58 59 func TestDiagnostics(t *testing.T) { 60 type diagFlat struct { 61 Severity tfdiags.Severity 62 Attr []interface{} 63 Summary string 64 Detail string 65 } 66 67 tests := map[string]struct { 68 Cons func([]*proto.Diagnostic) []*proto.Diagnostic 69 Want []diagFlat 70 }{ 71 "nil": { 72 func(diags []*proto.Diagnostic) []*proto.Diagnostic { 73 return diags 74 }, 75 nil, 76 }, 77 "error": { 78 func(diags []*proto.Diagnostic) []*proto.Diagnostic { 79 return append(diags, &proto.Diagnostic{ 80 Severity: proto.Diagnostic_ERROR, 81 Summary: "simple error", 82 }) 83 }, 84 []diagFlat{ 85 { 86 Severity: tfdiags.Error, 87 Summary: "simple error", 88 }, 89 }, 90 }, 91 "detailed error": { 92 func(diags []*proto.Diagnostic) []*proto.Diagnostic { 93 return append(diags, &proto.Diagnostic{ 94 Severity: proto.Diagnostic_ERROR, 95 Summary: "simple error", 96 Detail: "detailed error", 97 }) 98 }, 99 []diagFlat{ 100 { 101 Severity: tfdiags.Error, 102 Summary: "simple error", 103 Detail: "detailed error", 104 }, 105 }, 106 }, 107 "warning": { 108 func(diags []*proto.Diagnostic) []*proto.Diagnostic { 109 return append(diags, &proto.Diagnostic{ 110 Severity: proto.Diagnostic_WARNING, 111 Summary: "simple warning", 112 }) 113 }, 114 []diagFlat{ 115 { 116 Severity: tfdiags.Warning, 117 Summary: "simple warning", 118 }, 119 }, 120 }, 121 "detailed warning": { 122 func(diags []*proto.Diagnostic) []*proto.Diagnostic { 123 return append(diags, &proto.Diagnostic{ 124 Severity: proto.Diagnostic_WARNING, 125 Summary: "simple warning", 126 Detail: "detailed warning", 127 }) 128 }, 129 []diagFlat{ 130 { 131 Severity: tfdiags.Warning, 132 Summary: "simple warning", 133 Detail: "detailed warning", 134 }, 135 }, 136 }, 137 "multi error": { 138 func(diags []*proto.Diagnostic) []*proto.Diagnostic { 139 diags = append(diags, &proto.Diagnostic{ 140 Severity: proto.Diagnostic_ERROR, 141 Summary: "first error", 142 }, &proto.Diagnostic{ 143 Severity: proto.Diagnostic_ERROR, 144 Summary: "second error", 145 }) 146 return diags 147 }, 148 []diagFlat{ 149 { 150 Severity: tfdiags.Error, 151 Summary: "first error", 152 }, 153 { 154 Severity: tfdiags.Error, 155 Summary: "second error", 156 }, 157 }, 158 }, 159 "warning and error": { 160 func(diags []*proto.Diagnostic) []*proto.Diagnostic { 161 diags = append(diags, &proto.Diagnostic{ 162 Severity: proto.Diagnostic_WARNING, 163 Summary: "warning", 164 }, &proto.Diagnostic{ 165 Severity: proto.Diagnostic_ERROR, 166 Summary: "error", 167 }) 168 return diags 169 }, 170 []diagFlat{ 171 { 172 Severity: tfdiags.Warning, 173 Summary: "warning", 174 }, 175 { 176 Severity: tfdiags.Error, 177 Summary: "error", 178 }, 179 }, 180 }, 181 "attr error": { 182 func(diags []*proto.Diagnostic) []*proto.Diagnostic { 183 diags = append(diags, &proto.Diagnostic{ 184 Severity: proto.Diagnostic_ERROR, 185 Summary: "error", 186 Detail: "error detail", 187 Attribute: &proto.AttributePath{ 188 Steps: []*proto.AttributePath_Step{ 189 { 190 Selector: &proto.AttributePath_Step_AttributeName{ 191 AttributeName: "attribute_name", 192 }, 193 }, 194 }, 195 }, 196 }) 197 return diags 198 }, 199 []diagFlat{ 200 { 201 Severity: tfdiags.Error, 202 Summary: "error", 203 Detail: "error detail", 204 Attr: []interface{}{"attribute_name"}, 205 }, 206 }, 207 }, 208 "multi attr": { 209 func(diags []*proto.Diagnostic) []*proto.Diagnostic { 210 diags = append(diags, 211 &proto.Diagnostic{ 212 Severity: proto.Diagnostic_ERROR, 213 Summary: "error 1", 214 Detail: "error 1 detail", 215 Attribute: &proto.AttributePath{ 216 Steps: []*proto.AttributePath_Step{ 217 { 218 Selector: &proto.AttributePath_Step_AttributeName{ 219 AttributeName: "attr", 220 }, 221 }, 222 }, 223 }, 224 }, 225 &proto.Diagnostic{ 226 Severity: proto.Diagnostic_ERROR, 227 Summary: "error 2", 228 Detail: "error 2 detail", 229 Attribute: &proto.AttributePath{ 230 Steps: []*proto.AttributePath_Step{ 231 { 232 Selector: &proto.AttributePath_Step_AttributeName{ 233 AttributeName: "attr", 234 }, 235 }, 236 { 237 Selector: &proto.AttributePath_Step_AttributeName{ 238 AttributeName: "sub", 239 }, 240 }, 241 }, 242 }, 243 }, 244 &proto.Diagnostic{ 245 Severity: proto.Diagnostic_WARNING, 246 Summary: "warning", 247 Detail: "warning detail", 248 Attribute: &proto.AttributePath{ 249 Steps: []*proto.AttributePath_Step{ 250 { 251 Selector: &proto.AttributePath_Step_AttributeName{ 252 AttributeName: "attr", 253 }, 254 }, 255 { 256 Selector: &proto.AttributePath_Step_ElementKeyInt{ 257 ElementKeyInt: 1, 258 }, 259 }, 260 { 261 Selector: &proto.AttributePath_Step_AttributeName{ 262 AttributeName: "sub", 263 }, 264 }, 265 }, 266 }, 267 }, 268 &proto.Diagnostic{ 269 Severity: proto.Diagnostic_ERROR, 270 Summary: "error 3", 271 Detail: "error 3 detail", 272 Attribute: &proto.AttributePath{ 273 Steps: []*proto.AttributePath_Step{ 274 { 275 Selector: &proto.AttributePath_Step_AttributeName{ 276 AttributeName: "attr", 277 }, 278 }, 279 { 280 Selector: &proto.AttributePath_Step_ElementKeyString{ 281 ElementKeyString: "idx", 282 }, 283 }, 284 { 285 Selector: &proto.AttributePath_Step_AttributeName{ 286 AttributeName: "sub", 287 }, 288 }, 289 }, 290 }, 291 }, 292 ) 293 294 return diags 295 }, 296 []diagFlat{ 297 { 298 Severity: tfdiags.Error, 299 Summary: "error 1", 300 Detail: "error 1 detail", 301 Attr: []interface{}{"attr"}, 302 }, 303 { 304 Severity: tfdiags.Error, 305 Summary: "error 2", 306 Detail: "error 2 detail", 307 Attr: []interface{}{"attr", "sub"}, 308 }, 309 { 310 Severity: tfdiags.Warning, 311 Summary: "warning", 312 Detail: "warning detail", 313 Attr: []interface{}{"attr", 1, "sub"}, 314 }, 315 { 316 Severity: tfdiags.Error, 317 Summary: "error 3", 318 Detail: "error 3 detail", 319 Attr: []interface{}{"attr", "idx", "sub"}, 320 }, 321 }, 322 }, 323 } 324 325 flattenTFDiags := func(ds tfdiags.Diagnostics) []diagFlat { 326 var flat []diagFlat 327 for _, item := range ds { 328 desc := item.Description() 329 330 var attr []interface{} 331 332 for _, a := range tfdiags.GetAttribute(item) { 333 switch step := a.(type) { 334 case cty.GetAttrStep: 335 attr = append(attr, step.Name) 336 case cty.IndexStep: 337 switch step.Key.Type() { 338 case cty.Number: 339 i, _ := step.Key.AsBigFloat().Int64() 340 attr = append(attr, int(i)) 341 case cty.String: 342 attr = append(attr, step.Key.AsString()) 343 } 344 } 345 } 346 347 flat = append(flat, diagFlat{ 348 Severity: item.Severity(), 349 Attr: attr, 350 Summary: desc.Summary, 351 Detail: desc.Detail, 352 }) 353 } 354 return flat 355 } 356 357 for name, tc := range tests { 358 t.Run(name, func(t *testing.T) { 359 // we take the 360 tfDiags := ProtoToDiagnostics(tc.Cons(nil)) 361 362 flat := flattenTFDiags(tfDiags) 363 364 if !cmp.Equal(flat, tc.Want, typeComparer, valueComparer, equateEmpty) { 365 t.Fatal(cmp.Diff(flat, tc.Want, typeComparer, valueComparer, equateEmpty)) 366 } 367 }) 368 } 369 } 370 371 // Test that a diagnostic with a present but empty attribute results in a 372 // whole body diagnostic. We verify this by inspecting the resulting Subject 373 // from the diagnostic when considered in the context of a config body. 374 func TestProtoDiagnostics_emptyAttributePath(t *testing.T) { 375 protoDiags := []*proto.Diagnostic{ 376 { 377 Severity: proto.Diagnostic_ERROR, 378 Summary: "error 1", 379 Detail: "error 1 detail", 380 Attribute: &proto.AttributePath{ 381 Steps: []*proto.AttributePath_Step{ 382 // this slice is intentionally left empty 383 }, 384 }, 385 }, 386 } 387 tfDiags := ProtoToDiagnostics(protoDiags) 388 389 testConfig := `provider "test" { 390 foo = "bar" 391 }` 392 f, parseDiags := hclsyntax.ParseConfig([]byte(testConfig), "test.tf", hcl.Pos{Line: 1, Column: 1}) 393 if parseDiags.HasErrors() { 394 t.Fatal(parseDiags) 395 } 396 diags := tfDiags.InConfigBody(f.Body, "") 397 398 if len(tfDiags) != 1 { 399 t.Fatalf("expected 1 diag, got %d", len(tfDiags)) 400 } 401 got := diags[0].Source().Subject 402 want := &tfdiags.SourceRange{ 403 Filename: "test.tf", 404 Start: tfdiags.SourcePos{Line: 1, Column: 1}, 405 End: tfdiags.SourcePos{Line: 1, Column: 1}, 406 } 407 408 if !cmp.Equal(got, want, typeComparer, valueComparer) { 409 t.Fatal(cmp.Diff(got, want, typeComparer, valueComparer)) 410 } 411 }