From 12c9f12bf1ab1efb415d01a041a5bf5a62da6a51 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:36:27 +0000 Subject: [PATCH] Optimize JobControl to use async fs operations Replaced synchronous file system operations (existsSync, statSync) with asynchronous counterparts (fs.promises.access, fs.promises.stat) in JobControl.ts. This prevents blocking the event loop during file uploads. Also updated tests to verify the async behavior. --- src/api/controls/JobControl.test.ts | 44 +++++++++++++++++++++++++++++ src/api/controls/JobControl.ts | 12 +++++--- 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/api/controls/JobControl.test.ts b/src/api/controls/JobControl.test.ts index f15844f..0398eef 100644 --- a/src/api/controls/JobControl.test.ts +++ b/src/api/controls/JobControl.test.ts @@ -1,4 +1,5 @@ import axios from 'axios'; +import * as fs from 'fs'; import { JobControl } from './JobControl'; import { FiveMClient } from '../../FiveMClient'; import { Control } from './Control'; @@ -6,7 +7,9 @@ import { Endpoints } from '../server/Endpoints'; import { AD5XMaterialMapping } from '../../models/ff-models'; jest.mock('axios'); +jest.mock('fs'); const mockedAxios = axios as jest.Mocked; +const mockedFs = fs as jest.Mocked; jest.mock('./Control'); @@ -106,6 +109,47 @@ describe('JobControl', () => { }); }); + describe('uploadFile', () => { + const testFilePath = '/path/to/test.gcode'; + const testFileSize = 1024; + + beforeEach(() => { + // @ts-ignore + mockedFs.promises = { + access: jest.fn().mockResolvedValue(undefined), + stat: jest.fn().mockResolvedValue({ size: testFileSize } as fs.Stats) + } as any; + // @ts-ignore + fs.promises = mockedFs.promises; + mockedFs.createReadStream.mockReturnValue('dummy-stream' as any); + }); + + it('should return false if file does not exist', async () => { + // @ts-ignore + mockedFs.promises.access.mockRejectedValue(new Error('File not found')); + + const result = await jobControl.uploadFile(testFilePath, false, false); + + expect(result).toBe(false); + expect(mockedFs.promises.access).toHaveBeenCalledWith(testFilePath); + }); + + it('should upload file successfully', async () => { + mockedAxios.post.mockResolvedValue({ + status: 200, + data: { code: 0, message: 'Success' } + }); + + const result = await jobControl.uploadFile(testFilePath, false, false); + + expect(result).toBe(true); + expect(mockedFs.promises.access).toHaveBeenCalledWith(testFilePath); + expect(mockedFs.promises.stat).toHaveBeenCalledWith(testFilePath); + expect(mockedFs.createReadStream).toHaveBeenCalledWith(testFilePath); + expect(mockedAxios.post).toHaveBeenCalled(); + }); + }); + describe('printLocalFile', () => { it('should print local file with new firmware version', async () => { mockFiveMClient.firmVer = '3.1.3'; diff --git a/src/api/controls/JobControl.ts b/src/api/controls/JobControl.ts index c2aa86d..8e75a9c 100644 --- a/src/api/controls/JobControl.ts +++ b/src/api/controls/JobControl.ts @@ -99,12 +99,14 @@ export class JobControl { * @returns A Promise that resolves to true if the file upload (and optional print start) is successful, false otherwise. */ public async uploadFile(filePath: string, startPrint: boolean, levelBeforePrint: boolean): Promise { - if (!fs.existsSync(filePath)) { + try { + await fs.promises.access(filePath); + } catch { console.error(`UploadFile error: File not found at ${filePath}`); return false; } - const stats = fs.statSync(filePath); + const stats = await fs.promises.stat(filePath); const fileSize = stats.size; const fileName = path.basename(filePath); @@ -217,12 +219,14 @@ export class JobControl { } // Validate file exists - if (!fs.existsSync(params.filePath)) { + try { + await fs.promises.access(params.filePath); + } catch { console.error(`UploadFileAD5X error: File not found at ${params.filePath}`); return false; } - const stats = fs.statSync(params.filePath); + const stats = await fs.promises.stat(params.filePath); const fileSize = stats.size; const fileName = path.basename(params.filePath);