From 48a486d71c1ef7f2b75a70e73d152cc3ec60d323 Mon Sep 17 00:00:00 2001 From: Adam Lesinski Date: Wed, 19 Nov 2025 12:41:56 -0800 Subject: [PATCH] Use non-blocking IO in StorageObject --- src/sdk/storage-object.ts | 6 +++--- tests/objects/storage-object.test.ts | 32 +++++++++++++--------------- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/sdk/storage-object.ts b/src/sdk/storage-object.ts index 714078f70..1555cf19f 100644 --- a/src/sdk/storage-object.ts +++ b/src/sdk/storage-object.ts @@ -6,7 +6,7 @@ import type { ObjectDownloadURLView, ObjectListParams, } from '../resources/objects'; -import * as fs from 'node:fs'; +import * as fs from 'node:fs/promises'; import * as path from 'node:path'; // Extract the content type from the API types @@ -293,7 +293,7 @@ export class StorageObject { // Check if file exists and get stats try { - const stats = fs.statSync(filePath); + const stats = await fs.stat(filePath); if (!stats.isFile()) { throw new Error(`Path is not a file: ${filePath}`); } @@ -306,7 +306,7 @@ export class StorageObject { // Read file content immediately to fail fast if file doesn't exist let fileBuffer: Buffer; try { - fileBuffer = fs.readFileSync(filePath); + fileBuffer = await fs.readFile(filePath); } catch (error) { throw new Error( `Failed to read file ${filePath}: ${error instanceof Error ? error.message : 'Unknown error'}`, diff --git a/tests/objects/storage-object.test.ts b/tests/objects/storage-object.test.ts index e6d7fe56f..4b8282d12 100644 --- a/tests/objects/storage-object.test.ts +++ b/tests/objects/storage-object.test.ts @@ -8,9 +8,9 @@ jest.mock('../../src/index'); (global as any).fetch = jest.fn(); // Mock fs and path modules -jest.mock('node:fs', () => ({ - statSync: jest.fn(), - readFileSync: jest.fn(), +jest.mock('node:fs/promises', () => ({ + stat: jest.fn(), + readFile: jest.fn(), })); jest.mock('node:path', () => ({ @@ -29,7 +29,7 @@ describe('StorageObject (New API)', () => { beforeEach(() => { // Get mocked modules - mockFs = require('node:fs'); + mockFs = require('node:fs/promises'); mockPath = require('node:path'); // Create mock client instance with proper structure @@ -462,8 +462,8 @@ describe('StorageObject (New API)', () => { it('should upload a text file with auto-detected content-type', async () => { const mockFileBuffer = Buffer.from('test content'); - mockFs.statSync.mockReturnValue({ isFile: () => true }); - mockFs.readFileSync.mockReturnValue(mockFileBuffer); + mockFs.stat.mockResolvedValue({ isFile: () => true }); + mockFs.readFile.mockResolvedValue(mockFileBuffer); const mockObjectData = { id: 'file-123', upload_url: 'https://upload.example.com/file' }; const mockObjectInfo = { ...mockObjectData, name: 'test.txt', state: 'UPLOADING' }; @@ -485,15 +485,15 @@ describe('StorageObject (New API)', () => { { name: 'test.txt', content_type: 'text', metadata: null }, undefined, ); - expect(mockFs.readFileSync).toHaveBeenCalledWith('./test.txt'); + expect(mockFs.readFile).toHaveBeenCalledWith('./test.txt'); expect(result).toBeInstanceOf(StorageObject); expect(result.id).toBe('file-123'); }); it('should upload a file with explicit content-type and custom name', async () => { const mockFileBuffer = Buffer.from('binary content'); - mockFs.statSync.mockReturnValue({ isFile: () => true }); - mockFs.readFileSync.mockReturnValue(mockFileBuffer); + mockFs.stat.mockResolvedValue({ isFile: () => true }); + mockFs.readFile.mockResolvedValue(mockFileBuffer); const mockObjectData = { id: 'file-456', upload_url: 'https://upload.example.com/file' }; const mockObjectInfo = { ...mockObjectData, name: 'custom.bin', state: 'UPLOADING' }; @@ -535,9 +535,7 @@ describe('StorageObject (New API)', () => { }); it('should handle file read errors gracefully', async () => { - mockFs.statSync.mockImplementation(() => { - throw new Error('File not found'); - }); + mockFs.stat.mockRejectedValue(new Error('File not found')); await expect( StorageObject.uploadFromFile(mockClient, './nonexistent.txt', 'nonexistent.txt', {}), @@ -546,8 +544,8 @@ describe('StorageObject (New API)', () => { it('should handle upload failures gracefully', async () => { const mockFileBuffer = Buffer.from('test content'); - mockFs.statSync.mockReturnValue({ isFile: () => true }); - mockFs.readFileSync.mockReturnValue(mockFileBuffer); + mockFs.stat.mockResolvedValue({ isFile: () => true }); + mockFs.readFile.mockResolvedValue(mockFileBuffer); const mockObjectData = { id: 'file-789', upload_url: 'https://upload.example.com/file' }; const mockObjectInfo = { ...mockObjectData, name: 'test.txt', state: 'UPLOADING' }; @@ -568,8 +566,8 @@ describe('StorageObject (New API)', () => { it('should upload an archive file with auto-detected content-type', async () => { const mockArchiveBuffer = Buffer.from('compressed archive content'); - mockFs.statSync.mockReturnValue({ isFile: () => true }); - mockFs.readFileSync.mockReturnValue(mockArchiveBuffer); + mockFs.stat.mockResolvedValue({ isFile: () => true }); + mockFs.readFile.mockResolvedValue(mockArchiveBuffer); const mockObjectData = { id: 'archive-123', upload_url: 'https://upload.example.com/archive' }; const mockObjectInfo = { ...mockObjectData, name: 'project.tar.gz', state: 'UPLOADING' }; @@ -595,7 +593,7 @@ describe('StorageObject (New API)', () => { { name: 'test-archive.tar.gz', content_type: 'tgz', metadata: null }, undefined, ); - expect(mockFs.readFileSync).toHaveBeenCalledWith('./files/test-archive.tar.gz'); + expect(mockFs.readFile).toHaveBeenCalledWith('./files/test-archive.tar.gz'); expect(result).toBeInstanceOf(StorageObject); expect(result.id).toBe('archive-123'); });