mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-03-07 08:40:19 +01: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
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
* fix: subdirectory redirects * fix: use path-segment boundary check when stripping BASE_URL prefix A bare `startsWith(BASE_URL)` matches on character prefix, not path segments. With BASE_URL="/chat", a path like "/chatroom/c/abc" would incorrectly strip to "room/c/abc" (no leading slash). Guard with an exact-match-or-slash check: `p === BASE_URL || p.startsWith(BASE_URL + '/')`. Also removes the dead `BASE_URL !== '/'` guard — module init already converts '/' to ''. * test: add path-segment boundary tests and clarify subdirectory coverage - Add /chatroom, /chatbot, /app/chatroom regression tests to verify BASE_URL stripping only matches on segment boundaries - Clarify useAuthRedirect subdirectory test documents React Router basename behavior (BASE_URL stripping tested in api-endpoints-subdir) - Use `delete proc.browser` instead of undefined assignment for cleanup - Add rationale to eslint-disable comment for isolateModules require * fix: use relative path and correct instructions in subdirectory test script - Replace hardcoded /home/danny/LibreChat/.env with repo-root-relative path so the script works from any checkout location - Update instructions to use production build (npm run build && npm run backend) since nginx proxies to :3080 which only serves the SPA after a full build, not during frontend:dev on :3090 * fix: skip pointless redirect_to=/ for root path and fix jsdom 26+ compat buildLoginRedirectUrl now returns plain /login when the resolved path is root — redirect_to=/ adds no value since / immediately redirects to /c/new after login anyway. Also rewrites api-endpoints.spec.ts to use window.history.replaceState instead of Object.defineProperty(window, 'location', ...) which jsdom 26+ no longer allows. * test: fix request-interceptor.spec.ts for jsdom 26+ compatibility Switch from jsdom to happy-dom environment which allows Object.defineProperty on window.location. jsdom 26+ made location non-configurable, breaking all 8 tests in this file. * chore: update browser property handling in api-endpoints-subdir test Changed the handling of the `proc.browser` property from deletion to setting it to false, ensuring compatibility with the current testing environment. * chore: update backend restart instructions in test subdirectory setup script Changed the instruction for restarting the backend from "npm run backend:dev" to "npm run backend" to reflect the correct command for the current setup. * refactor: ensure proper cleanup in loadModuleWithBase function Wrapped the module loading logic in a try-finally block to guarantee that the `proc.browser` property is reset to false and the base element is removed, improving reliability in the testing environment. * refactor: improve browser property handling in loadModuleWithBase function Revised the management of the `proc.browser` property to store the original value before modification, ensuring it is restored correctly after module loading. This enhances the reliability of the testing environment.
140 lines
5.4 KiB
TypeScript
140 lines
5.4 KiB
TypeScript
/**
|
|
* @jest-environment jsdom
|
|
*/
|
|
|
|
/**
|
|
* Tests for buildLoginRedirectUrl and apiBaseUrl under subdirectory deployments.
|
|
*
|
|
* Uses jest.isolateModules to re-import api-endpoints with a <base href="/chat/">
|
|
* element present, simulating a subdirectory deployment where BASE_URL = '/chat'.
|
|
*
|
|
* Tests that need to override window.location use explicit function arguments
|
|
* instead of mocking the global, since jsdom 26+ does not allow redefining it.
|
|
*/
|
|
|
|
function loadModuleWithBase(baseHref: string) {
|
|
const base = document.createElement('base');
|
|
base.setAttribute('href', baseHref);
|
|
document.head.appendChild(base);
|
|
|
|
const proc = process as typeof process & { browser?: boolean };
|
|
const originalBrowser = proc.browser;
|
|
|
|
let mod: typeof import('../src/api-endpoints');
|
|
try {
|
|
proc.browser = true;
|
|
jest.isolateModules(() => {
|
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- static import not usable inside isolateModules
|
|
mod = require('../src/api-endpoints');
|
|
});
|
|
return mod!;
|
|
} finally {
|
|
proc.browser = originalBrowser;
|
|
document.head.removeChild(base);
|
|
}
|
|
}
|
|
|
|
describe('buildLoginRedirectUrl — subdirectory deployment (BASE_URL = /chat)', () => {
|
|
let buildLoginRedirectUrl: typeof import('../src/api-endpoints').buildLoginRedirectUrl;
|
|
let apiBaseUrl: typeof import('../src/api-endpoints').apiBaseUrl;
|
|
|
|
beforeAll(() => {
|
|
const mod = loadModuleWithBase('/chat/');
|
|
buildLoginRedirectUrl = mod.buildLoginRedirectUrl;
|
|
apiBaseUrl = mod.apiBaseUrl;
|
|
});
|
|
|
|
it('sets BASE_URL to "/chat" (trailing slash stripped)', () => {
|
|
expect(apiBaseUrl()).toBe('/chat');
|
|
});
|
|
|
|
it('returns "/login" without base prefix (compatible with React Router navigate)', () => {
|
|
const result = buildLoginRedirectUrl('/chat/c/new', '', '');
|
|
expect(result).toMatch(/^\/login/);
|
|
expect(result).not.toMatch(/^\/chat/);
|
|
});
|
|
|
|
it('strips base prefix from redirect_to when pathname includes base', () => {
|
|
const result = buildLoginRedirectUrl('/chat/c/abc123', '?model=gpt-4', '');
|
|
const redirectTo = decodeURIComponent(result.split('redirect_to=')[1]);
|
|
expect(redirectTo).toBe('/c/abc123?model=gpt-4');
|
|
expect(redirectTo).not.toContain('/chat/');
|
|
});
|
|
|
|
it('works with pathnames that do not include the base prefix', () => {
|
|
const result = buildLoginRedirectUrl('/c/new', '', '');
|
|
const redirectTo = decodeURIComponent(result.split('redirect_to=')[1]);
|
|
expect(redirectTo).toBe('/c/new');
|
|
});
|
|
|
|
it('returns plain /login for base-prefixed login path', () => {
|
|
expect(buildLoginRedirectUrl('/chat/login', '', '')).toBe('/login');
|
|
});
|
|
|
|
it('returns plain /login for base-prefixed login sub-path', () => {
|
|
expect(buildLoginRedirectUrl('/chat/login/2fa', '', '')).toBe('/login');
|
|
});
|
|
|
|
it('returns plain /login when stripped path is root (no pointless redirect_to=/)', () => {
|
|
const result = buildLoginRedirectUrl('/chat', '', '');
|
|
expect(result).toBe('/login');
|
|
expect(result).not.toContain('redirect_to');
|
|
});
|
|
|
|
it('composes correct full URL for window.location.href (apiBaseUrl + buildLoginRedirectUrl)', () => {
|
|
const fullUrl = apiBaseUrl() + buildLoginRedirectUrl('/chat/c/abc123', '', '');
|
|
expect(fullUrl).toBe('/chat/login?redirect_to=%2Fc%2Fabc123');
|
|
expect(fullUrl).not.toContain('/chat/chat/');
|
|
});
|
|
|
|
it('encodes query params and hash correctly after stripping base', () => {
|
|
const result = buildLoginRedirectUrl('/chat/c/deep', '?q=hello&submit=true', '#section');
|
|
const redirectTo = decodeURIComponent(result.split('redirect_to=')[1]);
|
|
expect(redirectTo).toBe('/c/deep?q=hello&submit=true#section');
|
|
});
|
|
|
|
it('does not strip base when path shares a prefix but is not a segment match', () => {
|
|
const result = buildLoginRedirectUrl('/chatroom/c/abc123', '', '');
|
|
const redirectTo = decodeURIComponent(result.split('redirect_to=')[1]);
|
|
expect(redirectTo).toBe('/chatroom/c/abc123');
|
|
});
|
|
|
|
it('does not strip base from /chatbot path', () => {
|
|
const result = buildLoginRedirectUrl('/chatbot', '', '');
|
|
const redirectTo = decodeURIComponent(result.split('redirect_to=')[1]);
|
|
expect(redirectTo).toBe('/chatbot');
|
|
});
|
|
});
|
|
|
|
describe('buildLoginRedirectUrl — deep subdirectory (BASE_URL = /app/chat)', () => {
|
|
let buildLoginRedirectUrl: typeof import('../src/api-endpoints').buildLoginRedirectUrl;
|
|
let apiBaseUrl: typeof import('../src/api-endpoints').apiBaseUrl;
|
|
|
|
beforeAll(() => {
|
|
const mod = loadModuleWithBase('/app/chat/');
|
|
buildLoginRedirectUrl = mod.buildLoginRedirectUrl;
|
|
apiBaseUrl = mod.apiBaseUrl;
|
|
});
|
|
|
|
it('sets BASE_URL to "/app/chat"', () => {
|
|
expect(apiBaseUrl()).toBe('/app/chat');
|
|
});
|
|
|
|
it('strips deep base prefix from redirect_to', () => {
|
|
const result = buildLoginRedirectUrl('/app/chat/c/abc123', '', '');
|
|
const redirectTo = decodeURIComponent(result.split('redirect_to=')[1]);
|
|
expect(redirectTo).toBe('/c/abc123');
|
|
});
|
|
|
|
it('full URL does not double the base prefix', () => {
|
|
const fullUrl = apiBaseUrl() + buildLoginRedirectUrl('/app/chat/c/abc123', '', '');
|
|
expect(fullUrl).toBe('/app/chat/login?redirect_to=%2Fc%2Fabc123');
|
|
expect(fullUrl).not.toContain('/app/chat/app/chat/');
|
|
});
|
|
|
|
it('does not strip from /app/chatroom (segment boundary check)', () => {
|
|
const result = buildLoginRedirectUrl('/app/chatroom/page', '', '');
|
|
const redirectTo = decodeURIComponent(result.split('redirect_to=')[1]);
|
|
expect(redirectTo).toBe('/app/chatroom/page');
|
|
});
|
|
});
|