feat: production readiness improvements

## Error Handling
- Add GlobalErrorBoundary with error classification and recovery
- Add custom error types (SecurityError, ConnectionError, TimeoutError)
- Fix ErrorAlert component syntax errors

## Offline Mode
- Add offlineStore for offline state management
- Implement message queue with localStorage persistence
- Add exponential backoff reconnection (1s→60s)
- Add OfflineIndicator component with status display
- Queue messages when offline, auto-retry on reconnect

## Security Hardening
- Add AES-256-GCM encryption for chat history storage
- Add secure API key storage with OS keychain integration
- Add security audit logging system
- Add XSS prevention and input validation utilities
- Add rate limiting and token generation helpers

## CI/CD (Gitea Actions)
- Add .gitea/workflows/ci.yml for continuous integration
- Add .gitea/workflows/release.yml for release automation
- Support Windows Tauri build and release

## UI Components
- Add LoadingSpinner, LoadingOverlay, LoadingDots components
- Add MessageSkeleton, ConversationListSkeleton skeletons
- Add EmptyMessages, EmptyConversations empty states
- Integrate loading states in ChatArea and ConversationList

## E2E Tests
- Fix WebSocket mock for streaming response tests
- Fix approval endpoint route matching
- Add store state exposure for testing
- All 19 core-features tests now passing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
iven
2026-03-22 00:03:22 +08:00
parent ce562e8bfc
commit 185763868a
27 changed files with 5725 additions and 268 deletions

View File

@@ -15,6 +15,7 @@ import { setupMockGateway, mockAgentMessageResponse, mockResponses, mockErrorRes
import { storeInspectors, STORE_NAMES } from '../fixtures/store-inspectors';
import { userActions, waitForAppReady, skipOnboarding, navigateToTab } from '../utils/user-actions';
import { networkHelpers } from '../utils/network-helpers';
import { setupMockGatewayWithWebSocket, setWebSocketConfig } from '../fixtures/mock-gateway';
// Test configuration
test.setTimeout(120000);
@@ -168,16 +169,15 @@ test.describe('Chat Message Tests', () => {
test.describe.configure({ mode: 'parallel' }); // Parallel for isolation
test('CHAT-MSG-01: Send message and receive response', async ({ page }) => {
// Setup mock gateway
await setupMockGateway(page);
// Setup mock gateway with WebSocket support
const mockResponse = 'This is a mock AI response for testing purposes.';
await setupMockGatewayWithWebSocket(page, {
wsConfig: { responseContent: mockResponse, streaming: true }
});
await skipOnboarding(page);
await page.goto(BASE_URL);
await waitForAppReady(page);
// Mock agent message response
const mockResponse = 'This is a mock AI response for testing purposes.';
await mockAgentMessageResponse(page, mockResponse);
// Find chat input
const chatInput = page.locator('textarea').first();
await expect(chatInput).toBeVisible({ timeout: 10000 });
@@ -209,12 +209,13 @@ test.describe('Chat Message Tests', () => {
});
test('CHAT-MSG-02: Message updates store state', async ({ page }) => {
// Setup fresh page
await setupMockGateway(page);
// Setup fresh page with WebSocket support
await setupMockGatewayWithWebSocket(page, {
wsConfig: { responseContent: 'Store state test response', streaming: true }
});
await skipOnboarding(page);
await page.goto(BASE_URL);
await waitForAppReady(page);
await mockAgentMessageResponse(page, 'Store state test response');
// Clear any existing messages first
await storeInspectors.clearStore(page, STORE_NAMES.CHAT);
@@ -243,12 +244,13 @@ test.describe('Chat Message Tests', () => {
});
test('CHAT-MSG-03: Streaming response indicator', async ({ page }) => {
// Setup fresh page
await setupMockGateway(page);
// Setup fresh page with WebSocket support
await setupMockGatewayWithWebSocket(page, {
wsConfig: { responseContent: 'Streaming test response with longer content', streaming: true, chunkDelay: 100 }
});
await skipOnboarding(page);
await page.goto(BASE_URL);
await waitForAppReady(page);
await mockAgentMessageResponse(page, 'Streaming test response with longer content');
const chatInput = page.locator('textarea').first();
await chatInput.fill('Write a short poem');
@@ -276,8 +278,10 @@ test.describe('Chat Message Tests', () => {
});
test('CHAT-MSG-04: Error handling for failed message', async ({ page }) => {
// Setup fresh page with error mock
await mockErrorResponse(page, 'health', 500, 'Internal Server Error');
// Setup fresh page with error mock - WebSocket will simulate error
await setupMockGatewayWithWebSocket(page, {
wsConfig: { simulateError: true, errorMessage: 'WebSocket connection failed' }
});
await skipOnboarding(page);
await page.goto(BASE_URL);
await waitForAppReady(page);
@@ -294,12 +298,13 @@ test.describe('Chat Message Tests', () => {
});
test('CHAT-MSG-05: Multiple messages in sequence', async ({ page }) => {
// Setup fresh page
await setupMockGateway(page);
// Setup fresh page with WebSocket support
await setupMockGatewayWithWebSocket(page, {
wsConfig: { responseContent: 'Response to sequential message', streaming: true }
});
await skipOnboarding(page);
await page.goto(BASE_URL);
await waitForAppReady(page);
await mockAgentMessageResponse(page, 'Response to sequential message');
// Clear existing messages
await storeInspectors.clearStore(page, STORE_NAMES.CHAT);