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 Nightly Race / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (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 Nightly Race / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 1s
This commit is contained in:
@@ -0,0 +1,468 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {expect, getRandomId, test} from '@mattermost/playwright-lib';
|
||||
|
||||
test.describe('Single-channel guests', () => {
|
||||
test.setTimeout(180000);
|
||||
test.describe.configure({mode: 'serial'});
|
||||
|
||||
test.beforeEach(async ({pw}) => {
|
||||
await pw.ensureLicense();
|
||||
await pw.skipIfNoLicense();
|
||||
});
|
||||
|
||||
async function setupSingleChannelGuestsTest(pw: any) {
|
||||
const {adminClient, adminUser} = await pw.getAdminClient();
|
||||
const suffix = getRandomId();
|
||||
const team = await adminClient.createTeam({
|
||||
name: `scg-${suffix}`,
|
||||
display_name: `SCG ${suffix}`,
|
||||
type: 'O',
|
||||
});
|
||||
await adminClient.addToTeam(team.id, adminUser.id);
|
||||
return {adminClient, adminUser, team};
|
||||
}
|
||||
|
||||
async function patchGuestEnabled(adminClient: any, enabled: boolean): Promise<boolean> {
|
||||
const cfg = await adminClient.getConfig();
|
||||
const previous = cfg.GuestAccountsSettings?.Enable ?? false;
|
||||
await adminClient.patchConfig({GuestAccountsSettings: {Enable: enabled}});
|
||||
return previous;
|
||||
}
|
||||
|
||||
async function navigateWithGuestPatch(page: any, adminClient: any, url: string, guestEnabled: boolean) {
|
||||
await page.goto(url);
|
||||
await page.waitForLoadState('networkidle');
|
||||
await patchGuestEnabled(adminClient, guestEnabled);
|
||||
await page.reload();
|
||||
await page.waitForLoadState('networkidle');
|
||||
}
|
||||
|
||||
/**
|
||||
* @objective Verify the Single-channel Guests stat card appears on the Site Statistics page when guests are enabled
|
||||
*
|
||||
* @precondition
|
||||
* Server has a non-Entry license with guest accounts enabled
|
||||
*/
|
||||
test(
|
||||
'displays single-channel guests card on site statistics page when guest accounts are enabled',
|
||||
{tag: '@system_console'},
|
||||
async ({pw}) => {
|
||||
const {adminUser, adminClient, team} = await setupSingleChannelGuestsTest(pw);
|
||||
|
||||
if (!adminUser) {
|
||||
throw new Error('Failed to create admin user');
|
||||
}
|
||||
|
||||
// # Enable guest accounts (narrow patch, not destructive full-config update)
|
||||
await patchGuestEnabled(adminClient, true);
|
||||
|
||||
// # Create a guest user and add to one channel
|
||||
const guestUser = await adminClient.createUser(await pw.random.user(), '', '');
|
||||
await adminClient.updateUserRoles(guestUser.id, 'system_guest');
|
||||
await adminClient.addToTeam(team.id, guestUser.id);
|
||||
|
||||
const channel = await adminClient.createChannel(
|
||||
pw.random.channel({teamId: team.id, name: 'guest-channel', displayName: 'Guest Channel', unique: true}),
|
||||
);
|
||||
await adminClient.addToChannel(guestUser.id, channel.id);
|
||||
|
||||
// # Log in as admin and navigate to site statistics
|
||||
const {systemConsolePage} = await pw.testBrowser.login(adminUser);
|
||||
await systemConsolePage.goto();
|
||||
await systemConsolePage.toBeVisible();
|
||||
|
||||
// Re-apply patch after initial load + reload to counter WebSocket config resets
|
||||
// from concurrent initSetup() calls (default_config has Enable: false).
|
||||
await navigateWithGuestPatch(
|
||||
systemConsolePage.page,
|
||||
adminClient,
|
||||
'/admin_console/reporting/system_analytics',
|
||||
true,
|
||||
);
|
||||
|
||||
// * Verify the single-channel guests card is visible
|
||||
await expect(systemConsolePage.page.getByTestId('singleChannelGuests')).toBeVisible({timeout: 30000});
|
||||
|
||||
// * Verify the count is at least 1.
|
||||
// Analytics are indexed asynchronously on the server — poll with reload until
|
||||
// the card reflects the newly created guest (can take several seconds in CI).
|
||||
await expect
|
||||
.poll(
|
||||
async () => {
|
||||
// Re-apply guest patch before reload: a concurrent initSetup() may
|
||||
// reset the config back to the default (Enable: false), which would
|
||||
// hide the card and make the textContent call return null.
|
||||
await patchGuestEnabled(adminClient, true);
|
||||
await systemConsolePage.page.reload();
|
||||
await systemConsolePage.page.waitForLoadState('networkidle');
|
||||
const text = await systemConsolePage.page
|
||||
.getByTestId('singleChannelGuests')
|
||||
.textContent()
|
||||
.catch(() => '');
|
||||
return Number(text?.match(/(\d+)/)?.[1] ?? 0);
|
||||
},
|
||||
{timeout: 180000, intervals: [3000, 5000, 8000, 12000]},
|
||||
)
|
||||
.toBeGreaterThanOrEqual(1);
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* @objective Verify the Single-channel Guests row appears on the Edition and License page when guests are enabled
|
||||
*
|
||||
* @precondition
|
||||
* Server has a non-Entry license with guest accounts enabled and a single-channel guest limit configured
|
||||
*/
|
||||
test(
|
||||
'displays single-channel guests row on edition and license page when guest accounts are enabled',
|
||||
{tag: '@system_console'},
|
||||
async ({pw}) => {
|
||||
const {adminUser, adminClient, team} = await setupSingleChannelGuestsTest(pw);
|
||||
|
||||
if (!adminUser) {
|
||||
throw new Error('Failed to create admin user');
|
||||
}
|
||||
|
||||
// # Enable guest accounts (narrow patch, not destructive full-config update)
|
||||
await patchGuestEnabled(adminClient, true);
|
||||
|
||||
// # Create a guest user and add to one channel
|
||||
const guestUser = await adminClient.createUser(await pw.random.user(), '', '');
|
||||
await adminClient.updateUserRoles(guestUser.id, 'system_guest');
|
||||
await adminClient.addToTeam(team.id, guestUser.id);
|
||||
|
||||
const channel = await adminClient.createChannel(
|
||||
pw.random.channel({teamId: team.id, name: 'guest-channel', displayName: 'Guest Channel', unique: true}),
|
||||
);
|
||||
await adminClient.addToChannel(guestUser.id, channel.id);
|
||||
|
||||
// # Log in as admin and navigate to edition and license page
|
||||
const {systemConsolePage} = await pw.testBrowser.login(adminUser);
|
||||
await systemConsolePage.goto();
|
||||
await systemConsolePage.toBeVisible();
|
||||
|
||||
// Re-apply patch after initial load + reload to counter WebSocket config resets.
|
||||
await navigateWithGuestPatch(systemConsolePage.page, adminClient, '/admin_console/about/license', true);
|
||||
|
||||
// * Verify the single-channel guests row is visible
|
||||
await expect(systemConsolePage.page.getByText('SINGLE-CHANNEL GUESTS:')).toBeVisible();
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* @objective Verify the Single-channel Guests stat card is not shown when guest accounts are disabled
|
||||
*/
|
||||
test(
|
||||
'hides single-channel guests card on site statistics page when guest accounts are disabled',
|
||||
{tag: '@system_console'},
|
||||
async ({pw}) => {
|
||||
const {adminUser, adminClient} = await setupSingleChannelGuestsTest(pw);
|
||||
|
||||
if (!adminUser) {
|
||||
throw new Error('Failed to create admin user');
|
||||
}
|
||||
|
||||
// # Disable guest accounts (narrow patch, not destructive full-config update)
|
||||
await patchGuestEnabled(adminClient, false);
|
||||
|
||||
// # Log in as admin and navigate to site statistics
|
||||
const {systemConsolePage} = await pw.testBrowser.login(adminUser);
|
||||
await systemConsolePage.goto();
|
||||
await systemConsolePage.toBeVisible();
|
||||
|
||||
// Re-apply patch (disabled) + reload to ensure the browser reads the fresh config.
|
||||
await navigateWithGuestPatch(
|
||||
systemConsolePage.page,
|
||||
adminClient,
|
||||
'/admin_console/reporting/system_analytics',
|
||||
false,
|
||||
);
|
||||
|
||||
// * Verify the single-channel guests card is not in the DOM
|
||||
await expect(systemConsolePage.page.getByTestId('singleChannelGuests')).not.toBeVisible();
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* @objective Verify the server limits API returns single-channel guest count and limit for admin users
|
||||
*
|
||||
* @precondition
|
||||
* Server has a non-Entry license with guest accounts enabled
|
||||
*/
|
||||
test(
|
||||
'returns single-channel guest data from server limits API for admin users',
|
||||
{tag: '@system_console'},
|
||||
async ({pw}) => {
|
||||
const {adminClient} = await setupSingleChannelGuestsTest(pw);
|
||||
|
||||
// # Enable guest accounts (narrow patch, not destructive full-config update)
|
||||
await patchGuestEnabled(adminClient, true);
|
||||
|
||||
// # Fetch server limits
|
||||
const {data: limits} = await adminClient.getServerLimits();
|
||||
|
||||
// * Verify the response includes single-channel guest fields
|
||||
expect(limits).toHaveProperty('singleChannelGuestCount');
|
||||
expect(limits).toHaveProperty('singleChannelGuestLimit');
|
||||
expect(typeof limits.singleChannelGuestCount).toBe('number');
|
||||
expect(typeof limits.singleChannelGuestLimit).toBe('number');
|
||||
expect(limits.singleChannelGuestCount).toBeGreaterThanOrEqual(0);
|
||||
expect(limits.singleChannelGuestLimit).toBeGreaterThanOrEqual(0);
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* @objective Verify the single-channel guests card does not show error styling when count is within limit
|
||||
*
|
||||
* @precondition
|
||||
* Server has a non-Entry license with guest accounts enabled and guest count is within the allowed limit
|
||||
*/
|
||||
test(
|
||||
'shows no error styling on single-channel guests card when count is within limit',
|
||||
{tag: '@system_console'},
|
||||
async ({pw}) => {
|
||||
const {adminUser, adminClient, team} = await setupSingleChannelGuestsTest(pw);
|
||||
|
||||
if (!adminUser) {
|
||||
throw new Error('Failed to create admin user');
|
||||
}
|
||||
|
||||
// # Enable guest accounts (narrow patch, not destructive full-config update)
|
||||
await patchGuestEnabled(adminClient, true);
|
||||
|
||||
// # Create a single-channel guest (count will be well within any license limit)
|
||||
const guestUser = await adminClient.createUser(await pw.random.user(), '', '');
|
||||
await adminClient.updateUserRoles(guestUser.id, 'system_guest');
|
||||
await adminClient.addToTeam(team.id, guestUser.id);
|
||||
|
||||
const channel = await adminClient.createChannel(
|
||||
pw.random.channel({
|
||||
teamId: team.id,
|
||||
name: 'guest-no-overage',
|
||||
displayName: 'Guest No Overage',
|
||||
unique: true,
|
||||
}),
|
||||
);
|
||||
await adminClient.addToChannel(guestUser.id, channel.id);
|
||||
|
||||
// # Navigate to site statistics
|
||||
const {systemConsolePage} = await pw.testBrowser.login(adminUser);
|
||||
await systemConsolePage.goto();
|
||||
await systemConsolePage.toBeVisible();
|
||||
|
||||
// Re-apply patch + reload to counter WebSocket config resets.
|
||||
await navigateWithGuestPatch(
|
||||
systemConsolePage.page,
|
||||
adminClient,
|
||||
'/admin_console/reporting/system_analytics',
|
||||
true,
|
||||
);
|
||||
|
||||
// * Verify the card title does NOT have error class (count is within limit)
|
||||
const cardTitle = systemConsolePage.page.getByTestId('singleChannelGuestsTitle');
|
||||
await expect(cardTitle).toBeVisible({timeout: 15000});
|
||||
await expect(cardTitle).not.toHaveClass(/team_statistics--error/);
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* @objective Verify the dismissible banner is not shown when single-channel guest count is within limit
|
||||
*
|
||||
* @precondition
|
||||
* Server has a non-Entry license with guest accounts enabled and guest count is within the allowed limit
|
||||
*/
|
||||
test('does not show guest limit banner when count is within limit', {tag: '@system_console'}, async ({pw}) => {
|
||||
const {adminUser, adminClient} = await setupSingleChannelGuestsTest(pw);
|
||||
|
||||
if (!adminUser) {
|
||||
throw new Error('Failed to create admin user');
|
||||
}
|
||||
|
||||
// # Enable guest accounts (narrow patch, not destructive full-config update)
|
||||
await patchGuestEnabled(adminClient, true);
|
||||
|
||||
// # Navigate to system console and re-apply patch + reload so the browser reads
|
||||
// the latest config (not a WebSocket-clobbered Redux store from a concurrent initSetup).
|
||||
const {systemConsolePage} = await pw.testBrowser.login(adminUser);
|
||||
await systemConsolePage.goto();
|
||||
await systemConsolePage.toBeVisible();
|
||||
await patchGuestEnabled(adminClient, true);
|
||||
await systemConsolePage.page.reload();
|
||||
await systemConsolePage.page.waitForLoadState('networkidle');
|
||||
|
||||
// * Verify the guest limit banner is not visible (count is within limit)
|
||||
await expect(systemConsolePage.page.getByTestId('single_channel_guest_limit_banner')).not.toBeVisible();
|
||||
});
|
||||
|
||||
/**
|
||||
* @objective Verify error styling appears on the single-channel guests card and dismissible banner shows when single-channel guest count exceeds the limit
|
||||
*
|
||||
* @precondition
|
||||
* Server has a non-Entry license with guest accounts enabled
|
||||
*/
|
||||
test(
|
||||
'shows error styling on guests card and banner when single-channel guest count exceeds limit',
|
||||
{tag: '@system_console'},
|
||||
async ({pw}) => {
|
||||
const {adminUser, adminClient, team} = await setupSingleChannelGuestsTest(pw);
|
||||
|
||||
if (!adminUser) {
|
||||
throw new Error('Failed to create admin user');
|
||||
}
|
||||
|
||||
// # Enable guest accounts (narrow patch, not destructive full-config update)
|
||||
await patchGuestEnabled(adminClient, true);
|
||||
|
||||
// # Create multiple single-channel guests so the analytics count exceeds the mocked limit
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const guest = await adminClient.createUser(await pw.random.user(), '', '');
|
||||
await adminClient.updateUserRoles(guest.id, 'system_guest');
|
||||
await adminClient.addToTeam(team.id, guest.id);
|
||||
|
||||
const ch = await adminClient.createChannel(
|
||||
pw.random.channel({
|
||||
teamId: team.id,
|
||||
name: `scg-overage-${i}`,
|
||||
displayName: `SCG Overage ${i}`,
|
||||
unique: true,
|
||||
}),
|
||||
);
|
||||
await adminClient.addToChannel(guest.id, ch.id);
|
||||
}
|
||||
|
||||
// # Log in as admin (new browser context + page).
|
||||
const {context, systemConsolePage} = await pw.testBrowser.login(adminUser);
|
||||
|
||||
// # Mock GET /api/v4/limits/server on the *context* before any navigation so the first
|
||||
// getServerLimits() populates Redux with a low limit. Also fulfill with an explicit
|
||||
// JSON body — route.fulfill({response, json}) does not reliably merge bodies in Playwright.
|
||||
await context.route('**/api/v4/limits/server', async (route) => {
|
||||
const response = await route.fetch();
|
||||
let json: Record<string, unknown> = {};
|
||||
try {
|
||||
json = (await response.json()) as Record<string, unknown>;
|
||||
} catch {
|
||||
// ignore — replace with minimal payload
|
||||
}
|
||||
json.singleChannelGuestLimit = 1;
|
||||
json.singleChannelGuestCount = 3;
|
||||
await route.fulfill({
|
||||
status: response.status(),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(json),
|
||||
});
|
||||
});
|
||||
|
||||
// # Navigate to site statistics page, re-applying patch to counter concurrent resets.
|
||||
await systemConsolePage.goto();
|
||||
await systemConsolePage.toBeVisible();
|
||||
await navigateWithGuestPatch(
|
||||
systemConsolePage.page,
|
||||
adminClient,
|
||||
'/admin_console/reporting/system_analytics',
|
||||
true,
|
||||
);
|
||||
|
||||
// * Verify the card title has error styling
|
||||
const cardTitle = systemConsolePage.page.getByTestId('singleChannelGuestsTitle');
|
||||
await expect(cardTitle).toBeVisible({timeout: 15000});
|
||||
await expect(cardTitle).toHaveClass(/team_statistics--error/);
|
||||
|
||||
// * Verify the dismissible guest limit banner is visible
|
||||
await expect(systemConsolePage.page.getByTestId('single_channel_guest_limit_banner')).toBeVisible();
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* @objective Verify that a guest in multiple channels is not counted as a single-channel guest
|
||||
*
|
||||
* @precondition
|
||||
* Server has a non-Entry license with guest accounts enabled
|
||||
*/
|
||||
test(
|
||||
'does not count multi-channel guest as single-channel guest on site statistics page',
|
||||
{tag: '@system_console'},
|
||||
async ({pw}) => {
|
||||
const {adminUser, adminClient, team} = await setupSingleChannelGuestsTest(pw);
|
||||
|
||||
if (!adminUser) {
|
||||
throw new Error('Failed to create admin user');
|
||||
}
|
||||
|
||||
// # Enable guest accounts (narrow patch, not destructive full-config update)
|
||||
await patchGuestEnabled(adminClient, true);
|
||||
|
||||
// # Create a guest user and add to TWO channels
|
||||
const multiChannelGuest = await adminClient.createUser(await pw.random.user(), '', '');
|
||||
await adminClient.updateUserRoles(multiChannelGuest.id, 'system_guest');
|
||||
await adminClient.addToTeam(team.id, multiChannelGuest.id);
|
||||
|
||||
const channelA = await adminClient.createChannel(
|
||||
pw.random.channel({
|
||||
teamId: team.id,
|
||||
name: 'guest-channel-a',
|
||||
displayName: 'Guest Channel A',
|
||||
unique: true,
|
||||
}),
|
||||
);
|
||||
const channelB = await adminClient.createChannel(
|
||||
pw.random.channel({
|
||||
teamId: team.id,
|
||||
name: 'guest-channel-b',
|
||||
displayName: 'Guest Channel B',
|
||||
unique: true,
|
||||
}),
|
||||
);
|
||||
await adminClient.addToChannel(multiChannelGuest.id, channelA.id);
|
||||
await adminClient.addToChannel(multiChannelGuest.id, channelB.id);
|
||||
|
||||
// # Log in as admin and navigate to site statistics, re-applying patch to counter
|
||||
// concurrent initSetup() resets (default_config has GuestAccountsSettings.Enable: false).
|
||||
const {systemConsolePage} = await pw.testBrowser.login(adminUser);
|
||||
await systemConsolePage.goto();
|
||||
await systemConsolePage.toBeVisible();
|
||||
await navigateWithGuestPatch(
|
||||
systemConsolePage.page,
|
||||
adminClient,
|
||||
'/admin_console/reporting/system_analytics',
|
||||
true,
|
||||
);
|
||||
|
||||
// * Verify the single-channel guests card is visible
|
||||
const singleChannelGuestsCard = systemConsolePage.page.getByTestId('singleChannelGuests');
|
||||
await expect(singleChannelGuestsCard).toBeVisible({timeout: 15000});
|
||||
|
||||
// * Verify the count text is present — multi-channel guest should not increment it
|
||||
const readGuestCount = async () => {
|
||||
await patchGuestEnabled(adminClient, true);
|
||||
await systemConsolePage.page.reload();
|
||||
await systemConsolePage.page.waitForLoadState('networkidle');
|
||||
const text = await singleChannelGuestsCard.textContent().catch(() => '');
|
||||
return Number(text?.match(/(\d+)/)?.[1] ?? NaN);
|
||||
};
|
||||
let singleChannelGuestCount = NaN;
|
||||
await expect
|
||||
.poll(
|
||||
async () => {
|
||||
singleChannelGuestCount = await readGuestCount();
|
||||
return singleChannelGuestCount;
|
||||
},
|
||||
{timeout: 180000, intervals: [3000, 5000, 8000, 12000]},
|
||||
)
|
||||
.toBeGreaterThanOrEqual(0);
|
||||
|
||||
// # Now create a single-channel guest to confirm baseline counting works
|
||||
const singleChannelGuest = await adminClient.createUser(await pw.random.user(), '', '');
|
||||
await adminClient.updateUserRoles(singleChannelGuest.id, 'system_guest');
|
||||
await adminClient.addToTeam(team.id, singleChannelGuest.id);
|
||||
await adminClient.addToChannel(singleChannelGuest.id, channelA.id);
|
||||
|
||||
// * Verify the count increased by exactly 1 for the new single-channel guest
|
||||
await expect
|
||||
.poll(readGuestCount, {timeout: 180000, intervals: [3000, 5000, 8000, 12000]})
|
||||
.toBe(singleChannelGuestCount + 1);
|
||||
},
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user