]> git.r.bdr.sh - rbdr/mobius/blame - hotline/transaction_handlers_test.go
Implement reply quoting
[rbdr/mobius] / hotline / transaction_handlers_test.go
CommitLineData
6988a057
JH
1package hotline
2
3import (
decc2fbf 4 "errors"
9ebf276d 5 "fmt"
6988a057 6 "github.com/stretchr/testify/assert"
00d1ef67 7 "io/fs"
6988a057 8 "math/rand"
00d1ef67 9 "os"
decc2fbf 10 "strings"
6988a057
JH
11 "testing"
12)
13
14func 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{
72dd37f1 29 UserName: []byte{0x00, 0x01},
6988a057
JH
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
123func 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 },
5c34f875 174 t: NewTransaction(tranDeleteUser, nil, NewField(fieldChatID, []byte{0, 0, 0, 1})),
6988a057
JH
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
6988a057
JH
208func 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},
72dd37f1 231 UserName: []byte{0, 4},
bd1ce113 232 Agreed: true,
6988a057 233 },
c7e932c0
JH
234 uint16(2): {
235 ID: &[]byte{0, 2},
236 Icon: &[]byte{0, 2},
237 Flags: &[]byte{0, 3},
238 UserName: []byte{0, 4},
bd1ce113
JH
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,
c7e932c0 247 },
6988a057
JH
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 ),
c7e932c0
JH
269 NewField(
270 fieldUsernameWithInfo,
271 []byte{00, 02, 00, 02, 00, 03, 00, 02, 00, 04},
272 ),
6988a057
JH
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 }
bd1ce113 286 assert.Equal(t, tt.want, got)
6988a057
JH
287 })
288 }
289}
290
291func 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{
9ebf276d
JH
306 Account: &Account{
307 Access: func() *[]byte {
308 var bits accessBitmap
309 bits.Set(accessSendChat)
310 access := bits[:]
311 return &access
312 }(),
313 },
72dd37f1 314 UserName: []byte{0x00, 0x01},
6988a057
JH
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 },
9ebf276d
JH
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 },
72dd37f1
JH
398 {
399 name: "sends chat msg as emote if fieldChatOptions is set",
400 args: args{
401 cc: &ClientConn{
9ebf276d
JH
402 Account: &Account{
403 Access: func() *[]byte {
404 var bits accessBitmap
405 bits.Set(accessSendChat)
406 access := bits[:]
407 return &access
408 }(),
409 },
72dd37f1
JH
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 },
6988a057
JH
461 {
462 name: "only sends chat msg to clients with accessReadChat permission",
463 args: args{
464 cc: &ClientConn{
9ebf276d
JH
465 Account: &Account{
466 Access: func() *[]byte {
467 var bits accessBitmap
468 bits.Set(accessSendChat)
469 access := bits[:]
470 return &access
471 }(),
472 },
72dd37f1 473 UserName: []byte{0x00, 0x01},
6988a057
JH
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 {
6988a057
JH
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 }
9ebf276d 521 tranAssertEqual(t, tt.want, got)
6988a057
JH
522 })
523 }
524}
72dd37f1
JH
525
526func 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{
29f329ae
JH
546 FileRoot: func() string {
547 path, _ := os.Getwd()
548 return path + "/test/config/Files"
549 }(),
72dd37f1
JH
550 },
551 },
552 },
553 t: NewTransaction(
554 tranGetFileInfo, nil,
555 NewField(fieldFileName, []byte("testfile.txt")),
556 NewField(fieldFilePath, []byte{0x00, 0x00}),
72dd37f1
JH
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")),
2728d12b 570 NewField(fieldFileCreatorString, []byte("ttxt")),
5218c782 571 NewField(fieldFileComment, []byte{}),
72dd37f1 572 NewField(fieldFileType, []byte("TEXT")),
29f329ae
JH
573 NewField(fieldFileCreateDate, make([]byte, 8)),
574 NewField(fieldFileModifyDate, make([]byte, 8)),
72dd37f1
JH
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 }
29f329ae
JH
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)
00d1ef67 596 if !assert.Equal(t, tt.wantRes, gotRes) {
72dd37f1
JH
597 t.Errorf("HandleGetFileInfo() gotRes = %v, want %v", gotRes, tt.wantRes)
598 }
599 })
600 }
601}
00d1ef67
JH
602
603func 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() {
aebc4d36 638 mfs := &MockFileStore{}
00d1ef67
JH
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() {
aebc4d36 672 mfs := &MockFileStore{}
00d1ef67
JH
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() {
aebc4d36 709 mfs := &MockFileStore{}
00d1ef67
JH
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")),
00d1ef67
JH
731 ),
732 },
733 setup: func() {
aebc4d36 734 mfs := &MockFileStore{}
00d1ef67
JH
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 },
92a7e455 748 }, wantErr: false,
00d1ef67
JH
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() {
aebc4d36 776 mfs := &MockFileStore{}
00d1ef67
JH
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 },
92a7e455 790 }, wantErr: false,
00d1ef67
JH
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
92a7e455
JH
809func 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 {
7e2e07da 821 name: "when request is valid and user has Upload Anywhere permission",
92a7e455
JH
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)
7e2e07da 831 bits.Set(accessUploadAnywhere)
92a7e455
JH
832 access := bits[:]
833 return &access
834 }(),
835 },
836 },
837 t: NewTransaction(
838 tranUploadFile, &[]byte{0, 1},
839 NewField(fieldFileName, []byte("testFile")),
840 NewField(fieldFilePath, []byte{
841 0x00, 0x01,
842 0x00, 0x00,
843 0x03,
844 0x2e, 0x2e, 0x2f,
845 }),
846 ),
847 },
848 wantRes: []Transaction{
849 {
850 Flags: 0x00,
851 IsReply: 0x01,
852 Type: []byte{0, 0xcb},
853 ID: []byte{0x9a, 0xcb, 0x04, 0x42},
854 ErrorCode: []byte{0, 0, 0, 0},
855 Fields: []Field{
856 NewField(fieldRefNum, []byte{0x52, 0xfd, 0xfc, 0x07}), // rand.Seed(1)
857 },
858 },
859 },
860 wantErr: false,
861 },
862 {
863 name: "when user does not have required access",
864 args: args{
865 cc: &ClientConn{
866 Account: &Account{
867 Access: func() *[]byte {
868 var bits accessBitmap
869 access := bits[:]
870 return &access
871 }(),
872 },
873 Server: &Server{
874 FileTransfers: map[uint32]*FileTransfer{},
875 },
876 },
877 t: NewTransaction(
878 tranUploadFile, &[]byte{0, 1},
879 NewField(fieldFileName, []byte("testFile")),
880 NewField(fieldFilePath, []byte{
881 0x00, 0x01,
882 0x00, 0x00,
883 0x03,
884 0x2e, 0x2e, 0x2f,
885 }),
886 ),
887 },
888 wantRes: []Transaction{
889 {
890 Flags: 0x00,
891 IsReply: 0x01,
892 Type: []byte{0, 0x00},
893 ID: []byte{0x9a, 0xcb, 0x04, 0x42},
894 ErrorCode: []byte{0, 0, 0, 1},
895 Fields: []Field{
896 NewField(fieldError, []byte("You are not allowed to upload files.")), // rand.Seed(1)
897 },
898 },
899 },
900 wantErr: false,
901 },
902 }
903 for _, tt := range tests {
904 t.Run(tt.name, func(t *testing.T) {
905 rand.Seed(1)
906 gotRes, err := HandleUploadFile(tt.args.cc, tt.args.t)
907 if (err != nil) != tt.wantErr {
908 t.Errorf("HandleUploadFile() error = %v, wantErr %v", err, tt.wantErr)
909 return
910 }
aebc4d36
JH
911
912 tranAssertEqual(t, tt.wantRes, gotRes)
913
92a7e455
JH
914 })
915 }
916}
decc2fbf
JH
917
918func TestHandleMakeAlias(t *testing.T) {
919 type args struct {
920 cc *ClientConn
921 t *Transaction
922 }
923 tests := []struct {
924 name string
925 setup func()
926 args args
927 wantRes []Transaction
928 wantErr bool
929 }{
930 {
931 name: "with valid input and required permissions",
932 setup: func() {
aebc4d36 933 mfs := &MockFileStore{}
decc2fbf
JH
934 path, _ := os.Getwd()
935 mfs.On(
936 "Symlink",
937 path+"/test/config/Files/foo/testFile",
938 path+"/test/config/Files/bar/testFile",
939 ).Return(nil)
940 FS = mfs
941 },
942 args: args{
943 cc: &ClientConn{
944 Account: &Account{
945 Access: func() *[]byte {
946 var bits accessBitmap
947 bits.Set(accessMakeAlias)
948 access := bits[:]
949 return &access
950 }(),
951 },
952 Server: &Server{
953 Config: &Config{
954 FileRoot: func() string {
955 path, _ := os.Getwd()
956 return path + "/test/config/Files"
957 }(),
958 },
959 Logger: NewTestLogger(),
960 },
961 },
962 t: NewTransaction(
963 tranMakeFileAlias, &[]byte{0, 1},
964 NewField(fieldFileName, []byte("testFile")),
965 NewField(fieldFilePath, EncodeFilePath(strings.Join([]string{"foo"}, "/"))),
966 NewField(fieldFileNewPath, EncodeFilePath(strings.Join([]string{"bar"}, "/"))),
967 ),
968 },
969 wantRes: []Transaction{
970 {
971 Flags: 0x00,
972 IsReply: 0x01,
973 Type: []byte{0, 0xd1},
974 ID: []byte{0x9a, 0xcb, 0x04, 0x42},
975 ErrorCode: []byte{0, 0, 0, 0},
976 Fields: []Field(nil),
977 },
978 },
979 wantErr: false,
980 },
981 {
982 name: "when symlink returns an error",
983 setup: func() {
aebc4d36 984 mfs := &MockFileStore{}
decc2fbf
JH
985 path, _ := os.Getwd()
986 mfs.On(
987 "Symlink",
988 path+"/test/config/Files/foo/testFile",
989 path+"/test/config/Files/bar/testFile",
990 ).Return(errors.New("ohno"))
991 FS = mfs
992 },
993 args: args{
994 cc: &ClientConn{
995 Account: &Account{
996 Access: func() *[]byte {
997 var bits accessBitmap
998 bits.Set(accessMakeAlias)
999 access := bits[:]
1000 return &access
1001 }(),
1002 },
1003 Server: &Server{
1004 Config: &Config{
1005 FileRoot: func() string {
1006 path, _ := os.Getwd()
1007 return path + "/test/config/Files"
1008 }(),
1009 },
1010 Logger: NewTestLogger(),
1011 },
1012 },
1013 t: NewTransaction(
1014 tranMakeFileAlias, &[]byte{0, 1},
1015 NewField(fieldFileName, []byte("testFile")),
1016 NewField(fieldFilePath, EncodeFilePath(strings.Join([]string{"foo"}, "/"))),
1017 NewField(fieldFileNewPath, EncodeFilePath(strings.Join([]string{"bar"}, "/"))),
1018 ),
1019 },
1020 wantRes: []Transaction{
1021 {
1022 Flags: 0x00,
1023 IsReply: 0x01,
1024 Type: []byte{0, 0x00},
1025 ID: []byte{0x9a, 0xcb, 0x04, 0x42},
1026 ErrorCode: []byte{0, 0, 0, 1},
1027 Fields: []Field{
1028 NewField(fieldError, []byte("Error creating alias")),
1029 },
1030 },
1031 },
1032 wantErr: false,
1033 },
1034 {
1035 name: "when user does not have required permission",
1036 setup: func() {},
1037 args: args{
1038 cc: &ClientConn{
1039 Account: &Account{
1040 Access: func() *[]byte {
1041 var bits accessBitmap
1042 access := bits[:]
1043 return &access
1044 }(),
1045 },
1046 Server: &Server{
1047 Config: &Config{
1048 FileRoot: func() string {
1049 path, _ := os.Getwd()
1050 return path + "/test/config/Files"
1051 }(),
1052 },
1053 },
1054 },
1055 t: NewTransaction(
1056 tranMakeFileAlias, &[]byte{0, 1},
1057 NewField(fieldFileName, []byte("testFile")),
1058 NewField(fieldFilePath, []byte{
1059 0x00, 0x01,
1060 0x00, 0x00,
1061 0x03,
1062 0x2e, 0x2e, 0x2e,
1063 }),
1064 NewField(fieldFileNewPath, []byte{
1065 0x00, 0x01,
1066 0x00, 0x00,
1067 0x03,
1068 0x2e, 0x2e, 0x2e,
1069 }),
1070 ),
1071 },
1072 wantRes: []Transaction{
1073 {
1074 Flags: 0x00,
1075 IsReply: 0x01,
1076 Type: []byte{0, 0x00},
1077 ID: []byte{0x9a, 0xcb, 0x04, 0x42},
1078 ErrorCode: []byte{0, 0, 0, 1},
1079 Fields: []Field{
1080 NewField(fieldError, []byte("You are not allowed to make aliases.")),
1081 },
1082 },
1083 },
1084 wantErr: false,
1085 },
1086 }
1087 for _, tt := range tests {
1088 t.Run(tt.name, func(t *testing.T) {
1089 tt.setup()
1090
1091 gotRes, err := HandleMakeAlias(tt.args.cc, tt.args.t)
1092 if (err != nil) != tt.wantErr {
1093 t.Errorf("HandleMakeAlias(%v, %v)", tt.args.cc, tt.args.t)
1094 return
1095 }
1096
1097 tranAssertEqual(t, tt.wantRes, gotRes)
1098 })
1099 }
1100}
9ebf276d
JH
1101
1102func TestHandleGetUser(t *testing.T) {
1103 type args struct {
1104 cc *ClientConn
1105 t *Transaction
1106 }
1107 tests := []struct {
1108 name string
1109 args args
1110 wantRes []Transaction
1111 wantErr assert.ErrorAssertionFunc
1112 }{
1113 {
1114 name: "when account is valid",
1115 args: args{
1116 cc: &ClientConn{
1117 Account: &Account{
1118 Access: func() *[]byte {
1119 var bits accessBitmap
1120 bits.Set(accessOpenUser)
1121 access := bits[:]
1122 return &access
1123 }(),
1124 },
1125 Server: &Server{
1126 Accounts: map[string]*Account{
1127 "guest": {
1128 Login: "guest",
1129 Name: "Guest",
1130 Password: "password",
1131 Access: &[]byte{1},
1132 },
1133 },
1134 },
1135 },
1136 t: NewTransaction(
1137 tranGetUser, &[]byte{0, 1},
1138 NewField(fieldUserLogin, []byte("guest")),
1139 ),
1140 },
1141 wantRes: []Transaction{
1142 {
1143 Flags: 0x00,
1144 IsReply: 0x01,
1145 Type: []byte{0x01, 0x60},
1146 ID: []byte{0x9a, 0xcb, 0x04, 0x42},
1147 ErrorCode: []byte{0, 0, 0, 0},
1148 Fields: []Field{
1149 NewField(fieldUserName, []byte("Guest")),
1150 NewField(fieldUserLogin, negateString([]byte("guest"))),
1151 NewField(fieldUserPassword, []byte("password")),
1152 NewField(fieldUserAccess, []byte{1}),
1153 },
1154 },
1155 },
1156 wantErr: assert.NoError,
1157 },
1158 {
1159 name: "when user does not have required permission",
1160 args: args{
1161 cc: &ClientConn{
1162 Account: &Account{
1163 Access: func() *[]byte {
1164 var bits accessBitmap
1165 access := bits[:]
1166 return &access
1167 }(),
1168 },
1169 Server: &Server{
1170 Accounts: map[string]*Account{},
1171 },
1172 },
1173 t: NewTransaction(
1174 tranGetUser, &[]byte{0, 1},
1175 NewField(fieldUserLogin, []byte("nonExistentUser")),
1176 ),
1177 },
1178 wantRes: []Transaction{
1179 {
1180 Flags: 0x00,
1181 IsReply: 0x01,
1182 Type: []byte{0, 0x00},
1183 ID: []byte{0x9a, 0xcb, 0x04, 0x42},
1184 ErrorCode: []byte{0, 0, 0, 1},
1185 Fields: []Field{
1186 NewField(fieldError, []byte("You are not allowed to view accounts.")),
1187 },
1188 },
1189 },
1190 wantErr: assert.NoError,
1191 },
1192 {
1193 name: "when account does not exist",
1194 args: args{
1195 cc: &ClientConn{
1196 Account: &Account{
1197 Access: func() *[]byte {
1198 var bits accessBitmap
1199 bits.Set(accessOpenUser)
1200 access := bits[:]
1201 return &access
1202 }(),
1203 },
1204 Server: &Server{
1205 Accounts: map[string]*Account{},
1206 },
1207 },
1208 t: NewTransaction(
1209 tranGetUser, &[]byte{0, 1},
1210 NewField(fieldUserLogin, []byte("nonExistentUser")),
1211 ),
1212 },
1213 wantRes: []Transaction{
1214 {
1215 Flags: 0x00,
1216 IsReply: 0x01,
1217 Type: []byte{0, 0x00},
1218 ID: []byte{0x9a, 0xcb, 0x04, 0x42},
1219 ErrorCode: []byte{0, 0, 0, 1},
1220 Fields: []Field{
1221 NewField(fieldError, []byte("Account does not exist.")),
1222 },
1223 },
1224 },
1225 wantErr: assert.NoError,
1226 },
1227 }
1228 for _, tt := range tests {
1229 t.Run(tt.name, func(t *testing.T) {
1230 gotRes, err := HandleGetUser(tt.args.cc, tt.args.t)
1231 if !tt.wantErr(t, err, fmt.Sprintf("HandleGetUser(%v, %v)", tt.args.cc, tt.args.t)) {
1232 return
1233 }
1234
1235 tranAssertEqual(t, tt.wantRes, gotRes)
1236 })
1237 }
1238}
1239
1240func TestHandleDeleteUser(t *testing.T) {
1241 type args struct {
1242 cc *ClientConn
1243 t *Transaction
1244 }
1245 tests := []struct {
1246 name string
1247 setup func()
1248 args args
1249 wantRes []Transaction
1250 wantErr assert.ErrorAssertionFunc
1251 }{
1252 {
1253 name: "when user exists",
1254 setup: func() {
1255 mfs := &MockFileStore{}
1256 mfs.On("Remove", "Users/testuser.yaml").Return(nil)
1257 FS = mfs
1258 },
1259 args: args{
1260 cc: &ClientConn{
1261 Account: &Account{
1262 Access: func() *[]byte {
1263 var bits accessBitmap
1264 bits.Set(accessDeleteUser)
1265 access := bits[:]
1266 return &access
1267 }(),
1268 },
1269 Server: &Server{
1270 Accounts: map[string]*Account{
1271 "testuser": {
1272 Login: "testuser",
1273 Name: "Testy McTest",
1274 Password: "password",
1275 Access: &[]byte{1},
1276 },
1277 },
1278 },
1279 },
1280 t: NewTransaction(
1281 tranDeleteUser, &[]byte{0, 1},
1282 NewField(fieldUserLogin, negateString([]byte("testuser"))),
1283 ),
1284 },
1285 wantRes: []Transaction{
1286 {
1287 Flags: 0x00,
1288 IsReply: 0x01,
1289 Type: []byte{0x1, 0x5f},
1290 ID: []byte{0x9a, 0xcb, 0x04, 0x42},
1291 ErrorCode: []byte{0, 0, 0, 0},
1292 Fields: []Field(nil),
1293 },
1294 },
1295 wantErr: assert.NoError,
1296 },
1297 {
1298 name: "when user does not have required permission",
1299 setup: func() {},
1300 args: args{
1301 cc: &ClientConn{
1302 Account: &Account{
1303 Access: func() *[]byte {
1304 var bits accessBitmap
1305 access := bits[:]
1306 return &access
1307 }(),
1308 },
1309 Server: &Server{
1310 Accounts: map[string]*Account{},
1311 },
1312 },
1313 t: NewTransaction(
1314 tranDeleteUser, &[]byte{0, 1},
1315 NewField(fieldUserLogin, negateString([]byte("testuser"))),
1316 ),
1317 },
1318 wantRes: []Transaction{
1319 {
1320 Flags: 0x00,
1321 IsReply: 0x01,
1322 Type: []byte{0, 0x00},
1323 ID: []byte{0x9a, 0xcb, 0x04, 0x42},
1324 ErrorCode: []byte{0, 0, 0, 1},
1325 Fields: []Field{
1326 NewField(fieldError, []byte("You are not allowed to delete accounts.")),
1327 },
1328 },
1329 },
1330 wantErr: assert.NoError,
1331 },
1332 }
1333 for _, tt := range tests {
1334 t.Run(tt.name, func(t *testing.T) {
1335 tt.setup()
1336 gotRes, err := HandleDeleteUser(tt.args.cc, tt.args.t)
1337 if !tt.wantErr(t, err, fmt.Sprintf("HandleDeleteUser(%v, %v)", tt.args.cc, tt.args.t)) {
1338 return
1339 }
1340
1341 tranAssertEqual(t, tt.wantRes, gotRes)
1342 })
1343 }
1344}