| // Copyright (c) The OpenTofu Authors | |
| // SPDX-License-Identifier: MPL-2.0 | |
| // Copyright (c) 2023 HashiCorp, Inc. | |
| // SPDX-License-Identifier: MPL-2.0 | |
| package arguments | |
| import ( | |
| "strings" | |
| "testing" | |
| "github.com/google/go-cmp/cmp" | |
| "github.com/opentofu/opentofu/internal/collections" | |
| "github.com/opentofu/opentofu/internal/linting" | |
| ) | |
| func TestParseStatePush_basicValidation(t *testing.T) { | |
| testCases := map[string]struct { | |
| args []string | |
| want *StatePush | |
| wantErrText string | |
| }{ | |
| "no arguments": { | |
| args: nil, | |
| want: statePushArgsWithDefaults(nil), | |
| wantErrText: "Expected exactly one positional argument", | |
| }, | |
| "too many arguments": { | |
| args: []string{"state1.tfstate", "state2.tfstate"}, | |
| want: statePushArgsWithDefaults(func(v *StatePush) { | |
| v.StateSrc = "state1.tfstate" | |
| }), | |
| wantErrText: "Expected exactly one positional argument", | |
| }, | |
| "valid state file path": { | |
| args: []string{"terraform.tfstate"}, | |
| want: statePushArgsWithDefaults(func(v *StatePush) { | |
| v.StateSrc = "terraform.tfstate" | |
| }), | |
| }, | |
| "stdin as source": { | |
| args: []string{"-"}, | |
| want: statePushArgsWithDefaults(func(v *StatePush) { | |
| v.StateSrc = "-" | |
| }), | |
| }, | |
| "force flag": { | |
| args: []string{"-force", "terraform.tfstate"}, | |
| want: statePushArgsWithDefaults(func(v *StatePush) { | |
| v.Force = true | |
| v.StateSrc = "terraform.tfstate" | |
| }), | |
| }, | |
| "lock flag": { | |
| args: []string{"-lock=false", "terraform.tfstate"}, | |
| want: statePushArgsWithDefaults(func(v *StatePush) { | |
| v.State.Lock = false | |
| v.StateSrc = "terraform.tfstate" | |
| }), | |
| }, | |
| "lock-timeout flag": { | |
| args: []string{"-lock-timeout=30s", "terraform.tfstate"}, | |
| want: statePushArgsWithDefaults(func(v *StatePush) { | |
| v.State.LockTimeout = 30000000000 // 30s in nanoseconds | |
| v.StateSrc = "terraform.tfstate" | |
| }), | |
| }, | |
| "ignore-remote-version flag": { | |
| args: []string{"-ignore-remote-version", "terraform.tfstate"}, | |
| want: statePushArgsWithDefaults(func(v *StatePush) { | |
| v.Backend.IgnoreRemoteVersion = true | |
| v.StateSrc = "terraform.tfstate" | |
| }), | |
| }, | |
| "multiple flags combined": { | |
| args: []string{"-force", "-lock=false", "-ignore-remote-version", "terraform.tfstate"}, | |
| want: statePushArgsWithDefaults(func(v *StatePush) { | |
| v.Force = true | |
| v.State.Lock = false | |
| v.Backend.IgnoreRemoteVersion = true | |
| v.StateSrc = "terraform.tfstate" | |
| }), | |
| }, | |
| "unknown flag": { | |
| args: []string{"-unknown-flag", "terraform.tfstate"}, | |
| want: statePushArgsWithDefaults(nil), | |
| wantErrText: "flag provided but not defined: -unknown-flag", | |
| }, | |
| } | |
| for name, tc := range testCases { | |
| t.Run(name, func(t *testing.T) { | |
| got, closer, diags := ParseStatePush(tc.args) | |
| defer closer() | |
| if tc.wantErrText != "" && len(diags) == 0 { | |
| t.Errorf("test wanted error but got nothing") | |
| } else if tc.wantErrText == "" && len(diags) > 0 { | |
| t.Errorf("test didn't expect errors but got some: %s", diags.ErrWithWarnings()) | |
| } else if tc.wantErrText != "" && len(diags) > 0 { | |
| errStr := diags.ErrWithWarnings().Error() | |
| if !strings.Contains(errStr, tc.wantErrText) { | |
| t.Errorf("the returned diagnostics does not contain the expected error message.\ndiags:\n%s\nwanted: %s\n", errStr, tc.wantErrText) | |
| } | |
| } | |
| if diff := cmp.Diff(tc.want, got); diff != "" { | |
| t.Errorf("unexpected result\n%s", diff) | |
| } | |
| }) | |
| } | |
| } | |
| func TestParseStatePush_vars(t *testing.T) { | |
| testCases := map[string]struct { | |
| args []string | |
| wantCount int | |
| wantEmpty bool | |
| }{ | |
| "no vars": { | |
| args: []string{"terraform.tfstate"}, | |
| wantCount: 0, | |
| wantEmpty: true, | |
| }, | |
| "single var": { | |
| args: []string{"-var", "foo=bar", "terraform.tfstate"}, | |
| wantCount: 1, | |
| wantEmpty: false, | |
| }, | |
| "single var-file": { | |
| args: []string{"-var-file", "terraform.tfvars", "terraform.tfstate"}, | |
| wantCount: 1, | |
| wantEmpty: false, | |
| }, | |
| "multiple vars mixed": { | |
| args: []string{"-var", "a=1", "-var-file", "f.tfvars", "-var", "b=2", "terraform.tfstate"}, | |
| wantCount: 3, | |
| wantEmpty: false, | |
| }, | |
| } | |
| for name, tc := range testCases { | |
| t.Run(name, func(t *testing.T) { | |
| got, closer, diags := ParseStatePush(tc.args) | |
| defer closer() | |
| if len(diags) > 0 { | |
| t.Fatalf("unexpected diags: %v", diags) | |
| } | |
| if got.Vars.Empty() != tc.wantEmpty { | |
| t.Errorf("Vars.Empty() = %v, want %v", got.Vars.Empty(), tc.wantEmpty) | |
| } | |
| if len(got.Vars.All()) != tc.wantCount { | |
| t.Errorf("len(Vars.All()) = %d, want %d", len(got.Vars.All()), tc.wantCount) | |
| } | |
| }) | |
| } | |
| } | |
| func statePushArgsWithDefaults(mutate func(v *StatePush)) *StatePush { | |
| ret := &StatePush{ | |
| StateSrc: "", | |
| Force: false, | |
| View: &View{ | |
| ConsolidateWarnings: true, | |
| ViewType: ViewHuman, | |
| InputEnabled: false, | |
| LintInclude: make(collections.Set[linting.RuleAddr]), | |
| LintExclude: make(collections.Set[linting.RuleAddr]), | |
| }, | |
| Vars: &Vars{}, | |
| Backend: &Backend{ | |
| IgnoreRemoteVersion: false, | |
| Reconfigure: false, | |
| MigrateState: false, | |
| ForceInitCopy: false, | |
| }, | |
| State: &State{ | |
| Lock: true, | |
| }, | |
| } | |
| if mutate != nil { | |
| mutate(ret) | |
| } | |
| return ret | |
| } |