github.com/opentofu/opentofu@v1.7.1/internal/configs/configload/loader_load_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 configload
     7  
     8  import (
     9  	"path/filepath"
    10  	"reflect"
    11  	"sort"
    12  	"strings"
    13  	"testing"
    14  
    15  	"github.com/davecgh/go-spew/spew"
    16  	"github.com/zclconf/go-cty/cty"
    17  
    18  	"github.com/opentofu/opentofu/internal/configs"
    19  )
    20  
    21  func TestLoaderLoadConfig_okay(t *testing.T) {
    22  	fixtureDir := filepath.Clean("testdata/already-installed")
    23  	loader, err := NewLoader(&Config{
    24  		ModulesDir: filepath.Join(fixtureDir, ".terraform/modules"),
    25  	})
    26  	if err != nil {
    27  		t.Fatalf("unexpected error from NewLoader: %s", err)
    28  	}
    29  
    30  	cfg, diags := loader.LoadConfig(fixtureDir)
    31  	assertNoDiagnostics(t, diags)
    32  	if cfg == nil {
    33  		t.Fatalf("config is nil; want non-nil")
    34  	}
    35  
    36  	var gotPaths []string
    37  	cfg.DeepEach(func(c *configs.Config) {
    38  		gotPaths = append(gotPaths, strings.Join(c.Path, "."))
    39  	})
    40  	sort.Strings(gotPaths)
    41  	wantPaths := []string{
    42  		"", // root module
    43  		"child_a",
    44  		"child_a.child_c",
    45  		"child_b",
    46  		"child_b.child_d",
    47  	}
    48  
    49  	if !reflect.DeepEqual(gotPaths, wantPaths) {
    50  		t.Fatalf("wrong module paths\ngot: %swant %s", spew.Sdump(gotPaths), spew.Sdump(wantPaths))
    51  	}
    52  
    53  	t.Run("child_a.child_c output", func(t *testing.T) {
    54  		output := cfg.Children["child_a"].Children["child_c"].Module.Outputs["hello"]
    55  		got, diags := output.Expr.Value(nil)
    56  		assertNoDiagnostics(t, diags)
    57  		assertResultCtyEqual(t, got, cty.StringVal("Hello from child_c"))
    58  	})
    59  	t.Run("child_b.child_d output", func(t *testing.T) {
    60  		output := cfg.Children["child_b"].Children["child_d"].Module.Outputs["hello"]
    61  		got, diags := output.Expr.Value(nil)
    62  		assertNoDiagnostics(t, diags)
    63  		assertResultCtyEqual(t, got, cty.StringVal("Hello from child_d"))
    64  	})
    65  }
    66  
    67  func TestLoaderLoadConfig_addVersion(t *testing.T) {
    68  	// This test is for what happens when there is a version constraint added
    69  	// to a module that previously didn't have one.
    70  	fixtureDir := filepath.Clean("testdata/add-version-constraint")
    71  	loader, err := NewLoader(&Config{
    72  		ModulesDir: filepath.Join(fixtureDir, ".terraform/modules"),
    73  	})
    74  	if err != nil {
    75  		t.Fatalf("unexpected error from NewLoader: %s", err)
    76  	}
    77  
    78  	_, diags := loader.LoadConfig(fixtureDir)
    79  	if !diags.HasErrors() {
    80  		t.Fatalf("success; want error")
    81  	}
    82  	got := diags.Error()
    83  	want := "Module version requirements have changed"
    84  	if !strings.Contains(got, want) {
    85  		t.Fatalf("wrong error\ngot:\n%s\n\nwant: containing %q", got, want)
    86  	}
    87  }
    88  
    89  func TestLoaderLoadConfig_loadDiags(t *testing.T) {
    90  	// building a config which didn't load correctly may cause configs to panic
    91  	fixtureDir := filepath.Clean("testdata/invalid-names")
    92  	loader, err := NewLoader(&Config{
    93  		ModulesDir: filepath.Join(fixtureDir, ".terraform/modules"),
    94  	})
    95  	if err != nil {
    96  		t.Fatalf("unexpected error from NewLoader: %s", err)
    97  	}
    98  
    99  	cfg, diags := loader.LoadConfig(fixtureDir)
   100  	if !diags.HasErrors() {
   101  		t.Fatal("success; want error")
   102  	}
   103  
   104  	if cfg == nil {
   105  		t.Fatal("partial config not returned with diagnostics")
   106  	}
   107  
   108  	if cfg.Module == nil {
   109  		t.Fatal("expected config module")
   110  	}
   111  }
   112  
   113  func TestLoaderLoadConfig_loadDiagsFromSubmodules(t *testing.T) {
   114  	// building a config which didn't load correctly may cause configs to panic
   115  	fixtureDir := filepath.Clean("testdata/invalid-names-in-submodules")
   116  	loader, err := NewLoader(&Config{
   117  		ModulesDir: filepath.Join(fixtureDir, ".terraform/modules"),
   118  	})
   119  	if err != nil {
   120  		t.Fatalf("unexpected error from NewLoader: %s", err)
   121  	}
   122  
   123  	cfg, diags := loader.LoadConfig(fixtureDir)
   124  	if !diags.HasErrors() {
   125  		t.Fatalf("loading succeeded; want an error")
   126  	}
   127  	if got, want := diags.Error(), " Invalid provider local name"; !strings.Contains(got, want) {
   128  		t.Errorf("missing expected error\nwant substring: %s\ngot: %s", want, got)
   129  	}
   130  
   131  	if cfg == nil {
   132  		t.Fatal("partial config not returned with diagnostics")
   133  	}
   134  
   135  	if cfg.Module == nil {
   136  		t.Fatal("expected config module")
   137  	}
   138  }
   139  
   140  func TestLoaderLoadConfig_childProviderGrandchildCount(t *testing.T) {
   141  	// This test is focused on the specific situation where:
   142  	// - A child module contains a nested provider block, which is no longer
   143  	//   recommended but supported for backward-compatibility.
   144  	// - A child of that child does _not_ contain a nested provider block,
   145  	//   and is called with "count" (would also apply to "for_each" and
   146  	//   "depends_on").
   147  	// It isn't valid to use "count" with a module that _itself_ contains
   148  	// a provider configuration, but it _is_ valid for a module with a
   149  	// provider configuration to call another module with count. We previously
   150  	// botched this rule and so this is a regression test to cover the
   151  	// solution to that mistake:
   152  	//     https://github.com/hashicorp/terraform/issues/31081
   153  
   154  	// Since this test is based on success rather than failure and it's
   155  	// covering a relatively large set of code where only a small part
   156  	// contributes to the test, we'll make sure to test both the success and
   157  	// failure cases here so that we'll have a better chance of noticing if a
   158  	// future change makes this succeed only because we've reorganized the code
   159  	// so that the check isn't happening at all anymore.
   160  	//
   161  	// If the "not okay" subtest fails, you should also be skeptical about
   162  	// whether the "okay" subtest is still valid, even if it happens to
   163  	// still be passing.
   164  	t.Run("okay", func(t *testing.T) {
   165  		fixtureDir := filepath.Clean("testdata/child-provider-grandchild-count")
   166  		loader, err := NewLoader(&Config{
   167  			ModulesDir: filepath.Join(fixtureDir, ".terraform/modules"),
   168  		})
   169  		if err != nil {
   170  			t.Fatalf("unexpected error from NewLoader: %s", err)
   171  		}
   172  
   173  		cfg, diags := loader.LoadConfig(fixtureDir)
   174  		assertNoDiagnostics(t, diags)
   175  		if cfg == nil {
   176  			t.Fatalf("config is nil; want non-nil")
   177  		}
   178  
   179  		var gotPaths []string
   180  		cfg.DeepEach(func(c *configs.Config) {
   181  			gotPaths = append(gotPaths, strings.Join(c.Path, "."))
   182  		})
   183  		sort.Strings(gotPaths)
   184  		wantPaths := []string{
   185  			"", // root module
   186  			"child",
   187  			"child.grandchild",
   188  		}
   189  
   190  		if !reflect.DeepEqual(gotPaths, wantPaths) {
   191  			t.Fatalf("wrong module paths\ngot: %swant %s", spew.Sdump(gotPaths), spew.Sdump(wantPaths))
   192  		}
   193  	})
   194  	t.Run("not okay", func(t *testing.T) {
   195  		fixtureDir := filepath.Clean("testdata/child-provider-child-count")
   196  		loader, err := NewLoader(&Config{
   197  			ModulesDir: filepath.Join(fixtureDir, ".terraform/modules"),
   198  		})
   199  		if err != nil {
   200  			t.Fatalf("unexpected error from NewLoader: %s", err)
   201  		}
   202  
   203  		_, diags := loader.LoadConfig(fixtureDir)
   204  		if !diags.HasErrors() {
   205  			t.Fatalf("loading succeeded; want an error")
   206  		}
   207  		if got, want := diags.Error(), "Module is incompatible with count, for_each, and depends_on"; !strings.Contains(got, want) {
   208  			t.Errorf("missing expected error\nwant substring: %s\ngot: %s", want, got)
   209  		}
   210  	})
   211  
   212  }