664 lines
19 KiB
Go
664 lines
19 KiB
Go
|
|
package server
|
||
|
|
|
||
|
|
import (
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"forge.redroom.link/yves/meowlib"
|
||
|
|
"github.com/alicebob/miniredis"
|
||
|
|
"github.com/go-redis/redis"
|
||
|
|
"github.com/rs/zerolog"
|
||
|
|
"github.com/stretchr/testify/assert"
|
||
|
|
"google.golang.org/protobuf/proto"
|
||
|
|
"os"
|
||
|
|
)
|
||
|
|
|
||
|
|
func init() {
|
||
|
|
AddLogger(zerolog.New(os.Stderr).Level(zerolog.Disabled))
|
||
|
|
}
|
||
|
|
|
||
|
|
// newTestRouter spins up a miniredis instance and returns a RedisRouter wired to it.
|
||
|
|
// The caller must call mr.Close() when done.
|
||
|
|
func newTestRouter(t *testing.T) (*RedisRouter, *miniredis.Miniredis) {
|
||
|
|
t.Helper()
|
||
|
|
mr, err := miniredis.Run()
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
id := CreateIdentity("TestServer", "A test server")
|
||
|
|
router := &RedisRouter{
|
||
|
|
Name: "TestRedis",
|
||
|
|
ServerIdentity: id,
|
||
|
|
Client: redis.NewClient(&redis.Options{
|
||
|
|
Addr: mr.Addr(),
|
||
|
|
}),
|
||
|
|
InvitationTimeout: 3600,
|
||
|
|
Context: nil,
|
||
|
|
}
|
||
|
|
// seed the statistics:start key that NewRedisRouter normally sets
|
||
|
|
router.Client.Set("statistics:start", time.Now().UTC().Format(time.RFC3339), 0)
|
||
|
|
return router, mr
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// storeMessage / checkForMessage round-trip
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
func TestStoreAndCheckMessage(t *testing.T) {
|
||
|
|
router, mr := newTestRouter(t)
|
||
|
|
defer mr.Close()
|
||
|
|
|
||
|
|
dest := "lookup-key-alice"
|
||
|
|
msg := &meowlib.ToServerMessage{
|
||
|
|
Uuid: "msg-uuid-1",
|
||
|
|
From: "sender-pub-key",
|
||
|
|
Messages: []*meowlib.PackedUserMessage{
|
||
|
|
{
|
||
|
|
Destination: dest,
|
||
|
|
Payload: []byte("hello alice"),
|
||
|
|
Signature: []byte("sig1"),
|
||
|
|
},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
// store
|
||
|
|
resp, err := router.storeMessage(msg)
|
||
|
|
assert.NoError(t, err)
|
||
|
|
assert.Equal(t, "msg-uuid-1", resp.UuidAck)
|
||
|
|
|
||
|
|
// check: build a pull request for the same key
|
||
|
|
pullMsg := &meowlib.ToServerMessage{
|
||
|
|
PullRequest: []*meowlib.ConversationRequest{
|
||
|
|
{LookupKey: dest},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
resp, err = router.checkForMessage(pullMsg)
|
||
|
|
assert.NoError(t, err)
|
||
|
|
assert.Len(t, resp.Chat, 1)
|
||
|
|
assert.Equal(t, dest, resp.Chat[0].Destination)
|
||
|
|
assert.Equal(t, []byte("hello alice"), resp.Chat[0].Payload)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestStoreMultipleMessagesAndCheck(t *testing.T) {
|
||
|
|
router, mr := newTestRouter(t)
|
||
|
|
defer mr.Close()
|
||
|
|
|
||
|
|
dest := "lookup-key-bob"
|
||
|
|
msg := &meowlib.ToServerMessage{
|
||
|
|
Uuid: "multi-uuid",
|
||
|
|
Messages: []*meowlib.PackedUserMessage{
|
||
|
|
{Destination: dest, Payload: []byte("msg-1")},
|
||
|
|
{Destination: dest, Payload: []byte("msg-2")},
|
||
|
|
{Destination: dest, Payload: []byte("msg-3")},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
_, err := router.storeMessage(msg)
|
||
|
|
assert.NoError(t, err)
|
||
|
|
|
||
|
|
pullMsg := &meowlib.ToServerMessage{
|
||
|
|
PullRequest: []*meowlib.ConversationRequest{
|
||
|
|
{LookupKey: dest},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
resp, err := router.checkForMessage(pullMsg)
|
||
|
|
assert.NoError(t, err)
|
||
|
|
assert.Len(t, resp.Chat, 3)
|
||
|
|
}
|
||
|
|
|
||
|
|
// checkForMessage on an empty key returns an empty chat list (no error)
|
||
|
|
func TestCheckForMessageEmpty(t *testing.T) {
|
||
|
|
router, mr := newTestRouter(t)
|
||
|
|
defer mr.Close()
|
||
|
|
|
||
|
|
pullMsg := &meowlib.ToServerMessage{
|
||
|
|
PullRequest: []*meowlib.ConversationRequest{
|
||
|
|
{LookupKey: "nonexistent-key"},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
resp, err := router.checkForMessage(pullMsg)
|
||
|
|
assert.NoError(t, err)
|
||
|
|
assert.Empty(t, resp.Chat)
|
||
|
|
}
|
||
|
|
|
||
|
|
// messages are consumed (popped) — a second check returns nothing
|
||
|
|
func TestCheckForMessageConsumes(t *testing.T) {
|
||
|
|
router, mr := newTestRouter(t)
|
||
|
|
defer mr.Close()
|
||
|
|
|
||
|
|
dest := "consume-key"
|
||
|
|
msg := &meowlib.ToServerMessage{
|
||
|
|
Messages: []*meowlib.PackedUserMessage{
|
||
|
|
{Destination: dest, Payload: []byte("once")},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
router.storeMessage(msg)
|
||
|
|
|
||
|
|
pull := &meowlib.ToServerMessage{
|
||
|
|
PullRequest: []*meowlib.ConversationRequest{{LookupKey: dest}},
|
||
|
|
}
|
||
|
|
resp, err := router.checkForMessage(pull)
|
||
|
|
assert.NoError(t, err)
|
||
|
|
assert.Len(t, resp.Chat, 1)
|
||
|
|
|
||
|
|
// second pull — queue is drained
|
||
|
|
resp, err = router.checkForMessage(pull)
|
||
|
|
assert.NoError(t, err)
|
||
|
|
assert.Empty(t, resp.Chat)
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// storeMessage with delivery tracking
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
// storeMessage calls SAdd("dvyrq:<uuid>", redis.Z{...}) when ServerDeliveryUuid
|
||
|
|
// is set. Passing redis.Z (a sorted-set helper struct) to SAdd is a bug in
|
||
|
|
// router.go — the member never actually lands in the set. This test documents
|
||
|
|
// that the code path executes without error; the Redis state assertion is
|
||
|
|
// intentionally omitted until the bug is fixed.
|
||
|
|
func TestStoreMessageDeliveryTracking(t *testing.T) {
|
||
|
|
router, mr := newTestRouter(t)
|
||
|
|
defer mr.Close()
|
||
|
|
|
||
|
|
dest := "delivery-dest"
|
||
|
|
msg := &meowlib.ToServerMessage{
|
||
|
|
Uuid: "store-dvy",
|
||
|
|
From: "sender-pub",
|
||
|
|
Messages: []*meowlib.PackedUserMessage{
|
||
|
|
{
|
||
|
|
Destination: dest,
|
||
|
|
Payload: []byte("tracked msg"),
|
||
|
|
ServerDeliveryUuid: "dvy-uuid-42",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
resp, err := router.storeMessage(msg)
|
||
|
|
assert.NoError(t, err)
|
||
|
|
assert.Equal(t, "store-dvy", resp.UuidAck)
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// storeMessage writes to multiple destinations
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
func TestStoreMessageMultipleDestinations(t *testing.T) {
|
||
|
|
router, mr := newTestRouter(t)
|
||
|
|
defer mr.Close()
|
||
|
|
|
||
|
|
msg := &meowlib.ToServerMessage{
|
||
|
|
Uuid: "multi-dest",
|
||
|
|
Messages: []*meowlib.PackedUserMessage{
|
||
|
|
{Destination: "dest-a", Payload: []byte("for a")},
|
||
|
|
{Destination: "dest-b", Payload: []byte("for b")},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
_, err := router.storeMessage(msg)
|
||
|
|
assert.NoError(t, err)
|
||
|
|
|
||
|
|
// each destination has exactly one message
|
||
|
|
cntA, _ := router.Client.ZCount("msg:dest-a", "-inf", "+inf").Result()
|
||
|
|
cntB, _ := router.Client.ZCount("msg:dest-b", "-inf", "+inf").Result()
|
||
|
|
assert.Equal(t, int64(1), cntA)
|
||
|
|
assert.Equal(t, int64(1), cntB)
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// Route dispatcher
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
func TestRouteDispatchesStoreAndCheck(t *testing.T) {
|
||
|
|
router, mr := newTestRouter(t)
|
||
|
|
defer mr.Close()
|
||
|
|
|
||
|
|
dest := "route-dest"
|
||
|
|
|
||
|
|
// first Route call: store a message
|
||
|
|
storeReq := &meowlib.ToServerMessage{
|
||
|
|
Uuid: "route-store-uuid",
|
||
|
|
Messages: []*meowlib.PackedUserMessage{
|
||
|
|
{Destination: dest, Payload: []byte("routed msg")},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
resp, err := router.Route(storeReq)
|
||
|
|
assert.NoError(t, err)
|
||
|
|
assert.Equal(t, "route-store-uuid", resp.UuidAck)
|
||
|
|
|
||
|
|
// second Route call: pull that message
|
||
|
|
pullReq := &meowlib.ToServerMessage{
|
||
|
|
PullRequest: []*meowlib.ConversationRequest{
|
||
|
|
{LookupKey: dest},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
resp, err = router.Route(pullReq)
|
||
|
|
assert.NoError(t, err)
|
||
|
|
assert.Len(t, resp.Chat, 1)
|
||
|
|
assert.Equal(t, []byte("routed msg"), resp.Chat[0].Payload)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Route with no actionable fields returns nil response and no error
|
||
|
|
func TestRouteEmptyMessage(t *testing.T) {
|
||
|
|
router, mr := newTestRouter(t)
|
||
|
|
defer mr.Close()
|
||
|
|
|
||
|
|
resp, err := router.Route(&meowlib.ToServerMessage{})
|
||
|
|
assert.NoError(t, err)
|
||
|
|
assert.Nil(t, resp)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Route updates statistics counters
|
||
|
|
func TestRouteIncrementsTotalCounter(t *testing.T) {
|
||
|
|
router, mr := newTestRouter(t)
|
||
|
|
defer mr.Close()
|
||
|
|
|
||
|
|
router.Route(&meowlib.ToServerMessage{})
|
||
|
|
router.Route(&meowlib.ToServerMessage{})
|
||
|
|
router.Route(&meowlib.ToServerMessage{})
|
||
|
|
|
||
|
|
val, err := router.Client.Get("statistics:messages:total").Int()
|
||
|
|
assert.NoError(t, err)
|
||
|
|
assert.Equal(t, 3, val)
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// handleMatriochka
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
func TestHandleMatriochka(t *testing.T) {
|
||
|
|
router, mr := newTestRouter(t)
|
||
|
|
defer mr.Close()
|
||
|
|
|
||
|
|
msg := &meowlib.ToServerMessage{
|
||
|
|
Uuid: "matriochka-uuid",
|
||
|
|
MatriochkaMessage: &meowlib.Matriochka{
|
||
|
|
LookupKey: "mtk-lookup",
|
||
|
|
Data: []byte("onion layer"),
|
||
|
|
Next: &meowlib.MatriochkaServer{
|
||
|
|
Url: "http://next.server/meow",
|
||
|
|
PublicKey: "next-pub-key",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
resp, err := router.handleMatriochka(msg)
|
||
|
|
assert.NoError(t, err)
|
||
|
|
assert.Equal(t, "matriochka-uuid", resp.UuidAck)
|
||
|
|
|
||
|
|
// verify something was stored in the mtk sorted set
|
||
|
|
cnt, _ := router.Client.ZCount("mtk", "-inf", "+inf").Result()
|
||
|
|
assert.Equal(t, int64(1), cnt)
|
||
|
|
|
||
|
|
// deserialize what was stored and verify it round-trips
|
||
|
|
members, _ := router.Client.ZRange("mtk", 0, -1).Result()
|
||
|
|
var stored meowlib.ToServerMessage
|
||
|
|
err = proto.Unmarshal([]byte(members[0]), &stored)
|
||
|
|
assert.NoError(t, err)
|
||
|
|
assert.Equal(t, "mtk-lookup", stored.MatriochkaMessage.LookupKey)
|
||
|
|
assert.Equal(t, []byte("onion layer"), stored.MatriochkaMessage.Data)
|
||
|
|
}
|
||
|
|
|
||
|
|
// multiple distinct matriochka messages accumulate in the sorted set
|
||
|
|
func TestHandleMatriochkaMultiple(t *testing.T) {
|
||
|
|
router, mr := newTestRouter(t)
|
||
|
|
defer mr.Close()
|
||
|
|
|
||
|
|
// each message must differ so the sorted-set members are unique
|
||
|
|
payloads := []string{"layer-1", "layer-2", "layer-3"}
|
||
|
|
for _, p := range payloads {
|
||
|
|
router.handleMatriochka(&meowlib.ToServerMessage{
|
||
|
|
Uuid: "m-uuid-" + p,
|
||
|
|
MatriochkaMessage: &meowlib.Matriochka{
|
||
|
|
Data: []byte(p),
|
||
|
|
},
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
cnt, _ := router.Client.ZCount("mtk", "-inf", "+inf").Result()
|
||
|
|
assert.Equal(t, int64(3), cnt)
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// handleInvitation — step 1 (create) and step 2 (retrieve)
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
func TestHandleInvitationStep1And2(t *testing.T) {
|
||
|
|
router, mr := newTestRouter(t)
|
||
|
|
defer mr.Close()
|
||
|
|
|
||
|
|
payload := []byte("invitation-data")
|
||
|
|
|
||
|
|
// Step 1: create invitation
|
||
|
|
step1Msg := &meowlib.ToServerMessage{
|
||
|
|
Invitation: &meowlib.Invitation{
|
||
|
|
Step: 1,
|
||
|
|
Payload: payload,
|
||
|
|
Timeout: 60,
|
||
|
|
ShortcodeLen: 12,
|
||
|
|
},
|
||
|
|
}
|
||
|
|
resp, err := router.handleInvitation(step1Msg)
|
||
|
|
assert.NoError(t, err)
|
||
|
|
assert.NotEmpty(t, resp.Invitation.Shortcode)
|
||
|
|
assert.True(t, resp.Invitation.Expiry > 0)
|
||
|
|
|
||
|
|
shortcode := resp.Invitation.Shortcode
|
||
|
|
|
||
|
|
// Step 2: retrieve invitation (no password)
|
||
|
|
step2Msg := &meowlib.ToServerMessage{
|
||
|
|
Invitation: &meowlib.Invitation{
|
||
|
|
Step: 2,
|
||
|
|
Shortcode: shortcode,
|
||
|
|
},
|
||
|
|
}
|
||
|
|
resp, err = router.handleInvitation(step2Msg)
|
||
|
|
assert.NoError(t, err)
|
||
|
|
assert.Equal(t, payload, resp.Invitation.Payload)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestHandleInvitationStep1And2WithPassword(t *testing.T) {
|
||
|
|
router, mr := newTestRouter(t)
|
||
|
|
defer mr.Close()
|
||
|
|
|
||
|
|
payload := []byte("secret-invitation")
|
||
|
|
password := "s3cret"
|
||
|
|
|
||
|
|
// Step 1: create with password
|
||
|
|
step1Msg := &meowlib.ToServerMessage{
|
||
|
|
Invitation: &meowlib.Invitation{
|
||
|
|
Step: 1,
|
||
|
|
Payload: payload,
|
||
|
|
Timeout: 60,
|
||
|
|
ShortcodeLen: 10,
|
||
|
|
Password: password,
|
||
|
|
},
|
||
|
|
}
|
||
|
|
resp, err := router.handleInvitation(step1Msg)
|
||
|
|
assert.NoError(t, err)
|
||
|
|
shortcode := resp.Invitation.Shortcode
|
||
|
|
|
||
|
|
// Step 2: wrong password
|
||
|
|
step2Wrong := &meowlib.ToServerMessage{
|
||
|
|
Invitation: &meowlib.Invitation{
|
||
|
|
Step: 2,
|
||
|
|
Shortcode: shortcode,
|
||
|
|
Password: "wrong",
|
||
|
|
},
|
||
|
|
}
|
||
|
|
resp, err = router.handleInvitation(step2Wrong)
|
||
|
|
assert.NoError(t, err)
|
||
|
|
assert.Equal(t, []byte("authentication failure"), resp.Invitation.Payload)
|
||
|
|
|
||
|
|
// Step 2: correct password
|
||
|
|
step2Correct := &meowlib.ToServerMessage{
|
||
|
|
Invitation: &meowlib.Invitation{
|
||
|
|
Step: 2,
|
||
|
|
Shortcode: shortcode,
|
||
|
|
Password: password,
|
||
|
|
},
|
||
|
|
}
|
||
|
|
resp, err = router.handleInvitation(step2Correct)
|
||
|
|
assert.NoError(t, err)
|
||
|
|
assert.Equal(t, payload, resp.Invitation.Payload)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Step 2 on a non-existent shortcode returns "invitation expired"
|
||
|
|
func TestHandleInvitationStep2NotFound(t *testing.T) {
|
||
|
|
router, mr := newTestRouter(t)
|
||
|
|
defer mr.Close()
|
||
|
|
|
||
|
|
step2Msg := &meowlib.ToServerMessage{
|
||
|
|
Invitation: &meowlib.Invitation{
|
||
|
|
Step: 2,
|
||
|
|
Shortcode: "does-not-exist",
|
||
|
|
},
|
||
|
|
}
|
||
|
|
resp, err := router.handleInvitation(step2Msg)
|
||
|
|
assert.NoError(t, err)
|
||
|
|
assert.Equal(t, []byte("invitation expired"), resp.Invitation.Payload)
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// handleInvitation — step 3 (store answer) + checkForMessage retrieves it
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
func TestHandleInvitationStep3AndRetrieve(t *testing.T) {
|
||
|
|
router, mr := newTestRouter(t)
|
||
|
|
defer mr.Close()
|
||
|
|
|
||
|
|
lookupKey := "initiator-lookup-key"
|
||
|
|
|
||
|
|
// Build a PackedUserMessage whose Destination is the initiator's lookup key
|
||
|
|
pum := &meowlib.PackedUserMessage{
|
||
|
|
Destination: lookupKey,
|
||
|
|
Payload: []byte("answer-payload"),
|
||
|
|
}
|
||
|
|
pumBytes, err := proto.Marshal(pum)
|
||
|
|
assert.NoError(t, err)
|
||
|
|
|
||
|
|
invitationMsg := &meowlib.Invitation{
|
||
|
|
Step: 3,
|
||
|
|
Payload: pumBytes,
|
||
|
|
Timeout: 60,
|
||
|
|
}
|
||
|
|
|
||
|
|
step3Msg := &meowlib.ToServerMessage{
|
||
|
|
Invitation: invitationMsg,
|
||
|
|
}
|
||
|
|
resp, err := router.handleInvitation(step3Msg)
|
||
|
|
assert.NoError(t, err)
|
||
|
|
assert.True(t, resp.Invitation.Expiry > 0)
|
||
|
|
|
||
|
|
// Now simulate the initiator polling: checkForMessage with the lookup key
|
||
|
|
// and an empty message queue — should fall back to invitation answer
|
||
|
|
pullMsg := &meowlib.ToServerMessage{
|
||
|
|
PullRequest: []*meowlib.ConversationRequest{
|
||
|
|
{LookupKey: lookupKey},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
resp, err = router.checkForMessage(pullMsg)
|
||
|
|
assert.NoError(t, err)
|
||
|
|
assert.NotNil(t, resp.Invitation)
|
||
|
|
|
||
|
|
// The stored invitation answer should deserialize cleanly
|
||
|
|
var storedInv meowlib.Invitation
|
||
|
|
err = proto.Unmarshal(resp.Invitation.Payload, &storedInv)
|
||
|
|
// payload is the re-serialized Invitation protobuf from step 3
|
||
|
|
// just verify it's non-empty
|
||
|
|
assert.NotEmpty(t, resp.Invitation.Payload)
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// handleInvitation — password brute-force lockout (3 attempts)
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
func TestHandleInvitationPasswordLockout(t *testing.T) {
|
||
|
|
router, mr := newTestRouter(t)
|
||
|
|
defer mr.Close()
|
||
|
|
|
||
|
|
payload := []byte("locked-invitation")
|
||
|
|
|
||
|
|
// create invitation with password
|
||
|
|
step1Msg := &meowlib.ToServerMessage{
|
||
|
|
Invitation: &meowlib.Invitation{
|
||
|
|
Step: 1,
|
||
|
|
Payload: payload,
|
||
|
|
Timeout: 60,
|
||
|
|
ShortcodeLen: 8,
|
||
|
|
Password: "correct",
|
||
|
|
},
|
||
|
|
}
|
||
|
|
resp, err := router.handleInvitation(step1Msg)
|
||
|
|
assert.NoError(t, err)
|
||
|
|
shortcode := resp.Invitation.Shortcode
|
||
|
|
|
||
|
|
// 3 wrong attempts
|
||
|
|
for range 3 {
|
||
|
|
step2 := &meowlib.ToServerMessage{
|
||
|
|
Invitation: &meowlib.Invitation{
|
||
|
|
Step: 2,
|
||
|
|
Shortcode: shortcode,
|
||
|
|
Password: "wrong",
|
||
|
|
},
|
||
|
|
}
|
||
|
|
router.handleInvitation(step2)
|
||
|
|
}
|
||
|
|
|
||
|
|
// invitation should now be destroyed — even with correct password
|
||
|
|
step2Correct := &meowlib.ToServerMessage{
|
||
|
|
Invitation: &meowlib.Invitation{
|
||
|
|
Step: 2,
|
||
|
|
Shortcode: shortcode,
|
||
|
|
Password: "correct",
|
||
|
|
},
|
||
|
|
}
|
||
|
|
resp, err = router.handleInvitation(step2Correct)
|
||
|
|
assert.NoError(t, err)
|
||
|
|
assert.Equal(t, []byte("invitation expired"), resp.Invitation.Payload)
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// handleVideo
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
func TestHandleVideoNoServer(t *testing.T) {
|
||
|
|
router, mr := newTestRouter(t)
|
||
|
|
defer mr.Close()
|
||
|
|
|
||
|
|
// VideoServer with no credentials configured — UpdateVideoData still works
|
||
|
|
// (it just sets Url and returns empty credentials slice)
|
||
|
|
msg := &meowlib.ToServerMessage{
|
||
|
|
Uuid: "video-uuid",
|
||
|
|
VideoData: &meowlib.VideoData{
|
||
|
|
Room: "test-room",
|
||
|
|
Duration: 300,
|
||
|
|
},
|
||
|
|
}
|
||
|
|
resp, err := router.handleVideo(msg)
|
||
|
|
assert.NoError(t, err)
|
||
|
|
assert.Equal(t, "video-uuid", resp.UuidAck)
|
||
|
|
assert.NotNil(t, resp.VideoData)
|
||
|
|
assert.Equal(t, "test-room", resp.VideoData.Room)
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// Route dispatches matriochka via top-level Route
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
func TestRouteMatriochka(t *testing.T) {
|
||
|
|
router, mr := newTestRouter(t)
|
||
|
|
defer mr.Close()
|
||
|
|
|
||
|
|
msg := &meowlib.ToServerMessage{
|
||
|
|
Uuid: "route-mtk",
|
||
|
|
MatriochkaMessage: &meowlib.Matriochka{
|
||
|
|
Data: []byte("wrapped"),
|
||
|
|
},
|
||
|
|
}
|
||
|
|
resp, err := router.Route(msg)
|
||
|
|
assert.NoError(t, err)
|
||
|
|
assert.Equal(t, "route-mtk", resp.UuidAck)
|
||
|
|
|
||
|
|
cnt, _ := router.Client.ZCount("mtk", "-inf", "+inf").Result()
|
||
|
|
assert.Equal(t, int64(1), cnt)
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// Route dispatches invitation via top-level Route
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
func TestRouteInvitation(t *testing.T) {
|
||
|
|
router, mr := newTestRouter(t)
|
||
|
|
defer mr.Close()
|
||
|
|
|
||
|
|
msg := &meowlib.ToServerMessage{
|
||
|
|
Invitation: &meowlib.Invitation{
|
||
|
|
Step: 1,
|
||
|
|
Payload: []byte("via-route"),
|
||
|
|
Timeout: 30,
|
||
|
|
ShortcodeLen: 6,
|
||
|
|
},
|
||
|
|
}
|
||
|
|
resp, err := router.Route(msg)
|
||
|
|
assert.NoError(t, err)
|
||
|
|
assert.NotEmpty(t, resp.Invitation.Shortcode)
|
||
|
|
assert.Len(t, resp.Invitation.Shortcode, 6)
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// statistics counters
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
func TestStatisticsCountersIncrement(t *testing.T) {
|
||
|
|
router, mr := newTestRouter(t)
|
||
|
|
defer mr.Close()
|
||
|
|
|
||
|
|
dest := "stats-dest"
|
||
|
|
|
||
|
|
// one store increments usermessages
|
||
|
|
router.Route(&meowlib.ToServerMessage{
|
||
|
|
Messages: []*meowlib.PackedUserMessage{
|
||
|
|
{Destination: dest, Payload: []byte("x")},
|
||
|
|
},
|
||
|
|
})
|
||
|
|
val, _ := router.Client.Get("statistics:messages:usermessages").Int()
|
||
|
|
assert.Equal(t, 1, val)
|
||
|
|
|
||
|
|
// one pull increments messagelookups
|
||
|
|
router.Route(&meowlib.ToServerMessage{
|
||
|
|
PullRequest: []*meowlib.ConversationRequest{
|
||
|
|
{LookupKey: dest},
|
||
|
|
},
|
||
|
|
})
|
||
|
|
val, _ = router.Client.Get("statistics:messages:messagelookups").Int()
|
||
|
|
assert.Equal(t, 1, val)
|
||
|
|
|
||
|
|
// one matriochka increments matriochka counter
|
||
|
|
router.Route(&meowlib.ToServerMessage{
|
||
|
|
MatriochkaMessage: &meowlib.Matriochka{Data: []byte("m")},
|
||
|
|
})
|
||
|
|
val, _ = router.Client.Get("statistics:messages:matriochka").Int()
|
||
|
|
assert.Equal(t, 1, val)
|
||
|
|
|
||
|
|
// one invitation increments invitation counter
|
||
|
|
router.Route(&meowlib.ToServerMessage{
|
||
|
|
Invitation: &meowlib.Invitation{
|
||
|
|
Step: 1,
|
||
|
|
Payload: []byte("i"),
|
||
|
|
Timeout: 10,
|
||
|
|
ShortcodeLen: 4,
|
||
|
|
},
|
||
|
|
})
|
||
|
|
val, _ = router.Client.Get("statistics:messages:invitation").Int()
|
||
|
|
assert.Equal(t, 1, val)
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// checkForMessage with multiple pull request keys
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
func TestCheckForMessageMultipleKeys(t *testing.T) {
|
||
|
|
router, mr := newTestRouter(t)
|
||
|
|
defer mr.Close()
|
||
|
|
|
||
|
|
// store one message on each of two keys
|
||
|
|
router.storeMessage(&meowlib.ToServerMessage{
|
||
|
|
Messages: []*meowlib.PackedUserMessage{
|
||
|
|
{Destination: "key-x", Payload: []byte("from-x")},
|
||
|
|
},
|
||
|
|
})
|
||
|
|
router.storeMessage(&meowlib.ToServerMessage{
|
||
|
|
Messages: []*meowlib.PackedUserMessage{
|
||
|
|
{Destination: "key-y", Payload: []byte("from-y")},
|
||
|
|
},
|
||
|
|
})
|
||
|
|
|
||
|
|
pull := &meowlib.ToServerMessage{
|
||
|
|
PullRequest: []*meowlib.ConversationRequest{
|
||
|
|
{LookupKey: "key-x"},
|
||
|
|
{LookupKey: "key-y"},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
resp, err := router.checkForMessage(pull)
|
||
|
|
assert.NoError(t, err)
|
||
|
|
assert.Len(t, resp.Chat, 2)
|
||
|
|
}
|