Интеграционное тестирование аутентификации проверяет поведение системы как единого целого: HTTP-слой, бизнес-логику, работу с базой данных и криптографические операции с паролями. В случае использования bcrypt.js критически важно не изолировать хеширование паролей в моках, поскольку именно взаимодействие компонентов формирует реальную безопасность и корректность системы.
Ключевая задача таких тестов — подтвердить, что пользователь может зарегистрироваться, пароль корректно хешируется, затем успешно проходит проверку при входе, а также получает отказ при неверных данных.
bcrypt.js реализует алгоритм хеширования паролей на основе Blowfish cipher с добавлением соли. Основные свойства:
Типичный сценарий использования:
bcrypt.compareИнтеграционные тесты обычно охватывают следующую цепочку:
HTTP запрос → контроллер → сервис аутентификации → bcrypt.js → база данных
Пример структуры:
AuthController — принимает запросыAuthService — содержит бизнес-логикуUserRepository — взаимодействие с БДbcrypt.js — хеширование и проверка паролейДля интеграционных тестов используется отдельная тестовая база данных или in-memory решение (например, SQLite in-memory или MongoMemoryServer).
Пример конфигурации Jest:
module.exports = {
testEnvironment: "node",
setupFilesAfterEnv: ["./tests/setup.js"]
};
Подготовка базы:
beforeAll(async () => {
await db.connect();
});
afterEach(async () => {
await db.clear();
});
afterAll(async () => {
await db.close();
});
Тест должен подтверждать, что пароль не сохраняется в открытом виде.
Пример теста:
const request = require("supertest");
const app = require("../app");
const User = require("../models/User");
test("регистрация пользователя сохраняет хеш пароля", async () => {
await request(app)
.post("/auth/register")
.send({
email: "user@test.com",
password: "plainPassword123"
})
.expect(201);
const user = await User.findOne({ email: "user@test.com" });
expect(user.password).not.toBe("plainPassword123");
expect(user.password.startsWith("$2")).toBe(true);
});
Проверяется не только факт создания пользователя, но и структура хеша
bcrypt (префикс $2a$, $2b$ или
$2y$).
Сценарий входа включает сравнение пароля через
bcrypt.compare.
test("успешный вход с корректным паролем", async () => {
await request(app)
.post("/auth/register")
.send({
email: "login@test.com",
password: "securePass"
});
const response = await request(app)
.post("/auth/login")
.send({
email: "login@test.com",
password: "securePass"
})
.expect(200);
expect(response.body.token).toBeDefined();
});
test("отказ при неверном пароле", async () => {
await request(app)
.post("/auth/register")
.send({
email: "fail@test.com",
password: "correctPassword"
});
await request(app)
.post("/auth/login")
.send({
email: "fail@test.com",
password: "wrongPassword"
})
.expect(401);
});
bcrypt является CPU-интенсивной операцией, поэтому важно учитывать:
В production часто используется значение 10–12, но в тестах его снижают:
const saltRounds = process.env.NODE_ENV === "test" ? 4 : 10;
Это ускоряет выполнение тестов без изменения логики.
Мокирование bcrypt.hash или
bcrypt.compare:
Интеграционные тесты должны использовать реальный bcrypt.js.
Некоторые системы позволяют смену пароля или миграцию алгоритма.
test("обновление пароля изменяет bcrypt хеш", async () => {
await request(app)
.post("/auth/register")
.send({
email: "update@test.com",
password: "oldPassword"
});
const oldUser = await User.findOne({ email: "update@test.com" });
await request(app)
.post("/auth/change-password")
.send({
email: "update@test.com",
newPassword: "newPassword"
});
const updatedUser = await User.findOne({ email: "update@test.com" });
expect(updatedUser.password).not.toBe(oldUser.password);
});
bcrypt должен корректно обрабатывать некорректные входы:
test("bcrypt не падает при пустом пароле", async () => {
const response = await request(app)
.post("/auth/login")
.send({
email: "test@test.com",
password: ""
});
expect(response.status).toBe(400);
});
Интеграционные тесты должны учитывать параллельные регистрации:
test("одновременная регистрация не создаёт дубликаты", async () => {
const payload = {
email: "race@test.com",
password: "password123"
};
await Promise.all([
request(app).post("/auth/register").send(payload),
request(app).post("/auth/register").send(payload)
]);
const users = await User.find({ email: "race@test.com" });
expect(users.length).toBe(1);
});
Интеграционные тесты аутентификации часто требуют отключения логирования чувствительных данных:
Хеш bcrypt имеет предсказуемую структуру:
$2b$10$......................
Тестирование может включать:
expect(user.password).toMatch(/^\$2[aby]\$\d{2}\$/);
Это гарантирует корректный алгоритм и cost factor.
При изменении конфигурации безопасности может потребоваться перехеширование:
test("обновление salt rounds приводит к перехешированию", async () => {
process.env.BCRYPT_ROUNDS = "12";
await request(app)
.post("/auth/register")
.send({
email: "salt@test.com",
password: "testpass"
});
const user = await User.findOne({ email: "salt@test.com" });
const rounds = parseInt(user.password.split("$")[2], 10);
expect(rounds).toBe(12);
});