import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { renderHook, waitFor } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import React from 'react'; import apiClient from '../../api/client'; import { useCommunicationCredits, useCreditTransactions, useUpdateCreditsSettings, useAddCredits, useCreatePaymentIntent, useConfirmPayment, useSetupPaymentMethod, useSavePaymentMethod, useCommunicationUsageStats, usePhoneNumbers, useSearchPhoneNumbers, usePurchasePhoneNumber, useReleasePhoneNumber, useChangePhoneNumber, CommunicationCredits, CreditTransaction, ProxyPhoneNumber, AvailablePhoneNumber, } from '../useCommunicationCredits'; // Mock the API client vi.mock('../../api/client', () => ({ default: { get: vi.fn(), post: vi.fn(), patch: vi.fn(), delete: vi.fn(), }, })); describe('useCommunicationCredits', () => { let queryClient: QueryClient; let wrapper: React.FC<{ children: React.ReactNode }>; const mockCredits: CommunicationCredits = { id: 1, balance_cents: 50000, auto_reload_enabled: true, auto_reload_threshold_cents: 10000, auto_reload_amount_cents: 50000, low_balance_warning_cents: 20000, low_balance_warning_sent: false, stripe_payment_method_id: 'pm_test123', last_twilio_sync_at: '2025-12-07T10:00:00Z', total_loaded_cents: 100000, total_spent_cents: 50000, created_at: '2025-01-01T00:00:00Z', updated_at: '2025-12-07T10:00:00Z', }; const mockTransactions: CreditTransaction[] = [ { id: 1, amount_cents: 50000, balance_after_cents: 50000, transaction_type: 'manual', description: 'Manual credit purchase', reference_type: 'payment_intent', reference_id: 'pi_test123', stripe_charge_id: 'ch_test123', created_at: '2025-12-07T10:00:00Z', }, { id: 2, amount_cents: -1000, balance_after_cents: 49000, transaction_type: 'usage', description: 'SMS to +15551234567', reference_type: 'sms_message', reference_id: 'msg_123', stripe_charge_id: '', created_at: '2025-12-07T11:00:00Z', }, ]; const mockPhoneNumber: ProxyPhoneNumber = { id: 1, phone_number: '+15551234567', friendly_name: 'Main Office Line', status: 'assigned', monthly_fee_cents: 100, capabilities: { voice: true, sms: true, mms: true, }, assigned_at: '2025-12-01T00:00:00Z', last_billed_at: '2025-12-01T00:00:00Z', }; const mockAvailableNumber: AvailablePhoneNumber = { phone_number: '+15559876543', friendly_name: '(555) 987-6543', locality: 'New York', region: 'NY', postal_code: '10001', capabilities: { voice: true, sms: true, mms: true, }, monthly_cost_cents: 100, }; beforeEach(() => { queryClient = new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false }, }, }); wrapper = ({ children }) => React.createElement(QueryClientProvider, { client: queryClient }, children); vi.clearAllMocks(); }); afterEach(() => { queryClient.clear(); }); describe('useCommunicationCredits', () => { it('should fetch communication credits successfully', async () => { vi.mocked(apiClient.get).mockResolvedValueOnce({ data: mockCredits }); const { result } = renderHook(() => useCommunicationCredits(), { wrapper }); await waitFor(() => expect(result.current.isSuccess).toBe(true)); expect(apiClient.get).toHaveBeenCalledWith('/communication-credits/'); expect(result.current.data).toEqual(mockCredits); expect(result.current.isLoading).toBe(false); expect(result.current.error).toBeNull(); }); it('should handle fetch errors', async () => { const mockError = new Error('Failed to fetch credits'); vi.mocked(apiClient.get).mockRejectedValueOnce(mockError); const { result } = renderHook(() => useCommunicationCredits(), { wrapper }); await waitFor(() => expect(result.current.isError).toBe(true)); expect(result.current.data).toBeUndefined(); expect(result.current.error).toEqual(mockError); }); it('should use correct query key', async () => { vi.mocked(apiClient.get).mockResolvedValueOnce({ data: mockCredits }); const { result } = renderHook(() => useCommunicationCredits(), { wrapper }); await waitFor(() => expect(result.current.isSuccess).toBe(true)); const cachedData = queryClient.getQueryData(['communicationCredits']); expect(cachedData).toEqual(mockCredits); }); it('should have staleTime of 30 seconds', async () => { vi.mocked(apiClient.get).mockResolvedValueOnce({ data: mockCredits }); const { result } = renderHook(() => useCommunicationCredits(), { wrapper }); await waitFor(() => expect(result.current.isSuccess).toBe(true)); const queryState = queryClient.getQueryState(['communicationCredits']); expect(queryState?.dataUpdatedAt).toBeDefined(); }); }); describe('useCreditTransactions', () => { it('should fetch credit transactions with pagination', async () => { const mockResponse = { results: mockTransactions, count: 2, next: null, previous: null, }; vi.mocked(apiClient.get).mockResolvedValueOnce({ data: mockResponse }); const { result } = renderHook(() => useCreditTransactions(1, 20), { wrapper }); await waitFor(() => expect(result.current.isSuccess).toBe(true)); expect(apiClient.get).toHaveBeenCalledWith('/communication-credits/transactions/', { params: { page: 1, limit: 20 }, }); expect(result.current.data).toEqual(mockResponse); }); it('should support custom page and limit', async () => { const mockResponse = { results: [mockTransactions[0]], count: 10, next: 'http://api.example.com/page=3', previous: 'http://api.example.com/page=1', }; vi.mocked(apiClient.get).mockResolvedValueOnce({ data: mockResponse }); const { result } = renderHook(() => useCreditTransactions(2, 10), { wrapper }); await waitFor(() => expect(result.current.isSuccess).toBe(true)); expect(apiClient.get).toHaveBeenCalledWith('/communication-credits/transactions/', { params: { page: 2, limit: 10 }, }); }); it('should handle fetch errors', async () => { const mockError = new Error('Failed to fetch transactions'); vi.mocked(apiClient.get).mockRejectedValueOnce(mockError); const { result } = renderHook(() => useCreditTransactions(), { wrapper }); await waitFor(() => expect(result.current.isError).toBe(true)); expect(result.current.error).toEqual(mockError); }); }); describe('useUpdateCreditsSettings', () => { it('should update credit settings successfully', async () => { const updatedCredits = { ...mockCredits, auto_reload_enabled: false, auto_reload_threshold_cents: 5000, }; vi.mocked(apiClient.patch).mockResolvedValueOnce({ data: updatedCredits }); const { result } = renderHook(() => useUpdateCreditsSettings(), { wrapper }); result.current.mutate({ auto_reload_enabled: false, auto_reload_threshold_cents: 5000, }); await waitFor(() => expect(result.current.isSuccess).toBe(true)); expect(apiClient.patch).toHaveBeenCalledWith('/communication-credits/settings/', { auto_reload_enabled: false, auto_reload_threshold_cents: 5000, }); expect(result.current.data).toEqual(updatedCredits); }); it('should update query cache on success', async () => { const updatedCredits = { ...mockCredits, auto_reload_enabled: false }; queryClient.setQueryData(['communicationCredits'], mockCredits); vi.mocked(apiClient.patch).mockResolvedValueOnce({ data: updatedCredits }); const { result } = renderHook(() => useUpdateCreditsSettings(), { wrapper }); result.current.mutate({ auto_reload_enabled: false }); await waitFor(() => expect(result.current.isSuccess).toBe(true)); const cachedData = queryClient.getQueryData(['communicationCredits']); expect(cachedData).toEqual(updatedCredits); }); it('should handle update errors', async () => { const mockError = new Error('Failed to update settings'); vi.mocked(apiClient.patch).mockRejectedValueOnce(mockError); const { result } = renderHook(() => useUpdateCreditsSettings(), { wrapper }); result.current.mutate({ auto_reload_enabled: false }); await waitFor(() => expect(result.current.isError).toBe(true)); expect(result.current.error).toEqual(mockError); }); }); describe('useAddCredits', () => { it('should add credits successfully', async () => { const mockResponse = { success: true, balance_cents: 100000, transaction_id: 123, }; vi.mocked(apiClient.post).mockResolvedValueOnce({ data: mockResponse }); const { result } = renderHook(() => useAddCredits(), { wrapper }); result.current.mutate({ amount_cents: 50000, payment_method_id: 'pm_test123', }); await waitFor(() => expect(result.current.isSuccess).toBe(true)); expect(apiClient.post).toHaveBeenCalledWith('/communication-credits/add/', { amount_cents: 50000, payment_method_id: 'pm_test123', }); expect(result.current.data).toEqual(mockResponse); }); it('should invalidate credits and transactions queries on success', async () => { const mockResponse = { success: true, balance_cents: 100000 }; vi.mocked(apiClient.post).mockResolvedValueOnce({ data: mockResponse }); const invalidateSpy = vi.spyOn(queryClient, 'invalidateQueries'); const { result } = renderHook(() => useAddCredits(), { wrapper }); result.current.mutate({ amount_cents: 50000 }); await waitFor(() => expect(result.current.isSuccess).toBe(true)); expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['communicationCredits'] }); expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['creditTransactions'] }); }); it('should handle add credits errors', async () => { const mockError = new Error('Payment failed'); vi.mocked(apiClient.post).mockRejectedValueOnce(mockError); const { result } = renderHook(() => useAddCredits(), { wrapper }); result.current.mutate({ amount_cents: 50000 }); await waitFor(() => expect(result.current.isError).toBe(true)); expect(result.current.error).toEqual(mockError); }); }); describe('useCreatePaymentIntent', () => { it('should create payment intent successfully', async () => { const mockResponse = { client_secret: 'pi_test_secret', payment_intent_id: 'pi_test123', }; vi.mocked(apiClient.post).mockResolvedValueOnce({ data: mockResponse }); const { result } = renderHook(() => useCreatePaymentIntent(), { wrapper }); result.current.mutate(50000); await waitFor(() => expect(result.current.isSuccess).toBe(true)); expect(apiClient.post).toHaveBeenCalledWith('/communication-credits/create-payment-intent/', { amount_cents: 50000, }); expect(result.current.data).toEqual(mockResponse); }); it('should handle payment intent creation errors', async () => { const mockError = new Error('Failed to create payment intent'); vi.mocked(apiClient.post).mockRejectedValueOnce(mockError); const { result } = renderHook(() => useCreatePaymentIntent(), { wrapper }); result.current.mutate(50000); await waitFor(() => expect(result.current.isError).toBe(true)); expect(result.current.error).toEqual(mockError); }); }); describe('useConfirmPayment', () => { it('should confirm payment successfully', async () => { const mockResponse = { success: true, balance_cents: 100000, transaction_id: 123, }; vi.mocked(apiClient.post).mockResolvedValueOnce({ data: mockResponse }); const { result } = renderHook(() => useConfirmPayment(), { wrapper }); result.current.mutate({ payment_intent_id: 'pi_test123', save_payment_method: true, }); await waitFor(() => expect(result.current.isSuccess).toBe(true)); expect(apiClient.post).toHaveBeenCalledWith('/communication-credits/confirm-payment/', { payment_intent_id: 'pi_test123', save_payment_method: true, }); expect(result.current.data).toEqual(mockResponse); }); it('should invalidate credits and transactions queries on success', async () => { const mockResponse = { success: true }; vi.mocked(apiClient.post).mockResolvedValueOnce({ data: mockResponse }); const invalidateSpy = vi.spyOn(queryClient, 'invalidateQueries'); const { result } = renderHook(() => useConfirmPayment(), { wrapper }); result.current.mutate({ payment_intent_id: 'pi_test123' }); await waitFor(() => expect(result.current.isSuccess).toBe(true)); expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['communicationCredits'] }); expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['creditTransactions'] }); }); it('should handle confirmation errors', async () => { const mockError = new Error('Payment confirmation failed'); vi.mocked(apiClient.post).mockRejectedValueOnce(mockError); const { result } = renderHook(() => useConfirmPayment(), { wrapper }); result.current.mutate({ payment_intent_id: 'pi_test123' }); await waitFor(() => expect(result.current.isError).toBe(true)); expect(result.current.error).toEqual(mockError); }); }); describe('useSetupPaymentMethod', () => { it('should setup payment method successfully', async () => { const mockResponse = { client_secret: 'seti_test_secret', }; vi.mocked(apiClient.post).mockResolvedValueOnce({ data: mockResponse }); const { result } = renderHook(() => useSetupPaymentMethod(), { wrapper }); result.current.mutate(); await waitFor(() => expect(result.current.isSuccess).toBe(true)); expect(apiClient.post).toHaveBeenCalledWith('/communication-credits/setup-payment-method/'); expect(result.current.data).toEqual(mockResponse); }); it('should handle setup errors', async () => { const mockError = new Error('Failed to setup payment method'); vi.mocked(apiClient.post).mockRejectedValueOnce(mockError); const { result } = renderHook(() => useSetupPaymentMethod(), { wrapper }); result.current.mutate(); await waitFor(() => expect(result.current.isError).toBe(true)); expect(result.current.error).toEqual(mockError); }); }); describe('useSavePaymentMethod', () => { it('should save payment method successfully', async () => { const mockResponse = { success: true, payment_method_id: 'pm_test123', }; vi.mocked(apiClient.post).mockResolvedValueOnce({ data: mockResponse }); const { result } = renderHook(() => useSavePaymentMethod(), { wrapper }); result.current.mutate('pm_test123'); await waitFor(() => expect(result.current.isSuccess).toBe(true)); expect(apiClient.post).toHaveBeenCalledWith('/communication-credits/save-payment-method/', { payment_method_id: 'pm_test123', }); expect(result.current.data).toEqual(mockResponse); }); it('should invalidate credits query on success', async () => { const mockResponse = { success: true }; vi.mocked(apiClient.post).mockResolvedValueOnce({ data: mockResponse }); const invalidateSpy = vi.spyOn(queryClient, 'invalidateQueries'); const { result } = renderHook(() => useSavePaymentMethod(), { wrapper }); result.current.mutate('pm_test123'); await waitFor(() => expect(result.current.isSuccess).toBe(true)); expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['communicationCredits'] }); }); it('should handle save errors', async () => { const mockError = new Error('Failed to save payment method'); vi.mocked(apiClient.post).mockRejectedValueOnce(mockError); const { result } = renderHook(() => useSavePaymentMethod(), { wrapper }); result.current.mutate('pm_test123'); await waitFor(() => expect(result.current.isError).toBe(true)); expect(result.current.error).toEqual(mockError); }); }); describe('useCommunicationUsageStats', () => { it('should fetch usage stats successfully', async () => { const mockStats = { sms_sent_this_month: 150, voice_minutes_this_month: 45.5, proxy_numbers_active: 2, estimated_cost_cents: 2500, }; vi.mocked(apiClient.get).mockResolvedValueOnce({ data: mockStats }); const { result } = renderHook(() => useCommunicationUsageStats(), { wrapper }); await waitFor(() => expect(result.current.isSuccess).toBe(true)); expect(apiClient.get).toHaveBeenCalledWith('/communication-credits/usage-stats/'); expect(result.current.data).toEqual(mockStats); }); it('should handle fetch errors', async () => { const mockError = new Error('Failed to fetch stats'); vi.mocked(apiClient.get).mockRejectedValueOnce(mockError); const { result } = renderHook(() => useCommunicationUsageStats(), { wrapper }); await waitFor(() => expect(result.current.isError).toBe(true)); expect(result.current.error).toEqual(mockError); }); it('should use correct query key', async () => { const mockStats = { sms_sent_this_month: 150, voice_minutes_this_month: 45.5, proxy_numbers_active: 2, estimated_cost_cents: 2500, }; vi.mocked(apiClient.get).mockResolvedValueOnce({ data: mockStats }); const { result } = renderHook(() => useCommunicationUsageStats(), { wrapper }); await waitFor(() => expect(result.current.isSuccess).toBe(true)); const cachedData = queryClient.getQueryData(['communicationUsageStats']); expect(cachedData).toEqual(mockStats); }); }); describe('usePhoneNumbers', () => { it('should fetch phone numbers successfully', async () => { const mockResponse = { numbers: [mockPhoneNumber], count: 1, }; vi.mocked(apiClient.get).mockResolvedValueOnce({ data: mockResponse }); const { result } = renderHook(() => usePhoneNumbers(), { wrapper }); await waitFor(() => expect(result.current.isSuccess).toBe(true)); expect(apiClient.get).toHaveBeenCalledWith('/communication-credits/phone-numbers/'); expect(result.current.data).toEqual(mockResponse); }); it('should handle fetch errors', async () => { const mockError = new Error('Failed to fetch phone numbers'); vi.mocked(apiClient.get).mockRejectedValueOnce(mockError); const { result } = renderHook(() => usePhoneNumbers(), { wrapper }); await waitFor(() => expect(result.current.isError).toBe(true)); expect(result.current.error).toEqual(mockError); }); }); describe('useSearchPhoneNumbers', () => { it('should search phone numbers successfully', async () => { const mockResponse = { numbers: [mockAvailableNumber], count: 1, }; vi.mocked(apiClient.get).mockResolvedValueOnce({ data: mockResponse }); const { result } = renderHook(() => useSearchPhoneNumbers(), { wrapper }); result.current.mutate({ area_code: '555', country: 'US', limit: 10, }); await waitFor(() => expect(result.current.isSuccess).toBe(true)); expect(apiClient.get).toHaveBeenCalledWith('/communication-credits/phone-numbers/search/', { params: { area_code: '555', country: 'US', limit: 10, }, }); expect(result.current.data).toEqual(mockResponse); }); it('should support contains parameter', async () => { const mockResponse = { numbers: [mockAvailableNumber], count: 1, }; vi.mocked(apiClient.get).mockResolvedValueOnce({ data: mockResponse }); const { result } = renderHook(() => useSearchPhoneNumbers(), { wrapper }); result.current.mutate({ contains: '123', country: 'US', }); await waitFor(() => expect(result.current.isSuccess).toBe(true)); expect(apiClient.get).toHaveBeenCalledWith('/communication-credits/phone-numbers/search/', { params: { contains: '123', country: 'US', }, }); }); it('should handle search errors', async () => { const mockError = new Error('Search failed'); vi.mocked(apiClient.get).mockRejectedValueOnce(mockError); const { result } = renderHook(() => useSearchPhoneNumbers(), { wrapper }); result.current.mutate({ area_code: '555' }); await waitFor(() => expect(result.current.isError).toBe(true)); expect(result.current.error).toEqual(mockError); }); }); describe('usePurchasePhoneNumber', () => { it('should purchase phone number successfully', async () => { const mockResponse = { success: true, phone_number: mockPhoneNumber, balance_cents: 49900, }; vi.mocked(apiClient.post).mockResolvedValueOnce({ data: mockResponse }); const { result } = renderHook(() => usePurchasePhoneNumber(), { wrapper }); result.current.mutate({ phone_number: '+15551234567', friendly_name: 'Main Office Line', }); await waitFor(() => expect(result.current.isSuccess).toBe(true)); expect(apiClient.post).toHaveBeenCalledWith('/communication-credits/phone-numbers/purchase/', { phone_number: '+15551234567', friendly_name: 'Main Office Line', }); expect(result.current.data).toEqual(mockResponse); }); it('should invalidate queries on success', async () => { const mockResponse = { success: true, phone_number: mockPhoneNumber, balance_cents: 49900, }; vi.mocked(apiClient.post).mockResolvedValueOnce({ data: mockResponse }); const invalidateSpy = vi.spyOn(queryClient, 'invalidateQueries'); const { result } = renderHook(() => usePurchasePhoneNumber(), { wrapper }); result.current.mutate({ phone_number: '+15551234567' }); await waitFor(() => expect(result.current.isSuccess).toBe(true)); expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['phoneNumbers'] }); expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['communicationCredits'] }); expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['creditTransactions'] }); }); it('should handle purchase errors', async () => { const mockError = new Error('Insufficient credits'); vi.mocked(apiClient.post).mockRejectedValueOnce(mockError); const { result } = renderHook(() => usePurchasePhoneNumber(), { wrapper }); result.current.mutate({ phone_number: '+15551234567' }); await waitFor(() => expect(result.current.isError).toBe(true)); expect(result.current.error).toEqual(mockError); }); }); describe('useReleasePhoneNumber', () => { it('should release phone number successfully', async () => { const mockResponse = { success: true, message: 'Phone number released successfully', }; vi.mocked(apiClient.delete).mockResolvedValueOnce({ data: mockResponse }); const { result } = renderHook(() => useReleasePhoneNumber(), { wrapper }); result.current.mutate(1); await waitFor(() => expect(result.current.isSuccess).toBe(true)); expect(apiClient.delete).toHaveBeenCalledWith('/communication-credits/phone-numbers/1/'); expect(result.current.data).toEqual(mockResponse); }); it('should invalidate queries on success', async () => { const mockResponse = { success: true, message: 'Phone number released successfully', }; vi.mocked(apiClient.delete).mockResolvedValueOnce({ data: mockResponse }); const invalidateSpy = vi.spyOn(queryClient, 'invalidateQueries'); const { result } = renderHook(() => useReleasePhoneNumber(), { wrapper }); result.current.mutate(1); await waitFor(() => expect(result.current.isSuccess).toBe(true)); expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['phoneNumbers'] }); expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['communicationUsageStats'] }); }); it('should handle release errors', async () => { const mockError = new Error('Failed to release phone number'); vi.mocked(apiClient.delete).mockRejectedValueOnce(mockError); const { result } = renderHook(() => useReleasePhoneNumber(), { wrapper }); result.current.mutate(1); await waitFor(() => expect(result.current.isError).toBe(true)); expect(result.current.error).toEqual(mockError); }); }); describe('useChangePhoneNumber', () => { it('should change phone number successfully', async () => { const newPhoneNumber = { ...mockPhoneNumber, phone_number: '+15559876543', friendly_name: 'Updated Office Line', }; const mockResponse = { success: true, phone_number: newPhoneNumber, balance_cents: 49900, }; vi.mocked(apiClient.post).mockResolvedValueOnce({ data: mockResponse }); const { result } = renderHook(() => useChangePhoneNumber(), { wrapper }); result.current.mutate({ numberId: 1, new_phone_number: '+15559876543', friendly_name: 'Updated Office Line', }); await waitFor(() => expect(result.current.isSuccess).toBe(true)); expect(apiClient.post).toHaveBeenCalledWith('/communication-credits/phone-numbers/1/change/', { new_phone_number: '+15559876543', friendly_name: 'Updated Office Line', }); expect(result.current.data).toEqual(mockResponse); }); it('should invalidate queries on success', async () => { const mockResponse = { success: true, phone_number: mockPhoneNumber, balance_cents: 49900, }; vi.mocked(apiClient.post).mockResolvedValueOnce({ data: mockResponse }); const invalidateSpy = vi.spyOn(queryClient, 'invalidateQueries'); const { result } = renderHook(() => useChangePhoneNumber(), { wrapper }); result.current.mutate({ numberId: 1, new_phone_number: '+15559876543', }); await waitFor(() => expect(result.current.isSuccess).toBe(true)); expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['phoneNumbers'] }); expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['communicationCredits'] }); expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['creditTransactions'] }); }); it('should handle change errors', async () => { const mockError = new Error('Failed to change phone number'); vi.mocked(apiClient.post).mockRejectedValueOnce(mockError); const { result } = renderHook(() => useChangePhoneNumber(), { wrapper }); result.current.mutate({ numberId: 1, new_phone_number: '+15559876543', }); await waitFor(() => expect(result.current.isError).toBe(true)); expect(result.current.error).toEqual(mockError); }); it('should exclude numberId from request body', async () => { const mockResponse = { success: true, phone_number: mockPhoneNumber, balance_cents: 49900, }; vi.mocked(apiClient.post).mockResolvedValueOnce({ data: mockResponse }); const { result } = renderHook(() => useChangePhoneNumber(), { wrapper }); result.current.mutate({ numberId: 1, new_phone_number: '+15559876543', }); await waitFor(() => expect(result.current.isSuccess).toBe(true)); // Verify numberId is NOT in the request body expect(apiClient.post).toHaveBeenCalledWith('/communication-credits/phone-numbers/1/change/', { new_phone_number: '+15559876543', }); }); }); describe('Integration Tests', () => { it('should update credits after adding credits', async () => { const initialCredits = mockCredits; const updatedCredits = { ...mockCredits, balance_cents: 100000 }; // Initial fetch vi.mocked(apiClient.get).mockResolvedValueOnce({ data: initialCredits }); const { result: creditsResult } = renderHook(() => useCommunicationCredits(), { wrapper }); await waitFor(() => expect(creditsResult.current.isSuccess).toBe(true)); expect(creditsResult.current.data?.balance_cents).toBe(50000); // Add credits vi.mocked(apiClient.post).mockResolvedValueOnce({ data: { success: true } }); vi.mocked(apiClient.get).mockResolvedValueOnce({ data: updatedCredits }); const { result: addResult } = renderHook(() => useAddCredits(), { wrapper }); addResult.current.mutate({ amount_cents: 50000 }); await waitFor(() => expect(addResult.current.isSuccess).toBe(true)); // Refetch credits await creditsResult.current.refetch(); expect(creditsResult.current.data?.balance_cents).toBe(100000); }); it('should update phone numbers list after purchasing', async () => { const initialResponse = { numbers: [], count: 0 }; const updatedResponse = { numbers: [mockPhoneNumber], count: 1 }; // Initial fetch vi.mocked(apiClient.get).mockResolvedValueOnce({ data: initialResponse }); const { result: numbersResult } = renderHook(() => usePhoneNumbers(), { wrapper }); await waitFor(() => expect(numbersResult.current.isSuccess).toBe(true)); expect(numbersResult.current.data?.count).toBe(0); // Purchase number vi.mocked(apiClient.post).mockResolvedValueOnce({ data: { success: true, phone_number: mockPhoneNumber, balance_cents: 49900, }, }); vi.mocked(apiClient.get).mockResolvedValueOnce({ data: updatedResponse }); const { result: purchaseResult } = renderHook(() => usePurchasePhoneNumber(), { wrapper }); purchaseResult.current.mutate({ phone_number: '+15551234567' }); await waitFor(() => expect(purchaseResult.current.isSuccess).toBe(true)); // Refetch numbers await numbersResult.current.refetch(); expect(numbersResult.current.data?.count).toBe(1); expect(numbersResult.current.data?.numbers[0]).toEqual(mockPhoneNumber); }); }); });