github.com/opentofu/opentofu@v1.7.1/internal/plugin6/convert/diagnostics_test.go (about)

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