/
githubmirror
/
terraform
Обзор
Документация
Войти
/
githubmirror
/
terraform
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
internal/command/console_test.go
495 строк
16 KB
Sarah French
refactor: Replace remaining use of `cli.NewMockUi()` or `new(cli.MockUi)` in tests in the command package
16 июл 2026, 00:45
16 июл 2026, 00:45
91f04bd
Код
Авторство
О чём код?
// Copyright IBM Corp. 2014, 2026 // SPDX-License-Identifier: BUSL-1.1 package command import ( "bytes" "io/ioutil" "path/filepath" "strings" "testing" "github.com/hashicorp/cli" testing_command "github.com/hashicorp/terraform/internal/command/testing" "github.com/hashicorp/terraform/internal/configs/configschema" "github.com/hashicorp/terraform/internal/providers" "github.com/zclconf/go-cty/cty" ) // ConsoleCommand is tested primarily with tests in the "repl" package. // It is not tested here because the Console uses a readline-like library // that takes over stdin/stdout. It is difficult to test directly. The // core logic is tested in "repl" // // This file still contains some tests using the stdin-based input. func TestConsole_basic(t *testing.T) { tmp := t.TempDir() t.Chdir(tmp) p := testProvider() ui := testUiWrapped(t) view, _ := testView(t) c := &ConsoleCommand{ Meta: Meta{ testingOverrides: metaOverridesForProvider(p), Ui: ui, View: view, }, } var output bytes.Buffer defer testStdinPipe(t, strings.NewReader("1+5\n"))() outCloser := testStdoutCapture(t, &output) args := []string{} code := c.Run(args) outCloser() if code != 0 { t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) } actual := output.String() if actual != "6\n" { t.Fatalf("bad: %q", actual) } } func TestConsole_tfvars(t *testing.T) { td := t.TempDir() testCopyDir(t, testFixturePath("apply-vars"), td) t.Chdir(td) // Write a terraform.tvars varFilePath := filepath.Join(td, "terraform.tfvars") if err := ioutil.WriteFile(varFilePath, []byte(applyVarFile), 0644); err != nil { t.Fatalf("err: %s", err) } p := testProvider() p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ ResourceTypes: map[string]providers.Schema{ "test_instance": { Body: &configschema.Block{ Attributes: map[string]*configschema.Attribute{ "value": {Type: cty.String, Optional: true}, }, }, }, }, } ui := testUiWrapped(t) view, _ := testView(t) c := &ConsoleCommand{ Meta: Meta{ testingOverrides: metaOverridesForProvider(p), Ui: ui, View: view, }, } var output bytes.Buffer defer testStdinPipe(t, strings.NewReader("var.foo\n"))() outCloser := testStdoutCapture(t, &output) args := []string{} code := c.Run(args) outCloser() if code != 0 { t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) } actual := output.String() if actual != "\"bar\"\n" { t.Fatalf("bad: %q", actual) } } func TestConsole_unsetRequiredVars(t *testing.T) { // This test is verifying that it's possible to run "terraform console" // without providing values for all required variables, without // "terraform console" producing an interactive prompt for those variables // or producing errors. Instead, it should allow evaluation in that // partial context but see the unset variables values as being unknown. // // This test fixture includes variable "foo" {}, which we are // intentionally not setting here. td := t.TempDir() testCopyDir(t, testFixturePath("apply-vars"), td) t.Chdir(td) p := testProvider() p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ ResourceTypes: map[string]providers.Schema{ "test_instance": { Body: &configschema.Block{ Attributes: map[string]*configschema.Attribute{ "value": {Type: cty.String, Optional: true}, }, }, }, }, } ui := testUiWrapped(t) view, _ := testView(t) c := &ConsoleCommand{ Meta: Meta{ testingOverrides: metaOverridesForProvider(p), Ui: ui, View: view, }, } var output bytes.Buffer defer testStdinPipe(t, strings.NewReader("var.foo\n"))() outCloser := testStdoutCapture(t, &output) args := []string{} code := c.Run(args) outCloser() if code != 0 { t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) } if got, want := output.String(), "(known after apply)\n"; got != want { t.Fatalf("unexpected output\n got: %q\nwant: %q", got, want) } } func TestConsole_variables(t *testing.T) { td := t.TempDir() testCopyDir(t, testFixturePath("variables"), td) t.Chdir(td) p := testProvider() ui := testUiWrapped(t) view, _ := testView(t) c := &ConsoleCommand{ Meta: Meta{ testingOverrides: metaOverridesForProvider(p), Ui: ui, View: view, }, } commands := map[string]string{ "var.foo\n": "\"bar\"\n", "var.snack\n": "\"popcorn\"\n", "var.secret_snack\n": "(sensitive value)\n", "local.snack_bar\n": "[\n \"popcorn\",\n (sensitive value),\n]\n", } args := []string{} for cmd, val := range commands { var output bytes.Buffer defer testStdinPipe(t, strings.NewReader(cmd))() outCloser := testStdoutCapture(t, &output) code := c.Run(args) outCloser() if code != 0 { t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) } actual := output.String() if output.String() != val { t.Fatalf("bad: %q, expected %q", actual, val) } } } func TestConsole_modules(t *testing.T) { td := t.TempDir() testCopyDir(t, testFixturePath("modules"), td) t.Chdir(td) p := applyFixtureProvider() ui := testUiWrapped(t) view, _ := testView(t) c := &ConsoleCommand{ Meta: Meta{ testingOverrides: metaOverridesForProvider(p), Ui: ui, View: view, }, } commands := map[string]string{ "module.child.myoutput\n": "\"bar\"\n", "module.count_child[0].myoutput\n": "\"bar\"\n", "local.foo\n": "3\n", } args := []string{} for cmd, val := range commands { var output bytes.Buffer defer testStdinPipe(t, strings.NewReader(cmd))() outCloser := testStdoutCapture(t, &output) code := c.Run(args) outCloser() if code != 0 { t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) } actual := output.String() if output.String() != val { t.Fatalf("bad: %q, expected %q", actual, val) } } } func TestConsole_modulesPlan(t *testing.T) { td := t.TempDir() testCopyDir(t, testFixturePath("apply"), td) t.Chdir(td) p := applyFixtureProvider() ui := testUiWrapped(t) view, _ := testView(t) c := &ConsoleCommand{ Meta: Meta{ testingOverrides: metaOverridesForProvider(p), Ui: ui, View: view, }, } commands := map[string]string{ "test_instance.foo.ami\n": "\"bar\"\n", } // The -plan option means that we'll be evaluating expressions against // a plan constructed from this configuration, instead of against its // (non-existent) prior state. args := []string{"-plan"} for cmd, val := range commands { var output bytes.Buffer defer testStdinPipe(t, strings.NewReader(cmd))() outCloser := testStdoutCapture(t, &output) code := c.Run(args) outCloser() if code != 0 { t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) } actual := output.String() if output.String() != val { t.Fatalf("bad: %q, expected %q", actual, val) } } } func TestConsole_scope(t *testing.T) { testCases := map[string]struct { args []string expectedConsoleEvals map[string]string }{ "root-module": { expectedConsoleEvals: map[string]string{ "var.root\n": "\"variable data\"\n", "local.root\n": "\"variable data -> local\"\n", // resource data is unknown as the state of this resource doesn't exist "test_resource.root.value\n": "(known after apply)\n", "module.child_single.child_output\n": "\"child module output\"\n", "module.child_foreach[\"key1\"].child_output\n": "\"child module output\"\n", "module.child_foreach[\"key2\"].child_output\n": "\"child module output\"\n", "module.child_count[0].child_output\n": "\"child module output\"\n", "module.child_count[1].child_output\n": "\"child module output\"\n", }, }, "root-module-planeval": { args: []string{"-plan"}, expectedConsoleEvals: map[string]string{ "var.root\n": "\"variable data\"\n", "local.root\n": "\"variable data -> local\"\n", "test_resource.root.value\n": "\"resource attr set to local -> variable data -> local\"\n", "module.child_single.child_output\n": "\"child module output\"\n", "module.child_foreach[\"key1\"].child_output\n": "\"child module output\"\n", "module.child_foreach[\"key2\"].child_output\n": "\"child module output\"\n", "module.child_count[0].child_output\n": "\"child module output\"\n", "module.child_count[1].child_output\n": "\"child module output\"\n", }, }, "child-single-module": { args: []string{"-scope=module.child_single"}, expectedConsoleEvals: map[string]string{ "var.child_input\n": "\"variable data -> child\"\n", "local.child\n": "\"variable data -> child -> local\"\n", // resource data is unknown as the state of this resource doesn't exist "test_resource.child.value\n": "(known after apply)\n", "module.grandchild[\"key\"].grandchild_output\n": "\"grandchild module output\"\n", }, }, "child-single-module-planeval": { args: []string{"-plan", "-scope=module.child_single"}, expectedConsoleEvals: map[string]string{ "var.child_input\n": "\"variable data -> child\"\n", "local.child\n": "\"variable data -> child -> local\"\n", "test_resource.child.value\n": "\"resource attr set to local -> variable data -> child -> local\"\n", "module.grandchild[\"key\"].grandchild_output\n": "\"grandchild module output\"\n", }, }, "child-foreach-module": { args: []string{`-scope=module.child_foreach["key1"]`}, expectedConsoleEvals: map[string]string{ "var.child_input\n": "\"variable data -> child[key1]\"\n", "local.child\n": "\"variable data -> child[key1] -> local\"\n", // resource data is unknown as the state of this resource doesn't exist "test_resource.child.value\n": "(known after apply)\n", "module.grandchild[\"key\"].grandchild_output\n": "\"grandchild module output\"\n", }, }, "child-foreach-module-planeval": { args: []string{"-plan", `-scope=module.child_foreach["key2"]`}, expectedConsoleEvals: map[string]string{ "var.child_input\n": "\"variable data -> child[key2]\"\n", "local.child\n": "\"variable data -> child[key2] -> local\"\n", "test_resource.child.value\n": "\"resource attr set to local -> variable data -> child[key2] -> local\"\n", "module.grandchild[\"key\"].grandchild_output\n": "\"grandchild module output\"\n", }, }, "child-count-module": { args: []string{`-scope=module.child_count[0]`}, expectedConsoleEvals: map[string]string{ "var.child_input\n": "\"variable data -> child[0]\"\n", "local.child\n": "\"variable data -> child[0] -> local\"\n", // resource data is unknown as the state of this resource doesn't exist "test_resource.child.value\n": "(known after apply)\n", "module.grandchild[\"key\"].grandchild_output\n": "\"grandchild module output\"\n", }, }, "child-count-module-planeval": { args: []string{"-plan", `-scope=module.child_count[1]`}, expectedConsoleEvals: map[string]string{ "var.child_input\n": "\"variable data -> child[1]\"\n", "local.child\n": "\"variable data -> child[1] -> local\"\n", "test_resource.child.value\n": "\"resource attr set to local -> variable data -> child[1] -> local\"\n", "module.grandchild[\"key\"].grandchild_output\n": "\"grandchild module output\"\n", }, }, "grandchild-module": { args: []string{`-scope=module.child_single.module.grandchild["key"]`}, expectedConsoleEvals: map[string]string{ "var.grandchild_input\n": "\"variable data -> child -> grandchild[key]\"\n", "local.grandchild\n": "\"variable data -> child -> grandchild[key] -> local\"\n", // resource data is unknown as the state of this resource doesn't exist "test_resource.grandchild.value\n": "(known after apply)\n", }, }, "grandchild-module-planeval": { args: []string{"-plan", `-scope=module.child_count[0].module.grandchild["key"]`}, expectedConsoleEvals: map[string]string{ "var.grandchild_input\n": "\"variable data -> child[0] -> grandchild[key]\"\n", "local.grandchild\n": "\"variable data -> child[0] -> grandchild[key] -> local\"\n", "test_resource.grandchild.value\n": "\"resource attr set to local -> variable data -> child[0] -> grandchild[key] -> local\"\n", }, }, } for name, tc := range testCases { t.Run(name, func(t *testing.T) { td := t.TempDir() testCopyDir(t, testFixturePath("console-nested-module-scopes"), td) t.Chdir(td) ui := cli.NewMockUi() view, _ := testView(t) p := testing_command.NewProvider(&testing_command.ResourceStore{}).Provider c := &ConsoleCommand{ Meta: Meta{ testingOverrides: metaOverridesForProvider(p), Ui: ui, View: view, }, } tc.args = append(tc.args, []string{"-var", "root=variable data"}...) for cmd, val := range tc.expectedConsoleEvals { var output bytes.Buffer defer testStdinPipe(t, strings.NewReader(cmd))() outCloser := testStdoutCapture(t, &output) code := c.Run(tc.args) outCloser() if code != 0 { t.Errorf("bad exit code: %d\n\n%s", code, ui.ErrorWriter.String()) } actual := output.String() if output.String() != val { t.Errorf("bad result for %q: %q, expected %q", cmd, actual, val) } } }) } } func TestConsole_scope_errors(t *testing.T) { testCases := map[string]struct { args []string expectedError string }{ "invalid-traversal": { args: []string{"-scope=foo."}, expectedError: `Dot must be followed by attribute name.`, }, "invalid-module-address-syntax": { args: []string{"-scope=test_resource.root"}, expectedError: `A module instance address must begin with "module."`, }, "module-not-found": { args: []string{"-scope=module.nonexistent"}, expectedError: `The module address "module.nonexistent" does not have an evaluation scope.`, }, "module-not-found-planeval": { args: []string{"-plan", "-scope=module.nonexistent[0]"}, expectedError: `The module address "module.nonexistent[0]" does not have an evaluation scope.`, }, "module-instance-not-found": { args: []string{"-scope=module.child_count[2]"}, expectedError: `The module address "module.child_count[2]" does not have an evaluation scope.`, }, "module-instance-not-found-planeval": { args: []string{"-plan", `-scope=module.child_foreach["key3"]`}, expectedError: "The module address \"module.child_foreach[\"key3\"]\" does not have an evaluation\nscope.", }, } for name, tc := range testCases { t.Run(name, func(t *testing.T) { td := t.TempDir() testCopyDir(t, testFixturePath("console-nested-module-scopes"), td) t.Chdir(td) ui := cli.NewMockUi() view, _ := testView(t) p := testing_command.NewProvider(&testing_command.ResourceStore{}).Provider c := &ConsoleCommand{ Meta: Meta{ testingOverrides: metaOverridesForProvider(p), View: view, Ui: ui, }, } tc.args = append(tc.args, []string{"-var", "root=variable data"}...) defer testStdinPipe(t, strings.NewReader(""))() code := c.Run(tc.args) if code == 0 { t.Fatal("expected a non-zero exit code with error") } actual := ui.ErrorWriter.String() if !strings.Contains(actual, tc.expectedError) { t.Fatalf("expected error to include %q, but got:\n%s", tc.expectedError, actual) } }) } }