mirror of
https://github.com/opentffoundation/opentf.git
synced 2025-12-21 10:47:34 -05:00
335 lines
8.8 KiB
Go
335 lines
8.8 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 (
|
|
"encoding/json"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
"github.com/opentofu/opentofu/internal/addrs"
|
|
"github.com/opentofu/opentofu/internal/cloud/cloudplan"
|
|
"github.com/opentofu/opentofu/internal/command/arguments"
|
|
"github.com/opentofu/opentofu/internal/configs/configschema"
|
|
"github.com/opentofu/opentofu/internal/initwd"
|
|
"github.com/opentofu/opentofu/internal/plans"
|
|
"github.com/opentofu/opentofu/internal/providers"
|
|
"github.com/opentofu/opentofu/internal/states"
|
|
"github.com/opentofu/opentofu/internal/states/statefile"
|
|
"github.com/opentofu/opentofu/internal/terminal"
|
|
"github.com/opentofu/opentofu/internal/tofu"
|
|
)
|
|
|
|
func TestShowHuman_DisplayPlan(t *testing.T) {
|
|
redactedPath := "./testdata/plans/redacted-plan.json"
|
|
redactedPlanJson, err := os.ReadFile(redactedPath)
|
|
if err != nil {
|
|
t.Fatalf("couldn't read json plan test data at %s for showing a cloud plan. Did the file get moved?", redactedPath)
|
|
}
|
|
testCases := map[string]struct {
|
|
plan *plans.Plan
|
|
jsonPlan *cloudplan.RemotePlanJSON
|
|
schemas *tofu.Schemas
|
|
wantExact bool
|
|
wantString string
|
|
}{
|
|
"plan file": {
|
|
testPlan(t),
|
|
nil,
|
|
testSchemas(),
|
|
false,
|
|
"# test_resource.foo will be created",
|
|
},
|
|
"cloud plan file": {
|
|
nil,
|
|
&cloudplan.RemotePlanJSON{
|
|
JSONBytes: redactedPlanJson,
|
|
Redacted: true,
|
|
Mode: plans.NormalMode,
|
|
Qualities: []plans.Quality{},
|
|
RunHeader: "[reset][yellow]To view this run in a browser, visit:\nhttps://app.example.com/app/example_org/example_workspace/runs/run-run-bugsBUGSbugsBUGS[reset]",
|
|
RunFooter: "[reset][green]Run status: planned and saved (confirmable)[reset]\n[green]Workspace is unlocked[reset]",
|
|
},
|
|
nil,
|
|
false,
|
|
"# null_resource.foo will be created",
|
|
},
|
|
"nothing": {
|
|
nil,
|
|
nil,
|
|
nil,
|
|
true,
|
|
"No plan.\n",
|
|
},
|
|
}
|
|
for name, testCase := range testCases {
|
|
t.Run(name, func(t *testing.T) {
|
|
streams, done := terminal.StreamsForTesting(t)
|
|
view := NewView(streams)
|
|
view.Configure(&arguments.View{NoColor: true})
|
|
v := NewShow(arguments.ViewHuman, view)
|
|
|
|
code := v.DisplayPlan(t.Context(), testCase.plan, testCase.jsonPlan, nil, nil, testCase.schemas)
|
|
if code != 0 {
|
|
t.Errorf("expected 0 return code, got %d", code)
|
|
}
|
|
|
|
output := done(t)
|
|
got := output.Stdout()
|
|
want := testCase.wantString
|
|
if (testCase.wantExact && got != want) || (!testCase.wantExact && !strings.Contains(got, want)) {
|
|
t.Fatalf("unexpected output\ngot: %s\nwant: %s", got, want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestShowHuman_DisplayState(t *testing.T) {
|
|
testCases := map[string]struct {
|
|
stateFile *statefile.File
|
|
schemas *tofu.Schemas
|
|
wantExact bool
|
|
wantString string
|
|
}{
|
|
"non-empty statefile": {
|
|
&statefile.File{
|
|
Serial: 0,
|
|
Lineage: "fake-for-testing",
|
|
State: testState(),
|
|
},
|
|
testSchemas(),
|
|
false,
|
|
"# test_resource.foo:",
|
|
},
|
|
"empty statefile": {
|
|
&statefile.File{
|
|
Serial: 0,
|
|
Lineage: "fake-for-testing",
|
|
State: states.NewState(),
|
|
},
|
|
testSchemas(),
|
|
true,
|
|
"The state file is empty. No resources are represented.\n",
|
|
},
|
|
"nothing": {
|
|
nil,
|
|
nil,
|
|
true,
|
|
"No state.\n",
|
|
},
|
|
}
|
|
for name, testCase := range testCases {
|
|
t.Run(name, func(t *testing.T) {
|
|
streams, done := terminal.StreamsForTesting(t)
|
|
view := NewView(streams)
|
|
view.Configure(&arguments.View{NoColor: true})
|
|
v := NewShow(arguments.ViewHuman, view)
|
|
|
|
code := v.DisplayState(t.Context(), testCase.stateFile, testCase.schemas)
|
|
if code != 0 {
|
|
t.Errorf("expected 0 return code, got %d", code)
|
|
}
|
|
|
|
output := done(t)
|
|
got := output.Stdout()
|
|
want := testCase.wantString
|
|
if (testCase.wantExact && got != want) || (!testCase.wantExact && !strings.Contains(got, want)) {
|
|
t.Fatalf("unexpected output\ngot: %s\nwant: %s", got, want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestShowJSON_DisplayPlan(t *testing.T) {
|
|
unredactedPath := "../testdata/show-json/basic-create/output.json"
|
|
unredactedPlanJson, err := os.ReadFile(unredactedPath)
|
|
if err != nil {
|
|
t.Fatalf("couldn't read json plan test data at %s for showing a cloud plan. Did the file get moved?", unredactedPath)
|
|
}
|
|
testCases := map[string]struct {
|
|
plan *plans.Plan
|
|
jsonPlan *cloudplan.RemotePlanJSON
|
|
stateFile *statefile.File
|
|
}{
|
|
"plan file": {
|
|
testPlan(t),
|
|
nil,
|
|
nil,
|
|
},
|
|
"cloud plan file": {
|
|
nil,
|
|
&cloudplan.RemotePlanJSON{
|
|
JSONBytes: unredactedPlanJson,
|
|
Redacted: false,
|
|
Mode: plans.NormalMode,
|
|
Qualities: []plans.Quality{},
|
|
RunHeader: "[reset][yellow]To view this run in a browser, visit:\nhttps://app.example.com/app/example_org/example_workspace/runs/run-run-bugsBUGSbugsBUGS[reset]",
|
|
RunFooter: "[reset][green]Run status: planned and saved (confirmable)[reset]\n[green]Workspace is unlocked[reset]",
|
|
},
|
|
nil,
|
|
},
|
|
"statefile": {
|
|
nil,
|
|
nil,
|
|
&statefile.File{
|
|
Serial: 0,
|
|
Lineage: "fake-for-testing",
|
|
State: testState(),
|
|
},
|
|
},
|
|
"empty statefile": {
|
|
nil,
|
|
nil,
|
|
&statefile.File{
|
|
Serial: 0,
|
|
Lineage: "fake-for-testing",
|
|
State: states.NewState(),
|
|
},
|
|
},
|
|
"nothing": {
|
|
nil,
|
|
nil,
|
|
nil,
|
|
},
|
|
}
|
|
|
|
config, _ := initwd.MustLoadConfigForTests(t, "./testdata/show", "tests")
|
|
|
|
for name, testCase := range testCases {
|
|
t.Run(name, func(t *testing.T) {
|
|
streams, done := terminal.StreamsForTesting(t)
|
|
view := NewView(streams)
|
|
view.Configure(&arguments.View{NoColor: true})
|
|
v := NewShow(arguments.ViewJSON, view)
|
|
|
|
schemas := &tofu.Schemas{
|
|
Providers: map[addrs.Provider]providers.ProviderSchema{
|
|
addrs.NewDefaultProvider("test"): {
|
|
ResourceTypes: map[string]providers.Schema{
|
|
"test_resource": {
|
|
Block: &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {Type: cty.String, Optional: true, Computed: true},
|
|
"foo": {Type: cty.String, Optional: true},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
code := v.DisplayPlan(t.Context(), testCase.plan, testCase.jsonPlan, config, testCase.stateFile, schemas)
|
|
|
|
if code != 0 {
|
|
t.Errorf("expected 0 return code, got %d", code)
|
|
}
|
|
|
|
// Make sure the result looks like JSON; we comprehensively test
|
|
// the structure of this output in the command package tests.
|
|
var result map[string]any
|
|
got := done(t).All()
|
|
t.Logf("output: %s", got)
|
|
if err := json.Unmarshal([]byte(got), &result); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestShowJSON_DisplayState(t *testing.T) {
|
|
testCases := map[string]struct {
|
|
stateFile *statefile.File
|
|
}{
|
|
"non-empty statefile": {
|
|
&statefile.File{
|
|
Serial: 0,
|
|
Lineage: "fake-for-testing",
|
|
State: testState(),
|
|
},
|
|
},
|
|
"empty statefile": {
|
|
&statefile.File{
|
|
Serial: 0,
|
|
Lineage: "fake-for-testing",
|
|
State: states.NewState(),
|
|
},
|
|
},
|
|
"nothing": {
|
|
nil,
|
|
},
|
|
}
|
|
|
|
for name, testCase := range testCases {
|
|
t.Run(name, func(t *testing.T) {
|
|
streams, done := terminal.StreamsForTesting(t)
|
|
view := NewView(streams)
|
|
view.Configure(&arguments.View{NoColor: true})
|
|
v := NewShow(arguments.ViewJSON, view)
|
|
|
|
schemas := &tofu.Schemas{
|
|
Providers: map[addrs.Provider]providers.ProviderSchema{
|
|
addrs.NewDefaultProvider("test"): {
|
|
ResourceTypes: map[string]providers.Schema{
|
|
"test_resource": {
|
|
Block: &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {Type: cty.String, Optional: true, Computed: true},
|
|
"foo": {Type: cty.String, Optional: true},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
code := v.DisplayState(t.Context(), testCase.stateFile, schemas)
|
|
|
|
if code != 0 {
|
|
t.Errorf("expected 0 return code, got %d", code)
|
|
}
|
|
|
|
// Make sure the result looks like JSON; we comprehensively test
|
|
// the structure of this output in the command package tests.
|
|
var result map[string]any
|
|
got := done(t).All()
|
|
t.Logf("output: %s", got)
|
|
if err := json.Unmarshal([]byte(got), &result); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// testState returns a test State structure.
|
|
func testState() *states.State {
|
|
return states.BuildState(func(s *states.SyncState) {
|
|
s.SetResourceInstanceCurrent(
|
|
addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test_resource",
|
|
Name: "foo",
|
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
|
&states.ResourceInstanceObjectSrc{
|
|
AttrsJSON: []byte(`{"id":"bar","foo":"value"}`),
|
|
Status: states.ObjectReady,
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("test"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
addrs.NoKey,
|
|
)
|
|
// DeepCopy is used here to ensure our synthetic state matches exactly
|
|
// with a state that will have been copied during the command
|
|
// operation, and all fields have been copied correctly.
|
|
}).DeepCopy()
|
|
}
|