mirror of
https://github.com/documenso/documenso.git
synced 2026-07-25 09:25:08 +10:00
feat: add license integration (#2346)
Changes: - Adds integration for the license server. - Prevent adding flags that the instance is not allowed to add
This commit is contained in:
@@ -0,0 +1,326 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
|
||||
import type { TCachedLicense, TLicenseClaim } from '@documenso/lib/types/license';
|
||||
import { seedUser } from '@documenso/prisma/seed/users';
|
||||
|
||||
import { apiSignin } from '../fixtures/authentication';
|
||||
|
||||
const LICENSE_FILE_NAME = '.documenso-license.json';
|
||||
const LICENSE_BACKUP_FILE_NAME = '.documenso-license-backup.json';
|
||||
|
||||
/**
|
||||
* Get the path to the license file.
|
||||
*
|
||||
* The server reads from process.cwd() which is apps/remix when the dev server runs.
|
||||
* Tests run from packages/app-tests, so we need to go up to the root then into apps/remix.
|
||||
*/
|
||||
const getLicenseFilePath = () => {
|
||||
// From packages/app-tests/e2e/license -> ../../../../apps/remix/.documenso-license.json
|
||||
return path.join(__dirname, '../../../../apps/remix', LICENSE_FILE_NAME);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the path to the backup license file.
|
||||
*/
|
||||
const getBackupLicenseFilePath = () => {
|
||||
return path.join(__dirname, '../../../../apps/remix', LICENSE_BACKUP_FILE_NAME);
|
||||
};
|
||||
|
||||
/**
|
||||
* Backup the existing license file if it exists.
|
||||
*/
|
||||
const backupLicenseFile = async () => {
|
||||
const licensePath = getLicenseFilePath();
|
||||
const backupPath = getBackupLicenseFilePath();
|
||||
|
||||
try {
|
||||
await fs.access(licensePath);
|
||||
await fs.rename(licensePath, backupPath);
|
||||
} catch (e) {
|
||||
// File doesn't exist, nothing to backup
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Restore the backup license file if it exists.
|
||||
*/
|
||||
const restoreLicenseFile = async () => {
|
||||
const licensePath = getLicenseFilePath();
|
||||
const backupPath = getBackupLicenseFilePath();
|
||||
|
||||
try {
|
||||
await fs.access(backupPath);
|
||||
await fs.rename(backupPath, licensePath);
|
||||
} catch (e) {
|
||||
// Backup doesn't exist, nothing to restore
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Write a license file with the given data.
|
||||
* Pass null to delete the license file.
|
||||
*/
|
||||
const writeLicenseFile = async (data: TCachedLicense | null) => {
|
||||
const licensePath = getLicenseFilePath();
|
||||
|
||||
if (data === null) {
|
||||
await fs.unlink(licensePath).catch(() => {
|
||||
// File doesn't exist, ignore
|
||||
});
|
||||
} else {
|
||||
await fs.writeFile(licensePath, JSON.stringify(data, null, 2), 'utf-8');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a mock license object with the given flags.
|
||||
*/
|
||||
const createMockLicenseWithFlags = (flags: TLicenseClaim): TCachedLicense => {
|
||||
return {
|
||||
lastChecked: new Date().toISOString(),
|
||||
license: {
|
||||
status: 'ACTIVE',
|
||||
createdAt: new Date(),
|
||||
name: 'Test License',
|
||||
periodEnd: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // 30 days from now
|
||||
cancelAtPeriodEnd: false,
|
||||
licenseKey: 'test-license-key',
|
||||
flags,
|
||||
},
|
||||
requestedLicenseKey: 'test-license-key',
|
||||
derivedStatus: 'ACTIVE',
|
||||
unauthorizedFlagUsage: false,
|
||||
};
|
||||
};
|
||||
|
||||
// Run tests serially to avoid race conditions with the license file
|
||||
test.describe.configure({ mode: 'serial' });
|
||||
|
||||
// SKIPPING TEST UNTIL WE ADD A WAY TO OVERRIDE THE LICENSE FILE.
|
||||
test.describe.skip('Enterprise Feature Restrictions', () => {
|
||||
test.beforeAll(async () => {
|
||||
// Backup any existing license file before running tests
|
||||
await backupLicenseFile();
|
||||
});
|
||||
|
||||
test.afterAll(async () => {
|
||||
// Restore the backup license file after all tests complete
|
||||
await restoreLicenseFile();
|
||||
});
|
||||
|
||||
test.beforeEach(async () => {
|
||||
// Clean up license file before each test to ensure clean state
|
||||
await writeLicenseFile(null);
|
||||
});
|
||||
|
||||
test.afterEach(async () => {
|
||||
// Clean up license file after each test
|
||||
await writeLicenseFile(null);
|
||||
});
|
||||
|
||||
test('[ADMIN CLAIMS]: shows restricted features with asterisk when no license', async ({
|
||||
page,
|
||||
}) => {
|
||||
// Ensure no license file exists
|
||||
await writeLicenseFile(null);
|
||||
|
||||
const { user: adminUser } = await seedUser({
|
||||
isAdmin: true,
|
||||
});
|
||||
|
||||
await apiSignin({
|
||||
page,
|
||||
email: adminUser.email,
|
||||
redirectPath: '/admin/claims',
|
||||
});
|
||||
|
||||
// Click Create claim button to open the dialog
|
||||
await page.getByRole('button', { name: 'Create claim' }).click();
|
||||
|
||||
// Wait for dialog to open
|
||||
await expect(page.getByRole('dialog')).toBeVisible();
|
||||
|
||||
// Check that enterprise features have asterisks (are restricted)
|
||||
// These are the enterprise features that should be marked with *
|
||||
await expect(page.getByText(/Email domains\s¹/)).toBeVisible();
|
||||
await expect(page.getByText(/Embed authoring\s¹/)).toBeVisible();
|
||||
await expect(page.getByText(/White label for embed authoring\s¹/)).toBeVisible();
|
||||
await expect(page.getByText(/21 CFR\s¹/)).toBeVisible();
|
||||
await expect(page.getByText(/Authentication portal\s¹/)).toBeVisible();
|
||||
|
||||
// Check that the alert is visible
|
||||
await expect(
|
||||
page.getByText('Your current license does not include these features.'),
|
||||
).toBeVisible();
|
||||
await expect(page.getByRole('link', { name: 'Learn more' })).toBeVisible();
|
||||
|
||||
// Check that enterprise feature checkboxes are disabled
|
||||
const emailDomainsCheckbox = page.locator('#flag-emailDomains');
|
||||
await expect(emailDomainsCheckbox).toBeDisabled();
|
||||
|
||||
const cfr21Checkbox = page.locator('#flag-cfr21');
|
||||
await expect(cfr21Checkbox).toBeDisabled();
|
||||
|
||||
const authPortalCheckbox = page.locator('#flag-authenticationPortal');
|
||||
await expect(authPortalCheckbox).toBeDisabled();
|
||||
});
|
||||
|
||||
test('[ADMIN CLAIMS]: no restrictions when license has all enterprise features', async ({
|
||||
page,
|
||||
}) => {
|
||||
// Create a license with ALL enterprise features enabled
|
||||
await writeLicenseFile(
|
||||
createMockLicenseWithFlags({
|
||||
emailDomains: true,
|
||||
embedAuthoring: true,
|
||||
embedAuthoringWhiteLabel: true,
|
||||
cfr21: true,
|
||||
authenticationPortal: true,
|
||||
billing: true,
|
||||
}),
|
||||
);
|
||||
|
||||
const { user: adminUser } = await seedUser({
|
||||
isAdmin: true,
|
||||
});
|
||||
|
||||
await apiSignin({
|
||||
page,
|
||||
email: adminUser.email,
|
||||
redirectPath: '/admin/claims',
|
||||
});
|
||||
|
||||
// Click Create claim button to open the dialog
|
||||
await page.getByRole('button', { name: 'Create claim' }).click();
|
||||
|
||||
// Wait for dialog to open
|
||||
await expect(page.getByRole('dialog')).toBeVisible();
|
||||
|
||||
// Check that enterprise features do NOT have asterisks
|
||||
// They should show without the * since the license covers them
|
||||
await expect(page.getByText(/Email domains\s¹/)).not.toBeVisible();
|
||||
await expect(page.getByText(/Embed authoring\s¹/)).not.toBeVisible();
|
||||
await expect(page.getByText(/21 CFR\s¹/)).not.toBeVisible();
|
||||
await expect(page.getByText(/Authentication portal\s¹/)).not.toBeVisible();
|
||||
|
||||
// The plain labels should be visible (without asterisks)
|
||||
await expect(page.locator('label[for="flag-emailDomains"]')).toContainText('Email domains');
|
||||
await expect(page.locator('label[for="flag-cfr21"]')).toContainText('21 CFR');
|
||||
|
||||
// The alert should NOT be visible
|
||||
await expect(
|
||||
page.getByText('Your current license does not include these features.'),
|
||||
).not.toBeVisible();
|
||||
|
||||
// Check that enterprise feature checkboxes are enabled
|
||||
const emailDomainsCheckbox = page.locator('#flag-emailDomains');
|
||||
await expect(emailDomainsCheckbox).toBeEnabled();
|
||||
|
||||
const cfr21Checkbox = page.locator('#flag-cfr21');
|
||||
await expect(cfr21Checkbox).toBeEnabled();
|
||||
|
||||
const authPortalCheckbox = page.locator('#flag-authenticationPortal');
|
||||
await expect(authPortalCheckbox).toBeEnabled();
|
||||
});
|
||||
|
||||
test('[ADMIN CLAIMS]: only unlicensed features show asterisk with partial license', async ({
|
||||
page,
|
||||
}) => {
|
||||
// Create a license with SOME enterprise features (emailDomains and cfr21)
|
||||
await writeLicenseFile(
|
||||
createMockLicenseWithFlags({
|
||||
emailDomains: true,
|
||||
cfr21: true,
|
||||
// embedAuthoring, embedAuthoringWhiteLabel, authenticationPortal are NOT included
|
||||
}),
|
||||
);
|
||||
|
||||
const { user: adminUser } = await seedUser({
|
||||
isAdmin: true,
|
||||
});
|
||||
|
||||
await apiSignin({
|
||||
page,
|
||||
email: adminUser.email,
|
||||
redirectPath: '/admin/claims',
|
||||
});
|
||||
|
||||
// Click Create claim button to open the dialog
|
||||
await page.getByRole('button', { name: 'Create claim' }).click();
|
||||
|
||||
// Wait for dialog to open
|
||||
await expect(page.getByRole('dialog')).toBeVisible();
|
||||
|
||||
// Features NOT in license should have asterisks
|
||||
await expect(page.getByText(/Embed authoring\s¹/)).toBeVisible();
|
||||
await expect(page.getByText(/White label for embed authoring\s¹/)).toBeVisible();
|
||||
await expect(page.getByText(/Authentication portal\s¹/)).toBeVisible();
|
||||
|
||||
// Features IN license should NOT have asterisks
|
||||
await expect(page.getByText(/Email domains\s¹/)).not.toBeVisible();
|
||||
await expect(page.getByText(/21 CFR\s¹/)).not.toBeVisible();
|
||||
|
||||
// The plain labels for licensed features should be visible
|
||||
await expect(page.locator('label[for="flag-emailDomains"]')).toContainText('Email domains');
|
||||
await expect(page.locator('label[for="flag-cfr21"]')).toContainText('21 CFR');
|
||||
|
||||
// Alert should be visible since some features are restricted
|
||||
await expect(
|
||||
page.getByText('Your current license does not include these features.'),
|
||||
).toBeVisible();
|
||||
|
||||
// Licensed features should be enabled
|
||||
const emailDomainsCheckbox = page.locator('#flag-emailDomains');
|
||||
await expect(emailDomainsCheckbox).toBeEnabled();
|
||||
|
||||
const cfr21Checkbox = page.locator('#flag-cfr21');
|
||||
await expect(cfr21Checkbox).toBeEnabled();
|
||||
|
||||
// Unlicensed features should be disabled
|
||||
const embedAuthoringCheckbox = page.locator('#flag-embedAuthoring');
|
||||
await expect(embedAuthoringCheckbox).toBeDisabled();
|
||||
|
||||
const authPortalCheckbox = page.locator('#flag-authenticationPortal');
|
||||
await expect(authPortalCheckbox).toBeDisabled();
|
||||
});
|
||||
|
||||
test('[ADMIN CLAIMS]: non-enterprise features are always enabled', async ({ page }) => {
|
||||
// Ensure no license file exists
|
||||
await writeLicenseFile(null);
|
||||
|
||||
const { user: adminUser } = await seedUser({
|
||||
isAdmin: true,
|
||||
});
|
||||
|
||||
await apiSignin({
|
||||
page,
|
||||
email: adminUser.email,
|
||||
redirectPath: '/admin/claims',
|
||||
});
|
||||
|
||||
// Click Create claim button to open the dialog
|
||||
await page.getByRole('button', { name: 'Create claim' }).click();
|
||||
|
||||
// Wait for dialog to open
|
||||
await expect(page.getByRole('dialog')).toBeVisible();
|
||||
|
||||
// Non-enterprise features should NOT have asterisks
|
||||
await expect(page.getByText(/Unlimited documents\s¹/)).not.toBeVisible();
|
||||
await expect(page.getByText(/Branding\s¹/)).not.toBeVisible();
|
||||
await expect(page.getByText(/Embed signing\s¹/)).not.toBeVisible();
|
||||
|
||||
// Non-enterprise features should always be enabled
|
||||
const unlimitedDocsCheckbox = page.locator('#flag-unlimitedDocuments');
|
||||
await expect(unlimitedDocsCheckbox).toBeEnabled();
|
||||
|
||||
const brandingCheckbox = page.locator('#flag-allowCustomBranding');
|
||||
await expect(brandingCheckbox).toBeEnabled();
|
||||
|
||||
const embedSigningCheckbox = page.locator('#flag-embedSigning');
|
||||
await expect(embedSigningCheckbox).toBeEnabled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,392 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
|
||||
import type { TCachedLicense } from '@documenso/lib/types/license';
|
||||
import { seedUser } from '@documenso/prisma/seed/users';
|
||||
|
||||
import { apiSignin } from '../fixtures/authentication';
|
||||
|
||||
const LICENSE_FILE_NAME = '.documenso-license.json';
|
||||
const LICENSE_BACKUP_FILE_NAME = '.documenso-license-backup.json';
|
||||
|
||||
/**
|
||||
* Get the path to the license file.
|
||||
*
|
||||
* The server reads from process.cwd() which is apps/remix when the dev server runs.
|
||||
* Tests run from packages/app-tests, so we need to go up to the root then into apps/remix.
|
||||
*/
|
||||
const getLicenseFilePath = () => {
|
||||
// From packages/app-tests/e2e/license -> ../../../../apps/remix/.documenso-license.json
|
||||
return path.join(__dirname, '../../../../apps/remix', LICENSE_FILE_NAME);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the path to the backup license file.
|
||||
*/
|
||||
const getBackupLicenseFilePath = () => {
|
||||
return path.join(__dirname, '../../../../apps/remix', LICENSE_BACKUP_FILE_NAME);
|
||||
};
|
||||
|
||||
/**
|
||||
* Backup the existing license file if it exists.
|
||||
*/
|
||||
const backupLicenseFile = async () => {
|
||||
const licensePath = getLicenseFilePath();
|
||||
const backupPath = getBackupLicenseFilePath();
|
||||
|
||||
try {
|
||||
await fs.access(licensePath);
|
||||
await fs.rename(licensePath, backupPath);
|
||||
} catch (e) {
|
||||
// File doesn't exist, nothing to backup
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Restore the backup license file if it exists.
|
||||
*/
|
||||
const restoreLicenseFile = async () => {
|
||||
const licensePath = getLicenseFilePath();
|
||||
const backupPath = getBackupLicenseFilePath();
|
||||
|
||||
try {
|
||||
await fs.access(backupPath);
|
||||
await fs.rename(backupPath, licensePath);
|
||||
} catch (e) {
|
||||
// Backup doesn't exist, nothing to restore
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Write a license file with the given data.
|
||||
* Pass null to delete the license file.
|
||||
*/
|
||||
const writeLicenseFile = async (data: TCachedLicense | null) => {
|
||||
const licensePath = getLicenseFilePath();
|
||||
|
||||
if (data === null) {
|
||||
await fs.unlink(licensePath).catch(() => {
|
||||
// File doesn't exist, ignore
|
||||
});
|
||||
} else {
|
||||
await fs.writeFile(licensePath, JSON.stringify(data, null, 2), 'utf-8');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a mock license object with the given status and unauthorized flag.
|
||||
*/
|
||||
const createMockLicense = (
|
||||
status: 'ACTIVE' | 'EXPIRED' | 'PAST_DUE',
|
||||
unauthorizedFlagUsage: boolean,
|
||||
): TCachedLicense => {
|
||||
return {
|
||||
lastChecked: new Date().toISOString(),
|
||||
license: {
|
||||
status,
|
||||
createdAt: new Date(),
|
||||
name: 'Test License',
|
||||
periodEnd: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // 30 days from now
|
||||
cancelAtPeriodEnd: false,
|
||||
licenseKey: 'test-license-key',
|
||||
flags: {},
|
||||
},
|
||||
requestedLicenseKey: 'test-license-key',
|
||||
derivedStatus: unauthorizedFlagUsage ? 'UNAUTHORIZED' : status,
|
||||
unauthorizedFlagUsage,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a mock license object with no license data (only unauthorized flag).
|
||||
*/
|
||||
const createMockUnauthorizedWithoutLicense = (): TCachedLicense => {
|
||||
return {
|
||||
lastChecked: new Date().toISOString(),
|
||||
license: null,
|
||||
unauthorizedFlagUsage: true,
|
||||
derivedStatus: 'UNAUTHORIZED',
|
||||
};
|
||||
};
|
||||
|
||||
// Run tests serially to avoid race conditions with the license file
|
||||
test.describe.configure({ mode: 'serial' });
|
||||
|
||||
// SKIPPING TEST UNTIL WE ADD A WAY TO OVERRIDE THE LICENSE FILE.
|
||||
test.describe.skip('License Status Banner', () => {
|
||||
test.beforeAll(async () => {
|
||||
// Backup any existing license file before running tests
|
||||
await backupLicenseFile();
|
||||
});
|
||||
|
||||
test.afterAll(async () => {
|
||||
// Restore the backup license file after all tests complete
|
||||
await restoreLicenseFile();
|
||||
});
|
||||
|
||||
test.beforeEach(async () => {
|
||||
// Clean up license file before each test to ensure clean state
|
||||
await writeLicenseFile(null);
|
||||
});
|
||||
|
||||
test.afterEach(async () => {
|
||||
// Clean up license file after each test
|
||||
await writeLicenseFile(null);
|
||||
});
|
||||
|
||||
test('[ADMIN]: no banner when license file is missing', async ({ page }) => {
|
||||
// Ensure no license file exists BEFORE any page loads
|
||||
await writeLicenseFile(null);
|
||||
|
||||
const { user: adminUser } = await seedUser({
|
||||
isAdmin: true,
|
||||
});
|
||||
|
||||
// Navigate to admin page - license is read during page load
|
||||
await apiSignin({
|
||||
page,
|
||||
email: adminUser.email,
|
||||
redirectPath: '/admin',
|
||||
});
|
||||
|
||||
// Verify we're on the admin page
|
||||
await expect(page.getByRole('heading', { name: 'Admin Panel' })).toBeVisible();
|
||||
|
||||
// Global banner should not be visible (no license file)
|
||||
await expect(
|
||||
page.getByText('This is an expired license instance of Documenso'),
|
||||
).not.toBeVisible();
|
||||
|
||||
// Admin banner messages should not be visible (no license file means no banner)
|
||||
await expect(page.getByText('License payment overdue')).not.toBeVisible();
|
||||
await expect(page.getByText('License expired')).not.toBeVisible();
|
||||
await expect(page.getByText('Invalid License Type')).not.toBeVisible();
|
||||
await expect(page.getByText('Missing License')).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('[ADMIN]: no banner when license is ACTIVE', async ({ page }) => {
|
||||
// Create an ACTIVE license BEFORE any page loads
|
||||
await writeLicenseFile(createMockLicense('ACTIVE', false));
|
||||
|
||||
const { user: adminUser } = await seedUser({
|
||||
isAdmin: true,
|
||||
});
|
||||
|
||||
// Navigate to admin page - license is read during page load
|
||||
await apiSignin({
|
||||
page,
|
||||
email: adminUser.email,
|
||||
redirectPath: '/admin',
|
||||
});
|
||||
|
||||
// Verify we're on the admin page
|
||||
await expect(page.getByRole('heading', { name: 'Admin Panel' })).toBeVisible();
|
||||
|
||||
// Global banner should not be visible (license is ACTIVE)
|
||||
await expect(
|
||||
page.getByText('This is an expired license instance of Documenso'),
|
||||
).not.toBeVisible();
|
||||
|
||||
// Admin banner messages should not be visible (license is ACTIVE)
|
||||
await expect(page.getByText('License payment overdue')).not.toBeVisible();
|
||||
await expect(page.getByText('License expired')).not.toBeVisible();
|
||||
await expect(page.getByText('Invalid License Type')).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('[ADMIN]: admin banner shows PAST_DUE warning', async ({ page }) => {
|
||||
// Create a PAST_DUE license BEFORE any page loads
|
||||
await writeLicenseFile(createMockLicense('PAST_DUE', false));
|
||||
|
||||
const { user: adminUser } = await seedUser({
|
||||
isAdmin: true,
|
||||
});
|
||||
|
||||
// Navigate to admin page - license is read during page load
|
||||
await apiSignin({
|
||||
page,
|
||||
email: adminUser.email,
|
||||
redirectPath: '/admin',
|
||||
});
|
||||
|
||||
// Verify we're on the admin page
|
||||
await expect(page.getByRole('heading', { name: 'Admin Panel' })).toBeVisible();
|
||||
|
||||
// Global banner should NOT be visible (only shows for EXPIRED + unauthorized)
|
||||
await expect(
|
||||
page.getByText('This is an expired license instance of Documenso'),
|
||||
).not.toBeVisible();
|
||||
|
||||
// Admin banner should show PAST_DUE message
|
||||
await expect(page.getByText('License payment overdue')).toBeVisible();
|
||||
await expect(
|
||||
page.getByText('Please update your payment to avoid service disruptions.'),
|
||||
).toBeVisible();
|
||||
|
||||
// Should have the "See Documentation" link
|
||||
await expect(page.getByRole('link', { name: 'See Documentation' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('[ADMIN]: admin banner shows EXPIRED error', async ({ page }) => {
|
||||
// Create an EXPIRED license WITHOUT unauthorized usage BEFORE any page loads
|
||||
await writeLicenseFile(createMockLicense('EXPIRED', false));
|
||||
|
||||
const { user: adminUser } = await seedUser({
|
||||
isAdmin: true,
|
||||
});
|
||||
|
||||
// Navigate to admin page - license is read during page load
|
||||
await apiSignin({
|
||||
page,
|
||||
email: adminUser.email,
|
||||
redirectPath: '/admin',
|
||||
});
|
||||
|
||||
// Verify we're on the admin page
|
||||
await expect(page.getByRole('heading', { name: 'Admin Panel' })).toBeVisible();
|
||||
|
||||
// Global banner should NOT be visible (requires BOTH expired AND unauthorized)
|
||||
await expect(
|
||||
page.getByText('This is an expired license instance of Documenso'),
|
||||
).not.toBeVisible();
|
||||
|
||||
// Admin banner should show EXPIRED message
|
||||
await expect(page.getByText('License expired')).toBeVisible();
|
||||
await expect(
|
||||
page.getByText('Please renew your license to continue using enterprise features.'),
|
||||
).toBeVisible();
|
||||
|
||||
// Should have the "See Documentation" link
|
||||
await expect(page.getByRole('link', { name: 'See Documentation' })).toBeVisible();
|
||||
});
|
||||
|
||||
test.skip('[ADMIN]: global banner shows when EXPIRED with unauthorized usage', async ({
|
||||
page,
|
||||
}) => {
|
||||
// Create an EXPIRED license WITH unauthorized usage BEFORE any page loads
|
||||
await writeLicenseFile(createMockLicense('EXPIRED', true));
|
||||
|
||||
const { user: adminUser } = await seedUser({
|
||||
isAdmin: true,
|
||||
});
|
||||
|
||||
// Navigate to admin page - license is read during page load
|
||||
await apiSignin({
|
||||
page,
|
||||
email: adminUser.email,
|
||||
redirectPath: '/admin',
|
||||
});
|
||||
|
||||
// Verify we're on the admin page
|
||||
await expect(page.getByRole('heading', { name: 'Admin Panel' })).toBeVisible();
|
||||
|
||||
// Global banner SHOULD be visible (EXPIRED + unauthorized)
|
||||
await expect(page.getByText('This is an expired license instance of Documenso')).toBeVisible();
|
||||
|
||||
// Admin banner should show UNAUTHORIZED message (takes precedence over EXPIRED)
|
||||
await expect(page.getByText('Invalid License Type')).toBeVisible();
|
||||
await expect(
|
||||
page.getByText(
|
||||
'Your Documenso instance is using features that are not part of your license.',
|
||||
),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test('[ADMIN]: admin banner shows UNAUTHORIZED when flags are misused with license', async ({
|
||||
page,
|
||||
}) => {
|
||||
// Create an ACTIVE license but WITH unauthorized flag usage BEFORE any page loads
|
||||
await writeLicenseFile(createMockLicense('ACTIVE', true));
|
||||
|
||||
const { user: adminUser } = await seedUser({
|
||||
isAdmin: true,
|
||||
});
|
||||
|
||||
// Navigate to admin page - license is read during page load
|
||||
await apiSignin({
|
||||
page,
|
||||
email: adminUser.email,
|
||||
redirectPath: '/admin',
|
||||
});
|
||||
|
||||
// Verify we're on the admin page
|
||||
await expect(page.getByRole('heading', { name: 'Admin Panel' })).toBeVisible();
|
||||
|
||||
// Global banner should NOT be visible (requires EXPIRED status)
|
||||
await expect(
|
||||
page.getByText('This is an expired license instance of Documenso'),
|
||||
).not.toBeVisible();
|
||||
|
||||
// Admin banner should show UNAUTHORIZED message
|
||||
await expect(page.getByText('Invalid License Type')).toBeVisible();
|
||||
await expect(
|
||||
page.getByText(
|
||||
'Your Documenso instance is using features that are not part of your license.',
|
||||
),
|
||||
).toBeVisible();
|
||||
|
||||
// Should have the "See Documentation" link
|
||||
await expect(page.getByRole('link', { name: 'See Documentation' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('[ADMIN]: admin banner shows Invalid License Type when unauthorized without license data', async ({
|
||||
page,
|
||||
}) => {
|
||||
// Create a license file with unauthorized flag but no license data BEFORE any page loads
|
||||
// Note: Even without license data, the banner shows "Invalid License Type" because the
|
||||
// license file exists (just with license: null). The "Missing License" message would only
|
||||
// show if the entire license prop was null, which doesn't happen with a valid file.
|
||||
await writeLicenseFile(createMockUnauthorizedWithoutLicense());
|
||||
|
||||
const { user: adminUser } = await seedUser({
|
||||
isAdmin: true,
|
||||
});
|
||||
|
||||
// Navigate to admin page - license is read during page load
|
||||
await apiSignin({
|
||||
page,
|
||||
email: adminUser.email,
|
||||
redirectPath: '/admin',
|
||||
});
|
||||
|
||||
// Verify we're on the admin page
|
||||
await expect(page.getByRole('heading', { name: 'Admin Panel' })).toBeVisible();
|
||||
|
||||
// Global banner should NOT be visible (no EXPIRED status, only unauthorized flag)
|
||||
await expect(
|
||||
page.getByText('This is an expired license instance of Documenso'),
|
||||
).not.toBeVisible();
|
||||
|
||||
// Admin banner should show Invalid License Type message (unauthorized flag is set)
|
||||
await expect(page.getByText('Invalid License Type')).toBeVisible();
|
||||
await expect(
|
||||
page.getByText(
|
||||
'Your Documenso instance is using features that are not part of your license.',
|
||||
),
|
||||
).toBeVisible();
|
||||
|
||||
// Should have the "See Documentation" link
|
||||
await expect(page.getByRole('link', { name: 'See Documentation' })).toBeVisible();
|
||||
});
|
||||
|
||||
test.skip('[ADMIN]: global banner visible on non-admin pages when EXPIRED with unauthorized', async ({
|
||||
page,
|
||||
}) => {
|
||||
// Create an EXPIRED license WITH unauthorized usage BEFORE any page loads
|
||||
await writeLicenseFile(createMockLicense('EXPIRED', true));
|
||||
|
||||
const { user } = await seedUser();
|
||||
|
||||
// Navigate to documents page - license is read during page load
|
||||
await apiSignin({
|
||||
page,
|
||||
email: user.email,
|
||||
redirectPath: '/documents',
|
||||
});
|
||||
|
||||
// Global banner SHOULD be visible on any authenticated page (EXPIRED + unauthorized)
|
||||
await expect(page.getByText('This is an expired license instance of Documenso')).toBeVisible();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user