mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-07-10 08:13:46 +00:00
test: stabilize librechat suite
This commit is contained in:
parent
cef05dd443
commit
225d50bebe
13 changed files with 199 additions and 142 deletions
|
|
@ -27,23 +27,27 @@ describe('Convos Routes', () => {
|
|||
deleteConvoSharedLinksWithCleanup,
|
||||
} = require('@librechat/api');
|
||||
|
||||
beforeAll(() => {
|
||||
convosRouter = require('../convos');
|
||||
|
||||
app = express();
|
||||
app.use(express.json());
|
||||
const createApp = (userId = 'test-user-123') => {
|
||||
const testApp = express();
|
||||
testApp.use(express.json());
|
||||
|
||||
/** Mock authenticated user */
|
||||
app.use((req, res, next) => {
|
||||
req.user = { id: 'test-user-123' };
|
||||
testApp.use((req, res, next) => {
|
||||
req.user = { id: userId };
|
||||
next();
|
||||
});
|
||||
|
||||
app.use('/api/convos', convosRouter);
|
||||
testApp.use('/api/convos', convosRouter);
|
||||
return testApp;
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
convosRouter = require('../convos');
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
jest.resetAllMocks();
|
||||
app = createApp();
|
||||
});
|
||||
|
||||
describe('DELETE /all', () => {
|
||||
|
|
@ -146,13 +150,7 @@ describe('Convos Routes', () => {
|
|||
jest.clearAllMocks();
|
||||
|
||||
/** Second user (simulate different user by modifying middleware) */
|
||||
const app2 = express();
|
||||
app2.use(express.json());
|
||||
app2.use((req, res, next) => {
|
||||
req.user = { id: 'test-user-456' };
|
||||
next();
|
||||
});
|
||||
app2.use('/api/convos', require('../convos'));
|
||||
const app2 = createApp('test-user-456');
|
||||
|
||||
deleteConvos.mockResolvedValue({ deletedCount: 7 });
|
||||
deleteToolCalls.mockResolvedValue({ deletedCount: 12 });
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ jest.mock('@librechat/agents', () => ({
|
|||
jest.mock('@librechat/api', () => ({
|
||||
unescapeLaTeX: jest.fn((x) => x),
|
||||
countTokens: jest.fn().mockResolvedValue(10),
|
||||
sendFeedbackScore: jest.fn().mockResolvedValue(undefined),
|
||||
traceIdForMessage: jest.fn((messageId) => `trace-${messageId}`),
|
||||
}));
|
||||
|
||||
jest.mock('@librechat/data-schemas', () => ({
|
||||
|
|
@ -49,6 +51,7 @@ jest.mock('~/server/middleware/requireJwtAuth', () => (req, res, next) => next()
|
|||
jest.mock('~/server/middleware', () => ({
|
||||
requireJwtAuth: (req, res, next) => next(),
|
||||
validateMessageReq: (req, res, next) => next(),
|
||||
configMiddleware: (req, res, next) => next(),
|
||||
}));
|
||||
|
||||
jest.mock('~/db/models', () => ({
|
||||
|
|
|
|||
|
|
@ -25,6 +25,16 @@ jest.mock('@librechat/api', () => ({
|
|||
GenerationJobManager: mockGenerationJobManager,
|
||||
}));
|
||||
|
||||
jest.mock('@librechat/api/telemetry', () => ({
|
||||
createSseStreamTelemetry: jest.fn(() => ({
|
||||
recordHeadersFlushed: jest.fn(),
|
||||
recordWrite: jest.fn(),
|
||||
recordFinalEventEmitted: jest.fn(),
|
||||
recordErrorEventEmitted: jest.fn(),
|
||||
recordSubscribeFailed: jest.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('~/models', () => ({
|
||||
saveMessage: jest.fn(),
|
||||
}));
|
||||
|
|
@ -67,7 +77,8 @@ function mockSubscribeSuccess() {
|
|||
|
||||
describe('SSE stream tenant isolation', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
Object.values(mockGenerationJobManager).forEach((mock) => mock.mockReset());
|
||||
mockGenerationJobManager.getActiveJobIdsForUser.mockResolvedValue([]);
|
||||
mockUserId = 'user-123';
|
||||
mockTenantId = undefined;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -11,14 +11,15 @@ const mockLogger = {
|
|||
debug: jest.fn(),
|
||||
};
|
||||
|
||||
let mockOpenIDCallbackAuthenticatorOptions;
|
||||
const mockOAuthHandler = jest.fn((_req, res) => res.status(204).end());
|
||||
const mockOpenIDCallbackMiddleware = jest.fn((_req, _res, next) => next());
|
||||
let mockOpenIDCallbackAuthenticatorOptions;
|
||||
const mockCreateOpenIDCallbackAuthenticator = jest.fn((options) => {
|
||||
const setMockOpenIDCallbackAuthenticator = (options) => {
|
||||
mockOpenIDCallbackAuthenticatorOptions = options;
|
||||
return mockOpenIDCallbackMiddleware;
|
||||
});
|
||||
const mockBuildOAuthFailureLog = jest.fn(({ provider, req, err, info, defaultMessage }) => ({
|
||||
};
|
||||
const mockCreateOpenIDCallbackAuthenticator = jest.fn(setMockOpenIDCallbackAuthenticator);
|
||||
const buildMockOAuthFailureLog = ({ provider, req, err, info, defaultMessage }) => ({
|
||||
provider,
|
||||
code: err?.code ?? info?.code ?? info?.error ?? req.query?.error,
|
||||
name: err?.name ?? info?.name,
|
||||
|
|
@ -37,17 +38,17 @@ const mockBuildOAuthFailureLog = jest.fn(({ provider, req, err, info, defaultMes
|
|||
path: req.path,
|
||||
forwarded_for: req.headers?.['x-forwarded-for'],
|
||||
user_agent: req.headers?.['user-agent'],
|
||||
}));
|
||||
const mockGetOAuthFailureMessage = jest.fn(
|
||||
(req) =>
|
||||
req.session?.messages?.pop() ??
|
||||
req.query?.error_description ??
|
||||
req.query?.error ??
|
||||
'OAuth authentication failed',
|
||||
);
|
||||
const mockRedirectToAuthFailure = jest.fn((res, { clientDomain, authFailedError }) =>
|
||||
res.redirect(`${clientDomain}/login?redirect=false&error=${authFailedError}`),
|
||||
);
|
||||
});
|
||||
const mockBuildOAuthFailureLog = jest.fn(buildMockOAuthFailureLog);
|
||||
const getMockOAuthFailureMessage = (req) =>
|
||||
req.session?.messages?.pop() ??
|
||||
req.query?.error_description ??
|
||||
req.query?.error ??
|
||||
'OAuth authentication failed';
|
||||
const mockGetOAuthFailureMessage = jest.fn(getMockOAuthFailureMessage);
|
||||
const redirectMockToAuthFailure = (res, { clientDomain, authFailedError }) =>
|
||||
res.redirect(`${clientDomain}/login?redirect=false&error=${authFailedError}`);
|
||||
const mockRedirectToAuthFailure = jest.fn(redirectMockToAuthFailure);
|
||||
const mockPassportAuthenticate = jest.fn(() => (_req, _res, next) => next());
|
||||
|
||||
jest.mock('passport', () => ({
|
||||
|
|
@ -105,8 +106,11 @@ afterAll(() => {
|
|||
});
|
||||
|
||||
function getOAuthRouter() {
|
||||
jest.resetModules();
|
||||
return require('./oauth');
|
||||
let router;
|
||||
jest.isolateModules(() => {
|
||||
router = require('./oauth');
|
||||
});
|
||||
return router;
|
||||
}
|
||||
|
||||
function createApp(sessionMessages) {
|
||||
|
|
@ -126,19 +130,17 @@ function createApp(sessionMessages) {
|
|||
|
||||
describe('OAuth route failure logging', () => {
|
||||
beforeEach(() => {
|
||||
mockLogger.warn.mockClear();
|
||||
mockLogger.error.mockClear();
|
||||
mockLogger.info.mockClear();
|
||||
mockLogger.debug.mockClear();
|
||||
mockOAuthHandler.mockClear();
|
||||
mockOpenIDCallbackMiddleware.mockClear();
|
||||
mockBuildOAuthFailureLog.mockClear();
|
||||
mockGetOAuthFailureMessage.mockClear();
|
||||
mockRedirectToAuthFailure.mockClear();
|
||||
mockPassportAuthenticate.mockClear();
|
||||
jest.resetModules();
|
||||
jest.clearAllMocks();
|
||||
process.env.DOMAIN_CLIENT = 'http://client.test';
|
||||
mockOpenIDCallbackAuthenticatorOptions = undefined;
|
||||
mockPassportAuthenticate.mockImplementation(() => (_req, _res, next) => next());
|
||||
mockCreateOpenIDCallbackAuthenticator.mockImplementation(setMockOpenIDCallbackAuthenticator);
|
||||
mockOpenIDCallbackMiddleware.mockImplementation((_req, _res, next) => next());
|
||||
mockOAuthHandler.mockImplementation((_req, res) => res.status(204).end());
|
||||
mockBuildOAuthFailureLog.mockImplementation(buildMockOAuthFailureLog);
|
||||
mockGetOAuthFailureMessage.mockImplementation(getMockOAuthFailureMessage);
|
||||
mockRedirectToAuthFailure.mockImplementation(redirectMockToAuthFailure);
|
||||
});
|
||||
|
||||
it('wires the package OpenID callback middleware into the route', async () => {
|
||||
|
|
|
|||
|
|
@ -56,6 +56,10 @@ function setTestUser(app, user) {
|
|||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
if (mongoose.connection.readyState !== 0) {
|
||||
await mongoose.disconnect();
|
||||
}
|
||||
|
||||
mongoServer = await MongoMemoryServer.create();
|
||||
const mongoUri = mongoServer.getUri();
|
||||
await mongoose.connect(mongoUri);
|
||||
|
|
@ -97,8 +101,10 @@ beforeAll(async () => {
|
|||
// Set default user
|
||||
currentTestUser = testUsers.owner;
|
||||
|
||||
// Import routes after middleware is set up
|
||||
promptRoutes = require('./prompts');
|
||||
// Import routes after middleware is set up and test-local mocks are registered.
|
||||
jest.isolateModules(() => {
|
||||
promptRoutes = require('./prompts');
|
||||
});
|
||||
app.use('/api/prompts', promptRoutes);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,45 +1,33 @@
|
|||
jest.mock(
|
||||
'@librechat/data-schemas',
|
||||
() => ({
|
||||
logger: { info: jest.fn(), warn: jest.fn(), debug: jest.fn(), error: jest.fn() },
|
||||
getTenantId: jest.fn(() => undefined),
|
||||
DEFAULT_SESSION_EXPIRY: 900000,
|
||||
DEFAULT_REFRESH_TOKEN_EXPIRY: 604800000,
|
||||
}),
|
||||
{ virtual: true },
|
||||
);
|
||||
jest.mock(
|
||||
'librechat-data-provider',
|
||||
() => ({
|
||||
ErrorTypes: {},
|
||||
SystemRoles: { USER: 'USER', ADMIN: 'ADMIN' },
|
||||
errorsToString: jest.fn(),
|
||||
}),
|
||||
{ virtual: true },
|
||||
);
|
||||
jest.mock(
|
||||
'@librechat/api',
|
||||
() => ({
|
||||
isEnabled: jest.fn((val) => val === 'true' || val === true),
|
||||
checkEmailConfig: jest.fn(),
|
||||
isEmailDomainAllowed: jest.fn(),
|
||||
math: jest.fn((val, fallback) => (val ? Number(val) : fallback)),
|
||||
shouldUseSecureCookie: jest.fn(() => false),
|
||||
resolveAppConfigForUser: jest.fn(async (_getAppConfig, _user) => ({})),
|
||||
setCloudFrontCookies: jest.fn(() => true),
|
||||
getCloudFrontConfig: jest.fn(() => ({
|
||||
domain: 'https://cdn.example.com',
|
||||
imageSigning: 'cookies',
|
||||
cookieDomain: '.example.com',
|
||||
privateKey: 'test-private-key',
|
||||
keyPairId: 'K123ABC',
|
||||
})),
|
||||
parseCloudFrontCookieScope: jest.fn(() => null),
|
||||
CLOUDFRONT_SCOPE_COOKIE: 'LibreChat-CloudFront-Scope',
|
||||
}),
|
||||
{ virtual: true },
|
||||
);
|
||||
jest.mock('~/models', () => ({
|
||||
const dataSchemasMock = {
|
||||
logger: { info: jest.fn(), warn: jest.fn(), debug: jest.fn(), error: jest.fn() },
|
||||
getTenantId: jest.fn(() => undefined),
|
||||
DEFAULT_SESSION_EXPIRY: 900000,
|
||||
DEFAULT_REFRESH_TOKEN_EXPIRY: 604800000,
|
||||
};
|
||||
const dataProviderMock = {
|
||||
ErrorTypes: {},
|
||||
SystemRoles: { USER: 'USER', ADMIN: 'ADMIN' },
|
||||
errorsToString: jest.fn(),
|
||||
};
|
||||
const apiMock = {
|
||||
isEnabled: jest.fn((val) => val === 'true' || val === true),
|
||||
checkEmailConfig: jest.fn(),
|
||||
isEmailDomainAllowed: jest.fn(),
|
||||
math: jest.fn((val, fallback) => (val ? Number(val) : fallback)),
|
||||
shouldUseSecureCookie: jest.fn(() => false),
|
||||
resolveAppConfigForUser: jest.fn(async (_getAppConfig, _user) => ({})),
|
||||
setCloudFrontCookies: jest.fn(() => true),
|
||||
getCloudFrontConfig: jest.fn(() => ({
|
||||
domain: 'https://cdn.example.com',
|
||||
imageSigning: 'cookies',
|
||||
cookieDomain: '.example.com',
|
||||
privateKey: 'test-private-key',
|
||||
keyPairId: 'K123ABC',
|
||||
})),
|
||||
parseCloudFrontCookieScope: jest.fn(() => null),
|
||||
CLOUDFRONT_SCOPE_COOKIE: 'LibreChat-CloudFront-Scope',
|
||||
};
|
||||
const modelsMock = {
|
||||
findUser: jest.fn(),
|
||||
findToken: jest.fn(),
|
||||
createUser: jest.fn(),
|
||||
|
|
@ -54,8 +42,8 @@ jest.mock('~/models', () => ({
|
|||
generateToken: jest.fn(),
|
||||
deleteUserById: jest.fn(),
|
||||
generateRefreshToken: jest.fn(),
|
||||
}));
|
||||
jest.mock('~/strategies/validators', () => ({
|
||||
};
|
||||
const validatorsMock = {
|
||||
registerSchema: {
|
||||
safeParse: jest.fn((user) => ({
|
||||
success: true,
|
||||
|
|
@ -68,9 +56,18 @@ jest.mock('~/strategies/validators', () => ({
|
|||
},
|
||||
})),
|
||||
},
|
||||
}));
|
||||
jest.mock('~/server/services/Config', () => ({ getAppConfig: jest.fn() }));
|
||||
jest.mock('~/server/utils', () => ({ sendEmail: jest.fn() }));
|
||||
};
|
||||
const configMock = { getAppConfig: jest.fn() };
|
||||
const serverUtilsMock = { sendEmail: jest.fn() };
|
||||
|
||||
jest.resetModules();
|
||||
jest.doMock('@librechat/data-schemas', () => dataSchemasMock);
|
||||
jest.doMock('librechat-data-provider', () => dataProviderMock);
|
||||
jest.doMock('@librechat/api', () => apiMock);
|
||||
jest.doMock('~/models', () => modelsMock);
|
||||
jest.doMock('~/strategies/validators', () => validatorsMock);
|
||||
jest.doMock('~/server/services/Config', () => configMock);
|
||||
jest.doMock('~/server/utils', () => serverUtilsMock);
|
||||
|
||||
const {
|
||||
checkEmailConfig,
|
||||
|
|
@ -80,9 +77,9 @@ const {
|
|||
setCloudFrontCookies,
|
||||
getCloudFrontConfig,
|
||||
parseCloudFrontCookieScope,
|
||||
} = require('@librechat/api');
|
||||
} = apiMock;
|
||||
const jwt = require('jsonwebtoken');
|
||||
const { logger, getTenantId } = require('@librechat/data-schemas');
|
||||
const { logger, getTenantId } = dataSchemasMock;
|
||||
const {
|
||||
findUser,
|
||||
findToken,
|
||||
|
|
@ -95,9 +92,9 @@ const {
|
|||
createSession,
|
||||
createToken,
|
||||
deleteTokens,
|
||||
} = require('~/models');
|
||||
const { getAppConfig } = require('~/server/services/Config');
|
||||
const { sendEmail } = require('~/server/utils');
|
||||
} = modelsMock;
|
||||
const { getAppConfig } = configMock;
|
||||
const { sendEmail } = serverUtilsMock;
|
||||
const bcrypt = require('bcryptjs');
|
||||
const {
|
||||
setOpenIDAuthTokens,
|
||||
|
|
@ -130,6 +127,36 @@ function mockRequest(sessionData = {}, cookies = {}) {
|
|||
};
|
||||
}
|
||||
|
||||
const originalEnv = { ...process.env };
|
||||
const defaultCloudFrontCookieConfig = {
|
||||
domain: 'https://cdn.example.com',
|
||||
imageSigning: 'cookies',
|
||||
cookieDomain: '.example.com',
|
||||
privateKey: 'test-private-key',
|
||||
keyPairId: 'K123ABC',
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
process.env = {
|
||||
...originalEnv,
|
||||
JWT_REFRESH_SECRET: 'test-refresh-secret',
|
||||
OPENID_REUSE_TOKENS: 'true',
|
||||
};
|
||||
checkEmailConfig.mockReturnValue(false);
|
||||
shouldUseSecureCookie.mockReturnValue(false);
|
||||
isEmailDomainAllowed.mockReturnValue(true);
|
||||
resolveAppConfigForUser.mockResolvedValue({});
|
||||
setCloudFrontCookies.mockReturnValue(true);
|
||||
getCloudFrontConfig.mockReturnValue(defaultCloudFrontCookieConfig);
|
||||
parseCloudFrontCookieScope.mockReturnValue(null);
|
||||
getTenantId.mockReturnValue(undefined);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
process.env = originalEnv;
|
||||
});
|
||||
|
||||
describe('setOpenIDAuthTokens', () => {
|
||||
const env = process.env;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const express = require('express');
|
||||
const request = require('supertest');
|
||||
const zlib = require('zlib');
|
||||
const staticCache = require('../staticCache');
|
||||
|
||||
const binaryParser = (res, callback) => {
|
||||
const chunks = [];
|
||||
|
|
@ -18,15 +18,9 @@ describe('staticCache', () => {
|
|||
let indexFile;
|
||||
let manifestFile;
|
||||
let swFile;
|
||||
let staticCache;
|
||||
|
||||
beforeAll(() => {
|
||||
// Create a test directory and files
|
||||
testDir = path.join(__dirname, 'test-static');
|
||||
if (!fs.existsSync(testDir)) {
|
||||
fs.mkdirSync(testDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Create test files
|
||||
const createTestFiles = () => {
|
||||
testFile = path.join(testDir, 'test.js');
|
||||
indexFile = path.join(testDir, 'index.html');
|
||||
manifestFile = path.join(testDir, 'manifest.json');
|
||||
|
|
@ -62,6 +56,10 @@ describe('staticCache', () => {
|
|||
const distImagesDir = path.join(testDir, 'dist', 'images');
|
||||
fs.mkdirSync(distImagesDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(distImagesDir, 'logo.png'), 'fake-png-data');
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'librechat-static-cache-'));
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
|
|
@ -79,6 +77,15 @@ describe('staticCache', () => {
|
|||
delete process.env.STATIC_CACHE_S_MAX_AGE;
|
||||
delete process.env.STATIC_CACHE_MAX_AGE;
|
||||
delete process.env.ENABLE_STATIC_ASSET_BROTLI;
|
||||
|
||||
if (fs.existsSync(testDir)) {
|
||||
fs.rmSync(testDir, { recursive: true, force: true });
|
||||
}
|
||||
fs.mkdirSync(testDir, { recursive: true });
|
||||
createTestFiles();
|
||||
|
||||
jest.resetModules();
|
||||
staticCache = require('../staticCache');
|
||||
});
|
||||
describe('cache headers in production', () => {
|
||||
beforeEach(() => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import type { ReactNode } from 'react';
|
||||
import type { FileConfigInput } from 'librechat-data-provider';
|
||||
import UploadSkillDialog from '../UploadSkillDialog';
|
||||
|
|
@ -70,12 +70,14 @@ jest.mock('~/utils', () => ({
|
|||
cn: (...classes: Array<string | false | null | undefined>) => classes.filter(Boolean).join(' '),
|
||||
}));
|
||||
|
||||
function getFileInput(container: HTMLElement): HTMLInputElement {
|
||||
const input = container.querySelector('input[type="file"]');
|
||||
if (!(input instanceof HTMLInputElement)) {
|
||||
throw new Error('Upload input was not rendered');
|
||||
}
|
||||
return input;
|
||||
async function getFileInput(baseElement: HTMLElement): Promise<HTMLInputElement> {
|
||||
return await waitFor(() => {
|
||||
const input = baseElement.querySelector('input[type="file"]');
|
||||
if (!(input instanceof HTMLInputElement)) {
|
||||
throw new Error('Upload input was not rendered');
|
||||
}
|
||||
return input;
|
||||
});
|
||||
}
|
||||
|
||||
describe('UploadSkillDialog', () => {
|
||||
|
|
@ -106,13 +108,13 @@ describe('UploadSkillDialog', () => {
|
|||
expect(screen.getByText('File size must not exceed 1.06 MB')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('rejects files above the configured skill import limit before upload', () => {
|
||||
const { container } = render(<UploadSkillDialog isOpen={true} setIsOpen={mockSetIsOpen} />);
|
||||
it('rejects files above the configured skill import limit before upload', async () => {
|
||||
const { baseElement } = render(<UploadSkillDialog isOpen={true} setIsOpen={mockSetIsOpen} />);
|
||||
const file = new File([new Uint8Array(1024 * 1024 + 1)], 'too-large.skill', {
|
||||
type: 'application/zip',
|
||||
});
|
||||
|
||||
fireEvent.change(getFileInput(container), {
|
||||
fireEvent.change(await getFileInput(baseElement), {
|
||||
target: {
|
||||
files: [file],
|
||||
},
|
||||
|
|
@ -125,14 +127,14 @@ describe('UploadSkillDialog', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('uploads files exactly at the configured skill import limit', () => {
|
||||
it('uploads files exactly at the configured skill import limit', async () => {
|
||||
const appendSpy = jest.spyOn(FormData.prototype, 'append');
|
||||
const { container } = render(<UploadSkillDialog isOpen={true} setIsOpen={mockSetIsOpen} />);
|
||||
const { baseElement } = render(<UploadSkillDialog isOpen={true} setIsOpen={mockSetIsOpen} />);
|
||||
const file = new File([new Uint8Array(1024 * 1024)], 'exact-limit.skill', {
|
||||
type: 'application/zip',
|
||||
});
|
||||
|
||||
fireEvent.change(getFileInput(container), {
|
||||
fireEvent.change(await getFileInput(baseElement), {
|
||||
target: {
|
||||
files: [file],
|
||||
},
|
||||
|
|
@ -144,14 +146,14 @@ describe('UploadSkillDialog', () => {
|
|||
appendSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('uploads files under the configured skill import limit', () => {
|
||||
it('uploads files under the configured skill import limit', async () => {
|
||||
const appendSpy = jest.spyOn(FormData.prototype, 'append');
|
||||
const { container } = render(<UploadSkillDialog isOpen={true} setIsOpen={mockSetIsOpen} />);
|
||||
const { baseElement } = render(<UploadSkillDialog isOpen={true} setIsOpen={mockSetIsOpen} />);
|
||||
const file = new File([new Uint8Array(1024)], 'small.skill', {
|
||||
type: 'application/zip',
|
||||
});
|
||||
|
||||
fireEvent.change(getFileInput(container), {
|
||||
fireEvent.change(await getFileInput(baseElement), {
|
||||
target: {
|
||||
files: [file],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -78,8 +78,8 @@
|
|||
"test:all": "npm run test:client && npm run test:api && npm run test:packages:api && npm run test:packages:data-provider && npm run test:packages:data-schemas",
|
||||
"e2e:update": "npm run e2e:prepare && playwright test --config=e2e/playwright.config.local.ts --update-snapshots",
|
||||
"e2e:report": "npx playwright show-report e2e/playwright-report",
|
||||
"lint:fix": "eslint --fix \"{,!(node_modules|venv)/**/}*.{js,jsx,ts,tsx}\"",
|
||||
"lint": "eslint \"{,!(node_modules|venv)/**/}*.{js,jsx,ts,tsx}\"",
|
||||
"lint:fix": "eslint --fix --no-error-on-unmatched-pattern \"**/*.{js,jsx,ts,tsx}\"",
|
||||
"lint": "eslint --no-error-on-unmatched-pattern \"**/*.{js,jsx,ts,tsx}\"",
|
||||
"sort-imports": "node scripts/sort-imports.mts",
|
||||
"sort-imports:check": "node scripts/sort-imports.mts --check",
|
||||
"format": "npx prettier --write \"{,!(node_modules|venv)/**/}*.{js,jsx,ts,tsx}\"",
|
||||
|
|
|
|||
|
|
@ -178,6 +178,7 @@ type CreateRunFn = (params: {
|
|||
requestBody: Record<string, unknown>;
|
||||
user: Record<string, unknown>;
|
||||
tenantId?: string;
|
||||
appConfig?: AppConfig;
|
||||
tokenCounter?: (message: unknown) => number;
|
||||
}) => Promise<{
|
||||
Graph?: unknown;
|
||||
|
|
|
|||
|
|
@ -62,9 +62,7 @@ export function buildLangfuseConfig({
|
|||
normalizeString(fanout?.collectorUrl) ??
|
||||
normalizeString(process.env.LANGFUSE_FANOUT_COLLECTOR_URL);
|
||||
const baseUrl =
|
||||
fanoutEnabled && fanoutCollectorUrl
|
||||
? fanoutCollectorUrl
|
||||
: normalizeString(config?.baseUrl);
|
||||
fanoutEnabled && fanoutCollectorUrl ? fanoutCollectorUrl : normalizeString(config?.baseUrl);
|
||||
|
||||
if (baseUrl) {
|
||||
langfuse.baseUrl = baseUrl;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import mongoose, { Types } from 'mongoose';
|
||||
import mongoose, { Mongoose, Types } from 'mongoose';
|
||||
import { MongoMemoryServer } from 'mongodb-memory-server';
|
||||
import { PrincipalType, PrincipalModel } from 'librechat-data-provider';
|
||||
import type { IConfig } from '~/types';
|
||||
|
|
@ -6,24 +6,25 @@ import { createConfigMethods } from './config';
|
|||
import configSchema from '~/schema/config';
|
||||
|
||||
let mongoServer: MongoMemoryServer;
|
||||
let testMongoose: Mongoose;
|
||||
let methods: ReturnType<typeof createConfigMethods>;
|
||||
|
||||
beforeAll(async () => {
|
||||
mongoServer = await MongoMemoryServer.create();
|
||||
await mongoose.connect(mongoServer.getUri());
|
||||
if (!mongoose.models.Config) {
|
||||
mongoose.model<IConfig>('Config', configSchema);
|
||||
}
|
||||
methods = createConfigMethods(mongoose);
|
||||
testMongoose = new mongoose.Mongoose();
|
||||
await testMongoose.connect(mongoServer.getUri());
|
||||
testMongoose.model<IConfig>('Config', configSchema.clone());
|
||||
await testMongoose.models.Config.init();
|
||||
methods = createConfigMethods(testMongoose as unknown as typeof mongoose);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await mongoose.disconnect();
|
||||
await testMongoose.disconnect();
|
||||
await mongoServer.stop();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await mongoose.models.Config.deleteMany({});
|
||||
await testMongoose.models.Config.deleteMany({});
|
||||
});
|
||||
|
||||
describe('upsertConfig tombstone preservation', () => {
|
||||
|
|
@ -61,7 +62,7 @@ describe('upsertConfig tombstone preservation', () => {
|
|||
10,
|
||||
);
|
||||
|
||||
const count = await mongoose.models.Config.countDocuments({});
|
||||
const count = await testMongoose.models.Config.countDocuments({});
|
||||
expect(count).toBe(1);
|
||||
});
|
||||
|
||||
|
|
@ -486,7 +487,7 @@ describe('expectEmpty atomic guard', () => {
|
|||
});
|
||||
expect(result).toBeTruthy();
|
||||
|
||||
const remaining = await mongoose.models.Config.countDocuments({});
|
||||
const remaining = await testMongoose.models.Config.countDocuments({});
|
||||
expect(remaining).toBe(0);
|
||||
});
|
||||
|
||||
|
|
@ -504,12 +505,12 @@ describe('expectEmpty atomic guard', () => {
|
|||
});
|
||||
expect(result).toBeNull();
|
||||
|
||||
const remaining = await mongoose.models.Config.countDocuments({});
|
||||
const remaining = await testMongoose.models.Config.countDocuments({});
|
||||
expect(remaining).toBe(1);
|
||||
});
|
||||
|
||||
it('deleteConfig with expectEmpty returns null when tombstones is non-empty (doc preserved)', async () => {
|
||||
await mongoose.models.Config.create({
|
||||
await testMongoose.models.Config.create({
|
||||
principalType: PrincipalType.ROLE,
|
||||
principalId: 'admin',
|
||||
principalModel: PrincipalModel.ROLE,
|
||||
|
|
@ -525,7 +526,7 @@ describe('expectEmpty atomic guard', () => {
|
|||
});
|
||||
expect(result).toBeNull();
|
||||
|
||||
const remaining = await mongoose.models.Config.countDocuments({});
|
||||
const remaining = await testMongoose.models.Config.countDocuments({});
|
||||
expect(remaining).toBe(1);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -21,10 +21,11 @@ let methods: ReturnType<typeof createSystemGrantMethods>;
|
|||
|
||||
beforeAll(async () => {
|
||||
mongoServer = await MongoMemoryServer.create();
|
||||
await mongoose.connect(mongoServer.getUri());
|
||||
SystemGrant =
|
||||
mongoose.models.SystemGrant || mongoose.model<t.ISystemGrant>('SystemGrant', systemGrantSchema);
|
||||
await SystemGrant.init();
|
||||
methods = createSystemGrantMethods(mongoose);
|
||||
await mongoose.connect(mongoServer.getUri());
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue