mirror of
https://github.com/hanzoai/team.git
synced 2026-09-30 01:57:35 +00:00
* Route Calls pushes through a VoIP token if present
Add VoIP device id support so calls notifications can be delivered as iOS
PushKit pushes, while standard pushes continue using the regular device
token.
* New SessionPropVoIPDeviceId + IsValidVoIPDeviceID validator
* PushNotifyAppleReactNativeVoIP platform constant
* voip_device_id accepted on PUT /users/sessions/device and on login
* DoLogin refactored to accept LoginOptions struct; all four call sites
(api4/user, channels/web/{saml,magic_link,oauth}) converted
* sendPushNotificationToAllSessions: when SubType=calls, prefer
Session.Props[voip_device_id]; fall back to standard DeviceId
Tests cover the validator, the device-props endpoint (accept/reject), and
the SubType=calls routing matrix.
* Skip session per Calls plugin "answered_elsewhere" convention
When the Calls plugin fans out a cancel-ring push to a user's other
VoIP devices after they answer a call from one of them, it can't tell
core which session to skip because the public plugin API doesn't expose
a skip-session-id parameter. It encodes the auth-session-id on SenderId
together with Category="answered_elsewhere". Core honors the convention
and clears SenderId on the per-session copy before forwarding so the
session id never reaches the proxy or device. Category is preserved on
the wire so the mobile client can pick CXCallEndedReason.answeredElsewhere.
The matching side lives in mattermost-plugin-calls' push_notifications.go;
both sides cross-reference the constant.
* delay voip token audit until session props are set
* capture voip audit failed case
* Promote VoIP token to a column; dispatch by Transport; harden revocation
* ai feedback review
* feedback review
* feedback review
* fix tests
* update openAPI
* update serialized session model
---------
Co-authored-by: Jesse Hallam <[email protected]>
691 lines
21 KiB
Go
691 lines
21 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package storetest
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
"github.com/mattermost/mattermost/server/public/shared/request"
|
|
"github.com/mattermost/mattermost/server/v8/channels/store"
|
|
)
|
|
|
|
const (
|
|
TenMinutes = 600000
|
|
)
|
|
|
|
func TestSessionStore(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
// Run serially to prevent interfering with other tests
|
|
testSessionCleanup(t, rctx, ss)
|
|
|
|
t.Run("Save", func(t *testing.T) { testSessionStoreSave(t, rctx, ss) })
|
|
t.Run("SessionGet", func(t *testing.T) { testSessionGet(t, rctx, ss) })
|
|
t.Run("SessionGetWithDeviceId", func(t *testing.T) { testSessionGetWithDeviceId(t, rctx, ss) })
|
|
t.Run("SessionRemove", func(t *testing.T) { testSessionRemove(t, rctx, ss) })
|
|
t.Run("SessionRemoveAll", func(t *testing.T) { testSessionRemoveAll(t, rctx, ss) })
|
|
t.Run("SessionRemoveByUser", func(t *testing.T) { testSessionRemoveByUser(t, rctx, ss) })
|
|
t.Run("SessionRemoveToken", func(t *testing.T) { testSessionRemoveToken(t, rctx, ss) })
|
|
t.Run("SessionUpdateDeviceId", func(t *testing.T) { testSessionUpdateDeviceId(t, rctx, ss) })
|
|
t.Run("SessionUpdateDeviceId2", func(t *testing.T) { testSessionUpdateDeviceId2(t, rctx, ss) })
|
|
t.Run("SessionUpdateDeviceIdClearsOnEmpty", func(t *testing.T) { testSessionUpdateDeviceIdClearsOnEmpty(t, rctx, ss) })
|
|
t.Run("UpdateExpiresAt", func(t *testing.T) { testSessionStoreUpdateExpiresAt(t, rctx, ss) })
|
|
t.Run("UpdateLastActivityAt", func(t *testing.T) { testSessionStoreUpdateLastActivityAt(t, rctx, ss) })
|
|
t.Run("SessionCount", func(t *testing.T) { testSessionCount(t, rctx, ss) })
|
|
t.Run("GetSessionsExpired", func(t *testing.T) { testGetSessionsExpired(t, rctx, ss) })
|
|
t.Run("UpdateExpiredNotify", func(t *testing.T) { testUpdateExpiredNotify(t, rctx, ss) })
|
|
t.Run("GetLRUSessions", func(t *testing.T) { testGetLRUSessions(t, rctx, ss) })
|
|
t.Run("GetSessionsWithActiveDeviceIds", func(t *testing.T) { testGetSessionsWithActiveDeviceIds(t, rctx, ss) })
|
|
t.Run("GetMobileSessionMetadata", func(t *testing.T) { testGetMobileSessionMetadata(t, rctx, ss) })
|
|
}
|
|
|
|
func testSessionStoreSave(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
s1 := &model.Session{}
|
|
s1.UserId = model.NewId()
|
|
|
|
_, err := ss.Session().Save(rctx, s1)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func testSessionGet(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
s1 := &model.Session{}
|
|
s1.UserId = model.NewId()
|
|
|
|
s1, err := ss.Session().Save(rctx, s1)
|
|
require.NoError(t, err)
|
|
|
|
s2 := &model.Session{}
|
|
s2.UserId = s1.UserId
|
|
|
|
_, err = ss.Session().Save(rctx, s2)
|
|
require.NoError(t, err)
|
|
|
|
s3 := &model.Session{}
|
|
s3.UserId = s1.UserId
|
|
s3.ExpiresAt = 1
|
|
|
|
_, err = ss.Session().Save(rctx, s3)
|
|
require.NoError(t, err)
|
|
|
|
session, err := ss.Session().Get(rctx, s1.Id)
|
|
require.NoError(t, err)
|
|
require.Equal(t, session.Id, s1.Id, "should match")
|
|
|
|
session.Props[model.SessionPropOs] = "linux"
|
|
session.Props[model.SessionPropBrowser] = "Chrome"
|
|
err = ss.Session().UpdateProps(session)
|
|
require.NoError(t, err)
|
|
|
|
session2, err := ss.Session().Get(rctx, session.Id)
|
|
require.NoError(t, err)
|
|
require.Equal(t, session.Props, session2.Props, "should match")
|
|
|
|
data, err := ss.Session().GetSessions(rctx, s1.UserId)
|
|
require.NoError(t, err)
|
|
require.Len(t, data, 3, "should match len")
|
|
}
|
|
|
|
func testSessionGetWithDeviceId(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
s1 := &model.Session{}
|
|
s1.UserId = model.NewId()
|
|
s1.ExpiresAt = model.GetMillis() + 10000
|
|
s1.Props = model.StringMap{}
|
|
|
|
s1, err := ss.Session().Save(rctx, s1)
|
|
require.NoError(t, err)
|
|
|
|
s2 := &model.Session{}
|
|
s2.UserId = s1.UserId
|
|
s2.DeviceId = model.NewId()
|
|
s2.ExpiresAt = model.GetMillis() + 10000
|
|
s2.Props = model.StringMap{}
|
|
|
|
_, err = ss.Session().Save(rctx, s2)
|
|
require.NoError(t, err)
|
|
|
|
s3 := &model.Session{}
|
|
s3.UserId = s1.UserId
|
|
s3.ExpiresAt = 1
|
|
s3.DeviceId = model.NewId()
|
|
s3.Props = model.StringMap{}
|
|
|
|
_, err = ss.Session().Save(rctx, s3)
|
|
require.NoError(t, err)
|
|
|
|
s4 := &model.Session{}
|
|
s4.UserId = s1.UserId
|
|
s4.DeviceId = model.NewId()
|
|
s4.ExpiresAt = model.GetMillis() + 10000
|
|
s4.Props = model.StringMap{
|
|
model.SessionPropLastRemovedDeviceId: s4.DeviceId,
|
|
}
|
|
|
|
_, err = ss.Session().Save(rctx, s4)
|
|
require.NoError(t, err)
|
|
|
|
data, err := ss.Session().GetSessionsWithActiveDeviceIds(s1.UserId)
|
|
require.NoError(t, err)
|
|
require.Len(t, data, 1, "should match len")
|
|
}
|
|
|
|
func testSessionRemove(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
s1 := &model.Session{}
|
|
s1.UserId = model.NewId()
|
|
|
|
s1, err := ss.Session().Save(rctx, s1)
|
|
require.NoError(t, err)
|
|
|
|
session, err := ss.Session().Get(rctx, s1.Id)
|
|
require.NoError(t, err)
|
|
require.Equal(t, session.Id, s1.Id, "should match")
|
|
|
|
removeErr := ss.Session().Remove(s1.Id)
|
|
require.NoError(t, removeErr)
|
|
|
|
_, err = ss.Session().Get(rctx, s1.Id)
|
|
require.Error(t, err, "should have been removed")
|
|
}
|
|
|
|
func testSessionRemoveAll(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
s1 := &model.Session{}
|
|
s1.UserId = model.NewId()
|
|
|
|
s1, err := ss.Session().Save(rctx, s1)
|
|
require.NoError(t, err)
|
|
|
|
session, err := ss.Session().Get(rctx, s1.Id)
|
|
require.NoError(t, err)
|
|
require.Equal(t, session.Id, s1.Id, "should match")
|
|
|
|
removeErr := ss.Session().RemoveAllSessions()
|
|
require.NoError(t, removeErr)
|
|
|
|
_, err = ss.Session().Get(rctx, s1.Id)
|
|
require.Error(t, err, "should have been removed")
|
|
}
|
|
|
|
func testSessionRemoveByUser(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
s1 := &model.Session{}
|
|
s1.UserId = model.NewId()
|
|
|
|
s1, err := ss.Session().Save(rctx, s1)
|
|
require.NoError(t, err)
|
|
|
|
session, err := ss.Session().Get(rctx, s1.Id)
|
|
require.NoError(t, err)
|
|
require.Equal(t, session.Id, s1.Id, "should match")
|
|
|
|
deleteErr := ss.Session().PermanentDeleteSessionsByUser(s1.UserId)
|
|
require.NoError(t, deleteErr)
|
|
|
|
_, err = ss.Session().Get(rctx, s1.Id)
|
|
require.Error(t, err, "should have been removed")
|
|
}
|
|
|
|
func testSessionRemoveToken(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
s1 := &model.Session{}
|
|
s1.UserId = model.NewId()
|
|
|
|
s1, err := ss.Session().Save(rctx, s1)
|
|
require.NoError(t, err)
|
|
|
|
session, err := ss.Session().Get(rctx, s1.Id)
|
|
require.NoError(t, err)
|
|
require.Equal(t, session.Id, s1.Id, "should match")
|
|
|
|
removeErr := ss.Session().Remove(s1.Token)
|
|
require.NoError(t, removeErr)
|
|
|
|
_, err = ss.Session().Get(rctx, s1.Id)
|
|
require.Error(t, err, "should have been removed")
|
|
|
|
data, err := ss.Session().GetSessions(rctx, s1.UserId)
|
|
require.NoError(t, err)
|
|
require.Empty(t, data, "should match len")
|
|
}
|
|
|
|
func testSessionUpdateDeviceId(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
s1 := &model.Session{}
|
|
s1.UserId = model.NewId()
|
|
|
|
s1, err := ss.Session().Save(rctx, s1)
|
|
require.NoError(t, err)
|
|
|
|
err = ss.Session().UpdateDeviceId(s1.Id, model.PushNotifyAppleReactNative+":1234567890", "", s1.ExpiresAt)
|
|
require.NoError(t, err)
|
|
|
|
s2 := &model.Session{}
|
|
s2.UserId = model.NewId()
|
|
|
|
s2, err = ss.Session().Save(rctx, s2)
|
|
require.NoError(t, err)
|
|
|
|
err = ss.Session().UpdateDeviceId(s2.Id, model.PushNotifyAppleReactNative+":1234567890", "", s1.ExpiresAt)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func testSessionUpdateDeviceId2(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
s1 := &model.Session{}
|
|
s1.UserId = model.NewId()
|
|
|
|
s1, err := ss.Session().Save(rctx, s1)
|
|
require.NoError(t, err)
|
|
|
|
err = ss.Session().UpdateDeviceId(s1.Id, model.PushNotifyAppleReactNative+":1234567890", model.PushNotifyAppleReactNative+":voiptoken", s1.ExpiresAt)
|
|
require.NoError(t, err)
|
|
|
|
s2 := &model.Session{}
|
|
s2.UserId = model.NewId()
|
|
|
|
s2, err = ss.Session().Save(rctx, s2)
|
|
require.NoError(t, err)
|
|
|
|
err = ss.Session().UpdateDeviceId(s2.Id, model.PushNotifyAppleReactNative+":1234567890", "", s1.ExpiresAt)
|
|
require.NoError(t, err)
|
|
|
|
// Verify both columns round-tripped via the regular Get path.
|
|
got, err := ss.Session().Get(rctx, s1.Id)
|
|
require.NoError(t, err)
|
|
require.Equal(t, model.PushNotifyAppleReactNative+":1234567890", got.DeviceId)
|
|
require.Equal(t, model.PushNotifyAppleReactNative+":voiptoken", got.VoIPDeviceId)
|
|
}
|
|
|
|
func testSessionUpdateDeviceIdClearsOnEmpty(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
s := &model.Session{UserId: model.NewId()}
|
|
s, err := ss.Session().Save(rctx, s)
|
|
require.NoError(t, err)
|
|
|
|
standardToken := model.PushNotifyAppleReactNative + ":standard"
|
|
voIPToken := model.PushNotifyAppleReactNative + ":voip"
|
|
err = ss.Session().UpdateDeviceId(s.Id, standardToken, voIPToken, s.ExpiresAt)
|
|
require.NoError(t, err)
|
|
|
|
err = ss.Session().UpdateDeviceId(s.Id, "", "", s.ExpiresAt)
|
|
require.NoError(t, err)
|
|
|
|
got, err := ss.Session().Get(rctx, s.Id)
|
|
require.NoError(t, err)
|
|
require.Empty(t, got.DeviceId)
|
|
require.Empty(t, got.VoIPDeviceId)
|
|
}
|
|
|
|
func testSessionStoreUpdateExpiresAt(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
s1 := &model.Session{}
|
|
s1.UserId = model.NewId()
|
|
|
|
s1, err := ss.Session().Save(rctx, s1)
|
|
require.NoError(t, err)
|
|
|
|
err = ss.Session().UpdateExpiresAt(s1.Id, 1234567890)
|
|
require.NoError(t, err)
|
|
|
|
session, err := ss.Session().Get(rctx, s1.Id)
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, session.ExpiresAt, 1234567890, "ExpiresAt not updated correctly")
|
|
}
|
|
|
|
func testSessionStoreUpdateLastActivityAt(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
s1 := &model.Session{}
|
|
s1.UserId = model.NewId()
|
|
|
|
s1, err := ss.Session().Save(rctx, s1)
|
|
require.NoError(t, err)
|
|
|
|
err = ss.Session().UpdateLastActivityAt(s1.Id, 1234567890)
|
|
require.NoError(t, err)
|
|
|
|
session, err := ss.Session().Get(rctx, s1.Id)
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, session.LastActivityAt, 1234567890, "LastActivityAt not updated correctly")
|
|
}
|
|
|
|
func testSessionCount(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
s1 := &model.Session{}
|
|
s1.UserId = model.NewId()
|
|
s1.ExpiresAt = model.GetMillis() + 100000
|
|
|
|
_, err := ss.Session().Save(rctx, s1)
|
|
require.NoError(t, err)
|
|
|
|
count, err := ss.Session().AnalyticsSessionCount()
|
|
require.NoError(t, err)
|
|
require.NotZero(t, count, "should have at least 1 session")
|
|
}
|
|
|
|
func testSessionCleanup(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
now := model.GetMillis()
|
|
|
|
s1 := &model.Session{}
|
|
s1.UserId = model.NewId()
|
|
s1.ExpiresAt = 0 // never expires
|
|
|
|
s1, err := ss.Session().Save(rctx, s1)
|
|
require.NoError(t, err)
|
|
|
|
s2 := &model.Session{}
|
|
s2.UserId = s1.UserId
|
|
s2.ExpiresAt = now + 1000000 // expires in the future
|
|
|
|
s2, err = ss.Session().Save(rctx, s2)
|
|
require.NoError(t, err)
|
|
|
|
s3 := &model.Session{}
|
|
s3.UserId = model.NewId()
|
|
s3.ExpiresAt = 1 // expired
|
|
|
|
s3, err = ss.Session().Save(rctx, s3)
|
|
require.NoError(t, err)
|
|
|
|
s4 := &model.Session{}
|
|
s4.UserId = model.NewId()
|
|
s4.ExpiresAt = 2 // expired
|
|
|
|
s4, err = ss.Session().Save(rctx, s4)
|
|
require.NoError(t, err)
|
|
|
|
err = ss.Session().Cleanup(now, 1)
|
|
require.NoError(t, err)
|
|
|
|
_, err = ss.Session().Get(rctx, s1.Id)
|
|
assert.NoError(t, err)
|
|
|
|
_, err = ss.Session().Get(rctx, s2.Id)
|
|
assert.NoError(t, err)
|
|
|
|
_, err = ss.Session().Get(rctx, s3.Id)
|
|
assert.Error(t, err)
|
|
|
|
_, err = ss.Session().Get(rctx, s4.Id)
|
|
assert.Error(t, err)
|
|
|
|
removeErr := ss.Session().Remove(s1.Id)
|
|
require.NoError(t, removeErr)
|
|
|
|
removeErr = ss.Session().Remove(s2.Id)
|
|
require.NoError(t, removeErr)
|
|
}
|
|
|
|
func testGetSessionsExpired(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
now := model.GetMillis()
|
|
|
|
// Clear existing sessions.
|
|
err := ss.Session().RemoveAllSessions()
|
|
require.NoError(t, err)
|
|
|
|
s1 := &model.Session{}
|
|
s1.UserId = model.NewId()
|
|
s1.DeviceId = model.NewId()
|
|
s1.ExpiresAt = 0 // never expires
|
|
_, err = ss.Session().Save(rctx, s1)
|
|
require.NoError(t, err)
|
|
|
|
s2 := &model.Session{}
|
|
s2.UserId = model.NewId()
|
|
s2.DeviceId = model.NewId()
|
|
s2.ExpiresAt = now - TenMinutes // expired within threshold
|
|
s2, err = ss.Session().Save(rctx, s2)
|
|
require.NoError(t, err)
|
|
|
|
s3 := &model.Session{}
|
|
s3.UserId = model.NewId()
|
|
s3.DeviceId = model.NewId()
|
|
s3.ExpiresAt = now - (TenMinutes * 100) // expired outside threshold
|
|
_, err = ss.Session().Save(rctx, s3)
|
|
require.NoError(t, err)
|
|
|
|
s4 := &model.Session{}
|
|
s4.UserId = model.NewId()
|
|
s4.ExpiresAt = now - TenMinutes // expired within threshold, but not mobile
|
|
s4, err = ss.Session().Save(rctx, s4)
|
|
require.NoError(t, err)
|
|
|
|
s5 := &model.Session{}
|
|
s5.UserId = model.NewId()
|
|
s5.DeviceId = model.NewId()
|
|
s5.ExpiresAt = now + (TenMinutes * 100000) // not expired
|
|
_, err = ss.Session().Save(rctx, s5)
|
|
require.NoError(t, err)
|
|
|
|
sessions, err := ss.Session().GetSessionsExpired(TenMinutes*2, true, true) // mobile only
|
|
require.NoError(t, err)
|
|
require.Len(t, sessions, 1)
|
|
require.Equal(t, s2.Id, sessions[0].Id)
|
|
|
|
sessions, err = ss.Session().GetSessionsExpired(TenMinutes*2, false, true) // all client types
|
|
require.NoError(t, err)
|
|
require.Len(t, sessions, 2)
|
|
expected := []string{s2.Id, s4.Id}
|
|
for _, sess := range sessions {
|
|
require.Contains(t, expected, sess.Id)
|
|
}
|
|
}
|
|
|
|
func testGetSessionsWithActiveDeviceIds(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
userId := model.NewId()
|
|
|
|
// Create session 1 with a device ID
|
|
s1 := &model.Session{}
|
|
s1.UserId = userId
|
|
s1.ExpiresAt = model.GetMillis() + 100000
|
|
s1.DeviceId = model.NewId()
|
|
s1, err := ss.Session().Save(rctx, s1)
|
|
require.NoError(t, err)
|
|
|
|
// Create session 2 with a device ID and a prop for last_removed_device_id that doesn't match the device ID
|
|
s2 := &model.Session{}
|
|
s2.UserId = userId
|
|
s2.ExpiresAt = model.GetMillis() + 100000
|
|
s2.DeviceId = model.NewId()
|
|
s2.AddProp(model.SessionPropLastRemovedDeviceId, model.NewId())
|
|
s2, err = ss.Session().Save(rctx, s2)
|
|
require.NoError(t, err)
|
|
|
|
// Create session 3 with a device ID and a prop for last_removed_device_id that matches the device ID - this should be filtered out
|
|
s3 := &model.Session{}
|
|
s3.UserId = userId
|
|
s3.ExpiresAt = model.GetMillis() + 100000
|
|
s3.DeviceId = model.NewId()
|
|
s3.AddProp(model.SessionPropLastRemovedDeviceId, s3.DeviceId)
|
|
s3, err = ss.Session().Save(rctx, s3)
|
|
require.NoError(t, err)
|
|
|
|
// Create session 4 with no device ID - this should be filtered out
|
|
s4 := &model.Session{}
|
|
s4.UserId = userId
|
|
s4.ExpiresAt = model.GetMillis() + 100000
|
|
s4, err = ss.Session().Save(rctx, s4)
|
|
require.NoError(t, err)
|
|
|
|
// Create session 5 with a device ID but expired - this should be filtered out
|
|
s5 := &model.Session{}
|
|
s5.UserId = userId
|
|
s5.ExpiresAt = model.GetMillis() - 100000
|
|
s5.DeviceId = model.NewId()
|
|
s5, err = ss.Session().Save(rctx, s5)
|
|
require.NoError(t, err)
|
|
|
|
// Create session 6 with dead standard token but live VoIP token - should be INCLUDED
|
|
// (the "silence chat, keep ringing" case: alert topic dead, PushKit still alive).
|
|
s6 := &model.Session{}
|
|
s6.UserId = userId
|
|
s6.ExpiresAt = model.GetMillis() + 100000
|
|
s6.DeviceId = model.NewId()
|
|
s6.VoIPDeviceId = model.NewId()
|
|
s6.AddProp(model.SessionPropLastRemovedDeviceId, s6.DeviceId)
|
|
s6, err = ss.Session().Save(rctx, s6)
|
|
require.NoError(t, err)
|
|
|
|
// Create session 7 with live standard + dead VoIP - should be INCLUDED (via standard).
|
|
s7 := &model.Session{}
|
|
s7.UserId = userId
|
|
s7.ExpiresAt = model.GetMillis() + 100000
|
|
s7.DeviceId = model.NewId()
|
|
s7.VoIPDeviceId = model.NewId()
|
|
s7.AddProp(model.SessionPropLastRemovedVoIPDeviceId, s7.VoIPDeviceId)
|
|
s7, err = ss.Session().Save(rctx, s7)
|
|
require.NoError(t, err)
|
|
|
|
// Create session 8 with both tokens dead - should be EXCLUDED.
|
|
s8 := &model.Session{}
|
|
s8.UserId = userId
|
|
s8.ExpiresAt = model.GetMillis() + 100000
|
|
s8.DeviceId = model.NewId()
|
|
s8.VoIPDeviceId = model.NewId()
|
|
s8.AddProp(model.SessionPropLastRemovedDeviceId, s8.DeviceId)
|
|
s8.AddProp(model.SessionPropLastRemovedVoIPDeviceId, s8.VoIPDeviceId)
|
|
s8, err = ss.Session().Save(rctx, s8)
|
|
require.NoError(t, err)
|
|
|
|
// Create session 9 with VoIP token only (no standard) - should be INCLUDED.
|
|
s9 := &model.Session{}
|
|
s9.UserId = userId
|
|
s9.ExpiresAt = model.GetMillis() + 100000
|
|
s9.VoIPDeviceId = model.NewId()
|
|
s9, err = ss.Session().Save(rctx, s9)
|
|
require.NoError(t, err)
|
|
|
|
// Get sessions with active device IDs
|
|
sessions, err := ss.Session().GetSessionsWithActiveDeviceIds(userId)
|
|
require.NoError(t, err)
|
|
|
|
// Expected included: s1, s2, s6, s7, s9.
|
|
require.Len(t, sessions, 5)
|
|
|
|
sessionIds := make(map[string]bool)
|
|
for _, session := range sessions {
|
|
sessionIds[session.Id] = true
|
|
}
|
|
require.True(t, sessionIds[s1.Id])
|
|
require.True(t, sessionIds[s2.Id])
|
|
require.True(t, sessionIds[s6.Id])
|
|
require.True(t, sessionIds[s7.Id])
|
|
require.True(t, sessionIds[s9.Id])
|
|
|
|
// Excluded: s3 (dead standard, no VoIP), s4 (no device IDs), s5 (expired),
|
|
// s8 (both dead).
|
|
require.False(t, sessionIds[s3.Id])
|
|
require.False(t, sessionIds[s4.Id])
|
|
require.False(t, sessionIds[s5.Id])
|
|
require.False(t, sessionIds[s8.Id])
|
|
}
|
|
|
|
func testUpdateExpiredNotify(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
s1 := &model.Session{}
|
|
s1.UserId = model.NewId()
|
|
s1.DeviceId = model.NewId()
|
|
s1.ExpiresAt = model.GetMillis() + TenMinutes
|
|
s1, err := ss.Session().Save(rctx, s1)
|
|
require.NoError(t, err)
|
|
|
|
session, err := ss.Session().Get(rctx, s1.Id)
|
|
require.NoError(t, err)
|
|
require.False(t, session.ExpiredNotify)
|
|
|
|
err = ss.Session().UpdateExpiredNotify(session.Id, true)
|
|
require.NoError(t, err)
|
|
session, err = ss.Session().Get(rctx, s1.Id)
|
|
require.NoError(t, err)
|
|
require.True(t, session.ExpiredNotify)
|
|
|
|
err = ss.Session().UpdateExpiredNotify(session.Id, false)
|
|
require.NoError(t, err)
|
|
session, err = ss.Session().Get(rctx, s1.Id)
|
|
require.NoError(t, err)
|
|
require.False(t, session.ExpiredNotify)
|
|
}
|
|
|
|
func testGetLRUSessions(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
userId := model.NewId()
|
|
|
|
// Clear existing sessions.
|
|
err := ss.Session().RemoveAllSessions()
|
|
require.NoError(t, err)
|
|
|
|
s1 := &model.Session{}
|
|
s1.UserId = userId
|
|
s1.DeviceId = model.NewId()
|
|
_, err = ss.Session().Save(rctx, s1)
|
|
require.NoError(t, err)
|
|
time.Sleep(1 * time.Millisecond)
|
|
|
|
s2 := &model.Session{}
|
|
s2.UserId = userId
|
|
s2.DeviceId = model.NewId()
|
|
s2, err = ss.Session().Save(rctx, s2)
|
|
require.NoError(t, err)
|
|
time.Sleep(1 * time.Millisecond)
|
|
|
|
s3 := &model.Session{}
|
|
s3.UserId = userId
|
|
s3.DeviceId = model.NewId()
|
|
s3, err = ss.Session().Save(rctx, s3)
|
|
require.NoError(t, err)
|
|
|
|
sessions, err := ss.Session().GetLRUSessions(rctx, userId, 3, 3)
|
|
require.NoError(t, err)
|
|
require.Len(t, sessions, 0)
|
|
|
|
sessions, err = ss.Session().GetLRUSessions(rctx, userId, 3, 2)
|
|
require.NoError(t, err)
|
|
require.Len(t, sessions, 1)
|
|
require.Equal(t, s1.Id, sessions[0].Id)
|
|
|
|
sessions, err = ss.Session().GetLRUSessions(rctx, userId, 3, 1)
|
|
require.NoError(t, err)
|
|
require.Len(t, sessions, 2)
|
|
require.Equal(t, s2.Id, sessions[0].Id)
|
|
require.Equal(t, s1.Id, sessions[1].Id)
|
|
|
|
sessions, err = ss.Session().GetLRUSessions(rctx, userId, 3, 0)
|
|
require.NoError(t, err)
|
|
require.Len(t, sessions, 3)
|
|
require.Equal(t, s3.Id, sessions[0].Id)
|
|
require.Equal(t, s2.Id, sessions[1].Id)
|
|
require.Equal(t, s1.Id, sessions[2].Id)
|
|
}
|
|
|
|
func testGetMobileSessionMetadata(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
userId1 := model.NewId()
|
|
userId2 := model.NewId()
|
|
userId3 := model.NewId()
|
|
userId4 := model.NewId()
|
|
userId5 := model.NewId()
|
|
|
|
// Clear existing sessions.
|
|
err := ss.Session().RemoveAllSessions()
|
|
require.NoError(t, err)
|
|
|
|
s1 := &model.Session{}
|
|
s1.UserId = userId1
|
|
s1.ExpiresAt = model.GetMillis() + 10000
|
|
|
|
_, err = ss.Session().Save(rctx, s1)
|
|
require.NoError(t, err)
|
|
|
|
s2 := &model.Session{}
|
|
s2.UserId = userId2
|
|
s2.DeviceId = "android:" + model.NewId()
|
|
s2.ExpiresAt = model.GetMillis() + 10000
|
|
s2.Props = model.StringMap{
|
|
model.SessionPropDeviceNotificationDisabled: "false",
|
|
model.SessionPropMobileVersion: "1.2.3",
|
|
}
|
|
|
|
_, err = ss.Session().Save(rctx, s2)
|
|
require.NoError(t, err)
|
|
|
|
s3 := &model.Session{}
|
|
s3.UserId = userId3
|
|
s3.DeviceId = "ios:" + model.NewId()
|
|
s3.ExpiresAt = model.GetMillis() + 10000
|
|
s3.Props = model.StringMap{
|
|
model.SessionPropDeviceNotificationDisabled: "true",
|
|
model.SessionPropMobileVersion: "1.2.3",
|
|
}
|
|
|
|
_, err = ss.Session().Save(rctx, s3)
|
|
require.NoError(t, err)
|
|
|
|
s4 := &model.Session{}
|
|
s4.UserId = userId4
|
|
s4.DeviceId = "android:" + model.NewId()
|
|
s4.ExpiresAt = model.GetMillis() + 10000
|
|
s4.Props = model.StringMap{
|
|
model.SessionPropDeviceNotificationDisabled: "true",
|
|
model.SessionPropMobileVersion: "3.2.1",
|
|
}
|
|
|
|
_, err = ss.Session().Save(rctx, s4)
|
|
require.NoError(t, err)
|
|
|
|
s5 := &model.Session{}
|
|
s5.UserId = userId5
|
|
s5.DeviceId = "android:" + model.NewId()
|
|
s5.ExpiresAt = model.GetMillis() + 10000
|
|
s5.Props = model.StringMap{
|
|
model.SessionPropDeviceNotificationDisabled: "true",
|
|
model.SessionPropMobileVersion: "3.2.1",
|
|
}
|
|
|
|
_, err = ss.Session().Save(rctx, s5)
|
|
require.NoError(t, err)
|
|
|
|
metadata, err := ss.Session().GetMobileSessionMetadata()
|
|
require.NoError(t, err)
|
|
require.Len(t, metadata, 4)
|
|
found := false
|
|
for _, d := range metadata {
|
|
if d.NotificationDisabled == "true" &&
|
|
d.Platform == "android" &&
|
|
d.Version == "3.2.1" {
|
|
found = true
|
|
require.Equal(t, float64(2), d.Count)
|
|
}
|
|
}
|
|
require.True(t, found)
|
|
}
|