mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-09-21 21:50:49 +02:00
feat(db & e2e): Enhance DB Schemas/Controllers and Improve E2E Tests (#966)
* feat: add global teardown to remove test data and add registration/log-out to auth flow * refactor(models/Conversation): index user field and add JSDoc to deleteConvos * refactor: add user index to message schema and ensure user is saved to each Message * refactor: add user to each saveMessage call * fix: handle case where title is null in zod schema * feat(e2e): ensure messages are deleted on cleanUp * fix: set last convo for all endpoints on conversation update * fix: enable registration for CI env
This commit is contained in:
parent
fd70e21732
commit
6358383001
28 changed files with 229 additions and 72 deletions
|
@ -9,11 +9,13 @@ const config: PlaywrightTestConfig = {
|
|||
...mainConfig,
|
||||
retries: 0,
|
||||
globalSetup: require.resolve('./setup/global-setup.local'),
|
||||
globalTeardown: require.resolve('./setup/global-teardown.local'),
|
||||
webServer: {
|
||||
...mainConfig.webServer,
|
||||
command: `node ${absolutePath}`,
|
||||
env: {
|
||||
...process.env,
|
||||
SEARCH: 'false',
|
||||
NODE_ENV: 'development',
|
||||
SESSION_EXPIRY: '60000',
|
||||
REFRESH_TOKEN_EXPIRY: '300000',
|
||||
|
|
|
@ -6,6 +6,7 @@ dotenv.config();
|
|||
|
||||
export default defineConfig({
|
||||
globalSetup: require.resolve('./setup/global-setup'),
|
||||
globalTeardown: require.resolve('./setup/global-teardown'),
|
||||
testDir: 'specs/',
|
||||
outputDir: 'specs/.test-results',
|
||||
/* Run tests in files in parallel.
|
||||
|
@ -61,8 +62,10 @@ export default defineConfig({
|
|||
reuseExistingServer: true,
|
||||
env: {
|
||||
...process.env,
|
||||
SEARCH: 'false',
|
||||
NODE_ENV: 'development',
|
||||
SESSION_EXPIRY: '60000',
|
||||
ALLOW_REGISTRATION: 'true',
|
||||
REFRESH_TOKEN_EXPIRY: '300000',
|
||||
},
|
||||
},
|
||||
|
|
|
@ -2,10 +2,31 @@ import { Page, FullConfig, chromium } from '@playwright/test';
|
|||
import dotenv from 'dotenv';
|
||||
dotenv.config();
|
||||
|
||||
type User = { username: string; password: string };
|
||||
type User = { email: string; name: string; password: string };
|
||||
|
||||
async function register(page: Page, user: User) {
|
||||
await page.getByRole('link', { name: 'Sign up' }).click();
|
||||
await page.getByLabel('Full name').click();
|
||||
await page.getByLabel('Full name').fill('test');
|
||||
await page.getByText('Username (optional)').click();
|
||||
await page.getByLabel('Username (optional)').fill('test');
|
||||
await page.getByLabel('Email').click();
|
||||
await page.getByLabel('Email').fill(user.email);
|
||||
await page.getByLabel('Email').press('Tab');
|
||||
await page.getByTestId('password').click();
|
||||
await page.getByTestId('password').fill(user.password);
|
||||
await page.getByTestId('confirm_password').click();
|
||||
await page.getByTestId('confirm_password').fill(user.password);
|
||||
await page.getByLabel('Submit registration').click();
|
||||
}
|
||||
|
||||
async function logout(page: Page, user: User) {
|
||||
await page.getByRole('button', { name: user.name }).click();
|
||||
await page.getByRole('button', { name: 'Log out' }).click();
|
||||
}
|
||||
|
||||
async function login(page: Page, user: User) {
|
||||
await page.locator('input[name="email"]').fill(user.username);
|
||||
await page.locator('input[name="email"]').fill(user.email);
|
||||
await page.locator('input[name="password"]').fill(user.password);
|
||||
await page.locator('input[name="password"]').press('Enter');
|
||||
}
|
||||
|
@ -15,22 +36,36 @@ async function authenticate(config: FullConfig, user: User) {
|
|||
const { baseURL, storageState } = config.projects[0].use;
|
||||
console.log('🤖: using baseURL', baseURL);
|
||||
console.dir(user, { depth: null });
|
||||
const browser = await chromium.launch();
|
||||
const browser = await chromium.launch({
|
||||
// headless: false,
|
||||
});
|
||||
const page = await browser.newPage();
|
||||
console.log('🤖: 🗝 authenticating user:', user.username);
|
||||
console.log('🤖: 🗝 authenticating user:', user.email);
|
||||
|
||||
if (!baseURL) {
|
||||
throw new Error('🤖: baseURL is not defined');
|
||||
}
|
||||
await page.goto(baseURL, { timeout: 5000 });
|
||||
await login(page, user);
|
||||
await page.waitForURL(`${baseURL}/chat/new`);
|
||||
console.log('🤖: ✔️ user successfully authenticated');
|
||||
|
||||
// Set localStorage before navigating to the page
|
||||
await page.context().addInitScript(() => {
|
||||
localStorage.setItem('navVisible', 'true');
|
||||
});
|
||||
console.log('🤖: ✔️ localStorage: set Nav as Visible', storageState);
|
||||
|
||||
await page.goto(baseURL, { timeout: 5000 });
|
||||
await register(page, user);
|
||||
await page.waitForURL(`${baseURL}/chat/new`);
|
||||
console.log('🤖: ✔️ user successfully registered');
|
||||
|
||||
// Logout
|
||||
await logout(page, user);
|
||||
await page.waitForURL(`${baseURL}/login`);
|
||||
console.log('🤖: ✔️ user successfully logged out');
|
||||
|
||||
await login(page, user);
|
||||
await page.waitForURL(`${baseURL}/chat/new`);
|
||||
console.log('🤖: ✔️ user successfully authenticated');
|
||||
|
||||
await page.context().storageState({ path: storageState as string });
|
||||
console.log('🤖: ✔️ authentication state successfully saved in', storageState);
|
||||
await browser.close();
|
||||
|
|
43
e2e/setup/cleanupUser.ts
Normal file
43
e2e/setup/cleanupUser.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
import connectDb from '@librechat/backend/lib/db/connectDb';
|
||||
import User from '@librechat/backend/models/User';
|
||||
import Session from '@librechat/backend/models/Session';
|
||||
import { deleteMessages } from '@librechat/backend/models/Message';
|
||||
import { deleteConvos } from '@librechat/backend/models/Conversation';
|
||||
type TUser = { email: string; password: string };
|
||||
|
||||
export default async function cleanupUser(user: TUser) {
|
||||
const { email } = user;
|
||||
try {
|
||||
console.log('🤖: global teardown has been started');
|
||||
const db = await connectDb();
|
||||
console.log('🤖: ✅ Connected to Database');
|
||||
|
||||
const { _id } = await User.findOne({ email }).lean();
|
||||
console.log('🤖: ✅ Found user in Database');
|
||||
|
||||
// Delete all conversations & associated messages
|
||||
const { deletedCount, messages } = await deleteConvos(_id, {});
|
||||
|
||||
if (messages.deletedCount > 0 || deletedCount > 0) {
|
||||
console.log(`🤖: ✅ Deleted ${deletedCount} convos & ${messages.deletedCount} messages`);
|
||||
}
|
||||
|
||||
// Ensure all user messages are deleted
|
||||
const { deletedCount: deletedMessages } = await deleteMessages({ user: _id });
|
||||
if (deletedMessages > 0) {
|
||||
console.log(`🤖: ✅ Deleted ${deletedMessages} remaining message(s)`);
|
||||
}
|
||||
|
||||
await Session.deleteAllUserSessions(_id);
|
||||
|
||||
await User.deleteMany({ email });
|
||||
|
||||
console.log('🤖: ✅ Deleted user from Database');
|
||||
|
||||
await db.connection.close();
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
process.on('uncaughtException', (err) => console.error('Uncaught Exception:', err));
|
|
@ -3,7 +3,8 @@ import authenticate from './authenticate';
|
|||
|
||||
async function globalSetup(config: FullConfig) {
|
||||
const user = {
|
||||
username: String(process.env.E2E_USER_EMAIL),
|
||||
name: 'test',
|
||||
email: String(process.env.E2E_USER_EMAIL),
|
||||
password: String(process.env.E2E_USER_PASSWORD),
|
||||
};
|
||||
|
||||
|
|
12
e2e/setup/global-teardown.local.ts
Normal file
12
e2e/setup/global-teardown.local.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import localUser from '../config.local';
|
||||
import cleanupUser from './cleanupUser';
|
||||
|
||||
async function globalTeardown() {
|
||||
try {
|
||||
await cleanupUser(localUser);
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
export default globalTeardown;
|
16
e2e/setup/global-teardown.ts
Normal file
16
e2e/setup/global-teardown.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import cleanupUser from './cleanupUser';
|
||||
|
||||
async function globalTeardown() {
|
||||
const user = {
|
||||
email: String(process.env.E2E_USER_EMAIL),
|
||||
password: String(process.env.E2E_USER_PASSWORD),
|
||||
};
|
||||
|
||||
try {
|
||||
await cleanupUser(user);
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
export default globalTeardown;
|
Loading…
Add table
Add a link
Reference in a new issue