diff --git a/src/tests/backend/specs/admin/adminSettingsResolved.ts b/src/tests/backend/specs/admin/adminSettingsResolved.ts index 9d95f089f..a4e31e1b8 100644 --- a/src/tests/backend/specs/admin/adminSettingsResolved.ts +++ b/src/tests/backend/specs/admin/adminSettingsResolved.ts @@ -1,6 +1,7 @@ 'use strict'; import {strict as assert} from 'assert'; +import {vi} from 'vitest'; import setCookieParser from 'set-cookie-parser'; import * as fs from 'fs'; import * as os from 'os'; @@ -81,7 +82,7 @@ const ask = (socket: any, evt: string, payload: any, replyEvt: string) => socket.emit(evt, payload); }); -describe(__filename, function () { +describe(__filename, () => { let socket: any; let savedUsers: any; let savedRequireAuthentication: boolean; @@ -92,9 +93,10 @@ describe(__filename, function () { let savedSettingsFilename: any; let tmpSettingsPath: string | null = null; let setupCompleted = false; + let skipReason: string | null = null; - before(async function () { - this.timeout(60000); + before(async () => { + vi.setConfig({hookTimeout: 60000}); await common.init(); // The load handler bails with logger.error + early return if the @@ -125,13 +127,13 @@ describe(__filename, function () { console.warn( `[adminSettingsResolved] admin socket probe failed (${probe.reason}); ` + 'skipping suite — likely an authenticate-hook plugin rejecting test creds.'); - this.skip(); + skipReason = probe.reason; return; } socket = probe.socket; }); - after(function () { + after(() => { if (socket) socket.disconnect(); if (!setupCompleted) return; if (savedDbPwd === undefined) delete settings.dbSettings.password; @@ -148,7 +150,8 @@ describe(__filename, function () { settings.requireAuthentication = savedRequireAuthentication; }); - it('emits {results, resolved, flags}', async function () { + it('emits {results, resolved, flags}', async (ctx) => { + if (skipReason) return ctx.skip(); const reply: any = await ask(socket, 'load', null, 'settings'); assert.ok(reply, 'reply present'); assert.equal(typeof reply.results, 'string', 'raw file string'); @@ -156,19 +159,22 @@ describe(__filename, function () { assert.ok(reply.flags, 'flags present'); }); - it('resolved reflects live in-memory values, not the file on disk', async function () { + it('resolved reflects live in-memory values, not the file on disk', async (ctx) => { + if (skipReason) return ctx.skip(); const reply: any = await ask(socket, 'load', null, 'settings'); assert.equal(reply.resolved.trustProxy, true, 'resolved should show the in-memory trustProxy'); }); - it('resolved redacts secrets', async function () { + it('resolved redacts secrets', async (ctx) => { + if (skipReason) return ctx.skip(); const reply: any = await ask(socket, 'load', null, 'settings'); assert.equal(reply.resolved.dbSettings.password, '[REDACTED]'); assert.equal(reply.resolved.sessionKey, '[REDACTED]'); }); - it('resolved is omitted when showSettingsInAdminPage is false', async function () { + it('resolved is omitted when showSettingsInAdminPage is false', async (ctx) => { + if (skipReason) return ctx.skip(); settings.showSettingsInAdminPage = false; try { const reply: any = await ask(socket, 'load', null, 'settings'); diff --git a/src/tests/backend/specs/admin/adminSettingsSave.ts b/src/tests/backend/specs/admin/adminSettingsSave.ts index cab307352..4006e0dac 100644 --- a/src/tests/backend/specs/admin/adminSettingsSave.ts +++ b/src/tests/backend/specs/admin/adminSettingsSave.ts @@ -1,6 +1,7 @@ 'use strict'; import {strict as assert} from 'assert'; +import {vi} from 'vitest'; import setCookieParser from 'set-cookie-parser'; import * as fs from 'fs'; import * as os from 'os'; @@ -97,7 +98,7 @@ const save = (socket: any, payload: string) => // disk byte-for-byte at settings.settingsFilename, and the subsequent // `load` reply reflects the new file contents. We do NOT exercise // runtime reload — that's reloadSettings()' job and is covered elsewhere. -describe(__filename, function () { +describe(__filename, () => { let socket: any; let savedUsers: any; let savedRequireAuthentication: boolean; @@ -105,9 +106,10 @@ describe(__filename, function () { let tmpSettingsPath: string | null = null; let baselineContents: string; let setupCompleted = false; + let skipReason: string | null = null; - before(async function () { - this.timeout(60000); + before(async () => { + vi.setConfig({hookTimeout: 60000}); await common.init(); savedSettingsFilename = settings.settingsFilename; @@ -134,13 +136,13 @@ describe(__filename, function () { console.warn( `[adminSettingsSave] admin socket probe failed (${probe.reason}); ` + 'skipping suite — likely an authenticate-hook plugin rejecting test creds.'); - this.skip(); + skipReason = probe.reason; return; } socket = probe.socket; }); - after(function () { + after(() => { if (socket) socket.disconnect(); if (!setupCompleted) return; settings.settingsFilename = savedSettingsFilename; @@ -153,14 +155,15 @@ describe(__filename, function () { }); // Reset to baseline between tests so each it() is independent — earlier - // suites in the same mocha run can leave behind state via shared sockets. - beforeEach(function () { - if (!tmpSettingsPath) this.skip(); + // suites in the same run can leave behind state via shared sockets. + beforeEach((ctx) => { + if (!tmpSettingsPath) return ctx.skip(); fs.writeFileSync(tmpSettingsPath!, baselineContents); }); it('saveSettings writes the payload byte-for-byte to settings.settingsFilename', - async function () { + async (ctx) => { + if (skipReason) return ctx.skip(); const payload = JSON.stringify({title: 'EtherpadWrittenViaSocket'}, null, 2); const ack = await save(socket, payload); assert.equal(ack.status, 'saved', 'saveprogress should be "saved"'); @@ -173,7 +176,8 @@ describe(__filename, function () { // one new top-level block (a plugin config). The block must persist on // disk verbatim and reappear in the next `load` reply. it('augmenting existing JSON with a new top-level plugin block round-trips', - async function () { + async (ctx) => { + if (skipReason) return ctx.skip(); const augmented = JSON.stringify({ title: 'Etherpad', ip: '0.0.0.0', @@ -207,7 +211,8 @@ describe(__filename, function () { // path must not normalize or strip them — the SPA test // 'preserves /* */ comments after save round-trip' covers the UI side; // this one covers the socket-level guarantee. - it('preserves /* */ comments in the written file', async function () { + it('preserves /* */ comments in the written file', async (ctx) => { + if (skipReason) return ctx.skip(); const withComment = '/* persisted-marker-7819 */\n' + JSON.stringify({title: 'Etherpad'}, null, 2); diff --git a/src/tests/backend/specs/admin/padLoadFilter.ts b/src/tests/backend/specs/admin/padLoadFilter.ts index a53b75e32..1e16d7e6c 100644 --- a/src/tests/backend/specs/admin/padLoadFilter.ts +++ b/src/tests/backend/specs/admin/padLoadFilter.ts @@ -9,6 +9,7 @@ // offset/limit slice, so `total` reflects the filtered universe. import {strict as assert} from 'assert'; +import {vi} from 'vitest'; import setCookieParser from 'set-cookie-parser'; const io = require('socket.io-client'); @@ -86,19 +87,20 @@ const adminSocketWithProbe = async (budgetMs: number): Promise<{ return {ok: true, socket}; }; -describe(__filename, function () { +describe(__filename, () => { let socket: any; let savedUsers: any; let savedRequireAuthentication: boolean; let setupCompleted = false; + let skipReason: string | null = null; // Distinct per-suite tag so concurrent test runs / leftover pads from // earlier suites don't pollute the filter assertions. const tag = `padLoadFilter-${Date.now()}-${Math.floor(Math.random() * 1e6)}`; const emptyPadIds: string[] = []; const editedPadIds: string[] = []; - before(async function () { - this.timeout(120000); + before(async () => { + vi.setConfig({hookTimeout: 120000}); await common.init(); savedUsers = settings.users; @@ -111,7 +113,7 @@ describe(__filename, function () { `[padLoadFilter] admin socket probe failed (${probe.reason}); ` + "skipping suite — likely an authenticate-hook plugin rejecting the test's " + 'admin credentials.'); - this.skip(); + skipReason = probe.reason; return; } socket = probe.socket; @@ -132,7 +134,7 @@ describe(__filename, function () { } }); - after(async function () { + after(async () => { if (socket) socket.disconnect(); if (!setupCompleted) return; // `savedUsers` may point at the same object that adminSocket mutated, @@ -150,7 +152,8 @@ describe(__filename, function () { } }); - it('filter:"empty" returns only revisionNumber===0 pads from the full set', async function () { + it('filter:"empty" returns only revisionNumber===0 pads from the full set', async (ctx) => { + if (skipReason) return ctx.skip(); const res = await ask(socket, 'padLoad', { pattern: tag, offset: 0, limit: 12, sortBy: 'padName', ascending: true, filter: 'empty', @@ -162,7 +165,8 @@ describe(__filename, function () { } }); - it('filter:"empty" with limit=2 still reports the correct total (regression: thm)', async function () { + it('filter:"empty" with limit=2 still reports the correct total (regression: thm)', async (ctx) => { + if (skipReason) return ctx.skip(); // The bug thm hit: clicking "empty" showed at most `limit` empties // because filtering happened on the client AFTER pagination. The // server now applies filter first, so total reflects the filtered @@ -175,7 +179,8 @@ describe(__filename, function () { assert.equal(res.results.length, 2, `expected limit=2 page, got ${res.results.length} rows`); }); - it('filter omitted (older client) falls back to "all"', async function () { + it('filter omitted (older client) falls back to "all"', async (ctx) => { + if (skipReason) return ctx.skip(); const res = await ask(socket, 'padLoad', { pattern: tag, offset: 0, limit: 12, sortBy: 'padName', ascending: true, @@ -184,7 +189,8 @@ describe(__filename, function () { `expected total=8 (5 empty + 3 edited), got ${JSON.stringify(res)}`); }); - it('filter:"all" matches the no-filter behaviour', async function () { + it('filter:"all" matches the no-filter behaviour', async (ctx) => { + if (skipReason) return ctx.skip(); const res = await ask(socket, 'padLoad', { pattern: tag, offset: 0, limit: 12, sortBy: 'padName', ascending: true, filter: 'all', @@ -192,7 +198,8 @@ describe(__filename, function () { assert.equal(res.total, 8); }); - it('filter:"active" excludes pads with no active users', async function () { + it('filter:"active" excludes pads with no active users', async (ctx) => { + if (skipReason) return ctx.skip(); // No connected clients in this test, so every test pad has // userCount === 0 → filter:"active" must return zero. const res = await ask(socket, 'padLoad', {