views: Expand test coverage for views.Output
Now that the view code is separated, we can increase test coverage in unit tests. This commit moves some tests from the command package which were testing only view code, and adds more new test cases.
This commit is contained in:
parent
fa774dafc0
commit
96cf29111d
|
@ -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