// Copyright (c) The OpenTofu Authors // SPDX-License-Identifier: MPL-2.0 // Copyright (c) 2023 HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package views import ( "os" "testing" "github.com/google/go-cmp/cmp" "github.com/hashicorp/go-version" "github.com/opentofu/opentofu/internal/initwd" ) func TestModuleInstallationHooks(t *testing.T) { tests := map[string]struct { viewCall func(hook initwd.ModuleInstallHooks) showLocalPaths bool wantJson []map[string]any wantStdout string wantStderr string }{ "download_with_version": { viewCall: func(hook initwd.ModuleInstallHooks) { hook.Download("root.networking", "git::https://example.com/module.git", version.Must(version.NewVersion("2.5.3"))) }, wantJson: []map[string]any{ { "@level": "info", "@message": "Downloading git::https://example.com/module.git 2.5.3 for root.networking...", "@module": "tofu.ui", }, }, wantStdout: withNewline("Downloading git::https://example.com/module.git 2.5.3 for root.networking..."), }, "download_without_version": { viewCall: func(hook initwd.ModuleInstallHooks) { hook.Download("root.storage", "git::https://example.com/storage.git", nil) }, wantJson: []map[string]any{ { "@level": "info", "@message": "Downloading git::https://example.com/storage.git for root.storage...", "@module": "tofu.ui", }, }, wantStdout: withNewline("Downloading git::https://example.com/storage.git for root.storage..."), }, "install_without_local_path": { viewCall: func(hook initwd.ModuleInstallHooks) { hook.Install("root.networking", version.Must(version.NewVersion("2.5.3")), "/path/to/.terraform/modules/networking") }, wantJson: []map[string]any{ { "@level": "info", "@message": "installing root.networking", "@module": "tofu.ui", }, }, wantStdout: withNewline("- root.networking"), }, "install_with_local_path": { viewCall: func(hook initwd.ModuleInstallHooks) { hook.Install("root.networking", version.Must(version.NewVersion("2.5.3")), "/path/to/.terraform/modules/networking") }, showLocalPaths: true, wantJson: []map[string]any{ { "@level": "info", "@message": "installing root.networking in /path/to/.terraform/modules/networking", "@module": "tofu.ui", }, }, wantStdout: withNewline("- root.networking in /path/to/.terraform/modules/networking"), }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { testModuleInstallationHookHuman(t, tc.viewCall, tc.showLocalPaths, tc.wantStdout, tc.wantStderr) testModuleInstallationHookJson(t, tc.viewCall, tc.showLocalPaths, tc.wantJson) testModuleInstallationHookMulti(t, tc.viewCall, tc.showLocalPaths, tc.wantStdout, tc.wantStderr, tc.wantJson) }) } } func testModuleInstallationHookHuman(t *testing.T, call func(init initwd.ModuleInstallHooks), showLocalPaths bool, wantStdout, wantStderr string) { view, done := testView(t) moduleInstallationViewCall := moduleInstallationHookHuman{v: view, showLocalPaths: showLocalPaths} call(moduleInstallationViewCall) output := done(t) if diff := cmp.Diff(wantStderr, output.Stderr()); diff != "" { t.Errorf("invalid stderr (-want, +got):\n%s", diff) } if diff := cmp.Diff(wantStdout, output.Stdout()); diff != "" { t.Errorf("invalid stdout (-want, +got):\n%s", diff) } } func testModuleInstallationHookJson(t *testing.T, call func(init initwd.ModuleInstallHooks), showLocalPaths bool, want []map[string]any) { // New type just to assert the fields that we are interested in view, done := testView(t) moduleInstallationViewCall := moduleInstallationHookJSON{v: NewJSONView(view, nil), showLocalPaths: showLocalPaths} call(moduleInstallationViewCall) output := done(t) if output.Stderr() != "" { t.Errorf("expected no stderr but got:\n%s", output.Stderr()) } testJSONViewOutputEquals(t, output.Stdout(), want) } func testModuleInstallationHookMulti(t *testing.T, call func(init initwd.ModuleInstallHooks), showLocalPaths bool, wantStdout string, wantStderr string, want []map[string]any) { jsonInto, err := os.CreateTemp(t.TempDir(), "json-into-*") if err != nil { t.Fatalf("failed to create the file to write json content into: %s", err) } view, done := testView(t) moduleInstallationViewCall := moduleInstallationHookMulti{ moduleInstallationHookHuman{v: view, showLocalPaths: showLocalPaths}, moduleInstallationHookJSON{v: NewJSONView(view, jsonInto), showLocalPaths: showLocalPaths}, } call(moduleInstallationViewCall) { if err := jsonInto.Close(); err != nil { t.Fatalf("failed to close the jsonInto file: %s", err) } // check the fileInto content fileContent, err := os.ReadFile(jsonInto.Name()) if err != nil { t.Fatalf("failed to read the file content with the json output: %s", err) } testJSONViewOutputEquals(t, string(fileContent), want) } { // check the human output output := done(t) if diff := cmp.Diff(wantStderr, output.Stderr()); diff != "" { t.Errorf("invalid stderr (-want, +got):\n%s", diff) } if diff := cmp.Diff(wantStdout, output.Stdout()); diff != "" { t.Errorf("invalid stdout (-want, +got):\n%s", diff) } } }