From 5b11a5a0760ed8076e12fd29cc0074bed546b3e7 Mon Sep 17 00:00:00 2001 From: JorgeCosta87 Date: Fri, 15 May 2026 19:51:53 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=AA=B5=20chore:=20Restore=20Winston=20For?= =?UTF-8?q?mat=20Factory=20Shape=20In=20Test=20Mocks=20(#13139)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four jest mocks for `winston` in the test suite return the wrong shape: api/test/__mocks__/logger.js (returns inner fn directly) packages/api/src/agents/__tests__/memory.test.ts (`format` is a plain object) packages/api/src/agents/__tests__/run-summarization.test.ts (same) packages/api/src/agents/__tests__/initialize.test.ts (same) Real `winston.format(fn)` returns a Format constructor whose instances expose a `.transform(info, opts)` method that winston's pipeline calls with the log info object. The current mocks collapse this: - `(fn) => fn` returns the inner transform fn directly. When module-load code in `@librechat/data-schemas/dist/config/parsers.cjs:52` does `const redactFormat = winston.format((info) => ...)`, `redactFormat` becomes the inner fn. The next line in `winston.cjs` calls `parsers.redactFormat()` which invokes the inner fn with no `info`, throwing `TypeError: Cannot read properties of undefined (reading 'level')`. - `format: { combine, colorize, simple }` makes `winston.format` not callable at all — `winston.format((info) => ...)` throws `TypeError: winston.format is not a function`. These currently pass in CI on GitHub Actions Ubuntu / Node 20.19, but fail reproducibly on Node 24.x and on some Linux distros (verified on WSL Ubuntu with Node 24.9.0). The CI passes appears to be environmental luck around jest's mock-hoisting interaction with the workspace symlink chain — the mocks are genuinely wrong against the data-schemas contract. The fix: return a thunk that yields `{ transform: fn }` — matches real winston's shape just enough that module-load completes; the inner fn is only ever invoked by winston's pipeline (never at load time). Also adds the full `winston.format.*` method surface (printf, timestamp, errors, splat, json) plus `addColors` and the `DailyRotateFile`/`File` transports that data-schemas's dist code references at module-load. Verification (Node 24.9.0): npm run build:data-provider && npm run build:data-schemas && npm run build:api cd packages/api && npx jest src/agents/__tests__/{memory,run-summarization,initialize}.test.ts → 3 suites, 106 tests, all pass No production code or behavior changes — test-only patch. Co-authored-by: Jorge Costa <8352477+JorgeCosta87@users.noreply.github.com> --- api/test/__mocks__/logger.js | 10 ++++++- .../src/agents/__tests__/initialize.test.ts | 26 ++++++++++++++----- .../api/src/agents/__tests__/memory.test.ts | 26 +++++++++++++++---- .../__tests__/run-summarization.test.ts | 25 +++++++++++++++--- 4 files changed, 72 insertions(+), 15 deletions(-) diff --git a/api/test/__mocks__/logger.js b/api/test/__mocks__/logger.js index 62f9bee93a..94dd08bb1c 100644 --- a/api/test/__mocks__/logger.js +++ b/api/test/__mocks__/logger.js @@ -1,5 +1,13 @@ jest.mock('winston', () => { - const mockFormatFunction = jest.fn((fn) => fn); + // Real `winston.format(fn)` returns a Format constructor whose instances + // expose a `.transform(info, opts)` method that winston's pipeline calls. + // The previous mock `(fn) => fn` collapsed this — `parsers.redactFormat()` + // (called at @librechat/data-schemas dist module-load) ended up invoking + // the inner transform fn with no `info` argument, throwing on `info.level`. + // Returning a thunk that yields `{ transform: fn }` matches real winston's + // shape just enough that module-load completes cleanly; the inner fn is + // only ever invoked by winston's pipeline (never at load time). + const mockFormatFunction = jest.fn((fn) => () => ({ transform: fn })); mockFormatFunction.colorize = jest.fn(); mockFormatFunction.combine = jest.fn(); diff --git a/packages/api/src/agents/__tests__/initialize.test.ts b/packages/api/src/agents/__tests__/initialize.test.ts index b98af340aa..6636c13af6 100644 --- a/packages/api/src/agents/__tests__/initialize.test.ts +++ b/packages/api/src/agents/__tests__/initialize.test.ts @@ -39,20 +39,34 @@ import type { ServerRequest, InitializeResultBase, EndpointTokenConfig } from '~ import type { InitializeAgentDbMethods } from '../initialize'; import { DEFAULT_MAX_CONTEXT_TOKENS } from '../initialize'; -// Mock logger +// Mock logger — `format` must be a callable factory so @librechat/data-schemas +// dist module-load completes cleanly; see api/test/__mocks__/logger.js. jest.mock('winston', () => ({ createLogger: jest.fn(() => ({ debug: jest.fn(), warn: jest.fn(), error: jest.fn(), + info: jest.fn(), })), - format: { - combine: jest.fn(), - colorize: jest.fn(), - simple: jest.fn(), - }, + format: Object.assign( + jest.fn((fn) => () => ({ transform: fn })), + { + combine: jest.fn(), + colorize: jest.fn(), + simple: jest.fn(), + label: jest.fn(), + timestamp: jest.fn(), + printf: jest.fn(), + errors: jest.fn(), + splat: jest.fn(), + json: jest.fn(), + }, + ), + addColors: jest.fn(), transports: { Console: jest.fn(), + DailyRotateFile: jest.fn(), + File: jest.fn(), }, })); diff --git a/packages/api/src/agents/__tests__/memory.test.ts b/packages/api/src/agents/__tests__/memory.test.ts index dabe6de629..d23a07aa7d 100644 --- a/packages/api/src/agents/__tests__/memory.test.ts +++ b/packages/api/src/agents/__tests__/memory.test.ts @@ -5,19 +5,35 @@ import type { MemoryArtifact } from 'librechat-data-provider'; import { createMemoryTool, processMemory } from '../memory'; // Mock the logger +// `winston.format` must be a callable factory (real winston returns a Format +// constructor) so that `@librechat/data-schemas` dist code can complete its +// module-load — see api/test/__mocks__/logger.js for the canonical shape. jest.mock('winston', () => ({ createLogger: jest.fn(() => ({ debug: jest.fn(), warn: jest.fn(), error: jest.fn(), + info: jest.fn(), })), - format: { - combine: jest.fn(), - colorize: jest.fn(), - simple: jest.fn(), - }, + format: Object.assign( + jest.fn((fn) => () => ({ transform: fn })), + { + combine: jest.fn(), + colorize: jest.fn(), + simple: jest.fn(), + label: jest.fn(), + timestamp: jest.fn(), + printf: jest.fn(), + errors: jest.fn(), + splat: jest.fn(), + json: jest.fn(), + }, + ), + addColors: jest.fn(), transports: { Console: jest.fn(), + DailyRotateFile: jest.fn(), + File: jest.fn(), }, })); diff --git a/packages/api/src/agents/__tests__/run-summarization.test.ts b/packages/api/src/agents/__tests__/run-summarization.test.ts index 1aa8e0d58d..7d22511c21 100644 --- a/packages/api/src/agents/__tests__/run-summarization.test.ts +++ b/packages/api/src/agents/__tests__/run-summarization.test.ts @@ -9,7 +9,8 @@ import { } from 'librechat-data-provider'; import { createRun } from '~/agents/run'; -// Mock winston logger +// Mock winston logger — `format` must be callable so @librechat/data-schemas +// dist module-load completes cleanly; see api/test/__mocks__/logger.js. jest.mock('winston', () => ({ createLogger: jest.fn(() => ({ debug: jest.fn(), @@ -17,8 +18,26 @@ jest.mock('winston', () => ({ error: jest.fn(), info: jest.fn(), })), - format: { combine: jest.fn(), colorize: jest.fn(), simple: jest.fn() }, - transports: { Console: jest.fn() }, + format: Object.assign( + jest.fn((fn) => () => ({ transform: fn })), + { + combine: jest.fn(), + colorize: jest.fn(), + simple: jest.fn(), + label: jest.fn(), + timestamp: jest.fn(), + printf: jest.fn(), + errors: jest.fn(), + splat: jest.fn(), + json: jest.fn(), + }, + ), + addColors: jest.fn(), + transports: { + Console: jest.fn(), + DailyRotateFile: jest.fn(), + File: jest.fn(), + }, })); // Mock env utilities so header resolution doesn't fail