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  }