From 26789994bcd1755cfe914410760c676177249032 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 20 Jan 2026 01:38:19 +0000 Subject: [PATCH] Refactor uploadFileAD5X to use async file operations - Replaced synchronous `fs.existsSync` and `fs.statSync` with `fs.promises.stat` in `uploadFileAD5X` to prevent blocking the event loop. - Added unit tests for `uploadFileAD5X` in `JobControl.test.ts`. - Verified non-blocking behavior (benchmark showed blocking behavior in sync version vs potential concurrency in async, though single-op wall time is higher due to promise overhead). --- src/api/controls/JobControl.test.ts | 53 +++++++++++++++++++++++++++++ src/api/controls/JobControl.ts | 16 +++++---- 2 files changed, 62 insertions(+), 7 deletions(-) diff --git a/src/api/controls/JobControl.test.ts b/src/api/controls/JobControl.test.ts index f15844f..64a6177 100644 --- a/src/api/controls/JobControl.test.ts +++ b/src/api/controls/JobControl.test.ts @@ -436,4 +436,57 @@ describe('JobControl', () => { expect(result).toBe(false); }); }); + + describe('uploadFileAD5X', () => { + beforeEach(() => { + mockFiveMClient.isAD5X = true; + }); + + const validParams = { + filePath: 'test.gcode', + startPrint: false, + levelingBeforePrint: false, + flowCalibration: false, + firstLayerInspection: false, + timeLapseVideo: false, + materialMappings: [{ + toolId: 0, + slotId: 1, + materialName: 'PLA', + toolMaterialColor: '#FF0000', + slotMaterialColor: '#FF0000' + } as AD5XMaterialMapping] + }; + + it('should upload file successfully on AD5X', async () => { + const fs = require('fs'); + const fsStatSpy = jest.spyOn(fs.promises, 'stat').mockResolvedValue({ size: 1024 } as any); + const fsCreateReadStreamSpy = jest.spyOn(fs, 'createReadStream').mockReturnValue('mockStream' as any); + + mockedAxios.post.mockResolvedValue({ + status: 200, + data: { code: 0, message: 'Success' } + }); + + const result = await jobControl.uploadFileAD5X(validParams); + + expect(result).toBe(true); + expect(fsStatSpy).toHaveBeenCalledWith('test.gcode'); + + fsStatSpy.mockRestore(); + fsCreateReadStreamSpy.mockRestore(); + }); + + it('should fail if file does not exist', async () => { + const fs = require('fs'); + const fsStatSpy = jest.spyOn(fs.promises, 'stat').mockRejectedValue(new Error('File not found')); + + const result = await jobControl.uploadFileAD5X(validParams); + + expect(result).toBe(false); + expect(fsStatSpy).toHaveBeenCalledWith('test.gcode'); + + fsStatSpy.mockRestore(); + }); + }); }); diff --git a/src/api/controls/JobControl.ts b/src/api/controls/JobControl.ts index c2aa86d..b1836f4 100644 --- a/src/api/controls/JobControl.ts +++ b/src/api/controls/JobControl.ts @@ -216,16 +216,18 @@ export class JobControl { return false; } - // Validate file exists - if (!fs.existsSync(params.filePath)) { - console.error(`UploadFileAD5X error: File not found at ${params.filePath}`); + // Validate file exists and get stats + let fileSize: number; + let fileName: string; + try { + const stats = await fs.promises.stat(params.filePath); + fileSize = stats.size; + fileName = path.basename(params.filePath); + } catch (err: any) { + console.error(`UploadFileAD5X error: File check failed at ${params.filePath}. ${err.message}`); return false; } - const stats = fs.statSync(params.filePath); - const fileSize = stats.size; - const fileName = path.basename(params.filePath); - console.log(`Starting AD5X upload for ${fileName}, Size: ${fileSize}, Start: ${params.startPrint}, Level: ${params.levelingBeforePrint}, Tools: ${params.materialMappings.length}`); try {