// Copyright IBM Corp. 2014, 2026 // SPDX-License-Identifier: BUSL-1.1 package command import ( "encoding/json" "os" "reflect" "runtime" "sort" "strings" "testing" "github.com/google/go-cmp/cmp" "github.com/hashicorp/cli" "github.com/hashicorp/terraform/internal/moduleref" ) func TestModules_noJsonFlag(t *testing.T) { dir := t.TempDir() os.MkdirAll(dir, 0755) testCopyDir(t, testFixturePath("modules-nested-dependencies"), dir) t.Chdir(dir) ui := new(cli.MockUi) view, done := testView(t) cmd := &ModulesCommand{ Meta: Meta{ testingOverrides: metaOverridesForProvider(testProvider()), Ui: ui, View: view, }, } args := []string{} code := cmd.Run(args) if code != 0 { t.Fatalf("Got a non-zero exit code: %d\n", code) } actual := done(t).All() expectedOutputHuman := ` Modules declared by configuration: . ├── "other"[./mods/other] └── "test"[./mods/test] └── "test2"[./test2] └── "test3"[./test3] ` if runtime.GOOS == "windows" { expectedOutputHuman = ` Modules declared by configuration: . ├── "other"[.\mods\other] └── "test"[.\mods\test] └── "test2"[.\test2] └── "test3"[.\test3] ` } if diff := cmp.Diff(expectedOutputHuman, actual); diff != "" { t.Fatalf("unexpected output:\n%s\n", diff) } } func TestModules_noJsonFlag_noModules(t *testing.T) { dir := t.TempDir() os.MkdirAll(dir, 0755) t.Chdir(dir) ui := new(cli.MockUi) view, done := testView(t) cmd := &ModulesCommand{ Meta: Meta{ testingOverrides: metaOverridesForProvider(testProvider()), Ui: ui, View: view, }, } args := []string{} code := cmd.Run(args) if code != 0 { t.Fatalf("Got a non-zero exit code: %d\n", code) } actual := done(t).All() if diff := cmp.Diff("No modules found in configuration.\n", actual); diff != "" { t.Fatalf("unexpected output (-want +got):\n%s", diff) } } func TestModules_fullCmd(t *testing.T) { dir := t.TempDir() os.MkdirAll(dir, 0755) testCopyDir(t, testFixturePath("modules-nested-dependencies"), dir) t.Chdir(dir) ui := new(cli.MockUi) view, done := testView(t) cmd := &ModulesCommand{ Meta: Meta{ testingOverrides: metaOverridesForProvider(testProvider()), Ui: ui, View: view, }, } args := []string{"-json"} code := cmd.Run(args) if code != 0 { t.Fatalf("Got a non-zero exit code: %d\n", code) } output := done(t).All() compareJSONOutput(t, output, expectedOutputJSON) } func TestModules_fullCmd_unreferencedEntries(t *testing.T) { dir := t.TempDir() os.MkdirAll(dir, 0755) testCopyDir(t, testFixturePath("modules-unreferenced-entries"), dir) t.Chdir(dir) ui := new(cli.MockUi) view, done := testView(t) cmd := &ModulesCommand{ Meta: Meta{ testingOverrides: metaOverridesForProvider(testProvider()), Ui: ui, View: view, }, } args := []string{"-json"} code := cmd.Run(args) if code != 0 { t.Fatalf("Got a non-zero exit code: %d\n", code) } output := done(t).All() compareJSONOutput(t, output, expectedOutputJSON) } func TestModules_uninstalledModules(t *testing.T) { dir := t.TempDir() os.MkdirAll(dir, 0755) testCopyDir(t, testFixturePath("modules-uninstalled-entries"), dir) t.Chdir(dir) ui := new(cli.MockUi) view, done := testView(t) cmd := &ModulesCommand{ Meta: Meta{ testingOverrides: metaOverridesForProvider(testProvider()), Ui: ui, View: view, }, } args := []string{"-json"} code := cmd.Run(args) if code == 0 { t.Fatal("Expected a non-zero exit code\n") } output := done(t).All() if !strings.Contains(output, "Module not installed") { t.Fatalf("expected to see a `not installed` error message: %s\n", output) } if !strings.Contains(output, `Run "terraform init"`) { t.Fatalf("expected error message to ask user to run terraform init: %s\n", output) } } func compareJSONOutput(t *testing.T, got string, want string) { var expected, actual moduleref.Manifest if err := json.Unmarshal([]byte(got), &actual); err != nil { t.Fatalf("Failed to unmarshal actual JSON: %v", err) } if err := json.Unmarshal([]byte(want), &expected); err != nil { t.Fatalf("Failed to unmarshal expected JSON: %v", err) } sort.Slice(actual.Records, func(i, j int) bool { return actual.Records[i].Key < actual.Records[j].Key }) sort.Slice(expected.Records, func(i, j int) bool { return expected.Records[i].Key < expected.Records[j].Key }) if !reflect.DeepEqual(expected, actual) { t.Fatalf("unexpected output, got: %s\n, want:%s\n", got, want) } } var expectedOutputJSON = `{"format_version":"1.0","modules":[{"key":"test","source":"./mods/test","version":""},{"key":"test2","source":"./test2","version":""},{"key":"test3","source":"./test3","version":""},{"key":"other","source":"./mods/other","version":""}]}`