opentofu/internal/command/views/import_test.go
Andrei Ciobanu 866b067c0d
Some checks are pending
build / Build for freebsd_386 (push) Waiting to run
build / Build for linux_386 (push) Waiting to run
build / Build for openbsd_386 (push) Waiting to run
build / Build for windows_386 (push) Waiting to run
build / Build for freebsd_amd64 (push) Waiting to run
build / Build for linux_amd64 (push) Waiting to run
build / Build for openbsd_amd64 (push) Waiting to run
build / Build for solaris_amd64 (push) Waiting to run
build / Build for windows_amd64 (push) Waiting to run
build / Build for freebsd_arm (push) Waiting to run
build / Build for linux_arm (push) Waiting to run
build / Build for linux_arm64 (push) Waiting to run
build / Build for darwin_amd64 (push) Waiting to run
build / Build for darwin_arm64 (push) Waiting to run
build / End-to-end Tests for linux_386 (push) Waiting to run
build / End-to-end Tests for windows_386 (push) Waiting to run
build / End-to-end Tests for darwin_amd64 (push) Waiting to run
build / End-to-end Tests for linux_amd64 (push) Waiting to run
build / End-to-end Tests for windows_amd64 (push) Waiting to run
Quick Checks / List files changed for pull request (push) Waiting to run
Quick Checks / Unit tests for linux_386 (push) Blocked by required conditions
Quick Checks / Unit tests for linux_amd64 (push) Blocked by required conditions
Quick Checks / Unit tests for windows_amd64 (push) Blocked by required conditions
Quick Checks / Unit tests for linux_arm (push) Blocked by required conditions
Quick Checks / Unit tests for darwin_arm64 (push) Blocked by required conditions
Quick Checks / Unit tests for linux_arm64 (push) Blocked by required conditions
Quick Checks / Race Tests (push) Blocked by required conditions
Quick Checks / End-to-end Tests (push) Blocked by required conditions
Quick Checks / Code Consistency Checks (push) Blocked by required conditions
Quick Checks / License Checks (push) Waiting to run
Website checks / List files changed for pull request (push) Waiting to run
Website checks / Build (push) Blocked by required conditions
Website checks / Test Installation Instructions (push) Blocked by required conditions
Unify patterns across the refactored commands (#3941)
Signed-off-by: Andrei Ciobanu <andrei.ciobanu@opentofu.org>
2026-03-26 17:19:27 +02:00

316 lines
9.5 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/addrs"
"github.com/opentofu/opentofu/internal/command/arguments"
"github.com/opentofu/opentofu/internal/tfdiags"
"github.com/opentofu/opentofu/internal/tofu"
)
func TestImportViews(t *testing.T) {
tests := map[string]struct {
viewCall func(v Import)
wantJson []map[string]any
wantStdout string
wantStderr string
}{
"invalid address reference": {
viewCall: func(v Import) {
v.InvalidAddressReference()
},
wantJson: []map[string]any{
{
"@level": "info",
"@message": "For information on valid syntax, see: https://opentofu.org/docs/cli/state/resource-addressing/",
"@module": "tofu.ui",
},
},
wantStdout: withNewline(`For information on valid syntax, see:
https://opentofu.org/docs/cli/state/resource-addressing/`),
},
"missing resource configuration": {
viewCall: func(v Import) {
v.MissingResourceConfiguration(addrs.AbsResourceInstance{
Module: addrs.ModuleInstance{{Name: "mod"}},
Resource: addrs.ResourceInstance{Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test",
Name: "test_name",
}},
}, "./mod", "test", "test_name")
},
wantJson: []map[string]any{
{
"@level": "error",
"@message": `Resource address "module.mod.test.test_name" does not exist in the configuration. Before importing this resource, please create its configuration in ./mod`,
"@module": "tofu.ui",
},
},
wantStderr: withNewline(`Error: resource address "module.mod.test.test_name" does not exist in the configuration.
Before importing this resource, please create its configuration in ./mod. For example:
resource "test" "test_name" {
# (resource arguments)
}
`),
},
"success": {
viewCall: func(v Import) {
v.Success()
},
wantStdout: withNewline(`
Import successful!
The resources that were imported are shown above. These resources are now in
your OpenTofu state and will henceforth be managed by OpenTofu.
`),
wantStderr: "",
wantJson: []map[string]any{
{
"@level": "info",
"@message": "Import successful! The resources that were imported are shown above. These resources are now in your OpenTofu state and will henceforth be managed by OpenTofu",
"@module": "tofu.ui",
},
},
},
"unsupported local op": {
viewCall: func(v Import) {
v.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(v Import) {
diags := tfdiags.Diagnostics{
tfdiags.Sourceless(tfdiags.Warning, "A warning occurred", "foo bar"),
}
v.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(v Import) {
diags := tfdiags.Diagnostics{
tfdiags.Sourceless(tfdiags.Error, "An error occurred", "foo bar"),
}
v.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(v Import) {
diags := tfdiags.Diagnostics{
tfdiags.Sourceless(tfdiags.Warning, "A warning", "foo bar warning"),
tfdiags.Sourceless(tfdiags.Error, "An error", "foo bar error"),
}
v.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",
},
},
},
// hooks
"random hook": {
viewCall: func(v Import) {
resAddr := addrs.AbsResourceInstance{
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.EphemeralResourceMode,
Type: "foo",
Name: "bar",
},
},
}
for _, hook := range v.Hooks() {
// called here a hook method that prints something both in human, and json, formats
action, _ := hook.PreOpen(resAddr)
if action != tofu.HookActionContinue {
t.Errorf("invalid action returned by the PreImportState hook. wanted %d but got %d", tofu.HookActionContinue, action)
}
}
},
wantJson: []map[string]any{
{
"@level": "info",
"@message": "ephemeral.foo.bar: Opening...",
"@module": "tofu.ui",
"hook": map[string]any{
"Msg": "Opening...",
"resource": map[string]any{
"addr": "ephemeral.foo.bar",
"implied_provider": "foo",
"module": "",
"resource": "ephemeral.foo.bar",
"resource_key": nil,
"resource_name": "bar",
"resource_type": "foo",
},
},
"type": "ephemeral_action_started",
},
},
wantStdout: withNewline(`ephemeral.foo.bar: Opening...`),
},
// Operation
"random operation": {
viewCall: func(v Import) {
v.Operation().Interrupted()
},
wantJson: []map[string]any{
{
"@level": "info",
"@message": "\nInterrupt received.\nPlease wait for OpenTofu to exit or data loss may occur.\nGracefully shutting down...\n",
"@module": "tofu.ui",
"type": "log",
},
},
wantStdout: withNewline("\nInterrupt received.\nPlease wait for OpenTofu to exit or data loss may occur.\nGracefully shutting down...\n"),
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
testImportHuman(t, tc.viewCall, tc.wantStdout, tc.wantStderr)
testImportJson(t, tc.viewCall, tc.wantJson)
testImportMulti(t, tc.viewCall, tc.wantStdout, tc.wantStderr, tc.wantJson)
})
}
}
func testImportHuman(t *testing.T, call func(v Import), wantStdout, wantStderr string) {
view, done := testView(t)
v := NewImport(arguments.ViewOptions{ViewType: arguments.ViewHuman}, view)
call(v)
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 testImportJson(t *testing.T, call func(v Import), want []map[string]interface{}) {
// New type just to assert the fields that we are interested in
view, done := testView(t)
v := NewImport(arguments.ViewOptions{ViewType: arguments.ViewJSON}, view)
call(v)
output := done(t)
if output.Stderr() != "" {
t.Errorf("expected no stderr but got:\n%s", output.Stderr())
}
testJSONViewOutputEquals(t, output.Stdout(), want)
}
func testImportMulti(t *testing.T, call func(v Import), 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)
v := NewImport(arguments.ViewOptions{ViewType: arguments.ViewHuman, JSONInto: jsonInto}, view)
call(v)
{
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)
}
}
}