| package views |
| |
| import ( |
| "fmt" |
| "strings" |
| "testing" |
| |
| "github.com/hashicorp/terraform/internal/command/arguments" |
| "github.com/hashicorp/terraform/internal/lang/marks" |
| "github.com/hashicorp/terraform/internal/states" |
| "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) |
| v := NewApply(arguments.ViewHuman, false, NewView(streams).SetRunningInAutomation(true)) |
| 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) |
| v := NewApply(arguments.ViewHuman, false, NewView(streams)) |
| |
| 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) |
| v := NewApply(arguments.ViewHuman, false, NewView(streams)) |
| |
| 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) |
| v := NewApply(arguments.ViewHuman, false, NewView(streams).SetRunningInAutomation(true)).Operation() |
| 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) |
| v := NewApply(arguments.ViewHuman, destroy, NewView(streams)) |
| 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. |
| func TestApply_resourceCount(t *testing.T) { |
| 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.", |
| }, |
| } |
| |
| // For compatibility reasons, these tests should hold true for both human |
| // and JSON output modes |
| views := []arguments.ViewType{arguments.ViewHuman, arguments.ViewJSON} |
| |
| 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) |
| v := NewApply(viewType, tc.destroy, NewView(streams)) |
| 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 = 1 |
| count.Changed = 2 |
| count.Removed = 3 |
| |
| v.ResourceCount("") |
| |
| got := done(t).Stdout() |
| if !strings.Contains(got, tc.want) { |
| t.Errorf("wrong result\ngot: %q\nwant: %q", got, tc.want) |
| } |
| }) |
| } |
| } |
| } |
| |
| 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": { |
| added: 0, |
| changed: 5, |
| removed: 0, |
| statePath: "foo.tfstate", |
| wantContains: true, |
| }, |
| } |
| |
| for name, tc := range testCases { |
| t.Run(name, func(t *testing.T) { |
| streams, done := terminal.StreamsForTesting(t) |
| v := NewApply(arguments.ViewHuman, false, NewView(streams)) |
| 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) |
| } |
| }) |
| } |
| } |
| |
| // Basic test coverage of Outputs, since most of its functionality is tested |
| // elsewhere. |
| func TestApplyJSON_outputs(t *testing.T) { |
| streams, done := terminal.StreamsForTesting(t) |
| v := NewApply(arguments.ViewJSON, false, NewView(streams)) |
| |
| v.Outputs(map[string]*states.OutputValue{ |
| "boop_count": {Value: cty.NumberIntVal(92)}, |
| "password": {Value: cty.StringVal("horse-battery").Mark(marks.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, |
| "type": "string", |
| }, |
| }, |
| }, |
| } |
| testJSONViewOutputEquals(t, done(t).Stdout(), want) |
| } |