mattermost/server/channels/app/limits_test.go
Maria A Nunez 461db71178
Add single-channel guest tracking and reporting (#35451)
* Add single-channel guest tracking and reporting

- Add AnalyticsGetSingleChannelGuestCount store method to count guests in exactly one channel
- Exclude single-channel guests from active user seat count in GetServerLimits
- Add single-channel guest count to standard analytics response
- Add Single-channel Guests card to System Statistics page with overage warning
- Add Single-channel guests row to Edition and License page with overage styling
- Add dismissible admin-only banner when single-channel guest limit is exceeded
- Gate feature behind non-Entry SKU and guest accounts enabled checks
- Re-fetch server limits on config changes for reactive UI updates
- Fix label alignment in license details panel

Made-with: Cursor

* Refine single-channel guest tracking

- Remove license GuestAccounts feature check from shouldTrackSingleChannelGuests (only config matters)
- Re-add getServerLimits calls on page mount for fresh data
- Remove config-change reactivity code (componentDidUpdate, useEffect)
- Add server i18n translations for error strings
- Sync webapp i18n via extract
- Add inline comments for business logic
- Restore struct field comments in ServerLimits model
- Add Playwright E2E tests for single-channel guest feature
- Fix label alignment in license details panel

Made-with: Cursor

* Guests over limit fixes and PR feedback

* Fix linter issues and code quality improvements

- Use max() builtin to clamp adjusted user count instead of if-statement (modernize linter)
- Change banner type from ADVISOR to CRITICAL for proper red color styling

Made-with: Cursor

* Fix overage warnings incorrectly counting single-channel guests

Single-channel guests are free and should not trigger license seat
overage warnings. Update all overage checks to use
serverLimits.activeUserCount (seat-adjusted, excluding SCG) instead
of the raw total_users_count or TOTAL_USERS analytics stat.

- UserSeatAlertBanner on License page: use serverLimits.activeUserCount
- UserSeatAlertBanner on Site Statistics page: use serverLimits.activeUserCount
- ActivatedUserCard display and overage check: use serverLimits.activeUserCount
- OverageUsersBanner: use serverLimits.activeUserCount

Made-with: Cursor

* Use license.Users as fallback for singleChannelGuestLimit before limits load

This prevents the SingleChannelGuestsCard from showing a false overage
state before serverLimits has been fetched, while still rendering the
card immediately on page load.

Made-with: Cursor

* Fix invite modal overage banner incorrectly counting single-channel guests

Made-with: Cursor

* Fix invitation modal tests missing limits entity in mock state

Made-with: Cursor

* Fix tests

* Add E2E test for single-channel guest exceeded limit scenario

Made-with: Cursor

* Fix TypeScript errors in single channel guests E2E test

Made-with: Cursor

* Fix channel name validation error caused by unawaited async getRandomId()

Made-with: Cursor

* Add contextual tooltips to stat cards when guest accounts are enabled

Made-with: Cursor

* Code review feedback: query builder, readability, tooltips, and alignment fixes

Made-with: Cursor

* Fix license page tooltip alignment, width, and SaveLicense SCG exclusion

Made-with: Cursor

* Fix banner dismiss, license spacing, and add dismiss test

Made-with: Cursor

* Exclude DM/GM channels from single-channel guest count and fix E2E tests

Filter the AnalyticsGetSingleChannelGuestCount query to only count
memberships in public/private channels, excluding DMs and GMs. Update
store tests with DM-only, GM-only, and mixed membership cases. Fix E2E
overage test to mock the server limits API instead of skipping, and
correct banner locator to use data-testid.

Made-with: Cursor
2026-03-10 10:31:10 -04:00

835 lines
26 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package app
import (
"errors"
"testing"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/v8/channels/store"
storemocks "github.com/mattermost/mattermost/server/v8/channels/store/storetest/mocks"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
func TestGetServerLimits(t *testing.T) {
mainHelper.Parallel(t)
t.Run("unlicensed server shows hard-coded limits", func(t *testing.T) {
th := Setup(t).InitBasic(t)
th.App.Srv().SetLicense(nil)
serverLimits, appErr := th.App.GetServerLimits()
require.Nil(t, appErr)
// InitBasic creates 3 users by default
require.Equal(t, int64(3), serverLimits.ActiveUserCount)
require.Equal(t, int64(200), serverLimits.MaxUsersLimit)
require.Equal(t, int64(250), serverLimits.MaxUsersHardLimit)
})
t.Run("user count should increase on creating new user and decrease on permanently deleting", func(t *testing.T) {
th := Setup(t).InitBasic(t)
th.App.Srv().SetLicense(nil)
serverLimits, appErr := th.App.GetServerLimits()
require.Nil(t, appErr)
require.Equal(t, int64(3), serverLimits.ActiveUserCount)
// now we create a new user
newUser := th.CreateUser(t)
serverLimits, appErr = th.App.GetServerLimits()
require.Nil(t, appErr)
require.Equal(t, int64(4), serverLimits.ActiveUserCount)
// now we'll delete the user
_ = th.App.PermanentDeleteUser(th.Context, newUser)
serverLimits, appErr = th.App.GetServerLimits()
require.Nil(t, appErr)
require.Equal(t, int64(3), serverLimits.ActiveUserCount)
})
t.Run("user count should increase on creating new guest user and decrease on permanently deleting", func(t *testing.T) {
th := Setup(t).InitBasic(t)
th.App.Srv().SetLicense(nil)
serverLimits, appErr := th.App.GetServerLimits()
require.Nil(t, appErr)
require.Equal(t, int64(3), serverLimits.ActiveUserCount)
// now we create a new user
newGuestUser := th.CreateGuest(t)
serverLimits, appErr = th.App.GetServerLimits()
require.Nil(t, appErr)
require.Equal(t, int64(4), serverLimits.ActiveUserCount)
// now we'll delete the user
_ = th.App.PermanentDeleteUser(th.Context, newGuestUser)
serverLimits, appErr = th.App.GetServerLimits()
require.Nil(t, appErr)
require.Equal(t, int64(3), serverLimits.ActiveUserCount)
})
t.Run("user count should increase on creating new user and decrease on soft deleting", func(t *testing.T) {
th := Setup(t).InitBasic(t)
th.App.Srv().SetLicense(nil)
serverLimits, appErr := th.App.GetServerLimits()
require.Nil(t, appErr)
require.Equal(t, int64(3), serverLimits.ActiveUserCount)
// now we create a new user
newUser := th.CreateUser(t)
serverLimits, appErr = th.App.GetServerLimits()
require.Nil(t, appErr)
require.Equal(t, int64(4), serverLimits.ActiveUserCount)
// now we'll delete the user
_, appErr = th.App.UpdateActive(th.Context, newUser, false)
require.Nil(t, appErr)
serverLimits, appErr = th.App.GetServerLimits()
require.Nil(t, appErr)
require.Equal(t, int64(3), serverLimits.ActiveUserCount)
})
t.Run("user count should increase on creating new guest user and decrease on soft deleting", func(t *testing.T) {
th := Setup(t).InitBasic(t)
th.App.Srv().SetLicense(nil)
serverLimits, appErr := th.App.GetServerLimits()
require.Nil(t, appErr)
require.Equal(t, int64(3), serverLimits.ActiveUserCount)
// now we create a new user
newGuestUser := th.CreateGuest(t)
serverLimits, appErr = th.App.GetServerLimits()
require.Nil(t, appErr)
require.Equal(t, int64(4), serverLimits.ActiveUserCount)
// now we'll delete the user
_, appErr = th.App.UpdateActive(th.Context, newGuestUser, false)
require.Nil(t, appErr)
serverLimits, appErr = th.App.GetServerLimits()
require.Nil(t, appErr)
require.Equal(t, int64(3), serverLimits.ActiveUserCount)
})
t.Run("user count should not change on creating or deleting bots", func(t *testing.T) {
th := Setup(t).InitBasic(t)
th.App.Srv().SetLicense(nil)
serverLimits, appErr := th.App.GetServerLimits()
require.Nil(t, appErr)
require.Equal(t, int64(3), serverLimits.ActiveUserCount)
// now we create a new bot
newBot := th.CreateBot(t)
serverLimits, appErr = th.App.GetServerLimits()
require.Nil(t, appErr)
require.Equal(t, int64(3), serverLimits.ActiveUserCount)
// now we'll delete the bot
_ = th.App.PermanentDeleteBot(th.Context, newBot.UserId)
serverLimits, appErr = th.App.GetServerLimits()
require.Nil(t, appErr)
require.Equal(t, int64(3), serverLimits.ActiveUserCount)
})
t.Run("licensed server without seat count enforcement shows no limits", func(t *testing.T) {
th := Setup(t).InitBasic(t)
license := model.NewTestLicense("")
license.IsSeatCountEnforced = false
th.App.Srv().SetLicense(license)
serverLimits, appErr := th.App.GetServerLimits()
require.Nil(t, appErr)
require.Greater(t, serverLimits.ActiveUserCount, int64(0))
require.Equal(t, int64(0), serverLimits.MaxUsersLimit)
require.Equal(t, int64(0), serverLimits.MaxUsersHardLimit)
})
t.Run("licensed server with seat count enforcement shows license limits with configurable extra users", func(t *testing.T) {
th := Setup(t).InitBasic(t)
userLimit := 100
extraUsers := 10
license := model.NewTestLicense("")
license.IsSeatCountEnforced = true
license.Features.Users = &userLimit
license.ExtraUsers = &extraUsers
th.App.Srv().SetLicense(license)
serverLimits, appErr := th.App.GetServerLimits()
require.Nil(t, appErr)
// InitBasic creates 3 users by default
require.Equal(t, int64(3), serverLimits.ActiveUserCount)
require.Equal(t, int64(100), serverLimits.MaxUsersLimit)
require.Equal(t, int64(110), serverLimits.MaxUsersHardLimit) // 100 + 10 extra users = 110
})
t.Run("licensed server with seat count enforcement and no ExtraUsers configured defaults to zero", func(t *testing.T) {
th := Setup(t).InitBasic(t)
userLimit := 100
license := model.NewTestLicense("")
license.IsSeatCountEnforced = true
license.Features.Users = &userLimit
license.ExtraUsers = nil // Not configured
th.App.Srv().SetLicense(license)
serverLimits, appErr := th.App.GetServerLimits()
require.Nil(t, appErr)
// InitBasic creates 3 users by default
require.Equal(t, int64(3), serverLimits.ActiveUserCount)
require.Equal(t, int64(100), serverLimits.MaxUsersLimit)
require.Equal(t, int64(100), serverLimits.MaxUsersHardLimit) // 100 + 0 extra users = 100 (hard cap)
})
t.Run("licensed server with seat count enforcement and zero ExtraUsers creates hard cap", func(t *testing.T) {
th := Setup(t).InitBasic(t)
userLimit := 100
extraUsers := 0
license := model.NewTestLicense("")
license.IsSeatCountEnforced = true
license.Features.Users = &userLimit
license.ExtraUsers = &extraUsers
th.App.Srv().SetLicense(license)
serverLimits, appErr := th.App.GetServerLimits()
require.Nil(t, appErr)
// InitBasic creates 3 users by default
require.Equal(t, int64(3), serverLimits.ActiveUserCount)
require.Equal(t, int64(100), serverLimits.MaxUsersLimit)
require.Equal(t, int64(100), serverLimits.MaxUsersHardLimit) // 100 + 0 extra users = 100 (hard cap)
})
t.Run("licensed server with seat count enforcement but no Users feature shows no limits", func(t *testing.T) {
th := Setup(t).InitBasic(t)
license := model.NewTestLicense("")
license.IsSeatCountEnforced = true
license.Features.Users = nil
th.App.Srv().SetLicense(license)
serverLimits, appErr := th.App.GetServerLimits()
require.Nil(t, appErr)
require.Greater(t, serverLimits.ActiveUserCount, int64(0))
require.Equal(t, int64(0), serverLimits.MaxUsersLimit)
require.Equal(t, int64(0), serverLimits.MaxUsersHardLimit)
})
t.Run("licensed server with seat count enforcement and zero Users shows zero limits", func(t *testing.T) {
th := Setup(t).InitBasic(t)
userLimit := 0
license := model.NewTestLicense("")
license.IsSeatCountEnforced = true
license.Features.Users = &userLimit
th.App.Srv().SetLicense(license)
serverLimits, appErr := th.App.GetServerLimits()
require.Nil(t, appErr)
require.Greater(t, serverLimits.ActiveUserCount, int64(0))
require.Equal(t, int64(0), serverLimits.MaxUsersLimit)
require.Equal(t, int64(0), serverLimits.MaxUsersHardLimit) // 0 + 0 (default) extra users = 0
})
}
func TestIsAtUserLimit(t *testing.T) {
mainHelper.Parallel(t)
t.Run("unlicensed server", func(t *testing.T) {
t.Run("below hard limit", func(t *testing.T) {
th := SetupWithStoreMock(t)
th.App.Srv().SetLicense(nil)
mockUserStore := storemocks.UserStore{}
mockUserStore.On("Count", mock.Anything).Return(int64(200), nil) // Under hard limit of 250
mockUserStore.On("AnalyticsGetSingleChannelGuestCount").Return(int64(0), nil)
mockStore := th.App.Srv().Store().(*storemocks.Store)
mockStore.On("User").Return(&mockUserStore)
atLimit, appErr := th.App.isAtUserLimit()
require.Nil(t, appErr)
require.False(t, atLimit)
})
t.Run("at hard limit", func(t *testing.T) {
th := SetupWithStoreMock(t)
th.App.Srv().SetLicense(nil)
mockUserStore := storemocks.UserStore{}
mockUserStore.On("Count", mock.Anything).Return(int64(250), nil) // At hard limit of 250
mockUserStore.On("AnalyticsGetSingleChannelGuestCount").Return(int64(0), nil)
mockStore := th.App.Srv().Store().(*storemocks.Store)
mockStore.On("User").Return(&mockUserStore)
atLimit, appErr := th.App.isAtUserLimit()
require.Nil(t, appErr)
require.True(t, atLimit)
})
t.Run("above hard limit", func(t *testing.T) {
th := SetupWithStoreMock(t)
th.App.Srv().SetLicense(nil)
mockUserStore := storemocks.UserStore{}
mockUserStore.On("Count", mock.Anything).Return(int64(300), nil) // Over hard limit of 250
mockUserStore.On("AnalyticsGetSingleChannelGuestCount").Return(int64(0), nil)
mockStore := th.App.Srv().Store().(*storemocks.Store)
mockStore.On("User").Return(&mockUserStore)
atLimit, appErr := th.App.isAtUserLimit()
require.Nil(t, appErr)
require.True(t, atLimit)
})
})
t.Run("licensed server with seat count enforcement", func(t *testing.T) {
t.Run("below base limit", func(t *testing.T) {
th := Setup(t).InitBasic(t)
userLimit := 5
license := model.NewTestLicense("")
license.IsSeatCountEnforced = true
license.Features.Users = &userLimit
th.App.Srv().SetLicense(license)
// InitBasic creates 3 users, so we're below the base limit of 5 and grace limit of 6
atLimit, appErr := th.App.isAtUserLimit()
require.Nil(t, appErr)
require.False(t, atLimit)
})
t.Run("at base limit but below hard limit with extra users", func(t *testing.T) {
th := Setup(t).InitBasic(t)
userLimit := 5
extraUsers := 2
license := model.NewTestLicense("")
license.IsSeatCountEnforced = true
license.Features.Users = &userLimit
license.ExtraUsers = &extraUsers
th.App.Srv().SetLicense(license)
// Create 2 additional users to have 5 total (at base limit of 5, but below hard limit of 7)
th.CreateUser(t)
th.CreateUser(t)
atLimit, appErr := th.App.isAtUserLimit()
require.Nil(t, appErr)
require.False(t, atLimit) // Should be false due to extra users
})
t.Run("at hard limit with extra users", func(t *testing.T) {
th := SetupWithStoreMock(t)
userLimit := 5
extraUsers := 1
license := model.NewTestLicense("")
license.IsSeatCountEnforced = true
license.Features.Users = &userLimit
license.ExtraUsers = &extraUsers
th.App.Srv().SetLicense(license)
mockUserStore := storemocks.UserStore{}
mockUserStore.On("Count", mock.Anything).Return(int64(6), nil) // At hard limit of 6 (5 + 1)
mockUserStore.On("AnalyticsGetSingleChannelGuestCount").Return(int64(0), nil)
mockStore := th.App.Srv().Store().(*storemocks.Store)
mockStore.On("User").Return(&mockUserStore)
atLimit, appErr := th.App.isAtUserLimit()
require.Nil(t, appErr)
require.True(t, atLimit)
})
t.Run("above hard limit with extra users", func(t *testing.T) {
th := SetupWithStoreMock(t)
userLimit := 5
extraUsers := 1
license := model.NewTestLicense("")
license.IsSeatCountEnforced = true
license.Features.Users = &userLimit
license.ExtraUsers = &extraUsers
th.App.Srv().SetLicense(license)
mockUserStore := storemocks.UserStore{}
mockUserStore.On("Count", mock.Anything).Return(int64(7), nil) // Above hard limit of 6
mockUserStore.On("AnalyticsGetSingleChannelGuestCount").Return(int64(0), nil)
mockStore := th.App.Srv().Store().(*storemocks.Store)
mockStore.On("User").Return(&mockUserStore)
atLimit, appErr := th.App.isAtUserLimit()
require.Nil(t, appErr)
require.True(t, atLimit)
})
})
t.Run("licensed server without seat count enforcement", func(t *testing.T) {
t.Run("below unenforced limit", func(t *testing.T) {
th := Setup(t).InitBasic(t)
userLimit := 5
license := model.NewTestLicense("")
license.IsSeatCountEnforced = false
license.Features.Users = &userLimit
th.App.Srv().SetLicense(license)
// Create 2 additional users to have 3 total (below limit of 5)
th.CreateUser(t)
th.CreateUser(t)
atLimit, appErr := th.App.isAtUserLimit()
require.Nil(t, appErr)
require.False(t, atLimit)
})
t.Run("at unenforced limit", func(t *testing.T) {
th := Setup(t).InitBasic(t)
userLimit := 5
license := model.NewTestLicense("")
license.IsSeatCountEnforced = false
license.Features.Users = &userLimit
th.App.Srv().SetLicense(license)
// Create 4 additional users to have 5 total (at limit of 5)
th.CreateUser(t)
th.CreateUser(t)
th.CreateUser(t)
th.CreateUser(t)
atLimit, appErr := th.App.isAtUserLimit()
require.Nil(t, appErr)
require.False(t, atLimit)
})
t.Run("above unenforced limit", func(t *testing.T) {
th := Setup(t).InitBasic(t)
userLimit := 5
license := model.NewTestLicense("")
license.IsSeatCountEnforced = false
license.Features.Users = &userLimit
th.App.Srv().SetLicense(license)
// Create 5 additional users to have 6 total (above limit of 5)
th.CreateUser(t)
th.CreateUser(t)
th.CreateUser(t)
th.CreateUser(t)
th.CreateUser(t)
atLimit, appErr := th.App.isAtUserLimit()
require.Nil(t, appErr)
require.False(t, atLimit)
})
})
}
func TestExtraUsersBehavior(t *testing.T) {
mainHelper.Parallel(t)
t.Run("extra users examples", func(t *testing.T) {
tests := []struct {
name string
licenseUserLimit int
extraUsers *int
expectedBaseLimit int64
expectedHardLimit int64
}{
{
name: "zero license users with extra users",
licenseUserLimit: 0,
extraUsers: model.NewPointer(5),
expectedBaseLimit: 0,
expectedHardLimit: 5, // 0 + 5 extra users = 5
},
{
name: "license with configured extra users",
licenseUserLimit: 10,
extraUsers: model.NewPointer(2),
expectedBaseLimit: 10,
expectedHardLimit: 12, // 10 + 2 extra users = 12
},
{
name: "license with zero extra users (hard cap)",
licenseUserLimit: 100,
extraUsers: model.NewPointer(0),
expectedBaseLimit: 100,
expectedHardLimit: 100, // 100 + 0 extra users = 100 (hard cap)
},
{
name: "license with no extra users configured defaults to zero",
licenseUserLimit: 100,
extraUsers: nil,
expectedBaseLimit: 100,
expectedHardLimit: 100, // 100 + 0 (default) extra users = 100 (hard cap)
},
{
name: "license with large number of extra users",
licenseUserLimit: 1000,
extraUsers: model.NewPointer(200),
expectedBaseLimit: 1000,
expectedHardLimit: 1200, // 1000 + 200 extra users = 1200
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
th := Setup(t).InitBasic(t)
license := model.NewTestLicense("")
license.IsSeatCountEnforced = true
license.Features.Users = &tt.licenseUserLimit
license.ExtraUsers = tt.extraUsers
th.App.Srv().SetLicense(license)
serverLimits, appErr := th.App.GetServerLimits()
require.Nil(t, appErr)
require.Equal(t, tt.expectedBaseLimit, serverLimits.MaxUsersLimit)
require.Equal(t, tt.expectedHardLimit, serverLimits.MaxUsersHardLimit)
})
}
})
t.Run("unlicensed server has no extra users", func(t *testing.T) {
th := Setup(t).InitBasic(t)
th.App.Srv().SetLicense(nil)
serverLimits, appErr := th.App.GetServerLimits()
require.Nil(t, appErr)
// Unlicensed servers use hard-coded limits without extra users
require.Equal(t, int64(200), serverLimits.MaxUsersLimit)
require.Equal(t, int64(250), serverLimits.MaxUsersHardLimit)
})
}
func TestGetServerLimitsWithPostHistory(t *testing.T) {
mainHelper.Parallel(t)
t.Run("unlicensed server has no post history limits", func(t *testing.T) {
th := Setup(t).InitBasic(t)
th.App.Srv().SetLicense(nil)
serverLimits, appErr := th.App.GetServerLimits()
require.Nil(t, appErr)
// Unlicensed servers should have no post history limits
require.Equal(t, int64(0), serverLimits.PostHistoryLimit)
require.Equal(t, int64(0), serverLimits.LastAccessiblePostTime)
})
t.Run("licensed server without post history limits", func(t *testing.T) {
th := Setup(t).InitBasic(t)
license := model.NewTestLicense("")
license.Limits = nil // No limits configured
th.App.Srv().SetLicense(license)
serverLimits, appErr := th.App.GetServerLimits()
require.Nil(t, appErr)
// Should have no post history limits when Limits is nil
require.Equal(t, int64(0), serverLimits.PostHistoryLimit)
require.Equal(t, int64(0), serverLimits.LastAccessiblePostTime)
})
t.Run("licensed server with zero post history limit", func(t *testing.T) {
th := Setup(t).InitBasic(t)
license := model.NewTestLicense("")
license.Limits = &model.LicenseLimits{
PostHistory: 0, // Zero limit
}
th.App.Srv().SetLicense(license)
serverLimits, appErr := th.App.GetServerLimits()
require.Nil(t, appErr)
// Should have no post history limits when PostHistory is 0
require.Equal(t, int64(0), serverLimits.PostHistoryLimit)
require.Equal(t, int64(0), serverLimits.LastAccessiblePostTime)
})
t.Run("licensed server with positive post history limit and successful GetLastAccessiblePostTime", func(t *testing.T) {
th := SetupWithStoreMock(t)
// Mock user store for existing functionality
mockStore := th.App.Srv().Store().(*storemocks.Store)
mockUserStore := storemocks.UserStore{}
mockUserStore.On("Count", mock.Anything).Return(int64(5), nil)
mockUserStore.On("AnalyticsGetSingleChannelGuestCount").Return(int64(0), nil)
mockStore.On("User").Return(&mockUserStore)
// Mock system store for GetLastAccessiblePostTime
mockSystemStore := storemocks.SystemStore{}
mockSystemStore.On("GetByName", model.SystemLastAccessiblePostTime).Return(&model.System{
Name: model.SystemLastAccessiblePostTime,
Value: "1234567890",
}, nil)
mockStore.On("System").Return(&mockSystemStore)
// Create Entry license with post history limit
license := model.NewTestLicenseSKU(model.LicenseShortSkuMattermostEntry)
license.Limits = &model.LicenseLimits{
PostHistory: 1000,
}
th.App.Srv().SetLicense(license)
serverLimits, appErr := th.App.GetServerLimits()
require.Nil(t, appErr)
// Should have proper post history limits set
require.Equal(t, int64(1000), serverLimits.PostHistoryLimit)
require.Equal(t, int64(1234567890), serverLimits.LastAccessiblePostTime)
require.Equal(t, int64(5), serverLimits.ActiveUserCount)
})
t.Run("licensed server with post history limit but GetLastAccessiblePostTime fails", func(t *testing.T) {
th := SetupWithStoreMock(t)
// Mock user store for existing functionality
mockStore := th.App.Srv().Store().(*storemocks.Store)
mockUserStore := storemocks.UserStore{}
mockUserStore.On("Count", mock.Anything).Return(int64(5), nil)
mockUserStore.On("AnalyticsGetSingleChannelGuestCount").Return(int64(0), nil)
mockStore.On("User").Return(&mockUserStore)
// Mock system store to return error
mockSystemStore := storemocks.SystemStore{}
mockSystemStore.On("GetByName", model.SystemLastAccessiblePostTime).Return(nil, errors.New("database error"))
mockStore.On("System").Return(&mockSystemStore)
// Create Entry license with post history limit
license := model.NewTestLicenseSKU(model.LicenseShortSkuMattermostEntry)
license.Limits = &model.LicenseLimits{
PostHistory: 1000,
}
th.App.Srv().SetLicense(license)
_, appErr := th.App.GetServerLimits()
require.NotNil(t, appErr)
require.Contains(t, appErr.Message, "Unable to find the system variable")
})
t.Run("licensed server with post history limit but no system value found", func(t *testing.T) {
th := SetupWithStoreMock(t)
// Mock user store for existing functionality
mockStore := th.App.Srv().Store().(*storemocks.Store)
mockUserStore := storemocks.UserStore{}
mockUserStore.On("Count", mock.Anything).Return(int64(5), nil)
mockUserStore.On("AnalyticsGetSingleChannelGuestCount").Return(int64(0), nil)
mockStore.On("User").Return(&mockUserStore)
// Mock system store to return ErrNotFound (all posts accessible)
mockSystemStore := storemocks.SystemStore{}
mockSystemStore.On("GetByName", model.SystemLastAccessiblePostTime).Return(nil, store.NewErrNotFound("", ""))
mockStore.On("System").Return(&mockSystemStore)
// Create Entry license with post history limit
license := model.NewTestLicenseSKU(model.LicenseShortSkuMattermostEntry)
license.Limits = &model.LicenseLimits{
PostHistory: 1000,
}
th.App.Srv().SetLicense(license)
serverLimits, appErr := th.App.GetServerLimits()
require.Nil(t, appErr)
// Should have post history limit set but LastAccessiblePostTime should be 0 (all posts accessible)
require.Equal(t, int64(1000), serverLimits.PostHistoryLimit)
require.Equal(t, int64(0), serverLimits.LastAccessiblePostTime)
require.Equal(t, int64(5), serverLimits.ActiveUserCount)
})
}
func TestShouldTrackSingleChannelGuests(t *testing.T) {
mainHelper.Parallel(t)
t.Run("returns false for unlicensed server", func(t *testing.T) {
th := Setup(t).InitBasic(t)
th.App.Srv().SetLicense(nil)
require.False(t, th.App.shouldTrackSingleChannelGuests())
})
t.Run("returns false for Entry SKU license", func(t *testing.T) {
th := Setup(t).InitBasic(t)
license := model.NewTestLicenseSKU(model.LicenseShortSkuMattermostEntry)
th.App.Srv().SetLicense(license)
require.False(t, th.App.shouldTrackSingleChannelGuests())
})
t.Run("returns true when license GuestAccounts feature is disabled but config is enabled", func(t *testing.T) {
th := Setup(t).InitBasic(t)
license := model.NewTestLicense("")
license.Features.GuestAccounts = model.NewPointer(false)
th.App.Srv().SetLicense(license)
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GuestAccountsSettings.Enable = true })
require.True(t, th.App.shouldTrackSingleChannelGuests())
})
t.Run("returns false when config GuestAccountsSettings.Enable is false", func(t *testing.T) {
th := Setup(t).InitBasic(t)
license := model.NewTestLicense("")
th.App.Srv().SetLicense(license)
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GuestAccountsSettings.Enable = false })
require.False(t, th.App.shouldTrackSingleChannelGuests())
})
t.Run("returns true for non-Entry license with guest accounts enabled", func(t *testing.T) {
th := Setup(t).InitBasic(t)
license := model.NewTestLicense("")
th.App.Srv().SetLicense(license)
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GuestAccountsSettings.Enable = true })
require.True(t, th.App.shouldTrackSingleChannelGuests())
})
}
func TestGetServerLimitsWithSingleChannelGuests(t *testing.T) {
mainHelper.Parallel(t)
t.Run("populates single channel guest fields when tracking is enabled", func(t *testing.T) {
th := Setup(t).InitBasic(t)
userLimit := 100
license := model.NewTestLicense("")
license.Features.Users = &userLimit
th.App.Srv().SetLicense(license)
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GuestAccountsSettings.Enable = true })
guest := th.CreateGuest(t)
th.LinkUserToTeam(t, guest, th.BasicTeam)
th.AddUserToChannel(t, guest, th.BasicChannel)
serverLimits, appErr := th.App.GetServerLimits()
require.Nil(t, appErr)
require.Greater(t, serverLimits.SingleChannelGuestCount, int64(0))
require.Equal(t, int64(userLimit), serverLimits.SingleChannelGuestLimit)
require.Equal(t, serverLimits.ActiveUserCount, int64(4)-serverLimits.SingleChannelGuestCount)
})
t.Run("does not populate single channel guest fields for Entry SKU", func(t *testing.T) {
th := Setup(t).InitBasic(t)
license := model.NewTestLicenseSKU(model.LicenseShortSkuMattermostEntry)
th.App.Srv().SetLicense(license)
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GuestAccountsSettings.Enable = true })
serverLimits, appErr := th.App.GetServerLimits()
require.Nil(t, appErr)
require.Equal(t, int64(0), serverLimits.SingleChannelGuestCount)
require.Equal(t, int64(0), serverLimits.SingleChannelGuestLimit)
require.Equal(t, int64(3), serverLimits.ActiveUserCount)
})
}
func TestGetPostHistoryLimit(t *testing.T) {
mainHelper.Parallel(t)
t.Run("unlicensed server returns zero limit", func(t *testing.T) {
th := Setup(t).InitBasic(t)
th.App.Srv().SetLicense(nil)
limit := th.App.GetPostHistoryLimit()
require.Equal(t, int64(0), limit)
})
t.Run("licensed server with no Limits returns zero", func(t *testing.T) {
th := Setup(t).InitBasic(t)
license := model.NewTestLicense("")
license.Limits = nil // No limits configured
th.App.Srv().SetLicense(license)
limit := th.App.GetPostHistoryLimit()
require.Equal(t, int64(0), limit)
})
t.Run("licensed server with zero PostHistory returns zero", func(t *testing.T) {
th := Setup(t).InitBasic(t)
license := model.NewTestLicense("")
license.Limits = &model.LicenseLimits{
PostHistory: 0,
}
th.App.Srv().SetLicense(license)
limit := th.App.GetPostHistoryLimit()
require.Equal(t, int64(0), limit)
})
t.Run("licensed server with positive PostHistory returns exact value", func(t *testing.T) {
th := Setup(t).InitBasic(t)
license := model.NewTestLicense("")
license.Limits = &model.LicenseLimits{
PostHistory: 1500,
}
th.App.Srv().SetLicense(license)
limit := th.App.GetPostHistoryLimit()
require.Equal(t, int64(1500), limit)
})
t.Run("Entry license with PostHistory returns exact value", func(t *testing.T) {
th := Setup(t).InitBasic(t)
license := model.NewTestLicenseSKU(model.LicenseShortSkuMattermostEntry)
license.Limits = &model.LicenseLimits{
PostHistory: 2000,
}
th.App.Srv().SetLicense(license)
limit := th.App.GetPostHistoryLimit()
require.Equal(t, int64(2000), limit)
})
}