- Add frontend unit tests with Vitest for components, hooks, pages, and utilities - Add backend tests for webhooks, notifications, middleware, and edge cases - Add ForgotPassword, NotFound, and ResetPassword pages - Add migration for orphaned staff resources conversion - Add coverage directory to gitignore (generated reports) - Various bug fixes and improvements from previous work 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
922 lines
26 KiB
TypeScript
922 lines
26 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import { renderHook, waitFor, act } from '@testing-library/react';
|
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
import React from 'react';
|
|
|
|
// Mock the business API
|
|
vi.mock('../../api/business', () => ({
|
|
getBusinessOAuthCredentials: vi.fn(),
|
|
updateBusinessOAuthCredentials: vi.fn(),
|
|
}));
|
|
|
|
import {
|
|
useBusinessOAuthCredentials,
|
|
useUpdateBusinessOAuthCredentials,
|
|
} from '../useBusinessOAuthCredentials';
|
|
import * as businessApi from '../../api/business';
|
|
|
|
// Create wrapper for React Query
|
|
const createWrapper = () => {
|
|
const queryClient = new QueryClient({
|
|
defaultOptions: {
|
|
queries: { retry: false },
|
|
mutations: { retry: false },
|
|
},
|
|
});
|
|
|
|
return function Wrapper({ children }: { children: React.ReactNode }) {
|
|
return React.createElement(QueryClientProvider, { client: queryClient }, children);
|
|
};
|
|
};
|
|
|
|
describe('useBusinessOAuthCredentials hooks', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
describe('useBusinessOAuthCredentials', () => {
|
|
it('fetches business OAuth credentials successfully', async () => {
|
|
const mockResponse = {
|
|
credentials: {
|
|
google: {
|
|
client_id: 'google-client-id-123',
|
|
client_secret: 'google-client-secret-456',
|
|
has_secret: true,
|
|
},
|
|
microsoft: {
|
|
client_id: 'microsoft-client-id-789',
|
|
client_secret: 'microsoft-client-secret-012',
|
|
has_secret: true,
|
|
},
|
|
},
|
|
useCustomCredentials: true,
|
|
};
|
|
|
|
vi.mocked(businessApi.getBusinessOAuthCredentials).mockResolvedValue(mockResponse);
|
|
|
|
const { result } = renderHook(() => useBusinessOAuthCredentials(), {
|
|
wrapper: createWrapper(),
|
|
});
|
|
|
|
// Initially loading
|
|
expect(result.current.isLoading).toBe(true);
|
|
|
|
// Wait for success
|
|
await waitFor(() => {
|
|
expect(result.current.isSuccess).toBe(true);
|
|
});
|
|
|
|
expect(businessApi.getBusinessOAuthCredentials).toHaveBeenCalledTimes(1);
|
|
expect(result.current.data).toEqual(mockResponse);
|
|
expect(result.current.data?.credentials.google.client_id).toBe('google-client-id-123');
|
|
expect(result.current.data?.credentials.google.has_secret).toBe(true);
|
|
expect(result.current.data?.credentials.microsoft.client_id).toBe('microsoft-client-id-789');
|
|
expect(result.current.data?.useCustomCredentials).toBe(true);
|
|
});
|
|
|
|
it('handles empty credentials', async () => {
|
|
const mockResponse = {
|
|
credentials: {},
|
|
useCustomCredentials: false,
|
|
};
|
|
|
|
vi.mocked(businessApi.getBusinessOAuthCredentials).mockResolvedValue(mockResponse);
|
|
|
|
const { result } = renderHook(() => useBusinessOAuthCredentials(), {
|
|
wrapper: createWrapper(),
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.isSuccess).toBe(true);
|
|
});
|
|
|
|
expect(result.current.data?.credentials).toEqual({});
|
|
expect(result.current.data?.useCustomCredentials).toBe(false);
|
|
});
|
|
|
|
it('handles credentials with has_secret false', async () => {
|
|
const mockResponse = {
|
|
credentials: {
|
|
google: {
|
|
client_id: 'google-client-id-123',
|
|
client_secret: '',
|
|
has_secret: false,
|
|
},
|
|
},
|
|
useCustomCredentials: true,
|
|
};
|
|
|
|
vi.mocked(businessApi.getBusinessOAuthCredentials).mockResolvedValue(mockResponse);
|
|
|
|
const { result } = renderHook(() => useBusinessOAuthCredentials(), {
|
|
wrapper: createWrapper(),
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.isSuccess).toBe(true);
|
|
});
|
|
|
|
expect(result.current.data?.credentials.google.has_secret).toBe(false);
|
|
expect(result.current.data?.credentials.google.client_secret).toBe('');
|
|
});
|
|
|
|
it('handles multiple providers with mixed credential states', async () => {
|
|
const mockResponse = {
|
|
credentials: {
|
|
google: {
|
|
client_id: 'google-client-id',
|
|
client_secret: 'google-secret',
|
|
has_secret: true,
|
|
},
|
|
microsoft: {
|
|
client_id: 'microsoft-client-id',
|
|
client_secret: '',
|
|
has_secret: false,
|
|
},
|
|
github: {
|
|
client_id: '',
|
|
client_secret: '',
|
|
has_secret: false,
|
|
},
|
|
},
|
|
useCustomCredentials: true,
|
|
};
|
|
|
|
vi.mocked(businessApi.getBusinessOAuthCredentials).mockResolvedValue(mockResponse);
|
|
|
|
const { result } = renderHook(() => useBusinessOAuthCredentials(), {
|
|
wrapper: createWrapper(),
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.isSuccess).toBe(true);
|
|
});
|
|
|
|
expect(Object.keys(result.current.data?.credentials || {})).toHaveLength(3);
|
|
expect(result.current.data?.credentials.google.has_secret).toBe(true);
|
|
expect(result.current.data?.credentials.microsoft.has_secret).toBe(false);
|
|
expect(result.current.data?.credentials.github.has_secret).toBe(false);
|
|
});
|
|
|
|
it('handles API error gracefully', async () => {
|
|
const mockError = new Error('Failed to fetch OAuth credentials');
|
|
vi.mocked(businessApi.getBusinessOAuthCredentials).mockRejectedValue(mockError);
|
|
|
|
const { result } = renderHook(() => useBusinessOAuthCredentials(), {
|
|
wrapper: createWrapper(),
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.isError).toBe(true);
|
|
});
|
|
|
|
expect(result.current.error).toEqual(mockError);
|
|
expect(result.current.data).toBeUndefined();
|
|
});
|
|
|
|
it('does not retry on failure (404)', async () => {
|
|
vi.mocked(businessApi.getBusinessOAuthCredentials).mockRejectedValue(
|
|
new Error('404 Not Found')
|
|
);
|
|
|
|
const { result } = renderHook(() => useBusinessOAuthCredentials(), {
|
|
wrapper: createWrapper(),
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.isError).toBe(true);
|
|
});
|
|
|
|
// Should be called only once (no retries)
|
|
expect(businessApi.getBusinessOAuthCredentials).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('caches data with 5 minute stale time', async () => {
|
|
const mockResponse = {
|
|
credentials: {
|
|
google: {
|
|
client_id: 'google-client-id',
|
|
client_secret: 'google-secret',
|
|
has_secret: true,
|
|
},
|
|
},
|
|
useCustomCredentials: true,
|
|
};
|
|
|
|
vi.mocked(businessApi.getBusinessOAuthCredentials).mockResolvedValue(mockResponse);
|
|
|
|
const { result, rerender } = renderHook(() => useBusinessOAuthCredentials(), {
|
|
wrapper: createWrapper(),
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.isSuccess).toBe(true);
|
|
});
|
|
|
|
// Rerender should use cached data (within stale time)
|
|
rerender();
|
|
|
|
// Should still only be called once
|
|
expect(businessApi.getBusinessOAuthCredentials).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('handles 401 unauthorized error', async () => {
|
|
const mockError = new Error('401 Unauthorized');
|
|
vi.mocked(businessApi.getBusinessOAuthCredentials).mockRejectedValue(mockError);
|
|
|
|
const { result } = renderHook(() => useBusinessOAuthCredentials(), {
|
|
wrapper: createWrapper(),
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.isError).toBe(true);
|
|
});
|
|
|
|
expect(result.current.error).toEqual(mockError);
|
|
});
|
|
|
|
it('handles network error', async () => {
|
|
const mockError = new Error('Network Error');
|
|
vi.mocked(businessApi.getBusinessOAuthCredentials).mockRejectedValue(mockError);
|
|
|
|
const { result } = renderHook(() => useBusinessOAuthCredentials(), {
|
|
wrapper: createWrapper(),
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.isError).toBe(true);
|
|
});
|
|
|
|
expect(result.current.error).toEqual(mockError);
|
|
});
|
|
});
|
|
|
|
describe('useUpdateBusinessOAuthCredentials', () => {
|
|
it('updates credentials for a single provider successfully', async () => {
|
|
const mockResponse = {
|
|
credentials: {
|
|
google: {
|
|
client_id: 'new-google-client-id',
|
|
client_secret: 'new-google-secret',
|
|
has_secret: true,
|
|
},
|
|
},
|
|
useCustomCredentials: true,
|
|
};
|
|
|
|
vi.mocked(businessApi.updateBusinessOAuthCredentials).mockResolvedValue(mockResponse);
|
|
|
|
const { result } = renderHook(() => useUpdateBusinessOAuthCredentials(), {
|
|
wrapper: createWrapper(),
|
|
});
|
|
|
|
await act(async () => {
|
|
await result.current.mutateAsync({
|
|
credentials: {
|
|
google: {
|
|
client_id: 'new-google-client-id',
|
|
client_secret: 'new-google-secret',
|
|
},
|
|
},
|
|
});
|
|
});
|
|
|
|
expect(businessApi.updateBusinessOAuthCredentials).toHaveBeenCalledWith({
|
|
credentials: {
|
|
google: {
|
|
client_id: 'new-google-client-id',
|
|
client_secret: 'new-google-secret',
|
|
},
|
|
},
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.isSuccess).toBe(true);
|
|
});
|
|
|
|
expect(result.current.data?.credentials.google.client_id).toBe('new-google-client-id');
|
|
expect(result.current.data?.credentials.google.has_secret).toBe(true);
|
|
});
|
|
|
|
it('updates credentials for multiple providers', async () => {
|
|
const mockResponse = {
|
|
credentials: {
|
|
google: {
|
|
client_id: 'google-id',
|
|
client_secret: 'google-secret',
|
|
has_secret: true,
|
|
},
|
|
microsoft: {
|
|
client_id: 'microsoft-id',
|
|
client_secret: 'microsoft-secret',
|
|
has_secret: true,
|
|
},
|
|
},
|
|
useCustomCredentials: true,
|
|
};
|
|
|
|
vi.mocked(businessApi.updateBusinessOAuthCredentials).mockResolvedValue(mockResponse);
|
|
|
|
const { result } = renderHook(() => useUpdateBusinessOAuthCredentials(), {
|
|
wrapper: createWrapper(),
|
|
});
|
|
|
|
await act(async () => {
|
|
await result.current.mutateAsync({
|
|
credentials: {
|
|
google: {
|
|
client_id: 'google-id',
|
|
client_secret: 'google-secret',
|
|
},
|
|
microsoft: {
|
|
client_id: 'microsoft-id',
|
|
client_secret: 'microsoft-secret',
|
|
},
|
|
},
|
|
});
|
|
});
|
|
|
|
expect(businessApi.updateBusinessOAuthCredentials).toHaveBeenCalledWith({
|
|
credentials: {
|
|
google: {
|
|
client_id: 'google-id',
|
|
client_secret: 'google-secret',
|
|
},
|
|
microsoft: {
|
|
client_id: 'microsoft-id',
|
|
client_secret: 'microsoft-secret',
|
|
},
|
|
},
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.isSuccess).toBe(true);
|
|
});
|
|
|
|
expect(Object.keys(result.current.data?.credentials || {})).toHaveLength(2);
|
|
});
|
|
|
|
it('updates only client_id without client_secret', async () => {
|
|
const mockResponse = {
|
|
credentials: {
|
|
google: {
|
|
client_id: 'updated-google-id',
|
|
client_secret: 'existing-secret',
|
|
has_secret: true,
|
|
},
|
|
},
|
|
useCustomCredentials: true,
|
|
};
|
|
|
|
vi.mocked(businessApi.updateBusinessOAuthCredentials).mockResolvedValue(mockResponse);
|
|
|
|
const { result } = renderHook(() => useUpdateBusinessOAuthCredentials(), {
|
|
wrapper: createWrapper(),
|
|
});
|
|
|
|
await act(async () => {
|
|
await result.current.mutateAsync({
|
|
credentials: {
|
|
google: {
|
|
client_id: 'updated-google-id',
|
|
},
|
|
},
|
|
});
|
|
});
|
|
|
|
expect(businessApi.updateBusinessOAuthCredentials).toHaveBeenCalledWith({
|
|
credentials: {
|
|
google: {
|
|
client_id: 'updated-google-id',
|
|
},
|
|
},
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.isSuccess).toBe(true);
|
|
});
|
|
});
|
|
|
|
it('updates only client_secret without client_id', async () => {
|
|
const mockResponse = {
|
|
credentials: {
|
|
google: {
|
|
client_id: 'existing-google-id',
|
|
client_secret: 'new-secret',
|
|
has_secret: true,
|
|
},
|
|
},
|
|
useCustomCredentials: true,
|
|
};
|
|
|
|
vi.mocked(businessApi.updateBusinessOAuthCredentials).mockResolvedValue(mockResponse);
|
|
|
|
const { result } = renderHook(() => useUpdateBusinessOAuthCredentials(), {
|
|
wrapper: createWrapper(),
|
|
});
|
|
|
|
await act(async () => {
|
|
await result.current.mutateAsync({
|
|
credentials: {
|
|
google: {
|
|
client_secret: 'new-secret',
|
|
},
|
|
},
|
|
});
|
|
});
|
|
|
|
expect(businessApi.updateBusinessOAuthCredentials).toHaveBeenCalledWith({
|
|
credentials: {
|
|
google: {
|
|
client_secret: 'new-secret',
|
|
},
|
|
},
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.isSuccess).toBe(true);
|
|
});
|
|
});
|
|
|
|
it('updates useCustomCredentials flag only', async () => {
|
|
const mockResponse = {
|
|
credentials: {
|
|
google: {
|
|
client_id: 'google-id',
|
|
client_secret: 'google-secret',
|
|
has_secret: true,
|
|
},
|
|
},
|
|
useCustomCredentials: false,
|
|
};
|
|
|
|
vi.mocked(businessApi.updateBusinessOAuthCredentials).mockResolvedValue(mockResponse);
|
|
|
|
const { result } = renderHook(() => useUpdateBusinessOAuthCredentials(), {
|
|
wrapper: createWrapper(),
|
|
});
|
|
|
|
await act(async () => {
|
|
await result.current.mutateAsync({
|
|
useCustomCredentials: false,
|
|
});
|
|
});
|
|
|
|
expect(businessApi.updateBusinessOAuthCredentials).toHaveBeenCalledWith({
|
|
useCustomCredentials: false,
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.isSuccess).toBe(true);
|
|
});
|
|
|
|
expect(result.current.data?.useCustomCredentials).toBe(false);
|
|
});
|
|
|
|
it('updates both credentials and useCustomCredentials flag', async () => {
|
|
const mockResponse = {
|
|
credentials: {
|
|
google: {
|
|
client_id: 'custom-google-id',
|
|
client_secret: 'custom-google-secret',
|
|
has_secret: true,
|
|
},
|
|
},
|
|
useCustomCredentials: true,
|
|
};
|
|
|
|
vi.mocked(businessApi.updateBusinessOAuthCredentials).mockResolvedValue(mockResponse);
|
|
|
|
const { result } = renderHook(() => useUpdateBusinessOAuthCredentials(), {
|
|
wrapper: createWrapper(),
|
|
});
|
|
|
|
await act(async () => {
|
|
await result.current.mutateAsync({
|
|
credentials: {
|
|
google: {
|
|
client_id: 'custom-google-id',
|
|
client_secret: 'custom-google-secret',
|
|
},
|
|
},
|
|
useCustomCredentials: true,
|
|
});
|
|
});
|
|
|
|
expect(businessApi.updateBusinessOAuthCredentials).toHaveBeenCalledWith({
|
|
credentials: {
|
|
google: {
|
|
client_id: 'custom-google-id',
|
|
client_secret: 'custom-google-secret',
|
|
},
|
|
},
|
|
useCustomCredentials: true,
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.isSuccess).toBe(true);
|
|
});
|
|
|
|
expect(result.current.data?.useCustomCredentials).toBe(true);
|
|
expect(result.current.data?.credentials.google.has_secret).toBe(true);
|
|
});
|
|
|
|
it('updates query cache on success', async () => {
|
|
const mockResponse = {
|
|
credentials: {
|
|
google: {
|
|
client_id: 'google-id',
|
|
client_secret: 'google-secret',
|
|
has_secret: true,
|
|
},
|
|
},
|
|
useCustomCredentials: true,
|
|
};
|
|
|
|
vi.mocked(businessApi.updateBusinessOAuthCredentials).mockResolvedValue(mockResponse);
|
|
|
|
const queryClient = new QueryClient({
|
|
defaultOptions: {
|
|
queries: { retry: false },
|
|
mutations: { retry: false },
|
|
},
|
|
});
|
|
|
|
const wrapper = ({ children }: { children: React.ReactNode }) =>
|
|
React.createElement(QueryClientProvider, { client: queryClient }, children);
|
|
|
|
const { result } = renderHook(() => useUpdateBusinessOAuthCredentials(), {
|
|
wrapper,
|
|
});
|
|
|
|
await act(async () => {
|
|
await result.current.mutateAsync({
|
|
credentials: {
|
|
google: {
|
|
client_id: 'google-id',
|
|
client_secret: 'google-secret',
|
|
},
|
|
},
|
|
});
|
|
});
|
|
|
|
// Verify cache was updated
|
|
const cachedData = queryClient.getQueryData(['businessOAuthCredentials']);
|
|
expect(cachedData).toEqual(mockResponse);
|
|
});
|
|
|
|
it('handles update error gracefully', async () => {
|
|
const mockError = new Error('Failed to update credentials');
|
|
vi.mocked(businessApi.updateBusinessOAuthCredentials).mockRejectedValue(mockError);
|
|
|
|
const { result } = renderHook(() => useUpdateBusinessOAuthCredentials(), {
|
|
wrapper: createWrapper(),
|
|
});
|
|
|
|
let caughtError: any = null;
|
|
|
|
await act(async () => {
|
|
try {
|
|
await result.current.mutateAsync({
|
|
credentials: {
|
|
google: {
|
|
client_id: 'test-id',
|
|
},
|
|
},
|
|
});
|
|
} catch (error) {
|
|
caughtError = error;
|
|
}
|
|
});
|
|
|
|
expect(caughtError).toEqual(mockError);
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.isError).toBe(true);
|
|
});
|
|
|
|
expect(result.current.error).toEqual(mockError);
|
|
});
|
|
|
|
it('handles validation error from API', async () => {
|
|
const mockError = new Error('Invalid client_id format');
|
|
vi.mocked(businessApi.updateBusinessOAuthCredentials).mockRejectedValue(mockError);
|
|
|
|
const { result } = renderHook(() => useUpdateBusinessOAuthCredentials(), {
|
|
wrapper: createWrapper(),
|
|
});
|
|
|
|
let caughtError: any = null;
|
|
|
|
await act(async () => {
|
|
try {
|
|
await result.current.mutateAsync({
|
|
credentials: {
|
|
google: {
|
|
client_id: 'invalid-format',
|
|
},
|
|
},
|
|
});
|
|
} catch (error) {
|
|
caughtError = error;
|
|
}
|
|
});
|
|
|
|
expect(caughtError).toEqual(mockError);
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.isError).toBe(true);
|
|
});
|
|
});
|
|
|
|
it('handles clearing credentials by passing empty values', async () => {
|
|
const mockResponse = {
|
|
credentials: {
|
|
google: {
|
|
client_id: '',
|
|
client_secret: '',
|
|
has_secret: false,
|
|
},
|
|
},
|
|
useCustomCredentials: false,
|
|
};
|
|
|
|
vi.mocked(businessApi.updateBusinessOAuthCredentials).mockResolvedValue(mockResponse);
|
|
|
|
const { result } = renderHook(() => useUpdateBusinessOAuthCredentials(), {
|
|
wrapper: createWrapper(),
|
|
});
|
|
|
|
await act(async () => {
|
|
await result.current.mutateAsync({
|
|
credentials: {
|
|
google: {
|
|
client_id: '',
|
|
client_secret: '',
|
|
},
|
|
},
|
|
useCustomCredentials: false,
|
|
});
|
|
});
|
|
|
|
expect(businessApi.updateBusinessOAuthCredentials).toHaveBeenCalledWith({
|
|
credentials: {
|
|
google: {
|
|
client_id: '',
|
|
client_secret: '',
|
|
},
|
|
},
|
|
useCustomCredentials: false,
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.isSuccess).toBe(true);
|
|
});
|
|
|
|
expect(result.current.data?.credentials.google.has_secret).toBe(false);
|
|
expect(result.current.data?.useCustomCredentials).toBe(false);
|
|
});
|
|
|
|
it('handles permission error (403)', async () => {
|
|
const mockError = new Error('403 Forbidden - Insufficient permissions');
|
|
vi.mocked(businessApi.updateBusinessOAuthCredentials).mockRejectedValue(mockError);
|
|
|
|
const { result } = renderHook(() => useUpdateBusinessOAuthCredentials(), {
|
|
wrapper: createWrapper(),
|
|
});
|
|
|
|
let caughtError: any = null;
|
|
|
|
await act(async () => {
|
|
try {
|
|
await result.current.mutateAsync({
|
|
useCustomCredentials: true,
|
|
});
|
|
} catch (error) {
|
|
caughtError = error;
|
|
}
|
|
});
|
|
|
|
expect(caughtError).toEqual(mockError);
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.isError).toBe(true);
|
|
});
|
|
});
|
|
|
|
it('preserves backend response structure with has_secret flags', async () => {
|
|
const mockResponse = {
|
|
credentials: {
|
|
google: {
|
|
client_id: 'google-id',
|
|
client_secret: 'google-secret',
|
|
has_secret: true,
|
|
},
|
|
microsoft: {
|
|
client_id: 'microsoft-id',
|
|
client_secret: '',
|
|
has_secret: false,
|
|
},
|
|
},
|
|
useCustomCredentials: true,
|
|
};
|
|
|
|
vi.mocked(businessApi.updateBusinessOAuthCredentials).mockResolvedValue(mockResponse);
|
|
|
|
const { result } = renderHook(() => useUpdateBusinessOAuthCredentials(), {
|
|
wrapper: createWrapper(),
|
|
});
|
|
|
|
await act(async () => {
|
|
await result.current.mutateAsync({
|
|
credentials: {
|
|
google: {
|
|
client_id: 'google-id',
|
|
client_secret: 'google-secret',
|
|
},
|
|
microsoft: {
|
|
client_id: 'microsoft-id',
|
|
},
|
|
},
|
|
});
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.isSuccess).toBe(true);
|
|
});
|
|
|
|
expect(result.current.data?.credentials.google.has_secret).toBe(true);
|
|
expect(result.current.data?.credentials.microsoft.has_secret).toBe(false);
|
|
expect(result.current.data?.credentials.microsoft.client_secret).toBe('');
|
|
});
|
|
});
|
|
|
|
describe('integration tests', () => {
|
|
it('fetches credentials then updates them', async () => {
|
|
const initialResponse = {
|
|
credentials: {
|
|
google: {
|
|
client_id: 'initial-google-id',
|
|
client_secret: 'initial-google-secret',
|
|
has_secret: true,
|
|
},
|
|
},
|
|
useCustomCredentials: true,
|
|
};
|
|
|
|
const updatedResponse = {
|
|
credentials: {
|
|
google: {
|
|
client_id: 'updated-google-id',
|
|
client_secret: 'updated-google-secret',
|
|
has_secret: true,
|
|
},
|
|
microsoft: {
|
|
client_id: 'new-microsoft-id',
|
|
client_secret: 'new-microsoft-secret',
|
|
has_secret: true,
|
|
},
|
|
},
|
|
useCustomCredentials: true,
|
|
};
|
|
|
|
vi.mocked(businessApi.getBusinessOAuthCredentials).mockResolvedValue(initialResponse);
|
|
vi.mocked(businessApi.updateBusinessOAuthCredentials).mockResolvedValue(updatedResponse);
|
|
|
|
const queryClient = new QueryClient({
|
|
defaultOptions: {
|
|
queries: { retry: false },
|
|
mutations: { retry: false },
|
|
},
|
|
});
|
|
|
|
const wrapper = ({ children }: { children: React.ReactNode }) =>
|
|
React.createElement(QueryClientProvider, { client: queryClient }, children);
|
|
|
|
// Fetch initial credentials
|
|
const { result: fetchResult } = renderHook(() => useBusinessOAuthCredentials(), {
|
|
wrapper,
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(fetchResult.current.isSuccess).toBe(true);
|
|
});
|
|
|
|
expect(fetchResult.current.data?.credentials.google.client_id).toBe('initial-google-id');
|
|
expect(Object.keys(fetchResult.current.data?.credentials || {})).toHaveLength(1);
|
|
|
|
// Update credentials
|
|
const { result: updateResult } = renderHook(() => useUpdateBusinessOAuthCredentials(), {
|
|
wrapper,
|
|
});
|
|
|
|
await act(async () => {
|
|
await updateResult.current.mutateAsync({
|
|
credentials: {
|
|
google: {
|
|
client_id: 'updated-google-id',
|
|
client_secret: 'updated-google-secret',
|
|
},
|
|
microsoft: {
|
|
client_id: 'new-microsoft-id',
|
|
client_secret: 'new-microsoft-secret',
|
|
},
|
|
},
|
|
});
|
|
});
|
|
|
|
// Verify cache was updated
|
|
const cachedData = queryClient.getQueryData(['businessOAuthCredentials']);
|
|
expect(cachedData).toEqual(updatedResponse);
|
|
expect((cachedData as any).credentials.google.client_id).toBe('updated-google-id');
|
|
expect((cachedData as any).credentials.microsoft.client_id).toBe('new-microsoft-id');
|
|
});
|
|
|
|
it('toggles custom credentials on and off', async () => {
|
|
const initialResponse = {
|
|
credentials: {
|
|
google: {
|
|
client_id: 'google-id',
|
|
client_secret: 'google-secret',
|
|
has_secret: true,
|
|
},
|
|
},
|
|
useCustomCredentials: true,
|
|
};
|
|
|
|
const toggledOffResponse = {
|
|
credentials: {
|
|
google: {
|
|
client_id: 'google-id',
|
|
client_secret: 'google-secret',
|
|
has_secret: true,
|
|
},
|
|
},
|
|
useCustomCredentials: false,
|
|
};
|
|
|
|
const toggledOnResponse = {
|
|
credentials: {
|
|
google: {
|
|
client_id: 'google-id',
|
|
client_secret: 'google-secret',
|
|
has_secret: true,
|
|
},
|
|
},
|
|
useCustomCredentials: true,
|
|
};
|
|
|
|
vi.mocked(businessApi.getBusinessOAuthCredentials).mockResolvedValue(initialResponse);
|
|
|
|
const queryClient = new QueryClient({
|
|
defaultOptions: {
|
|
queries: { retry: false },
|
|
mutations: { retry: false },
|
|
},
|
|
});
|
|
|
|
const wrapper = ({ children }: { children: React.ReactNode }) =>
|
|
React.createElement(QueryClientProvider, { client: queryClient }, children);
|
|
|
|
// Fetch initial state
|
|
const { result: fetchResult } = renderHook(() => useBusinessOAuthCredentials(), {
|
|
wrapper,
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(fetchResult.current.isSuccess).toBe(true);
|
|
});
|
|
|
|
expect(fetchResult.current.data?.useCustomCredentials).toBe(true);
|
|
|
|
// Toggle off
|
|
const { result: updateResult } = renderHook(() => useUpdateBusinessOAuthCredentials(), {
|
|
wrapper,
|
|
});
|
|
|
|
vi.mocked(businessApi.updateBusinessOAuthCredentials).mockResolvedValue(toggledOffResponse);
|
|
|
|
await act(async () => {
|
|
await updateResult.current.mutateAsync({
|
|
useCustomCredentials: false,
|
|
});
|
|
});
|
|
|
|
let cachedData = queryClient.getQueryData(['businessOAuthCredentials']);
|
|
expect((cachedData as any).useCustomCredentials).toBe(false);
|
|
|
|
// Toggle back on
|
|
vi.mocked(businessApi.updateBusinessOAuthCredentials).mockResolvedValue(toggledOnResponse);
|
|
|
|
await act(async () => {
|
|
await updateResult.current.mutateAsync({
|
|
useCustomCredentials: true,
|
|
});
|
|
});
|
|
|
|
cachedData = queryClient.getQueryData(['businessOAuthCredentials']);
|
|
expect((cachedData as any).useCustomCredentials).toBe(true);
|
|
});
|
|
});
|
|
});
|