snapshot of https://github.com/mattermost/mattermost.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s
This commit is contained in:
256
server/channels/app/platform/status_test.go
Normal file
256
server/channels/app/platform/status_test.go
Normal file
@@ -0,0 +1,256 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package platform
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
)
|
||||
|
||||
func TestSaveStatus(t *testing.T) {
|
||||
th := Setup(t).InitBasic(t)
|
||||
|
||||
user := th.BasicUser
|
||||
|
||||
for _, statusString := range []string{
|
||||
model.StatusOnline,
|
||||
model.StatusAway,
|
||||
model.StatusDnd,
|
||||
model.StatusOffline,
|
||||
} {
|
||||
t.Run(statusString, func(t *testing.T) {
|
||||
status := &model.Status{
|
||||
UserId: user.Id,
|
||||
Status: statusString,
|
||||
}
|
||||
|
||||
th.Service.SaveAndBroadcastStatus(status)
|
||||
|
||||
after, err := th.Service.GetStatus(user.Id)
|
||||
require.Nil(t, err, "failed to get status after save: %v", err)
|
||||
require.Equal(t, statusString, after.Status, "failed to save status, got %v, expected %v", after.Status, statusString)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTruncateDNDEndTime(t *testing.T) {
|
||||
// 2025-Jan-20 at 17:13:32 GMT becomes 17:13:00
|
||||
assert.Equal(t, int64(1737393180), truncateDNDEndTime(1737393212))
|
||||
|
||||
// 2025-Jan-20 at 17:13:00 GMT remains unchanged
|
||||
assert.Equal(t, int64(1737393180), truncateDNDEndTime(1737393180))
|
||||
|
||||
// 2025-Jan-20 at 00:00:10 GMT becomes 00:00:00
|
||||
assert.Equal(t, int64(1737331200), truncateDNDEndTime(1737331210))
|
||||
|
||||
// 2025-Jan-20 at 00:00:10 GMT remains unchanged
|
||||
assert.Equal(t, int64(1737331200), truncateDNDEndTime(1737331200))
|
||||
}
|
||||
|
||||
func TestQueueSetStatusOffline(t *testing.T) {
|
||||
th := Setup(t).InitBasic(t)
|
||||
|
||||
// Create multiple user IDs
|
||||
userIDs := []string{
|
||||
th.BasicUser.Id,
|
||||
model.NewId(),
|
||||
model.NewId(),
|
||||
model.NewId(),
|
||||
}
|
||||
|
||||
// Add duplicate user IDs to test duplicate handling
|
||||
// The second occurrence should override the first
|
||||
userIDs = append(userIDs, userIDs[0], userIDs[1])
|
||||
|
||||
// Initially set all users to online
|
||||
for _, userID := range userIDs {
|
||||
th.Service.SetStatusOnline(userID, false)
|
||||
status, err := th.Service.GetStatus(userID)
|
||||
require.Nil(t, err, "Failed to get initial status")
|
||||
require.Equal(t, model.StatusOnline, status.Status, "User should be online initially")
|
||||
}
|
||||
|
||||
// Queue status updates to offline
|
||||
for i, userID := range userIDs {
|
||||
// Set every other status as manual to test both cases
|
||||
manual := i%2 == 0
|
||||
th.Service.QueueSetStatusOffline(userID, manual)
|
||||
}
|
||||
|
||||
// Wait for the background processor to handle the updates
|
||||
// Use eventually consistent approach with retries
|
||||
for idx, userID := range userIDs {
|
||||
var status *model.Status
|
||||
var err *model.AppError
|
||||
|
||||
// Use poll-wait pattern to account for async processing
|
||||
require.Eventually(t, func() bool {
|
||||
status, err = th.Service.GetStatus(userID)
|
||||
return err == nil && status.Status == model.StatusOffline
|
||||
}, 5*time.Second, 100*time.Millisecond, "Status wasn't updated to offline")
|
||||
|
||||
// For the duplicated user IDs, check that manual setting is based on the last call
|
||||
// User[0] and User[1] are duplicated at the end of the slice
|
||||
switch idx {
|
||||
case 0, 4: // first duplicated user
|
||||
// Last update for userIDs[0] was at index 4 (i%2 == 0, so manual = true)
|
||||
require.True(t, status.Manual, "User should have manual status (duplicate case)")
|
||||
case 1, 5:
|
||||
// Last update for userIDs[1] was at index 5 (i%2 == 1, so manual = false)
|
||||
require.False(t, status.Manual, "User should have automatic status (duplicate case)")
|
||||
default:
|
||||
require.Equal(t, idx%2 == 0, status.Manual, "Manual flag incorrect")
|
||||
}
|
||||
}
|
||||
|
||||
// Verify all relevant status fields
|
||||
for _, userID := range model.RemoveDuplicateStrings(userIDs) {
|
||||
status, err := th.Service.GetStatus(userID)
|
||||
require.Nil(t, err, "Failed to get status")
|
||||
require.Equal(t, model.StatusOffline, status.Status, "User should be offline")
|
||||
require.Equal(t, "", status.ActiveChannel, "ActiveChannel should be empty")
|
||||
}
|
||||
|
||||
// First shut down the test environment
|
||||
th.Shutdown(t)
|
||||
|
||||
// Then verify that the status update processor has properly shut down
|
||||
// by checking that the done signal channel is closed
|
||||
select {
|
||||
case _, ok := <-th.Service.statusUpdateDoneSignal:
|
||||
// If channel is closed, ok will be false
|
||||
assert.False(t, ok, "statusUpdateDoneSignal channel should be closed after teardown")
|
||||
case <-time.After(5 * time.Second):
|
||||
require.Fail(t, "Timed out waiting for status update processor to shut down")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetStatusOffline(t *testing.T) {
|
||||
th := Setup(t).InitBasic(t)
|
||||
|
||||
user := th.BasicUser
|
||||
|
||||
t.Run("when user statuses are disabled", func(t *testing.T) {
|
||||
th.Service.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.EnableUserStatuses = false
|
||||
})
|
||||
|
||||
// Set initial status to online
|
||||
status := &model.Status{
|
||||
UserId: user.Id,
|
||||
Status: model.StatusOnline,
|
||||
}
|
||||
th.Service.SaveAndBroadcastStatus(status)
|
||||
|
||||
// Try to set offline
|
||||
th.Service.SetStatusOffline(user.Id, false, false)
|
||||
|
||||
// Enable user statuses to see what is really in the database
|
||||
th.Service.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.EnableUserStatuses = true
|
||||
})
|
||||
|
||||
// Status should remain unchanged
|
||||
after, err := th.Service.GetStatus(user.Id)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, model.StatusOnline, after.Status)
|
||||
})
|
||||
|
||||
t.Run("when setting status manually over manually set status", func(t *testing.T) {
|
||||
th.Service.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.EnableUserStatuses = true
|
||||
})
|
||||
|
||||
// Set initial status to online manually
|
||||
status := &model.Status{
|
||||
UserId: user.Id,
|
||||
Status: model.StatusOnline,
|
||||
Manual: true,
|
||||
}
|
||||
th.Service.SaveAndBroadcastStatus(status)
|
||||
|
||||
// Try to set offline non-manually
|
||||
th.Service.SetStatusOffline(user.Id, false, false)
|
||||
|
||||
// Status should remain unchanged because manual status takes precedence
|
||||
after, err := th.Service.GetStatus(user.Id)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, model.StatusOnline, after.Status)
|
||||
assert.True(t, after.Manual)
|
||||
})
|
||||
|
||||
t.Run("when force flag is true over manually set status", func(t *testing.T) {
|
||||
th.Service.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.EnableUserStatuses = true
|
||||
})
|
||||
|
||||
// Set initial status to online manually
|
||||
status := &model.Status{
|
||||
UserId: user.Id,
|
||||
Status: model.StatusOnline,
|
||||
Manual: true,
|
||||
}
|
||||
th.Service.SaveAndBroadcastStatus(status)
|
||||
|
||||
// Try to set offline with force flag
|
||||
th.Service.SetStatusOffline(user.Id, false, true)
|
||||
|
||||
// Status should change despite being manual
|
||||
after, err := th.Service.GetStatus(user.Id)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, model.StatusOffline, after.Status)
|
||||
assert.False(t, after.Manual)
|
||||
})
|
||||
|
||||
t.Run("when setting status normally", func(t *testing.T) {
|
||||
th.Service.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.EnableUserStatuses = true
|
||||
})
|
||||
|
||||
// Set initial status to online
|
||||
status := &model.Status{
|
||||
UserId: user.Id,
|
||||
Status: model.StatusOnline,
|
||||
Manual: false,
|
||||
}
|
||||
th.Service.SaveAndBroadcastStatus(status)
|
||||
|
||||
// Set offline
|
||||
th.Service.SetStatusOffline(user.Id, false, false)
|
||||
|
||||
// Status should change
|
||||
after, err := th.Service.GetStatus(user.Id)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, model.StatusOffline, after.Status)
|
||||
assert.False(t, after.Manual)
|
||||
})
|
||||
|
||||
t.Run("when setting status manually over normal status", func(t *testing.T) {
|
||||
th.Service.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.EnableUserStatuses = true
|
||||
})
|
||||
|
||||
// Set initial status to online
|
||||
status := &model.Status{
|
||||
UserId: user.Id,
|
||||
Status: model.StatusOnline,
|
||||
Manual: false,
|
||||
}
|
||||
th.Service.SaveAndBroadcastStatus(status)
|
||||
|
||||
// Set offline manually
|
||||
th.Service.SetStatusOffline(user.Id, true, false)
|
||||
|
||||
// Status should change and be marked as manual
|
||||
after, err := th.Service.GetStatus(user.Id)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, model.StatusOffline, after.Status)
|
||||
assert.True(t, after.Manual)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user