fix(i-d): ai/sessions-API-Bruche behoben — tote Frontend-Calls eliminiert statt Backend-Shims
Root-Cause: Backend hat KEIN /ai/sessions-CRUD (nur Conversations-Routen im kommunikation/ai_assistant). Frontend-Nutzer war NUR AISidebar — dessen Chat-Tab renderte nie einen echten Chat sondern nur Platzhalter gesteuert von Session-Calls auf 404. Nach AGENTS.md 0.2/0.3 keine Backend-Shims gebaut: (1) Geister-Tests ChatWindow.test.tsx + SessionList.test.tsx geloescht — importierten nicht existierende Komponenten @/components/ai/ChatWindow + SessionList (BUG-099-Muster, Plan sanktioniert Loeschung). (2) AISidebar: tote fetchSessions/createSession-Calls + sessionId/loading-State entfernt; Chat-Tab zeigt jetzt Verweis-Link auf existierende /ai-assistant-Seite (962e0ee). (3) api/ai.ts 253→170 Zeilen: tote Interfaces ChatFolder/ChatSession/ChatMessage/ChatAttachment + Folders/Sessions/Attachments-Sektionen entfernt; fetchMessages/streamChat bleiben (genutzt von AiChatPanel/Communication).
Beweise: tsc --noEmit exit=0; vitest src/__tests__/ai/ 26/26 gruen (vorher 2 Geister-Suites mit Import-Error); ruff unberuehrt.
This commit is contained in:
@@ -1,113 +0,0 @@
|
||||
import React from 'react';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
||||
import { ChatWindow } from '@/components/ai/ChatWindow';
|
||||
|
||||
const mockMessages = [
|
||||
{ id: 'm1', session_id: 's1', role: 'user', content: 'Hello AI', tokens: 10, model_used: '' },
|
||||
{ id: 'm2', session_id: 's1', role: 'assistant', content: 'Hello User', tokens: 10, model_used: 'gpt-4' },
|
||||
];
|
||||
|
||||
const fetchMessagesMock = vi.fn();
|
||||
const fetchAttachmentsMock = vi.fn();
|
||||
const streamChatMock = vi.fn();
|
||||
const uploadAttachmentMock = vi.fn();
|
||||
|
||||
vi.mock('@/api/ai', () => ({
|
||||
fetchMessages: (...args: any[]) => fetchMessagesMock(...args),
|
||||
fetchAttachments: (...args: any[]) => fetchAttachmentsMock(...args),
|
||||
streamChat: (...args: any[]) => streamChatMock(...args),
|
||||
uploadAttachment: (...args: any[]) => uploadAttachmentMock(...args),
|
||||
getAttachmentDownloadUrl: (id: string) => `/api/v1/ai/attachments/${id}/download`,
|
||||
}));
|
||||
|
||||
vi.mock('react-markdown', () => ({
|
||||
default: ({ children }: { children: string }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('remark-gfm', () => ({
|
||||
default: () => ({}),
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
fetchMessagesMock.mockResolvedValue(mockMessages);
|
||||
fetchAttachmentsMock.mockResolvedValue([]);
|
||||
streamChatMock.mockReturnValue({
|
||||
async *[Symbol.asyncIterator]() {
|
||||
yield { type: 'token', content: 'Hi' };
|
||||
yield { type: 'done' };
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe('ChatWindow', () => {
|
||||
it('renders chat window with messages', async () => {
|
||||
render(<ChatWindow sessionId="s1" />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Hello AI')).toBeInTheDocument();
|
||||
expect(screen.getByText('Hello User')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders textarea input for message', async () => {
|
||||
render(<ChatWindow sessionId="s1" />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByPlaceholderText('Nachricht eingeben...')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders send button', async () => {
|
||||
render(<ChatWindow sessionId="s1" />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Senden')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('disables send button when input is empty', async () => {
|
||||
render(<ChatWindow sessionId="s1" />);
|
||||
await waitFor(() => {
|
||||
const sendBtn = screen.getByText('Senden');
|
||||
expect(sendBtn).toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
it('enables send button when input has text', async () => {
|
||||
render(<ChatWindow sessionId="s1" />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByPlaceholderText('Nachricht eingeben...')).toBeInTheDocument();
|
||||
});
|
||||
const input = screen.getByPlaceholderText('Nachricht eingeben...');
|
||||
fireEvent.change(input, { target: { value: 'Test message' } });
|
||||
expect(screen.getByText('Senden')).not.toBeDisabled();
|
||||
});
|
||||
|
||||
it('shows empty state when no messages', async () => {
|
||||
fetchMessagesMock.mockResolvedValueOnce([]);
|
||||
render(<ChatWindow sessionId="s1" />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Starte eine Konversation...')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('displays error when fetchMessages fails', async () => {
|
||||
fetchMessagesMock.mockRejectedValueOnce(new Error('Load failed'));
|
||||
render(<ChatWindow sessionId="s1" />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Load failed')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('sends message on button click', async () => {
|
||||
render(<ChatWindow sessionId="s1" />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByPlaceholderText('Nachricht eingeben...')).toBeInTheDocument();
|
||||
});
|
||||
const input = screen.getByPlaceholderText('Nachricht eingeben...');
|
||||
fireEvent.change(input, { target: { value: 'Test message' } });
|
||||
fireEvent.click(screen.getByText('Senden'));
|
||||
await waitFor(() => {
|
||||
expect(streamChatMock).toHaveBeenCalledWith('s1', 'Test message', undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,104 +0,0 @@
|
||||
import React from 'react';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { SessionList } from '@/components/ai/SessionList';
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: { queries: { retry: false } },
|
||||
});
|
||||
|
||||
function renderWithProvider(ui: React.ReactElement) {
|
||||
return render(
|
||||
<QueryClientProvider client={queryClient}>{ui}</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
|
||||
const mockSessions = [
|
||||
{ id: 's1', title: 'First Chat', folder_id: null, agent_id: null, is_sidebar: false, created_at: '2024-01-01T00:00:00Z', updated_at: '2024-01-01T00:00:00Z' },
|
||||
{ id: 's2', title: 'Second Chat', folder_id: 'f1', agent_id: null, is_sidebar: false, created_at: '2024-01-01T00:00:00Z', updated_at: '2024-01-01T00:00:00Z' },
|
||||
];
|
||||
|
||||
const mockFolders = [
|
||||
{ id: 'f1', name: 'My Folder', parent_id: null, created_at: '2024-01-01T00:00:00Z' },
|
||||
];
|
||||
|
||||
const fetchSessionsMock = vi.fn();
|
||||
const fetchFoldersMock = vi.fn();
|
||||
const createSessionMock = vi.fn();
|
||||
const deleteSessionMock = vi.fn();
|
||||
const updateSessionMock = vi.fn();
|
||||
const createFolderMock = vi.fn();
|
||||
const deleteFolderMock = vi.fn();
|
||||
const updateFolderMock = vi.fn();
|
||||
|
||||
vi.mock('@/api/ai', () => ({
|
||||
fetchSessions: (...args: any[]) => fetchSessionsMock(...args),
|
||||
fetchFolders: (...args: any[]) => fetchFoldersMock(...args),
|
||||
createSession: (...args: any[]) => createSessionMock(...args),
|
||||
deleteSession: (...args: any[]) => deleteSessionMock(...args),
|
||||
updateSession: (...args: any[]) => updateSessionMock(...args),
|
||||
createFolder: (...args: any[]) => createFolderMock(...args),
|
||||
deleteFolder: (...args: any[]) => deleteFolderMock(...args),
|
||||
updateFolder: (...args: any[]) => updateFolderMock(...args),
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
fetchSessionsMock.mockResolvedValue(mockSessions);
|
||||
fetchFoldersMock.mockResolvedValue(mockFolders);
|
||||
createSessionMock.mockResolvedValue({ id: 's3', title: 'New Chat', folder_id: null, agent_id: null, is_sidebar: false, created_at: '', updated_at: '' });
|
||||
deleteSessionMock.mockResolvedValue({});
|
||||
updateSessionMock.mockResolvedValue({});
|
||||
createFolderMock.mockResolvedValue({ id: 'f2', name: 'New Folder', parent_id: null, created_at: '' });
|
||||
deleteFolderMock.mockResolvedValue({});
|
||||
updateFolderMock.mockResolvedValue({});
|
||||
});
|
||||
|
||||
describe('SessionList', () => {
|
||||
it('renders session list with sessions', async () => {
|
||||
renderWithProvider(<SessionList activeSessionId="s1" onSelectSession={vi.fn()} />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('First Chat')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders folder names', async () => {
|
||||
renderWithProvider(<SessionList activeSessionId="s1" onSelectSession={vi.fn()} />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('My Folder')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders sessions inside folders', async () => {
|
||||
renderWithProvider(<SessionList activeSessionId="s1" onSelectSession={vi.fn()} />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Second Chat')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('calls onSelectSession when session clicked', async () => {
|
||||
const onSelect = vi.fn();
|
||||
renderWithProvider(<SessionList activeSessionId={null} onSelectSession={onSelect} />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('First Chat')).toBeInTheDocument();
|
||||
});
|
||||
fireEvent.click(screen.getByText('First Chat'));
|
||||
expect(onSelect).toHaveBeenCalledWith('s1');
|
||||
});
|
||||
|
||||
it('shows loading state initially', () => {
|
||||
fetchSessionsMock.mockReturnValue(new Promise(() => {}));
|
||||
fetchFoldersMock.mockReturnValue(new Promise(() => {}));
|
||||
renderWithProvider(<SessionList activeSessionId={null} onSelectSession={vi.fn()} />);
|
||||
expect(screen.getByText('Laden...')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('displays error when fetch fails', async () => {
|
||||
fetchSessionsMock.mockRejectedValueOnce(new Error('Failed to load'));
|
||||
renderWithProvider(<SessionList activeSessionId={null} onSelectSession={vi.fn()} />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Failed to load')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user