func TestAddAndRemoveRoutes(t *testing.T) { a := assert.New(t) // Given a Multiplexer router := NewPubSubRouter().Go() // when i add two routes in the same path channel := make(chan MsgAndRoute, chanSize) routeBlah1 := router.Subscribe(NewRoute("/blah", channel, "appid01", "user01")) routeBlah2 := router.Subscribe(NewRoute("/blah", channel, "appid02", "user01")) // and one route in another path routeFoo := router.Subscribe(NewRoute("/foo", channel, "appid01", "user01")) // then // the routes are stored a.Equal(2, len(router.routes[guble.Path("/blah")])) a.True(routeBlah1.equals(router.routes[guble.Path("/blah")][0])) a.True(routeBlah2.equals(router.routes[guble.Path("/blah")][1])) a.Equal(1, len(router.routes[guble.Path("/foo")])) a.True(routeFoo.equals(router.routes[guble.Path("/foo")][0])) // WHEN i remove routes router.Unsubscribe(routeBlah1) router.Unsubscribe(routeFoo) // then they are gone a.Equal(1, len(router.routes[guble.Path("/blah")])) a.True(routeBlah2.equals(router.routes[guble.Path("/blah")][0])) a.Nil(router.routes[guble.Path("/foo")]) }
func TestAnIncommingMessageIsNotAllowed(t *testing.T) { defer initCtrl(t)() wsconn, pubSubSource, messageSink, messageStore := createDefaultMocks([]string{}) tam := NewTestAccessManager() handler := NewWSHandler(pubSubSource, messageSink, messageStore, wsconn, "testuser", AccessManager(tam)) go func() { handler.Start() }() time.Sleep(time.Millisecond * 2) handler.sendChannel <- aTestMessage.Bytes() time.Sleep(time.Millisecond * 2) //nothing shall have been sent //now allow tam.allow("testuser", guble.Path("/foo")) wsconn.EXPECT().Send(aTestMessage.Bytes()) time.Sleep(time.Millisecond * 2) handler.sendChannel <- aTestMessage.Bytes() time.Sleep(time.Millisecond * 2) }
// Parses the info in the command func NewReceiverFromCmd(applicationId string, cmd *guble.Cmd, sendChannel chan []byte, messageSouce PubSubSource, messageStore store.MessageStore) (*Receiver, error) { var err error rec := &Receiver{ applicationId: applicationId, sendChannel: sendChannel, messageSouce: messageSouce, messageStore: messageStore, cancelChannel: make(chan bool, 1), enableNotifications: true, } if len(cmd.Arg) == 0 || cmd.Arg[0] != '/' { return nil, fmt.Errorf("command requires at least a path argument, but non given") } args := strings.SplitN(cmd.Arg, " ", 3) rec.path = guble.Path(args[0]) if len(args) > 1 { rec.doFetch = true rec.startId, err = strconv.ParseInt(args[1], 10, 64) if err != nil { return nil, fmt.Errorf("startid has to be empty or int, but was %q: %v", args[1], err) } } rec.doSubscription = true if len(args) > 2 { rec.doSubscription = false rec.maxCount, err = strconv.Atoi(args[2]) if err != nil { return nil, fmt.Errorf("maxCount has to be empty or int, but was %q: %v", args[1], err) } } return rec, nil }
func Test_MessageEntry_MessagesIsStored_And_GetsCorrectParameters(t *testing.T) { defer initCtrl(t)() a := assert.New(t) startTime := time.Now() msg := &guble.Message{Path: guble.Path("/topic1")} var storedMsg []byte var routedMsg *guble.Message routerMock := NewMockMessageSink(ctrl) messageEntry := NewMessageEntry(routerMock) messageStoreMock := NewMockMessageStore(ctrl) messageEntry.SetMessageStore(messageStoreMock) messageStoreMock.EXPECT().StoreTx("topic1", gomock.Any()). Do(func(topic string, callback func(msgId uint64) []byte) { storedMsg = callback(uint64(42)) }) routerMock.EXPECT().HandleMessage(gomock.Any()).Do(func(msg *guble.Message) { routedMsg = msg a.Equal(uint64(42), msg.Id) t, e := time.Parse(time.RFC3339, msg.PublishingTime) // publishing time a.NoError(e) a.True(t.After(startTime.Add(-1 * time.Second))) a.True(t.Before(time.Now().Add(time.Second))) }) messageEntry.HandleMessage(msg) a.Equal(routedMsg.Bytes(), storedMsg) }
func NewRoute(path string, channel chan MsgAndRoute, applicationId string, userId string) *Route { return &Route{ Path: guble.Path(path), C: channel, UserId: userId, ApplicationId: applicationId, } }
func Test_WSHandler_SubscribeAndUnsubscribe(t *testing.T) { defer initCtrl(t)() a := assert.New(t) messages := []string{"+ /foo", "+ /bar", "- /foo"} wsconn, pubSubSource, messageSink, messageStore := createDefaultMocks(messages) pubSubSource.EXPECT().Subscribe(routeMatcher{"/foo"}).Return(nil) wsconn.EXPECT().Send([]byte("#" + guble.SUCCESS_SUBSCRIBED_TO + " /foo")) pubSubSource.EXPECT().Subscribe(routeMatcher{"/bar"}).Return(nil) wsconn.EXPECT().Send([]byte("#" + guble.SUCCESS_SUBSCRIBED_TO + " /bar")) pubSubSource.EXPECT().Unsubscribe(routeMatcher{"/foo"}) wsconn.EXPECT().Send([]byte("#" + guble.SUCCESS_CANCELED + " /foo")) wshandler := runNewWsHandler(wsconn, pubSubSource, messageSink, messageStore) a.Equal(1, len(wshandler.receiver)) a.Equal(guble.Path("/bar"), wshandler.receiver[guble.Path("/bar")].path) }
func (srv *WSHandler) handleCancelCmd(cmd *guble.Cmd) { if len(cmd.Arg) == 0 { srv.sendError(guble.ERROR_BAD_REQUEST, "- command requires a path argument, but non given") return } path := guble.Path(cmd.Arg) rec, exist := srv.receiver[path] if exist { rec.Stop() delete(srv.receiver, path) } }
func TestSendMessageWirthPublisherMessageId(t *testing.T) { defer initCtrl(t)() // given: a send command with PublisherMessageId commands := []string{"> /path 42"} wsconn, pubSubSource, messageSink, messageStore := createDefaultMocks(commands) messageSink.EXPECT().HandleMessage(gomock.Any()).Do(func(msg *guble.Message) { assert.Equal(t, guble.Path("/path"), msg.Path) assert.Equal(t, "42", msg.PublisherMessageId) }) wsconn.EXPECT().Send([]byte("#send 42")) runNewWsHandler(wsconn, pubSubSource, messageSink, messageStore) }
func (api *RestMessageApi) PostMessage(w http.ResponseWriter, r *http.Request, params httprouter.Params) { body, err := ioutil.ReadAll(r.Body) if err != nil { http.Error(w, `Can not read body`, http.StatusBadRequest) return } msg := &guble.Message{ Path: guble.Path(params.ByName(`topic`)), Body: body, PublisherUserId: q(r, `userId`), PublisherApplicationId: xid.New().String(), PublisherMessageId: q(r, `messageId`), HeaderJson: headersToJson(r.Header), } api.MessageSink.HandleMessage(msg) }
func (srv *WSHandler) handleSendCmd(cmd *guble.Cmd) { guble.Debug("sending %v", string(cmd.Bytes())) if len(cmd.Arg) == 0 { srv.sendError(guble.ERROR_BAD_REQUEST, "send command requires a path argument, but non given") return } args := strings.SplitN(cmd.Arg, " ", 2) msg := &guble.Message{ Path: guble.Path(args[0]), PublisherApplicationId: srv.applicationId, PublisherUserId: srv.userId, HeaderJson: cmd.HeaderJson, Body: cmd.Body, } if len(args) == 2 { msg.PublisherMessageId = args[1] } srv.messageSink.HandleMessage(msg) srv.sendOK(guble.SUCCESS_SEND, msg.PublisherMessageId) }
func Test_Receiver_Fetch_Subscribe_Fetch_Subscribe(t *testing.T) { defer initCtrl(t)() a := assert.New(t) rec, msgChannel, routerMock, messageStore, err := aMockedReceiver("/foo 0") a.NoError(err) // fetch first, starting at 0 fetch_first1 := messageStore.EXPECT().Fetch(gomock.Any()).Do(func(r store.FetchRequest) { go func() { a.Equal("foo", r.Partition) a.Equal(1, r.Direction) a.Equal(uint64(0), r.StartId) a.Equal(int(math.MaxInt32), r.Count) r.StartCallback <- 2 r.MessageC <- store.MessageAndId{Id: uint64(1), Message: []byte("fetch_first1-a")} r.MessageC <- store.MessageAndId{Id: uint64(2), Message: []byte("fetch_first1-b")} close(r.MessageC) }() }) // there is a gap between fetched and max id messageId1 := messageStore.EXPECT().DoInTx(gomock.Any(), gomock.Any()). Do(func(partition string, callback func(maxMessageId uint64) error) { callback(uint64(3)) }).Return(unread_messages_available) messageId1.After(fetch_first1) // fetch again, starting at 3, because, there is still a gap fetch_first2 := messageStore.EXPECT().Fetch(gomock.Any()).Do(func(r store.FetchRequest) { go func() { a.Equal("foo", r.Partition) a.Equal(1, r.Direction) a.Equal(uint64(3), r.StartId) a.Equal(int(math.MaxInt32), r.Count) r.StartCallback <- 1 r.MessageC <- store.MessageAndId{Id: uint64(3), Message: []byte("fetch_first2-a")} close(r.MessageC) }() }) fetch_first2.After(messageId1) // the gap is closed messageId2 := messageStore.EXPECT().DoInTx(gomock.Any(), gomock.Any()). Do(func(partition string, callback func(maxMessageId uint64) error) { callback(uint64(3)) }) messageId2.After(fetch_first2) // subscribe subscribe := routerMock.EXPECT().Subscribe(gomock.Any()).Do(func(r *Route) { a.Equal(r.Path, guble.Path("/foo")) r.C <- MsgAndRoute{Message: &guble.Message{Id: uint64(4), Body: []byte("router-a")}, Route: r} r.C <- MsgAndRoute{Message: &guble.Message{Id: uint64(5), Body: []byte("router-b")}, Route: r} close(r.C) // emulate router close }) subscribe.After(messageId2) // router closed, so we fetch again, starting at 6 (after meesages from subscribe) fetch_after := messageStore.EXPECT().Fetch(gomock.Any()).Do(func(r store.FetchRequest) { go func() { a.Equal(uint64(6), r.StartId) a.Equal(int(math.MaxInt32), r.Count) r.StartCallback <- 1 r.MessageC <- store.MessageAndId{Id: uint64(6), Message: []byte("fetch_after-a")} close(r.MessageC) }() }) fetch_after.After(subscribe) // no gap messageId3 := messageStore.EXPECT().DoInTx(gomock.Any(), gomock.Any()). Do(func(partition string, callback func(maxMessageId uint64) error) { callback(uint64(6)) }) messageId3.After(fetch_after) // subscribe and don't send messages, // so the client has to wait until we stop subscribe2 := routerMock.EXPECT().Subscribe(gomock.Any()) subscribe2.After(messageId3) subscriptionLoopDone := make(chan bool) go func() { rec.subscriptionLoop() subscriptionLoopDone <- true }() expectMessages(a, msgChannel, "#"+guble.SUCCESS_FETCH_START+" /foo 2", "fetch_first1-a", "fetch_first1-b", "#"+guble.SUCCESS_FETCH_END+" /foo", "#"+guble.SUCCESS_FETCH_START+" /foo 1", "fetch_first2-a", "#"+guble.SUCCESS_FETCH_END+" /foo", "#"+guble.SUCCESS_SUBSCRIBED_TO+" /foo", ",4,,,,\n\nrouter-a", ",5,,,,\n\nrouter-b", "#"+guble.SUCCESS_FETCH_START+" /foo 1", "fetch_after-a", "#"+guble.SUCCESS_FETCH_END+" /foo", "#"+guble.SUCCESS_SUBSCRIBED_TO+" /foo", ) time.Sleep(time.Millisecond) routerMock.EXPECT().Unsubscribe(gomock.Any()) rec.Stop() expectMessages(a, msgChannel, "#"+guble.SUCCESS_CANCELED+" /foo", ) expectDone(a, subscriptionLoopDone) }
func getPathFromRawMessage(raw []byte) guble.Path { i := strings.Index(string(raw), ",") return guble.Path(raw[:i]) }