learning_ai_invt_trdg/web/src/components/AuthContext.dom.test.tsx

213 lines
7.8 KiB
TypeScript

// @vitest-environment jsdom
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
// import userEvent from '@testing-library/user-event';
import { AuthProvider, useAuth } from './AuthContext';
import { tableNameProfiles, tableNameUsers } from '../lib/const';
const {
getSessionMock,
signOutMock,
fromMock,
usersSingleMock,
profilesLimitMock,
profilesInsertMock,
unsubscribeMock,
tradingAuthState
} = vi.hoisted(() => ({
getSessionMock: vi.fn(),
signOutMock: vi.fn(),
fromMock: vi.fn(),
usersSingleMock: vi.fn(),
profilesLimitMock: vi.fn(),
profilesInsertMock: vi.fn(),
unsubscribeMock: vi.fn(),
tradingAuthState: {
user: { id: 'user-1', email: 'sarah@example.com', role: 'admin', name: 'Sarah Algo' } as any,
isLoading: false,
logout: vi.fn()
}
}));
vi.mock('../lib/tradingAuth', () => ({
TradingAuthProvider: ({ children }: { children: React.ReactNode }) => children,
useTradingAuth: () => tradingAuthState
}));
vi.mock('../lib/supabaseClient', () => ({
supabase: {
auth: {
getSession: getSessionMock,
signOut: signOutMock
},
from: fromMock
}
}));
const Probe = () => {
const auth = useAuth();
return (
<div>
<div data-testid="loading">{String(auth.loading)}</div>
<div data-testid="user">{auth.user?.id || 'none'}</div>
<div data-testid="role">{auth.profile?.role || 'none'}</div>
<button onClick={() => void auth.refreshProfile()}>Refresh</button>
<button onClick={() => void auth.signOut()}>Sign Out</button>
</div>
);
};
describe('AuthContext DOM behavior', () => {
beforeEach(() => {
getSessionMock.mockReset();
signOutMock.mockReset();
fromMock.mockReset();
usersSingleMock.mockReset();
profilesLimitMock.mockReset();
profilesInsertMock.mockReset();
unsubscribeMock.mockReset();
tradingAuthState.user = { id: 'user-1', email: 'sarah@example.com', role: 'admin', name: 'Sarah Algo' };
tradingAuthState.isLoading = false;
tradingAuthState.logout.mockReset();
signOutMock.mockResolvedValue({ error: null });
usersSingleMock.mockResolvedValue({
data: {
user_id: 'user-1',
first_name: 'Sarah',
last_name: 'Algo',
email: 'sarah@example.com',
role: 'admin',
trade_enable: true
},
error: null
});
profilesLimitMock.mockResolvedValue({ data: [], error: null });
profilesInsertMock.mockResolvedValue({ error: null });
fromMock.mockImplementation((table: string) => {
if (table === tableNameUsers) {
const usersBuilder: any = {
select: vi.fn(() => usersBuilder),
eq: vi.fn(() => usersBuilder),
single: usersSingleMock
};
return usersBuilder;
}
if (table === tableNameProfiles) {
const profilesBuilder: any = {
select: vi.fn(() => profilesBuilder),
eq: vi.fn(() => profilesBuilder),
limit: profilesLimitMock,
insert: profilesInsertMock
};
return profilesBuilder;
}
return {
select: vi.fn(),
eq: vi.fn(),
single: vi.fn(),
limit: vi.fn(),
insert: vi.fn()
};
});
});
it('loads session/profile, ensures default profile, and cleans up subscription', async () => {
getSessionMock.mockResolvedValue({
data: { session: { user: { id: 'user-1' } } }
});
const dispatchSpy = vi.spyOn(window, 'dispatchEvent');
render(
<AuthProvider>
<Probe />
</AuthProvider>
);
await waitFor(() => {
expect(screen.getByTestId('loading')).toHaveTextContent('false');
expect(screen.getByTestId('user')).toHaveTextContent('user-1');
expect(screen.getByTestId('role')).toHaveTextContent('admin');
});
expect(profilesInsertMock).toHaveBeenCalledTimes(1);
expect(profilesInsertMock.mock.calls[0][0]).toEqual([
expect.objectContaining({ user_id: 'user-1', name: 'My First Strategy' })
]);
expect(dispatchSpy).toHaveBeenCalled();
expect((dispatchSpy.mock.calls[0][0] as Event).type).toBe('profiles-updated');
}, 20000);
it('handles no initial session gracefully', async () => {
tradingAuthState.user = null;
getSessionMock.mockResolvedValue({ data: { session: null } });
render(<AuthProvider><Probe /></AuthProvider>);
await waitFor(() => {
expect(screen.getByTestId('loading')).toHaveTextContent('false');
expect(screen.getByTestId('user')).toHaveTextContent('none');
});
expect(usersSingleMock).not.toHaveBeenCalled();
});
it('handles auth state changes with no session', async () => {
getSessionMock.mockResolvedValue({ data: { session: { user: { id: 'u1' } } } });
const { rerender } = render(<AuthProvider><Probe /></AuthProvider>);
await waitFor(() => {
expect(screen.getByTestId('user')).toHaveTextContent('u1');
});
tradingAuthState.user = null;
rerender(<AuthProvider><Probe /></AuthProvider>);
await waitFor(() => {
expect(screen.getByTestId('user')).toHaveTextContent('none');
expect(screen.getByTestId('role')).toHaveTextContent('none');
});
});
it('logs error when profile fetch fails', async () => {
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
tradingAuthState.user = { id: 'u1', email: 'u1@example.com', role: 'member', name: 'U One' };
getSessionMock.mockResolvedValue({ data: { session: { user: { id: 'u1' } } } });
usersSingleMock.mockResolvedValue({ data: null, error: { message: 'Profile Not Found' } });
render(<AuthProvider><Probe /></AuthProvider>);
await waitFor(() => {
expect(consoleSpy).toHaveBeenCalledWith('Error fetching user profile:', expect.objectContaining({ message: 'Profile Not Found' }));
});
consoleSpy.mockRestore();
});
it('handles unexpected errors in fetchProfile', async () => {
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
tradingAuthState.user = { id: 'u1', email: 'u1@example.com', role: 'member', name: 'U One' };
getSessionMock.mockResolvedValue({ data: { session: { user: { id: 'u1' } } } });
usersSingleMock.mockImplementation(() => { throw new Error('Crashed'); });
render(<AuthProvider><Probe /></AuthProvider>);
await waitFor(() => {
expect(consoleSpy).toHaveBeenCalledWith('Unexpected error fetching profile:', expect.any(Error));
});
consoleSpy.mockRestore();
});
it('handles unexpected errors in ensureDefaultProfile', async () => {
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
tradingAuthState.user = { id: 'u1', email: 'u1@example.com', role: 'member', name: 'U One' };
getSessionMock.mockResolvedValue({ data: { session: { user: { id: 'u1' } } } });
profilesLimitMock.mockImplementation(() => { throw new Error('Limit Error'); });
render(<AuthProvider><Probe /></AuthProvider>);
await waitFor(() => {
expect(consoleSpy).toHaveBeenCalledWith('[Auth] Error ensuring default profile:', expect.any(Error));
});
consoleSpy.mockRestore();
});
});