Files
store-platform/tests/utils/keycloak.test.js
T
2025-12-07 13:34:02 +03:00

756 lines
21 KiB
JavaScript

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);
});
});
});