@ -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 )
}
} )
}
}