// 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 ( "flag" "os" "path/filepath" "strings" "testing" "github.com/opentofu/opentofu/internal/command/arguments" "github.com/opentofu/opentofu/internal/command/workdir" "github.com/opentofu/opentofu/internal/backend" "github.com/opentofu/opentofu/internal/backend/local" "github.com/opentofu/opentofu/internal/tofu" ) func TestMetaInputMode(t *testing.T) { test = false defer func() { test = true }() m := &Meta{ WorkingDir: workdir.NewDir("."), } // TODO meta-refactor: these assignments are needed because the extendedFlagSet was used here before, // which had these with defaults as "true". In a future iteration, once these are not needed, we need to remove them. m.input = true m.stateLock = true args := []string{} fs := flag.NewFlagSet("foo", flag.ContinueOnError) if err := fs.Parse(args); err != nil { t.Fatalf("err: %s", err) } if m.InputMode() != tofu.InputModeStd { t.Fatalf("bad: %#v", m.InputMode()) } } func TestMetaInputMode_envVar(t *testing.T) { test = false defer func() { test = true }() m := &Meta{ WorkingDir: workdir.NewDir("."), } // TODO meta-refactor: these assignments are needed because the extendedFlagSet was used here before, // which had these with defaults as "true". In a future iteration, once these are not needed, we need to remove them. m.input = true m.stateLock = true args := []string{} fs := flag.NewFlagSet("foo", flag.ContinueOnError) if err := fs.Parse(args); err != nil { t.Fatalf("err: %s", err) } off := tofu.InputMode(0) on := tofu.InputModeStd cases := []struct { EnvVar string Expected tofu.InputMode }{ {"false", off}, {"0", off}, {"true", on}, {"1", on}, } for _, tc := range cases { t.Setenv(InputModeEnvVar, tc.EnvVar) if m.InputMode() != tc.Expected { t.Fatalf("expected InputMode: %#v, got: %#v", tc.Expected, m.InputMode()) } } } func TestMetaInputMode_disable(t *testing.T) { test = false defer func() { test = true }() m := &Meta{ WorkingDir: workdir.NewDir("."), } // TODO meta-refactor: these assignments are needed because the extendedFlagSet was used here before, // which had these with defaults as "true". In a future iteration, once these are not needed, we need to remove them. m.input = true m.stateLock = true args := []string{"-input=false"} fs := flag.NewFlagSet("foo", flag.ContinueOnError) var viewOpts arguments.ViewOptions viewOpts.AddFlags(fs, true) if err := fs.Parse(args); err != nil { t.Fatalf("err: %s", err) } if _, diags := viewOpts.Parse(); len(diags) > 0 { t.Fatalf("unexpected diagnostics: %s", diags) } m.input = viewOpts.InputEnabled if m.InputMode() > 0 { t.Fatalf("bad: %#v", m.InputMode()) } } func TestMeta_initStatePaths(t *testing.T) { m := &Meta{ WorkingDir: workdir.NewDir("."), } m.initStatePaths() if m.statePath != arguments.DefaultStateFilename { t.Fatalf("bad: %#v", m) } if m.stateOutPath != arguments.DefaultStateFilename { t.Fatalf("bad: %#v", m) } if m.backupPath != arguments.DefaultStateFilename+DefaultBackupExtension { t.Fatalf("bad: %#v", m) } m = &Meta{ WorkingDir: workdir.NewDir("."), } m.statePath = "foo" m.initStatePaths() if m.stateOutPath != "foo" { t.Fatalf("bad: %#v", m) } if m.backupPath != "foo"+DefaultBackupExtension { t.Fatalf("bad: %#v", m) } m = &Meta{ WorkingDir: workdir.NewDir("."), } m.stateOutPath = "foo" m.initStatePaths() if m.statePath != arguments.DefaultStateFilename { t.Fatalf("bad: %#v", m) } if m.backupPath != "foo"+DefaultBackupExtension { t.Fatalf("bad: %#v", m) } } func TestMeta_Env(t *testing.T) { td := t.TempDir() t.Chdir(td) m := &Meta{ WorkingDir: workdir.NewDir("."), } env, err := m.Workspace(t.Context()) if err != nil { t.Fatal(err) } if env != backend.DefaultStateName { t.Fatalf("expected env %q, got env %q", backend.DefaultStateName, env) } testEnv := "test_env" if err := m.SetWorkspace(testEnv); err != nil { t.Fatal("error setting env:", err) } env, _ = m.Workspace(t.Context()) if env != testEnv { t.Fatalf("expected env %q, got env %q", testEnv, env) } if err := m.SetWorkspace(backend.DefaultStateName); err != nil { t.Fatal("error setting env:", err) } env, _ = m.Workspace(t.Context()) if env != backend.DefaultStateName { t.Fatalf("expected env %q, got env %q", backend.DefaultStateName, env) } } func TestMeta_Workspace_override(t *testing.T) { m := &Meta{ WorkingDir: workdir.NewDir("."), } testCases := map[string]struct { workspace string err error }{ "": { "default", nil, }, "development": { "development", nil, }, "invalid name": { "", errInvalidWorkspaceNameEnvVar, }, } for name, tc := range testCases { t.Run(name, func(t *testing.T) { t.Setenv(WorkspaceNameEnvVar, name) workspace, err := m.Workspace(t.Context()) if workspace != tc.workspace { t.Errorf("Unexpected workspace\n got: %s\nwant: %s\n", workspace, tc.workspace) } if err != tc.err { t.Errorf("Unexpected error\n got: %s\nwant: %s\n", err, tc.err) } }) } } func TestMeta_Workspace_invalidSelected(t *testing.T) { td := t.TempDir() t.Chdir(td) // this is an invalid workspace name workspace := "test workspace" // create the workspace directories if err := os.MkdirAll(filepath.Join(local.DefaultWorkspaceDir, workspace), 0755); err != nil { t.Fatal(err) } // create the workspace file to select it if err := os.MkdirAll(workdir.DefaultDataDir, 0755); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(workdir.DefaultDataDir, local.DefaultWorkspaceFile), []byte(workspace), 0644); err != nil { t.Fatal(err) } m := &Meta{ WorkingDir: workdir.NewDir("."), } ws, err := m.Workspace(t.Context()) if ws != workspace { t.Errorf("Unexpected workspace\n got: %s\nwant: %s\n", ws, workspace) } if err != nil { t.Errorf("Unexpected error: %s", err) } } func TestCommand_checkRequiredVersion(t *testing.T) { // Create a temporary working directory that is empty td := t.TempDir() testCopyDir(t, testFixturePath("command-check-required-version"), td) t.Chdir(td) view, done := testView(t) meta := Meta{ WorkingDir: workdir.NewDir("."), View: view, } diags := meta.checkRequiredVersion(t.Context()) if diags == nil { t.Fatalf("diagnostics should contain unmet version constraint, but is nil") } view.Diagnostics(diags) // Required version diags are correct output := done(t) errStr := output.Stderr() if !strings.Contains(errStr, `required_version = "~> 0.9.0"`) { t.Fatalf("output should point to unmet version constraint, but is:\n\n%s", errStr) } if strings.Contains(errStr, `required_version = ">= 0.13.0"`) { t.Fatalf("output should not point to met version constraint, but is:\n\n%s", errStr) } }