mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-06-30 03:12:11 +00: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
GitNexus Index / index (push) Waiting to run
GitNexus Index / post-index (push) Blocked by required conditions
* 🛡️ fix: Bound object-traverse against DAG fan-out and shared refs Detect cycles via the ancestor chain (so shared, non-circular references in sibling branches / DAGs are traversed correctly) and add defensive maxNodes (100k) / maxDepth (100) caps. The removed global visited set was implicitly bounding work at O(distinct nodes); ancestor-chain-only detection is O(root-to-node paths), exponential on DAGs (a depth-24 diamond went from 26 to 50M visits / 1.6s of synchronous work). The caps bound it to ~9ms while leaving normal traversal untouched. Adds a spec covering shared refs, cycles, DAGs, and both bounds. The lone consumer, debugTraverse, inherits the defaults with no change. * 🪵 refactor: Remove legacy api/config logger duplicate The api/config winston logger was a stale parallel implementation of the canonical @librechat/data-schemas logger, with unbounded redaction (regex-only redactFormat, npm traverse-based debugTraverse). Its winston instance and the logger export from api/config/index.js had zero consumers — every ~/config importer uses the MCP/flow-manager exports. The only live tie was ToolService's use of redactMessage. Re-export redactMessage from @librechat/data-schemas (behaviorally identical, a superset of the regex set), point ToolService at it, delete api/config/winston.js and api/config/parsers.js, drop the dead logger export, and remove the orphaned ~/config/parsers mock from the global test setup. * 🧹 chore: Drop orphaned traverse dep and stale legacy logger tests Deleting api/config/{winston,parsers}.js left the npm 'traverse' package unused in api/package.json (flagged by the detect-unused-packages CI check) and orphaned two tests that imported the deleted modules. Remove the traverse dependency (sync package-lock), and delete api/config/__tests__/{parsers,logToFile}.spec.js — the canonical logger's behavior is covered by packages/data-schemas/src/config/parsers.spec.ts. * 🩹 fix: Make object-traverse caps bound work and survive update() Address Codex review: (1) break the child loops as soon as the node budget is spent and iterate objects via for...in instead of materializing Object.entries/Object.keys, so maxNodes actually bounds work for wide arrays/objects; (2) detect ancestor cycles against an immutable original-node stack rather than context.node, which a callback's update() can reassign (the debug formatter rewrites array nodes in place). Adds tests for the wide-array bound and the update()-cycle case. * 🎚️ fix: Tighten object-traverse defaults to a ~1ms log budget Lower maxNodes 100000 -> 2500 and maxDepth 100 -> 5. Measured cost is ~140ns/node with the debug formatter callback, so 2500 nodes keeps a single log under ~1ms even on slower prod hardware; real log objects are ~25-30 nodes at depth 3-4, leaving ample headroom. maxNodes is the fan-out/cost lever; maxDepth bounds recursion and output readability (depth-5 covers typical logs, deeper renders compactly).
60 lines
1.8 KiB
JavaScript
60 lines
1.8 KiB
JavaScript
jest.mock('winston', () => {
|
|
// 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();
|
|
mockFormatFunction.label = jest.fn();
|
|
mockFormatFunction.timestamp = jest.fn();
|
|
mockFormatFunction.printf = jest.fn();
|
|
mockFormatFunction.errors = jest.fn();
|
|
mockFormatFunction.splat = jest.fn();
|
|
mockFormatFunction.json = jest.fn();
|
|
return {
|
|
format: mockFormatFunction,
|
|
createLogger: jest.fn().mockReturnValue({
|
|
info: jest.fn(),
|
|
warn: jest.fn(),
|
|
debug: jest.fn(),
|
|
error: jest.fn(),
|
|
}),
|
|
transports: {
|
|
Console: jest.fn(),
|
|
DailyRotateFile: jest.fn(),
|
|
File: jest.fn(),
|
|
},
|
|
addColors: jest.fn(),
|
|
};
|
|
});
|
|
|
|
jest.mock('winston-daily-rotate-file', () => {
|
|
return jest.fn().mockImplementation(() => {
|
|
return {
|
|
level: 'error',
|
|
filename: '../logs/error-%DATE%.log',
|
|
datePattern: 'YYYY-MM-DD',
|
|
zippedArchive: true,
|
|
maxSize: '20m',
|
|
maxFiles: '14d',
|
|
format: 'format',
|
|
};
|
|
});
|
|
});
|
|
|
|
jest.mock('~/config', () => {
|
|
return {
|
|
logger: {
|
|
info: jest.fn(),
|
|
warn: jest.fn(),
|
|
debug: jest.fn(),
|
|
error: jest.fn(),
|
|
},
|
|
};
|
|
});
|