github.com/opentofu/opentofu@v1.7.1/internal/plans/objchange/normalize_obj_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 objchange
     7  
     8  import (
     9  	"testing"
    10  
    11  	"github.com/apparentlymart/go-dump/dump"
    12  	"github.com/opentofu/opentofu/internal/configs/configschema"
    13  	"github.com/zclconf/go-cty/cty"
    14  )
    15  
    16  func TestNormalizeObjectFromLegacySDK(t *testing.T) {
    17  	tests := map[string]struct {
    18  		Schema *configschema.Block
    19  		Input  cty.Value
    20  		Want   cty.Value
    21  	}{
    22  		"empty": {
    23  			&configschema.Block{},
    24  			cty.EmptyObjectVal,
    25  			cty.EmptyObjectVal,
    26  		},
    27  		"attributes only": {
    28  			&configschema.Block{
    29  				Attributes: map[string]*configschema.Attribute{
    30  					"a": {Type: cty.String, Required: true},
    31  					"b": {Type: cty.String, Optional: true},
    32  				},
    33  			},
    34  			cty.ObjectVal(map[string]cty.Value{
    35  				"a": cty.StringVal("a value"),
    36  				"b": cty.StringVal("b value"),
    37  			}),
    38  			cty.ObjectVal(map[string]cty.Value{
    39  				"a": cty.StringVal("a value"),
    40  				"b": cty.StringVal("b value"),
    41  			}),
    42  		},
    43  		"null block single": {
    44  			&configschema.Block{
    45  				BlockTypes: map[string]*configschema.NestedBlock{
    46  					"a": {
    47  						Nesting: configschema.NestingSingle,
    48  						Block: configschema.Block{
    49  							Attributes: map[string]*configschema.Attribute{
    50  								"b": {Type: cty.String, Optional: true},
    51  							},
    52  						},
    53  					},
    54  				},
    55  			},
    56  			cty.ObjectVal(map[string]cty.Value{
    57  				"a": cty.NullVal(cty.Object(map[string]cty.Type{
    58  					"b": cty.String,
    59  				})),
    60  			}),
    61  			cty.ObjectVal(map[string]cty.Value{
    62  				"a": cty.NullVal(cty.Object(map[string]cty.Type{
    63  					"b": cty.String,
    64  				})),
    65  			}),
    66  		},
    67  		"unknown block single": {
    68  			&configschema.Block{
    69  				BlockTypes: map[string]*configschema.NestedBlock{
    70  					"a": {
    71  						Nesting: configschema.NestingSingle,
    72  						Block: configschema.Block{
    73  							Attributes: map[string]*configschema.Attribute{
    74  								"b": {Type: cty.String, Optional: true},
    75  							},
    76  							BlockTypes: map[string]*configschema.NestedBlock{
    77  								"c": {Nesting: configschema.NestingSingle},
    78  							},
    79  						},
    80  					},
    81  				},
    82  			},
    83  			cty.ObjectVal(map[string]cty.Value{
    84  				"a": cty.UnknownVal(cty.Object(map[string]cty.Type{
    85  					"b": cty.String,
    86  					"c": cty.EmptyObject,
    87  				})),
    88  			}),
    89  			cty.ObjectVal(map[string]cty.Value{
    90  				"a": cty.ObjectVal(map[string]cty.Value{
    91  					"b": cty.UnknownVal(cty.String),
    92  					"c": cty.EmptyObjectVal,
    93  				}),
    94  			}),
    95  		},
    96  		"null block list": {
    97  			&configschema.Block{
    98  				BlockTypes: map[string]*configschema.NestedBlock{
    99  					"a": {
   100  						Nesting: configschema.NestingList,
   101  						Block: configschema.Block{
   102  							Attributes: map[string]*configschema.Attribute{
   103  								"b": {Type: cty.String, Optional: true},
   104  							},
   105  							BlockTypes: map[string]*configschema.NestedBlock{
   106  								"c": {Nesting: configschema.NestingSingle},
   107  							},
   108  						},
   109  					},
   110  				},
   111  			},
   112  			cty.ObjectVal(map[string]cty.Value{
   113  				"a": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{
   114  					"b": cty.String,
   115  					"c": cty.EmptyObject,
   116  				}))),
   117  			}),
   118  			cty.ObjectVal(map[string]cty.Value{
   119  				"a": cty.ListValEmpty(cty.Object(map[string]cty.Type{
   120  					"b": cty.String,
   121  					"c": cty.EmptyObject,
   122  				})),
   123  			}),
   124  		},
   125  		"unknown block list": {
   126  			&configschema.Block{
   127  				BlockTypes: map[string]*configschema.NestedBlock{
   128  					"a": {
   129  						Nesting: configschema.NestingList,
   130  						Block: configschema.Block{
   131  							Attributes: map[string]*configschema.Attribute{
   132  								"b": {Type: cty.String, Optional: true},
   133  							},
   134  						},
   135  					},
   136  				},
   137  			},
   138  			cty.ObjectVal(map[string]cty.Value{
   139  				"a": cty.UnknownVal(cty.List(cty.Object(map[string]cty.Type{
   140  					"b": cty.String,
   141  				}))),
   142  			}),
   143  			cty.ObjectVal(map[string]cty.Value{
   144  				"a": cty.ListVal([]cty.Value{
   145  					cty.ObjectVal(map[string]cty.Value{
   146  						"b": cty.UnknownVal(cty.String),
   147  					}),
   148  				}),
   149  			}),
   150  		},
   151  		"null block set": {
   152  			&configschema.Block{
   153  				BlockTypes: map[string]*configschema.NestedBlock{
   154  					"a": {
   155  						Nesting: configschema.NestingSet,
   156  						Block: configschema.Block{
   157  							Attributes: map[string]*configschema.Attribute{
   158  								"b": {Type: cty.String, Optional: true},
   159  							},
   160  						},
   161  					},
   162  				},
   163  			},
   164  			cty.ObjectVal(map[string]cty.Value{
   165  				"a": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{
   166  					"b": cty.String,
   167  				}))),
   168  			}),
   169  			cty.ObjectVal(map[string]cty.Value{
   170  				"a": cty.SetValEmpty(cty.Object(map[string]cty.Type{
   171  					"b": cty.String,
   172  				})),
   173  			}),
   174  		},
   175  		"unknown block set": {
   176  			&configschema.Block{
   177  				BlockTypes: map[string]*configschema.NestedBlock{
   178  					"a": {
   179  						Nesting: configschema.NestingSet,
   180  						Block: configschema.Block{
   181  							Attributes: map[string]*configschema.Attribute{
   182  								"b": {Type: cty.String, Optional: true},
   183  							},
   184  						},
   185  					},
   186  				},
   187  			},
   188  			cty.ObjectVal(map[string]cty.Value{
   189  				"a": cty.UnknownVal(cty.Set(cty.Object(map[string]cty.Type{
   190  					"b": cty.String,
   191  				}))),
   192  			}),
   193  			cty.ObjectVal(map[string]cty.Value{
   194  				"a": cty.SetVal([]cty.Value{
   195  					cty.ObjectVal(map[string]cty.Value{
   196  						"b": cty.UnknownVal(cty.String),
   197  					}),
   198  				}),
   199  			}),
   200  		},
   201  		"map block passes through": {
   202  			// Legacy SDK doesn't use NestingMap, so we don't do any transforms
   203  			// related to it but we still need to verify that map blocks pass
   204  			// through unscathed.
   205  			&configschema.Block{
   206  				BlockTypes: map[string]*configschema.NestedBlock{
   207  					"a": {
   208  						Nesting: configschema.NestingMap,
   209  						Block: configschema.Block{
   210  							Attributes: map[string]*configschema.Attribute{
   211  								"b": {Type: cty.String, Optional: true},
   212  							},
   213  						},
   214  					},
   215  				},
   216  			},
   217  			cty.ObjectVal(map[string]cty.Value{
   218  				"a": cty.MapVal(map[string]cty.Value{
   219  					"foo": cty.ObjectVal(map[string]cty.Value{
   220  						"b": cty.StringVal("b value"),
   221  					}),
   222  				}),
   223  			}),
   224  			cty.ObjectVal(map[string]cty.Value{
   225  				"a": cty.MapVal(map[string]cty.Value{
   226  					"foo": cty.ObjectVal(map[string]cty.Value{
   227  						"b": cty.StringVal("b value"),
   228  					}),
   229  				}),
   230  			}),
   231  		},
   232  		"block list with dynamic type": {
   233  			&configschema.Block{
   234  				BlockTypes: map[string]*configschema.NestedBlock{
   235  					"a": {
   236  						Nesting: configschema.NestingList,
   237  						Block: configschema.Block{
   238  							Attributes: map[string]*configschema.Attribute{
   239  								"b": {Type: cty.DynamicPseudoType, Optional: true},
   240  							},
   241  						},
   242  					},
   243  				},
   244  			},
   245  			cty.ObjectVal(map[string]cty.Value{
   246  				"a": cty.TupleVal([]cty.Value{
   247  					cty.ObjectVal(map[string]cty.Value{
   248  						"b": cty.StringVal("hello"),
   249  					}),
   250  					cty.ObjectVal(map[string]cty.Value{
   251  						"b": cty.True,
   252  					}),
   253  				}),
   254  			}),
   255  			cty.ObjectVal(map[string]cty.Value{
   256  				"a": cty.TupleVal([]cty.Value{
   257  					cty.ObjectVal(map[string]cty.Value{
   258  						"b": cty.StringVal("hello"),
   259  					}),
   260  					cty.ObjectVal(map[string]cty.Value{
   261  						"b": cty.True,
   262  					}),
   263  				}),
   264  			}),
   265  		},
   266  		"block map with dynamic type": {
   267  			&configschema.Block{
   268  				BlockTypes: map[string]*configschema.NestedBlock{
   269  					"a": {
   270  						Nesting: configschema.NestingMap,
   271  						Block: configschema.Block{
   272  							Attributes: map[string]*configschema.Attribute{
   273  								"b": {Type: cty.DynamicPseudoType, Optional: true},
   274  							},
   275  						},
   276  					},
   277  				},
   278  			},
   279  			cty.ObjectVal(map[string]cty.Value{
   280  				"a": cty.ObjectVal(map[string]cty.Value{
   281  					"one": cty.ObjectVal(map[string]cty.Value{
   282  						"b": cty.StringVal("hello"),
   283  					}),
   284  					"another": cty.ObjectVal(map[string]cty.Value{
   285  						"b": cty.True,
   286  					}),
   287  				}),
   288  			}),
   289  			cty.ObjectVal(map[string]cty.Value{
   290  				"a": cty.ObjectVal(map[string]cty.Value{
   291  					"one": cty.ObjectVal(map[string]cty.Value{
   292  						"b": cty.StringVal("hello"),
   293  					}),
   294  					"another": cty.ObjectVal(map[string]cty.Value{
   295  						"b": cty.True,
   296  					}),
   297  				}),
   298  			}),
   299  		},
   300  	}
   301  
   302  	for name, test := range tests {
   303  		t.Run(name, func(t *testing.T) {
   304  			got := NormalizeObjectFromLegacySDK(test.Input, test.Schema)
   305  			if !got.RawEquals(test.Want) {
   306  				t.Errorf(
   307  					"wrong result\ngot:  %s\nwant: %s",
   308  					dump.Value(got), dump.Value(test.Want),
   309  				)
   310  			}
   311  		})
   312  	}
   313  }