/ frontend / test_frontend_stripe.txt
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  });