| // Copyright (c) The OpenTofu Authors | |
| // SPDX-License-Identifier: MPL-2.0 | |
| // Copyright (c) 2023 HashiCorp, Inc. | |
| // SPDX-License-Identifier: MPL-2.0 | |
| package views | |
| import ( | |
| "os" | |
| "testing" | |
| "github.com/google/go-cmp/cmp" | |
| "github.com/opentofu/opentofu/internal/command/arguments" | |
| "github.com/opentofu/opentofu/internal/tfdiags" | |
| ) | |
| func TestConsoleViews(t *testing.T) { | |
| tests := map[string]struct { | |
| viewCall func(console Console) | |
| wantJson []map[string]any | |
| wantStdout string | |
| wantStderr string | |
| }{ | |
| "unsupportedLocalOp": { | |
| viewCall: func(console Console) { | |
| console.UnsupportedLocalOp() | |
| }, | |
| wantJson: []map[string]any{ | |
| { | |
| "@level": "error", | |
| "@message": "Error: The configured backend doesn't support this operation", | |
| "@module": "tofu.ui", | |
| "diagnostic": map[string]any{ | |
| "detail": `The "backend" in OpenTofu defines how OpenTofu operates. The default backend performs all operations locally on your machine. Your configuration is configured to use a non-local backend. This backend doesn't support this operation.`, | |
| "severity": "error", | |
| "summary": "The configured backend doesn't support this operation", | |
| }, | |
| "type": "diagnostic", | |
| }, | |
| }, | |
| wantStderr: ` | |
| Error: The configured backend doesn't support this operation | |
| The "backend" in OpenTofu defines how OpenTofu operates. The default backend | |
| performs all operations locally on your machine. Your configuration is | |
| configured to use a non-local backend. This backend doesn't support this | |
| operation. | |
| `, | |
| }, | |
| // Diagnostics | |
| "warning": { | |
| viewCall: func(console Console) { | |
| diags := tfdiags.Diagnostics{ | |
| tfdiags.Sourceless(tfdiags.Warning, "A warning occurred", "foo bar"), | |
| } | |
| console.Diagnostics(diags) | |
| }, | |
| wantStdout: withNewline("\nWarning: A warning occurred\n\nfoo bar"), | |
| wantStderr: "", | |
| wantJson: []map[string]any{ | |
| { | |
| "@level": "warn", | |
| "@message": "Warning: A warning occurred", | |
| "@module": "tofu.ui", | |
| "diagnostic": map[string]any{ | |
| "detail": "foo bar", | |
| "severity": "warning", | |
| "summary": "A warning occurred", | |
| }, | |
| "type": "diagnostic", | |
| }, | |
| }, | |
| }, | |
| "error": { | |
| viewCall: func(console Console) { | |
| diags := tfdiags.Diagnostics{ | |
| tfdiags.Sourceless(tfdiags.Error, "An error occurred", "foo bar"), | |
| } | |
| console.Diagnostics(diags) | |
| }, | |
| wantStdout: "", | |
| wantStderr: withNewline("\nError: An error occurred\n\nfoo bar"), | |
| wantJson: []map[string]any{ | |
| { | |
| "@level": "error", | |
| "@message": "Error: An error occurred", | |
| "@module": "tofu.ui", | |
| "diagnostic": map[string]any{ | |
| "detail": "foo bar", | |
| "severity": "error", | |
| "summary": "An error occurred", | |
| }, | |
| "type": "diagnostic", | |
| }, | |
| }, | |
| }, | |
| "multiple_diagnostics": { | |
| viewCall: func(console Console) { | |
| diags := tfdiags.Diagnostics{ | |
| tfdiags.Sourceless(tfdiags.Warning, "A warning", "foo bar warning"), | |
| tfdiags.Sourceless(tfdiags.Error, "An error", "foo bar error"), | |
| } | |
| console.Diagnostics(diags) | |
| }, | |
| wantStdout: withNewline("\nWarning: A warning\n\nfoo bar warning"), | |
| wantStderr: withNewline("\nError: An error\n\nfoo bar error"), | |
| wantJson: []map[string]any{ | |
| { | |
| "@level": "warn", | |
| "@message": "Warning: A warning", | |
| "@module": "tofu.ui", | |
| "diagnostic": map[string]any{ | |
| "detail": "foo bar warning", | |
| "severity": "warning", | |
| "summary": "A warning", | |
| }, | |
| "type": "diagnostic", | |
| }, | |
| { | |
| "@level": "error", | |
| "@message": "Error: An error", | |
| "@module": "tofu.ui", | |
| "diagnostic": map[string]any{ | |
| "detail": "foo bar error", | |
| "severity": "error", | |
| "summary": "An error", | |
| }, | |
| "type": "diagnostic", | |
| }, | |
| }, | |
| }, | |
| } | |
| for name, tc := range tests { | |
| t.Run(name, func(t *testing.T) { | |
| testConsoleHuman(t, tc.viewCall, tc.wantStdout, tc.wantStderr) | |
| testConsoleJson(t, tc.viewCall, tc.wantJson) | |
| testConsoleMulti(t, tc.viewCall, tc.wantStdout, tc.wantStderr, tc.wantJson) | |
| }) | |
| } | |
| } | |
| func testConsoleHuman(t *testing.T, call func(console Console), wantStdout, wantStderr string) { | |
| view, done := testView(t) | |
| consoleView := NewConsole(&arguments.View{ViewType: arguments.ViewHuman}, view) | |
| call(consoleView) | |
| output := done(t) | |
| if diff := cmp.Diff(wantStderr, output.Stderr()); diff != "" { | |
| t.Errorf("invalid stderr (-want, +got):\n%s", diff) | |
| } | |
| if diff := cmp.Diff(wantStdout, output.Stdout()); diff != "" { | |
| t.Errorf("invalid stdout (-want, +got):\n%s", diff) | |
| } | |
| } | |
| func testConsoleJson(t *testing.T, call func(console Console), want []map[string]interface{}) { | |
| view, done := testView(t) | |
| consoleView := NewConsole(&arguments.View{ViewType: arguments.ViewJSON}, view) | |
| call(consoleView) | |
| output := done(t) | |
| if output.Stderr() != "" { | |
| t.Errorf("expected no stderr but got:\n%s", output.Stderr()) | |
| } | |
| testJSONViewOutputEquals(t, output.Stdout(), want) | |
| } | |
| func testConsoleMulti(t *testing.T, call func(console Console), wantStdout string, wantStderr string, want []map[string]interface{}) { | |
| jsonInto, err := os.CreateTemp(t.TempDir(), "json-into-*") | |
| if err != nil { | |
| t.Fatalf("failed to create the file to write json content into: %s", err) | |
| } | |
| view, done := testView(t) | |
| consoleView := NewConsole(&arguments.View{ViewType: arguments.ViewHuman, JSONInto: jsonInto}, view) | |
| call(consoleView) | |
| { | |
| if err := jsonInto.Close(); err != nil { | |
| t.Fatalf("failed to close the jsonInto file: %s", err) | |
| } | |
| // check the fileInto content | |
| fileContent, err := os.ReadFile(jsonInto.Name()) | |
| if err != nil { | |
| t.Fatalf("failed to read the file content with the json output: %s", err) | |
| } | |
| testJSONViewOutputEquals(t, string(fileContent), want) | |
| } | |
| { | |
| // check the human output | |
| output := done(t) | |
| if diff := cmp.Diff(wantStderr, output.Stderr()); diff != "" { | |
| t.Errorf("invalid stderr (-want, +got):\n%s", diff) | |
| } | |
| if diff := cmp.Diff(wantStdout, output.Stdout()); diff != "" { | |
| t.Errorf("invalid stdout (-want, +got):\n%s", diff) | |
| } | |
| } | |
| } |