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  }