UNPKG

@hashgraphonline/conversational-agent

Version:

Hashgraph Online conversational AI agent implementing HCS-10 communication, HCS-2 registries, and content inscription on Hedera. https://hol.org

195 lines (160 loc) 6.25 kB
import { describe, expect, it, beforeEach, jest } from '@jest/globals'; import './SwarmTool.mocks'; import { UploadFileTool } from '../../tools/UploadFileTool'; import { beeMock, contextMock, mockedUtils, swarmConfigMock } from './SwarmTool.mocks'; import { HederaAgentKit } from 'hedera-agent-kit'; import { Reference, UploadResult } from '@ethersphere/bee-js'; import { GATEWAY_TAG_ERROR_MESSAGE } from '../../constants'; describe('UploadFileTool', () => { let tool: UploadFileTool; const hederaKitMock = {} as HederaAgentKit; beforeEach(() => { jest.clearAllMocks(); tool = new UploadFileTool({ hederaKit: hederaKitMock, config: swarmConfigMock, bee: beeMock, logger: contextMock.logger, }); // Default mocks mockedUtils.getUploadPostageBatchId.mockResolvedValue('postage123'); const reference = { toString: () => 'postage123' } as unknown as Reference; const uploadResult = { reference } as unknown as UploadResult; beeMock.uploadFile.mockResolvedValue(uploadResult); const tagDataMock = { address: '0x123', synced: 50, seen: 25, split: 100, stored: 10, sent: 10, uid: 123, startedAt: '2025-11-04T12:00:00Z', }; beeMock.createTag.mockResolvedValue(tagDataMock); }); it('uploads base64 data successfully', async () => { const data = Buffer.from('hello world').toString('base64'); const result = await tool['executeQuery']({ data, isPath: false, }); expect(mockedUtils.getUploadPostageBatchId).toHaveBeenCalledWith( undefined, beeMock, swarmConfigMock ); expect(beeMock.uploadFile).toHaveBeenCalledWith( 'postage123', expect.any(Buffer), undefined, expect.objectContaining({ deferred: false }) ); expect(result['structuredContent']).toEqual( expect.objectContaining({ reference: "postage123", url: "http://127.0.0.1:1633/bzz/postage123", message: "File successfully uploaded to Swarm", }) ); }); it('uploads file from path successfully', async () => { const readFileMock = jest.fn<any>().mockResolvedValue(Buffer.from('file content')); jest.spyOn(require('util'), 'promisify').mockReturnValue(readFileMock); const result = await tool['executeQuery']({ data: '/mock/path/file.txt', isPath: true, }); expect(readFileMock).toHaveBeenCalledWith('/mock/path/file.txt'); expect(beeMock.uploadFile).toHaveBeenCalledWith( 'postage123', expect.any(Buffer), 'file.txt', expect.objectContaining({ deferred: false }) ); expect(result['structuredContent']).toEqual( expect.objectContaining({ reference: 'postage123', url: 'http://127.0.0.1:1633/bzz/postage123', message: 'File successfully uploaded to Swarm', }) ); }); it('throws error if data is missing', async () => { await expect( tool['executeQuery']({ data: '', isPath: false }) ).rejects.toThrow('Missing required parameter: data.'); expect(contextMock.logger.error).toHaveBeenCalledWith( 'Missing required parameter: data.' ); }); it('throws error if reading file fails', async () => { const readFileMock = jest.fn<any>().mockRejectedValue(new Error('File read failed')); jest.spyOn(require('util'), 'promisify').mockReturnValue(readFileMock); jest.spyOn(require('util'), 'promisify').mockReturnValue(readFileMock); await expect( tool['executeQuery']({ data: '/bad/path.txt', isPath: true }) ).rejects.toThrow('Unable to read file at path: /bad/path.txt.'); expect(contextMock.logger.error).toHaveBeenCalledWith( 'Unable to read file at path: /bad/path.txt.', expect.any(Error) ); }); it('throws error if getUploadPostageBatchId fails', async () => { const error = new Error('Failed to get postage batch'); mockedUtils.getUploadPostageBatchId.mockRejectedValue(error); await expect( tool['executeQuery']({ data: 'abc', isPath: false }) ).rejects.toThrow('Failed to get postage batch'); expect(contextMock.logger.error).toHaveBeenCalledWith('Failed to get postage batch'); }); it('uploads large file in deferred mode and creates tag', async () => { // Pretend threshold is small so any data triggers deferred mode swarmConfigMock.deferredUploadSizeThresholdMB = 0.000001; const bigData = Buffer.alloc(1024 * 1024, 'a').toString('base64'); // 1MB const result = await tool['executeQuery']({ data: bigData, isPath: false, }); expect(beeMock.createTag).toHaveBeenCalled(); expect(beeMock.uploadFile).toHaveBeenCalledWith( 'postage123', expect.any(Buffer), undefined, expect.objectContaining({ deferred: true, tag: 123, }) ); expect(result['structuredContent']).toEqual( expect.objectContaining({ tagId: '123', message: 'File upload started in deferred mode. Use query_upload_progress to track progress.', }) ); }); it('handles createTag 404 and throws GATEWAY_TAG_ERROR_MESSAGE', async () => { beeMock.createTag.mockRejectedValue({ status: 404 }); swarmConfigMock.deferredUploadSizeThresholdMB = 0.000001; const bigData = Buffer.alloc(1024 * 1024, 'a').toString('base64'); await expect( tool['executeQuery']({ data: bigData, isPath: false }) ).rejects.toThrow(GATEWAY_TAG_ERROR_MESSAGE); expect(contextMock.logger.error).toHaveBeenCalledWith( GATEWAY_TAG_ERROR_MESSAGE, expect.any(Object) ); }); it('handles uploadFile error gracefully', async () => { const error = new Error('Upload failed'); beeMock.uploadFile.mockRejectedValueOnce(error); await expect( tool['executeQuery']({ data: 'abc', isPath: false }) ).rejects.toThrow('Unable to upload file.'); expect(contextMock.logger.error).toHaveBeenCalledWith( 'Unable to upload file.', error ); }); });