Интеграционные тесты с реальной базой данных в проектах, использующих password-hash в JavaScript, проверяют не только корректность хэширования паролей, но и целостность всей цепочки: от получения пользовательского ввода до записи и чтения данных из хранилища. В отличие от модульных тестов, где библиотека хэширования изолируется и подменяется моками, здесь проверяется поведение системы в условиях, максимально приближенных к продакшену.
Основная цель таких тестов заключается в подтверждении того, что процесс регистрации и аутентификации не нарушается на уровне взаимодействия с базой данных, а также что хэши паролей корректно сохраняются и сравниваются в реальных условиях.
Библиотека password-hash в JavaScript обычно используется как абстракция над криптографическими алгоритмами (bcrypt, argon2 или pbkdf2). На уровне интеграции важно учитывать:
В контексте базы данных эти свойства проверяются через полный цикл: создание пользователя → сохранение хэша → извлечение → проверка пароля.
Использование реальной базы данных в тестах требует строгой изоляции окружения. Обычно применяются следующие подходы:
1. Отдельная тестовая база
2. Контейнеризация
3. In-memory решения (ограниченно)
Пример конфигурации окружения:
process.env.DB_HOST = "localhost";
process.env.DB_PORT = "5433";
process.env.DB_NAME = "test_db";
process.env.DB_USER = "test_user";
process.env.DB_PASSWORD = "test_password";
Типичный интеграционный тест для password-hash включает несколько этапов:
import { hashPassword, verifyPassword } fr om "password-hash";
import { db } fr om "../db";
beforeEach(async () => {
await db.query("DELETE FR OM users");
});
test("создание пользователя сохраняет хэш пароля", async () => {
const password = "securePassword123";
const hashed = await hashPassword(password);
await db.query(
"INS ERT IN TO users (email, password_hash) VALUES ($1, $2)",
["user@test.com", hashed]
);
const user = await db.query(
"SEL ECT * FR OM users WH ERE email = $1",
["user@test.com"]
);
const isValid = await verifyPassword(password, user.rows[0].password_hash);
expect(isValid).toBe(true);
});
В интеграционных тестах важно убедиться, что хэш:
Пример проверки стабильности:
test("хэш остается валидным после повторного чтения из БД", async () => {
const password = "myPassword";
const hashed = await hashPassword(password);
await db.query(
"INS ERT IN TO users (email, password_hash) VALUES ($1, $2)",
["stable@test.com", hashed]
);
const firstRead = await db.query(
"SEL ECT password_hash FR OM users WH ERE email=$1",
["stable@test.com"]
);
const secondRead = await db.query(
"SEL ECT password_hash FR OM users WH ERE email=$1",
["stable@test.com"]
);
const validFirst = await verifyPassword(password, firstRead.rows[0].password_hash);
const validSecond = await verifyPassword(password, secondRead.rows[0].password_hash);
expect(validFirst).toBe(true);
expect(validSecond).toBe(true);
});
Использование транзакций позволяет ускорить тестирование и упростить откат состояния базы:
beforeEach(async () => {
await db.query("BEGIN");
});
afterEach(async () => {
await db.query("ROLLBACK");
});
Однако при тестировании password-hash важно учитывать, что некоторые ORM могут кешировать соединения, что приводит к неожиданному поведению при параллельных тестах.
Интеграционные тесты часто моделируют реальный логин пользователя:
test("аутентификация пользователя с корректным паролем", async () => {
const password = "loginPass";
const hashed = await hashPassword(password);
await db.query(
"INS ERT IN TO users (email, password_hash) VALUES ($1, $2)",
["auth@test.com", hashed]
);
const user = await db.query(
"SEL ECT * FR OM users WH ERE email=$1",
["auth@test.com"]
);
const match = await verifyPassword(password, user.rows[0].password_hash);
expect(match).toBe(true);
});
Так как password-hash использует вычислительно дорогие алгоритмы, в интеграционных тестах учитывается:
Иногда в тестовом окружении используют пониженный cost factor:
const hashed = await hashPassword(password, { rounds: 4 });
Это ускоряет тесты, но не должно использоваться в продакшене.
Критически важно избегать утечек данных между тестами:
await db.query("TRUNCATE TABLE users RESTART IDENTITY");
Интеграционные тесты должны проверять и негативные кейсы:
test("ошибка при неверном пароле", async () => {
const hashed = await hashPassword("correct");
await db.query(
"INS ERT IN TO users (email, password_hash) VALUES ($1, $2)",
["fail@test.com", hashed]
);
const user = await db.query(
"SELECT * FR OM users WH ERE email=$1",
["fail@test.com"]
);
const result = await verifyPassword("wrong", user.rows[0].password_hash);
expect(result).toBe(false);
});
Для максимальной близости к production-среде применяется Testcontainers:
import { PostgreSqlContainer } fr om "@testcontainers/postgresql";
let container;
beforeAll(async () => {
container = await new PostgreSqlContainer().start();
process.env.DB_URL = container.getConnectionUri();
});
afterAll(async () => {
await container.stop();
});
Это позволяет запускать реальные СУБД в изолированных контейнерах.
При использовании Prisma, TypeORM или Sequelize важно учитывать:
Поэтому часто добавляют прямые SQL-запросы для критических проверок password-hash.
Каждая из этих ошибок приводит к ложным результатам тестирования и снижает надёжность проверки безопасности паролей.
Обычно структура выглядит следующим образом:
tests/
integration/
auth.test.js
users.test.js
setup/
db.js
teardown.js
Файл подключения БД:
export const db = new Pool({
host: process.env.DB_HOST,
port: process.env.DB_PORT,
database: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
});