Files
cim_summary/frontend/src/components/__tests__/LogoutButton.test.tsx
Jon 57770fd99d feat: Implement hybrid LLM approach with enhanced prompts for CIM analysis
🎯 Major Features:
- Hybrid LLM configuration: Claude 3.7 Sonnet (primary) + GPT-4.5 (fallback)
- Task-specific model selection for optimal performance
- Enhanced prompts for all analysis types with proven results

🔧 Technical Improvements:
- Enhanced financial analysis with fiscal year mapping (100% success rate)
- Business model analysis with scalability assessment
- Market positioning analysis with TAM/SAM extraction
- Management team assessment with succession planning
- Creative content generation with GPT-4.5

📊 Performance & Cost Optimization:
- Claude 3.7 Sonnet: /5 per 1M tokens (82.2% MATH score)
- GPT-4.5: Premium creative content (5/50 per 1M tokens)
- ~80% cost savings using Claude for analytical tasks
- Automatic fallback system for reliability

 Proven Results:
- Successfully extracted 3-year financial data from STAX CIM
- Correctly mapped fiscal years (2023→FY-3, 2024→FY-2, 2025E→FY-1, LTM Mar-25→LTM)
- Identified revenue: 4M→1M→1M→6M (LTM)
- Identified EBITDA: 8.9M→3.9M→1M→7.2M (LTM)

🚀 Files Added/Modified:
- Enhanced LLM service with task-specific model selection
- Updated environment configuration for hybrid approach
- Enhanced prompt builders for all analysis types
- Comprehensive testing scripts and documentation
- Updated frontend components for improved UX

📚 References:
- Eden AI Model Comparison: Claude 3.7 Sonnet vs GPT-4.5
- Artificial Analysis Benchmarks for performance metrics
- Cost optimization based on model strengths and pricing
2025-07-28 16:46:06 -04:00

269 lines
7.3 KiB
TypeScript

