Files
opentf/internal/command/views/console_test.go
2026-03-26 17:19:27 +02:00

197 lines
5.9 KiB
Go

// 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.ViewOptions{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.ViewOptions{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.ViewOptions{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)
}
}
}