Функции seal и unseal в Iron отвечают за
криптографическую упаковку и распаковку данных. Их корректность
критична, поскольку любые ошибки приводят либо к невозможности
восстановить данные, либо к уязвимостям безопасности.
Юнит-тестирование этих операций строится вокруг трёх ключевых аспектов:
Особенность заключается в том, что криптографические операции часто содержат элементы случайности (nonce, IV), поэтому тесты не должны зависеть от точного совпадения зашифрованной строки.
Типичный набор тестов для seal и unseal
включает проверку обратимости:
seal должны успешно проходить через
unsealПример структуры:
import Iron from '@hapi/iron';
const password = 'strong-password';
const options = Iron.defaults;
Тест обратимости:
test('seal и unseal восстанавливают исходные данные', async () => {
const data = { user: 'alice', role: 'admin' };
const sealed = await Iron.seal(data, password, options);
const unsealed = await Iron.unseal(sealed, password, options);
expect(unsealed).toEqual(data);
});
Здесь проверяется фундаментальное свойство: криптографическое преобразование должно быть обратимым при корректных параметрах.
Одно из ключевых требований к unseal — строгая проверка
целостности. Любое изменение строки должно приводить к ошибке.
test('unseal должен отклонять изменённые данные', async () => {
const data = { id: 123 };
const sealed = await Iron.seal(data, password, options);
const tampered = sealed.slice(0, -2) + 'aa';
await expect(Iron.unseal(tampered, password, options))
.rejects
.toThrow();
});
Такая проверка гарантирует, что механизм аутентифицированного шифрования действительно защищает от модификации данных.
Критически важный сценарий — использование неверного ключа.
test('unseal должен отклонять неверный пароль', async () => {
const data = { session: 'token' };
const sealed = await Iron.seal(data, password, options);
await expect(Iron.unseal(sealed, 'wrong-password', options))
.rejects
.toThrow();
});
Эта проверка подтверждает, что данные не могут быть расшифрованы без корректного секретного ключа.
Iron сериализует данные перед шифрованием. Важно убедиться, что структура данных сохраняется без искажений.
Особое внимание уделяется:
test('корректная обработка сложных структур', async () => {
const data = {
user: {
id: 1,
tags: ['a', 'b', 'c'],
meta: {
active: true,
score: 42
}
}
};
const sealed = await Iron.seal(data, password, options);
const unsealed = await Iron.unseal(sealed, password, options);
expect(unsealed).toEqual(data);
});
Iron позволяет задавать параметры (например, TTL или алгоритмы). Эти
параметры должны быть строго согласованы между seal и
unseal.
Ошибка в конфигурации должна приводить к отказу восстановления данных.
test('ошибка при несогласованных options', async () => {
const data = { value: 100 };
const sealed = await Iron.seal(data, password, options);
const alteredOptions = {
...options,
ttl: 1000
};
await expect(Iron.unseal(sealed, password, alteredOptions))
.rejects
.toThrow();
});
Если используется параметр времени жизни, необходимо проверить истечение срока действия.
Для этого применяется управление временем через fake timers.
jest.useFakeTimers();
test('данные истекают после ttl', async () => {
const data = { session: 'active' };
const optionsWithTtl = {
...Iron.defaults,
ttl: 1000
};
const sealed = await Iron.seal(data, password, optionsWithTtl);
jest.advanceTimersByTime(2000);
await expect(Iron.unseal(sealed, password, optionsWithTtl))
.rejects
.toThrow();
});
Этот сценарий проверяет корректность работы механизма истечения срока действия защищённых данных.
Повторное шифрование одного и того же объекта не должно приводить к идентичному результату из-за использования случайных параметров (например, IV).
test('seal создаёт разные результаты для одинаковых данных', async () => {
const data = { id: 1 };
const first = await Iron.seal(data, password, options);
const second = await Iron.seal(data, password, options);
expect(first).not.toBe(second);
});
При этом оба значения должны успешно расшифровываться:
test('оба зашифрованных значения корректно восстанавливаются', async () => {
const data = { id: 1 };
const first = await Iron.seal(data, password, options);
const second = await Iron.seal(data, password, options);
expect(await Iron.unseal(first, password, options)).toEqual(data);
expect(await Iron.unseal(second, password, options)).toEqual(data);
});
Система должна устойчиво обрабатывать ошибки входа:
test('unseal отклоняет некорректные входные данные', async () => {
await expect(Iron.unseal('', password, options))
.rejects
.toThrow();
await expect(Iron.unseal(null, password, options))
.rejects
.toThrow();
});
При обновлении Iron важно учитывать обратную совместимость. Тесты могут включать фикстуры старых sealed-значений.
test('старый формат данных корректно расшифровывается', async () => {
const legacySealed = 'Fe26.2...legacy-value...';
const result = await Iron.unseal(legacySealed, password, options);
expect(result).toBeDefined();
});
Хотя точный результат seal не фиксируется, важно
контролировать структуру выходной строки:
test('sealed строка соответствует формату', async () => {
const data = { test: true };
const sealed = await Iron.seal(data, password, options);
expect(typeof sealed).toBe('string');
expect(sealed.split('.').length).toBeGreaterThan(1);
});