import React from 'react';
import { render, screen, waitFor, act } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { vi, describe, it, expect, beforeEach } from 'vitest';
import LogoutButton from '../LogoutButton';
import { AuthProvider } from '../../contexts/AuthContext';
import { authService } from '../../services/authService';
// Mock the auth service
vi.mock('../../services/authService', () => ({
authService: {
login: vi.fn(),
logout: vi.fn(),
getToken: vi.fn(),
getCurrentUser: vi.fn(),
validateToken: vi.fn(),
},
}));
const MockedAuthService = authService as any;
// Wrapper component for tests
const TestWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => (
<AuthProvider>{children}</AuthProvider>
);
// Helper to wait for auth initialization
const waitForAuthInit = async () => {
await waitFor(() => {
expect(screen.getByRole('button', { name: /sign out/i })).toBeInTheDocument();
}, { timeout: 5000 });
};
describe('LogoutButton', () => {
const user = userEvent.setup();
beforeEach(() => {
vi.clearAllMocks();
MockedAuthService.getToken.mockReturnValue('mock-token');
MockedAuthService.getCurrentUser.mockReturnValue({
id: '1',
email: 'test@example.com',
name: 'Test User',
role: 'user',
});
MockedAuthService.validateToken.mockResolvedValue({
id: '1',
email: 'test@example.com',
name: 'Test User',
role: 'user',
});
MockedAuthService.logout.mockResolvedValue(undefined);
});
it('renders logout button with default variant', async () => {
await act(async () => {
render(
<TestWrapper>
<LogoutButton />
</TestWrapper>
);
});
await waitForAuthInit();
const button = screen.getByRole('button', { name: /sign out/i });
expect(button).toBeInTheDocument();
expect(button).toHaveClass('bg-error-600'); // Button variant styling
});
it('renders logout link with link variant', async () => {
await act(async () => {
render(
<TestWrapper>
<LogoutButton variant="link" />
</TestWrapper>
);
});
await waitForAuthInit();
const button = screen.getByRole('button', { name: /sign out/i });
expect(button).toBeInTheDocument();
expect(button).not.toHaveClass('bg-red-600'); // Link variant styling
});
it('shows confirmation dialog when showConfirmation is true', async () => {
await act(async () => {
render(
<TestWrapper>
<LogoutButton showConfirmation={true} />
</TestWrapper>
);
});
await waitForAuthInit();
const button = screen.getByRole('button', { name: /sign out/i });
await act(async () => {
await user.click(button);
});
await waitFor(() => {
expect(screen.getByText(/confirm logout/i)).toBeInTheDocument();
});
expect(screen.getByText(/are you sure you want to sign out/i)).toBeInTheDocument();
expect(screen.getByRole('button', { name: /cancel/i })).toBeInTheDocument();
});
it('does not show confirmation dialog when showConfirmation is false', async () => {
await act(async () => {
render(
<TestWrapper>
<LogoutButton showConfirmation={false} />
</TestWrapper>
);
});
await waitForAuthInit();
const button = screen.getByRole('button', { name: /sign out/i });
await act(async () => {
await user.click(button);
});
// Should not show confirmation dialog, should call logout directly
await waitFor(() => {
expect(MockedAuthService.logout).toHaveBeenCalled();
});
});
it('calls logout service when confirmed', async () => {
// Ensure the mock is properly set up
MockedAuthService.logout.mockResolvedValue(undefined);
await act(async () => {
render(
<TestWrapper>
<LogoutButton showConfirmation={true} />
</TestWrapper>
);
});
await waitForAuthInit();
const button = screen.getByRole('button', { name: /sign out/i });
await act(async () => {
await user.click(button);
});
await waitFor(() => {
expect(screen.getByText(/confirm logout/i)).toBeInTheDocument();
});
// In the confirmation dialog, there's only one "Sign Out" button
const confirmButton = screen.getByRole('button', { name: /sign out/i });
await act(async () => {
await user.click(confirmButton);
});
// Wait for the logout to be called
await waitFor(() => {
expect(MockedAuthService.logout).toHaveBeenCalled();
}, { timeout: 3000 });
});
it('cancels logout when cancel button is clicked', async () => {
await act(async () => {
render(
<TestWrapper>
<LogoutButton showConfirmation={true} />
</TestWrapper>
);
});
await waitForAuthInit();
const button = screen.getByRole('button', { name: /sign out/i });
await act(async () => {
await user.click(button);
});
await waitFor(() => {
expect(screen.getByText(/confirm logout/i)).toBeInTheDocument();
});
const cancelButton = screen.getByRole('button', { name: /cancel/i });
await act(async () => {
await user.click(cancelButton);
});
await waitFor(() => {
expect(screen.queryByText(/confirm logout/i)).not.toBeInTheDocument();
});
expect(MockedAuthService.logout).not.toHaveBeenCalled();
});
it('shows loading state during logout', async () => {
// Mock logout to be slow so we can see loading state
MockedAuthService.logout.mockImplementation(() => new Promise(resolve => setTimeout(resolve, 100)));
await act(async () => {
render(
<TestWrapper>
<LogoutButton showConfirmation={false} />
</TestWrapper>
);
});
await waitForAuthInit();
const button = screen.getByRole('button', { name: /sign out/i });
await act(async () => {
await user.click(button);
});
// Should show loading state immediately
await waitFor(() => {
expect(screen.getByText(/signing out.../i)).toBeInTheDocument();
});
const loadingButton = screen.getByText(/signing out.../i).closest('button');
expect(loadingButton).toBeDisabled();
});
it('handles logout errors gracefully', async () => {
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
MockedAuthService.logout.mockRejectedValue(new Error('Logout failed'));
await act(async () => {
render(
<TestWrapper>
<LogoutButton showConfirmation={false} />
</TestWrapper>
);
});
await waitForAuthInit();
const button = screen.getByRole('button', { name: /sign out/i });
await act(async () => {
await user.click(button);
});
// The error is logged in AuthContext, not directly in the component
await waitFor(() => {
expect(consoleSpy).toHaveBeenCalledWith('Logout error:', expect.any(Error));
});
consoleSpy.mockRestore();
});
it('applies custom className', async () => {
await act(async () => {
render(
<TestWrapper>
<LogoutButton className="custom-class" />
</TestWrapper>
);
});
await waitForAuthInit();
const button = screen.getByRole('button', { name: /sign out/i });
expect(button).toHaveClass('custom-class');
});
});