- Replace sessionStorage with localStorage for user data persistence - Add refreshSession() function to restore session from HTTPOnly cookie via /users/me - Update tests to use localStorage mocks - Add 3 new tests for refreshSession() functionality - Update superadmin credentials in README.md and DEVOPS.md
259 lines
8.5 KiB
TypeScript
259 lines
8.5 KiB
TypeScript
import { RegisterCandidateData } from '../auth';
|
|
|
|
// Mock fetch globally
|
|
const mockFetch = jest.fn();
|
|
global.fetch = mockFetch;
|
|
|
|
// Mock localStorage
|
|
const localStorageMock = {
|
|
getItem: jest.fn(),
|
|
setItem: jest.fn(),
|
|
removeItem: jest.fn(),
|
|
clear: jest.fn(),
|
|
};
|
|
Object.defineProperty(window, 'localStorage', { value: localStorageMock });
|
|
|
|
// Mock config module to avoid initConfig fetching
|
|
jest.mock('../config', () => ({
|
|
getApiUrl: jest.fn().mockReturnValue('http://test-api.com'),
|
|
initConfig: jest.fn().mockResolvedValue({}),
|
|
getConfig: jest.fn().mockReturnValue({}),
|
|
}));
|
|
|
|
describe('Auth Module', () => {
|
|
let authModule: typeof import('../auth');
|
|
|
|
beforeEach(() => {
|
|
jest.resetModules();
|
|
mockFetch.mockReset();
|
|
localStorageMock.getItem.mockReset();
|
|
localStorageMock.setItem.mockReset();
|
|
localStorageMock.removeItem.mockReset();
|
|
|
|
// Re-import the module fresh
|
|
authModule = require('../auth');
|
|
});
|
|
|
|
describe('registerCandidate', () => {
|
|
const validData: RegisterCandidateData = {
|
|
name: 'John Doe',
|
|
email: 'john@example.com',
|
|
password: 'password123',
|
|
username: 'johndoe',
|
|
phone: '+5511999999999',
|
|
};
|
|
|
|
it('should register candidate successfully', async () => {
|
|
const mockResponse = {
|
|
token: 'mock-jwt-token',
|
|
user: {
|
|
id: '123',
|
|
name: 'John Doe',
|
|
email: 'john@example.com',
|
|
roles: ['CANDIDATE'],
|
|
},
|
|
};
|
|
|
|
mockFetch.mockResolvedValueOnce({
|
|
ok: true,
|
|
json: async () => mockResponse,
|
|
});
|
|
|
|
await expect(authModule.registerCandidate(validData)).resolves.not.toThrow();
|
|
|
|
expect(mockFetch).toHaveBeenCalledWith(
|
|
expect.stringContaining('/auth/register'),
|
|
expect.objectContaining({
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(validData),
|
|
})
|
|
);
|
|
});
|
|
|
|
it('should throw error when email already exists', async () => {
|
|
const errorResponse = { message: 'email already registered' };
|
|
|
|
mockFetch.mockResolvedValueOnce({
|
|
ok: false,
|
|
status: 409,
|
|
json: async () => errorResponse,
|
|
});
|
|
|
|
await expect(authModule.registerCandidate(validData)).rejects.toThrow(
|
|
'email already registered'
|
|
);
|
|
});
|
|
|
|
it('should throw error with status code when no message provided', async () => {
|
|
mockFetch.mockResolvedValueOnce({
|
|
ok: false,
|
|
status: 500,
|
|
json: async () => ({}),
|
|
});
|
|
|
|
await expect(authModule.registerCandidate(validData)).rejects.toThrow(
|
|
'Erro no registro: 500'
|
|
);
|
|
});
|
|
|
|
it('should handle network errors gracefully', async () => {
|
|
mockFetch.mockRejectedValueOnce(new Error('Network error'));
|
|
|
|
await expect(authModule.registerCandidate(validData)).rejects.toThrow(
|
|
'Network error'
|
|
);
|
|
});
|
|
|
|
it('should send all required fields in request body', async () => {
|
|
mockFetch.mockResolvedValueOnce({
|
|
ok: true,
|
|
json: async () => ({}),
|
|
});
|
|
|
|
await authModule.registerCandidate(validData);
|
|
|
|
const callArgs = mockFetch.mock.calls[0];
|
|
const body = JSON.parse(callArgs[1].body);
|
|
|
|
expect(body).toEqual({
|
|
name: 'John Doe',
|
|
email: 'john@example.com',
|
|
password: 'password123',
|
|
username: 'johndoe',
|
|
phone: '+5511999999999',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('login', () => {
|
|
it('should login and store user in localStorage', async () => {
|
|
const mockResponse = {
|
|
token: 'mock-jwt-token',
|
|
user: {
|
|
id: '123',
|
|
name: 'Test User',
|
|
email: 'test@example.com',
|
|
roles: ['CANDIDATE'],
|
|
status: 'active',
|
|
created_at: '2023-01-01T00:00:00Z',
|
|
},
|
|
};
|
|
|
|
mockFetch.mockResolvedValueOnce({
|
|
ok: true,
|
|
json: async () => mockResponse,
|
|
});
|
|
|
|
const user = await authModule.login('test@example.com', 'password123');
|
|
|
|
expect(user).toBeDefined();
|
|
expect(user?.email).toBe('test@example.com');
|
|
expect(localStorageMock.setItem).toHaveBeenCalledWith(
|
|
'job-portal-auth',
|
|
expect.any(String)
|
|
);
|
|
// Token is in cookie, not in storage
|
|
expect(localStorageMock.setItem).not.toHaveBeenCalledWith(
|
|
'auth_token',
|
|
expect.any(String)
|
|
);
|
|
});
|
|
|
|
it('should throw error on invalid credentials', async () => {
|
|
mockFetch.mockResolvedValueOnce({
|
|
ok: false,
|
|
status: 401,
|
|
});
|
|
|
|
await expect(
|
|
authModule.login('test@example.com', 'wrongpassword')
|
|
).rejects.toThrow('Credenciais inválidas');
|
|
});
|
|
});
|
|
|
|
describe('logout', () => {
|
|
it('should remove auth data from localStorage', () => {
|
|
authModule.logout();
|
|
|
|
expect(localStorageMock.removeItem).toHaveBeenCalledWith('job-portal-auth');
|
|
});
|
|
});
|
|
|
|
describe('refreshSession', () => {
|
|
it('should restore user from /users/me endpoint', async () => {
|
|
mockFetch.mockResolvedValueOnce({
|
|
ok: true,
|
|
json: async () => ({
|
|
id: '123',
|
|
name: 'Test User',
|
|
email: 'test@example.com',
|
|
roles: ['candidate'],
|
|
}),
|
|
});
|
|
|
|
const user = await authModule.refreshSession();
|
|
|
|
expect(user).toBeDefined();
|
|
expect(user?.email).toBe('test@example.com');
|
|
expect(localStorageMock.setItem).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should clear storage on 401', async () => {
|
|
mockFetch.mockResolvedValueOnce({ ok: false, status: 401 });
|
|
|
|
const user = await authModule.refreshSession();
|
|
|
|
expect(user).toBeNull();
|
|
expect(localStorageMock.removeItem).toHaveBeenCalledWith('job-portal-auth');
|
|
});
|
|
|
|
it('should return null on network error', async () => {
|
|
mockFetch.mockRejectedValueOnce(new Error('Network error'));
|
|
|
|
const user = await authModule.refreshSession();
|
|
|
|
expect(user).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('getCurrentUser', () => {
|
|
it('should return user from localStorage', () => {
|
|
const storedUser = { id: '123', name: 'Test', email: 'test@test.com', role: 'candidate' };
|
|
localStorageMock.getItem.mockReturnValueOnce(JSON.stringify(storedUser));
|
|
|
|
const user = authModule.getCurrentUser();
|
|
|
|
expect(user).toEqual(storedUser);
|
|
});
|
|
|
|
it('should return null when no user stored', () => {
|
|
localStorageMock.getItem.mockReturnValueOnce(null);
|
|
|
|
const user = authModule.getCurrentUser();
|
|
|
|
expect(user).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('isAdminUser', () => {
|
|
it('should return true for admin role', () => {
|
|
const adminUser = { id: '1', name: 'Admin', email: 'a@a.com', role: 'admin' as const, roles: ['admin'] };
|
|
expect(authModule.isAdminUser(adminUser)).toBe(true);
|
|
});
|
|
|
|
it('should return true for SUPERADMIN in roles array', () => {
|
|
const superAdmin = { id: '1', name: 'Super', email: 's@s.com', role: 'candidate' as const, roles: ['SUPERADMIN'] };
|
|
expect(authModule.isAdminUser(superAdmin)).toBe(true);
|
|
});
|
|
|
|
it('should return false for regular candidate', () => {
|
|
const candidate = { id: '1', name: 'User', email: 'u@u.com', role: 'candidate' as const, roles: ['CANDIDATE'] };
|
|
expect(authModule.isAdminUser(candidate)).toBe(false);
|
|
});
|
|
|
|
it('should return false for null user', () => {
|
|
expect(authModule.isAdminUser(null)).toBe(false);
|
|
});
|
|
});
|
|
});
|