github.com/jaredpalmer/terraform@v1.1.0-alpha20210908.0.20210911170307-88705c943a03/internal/repl/session_test.go (about)

     1  package repl
     2  
     3  import (
     4  	"flag"
     5  	"os"
     6  	"strings"
     7  	"testing"
     8  
     9  	"github.com/zclconf/go-cty/cty"
    10  
    11  	"github.com/hashicorp/terraform/internal/addrs"
    12  	"github.com/hashicorp/terraform/internal/configs/configschema"
    13  	"github.com/hashicorp/terraform/internal/initwd"
    14  	"github.com/hashicorp/terraform/internal/providers"
    15  	"github.com/hashicorp/terraform/internal/states"
    16  	"github.com/hashicorp/terraform/internal/terraform"
    17  
    18  	_ "github.com/hashicorp/terraform/internal/logging"
    19  )
    20  
    21  func TestMain(m *testing.M) {
    22  	flag.Parse()
    23  	os.Exit(m.Run())
    24  }
    25  
    26  func TestSession_basicState(t *testing.T) {
    27  	state := states.BuildState(func(s *states.SyncState) {
    28  		s.SetResourceInstanceCurrent(
    29  			addrs.Resource{
    30  				Mode: addrs.ManagedResourceMode,
    31  				Type: "test_instance",
    32  				Name: "foo",
    33  			}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
    34  			&states.ResourceInstanceObjectSrc{
    35  				Status:    states.ObjectReady,
    36  				AttrsJSON: []byte(`{"id":"bar"}`),
    37  			},
    38  			addrs.AbsProviderConfig{
    39  				Provider: addrs.NewDefaultProvider("test"),
    40  				Module:   addrs.RootModule,
    41  			},
    42  		)
    43  		s.SetResourceInstanceCurrent(
    44  			addrs.Resource{
    45  				Mode: addrs.ManagedResourceMode,
    46  				Type: "test_instance",
    47  				Name: "foo",
    48  			}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance.Child("module", addrs.NoKey)),
    49  			&states.ResourceInstanceObjectSrc{
    50  				Status:    states.ObjectReady,
    51  				AttrsJSON: []byte(`{"id":"bar"}`),
    52  			},
    53  			addrs.AbsProviderConfig{
    54  				Provider: addrs.NewDefaultProvider("test"),
    55  				Module:   addrs.RootModule,
    56  			},
    57  		)
    58  	})
    59  
    60  	t.Run("basic", func(t *testing.T) {
    61  		testSession(t, testSessionTest{
    62  			State: state,
    63  			Inputs: []testSessionInput{
    64  				{
    65  					Input:  "test_instance.foo.id",
    66  					Output: `"bar"`,
    67  				},
    68  			},
    69  		})
    70  	})
    71  
    72  	t.Run("missing resource", func(t *testing.T) {
    73  		testSession(t, testSessionTest{
    74  			State: state,
    75  			Inputs: []testSessionInput{
    76  				{
    77  					Input:         "test_instance.bar.id",
    78  					Error:         true,
    79  					ErrorContains: `A managed resource "test_instance" "bar" has not been declared`,
    80  				},
    81  			},
    82  		})
    83  	})
    84  
    85  	t.Run("missing module", func(t *testing.T) {
    86  		testSession(t, testSessionTest{
    87  			State: state,
    88  			Inputs: []testSessionInput{
    89  				{
    90  					Input:         "module.child",
    91  					Error:         true,
    92  					ErrorContains: `No module call named "child" is declared in the root module.`,
    93  				},
    94  			},
    95  		})
    96  	})
    97  
    98  	t.Run("missing module referencing just one output", func(t *testing.T) {
    99  		testSession(t, testSessionTest{
   100  			State: state,
   101  			Inputs: []testSessionInput{
   102  				{
   103  					Input:         "module.child.foo",
   104  					Error:         true,
   105  					ErrorContains: `No module call named "child" is declared in the root module.`,
   106  				},
   107  			},
   108  		})
   109  	})
   110  
   111  	t.Run("missing module output", func(t *testing.T) {
   112  		testSession(t, testSessionTest{
   113  			State: state,
   114  			Inputs: []testSessionInput{
   115  				{
   116  					Input:         "module.module.foo",
   117  					Error:         true,
   118  					ErrorContains: `Unsupported attribute: This object does not have an attribute named "foo"`,
   119  				},
   120  			},
   121  		})
   122  	})
   123  }
   124  
   125  func TestSession_stateless(t *testing.T) {
   126  	t.Run("exit", func(t *testing.T) {
   127  		testSession(t, testSessionTest{
   128  			Inputs: []testSessionInput{
   129  				{
   130  					Input: "exit",
   131  					Exit:  true,
   132  				},
   133  			},
   134  		})
   135  	})
   136  
   137  	t.Run("help", func(t *testing.T) {
   138  		testSession(t, testSessionTest{
   139  			Inputs: []testSessionInput{
   140  				{
   141  					Input:          "help",
   142  					OutputContains: "allows you to",
   143  				},
   144  			},
   145  		})
   146  	})
   147  
   148  	t.Run("help with spaces", func(t *testing.T) {
   149  		testSession(t, testSessionTest{
   150  			Inputs: []testSessionInput{
   151  				{
   152  					Input:          "help   ",
   153  					OutputContains: "allows you to",
   154  				},
   155  			},
   156  		})
   157  	})
   158  
   159  	t.Run("basic math", func(t *testing.T) {
   160  		testSession(t, testSessionTest{
   161  			Inputs: []testSessionInput{
   162  				{
   163  					Input:  "1 + 5",
   164  					Output: "6",
   165  				},
   166  			},
   167  		})
   168  	})
   169  
   170  	t.Run("missing resource", func(t *testing.T) {
   171  		testSession(t, testSessionTest{
   172  			Inputs: []testSessionInput{
   173  				{
   174  					Input:         "test_instance.bar.id",
   175  					Error:         true,
   176  					ErrorContains: `resource "test_instance" "bar" has not been declared`,
   177  				},
   178  			},
   179  		})
   180  	})
   181  }
   182  
   183  func testSession(t *testing.T, test testSessionTest) {
   184  	t.Helper()
   185  
   186  	p := &terraform.MockProvider{}
   187  	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
   188  		ResourceTypes: map[string]providers.Schema{
   189  			"test_instance": {
   190  				Block: &configschema.Block{
   191  					Attributes: map[string]*configschema.Attribute{
   192  						"id": {Type: cty.String, Computed: true},
   193  					},
   194  				},
   195  			},
   196  		},
   197  	}
   198  
   199  	config, _, cleanup, configDiags := initwd.LoadConfigForTests(t, "testdata/config-fixture")
   200  	defer cleanup()
   201  	if configDiags.HasErrors() {
   202  		t.Fatalf("unexpected problems loading config: %s", configDiags.Err())
   203  	}
   204  
   205  	// Build the TF context
   206  	ctx, diags := terraform.NewContext(&terraform.ContextOpts{
   207  		Providers: map[addrs.Provider]providers.Factory{
   208  			addrs.NewDefaultProvider("test"): providers.FactoryFixed(p),
   209  		},
   210  	})
   211  	if diags.HasErrors() {
   212  		t.Fatalf("failed to create context: %s", diags.Err())
   213  	}
   214  
   215  	state := test.State
   216  	if state == nil {
   217  		state = states.NewState()
   218  	}
   219  	scope, diags := ctx.Eval(config, state, addrs.RootModuleInstance, &terraform.EvalOpts{})
   220  	if diags.HasErrors() {
   221  		t.Fatalf("failed to create scope: %s", diags.Err())
   222  	}
   223  
   224  	// Build the session
   225  	s := &Session{
   226  		Scope: scope,
   227  	}
   228  
   229  	// Test the inputs. We purposely don't use subtests here because
   230  	// the inputs don't represent subtests, but a sequence of stateful
   231  	// operations.
   232  	for _, input := range test.Inputs {
   233  		result, exit, diags := s.Handle(input.Input)
   234  		if exit != input.Exit {
   235  			t.Fatalf("incorrect 'exit' result %t; want %t", exit, input.Exit)
   236  		}
   237  		if (diags.HasErrors()) != input.Error {
   238  			t.Fatalf("%q: unexpected errors: %s", input.Input, diags.Err())
   239  		}
   240  		if diags.HasErrors() {
   241  			if input.ErrorContains != "" {
   242  				if !strings.Contains(diags.Err().Error(), input.ErrorContains) {
   243  					t.Fatalf(
   244  						"%q: diagnostics should contain: %q\n\n%s",
   245  						input.Input, input.ErrorContains, diags.Err(),
   246  					)
   247  				}
   248  			}
   249  
   250  			continue
   251  		}
   252  
   253  		if input.Output != "" && result != input.Output {
   254  			t.Fatalf(
   255  				"%q: expected:\n\n%s\n\ngot:\n\n%s",
   256  				input.Input, input.Output, result)
   257  		}
   258  
   259  		if input.OutputContains != "" && !strings.Contains(result, input.OutputContains) {
   260  			t.Fatalf(
   261  				"%q: expected contains:\n\n%s\n\ngot:\n\n%s",
   262  				input.Input, input.OutputContains, result)
   263  		}
   264  	}
   265  }
   266  
   267  type testSessionTest struct {
   268  	State  *states.State // State to use
   269  	Module string        // Module name in testdata to load
   270  
   271  	// Inputs are the list of test inputs that are run in order.
   272  	// Each input can test the output of each step.
   273  	Inputs []testSessionInput
   274  }
   275  
   276  // testSessionInput is a single input to test for a session.
   277  type testSessionInput struct {
   278  	Input          string // Input string
   279  	Output         string // Exact output string to check
   280  	OutputContains string
   281  	Error          bool // Error is true if error is expected
   282  	Exit           bool // Exit is true if exiting is expected
   283  	ErrorContains  string
   284  }