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 }