// Copyright (c) The OpenTofu Authors // SPDX-License-Identifier: MPL-2.0 // Copyright (c) 2023 HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package command import ( "context" "strings" "testing" "time" "github.com/mitchellh/cli" "github.com/opentofu/opentofu/internal/getmodules" ) func TestGet(t *testing.T) { wd := tempWorkingDirFixture(t, "get") t.Chdir(wd.RootModuleDir()) ui := cli.NewMockUi() c := &GetCommand{ Meta: Meta{ testingOverrides: metaOverridesForProvider(testProvider()), Ui: ui, WorkingDir: wd, }, } args := []string{} if code := c.Run(args); code != 0 { t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) } output := ui.OutputWriter.String() if !strings.Contains(output, "- foo in") { t.Fatalf("doesn't look like get: %s", output) } } func TestGet_multipleArgs(t *testing.T) { wd := tempWorkingDir(t) t.Chdir(wd.RootModuleDir()) ui := cli.NewMockUi() c := &GetCommand{ Meta: Meta{ testingOverrides: metaOverridesForProvider(testProvider()), Ui: ui, WorkingDir: wd, }, } args := []string{ "bad", "bad", } if code := c.Run(args); code != 1 { t.Fatalf("bad: \n%s", ui.OutputWriter.String()) } } func TestGet_update(t *testing.T) { wd := tempWorkingDirFixture(t, "get") t.Chdir(wd.RootModuleDir()) ui := cli.NewMockUi() c := &GetCommand{ Meta: Meta{ testingOverrides: metaOverridesForProvider(testProvider()), Ui: ui, WorkingDir: wd, }, } args := []string{ "-update", } if code := c.Run(args); code != 0 { t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) } output := ui.OutputWriter.String() if !strings.Contains(output, `- foo in`) { t.Fatalf("doesn't look like get: %s", output) } } func TestGet_cancel(t *testing.T) { // This test runs `tofu get` against a server that stalls indefinitely // instead of responding, and then requests shutdown in the same way // as package main would in response to SIGINT (or similar on other // platforms). This ensures that slow requests can be interrupted. wd := tempWorkingDirFixture(t, "init-module-early-eval") t.Chdir(wd.RootModuleDir()) // One failure mode of this test is for the cancellation to fail and // so the command runs indefinitely, and so we'll impose a timeout // to allow us to eventually catch that and diagnose it as a test // failure message. ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) defer cancel() // This server intentionally stalls any incoming request by leaving // the connection open but not responding. "reqs" will become // readable each time a request arrives. server, reqs := testHangServer(t) // We'll close this channel once we've been notified that the server // received our request, which should then cause cancellation. shutdownCh := make(chan struct{}) go func() { select { case <-reqs: // Request received, so time to interrupt. t.Log("server received request, but won't respond") close(shutdownCh) case <-ctx.Done(): // Exit early if we reach our timeout. t.Log("timeout before server received request") } server.CloseClientConnections() // force any active client request to fail }() ui := cli.NewMockUi() c := &GetCommand{ Meta: Meta{ testingOverrides: metaOverridesForProvider(testProvider()), Ui: ui, WorkingDir: wd, ShutdownCh: shutdownCh, // This test needs a real module package fetcher instance because // we want to attempt installing a module package from our server. ModulePackageFetcher: getmodules.NewPackageFetcher(t.Context(), nil), }, } fakeModuleSourceAddr := server.URL + "/example.zip" t.Logf("attempting to install module package from %s", fakeModuleSourceAddr) args := []string{"-var=module_source=" + fakeModuleSourceAddr} code := c.Run(args) if err := ctx.Err(); err != nil { t.Errorf("context error: %s", err) // probably reporting a timeout } if code == 0 { t.Fatalf("succeeded; wanted error\n%s", ui.OutputWriter.String()) } if got, want := ui.ErrorWriter.String(), `Module installation was canceled by an interrupt signal`; !strings.Contains(got, want) { t.Fatalf("wrong error message\nshould contain: %s\ngot:\n%s", want, got) } } func TestGetCommand_InvalidArgs(t *testing.T) { testCases := []struct { name string args []string expected string }{ { name: "TooManyArgs", args: []string{"too", "many", "args"}, expected: "Too many command line arguments", }, { name: "InvalidArgFormat", args: []string{"--invalid-arg"}, expected: "flag provided but not defined", }, { name: "MixedValidAndInvalidArgs", args: []string{"--update", "--invalid-arg"}, expected: "flag provided but not defined", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { wd := tempWorkingDirFixture(t, "get") t.Chdir(wd.RootModuleDir()) ui := cli.NewMockUi() c := &GetCommand{ Meta: Meta{ testingOverrides: metaOverridesForProvider(testProvider()), Ui: ui, WorkingDir: wd, }, } if code := c.Run(tc.args); code != 1 { t.Errorf("Expected error code 1 for invalid arguments, got %d", code) } if !strings.Contains(ui.ErrorWriter.String(), tc.expected) { t.Errorf("Expected error message to contain '%s', got: %s", tc.expected, ui.ErrorWriter.String()) } }) } }