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 }