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
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
34 connectC <- true
35
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
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
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
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
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
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
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
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
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
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
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
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
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
436 assert.Len(t, em.commandOptions, 1)
437 })
438 }
439 }
440
441 func TestReturnOnEmptyMessage(t *testing.T) {
442
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
453 go em.display(ctx)
454
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
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
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
590 go em.handleExitEvents(ctx)
591 assert.True(t, <-timeoutChan)
592 }
593
594 func TestSessionExitChecker(t *testing.T) {
595
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