github.com/GoogleCloudPlatform/deploystack@v1.12.8/tui/post_processors_test.go (about)

     1  // Copyright 2023 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package tui
    16  
    17  import (
    18  	"fmt"
    19  	"reflect"
    20  	"testing"
    21  
    22  	"cloud.google.com/go/domains/apiv1beta1/domainspb"
    23  	"github.com/GoogleCloudPlatform/deploystack/gcloud"
    24  	tea "github.com/charmbracelet/bubbletea"
    25  	"github.com/stretchr/testify/assert"
    26  )
    27  
    28  func TestProcessProjectSelection(t *testing.T) {
    29  	tests := map[string]struct {
    30  		in       string
    31  		want     tea.Msg
    32  		setError bool
    33  		err      error
    34  	}{
    35  		"basic": {
    36  			in:   "testproject",
    37  			want: successMsg{},
    38  		},
    39  		"fail": {
    40  			in:       "testproject",
    41  			want:     errMsg{err: errForced},
    42  			setError: true,
    43  		},
    44  	}
    45  
    46  	for name, tc := range tests {
    47  		t.Run(name, func(t *testing.T) {
    48  
    49  			q := getTestQueue(appTitle, "test")
    50  
    51  			if tc.setError {
    52  				errMock := GetMock(0)
    53  				errMock.forceErr = true
    54  				q.client = errMock
    55  			}
    56  
    57  			cmd := processProjectSelection(tc.in, &q)
    58  			got := cmd()
    59  			if !reflect.DeepEqual(tc.want, got) {
    60  				t.Fatalf("expected: %+v, got: %+v", tc.want, got)
    61  			}
    62  		})
    63  	}
    64  }
    65  
    66  func TestCheckYesOrNo(t *testing.T) {
    67  	tests := map[string]struct {
    68  		in   string
    69  		want bool
    70  	}{
    71  		"y":         {in: "y", want: true},
    72  		"yes":       {in: "yes", want: true},
    73  		"n":         {in: "n", want: true},
    74  		"no":        {in: "no", want: true},
    75  		"Y":         {in: "Y", want: true},
    76  		"YES":       {in: "YES", want: true},
    77  		"N":         {in: "N", want: true},
    78  		"NO":        {in: "NO", want: true},
    79  		"yEs":       {in: "yEs", want: true},
    80  		"noT":       {in: "noT", want: false},
    81  		"sadasdasf": {in: "sadasdasf", want: false},
    82  		"ye":        {in: "ye", want: true},
    83  		"4567":      {in: "4567", want: false},
    84  	}
    85  
    86  	for name, tc := range tests {
    87  		t.Run(name, func(t *testing.T) {
    88  			got := checkYesOrNo(tc.in)
    89  
    90  			if tc.want != got {
    91  				t.Fatalf("%s - want '%t' got '%t'", tc.in, tc.want, got)
    92  			}
    93  		})
    94  	}
    95  }
    96  
    97  func TestValidateYesOrNo(t *testing.T) {
    98  	tests := map[string]struct {
    99  		in  string
   100  		msg tea.Msg
   101  	}{
   102  		"y":         {in: "y", msg: successMsg{}},
   103  		"yes":       {in: "yes", msg: successMsg{}},
   104  		"n":         {in: "n", msg: successMsg{}},
   105  		"no":        {in: "no", msg: successMsg{}},
   106  		"Y":         {in: "Y", msg: successMsg{}},
   107  		"YES":       {in: "YES", msg: successMsg{}},
   108  		"N":         {in: "N", msg: successMsg{}},
   109  		"NO":        {in: "NO", msg: successMsg{}},
   110  		"yEs":       {in: "yEs", msg: successMsg{}},
   111  		"noT":       {in: "noT", msg: errMsg{err: fmt.Errorf("Your answer '%s' is neither 'yes' nor 'no'", "noT")}},
   112  		"sadasdasf": {in: "sadasdasf", msg: errMsg{err: fmt.Errorf("Your answer '%s' is neither 'yes' nor 'no'", "sadasdasf")}},
   113  		"ye":        {in: "ye", msg: successMsg{}},
   114  		"4567":      {in: "4567", msg: errMsg{err: fmt.Errorf("Your answer '%s' is neither 'yes' nor 'no'", "4567")}},
   115  	}
   116  
   117  	for name, tc := range tests {
   118  		t.Run(name, func(t *testing.T) {
   119  			q := getTestQueue(appTitle, "test")
   120  			cmd := validateYesOrNo(tc.in, &q)
   121  
   122  			got := cmd()
   123  
   124  			switch tc.msg.(type) {
   125  			case successMsg:
   126  				if tc.msg != got {
   127  					t.Fatalf("%s - want: \n'%+v' \ngot: \n'%+v'", tc.in, tc.msg, got)
   128  				}
   129  			case errMsg:
   130  				gotE := got.(errMsg)
   131  				tcmsgE := tc.msg.(errMsg)
   132  
   133  				if tcmsgE.err.Error() != gotE.err.Error() {
   134  					t.Fatalf("want: \n'%+v' \ngot: \n'%+v'", tcmsgE.err.Error(), gotE.err.Error())
   135  				}
   136  
   137  			}
   138  		})
   139  	}
   140  }
   141  
   142  func TestValidatePhoneNumber(t *testing.T) {
   143  	tests := map[string]struct {
   144  		in  string
   145  		msg tea.Msg
   146  	}{
   147  		"Good":  {"800 555 1234", successMsg{}},
   148  		"Weird": {"d746fd83843", successMsg{}},
   149  		"BAD":   {"dghdhdfuejfhfhfhrghfhfhdhgreh", errMsg{err: fmt.Errorf("Your answer '%s' is not a valid phone number. Please try again", "dghdhdfuejfhfhfhrghfhfhdhgreh")}},
   150  	}
   151  
   152  	for name, tc := range tests {
   153  		t.Run(name, func(t *testing.T) {
   154  			q := getTestQueue(appTitle, "test")
   155  			cmd := validatePhoneNumber(tc.in, &q)
   156  
   157  			got := cmd()
   158  
   159  			switch tc.msg.(type) {
   160  			case successMsg:
   161  				if tc.msg != got {
   162  					t.Fatalf("%s - want: \n'%+v' \ngot: \n'%+v'", tc.in, tc.msg, got)
   163  				}
   164  			case errMsg:
   165  				gotE := got.(errMsg)
   166  				tcmsgE := tc.msg.(errMsg)
   167  
   168  				if tcmsgE.err.Error() != gotE.err.Error() {
   169  					t.Fatalf("want: \n'%+v' \ngot: \n'%+v'", tcmsgE.err.Error(), gotE.err.Error())
   170  				}
   171  
   172  			}
   173  		})
   174  	}
   175  }
   176  
   177  func TestMassagePhoneNumber(t *testing.T) {
   178  	tests := map[string]struct {
   179  		input string
   180  		want  string
   181  		err   error
   182  	}{
   183  		"Good":  {"800 555 1234", "+1.8005551234", nil},
   184  		"Weird": {"d746fd83843", "+1.74683843", nil},
   185  		"BAD":   {"dghdhdfuejfhfhfhrghfhfhdhgreh", "", ErrorCustomNotValidPhoneNumber},
   186  	}
   187  
   188  	for name, tc := range tests {
   189  		t.Run(name, func(t *testing.T) {
   190  			got, err := massagePhoneNumber(tc.input)
   191  			if err != tc.err {
   192  				t.Fatalf("expected: %v, got: %v", tc.err, err)
   193  			}
   194  			if !reflect.DeepEqual(tc.want, got) {
   195  				t.Fatalf("expected: %v, got: %v", tc.want, got)
   196  			}
   197  		})
   198  	}
   199  }
   200  
   201  func TestValidateInteger(t *testing.T) {
   202  	tests := map[string]struct {
   203  		in  string
   204  		msg tea.Msg
   205  	}{
   206  		"1":    {in: "1", msg: successMsg{}},
   207  		"12":   {in: "12", msg: successMsg{}},
   208  		"1.4":  {in: "1.4", msg: errMsg{err: fmt.Errorf("Your answer '%s' not a valid integer", "1.4")}},
   209  		"12n":  {in: "12n", msg: errMsg{err: fmt.Errorf("Your answer '%s' not a valid integer", "12n")}},
   210  		"dsds": {in: "dsds", msg: errMsg{err: fmt.Errorf("Your answer '%s' not a valid integer", "dsds")}},
   211  	}
   212  
   213  	for name, tc := range tests {
   214  		t.Run(name, func(t *testing.T) {
   215  			q := getTestQueue(appTitle, "test")
   216  			cmd := validateInteger(tc.in, &q)
   217  
   218  			got := cmd()
   219  
   220  			switch tc.msg.(type) {
   221  			case successMsg:
   222  				if tc.msg != got {
   223  					t.Fatalf("%s - want: \n'%+v' \ngot: \n'%+v'", tc.in, tc.msg, got)
   224  				}
   225  			case errMsg:
   226  				gotE := got.(errMsg)
   227  				tcmsgE := tc.msg.(errMsg)
   228  
   229  				if tcmsgE.err.Error() != gotE.err.Error() {
   230  					t.Fatalf("want: \n'%+v' \ngot: \n'%+v'", tcmsgE.err.Error(), gotE.err.Error())
   231  				}
   232  
   233  			}
   234  		})
   235  	}
   236  }
   237  
   238  func TestValidateDomain(t *testing.T) {
   239  	tests := map[string]struct {
   240  		in  string
   241  		msg tea.Msg
   242  	}{
   243  		"example.com":  {in: "example.com", msg: errMsg{err: fmt.Errorf("validateDomain: error verifying domain: domain is not verified")}},
   244  		"example2.com": {in: "example2.com", msg: errMsg{err: fmt.Errorf("validateDomain: not owned by requestor: %%!w(<nil>)")}},
   245  		"example3.com": {in: "example3.com", msg: successMsg{}},
   246  		"example4.com": {in: "example4.com", msg: successMsg{}},
   247  	}
   248  	for name, tc := range tests {
   249  		t.Run(name, func(t *testing.T) {
   250  			q := getTestQueue(appTitle, "test")
   251  			cmd := validateDomain(tc.in, &q)
   252  
   253  			got := cmd()
   254  
   255  			switch tc.msg.(type) {
   256  			case successMsg:
   257  				if tc.msg != got {
   258  					t.Fatalf("%s - want: \n'%+v' \ngot: \n'%+v'", tc.in, tc.msg, got)
   259  				}
   260  			case errMsg:
   261  				gotE := got.(errMsg)
   262  				tcmsgE := tc.msg.(errMsg)
   263  
   264  				if tcmsgE.err.Error() != gotE.err.Error() {
   265  					t.Fatalf("want: \n'%+v' \ngot: \n'%+v'", tcmsgE.err.Error(), gotE.err.Error())
   266  				}
   267  
   268  			}
   269  		})
   270  	}
   271  }
   272  
   273  func TestRegisterDomain(t *testing.T) {
   274  	tests := map[string]struct {
   275  		in   string
   276  		msg  tea.Msg
   277  		info *domainspb.RegisterParameters
   278  	}{
   279  		"example.com": {
   280  			in:  "y",
   281  			msg: errMsg{err: fmt.Errorf("registerDomain: error registering domain: domain is already owned. This should have been caught")},
   282  			info: &domainspb.RegisterParameters{
   283  				DomainName: "example.com",
   284  			},
   285  		},
   286  		"example2.com": {
   287  			in:  "y",
   288  			msg: errMsg{err: fmt.Errorf("registerDomain: error registering domain: domain is already owned. This should have been caught")},
   289  			info: &domainspb.RegisterParameters{
   290  				DomainName: "example2.com",
   291  			},
   292  		},
   293  		"example3.com": {
   294  			in:  "y",
   295  			msg: errMsg{err: fmt.Errorf("registerDomain: error registering domain: domain is cursed and cannot be obtained by mortals")},
   296  			info: &domainspb.RegisterParameters{
   297  				DomainName: "example3.com",
   298  			},
   299  		},
   300  
   301  		"example4.com": {
   302  			in:  "y",
   303  			msg: successMsg{},
   304  			info: &domainspb.RegisterParameters{
   305  				DomainName: "example4.com",
   306  			},
   307  		},
   308  		"example4.com_no": {
   309  			in:  "n",
   310  			msg: errMsg{err: fmt.Errorf("did not consent to being charged")},
   311  			info: &domainspb.RegisterParameters{
   312  				DomainName: "example4.com",
   313  			},
   314  		},
   315  		"example4.com_garbage": {
   316  			in:  "dasdasdasdsdas",
   317  			msg: errMsg{err: fmt.Errorf("did not consent to being charged")},
   318  			info: &domainspb.RegisterParameters{
   319  				DomainName: "example4.com",
   320  			},
   321  		},
   322  	}
   323  	for name, tc := range tests {
   324  		t.Run(name, func(t *testing.T) {
   325  			q := getTestQueue(appTitle, "test")
   326  			q.Save("domainInfo", tc.info)
   327  			q.stack.AddSetting("domain_consent", "y")
   328  			cmd := registerDomain(tc.in, &q)
   329  
   330  			got := cmd()
   331  
   332  			switch tc.msg.(type) {
   333  			case successMsg:
   334  				if tc.msg != got {
   335  					t.Fatalf("%s - want: \n'%+v' \ngot: \n'%+v'", tc.in, tc.msg, got)
   336  				}
   337  			case errMsg:
   338  				gotE := got.(errMsg)
   339  				tcmsgE := tc.msg.(errMsg)
   340  
   341  				if tcmsgE.err.Error() != gotE.err.Error() {
   342  					t.Fatalf("want: \n'%+v' \ngot: \n'%+v'", tcmsgE.err.Error(), gotE.err.Error())
   343  				}
   344  
   345  			}
   346  		})
   347  	}
   348  }
   349  
   350  func TestAttachBilling(t *testing.T) {
   351  	tests := map[string]struct {
   352  		in       string
   353  		want     tea.Msg
   354  		setError bool
   355  		err      error
   356  	}{
   357  		"basic": {
   358  			in:   "000000-000000-000000",
   359  			want: successMsg{},
   360  		},
   361  		"fail": {
   362  			in:       "000000-000000-000000",
   363  			want:     errMsg{err: fmt.Errorf("attachBilling: could not attach billing to project: %w", errForced)},
   364  			setError: true,
   365  		},
   366  	}
   367  
   368  	for name, tc := range tests {
   369  		t.Run(name, func(t *testing.T) {
   370  			q := getTestQueue(appTitle, "test")
   371  
   372  			if tc.setError {
   373  				errMock := GetMock(0)
   374  				errMock.forceErr = true
   375  				q.client = errMock
   376  			}
   377  
   378  			cmd := attachBilling(tc.in, &q)
   379  			got := cmd()
   380  
   381  			if !reflect.DeepEqual(tc.want, got) {
   382  				t.Fatalf("expected: %+v, got: %+v", tc.want, got)
   383  			}
   384  		})
   385  	}
   386  }
   387  
   388  func TestCreateProject(t *testing.T) {
   389  	tests := map[string]struct {
   390  		in  string
   391  		msg tea.Msg
   392  	}{
   393  		"ds-tester-deploystack": {in: "ds-tester-deploystack", msg: errMsg{err: fmt.Errorf("createProject: could not create project: project_id already exists")}},
   394  		"1234":                  {in: "1234", msg: errMsg{err: fmt.Errorf("createProject: could not create project: project_id contains too many characters, limit 30")}},
   395  		"sa1234122132132143145315246754736573568765": {in: "1234", msg: errMsg{err: fmt.Errorf("createProject: could not create project: project_id contains too many characters, limit 30")}},
   396  		"tp-never-used": {in: "tp-never-used", msg: successMsg{}},
   397  	}
   398  	for name, tc := range tests {
   399  		t.Run(name, func(t *testing.T) {
   400  			q := getTestQueue(appTitle, "test")
   401  			cmd := createProject(tc.in, &q)
   402  
   403  			got := cmd()
   404  
   405  			switch tc.msg.(type) {
   406  			case successMsg:
   407  				if tc.msg != got {
   408  					t.Fatalf("%s - want: \n'%+v' \ngot: \n'%+v'", tc.in, tc.msg, got)
   409  				}
   410  			case errMsg:
   411  				gotE := got.(errMsg)
   412  				tcmsgE := tc.msg.(errMsg)
   413  
   414  				if tcmsgE.err.Error() != gotE.err.Error() {
   415  					t.Fatalf("want: \n'%+v' \ngot: \n'%+v'", tcmsgE.err.Error(), gotE.err.Error())
   416  				}
   417  
   418  			}
   419  		})
   420  	}
   421  }
   422  
   423  func TestValidateGCEDefault(t *testing.T) {
   424  	tests := map[string]struct {
   425  		in       string
   426  		msg      tea.Msg
   427  		lenItems int
   428  	}{
   429  		"donotdefault": {in: "n", msg: successMsg{}, lenItems: 12},
   430  		"default":      {in: "y", msg: successMsg{}, lenItems: 1},
   431  	}
   432  	for name, tc := range tests {
   433  		t.Run(name, func(t *testing.T) {
   434  			q := getTestQueue(appTitle, "test")
   435  			newGCEInstance(&q)
   436  
   437  			cmd := validateGCEDefault(tc.in, &q)
   438  
   439  			got := cmd()
   440  
   441  			switch tc.msg.(type) {
   442  			case successMsg:
   443  				if tc.msg != got {
   444  					t.Fatalf("%s - want: \n'%+v' \ngot: \n'%+v'", tc.in, tc.msg, got)
   445  				}
   446  			case errMsg:
   447  				gotE := got.(errMsg)
   448  				tcmsgE := tc.msg.(errMsg)
   449  
   450  				if tcmsgE.err.Error() != gotE.err.Error() {
   451  					t.Fatalf("want: \n'%+v' \ngot: \n'%+v'", tcmsgE.err.Error(), gotE.err.Error())
   452  				}
   453  			}
   454  
   455  			if tc.lenItems != len(q.models) {
   456  				for _, v := range q.models {
   457  					t.Logf("%s %s", v.getKey(), "")
   458  				}
   459  
   460  				t.Fatalf("number of models want: '%d' got: '%d'", tc.lenItems, len(q.models))
   461  			}
   462  		})
   463  	}
   464  }
   465  
   466  func TestValidateGCEConfiguration(t *testing.T) {
   467  	tests := map[string]struct {
   468  		in    string
   469  		msg   tea.Msg
   470  		value string
   471  	}{
   472  		// "nowebserver":  {in: "n", msg: successMsg{unset: true}, value: ""},
   473  		"yeswebserver": {
   474  			in:    "y",
   475  			msg:   successMsg{unset: true},
   476  			value: gcloud.HTTPServerTags,
   477  		},
   478  	}
   479  	for name, tc := range tests {
   480  		t.Run(name, func(t *testing.T) {
   481  			q := getTestQueue(appTitle, "test")
   482  
   483  			q.stack.AddSetting("instance-webserver", tc.in)
   484  			q.stack.AddSetting("gce-use-defaults", "n")
   485  			q.stack.AddSetting("instance-webserver", "n")
   486  			q.stack.AddSetting("instance-image-project", "n")
   487  			q.stack.AddSetting("instance-machine-type-family", "n")
   488  			q.stack.AddSetting("instance-image-family", "n")
   489  
   490  			cmd := validateGCEConfiguration(tc.in, &q)
   491  
   492  			got := cmd()
   493  
   494  			switch tc.msg.(type) {
   495  			case successMsg:
   496  				if tc.msg != got {
   497  					t.Fatalf("%s - want: \n'%+v' \ngot: \n'%+v'", tc.in, tc.msg, got)
   498  				}
   499  			case errMsg:
   500  				gotE := got.(errMsg)
   501  				tcmsgE := tc.msg.(errMsg)
   502  
   503  				if tcmsgE.err.Error() != gotE.err.Error() {
   504  					t.Fatalf("want: \n'%+v' \ngot: \n'%+v'", tcmsgE.err.Error(), gotE.err.Error())
   505  				}
   506  			}
   507  
   508  			if tc.value != q.stack.GetSetting("instance-tags") {
   509  				t.Fatalf("tags want: '%s' got: '%s'", tc.value, q.stack.GetSetting("instance-tags"))
   510  			}
   511  		})
   512  	}
   513  }
   514  
   515  func TestStackSelection(t *testing.T) {
   516  	tests := map[string]struct {
   517  		input string
   518  		want  string
   519  	}{
   520  		"Basic": {"examplePath", "examplePath"},
   521  	}
   522  
   523  	for name, tc := range tests {
   524  		t.Run(name, func(t *testing.T) {
   525  			q := getTestQueue(appTitle, "test")
   526  
   527  			cmd := handleStackSelection(tc.input, &q)
   528  			cmd()
   529  
   530  			got := q.Get("stack").(string)
   531  
   532  			if !reflect.DeepEqual(tc.want, got) {
   533  				t.Fatalf("expected: %v, got: %v", tc.want, got)
   534  			}
   535  		})
   536  	}
   537  }
   538  
   539  func TestHandleProjectNumbe(t *testing.T) {
   540  	tests := map[string]struct {
   541  		forceErr bool
   542  		want     interface{}
   543  	}{
   544  		"noerror": {
   545  			forceErr: false,
   546  			want:     nil,
   547  		},
   548  		"error": {
   549  			forceErr: true,
   550  			want:     errMsg{err: errForced},
   551  		},
   552  	}
   553  
   554  	for name, tc := range tests {
   555  		t.Run(name, func(t *testing.T) {
   556  			q := getTestQueue(appTitle, "test")
   557  			q.stack.Config.ProjectNumber = true
   558  			m := GetMock(0)
   559  			m.forceErr = tc.forceErr
   560  			q.client = m
   561  			got := handleProjectNumber("test", &q)
   562  
   563  			assert.Equal(t, tc.want, got)
   564  		})
   565  	}
   566  }