refactor: update knowledge document types and API client interfaces
- Refactored types in `Knowledge/types.ts` to introduce new interfaces for document handling. - Added `KnowledgeDocItem`, `KnowledgeDocsListResponse`, `KnowledgeDocsUploadInput`, `KnowledgeDocsUploadResponse`, and `KnowledgeDocsDeleteResponse` for better structure and clarity. - Updated `KnowledgeDocsApiClient` interface to include methods for listing, uploading, and deleting documents. fix: replace deprecated icons in AccountSettingsPanel and SettingMenu - Replaced `CheckCircleIcon` with `CheckCircle` from `lucide-react` in `AccountSettingsPanel.tsx`. - Updated `SettingMenu.tsx` to use `Settings` and `User` from `lucide-react` instead of custom icons. test: add tests for knowledge docs routes and KnowledgePage - Created `knowledge-docs-routes.test.ts` to test API routes for listing, uploading, and deleting knowledge documents. - Added `knowledge-page.test.tsx` to test the rendering and functionality of the KnowledgePage component, including document loading and deletion.
This commit is contained in:
112
tests/knowledge-docs-routes.test.ts
Normal file
112
tests/knowledge-docs-routes.test.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
listKnowledgeDocs: vi.fn(),
|
||||
uploadKnowledgeDoc: vi.fn(),
|
||||
deleteKnowledgeDoc: vi.fn(),
|
||||
isSafeKnowledgeDocName: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../electron/utils/knowledge-docs', () => ({
|
||||
listKnowledgeDocs: mocks.listKnowledgeDocs,
|
||||
uploadKnowledgeDoc: mocks.uploadKnowledgeDoc,
|
||||
deleteKnowledgeDoc: mocks.deleteKnowledgeDoc,
|
||||
isSafeKnowledgeDocName: mocks.isSafeKnowledgeDocName,
|
||||
}));
|
||||
|
||||
import { normalizeRequest } from '../electron/api/route-utils';
|
||||
import { handleKnowledgeRoutes } from '../electron/api/routes/knowledge';
|
||||
|
||||
const ctx = {
|
||||
gatewayManager: null,
|
||||
providerApiService: null,
|
||||
mainWindow: null,
|
||||
} as any;
|
||||
|
||||
describe('knowledge docs routes', () => {
|
||||
beforeEach(() => {
|
||||
mocks.listKnowledgeDocs.mockReset();
|
||||
mocks.uploadKnowledgeDoc.mockReset();
|
||||
mocks.deleteKnowledgeDoc.mockReset();
|
||||
mocks.isSafeKnowledgeDocName.mockReset();
|
||||
mocks.isSafeKnowledgeDocName.mockReturnValue(true);
|
||||
});
|
||||
|
||||
it('returns listed knowledge docs from the local route', async () => {
|
||||
mocks.listKnowledgeDocs.mockResolvedValue([
|
||||
{
|
||||
name: 'guide.md',
|
||||
size: 128,
|
||||
modifiedAt: '2026-04-18T10:00:00.000Z',
|
||||
type: 'md',
|
||||
},
|
||||
]);
|
||||
|
||||
const response = await handleKnowledgeRoutes(normalizeRequest({
|
||||
path: '/api/knowledge/docs',
|
||||
method: 'GET',
|
||||
}), ctx);
|
||||
|
||||
expect(response?.ok).toBe(true);
|
||||
expect(response?.json).toMatchObject({
|
||||
success: true,
|
||||
files: [
|
||||
expect.objectContaining({
|
||||
name: 'guide.md',
|
||||
type: 'md',
|
||||
}),
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('uploads and deletes docs through the route contract', async () => {
|
||||
mocks.uploadKnowledgeDoc.mockResolvedValue({
|
||||
name: 'guide.md',
|
||||
size: 128,
|
||||
modifiedAt: '2026-04-18T10:00:00.000Z',
|
||||
type: 'md',
|
||||
});
|
||||
|
||||
const upload = await handleKnowledgeRoutes(normalizeRequest({
|
||||
path: '/api/knowledge/docs',
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
fileName: 'guide.md',
|
||||
base64: Buffer.from('hello').toString('base64'),
|
||||
}),
|
||||
}), ctx);
|
||||
|
||||
expect(upload?.ok).toBe(true);
|
||||
expect(upload?.json).toMatchObject({
|
||||
success: true,
|
||||
file: expect.objectContaining({
|
||||
name: 'guide.md',
|
||||
}),
|
||||
});
|
||||
|
||||
const remove = await handleKnowledgeRoutes(normalizeRequest({
|
||||
path: '/api/knowledge/docs/guide.md',
|
||||
method: 'DELETE',
|
||||
}), ctx);
|
||||
|
||||
expect(remove?.ok).toBe(true);
|
||||
expect(remove?.json).toMatchObject({ success: true });
|
||||
expect(mocks.deleteKnowledgeDoc).toHaveBeenCalledWith('guide.md');
|
||||
});
|
||||
|
||||
it('rejects unsafe file names', async () => {
|
||||
mocks.isSafeKnowledgeDocName.mockReturnValue(false);
|
||||
|
||||
const response = await handleKnowledgeRoutes(normalizeRequest({
|
||||
path: '/api/knowledge/docs',
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
fileName: '../escape.md',
|
||||
base64: Buffer.from('bad').toString('base64'),
|
||||
}),
|
||||
}), ctx);
|
||||
|
||||
expect(response?.ok).toBe(false);
|
||||
expect(response?.status).toBe(400);
|
||||
});
|
||||
});
|
||||
95
tests/knowledge-page.test.tsx
Normal file
95
tests/knowledge-page.test.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
import React from 'react';
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
const apiMocks = vi.hoisted(() => ({
|
||||
list: vi.fn(),
|
||||
upload: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../src/lib/knowledge-docs-api', () => ({
|
||||
knowledgeDocsApi: apiMocks,
|
||||
}));
|
||||
|
||||
vi.mock('../src/pages/Knowledge/copy', () => ({
|
||||
useKnowledgeCopy: () => (path: string, params?: Record<string, string>, fallback?: string) => {
|
||||
const dictionary: Record<string, string> = {
|
||||
'knowledge.title': 'Knowledge Docs',
|
||||
'knowledge.subtitle': 'Manage docs',
|
||||
'knowledge.documentsLabel': 'Documents',
|
||||
'knowledge.storageLabel': 'Storage',
|
||||
'knowledge.refresh': 'Refresh',
|
||||
'knowledge.upload': 'Upload',
|
||||
'knowledge.uploadHint': 'Knowledge docs only',
|
||||
'knowledge.status.loading': 'Loading documents...',
|
||||
'knowledge.status.uploading': 'Uploading...',
|
||||
'knowledge.status.deleting': 'Deleting...',
|
||||
'knowledge.status.uploadSuccess': 'Uploaded',
|
||||
'knowledge.status.deleteSuccess': 'Deleted',
|
||||
'knowledge.emptyTitle': 'No documents yet',
|
||||
'knowledge.emptyDescription': 'Upload a file',
|
||||
'knowledge.deleteConfirm': 'Delete this document?',
|
||||
'knowledge.table.name': 'Name',
|
||||
'knowledge.table.size': 'Size',
|
||||
'knowledge.table.modifiedAt': 'Modified At',
|
||||
'knowledge.table.type': 'Type',
|
||||
'knowledge.table.actions': 'Actions',
|
||||
'knowledge.table.delete': 'Delete',
|
||||
};
|
||||
|
||||
if (path === 'knowledge.status.failed') {
|
||||
return `Knowledge docs request failed: ${params?.error ?? ''}`;
|
||||
}
|
||||
|
||||
return dictionary[path] ?? fallback ?? path;
|
||||
},
|
||||
}));
|
||||
|
||||
import KnowledgePage from '../src/pages/Knowledge';
|
||||
|
||||
describe('KnowledgePage', () => {
|
||||
beforeEach(() => {
|
||||
apiMocks.list.mockReset();
|
||||
apiMocks.upload.mockReset();
|
||||
apiMocks.delete.mockReset();
|
||||
vi.stubGlobal('confirm', vi.fn(() => true));
|
||||
});
|
||||
|
||||
it('loads and renders docs from the knowledge docs api', async () => {
|
||||
apiMocks.list.mockResolvedValue([
|
||||
{
|
||||
name: 'guide.md',
|
||||
size: 1024,
|
||||
modifiedAt: '2026-04-18T10:00:00.000Z',
|
||||
type: 'md',
|
||||
},
|
||||
]);
|
||||
|
||||
render(<KnowledgePage />);
|
||||
|
||||
expect(await screen.findByText('guide.md')).toBeTruthy();
|
||||
expect(screen.getByText('MD')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('deletes a doc after confirmation', async () => {
|
||||
apiMocks.list.mockResolvedValue([
|
||||
{
|
||||
name: 'guide.md',
|
||||
size: 1024,
|
||||
modifiedAt: '2026-04-18T10:00:00.000Z',
|
||||
type: 'md',
|
||||
},
|
||||
]);
|
||||
apiMocks.delete.mockResolvedValue(undefined);
|
||||
|
||||
render(<KnowledgePage />);
|
||||
|
||||
const deleteButton = await screen.findByRole('button', { name: /delete/i });
|
||||
fireEvent.click(deleteButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(apiMocks.delete).toHaveBeenCalledWith('guide.md');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user