github.com/opentofu/opentofu@v1.7.1/internal/builtin/providers/tf/resource_data_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 tf
     7  
     8  import (
     9  	"strings"
    10  	"testing"
    11  
    12  	"github.com/opentofu/opentofu/internal/providers"
    13  	"github.com/zclconf/go-cty/cty"
    14  	ctyjson "github.com/zclconf/go-cty/cty/json"
    15  )
    16  
    17  func TestManagedDataValidate(t *testing.T) {
    18  	cfg := map[string]cty.Value{
    19  		"input":            cty.NullVal(cty.DynamicPseudoType),
    20  		"output":           cty.NullVal(cty.DynamicPseudoType),
    21  		"triggers_replace": cty.NullVal(cty.DynamicPseudoType),
    22  		"id":               cty.NullVal(cty.String),
    23  	}
    24  
    25  	// empty
    26  	req := providers.ValidateResourceConfigRequest{
    27  		TypeName: "terraform_data",
    28  		Config:   cty.ObjectVal(cfg),
    29  	}
    30  
    31  	resp := validateDataStoreResourceConfig(req)
    32  	if resp.Diagnostics.HasErrors() {
    33  		t.Error("empty config error:", resp.Diagnostics.ErrWithWarnings())
    34  	}
    35  
    36  	// invalid computed values
    37  	cfg["output"] = cty.StringVal("oops")
    38  	req.Config = cty.ObjectVal(cfg)
    39  
    40  	resp = validateDataStoreResourceConfig(req)
    41  	if !resp.Diagnostics.HasErrors() {
    42  		t.Error("expected error")
    43  	}
    44  
    45  	msg := resp.Diagnostics.Err().Error()
    46  	if !strings.Contains(msg, "attribute is read-only") {
    47  		t.Error("unexpected error", msg)
    48  	}
    49  }
    50  
    51  func TestManagedDataUpgradeState(t *testing.T) {
    52  	schema := dataStoreResourceSchema()
    53  	ty := schema.Block.ImpliedType()
    54  
    55  	state := cty.ObjectVal(map[string]cty.Value{
    56  		"input":  cty.StringVal("input"),
    57  		"output": cty.StringVal("input"),
    58  		"triggers_replace": cty.ListVal([]cty.Value{
    59  			cty.StringVal("a"), cty.StringVal("b"),
    60  		}),
    61  		"id": cty.StringVal("not-quite-unique"),
    62  	})
    63  
    64  	jsState, err := ctyjson.Marshal(state, ty)
    65  	if err != nil {
    66  		t.Fatal(err)
    67  	}
    68  
    69  	// empty
    70  	req := providers.UpgradeResourceStateRequest{
    71  		TypeName:     "terraform_data",
    72  		RawStateJSON: jsState,
    73  	}
    74  
    75  	resp := upgradeDataStoreResourceState(req)
    76  	if resp.Diagnostics.HasErrors() {
    77  		t.Error("upgrade state error:", resp.Diagnostics.ErrWithWarnings())
    78  	}
    79  
    80  	if !resp.UpgradedState.RawEquals(state) {
    81  		t.Errorf("prior state was:\n%#v\nupgraded state is:\n%#v\n", state, resp.UpgradedState)
    82  	}
    83  }
    84  
    85  func TestManagedDataRead(t *testing.T) {
    86  	req := providers.ReadResourceRequest{
    87  		TypeName: "terraform_data",
    88  		PriorState: cty.ObjectVal(map[string]cty.Value{
    89  			"input":  cty.StringVal("input"),
    90  			"output": cty.StringVal("input"),
    91  			"triggers_replace": cty.ListVal([]cty.Value{
    92  				cty.StringVal("a"), cty.StringVal("b"),
    93  			}),
    94  			"id": cty.StringVal("not-quite-unique"),
    95  		}),
    96  	}
    97  
    98  	resp := readDataStoreResourceState(req)
    99  	if resp.Diagnostics.HasErrors() {
   100  		t.Fatal("unexpected error", resp.Diagnostics.ErrWithWarnings())
   101  	}
   102  
   103  	if !resp.NewState.RawEquals(req.PriorState) {
   104  		t.Errorf("prior state was:\n%#v\nnew state is:\n%#v\n", req.PriorState, resp.NewState)
   105  	}
   106  }
   107  
   108  func TestManagedDataPlan(t *testing.T) {
   109  	schema := dataStoreResourceSchema().Block
   110  	ty := schema.ImpliedType()
   111  
   112  	for name, tc := range map[string]struct {
   113  		prior    cty.Value
   114  		proposed cty.Value
   115  		planned  cty.Value
   116  	}{
   117  		"create": {
   118  			prior: cty.NullVal(ty),
   119  			proposed: cty.ObjectVal(map[string]cty.Value{
   120  				"input":            cty.NullVal(cty.DynamicPseudoType),
   121  				"output":           cty.NullVal(cty.DynamicPseudoType),
   122  				"triggers_replace": cty.NullVal(cty.DynamicPseudoType),
   123  				"id":               cty.NullVal(cty.String),
   124  			}),
   125  			planned: cty.ObjectVal(map[string]cty.Value{
   126  				"input":            cty.NullVal(cty.DynamicPseudoType),
   127  				"output":           cty.NullVal(cty.DynamicPseudoType),
   128  				"triggers_replace": cty.NullVal(cty.DynamicPseudoType),
   129  				"id":               cty.UnknownVal(cty.String).RefineNotNull(),
   130  			}),
   131  		},
   132  
   133  		"create-typed-null-input": {
   134  			prior: cty.NullVal(ty),
   135  			proposed: cty.ObjectVal(map[string]cty.Value{
   136  				"input":            cty.NullVal(cty.String),
   137  				"output":           cty.NullVal(cty.DynamicPseudoType),
   138  				"triggers_replace": cty.NullVal(cty.DynamicPseudoType),
   139  				"id":               cty.NullVal(cty.String),
   140  			}),
   141  			planned: cty.ObjectVal(map[string]cty.Value{
   142  				"input":            cty.NullVal(cty.String),
   143  				"output":           cty.NullVal(cty.String),
   144  				"triggers_replace": cty.NullVal(cty.DynamicPseudoType),
   145  				"id":               cty.UnknownVal(cty.String).RefineNotNull(),
   146  			}),
   147  		},
   148  
   149  		"create-output": {
   150  			prior: cty.NullVal(ty),
   151  			proposed: cty.ObjectVal(map[string]cty.Value{
   152  				"input":            cty.StringVal("input"),
   153  				"output":           cty.NullVal(cty.DynamicPseudoType),
   154  				"triggers_replace": cty.NullVal(cty.DynamicPseudoType),
   155  				"id":               cty.NullVal(cty.String),
   156  			}),
   157  			planned: cty.ObjectVal(map[string]cty.Value{
   158  				"input":            cty.StringVal("input"),
   159  				"output":           cty.UnknownVal(cty.String),
   160  				"triggers_replace": cty.NullVal(cty.DynamicPseudoType),
   161  				"id":               cty.UnknownVal(cty.String).RefineNotNull(),
   162  			}),
   163  		},
   164  
   165  		"update-input": {
   166  			prior: cty.ObjectVal(map[string]cty.Value{
   167  				"input":            cty.StringVal("input"),
   168  				"output":           cty.StringVal("input"),
   169  				"triggers_replace": cty.NullVal(cty.DynamicPseudoType),
   170  				"id":               cty.StringVal("not-quite-unique"),
   171  			}),
   172  			proposed: cty.ObjectVal(map[string]cty.Value{
   173  				"input":            cty.UnknownVal(cty.List(cty.String)),
   174  				"output":           cty.StringVal("input"),
   175  				"triggers_replace": cty.NullVal(cty.DynamicPseudoType),
   176  				"id":               cty.StringVal("not-quite-unique"),
   177  			}),
   178  			planned: cty.ObjectVal(map[string]cty.Value{
   179  				"input":            cty.UnknownVal(cty.List(cty.String)),
   180  				"output":           cty.UnknownVal(cty.List(cty.String)),
   181  				"triggers_replace": cty.NullVal(cty.DynamicPseudoType),
   182  				"id":               cty.StringVal("not-quite-unique"),
   183  			}),
   184  		},
   185  
   186  		"update-trigger": {
   187  			prior: cty.ObjectVal(map[string]cty.Value{
   188  				"input":            cty.StringVal("input"),
   189  				"output":           cty.StringVal("input"),
   190  				"triggers_replace": cty.NullVal(cty.DynamicPseudoType),
   191  				"id":               cty.StringVal("not-quite-unique"),
   192  			}),
   193  			proposed: cty.ObjectVal(map[string]cty.Value{
   194  				"input":            cty.StringVal("input"),
   195  				"output":           cty.StringVal("input"),
   196  				"triggers_replace": cty.StringVal("new-value"),
   197  				"id":               cty.StringVal("not-quite-unique"),
   198  			}),
   199  			planned: cty.ObjectVal(map[string]cty.Value{
   200  				"input":            cty.StringVal("input"),
   201  				"output":           cty.UnknownVal(cty.String),
   202  				"triggers_replace": cty.StringVal("new-value"),
   203  				"id":               cty.UnknownVal(cty.String).RefineNotNull(),
   204  			}),
   205  		},
   206  
   207  		"update-input-trigger": {
   208  			prior: cty.ObjectVal(map[string]cty.Value{
   209  				"input":  cty.StringVal("input"),
   210  				"output": cty.StringVal("input"),
   211  				"triggers_replace": cty.MapVal(map[string]cty.Value{
   212  					"key": cty.StringVal("value"),
   213  				}),
   214  				"id": cty.StringVal("not-quite-unique"),
   215  			}),
   216  			proposed: cty.ObjectVal(map[string]cty.Value{
   217  				"input":  cty.ListVal([]cty.Value{cty.StringVal("new-input")}),
   218  				"output": cty.StringVal("input"),
   219  				"triggers_replace": cty.MapVal(map[string]cty.Value{
   220  					"key": cty.StringVal("new value"),
   221  				}),
   222  				"id": cty.StringVal("not-quite-unique"),
   223  			}),
   224  			planned: cty.ObjectVal(map[string]cty.Value{
   225  				"input":  cty.ListVal([]cty.Value{cty.StringVal("new-input")}),
   226  				"output": cty.UnknownVal(cty.List(cty.String)),
   227  				"triggers_replace": cty.MapVal(map[string]cty.Value{
   228  					"key": cty.StringVal("new value"),
   229  				}),
   230  				"id": cty.UnknownVal(cty.String).RefineNotNull(),
   231  			}),
   232  		},
   233  	} {
   234  		t.Run("plan-"+name, func(t *testing.T) {
   235  			req := providers.PlanResourceChangeRequest{
   236  				TypeName:         "terraform_data",
   237  				PriorState:       tc.prior,
   238  				ProposedNewState: tc.proposed,
   239  			}
   240  
   241  			resp := planDataStoreResourceChange(req)
   242  			if resp.Diagnostics.HasErrors() {
   243  				t.Fatal(resp.Diagnostics.ErrWithWarnings())
   244  			}
   245  
   246  			if !resp.PlannedState.RawEquals(tc.planned) {
   247  				t.Errorf("expected:\n%#v\ngot:\n%#v\n", tc.planned, resp.PlannedState)
   248  			}
   249  		})
   250  	}
   251  }
   252  
   253  func TestManagedDataApply(t *testing.T) {
   254  	testUUIDHook = func() string {
   255  		return "not-quite-unique"
   256  	}
   257  	defer func() {
   258  		testUUIDHook = nil
   259  	}()
   260  
   261  	schema := dataStoreResourceSchema().Block
   262  	ty := schema.ImpliedType()
   263  
   264  	for name, tc := range map[string]struct {
   265  		prior   cty.Value
   266  		planned cty.Value
   267  		state   cty.Value
   268  	}{
   269  		"create": {
   270  			prior: cty.NullVal(ty),
   271  			planned: cty.ObjectVal(map[string]cty.Value{
   272  				"input":            cty.NullVal(cty.DynamicPseudoType),
   273  				"output":           cty.NullVal(cty.DynamicPseudoType),
   274  				"triggers_replace": cty.NullVal(cty.DynamicPseudoType),
   275  				"id":               cty.UnknownVal(cty.String),
   276  			}),
   277  			state: cty.ObjectVal(map[string]cty.Value{
   278  				"input":            cty.NullVal(cty.DynamicPseudoType),
   279  				"output":           cty.NullVal(cty.DynamicPseudoType),
   280  				"triggers_replace": cty.NullVal(cty.DynamicPseudoType),
   281  				"id":               cty.StringVal("not-quite-unique"),
   282  			}),
   283  		},
   284  
   285  		"create-output": {
   286  			prior: cty.NullVal(ty),
   287  			planned: cty.ObjectVal(map[string]cty.Value{
   288  				"input":            cty.StringVal("input"),
   289  				"output":           cty.UnknownVal(cty.String),
   290  				"triggers_replace": cty.NullVal(cty.DynamicPseudoType),
   291  				"id":               cty.UnknownVal(cty.String),
   292  			}),
   293  			state: cty.ObjectVal(map[string]cty.Value{
   294  				"input":            cty.StringVal("input"),
   295  				"output":           cty.StringVal("input"),
   296  				"triggers_replace": cty.NullVal(cty.DynamicPseudoType),
   297  				"id":               cty.StringVal("not-quite-unique"),
   298  			}),
   299  		},
   300  
   301  		"update-input": {
   302  			prior: cty.ObjectVal(map[string]cty.Value{
   303  				"input":            cty.StringVal("input"),
   304  				"output":           cty.StringVal("input"),
   305  				"triggers_replace": cty.NullVal(cty.DynamicPseudoType),
   306  				"id":               cty.StringVal("not-quite-unique"),
   307  			}),
   308  			planned: cty.ObjectVal(map[string]cty.Value{
   309  				"input":            cty.ListVal([]cty.Value{cty.StringVal("new-input")}),
   310  				"output":           cty.UnknownVal(cty.List(cty.String)),
   311  				"triggers_replace": cty.NullVal(cty.DynamicPseudoType),
   312  				"id":               cty.StringVal("not-quite-unique"),
   313  			}),
   314  			state: cty.ObjectVal(map[string]cty.Value{
   315  				"input":            cty.ListVal([]cty.Value{cty.StringVal("new-input")}),
   316  				"output":           cty.ListVal([]cty.Value{cty.StringVal("new-input")}),
   317  				"triggers_replace": cty.NullVal(cty.DynamicPseudoType),
   318  				"id":               cty.StringVal("not-quite-unique"),
   319  			}),
   320  		},
   321  
   322  		"update-trigger": {
   323  			prior: cty.ObjectVal(map[string]cty.Value{
   324  				"input":            cty.StringVal("input"),
   325  				"output":           cty.StringVal("input"),
   326  				"triggers_replace": cty.NullVal(cty.DynamicPseudoType),
   327  				"id":               cty.StringVal("not-quite-unique"),
   328  			}),
   329  			planned: cty.ObjectVal(map[string]cty.Value{
   330  				"input":            cty.StringVal("input"),
   331  				"output":           cty.UnknownVal(cty.String),
   332  				"triggers_replace": cty.StringVal("new-value"),
   333  				"id":               cty.UnknownVal(cty.String),
   334  			}),
   335  			state: cty.ObjectVal(map[string]cty.Value{
   336  				"input":            cty.StringVal("input"),
   337  				"output":           cty.StringVal("input"),
   338  				"triggers_replace": cty.StringVal("new-value"),
   339  				"id":               cty.StringVal("not-quite-unique"),
   340  			}),
   341  		},
   342  
   343  		"update-input-trigger": {
   344  			prior: cty.ObjectVal(map[string]cty.Value{
   345  				"input":  cty.StringVal("input"),
   346  				"output": cty.StringVal("input"),
   347  				"triggers_replace": cty.MapVal(map[string]cty.Value{
   348  					"key": cty.StringVal("value"),
   349  				}),
   350  				"id": cty.StringVal("not-quite-unique"),
   351  			}),
   352  			planned: cty.ObjectVal(map[string]cty.Value{
   353  				"input":  cty.ListVal([]cty.Value{cty.StringVal("new-input")}),
   354  				"output": cty.UnknownVal(cty.List(cty.String)),
   355  				"triggers_replace": cty.MapVal(map[string]cty.Value{
   356  					"key": cty.StringVal("new value"),
   357  				}),
   358  				"id": cty.UnknownVal(cty.String),
   359  			}),
   360  			state: cty.ObjectVal(map[string]cty.Value{
   361  				"input":  cty.ListVal([]cty.Value{cty.StringVal("new-input")}),
   362  				"output": cty.ListVal([]cty.Value{cty.StringVal("new-input")}),
   363  				"triggers_replace": cty.MapVal(map[string]cty.Value{
   364  					"key": cty.StringVal("new value"),
   365  				}),
   366  				"id": cty.StringVal("not-quite-unique"),
   367  			}),
   368  		},
   369  	} {
   370  		t.Run("apply-"+name, func(t *testing.T) {
   371  			req := providers.ApplyResourceChangeRequest{
   372  				TypeName:     "terraform_data",
   373  				PriorState:   tc.prior,
   374  				PlannedState: tc.planned,
   375  			}
   376  
   377  			resp := applyDataStoreResourceChange(req)
   378  			if resp.Diagnostics.HasErrors() {
   379  				t.Fatal(resp.Diagnostics.ErrWithWarnings())
   380  			}
   381  
   382  			if !resp.NewState.RawEquals(tc.state) {
   383  				t.Errorf("expected:\n%#v\ngot:\n%#v\n", tc.state, resp.NewState)
   384  			}
   385  		})
   386  	}
   387  }