LibreChat/api/server/services/Files/Code/preflight.spec.js
Danny Avila 30cd1c2f2e
Some checks are pending
Backend Unit Tests / Build packages (push) Waiting to run
Backend Unit Tests / Codegraph select (push) Waiting to run
Backend Unit Tests / TypeScript type checks (push) Blocked by required conditions
Backend Unit Tests / Circular dependency checks (push) Waiting to run
Backend Unit Tests / Tests: api (shard 1/3) (push) Blocked by required conditions
Backend Unit Tests / Tests: api (shard 2/3) (push) Blocked by required conditions
Backend Unit Tests / Tests: api (shard 3/3) (push) Blocked by required conditions
Backend Unit Tests / Tests: data-provider (push) Blocked by required conditions
Backend Unit Tests / Tests: data-schemas (push) Blocked by required conditions
Backend Unit Tests / Tests: @librechat/api (shard 1/4) (push) Blocked by required conditions
Backend Unit Tests / Tests: @librechat/api (shard 2/4) (push) Blocked by required conditions
Backend Unit Tests / Tests: @librechat/api (shard 3/4) (push) Blocked by required conditions
Backend Unit Tests / Tests: @librechat/api (shard 4/4) (push) Blocked by required conditions
Codegraph E2E Votes / vote (full suite) (push) Waiting to run
Docker Dev Branch Images Build / publish (push) Waiting to run
Frontend Unit Tests / Codegraph select (push) Waiting to run
Frontend Unit Tests / Build packages (push) Waiting to run
Frontend Unit Tests / TypeScript type checks (client) (push) Blocked by required conditions
Frontend Unit Tests / Tests: @librechat/client (push) Blocked by required conditions
Frontend Unit Tests / Tests: Ubuntu (shard 1/2) (push) Blocked by required conditions
Frontend Unit Tests / Tests: Ubuntu (shard 2/2) (push) Blocked by required conditions
Frontend Unit Tests / Vite build verification (push) Blocked by required conditions
🔇 fix: Stop logging an absent create_file target as an error (#15591)
`create_file` reads its target before writing so it can tell a create from
an overwrite, so `readSandboxFile` missing the path is the ordinary case for
a create — yet it was reported at error level, through `logAxiosError`, which
rendered the sandbox's own `cat: …: No such file or directory` as "An error
occurred while setting up the request" plus a stack. Every ordinary file
creation emitted an error-level entry claiming a transport fault about a file
that was written half a second later, and an operator reading it concluded the
file had been lost.

Split the two failures `readSandboxFile` was conflating. A request that never
completed still goes to `logAxiosError`; a request the sandbox answered with
stderr is now classified on that stderr — an absent path logs at debug, and
anything else (permission, a broken interpreter) keeps error level with the
sandbox's own wording. The thrown error is unchanged, so the authoring flow's
create/overwrite decision still reads it the same way.

The classifier moves to `packages/api/src/files/code/errors.ts` and is now
shared: `handlers.ts` had two near-duplicate copies of it, the narrower of
which documented itself as narrower than the broader one without either
deriving from the other.

Also carry the cause on `[preflightCodeOutputBatch] … could not be inspected`.
That warning follows each `create_file` in the same request, and the batch
discarded the error entirely — so a Code API that refused the download, one
routed to the wrong execution profile, and a file too large to inspect all
arrived as the same sentence, and neither this warning nor the one above could
be told apart from the failures around them. It goes through
`getSafeErrorMetadata`, which is what keeps the upstream message and the
artifact's name — both of which can echo submitted content — out of the log.
2026-09-04 09:21:24 -04:00

101 lines
3.5 KiB
JavaScript

const mockRunCodeOutputBatchPreflight = jest.fn();
const mockPrepareCodeOutputForInspection = jest.fn();
const mockGetSafeErrorMetadata = jest.fn(() => ({ type: 'Error', status: 404 }));
jest.mock('@librechat/data-schemas', () => ({
logger: { warn: jest.fn() },
}));
jest.mock('@librechat/api', () => ({
preflightCodeOutputBatch: (...args) => mockRunCodeOutputBatchPreflight(...args),
getSafeErrorMetadata: (...args) => mockGetSafeErrorMetadata(...args),
}));
jest.mock('librechat-data-provider', () => ({
EModelEndpoint: { agents: 'agents' },
mergeFileConfig: jest.fn(() => ({ serverFileSizeLimit: 32 })),
getEndpointFileConfig: jest.fn(() => ({
fileLimit: 4,
fileSizeLimit: 8,
totalSizeLimit: 16,
})),
}));
jest.mock('./process', () => ({
prepareCodeOutputForInspection: (...args) => mockPrepareCodeOutputForInspection(...args),
}));
const { logger } = require('@librechat/data-schemas');
const { preflightCodeOutputBatch } = require('./preflight');
describe('preflightCodeOutputBatch Code API routing', () => {
beforeEach(() => {
jest.clearAllMocks();
mockPrepareCodeOutputForInspection.mockResolvedValue({
buffer: Buffer.from('safe'),
file: { name: 'output.txt', content: 'safe' },
});
mockRunCodeOutputBatchPreflight.mockImplementation(async ({ prepare }) => {
await prepare({
file: { id: 'file-1', name: 'output.txt' },
sessionId: 'session-1',
maxBytes: 8,
inspectContent: true,
});
return [];
});
});
it('forwards the trusted per-agent Code API route to inspection downloads', async () => {
const req = { config: {}, user: { id: 'user-1' } };
await preflightCodeOutputBatch({
req,
artifact: { files: [{ id: 'file-1', name: 'output.txt' }] },
codeExecutionContext: {
baseUrl: 'https://code-stateful.example.com',
executionProfile: 'stateful',
executionRouteKey: 'stateful:route',
},
});
expect(mockPrepareCodeOutputForInspection).toHaveBeenCalledWith({
req,
id: 'file-1',
name: 'output.txt',
session_id: 'session-1',
maxBytes: 8,
inspectContent: true,
codeApiBaseUrl: 'https://code-stateful.example.com',
executionProfile: 'stateful',
executionRouteKey: 'stateful:route',
});
});
/* The batch degrades an uninspectable artifact to the download fallback and
* the turn still succeeds, so this warning is the only trace it leaves.
* Without the cause, a refused download, a misrouted execution profile and
* an oversized file are one indistinguishable sentence. */
it('reports why an artifact could not be inspected, through the safe-metadata filter', async () => {
const cause = Object.assign(new Error('Request failed with status code 404'), {
response: { status: 404 },
});
mockRunCodeOutputBatchPreflight.mockImplementation(async ({ onInspectionUnavailable }) => {
onInspectionUnavailable(0, cause);
return [];
});
await preflightCodeOutputBatch({
req: { config: {}, user: { id: 'user-1' } },
artifact: { files: [{ id: 'file-1', name: 'PRIVATE-name.txt' }] },
});
expect(mockGetSafeErrorMetadata).toHaveBeenCalledWith(cause);
expect(logger.warn).toHaveBeenCalledWith(
'[preflightCodeOutputBatch] Generated artifact 1 could not be inspected',
{ type: 'Error', status: 404 },
);
/* The artifact's name is submitted content and must never ride the log. */
expect(JSON.stringify(logger.warn.mock.calls)).not.toContain('PRIVATE-');
});
});