Границы проверок серверного проекта
У сервера есть несколько границ: предметные правила, repository базы, HTTP-обработчик, middleware и браузерная сессия. Один большой запрос не объяснит причину любой ошибки, а один тест validator не докажет правильный status code. Выберем сценарии так, чтобы каждый проверял конкретную договорённость и давал понятное место отказа.
В lesson-31 архива продолжения находится законченная матрица сценариев, исходные данные и таблица ожидаемых результатов. Этот урок не добавляет test runner и не запускает тесты: по текущему этапу весь материал готовится локально для чтения. Не выполнены также компиляция, SQL, HTTP и браузерный вход. Результат — план проверок с точными границами, а не отчёт «всё прошло».
Предметное правило проверяется без сети
Наш CourseValidation принимает nullable title/topic и число уроков. Проверка trim, диапазона 1–500 и разрешённых тем не зависит от доступности PostgreSQL. Это хороший отдельный уровень: можно передать команду и сопоставить набор ошибок, не поднимая сервер.
Возьмём знакомые значения. Название с крайними пробелами HTTP API должно после успешной validation нормализоваться в HTTP API. Число ноль должно дать ошибку lessons, неизвестная тема — ошибку topic, нулевой символ — ошибку title. Unicode-граница считается скалярами, поэтому нельзя заменить пример строкой из одинакового числа UTF-16 units и считать сценарий эквивалентным.
В матрице зафиксированы два соседних случая: ровно сто двадцать скаляров допускаются, сто двадцать один отклоняется. Это полезнее пары случайных длинных строк, потому что обнаруживает ошибку на границе. Но эти проверки не доказывают, что HTTP-handler действительно вызывает validator перед SQL.
Boundary: CourseValidation
Input: title=" HTTP API ", topic="frontend", lessons=8
Expected: no errors; normalized title="HTTP API"
Observed: not run
Поле Observed остаётся not run. Его нельзя заменять ожидаемым значением или вычислять из копии того же кода. Проверка имеет смысл, когда реальная реализация сравнивается с независимо сформулированным требованием.
Repository проверяет интеграцию со схемой
Параметризация SQL, типы Npgsql, имена столбцов и ограничения требуют отдельной базы с соответствующей схемой. Подмена repository словарём удобна для некоторых HTTP-сценариев, но не обнаружит неправильный тип bigint, неверный RETURNING или потерю internal_notes при UPDATE.
Для CRUD исходное состояние — четыре курса и сумма 60. Создание восьми уроков даёт пять курсов и 68; замена восьми на десять — пять и 70; удаление возвращает четыре и 60. Проверять нужно также ID, Location на HTTP-уровне и сохранность внутренней заметки, а не только арифметику.
Транзакционный сценарий 18 требует одновременно читать courses и audit. Намеренно неверная вторая запись должна оставить обе таблицы исходными. Версионный сценарий 19 требует двух команд с expectedVersion один: только первая принимается. Если проверить один UPDATE отдельно, потерянное обновление останется незамеченным.
Изоляция таких сценариев обязательна. Нельзя выполнять destructive cleanup в рабочей базе ProfessorWeb или делить одну подготовленную таблицу между параллельными примерами без отдельных схем/данных. Наши учебные ветки используют независимые начала; матрица отмечает, какой DDL относится к каждой ветке. Порядок статей не является разрешением выполнить все схемы подряд в одном production-кластере.
HTTP проверяет форму границы
Серверный интеграционный сценарий должен пройти routing, binding, middleware и сериализацию. Строка minLessons=wrong проверяет 400 до handler; отсутствующий ID — 404; неизвестный метод — 405. Для Problem Details важны content type, status и выбранный type, а не случайный requestId, который меняется от обращения к обращению.
WebApplicationFactory и TestServer позволяют строить часть таких проверок внутри процесса. Но они не заменяют реальный reverse proxy, сертификат, browser-cookie поведение и внешний socket. Если использовать in-memory transport, это должно быть известно из отчёта. Нельзя назвать его полноценным проходом продакшена от пользователя до хостинга.
Для документа OpenAPI отдельно сравниваются paths, параметры и schema с реально поддержанными ответами. Красиво сформированный JSON не доказывает, что endpoint возвращает те же поля. Поэтому контрактная проверка опирается на наблюдаемые HTTP-ответы, а чтение metadata остаётся подготовительной работой.
Сессия требует последовательности действий
Вход и CSRF проверяются не отдельным произвольным POST. Сценарий включает анонимный token, login, новую пару после смены личности, защищённое чтение и logout. Клиент должен сохранять cookies. Иначе 401 может означать просто забытый cookie jar, а не ошибку password verification.
Для политики нужен один и тот же аутентифицированный пользователь и два разных ресурса. JavaScript принадлежит ему, Markdown — нет. С правильным token первый rename допускается, второй получает 403. Без token собственная команда получает 400 и не меняет название. Эти случаи отделяют аутентификацию, право и происхождение операции.
Для файлов проверяются корректный UTF-8, неверная последовательность, размер выше предела, недоверенное имя и выдача чужому пользователю. Короткий успешный upload не доказывает отсутствие path traversal и не проверяет поведение при заполнении диска. В матрице явно указаны отсутствующие production-квоты и долговременные метаданные.
Отказы должны быть наблюдаемыми
Для внешнего HTTP важны задержка headers и задержка body, потому что одна настройка Timeout не обязательно охватывает оба этапа. Для health — различие liveness 200 и readiness 503 при недоступной зависимости. Для output cache — конкретные курсы в разных вариантах и новая генерация после истечения, а не придуманный процент ускорения.
Матрица содержит предусловие, действие, ожидаемую форму и наблюдение состояния после отказа. Это помогает заметить ошибку, когда status правильный, но данные уже изменились. Поле «прошло» без исходных условий не позволяет воспроизвести вывод. Также указано, какие сценарии требуют реальной PostgreSQL, HTTPS и второго процесса.
После редакционного этапа можно начать с предметных правил, затем изолированной базы и HTTP, а браузерные границы проверить отдельно. Такой порядок объясняет место ошибки и не выдаёт одно зелёное наблюдение за доказательство всей системы. Сейчас мы сохранили конкретную проверяемую договорённость; запуск остаётся последующим действием. Возможности интеграционного host описаны в документации ASP.NET Core.