import { test, describe, mock } from 'node:test'; import assert from 'node:assert'; import { KeycloakAPI } from '../../src/utils/keycloak.js'; import http from 'http'; import https from 'https'; import fs from 'fs'; describe('KeycloakAPI', () => { describe('constructor', () => { test('должен создавать экземпляр с HTTP клиентом когда ssl=false', () => { const keycloakData = { host: 'localhost', port: 8080, realm: 'test-realm', ssl: 'false', grant_type: 'password', client_id: 'test-client', client_secret: 'test-secret' }; const keycloak = new KeycloakAPI(keycloakData); assert.ok(keycloak); assert.strictEqual(keycloak.keycloakData, keycloakData); assert.strictEqual(keycloak.client, http); assert.strictEqual(keycloak.ca, undefined); }); test('должен создавать экземпляр с HTTPS клиентом когда ssl=true', () => { // Мокаем fs.readFileSync const originalReadFileSync = fs.readFileSync; let mockReadFileSyncCalls = []; const mockReadFileSync = (...args) => { mockReadFileSyncCalls.push(args); return 'mock-cert-content'; }; fs.readFileSync = mockReadFileSync; try { const keycloakData = { host: 'localhost', port: 8443, realm: 'test-realm', ssl: 'true', cert: '/path/to/cert.pem', grant_type: 'password', client_id: 'test-client', client_secret: 'test-secret' }; const keycloak = new KeycloakAPI(keycloakData); assert.ok(keycloak); assert.strictEqual(keycloak.keycloakData, keycloakData); assert.strictEqual(keycloak.client, https); assert.strictEqual(keycloak.ca, 'mock-cert-content'); assert.strictEqual(mockReadFileSyncCalls.length, 1); assert.strictEqual(mockReadFileSyncCalls[0][0], '/path/to/cert.pem'); } finally { fs.readFileSync = originalReadFileSync; } }); }); describe('getToken', () => { test('должен успешно получать токен при валидных credentials', async () => { const keycloakData = { host: 'localhost', port: 8080, realm: 'test-realm', ssl: 'false', grant_type: 'password', client_id: 'test-client', client_secret: 'test-secret' }; const mockResponse = { access_token: 'test-access-token', expires_in: 3600, refresh_token: 'test-refresh-token' }; // Мокаем http.request const originalRequest = http.request; let responseCallback; let dataHandler; let endHandler; http.request = mock.fn((options, callback) => { responseCallback = callback; const mockRes = { statusCode: 200, on: mock.fn((event, handler) => { if (event === 'data') { dataHandler = handler; } if (event === 'end') { endHandler = handler; } }) }; // Вызываем callback с ответом синхронно callback(mockRes); const mockReq = { write: mock.fn(), end: mock.fn(() => { // Симулируем получение данных после end if (dataHandler) { dataHandler(Buffer.from(JSON.stringify(mockResponse))); } if (endHandler) { endHandler(); } }), on: mock.fn() }; return mockReq; }); try { const keycloak = new KeycloakAPI(keycloakData); const loginData = { username: 'testuser', password: 'testpass' }; const result = await keycloak.getToken(loginData); assert.strictEqual(result.code, 200); assert.ok(result.payload); assert.strictEqual(result.payload.accessToken, 'test-access-token'); assert.strictEqual(result.payload.expiresIn, 3600); assert.strictEqual(result.payload.refreshToken, 'test-refresh-token'); } finally { http.request = originalRequest; } }); test('должен возвращать код ошибки при неверных credentials', async () => { const keycloakData = { host: 'localhost', port: 8080, realm: 'test-realm', ssl: 'false', grant_type: 'password', client_id: 'test-client', client_secret: 'test-secret' }; // Мокаем http.request для ошибки 401 const originalRequest = http.request; let dataHandler; let endHandler; http.request = mock.fn((options, callback) => { const mockRes = { statusCode: 401, on: mock.fn((event, handler) => { if (event === 'data') { dataHandler = handler; } if (event === 'end') { endHandler = handler; } }) }; callback(mockRes); return { write: mock.fn(), end: mock.fn(() => { if (dataHandler) { dataHandler(Buffer.from('Unauthorized')); } if (endHandler) { endHandler(); } }), on: mock.fn() }; }); try { const keycloak = new KeycloakAPI(keycloakData); const loginData = { username: 'wronguser', password: 'wrongpass' }; const result = await keycloak.getToken(loginData); assert.strictEqual(result.code, 401); assert.ok(!result.payload); } finally { http.request = originalRequest; } }); test('должен обрабатывать ошибки сети', async () => { const keycloakData = { host: 'localhost', port: 8080, realm: 'test-realm', ssl: 'false', grant_type: 'password', client_id: 'test-client', client_secret: 'test-secret' }; // Мокаем http.request для ошибки сети const originalRequest = http.request; let errorHandler; http.request = mock.fn(() => { const mockReq = { write: mock.fn(), end: mock.fn(() => { // Вызываем обработчик ошибки после end if (errorHandler) { errorHandler(new Error('Network error')); } }), on: mock.fn((event, handler) => { if (event === 'error') { errorHandler = handler; } }) }; return mockReq; }); try { const keycloak = new KeycloakAPI(keycloakData); const loginData = { username: 'testuser', password: 'testpass' }; await assert.rejects( async () => await keycloak.getToken(loginData), /Request error: Network error/ ); } finally { http.request = originalRequest; } }); test('должен обрабатывать невалидный JSON ответ', async () => { const keycloakData = { host: 'localhost', port: 8080, realm: 'test-realm', ssl: 'false', grant_type: 'password', client_id: 'test-client', client_secret: 'test-secret' }; // Мокаем http.request для невалидного JSON const originalRequest = http.request; let dataHandler; let endHandler; http.request = mock.fn((options, callback) => { const mockRes = { statusCode: 200, on: mock.fn((event, handler) => { if (event === 'data') { dataHandler = handler; } if (event === 'end') { endHandler = handler; } }) }; callback(mockRes); return { write: mock.fn(), end: mock.fn(() => { if (dataHandler) { dataHandler(Buffer.from('invalid json')); } if (endHandler) { endHandler(); } }), on: mock.fn() }; }); try { const keycloak = new KeycloakAPI(keycloakData); const loginData = { username: 'testuser', password: 'testpass' }; await assert.rejects( async () => await keycloak.getToken(loginData), /Failed to parse response/ ); } finally { http.request = originalRequest; } }); }); describe('getAdminToken', () => { test('должен успешно получать admin токен', async () => { const keycloakData = { host: 'localhost', port: 8080, realm: 'test-realm', ssl: 'false', grant_type: 'password', client_id: 'test-client', client_secret: 'test-secret' }; const mockResponse = { access_token: 'admin-access-token', expires_in: 3600, refresh_token: 'admin-refresh-token' }; // Мокаем http.request const originalRequest = http.request; let dataHandler; let endHandler; http.request = mock.fn((options, callback) => { // Проверяем, что используется admin-cli assert.ok(options.path.includes('/realms/test-realm')); const mockRes = { statusCode: 200, on: mock.fn((event, handler) => { if (event === 'data') { dataHandler = handler; } if (event === 'end') { endHandler = handler; } }) }; callback(mockRes); return { write: mock.fn(), end: mock.fn(() => { if (dataHandler) { dataHandler(Buffer.from(JSON.stringify(mockResponse))); } if (endHandler) { endHandler(); } }), on: mock.fn() }; }); try { const keycloak = new KeycloakAPI(keycloakData); const loginData = { username: 'admin', password: 'adminpass' }; const result = await keycloak.getAdminToken(loginData); assert.strictEqual(result.accessToken, 'admin-access-token'); assert.strictEqual(result.expiresIn, 3600); assert.strictEqual(result.refreshToken, 'admin-refresh-token'); } finally { http.request = originalRequest; } }); test('должен отклонять промис при ошибке', async () => { const keycloakData = { host: 'localhost', port: 8080, realm: 'test-realm', ssl: 'false', grant_type: 'password', client_id: 'test-client', client_secret: 'test-secret' }; // Мокаем http.request для ошибки 401 const originalRequest = http.request; let dataHandler; let endHandler; http.request = mock.fn((options, callback) => { const mockRes = { statusCode: 401, on: mock.fn((event, handler) => { if (event === 'data') { dataHandler = handler; } if (event === 'end') { endHandler = handler; } }) }; callback(mockRes); return { write: mock.fn(), end: mock.fn(() => { if (dataHandler) { dataHandler(Buffer.from('Unauthorized')); } if (endHandler) { endHandler(); } }), on: mock.fn() }; }); try { const keycloak = new KeycloakAPI(keycloakData); const loginData = { username: 'wrongadmin', password: 'wrongpass' }; await assert.rejects( async () => await keycloak.getAdminToken(loginData), /Request failed with status code: 401/ ); } finally { http.request = originalRequest; } }); }); describe('getUserInfo', () => { test('должен успешно получать информацию о пользователе', async () => { const keycloakData = { host: 'localhost', port: 8080, realm: 'test-realm', ssl: 'false', grant_type: 'password', client_id: 'test-client', client_secret: 'test-secret' }; const mockUserInfo = { sub: 'user-id-123', email: 'user@example.com', name: 'Test User' }; // Мокаем http.request const originalRequest = http.request; let dataHandler; let endHandler; http.request = mock.fn((options, callback) => { assert.strictEqual(options.method, 'GET'); assert.ok(options.path.includes('/userinfo')); assert.strictEqual(options.headers.Authorization, 'Bearer test-token'); const mockRes = { statusCode: 200, on: mock.fn((event, handler) => { if (event === 'data') { dataHandler = handler; } if (event === 'end') { endHandler = handler; } }) }; callback(mockRes); return { write: mock.fn(), end: mock.fn(() => { if (dataHandler) { dataHandler(Buffer.from(JSON.stringify(mockUserInfo))); } if (endHandler) { endHandler(); } }), on: mock.fn() }; }); try { const keycloak = new KeycloakAPI(keycloakData); const result = await keycloak.getUserInfo('test-token'); assert.ok(result); assert.strictEqual(result.sub, 'user-id-123'); assert.strictEqual(result.email, 'user@example.com'); assert.strictEqual(result.name, 'Test User'); } finally { http.request = originalRequest; } }); test('должен обрабатывать ошибку 401', async () => { const keycloakData = { host: 'localhost', port: 8080, realm: 'test-realm', ssl: 'false', grant_type: 'password', client_id: 'test-client', client_secret: 'test-secret' }; // Мокаем http.request const originalRequest = http.request; let dataHandler; let endHandler; http.request = mock.fn((options, callback) => { const mockRes = { statusCode: 401, on: mock.fn((event, handler) => { if (event === 'data') { dataHandler = handler; } if (event === 'end') { endHandler = handler; } }) }; callback(mockRes); return { write: mock.fn(), end: mock.fn(() => { if (dataHandler) { dataHandler(Buffer.from('Unauthorized')); } if (endHandler) { endHandler(); } }), on: mock.fn() }; }); try { const keycloak = new KeycloakAPI(keycloakData); await assert.rejects( async () => await keycloak.getUserInfo('invalid-token'), /Request failed with status code: 401/ ); } finally { http.request = originalRequest; } }); }); describe('getUserByUsername', () => { test('должен успешно получать пользователя по username', async () => { const keycloakData = { host: 'localhost', port: 8080, realm: 'test-realm', ssl: 'false', grant_type: 'password', client_id: 'test-client', client_secret: 'test-secret' }; const mockUsers = [ { id: 'user-123', username: 'testuser', email: 'testuser@example.com' } ]; // Мокаем http.request const originalRequest = http.request; let dataHandler; let endHandler; http.request = mock.fn((options, callback) => { assert.strictEqual(options.method, 'GET'); assert.ok(options.path.includes('/admin/realms/test-realm/users')); assert.ok(options.path.includes('username=testuser')); const mockRes = { statusCode: 200, on: mock.fn((event, handler) => { if (event === 'data') { dataHandler = handler; } if (event === 'end') { endHandler = handler; } }) }; callback(mockRes); return { write: mock.fn(), end: mock.fn(() => { if (dataHandler) { dataHandler(Buffer.from(JSON.stringify(mockUsers))); } if (endHandler) { endHandler(); } }), on: mock.fn() }; }); try { const keycloak = new KeycloakAPI(keycloakData); const result = await keycloak.getUserByUsername('admin-token', 'testuser'); assert.ok(result); assert.ok(Array.isArray(result)); assert.strictEqual(result[0].username, 'testuser'); } finally { http.request = originalRequest; } }); }); describe('getUserGroup', () => { test('должен успешно получать группы пользователя', async () => { const keycloakData = { host: 'localhost', port: 8080, realm: 'test-realm', ssl: 'false', grant_type: 'password', client_id: 'test-client', client_secret: 'test-secret' }; const mockGroups = [ { id: 'group-1', name: 'Admins' }, { id: 'group-2', name: 'Users' } ]; // Мокаем http.request const originalRequest = http.request; let dataHandler; let endHandler; http.request = mock.fn((options, callback) => { assert.strictEqual(options.method, 'GET'); assert.ok(options.path.includes('/admin/realms/test-realm/users/user-123/groups')); const mockRes = { statusCode: 200, on: mock.fn((event, handler) => { if (event === 'data') { dataHandler = handler; } if (event === 'end') { endHandler = handler; } }) }; callback(mockRes); return { write: mock.fn(), end: mock.fn(() => { if (dataHandler) { dataHandler(Buffer.from(JSON.stringify(mockGroups))); } if (endHandler) { endHandler(); } }), on: mock.fn() }; }); try { const keycloak = new KeycloakAPI(keycloakData); const result = await keycloak.getUserGroup('admin-token', 'user-123'); assert.ok(result); assert.ok(Array.isArray(result)); assert.strictEqual(result.length, 2); assert.strictEqual(result[0].name, 'Admins'); } finally { http.request = originalRequest; } }); }); describe('getDataFromJWT', () => { test('должен декодировать JWT токен', () => { const keycloakData = { host: 'localhost', port: 8080, realm: 'test-realm', ssl: 'false', grant_type: 'password', client_id: 'test-client', client_secret: 'test-secret' }; // Создаем простой JWT токен (без подписи для теста) const payload = { sub: 'user-123', email: 'test@example.com', exp: Math.floor(Date.now() / 1000) + 3600 }; // Простой base64 encoded JWT (header.payload.signature) const header = Buffer.from(JSON.stringify({ alg: 'HS256', typ: 'JWT' })).toString('base64url'); const payloadEncoded = Buffer.from(JSON.stringify(payload)).toString('base64url'); const token = `${header}.${payloadEncoded}.signature`; const keycloak = new KeycloakAPI(keycloakData); const result = keycloak.getDataFromJWT(token); assert.ok(result); assert.ok(result.payload); assert.strictEqual(result.payload.sub, 'user-123'); assert.strictEqual(result.payload.email, 'test@example.com'); }); test('должен возвращать null для невалидного токена', () => { const keycloakData = { host: 'localhost', port: 8080, realm: 'test-realm', ssl: 'false', grant_type: 'password', client_id: 'test-client', client_secret: 'test-secret' }; const keycloak = new KeycloakAPI(keycloakData); const result = keycloak.getDataFromJWT('invalid.token.here'); // jwt.decode может вернуть null для невалидного токена assert.ok(result === null || result !== undefined); }); }); });