github.com/opentofu/opentofu@v1.7.1/internal/command/console_test.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  
     6  package command
     7  
     8  import (
     9  	"bytes"
    10  	"os"
    11  	"path/filepath"
    12  	"strings"
    13  	"testing"
    14  
    15  	"github.com/mitchellh/cli"
    16  	"github.com/opentofu/opentofu/internal/configs/configschema"
    17  	"github.com/opentofu/opentofu/internal/providers"
    18  	"github.com/zclconf/go-cty/cty"
    19  )
    20  
    21  // ConsoleCommand is tested primarily with tests in the "repl" package.
    22  // It is not tested here because the Console uses a readline-like library
    23  // that takes over stdin/stdout. It is difficult to test directly. The
    24  // core logic is tested in "repl"
    25  //
    26  // This file still contains some tests using the stdin-based input.
    27  
    28  func TestConsole_basic(t *testing.T) {
    29  	testCwd(t)
    30  
    31  	p := testProvider()
    32  	ui := cli.NewMockUi()
    33  	view, _ := testView(t)
    34  	c := &ConsoleCommand{
    35  		Meta: Meta{
    36  			testingOverrides: metaOverridesForProvider(p),
    37  			Ui:               ui,
    38  			View:             view,
    39  		},
    40  	}
    41  
    42  	var output bytes.Buffer
    43  	defer testStdinPipe(t, strings.NewReader("1+5\n"))()
    44  	outCloser := testStdoutCapture(t, &output)
    45  
    46  	args := []string{}
    47  	code := c.Run(args)
    48  	outCloser()
    49  	if code != 0 {
    50  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
    51  	}
    52  
    53  	actual := output.String()
    54  	if actual != "6\n" {
    55  		t.Fatalf("bad: %q", actual)
    56  	}
    57  }
    58  
    59  func TestConsole_tfvars(t *testing.T) {
    60  	td := t.TempDir()
    61  	testCopyDir(t, testFixturePath("apply-vars"), td)
    62  	defer testChdir(t, td)()
    63  
    64  	// Write a terraform.tvars
    65  	varFilePath := filepath.Join(td, "terraform.tfvars")
    66  	if err := os.WriteFile(varFilePath, []byte(applyVarFile), 0644); err != nil {
    67  		t.Fatalf("err: %s", err)
    68  	}
    69  
    70  	p := testProvider()
    71  	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
    72  		ResourceTypes: map[string]providers.Schema{
    73  			"test_instance": {
    74  				Block: &configschema.Block{
    75  					Attributes: map[string]*configschema.Attribute{
    76  						"value": {Type: cty.String, Optional: true},
    77  					},
    78  				},
    79  			},
    80  		},
    81  	}
    82  	ui := cli.NewMockUi()
    83  	view, _ := testView(t)
    84  	c := &ConsoleCommand{
    85  		Meta: Meta{
    86  			testingOverrides: metaOverridesForProvider(p),
    87  			Ui:               ui,
    88  			View:             view,
    89  		},
    90  	}
    91  
    92  	var output bytes.Buffer
    93  	defer testStdinPipe(t, strings.NewReader("var.foo\n"))()
    94  	outCloser := testStdoutCapture(t, &output)
    95  
    96  	args := []string{}
    97  	code := c.Run(args)
    98  	outCloser()
    99  	if code != 0 {
   100  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   101  	}
   102  
   103  	actual := output.String()
   104  	if actual != "\"bar\"\n" {
   105  		t.Fatalf("bad: %q", actual)
   106  	}
   107  }
   108  
   109  func TestConsole_unsetRequiredVars(t *testing.T) {
   110  	// This test is verifying that it's possible to run "tofu console"
   111  	// without providing values for all required variables, without
   112  	// "tofu console" producing an interactive prompt for those variables
   113  	// or producing errors. Instead, it should allow evaluation in that
   114  	// partial context but see the unset variables values as being unknown.
   115  	//
   116  	// This test fixture includes variable "foo" {}, which we are
   117  	// intentionally not setting here.
   118  	td := t.TempDir()
   119  	testCopyDir(t, testFixturePath("apply-vars"), td)
   120  	defer testChdir(t, td)()
   121  
   122  	p := testProvider()
   123  	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
   124  		ResourceTypes: map[string]providers.Schema{
   125  			"test_instance": {
   126  				Block: &configschema.Block{
   127  					Attributes: map[string]*configschema.Attribute{
   128  						"value": {Type: cty.String, Optional: true},
   129  					},
   130  				},
   131  			},
   132  		},
   133  	}
   134  	ui := cli.NewMockUi()
   135  	view, _ := testView(t)
   136  	c := &ConsoleCommand{
   137  		Meta: Meta{
   138  			testingOverrides: metaOverridesForProvider(p),
   139  			Ui:               ui,
   140  			View:             view,
   141  		},
   142  	}
   143  
   144  	var output bytes.Buffer
   145  	defer testStdinPipe(t, strings.NewReader("var.foo\n"))()
   146  	outCloser := testStdoutCapture(t, &output)
   147  
   148  	args := []string{}
   149  	code := c.Run(args)
   150  	outCloser()
   151  
   152  	if code != 0 {
   153  		t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   154  	}
   155  
   156  	if got, want := output.String(), "(known after apply)\n"; got != want {
   157  		t.Fatalf("unexpected output\n got: %q\nwant: %q", got, want)
   158  	}
   159  }
   160  
   161  func TestConsole_variables(t *testing.T) {
   162  	td := t.TempDir()
   163  	testCopyDir(t, testFixturePath("variables"), td)
   164  	defer testChdir(t, td)()
   165  
   166  	p := testProvider()
   167  	ui := cli.NewMockUi()
   168  	view, _ := testView(t)
   169  	c := &ConsoleCommand{
   170  		Meta: Meta{
   171  			testingOverrides: metaOverridesForProvider(p),
   172  			Ui:               ui,
   173  			View:             view,
   174  		},
   175  	}
   176  
   177  	commands := map[string]string{
   178  		"var.foo\n":          "\"bar\"\n",
   179  		"var.snack\n":        "\"popcorn\"\n",
   180  		"var.secret_snack\n": "(sensitive value)\n",
   181  		"local.snack_bar\n":  "[\n  \"popcorn\",\n  (sensitive value),\n]\n",
   182  	}
   183  
   184  	args := []string{}
   185  
   186  	for cmd, val := range commands {
   187  		var output bytes.Buffer
   188  		defer testStdinPipe(t, strings.NewReader(cmd))()
   189  		outCloser := testStdoutCapture(t, &output)
   190  		code := c.Run(args)
   191  		outCloser()
   192  		if code != 0 {
   193  			t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   194  		}
   195  
   196  		actual := output.String()
   197  		if output.String() != val {
   198  			t.Fatalf("bad: %q, expected %q", actual, val)
   199  		}
   200  	}
   201  }
   202  
   203  func TestConsole_modules(t *testing.T) {
   204  	td := t.TempDir()
   205  	testCopyDir(t, testFixturePath("modules"), td)
   206  	defer testChdir(t, td)()
   207  
   208  	p := applyFixtureProvider()
   209  	ui := cli.NewMockUi()
   210  	view, _ := testView(t)
   211  
   212  	c := &ConsoleCommand{
   213  		Meta: Meta{
   214  			testingOverrides: metaOverridesForProvider(p),
   215  			Ui:               ui,
   216  			View:             view,
   217  		},
   218  	}
   219  
   220  	commands := map[string]string{
   221  		"module.child.myoutput\n":          "\"bar\"\n",
   222  		"module.count_child[0].myoutput\n": "\"bar\"\n",
   223  		"local.foo\n":                      "3\n",
   224  	}
   225  
   226  	args := []string{}
   227  
   228  	for cmd, val := range commands {
   229  		var output bytes.Buffer
   230  		defer testStdinPipe(t, strings.NewReader(cmd))()
   231  		outCloser := testStdoutCapture(t, &output)
   232  		code := c.Run(args)
   233  		outCloser()
   234  		if code != 0 {
   235  			t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
   236  		}
   237  
   238  		actual := output.String()
   239  		if output.String() != val {
   240  			t.Fatalf("bad: %q, expected %q", actual, val)
   241  		}
   242  	}
   243  }