@lobehub/chat
Version:
Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.
212 lines (172 loc) • 6.31 kB
text/typescript
import { LocalFileItem, LocalMoveFilesResultItem } from '@lobechat/electron-client-ipc';
import { describe, expect, it, vi } from 'vitest';
import { localFileService } from '@/services/electron/localFileService';
import { ChatStore } from '@/store/chat/store';
import { localFileSlice } from '../localFile';
vi.mock('@/services/electron/localFileService', () => ({
localFileService: {
listLocalFiles: vi.fn(),
moveLocalFiles: vi.fn(),
readLocalFile: vi.fn(),
readLocalFiles: vi.fn(),
renameLocalFile: vi.fn(),
searchLocalFiles: vi.fn(),
writeFile: vi.fn(),
},
}));
const mockSet = vi.fn();
const mockStore = {
internal_triggerLocalFileToolCalling: vi.fn(),
internal_updateMessageContent: vi.fn(),
internal_updateMessagePluginError: vi.fn(),
set: mockSet,
toggleLocalFileLoading: vi.fn(),
updatePluginArguments: vi.fn(),
updatePluginState: vi.fn(),
} as unknown as ChatStore;
const createStore = () => {
return localFileSlice(
(set) => ({
...mockStore,
set,
}),
() => mockStore,
{} as any,
);
};
describe('localFileSlice', () => {
const store = createStore();
beforeEach(() => {
vi.clearAllMocks();
});
describe('internal_triggerLocalFileToolCalling', () => {
it('should handle successful calling', async () => {
const mockContent = { foo: 'bar' };
const mockState = { state: 'test' };
const mockService = vi.fn().mockResolvedValue({ content: mockContent, state: mockState });
await store.internal_triggerLocalFileToolCalling('test-id', mockService);
expect(mockStore.toggleLocalFileLoading).toBeCalledWith('test-id', true);
expect(mockStore.updatePluginState).toBeCalledWith('test-id', mockState);
expect(mockStore.internal_updateMessageContent).toBeCalledWith(
'test-id',
JSON.stringify(mockContent),
);
expect(mockStore.toggleLocalFileLoading).toBeCalledWith('test-id', false);
});
it('should handle error', async () => {
const mockError = new Error('test error');
const mockService = vi.fn().mockRejectedValue(mockError);
await store.internal_triggerLocalFileToolCalling('test-id', mockService);
expect(mockStore.internal_updateMessagePluginError).toBeCalledWith('test-id', {
body: mockError,
message: 'test error',
type: 'PluginServerError',
});
});
});
describe('listLocalFiles', () => {
it('should call listLocalFiles service and update state', async () => {
const mockResult: LocalFileItem[] = [
{
name: 'test.txt',
path: '/test.txt',
isDirectory: false,
createdTime: new Date(),
lastAccessTime: new Date(),
modifiedTime: new Date(),
size: 100,
type: 'file',
},
];
vi.mocked(localFileService.listLocalFiles).mockResolvedValue(mockResult);
await store.listLocalFiles('test-id', { path: '/test' });
expect(mockStore.internal_triggerLocalFileToolCalling).toBeCalled();
});
});
describe('moveLocalFiles', () => {
it('should handle successful move', async () => {
const mockResults = [
{
sourcePath: '/test.txt',
destinationPath: '/target/test.txt',
success: true,
},
] as unknown as LocalMoveFilesResultItem[];
vi.mocked(localFileService.moveLocalFiles).mockResolvedValue(mockResults);
await store.moveLocalFiles('test-id', {
sourcePaths: ['/test.txt'],
destinationDir: '/target',
} as any);
expect(mockStore.internal_triggerLocalFileToolCalling).toBeCalled();
});
});
describe('writeLocalFile', () => {
it('should handle successful write', async () => {
vi.mocked(localFileService.writeFile).mockResolvedValue({
success: true,
newPath: '/test.txt',
});
await store.writeLocalFile('test-id', { path: '/test.txt', content: 'test' });
expect(mockStore.internal_triggerLocalFileToolCalling).toBeCalled();
});
it('should handle write error', async () => {
vi.mocked(localFileService.writeFile).mockResolvedValue({
success: false,
error: 'Write failed',
newPath: '/test.txt',
});
await store.writeLocalFile('test-id', { path: '/test.txt', content: 'test' });
expect(mockStore.internal_triggerLocalFileToolCalling).toBeCalled();
});
});
describe('renameLocalFile', () => {
it('should handle successful rename', async () => {
vi.mocked(localFileService.renameLocalFile).mockResolvedValue({
success: true,
newPath: '/new.txt',
});
await store.renameLocalFile('test-id', { path: '/test.txt', newName: 'new.txt' });
expect(mockStore.internal_triggerLocalFileToolCalling).toBeCalled();
});
it('should handle rename error', async () => {
vi.mocked(localFileService.renameLocalFile).mockResolvedValue({
success: false,
error: 'Rename failed',
newPath: '/test.txt',
});
await store.renameLocalFile('test-id', { path: '/test.txt', newName: 'new.txt' });
expect(mockStore.internal_triggerLocalFileToolCalling).toBeCalled();
});
it('should validate new filename', async () => {
vi.mocked(localFileService.renameLocalFile).mockRejectedValue(
new Error('Invalid new name provided'),
);
await store.renameLocalFile('test-id', {
path: '/test.txt',
newName: '../invalid.txt',
});
expect(mockStore.internal_triggerLocalFileToolCalling).toBeCalledWith(
'test-id',
expect.any(Function),
);
});
});
describe('toggleLocalFileLoading', () => {
it('should toggle loading state', () => {
const mockSetFn = vi.fn();
const testStore = localFileSlice(mockSetFn, () => mockStore, {} as any);
testStore.toggleLocalFileLoading('test-id', true);
expect(mockSetFn).toHaveBeenCalledWith(
expect.any(Function),
false,
'toggleLocalFileLoading/start',
);
testStore.toggleLocalFileLoading('test-id', false);
expect(mockSetFn).toHaveBeenCalledWith(
expect.any(Function),
false,
'toggleLocalFileLoading/end',
);
});
});
});