| // Copyright IBM Corp. 2014, 2026 | |
| // SPDX-License-Identifier: BUSL-1.1 | |
| package refactoring | |
| import ( | |
| "testing" | |
| "github.com/hashicorp/hcl/v2/hcltest" | |
| "github.com/zclconf/go-cty/cty" | |
| "github.com/zclconf/go-cty/cty/gocty" | |
| "github.com/hashicorp/terraform/internal/addrs" | |
| "github.com/hashicorp/terraform/internal/configs" | |
| "github.com/hashicorp/terraform/internal/instances" | |
| ) | |
| // FakeExternalModuleSource is used in tests to simulate an external module source. | |
| var FakeExternalModuleSource = addrs.ModuleSourceRemote{ | |
| Package: addrs.ModulePackage("example.com/test/fake"), | |
| } | |
| // StaticPopulateExpanderModule populates an expander for testing by statically | |
| // evaluating count and for_each expressions in a configuration. | |
| // | |
| // This is exported so that test code in package refactoring_test can use it | |
| // without creating an import cycle with the terraform package. | |
| func StaticPopulateExpanderModule(t *testing.T, rootCfg *configs.Config, moduleAddr addrs.ModuleInstance, expander *instances.Expander) { | |
| t.Helper() | |
| modCfg := rootCfg.DescendantForInstance(moduleAddr) | |
| if modCfg == nil { | |
| t.Fatalf("no configuration for %s", moduleAddr) | |
| } | |
| if len(modCfg.Path) > 0 && modCfg.Path[len(modCfg.Path)-1] == "fake_external" { | |
| modCfg.SourceAddr = FakeExternalModuleSource | |
| } | |
| for _, call := range modCfg.Module.ModuleCalls { | |
| callAddr := addrs.ModuleCall{Name: call.Name} | |
| if call.Name == "fake_external" { | |
| call.SourceExpr = hcltest.MockExprLiteral(cty.StringVal(FakeExternalModuleSource.String())) | |
| } | |
| switch { | |
| case call.ForEach != nil: | |
| val, diags := call.ForEach.Value(nil) | |
| if diags.HasErrors() { | |
| t.Fatalf("invalid for_each: %s", diags.Error()) | |
| } | |
| expander.SetModuleForEach(moduleAddr, callAddr, val.AsValueMap()) | |
| case call.Count != nil: | |
| val, diags := call.Count.Value(nil) | |
| if diags.HasErrors() { | |
| t.Fatalf("invalid count: %s", diags.Error()) | |
| } | |
| var count int | |
| err := gocty.FromCtyValue(val, &count) | |
| if err != nil { | |
| t.Fatalf("invalid count at %s: %s", call.Count.Range(), err) | |
| } | |
| expander.SetModuleCount(moduleAddr, callAddr, count) | |
| default: | |
| expander.SetModuleSingle(moduleAddr, callAddr) | |
| } | |
| calledMod := modCfg.Path.Child(call.Name) | |
| for _, inst := range expander.ExpandModule(calledMod, false) { | |
| StaticPopulateExpanderModule(t, rootCfg, inst, expander) | |
| } | |
| } | |
| for _, rc := range modCfg.Module.ManagedResources { | |
| StaticPopulateExpanderResource(t, moduleAddr, rc, expander) | |
| } | |
| for _, rc := range modCfg.Module.DataResources { | |
| StaticPopulateExpanderResource(t, moduleAddr, rc, expander) | |
| } | |
| } | |
| // StaticPopulateExpanderResource populates resource instances in an expander for testing. | |
| func StaticPopulateExpanderResource(t *testing.T, moduleAddr addrs.ModuleInstance, rCfg *configs.Resource, expander *instances.Expander) { | |
| t.Helper() | |
| addr := rCfg.Addr() | |
| switch { | |
| case rCfg.ForEach != nil: | |
| val, diags := rCfg.ForEach.Value(nil) | |
| if diags.HasErrors() { | |
| t.Fatalf("invalid for_each: %s", diags.Error()) | |
| } | |
| expander.SetResourceForEach(moduleAddr, addr, val.AsValueMap()) | |
| case rCfg.Count != nil: | |
| val, diags := rCfg.Count.Value(nil) | |
| if diags.HasErrors() { | |
| t.Fatalf("invalid count: %s", diags.Error()) | |
| } | |
| var count int | |
| err := gocty.FromCtyValue(val, &count) | |
| if err != nil { | |
| t.Fatalf("invalid count at %s: %s", rCfg.Count.Range(), err) | |
| } | |
| expander.SetResourceCount(moduleAddr, addr, count) | |
| default: | |
| expander.SetResourceSingle(moduleAddr, addr) | |
| } | |
| } |