mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-27 04:07:05 +00:00
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
GitNexus Index / index (push) Waiting to run
GitNexus Index / post-index (push) Blocked by required conditions
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Waiting to run
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Waiting to run
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Blocked by required conditions
Sync Helm Chart Tags / Ignore non-main push (push) Waiting to run
Sync Helm Chart Tags / Sync chart tags (push) Waiting to run
* fix: rebuild enforced specs from preset * test: Add enforced model spec e2e coverage * test: Align enforced spec regression scope
170 lines
5.7 KiB
TypeScript
170 lines
5.7 KiB
TypeScript
import { randomUUID } from 'crypto';
|
|
import { expect, test } from '@playwright/test';
|
|
import type { Page } from '@playwright/test';
|
|
import {
|
|
MOCK_ENDPOINTS,
|
|
NEW_CHAT_PATH,
|
|
getAccessToken,
|
|
messagesView,
|
|
replyPrompt,
|
|
replyText,
|
|
requestJson,
|
|
selectModelSpec,
|
|
sendMessage,
|
|
} from './helpers';
|
|
|
|
const NO_PARENT = '00000000-0000-0000-0000-000000000000';
|
|
const ENFORCED_SPEC_NAME = 'e2e-mock-provider-a';
|
|
const ENFORCED_SPEC_LABEL = 'Mock Provider A';
|
|
|
|
type AgentStartResponse = {
|
|
conversationId: string;
|
|
streamId: string;
|
|
status: string;
|
|
};
|
|
|
|
async function createProject(page: Page, name: string): Promise<string> {
|
|
await page.goto('/projects', { timeout: 10000 });
|
|
await page.getByRole('button', { name: 'New project' }).first().click();
|
|
|
|
const dialog = page.getByRole('dialog');
|
|
await dialog.getByRole('textbox', { name: 'Project name' }).fill(name);
|
|
await dialog.getByRole('button', { name: 'Create project' }).click();
|
|
|
|
await expect(page.getByRole('heading', { name })).toBeVisible();
|
|
const projectId = new URL(page.url()).pathname.split('/projects/')[1];
|
|
expect(projectId).toBeTruthy();
|
|
return projectId;
|
|
}
|
|
|
|
const uniqueName = (prefix: string) => `${prefix} ${Date.now()}-${Math.floor(Math.random() * 1e4)}`;
|
|
|
|
async function waitForStreamReply(page: Page, token: string, streamId: string, expected: string) {
|
|
await page.evaluate(
|
|
async ({ accessToken, expectedText, currentStreamId }) => {
|
|
const controller = new AbortController();
|
|
const timeout = window.setTimeout(() => controller.abort(), 60000);
|
|
let buffered = '';
|
|
|
|
try {
|
|
const response = await fetch(
|
|
`/api/agents/chat/stream/${encodeURIComponent(currentStreamId)}?resume=true`,
|
|
{
|
|
method: 'GET',
|
|
credentials: 'include',
|
|
headers: { Authorization: `Bearer ${accessToken}` },
|
|
signal: controller.signal,
|
|
},
|
|
);
|
|
|
|
if (!response.ok || !response.body) {
|
|
throw new Error(`Expected stream to return 2xx, got ${response.status}`);
|
|
}
|
|
|
|
const reader = response.body.getReader();
|
|
const decoder = new TextDecoder();
|
|
|
|
while (true) {
|
|
const { done, value } = await reader.read();
|
|
if (done) {
|
|
break;
|
|
}
|
|
|
|
buffered += decoder.decode(value, { stream: true });
|
|
if (buffered.includes(expectedText)) {
|
|
await reader.cancel();
|
|
return;
|
|
}
|
|
|
|
if (buffered.includes('event: error')) {
|
|
await reader.cancel();
|
|
throw new Error(`Stream emitted an error before ${expectedText}:\n${buffered}`);
|
|
}
|
|
}
|
|
} finally {
|
|
window.clearTimeout(timeout);
|
|
}
|
|
|
|
throw new Error(`Timed out waiting for ${expectedText}. Latest stream:\n${buffered}`);
|
|
},
|
|
{ accessToken: token, currentStreamId: streamId, expectedText: expected },
|
|
);
|
|
}
|
|
|
|
test.describe('enforced model specs', () => {
|
|
test.skip(
|
|
process.env.E2E_MODEL_SPECS_ENFORCE !== 'true',
|
|
'requires E2E_MODEL_SPECS_ENFORCE=true',
|
|
);
|
|
|
|
test('rebuilds a valid enforced spec from the backend preset when the request is stale', async ({
|
|
page,
|
|
}) => {
|
|
test.setTimeout(120000);
|
|
const label = uniqueName('reported-spec').replace(/\s+/g, '-');
|
|
|
|
await page.goto(NEW_CHAT_PATH, { timeout: 10000 });
|
|
const token = await getAccessToken(page);
|
|
const userMessageId = randomUUID();
|
|
const start = await requestJson<AgentStartResponse>(page, {
|
|
path: `/api/agents/chat/${encodeURIComponent(MOCK_ENDPOINTS[0].label)}`,
|
|
token,
|
|
method: 'POST',
|
|
body: {
|
|
text: replyPrompt(label),
|
|
sender: 'User',
|
|
clientTimestamp: new Date().toLocaleString('sv').replace(' ', 'T'),
|
|
isCreatedByUser: true,
|
|
parentMessageId: NO_PARENT,
|
|
conversationId: 'new',
|
|
messageId: userMessageId,
|
|
responseMessageId: `${userMessageId}_`,
|
|
endpoint: MOCK_ENDPOINTS[0].label,
|
|
endpointType: 'custom',
|
|
model: { stale: 'cached-client-value' },
|
|
agent_id: 'agent_from_cached_client_state',
|
|
spec: ENFORCED_SPEC_NAME,
|
|
isTemporary: false,
|
|
isRegenerate: false,
|
|
error: false,
|
|
},
|
|
});
|
|
|
|
expect(start.status).toBe('started');
|
|
expect(start.conversationId).toBeTruthy();
|
|
|
|
await waitForStreamReply(page, token, start.streamId, replyText(label));
|
|
});
|
|
|
|
test('keeps a project-scoped chat attached when sending with an enforced spec', async ({
|
|
page,
|
|
}) => {
|
|
test.setTimeout(120000);
|
|
const name = uniqueName('E2E Enforced Project');
|
|
const projectId = await createProject(page, name);
|
|
const label = uniqueName('project-spec').replace(/\s+/g, '-');
|
|
|
|
await page.goto(`/c/new?projectId=${projectId}`, { timeout: 10000 });
|
|
await expect(page.getByRole('button', { name: 'Remove from project' })).toBeVisible();
|
|
await expect(page.getByRole('textbox', { name: 'Message input' })).toHaveAttribute(
|
|
'placeholder',
|
|
new RegExp(name),
|
|
);
|
|
|
|
await selectModelSpec(page, ENFORCED_SPEC_LABEL);
|
|
await expect(page.getByRole('button', { name: 'Remove from project' })).toBeVisible();
|
|
|
|
const response = await sendMessage(page, replyPrompt(label));
|
|
expect(response.ok()).toBeTruthy();
|
|
await expect(messagesView(page).getByText(replyText(label))).toBeVisible({ timeout: 30000 });
|
|
await expect(page).toHaveURL(/\/c\/(?!new)/, { timeout: 15000 });
|
|
|
|
const projectRow = page.getByRole('button', { name }).first();
|
|
if ((await projectRow.getAttribute('aria-expanded')) !== 'true') {
|
|
await projectRow.click();
|
|
}
|
|
await expect(
|
|
page.getByTestId(`project-chats-${projectId}`).getByTestId('convo-item').first(),
|
|
).toBeVisible();
|
|
});
|
|
});
|