В системах аутентификации ключевым элементом является преобразование
пароля в необратимое значение. Библиотека password-hash
предоставляет простой механизм создания и проверки хешей, который удобно
использовать в учебных и небольших проектах.
Основные операции:
При модульном тестировании важно изолировать эти операции от внешних зависимостей (база данных, HTTP-слой), чтобы проверять только бизнес-логику.
Логика обычно разделяется на два слоя: сервис аутентификации и слой хранения данных.
Пример упрощённого сервиса:
const passwordHash = require('password-hash');
class AuthService {
constructor(userRepository) {
this.userRepository = userRepository;
}
register(username, password) {
const existing = this.userRepository.findByUsername(username);
if (existing) {
throw new Error('User already exists');
}
const hashedPassword = passwordHash.generate(password);
const user = {
id: Date.now(),
username,
password: hashedPassword
};
this.userRepository.save(user);
return user;
}
login(username, password) {
const user = this.userRepository.findByUsername(username);
if (!user) {
throw new Error('User not found');
}
const isValid = passwordHash.verify(password, user.password);
if (!isValid) {
throw new Error('Invalid password');
}
return user;
}
}
module.exports = AuthService;
Модульные тесты должны проверять:
Для этого используется мок-репозиторий:
class MockUserRepository {
constructor() {
this.users = [];
}
findByUsername(username) {
return this.users.find(u => u.username === username);
}
save(user) {
this.users.push(user);
}
}
module.exports = MockUserRepository;
Тест должен проверять, что пароль не сохраняется в открытом виде и пользователь корректно создаётся.
Пример на Jest:
const AuthService = require('./AuthService');
const MockUserRepository = require('./MockUserRepository');
const passwordHash = require('password-hash');
test('регистрация создаёт пользователя с хешированным паролем', () => {
const repo = new MockUserRepository();
const auth = new AuthService(repo);
const user = auth.register('alice', '123456');
expect(user.username).toBe('alice');
expect(user.password).not.toBe('123456');
expect(passwordHash.verify('123456', user.password)).toBe(true);
});
Ключевая проверка — использование passwordHash.verify, а
не прямое сравнение строк.
Важно проверить защиту от повторной регистрации:
test('нельзя зарегистрировать пользователя с уже существующим username', () => {
const repo = new MockUserRepository();
const auth = new AuthService(repo);
auth.register('alice', '123456');
expect(() => {
auth.register('alice', '999999');
}).toThrow('User already exists');
});
test('успешный логин с правильным паролем', () => {
const repo = new MockUserRepository();
const auth = new AuthService(repo);
auth.register('alice', '123456');
const user = auth.login('alice', '123456');
expect(user.username).toBe('alice');
});
Здесь важно, что логин проходит через проверку хеша, а не через прямое сравнение.
test('логин с неверным паролем вызывает ошибку', () => {
const repo = new MockUserRepository();
const auth = new AuthService(repo);
auth.register('alice', '123456');
expect(() => {
auth.login('alice', 'wrong-password');
}).toThrow('Invalid password');
});
test('логин несуществующего пользователя', () => {
const repo = new MockUserRepository();
const auth = new AuthService(repo);
expect(() => {
auth.login('ghost', '123456');
}).toThrow('User not found');
});
При работе с password-hash важно учитывать:
verify.Принцип:
expect(user.password).toBe('123456')passwordHash.verify(password, user.password)Дополнительно проверяются нестандартные сценарии:
test('регистрация с пустым паролем', () => {
const repo = new MockUserRepository();
const auth = new AuthService(repo);
expect(() => {
auth.register('alice', '');
}).toThrow();
});
test('регистрация с длинным паролем', () => {
const repo = new MockUserRepository();
const auth = new AuthService(repo);
const longPassword = 'a'.repeat(1000);
const user = auth.register('alice', longPassword);
expect(passwordHash.verify(longPassword, user.password)).toBe(true);
});
Модульные тесты требуют полного контроля над данными. Мок-репозиторий обеспечивает:
Иногда добавляется очистка состояния:
afterEach(() => {
repo.users = [];
});
Интеграция двух операций в одном тесте:
test('полный цикл регистрации и логина', () => {
const repo = new MockUserRepository();
const auth = new AuthService(repo);
auth.register('alice', '123456');
const loggedUser = auth.login('alice', '123456');
expect(loggedUser.username).toBe('alice');
});
Модульные тесты часто обнаруживают:
undefined пользователя;verify.Такие дефекты выявляются без запуска всего приложения, только на уровне изолированного модуля.
Хотя password-hash скрывает реализацию соли, важно
убедиться, что:
verify;test('хеш всегда строка', () => {
const hash = passwordHash.generate('123456');
expect(typeof hash).toBe('string');
});