...

Source file src/edge-infra.dev/pkg/sds/emergencyaccess/emulator/emulator_test.go

Documentation: edge-infra.dev/pkg/sds/emergencyaccess/emulator

     1  package emulator
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"strings"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/stretchr/testify/assert"
    12  
    13  	"edge-infra.dev/pkg/sds/emergencyaccess/msgdata"
    14  )
    15  
    16  // Mock CLIService
    17  var (
    18  	connectC = make(chan bool)
    19  )
    20  
    21  type cliService struct {
    22  	topicTemplate       string
    23  	subsciptionTemplate string
    24  	sentMessage         string
    25  	idleTime            time.Time
    26  	darkmode            bool
    27  
    28  	enablePerSessionSubscription *bool
    29  	CLIService
    30  }
    31  
    32  func (cls cliService) Connect(context.Context, string, string, string, string) error {
    33  	// set the test value in our test channel
    34  	connectC <- true
    35  	// pausing the thread here to stop session prompt being called.
    36  	time.Sleep(10 * time.Second)
    37  	return nil
    38  }
    39  
    40  func (cls *cliService) Send(command string) (string, error) {
    41  	if command == "a bad command" {
    42  		return "", fmt.Errorf("a bad command was sent")
    43  	}
    44  	cls.sentMessage = command
    45  	return "abcd", nil
    46  }
    47  
    48  func (cls cliService) End() error { return nil }
    49  
    50  func (cls *cliService) SetTopicTemplate(topicTemplate string) {
    51  	cls.topicTemplate = topicTemplate
    52  }
    53  
    54  func (cls *cliService) SetSubscriptionTemplate(subscriptionTemplate string) {
    55  	cls.subsciptionTemplate = subscriptionTemplate
    56  }
    57  
    58  func (cls cliService) GetDisplayChannel() <-chan msgdata.CommandResponse {
    59  	return make(<-chan msgdata.CommandResponse)
    60  }
    61  
    62  func (cls cliService) GetErrorChannel() <-chan error {
    63  	return make(<-chan error)
    64  }
    65  
    66  func (cls cliService) GetSessionContext() context.Context {
    67  	return context.Background()
    68  }
    69  
    70  func (cls cliService) RetrieveIdentity(_ context.Context) error {
    71  	return nil
    72  }
    73  
    74  func (cls cliService) UserID() string {
    75  	return ""
    76  }
    77  
    78  func createTempFile(t *testing.T) (filepath string) {
    79  	d := t.TempDir()
    80  	f, err := os.CreateTemp(d, "")
    81  	assert.NoError(t, err)
    82  	return f.Name()
    83  }
    84  
    85  func (cls cliService) IdleTime() time.Duration { return time.Since(cls.idleTime) }
    86  func (cls *cliService) SetDarkmode(val bool) {
    87  	cls.darkmode = val
    88  }
    89  func (cls cliService) Darkmode() bool {
    90  	return cls.darkmode
    91  }
    92  
    93  func (cls *cliService) EnablePerSessionSubscription() {
    94  	b := true
    95  	cls.enablePerSessionSubscription = &b
    96  }
    97  
    98  func (cls *cliService) DisablePerSessionSubscription() {
    99  	b := false
   100  	cls.enablePerSessionSubscription = &b
   101  }
   102  
   103  func TestSuccessParseConnectInput(t *testing.T) {
   104  	cases := map[string]string{
   105  		"Standard prompt":            "connect sessionID bannerID storeID terminalID",
   106  		"Extra space in prompt":      "connect sessionID bannerID storeID  terminalID",
   107  		"Trailing newline in prompt": "connect sessionID bannerID storeID terminalID\n",
   108  	}
   109  
   110  	expVal := connectionData{
   111  		projectID:  "sessionID",
   112  		bannerID:   "bannerID",
   113  		storeID:    "storeID",
   114  		terminalID: "terminalID",
   115  	}
   116  	em := Emulator{}
   117  	for name, prompt := range cases {
   118  		t.Run(name, func(t *testing.T) {
   119  			res, err := em.parseConnectInput(prompt)
   120  			assert.NoError(t, err)
   121  			assert.Equal(t, res, expVal)
   122  		})
   123  	}
   124  }
   125  
   126  func TestFailParseConnectInputWithWorkspace(t *testing.T) {
   127  	type caseStruct struct {
   128  		name      string
   129  		input     string
   130  		workspace workspace
   131  		expErr    error
   132  	}
   133  
   134  	tests := []caseStruct{
   135  		{
   136  			name:      "No parameters",
   137  			input:     "connect",
   138  			workspace: workspace{},
   139  			expErr:    fmt.Errorf("wrong number of values given"),
   140  		},
   141  		{
   142  			name:      "No bannerID",
   143  			input:     "connect a-terminal-id",
   144  			workspace: workspace{},
   145  			// emulator shouldn't error when project ID is not set
   146  			expErr: fmt.Errorf("bannerID in workspace was not set"),
   147  		},
   148  		{
   149  			name:  "No projectID and two params",
   150  			input: "connect a-store-id a-terminal-id",
   151  			// emulator shouldn't error when project ID is not set
   152  			expErr: fmt.Errorf("bannerID in workspace was not set"),
   153  		},
   154  		{
   155  			name:  "No projectID and three params",
   156  			input: "connect a-banner-id a-store-id a-terminal-id",
   157  			// cliServiceItfc should return an error if project ID is required
   158  			expErr: nil,
   159  		},
   160  		{
   161  			name:      "No bannerID",
   162  			input:     "connect a-terminal-id",
   163  			workspace: workspace{ProjectID: "a-project-id"},
   164  			expErr:    fmt.Errorf("bannerID in workspace was not set"),
   165  		},
   166  		{
   167  			name:      "No bannerID and two params",
   168  			input:     "connect a-store-id a-terminal-id",
   169  			workspace: workspace{ProjectID: "a-project-id"},
   170  			expErr:    fmt.Errorf("bannerID in workspace was not set"),
   171  		},
   172  		{
   173  			name:      "No storeID",
   174  			input:     "connect a-terminal-id",
   175  			workspace: workspace{ProjectID: "a-project-id", BannerID: "a-banner-id"},
   176  			expErr:    fmt.Errorf("storeID in workspace was not set"),
   177  		},
   178  	}
   179  
   180  	for i, test := range tests {
   181  		em := Emulator{}
   182  		em.workspace = &tests[i].workspace
   183  		t.Run(test.name, func(t *testing.T) {
   184  			_, err := em.parseConnectInput(test.input)
   185  			assert.Equal(t, test.expErr, err)
   186  		})
   187  	}
   188  }
   189  
   190  func TestSuccessParseInputWithWorkspace(t *testing.T) {
   191  	type caseStruct struct {
   192  		name      string
   193  		input     string
   194  		workspace workspace
   195  	}
   196  	expOutput := connectionData{
   197  		projectID:  "a-project-ID",
   198  		bannerID:   "a-banner-ID",
   199  		storeID:    "a-store-ID",
   200  		terminalID: "a-terminal-ID",
   201  	}
   202  	tests := []caseStruct{
   203  		{
   204  			name:      "Connect with all params",
   205  			input:     "connect a-project-ID a-banner-ID a-store-ID a-terminal-ID",
   206  			workspace: workspace{},
   207  		},
   208  		{
   209  			name:      "Connect one param",
   210  			input:     "connect a-terminal-ID",
   211  			workspace: workspace{ProjectID: "a-project-ID", BannerID: "a-banner-ID", StoreID: "a-store-ID"},
   212  		},
   213  		{
   214  			name:      "Connect with two params",
   215  			input:     "connect a-store-ID a-terminal-ID",
   216  			workspace: workspace{ProjectID: "a-project-ID", BannerID: "a-banner-ID"},
   217  		},
   218  		{
   219  			name:      "Connect with three params",
   220  			input:     "connect a-banner-ID a-store-ID a-terminal-ID",
   221  			workspace: workspace{ProjectID: "a-project-ID"},
   222  		},
   223  	}
   224  	for i, test := range tests {
   225  		em := Emulator{}
   226  		em.workspace = &tests[i].workspace
   227  		t.Run(test.name, func(t *testing.T) {
   228  			res, err := em.parseConnectInput(test.input)
   229  			assert.Nil(t, err)
   230  			assert.Equal(t, res, expOutput)
   231  		})
   232  	}
   233  }
   234  
   235  func TestRcliConfig(t *testing.T) {
   236  	t.Parallel()
   237  
   238  	ttrue := true
   239  	ffalse := false
   240  
   241  	tests := map[string]struct {
   242  		command string
   243  		err     assert.ErrorAssertionFunc
   244  		expCls  cliService
   245  		expWs   workspace
   246  	}{
   247  		"Disable Session Subscription": {
   248  			command: "rcliconfig disablePerSessionSubscription",
   249  			err:     assert.NoError,
   250  			expCls: cliService{
   251  				enablePerSessionSubscription: &ffalse,
   252  			},
   253  			expWs: workspace{
   254  				DisablePerSessionSubscription: true,
   255  			},
   256  		},
   257  		"Enable Session Subscription": {
   258  			command: "rcliconfig enablePerSessionSubscription",
   259  			err:     assert.NoError,
   260  			expCls: cliService{
   261  				enablePerSessionSubscription: &ttrue,
   262  			},
   263  			expWs: workspace{
   264  				DisablePerSessionSubscription: false,
   265  			},
   266  		},
   267  		"Successful topicTemplate": {
   268  			command: "rcliconfig topicTemplate a-template",
   269  			err:     assert.NoError,
   270  			expCls: cliService{
   271  				topicTemplate: "a-template",
   272  			},
   273  		},
   274  		"Successful subsciptionTemplate": {
   275  			command: "rcliconfig subscriptionTemplate a-template",
   276  			err:     assert.NoError,
   277  			expCls: cliService{
   278  				subsciptionTemplate: "a-template",
   279  			},
   280  		},
   281  		"subsciptionTemplate too few argument": {
   282  			command: "rcliconfig subscriptionTemplate",
   283  			err:     EqualError("Unexpected number of arguments for subcommand subscriptionTemplate"),
   284  		},
   285  		"subsciptionTemplate too many arguments": {
   286  			command: "rcliconfig subscriptionTemplate a b",
   287  			err:     EqualError("Unexpected number of arguments for subcommand subscriptionTemplate"),
   288  		},
   289  		"unknown command": {
   290  			command: "rcliconfig not-a-command another-parameter",
   291  			err:     EqualError(ErrorRCLIUnknownSubcmd.Error()),
   292  		},
   293  	}
   294  
   295  	for name, tc := range tests {
   296  		tc := tc
   297  		t.Run(name, func(t *testing.T) {
   298  			t.Parallel()
   299  
   300  			ws := workspace{}
   301  			mcls := cliService{}
   302  			ctx := context.Background()
   303  			config := NewConfig(ctx, t.TempDir())
   304  			em := NewEmulator(ctx, &mcls, config)
   305  			em.workspace = &ws
   306  
   307  			err := em.rcliconfig(tc.command)
   308  			tc.err(t, err)
   309  
   310  			assert.Equal(t, tc.expCls, mcls)
   311  			assert.Equal(t, tc.expWs, ws)
   312  		})
   313  	}
   314  }
   315  
   316  func TestSuccessConnectionFromExecutor(t *testing.T) {
   317  	// mocking
   318  	mcls := cliService{}
   319  	ctx := context.Background()
   320  	config := NewConfig(ctx, t.TempDir())
   321  	em := NewEmulator(ctx, &mcls, config)
   322  	em.runCtx = ctx
   323  
   324  	filepath := createTempFile(t)
   325  	ch, err := newCommandHistory(filepath)
   326  	assert.NoError(t, err)
   327  	em.connectHistory = ch
   328  
   329  	// test
   330  	go func() {
   331  		em.connectPromptExecutor("connect a b c d")
   332  	}()
   333  	val := <-connectC
   334  	assert.True(t, val)
   335  }
   336  
   337  func TestSuccessDarkConnectionFromExecutor(t *testing.T) {
   338  	// mocking
   339  	mcls := cliService{}
   340  	ctx := context.Background()
   341  	config := NewConfig(ctx, t.TempDir())
   342  	em := NewEmulator(ctx, &mcls, config)
   343  	em.runCtx = ctx
   344  
   345  	filepath := createTempFile(t)
   346  	ch, err := newCommandHistory(filepath)
   347  	assert.NoError(t, err)
   348  	em.connectHistory = ch
   349  
   350  	// test
   351  	go func() {
   352  		em.connectPromptExecutor("dark connect a b c d")
   353  	}()
   354  	val := <-connectC
   355  	assert.True(t, val)
   356  	assert.True(t, mcls.darkmode)
   357  }
   358  
   359  func TestSuccessRcliConfFromExecutor(t *testing.T) {
   360  	// mocking
   361  	mcls := cliService{}
   362  	ctx := context.Background()
   363  	config := NewConfig(ctx, t.TempDir())
   364  	em := NewEmulator(ctx, &mcls, config)
   365  	em.runCtx = ctx
   366  
   367  	filepath := createTempFile(t)
   368  	ch, err := newCommandHistory(filepath)
   369  	assert.NoError(t, err)
   370  	em.connectHistory = ch
   371  
   372  	// test
   373  	em.connectPromptExecutor("rcliconfig subscriptionTemplate a-sub-template")
   374  	assert.Equal(t, mcls.subsciptionTemplate, "a-sub-template")
   375  	em.connectPromptExecutor("rcliconfig topicTemplate a-top-template")
   376  	assert.Equal(t, mcls.topicTemplate, "a-top-template")
   377  }
   378  
   379  func TestSessionPromptExecutor(t *testing.T) {
   380  	// mocking
   381  	mcls := cliService{}
   382  	ctx := context.Background()
   383  	config := NewConfig(ctx, t.TempDir())
   384  	em := NewEmulator(ctx, &mcls, config)
   385  
   386  	filepath := createTempFile(t)
   387  	ch, err := newCommandHistory(filepath)
   388  	assert.NoError(t, err)
   389  	em.sessionHistory = ch
   390  
   391  	// run display routine to stop executor from blocking
   392  	go em.display(ctx)
   393  
   394  	type caseStruct struct {
   395  		name           string
   396  		command        string
   397  		expectedOutput string
   398  	}
   399  	tests := []caseStruct{
   400  		{
   401  			name:           "Send",
   402  			command:        "a command",
   403  			expectedOutput: "a command",
   404  		},
   405  		{
   406  			name:           "Exit with q",
   407  			command:        "q",
   408  			expectedOutput: "",
   409  		},
   410  		{
   411  			name:           "Exit with exit",
   412  			command:        "exit",
   413  			expectedOutput: "",
   414  		},
   415  		{
   416  			name:           "Exit with end",
   417  			command:        "end",
   418  			expectedOutput: "",
   419  		},
   420  		{
   421  			name:           "Help",
   422  			command:        "help",
   423  			expectedOutput: "",
   424  		},
   425  	}
   426  	//tests
   427  	for _, test := range tests {
   428  		t.Run(test.name, func(t *testing.T) {
   429  			em.sessionPromptExecutor(test.command)
   430  			assert.Nil(t, err)
   431  			assert.Equal(t, test.expectedOutput, mcls.sentMessage)
   432  			mcls.sentMessage = ""
   433  			em.unPauseSessionPrompt()
   434  
   435  			// TODO: Confirm correct values and keys?
   436  			assert.Len(t, em.commandOptions, 1)
   437  		})
   438  	}
   439  }
   440  
   441  func TestReturnOnEmptyMessage(t *testing.T) {
   442  	// mocking
   443  	mcls := cliService{}
   444  	ctx := context.Background()
   445  	config := NewConfig(ctx, t.TempDir())
   446  	em := NewEmulator(ctx, &mcls, config)
   447  
   448  	filepath := createTempFile(t)
   449  	ch, err := newCommandHistory(filepath)
   450  	assert.NoError(t, err)
   451  	em.sessionHistory = ch
   452  	// run display routine to stop executor from blocking
   453  	go em.display(ctx)
   454  	// test
   455  	em.sessionPromptExecutor("")
   456  	assert.Empty(t, mcls.sentMessage)
   457  }
   458  
   459  func TestExitChecker(t *testing.T) {
   460  	cases := map[string]struct {
   461  		in        string
   462  		breakline bool
   463  		exp       bool
   464  	}{
   465  		"Exit with q": {
   466  			"q",
   467  			true,
   468  			true,
   469  		},
   470  		"Exit with end": {
   471  			"end",
   472  			true,
   473  			true,
   474  		},
   475  		"Exit with exit": {
   476  			"exit",
   477  			true,
   478  			true,
   479  		},
   480  		"Don't exit with q": {
   481  			"q",
   482  			false,
   483  			false,
   484  		},
   485  		"Don't exit with end": {
   486  			"end",
   487  			false,
   488  			false,
   489  		},
   490  		"Don't exit with exit": {
   491  			"exit",
   492  			false,
   493  			false,
   494  		},
   495  		"Don't exit 1": {
   496  			"leave",
   497  			true,
   498  			false,
   499  		},
   500  		"Don't exit 2": {
   501  			"leave",
   502  			false,
   503  			false,
   504  		},
   505  	}
   506  
   507  	for name, tc := range cases {
   508  		t.Run(name, func(t *testing.T) {
   509  			assert.Equal(t, tc.exp, exitChecker(tc.in, tc.breakline))
   510  		})
   511  	}
   512  }
   513  
   514  // CommandHistory tests
   515  
   516  func TestNewCommandHistory(t *testing.T) {
   517  	filepath := createTempFile(t)
   518  
   519  	actual, err := newCommandHistory(filepath)
   520  	assert.NoError(t, err)
   521  	assert.Equal(t, filepath, actual.file.Name())
   522  	assert.NotNil(t, actual.history)
   523  }
   524  
   525  func TestReadFromFile(t *testing.T) {
   526  	d := t.TempDir()
   527  	f, err := os.CreateTemp(d, "")
   528  	assert.NoError(t, err)
   529  	filepath := f.Name()
   530  
   531  	expected := []string{"good", "day", "world"}
   532  	for _, s := range expected {
   533  		_, err = f.WriteString(s + "\n")
   534  		assert.NoError(t, err)
   535  	}
   536  
   537  	ch, err := newCommandHistory(filepath)
   538  	assert.NoError(t, err)
   539  	err = ch.readFromFile()
   540  	assert.NoError(t, err)
   541  
   542  	assert.Equal(t, expected, ch.history)
   543  }
   544  
   545  func TestUpdateHistory(t *testing.T) {
   546  	filepath := createTempFile(t)
   547  
   548  	ch, err := newCommandHistory(filepath)
   549  	assert.NoError(t, err)
   550  
   551  	expected := []string{"good", "day", "world"}
   552  	for _, s := range expected {
   553  		err = ch.updateHistory(s, historyFileLimit)
   554  		assert.NoError(t, err)
   555  	}
   556  
   557  	data, err := os.ReadFile(filepath)
   558  	assert.NoError(t, err)
   559  	actual := strings.Fields(string(data))
   560  
   561  	assert.Equal(t, expected, actual)
   562  }
   563  
   564  func TestUpdateHistoryLimit(t *testing.T) {
   565  	filepath := createTempFile(t)
   566  
   567  	ch, err := newCommandHistory(filepath)
   568  	assert.NoError(t, err)
   569  
   570  	expected := []string{"day", "world"}
   571  	in := append([]string{"good"}, expected...)
   572  	for _, s := range in {
   573  		err = ch.updateHistory(s, 2)
   574  		assert.NoError(t, err)
   575  	}
   576  
   577  	assert.Equal(t, expected, ch.history)
   578  }
   579  
   580  func TestSessionTimeout(t *testing.T) {
   581  	// mocking
   582  	mcls := cliService{}
   583  	mcls.idleTime = time.Now()
   584  	ctx := context.Background()
   585  	os.Setenv("RCLI_SESSION_TIMEOUT", "100ms")
   586  	config := NewConfig(ctx, t.TempDir())
   587  	em := NewEmulator(ctx, &mcls, config)
   588  	time.Sleep(200 * time.Millisecond)
   589  	// test
   590  	go em.handleExitEvents(ctx)
   591  	assert.True(t, <-timeoutChan)
   592  }
   593  
   594  func TestSessionExitChecker(t *testing.T) {
   595  	// mocking
   596  	mcls := cliService{}
   597  	ctx := context.Background()
   598  	config := NewConfig(ctx, t.TempDir())
   599  	em := NewEmulator(ctx, &mcls, config)
   600  
   601  	cases := map[string]struct {
   602  		in        string
   603  		breakline bool
   604  		exp       bool
   605  	}{
   606  		"Exit with q": {
   607  			"q",
   608  			true,
   609  			true,
   610  		},
   611  		"Exit with end": {
   612  			"end",
   613  			true,
   614  			true,
   615  		},
   616  		"Exit with exit": {
   617  			"exit",
   618  			true,
   619  			true,
   620  		},
   621  		"Don't exit with q": {
   622  			"q",
   623  			false,
   624  			false,
   625  		},
   626  		"Don't exit with end": {
   627  			"end",
   628  			false,
   629  			false,
   630  		},
   631  		"Don't exit with exit": {
   632  			"exit",
   633  			false,
   634  			false,
   635  		},
   636  		"Don't exit 1": {
   637  			"leave",
   638  			true,
   639  			false,
   640  		},
   641  		"Don't exit 2": {
   642  			"leave",
   643  			false,
   644  			false,
   645  		},
   646  	}
   647  
   648  	for name, tc := range cases {
   649  		t.Run(name, func(t *testing.T) {
   650  			assert.Equal(t, tc.exp, em.sessionPromptExitHandler(tc.in, tc.breakline))
   651  		})
   652  	}
   653  }
   654  

View as plain text