2021-02-18 23:23:34 +01:00
|
|
|
package views
|
|
|
|
|
|
|
|
import (
|
2021-02-23 16:16:09 +01:00
|
|
|
"fmt"
|
2021-02-18 23:23:34 +01:00
|
|
|
"strings"
|
|
|
|
"testing"
|
|
|
|
|
2021-05-17 21:07:38 +02:00
|
|
|
"github.com/hashicorp/terraform/internal/command/arguments"
|
2021-05-17 21:43:35 +02:00
|
|
|
"github.com/hashicorp/terraform/internal/states"
|
2021-02-18 23:23:34 +01:00
|
|
|
"github.com/hashicorp/terraform/internal/terminal"
|
|
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
)
|
|
|
|
|
|
|
|
// This test is mostly because I am paranoid about having two consecutive
|
|
|
|
// boolean arguments.
|
|
|
|
func TestApply_new(t *testing.T) {
|
|
|
|
streams, done := terminal.StreamsForTesting(t)
|
|
|
|
defer done(t)
|
2021-05-10 19:20:43 +02:00
|
|
|
v := NewApply(arguments.ViewHuman, false, NewView(streams).SetRunningInAutomation(true))
|
2021-02-18 23:23:34 +01:00
|
|
|
hv, ok := v.(*ApplyHuman)
|
|
|
|
if !ok {
|
|
|
|
t.Fatalf("unexpected return type %t", v)
|
|
|
|
}
|
|
|
|
|
|
|
|
if hv.destroy != false {
|
|
|
|
t.Fatalf("unexpected destroy value")
|
|
|
|
}
|
|
|
|
|
|
|
|
if hv.inAutomation != true {
|
|
|
|
t.Fatalf("unexpected inAutomation value")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Basic test coverage of Outputs, since most of its functionality is tested
|
|
|
|
// elsewhere.
|
|
|
|
func TestApplyHuman_outputs(t *testing.T) {
|
|
|
|
streams, done := terminal.StreamsForTesting(t)
|
2021-05-10 19:20:43 +02:00
|
|
|
v := NewApply(arguments.ViewHuman, false, NewView(streams))
|
2021-02-18 23:23:34 +01:00
|
|
|
|
|
|
|
v.Outputs(map[string]*states.OutputValue{
|
|
|
|
"foo": {Value: cty.StringVal("secret")},
|
|
|
|
})
|
|
|
|
|
|
|
|
got := done(t).Stdout()
|
|
|
|
for _, want := range []string{"Outputs:", `foo = "secret"`} {
|
|
|
|
if !strings.Contains(got, want) {
|
|
|
|
t.Errorf("wrong result\ngot: %q\nwant: %q", got, want)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Outputs should do nothing if there are no outputs to render.
|
|
|
|
func TestApplyHuman_outputsEmpty(t *testing.T) {
|
|
|
|
streams, done := terminal.StreamsForTesting(t)
|
2021-05-10 19:20:43 +02:00
|
|
|
v := NewApply(arguments.ViewHuman, false, NewView(streams))
|
2021-02-18 23:23:34 +01:00
|
|
|
|
|
|
|
v.Outputs(map[string]*states.OutputValue{})
|
|
|
|
|
|
|
|
got := done(t).Stdout()
|
|
|
|
if got != "" {
|
|
|
|
t.Errorf("output should be empty, but got: %q", got)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure that the correct view type and in-automation settings propagate to the
|
|
|
|
// Operation view.
|
|
|
|
func TestApplyHuman_operation(t *testing.T) {
|
|
|
|
streams, done := terminal.StreamsForTesting(t)
|
|
|
|
defer done(t)
|
2021-05-10 19:20:43 +02:00
|
|
|
v := NewApply(arguments.ViewHuman, false, NewView(streams).SetRunningInAutomation(true)).Operation()
|
2021-02-18 23:23:34 +01:00
|
|
|
if hv, ok := v.(*OperationHuman); !ok {
|
|
|
|
t.Fatalf("unexpected return type %t", v)
|
|
|
|
} else if hv.inAutomation != true {
|
|
|
|
t.Fatalf("unexpected inAutomation value on Operation view")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// This view is used for both apply and destroy commands, so the help output
|
|
|
|
// needs to cover both.
|
|
|
|
func TestApplyHuman_help(t *testing.T) {
|
|
|
|
testCases := map[string]bool{
|
|
|
|
"apply": false,
|
|
|
|
"destroy": true,
|
|
|
|
}
|
|
|
|
|
|
|
|
for name, destroy := range testCases {
|
|
|
|
t.Run(name, func(t *testing.T) {
|
|
|
|
streams, done := terminal.StreamsForTesting(t)
|
2021-05-10 19:20:43 +02:00
|
|
|
v := NewApply(arguments.ViewHuman, destroy, NewView(streams))
|
2021-02-18 23:23:34 +01:00
|
|
|
v.HelpPrompt()
|
|
|
|
got := done(t).Stderr()
|
|
|
|
if !strings.Contains(got, name) {
|
|
|
|
t.Errorf("wrong result\ngot: %q\nwant: %q", got, name)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Hooks and ResourceCount are tangled up and easiest to test together.
|
2021-02-23 16:16:09 +01:00
|
|
|
func TestApply_resourceCount(t *testing.T) {
|
2021-02-18 23:23:34 +01:00
|
|
|
testCases := map[string]struct {
|
|
|
|
destroy bool
|
|
|
|
want string
|
|
|
|
}{
|
|
|
|
"apply": {
|
|
|
|
false,
|
|
|
|
"Apply complete! Resources: 1 added, 2 changed, 3 destroyed.",
|
|
|
|
},
|
|
|
|
"destroy": {
|
|
|
|
true,
|
|
|
|
"Destroy complete! Resources: 3 destroyed.",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2021-02-23 16:16:09 +01:00
|
|
|
// For compatibility reasons, these tests should hold true for both human
|
|
|
|
// and JSON output modes
|
|
|
|
views := []arguments.ViewType{arguments.ViewHuman, arguments.ViewJSON}
|
2021-02-18 23:23:34 +01:00
|
|
|
|
2021-02-23 16:16:09 +01:00
|
|
|
for name, tc := range testCases {
|
|
|
|
for _, viewType := range views {
|
|
|
|
t.Run(fmt.Sprintf("%s (%s view)", name, viewType), func(t *testing.T) {
|
|
|
|
streams, done := terminal.StreamsForTesting(t)
|
2021-05-10 19:20:43 +02:00
|
|
|
v := NewApply(viewType, tc.destroy, NewView(streams))
|
2021-02-23 16:16:09 +01:00
|
|
|
hooks := v.Hooks()
|
|
|
|
|
|
|
|
var count *countHook
|
|
|
|
for _, hook := range hooks {
|
|
|
|
if ch, ok := hook.(*countHook); ok {
|
|
|
|
count = ch
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if count == nil {
|
|
|
|
t.Fatalf("expected Hooks to include a countHook: %#v", hooks)
|
2021-02-18 23:23:34 +01:00
|
|
|
}
|
|
|
|
|
2021-02-23 16:16:09 +01:00
|
|
|
count.Added = 1
|
|
|
|
count.Changed = 2
|
|
|
|
count.Removed = 3
|
2021-02-18 23:23:34 +01:00
|
|
|
|
2021-02-23 16:16:09 +01:00
|
|
|
v.ResourceCount("")
|
2021-02-18 23:23:34 +01:00
|
|
|
|
2021-02-23 16:16:09 +01:00
|
|
|
got := done(t).Stdout()
|
|
|
|
if !strings.Contains(got, tc.want) {
|
|
|
|
t.Errorf("wrong result\ngot: %q\nwant: %q", got, tc.want)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2021-02-18 23:23:34 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestApplyHuman_resourceCountStatePath(t *testing.T) {
|
|
|
|
testCases := map[string]struct {
|
|
|
|
added int
|
|
|
|
changed int
|
|
|
|
removed int
|
|
|
|
statePath string
|
|
|
|
wantContains bool
|
|
|
|
}{
|
|
|
|
"default state path": {
|
|
|
|
added: 1,
|
|
|
|
changed: 2,
|
|
|
|
removed: 3,
|
|
|
|
statePath: "",
|
|
|
|
wantContains: false,
|
|
|
|
},
|
|
|
|
"only removed": {
|
|
|
|
added: 0,
|
|
|
|
changed: 0,
|
|
|
|
removed: 5,
|
|
|
|
statePath: "foo.tfstate",
|
|
|
|
wantContains: false,
|
|
|
|
},
|
|
|
|
"added": {
|
|
|
|
added: 5,
|
|
|
|
changed: 0,
|
|
|
|
removed: 0,
|
|
|
|
statePath: "foo.tfstate",
|
|
|
|
wantContains: true,
|
|
|
|
},
|
|
|
|
"changed": {
|
2021-02-23 16:16:09 +01:00
|
|
|
added: 0,
|
|
|
|
changed: 5,
|
2021-02-18 23:23:34 +01:00
|
|
|
removed: 0,
|
|
|
|
statePath: "foo.tfstate",
|
|
|
|
wantContains: true,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for name, tc := range testCases {
|
|
|
|
t.Run(name, func(t *testing.T) {
|
|
|
|
streams, done := terminal.StreamsForTesting(t)
|
2021-05-10 19:20:43 +02:00
|
|
|
v := NewApply(arguments.ViewHuman, false, NewView(streams))
|
2021-02-18 23:23:34 +01:00
|
|
|
hooks := v.Hooks()
|
|
|
|
|
|
|
|
var count *countHook
|
|
|
|
for _, hook := range hooks {
|
|
|
|
if ch, ok := hook.(*countHook); ok {
|
|
|
|
count = ch
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if count == nil {
|
|
|
|
t.Fatalf("expected Hooks to include a countHook: %#v", hooks)
|
|
|
|
}
|
|
|
|
|
|
|
|
count.Added = tc.added
|
|
|
|
count.Changed = tc.changed
|
|
|
|
count.Removed = tc.removed
|
|
|
|
|
|
|
|
v.ResourceCount(tc.statePath)
|
|
|
|
|
|
|
|
got := done(t).Stdout()
|
|
|
|
want := "State path: " + tc.statePath
|
|
|
|
contains := strings.Contains(got, want)
|
|
|
|
if contains && !tc.wantContains {
|
|
|
|
t.Errorf("wrong result\ngot: %q\nshould not contain: %q", got, want)
|
|
|
|
} else if !contains && tc.wantContains {
|
|
|
|
t.Errorf("wrong result\ngot: %q\nshould contain: %q", got, want)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2021-02-23 16:16:09 +01:00
|
|
|
|
|
|
|
// Basic test coverage of Outputs, since most of its functionality is tested
|
|
|
|
// elsewhere.
|
|
|
|
func TestApplyJSON_outputs(t *testing.T) {
|
|
|
|
streams, done := terminal.StreamsForTesting(t)
|
2021-05-10 19:20:43 +02:00
|
|
|
v := NewApply(arguments.ViewJSON, false, NewView(streams))
|
2021-02-23 16:16:09 +01:00
|
|
|
|
|
|
|
v.Outputs(map[string]*states.OutputValue{
|
|
|
|
"boop_count": {Value: cty.NumberIntVal(92)},
|
|
|
|
"password": {Value: cty.StringVal("horse-battery").Mark("sensitive"), Sensitive: true},
|
|
|
|
})
|
|
|
|
|
|
|
|
want := []map[string]interface{}{
|
|
|
|
{
|
|
|
|
"@level": "info",
|
|
|
|
"@message": "Outputs: 2",
|
|
|
|
"@module": "terraform.ui",
|
|
|
|
"type": "outputs",
|
|
|
|
"outputs": map[string]interface{}{
|
|
|
|
"boop_count": map[string]interface{}{
|
|
|
|
"sensitive": false,
|
|
|
|
"value": float64(92),
|
|
|
|
"type": "number",
|
|
|
|
},
|
|
|
|
"password": map[string]interface{}{
|
|
|
|
"sensitive": true,
|
|
|
|
"value": "horse-battery",
|
|
|
|
"type": "string",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
testJSONViewOutputEquals(t, done(t).Stdout(), want)
|
|
|
|
}
|