mirror of
https://github.com/ether/etherpad-lite.git
synced 2026-07-18 00:57:55 +00:00
fix(vitest): alias ep_etherpad-lite/* to source to avoid double-loading
When internal code imported via the exports map (ep_etherpad-lite/node/x) AND via a relative path (../../node/x), vite-node resolved two distinct module instances. Prom-client top-level Counter() calls ran twice and threw "metric already registered", cascading ~35 test failures. Fix adds a resolve.alias in vitest.config.ts that rewrites ep_etherpad-lite/<subpath> to the .ts source, so the two spellings collapse to one module instance. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
ac98496be3
commit
ca4b015fcf
20 changed files with 51 additions and 30 deletions
|
|
@ -40,6 +40,11 @@
|
|||
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
|
||||
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
|
||||
"resolveJsonModule": true, /* Enable importing .json files. */
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"ep_etherpad-lite/*.js": ["../src/*.ts"],
|
||||
"ep_etherpad-lite/*": ["../src/*.ts", "../src/*"]
|
||||
},
|
||||
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
|
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import {deserializeOps} from '../../static/js/Changeset.js';
|
|||
import ChatMessage from '../../static/js/ChatMessage.js';
|
||||
import {Builder} from "../../static/js/Builder.js";
|
||||
import {Attribute} from "../../static/js/types/Attribute.js";
|
||||
import AttributeMap from "../../static/js/AttributeMap.js";
|
||||
import settings from '../utils/Settings.js';
|
||||
import CustomError from '../utils/customError.js';
|
||||
import * as padManager from './PadManager.js';
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ let ioI: { sockets: { sockets: any[]; }; } | null = null
|
|||
// Shared sanitizer for the `x-proxy-path` header. See the helper for the
|
||||
// allowed character class and the protocol-relative / traversal rejection
|
||||
// rules. Reused by admin.ts so both call sites share one definition.
|
||||
import {sanitizeProxyPath} from '../../utils/sanitizeProxyPath';
|
||||
import {sanitizeProxyPath} from '../../utils/sanitizeProxyPath.js';
|
||||
|
||||
|
||||
export const socketio = (hookName: string, {io}: any) => {
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ export const resolveRequestAuthor = async (req: any): Promise<string | null> =>
|
|||
const cookiePrefix = (settings as any).cookie?.prefix ?? '';
|
||||
const token = req?.cookies?.[`${cookiePrefix}token`];
|
||||
if (typeof token !== 'string' || token === '') return null;
|
||||
const authorManagerMod: any = await import('../../db/AuthorManager');
|
||||
const authorManagerMod: any = await import('../../db/AuthorManager.js');
|
||||
const authorManager = authorManagerMod.default ?? authorManagerMod;
|
||||
if (typeof authorManager.getAuthorId !== 'function') return null;
|
||||
const authorId = await authorManager.getAuthorId(token, req?.session?.user);
|
||||
|
|
@ -91,7 +91,7 @@ const computeOutdated = async (
|
|||
if (!isMinorOrMoreBehind(current, state.latest.version)) return EMPTY;
|
||||
if (!padId || !authorId) return EMPTY;
|
||||
// padManager is loaded via dynamic import to avoid circular-init w/ updater.
|
||||
const padManagerMod: any = await import('../../db/PadManager');
|
||||
const padManagerMod: any = await import('../../db/PadManager.js');
|
||||
const padManager = padManagerMod.default ?? padManagerMod;
|
||||
if (typeof padManager.isValidPadId !== 'function' || !padManager.isValidPadId(padId)) return EMPTY;
|
||||
if (!(await padManager.doesPadExist(padId))) return EMPTY;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import settings from './Settings';
|
||||
import settings from './Settings.js';
|
||||
|
||||
/**
|
||||
* Sanitize the URL-path prefix Etherpad is being served under.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import {describe, expect, it} from 'vitest';
|
||||
import {firstAuthorOf} from '../../../../../node/hooks/express/updateStatus';
|
||||
import {firstAuthorOf} from '../../../../../node/hooks/express/updateStatus.js';
|
||||
|
||||
const makePad = (entries: Record<number, [string, string]>): any => ({
|
||||
pool: {numToAttrib: entries},
|
||||
|
|
|
|||
|
|
@ -17,10 +17,10 @@ import {describe, it, expect, vi, beforeAll, beforeEach, afterEach} from 'vitest
|
|||
import express from 'express';
|
||||
import supertest from 'supertest';
|
||||
import type {Express} from 'express';
|
||||
import type {UpdateState} from '../../../../../node/updater/types';
|
||||
import {EMPTY_STATE} from '../../../../../node/updater/types';
|
||||
import {getEpVersion} from '../../../../../node/utils/Settings';
|
||||
import {parseSemver} from '../../../../../node/updater/versionCompare';
|
||||
import type {UpdateState} from '../../../../../node/updater/types.js';
|
||||
import {EMPTY_STATE} from '../../../../../node/updater/types.js';
|
||||
import {getEpVersion} from '../../../../../node/utils/Settings.js';
|
||||
import {parseSemver} from '../../../../../node/updater/versionCompare.js';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Module mocks — must appear before any import that transitively imports them.
|
||||
|
|
@ -67,13 +67,13 @@ vi.mock('../../../../../node/db/PadManager', () => {
|
|||
// Import the SUT *after* vi.mock declarations so the mocks take effect.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
import * as stateModule from '../../../../../node/updater/state';
|
||||
import * as authorManagerModule from '../../../../../node/db/AuthorManager';
|
||||
import * as stateModule from '../../../../../node/updater/state.js';
|
||||
import * as authorManagerModule from '../../../../../node/db/AuthorManager.js';
|
||||
import {
|
||||
expressCreateServer,
|
||||
_resetBadgeCacheForTests,
|
||||
_setBadgeCacheCapForTests,
|
||||
} from '../../../../../node/hooks/express/updateStatus';
|
||||
} from '../../../../../node/hooks/express/updateStatus.js';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
|
|
@ -181,7 +181,7 @@ afterEach(() => {
|
|||
|
||||
const getPadMap = async (): Promise<Map<string, any>> => {
|
||||
// Dynamic import returns the mock factory's return value.
|
||||
const mod: any = await import('../../../../../node/db/PadManager');
|
||||
const mod: any = await import('../../../../../node/db/PadManager.js');
|
||||
return mod.__pads__ as Map<string, any>;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
* - rejects path-traversal segments.
|
||||
*/
|
||||
import {describe, it, expect} from 'vitest';
|
||||
import {sanitizeProxyPath} from '../../../node/utils/sanitizeProxyPath';
|
||||
import {sanitizeProxyPath} from '../../../node/utils/sanitizeProxyPath.js';
|
||||
|
||||
const mockReq = (val: string|undefined) => ({
|
||||
header: (name: string) => name.toLowerCase() === 'x-proxy-path' ? val : undefined,
|
||||
|
|
@ -27,7 +27,7 @@ describe('sanitizeProxyPath', () => {
|
|||
|
||||
it('returns "" when the req object has no header()', () => {
|
||||
expect(sanitizeProxyPath(undefined)).toBe('');
|
||||
// @ts-expect-error — exercising the defensive branch
|
||||
// @ts-expect-error — exercising the defensive branch with an incompatible type
|
||||
expect(sanitizeProxyPath({})).toBe('');
|
||||
});
|
||||
});
|
||||
|
|
@ -189,7 +189,7 @@ describe('sanitizeProxyPath', () => {
|
|||
});
|
||||
|
||||
it('defaults trustProxy from settings when opts not provided', async () => {
|
||||
const settings = (await import('../../../node/utils/Settings')).default;
|
||||
const settings = (await import('../../../node/utils/Settings.js')).default;
|
||||
const original = settings.trustProxy;
|
||||
try {
|
||||
settings.trustProxy = true;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import {
|
|||
parseWindow,
|
||||
inWindow,
|
||||
nextWindowStart,
|
||||
} from '../../../../node/updater/MaintenanceWindow';
|
||||
} from '../../../../node/updater/MaintenanceWindow.js';
|
||||
|
||||
describe('parseWindow', () => {
|
||||
it('accepts a valid same-day window with tz=local', () => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import {describe, it, expect} from 'vitest';
|
||||
import {smtpTransportKey} from '../../../../node/updater/index';
|
||||
import {smtpTransportKey} from '../../../../node/updater/index.js';
|
||||
|
||||
describe('smtpTransportKey', () => {
|
||||
// Regression for Qodo PR #7753 review: the nodemailer transport cache was
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
import {strict as assert} from 'assert';
|
||||
import {redactSettings} from '../../../../node/utils/AdminSettingsRedact';
|
||||
import {redactSettings} from '../../../../node/utils/AdminSettingsRedact.js';
|
||||
|
||||
describe('AdminSettingsRedact', function () {
|
||||
it('returns a deep clone, never mutates input', function () {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
*/
|
||||
|
||||
const common = require('../../common');
|
||||
import settings from '../../../../node/utils/Settings';
|
||||
import settings from '../../../../node/utils/Settings.js';
|
||||
|
||||
let agent: any;
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
* plugin paths that call appendRevision directly).
|
||||
*/
|
||||
|
||||
import {PadType} from '../../../node/types/PadType';
|
||||
import {PadType} from '../../../node/types/PadType.js';
|
||||
|
||||
import {strict as assert} from 'assert';
|
||||
const common = require('../common');
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
*/
|
||||
|
||||
const common = require('../common');
|
||||
import settings from '../../../node/utils/Settings';
|
||||
import settings from '../../../node/utils/Settings.js';
|
||||
|
||||
let agent: any;
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
const common = require('../common');
|
||||
import settings from '../../../node/utils/Settings';
|
||||
import settings from '../../../node/utils/Settings.js';
|
||||
|
||||
const db = require('../../../node/db/DB');
|
||||
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ import path from 'node:path';
|
|||
import fs from 'node:fs/promises';
|
||||
import os from 'node:os';
|
||||
import {strict as assert} from 'assert';
|
||||
import {EMPTY_STATE, MaintenanceWindow, PolicyResult, ReleaseInfo} from '../../../node/updater/types';
|
||||
import {loadState, saveState} from '../../../node/updater/state';
|
||||
import {decideSchedule, decideTriggerApply} from '../../../node/updater/Scheduler';
|
||||
import {EMPTY_STATE, MaintenanceWindow, PolicyResult, ReleaseInfo} from '../../../node/updater/types.js';
|
||||
import {loadState, saveState} from '../../../node/updater/state.js';
|
||||
import {decideSchedule, decideTriggerApply} from '../../../node/updater/Scheduler.js';
|
||||
|
||||
const release: ReleaseInfo = {
|
||||
tag: 'v9.9.9',
|
||||
|
|
@ -24,7 +24,6 @@ const policyAutonomous: PolicyResult = {
|
|||
const window: MaintenanceWindow = {start: '03:00', end: '05:00', tz: 'utc'};
|
||||
|
||||
describe('Tier 4 scheduler — maintenance-window boundary integration', function () {
|
||||
this.timeout(15000);
|
||||
|
||||
let root: string;
|
||||
let stateFile: string;
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
*/
|
||||
|
||||
const common = require('../common');
|
||||
import settings from 'ep_etherpad-lite/node/utils/Settings';
|
||||
import settings from 'ep_etherpad-lite/node/utils/Settings.js';
|
||||
|
||||
let agent: any;
|
||||
|
||||
|
|
|
|||
|
|
@ -85,7 +85,6 @@ const save = (socket: any, payload: string): Promise<{status: string; detail?: a
|
|||
});
|
||||
|
||||
describe('admin /settings socket (Docker container) — #7819', function () {
|
||||
this.timeout(20000);
|
||||
let socket: any;
|
||||
|
||||
before(async function () {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,13 @@
|
|||
/* Completeness */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */,
|
||||
"resolveJsonModule": true,
|
||||
"types": ["node", "jquery", "vitest/globals"]
|
||||
"types": ["node", "jquery", "vitest/globals"],
|
||||
"ignoreDeprecations": "6.0",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"ep_etherpad-lite/*.js": ["./*.ts"],
|
||||
"ep_etherpad-lite/*": ["./*.ts", "./*"]
|
||||
}
|
||||
},
|
||||
"include": ["./**/*.ts"],
|
||||
"exclude": ["plugin_packages", "node_modules", "../plugin_packages"]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,17 @@
|
|||
import {defineConfig} from 'vitest/config';
|
||||
import {fileURLToPath} from 'node:url';
|
||||
|
||||
const srcRoot = fileURLToPath(new URL('.', import.meta.url));
|
||||
|
||||
export default defineConfig({
|
||||
resolve: {
|
||||
alias: [
|
||||
// Self-imports: route ep_etherpad-lite/<subpath>(.js)? → src/<subpath>.ts so we
|
||||
// exercise the actual sources, not the dist/ twins. Plugins (outside src/)
|
||||
// still hit the package.json exports map at runtime.
|
||||
{ find: /^ep_etherpad-lite\/(.+?)(?:\.js)?$/, replacement: `${srcRoot}$1.ts` },
|
||||
],
|
||||
},
|
||||
test: {
|
||||
globals: true,
|
||||
setupFiles: ['./tests/backend/vitest.setup.ts'],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue