github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/command/views/output_test.go (about)

     1  package views
     2  
     3  import (
     4  	"strings"
     5  	"testing"
     6  
     7  	"github.com/hashicorp/terraform/internal/command/arguments"
     8  	"github.com/hashicorp/terraform/internal/states"
     9  	"github.com/hashicorp/terraform/internal/terminal"
    10  	"github.com/zclconf/go-cty/cty"
    11  )
    12  
    13  // Test various single output values for human-readable UI. Note that since
    14  // OutputHuman defers to repl.FormatValue to render a single value, most of the
    15  // test coverage should be in that package.
    16  func TestOutputHuman_single(t *testing.T) {
    17  	testCases := map[string]struct {
    18  		value   cty.Value
    19  		want    string
    20  		wantErr bool
    21  	}{
    22  		"string": {
    23  			value: cty.StringVal("hello"),
    24  			want:  "\"hello\"\n",
    25  		},
    26  		"list of maps": {
    27  			value: cty.ListVal([]cty.Value{
    28  				cty.MapVal(map[string]cty.Value{
    29  					"key":  cty.StringVal("value"),
    30  					"key2": cty.StringVal("value2"),
    31  				}),
    32  				cty.MapVal(map[string]cty.Value{
    33  					"key": cty.StringVal("value"),
    34  				}),
    35  			}),
    36  			want: `tolist([
    37    tomap({
    38      "key" = "value"
    39      "key2" = "value2"
    40    }),
    41    tomap({
    42      "key" = "value"
    43    }),
    44  ])
    45  `,
    46  		},
    47  	}
    48  
    49  	for name, tc := range testCases {
    50  		t.Run(name, func(t *testing.T) {
    51  			streams, done := terminal.StreamsForTesting(t)
    52  			v := NewOutput(arguments.ViewHuman, NewView(streams))
    53  
    54  			outputs := map[string]*states.OutputValue{
    55  				"foo": {Value: tc.value},
    56  			}
    57  			diags := v.Output("foo", outputs)
    58  
    59  			if diags.HasErrors() {
    60  				if !tc.wantErr {
    61  					t.Fatalf("unexpected diagnostics: %s", diags)
    62  				}
    63  			} else if tc.wantErr {
    64  				t.Fatalf("succeeded, but want error")
    65  			}
    66  
    67  			if got, want := done(t).Stdout(), tc.want; got != want {
    68  				t.Errorf("wrong result\ngot:  %q\nwant: %q", got, want)
    69  			}
    70  		})
    71  	}
    72  }
    73  
    74  // Sensitive output values are rendered to the console intentionally when
    75  // requesting a single output.
    76  func TestOutput_sensitive(t *testing.T) {
    77  	testCases := map[string]arguments.ViewType{
    78  		"human": arguments.ViewHuman,
    79  		"json":  arguments.ViewJSON,
    80  		"raw":   arguments.ViewRaw,
    81  	}
    82  	for name, vt := range testCases {
    83  		t.Run(name, func(t *testing.T) {
    84  			streams, done := terminal.StreamsForTesting(t)
    85  			v := NewOutput(vt, NewView(streams))
    86  
    87  			outputs := map[string]*states.OutputValue{
    88  				"foo": {
    89  					Value:     cty.StringVal("secret"),
    90  					Sensitive: true,
    91  				},
    92  			}
    93  			diags := v.Output("foo", outputs)
    94  
    95  			if diags.HasErrors() {
    96  				t.Fatalf("unexpected diagnostics: %s", diags)
    97  			}
    98  
    99  			// Test for substring match here because we don't care about exact
   100  			// output format in this test, just the presence of the sensitive
   101  			// value.
   102  			if got, want := done(t).Stdout(), "secret"; !strings.Contains(got, want) {
   103  				t.Errorf("wrong result\ngot:  %q\nwant: %q", got, want)
   104  			}
   105  		})
   106  	}
   107  }
   108  
   109  // Showing all outputs is supported by human and JSON output format.
   110  func TestOutput_all(t *testing.T) {
   111  	outputs := map[string]*states.OutputValue{
   112  		"foo": {
   113  			Value:     cty.StringVal("secret"),
   114  			Sensitive: true,
   115  		},
   116  		"bar": {
   117  			Value: cty.ListVal([]cty.Value{cty.True, cty.False, cty.True}),
   118  		},
   119  		"baz": {
   120  			Value: cty.ObjectVal(map[string]cty.Value{
   121  				"boop": cty.NumberIntVal(5),
   122  				"beep": cty.StringVal("true"),
   123  			}),
   124  		},
   125  	}
   126  
   127  	testCases := map[string]struct {
   128  		vt   arguments.ViewType
   129  		want string
   130  	}{
   131  		"human": {
   132  			arguments.ViewHuman,
   133  			`bar = tolist([
   134    true,
   135    false,
   136    true,
   137  ])
   138  baz = {
   139    "beep" = "true"
   140    "boop" = 5
   141  }
   142  foo = <sensitive>
   143  `,
   144  		},
   145  		"json": {
   146  			arguments.ViewJSON,
   147  			`{
   148    "bar": {
   149      "sensitive": false,
   150      "type": [
   151        "list",
   152        "bool"
   153      ],
   154      "value": [
   155        true,
   156        false,
   157        true
   158      ]
   159    },
   160    "baz": {
   161      "sensitive": false,
   162      "type": [
   163        "object",
   164        {
   165          "beep": "string",
   166          "boop": "number"
   167        }
   168      ],
   169      "value": {
   170        "beep": "true",
   171        "boop": 5
   172      }
   173    },
   174    "foo": {
   175      "sensitive": true,
   176      "type": "string",
   177      "value": "secret"
   178    }
   179  }
   180  `,
   181  		},
   182  	}
   183  
   184  	for name, tc := range testCases {
   185  		t.Run(name, func(t *testing.T) {
   186  			streams, done := terminal.StreamsForTesting(t)
   187  			v := NewOutput(tc.vt, NewView(streams))
   188  			diags := v.Output("", outputs)
   189  
   190  			if diags.HasErrors() {
   191  				t.Fatalf("unexpected diagnostics: %s", diags)
   192  			}
   193  
   194  			if got := done(t).Stdout(); got != tc.want {
   195  				t.Errorf("wrong result\ngot:  %q\nwant: %q", got, tc.want)
   196  			}
   197  		})
   198  	}
   199  }
   200  
   201  // JSON output format supports empty outputs by rendering an empty object
   202  // without diagnostics.
   203  func TestOutputJSON_empty(t *testing.T) {
   204  	streams, done := terminal.StreamsForTesting(t)
   205  	v := NewOutput(arguments.ViewJSON, NewView(streams))
   206  
   207  	diags := v.Output("", map[string]*states.OutputValue{})
   208  
   209  	if diags.HasErrors() {
   210  		t.Fatalf("unexpected diagnostics: %s", diags)
   211  	}
   212  
   213  	if got, want := done(t).Stdout(), "{}\n"; got != want {
   214  		t.Errorf("wrong result\ngot:  %q\nwant: %q", got, want)
   215  	}
   216  }
   217  
   218  // Human and raw formats render a warning if there are no outputs.
   219  func TestOutput_emptyWarning(t *testing.T) {
   220  	testCases := map[string]arguments.ViewType{
   221  		"human": arguments.ViewHuman,
   222  		"raw":   arguments.ViewRaw,
   223  	}
   224  
   225  	for name, vt := range testCases {
   226  		t.Run(name, func(t *testing.T) {
   227  			streams, done := terminal.StreamsForTesting(t)
   228  			v := NewOutput(vt, NewView(streams))
   229  
   230  			diags := v.Output("", map[string]*states.OutputValue{})
   231  
   232  			if got, want := done(t).Stdout(), ""; got != want {
   233  				t.Errorf("wrong result\ngot:  %q\nwant: %q", got, want)
   234  			}
   235  
   236  			if len(diags) != 1 {
   237  				t.Fatalf("expected 1 diagnostic, got %d", len(diags))
   238  			}
   239  
   240  			if diags.HasErrors() {
   241  				t.Fatalf("unexpected error diagnostics: %s", diags)
   242  			}
   243  
   244  			if got, want := diags[0].Description().Summary, "No outputs found"; got != want {
   245  				t.Errorf("unexpected diagnostics: %s", diags)
   246  			}
   247  		})
   248  	}
   249  }
   250  
   251  // Raw output is a simple unquoted output format designed for shell scripts,
   252  // which relies on the cty.AsString() implementation. This test covers
   253  // formatting for supported value types.
   254  func TestOutputRaw(t *testing.T) {
   255  	values := map[string]cty.Value{
   256  		"str":      cty.StringVal("bar"),
   257  		"multistr": cty.StringVal("bar\nbaz"),
   258  		"num":      cty.NumberIntVal(2),
   259  		"bool":     cty.True,
   260  		"obj":      cty.EmptyObjectVal,
   261  		"null":     cty.NullVal(cty.String),
   262  		"unknown":  cty.UnknownVal(cty.String),
   263  	}
   264  
   265  	tests := map[string]struct {
   266  		WantOutput string
   267  		WantErr    bool
   268  	}{
   269  		"str":      {WantOutput: "bar"},
   270  		"multistr": {WantOutput: "bar\nbaz"},
   271  		"num":      {WantOutput: "2"},
   272  		"bool":     {WantOutput: "true"},
   273  		"obj":      {WantErr: true},
   274  		"null":     {WantErr: true},
   275  		"unknown":  {WantErr: true},
   276  	}
   277  
   278  	for name, test := range tests {
   279  		t.Run(name, func(t *testing.T) {
   280  			streams, done := terminal.StreamsForTesting(t)
   281  			v := NewOutput(arguments.ViewRaw, NewView(streams))
   282  
   283  			value := values[name]
   284  			outputs := map[string]*states.OutputValue{
   285  				name: {Value: value},
   286  			}
   287  			diags := v.Output(name, outputs)
   288  
   289  			if diags.HasErrors() {
   290  				if !test.WantErr {
   291  					t.Fatalf("unexpected diagnostics: %s", diags)
   292  				}
   293  			} else if test.WantErr {
   294  				t.Fatalf("succeeded, but want error")
   295  			}
   296  
   297  			if got, want := done(t).Stdout(), test.WantOutput; got != want {
   298  				t.Errorf("wrong result\ngot:  %q\nwant: %q", got, want)
   299  			}
   300  		})
   301  	}
   302  }
   303  
   304  // Raw cannot render all outputs.
   305  func TestOutputRaw_all(t *testing.T) {
   306  	streams, done := terminal.StreamsForTesting(t)
   307  	v := NewOutput(arguments.ViewRaw, NewView(streams))
   308  
   309  	outputs := map[string]*states.OutputValue{
   310  		"foo": {Value: cty.StringVal("secret")},
   311  		"bar": {Value: cty.True},
   312  	}
   313  	diags := v.Output("", outputs)
   314  
   315  	if got, want := done(t).Stdout(), ""; got != want {
   316  		t.Errorf("wrong result\ngot:  %q\nwant: %q", got, want)
   317  	}
   318  
   319  	if !diags.HasErrors() {
   320  		t.Fatalf("expected diagnostics, got %s", diags)
   321  	}
   322  
   323  	if got, want := diags.Err().Error(), "Raw output format is only supported for single outputs"; got != want {
   324  		t.Errorf("unexpected diagnostics: %s", diags)
   325  	}
   326  }
   327  
   328  // All outputs render an error if a specific output is requested which is
   329  // missing from the map of outputs.
   330  func TestOutput_missing(t *testing.T) {
   331  	testCases := map[string]arguments.ViewType{
   332  		"human": arguments.ViewHuman,
   333  		"json":  arguments.ViewJSON,
   334  		"raw":   arguments.ViewRaw,
   335  	}
   336  
   337  	for name, vt := range testCases {
   338  		t.Run(name, func(t *testing.T) {
   339  			streams, done := terminal.StreamsForTesting(t)
   340  			v := NewOutput(vt, NewView(streams))
   341  
   342  			diags := v.Output("foo", map[string]*states.OutputValue{
   343  				"bar": {Value: cty.StringVal("boop")},
   344  			})
   345  
   346  			if len(diags) != 1 {
   347  				t.Fatalf("expected 1 diagnostic, got %d", len(diags))
   348  			}
   349  
   350  			if !diags.HasErrors() {
   351  				t.Fatalf("expected error diagnostics, got %s", diags)
   352  			}
   353  
   354  			if got, want := diags[0].Description().Summary, `Output "foo" not found`; got != want {
   355  				t.Errorf("unexpected diagnostics: %s", diags)
   356  			}
   357  
   358  			if got, want := done(t).Stdout(), ""; got != want {
   359  				t.Errorf("wrong result\ngot:  %q\nwant: %q", got, want)
   360  			}
   361  		})
   362  	}
   363  }