]> git.r.bdr.sh - rbdr/mobius/blame_incremental - hotline/transaction_handlers_test.go
patch: v0.0.12
[rbdr/mobius] / hotline / transaction_handlers_test.go
... / ...
CommitLineData
1package hotline
2
3import (
4 "github.com/stretchr/testify/assert"
5 "io/fs"
6 "math/rand"
7 "os"
8 "reflect"
9 "testing"
10)
11
12func TestHandleSetChatSubject(t *testing.T) {
13 type args struct {
14 cc *ClientConn
15 t *Transaction
16 }
17 tests := []struct {
18 name string
19 args args
20 want []Transaction
21 wantErr bool
22 }{
23 {
24 name: "sends chat subject to private chat members",
25 args: args{
26 cc: &ClientConn{
27 UserName: []byte{0x00, 0x01},
28 Server: &Server{
29 PrivateChats: map[uint32]*PrivateChat{
30 uint32(1): {
31 Subject: "unset",
32 ClientConn: map[uint16]*ClientConn{
33 uint16(1): {
34 Account: &Account{
35 Access: &[]byte{255, 255, 255, 255, 255, 255, 255, 255},
36 },
37 ID: &[]byte{0, 1},
38 },
39 uint16(2): {
40 Account: &Account{
41 Access: &[]byte{255, 255, 255, 255, 255, 255, 255, 255},
42 },
43 ID: &[]byte{0, 2},
44 },
45 },
46 },
47 },
48 Clients: map[uint16]*ClientConn{
49 uint16(1): {
50 Account: &Account{
51 Access: &[]byte{255, 255, 255, 255, 255, 255, 255, 255},
52 },
53 ID: &[]byte{0, 1},
54 },
55 uint16(2): {
56 Account: &Account{
57 Access: &[]byte{255, 255, 255, 255, 255, 255, 255, 255},
58 },
59 ID: &[]byte{0, 2},
60 },
61 },
62 },
63 },
64 t: &Transaction{
65 Flags: 0x00,
66 IsReply: 0x00,
67 Type: []byte{0, 0x6a},
68 ID: []byte{0, 0, 0, 1},
69 ErrorCode: []byte{0, 0, 0, 0},
70 Fields: []Field{
71 NewField(fieldChatID, []byte{0, 0, 0, 1}),
72 NewField(fieldChatSubject, []byte("Test Subject")),
73 },
74 },
75 },
76 want: []Transaction{
77 {
78 clientID: &[]byte{0, 1},
79 Flags: 0x00,
80 IsReply: 0x00,
81 Type: []byte{0, 0x77},
82 ID: []byte{0x9a, 0xcb, 0x04, 0x42}, // Random ID from rand.Seed(1)
83 ErrorCode: []byte{0, 0, 0, 0},
84 Fields: []Field{
85 NewField(fieldChatID, []byte{0, 0, 0, 1}),
86 NewField(fieldChatSubject, []byte("Test Subject")),
87 },
88 },
89 {
90 clientID: &[]byte{0, 2},
91 Flags: 0x00,
92 IsReply: 0x00,
93 Type: []byte{0, 0x77},
94 ID: []byte{0xf0, 0xc5, 0x34, 0x1e}, // Random ID from rand.Seed(1)
95 ErrorCode: []byte{0, 0, 0, 0},
96 Fields: []Field{
97 NewField(fieldChatID, []byte{0, 0, 0, 1}),
98 NewField(fieldChatSubject, []byte("Test Subject")),
99 },
100 },
101 },
102 wantErr: false,
103 },
104 }
105 for _, tt := range tests {
106 rand.Seed(1) // reset seed between tests to make transaction IDs predictable
107
108 t.Run(tt.name, func(t *testing.T) {
109 got, err := HandleSetChatSubject(tt.args.cc, tt.args.t)
110 if (err != nil) != tt.wantErr {
111 t.Errorf("HandleSetChatSubject() error = %v, wantErr %v", err, tt.wantErr)
112 return
113 }
114 if !assert.Equal(t, tt.want, got) {
115 t.Errorf("HandleSetChatSubject() got = %v, want %v", got, tt.want)
116 }
117 })
118 }
119}
120
121func TestHandleLeaveChat(t *testing.T) {
122 type args struct {
123 cc *ClientConn
124 t *Transaction
125 }
126 tests := []struct {
127 name string
128 args args
129 want []Transaction
130 wantErr bool
131 }{
132 {
133 name: "returns expected transactions",
134 args: args{
135 cc: &ClientConn{
136 ID: &[]byte{0, 2},
137 Server: &Server{
138 PrivateChats: map[uint32]*PrivateChat{
139 uint32(1): {
140 ClientConn: map[uint16]*ClientConn{
141 uint16(1): {
142 Account: &Account{
143 Access: &[]byte{255, 255, 255, 255, 255, 255, 255, 255},
144 },
145 ID: &[]byte{0, 1},
146 },
147 uint16(2): {
148 Account: &Account{
149 Access: &[]byte{255, 255, 255, 255, 255, 255, 255, 255},
150 },
151 ID: &[]byte{0, 2},
152 },
153 },
154 },
155 },
156 Clients: map[uint16]*ClientConn{
157 uint16(1): {
158 Account: &Account{
159 Access: &[]byte{255, 255, 255, 255, 255, 255, 255, 255},
160 },
161 ID: &[]byte{0, 1},
162 },
163 uint16(2): {
164 Account: &Account{
165 Access: &[]byte{255, 255, 255, 255, 255, 255, 255, 255},
166 },
167 ID: &[]byte{0, 2},
168 },
169 },
170 },
171 },
172 t: NewTransaction(tranDeleteUser, nil, NewField(fieldChatID, []byte{0, 0, 0, 1})),
173 },
174 want: []Transaction{
175 {
176 clientID: &[]byte{0, 1},
177 Flags: 0x00,
178 IsReply: 0x00,
179 Type: []byte{0, 0x76},
180 ID: []byte{0x9a, 0xcb, 0x04, 0x42}, // Random ID from rand.Seed(1)
181 ErrorCode: []byte{0, 0, 0, 0},
182 Fields: []Field{
183 NewField(fieldChatID, []byte{0, 0, 0, 1}),
184 NewField(fieldUserID, []byte{0, 2}),
185 },
186 },
187 },
188 wantErr: false,
189 },
190 }
191 for _, tt := range tests {
192 rand.Seed(1)
193 t.Run(tt.name, func(t *testing.T) {
194 got, err := HandleLeaveChat(tt.args.cc, tt.args.t)
195 if (err != nil) != tt.wantErr {
196 t.Errorf("HandleLeaveChat() error = %v, wantErr %v", err, tt.wantErr)
197 return
198 }
199 if !assert.Equal(t, tt.want, got) {
200 t.Errorf("HandleLeaveChat() got = %v, want %v", got, tt.want)
201 }
202 })
203 }
204}
205
206func TestHandleGetUserNameList(t *testing.T) {
207 type args struct {
208 cc *ClientConn
209 t *Transaction
210 }
211 tests := []struct {
212 name string
213 args args
214 want []Transaction
215 wantErr bool
216 }{
217 {
218 name: "replies with userlist transaction",
219 args: args{
220 cc: &ClientConn{
221
222 ID: &[]byte{1, 1},
223 Server: &Server{
224 Clients: map[uint16]*ClientConn{
225 uint16(1): {
226 ID: &[]byte{0, 1},
227 Icon: &[]byte{0, 2},
228 Flags: &[]byte{0, 3},
229 UserName: []byte{0, 4},
230 },
231 },
232 },
233 },
234 t: &Transaction{
235 ID: []byte{0, 0, 0, 1},
236 Type: []byte{0, 1},
237 },
238 },
239 want: []Transaction{
240 {
241 clientID: &[]byte{1, 1},
242 Flags: 0x00,
243 IsReply: 0x01,
244 Type: []byte{0, 1},
245 ID: []byte{0, 0, 0, 1},
246 ErrorCode: []byte{0, 0, 0, 0},
247 Fields: []Field{
248 NewField(
249 fieldUsernameWithInfo,
250 []byte{00, 01, 00, 02, 00, 03, 00, 02, 00, 04},
251 ),
252 },
253 },
254 },
255 wantErr: false,
256 },
257 }
258 for _, tt := range tests {
259 t.Run(tt.name, func(t *testing.T) {
260 got, err := HandleGetUserNameList(tt.args.cc, tt.args.t)
261 if (err != nil) != tt.wantErr {
262 t.Errorf("HandleGetUserNameList() error = %v, wantErr %v", err, tt.wantErr)
263 return
264 }
265 if !reflect.DeepEqual(got, tt.want) {
266 t.Errorf("HandleGetUserNameList() got = %v, want %v", got, tt.want)
267 }
268 })
269 }
270}
271
272func TestHandleChatSend(t *testing.T) {
273 type args struct {
274 cc *ClientConn
275 t *Transaction
276 }
277 tests := []struct {
278 name string
279 args args
280 want []Transaction
281 wantErr bool
282 }{
283 {
284 name: "sends chat msg transaction to all clients",
285 args: args{
286 cc: &ClientConn{
287 UserName: []byte{0x00, 0x01},
288 Server: &Server{
289 Clients: map[uint16]*ClientConn{
290 uint16(1): {
291 Account: &Account{
292 Access: &[]byte{255, 255, 255, 255, 255, 255, 255, 255},
293 },
294 ID: &[]byte{0, 1},
295 },
296 uint16(2): {
297 Account: &Account{
298 Access: &[]byte{255, 255, 255, 255, 255, 255, 255, 255},
299 },
300 ID: &[]byte{0, 2},
301 },
302 },
303 },
304 },
305 t: &Transaction{
306 Fields: []Field{
307 NewField(fieldData, []byte("hai")),
308 },
309 },
310 },
311 want: []Transaction{
312 {
313 clientID: &[]byte{0, 1},
314 Flags: 0x00,
315 IsReply: 0x00,
316 Type: []byte{0, 0x6a},
317 ID: []byte{0x9a, 0xcb, 0x04, 0x42}, // Random ID from rand.Seed(1)
318 ErrorCode: []byte{0, 0, 0, 0},
319 Fields: []Field{
320 NewField(fieldData, []byte{0x0d, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x01, 0x3a, 0x20, 0x20, 0x68, 0x61, 0x69}),
321 },
322 },
323 {
324 clientID: &[]byte{0, 2},
325 Flags: 0x00,
326 IsReply: 0x00,
327 Type: []byte{0, 0x6a},
328 ID: []byte{0xf0, 0xc5, 0x34, 0x1e}, // Random ID from rand.Seed(1)
329 ErrorCode: []byte{0, 0, 0, 0},
330 Fields: []Field{
331 NewField(fieldData, []byte{0x0d, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x01, 0x3a, 0x20, 0x20, 0x68, 0x61, 0x69}),
332 },
333 },
334 },
335 wantErr: false,
336 },
337 {
338 name: "sends chat msg as emote if fieldChatOptions is set",
339 args: args{
340 cc: &ClientConn{
341 UserName: []byte("Testy McTest"),
342 Server: &Server{
343 Clients: map[uint16]*ClientConn{
344 uint16(1): {
345 Account: &Account{
346 Access: &[]byte{255, 255, 255, 255, 255, 255, 255, 255},
347 },
348 ID: &[]byte{0, 1},
349 },
350 uint16(2): {
351 Account: &Account{
352 Access: &[]byte{255, 255, 255, 255, 255, 255, 255, 255},
353 },
354 ID: &[]byte{0, 2},
355 },
356 },
357 },
358 },
359 t: &Transaction{
360 Fields: []Field{
361 NewField(fieldData, []byte("performed action")),
362 NewField(fieldChatOptions, []byte{0x00, 0x01}),
363 },
364 },
365 },
366 want: []Transaction{
367 {
368 clientID: &[]byte{0, 1},
369 Flags: 0x00,
370 IsReply: 0x00,
371 Type: []byte{0, 0x6a},
372 ID: []byte{0x9a, 0xcb, 0x04, 0x42}, // Random ID from rand.Seed(1)
373 ErrorCode: []byte{0, 0, 0, 0},
374 Fields: []Field{
375 NewField(fieldData, []byte("\r*** Testy McTest performed action")),
376 },
377 },
378 {
379 clientID: &[]byte{0, 2},
380 Flags: 0x00,
381 IsReply: 0x00,
382 Type: []byte{0, 0x6a},
383 ID: []byte{0xf0, 0xc5, 0x34, 0x1e}, // Random ID from rand.Seed(1)
384 ErrorCode: []byte{0, 0, 0, 0},
385 Fields: []Field{
386 NewField(fieldData, []byte("\r*** Testy McTest performed action")),
387 },
388 },
389 },
390 wantErr: false,
391 },
392 {
393 name: "only sends chat msg to clients with accessReadChat permission",
394 args: args{
395 cc: &ClientConn{
396 UserName: []byte{0x00, 0x01},
397 Server: &Server{
398 Clients: map[uint16]*ClientConn{
399 uint16(1): {
400 Account: &Account{
401 Access: &[]byte{255, 255, 255, 255, 255, 255, 255, 255},
402 },
403 ID: &[]byte{0, 1},
404 },
405 uint16(2): {
406 Account: &Account{
407 Access: &[]byte{0, 0, 0, 0, 0, 0, 0, 0},
408 },
409 ID: &[]byte{0, 2},
410 },
411 },
412 },
413 },
414 t: &Transaction{
415 Fields: []Field{
416 NewField(fieldData, []byte("hai")),
417 },
418 },
419 },
420 want: []Transaction{
421 {
422 clientID: &[]byte{0, 1},
423 Flags: 0x00,
424 IsReply: 0x00,
425 Type: []byte{0, 0x6a},
426 ID: []byte{0x9a, 0xcb, 0x04, 0x42}, // Random ID from rand.Seed(1)
427 ErrorCode: []byte{0, 0, 0, 0},
428 Fields: []Field{
429 NewField(fieldData, []byte{0x0d, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x01, 0x3a, 0x20, 0x20, 0x68, 0x61, 0x69}),
430 },
431 },
432 },
433 wantErr: false,
434 },
435 }
436 for _, tt := range tests {
437 rand.Seed(1) // reset seed between tests to make transaction IDs predictable
438 t.Run(tt.name, func(t *testing.T) {
439 got, err := HandleChatSend(tt.args.cc, tt.args.t)
440
441 if (err != nil) != tt.wantErr {
442 t.Errorf("HandleChatSend() error = %v, wantErr %v", err, tt.wantErr)
443 return
444 }
445 if !assert.Equal(t, tt.want, got) {
446 t.Errorf("HandleChatSend() got = %v, want %v", got, tt.want)
447 }
448 })
449 }
450}
451
452func TestHandleGetFileInfo(t *testing.T) {
453 rand.Seed(1) // reset seed between tests to make transaction IDs predictable
454
455 type args struct {
456 cc *ClientConn
457 t *Transaction
458 }
459 tests := []struct {
460 name string
461 args args
462 wantRes []Transaction
463 wantErr bool
464 }{
465 {
466 name: "returns expected fields when a valid file is requested",
467 args: args{
468 cc: &ClientConn{
469 ID: &[]byte{0x00, 0x01},
470 Server: &Server{
471 Config: &Config{
472 FileRoot: "./test/config/Files/",
473 },
474 },
475 },
476 t: NewTransaction(
477 tranGetFileInfo, nil,
478 NewField(fieldFileName, []byte("testfile.txt")),
479 NewField(fieldFilePath, []byte{0x00, 0x00}),
480 //NewField(fieldFilePath, []byte{
481 // 0x00, 0x03,
482 // 0x00, 0x00,
483 // 0x04,
484 // 0x74, 0x65, 0x73, 0x74,
485 // 0x00, 0x00,
486 // 0x06,
487 // 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67,
488 //
489 // 0x00, 0x00,
490 // 0x05,
491 // 0x46, 0x69, 0x6c, 0x65, 73},
492 //),
493 ),
494 },
495 wantRes: []Transaction{
496 {
497 clientID: &[]byte{0, 1},
498 Flags: 0x00,
499 IsReply: 0x01,
500 Type: []byte{0, 0xce},
501 ID: []byte{0x9a, 0xcb, 0x04, 0x42}, // Random ID from rand.Seed(1)
502 ErrorCode: []byte{0, 0, 0, 0},
503 Fields: []Field{
504 NewField(fieldFileName, []byte("testfile.txt")),
505 NewField(fieldFileTypeString, []byte("TEXT")),
506 NewField(fieldFileCreatorString, []byte("TTXT")),
507 NewField(fieldFileComment, []byte("TODO")),
508 NewField(fieldFileType, []byte("TEXT")),
509 NewField(fieldFileCreateDate, []byte{0x07, 0x70, 0x00, 0x00, 0xba, 0x74, 0x24, 0x73}),
510 NewField(fieldFileModifyDate, []byte{0x07, 0x70, 0x00, 0x00, 0xba, 0x74, 0x24, 0x73}),
511 NewField(fieldFileSize, []byte{0x0, 0x0, 0x0, 0x17}),
512 },
513 },
514 },
515 wantErr: false,
516 },
517 }
518 for _, tt := range tests {
519 t.Run(tt.name, func(t *testing.T) {
520 rand.Seed(1) // reset seed between tests to make transaction IDs predictable
521
522 gotRes, err := HandleGetFileInfo(tt.args.cc, tt.args.t)
523 if (err != nil) != tt.wantErr {
524 t.Errorf("HandleGetFileInfo() error = %v, wantErr %v", err, tt.wantErr)
525 return
526 }
527 if !assert.Equal(t, tt.wantRes, gotRes) {
528 t.Errorf("HandleGetFileInfo() gotRes = %v, want %v", gotRes, tt.wantRes)
529 }
530 })
531 }
532}
533
534func TestHandleNewFolder(t *testing.T) {
535 type args struct {
536 cc *ClientConn
537 t *Transaction
538 }
539 tests := []struct {
540 setup func()
541 name string
542 args args
543 wantRes []Transaction
544 wantErr bool
545 }{
546 {
547 name: "when path is nested",
548 args: args{
549 cc: &ClientConn{
550 ID: &[]byte{0, 1},
551 Server: &Server{
552 Config: &Config{
553 FileRoot: "/Files/",
554 },
555 },
556 },
557 t: NewTransaction(
558 tranNewFolder, &[]byte{0, 1},
559 NewField(fieldFileName, []byte("testFolder")),
560 NewField(fieldFilePath, []byte{
561 0x00, 0x01,
562 0x00, 0x00,
563 0x03,
564 0x61, 0x61, 0x61,
565 }),
566 ),
567 },
568 setup: func() {
569 mfs := MockFileStore{}
570 mfs.On("Mkdir", "/Files/aaa/testFolder", fs.FileMode(0777)).Return(nil)
571 mfs.On("Stat", "/Files/aaa/testFolder").Return(nil, os.ErrNotExist)
572 FS = mfs
573 },
574 wantRes: []Transaction{
575 {
576 clientID: &[]byte{0, 1},
577 Flags: 0x00,
578 IsReply: 0x01,
579 Type: []byte{0, 0xcd},
580 ID: []byte{0x9a, 0xcb, 0x04, 0x42}, // Random ID from rand.Seed(1)
581 ErrorCode: []byte{0, 0, 0, 0},
582 },
583 },
584 wantErr: false,
585 },
586 {
587 name: "when path is not nested",
588 args: args{
589 cc: &ClientConn{
590 ID: &[]byte{0, 1},
591 Server: &Server{
592 Config: &Config{
593 FileRoot: "/Files",
594 },
595 },
596 },
597 t: NewTransaction(
598 tranNewFolder, &[]byte{0, 1},
599 NewField(fieldFileName, []byte("testFolder")),
600 ),
601 },
602 setup: func() {
603 mfs := MockFileStore{}
604 mfs.On("Mkdir", "/Files/testFolder", fs.FileMode(0777)).Return(nil)
605 mfs.On("Stat", "/Files/testFolder").Return(nil, os.ErrNotExist)
606 FS = mfs
607 },
608 wantRes: []Transaction{
609 {
610 clientID: &[]byte{0, 1},
611 Flags: 0x00,
612 IsReply: 0x01,
613 Type: []byte{0, 0xcd},
614 ID: []byte{0x9a, 0xcb, 0x04, 0x42}, // Random ID from rand.Seed(1)
615 ErrorCode: []byte{0, 0, 0, 0},
616 },
617 },
618 wantErr: false,
619 },
620 {
621 name: "when UnmarshalBinary returns an err",
622 args: args{
623 cc: &ClientConn{
624 ID: &[]byte{0, 1},
625 Server: &Server{
626 Config: &Config{
627 FileRoot: "/Files/",
628 },
629 },
630 },
631 t: NewTransaction(
632 tranNewFolder, &[]byte{0, 1},
633 NewField(fieldFileName, []byte("testFolder")),
634 NewField(fieldFilePath, []byte{
635 0x00,
636 }),
637 ),
638 },
639 setup: func() {
640 mfs := MockFileStore{}
641 mfs.On("Mkdir", "/Files/aaa/testFolder", fs.FileMode(0777)).Return(nil)
642 mfs.On("Stat", "/Files/aaa/testFolder").Return(nil, os.ErrNotExist)
643 FS = mfs
644 },
645 wantRes: []Transaction{},
646 wantErr: true,
647 },
648 {
649 name: "fieldFileName does not allow directory traversal",
650 args: args{
651 cc: &ClientConn{
652 ID: &[]byte{0, 1},
653 Server: &Server{
654 Config: &Config{
655 FileRoot: "/Files/",
656 },
657 },
658 },
659 t: NewTransaction(
660 tranNewFolder, &[]byte{0, 1},
661 NewField(fieldFileName, []byte("../../testFolder")),
662
663 ),
664 },
665 setup: func() {
666 mfs := MockFileStore{}
667 mfs.On("Mkdir", "/Files/testFolder", fs.FileMode(0777)).Return(nil)
668 mfs.On("Stat", "/Files/testFolder").Return(nil, os.ErrNotExist)
669 FS = mfs
670 },
671 wantRes: []Transaction{
672 {
673 clientID: &[]byte{0, 1},
674 Flags: 0x00,
675 IsReply: 0x01,
676 Type: []byte{0, 0xcd},
677 ID: []byte{0x9a, 0xcb, 0x04, 0x42}, // Random ID from rand.Seed(1)
678 ErrorCode: []byte{0, 0, 0, 0},
679 },
680 }, wantErr: false,
681 },
682 {
683 name: "fieldFilePath does not allow directory traversal",
684 args: args{
685 cc: &ClientConn{
686 ID: &[]byte{0, 1},
687 Server: &Server{
688 Config: &Config{
689 FileRoot: "/Files/",
690 },
691 },
692 },
693 t: NewTransaction(
694 tranNewFolder, &[]byte{0, 1},
695 NewField(fieldFileName, []byte("testFolder")),
696 NewField(fieldFilePath, []byte{
697 0x00, 0x02,
698 0x00, 0x00,
699 0x03,
700 0x2e, 0x2e, 0x2f,
701 0x00, 0x00,
702 0x03,
703 0x66, 0x6f, 0x6f,
704 }),
705 ),
706 },
707 setup: func() {
708 mfs := MockFileStore{}
709 mfs.On("Mkdir", "/Files/foo/testFolder", fs.FileMode(0777)).Return(nil)
710 mfs.On("Stat", "/Files/foo/testFolder").Return(nil, os.ErrNotExist)
711 FS = mfs
712 },
713 wantRes: []Transaction{
714 {
715 clientID: &[]byte{0, 1},
716 Flags: 0x00,
717 IsReply: 0x01,
718 Type: []byte{0, 0xcd},
719 ID: []byte{0x9a, 0xcb, 0x04, 0x42}, // Random ID from rand.Seed(1)
720 ErrorCode: []byte{0, 0, 0, 0},
721 },
722 }, wantErr: false,
723 },
724 }
725 for _, tt := range tests {
726 t.Run(tt.name, func(t *testing.T) {
727 tt.setup()
728
729 gotRes, err := HandleNewFolder(tt.args.cc, tt.args.t)
730 if (err != nil) != tt.wantErr {
731 t.Errorf("HandleNewFolder() error = %v, wantErr %v", err, tt.wantErr)
732 return
733 }
734 if !tranAssertEqual(t, tt.wantRes, gotRes) {
735 t.Errorf("HandleNewFolder() gotRes = %v, want %v", gotRes, tt.wantRes)
736 }
737 })
738 }
739}
740