Merge pull request #27788 from hashicorp/alisdair/command-views-output-tests
views: Expand test coverage for views.Output
This commit is contained in:
commit
20cb28b25f
|
@ -47,58 +47,6 @@ func TestOutput(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestOutput_nestedListAndMap(t *testing.T) {
|
||||
originalState := states.BuildState(func(s *states.SyncState) {
|
||||
s.SetOutputValue(
|
||||
addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance),
|
||||
cty.ListVal([]cty.Value{
|
||||
cty.MapVal(map[string]cty.Value{
|
||||
"key": cty.StringVal("value"),
|
||||
"key2": cty.StringVal("value2"),
|
||||
}),
|
||||
cty.MapVal(map[string]cty.Value{
|
||||
"key": cty.StringVal("value"),
|
||||
}),
|
||||
}),
|
||||
false,
|
||||
)
|
||||
})
|
||||
statePath := testStateFile(t, originalState)
|
||||
|
||||
view, done := testView(t)
|
||||
c := &OutputCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(testProvider()),
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"-state", statePath,
|
||||
}
|
||||
code := c.Run(args)
|
||||
output := done(t)
|
||||
if code != 0 {
|
||||
t.Fatalf("bad: \n%s", output.Stderr())
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(output.Stdout())
|
||||
expected := strings.TrimSpace(`
|
||||
foo = tolist([
|
||||
tomap({
|
||||
"key" = "value"
|
||||
"key2" = "value2"
|
||||
}),
|
||||
tomap({
|
||||
"key" = "value"
|
||||
}),
|
||||
])
|
||||
`)
|
||||
if actual != expected {
|
||||
t.Fatalf("wrong output\ngot: %s\nwant: %s", actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutput_json(t *testing.T) {
|
||||
originalState := states.BuildState(func(s *states.SyncState) {
|
||||
s.SetOutputValue(
|
||||
|
@ -163,36 +111,6 @@ func TestOutput_emptyOutputs(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestOutput_jsonEmptyOutputs(t *testing.T) {
|
||||
originalState := states.NewState()
|
||||
statePath := testStateFile(t, originalState)
|
||||
|
||||
p := testProvider()
|
||||
view, done := testView(t)
|
||||
c := &OutputCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"-state", statePath,
|
||||
"-json",
|
||||
}
|
||||
code := c.Run(args)
|
||||
output := done(t)
|
||||
if code != 0 {
|
||||
t.Fatalf("bad: \n%s", output.Stderr())
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(output.Stdout())
|
||||
expected := "{}"
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n%#v\n%#v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutput_badVar(t *testing.T) {
|
||||
originalState := states.BuildState(func(s *states.SyncState) {
|
||||
s.SetOutputValue(
|
||||
|
|
|
@ -166,10 +166,9 @@ func (v *OutputRaw) Output(name string, outputs map[string]*states.OutputValue)
|
|||
return diags
|
||||
}
|
||||
// If we get out here then we should have a valid string to print.
|
||||
// We're writing it directly to the output here so that a shell caller
|
||||
// will get exactly the value and no extra whitespace.
|
||||
str := strV.AsString()
|
||||
fmt.Fprint(v.streams.Stdout.File, str)
|
||||
// We're writing it using Print here so that a shell caller will get
|
||||
// exactly the value and no extra whitespace (including trailing newline).
|
||||
v.streams.Print(strV.AsString())
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -1,13 +1,256 @@
|
|||
package views
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/command/arguments"
|
||||
"github.com/hashicorp/terraform/internal/terminal"
|
||||
"github.com/hashicorp/terraform/states"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// Test various single output values for human-readable UI. Note that since
|
||||
// OutputHuman defers to repl.FormatValue to render a single value, most of the
|
||||
// test coverage should be in that package.
|
||||
func TestOutputHuman_single(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
value cty.Value
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
"string": {
|
||||
value: cty.StringVal("hello"),
|
||||
want: "\"hello\"\n",
|
||||
},
|
||||
"list of maps": {
|
||||
value: cty.ListVal([]cty.Value{
|
||||
cty.MapVal(map[string]cty.Value{
|
||||
"key": cty.StringVal("value"),
|
||||
"key2": cty.StringVal("value2"),
|
||||
}),
|
||||
cty.MapVal(map[string]cty.Value{
|
||||
"key": cty.StringVal("value"),
|
||||
}),
|
||||
}),
|
||||
want: `tolist([
|
||||
tomap({
|
||||
"key" = "value"
|
||||
"key2" = "value2"
|
||||
}),
|
||||
tomap({
|
||||
"key" = "value"
|
||||
}),
|
||||
])
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
streams, done := terminal.StreamsForTesting(t)
|
||||
v := NewOutput(arguments.ViewHuman, NewView(streams))
|
||||
|
||||
outputs := map[string]*states.OutputValue{
|
||||
"foo": {Value: tc.value},
|
||||
}
|
||||
diags := v.Output("foo", outputs)
|
||||
|
||||
if diags.HasErrors() {
|
||||
if !tc.wantErr {
|
||||
t.Fatalf("unexpected diagnostics: %s", diags)
|
||||
}
|
||||
} else if tc.wantErr {
|
||||
t.Fatalf("succeeded, but want error")
|
||||
}
|
||||
|
||||
if got, want := done(t).Stdout(), tc.want; got != want {
|
||||
t.Errorf("wrong result\ngot: %q\nwant: %q", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Sensitive output values are rendered to the console intentionally when
|
||||
// requesting a single output.
|
||||
func TestOutput_sensitive(t *testing.T) {
|
||||
testCases := map[string]arguments.ViewType{
|
||||
"human": arguments.ViewHuman,
|
||||
"json": arguments.ViewJSON,
|
||||
"raw": arguments.ViewRaw,
|
||||
}
|
||||
for name, vt := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
streams, done := terminal.StreamsForTesting(t)
|
||||
v := NewOutput(vt, NewView(streams))
|
||||
|
||||
outputs := map[string]*states.OutputValue{
|
||||
"foo": {
|
||||
Value: cty.StringVal("secret"),
|
||||
Sensitive: true,
|
||||
},
|
||||
}
|
||||
diags := v.Output("foo", outputs)
|
||||
|
||||
if diags.HasErrors() {
|
||||
t.Fatalf("unexpected diagnostics: %s", diags)
|
||||
}
|
||||
|
||||
// Test for substring match here because we don't care about exact
|
||||
// output format in this test, just the presence of the sensitive
|
||||
// value.
|
||||
if got, want := done(t).Stdout(), "secret"; !strings.Contains(got, want) {
|
||||
t.Errorf("wrong result\ngot: %q\nwant: %q", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Showing all outputs is supported by human and JSON output format.
|
||||
func TestOutput_all(t *testing.T) {
|
||||
outputs := map[string]*states.OutputValue{
|
||||
"foo": {
|
||||
Value: cty.StringVal("secret"),
|
||||
Sensitive: true,
|
||||
},
|
||||
"bar": {
|
||||
Value: cty.ListVal([]cty.Value{cty.True, cty.False, cty.True}),
|
||||
},
|
||||
"baz": {
|
||||
Value: cty.ObjectVal(map[string]cty.Value{
|
||||
"boop": cty.NumberIntVal(5),
|
||||
"beep": cty.StringVal("true"),
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
testCases := map[string]struct {
|
||||
vt arguments.ViewType
|
||||
want string
|
||||
}{
|
||||
"human": {
|
||||
arguments.ViewHuman,
|
||||
`bar = tolist([
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
])
|
||||
baz = {
|
||||
"beep" = "true"
|
||||
"boop" = 5
|
||||
}
|
||||
foo = <sensitive>
|
||||
`,
|
||||
},
|
||||
"json": {
|
||||
arguments.ViewJSON,
|
||||
`{
|
||||
"bar": {
|
||||
"sensitive": false,
|
||||
"type": [
|
||||
"list",
|
||||
"bool"
|
||||
],
|
||||
"value": [
|
||||
true,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"baz": {
|
||||
"sensitive": false,
|
||||
"type": [
|
||||
"object",
|
||||
{
|
||||
"beep": "string",
|
||||
"boop": "number"
|
||||
}
|
||||
],
|
||||
"value": {
|
||||
"beep": "true",
|
||||
"boop": 5
|
||||
}
|
||||
},
|
||||
"foo": {
|
||||
"sensitive": true,
|
||||
"type": "string",
|
||||
"value": "secret"
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
streams, done := terminal.StreamsForTesting(t)
|
||||
v := NewOutput(tc.vt, NewView(streams))
|
||||
diags := v.Output("", outputs)
|
||||
|
||||
if diags.HasErrors() {
|
||||
t.Fatalf("unexpected diagnostics: %s", diags)
|
||||
}
|
||||
|
||||
if got := done(t).Stdout(); got != tc.want {
|
||||
t.Errorf("wrong result\ngot: %q\nwant: %q", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// JSON output format supports empty outputs by rendering an empty object
|
||||
// without diagnostics.
|
||||
func TestOutputJSON_empty(t *testing.T) {
|
||||
streams, done := terminal.StreamsForTesting(t)
|
||||
v := NewOutput(arguments.ViewJSON, NewView(streams))
|
||||
|
||||
diags := v.Output("", map[string]*states.OutputValue{})
|
||||
|
||||
if diags.HasErrors() {
|
||||
t.Fatalf("unexpected diagnostics: %s", diags)
|
||||
}
|
||||
|
||||
if got, want := done(t).Stdout(), "{}\n"; got != want {
|
||||
t.Errorf("wrong result\ngot: %q\nwant: %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// Human and raw formats render a warning if there are no outputs.
|
||||
func TestOutput_emptyWarning(t *testing.T) {
|
||||
testCases := map[string]arguments.ViewType{
|
||||
"human": arguments.ViewHuman,
|
||||
"raw": arguments.ViewRaw,
|
||||
}
|
||||
|
||||
for name, vt := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
streams, done := terminal.StreamsForTesting(t)
|
||||
v := NewOutput(vt, NewView(streams))
|
||||
|
||||
diags := v.Output("", map[string]*states.OutputValue{})
|
||||
|
||||
if got, want := done(t).Stdout(), ""; got != want {
|
||||
t.Errorf("wrong result\ngot: %q\nwant: %q", got, want)
|
||||
}
|
||||
|
||||
if len(diags) != 1 {
|
||||
t.Fatalf("expected 1 diagnostic, got %d", len(diags))
|
||||
}
|
||||
|
||||
if diags.HasErrors() {
|
||||
t.Fatalf("unexpected error diagnostics: %s", diags)
|
||||
}
|
||||
|
||||
if got, want := diags[0].Description().Summary, "No outputs found"; got != want {
|
||||
t.Errorf("unexpected diagnostics: %s", diags)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Raw output is a simple unquoted output format designed for shell scripts,
|
||||
// which relies on the cty.AsString() implementation. This test covers
|
||||
// formatting for supported value types.
|
||||
func TestOutputRaw(t *testing.T) {
|
||||
values := map[string]cty.Value{
|
||||
"str": cty.StringVal("bar"),
|
||||
|
@ -16,6 +259,7 @@ func TestOutputRaw(t *testing.T) {
|
|||
"bool": cty.True,
|
||||
"obj": cty.EmptyObjectVal,
|
||||
"null": cty.NullVal(cty.String),
|
||||
"unknown": cty.UnknownVal(cty.String),
|
||||
}
|
||||
|
||||
tests := map[string]struct {
|
||||
|
@ -28,15 +272,13 @@ func TestOutputRaw(t *testing.T) {
|
|||
"bool": {WantOutput: "true"},
|
||||
"obj": {WantErr: true},
|
||||
"null": {WantErr: true},
|
||||
"unknown": {WantErr: true},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
streams, done := terminal.StreamsForTesting(t)
|
||||
view := NewView(streams)
|
||||
v := &OutputRaw{
|
||||
View: *view,
|
||||
}
|
||||
v := NewOutput(arguments.ViewRaw, NewView(streams))
|
||||
|
||||
value := values[name]
|
||||
outputs := map[string]*states.OutputValue{
|
||||
|
@ -58,3 +300,64 @@ func TestOutputRaw(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Raw cannot render all outputs.
|
||||
func TestOutputRaw_all(t *testing.T) {
|
||||
streams, done := terminal.StreamsForTesting(t)
|
||||
v := NewOutput(arguments.ViewRaw, NewView(streams))
|
||||
|
||||
outputs := map[string]*states.OutputValue{
|
||||
"foo": {Value: cty.StringVal("secret")},
|
||||
"bar": {Value: cty.True},
|
||||
}
|
||||
diags := v.Output("", outputs)
|
||||
|
||||
if got, want := done(t).Stdout(), ""; got != want {
|
||||
t.Errorf("wrong result\ngot: %q\nwant: %q", got, want)
|
||||
}
|
||||
|
||||
if !diags.HasErrors() {
|
||||
t.Fatalf("expected diagnostics, got %s", diags)
|
||||
}
|
||||
|
||||
if got, want := diags.Err().Error(), "Raw output format is only supported for single outputs"; got != want {
|
||||
t.Errorf("unexpected diagnostics: %s", diags)
|
||||
}
|
||||
}
|
||||
|
||||
// All outputs render an error if a specific output is requested which is
|
||||
// missing from the map of outputs.
|
||||
func TestOutput_missing(t *testing.T) {
|
||||
testCases := map[string]arguments.ViewType{
|
||||
"human": arguments.ViewHuman,
|
||||
"json": arguments.ViewJSON,
|
||||
"raw": arguments.ViewRaw,
|
||||
}
|
||||
|
||||
for name, vt := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
streams, done := terminal.StreamsForTesting(t)
|
||||
v := NewOutput(vt, NewView(streams))
|
||||
|
||||
diags := v.Output("foo", map[string]*states.OutputValue{
|
||||
"bar": {Value: cty.StringVal("boop")},
|
||||
})
|
||||
|
||||
if len(diags) != 1 {
|
||||
t.Fatalf("expected 1 diagnostic, got %d", len(diags))
|
||||
}
|
||||
|
||||
if !diags.HasErrors() {
|
||||
t.Fatalf("expected error diagnostics, got %s", diags)
|
||||
}
|
||||
|
||||
if got, want := diags[0].Description().Summary, `Output "foo" not found`; got != want {
|
||||
t.Errorf("unexpected diagnostics: %s", diags)
|
||||
}
|
||||
|
||||
if got, want := done(t).Stdout(), ""; got != want {
|
||||
t.Errorf("wrong result\ngot: %q\nwant: %q", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue