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

This commit is contained in:
upstream-mirror
2026-09-16 00:29:55 +05:00
commit 240db4e2a7
15309 changed files with 2173406 additions and 0 deletions

View File

@@ -0,0 +1,155 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// ***************************************************************
// - [#] indicates a test step (e.g. # Go to a page)
// - [*] indicates an assertion (e.g. * Check the title)
// - Use element ID when selecting an element. Create one if none.
// ***************************************************************
// Group: @channels @enterprise @not_cloud @system_console @license_removal
import {promoteToChannelOrTeamAdmin} from '../channel_moderation/helpers.ts';
import * as TIMEOUTS from '@/fixtures/timeouts';
import {getAdminAccount} from '@/support/env';
describe('System console', () => {
const sysadmin = getAdminAccount();
let teamAdmin;
let regularUser;
let teamName;
let privateChannelName;
before(() => {
cy.shouldNotRunOnCloudEdition();
// * Check if server has license
cy.apiRequireLicense();
// # Set channel permissions as listed in the test
setChannelPermission();
// # Create regular user and team admin
cy.apiInitSetup({userPrefix: 'regular-user'}).then(({team, user}) => {
teamName = team.name;
regularUser = user;
cy.apiCreateUser({prefix: 'team-admin'}).then(({user: newUser}) => {
cy.apiAddUserToTeam(team.id, newUser.id).then(() => {
teamAdmin = newUser;
promoteToChannelOrTeamAdmin(teamAdmin.id, team.id, 'teams');
cy.apiCreateChannel(team.id, 'private', 'Private', 'P').then(({channel}) => {
privateChannelName = channel.name;
Cypress._.forEach([teamAdmin.id, regularUser.id], (userId) => cy.apiAddUserToChannel(channel.id, userId));
});
});
});
});
});
it('MM-41397 - License page shows upgrade to Enterprise Advanced for Enterprise licenses', () => {
cy.visit('/admin_console/about/license');
cy.get('.admin-console__header').
should('be.visible').
and('have.text', 'Edition and License');
// Validate Enterprise to Enterprise Advanced upgrade content
cy.get('.EnterpriseEditionRightPannel').
should('be.visible').
within(() => {
// Check the title
cy.get('.upgrade-title').should('have.text', 'Upgrade to Enterprise Advanced');
// Check the advantages list
cy.findByText('Dynamic attribute-based access controls');
cy.findByText('Data spillage handling');
cy.findByText('Burn-on-read messages');
cy.findByText('Mobile biometrics & advanced security');
cy.findByText('Automatic channel translations');
cy.findByText('Channel banners');
cy.findByText('And more...');
cy.findByRole('button', {name: 'Contact Sales'});
});
// Validate Compare plans link is not present for Enterprise licenses
cy.findByRole('link', {name: 'Compare Plans'}).should('not.exist');
});
it('MM-T1201 - Remove and re-add license - Permissions freeze in place when license is removed (and then re-added)', () => {
// * Verify user access per permissions changed while on E20
verifyUserChannelPermission(teamName, privateChannelName, sysadmin, teamAdmin, regularUser);
// # Remove license and verify user access when downgraded to E0/team edition
cy.apiAdminLogin();
cy.apiDeleteLicense();
verifyUserChannelPermission(teamName, privateChannelName, sysadmin, teamAdmin, regularUser);
// # Re-add license and verify user access when upgraded to E20
cy.apiAdminLogin();
cy.apiRequireLicense();
verifyUserChannelPermission(teamName, privateChannelName, sysadmin, teamAdmin, regularUser);
});
});
// # Set channel permissions as listed in the test
function setChannelPermission() {
cy.visit('admin_console/user_management/permissions/system_scheme');
cy.findByTestId('resetPermissionsToDefault').click();
cy.get('#confirmModalButton').click();
cy.findByTestId('all_users-public_channel-create_public_channel-checkbox').click();
cy.findByTestId('all_users-private_channel-manage_private_channel_properties-checkbox').click();
cy.findByTestId('team_admin-private_channel-manage_private_channel_properties-checkbox').click();
cy.findByTestId('saveSetting').click();
}
function verifyCreatePublicChannel(teamName, testUsers) {
for (const testUser of testUsers) {
const {user, canCreate, isSysadmin} = testUser;
// # Login as a user, and visit the team and channel
cy.apiLogin(user);
cy.visit(`/${teamName}/channels/town-square`);
// # Click on create new channel at LHS
cy.uiBrowseOrCreateChannel('Create new channel');
cy.findByRole('dialog', {name: 'Create a new channel'}).within(() => {
// * Verify if creating a public channel is disabled or not
cy.get('#public-private-selector-button-O').should(isSysadmin || canCreate ? 'not.have.class' : 'have.class', 'disabled');
// * Verify if creating a private channel is not disabled
cy.get('#public-private-selector-button-P').should('not.have.class', 'disabled');
});
}
}
function verifyRenamePrivateChannel(teamName, privateChannelName, testUsers) {
for (const testUser of testUsers) {
const {user, canRename} = testUser;
cy.apiLogin(user);
cy.visit(`/${teamName}/channels/${privateChannelName}`);
// * Click the dropdown menu and verify if the rename option is visible or not
cy.get('#channelHeaderTitle', {timeout: TIMEOUTS.TWO_MIN}).should('be.visible').click();
cy.get('#channelRename').should(canRename ? 'be.visible' : 'not.exist');
}
}
function verifyUserChannelPermission(teamName, privateChannelName, sysadmin, teamAdmin, regularUser) {
// * Verify that system admin sees option to create public channels and team admins / members do not
verifyCreatePublicChannel(teamName, [
{user: sysadmin, canCreate: true, isSysadmin: true},
{user: teamAdmin, canCreate: false},
{user: regularUser, canCreate: false},
]);
// * Verify that team admin and system admin see option to rename private channel, and member does not
verifyRenamePrivateChannel(teamName, privateChannelName, [
{user: sysadmin, canRename: true},
{user: teamAdmin, canRename: true},
{user: regularUser, canRename: false},
]);
}

View File

@@ -0,0 +1,270 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// ***************************************************************
// - [#] indicates a test step (e.g. # Go to a page)
// - [*] indicates an assertion (e.g. * Check the title)
// - Use element ID when selecting an element. Create one if none.
// ***************************************************************
// Group: @channels @enterprise @not_cloud @system_console
import * as TIMEOUTS from '@/fixtures/timeouts';
// These specs exercise the license upload preview/diff flow added in MM-67113.
// The upload and preview endpoints require real, signed license bytes that are
// not available to the e2e environment, so the network is stubbed via
// cy.intercept and a tiny dummy file is fed into the hidden file input. The
// stubs are crafted to reproduce the two issues reported during QA:
// 1. After uploading a new license, the success screen showed the previous
// tier and the System Console license page did not refresh.
// 2. Re-uploading the currently applied license was not handled gracefully.
const OLD_LICENSE_ID = 'old-license-id-0000000000000';
// Current (already applied) license, as returned by GET /license/client.
const currentClientLicense = buildClientLicense({
id: OLD_LICENSE_ID,
skuName: 'Professional',
skuShortName: 'professional',
users: '100',
});
// New license returned by the preview/upload endpoints (an upgrade to Enterprise).
const newLicense = buildLicense({
id: 'new-license-id-0000000000000',
skuName: 'Enterprise',
skuShortName: 'enterprise',
users: 500,
});
describe('System console - License preview/diff view', () => {
before(() => {
cy.apiAdminLogin();
cy.shouldNotRunOnCloudEdition();
// * Ensure the server is licensed so the System Console renders the
// enterprise license page (the displayed license is stubbed below).
cy.apiRequireLicense();
});
beforeEach(() => {
cy.apiAdminLogin();
});
it('MM-67113 - Shows the newly uploaded license tier on success and refreshes the page after closing', () => {
// # The displayed license starts as Professional. It is served from a
// mutable holder so we can simulate the server only reporting the new
// license once propagation completes (by the time the modal closes).
const clientLicenseHolder = {body: currentClientLicense};
cy.intercept('GET', '**/api/v4/license/client', (req) => {
req.reply({statusCode: 200, body: clientLicenseHolder.body});
}).as('getClientLicense');
// # Preview and upload both return the new Enterprise license
cy.intercept('POST', '**/api/v4/license/preview', {statusCode: 200, body: newLicense}).as('previewLicense');
cy.intercept('POST', '**/api/v4/license', {statusCode: 200, body: newLicense}).as('uploadLicense');
cy.visit('/admin_console/about/license');
cy.wait('@getClientLicense');
// * The page initially shows the current (Professional) license
cy.get('.EnterpriseEditionLeftPanel__Title', {timeout: TIMEOUTS.TEN_SEC}).
should('contain.text', 'Mattermost Professional');
// # Select a license file to open the upload modal
selectLicenseFile();
// # Wait for the preview request and the diff view to render
cy.wait('@previewLicense');
cy.get('#UploadLicenseModal').should('be.visible').within(() => {
// * The diff view compares the current and new license tiers
cy.findByText('Review License Changes').should('be.visible');
cy.get('.diff-current').should('contain.text', 'Professional');
cy.get('.diff-new').should('contain.text', 'Enterprise');
// # Apply the new license
cy.get('#confirm-button').click();
});
cy.wait('@uploadLicense');
// * The success screen reflects the NEWLY uploaded tier (Enterprise),
// not the previously applied tier (Professional). This is the core of
// the "uploaded Enterprise, got Professional" report.
cy.get('#UploadLicenseModal').should('be.visible').within(() => {
cy.findByText('New license successfully applied').should('be.visible');
cy.get('.subtitle').
should('contain.text', 'Enterprise').
and('not.contain.text', 'Professional');
});
// # By the time the admin closes the modal, the server reports the new
// license. Closing must trigger a refresh that picks this up.
cy.then(() => {
clientLicenseHolder.body = buildClientLicense({
id: newLicense.id,
skuName: 'Enterprise',
skuShortName: 'enterprise',
users: '500',
});
});
cy.get('#UploadLicenseModal').within(() => {
cy.get('#done-button').click();
});
cy.get('#UploadLicenseModal').should('not.exist');
// * The System Console license page refreshes to show the new license
// without requiring a manual page reload.
cy.get('.EnterpriseEditionLeftPanel__Title', {timeout: TIMEOUTS.TEN_SEC}).
should('contain.text', 'Mattermost Enterprise');
});
it('MM-67113 - Warns when re-uploading the currently applied license and leaves it unchanged', () => {
// # The displayed license stays Professional throughout this flow
cy.intercept('GET', '**/api/v4/license/client', {
statusCode: 200,
body: currentClientLicense,
}).as('getClientLicense');
// # Preview and upload return the SAME license that is currently applied
// (same id), so the diff view should detect an unchanged license.
const sameLicense = buildLicense({
id: OLD_LICENSE_ID,
skuName: 'Professional',
skuShortName: 'professional',
users: 100,
});
cy.intercept('POST', '**/api/v4/license/preview', {statusCode: 200, body: sameLicense}).as('previewLicense');
cy.intercept('POST', '**/api/v4/license', {statusCode: 200, body: sameLicense}).as('uploadLicense');
cy.visit('/admin_console/about/license');
cy.wait('@getClientLicense');
cy.get('.EnterpriseEditionLeftPanel__Title', {timeout: TIMEOUTS.TEN_SEC}).
should('contain.text', 'Mattermost Professional');
// # Select the same license file to open the upload modal
selectLicenseFile();
cy.wait('@previewLicense');
// * The diff view warns that the license is already active
cy.get('#UploadLicenseModal').should('be.visible').within(() => {
cy.findByText('This license is already active').should('be.visible');
// # The admin can still apply it; doing so must not break the flow
cy.get('#confirm-button').click();
});
cy.wait('@uploadLicense');
// * The flow completes successfully and the modal can be closed
cy.get('#UploadLicenseModal').should('be.visible').within(() => {
cy.findByText('New license successfully applied').should('be.visible');
cy.get('#done-button').click();
});
cy.get('#UploadLicenseModal').should('not.exist');
// * The applied license is unchanged and the page remains functional
cy.get('.EnterpriseEditionLeftPanel__Title', {timeout: TIMEOUTS.TEN_SEC}).
should('contain.text', 'Mattermost Professional');
});
});
// Feed a small dummy file into the hidden license file input. The actual bytes
// are irrelevant because the preview/upload endpoints are stubbed.
function selectLicenseFile() {
cy.get('[data-testid="EnterpriseEditionLeftPanel"] input[type="file"]').
selectFile({
contents: Cypress.Buffer.from('dummy-license-content'),
fileName: 'test-license.mattermost-license',
}, {force: true});
}
// Build a License object matching the shape returned by POST /license/preview
// and POST /license (server model.License serialized to JSON).
function buildLicense({id, skuName, skuShortName, users}) {
const now = Date.now();
return {
id,
issued_at: now,
starts_at: now,
expires_at: now + (365 * 24 * 60 * 60 * 1000),
customer: {
id: 'customer-id',
name: 'Test Customer',
email: 'test@example.com',
company: 'Test Company',
},
features: {
users,
ldap: true,
ldap_groups: true,
mfa: true,
google_oauth: true,
office365_oauth: true,
compliance: true,
cluster: true,
metrics: true,
mhpns: true,
saml: true,
elastic_search: true,
announcement: true,
theme_management: true,
email_notification_contents: true,
data_retention: true,
message_export: true,
custom_permissions_schemes: true,
custom_terms_of_service: true,
guest_accounts: true,
guest_accounts_permissions: true,
},
sku_name: skuName,
sku_short_name: skuShortName,
is_gov_sku: false,
};
}
// Build a ClientLicense object (old format) matching GET /license/client.
// All values are strings, as produced by the server.
function buildClientLicense({id, skuName, skuShortName, users}) {
const now = Date.now();
return {
IsLicensed: 'true',
IsTrial: 'false',
Id: id,
SkuName: skuName,
SkuShortName: skuShortName,
Users: users,
IssuedAt: String(now),
StartsAt: String(now),
ExpiresAt: String(now + (365 * 24 * 60 * 60 * 1000)),
Name: 'Test Customer',
Email: 'test@example.com',
Company: 'Test Company',
IsGovSku: 'false',
LDAP: 'true',
LDAPGroups: 'true',
MFA: 'true',
SAML: 'true',
Cluster: 'true',
Metrics: 'true',
GoogleOAuth: 'true',
Office365OAuth: 'true',
OpenId: 'true',
Compliance: 'true',
MHPNS: 'true',
Announcement: 'true',
Elasticsearch: 'true',
DataRetention: 'true',
IDLoadedPushNotifications: 'true',
EmailNotificationContents: 'true',
MessageExport: 'true',
CustomPermissionsSchemes: 'true',
GuestAccounts: 'true',
GuestAccountsPermissions: 'true',
CustomTermsOfService: 'true',
LockTeammateNameDisplay: 'true',
Cloud: 'false',
SharedChannels: 'true',
RemoteClusterService: 'true',
OutgoingOAuthConnections: 'true',
};
}

View File

@@ -0,0 +1,31 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// ***************************************************************
// - [#] indicates a test step (e.g. # Go to a page)
// - [*] indicates an assertion (e.g. * Check the title)
// - Use element ID when selecting an element. Create one if none.
// ***************************************************************
// Group: @channels @enterprise @not_cloud @system_console @license_removal
describe('System console', () => {
before(() => {
// * Ensure we are on self-hosted Starter edition
cy.shouldNotRunOnCloudEdition();
cy.apiDeleteLicense();
});
it('MM-T5132 License page shows View plans button', () => {
cy.visit('/admin_console/about/license');
// *Validate View plans button exits
cy.get('.StarterLeftPanel').get('#starter_edition_view_plans').contains('View plans');
// # Click View plans
cy.get('.StarterLeftPanel').get('#starter_edition_view_plans').click();
// *Ensure pricing modal is open
cy.get('#pricingModal').should('exist');
});
});