Files
Carlos GarciaandGitHub 8bcc9baa29 Mm 68291 send wipe (#36945)
* sends wipe signal ro mobile devices when revoking user sessions

there is a session notification type that can be used with no content
to indicate the session has been revoked and data should be wiped client
side. This complements the use of session typed notifications to indicate
expiry of user sessions carrying containing some message.

empty push session typed notifications are sent to mobile devices on
revoking sessions only when mobile ephemeral mode is enabled.

* move send wipe push notifications to goroutines

* wraps goroutine launch in Srv().Go() for proper shutdown

* query active sessions to wipe only when ephemeral mode is enabled

* address review comments

* guard to prevent sending wipe notification to removed tokens
2026-06-26 10:28:05 +02:00

730 lines
22 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("GetAllSessionsWithActiveDeviceIds", func(t *testing.T) { testGetAllSessionsWithActiveDeviceIds(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 testGetAllSessionsWithActiveDeviceIds(t *testing.T, rctx request.CTX, ss store.Store) {
err := ss.Session().RemoveAllSessions()
require.NoError(t, err)
userId1 := model.NewId()
userId2 := model.NewId()
s1 := &model.Session{UserId: userId1, ExpiresAt: model.GetMillis() + 100000, DeviceId: model.NewId()}
s1, err = ss.Session().Save(rctx, s1)
require.NoError(t, err)
s2 := &model.Session{UserId: userId2, ExpiresAt: model.GetMillis() + 100000, DeviceId: model.NewId()}
s2, err = ss.Session().Save(rctx, s2)
require.NoError(t, err)
// no device ID — excluded
s3 := &model.Session{UserId: userId1, ExpiresAt: model.GetMillis() + 100000}
s3, err = ss.Session().Save(rctx, s3)
require.NoError(t, err)
// expired — still included so the wipe reaches devices with stale local data
s4 := &model.Session{UserId: userId2, ExpiresAt: model.GetMillis() - 100000, DeviceId: model.NewId()}
s4, err = ss.Session().Save(rctx, s4)
require.NoError(t, err)
sessions, err := ss.Session().GetAllSessionsWithActiveDeviceIds()
require.NoError(t, err)
sessionIds := make(map[string]bool, len(sessions))
for _, s := range sessions {
sessionIds[s.Id] = true
}
require.True(t, sessionIds[s1.Id], "expected session from user1")
require.True(t, sessionIds[s2.Id], "expected session from user2")
require.False(t, sessionIds[s3.Id], "expected session without device ID excluded")
require.True(t, sessionIds[s4.Id], "expected expired session included for wipe")
}
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)
}