Интеграционные тесты защищённых маршрутов

В веб-приложениях защищённые маршруты опираются на механизм аутентификации и авторизации, где сервер должен уметь проверять подлинность пользователя и целостность его сессии. В экосистеме Node.js одним из инструментов, применяемых для криптографического «запечатывания» данных, выступает библиотека @hapi/iron. Она позволяет сериализовать объект в строку с проверкой целостности и шифрованием, а затем безопасно восстанавливать его на сервере.

В контексте интеграционного тестирования защищённых маршрутов Iron часто используется косвенно — через сессионные cookies, JWT-обёртки или механизмы хранения состояния пользователя. Это требует тщательной подготовки тестового окружения, поскольку любые ошибки в процессе sealing/unsealing приводят к невозможности воспроизвести корректную авторизацию в тестах.

Модель защищённого маршрута в приложении

Типичная архитектура защищённого маршрута включает следующие элементы:

  • middleware проверки аутентификации
  • извлечение сессионных данных (cookie или заголовки)
  • декодирование и проверка целостности данных (в том числе через Iron)
  • принятие решения о доступе

Пример упрощённой логики:

import Iron from '@hapi/iron';

const password = process.env.IRON_PASSWORD;

async function authMiddleware(req, res, next) {
  const sealed = req.cookies.session;

  if (!sealed) {
    return res.status(401).send('Unauthorized');
  }

  try {
    const session = await Iron.unseal(sealed, password, Iron.defaults);

    req.user = session.user;
    next();
  } catch (e) {
    return res.status(401).send('Invalid session');
  }
}

Здесь Iron выполняет критическую функцию: гарантирует, что данные сессии не были изменены клиентом.

Особенности интеграционного тестирования защищённых маршрутов

Интеграционные тесты проверяют работу системы целиком: маршруты, middleware, работу с cookies, сериализацию сессий и криптографические операции.

Основные задачи таких тестов:

  • проверка доступа к защищённым маршрутам без авторизации
  • проверка доступа с корректной сессией
  • проверка отказа при повреждённой или поддельной сессии
  • проверка поведения при истёкших или некорректных данных

Ключевая сложность — корректное формирование sealed-данных, совместимых с Iron.

Подготовка тестового окружения

Для интеграционных тестов обычно используются:

  • jest — тестовый фреймворк
  • supertest — HTTP тестирование Express/Hapi приложений
  • отдельный конфиг с фиксированным IRON_PASSWORD

Важно: пароль для Iron должен быть одинаковым в приложении и тестах, иначе расшифровка будет невозможна.

process.env.IRON_PASSWORD = 'test-iron-password';

Формирование тестовой сессии

Чтобы протестировать защищённый маршрут, необходимо создать валидную сессию, зашифрованную Iron.

import Iron from '@hapi/iron';

const password = process.env.IRON_PASSWORD;

export async function createSessionCookie(user) {
  const session = {
    user: {
      id: user.id,
      role: user.role
    }
  };

  const sealed = await Iron.seal(session, password, Iron.defaults);

  return `session=${sealed}`;
}

Эта функция используется в тестах для имитации авторизованного пользователя.

Тестирование доступа без авторизации

Первый слой проверки — защита от неавторизованных запросов.

import request from 'supertest';
import app from '../app';

test('доступ к защищённому маршруту без сессии запрещён', async () => {
  const res = await request(app)
    .get('/api/profile');

  expect(res.status).toBe(401);
});

Тестирование доступа с валидной сессией

Здесь важно корректно сформировать Iron-сессию и передать её в cookie.

import request from 'supertest';
import app from '../app';
import { createSessionCookie } from './helpers/session';

test('доступ к защищённому маршруту с валидной сессией разрешён', async () => {
  const cookie = await createSessionCookie({
    id: 1,
    role: 'user'
  });

  const res = await request(app)
    .get('/api/profile')
    .set('Cookie', cookie);

  expect(res.status).toBe(200);
  expect(res.body.user.id).toBe(1);
});

Проверка подделанной сессии

Iron защищает данные от модификации, поэтому изменение даже одного символа должно приводить к ошибке.

test('поддельная сессия отклоняется', async () => {
  const fakeCookie = 'session=invalid-token';

  const res = await request(app)
    .get('/api/profile')
    .set('Cookie', fakeCookie);

  expect(res.status).toBe(401);
});

Тестирование повреждённых Iron-данных

Дополнительно важно проверить случаи, когда данные выглядят корректно, но повреждены криптографически.

test('повреждённая Iron-сессия отклоняется', async () => {
  const cookie = await createSessionCookie({ id: 1 });

  const corrupted = cookie + 'a';

  const res = await request(app)
    .get('/api/profile')
    .set('Cookie', corrupted);

  expect(res.status).toBe(401);
});

Тестирование роли пользователя

Защищённые маршруты часто зависят не только от факта аутентификации, но и от роли.

test('доступ запрещён для пользователя без прав администратора', async () => {
  const cookie = await createSessionCookie({
    id: 2,
    role: 'user'
  });

  const res = await request(app)
    .delete('/api/admin/users/1')
    .set('Cookie', cookie);

  expect(res.status).toBe(403);
});

Проверка интеграции Iron с middleware

Особое внимание уделяется тому, как Iron интегрирован в middleware слой.

Типовые ошибки:

  • различие password между тестами и приложением
  • использование разных Iron.defaults
  • некорректная сериализация вложенных объектов
  • попытка использовать устаревшие sealed-строки

Для выявления таких проблем добавляются тесты уровня инфраструктуры:

test('Iron корректно шифрует и расшифровывает сессию', async () => {
  const original = { user: { id: 10 } };

  const sealed = await Iron.seal(original, process.env.IRON_PASSWORD, Iron.defaults);
  const unsealed = await Iron.unseal(sealed, process.env.IRON_PASSWORD, Iron.defaults);

  expect(unsealed).toEqual(original);
});

Частые проблемы в интеграционных тестах с Iron

Несовпадение конфигурации

Даже небольшое отличие в параметрах шифрования приводит к невозможности декодирования данных.

Нестабильные тесты из-за времени жизни сессии

Если система включает TTL, тесты могут падать из-за истечения срока.

Попытка мокать Iron вместо реального использования

Мокирование снижает ценность интеграционного теста, так как убирает криптографический слой.

Неправильная передача cookies

Некорректный формат заголовка Cookie приводит к ложным ошибкам аутентификации.

Стратегия построения тестового набора

Эффективный набор интеграционных тестов защищённых маршрутов строится слоями:

  • базовая проверка доступа без авторизации
  • проверка валидной Iron-сессии
  • проверка повреждённых данных
  • проверка ролей и прав
  • проверка устойчивости middleware
  • проверка реальной интеграции с HTTP слоем

Такая структура позволяет выявить ошибки не только в бизнес-логике, но и в криптографическом и транспортном уровнях приложения.