AuthManager.test.ts•11.3 kB
/**
* AuthManager Unit Tests (Mock-based)
*
* Tests authentication flow with mocked HTTP responses
*/
import MockAdapter from 'axios-mock-adapter';
import axios from 'axios';
import * as fs from 'fs/promises';
import * as path from 'path';
import * as os from 'os';
import { AuthManager } from '../../../src/core/auth/AuthManager';
import { SessionStore } from '../../../src/core/auth/SessionStore';
import { AuthenticationError } from '../../../src/core/types';
describe('AuthManager (Unit Tests)', () => {
let testDir: string;
let sessionStore: SessionStore;
let authManager: AuthManager;
let mock: MockAdapter;
const mockCredentials = {
email: 'test@whatap.io',
password: 'test-password',
serviceUrl: 'https://test.whatap.io',
};
beforeEach(async () => {
// Create temporary test directory
testDir = path.join(os.tmpdir(), `whatap-test-${Date.now()}`);
await fs.mkdir(testDir, { recursive: true });
sessionStore = new SessionStore(testDir);
// Setup axios mock BEFORE creating AuthManager
mock = new MockAdapter(axios, { onNoMatch: 'throwException' });
authManager = new AuthManager(sessionStore);
});
afterEach(async () => {
// Clean up
mock.restore();
try {
await fs.rm(testDir, { recursive: true, force: true });
} catch (error) {
// Ignore cleanup errors
}
});
describe('login - success flow', () => {
test('should complete full login flow successfully', async () => {
// Mock Step 1: CSRF token
mock.onGet('https://test.whatap.io/account/login').reply(200, `
<html>
<input id="_csrf" value="test-csrf-token" />
</html>
`);
// Mock Step 2: Web login
mock.onPost('https://test.whatap.io/account/login').reply(200, 'success', {
'set-cookie': [
'wa=test-wa-cookie; Path=/; HttpOnly',
'JSESSIONID=test-jsessionid; Path=/; HttpOnly',
] as any,
});
// Mock Step 3: Mobile API token
mock.onPost('https://test.whatap.io/mobile/api/login').reply(200, {
accountId: 12345,
apiToken: 'test-api-token',
});
// Execute login
const session = await authManager.login(mockCredentials);
// Verify session
expect(session.email).toBe(mockCredentials.email);
expect(session.accountId).toBe(12345);
expect(session.apiToken).toBe('test-api-token');
expect(session.cookies.wa).toBe('test-wa-cookie');
expect(session.cookies.jsessionid).toBe('test-jsessionid');
expect(session.serviceUrl).toBe(mockCredentials.serviceUrl);
// Verify session is authenticated
expect(authManager.isAuthenticated()).toBe(true);
});
test('should save session after successful login', async () => {
// Setup mocks
mock.onGet('https://test.whatap.io/account/login').reply(200, `
<html><input id="_csrf" value="csrf" /></html>
`);
mock.onPost('https://test.whatap.io/account/login').reply(200, 'ok', {
'set-cookie': ['wa=wa-val; Path=/', 'JSESSIONID=session-val; Path=/'] as any,
});
mock.onPost('https://test.whatap.io/mobile/api/login').reply(200, {
accountId: 99999,
apiToken: 'token-123',
});
// Login
await authManager.login(mockCredentials);
// Load session from store
const loaded = await sessionStore.load();
expect(loaded).not.toBeNull();
expect(loaded?.accountId).toBe(99999);
});
});
describe('login - error cases', () => {
test('should throw error when CSRF token not found', async () => {
mock.onGet('https://test.whatap.io/account/login').reply(200, `
<html><body>No CSRF token here</body></html>
`);
await expect(authManager.login(mockCredentials)).rejects.toThrow(
AuthenticationError
);
await expect(authManager.login(mockCredentials)).rejects.toThrow(
'CSRF token not found'
);
});
test('should throw error when web login fails (no cookies)', async () => {
mock.onGet('https://test.whatap.io/account/login').reply(200, `
<html><input id="_csrf" value="csrf" /></html>
`);
mock.onPost('https://test.whatap.io/account/login').reply(200, 'ok');
// No set-cookie header
await expect(authManager.login(mockCredentials)).rejects.toThrow(
AuthenticationError
);
await expect(authManager.login(mockCredentials)).rejects.toThrow(
'Required cookies not found'
);
});
test('should throw error when account is locked', async () => {
mock.onGet('https://test.whatap.io/account/login').reply(200, `
<html><input id="_csrf" value="csrf" /></html>
`);
mock.onPost('https://test.whatap.io/account/login').reply(200, `
<html><body>Account is locked</body></html>
`);
await expect(authManager.login(mockCredentials)).rejects.toThrow(
AuthenticationError
);
await expect(authManager.login(mockCredentials)).rejects.toThrow(
'Account is locked'
);
});
test('should throw error when MFA is required', async () => {
mock.onGet('https://test.whatap.io/account/login').reply(200, `
<html><input id="_csrf" value="csrf" /></html>
`);
mock.onPost('https://test.whatap.io/account/login').reply(
302,
'redirect',
{
location: '/account/mfa',
'set-cookie': ['wa=wa-val; Path=/', 'JSESSIONID=session-val; Path=/'] as any,
}
);
await expect(authManager.login(mockCredentials)).rejects.toThrow(
AuthenticationError
);
await expect(authManager.login(mockCredentials)).rejects.toThrow('MFA');
});
test('should throw error when mobile API token not received', async () => {
mock.onGet('https://test.whatap.io/account/login').reply(200, `
<html><input id="_csrf" value="csrf" /></html>
`);
mock.onPost('https://test.whatap.io/account/login').reply(200, 'ok', {
'set-cookie': ['wa=wa-val; Path=/', 'JSESSIONID=session-val; Path=/'] as any,
});
mock.onPost('https://test.whatap.io/mobile/api/login').reply(200, {
// Missing apiToken
accountId: 12345,
});
await expect(authManager.login(mockCredentials)).rejects.toThrow(
AuthenticationError
);
await expect(authManager.login(mockCredentials)).rejects.toThrow(
'API token not received'
);
});
});
describe('session management', () => {
test('should load existing session', async () => {
// Create and save a session manually
const mockSession = {
email: 'test@whatap.io',
accountId: 12345,
cookies: { wa: 'wa-cookie', jsessionid: 'session-id' },
apiToken: 'api-token',
serviceUrl: 'https://test.whatap.io',
createdAt: new Date(),
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000),
};
await sessionStore.save(mockSession);
// Create new AuthManager and load session
const newAuthManager = new AuthManager(sessionStore);
const loaded = await newAuthManager.loadSession();
expect(loaded).not.toBeNull();
expect(loaded?.email).toBe(mockSession.email);
expect(newAuthManager.isAuthenticated()).toBe(true);
});
test('should return null when no session exists', async () => {
const loaded = await authManager.loadSession();
expect(loaded).toBeNull();
expect(authManager.isAuthenticated()).toBe(false);
});
test('should logout and clear session', async () => {
// Setup session
mock.onGet().reply(200, '<html><input id="_csrf" value="csrf" /></html>');
mock.onPost('https://test.whatap.io/account/login').reply(200, 'ok', {
'set-cookie': ['wa=wa; Path=/', 'JSESSIONID=jsession; Path=/'] as any,
});
mock.onPost('https://test.whatap.io/mobile/api/login').reply(200, {
accountId: 12345,
apiToken: 'token',
});
await authManager.login(mockCredentials);
expect(authManager.isAuthenticated()).toBe(true);
// Logout
await authManager.logout();
expect(authManager.isAuthenticated()).toBe(false);
// Verify session file is deleted
const loaded = await sessionStore.load();
expect(loaded).toBeNull();
});
});
describe('getSession and getCookieHeader', () => {
test('should get current session', async () => {
// Setup mocks and login
mock.onGet().reply(200, '<html><input id="_csrf" value="csrf" /></html>');
mock.onPost('https://test.whatap.io/account/login').reply(200, 'ok', {
'set-cookie': ['wa=wa-cookie; Path=/', 'JSESSIONID=session-id; Path=/'] as any,
});
mock.onPost('https://test.whatap.io/mobile/api/login').reply(200, {
accountId: 12345,
apiToken: 'token',
});
await authManager.login(mockCredentials);
const session = authManager.getSession();
expect(session.accountId).toBe(12345);
});
test('should throw error when not authenticated', () => {
expect(() => authManager.getSession()).toThrow(AuthenticationError);
expect(() => authManager.getCookieHeader()).toThrow(AuthenticationError);
});
test('should get cookie header string', async () => {
// Setup mocks and login
mock.onGet().reply(200, '<html><input id="_csrf" value="csrf" /></html>');
mock.onPost('https://test.whatap.io/account/login').reply(200, 'ok', {
'set-cookie': ['wa=my-wa-cookie; Path=/', 'JSESSIONID=my-session; Path=/'] as any,
});
mock.onPost('https://test.whatap.io/mobile/api/login').reply(200, {
accountId: 12345,
apiToken: 'token',
});
await authManager.login(mockCredentials);
const cookieHeader = authManager.getCookieHeader();
expect(cookieHeader).toBe('WHATAP=my-wa-cookie; JSESSIONID=my-session');
});
});
describe('isAuthenticated', () => {
test('should return false when no session', () => {
expect(authManager.isAuthenticated()).toBe(false);
});
test('should return true when session exists and not expired', async () => {
// Setup mocks and login
mock.onGet().reply(200, '<html><input id="_csrf" value="csrf" /></html>');
mock.onPost('https://test.whatap.io/account/login').reply(200, 'ok', {
'set-cookie': ['wa=wa; Path=/', 'JSESSIONID=js; Path=/'] as any,
});
mock.onPost('https://test.whatap.io/mobile/api/login').reply(200, {
accountId: 12345,
apiToken: 'token',
});
await authManager.login(mockCredentials);
expect(authManager.isAuthenticated()).toBe(true);
});
test('should return false when session expired', async () => {
// Create expired session
const expiredSession = {
email: 'test@whatap.io',
accountId: 12345,
cookies: { wa: 'wa', jsessionid: 'js' },
apiToken: 'token',
serviceUrl: 'https://test.whatap.io',
createdAt: new Date(Date.now() - 25 * 60 * 60 * 1000),
expiresAt: new Date(Date.now() - 1000), // Expired
};
await sessionStore.save(expiredSession);
await authManager.loadSession();
expect(authManager.isAuthenticated()).toBe(false);
});
});
});