test_frontend_stripe.txt
1 import React from 'react'; 2 import { render, screen, fireEvent, waitFor } from '@testing-library/react'; 3 import { Elements } from '@stripe/react-stripe-js'; 4 import { loadStripe } from '@stripe/stripe-js'; 5 import { BrowserRouter as Router } from 'react-router-dom'; 6 import axios from 'axios'; 7 import MockAdapter from 'axios-mock-adapter'; 8 9 // Pour résoudre les erreurs d'importation de composants, assurez-vous que ces chemins sont corrects 10 // et que les composants existent dans le projet 11 import BillingPage from '../src/components/billing/BillingPage'; 12 import SubscriptionPage from '../src/components/billing/SubscriptionPage'; 13 import PaymentMethodManager from '../src/components/billing/PaymentMethodManager'; 14 15 // Ajouter les types Jest pour TypeScript 16 /// <reference types="jest" /> 17 /// <reference types="@testing-library/react" /> 18 19 // Mock Stripe 20 jest.mock('@stripe/react-stripe-js', () => ({ 21 ...jest.requireActual('@stripe/react-stripe-js'), 22 CardElement: () => <div data-testid="card-element-mock">Card Element</div>, 23 useStripe: () => ({ 24 createPaymentMethod: jest.fn().mockResolvedValue({ 25 paymentMethod: { id: 'pm_test123' }, 26 }), 27 confirmCardPayment: jest.fn().mockResolvedValue({ 28 paymentIntent: { status: 'succeeded' }, 29 }), 30 }), 31 useElements: () => ({ 32 getElement: jest.fn(), 33 }), 34 })); 35 36 jest.mock('@stripe/stripe-js', () => ({ 37 loadStripe: jest.fn(), 38 })); 39 40 // Mock localStorage avec types explicites 41 const localStorageMock = (() => { 42 let store: Record<string, string> = {}; 43 return { 44 getItem: jest.fn((key: string) => store[key] || null), 45 setItem: jest.fn((key: string, value: string) => { 46 store[key] = value.toString(); 47 }), 48 clear: jest.fn(() => { 49 store = {}; 50 }), 51 removeItem: jest.fn((key: string) => { 52 delete store[key]; 53 }), 54 }; 55 })(); 56 57 Object.defineProperty(window, 'localStorage', { 58 value: localStorageMock, 59 }); 60 61 // Sample data for tests 62 const mockSubscription = { 63 id: 'sub_123456789', 64 status: 'active', 65 current_period_end: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), 66 cancel_at_period_end: false, 67 planId: 'plan_basic', 68 planName: 'Basic Plan', 69 }; 70 71 const mockInvoices = [ 72 { 73 id: 'in_123456789', 74 number: 'INV-001', 75 amount: 19.99, 76 currency: 'usd', 77 status: 'paid', 78 date: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(), 79 due_date: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(), 80 pdf: 'https://example.com/invoice.pdf', 81 }, 82 ]; 83 84 const mockPaymentMethods = [ 85 { 86 id: 'pm_123456789', 87 type: 'card', 88 last4: '4242', 89 brand: 'visa', 90 exp_month: 12, 91 exp_year: 2025, 92 is_default: true, 93 }, 94 ]; 95 96 const mockUsageStats = { 97 daily_requests: 25, 98 daily_limit: 50, 99 monthly_requests: 150, 100 monthly_limit: 500, 101 total_tokens: 15000, 102 total_tokens_input: 5000, 103 total_tokens_output: 10000, 104 }; 105 106 // Setup tests 107 beforeEach(() => { 108 jest.clearAllMocks(); 109 localStorageMock.setItem('authToken', 'fake-jwt-token'); 110 }); 111 112 describe('BillingPage Component', () => { 113 let mockAxios: MockAdapter; 114 115 beforeEach(() => { 116 mockAxios = new MockAdapter(axios); 117 118 // Mock API endpoints 119 mockAxios.onGet('/api/subscriptions/current').reply(200, { subscription: mockSubscription }); 120 mockAxios.onGet('/api/subscriptions/invoices').reply(200, { invoices: mockInvoices }); 121 mockAxios.onGet('/api/auth/usage-stats').reply(200, mockUsageStats); 122 mockAxios.onGet('/api/subscriptions/payment-methods').reply(200, { payment_methods: mockPaymentMethods }); 123 }); 124 125 afterEach(() => { 126 mockAxios.restore(); 127 }); 128 129 test('renders subscription information correctly', async () => { 130 render( 131 <Router> 132 <BillingPage /> 133 </Router> 134 ); 135 136 // Wait for data to load 137 await waitFor(() => { 138 expect(screen.getByText('Current Subscription')).toBeInTheDocument(); 139 }); 140 141 // Check that subscription details are displayed 142 expect(screen.getByText('Basic Plan')).toBeInTheDocument(); 143 expect(screen.getByText('Active')).toBeInTheDocument(); 144 145 // Check that usage stats are displayed 146 expect(screen.getByText('API Requests Today')).toBeInTheDocument(); 147 expect(screen.getByText('25')).toBeInTheDocument(); 148 expect(screen.getByText('150')).toBeInTheDocument(); 149 }); 150 151 test('can switch between tabs', async () => { 152 render( 153 <Router> 154 <BillingPage /> 155 </Router> 156 ); 157 158 // Wait for data to load 159 await waitFor(() => { 160 expect(screen.getByText('Current Subscription')).toBeInTheDocument(); 161 }); 162 163 // Switch to Payment Methods tab 164 fireEvent.click(screen.getByText('Payment Methods')); 165 166 // Check that payment methods tab is displayed 167 await waitFor(() => { 168 expect(screen.getByText('•••• 4242')).toBeInTheDocument(); 169 }); 170 171 // Switch to Billing History tab 172 fireEvent.click(screen.getByText('Billing History')); 173 174 // Check that invoices are displayed 175 await waitFor(() => { 176 expect(screen.getByText('Invoice INV-001')).toBeInTheDocument(); 177 }); 178 }); 179 180 test('handles cancellation of subscription', async () => { 181 // Mock cancel API endpoint 182 mockAxios.onPost('/api/subscriptions/cancel').reply(200, { 183 subscription: { 184 ...mockSubscription, 185 cancel_at_period_end: true, 186 } 187 }); 188 189 const confirmSpy = jest.spyOn(window, 'confirm'); 190 confirmSpy.mockImplementation(() => true); 191 192 render( 193 <Router> 194 <BillingPage /> 195 </Router> 196 ); 197 198 // Wait for data to load 199 await waitFor(() => { 200 expect(screen.getByText('Current Subscription')).toBeInTheDocument(); 201 }); 202 203 // Click on Cancel Subscription 204 fireEvent.click(screen.getByText('Cancel Subscription')); 205 206 // Confirm the cancellation 207 expect(confirmSpy).toHaveBeenCalled(); 208 209 // Check that cancellation message is displayed 210 await waitFor(() => { 211 expect(screen.getByText(/Your subscription will be canceled/)).toBeInTheDocument(); 212 }); 213 214 confirmSpy.mockRestore(); 215 }); 216 }); 217 218 describe('SubscriptionPage Component', () => { 219 let mockAxios: MockAdapter; 220 221 beforeEach(() => { 222 mockAxios = new MockAdapter(axios); 223 224 // Mock API endpoints 225 mockAxios.onGet('/api/subscriptions/current').reply(200, { subscription: mockSubscription }); 226 mockAxios.onPost('/api/subscriptions/create-subscription').reply(200, { 227 clientSecret: 'pi_1234_secret_5678', 228 subscription: { id: 'sub_new', status: 'incomplete' } 229 }); 230 }); 231 232 afterEach(() => { 233 mockAxios.restore(); 234 }); 235 236 test('renders subscription plans correctly', async () => { 237 render( 238 <Elements stripe={loadStripe('pk_test_xxx')}> 239 <SubscriptionPage /> 240 </Elements> 241 ); 242 243 // Check that plans are displayed 244 expect(screen.getByText('Free')).toBeInTheDocument(); 245 expect(screen.getByText('Basic')).toBeInTheDocument(); 246 expect(screen.getByText('Premium')).toBeInTheDocument(); 247 expect(screen.getByText('Enterprise')).toBeInTheDocument(); 248 }); 249 250 test('can select a plan and open checkout', async () => { 251 render( 252 <Elements stripe={loadStripe('pk_test_xxx')}> 253 <SubscriptionPage /> 254 </Elements> 255 ); 256 257 // Find and click on the Premium plan 258 const premiumButtons = screen.getAllByText('Select Plan'); 259 fireEvent.click(premiumButtons[1]); // Premium plan button 260 261 // Check that checkout form is displayed 262 await waitFor(() => { 263 expect(screen.getByText(/Subscribe to Premium Plan/)).toBeInTheDocument(); 264 expect(screen.getByTestId('card-element-mock')).toBeInTheDocument(); 265 }); 266 }); 267 }); 268 269 describe('PaymentMethodManager Component', () => { 270 let mockAxios: MockAdapter; 271 272 beforeEach(() => { 273 mockAxios = new MockAdapter(axios); 274 275 // Mock API endpoints 276 mockAxios.onGet('/api/subscriptions/payment-methods').reply(200, { payment_methods: mockPaymentMethods }); 277 mockAxios.onPost('/api/subscriptions/update-payment-method').reply(200, { 278 success: true, 279 payment_method: { 280 id: 'pm_new', 281 type: 'card', 282 last4: '1234', 283 brand: 'mastercard', 284 exp_month: 3, 285 exp_year: 2024 286 } 287 }); 288 mockAxios.onDelete('/api/subscriptions/payment-methods/pm_123456789').reply(200, { success: true }); 289 }); 290 291 afterEach(() => { 292 mockAxios.restore(); 293 }); 294 295 test('renders payment methods correctly', async () => { 296 render( 297 <Elements stripe={loadStripe('pk_test_xxx')}> 298 <PaymentMethodManager /> 299 </Elements> 300 ); 301 302 // Wait for data to load 303 await waitFor(() => { 304 expect(screen.getByText('•••• 4242')).toBeInTheDocument(); 305 }); 306 307 // Check that card details are displayed 308 expect(screen.getByText(/Visa/)).toBeInTheDocument(); 309 expect(screen.getByText(/Default/)).toBeInTheDocument(); 310 }); 311 312 test('can add a new payment method', async () => { 313 render( 314 <Elements stripe={loadStripe('pk_test_xxx')}> 315 <PaymentMethodManager /> 316 </Elements> 317 ); 318 319 // Wait for data to load 320 await waitFor(() => { 321 expect(screen.getByText('Add Payment Method')).toBeInTheDocument(); 322 }); 323 324 // Click on Add Payment Method 325 fireEvent.click(screen.getByText('Add Payment Method')); 326 327 // Check that form is displayed 328 await waitFor(() => { 329 expect(screen.getByText('Add New Payment Method')).toBeInTheDocument(); 330 expect(screen.getByTestId('card-element-mock')).toBeInTheDocument(); 331 }); 332 }); 333 334 test('can delete a payment method', async () => { 335 const confirmSpy = jest.spyOn(window, 'confirm'); 336 confirmSpy.mockImplementation(() => true); 337 338 render( 339 <Elements stripe={loadStripe('pk_test_xxx')}> 340 <PaymentMethodManager /> 341 </Elements> 342 ); 343 344 // Wait for data to load 345 await waitFor(() => { 346 expect(screen.getByText('Remove')).toBeInTheDocument(); 347 }); 348 349 // Click on Remove 350 fireEvent.click(screen.getByText('Remove')); 351 352 // Confirm the deletion 353 expect(confirmSpy).toHaveBeenCalled(); 354 355 // Check that API was called 356 await waitFor(() => { 357 expect(mockAxios.history.delete.length).toBe(1); 358 expect(mockAxios.history.delete[0].url).toBe('/api/subscriptions/payment-methods/pm_123456789'); 359 }); 360 // Check that payment method is removed 361 await waitFor(() => { 362 expect(screen.queryByText('•••• 4242')).not.toBeInTheDocument(); 363 }); 364 confirmSpy.mockRestore(); 365 }); 366 367 test('handles errors when adding a payment method', async () => { 368 // Mock error response 369 mockAxios.onPost('/api/subscriptions/update-payment-method').reply(400, { 370 error: 'Invalid card details', 371 }); 372 373 render( 374 <Elements stripe={loadStripe('pk_test_xxx')}> 375 <PaymentMethodManager /> 376 </Elements> 377 ); 378 379 // Wait for data to load 380 await waitFor(() => { 381 expect(screen.getByText('Add Payment Method')).toBeInTheDocument(); 382 }); 383 384 // Click on Add Payment Method 385 fireEvent.click(screen.getByText('Add Payment Method')); 386 387 // Simulate form submission 388 fireEvent.click(screen.getByText('Submit')); 389 390 // Check that error message is displayed 391 await waitFor(() => { 392 expect(screen.getByText('Invalid card details')).toBeInTheDocument(); 393 }); 394 }); 395 });