]> git.r.bdr.sh - rbdr/mobius/blob - hotline/transaction_handlers_test.go
Backfill tests
[rbdr/mobius] / hotline / transaction_handlers_test.go
1 package hotline
2
3 import (
4 "errors"
5 "fmt"
6 "github.com/stretchr/testify/assert"
7 "io/fs"
8 "math/rand"
9 "os"
10 "strings"
11 "testing"
12 )
13
14 func TestHandleSetChatSubject(t *testing.T) {
15 type args struct {
16 cc *ClientConn
17 t *Transaction
18 }
19 tests := []struct {
20 name string
21 args args
22 want []Transaction
23 wantErr bool
24 }{
25 {
26 name: "sends chat subject to private chat members",
27 args: args{
28 cc: &ClientConn{
29 UserName: []byte{0x00, 0x01},
30 Server: &Server{
31 PrivateChats: map[uint32]*PrivateChat{
32 uint32(1): {
33 Subject: "unset",
34 ClientConn: map[uint16]*ClientConn{
35 uint16(1): {
36 Account: &Account{
37 Access: &[]byte{255, 255, 255, 255, 255, 255, 255, 255},
38 },
39 ID: &[]byte{0, 1},
40 },
41 uint16(2): {
42 Account: &Account{
43 Access: &[]byte{255, 255, 255, 255, 255, 255, 255, 255},
44 },
45 ID: &[]byte{0, 2},
46 },
47 },
48 },
49 },
50 Clients: map[uint16]*ClientConn{
51 uint16(1): {
52 Account: &Account{
53 Access: &[]byte{255, 255, 255, 255, 255, 255, 255, 255},
54 },
55 ID: &[]byte{0, 1},
56 },
57 uint16(2): {
58 Account: &Account{
59 Access: &[]byte{255, 255, 255, 255, 255, 255, 255, 255},
60 },
61 ID: &[]byte{0, 2},
62 },
63 },
64 },
65 },
66 t: &Transaction{
67 Flags: 0x00,
68 IsReply: 0x00,
69 Type: []byte{0, 0x6a},
70 ID: []byte{0, 0, 0, 1},
71 ErrorCode: []byte{0, 0, 0, 0},
72 Fields: []Field{
73 NewField(fieldChatID, []byte{0, 0, 0, 1}),
74 NewField(fieldChatSubject, []byte("Test Subject")),
75 },
76 },
77 },
78 want: []Transaction{
79 {
80 clientID: &[]byte{0, 1},
81 Flags: 0x00,
82 IsReply: 0x00,
83 Type: []byte{0, 0x77},
84 ID: []byte{0x9a, 0xcb, 0x04, 0x42}, // Random ID from rand.Seed(1)
85 ErrorCode: []byte{0, 0, 0, 0},
86 Fields: []Field{
87 NewField(fieldChatID, []byte{0, 0, 0, 1}),
88 NewField(fieldChatSubject, []byte("Test Subject")),
89 },
90 },
91 {
92 clientID: &[]byte{0, 2},
93 Flags: 0x00,
94 IsReply: 0x00,
95 Type: []byte{0, 0x77},
96 ID: []byte{0xf0, 0xc5, 0x34, 0x1e}, // Random ID from rand.Seed(1)
97 ErrorCode: []byte{0, 0, 0, 0},
98 Fields: []Field{
99 NewField(fieldChatID, []byte{0, 0, 0, 1}),
100 NewField(fieldChatSubject, []byte("Test Subject")),
101 },
102 },
103 },
104 wantErr: false,
105 },
106 }
107 for _, tt := range tests {
108 rand.Seed(1) // reset seed between tests to make transaction IDs predictable
109
110 t.Run(tt.name, func(t *testing.T) {
111 got, err := HandleSetChatSubject(tt.args.cc, tt.args.t)
112 if (err != nil) != tt.wantErr {
113 t.Errorf("HandleSetChatSubject() error = %v, wantErr %v", err, tt.wantErr)
114 return
115 }
116 if !assert.Equal(t, tt.want, got) {
117 t.Errorf("HandleSetChatSubject() got = %v, want %v", got, tt.want)
118 }
119 })
120 }
121 }
122
123 func TestHandleLeaveChat(t *testing.T) {
124 type args struct {
125 cc *ClientConn
126 t *Transaction
127 }
128 tests := []struct {
129 name string
130 args args
131 want []Transaction
132 wantErr bool
133 }{
134 {
135 name: "returns expected transactions",
136 args: args{
137 cc: &ClientConn{
138 ID: &[]byte{0, 2},
139 Server: &Server{
140 PrivateChats: map[uint32]*PrivateChat{
141 uint32(1): {
142 ClientConn: map[uint16]*ClientConn{
143 uint16(1): {
144 Account: &Account{
145 Access: &[]byte{255, 255, 255, 255, 255, 255, 255, 255},
146 },
147 ID: &[]byte{0, 1},
148 },
149 uint16(2): {
150 Account: &Account{
151 Access: &[]byte{255, 255, 255, 255, 255, 255, 255, 255},
152 },
153 ID: &[]byte{0, 2},
154 },
155 },
156 },
157 },
158 Clients: map[uint16]*ClientConn{
159 uint16(1): {
160 Account: &Account{
161 Access: &[]byte{255, 255, 255, 255, 255, 255, 255, 255},
162 },
163 ID: &[]byte{0, 1},
164 },
165 uint16(2): {
166 Account: &Account{
167 Access: &[]byte{255, 255, 255, 255, 255, 255, 255, 255},
168 },
169 ID: &[]byte{0, 2},
170 },
171 },
172 },
173 },
174 t: NewTransaction(tranDeleteUser, nil, NewField(fieldChatID, []byte{0, 0, 0, 1})),
175 },
176 want: []Transaction{
177 {
178 clientID: &[]byte{0, 1},
179 Flags: 0x00,
180 IsReply: 0x00,
181 Type: []byte{0, 0x76},
182 ID: []byte{0x9a, 0xcb, 0x04, 0x42}, // Random ID from rand.Seed(1)
183 ErrorCode: []byte{0, 0, 0, 0},
184 Fields: []Field{
185 NewField(fieldChatID, []byte{0, 0, 0, 1}),
186 NewField(fieldUserID, []byte{0, 2}),
187 },
188 },
189 },
190 wantErr: false,
191 },
192 }
193 for _, tt := range tests {
194 rand.Seed(1)
195 t.Run(tt.name, func(t *testing.T) {
196 got, err := HandleLeaveChat(tt.args.cc, tt.args.t)
197 if (err != nil) != tt.wantErr {
198 t.Errorf("HandleLeaveChat() error = %v, wantErr %v", err, tt.wantErr)
199 return
200 }
201 if !assert.Equal(t, tt.want, got) {
202 t.Errorf("HandleLeaveChat() got = %v, want %v", got, tt.want)
203 }
204 })
205 }
206 }
207
208 func TestHandleGetUserNameList(t *testing.T) {
209 type args struct {
210 cc *ClientConn
211 t *Transaction
212 }
213 tests := []struct {
214 name string
215 args args
216 want []Transaction
217 wantErr bool
218 }{
219 {
220 name: "replies with userlist transaction",
221 args: args{
222 cc: &ClientConn{
223
224 ID: &[]byte{1, 1},
225 Server: &Server{
226 Clients: map[uint16]*ClientConn{
227 uint16(1): {
228 ID: &[]byte{0, 1},
229 Icon: &[]byte{0, 2},
230 Flags: &[]byte{0, 3},
231 UserName: []byte{0, 4},
232 Agreed: true,
233 },
234 uint16(2): {
235 ID: &[]byte{0, 2},
236 Icon: &[]byte{0, 2},
237 Flags: &[]byte{0, 3},
238 UserName: []byte{0, 4},
239 Agreed: true,
240 },
241 uint16(3): {
242 ID: &[]byte{0, 3},
243 Icon: &[]byte{0, 2},
244 Flags: &[]byte{0, 3},
245 UserName: []byte{0, 4},
246 Agreed: false,
247 },
248 },
249 },
250 },
251 t: &Transaction{
252 ID: []byte{0, 0, 0, 1},
253 Type: []byte{0, 1},
254 },
255 },
256 want: []Transaction{
257 {
258 clientID: &[]byte{1, 1},
259 Flags: 0x00,
260 IsReply: 0x01,
261 Type: []byte{0, 1},
262 ID: []byte{0, 0, 0, 1},
263 ErrorCode: []byte{0, 0, 0, 0},
264 Fields: []Field{
265 NewField(
266 fieldUsernameWithInfo,
267 []byte{00, 01, 00, 02, 00, 03, 00, 02, 00, 04},
268 ),
269 NewField(
270 fieldUsernameWithInfo,
271 []byte{00, 02, 00, 02, 00, 03, 00, 02, 00, 04},
272 ),
273 },
274 },
275 },
276 wantErr: false,
277 },
278 }
279 for _, tt := range tests {
280 t.Run(tt.name, func(t *testing.T) {
281 got, err := HandleGetUserNameList(tt.args.cc, tt.args.t)
282 if (err != nil) != tt.wantErr {
283 t.Errorf("HandleGetUserNameList() error = %v, wantErr %v", err, tt.wantErr)
284 return
285 }
286 assert.Equal(t, tt.want, got)
287 })
288 }
289 }
290
291 func TestHandleChatSend(t *testing.T) {
292 type args struct {
293 cc *ClientConn
294 t *Transaction
295 }
296 tests := []struct {
297 name string
298 args args
299 want []Transaction
300 wantErr bool
301 }{
302 {
303 name: "sends chat msg transaction to all clients",
304 args: args{
305 cc: &ClientConn{
306 Account: &Account{
307 Access: func() *[]byte {
308 var bits accessBitmap
309 bits.Set(accessSendChat)
310 access := bits[:]
311 return &access
312 }(),
313 },
314 UserName: []byte{0x00, 0x01},
315 Server: &Server{
316 Clients: map[uint16]*ClientConn{
317 uint16(1): {
318 Account: &Account{
319 Access: &[]byte{255, 255, 255, 255, 255, 255, 255, 255},
320 },
321 ID: &[]byte{0, 1},
322 },
323 uint16(2): {
324 Account: &Account{
325 Access: &[]byte{255, 255, 255, 255, 255, 255, 255, 255},
326 },
327 ID: &[]byte{0, 2},
328 },
329 },
330 },
331 },
332 t: &Transaction{
333 Fields: []Field{
334 NewField(fieldData, []byte("hai")),
335 },
336 },
337 },
338 want: []Transaction{
339 {
340 clientID: &[]byte{0, 1},
341 Flags: 0x00,
342 IsReply: 0x00,
343 Type: []byte{0, 0x6a},
344 ID: []byte{0x9a, 0xcb, 0x04, 0x42}, // Random ID from rand.Seed(1)
345 ErrorCode: []byte{0, 0, 0, 0},
346 Fields: []Field{
347 NewField(fieldData, []byte{0x0d, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x01, 0x3a, 0x20, 0x20, 0x68, 0x61, 0x69}),
348 },
349 },
350 {
351 clientID: &[]byte{0, 2},
352 Flags: 0x00,
353 IsReply: 0x00,
354 Type: []byte{0, 0x6a},
355 ID: []byte{0xf0, 0xc5, 0x34, 0x1e}, // Random ID from rand.Seed(1)
356 ErrorCode: []byte{0, 0, 0, 0},
357 Fields: []Field{
358 NewField(fieldData, []byte{0x0d, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x01, 0x3a, 0x20, 0x20, 0x68, 0x61, 0x69}),
359 },
360 },
361 },
362 wantErr: false,
363 },
364 {
365 name: "when user does not have required permission",
366 args: args{
367 cc: &ClientConn{
368 Account: &Account{
369 Access: func() *[]byte {
370 var bits accessBitmap
371 access := bits[:]
372 return &access
373 }(),
374 },
375 Server: &Server{
376 Accounts: map[string]*Account{},
377 },
378 },
379 t: NewTransaction(
380 tranChatSend, &[]byte{0, 1},
381 NewField(fieldData, []byte("hai")),
382 ),
383 },
384 want: []Transaction{
385 {
386 Flags: 0x00,
387 IsReply: 0x01,
388 Type: []byte{0, 0x00},
389 ID: []byte{0x9a, 0xcb, 0x04, 0x42},
390 ErrorCode: []byte{0, 0, 0, 1},
391 Fields: []Field{
392 NewField(fieldError, []byte("You are not allowed to participate in chat.")),
393 },
394 },
395 },
396 wantErr: false,
397 },
398 {
399 name: "sends chat msg as emote if fieldChatOptions is set",
400 args: args{
401 cc: &ClientConn{
402 Account: &Account{
403 Access: func() *[]byte {
404 var bits accessBitmap
405 bits.Set(accessSendChat)
406 access := bits[:]
407 return &access
408 }(),
409 },
410 UserName: []byte("Testy McTest"),
411 Server: &Server{
412 Clients: map[uint16]*ClientConn{
413 uint16(1): {
414 Account: &Account{
415 Access: &[]byte{255, 255, 255, 255, 255, 255, 255, 255},
416 },
417 ID: &[]byte{0, 1},
418 },
419 uint16(2): {
420 Account: &Account{
421 Access: &[]byte{255, 255, 255, 255, 255, 255, 255, 255},
422 },
423 ID: &[]byte{0, 2},
424 },
425 },
426 },
427 },
428 t: &Transaction{
429 Fields: []Field{
430 NewField(fieldData, []byte("performed action")),
431 NewField(fieldChatOptions, []byte{0x00, 0x01}),
432 },
433 },
434 },
435 want: []Transaction{
436 {
437 clientID: &[]byte{0, 1},
438 Flags: 0x00,
439 IsReply: 0x00,
440 Type: []byte{0, 0x6a},
441 ID: []byte{0x9a, 0xcb, 0x04, 0x42}, // Random ID from rand.Seed(1)
442 ErrorCode: []byte{0, 0, 0, 0},
443 Fields: []Field{
444 NewField(fieldData, []byte("\r*** Testy McTest performed action")),
445 },
446 },
447 {
448 clientID: &[]byte{0, 2},
449 Flags: 0x00,
450 IsReply: 0x00,
451 Type: []byte{0, 0x6a},
452 ID: []byte{0xf0, 0xc5, 0x34, 0x1e}, // Random ID from rand.Seed(1)
453 ErrorCode: []byte{0, 0, 0, 0},
454 Fields: []Field{
455 NewField(fieldData, []byte("\r*** Testy McTest performed action")),
456 },
457 },
458 },
459 wantErr: false,
460 },
461 {
462 name: "only sends chat msg to clients with accessReadChat permission",
463 args: args{
464 cc: &ClientConn{
465 Account: &Account{
466 Access: func() *[]byte {
467 var bits accessBitmap
468 bits.Set(accessSendChat)
469 access := bits[:]
470 return &access
471 }(),
472 },
473 UserName: []byte{0x00, 0x01},
474 Server: &Server{
475 Clients: map[uint16]*ClientConn{
476 uint16(1): {
477 Account: &Account{
478 Access: &[]byte{255, 255, 255, 255, 255, 255, 255, 255},
479 },
480 ID: &[]byte{0, 1},
481 },
482 uint16(2): {
483 Account: &Account{
484 Access: &[]byte{0, 0, 0, 0, 0, 0, 0, 0},
485 },
486 ID: &[]byte{0, 2},
487 },
488 },
489 },
490 },
491 t: &Transaction{
492 Fields: []Field{
493 NewField(fieldData, []byte("hai")),
494 },
495 },
496 },
497 want: []Transaction{
498 {
499 clientID: &[]byte{0, 1},
500 Flags: 0x00,
501 IsReply: 0x00,
502 Type: []byte{0, 0x6a},
503 ID: []byte{0x9a, 0xcb, 0x04, 0x42}, // Random ID from rand.Seed(1)
504 ErrorCode: []byte{0, 0, 0, 0},
505 Fields: []Field{
506 NewField(fieldData, []byte{0x0d, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x01, 0x3a, 0x20, 0x20, 0x68, 0x61, 0x69}),
507 },
508 },
509 },
510 wantErr: false,
511 },
512 }
513 for _, tt := range tests {
514 t.Run(tt.name, func(t *testing.T) {
515 got, err := HandleChatSend(tt.args.cc, tt.args.t)
516
517 if (err != nil) != tt.wantErr {
518 t.Errorf("HandleChatSend() error = %v, wantErr %v", err, tt.wantErr)
519 return
520 }
521 tranAssertEqual(t, tt.want, got)
522 })
523 }
524 }
525
526 func TestHandleGetFileInfo(t *testing.T) {
527 rand.Seed(1) // reset seed between tests to make transaction IDs predictable
528
529 type args struct {
530 cc *ClientConn
531 t *Transaction
532 }
533 tests := []struct {
534 name string
535 args args
536 wantRes []Transaction
537 wantErr bool
538 }{
539 {
540 name: "returns expected fields when a valid file is requested",
541 args: args{
542 cc: &ClientConn{
543 ID: &[]byte{0x00, 0x01},
544 Server: &Server{
545 Config: &Config{
546 FileRoot: func() string {
547 path, _ := os.Getwd()
548 return path + "/test/config/Files"
549 }(),
550 },
551 },
552 },
553 t: NewTransaction(
554 tranGetFileInfo, nil,
555 NewField(fieldFileName, []byte("testfile.txt")),
556 NewField(fieldFilePath, []byte{0x00, 0x00}),
557 ),
558 },
559 wantRes: []Transaction{
560 {
561 clientID: &[]byte{0, 1},
562 Flags: 0x00,
563 IsReply: 0x01,
564 Type: []byte{0, 0xce},
565 ID: []byte{0x9a, 0xcb, 0x04, 0x42}, // Random ID from rand.Seed(1)
566 ErrorCode: []byte{0, 0, 0, 0},
567 Fields: []Field{
568 NewField(fieldFileName, []byte("testfile.txt")),
569 NewField(fieldFileTypeString, []byte("TEXT")),
570 NewField(fieldFileCreatorString, []byte("ttxt")),
571 NewField(fieldFileComment, []byte{}),
572 NewField(fieldFileType, []byte("TEXT")),
573 NewField(fieldFileCreateDate, make([]byte, 8)),
574 NewField(fieldFileModifyDate, make([]byte, 8)),
575 NewField(fieldFileSize, []byte{0x0, 0x0, 0x0, 0x17}),
576 },
577 },
578 },
579 wantErr: false,
580 },
581 }
582 for _, tt := range tests {
583 t.Run(tt.name, func(t *testing.T) {
584 rand.Seed(1) // reset seed between tests to make transaction IDs predictable
585
586 gotRes, err := HandleGetFileInfo(tt.args.cc, tt.args.t)
587 if (err != nil) != tt.wantErr {
588 t.Errorf("HandleGetFileInfo() error = %v, wantErr %v", err, tt.wantErr)
589 return
590 }
591
592 // Clear the file timestamp fields to work around problems running the tests in multiple timezones
593 // TODO: revisit how to test this by mocking the stat calls
594 gotRes[0].Fields[5].Data = make([]byte, 8)
595 gotRes[0].Fields[6].Data = make([]byte, 8)
596 if !assert.Equal(t, tt.wantRes, gotRes) {
597 t.Errorf("HandleGetFileInfo() gotRes = %v, want %v", gotRes, tt.wantRes)
598 }
599 })
600 }
601 }
602
603 func TestHandleNewFolder(t *testing.T) {
604 type args struct {
605 cc *ClientConn
606 t *Transaction
607 }
608 tests := []struct {
609 setup func()
610 name string
611 args args
612 wantRes []Transaction
613 wantErr bool
614 }{
615 {
616 name: "when path is nested",
617 args: args{
618 cc: &ClientConn{
619 ID: &[]byte{0, 1},
620 Server: &Server{
621 Config: &Config{
622 FileRoot: "/Files/",
623 },
624 },
625 },
626 t: NewTransaction(
627 tranNewFolder, &[]byte{0, 1},
628 NewField(fieldFileName, []byte("testFolder")),
629 NewField(fieldFilePath, []byte{
630 0x00, 0x01,
631 0x00, 0x00,
632 0x03,
633 0x61, 0x61, 0x61,
634 }),
635 ),
636 },
637 setup: func() {
638 mfs := &MockFileStore{}
639 mfs.On("Mkdir", "/Files/aaa/testFolder", fs.FileMode(0777)).Return(nil)
640 mfs.On("Stat", "/Files/aaa/testFolder").Return(nil, os.ErrNotExist)
641 FS = mfs
642 },
643 wantRes: []Transaction{
644 {
645 clientID: &[]byte{0, 1},
646 Flags: 0x00,
647 IsReply: 0x01,
648 Type: []byte{0, 0xcd},
649 ID: []byte{0x9a, 0xcb, 0x04, 0x42}, // Random ID from rand.Seed(1)
650 ErrorCode: []byte{0, 0, 0, 0},
651 },
652 },
653 wantErr: false,
654 },
655 {
656 name: "when path is not nested",
657 args: args{
658 cc: &ClientConn{
659 ID: &[]byte{0, 1},
660 Server: &Server{
661 Config: &Config{
662 FileRoot: "/Files",
663 },
664 },
665 },
666 t: NewTransaction(
667 tranNewFolder, &[]byte{0, 1},
668 NewField(fieldFileName, []byte("testFolder")),
669 ),
670 },
671 setup: func() {
672 mfs := &MockFileStore{}
673 mfs.On("Mkdir", "/Files/testFolder", fs.FileMode(0777)).Return(nil)
674 mfs.On("Stat", "/Files/testFolder").Return(nil, os.ErrNotExist)
675 FS = mfs
676 },
677 wantRes: []Transaction{
678 {
679 clientID: &[]byte{0, 1},
680 Flags: 0x00,
681 IsReply: 0x01,
682 Type: []byte{0, 0xcd},
683 ID: []byte{0x9a, 0xcb, 0x04, 0x42}, // Random ID from rand.Seed(1)
684 ErrorCode: []byte{0, 0, 0, 0},
685 },
686 },
687 wantErr: false,
688 },
689 {
690 name: "when UnmarshalBinary returns an err",
691 args: args{
692 cc: &ClientConn{
693 ID: &[]byte{0, 1},
694 Server: &Server{
695 Config: &Config{
696 FileRoot: "/Files/",
697 },
698 },
699 },
700 t: NewTransaction(
701 tranNewFolder, &[]byte{0, 1},
702 NewField(fieldFileName, []byte("testFolder")),
703 NewField(fieldFilePath, []byte{
704 0x00,
705 }),
706 ),
707 },
708 setup: func() {
709 mfs := &MockFileStore{}
710 mfs.On("Mkdir", "/Files/aaa/testFolder", fs.FileMode(0777)).Return(nil)
711 mfs.On("Stat", "/Files/aaa/testFolder").Return(nil, os.ErrNotExist)
712 FS = mfs
713 },
714 wantRes: []Transaction{},
715 wantErr: true,
716 },
717 {
718 name: "fieldFileName does not allow directory traversal",
719 args: args{
720 cc: &ClientConn{
721 ID: &[]byte{0, 1},
722 Server: &Server{
723 Config: &Config{
724 FileRoot: "/Files/",
725 },
726 },
727 },
728 t: NewTransaction(
729 tranNewFolder, &[]byte{0, 1},
730 NewField(fieldFileName, []byte("../../testFolder")),
731 ),
732 },
733 setup: func() {
734 mfs := &MockFileStore{}
735 mfs.On("Mkdir", "/Files/testFolder", fs.FileMode(0777)).Return(nil)
736 mfs.On("Stat", "/Files/testFolder").Return(nil, os.ErrNotExist)
737 FS = mfs
738 },
739 wantRes: []Transaction{
740 {
741 clientID: &[]byte{0, 1},
742 Flags: 0x00,
743 IsReply: 0x01,
744 Type: []byte{0, 0xcd},
745 ID: []byte{0x9a, 0xcb, 0x04, 0x42}, // Random ID from rand.Seed(1)
746 ErrorCode: []byte{0, 0, 0, 0},
747 },
748 }, wantErr: false,
749 },
750 {
751 name: "fieldFilePath does not allow directory traversal",
752 args: args{
753 cc: &ClientConn{
754 ID: &[]byte{0, 1},
755 Server: &Server{
756 Config: &Config{
757 FileRoot: "/Files/",
758 },
759 },
760 },
761 t: NewTransaction(
762 tranNewFolder, &[]byte{0, 1},
763 NewField(fieldFileName, []byte("testFolder")),
764 NewField(fieldFilePath, []byte{
765 0x00, 0x02,
766 0x00, 0x00,
767 0x03,
768 0x2e, 0x2e, 0x2f,
769 0x00, 0x00,
770 0x03,
771 0x66, 0x6f, 0x6f,
772 }),
773 ),
774 },
775 setup: func() {
776 mfs := &MockFileStore{}
777 mfs.On("Mkdir", "/Files/foo/testFolder", fs.FileMode(0777)).Return(nil)
778 mfs.On("Stat", "/Files/foo/testFolder").Return(nil, os.ErrNotExist)
779 FS = mfs
780 },
781 wantRes: []Transaction{
782 {
783 clientID: &[]byte{0, 1},
784 Flags: 0x00,
785 IsReply: 0x01,
786 Type: []byte{0, 0xcd},
787 ID: []byte{0x9a, 0xcb, 0x04, 0x42}, // Random ID from rand.Seed(1)
788 ErrorCode: []byte{0, 0, 0, 0},
789 },
790 }, wantErr: false,
791 },
792 }
793 for _, tt := range tests {
794 t.Run(tt.name, func(t *testing.T) {
795 tt.setup()
796
797 gotRes, err := HandleNewFolder(tt.args.cc, tt.args.t)
798 if (err != nil) != tt.wantErr {
799 t.Errorf("HandleNewFolder() error = %v, wantErr %v", err, tt.wantErr)
800 return
801 }
802 if !tranAssertEqual(t, tt.wantRes, gotRes) {
803 t.Errorf("HandleNewFolder() gotRes = %v, want %v", gotRes, tt.wantRes)
804 }
805 })
806 }
807 }
808
809 func TestHandleUploadFile(t *testing.T) {
810 type args struct {
811 cc *ClientConn
812 t *Transaction
813 }
814 tests := []struct {
815 name string
816 args args
817 wantRes []Transaction
818 wantErr bool
819 }{
820 {
821 name: "when request is valid",
822 args: args{
823 cc: &ClientConn{
824 Server: &Server{
825 FileTransfers: map[uint32]*FileTransfer{},
826 },
827 Account: &Account{
828 Access: func() *[]byte {
829 var bits accessBitmap
830 bits.Set(accessUploadFile)
831 access := bits[:]
832 return &access
833 }(),
834 },
835 },
836 t: NewTransaction(
837 tranUploadFile, &[]byte{0, 1},
838 NewField(fieldFileName, []byte("testFile")),
839 NewField(fieldFilePath, []byte{
840 0x00, 0x01,
841 0x00, 0x00,
842 0x03,
843 0x2e, 0x2e, 0x2f,
844 }),
845 ),
846 },
847 wantRes: []Transaction{
848 {
849 Flags: 0x00,
850 IsReply: 0x01,
851 Type: []byte{0, 0xcb},
852 ID: []byte{0x9a, 0xcb, 0x04, 0x42},
853 ErrorCode: []byte{0, 0, 0, 0},
854 Fields: []Field{
855 NewField(fieldRefNum, []byte{0x52, 0xfd, 0xfc, 0x07}), // rand.Seed(1)
856 },
857 },
858 },
859 wantErr: false,
860 },
861 {
862 name: "when user does not have required access",
863 args: args{
864 cc: &ClientConn{
865 Account: &Account{
866 Access: func() *[]byte {
867 var bits accessBitmap
868 access := bits[:]
869 return &access
870 }(),
871 },
872 Server: &Server{
873 FileTransfers: map[uint32]*FileTransfer{},
874 },
875 },
876 t: NewTransaction(
877 tranUploadFile, &[]byte{0, 1},
878 NewField(fieldFileName, []byte("testFile")),
879 NewField(fieldFilePath, []byte{
880 0x00, 0x01,
881 0x00, 0x00,
882 0x03,
883 0x2e, 0x2e, 0x2f,
884 }),
885 ),
886 },
887 wantRes: []Transaction{
888 {
889 Flags: 0x00,
890 IsReply: 0x01,
891 Type: []byte{0, 0x00},
892 ID: []byte{0x9a, 0xcb, 0x04, 0x42},
893 ErrorCode: []byte{0, 0, 0, 1},
894 Fields: []Field{
895 NewField(fieldError, []byte("You are not allowed to upload files.")), // rand.Seed(1)
896 },
897 },
898 },
899 wantErr: false,
900 },
901 }
902 for _, tt := range tests {
903 t.Run(tt.name, func(t *testing.T) {
904 rand.Seed(1)
905 gotRes, err := HandleUploadFile(tt.args.cc, tt.args.t)
906 if (err != nil) != tt.wantErr {
907 t.Errorf("HandleUploadFile() error = %v, wantErr %v", err, tt.wantErr)
908 return
909 }
910
911 tranAssertEqual(t, tt.wantRes, gotRes)
912
913 })
914 }
915 }
916
917 func TestHandleMakeAlias(t *testing.T) {
918 type args struct {
919 cc *ClientConn
920 t *Transaction
921 }
922 tests := []struct {
923 name string
924 setup func()
925 args args
926 wantRes []Transaction
927 wantErr bool
928 }{
929 {
930 name: "with valid input and required permissions",
931 setup: func() {
932 mfs := &MockFileStore{}
933 path, _ := os.Getwd()
934 mfs.On(
935 "Symlink",
936 path+"/test/config/Files/foo/testFile",
937 path+"/test/config/Files/bar/testFile",
938 ).Return(nil)
939 FS = mfs
940 },
941 args: args{
942 cc: &ClientConn{
943 Account: &Account{
944 Access: func() *[]byte {
945 var bits accessBitmap
946 bits.Set(accessMakeAlias)
947 access := bits[:]
948 return &access
949 }(),
950 },
951 Server: &Server{
952 Config: &Config{
953 FileRoot: func() string {
954 path, _ := os.Getwd()
955 return path + "/test/config/Files"
956 }(),
957 },
958 Logger: NewTestLogger(),
959 },
960 },
961 t: NewTransaction(
962 tranMakeFileAlias, &[]byte{0, 1},
963 NewField(fieldFileName, []byte("testFile")),
964 NewField(fieldFilePath, EncodeFilePath(strings.Join([]string{"foo"}, "/"))),
965 NewField(fieldFileNewPath, EncodeFilePath(strings.Join([]string{"bar"}, "/"))),
966 ),
967 },
968 wantRes: []Transaction{
969 {
970 Flags: 0x00,
971 IsReply: 0x01,
972 Type: []byte{0, 0xd1},
973 ID: []byte{0x9a, 0xcb, 0x04, 0x42},
974 ErrorCode: []byte{0, 0, 0, 0},
975 Fields: []Field(nil),
976 },
977 },
978 wantErr: false,
979 },
980 {
981 name: "when symlink returns an error",
982 setup: func() {
983 mfs := &MockFileStore{}
984 path, _ := os.Getwd()
985 mfs.On(
986 "Symlink",
987 path+"/test/config/Files/foo/testFile",
988 path+"/test/config/Files/bar/testFile",
989 ).Return(errors.New("ohno"))
990 FS = mfs
991 },
992 args: args{
993 cc: &ClientConn{
994 Account: &Account{
995 Access: func() *[]byte {
996 var bits accessBitmap
997 bits.Set(accessMakeAlias)
998 access := bits[:]
999 return &access
1000 }(),
1001 },
1002 Server: &Server{
1003 Config: &Config{
1004 FileRoot: func() string {
1005 path, _ := os.Getwd()
1006 return path + "/test/config/Files"
1007 }(),
1008 },
1009 Logger: NewTestLogger(),
1010 },
1011 },
1012 t: NewTransaction(
1013 tranMakeFileAlias, &[]byte{0, 1},
1014 NewField(fieldFileName, []byte("testFile")),
1015 NewField(fieldFilePath, EncodeFilePath(strings.Join([]string{"foo"}, "/"))),
1016 NewField(fieldFileNewPath, EncodeFilePath(strings.Join([]string{"bar"}, "/"))),
1017 ),
1018 },
1019 wantRes: []Transaction{
1020 {
1021 Flags: 0x00,
1022 IsReply: 0x01,
1023 Type: []byte{0, 0x00},
1024 ID: []byte{0x9a, 0xcb, 0x04, 0x42},
1025 ErrorCode: []byte{0, 0, 0, 1},
1026 Fields: []Field{
1027 NewField(fieldError, []byte("Error creating alias")),
1028 },
1029 },
1030 },
1031 wantErr: false,
1032 },
1033 {
1034 name: "when user does not have required permission",
1035 setup: func() {},
1036 args: args{
1037 cc: &ClientConn{
1038 Account: &Account{
1039 Access: func() *[]byte {
1040 var bits accessBitmap
1041 access := bits[:]
1042 return &access
1043 }(),
1044 },
1045 Server: &Server{
1046 Config: &Config{
1047 FileRoot: func() string {
1048 path, _ := os.Getwd()
1049 return path + "/test/config/Files"
1050 }(),
1051 },
1052 },
1053 },
1054 t: NewTransaction(
1055 tranMakeFileAlias, &[]byte{0, 1},
1056 NewField(fieldFileName, []byte("testFile")),
1057 NewField(fieldFilePath, []byte{
1058 0x00, 0x01,
1059 0x00, 0x00,
1060 0x03,
1061 0x2e, 0x2e, 0x2e,
1062 }),
1063 NewField(fieldFileNewPath, []byte{
1064 0x00, 0x01,
1065 0x00, 0x00,
1066 0x03,
1067 0x2e, 0x2e, 0x2e,
1068 }),
1069 ),
1070 },
1071 wantRes: []Transaction{
1072 {
1073 Flags: 0x00,
1074 IsReply: 0x01,
1075 Type: []byte{0, 0x00},
1076 ID: []byte{0x9a, 0xcb, 0x04, 0x42},
1077 ErrorCode: []byte{0, 0, 0, 1},
1078 Fields: []Field{
1079 NewField(fieldError, []byte("You are not allowed to make aliases.")),
1080 },
1081 },
1082 },
1083 wantErr: false,
1084 },
1085 }
1086 for _, tt := range tests {
1087 t.Run(tt.name, func(t *testing.T) {
1088 tt.setup()
1089
1090 gotRes, err := HandleMakeAlias(tt.args.cc, tt.args.t)
1091 if (err != nil) != tt.wantErr {
1092 t.Errorf("HandleMakeAlias(%v, %v)", tt.args.cc, tt.args.t)
1093 return
1094 }
1095
1096 tranAssertEqual(t, tt.wantRes, gotRes)
1097 })
1098 }
1099 }
1100
1101 func TestHandleGetUser(t *testing.T) {
1102 type args struct {
1103 cc *ClientConn
1104 t *Transaction
1105 }
1106 tests := []struct {
1107 name string
1108 args args
1109 wantRes []Transaction
1110 wantErr assert.ErrorAssertionFunc
1111 }{
1112 {
1113 name: "when account is valid",
1114 args: args{
1115 cc: &ClientConn{
1116 Account: &Account{
1117 Access: func() *[]byte {
1118 var bits accessBitmap
1119 bits.Set(accessOpenUser)
1120 access := bits[:]
1121 return &access
1122 }(),
1123 },
1124 Server: &Server{
1125 Accounts: map[string]*Account{
1126 "guest": {
1127 Login: "guest",
1128 Name: "Guest",
1129 Password: "password",
1130 Access: &[]byte{1},
1131 },
1132 },
1133 },
1134 },
1135 t: NewTransaction(
1136 tranGetUser, &[]byte{0, 1},
1137 NewField(fieldUserLogin, []byte("guest")),
1138 ),
1139 },
1140 wantRes: []Transaction{
1141 {
1142 Flags: 0x00,
1143 IsReply: 0x01,
1144 Type: []byte{0x01, 0x60},
1145 ID: []byte{0x9a, 0xcb, 0x04, 0x42},
1146 ErrorCode: []byte{0, 0, 0, 0},
1147 Fields: []Field{
1148 NewField(fieldUserName, []byte("Guest")),
1149 NewField(fieldUserLogin, negateString([]byte("guest"))),
1150 NewField(fieldUserPassword, []byte("password")),
1151 NewField(fieldUserAccess, []byte{1}),
1152 },
1153 },
1154 },
1155 wantErr: assert.NoError,
1156 },
1157 {
1158 name: "when user does not have required permission",
1159 args: args{
1160 cc: &ClientConn{
1161 Account: &Account{
1162 Access: func() *[]byte {
1163 var bits accessBitmap
1164 access := bits[:]
1165 return &access
1166 }(),
1167 },
1168 Server: &Server{
1169 Accounts: map[string]*Account{},
1170 },
1171 },
1172 t: NewTransaction(
1173 tranGetUser, &[]byte{0, 1},
1174 NewField(fieldUserLogin, []byte("nonExistentUser")),
1175 ),
1176 },
1177 wantRes: []Transaction{
1178 {
1179 Flags: 0x00,
1180 IsReply: 0x01,
1181 Type: []byte{0, 0x00},
1182 ID: []byte{0x9a, 0xcb, 0x04, 0x42},
1183 ErrorCode: []byte{0, 0, 0, 1},
1184 Fields: []Field{
1185 NewField(fieldError, []byte("You are not allowed to view accounts.")),
1186 },
1187 },
1188 },
1189 wantErr: assert.NoError,
1190 },
1191 {
1192 name: "when account does not exist",
1193 args: args{
1194 cc: &ClientConn{
1195 Account: &Account{
1196 Access: func() *[]byte {
1197 var bits accessBitmap
1198 bits.Set(accessOpenUser)
1199 access := bits[:]
1200 return &access
1201 }(),
1202 },
1203 Server: &Server{
1204 Accounts: map[string]*Account{},
1205 },
1206 },
1207 t: NewTransaction(
1208 tranGetUser, &[]byte{0, 1},
1209 NewField(fieldUserLogin, []byte("nonExistentUser")),
1210 ),
1211 },
1212 wantRes: []Transaction{
1213 {
1214 Flags: 0x00,
1215 IsReply: 0x01,
1216 Type: []byte{0, 0x00},
1217 ID: []byte{0x9a, 0xcb, 0x04, 0x42},
1218 ErrorCode: []byte{0, 0, 0, 1},
1219 Fields: []Field{
1220 NewField(fieldError, []byte("Account does not exist.")),
1221 },
1222 },
1223 },
1224 wantErr: assert.NoError,
1225 },
1226 }
1227 for _, tt := range tests {
1228 t.Run(tt.name, func(t *testing.T) {
1229 gotRes, err := HandleGetUser(tt.args.cc, tt.args.t)
1230 if !tt.wantErr(t, err, fmt.Sprintf("HandleGetUser(%v, %v)", tt.args.cc, tt.args.t)) {
1231 return
1232 }
1233
1234 tranAssertEqual(t, tt.wantRes, gotRes)
1235 })
1236 }
1237 }
1238
1239 func TestHandleDeleteUser(t *testing.T) {
1240 type args struct {
1241 cc *ClientConn
1242 t *Transaction
1243 }
1244 tests := []struct {
1245 name string
1246 setup func()
1247 args args
1248 wantRes []Transaction
1249 wantErr assert.ErrorAssertionFunc
1250 }{
1251 {
1252 name: "when user exists",
1253 setup: func() {
1254 mfs := &MockFileStore{}
1255 mfs.On("Remove", "Users/testuser.yaml").Return(nil)
1256 FS = mfs
1257 },
1258 args: args{
1259 cc: &ClientConn{
1260 Account: &Account{
1261 Access: func() *[]byte {
1262 var bits accessBitmap
1263 bits.Set(accessDeleteUser)
1264 access := bits[:]
1265 return &access
1266 }(),
1267 },
1268 Server: &Server{
1269 Accounts: map[string]*Account{
1270 "testuser": {
1271 Login: "testuser",
1272 Name: "Testy McTest",
1273 Password: "password",
1274 Access: &[]byte{1},
1275 },
1276 },
1277 },
1278 },
1279 t: NewTransaction(
1280 tranDeleteUser, &[]byte{0, 1},
1281 NewField(fieldUserLogin, negateString([]byte("testuser"))),
1282 ),
1283 },
1284 wantRes: []Transaction{
1285 {
1286 Flags: 0x00,
1287 IsReply: 0x01,
1288 Type: []byte{0x1, 0x5f},
1289 ID: []byte{0x9a, 0xcb, 0x04, 0x42},
1290 ErrorCode: []byte{0, 0, 0, 0},
1291 Fields: []Field(nil),
1292 },
1293 },
1294 wantErr: assert.NoError,
1295 },
1296 {
1297 name: "when user does not have required permission",
1298 setup: func() {},
1299 args: args{
1300 cc: &ClientConn{
1301 Account: &Account{
1302 Access: func() *[]byte {
1303 var bits accessBitmap
1304 access := bits[:]
1305 return &access
1306 }(),
1307 },
1308 Server: &Server{
1309 Accounts: map[string]*Account{},
1310 },
1311 },
1312 t: NewTransaction(
1313 tranDeleteUser, &[]byte{0, 1},
1314 NewField(fieldUserLogin, negateString([]byte("testuser"))),
1315 ),
1316 },
1317 wantRes: []Transaction{
1318 {
1319 Flags: 0x00,
1320 IsReply: 0x01,
1321 Type: []byte{0, 0x00},
1322 ID: []byte{0x9a, 0xcb, 0x04, 0x42},
1323 ErrorCode: []byte{0, 0, 0, 1},
1324 Fields: []Field{
1325 NewField(fieldError, []byte("You are not allowed to delete accounts.")),
1326 },
1327 },
1328 },
1329 wantErr: assert.NoError,
1330 },
1331 }
1332 for _, tt := range tests {
1333 t.Run(tt.name, func(t *testing.T) {
1334 tt.setup()
1335 gotRes, err := HandleDeleteUser(tt.args.cc, tt.args.t)
1336 if !tt.wantErr(t, err, fmt.Sprintf("HandleDeleteUser(%v, %v)", tt.args.cc, tt.args.t)) {
1337 return
1338 }
1339
1340 tranAssertEqual(t, tt.wantRes, gotRes)
1341 })
1342 }
1343 }