github.com/philippseith/signalr@v0.6.3/streamclient_test.go (about) 1 package signalr 2 3 import ( 4 "context" 5 "fmt" 6 "strings" 7 "time" 8 9 . "github.com/onsi/ginkgo" 10 . "github.com/onsi/gomega" 11 ) 12 13 type clientStreamHub struct { 14 Hub 15 ch chan string 16 } 17 18 func (c *clientStreamHub) SendResult(result string) { 19 if c.ch != nil { 20 c.ch <- result 21 } 22 c.Hub.Clients().All().Send("OnResult", result) 23 } 24 25 func (c *clientStreamHub) UploadStreamSmoke(upload1 <-chan int, factor float64, upload2 <-chan float64) { 26 ok1 := true 27 ok2 := true 28 u1 := 0 29 u2 := 0.0 30 c.SendResult(fmt.Sprintf("f: %v", factor)) 31 for { 32 select { 33 case u1, ok1 = <-upload1: 34 if ok1 { 35 c.SendResult(fmt.Sprintf("u1: %v", u1)) 36 } 37 case u2, ok2 = <-upload2: 38 if ok2 { 39 c.SendResult(fmt.Sprintf("u2: %v", u2)) 40 } 41 case <-c.Hub.Context().Done(): 42 return 43 } 44 if !ok1 && !ok2 { 45 c.SendResult("Finished") 46 return 47 } 48 } 49 } 50 51 //noinspection GoUnusedParameter 52 func (c *clientStreamHub) UploadPanic(upload <-chan string) { 53 c.SendResult("Panic()") 54 panic("Don't panic!") 55 } 56 57 func (c *clientStreamHub) UploadInt(upload <-chan int) { 58 c.SendResult("UploadInt()") 59 for { 60 <-upload 61 } 62 } 63 64 //noinspection GoUnusedParameter 65 func (c *clientStreamHub) UploadHang(upload <-chan int) { 66 c.SendResult("UploadHang()") 67 // wait forever 68 select {} 69 } 70 71 func (c *clientStreamHub) UploadParamTypes( 72 u1 <-chan int8, u2 <-chan int16, u3 <-chan int32, u4 <-chan int64, 73 u5 <-chan uint, u6 <-chan uint8, u7 <-chan uint16, u8 <-chan uint32, u9 <-chan uint64, 74 u10 <-chan float32, u11 <-chan float64, 75 u12 <-chan string) { 76 for count := 0; count < 12; { 77 select { 78 case <-u1: 79 count++ 80 case <-u2: 81 count++ 82 case <-u3: 83 count++ 84 case <-u4: 85 count++ 86 case <-u5: 87 count++ 88 case <-u6: 89 count++ 90 case <-u7: 91 count++ 92 case <-u8: 93 count++ 94 case <-u9: 95 count++ 96 case <-u10: 97 count++ 98 case <-u11: 99 count++ 100 case <-u12: 101 count++ 102 case <-time.After(100 * time.Millisecond): 103 c.SendResult("timed out") 104 return 105 } 106 } 107 c.SendResult("UPT finished") 108 } 109 110 func (c *clientStreamHub) UploadArray(u <-chan []int) { 111 for r := range u { 112 c.SendResult(fmt.Sprintf("received %v", r)) 113 } 114 c.SendResult("UploadArray finished") 115 } 116 117 func (c *clientStreamHub) UploadError(u <-chan error) { 118 c.SendResult("UploadError start") 119 for range u { 120 } 121 } 122 123 func (c *clientStreamHub) UploadErrorArray(u <-chan []error) { 124 c.SendResult("UploadErrorArray start") 125 for range u { 126 } 127 } 128 129 type resultReceiver struct { 130 ch chan string 131 } 132 133 func (r *resultReceiver) OnResult(result string) { 134 r.ch <- result 135 } 136 137 func makeStreamingClientAndServer(options ...func(Party) error) (Client, *resultReceiver, context.CancelFunc) { 138 cliConn, srvConn := newClientServerConnections() 139 ctx, cancel := context.WithCancel(context.Background()) 140 if options == nil { 141 options = make([]func(Party) error, 0) 142 } 143 options = append(options, SimpleHubFactory(&clientStreamHub{}), testLoggerOption()) 144 server, _ := NewServer(ctx, options...) 145 go func() { _ = server.Serve(srvConn) }() 146 receiver := &resultReceiver{ch: make(chan string, 1)} 147 client, _ := NewClient(ctx, WithConnection(cliConn), WithReceiver(receiver), testLoggerOption()) 148 client.Start() 149 return client, receiver, cancel 150 } 151 152 var _ = Describe("ClientStreaming", func() { 153 154 Describe("Simple stream invocation", func() { 155 Context("When invoked by the client with streamIds", func() { 156 It("should be invoked on the server, and receive stream items until the caller sends a completion", func(done Done) { 157 client, receiver, cancel := makeStreamingClientAndServer() 158 ch1 := make(chan int, 1) 159 ch2 := make(chan float64, 1) 160 client.PushStreams("UploadStreamSmoke", ch1, 5, ch2) 161 go func() { 162 go func() { 163 for i := 0; i < 10; i++ { 164 ch1 <- i 165 time.Sleep(100 * time.Millisecond) 166 } 167 }() 168 go func() { 169 for i := 5; i < 10; i++ { 170 ch2 <- float64(i) * 7.1 171 time.Sleep(200 * time.Millisecond) 172 } 173 }() 174 }() 175 u1 := 0 176 u2 := 5 177 loop: 178 for { 179 select { 180 case r := <-receiver.ch: 181 switch { 182 case strings.HasPrefix(r, "u1"): 183 Expect(r).To(Equal(fmt.Sprintf("u1: %v", u1))) 184 u1++ 185 if u1 == 10 { 186 close(ch1) 187 } 188 case strings.HasPrefix(r, "u2"): 189 Expect(r).To(Equal(fmt.Sprintf("u2: %v", float64(u2)*7.1))) 190 u2++ 191 if u2 == 10 { 192 close(ch2) 193 } 194 case r == "Finished": 195 break loop 196 } 197 } 198 } 199 cancel() 200 close(done) 201 }, 2.0) 202 }) 203 }) 204 205 Describe("Stream invocation", func() { 206 Context(" When stream item type that could not be converted to the receiving hub methods channel type is sent", func() { 207 for i := 0; i < 1; i++ { 208 j := i 209 It(fmt.Sprintf("should end the connection with an error %v", j), func(done Done) { 210 client, _, cancel := makeStreamingClientAndServer() 211 ch := make(chan string, 1) 212 client.PushStreams("UploadInt", ch) 213 ch <- fmt.Sprintf("ShouldBeInt %v", j) 214 <-client.WaitForState(context.Background(), ClientClosed) 215 Expect(client.Err()).To(HaveOccurred()) 216 cancel() 217 close(done) 218 }, 2.0) 219 } 220 }) 221 }) 222 223 Describe("Stream invocation to the StreamBufferCapacity", func() { 224 Context(" When hub method is called as streaming receiver but does not handle channel input", func() { 225 It("should send a completion with error, but wait at least ChanReceiveTimeout", func(done Done) { 226 client, _, cancel := makeStreamingClientAndServer(ChanReceiveTimeout(500*time.Millisecond), 227 StreamBufferCapacity(5)) 228 <-client.WaitForState(context.Background(), ClientConnected) 229 ch := make(chan int, 1) 230 client.PushStreams("UploadHang", ch) 231 // connect() sets StreamBufferCapacity to 5, so 6 messages should be send to make it hang 232 for i := 0; i < 6; i++ { 233 ch <- i 234 } 235 sent := time.Now() 236 <-client.WaitForState(context.Background(), ClientClosed) 237 Expect(client.Err()).To(HaveOccurred()) 238 // ChanReceiveTimeout 200 ms should be over 239 Expect(time.Now().UnixNano()).To(BeNumerically(">", sent.Add(500*time.Millisecond).UnixNano())) 240 cancel() 241 close(done) 242 }) 243 }) 244 }) 245 246 Describe("Stream invocation with wrong streamId", func() { 247 Context("When invoked by the client with streamIds", func() { 248 It("should be invoked on the server, and receive stream items until the caller sends a completion. Unknown streamIds should be ignored", func(done Done) { 249 hub := &clientStreamHub{ch: make(chan string, 20)} 250 server, err := NewServer(context.TODO(), HubFactory(func() HubInterface { 251 return hub 252 }), testLoggerOption()) 253 Expect(err).NotTo(HaveOccurred()) 254 conn := newTestingConnectionForServer() 255 go func() { _ = server.Serve(conn) }() 256 257 conn.ClientSend(fmt.Sprintf( 258 `{"type":1,"invocationId":"upstream","target":"uploadstreamsmoke","arguments":[%v],"streamIds":["123","456"]}`, 5)) 259 Expect(<-hub.ch).To(Equal(fmt.Sprintf("f: %v", 5))) 260 // close the first stream 261 conn.ClientSend(`{"type":3,"invocationId":"123"}`) 262 // Send one with correct streamid 263 conn.ClientSend(fmt.Sprintf(`{"type":2,"invocationId":"456","item":%v}`, 7.1)) 264 // close the second stream 265 conn.ClientSend(`{"type":3,"invocationId":"456"}`) 266 loop: 267 for { 268 select { 269 case <-hub.ch: 270 break loop 271 case <-time.After(500 * time.Millisecond): 272 Fail("timed out") 273 } 274 } 275 // Read finished value from queue 276 <-hub.ch 277 server.cancel() 278 close(done) 279 }) 280 }) 281 }) 282 Describe("Stream invocation with wrong count of streamid", func() { 283 284 Context("When invoked by the client with to many streamIds", func() { 285 It("should end the connection with an error", func(done Done) { 286 server, conn := connect(&clientStreamHub{}) 287 conn.ClientSend(fmt.Sprintf(`{"type":1,"invocationId":"upstream","target":"uploadstreamsmoke","arguments":[%v],"streamIds":["123","456","789"]}`, 5)) 288 message := <-conn.received 289 Expect(message).To(BeAssignableToTypeOf(completionMessage{})) 290 completionMessage := message.(completionMessage) 291 Expect(completionMessage.Error).NotTo(BeNil()) 292 server.cancel() 293 close(done) 294 }, 2.0) 295 }) 296 Context("When invoked by the client with not enough streamIds", func() { 297 It("should end the connection with an error", func(done Done) { 298 server, conn := connect(&clientStreamHub{}) 299 conn.ClientSend(fmt.Sprintf(`{"type":1,"invocationId":"upstream","target":"uploadstreamsmoke","arguments":[%v],"streamIds":["123"]}`, 5)) 300 message := <-conn.received 301 Expect(message).To(BeAssignableToTypeOf(completionMessage{})) 302 completionMessage := message.(completionMessage) 303 Expect(completionMessage.Error).NotTo(BeNil()) 304 server.cancel() 305 close(done) 306 }, 2.0) 307 }) 308 }) 309 310 Describe("Panic in invoked stream client func", func() { 311 Context("When a func is invoked by the client and panics", func() { 312 It("should return a completion with error", func(done Done) { 313 client, _, cancel := makeStreamingClientAndServer() 314 client.PushStreams("UploadPanic", make(chan int)) 315 <-client.WaitForState(context.Background(), ClientClosed) 316 Expect(client.Err()).To(HaveOccurred()) 317 cancel() 318 close(done) 319 }) 320 }) 321 }) 322 Describe("Stream client with all numbertypes of channels", func() { 323 Context("When a func is invoked by the client with all number types of channels", func() { 324 It("should receive values on all of these types", func(done Done) { 325 u1 := make(chan int8, 1) 326 u2 := make(chan int16, 1) 327 u3 := make(chan int32, 1) 328 u4 := make(chan int64, 1) 329 u5 := make(chan uint, 1) 330 u6 := make(chan uint8, 1) 331 u7 := make(chan uint16, 1) 332 u8 := make(chan uint32, 1) 333 u9 := make(chan uint64, 1) 334 u10 := make(chan float32, 1) 335 u11 := make(chan float64, 1) 336 u12 := make(chan string, 1) 337 client, receiver, cancel := makeStreamingClientAndServer() 338 client.PushStreams("UploadParamTypes", u1, u2, u3, u4, u5, u6, u7, u8, u9, u10, u11, u12) 339 u1 <- 1 340 u2 <- 1 341 u3 <- 1 342 u4 <- 1 343 u5 <- 1 344 u6 <- 1 345 u7 <- 1 346 u8 <- 1 347 u9 <- 1 348 u10 <- 1.1 349 u11 <- 2.1 350 u12 <- "Some String" 351 Expect(<-receiver.ch).To(Equal("UPT finished")) 352 cancel() 353 close(done) 354 }) 355 }) 356 }) 357 358 Describe("Stream client with array channel", func() { 359 Context("When a func with an array channel is invoked by the client and stream items are send", func() { 360 It("should receive values and end after that", func(done Done) { 361 client, receiver, cancel := makeStreamingClientAndServer() 362 ch := make(chan []int) 363 client.PushStreams("UploadArray", ch) 364 ch <- []int{1, 2} 365 Expect(<-receiver.ch).To(Equal("received [1 2]")) 366 close(ch) 367 Expect(<-receiver.ch).To(Equal("UploadArray finished")) 368 cancel() 369 close(done) 370 }) 371 }) 372 }) 373 374 Describe("Client sending invalid streamitems", func() { 375 Context("When an invalid streamitem message with missing id and item is sent", func() { 376 It("should end the connection with an error", func(done Done) { 377 hub := &clientStreamHub{ch: make(chan string, 20)} 378 server, err := NewServer(context.TODO(), HubFactory(func() HubInterface { 379 return hub 380 }), testLoggerOption()) 381 Expect(err).NotTo(HaveOccurred()) 382 conn := newTestingConnectionForServer() 383 go func() { _ = server.Serve(conn) }() 384 conn.ClientSend(`{"type":1,"invocationId": "nnn","target":"uploadstreamsmoke","arguments":[5.0],"streamIds":["ff2","ggg"]}`) 385 <-hub.ch 386 <-conn.received 387 // Send invalid stream item message with missing id and item 388 conn.ClientSend(`{"type":2}`) 389 message := <-conn.received 390 Expect(message).To(BeAssignableToTypeOf(closeMessage{})) 391 Expect(message.(closeMessage).Error).NotTo(BeNil()) 392 server.cancel() 393 close(done) 394 }, 2.0) 395 }) 396 Context("When an invalid streamitem message with missing item is received", func() { 397 It("should end the connection with an error", func(done Done) { 398 hub := &clientStreamHub{ch: make(chan string, 20)} 399 server, err := NewServer(context.TODO(), HubFactory(func() HubInterface { 400 return hub 401 }), testLoggerOption()) 402 Expect(err).NotTo(HaveOccurred()) 403 conn := newTestingConnectionForServer() 404 go func() { _ = server.Serve(conn) }() 405 conn.ClientSend(`{"type":1,"invocationId": "nnn","target":"uploadstreamsmoke","arguments":[5.0],"streamIds":["ff3","ggg"]}`) 406 <-hub.ch 407 <-conn.received 408 // Send invalid stream item message with missing item 409 conn.ClientSend(`{"type":2,"InvocationId":"iii"}`) 410 message := <-conn.received 411 Expect(message).To(BeAssignableToTypeOf(closeMessage{})) 412 Expect(message.(closeMessage).Error).NotTo(BeNil()) 413 server.cancel() 414 close(done) 415 }, 2.0) 416 }) 417 Context("When an invalid streamitem message with wrong itemtype is received", func() { 418 It("should end the connection with an error", func(done Done) { 419 hub := &clientStreamHub{ch: make(chan string, 20)} 420 server, err := NewServer(context.TODO(), HubFactory(func() HubInterface { 421 return hub 422 }), testLoggerOption()) 423 Expect(err).NotTo(HaveOccurred()) 424 conn := newTestingConnectionForServer() 425 go func() { _ = server.Serve(conn) }() 426 conn.ClientSend(`{"type":1,"invocationId": "nnn","target":"uploadstreamsmoke","arguments":[5.0],"streamIds":["ff1","ggg"]}`) 427 <-hub.ch 428 <-conn.received 429 // Send invalid stream item message 430 conn.ClientSend(`{"type":2,"invocationId":"ff1","item":[42]}`) 431 message := <-conn.received 432 Expect(message).To(BeAssignableToTypeOf(closeMessage{})) 433 Expect(message.(closeMessage).Error).NotTo(BeNil()) 434 server.cancel() 435 close(done) 436 }, 2.0) 437 }) 438 Context("When an invalid stream item message with invalid invocation id is sent", func() { 439 It("should end the connection with an error", func(done Done) { 440 hub := &clientStreamHub{ch: make(chan string, 20)} 441 server, err := NewServer(context.TODO(), HubFactory(func() HubInterface { 442 return hub 443 }), testLoggerOption()) 444 Expect(err).NotTo(HaveOccurred()) 445 conn := newTestingConnectionForServer() 446 go func() { _ = server.Serve(conn) }() 447 conn.ClientSend(`{"type":1,"invocationId": "nnn","target":"uploadstreamsmoke","arguments":[5.0],"streamIds":["ff4","ggg"]}`) 448 <-hub.ch 449 <-conn.received 450 // Send invalid stream item message with invalid invocation id 451 conn.ClientSend(`{"type":2,"invocationId":1}`) 452 message := <-conn.received 453 Expect(message).To(BeAssignableToTypeOf(closeMessage{})) 454 Expect(message.(closeMessage).Error).NotTo(BeNil()) 455 server.cancel() 456 close(done) 457 }, 2.0) 458 }) 459 }) 460 461 Describe("Client sending invalid completion", func() { 462 Context("When an invalid completion message with missing id is sent", func() { 463 It("should end the connection with an error", func(done Done) { 464 hub := &clientStreamHub{ch: make(chan string, 20)} 465 server, err := NewServer(context.TODO(), HubFactory(func() HubInterface { 466 return hub 467 }), testLoggerOption()) 468 Expect(err).NotTo(HaveOccurred()) 469 conn := newTestingConnectionForServer() 470 go func() { _ = server.Serve(conn) }() 471 conn.ClientSend(`{"type":1,"invocationId": "nnn","target":"uploadstreamsmoke","arguments":[5.0],"streamIds":["ff5","ggg"]}`) 472 <-hub.ch 473 <-conn.received 474 // Send invalid completion message with missing id 475 conn.ClientSend(`{"type":3}`) 476 message := <-conn.received 477 Expect(message).To(BeAssignableToTypeOf(closeMessage{})) 478 Expect(message.(closeMessage).Error).NotTo(BeNil()) 479 server.cancel() 480 close(done) 481 }, 2.0) 482 }) 483 484 Context("When an invalid completion message with unknown id is sent", func() { 485 It("should end the connection with an error", func(done Done) { 486 hub := &clientStreamHub{ch: make(chan string, 20)} 487 server, err := NewServer(context.TODO(), HubFactory(func() HubInterface { 488 return hub 489 }), testLoggerOption()) 490 Expect(err).NotTo(HaveOccurred()) 491 conn := newTestingConnectionForServer() 492 go func() { _ = server.Serve(conn) }() 493 conn.ClientSend(`{"type":1,"invocationId":"nnn","target":"uploadstreamsmoke","arguments":[5.0],"streamIds":["ff6","ggg"]}`) 494 <-hub.ch 495 <-conn.received 496 // Send invalid completion message with unknown id 497 conn.ClientSend(`{"type":3,"invocationId":"qqq"}`) 498 message := <-conn.received 499 Expect(message).To(BeAssignableToTypeOf(closeMessage{})) 500 Expect(message.(closeMessage).Error).NotTo(BeNil()) 501 server.cancel() 502 close(done) 503 }, 2.0) 504 }) 505 506 Context("When an completion message with an result is sent before any stream item was received", func() { 507 It("should take the result as stream item and consider the streaming as finished", func(done Done) { 508 hub := &clientStreamHub{ch: make(chan string, 20)} 509 server, err := NewServer(context.TODO(), HubFactory(func() HubInterface { 510 return hub 511 }), testLoggerOption()) 512 Expect(err).NotTo(HaveOccurred()) 513 conn := newTestingConnectionForServer() 514 go func() { _ = server.Serve(conn) }() 515 conn.ClientSend(`{"type":1,"invocationId":"UPA","target":"uploadarray","streamIds":["aaa"]}`) 516 conn.ClientSend(`{"type":3,"invocationId":"aaa","result":[1,2]}`) 517 Expect(<-hub.ch).To(Equal("received [1 2]")) 518 Expect(<-hub.ch).To(Equal("UploadArray finished")) 519 server.cancel() 520 close(done) 521 }) 522 }) 523 524 Context("When an invalid completion message is sent", func() { 525 It("should close the connection with an error", func(done Done) { 526 hub := &clientStreamHub{ch: make(chan string, 20)} 527 server, err := NewServer(context.TODO(), HubFactory(func() HubInterface { 528 return hub 529 }), testLoggerOption()) 530 Expect(err).NotTo(HaveOccurred()) 531 conn := newTestingConnectionForServer() 532 go func() { _ = server.Serve(conn) }() 533 conn.ClientSend(`{"type":1,"invocationId":"UPA","target":"uploadarray","streamIds":["aaa"]}`) 534 conn.ClientSend(`{"type":3,"invocationId":1}`) 535 message := <-conn.received 536 Expect(message).To(BeAssignableToTypeOf(closeMessage{})) 537 Expect(message.(closeMessage).Error).NotTo(BeNil()) 538 server.cancel() 539 close(done) 540 }, 2.0) 541 }) 542 543 Context("When an completion message with an result is sent after a stream item was received", func() { 544 It("should end the connection with an error", func(done Done) { 545 hub := &clientStreamHub{ch: make(chan string, 20)} 546 server, err := NewServer(context.TODO(), HubFactory(func() HubInterface { 547 return hub 548 }), testLoggerOption()) 549 Expect(err).NotTo(HaveOccurred()) 550 conn := newTestingConnectionForServer() 551 go func() { _ = server.Serve(conn) }() 552 conn.ClientSend(`{"type":1,"invocationId":"nnn","target":"uploadstreamsmoke","arguments":[5.0],"streamIds":["fff","ggg"]}`) 553 <-hub.ch 554 <-conn.received 555 // Send stream item 556 conn.ClientSend(`{"type":2,"invocationId":"fff","item":1}`) 557 <-hub.ch 558 <-conn.received 559 // Send completion message with result 560 conn.ClientSend(`{"type":3,"invocationId":"fff","result":1}`) 561 message := <-conn.received 562 Expect(message).To(BeAssignableToTypeOf(closeMessage{})) 563 Expect(message.(closeMessage).Error).NotTo(BeNil()) 564 server.cancel() 565 close(done) 566 }) 567 }) 568 569 Context("When the stream item type could not converted to the hub methods receive channel type", func() { 570 It("should end the connection with an error", func(done Done) { 571 hub := &clientStreamHub{ch: make(chan string, 20)} 572 server, err := NewServer(context.TODO(), HubFactory(func() HubInterface { 573 return hub 574 }), testLoggerOption()) 575 Expect(err).NotTo(HaveOccurred()) 576 conn := newTestingConnectionForServer() 577 go func() { _ = server.Serve(conn) }() 578 conn.ClientSend(`{"type":1,"invocationId":"nnn","target":"uploaderror","streamIds":["eee"]}`) 579 <-hub.ch 580 <-conn.received 581 // Send stream item 582 conn.ClientSend(`{"type":2,"invocationId":"eee","item":1}`) 583 message := <-conn.received 584 Expect(message).To(BeAssignableToTypeOf(closeMessage{})) 585 Expect(message.(closeMessage).Error).NotTo(BeNil()) 586 server.cancel() 587 close(done) 588 }) 589 }) 590 591 Context("When the stream item array type could not converted to the hub methods receive channel array type", func() { 592 It("should end the connection with an error", func(done Done) { 593 hub := &clientStreamHub{ch: make(chan string, 20)} 594 server, err := NewServer(context.TODO(), HubFactory(func() HubInterface { 595 return hub 596 }), testLoggerOption()) 597 Expect(err).NotTo(HaveOccurred()) 598 conn := newTestingConnectionForServer() 599 go func() { _ = server.Serve(conn) }() 600 conn.ClientSend(`{"type":1,"invocationId":"nnn","target":"uploaderrorarray","streamIds":["aeae"]}`) 601 <-hub.ch 602 <-conn.received 603 // Send stream item 604 conn.ClientSend(`{"type":2,"invocationId":"aeae","item":[7,8]}`) 605 message := <-conn.received 606 Expect(message).To(BeAssignableToTypeOf(closeMessage{})) 607 Expect(message.(closeMessage).Error).NotTo(BeNil()) 608 server.cancel() 609 close(done) 610 }) 611 }) 612 }) 613 })