github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/command/views/apply_test.go (about) 1 package views 2 3 import ( 4 "fmt" 5 "strings" 6 "testing" 7 8 "github.com/hashicorp/terraform/internal/command/arguments" 9 "github.com/hashicorp/terraform/internal/lang/marks" 10 "github.com/hashicorp/terraform/internal/states" 11 "github.com/hashicorp/terraform/internal/terminal" 12 "github.com/zclconf/go-cty/cty" 13 ) 14 15 // This test is mostly because I am paranoid about having two consecutive 16 // boolean arguments. 17 func TestApply_new(t *testing.T) { 18 streams, done := terminal.StreamsForTesting(t) 19 defer done(t) 20 v := NewApply(arguments.ViewHuman, false, NewView(streams).SetRunningInAutomation(true)) 21 hv, ok := v.(*ApplyHuman) 22 if !ok { 23 t.Fatalf("unexpected return type %t", v) 24 } 25 26 if hv.destroy != false { 27 t.Fatalf("unexpected destroy value") 28 } 29 30 if hv.inAutomation != true { 31 t.Fatalf("unexpected inAutomation value") 32 } 33 } 34 35 // Basic test coverage of Outputs, since most of its functionality is tested 36 // elsewhere. 37 func TestApplyHuman_outputs(t *testing.T) { 38 streams, done := terminal.StreamsForTesting(t) 39 v := NewApply(arguments.ViewHuman, false, NewView(streams)) 40 41 v.Outputs(map[string]*states.OutputValue{ 42 "foo": {Value: cty.StringVal("secret")}, 43 }) 44 45 got := done(t).Stdout() 46 for _, want := range []string{"Outputs:", `foo = "secret"`} { 47 if !strings.Contains(got, want) { 48 t.Errorf("wrong result\ngot: %q\nwant: %q", got, want) 49 } 50 } 51 } 52 53 // Outputs should do nothing if there are no outputs to render. 54 func TestApplyHuman_outputsEmpty(t *testing.T) { 55 streams, done := terminal.StreamsForTesting(t) 56 v := NewApply(arguments.ViewHuman, false, NewView(streams)) 57 58 v.Outputs(map[string]*states.OutputValue{}) 59 60 got := done(t).Stdout() 61 if got != "" { 62 t.Errorf("output should be empty, but got: %q", got) 63 } 64 } 65 66 // Ensure that the correct view type and in-automation settings propagate to the 67 // Operation view. 68 func TestApplyHuman_operation(t *testing.T) { 69 streams, done := terminal.StreamsForTesting(t) 70 defer done(t) 71 v := NewApply(arguments.ViewHuman, false, NewView(streams).SetRunningInAutomation(true)).Operation() 72 if hv, ok := v.(*OperationHuman); !ok { 73 t.Fatalf("unexpected return type %t", v) 74 } else if hv.inAutomation != true { 75 t.Fatalf("unexpected inAutomation value on Operation view") 76 } 77 } 78 79 // This view is used for both apply and destroy commands, so the help output 80 // needs to cover both. 81 func TestApplyHuman_help(t *testing.T) { 82 testCases := map[string]bool{ 83 "apply": false, 84 "destroy": true, 85 } 86 87 for name, destroy := range testCases { 88 t.Run(name, func(t *testing.T) { 89 streams, done := terminal.StreamsForTesting(t) 90 v := NewApply(arguments.ViewHuman, destroy, NewView(streams)) 91 v.HelpPrompt() 92 got := done(t).Stderr() 93 if !strings.Contains(got, name) { 94 t.Errorf("wrong result\ngot: %q\nwant: %q", got, name) 95 } 96 }) 97 } 98 } 99 100 // Hooks and ResourceCount are tangled up and easiest to test together. 101 func TestApply_resourceCount(t *testing.T) { 102 testCases := map[string]struct { 103 destroy bool 104 want string 105 }{ 106 "apply": { 107 false, 108 "Apply complete! Resources: 1 added, 2 changed, 3 destroyed.", 109 }, 110 "destroy": { 111 true, 112 "Destroy complete! Resources: 3 destroyed.", 113 }, 114 } 115 116 // For compatibility reasons, these tests should hold true for both human 117 // and JSON output modes 118 views := []arguments.ViewType{arguments.ViewHuman, arguments.ViewJSON} 119 120 for name, tc := range testCases { 121 for _, viewType := range views { 122 t.Run(fmt.Sprintf("%s (%s view)", name, viewType), func(t *testing.T) { 123 streams, done := terminal.StreamsForTesting(t) 124 v := NewApply(viewType, tc.destroy, NewView(streams)) 125 hooks := v.Hooks() 126 127 var count *countHook 128 for _, hook := range hooks { 129 if ch, ok := hook.(*countHook); ok { 130 count = ch 131 } 132 } 133 if count == nil { 134 t.Fatalf("expected Hooks to include a countHook: %#v", hooks) 135 } 136 137 count.Added = 1 138 count.Changed = 2 139 count.Removed = 3 140 141 v.ResourceCount("") 142 143 got := done(t).Stdout() 144 if !strings.Contains(got, tc.want) { 145 t.Errorf("wrong result\ngot: %q\nwant: %q", got, tc.want) 146 } 147 }) 148 } 149 } 150 } 151 152 func TestApplyHuman_resourceCountStatePath(t *testing.T) { 153 testCases := map[string]struct { 154 added int 155 changed int 156 removed int 157 statePath string 158 wantContains bool 159 }{ 160 "default state path": { 161 added: 1, 162 changed: 2, 163 removed: 3, 164 statePath: "", 165 wantContains: false, 166 }, 167 "only removed": { 168 added: 0, 169 changed: 0, 170 removed: 5, 171 statePath: "foo.tfstate", 172 wantContains: false, 173 }, 174 "added": { 175 added: 5, 176 changed: 0, 177 removed: 0, 178 statePath: "foo.tfstate", 179 wantContains: true, 180 }, 181 "changed": { 182 added: 0, 183 changed: 5, 184 removed: 0, 185 statePath: "foo.tfstate", 186 wantContains: true, 187 }, 188 } 189 190 for name, tc := range testCases { 191 t.Run(name, func(t *testing.T) { 192 streams, done := terminal.StreamsForTesting(t) 193 v := NewApply(arguments.ViewHuman, false, NewView(streams)) 194 hooks := v.Hooks() 195 196 var count *countHook 197 for _, hook := range hooks { 198 if ch, ok := hook.(*countHook); ok { 199 count = ch 200 } 201 } 202 if count == nil { 203 t.Fatalf("expected Hooks to include a countHook: %#v", hooks) 204 } 205 206 count.Added = tc.added 207 count.Changed = tc.changed 208 count.Removed = tc.removed 209 210 v.ResourceCount(tc.statePath) 211 212 got := done(t).Stdout() 213 want := "State path: " + tc.statePath 214 contains := strings.Contains(got, want) 215 if contains && !tc.wantContains { 216 t.Errorf("wrong result\ngot: %q\nshould not contain: %q", got, want) 217 } else if !contains && tc.wantContains { 218 t.Errorf("wrong result\ngot: %q\nshould contain: %q", got, want) 219 } 220 }) 221 } 222 } 223 224 // Basic test coverage of Outputs, since most of its functionality is tested 225 // elsewhere. 226 func TestApplyJSON_outputs(t *testing.T) { 227 streams, done := terminal.StreamsForTesting(t) 228 v := NewApply(arguments.ViewJSON, false, NewView(streams)) 229 230 v.Outputs(map[string]*states.OutputValue{ 231 "boop_count": {Value: cty.NumberIntVal(92)}, 232 "password": {Value: cty.StringVal("horse-battery").Mark(marks.Sensitive), Sensitive: true}, 233 }) 234 235 want := []map[string]interface{}{ 236 { 237 "@level": "info", 238 "@message": "Outputs: 2", 239 "@module": "terraform.ui", 240 "type": "outputs", 241 "outputs": map[string]interface{}{ 242 "boop_count": map[string]interface{}{ 243 "sensitive": false, 244 "value": float64(92), 245 "type": "number", 246 }, 247 "password": map[string]interface{}{ 248 "sensitive": true, 249 "type": "string", 250 }, 251 }, 252 }, 253 } 254 testJSONViewOutputEquals(t, done(t).Stdout(), want) 255 }