mirror of
https://github.com/documenso/documenso.git
synced 2026-07-23 00:13:46 +10:00
365 lines
13 KiB
TypeScript
365 lines
13 KiB
TypeScript
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 { expect, test } from '@playwright/test';
|
|
|
|
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();
|
|
});
|
|
});
|