github.com/philippseith/signalr@v0.6.3/client_test.go (about) 1 package signalr 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "io" 8 "strings" 9 "sync/atomic" 10 "time" 11 12 . "github.com/onsi/ginkgo" 13 . "github.com/onsi/gomega" 14 ) 15 16 type pipeConnection struct { 17 reader io.Reader 18 writer io.Writer 19 timeout time.Duration 20 fail atomic.Value 21 connectionID string 22 } 23 24 func (pc *pipeConnection) Context() context.Context { 25 return context.TODO() 26 } 27 28 func (pc *pipeConnection) Read(p []byte) (n int, err error) { 29 if err, ok := pc.fail.Load().(error); ok { 30 return 0, err 31 } 32 return pc.reader.Read(p) 33 } 34 35 func (pc *pipeConnection) Write(p []byte) (n int, err error) { 36 if err, ok := pc.fail.Load().(error); ok { 37 return 0, err 38 } 39 return pc.writer.Write(p) 40 } 41 42 func (pc *pipeConnection) ConnectionID() string { 43 return pc.connectionID 44 } 45 46 func (pc *pipeConnection) SetConnectionID(cID string) { 47 pc.connectionID = cID 48 } 49 50 func (pc *pipeConnection) SetTimeout(timeout time.Duration) { 51 pc.timeout = timeout 52 } 53 54 func (pc *pipeConnection) Timeout() time.Duration { 55 return pc.timeout 56 } 57 58 func newClientServerConnections() (cliConn *pipeConnection, svrConn *pipeConnection) { 59 cliReader, srvWriter := io.Pipe() 60 srvReader, cliWriter := io.Pipe() 61 cliConn = &pipeConnection{ 62 reader: cliReader, 63 writer: cliWriter, 64 connectionID: "X", 65 } 66 svrConn = &pipeConnection{ 67 reader: srvReader, 68 writer: srvWriter, 69 connectionID: "X", 70 } 71 return cliConn, svrConn 72 } 73 74 type simpleHub struct { 75 Hub 76 receiveStreamArg string 77 receiveStreamDone chan struct{} 78 } 79 80 func (s *simpleHub) InvokeMe(arg1 string, arg2 int) string { 81 return fmt.Sprintf("%v%v", arg1, arg2) 82 } 83 84 func (s *simpleHub) Callback(arg1 string) { 85 s.Hub.Clients().Caller().Send("OnCallback", strings.ToUpper(arg1)) 86 } 87 88 func (s *simpleHub) ReadStream(i int) chan string { 89 ch := make(chan string) 90 go func() { 91 ch <- fmt.Sprintf("A%v", i) 92 ch <- fmt.Sprintf("B%v", i) 93 ch <- fmt.Sprintf("C%v", i) 94 ch <- fmt.Sprintf("D%v", i) 95 close(ch) 96 }() 97 return ch 98 } 99 100 func (s *simpleHub) ReceiveStream(arg string, ch <-chan int) int { 101 s.receiveStreamArg = arg 102 receiveStreamChanValues := make([]int, 0) 103 for v := range ch { 104 receiveStreamChanValues = append(receiveStreamChanValues, v) 105 } 106 s.receiveStreamDone <- struct{}{} 107 return 100 108 } 109 110 func (s *simpleHub) Abort() { 111 s.Hub.Abort() 112 } 113 114 type simpleReceiver struct { 115 result atomic.Value 116 ch chan string 117 } 118 119 func (s *simpleReceiver) OnCallback(result string) { 120 s.ch <- result 121 } 122 123 var _ = Describe("Client", func() { 124 formatOption := TransferFormat("Text") 125 j := 1 126 Context("Start/Cancel", func() { 127 It("should connect to the server and then be stopped without error", func(done Done) { 128 // Create a simple server 129 server, err := NewServer(context.TODO(), SimpleHubFactory(&simpleHub{}), 130 testLoggerOption(), 131 ChanReceiveTimeout(200*time.Millisecond), 132 StreamBufferCapacity(5)) 133 Expect(err).NotTo(HaveOccurred()) 134 Expect(server).NotTo(BeNil()) 135 // Create both ends of the connection 136 cliConn, srvConn := newClientServerConnections() 137 // Start the server 138 go func() { _ = server.Serve(srvConn) }() 139 // Create the Client 140 ctx, cancelClient := context.WithCancel(context.Background()) 141 clientConn, err := NewClient(ctx, WithConnection(cliConn), testLoggerOption(), formatOption) 142 Expect(err).NotTo(HaveOccurred()) 143 Expect(clientConn).NotTo(BeNil()) 144 // Start it 145 clientConn.Start() 146 Expect(<-clientConn.WaitForState(context.Background(), ClientConnected)).NotTo(HaveOccurred()) 147 cancelClient() 148 server.cancel() 149 close(done) 150 }, 1.0) 151 }) 152 Context("Invoke", func() { 153 It("should invoke a server method and return the result", func(done Done) { 154 _, client, _, cancelClient := getTestBed(&simpleReceiver{}, formatOption) 155 r := <-client.Invoke("InvokeMe", "A", 1) 156 Expect(r.Value).To(Equal("A1")) 157 Expect(r.Error).NotTo(HaveOccurred()) 158 cancelClient() 159 close(done) 160 }, 2.0) 161 It("should invoke a server method and return the error when arguments don't match", func(done Done) { 162 _, client, _, cancelClient := getTestBed(&simpleReceiver{}, formatOption) 163 r := <-client.Invoke("InvokeMe", "A", "B") 164 Expect(r.Error).To(HaveOccurred()) 165 cancelClient() 166 close(done) 167 }, 2.0) 168 It("should invoke a server method and return the result after a bad invocation", func(done Done) { 169 _, client, _, cancelClient := getTestBed(&simpleReceiver{}, formatOption) 170 client.Invoke("InvokeMe", "A", "B") 171 r := <-client.Invoke("InvokeMe", "A", 1) 172 Expect(r.Value).To(Equal("A1")) 173 Expect(r.Error).NotTo(HaveOccurred()) 174 cancelClient() 175 close(done) 176 }, 2.0) 177 It(fmt.Sprintf("should return an error when the connection fails: invocation %v", j), func(done Done) { 178 _, client, cliConn, cancelClient := getTestBed(&simpleReceiver{}, formatOption) 179 cliConn.fail.Store(errors.New("fail")) 180 r := <-client.Invoke("InvokeMe", "A", 1) 181 Expect(r.Error).To(HaveOccurred()) 182 cancelClient() 183 close(done) 184 }, 1.0) 185 }) 186 Context("Send", func() { 187 It("should invoke a server method and get the result via callback", func(done Done) { 188 receiver := &simpleReceiver{} 189 _, client, _, cancelClient := getTestBed(receiver, formatOption) 190 receiver.result.Store("x") 191 errCh := client.Send("Callback", "low") 192 ch := make(chan string, 1) 193 go func() { 194 for { 195 if result, ok := receiver.result.Load().(string); ok { 196 if result != "x" { 197 ch <- result 198 break 199 } 200 } 201 } 202 }() 203 select { 204 case val := <-ch: 205 Expect(val).To(Equal("LOW")) 206 case err := <-errCh: 207 Expect(err).NotTo(HaveOccurred()) 208 } 209 cancelClient() 210 close(done) 211 }, 1.0) 212 It("should invoke a server method and return the error when arguments don't match", func(done Done) { 213 receiver := &simpleReceiver{} 214 _, client, _, cancelClient := getTestBed(receiver, formatOption) 215 receiver.result.Store("x") 216 errCh := client.Send("Callback", 1) 217 ch := make(chan string, 1) 218 go func() { 219 for { 220 if result, ok := receiver.result.Load().(string); ok { 221 if result != "x" { 222 ch <- result 223 break 224 } 225 } 226 } 227 }() 228 select { 229 case val := <-ch: 230 Fail(fmt.Sprintf("Value %v should not be returned", val)) 231 case err := <-errCh: 232 Expect(err).To(HaveOccurred()) 233 } 234 // Stop the above go func 235 receiver.result.Store("Stop") 236 cancelClient() 237 close(done) 238 }, 2.0) 239 It(fmt.Sprintf("should return an error when the connection fails: invocation %v", j), func(done Done) { 240 _, client, cliConn, cancelClient := getTestBed(&simpleReceiver{}, formatOption) 241 cliConn.fail.Store(errors.New("fail")) 242 err := <-client.Send("Callback", 1) 243 Expect(err).To(HaveOccurred()) 244 cancelClient() 245 close(done) 246 }, 1.0) 247 }) 248 Context("PullStream", func() { 249 j := 1 250 It("should pull a stream from the server", func(done Done) { 251 _, client, _, cancelClient := getTestBed(&simpleReceiver{}, formatOption) 252 ch := client.PullStream("ReadStream", j) 253 values := make([]interface{}, 0) 254 for r := range ch { 255 Expect(r.Error).NotTo(HaveOccurred()) 256 values = append(values, r.Value) 257 } 258 Expect(values).To(Equal([]interface{}{ 259 fmt.Sprintf("A%v", j), 260 fmt.Sprintf("B%v", j), 261 fmt.Sprintf("C%v", j), 262 fmt.Sprintf("D%v", j), 263 })) 264 cancelClient() 265 close(done) 266 }) 267 It("should return no error when the method returns no stream but a single result", func(done Done) { 268 _, client, _, cancelClient := getTestBed(&simpleReceiver{}, formatOption) 269 r := <-client.PullStream("InvokeMe", "A", 1) 270 Expect(r.Error).NotTo(HaveOccurred()) 271 Expect(r.Value).To(Equal("A1")) 272 cancelClient() 273 close(done) 274 }, 2.0) 275 It("should return an error when the method returns no result", func(done Done) { 276 _, client, _, cancelClient := getTestBed(&simpleReceiver{}, formatOption) 277 r := <-client.PullStream("Callback", "A") 278 Expect(r.Error).To(HaveOccurred()) 279 cancelClient() 280 close(done) 281 }, 2.0) 282 It("should return an error when the method does not exist on the server", func(done Done) { 283 _, client, _, cancelClient := getTestBed(&simpleReceiver{}, formatOption) 284 r := <-client.PullStream("ReadStream2") 285 Expect(r.Error).To(HaveOccurred()) 286 cancelClient() 287 close(done) 288 }, 2.0) 289 It("should return an error when the method arguments are not matching", func(done Done) { 290 _, client, _, cancelClient := getTestBed(&simpleReceiver{}, formatOption) 291 r := <-client.PullStream("ReadStream", "A", 1) 292 Expect(r.Error).To(HaveOccurred()) 293 cancelClient() 294 close(done) 295 }, 2.0) 296 It("should return an error when the connection fails", func(done Done) { 297 _, client, cliConn, cancelClient := getTestBed(&simpleReceiver{}, formatOption) 298 cliConn.fail.Store(errors.New("fail")) 299 r := <-client.PullStream("ReadStream") 300 Expect(r.Error).To(HaveOccurred()) 301 cancelClient() 302 close(done) 303 }, 2.0) 304 }) 305 Context("PushStreams", func() { 306 var cliConn *pipeConnection 307 var srvConn *pipeConnection 308 var client Client 309 var cancelClient context.CancelFunc 310 var server Server 311 hub := &simpleHub{} 312 BeforeEach(func(done Done) { 313 hub.receiveStreamDone = make(chan struct{}, 1) 314 server, _ = NewServer(context.TODO(), HubFactory(func() HubInterface { return hub }), 315 testLoggerOption(), 316 ChanReceiveTimeout(200*time.Millisecond), 317 StreamBufferCapacity(5)) 318 // Create both ends of the connection 319 cliConn, srvConn = newClientServerConnections() 320 // Start the server 321 go func() { _ = server.Serve(srvConn) }() 322 // Create the Client 323 receiver := &simpleReceiver{} 324 var ctx context.Context 325 ctx, cancelClient = context.WithCancel(context.Background()) 326 client, _ = NewClient(ctx, WithConnection(cliConn), WithReceiver(receiver), testLoggerOption(), formatOption) 327 // Start it 328 client.Start() 329 Expect(<-client.WaitForState(context.Background(), ClientConnected)).NotTo(HaveOccurred()) 330 close(done) 331 }, 2.0) 332 AfterEach(func(done Done) { 333 cancelClient() 334 server.cancel() 335 close(done) 336 }, 2.0) 337 338 It("should push a stream to the server", func(done Done) { 339 ch := make(chan int, 1) 340 r := client.PushStreams("ReceiveStream", "test", ch) 341 go func(ch chan int) { 342 for i := 1; i < 5; i++ { 343 ch <- i 344 } 345 close(ch) 346 }(ch) 347 <-hub.receiveStreamDone 348 ir := <-r 349 Expect(ir.Error).To(BeNil()) 350 Expect(ir.Value).To(Equal(float64(100))) 351 Expect(hub.receiveStreamArg).To(Equal("test")) 352 cancelClient() 353 close(done) 354 }, 1.0) 355 356 It("should return an error when the connection fails", func(done Done) { 357 cliConn.fail.Store(errors.New("fail")) 358 ch := make(chan int, 1) 359 ir := <-client.PushStreams("ReceiveStream", "test", ch) 360 Expect(ir.Error).To(HaveOccurred()) 361 cancelClient() 362 close(done) 363 }, 1.0) 364 }) 365 366 Context("Reconnect", func() { 367 var cliConn *pipeConnection 368 var srvConn *pipeConnection 369 var client Client 370 var cancelClient context.CancelFunc 371 var server Server 372 hub := &simpleHub{} 373 BeforeEach(func(done Done) { 374 hub.receiveStreamDone = make(chan struct{}, 1) 375 server, _ = NewServer(context.TODO(), HubFactory(func() HubInterface { return hub }), 376 testLoggerOption(), 377 ChanReceiveTimeout(200*time.Millisecond), 378 StreamBufferCapacity(5)) 379 // Create both ends of the connection 380 cliConn, srvConn = newClientServerConnections() 381 // Start the server 382 go func() { _ = server.Serve(srvConn) }() 383 // Create the Client 384 receiver := &simpleReceiver{} 385 var ctx context.Context 386 ctx, cancelClient = context.WithCancel(context.Background()) 387 client, _ = NewClient(ctx, WithConnection(cliConn), WithReceiver(receiver), testLoggerOption(), formatOption) 388 // Start it 389 client.Start() 390 Expect(<-client.WaitForState(context.Background(), ClientConnected)).NotTo(HaveOccurred()) 391 close(done) 392 }, 2.0) 393 AfterEach(func(done Done) { 394 cancelClient() 395 server.cancel() 396 close(done) 397 }, 2.0) 398 // TODO 399 }) 400 }) 401 402 func getTestBed(receiver interface{}, formatOption func(Party) error) (Server, Client, *pipeConnection, context.CancelFunc) { 403 server, _ := NewServer(context.TODO(), SimpleHubFactory(&simpleHub{}), 404 testLoggerOption(), 405 ChanReceiveTimeout(200*time.Millisecond), 406 StreamBufferCapacity(5)) 407 // Create both ends of the connection 408 cliConn, srvConn := newClientServerConnections() 409 // Start the server 410 go func() { _ = server.Serve(srvConn) }() 411 // Create the Client 412 var ctx context.Context 413 ctx, cancelClient := context.WithCancel(context.Background()) 414 client, _ := NewClient(ctx, WithConnection(cliConn), WithReceiver(receiver), testLoggerOption(), formatOption) 415 // Start it 416 client.Start() 417 return server, client, cliConn, cancelClient 418 }