github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/command/views/show_test.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package views 5 6 import ( 7 "encoding/json" 8 "os" 9 "strings" 10 "testing" 11 12 "github.com/terramate-io/tf/addrs" 13 "github.com/terramate-io/tf/cloud/cloudplan" 14 "github.com/terramate-io/tf/command/arguments" 15 "github.com/terramate-io/tf/configs/configschema" 16 "github.com/terramate-io/tf/initwd" 17 "github.com/terramate-io/tf/plans" 18 "github.com/terramate-io/tf/providers" 19 "github.com/terramate-io/tf/states" 20 "github.com/terramate-io/tf/states/statefile" 21 "github.com/terramate-io/tf/terminal" 22 "github.com/terramate-io/tf/terraform" 23 24 "github.com/zclconf/go-cty/cty" 25 ) 26 27 func TestShowHuman(t *testing.T) { 28 redactedPath := "./testdata/plans/redacted-plan.json" 29 redactedPlanJson, err := os.ReadFile(redactedPath) 30 if err != nil { 31 t.Fatalf("couldn't read json plan test data at %s for showing a cloud plan. Did the file get moved?", redactedPath) 32 } 33 testCases := map[string]struct { 34 plan *plans.Plan 35 jsonPlan *cloudplan.RemotePlanJSON 36 stateFile *statefile.File 37 schemas *terraform.Schemas 38 wantExact bool 39 wantString string 40 }{ 41 "plan file": { 42 testPlan(t), 43 nil, 44 nil, 45 testSchemas(), 46 false, 47 "# test_resource.foo will be created", 48 }, 49 "cloud plan file": { 50 nil, 51 &cloudplan.RemotePlanJSON{ 52 JSONBytes: redactedPlanJson, 53 Redacted: true, 54 Mode: plans.NormalMode, 55 Qualities: []plans.Quality{}, 56 RunHeader: "[reset][yellow]To view this run in a browser, visit:\nhttps://app.terraform.io/app/example_org/example_workspace/runs/run-run-bugsBUGSbugsBUGS[reset]", 57 RunFooter: "[reset][green]Run status: planned and saved (confirmable)[reset]\n[green]Workspace is unlocked[reset]", 58 }, 59 nil, 60 nil, 61 false, 62 "# null_resource.foo will be created", 63 }, 64 "statefile": { 65 nil, 66 nil, 67 &statefile.File{ 68 Serial: 0, 69 Lineage: "fake-for-testing", 70 State: testState(), 71 }, 72 testSchemas(), 73 false, 74 "# test_resource.foo:", 75 }, 76 "empty statefile": { 77 nil, 78 nil, 79 &statefile.File{ 80 Serial: 0, 81 Lineage: "fake-for-testing", 82 State: states.NewState(), 83 }, 84 testSchemas(), 85 true, 86 "The state file is empty. No resources are represented.\n", 87 }, 88 "nothing": { 89 nil, 90 nil, 91 nil, 92 nil, 93 true, 94 "No state.\n", 95 }, 96 } 97 for name, testCase := range testCases { 98 t.Run(name, func(t *testing.T) { 99 streams, done := terminal.StreamsForTesting(t) 100 view := NewView(streams) 101 view.Configure(&arguments.View{NoColor: true}) 102 v := NewShow(arguments.ViewHuman, view) 103 104 code := v.Display(nil, testCase.plan, testCase.jsonPlan, testCase.stateFile, testCase.schemas) 105 if code != 0 { 106 t.Errorf("expected 0 return code, got %d", code) 107 } 108 109 output := done(t) 110 got := output.Stdout() 111 want := testCase.wantString 112 if (testCase.wantExact && got != want) || (!testCase.wantExact && !strings.Contains(got, want)) { 113 t.Fatalf("unexpected output\ngot: %s\nwant: %s", got, want) 114 } 115 }) 116 } 117 } 118 119 func TestShowJSON(t *testing.T) { 120 unredactedPath := "../testdata/show-json/basic-create/output.json" 121 unredactedPlanJson, err := os.ReadFile(unredactedPath) 122 if err != nil { 123 t.Fatalf("couldn't read json plan test data at %s for showing a cloud plan. Did the file get moved?", unredactedPath) 124 } 125 testCases := map[string]struct { 126 plan *plans.Plan 127 jsonPlan *cloudplan.RemotePlanJSON 128 stateFile *statefile.File 129 }{ 130 "plan file": { 131 testPlan(t), 132 nil, 133 nil, 134 }, 135 "cloud plan file": { 136 nil, 137 &cloudplan.RemotePlanJSON{ 138 JSONBytes: unredactedPlanJson, 139 Redacted: false, 140 Mode: plans.NormalMode, 141 Qualities: []plans.Quality{}, 142 RunHeader: "[reset][yellow]To view this run in a browser, visit:\nhttps://app.terraform.io/app/example_org/example_workspace/runs/run-run-bugsBUGSbugsBUGS[reset]", 143 RunFooter: "[reset][green]Run status: planned and saved (confirmable)[reset]\n[green]Workspace is unlocked[reset]", 144 }, 145 nil, 146 }, 147 "statefile": { 148 nil, 149 nil, 150 &statefile.File{ 151 Serial: 0, 152 Lineage: "fake-for-testing", 153 State: testState(), 154 }, 155 }, 156 "empty statefile": { 157 nil, 158 nil, 159 &statefile.File{ 160 Serial: 0, 161 Lineage: "fake-for-testing", 162 State: states.NewState(), 163 }, 164 }, 165 "nothing": { 166 nil, 167 nil, 168 nil, 169 }, 170 } 171 172 config, _, configCleanup := initwd.MustLoadConfigForTests(t, "./testdata/show", "tests") 173 defer configCleanup() 174 175 for name, testCase := range testCases { 176 t.Run(name, func(t *testing.T) { 177 streams, done := terminal.StreamsForTesting(t) 178 view := NewView(streams) 179 view.Configure(&arguments.View{NoColor: true}) 180 v := NewShow(arguments.ViewJSON, view) 181 182 schemas := &terraform.Schemas{ 183 Providers: map[addrs.Provider]providers.ProviderSchema{ 184 addrs.NewDefaultProvider("test"): { 185 ResourceTypes: map[string]providers.Schema{ 186 "test_resource": { 187 Block: &configschema.Block{ 188 Attributes: map[string]*configschema.Attribute{ 189 "id": {Type: cty.String, Optional: true, Computed: true}, 190 "foo": {Type: cty.String, Optional: true}, 191 }, 192 }, 193 }, 194 }, 195 }, 196 }, 197 } 198 199 code := v.Display(config, testCase.plan, testCase.jsonPlan, testCase.stateFile, schemas) 200 201 if code != 0 { 202 t.Errorf("expected 0 return code, got %d", code) 203 } 204 205 // Make sure the result looks like JSON; we comprehensively test 206 // the structure of this output in the command package tests. 207 var result map[string]interface{} 208 got := done(t).All() 209 t.Logf("output: %s", got) 210 if err := json.Unmarshal([]byte(got), &result); err != nil { 211 t.Fatal(err) 212 } 213 }) 214 } 215 } 216 217 // testState returns a test State structure. 218 func testState() *states.State { 219 return states.BuildState(func(s *states.SyncState) { 220 s.SetResourceInstanceCurrent( 221 addrs.Resource{ 222 Mode: addrs.ManagedResourceMode, 223 Type: "test_resource", 224 Name: "foo", 225 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 226 &states.ResourceInstanceObjectSrc{ 227 AttrsJSON: []byte(`{"id":"bar","foo":"value"}`), 228 Status: states.ObjectReady, 229 }, 230 addrs.AbsProviderConfig{ 231 Provider: addrs.NewDefaultProvider("test"), 232 Module: addrs.RootModule, 233 }, 234 ) 235 // DeepCopy is used here to ensure our synthetic state matches exactly 236 // with a state that will have been copied during the command 237 // operation, and all fields have been copied correctly. 238 }).DeepCopy() 239 }