mirror of
https://github.com/tinode/chat.git
synced 2026-06-06 20:18:25 +00:00
1201 lines
26 KiB
Go
1201 lines
26 KiB
Go
package main
|
|
|
|
import (
|
|
"net/http"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/golang/mock/gomock"
|
|
"github.com/tinode/chat/server/auth"
|
|
"github.com/tinode/chat/server/auth/mock_auth"
|
|
"github.com/tinode/chat/server/store"
|
|
"github.com/tinode/chat/server/store/mock_store"
|
|
"github.com/tinode/chat/server/store/types"
|
|
)
|
|
|
|
func test_makeSession(uid types.Uid) *Session {
|
|
return &Session{
|
|
send: make(chan any, 10),
|
|
uid: uid,
|
|
authLvl: auth.LevelAuth,
|
|
inflightReqs: newBoundedWaitGroup(1),
|
|
ver: 22,
|
|
}
|
|
}
|
|
|
|
func TestDispatchHello(t *testing.T) {
|
|
s := &Session{
|
|
send: make(chan any, 10),
|
|
uid: types.Uid(1),
|
|
authLvl: auth.LevelAuth,
|
|
}
|
|
wg := sync.WaitGroup{}
|
|
r := responses{}
|
|
wg.Add(1)
|
|
go s.testWriteLoop(&r, &wg)
|
|
msg := &ClientComMessage{
|
|
Hi: &MsgClientHi{
|
|
Id: "123",
|
|
Version: "1",
|
|
UserAgent: "test-ua",
|
|
Lang: "en-GB",
|
|
},
|
|
}
|
|
s.dispatch(msg)
|
|
close(s.send)
|
|
wg.Wait()
|
|
if len(r.messages) != 1 {
|
|
t.Errorf("responses: expected 1, received %d.", len(r.messages))
|
|
}
|
|
resp := r.messages[0].(*ServerComMessage)
|
|
if resp == nil {
|
|
t.Fatal("Response must be ServerComMessage")
|
|
}
|
|
if resp.Ctrl != nil {
|
|
if resp.Ctrl.Code != 201 {
|
|
t.Errorf("Response code: expected 201, got %d", resp.Ctrl.Code)
|
|
}
|
|
if resp.Ctrl.Params == nil {
|
|
t.Error("Response is expected to contain params dict.")
|
|
}
|
|
} else {
|
|
t.Error("Response must contain a ctrl message.")
|
|
}
|
|
|
|
if s.lang != "en-GB" {
|
|
t.Errorf("Session language expected to be 'en-GB' vs '%s'", s.lang)
|
|
}
|
|
if s.userAgent != "test-ua" {
|
|
t.Errorf("Session UA expected to be 'test-ua' vs '%s'", s.userAgent)
|
|
}
|
|
if s.countryCode != "GB" {
|
|
t.Errorf("Country code expected to be 'GB' vs '%s'", s.countryCode)
|
|
}
|
|
if s.ver == 0 {
|
|
t.Errorf("s.ver expected 0 vs found %d", s.ver)
|
|
}
|
|
}
|
|
|
|
func verifyResponseCodes(r *responses, codes []int, t *testing.T) {
|
|
if len(r.messages) != len(codes) {
|
|
t.Errorf("responses: expected %d, received %d.", len(codes), len(r.messages))
|
|
}
|
|
for i := range codes {
|
|
resp := r.messages[i].(*ServerComMessage)
|
|
if resp == nil {
|
|
t.Fatalf("Response %d must be ServerComMessage", i)
|
|
}
|
|
if resp.Ctrl == nil {
|
|
t.Fatalf("Response %d must contain a ctrl message.", i)
|
|
}
|
|
if resp.Ctrl.Code != codes[i] {
|
|
t.Errorf("Response code: expected %d, got %d", codes[i], resp.Ctrl.Code)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDispatchInvalidVersion(t *testing.T) {
|
|
s := &Session{
|
|
send: make(chan any, 10),
|
|
uid: types.Uid(1),
|
|
authLvl: auth.LevelAuth,
|
|
}
|
|
wg := sync.WaitGroup{}
|
|
r := responses{}
|
|
wg.Add(1)
|
|
go s.testWriteLoop(&r, &wg)
|
|
msg := &ClientComMessage{
|
|
Hi: &MsgClientHi{
|
|
Id: "123",
|
|
// Invalid version string.
|
|
Version: "INVALID VERSION STRING",
|
|
},
|
|
}
|
|
s.dispatch(msg)
|
|
close(s.send)
|
|
wg.Wait()
|
|
verifyResponseCodes(&r, []int{http.StatusBadRequest}, t)
|
|
}
|
|
|
|
func TestDispatchUnsupportedVersion(t *testing.T) {
|
|
s := &Session{
|
|
send: make(chan any, 10),
|
|
uid: types.Uid(1),
|
|
authLvl: auth.LevelAuth,
|
|
}
|
|
wg := sync.WaitGroup{}
|
|
r := responses{}
|
|
wg.Add(1)
|
|
go s.testWriteLoop(&r, &wg)
|
|
msg := &ClientComMessage{
|
|
Hi: &MsgClientHi{
|
|
Id: "123",
|
|
// Invalid version string.
|
|
Version: "0.1",
|
|
},
|
|
}
|
|
s.dispatch(msg)
|
|
close(s.send)
|
|
wg.Wait()
|
|
verifyResponseCodes(&r, []int{http.StatusHTTPVersionNotSupported}, t)
|
|
}
|
|
|
|
func TestDispatchLogin(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
ss := mock_store.NewMockPersistentStorageInterface(ctrl)
|
|
aa := mock_auth.NewMockAuthHandler(ctrl)
|
|
|
|
uid := types.Uid(1)
|
|
store.Store = ss
|
|
defer func() {
|
|
store.Store = nil
|
|
ctrl.Finish()
|
|
}()
|
|
|
|
secret := "<==auth-secret==>"
|
|
authRec := &auth.Rec{
|
|
Uid: uid,
|
|
AuthLevel: auth.LevelAuth,
|
|
Tags: []string{"tag1", "tag2"},
|
|
State: types.StateOK,
|
|
}
|
|
ss.EXPECT().GetLogicalAuthHandler("basic").Return(aa)
|
|
aa.EXPECT().Authenticate([]byte(secret), gomock.Any()).Return(authRec, nil, nil)
|
|
// Token generation.
|
|
ss.EXPECT().GetLogicalAuthHandler("token").Return(aa)
|
|
token := "<==auth-token==>"
|
|
expires, _ := time.Parse(time.RFC822, "01 Jan 50 00:00 UTC")
|
|
aa.EXPECT().GenSecret(authRec).Return([]byte(token), expires, nil)
|
|
|
|
s := &Session{
|
|
send: make(chan any, 10),
|
|
authLvl: auth.LevelAuth,
|
|
ver: 16,
|
|
}
|
|
wg := sync.WaitGroup{}
|
|
r := responses{}
|
|
wg.Add(1)
|
|
go s.testWriteLoop(&r, &wg)
|
|
|
|
msg := &ClientComMessage{
|
|
Login: &MsgClientLogin{
|
|
Id: "123",
|
|
Scheme: "basic",
|
|
Secret: []byte(secret),
|
|
},
|
|
}
|
|
|
|
s.dispatch(msg)
|
|
close(s.send)
|
|
wg.Wait()
|
|
|
|
if len(r.messages) != 1 {
|
|
t.Errorf("responses: expected 1, received %d.", len(r.messages))
|
|
}
|
|
resp := r.messages[0].(*ServerComMessage)
|
|
if resp == nil {
|
|
t.Fatal("Response must be ServerComMessage")
|
|
}
|
|
if resp.Ctrl != nil {
|
|
if resp.Ctrl.Id != "123" {
|
|
t.Errorf("Response id: expected '123', found '%s'", resp.Ctrl.Id)
|
|
}
|
|
if resp.Ctrl.Code != 200 {
|
|
t.Errorf("Response code: expected 200, got %d", resp.Ctrl.Code)
|
|
}
|
|
if resp.Ctrl.Params == nil {
|
|
t.Error("Response is expected to contain params dict.")
|
|
}
|
|
p := resp.Ctrl.Params.(map[string]any)
|
|
if authToken := string(p["token"].([]byte)); authToken != token {
|
|
t.Errorf("Auth token: expected '%s', found '%s'.", token, authToken)
|
|
}
|
|
if exp := p["expires"].(time.Time); exp != expires {
|
|
t.Errorf("Token expiration: expected '%s', found '%s'.", expires, exp)
|
|
}
|
|
} else {
|
|
t.Error("Response must contain a ctrl message.")
|
|
}
|
|
}
|
|
|
|
func TestDispatchSubscribe(t *testing.T) {
|
|
uid := types.Uid(1)
|
|
s := test_makeSession(uid)
|
|
wg := sync.WaitGroup{}
|
|
r := responses{}
|
|
wg.Add(1)
|
|
go s.testWriteLoop(&r, &wg)
|
|
|
|
hub := &Hub{
|
|
join: make(chan *ClientComMessage, 10),
|
|
}
|
|
globals.hub = hub
|
|
|
|
defer func() {
|
|
globals.hub = nil
|
|
}()
|
|
|
|
msg := &ClientComMessage{
|
|
Sub: &MsgClientSub{
|
|
Id: "123",
|
|
Topic: "me",
|
|
Get: &MsgGetQuery{
|
|
What: "sub desc tags cred",
|
|
},
|
|
},
|
|
}
|
|
|
|
s.dispatch(msg)
|
|
close(s.send)
|
|
wg.Wait()
|
|
|
|
// Check we've routed the join request via the hub.
|
|
if len(r.messages) != 0 {
|
|
t.Errorf("responses: expected 0, received %d.", len(r.messages))
|
|
}
|
|
if len(hub.join) == 1 {
|
|
join := <-hub.join
|
|
if join.sess != s {
|
|
t.Error("Hub.join request: sess field expected to be the session under test.")
|
|
}
|
|
if join != msg {
|
|
t.Error("Hub.join request: subscribe message expected to be the original subscribe message.")
|
|
}
|
|
} else {
|
|
t.Errorf("Hub join messages: expected 1, received %d.", len(hub.join))
|
|
}
|
|
s.inflightReqs.Done()
|
|
}
|
|
|
|
func TestDispatchAlreadySubscribed(t *testing.T) {
|
|
uid := types.Uid(1)
|
|
s := test_makeSession(uid)
|
|
wg := sync.WaitGroup{}
|
|
r := responses{}
|
|
wg.Add(1)
|
|
go s.testWriteLoop(&r, &wg)
|
|
|
|
msg := &ClientComMessage{
|
|
Sub: &MsgClientSub{
|
|
Id: "123",
|
|
Topic: "me",
|
|
Get: &MsgGetQuery{
|
|
What: "sub desc tags cred",
|
|
},
|
|
},
|
|
}
|
|
// Pretend the session's already subscribed to topic 'me'.
|
|
s.subs = make(map[string]*Subscription)
|
|
s.subs[uid.UserId()] = &Subscription{}
|
|
|
|
s.dispatch(msg)
|
|
close(s.send)
|
|
wg.Wait()
|
|
|
|
verifyResponseCodes(&r, []int{http.StatusNotModified}, t)
|
|
}
|
|
|
|
func TestDispatchSubscribeJoinChannelFull(t *testing.T) {
|
|
uid := types.Uid(1)
|
|
s := test_makeSession(uid)
|
|
wg := sync.WaitGroup{}
|
|
r := responses{}
|
|
wg.Add(1)
|
|
go s.testWriteLoop(&r, &wg)
|
|
|
|
hub := &Hub{
|
|
// Make it unbuffered with no readers - so emit operation fails immediately.
|
|
join: make(chan *ClientComMessage),
|
|
}
|
|
globals.hub = hub
|
|
|
|
defer func() {
|
|
globals.hub = nil
|
|
}()
|
|
|
|
msg := &ClientComMessage{
|
|
Sub: &MsgClientSub{
|
|
Id: "123",
|
|
Topic: "me",
|
|
Get: &MsgGetQuery{
|
|
What: "sub desc tags cred",
|
|
},
|
|
},
|
|
}
|
|
|
|
s.dispatch(msg)
|
|
close(s.send)
|
|
wg.Wait()
|
|
|
|
verifyResponseCodes(&r, []int{http.StatusServiceUnavailable}, t)
|
|
}
|
|
|
|
func TestDispatchLeave(t *testing.T) {
|
|
uid := types.Uid(1)
|
|
s := test_makeSession(uid)
|
|
wg := sync.WaitGroup{}
|
|
r := responses{}
|
|
wg.Add(1)
|
|
go s.testWriteLoop(&r, &wg)
|
|
|
|
destUid := types.Uid(2)
|
|
topicName := uid.P2PName(destUid)
|
|
leave := make(chan *ClientComMessage, 1)
|
|
s.subs = make(map[string]*Subscription)
|
|
s.subs[topicName] = &Subscription{
|
|
done: leave,
|
|
}
|
|
|
|
msg := &ClientComMessage{
|
|
Leave: &MsgClientLeave{
|
|
Id: "123",
|
|
Topic: destUid.UserId(),
|
|
},
|
|
}
|
|
|
|
s.dispatch(msg)
|
|
close(s.send)
|
|
wg.Wait()
|
|
|
|
// Check we've routed the join request via the leave channel.
|
|
if len(r.messages) != 0 {
|
|
t.Errorf("responses: expected 0, received %d.", len(r.messages))
|
|
}
|
|
if len(leave) == 1 {
|
|
req := <-leave
|
|
if req.sess != s {
|
|
t.Error("Leave request: sess field expected to be the session under test.")
|
|
}
|
|
if req != msg {
|
|
t.Error("Leave request: leave message expected to be the original leave message.")
|
|
}
|
|
// leave request handler is expected to clean up subs.
|
|
s.delSub(topicName)
|
|
} else {
|
|
t.Errorf("Unsub messages: expected 1, received %d.", len(leave))
|
|
}
|
|
if len(s.subs) != 0 {
|
|
t.Errorf("Session subs: expected to be empty, actual size: %d", len(s.subs))
|
|
}
|
|
s.inflightReqs.Done()
|
|
}
|
|
|
|
func TestDispatchLeaveUnsubMe(t *testing.T) {
|
|
uid := types.Uid(1)
|
|
s := test_makeSession(uid)
|
|
wg := sync.WaitGroup{}
|
|
r := responses{}
|
|
wg.Add(1)
|
|
go s.testWriteLoop(&r, &wg)
|
|
|
|
s.subs = make(map[string]*Subscription)
|
|
s.subs[uid.UserId()] = &Subscription{}
|
|
|
|
msg := &ClientComMessage{
|
|
Leave: &MsgClientLeave{
|
|
Id: "123",
|
|
// Cannot unsubscribe from 'me'.
|
|
Topic: "me",
|
|
Unsub: true,
|
|
},
|
|
}
|
|
|
|
s.dispatch(msg)
|
|
close(s.send)
|
|
wg.Wait()
|
|
|
|
verifyResponseCodes(&r, []int{http.StatusForbidden}, t)
|
|
}
|
|
|
|
func TestDispatchLeaveUnknownTopic(t *testing.T) {
|
|
uid := types.Uid(1)
|
|
s := test_makeSession(uid)
|
|
wg := sync.WaitGroup{}
|
|
r := responses{}
|
|
wg.Add(1)
|
|
go s.testWriteLoop(&r, &wg)
|
|
|
|
// Session isn't subscribed to topic 'me'.
|
|
// And wants to leave it => no change.
|
|
s.subs = make(map[string]*Subscription)
|
|
|
|
msg := &ClientComMessage{
|
|
Leave: &MsgClientLeave{
|
|
Id: "123",
|
|
Topic: "me",
|
|
},
|
|
}
|
|
|
|
s.dispatch(msg)
|
|
close(s.send)
|
|
wg.Wait()
|
|
|
|
verifyResponseCodes(&r, []int{http.StatusNotModified}, t)
|
|
}
|
|
|
|
func TestDispatchLeaveUnsubFromUnknownTopic(t *testing.T) {
|
|
uid := types.Uid(1)
|
|
s := test_makeSession(uid)
|
|
wg := sync.WaitGroup{}
|
|
r := responses{}
|
|
wg.Add(1)
|
|
go s.testWriteLoop(&r, &wg)
|
|
|
|
// Session isn't subscribed to topic 'me'.
|
|
// And wants to leave & unsubscribe from it.
|
|
s.subs = make(map[string]*Subscription)
|
|
|
|
msg := &ClientComMessage{
|
|
Leave: &MsgClientLeave{
|
|
Id: "123",
|
|
Topic: "me",
|
|
Unsub: true,
|
|
},
|
|
}
|
|
|
|
s.dispatch(msg)
|
|
close(s.send)
|
|
wg.Wait()
|
|
|
|
verifyResponseCodes(&r, []int{http.StatusConflict}, t)
|
|
}
|
|
|
|
func TestDispatchPublish(t *testing.T) {
|
|
uid := types.Uid(1)
|
|
s := test_makeSession(uid)
|
|
wg := sync.WaitGroup{}
|
|
r := responses{}
|
|
wg.Add(1)
|
|
go s.testWriteLoop(&r, &wg)
|
|
|
|
destUid := types.Uid(2)
|
|
topicName := uid.P2PName(destUid)
|
|
|
|
brdcst := make(chan *ClientComMessage, 1)
|
|
s.subs = make(map[string]*Subscription)
|
|
s.subs[topicName] = &Subscription{
|
|
broadcast: brdcst,
|
|
}
|
|
|
|
testMessage := "test content"
|
|
msg := &ClientComMessage{
|
|
Pub: &MsgClientPub{
|
|
Id: "123",
|
|
Topic: destUid.UserId(),
|
|
Content: testMessage,
|
|
},
|
|
}
|
|
|
|
s.dispatch(msg)
|
|
close(s.send)
|
|
wg.Wait()
|
|
|
|
// Check we've routed the join request via the broadcast channel.
|
|
if len(r.messages) != 0 {
|
|
t.Errorf("responses: expected 0, received %d.", len(r.messages))
|
|
}
|
|
if len(brdcst) == 1 {
|
|
req := <-brdcst
|
|
if req.sess != s {
|
|
t.Error("Pub request: sess field expected to be the session under test.")
|
|
}
|
|
if req.Pub.Content != testMessage {
|
|
t.Errorf("Pub request content: expected '%s' vs '%s'.", testMessage, req.Pub.Content)
|
|
}
|
|
if req.Pub.Topic != destUid.UserId() {
|
|
t.Errorf("Pub request topic: expected '%s' vs '%s'.", destUid.UserId(), req.Pub.Topic)
|
|
}
|
|
} else {
|
|
t.Errorf("Pub messages: expected 1, received %d.", len(brdcst))
|
|
}
|
|
}
|
|
|
|
func TestDispatchPublishBroadcastChannelFull(t *testing.T) {
|
|
uid := types.Uid(1)
|
|
s := test_makeSession(uid)
|
|
wg := sync.WaitGroup{}
|
|
r := responses{}
|
|
wg.Add(1)
|
|
go s.testWriteLoop(&r, &wg)
|
|
|
|
destUid := types.Uid(2)
|
|
topicName := uid.P2PName(destUid)
|
|
|
|
// Make broadcast channel unbuffered with no reader -
|
|
// emit op will fail.
|
|
brdcst := make(chan *ClientComMessage)
|
|
s.subs = make(map[string]*Subscription)
|
|
s.subs[topicName] = &Subscription{
|
|
broadcast: brdcst,
|
|
}
|
|
|
|
testMessage := "test content"
|
|
msg := &ClientComMessage{
|
|
Pub: &MsgClientPub{
|
|
Id: "123",
|
|
Topic: destUid.UserId(),
|
|
Content: testMessage,
|
|
},
|
|
}
|
|
|
|
s.dispatch(msg)
|
|
close(s.send)
|
|
wg.Wait()
|
|
|
|
verifyResponseCodes(&r, []int{http.StatusServiceUnavailable}, t)
|
|
}
|
|
|
|
func TestDispatchPublishMissingSubcription(t *testing.T) {
|
|
uid := types.Uid(1)
|
|
s := test_makeSession(uid)
|
|
wg := sync.WaitGroup{}
|
|
r := responses{}
|
|
wg.Add(1)
|
|
go s.testWriteLoop(&r, &wg)
|
|
|
|
destUid := types.Uid(2)
|
|
|
|
// Subscription to topic missing.
|
|
s.subs = make(map[string]*Subscription)
|
|
|
|
testMessage := "test content"
|
|
msg := &ClientComMessage{
|
|
Pub: &MsgClientPub{
|
|
Id: "123",
|
|
Topic: destUid.UserId(),
|
|
Content: testMessage,
|
|
},
|
|
}
|
|
|
|
s.dispatch(msg)
|
|
close(s.send)
|
|
wg.Wait()
|
|
|
|
verifyResponseCodes(&r, []int{http.StatusConflict}, t)
|
|
}
|
|
|
|
func TestDispatchGet(t *testing.T) {
|
|
uid := types.Uid(1)
|
|
s := test_makeSession(uid)
|
|
wg := sync.WaitGroup{}
|
|
r := responses{}
|
|
wg.Add(1)
|
|
go s.testWriteLoop(&r, &wg)
|
|
|
|
destUid := types.Uid(2)
|
|
topicName := uid.P2PName(destUid)
|
|
|
|
meta := make(chan *ClientComMessage, 1)
|
|
s.subs = make(map[string]*Subscription)
|
|
s.subs[topicName] = &Subscription{
|
|
meta: meta,
|
|
}
|
|
|
|
msg := &ClientComMessage{
|
|
Get: &MsgClientGet{
|
|
Id: "123",
|
|
Topic: destUid.UserId(),
|
|
MsgGetQuery: MsgGetQuery{
|
|
What: "desc sub del cred",
|
|
},
|
|
},
|
|
}
|
|
|
|
s.dispatch(msg)
|
|
close(s.send)
|
|
wg.Wait()
|
|
|
|
// Check we've routed the join request via the meta channel.
|
|
if len(r.messages) != 0 {
|
|
t.Errorf("responses: expected 0, received %d.", len(r.messages))
|
|
}
|
|
if len(meta) == 1 {
|
|
req := <-meta
|
|
if req.sess != s {
|
|
t.Error("Get request: sess field expected to be the session under test.")
|
|
}
|
|
} else {
|
|
t.Errorf("Get messages: expected 1, received %d.", len(meta))
|
|
}
|
|
}
|
|
|
|
func TestDispatchGetMalformedWhat(t *testing.T) {
|
|
uid := types.Uid(1)
|
|
s := test_makeSession(uid)
|
|
wg := sync.WaitGroup{}
|
|
r := responses{}
|
|
wg.Add(1)
|
|
go s.testWriteLoop(&r, &wg)
|
|
|
|
destUid := types.Uid(2)
|
|
msg := &ClientComMessage{
|
|
Get: &MsgClientGet{
|
|
Id: "123",
|
|
Topic: destUid.UserId(),
|
|
MsgGetQuery: MsgGetQuery{
|
|
// Empty 'what'. This will produce an error.
|
|
What: "",
|
|
},
|
|
},
|
|
}
|
|
|
|
s.dispatch(msg)
|
|
close(s.send)
|
|
wg.Wait()
|
|
|
|
verifyResponseCodes(&r, []int{http.StatusBadRequest}, t)
|
|
}
|
|
|
|
func TestDispatchGetMetaChannelFull(t *testing.T) {
|
|
uid := types.Uid(1)
|
|
s := test_makeSession(uid)
|
|
wg := sync.WaitGroup{}
|
|
r := responses{}
|
|
wg.Add(1)
|
|
go s.testWriteLoop(&r, &wg)
|
|
|
|
destUid := types.Uid(2)
|
|
topicName := uid.P2PName(destUid)
|
|
|
|
// Unbuffered chan with no readers - emit will fail.
|
|
meta := make(chan *ClientComMessage)
|
|
s.subs = make(map[string]*Subscription)
|
|
s.subs[topicName] = &Subscription{
|
|
meta: meta,
|
|
}
|
|
|
|
msg := &ClientComMessage{
|
|
Get: &MsgClientGet{
|
|
Id: "123",
|
|
Topic: destUid.UserId(),
|
|
MsgGetQuery: MsgGetQuery{
|
|
What: "desc sub",
|
|
},
|
|
},
|
|
}
|
|
|
|
s.dispatch(msg)
|
|
close(s.send)
|
|
wg.Wait()
|
|
|
|
verifyResponseCodes(&r, []int{http.StatusServiceUnavailable}, t)
|
|
}
|
|
|
|
func TestDispatchSet(t *testing.T) {
|
|
uid := types.Uid(1)
|
|
s := test_makeSession(uid)
|
|
wg := sync.WaitGroup{}
|
|
r := responses{}
|
|
wg.Add(1)
|
|
go s.testWriteLoop(&r, &wg)
|
|
|
|
destUid := types.Uid(2)
|
|
topicName := uid.P2PName(destUid)
|
|
|
|
meta := make(chan *ClientComMessage, 1)
|
|
s.subs = make(map[string]*Subscription)
|
|
s.subs[topicName] = &Subscription{
|
|
meta: meta,
|
|
}
|
|
|
|
msg := &ClientComMessage{
|
|
Set: &MsgClientSet{
|
|
Id: "123",
|
|
Topic: destUid.UserId(),
|
|
MsgSetQuery: MsgSetQuery{
|
|
Desc: &MsgSetDesc{},
|
|
Sub: &MsgSetSub{},
|
|
Tags: []string{"abc"},
|
|
Cred: &MsgCredClient{},
|
|
},
|
|
},
|
|
}
|
|
|
|
s.dispatch(msg)
|
|
close(s.send)
|
|
wg.Wait()
|
|
|
|
// Check we've routed the join request via the meta channel.
|
|
if len(r.messages) != 0 {
|
|
t.Errorf("responses: expected 0, received %d.", len(r.messages))
|
|
}
|
|
if len(meta) == 1 {
|
|
req := <-meta
|
|
if req.sess != s {
|
|
t.Error("Set request: sess field expected to be the session under test.")
|
|
}
|
|
expectedWhat := constMsgMetaDesc | constMsgMetaSub | constMsgMetaTags | constMsgMetaCred
|
|
if msg.MetaWhat != expectedWhat {
|
|
t.Errorf("Set request what: expected %d vs %d", expectedWhat, msg.MetaWhat)
|
|
}
|
|
} else {
|
|
t.Errorf("Set messages: expected 1, received %d.", len(meta))
|
|
}
|
|
}
|
|
|
|
func TestDispatchSetMalformedWhat(t *testing.T) {
|
|
uid := types.Uid(1)
|
|
s := test_makeSession(uid)
|
|
wg := sync.WaitGroup{}
|
|
r := responses{}
|
|
wg.Add(1)
|
|
go s.testWriteLoop(&r, &wg)
|
|
|
|
destUid := types.Uid(2)
|
|
msg := &ClientComMessage{
|
|
Set: &MsgClientSet{
|
|
Id: "123",
|
|
Topic: destUid.UserId(),
|
|
MsgSetQuery: MsgSetQuery{
|
|
// No meta requests.
|
|
},
|
|
},
|
|
}
|
|
|
|
s.dispatch(msg)
|
|
close(s.send)
|
|
wg.Wait()
|
|
|
|
verifyResponseCodes(&r, []int{http.StatusBadRequest}, t)
|
|
}
|
|
|
|
func TestDispatchSetMetaChannelFull(t *testing.T) {
|
|
uid := types.Uid(1)
|
|
s := test_makeSession(uid)
|
|
wg := sync.WaitGroup{}
|
|
r := responses{}
|
|
wg.Add(1)
|
|
go s.testWriteLoop(&r, &wg)
|
|
|
|
destUid := types.Uid(2)
|
|
topicName := uid.P2PName(destUid)
|
|
|
|
// Unbuffered meta channel w/ no readers - emit will fail.
|
|
meta := make(chan *ClientComMessage)
|
|
s.subs = make(map[string]*Subscription)
|
|
s.subs[topicName] = &Subscription{
|
|
meta: meta,
|
|
}
|
|
|
|
msg := &ClientComMessage{
|
|
Set: &MsgClientSet{
|
|
Id: "123",
|
|
Topic: destUid.UserId(),
|
|
MsgSetQuery: MsgSetQuery{
|
|
// No meta requests.
|
|
Desc: &MsgSetDesc{},
|
|
Sub: &MsgSetSub{},
|
|
Tags: []string{"abc"},
|
|
Cred: &MsgCredClient{},
|
|
},
|
|
},
|
|
}
|
|
|
|
s.dispatch(msg)
|
|
close(s.send)
|
|
wg.Wait()
|
|
|
|
verifyResponseCodes(&r, []int{http.StatusServiceUnavailable}, t)
|
|
}
|
|
|
|
func TestDispatchDelMsg(t *testing.T) {
|
|
uid := types.Uid(1)
|
|
s := test_makeSession(uid)
|
|
wg := sync.WaitGroup{}
|
|
r := responses{}
|
|
wg.Add(1)
|
|
go s.testWriteLoop(&r, &wg)
|
|
|
|
destUid := types.Uid(2)
|
|
topicName := uid.P2PName(destUid)
|
|
|
|
meta := make(chan *ClientComMessage, 1)
|
|
s.subs = make(map[string]*Subscription)
|
|
s.subs[topicName] = &Subscription{
|
|
meta: meta,
|
|
}
|
|
|
|
msg := &ClientComMessage{
|
|
Del: &MsgClientDel{
|
|
Id: "123",
|
|
Topic: destUid.UserId(),
|
|
What: "msg",
|
|
DelSeq: []MsgRange{{LowId: 3, HiId: 4}},
|
|
Hard: true,
|
|
},
|
|
}
|
|
|
|
s.dispatch(msg)
|
|
close(s.send)
|
|
wg.Wait()
|
|
|
|
// Check we've routed the join request via the meta channel.
|
|
if len(r.messages) != 0 {
|
|
t.Errorf("responses: expected 0, received %d.", len(r.messages))
|
|
}
|
|
if len(meta) == 1 {
|
|
req := <-meta
|
|
if req.sess != s {
|
|
t.Error("Del request: sess field expected to be the session under test.")
|
|
}
|
|
} else {
|
|
t.Errorf("Del messages: expected 1, received %d.", len(meta))
|
|
}
|
|
}
|
|
|
|
func TestDispatchDelMalformedWhat(t *testing.T) {
|
|
uid := types.Uid(1)
|
|
s := test_makeSession(uid)
|
|
wg := sync.WaitGroup{}
|
|
r := responses{}
|
|
wg.Add(1)
|
|
go s.testWriteLoop(&r, &wg)
|
|
|
|
destUid := types.Uid(2)
|
|
msg := &ClientComMessage{
|
|
Del: &MsgClientDel{
|
|
Id: "123",
|
|
Topic: destUid.UserId(),
|
|
// Invalid 'what' - this will produce an error.
|
|
What: "INVALID",
|
|
},
|
|
}
|
|
|
|
s.dispatch(msg)
|
|
close(s.send)
|
|
wg.Wait()
|
|
|
|
verifyResponseCodes(&r, []int{http.StatusBadRequest}, t)
|
|
}
|
|
|
|
func TestDispatchDelMetaChanFull(t *testing.T) {
|
|
uid := types.Uid(1)
|
|
s := test_makeSession(uid)
|
|
wg := sync.WaitGroup{}
|
|
r := responses{}
|
|
wg.Add(1)
|
|
go s.testWriteLoop(&r, &wg)
|
|
|
|
destUid := types.Uid(2)
|
|
topicName := uid.P2PName(destUid)
|
|
|
|
// Unbuffered chan - to simulate a full buffered chan.
|
|
meta := make(chan *ClientComMessage)
|
|
s.subs = make(map[string]*Subscription)
|
|
s.subs[topicName] = &Subscription{
|
|
meta: meta,
|
|
}
|
|
|
|
msg := &ClientComMessage{
|
|
Del: &MsgClientDel{
|
|
Id: "123",
|
|
Topic: destUid.UserId(),
|
|
What: "msg",
|
|
DelSeq: []MsgRange{{LowId: 3, HiId: 4}},
|
|
Hard: true,
|
|
},
|
|
}
|
|
|
|
s.dispatch(msg)
|
|
close(s.send)
|
|
wg.Wait()
|
|
|
|
verifyResponseCodes(&r, []int{http.StatusServiceUnavailable}, t)
|
|
}
|
|
|
|
func TestDispatchDelUnsubscribedSession(t *testing.T) {
|
|
uid := types.Uid(1)
|
|
s := test_makeSession(uid)
|
|
wg := sync.WaitGroup{}
|
|
r := responses{}
|
|
wg.Add(1)
|
|
go s.testWriteLoop(&r, &wg)
|
|
|
|
destUid := types.Uid(2)
|
|
// Session isn't subscribed.
|
|
s.subs = make(map[string]*Subscription)
|
|
msg := &ClientComMessage{
|
|
Del: &MsgClientDel{
|
|
Id: "123",
|
|
Topic: destUid.UserId(),
|
|
What: "msg",
|
|
DelSeq: []MsgRange{{LowId: 3, HiId: 4}},
|
|
Hard: true,
|
|
},
|
|
}
|
|
|
|
s.dispatch(msg)
|
|
close(s.send)
|
|
wg.Wait()
|
|
|
|
verifyResponseCodes(&r, []int{http.StatusConflict}, t)
|
|
}
|
|
|
|
func TestDispatchNote(t *testing.T) {
|
|
uid := types.Uid(1)
|
|
s := test_makeSession(uid)
|
|
wg := sync.WaitGroup{}
|
|
r := responses{}
|
|
wg.Add(1)
|
|
go s.testWriteLoop(&r, &wg)
|
|
|
|
destUid := types.Uid(2)
|
|
topicName := uid.P2PName(destUid)
|
|
|
|
brdcst := make(chan *ClientComMessage, 1)
|
|
s.subs = make(map[string]*Subscription)
|
|
s.subs[topicName] = &Subscription{
|
|
broadcast: brdcst,
|
|
}
|
|
|
|
msg := &ClientComMessage{
|
|
Note: &MsgClientNote{
|
|
Topic: destUid.UserId(),
|
|
What: "recv",
|
|
SeqId: 5,
|
|
},
|
|
}
|
|
|
|
s.dispatch(msg)
|
|
close(s.send)
|
|
wg.Wait()
|
|
|
|
// Check we've routed the join request via the broadcast channel.
|
|
if len(r.messages) != 0 {
|
|
t.Errorf("responses: expected 0, received %d.", len(r.messages))
|
|
}
|
|
if len(brdcst) == 1 {
|
|
req := <-brdcst
|
|
if req.sess != s {
|
|
t.Error("Pub request: sess field expected to be the session under test.")
|
|
}
|
|
if req.Note.What != msg.Note.What {
|
|
t.Errorf("Note request what: expected '%s' vs '%s'.", msg.Note.What, req.Note.What)
|
|
}
|
|
if req.Note.SeqId != msg.Note.SeqId {
|
|
t.Errorf("Note request seqId: expected %d vs %d.", msg.Note.SeqId, req.Note.SeqId)
|
|
}
|
|
if req.Note.Topic != destUid.UserId() {
|
|
t.Errorf("Note request topic: expected '%s' vs '%s'.", destUid.UserId(), req.Note.Topic)
|
|
}
|
|
} else {
|
|
t.Errorf("Note messages: expected 1, received %d.", len(brdcst))
|
|
}
|
|
}
|
|
|
|
func TestDispatchNoteBroadcastChanFull(t *testing.T) {
|
|
uid := types.Uid(1)
|
|
s := test_makeSession(uid)
|
|
wg := sync.WaitGroup{}
|
|
r := responses{}
|
|
wg.Add(1)
|
|
go s.testWriteLoop(&r, &wg)
|
|
|
|
destUid := types.Uid(2)
|
|
topicName := uid.P2PName(destUid)
|
|
|
|
// Unbuffered chan - to simulate a full buffered chan.
|
|
brdcst := make(chan *ClientComMessage)
|
|
s.subs = make(map[string]*Subscription)
|
|
s.subs[topicName] = &Subscription{
|
|
broadcast: brdcst,
|
|
}
|
|
|
|
msg := &ClientComMessage{
|
|
Note: &MsgClientNote{
|
|
Topic: destUid.UserId(),
|
|
What: "recv",
|
|
SeqId: 5,
|
|
},
|
|
}
|
|
|
|
s.dispatch(msg)
|
|
close(s.send)
|
|
wg.Wait()
|
|
|
|
verifyResponseCodes(&r, []int{http.StatusServiceUnavailable}, t)
|
|
}
|
|
|
|
func TestDispatchNoteOnNonSubscribedTopic(t *testing.T) {
|
|
uid := types.Uid(1)
|
|
s := test_makeSession(uid)
|
|
wg := sync.WaitGroup{}
|
|
r := responses{}
|
|
wg.Add(1)
|
|
go s.testWriteLoop(&r, &wg)
|
|
|
|
destUid := types.Uid(2)
|
|
s.subs = make(map[string]*Subscription)
|
|
|
|
msg := &ClientComMessage{
|
|
Note: &MsgClientNote{
|
|
Topic: destUid.UserId(),
|
|
What: "read",
|
|
SeqId: 5,
|
|
},
|
|
}
|
|
|
|
s.dispatch(msg)
|
|
close(s.send)
|
|
wg.Wait()
|
|
|
|
verifyResponseCodes(&r, []int{http.StatusConflict}, t)
|
|
}
|
|
|
|
func TestDispatchAccNew(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
ss := mock_store.NewMockPersistentStorageInterface(ctrl)
|
|
uu := mock_store.NewMockUsersPersistenceInterface(ctrl)
|
|
aa := mock_auth.NewMockAuthHandler(ctrl)
|
|
|
|
uid := types.Uid(1)
|
|
store.Store = ss
|
|
store.Users = uu
|
|
defer func() {
|
|
store.Store = nil
|
|
store.Users = nil
|
|
ctrl.Finish()
|
|
}()
|
|
|
|
remoteAddr := "192.168.0.1"
|
|
secret := "<==auth-secret==>"
|
|
tags := []string{"tag1", "tag2"}
|
|
authRec := &auth.Rec{
|
|
Uid: uid,
|
|
AuthLevel: auth.LevelAuth,
|
|
Tags: tags,
|
|
State: types.StateOK,
|
|
}
|
|
ss.EXPECT().GetLogicalAuthHandler("basic").Return(aa)
|
|
// This login is available.
|
|
aa.EXPECT().IsUnique([]byte(secret), remoteAddr).Return(true, nil)
|
|
uu.EXPECT().Create(gomock.Any(), gomock.Any()).DoAndReturn(
|
|
func(user *types.User, private any) (*types.User, error) {
|
|
user.SetUid(uid)
|
|
return user, nil
|
|
})
|
|
aa.EXPECT().AddRecord(gomock.Any(), []byte(secret), remoteAddr).Return(authRec, nil)
|
|
|
|
// Token generation.
|
|
ss.EXPECT().GetLogicalAuthHandler("token").Return(aa)
|
|
token := "<==auth-token==>"
|
|
aa.EXPECT().GenSecret(gomock.Any()).Return([]byte(token), time.Now(), nil)
|
|
uu.EXPECT().UpdateTags(uid, tags, nil, nil).Return(tags, nil)
|
|
|
|
s := &Session{
|
|
send: make(chan any, 10),
|
|
authLvl: auth.LevelAuth,
|
|
ver: 16,
|
|
remoteAddr: remoteAddr,
|
|
}
|
|
wg := sync.WaitGroup{}
|
|
r := responses{}
|
|
wg.Add(1)
|
|
go s.testWriteLoop(&r, &wg)
|
|
|
|
public := "public name"
|
|
msg := &ClientComMessage{
|
|
Acc: &MsgClientAcc{
|
|
Id: "123",
|
|
User: "newXYZ",
|
|
Scheme: "basic",
|
|
Secret: []byte(secret),
|
|
Tags: []string{"abc", "123"},
|
|
Desc: &MsgSetDesc{Public: public},
|
|
},
|
|
}
|
|
|
|
s.dispatch(msg)
|
|
close(s.send)
|
|
wg.Wait()
|
|
|
|
if len(r.messages) != 1 {
|
|
t.Errorf("responses: expected 1, received %d.", len(r.messages))
|
|
}
|
|
resp := r.messages[0].(*ServerComMessage)
|
|
if resp == nil {
|
|
t.Fatal("Response must be ServerComMessage")
|
|
}
|
|
if resp.Ctrl != nil {
|
|
if resp.Ctrl.Id != "123" {
|
|
t.Errorf("Response id: expected '123', found '%s'", resp.Ctrl.Id)
|
|
}
|
|
if resp.Ctrl.Code != 201 {
|
|
t.Errorf("Response code: expected 201, got %d", resp.Ctrl.Code)
|
|
}
|
|
if resp.Ctrl.Params == nil {
|
|
t.Error("Response is expected to contain params dict.")
|
|
}
|
|
p := resp.Ctrl.Params.(map[string]any)
|
|
if respUid := string(p["user"].(string)); respUid != uid.UserId() {
|
|
t.Errorf("Response uid: expected '%s', found '%s'.", uid.UserId(), respUid)
|
|
}
|
|
if lvl := p["authlvl"].(string); lvl != auth.LevelAuth.String() {
|
|
t.Errorf("Auth level: expected '%s', found '%s'.", auth.LevelAuth.String(), lvl)
|
|
}
|
|
if desc := p["desc"].(*MsgTopicDesc); desc.Public.(string) != public {
|
|
t.Errorf("Public: expected '%s', found '%s'.", public, desc.Public.(string))
|
|
}
|
|
} else {
|
|
t.Error("Response must contain a ctrl message.")
|
|
}
|
|
}
|
|
|
|
func TestDispatchNoMessage(t *testing.T) {
|
|
remoteAddr := "192.168.0.1"
|
|
s := &Session{
|
|
send: make(chan any, 10),
|
|
authLvl: auth.LevelAuth,
|
|
ver: 16,
|
|
remoteAddr: remoteAddr,
|
|
}
|
|
wg := sync.WaitGroup{}
|
|
r := responses{}
|
|
wg.Add(1)
|
|
go s.testWriteLoop(&r, &wg)
|
|
|
|
msg := &ClientComMessage{}
|
|
|
|
s.dispatch(msg)
|
|
close(s.send)
|
|
wg.Wait()
|
|
|
|
if len(r.messages) != 1 {
|
|
t.Errorf("responses: expected 1, received %d.", len(r.messages))
|
|
}
|
|
resp := r.messages[0].(*ServerComMessage)
|
|
if resp == nil {
|
|
t.Fatal("Response must be ServerComMessage")
|
|
}
|
|
if resp.Ctrl == nil {
|
|
t.Fatal("Response must contain a ctrl message.")
|
|
}
|
|
if resp.Ctrl.Code != 400 {
|
|
t.Errorf("Response code: expected 400, got %d", resp.Ctrl.Code)
|
|
}
|
|
}
|
|
|
|
func TestIsReactionAllowedFunction(t *testing.T) {
|
|
// No restriction set -> allow anything
|
|
globals.allowedReactions = nil
|
|
if !isReactionAllowed("any") {
|
|
t.Error("expected anystring to be allowed when no restriction set")
|
|
}
|
|
|
|
// Restrict to only 👍
|
|
globals.allowedReactions = map[string]bool{"👍": true}
|
|
if !isReactionAllowed("👍") {
|
|
t.Error("expected 👍 to be allowed when configured")
|
|
}
|
|
if isReactionAllowed("❤️") {
|
|
t.Error("expected ❤️ to be disallowed when only 👍 is configured")
|
|
}
|
|
// nullValue always allowed
|
|
if !isReactionAllowed(nullValue) {
|
|
t.Error("expected nullValue to be allowed always")
|
|
}
|
|
// restore
|
|
globals.allowedReactions = nil
|
|
}
|