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  }