github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/plugin6/convert/diagnostics_test.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package convert 5 6 import ( 7 "errors" 8 "testing" 9 10 "github.com/google/go-cmp/cmp" 11 "github.com/google/go-cmp/cmp/cmpopts" 12 "github.com/terramate-io/tf/tfdiags" 13 proto "github.com/terramate-io/tf/tfplugin6" 14 "github.com/zclconf/go-cty/cty" 15 ) 16 17 var ignoreUnexported = cmpopts.IgnoreUnexported( 18 proto.Diagnostic{}, 19 proto.Schema_Block{}, 20 proto.Schema_NestedBlock{}, 21 proto.Schema_Attribute{}, 22 ) 23 24 func TestProtoDiagnostics(t *testing.T) { 25 diags := WarnsAndErrsToProto( 26 []string{ 27 "warning 1", 28 "warning 2", 29 }, 30 []error{ 31 errors.New("error 1"), 32 errors.New("error 2"), 33 }, 34 ) 35 36 expected := []*proto.Diagnostic{ 37 { 38 Severity: proto.Diagnostic_WARNING, 39 Summary: "warning 1", 40 }, 41 { 42 Severity: proto.Diagnostic_WARNING, 43 Summary: "warning 2", 44 }, 45 { 46 Severity: proto.Diagnostic_ERROR, 47 Summary: "error 1", 48 }, 49 { 50 Severity: proto.Diagnostic_ERROR, 51 Summary: "error 2", 52 }, 53 } 54 55 if !cmp.Equal(expected, diags, ignoreUnexported) { 56 t.Fatal(cmp.Diff(expected, diags, ignoreUnexported)) 57 } 58 } 59 60 func TestDiagnostics(t *testing.T) { 61 type diagFlat struct { 62 Severity tfdiags.Severity 63 Attr []interface{} 64 Summary string 65 Detail string 66 } 67 68 tests := map[string]struct { 69 Cons func([]*proto.Diagnostic) []*proto.Diagnostic 70 Want []diagFlat 71 }{ 72 "nil": { 73 func(diags []*proto.Diagnostic) []*proto.Diagnostic { 74 return diags 75 }, 76 nil, 77 }, 78 "error": { 79 func(diags []*proto.Diagnostic) []*proto.Diagnostic { 80 return append(diags, &proto.Diagnostic{ 81 Severity: proto.Diagnostic_ERROR, 82 Summary: "simple error", 83 }) 84 }, 85 []diagFlat{ 86 { 87 Severity: tfdiags.Error, 88 Summary: "simple error", 89 }, 90 }, 91 }, 92 "detailed error": { 93 func(diags []*proto.Diagnostic) []*proto.Diagnostic { 94 return append(diags, &proto.Diagnostic{ 95 Severity: proto.Diagnostic_ERROR, 96 Summary: "simple error", 97 Detail: "detailed error", 98 }) 99 }, 100 []diagFlat{ 101 { 102 Severity: tfdiags.Error, 103 Summary: "simple error", 104 Detail: "detailed error", 105 }, 106 }, 107 }, 108 "warning": { 109 func(diags []*proto.Diagnostic) []*proto.Diagnostic { 110 return append(diags, &proto.Diagnostic{ 111 Severity: proto.Diagnostic_WARNING, 112 Summary: "simple warning", 113 }) 114 }, 115 []diagFlat{ 116 { 117 Severity: tfdiags.Warning, 118 Summary: "simple warning", 119 }, 120 }, 121 }, 122 "detailed warning": { 123 func(diags []*proto.Diagnostic) []*proto.Diagnostic { 124 return append(diags, &proto.Diagnostic{ 125 Severity: proto.Diagnostic_WARNING, 126 Summary: "simple warning", 127 Detail: "detailed warning", 128 }) 129 }, 130 []diagFlat{ 131 { 132 Severity: tfdiags.Warning, 133 Summary: "simple warning", 134 Detail: "detailed warning", 135 }, 136 }, 137 }, 138 "multi error": { 139 func(diags []*proto.Diagnostic) []*proto.Diagnostic { 140 diags = append(diags, &proto.Diagnostic{ 141 Severity: proto.Diagnostic_ERROR, 142 Summary: "first error", 143 }, &proto.Diagnostic{ 144 Severity: proto.Diagnostic_ERROR, 145 Summary: "second error", 146 }) 147 return diags 148 }, 149 []diagFlat{ 150 { 151 Severity: tfdiags.Error, 152 Summary: "first error", 153 }, 154 { 155 Severity: tfdiags.Error, 156 Summary: "second error", 157 }, 158 }, 159 }, 160 "warning and error": { 161 func(diags []*proto.Diagnostic) []*proto.Diagnostic { 162 diags = append(diags, &proto.Diagnostic{ 163 Severity: proto.Diagnostic_WARNING, 164 Summary: "warning", 165 }, &proto.Diagnostic{ 166 Severity: proto.Diagnostic_ERROR, 167 Summary: "error", 168 }) 169 return diags 170 }, 171 []diagFlat{ 172 { 173 Severity: tfdiags.Warning, 174 Summary: "warning", 175 }, 176 { 177 Severity: tfdiags.Error, 178 Summary: "error", 179 }, 180 }, 181 }, 182 "attr error": { 183 func(diags []*proto.Diagnostic) []*proto.Diagnostic { 184 diags = append(diags, &proto.Diagnostic{ 185 Severity: proto.Diagnostic_ERROR, 186 Summary: "error", 187 Detail: "error detail", 188 Attribute: &proto.AttributePath{ 189 Steps: []*proto.AttributePath_Step{ 190 { 191 Selector: &proto.AttributePath_Step_AttributeName{ 192 AttributeName: "attribute_name", 193 }, 194 }, 195 }, 196 }, 197 }) 198 return diags 199 }, 200 []diagFlat{ 201 { 202 Severity: tfdiags.Error, 203 Summary: "error", 204 Detail: "error detail", 205 Attr: []interface{}{"attribute_name"}, 206 }, 207 }, 208 }, 209 "multi attr": { 210 func(diags []*proto.Diagnostic) []*proto.Diagnostic { 211 diags = append(diags, 212 &proto.Diagnostic{ 213 Severity: proto.Diagnostic_ERROR, 214 Summary: "error 1", 215 Detail: "error 1 detail", 216 Attribute: &proto.AttributePath{ 217 Steps: []*proto.AttributePath_Step{ 218 { 219 Selector: &proto.AttributePath_Step_AttributeName{ 220 AttributeName: "attr", 221 }, 222 }, 223 }, 224 }, 225 }, 226 &proto.Diagnostic{ 227 Severity: proto.Diagnostic_ERROR, 228 Summary: "error 2", 229 Detail: "error 2 detail", 230 Attribute: &proto.AttributePath{ 231 Steps: []*proto.AttributePath_Step{ 232 { 233 Selector: &proto.AttributePath_Step_AttributeName{ 234 AttributeName: "attr", 235 }, 236 }, 237 { 238 Selector: &proto.AttributePath_Step_AttributeName{ 239 AttributeName: "sub", 240 }, 241 }, 242 }, 243 }, 244 }, 245 &proto.Diagnostic{ 246 Severity: proto.Diagnostic_WARNING, 247 Summary: "warning", 248 Detail: "warning detail", 249 Attribute: &proto.AttributePath{ 250 Steps: []*proto.AttributePath_Step{ 251 { 252 Selector: &proto.AttributePath_Step_AttributeName{ 253 AttributeName: "attr", 254 }, 255 }, 256 { 257 Selector: &proto.AttributePath_Step_ElementKeyInt{ 258 ElementKeyInt: 1, 259 }, 260 }, 261 { 262 Selector: &proto.AttributePath_Step_AttributeName{ 263 AttributeName: "sub", 264 }, 265 }, 266 }, 267 }, 268 }, 269 &proto.Diagnostic{ 270 Severity: proto.Diagnostic_ERROR, 271 Summary: "error 3", 272 Detail: "error 3 detail", 273 Attribute: &proto.AttributePath{ 274 Steps: []*proto.AttributePath_Step{ 275 { 276 Selector: &proto.AttributePath_Step_AttributeName{ 277 AttributeName: "attr", 278 }, 279 }, 280 { 281 Selector: &proto.AttributePath_Step_ElementKeyString{ 282 ElementKeyString: "idx", 283 }, 284 }, 285 { 286 Selector: &proto.AttributePath_Step_AttributeName{ 287 AttributeName: "sub", 288 }, 289 }, 290 }, 291 }, 292 }, 293 ) 294 295 return diags 296 }, 297 []diagFlat{ 298 { 299 Severity: tfdiags.Error, 300 Summary: "error 1", 301 Detail: "error 1 detail", 302 Attr: []interface{}{"attr"}, 303 }, 304 { 305 Severity: tfdiags.Error, 306 Summary: "error 2", 307 Detail: "error 2 detail", 308 Attr: []interface{}{"attr", "sub"}, 309 }, 310 { 311 Severity: tfdiags.Warning, 312 Summary: "warning", 313 Detail: "warning detail", 314 Attr: []interface{}{"attr", 1, "sub"}, 315 }, 316 { 317 Severity: tfdiags.Error, 318 Summary: "error 3", 319 Detail: "error 3 detail", 320 Attr: []interface{}{"attr", "idx", "sub"}, 321 }, 322 }, 323 }, 324 } 325 326 flattenTFDiags := func(ds tfdiags.Diagnostics) []diagFlat { 327 var flat []diagFlat 328 for _, item := range ds { 329 desc := item.Description() 330 331 var attr []interface{} 332 333 for _, a := range tfdiags.GetAttribute(item) { 334 switch step := a.(type) { 335 case cty.GetAttrStep: 336 attr = append(attr, step.Name) 337 case cty.IndexStep: 338 switch step.Key.Type() { 339 case cty.Number: 340 i, _ := step.Key.AsBigFloat().Int64() 341 attr = append(attr, int(i)) 342 case cty.String: 343 attr = append(attr, step.Key.AsString()) 344 } 345 } 346 } 347 348 flat = append(flat, diagFlat{ 349 Severity: item.Severity(), 350 Attr: attr, 351 Summary: desc.Summary, 352 Detail: desc.Detail, 353 }) 354 } 355 return flat 356 } 357 358 for name, tc := range tests { 359 t.Run(name, func(t *testing.T) { 360 // we take the 361 tfDiags := ProtoToDiagnostics(tc.Cons(nil)) 362 363 flat := flattenTFDiags(tfDiags) 364 365 if !cmp.Equal(flat, tc.Want, typeComparer, valueComparer, equateEmpty) { 366 t.Fatal(cmp.Diff(flat, tc.Want, typeComparer, valueComparer, equateEmpty)) 367 } 368 }) 369 } 370 }