Тест-кейсы и деплой MCP-сервера для Лавки Школы 21

Аннотация: В этом групповом проекте ты продолжишь работу над MCP-сервером для Лавки «Школы 21». В предыдущем групповом проекте команда спроектировала tools, собрала архитектуру domain / storage / gateway, обработала ошибки и проверила сервер через MCP Inspector. Теперь нужно сделать следующий шаг: проверить, можно ли этому серверу доверять, описать тест-кейсы, покрыть ключевую логику автоматическими тестами, настроить GitLab CI и упаковать проект в Docker.

Содержание

Введение

Привет! Это твой 9-й и 10-й день заплыва - проверка на прочность, финальный командный проект. Твоя роль сегодня двойная - QA Engineer и DevOps. Ты не просто плывёшь — ты проверяешь, выдерживает ли корабль шторм. Это контроль качества, надёжность и автоматизация. Ты превратишь требования в тест-кейсы, бизнес-правила — в автоматические проверки, а разрозненный код — в воспроизводимый продукт. Это не просто проверка кода — это проверка всей команды. Вы покажете, что продукт можно доверять, что он готов к использованию. Один последний рывок — и вы на финише.

Этот проект полезен не только будущим разработчикам. QA-инженер здесь учится превращать требования и контракты tools в тест-кейсы. DevOps/SRE — запускать приложение воспроизводимо и автоматически проверять его после изменений. BSA и проджект-менеджер — связывать требования, ошибки и приемку. Продуктовый специалист — понимать, какие пользовательские сценарии должны быть защищены проверками. Специалист по кибербезопасности — замечать небезопасные настройки, секреты и некорректную обработку ошибок.

Удачи!

Chapter I

Рекомендации к проекту

Как учиться в «Школе 21»:

Как работать с проектом:

Рекомендуемое распределение ролей (один человек может иметь несколько ролей):

Роль За что отвечает
Team Lead / Release Lead координация, ветки, merge requests, итоговый handoff, презентация результата, README, test summary, скриншоты, оформление результатов
QA Lead test_cases.md, ручные проверки, контроль покрытия сценариев
Developer unit- и integration-тесты, исправление дефектов, рефакторинг под тестируемость
DevOps Dockerfile, docker-compose, GitLab CI, команды запуска, проверка воспроизводимости

Роли можно распределить иначе, но каждый участник должен понимать общий результат и уметь объяснить не только «свою» часть.

Дисклеймер:

Chapter II. От MCP-сервера к тестированию и деплою

В предыдущем групповом проекте вы собрали MCP-сервер для Лавки «Школы 21». У него появились tools: показать каталог, получить товар, оформить заказ, отменить заказ, узнать статус, подтвердить выдачу и проверить баланс коинов. Вы разделили код на domain, storage и gateway, подключили SQLite, описали контракты в JSON-схемах, добавили обработку ошибок и проверили сервер через MCP Inspector.

На первый взгляд продукт готов: сервер запускается, tools видны, несколько вызовов прошли успешно. Но в реальной разработке этого недостаточно.

Представь, что ИИ-агент участника Основы действительно пользуется вашим сервером. Участник просит: «Подбери мне худи размера M и оформи заказ». Агент вызывает create_order. Сервер отвечает ok: true. Коины списались. Остаток уменьшился. Заказ появился.

Теперь менее удобная ситуация: пользователь пытается купить товар, которого нет; отменить уже выданный заказ; подтвердить выдачу второй раз; купить худи, когда размер закончился; заказать больше лимита. Если команда не проверила такие сценарии, она не может уверенно сказать: «Сервер готов к передаче дальше».

Тест-кейс — это зафиксированное ожидание: при таких условиях, с такими входными данными, система должна повести себя вот так. Автоматический тест — это способ проверять это ожидание снова и снова без ручной работы. Docker — это способ запускать проект одинаково на разных компьютерах. CI — это автоматическая проверка после push или merge request.

Требования для проверки

Основной сценарий проекта — MCP-сервер Лавки «Школы 21». Личный кейс из выбранных участниками сеттингов вынесен в бонусную часть. Так у всех команд остается единая обязательная база для тестирования и деплоя.

Минимально должны быть покрыты следующие обязательные tools:

Tool Что делает
list_products показывает каталог мерча и актуальные остатки
get_product показывает детали одного товара
create_order оформляет заказ, списывает коины и уменьшает остаток
cancel_order отменяет заказ до выдачи, возвращает коины и остаток
get_order_status возвращает статус заказа
confirm_pickup подтверждает выдачу заказа
get_coin_balance показывает баланс пользователя

Минимально должны быть проверены конфликтные сценарии:

Сценарий Пример ожидаемого поведения
Недостаточно коинов заказ не создается, возвращается INSUFFICIENT_COINS
Товар не существует возвращается PRODUCT_NOT_FOUND
Размер отсутствует или закончился возвращается SIZE_NOT_FOUND или OUT_OF_STOCK
Пользователь превысил лимит возвращается USER_LIMIT_EXCEEDED
Попытка отменить выданный заказ возвращается ORDER_STATUS_ERROR
Повторное подтверждение выдачи возвращается ORDER_STATUS_ERROR
Покупка последней единицы товара первый заказ проходит, следующий заказ получает OUT_OF_STOCK, остаток не уходит в минус

Ошибки должны возвращаться в едином формате:

{
    "ok": false,
    "error_code": "INSUFFICIENT_COINS",
    "message": "Не удалось оформить заказ: у пользователя student_02 недостаточно коинов. Нужно 500, доступно 120.",
    "details": {
        "required": 500,
        "available": 120
    }
}

Понятное сообщение об ошибке должно отвечать на три вопроса:

  1. Что произошло?
  2. Почему действие нельзя выполнить?
  3. Что пользователь или ИИ-агент может сделать дальше?

Плохое сообщение:

Error: failed

Понятное сообщение:

Не удалось оформить заказ: размер XL для товара hoodie закончился. Выберите другой размер или другой товар.

В этом проекте вы будете работать с несколькими уровнями проверки:

Уровень Что проверяет Пример
Test cases ожидаемое поведение системы на языке сценариев «нельзя купить товар при недостатке коинов»
Unit tests отдельные чистые функции domain.py can_purchase() возвращает отказ при превышении лимита
Integration tests совместную работу gateway, storage и domain вызов обработчика create_order меняет данные и возвращает корректный ответ
Проверка формата ответа единый контракт успешных и ошибочных ответов внутри integration-тестов ошибка содержит ok, error_code, message
MCP Inspector smoke tests поведение сервера через MCP-интерфейс tool виден и вызывается в Inspector
Docker checks воспроизводимый запуск проект собирается и тесты проходят внутри контейнера
CI checks автоматический запуск проверок после push или merge request pipeline в GitLab запускает pytest

Важное уточнение: MCP-сервер на stdio — это не обычный веб-сайт на localhost:8000. Поэтому в этом проекте не нужно делать отдельный web-адаптер и health-check endpoint. Достаточно, чтобы сервер можно было упаковать в Docker, а тесты проходили внутри контейнера и в GitLab CI.

Как использовать ИИ в этом проекте

Можно спрашивать:

Нельзя спрашивать:

После получения ответа от ИИ всегда обсуждайте его в команде, адаптируйте под свой проект и фиксируйте все значимые промпты и выводы в ai-logs.md.

Chapter III

Цель этого проекта: превратить учебный MCP-сервер в воспроизводимый мини-продукт для Лавки «Школы 21»: с тест-кейсами, автоматическими проверками, Docker-запуском, CI и понятной документацией.

Задание 1. Настройка командного репозитория в GitVerse

Прежде чем вы начнете писать тесты и настраивать конвейеры, вы должны создать единое цифровое пространство для команды. Без этого шага все остальные задания (CI, публикация образа) просто не смогут работать в командном режиме. До этого момента ваш код жил в репозитории одного из участников.

Зачем мы это делаем:

Ваша задача: создать место, где GitVerse сможет запускать workflow. (этот шаг выполняется с нуля, даже если код предыдущего проекта хранится в другом Git-сервисе).

Действия:

1. Каждый участник зарегистрируйте личную учетную запись на https://gitverse.ru/, подтвердите email и войдите в систему. Используйте личный пароль, который не повторяется в других сервисах. По возможности включите дополнительную защиту учетной записи.

2. Выберите владельца репозитория. Только владелец создает новый приватный репозиторий с именем school21-lavka-mcp.

3. При создании выберите видимость Private. Создавайте пустой репозиторий: не добавляйте README, .gitignore и лицензию через интерфейс, если эти файлы уже есть в исходной кодовой базе.

4. Не используйте mirror-import. GitVerse не запускает CI/CD для mirror-репозиториев. Обычный перенос Git-истории через новый remote не является зеркалированием.

5. В настройках репозитория добавьте участников команды как соавторов с правом записывать код. Не передавайте им пароль владельца или token. Каждый работает под своей учетной записью.

6. Выберите версию MCP-сервера из предыдущего проекта, которую будете использовать как исходную кодовую базу. Зафиксируйте:

Не объединяйте несколько разных реализаций участников на этом этапе.

7. Участник, ответственный за перенос, подготовьте локальную копию выбранного репозитория.

Если репозиторий еще не находится на вашем компьютере:

git clone <URL_ИСХОДНОГО_РЕПОЗИТОРИЯ>
cd <ИМЯ_КАТАЛОГА>
git switch <ИМЯ_ВЕТКИ>

Если локальная копия уже есть, перейдите в ее каталог и получите последние изменения:

cd <ПУТЬ_К_РЕПОЗИТОРИЮ>
git switch <ИМЯ_ВЕТКИ>
git pull

8. Проверьте состояние выбранной версии:

git status
git branch --show-current
git rev-parse HEAD
git remote -v

Убедитесь, что текущий commit совпадает с выбранной командой версией. Сначала сохраните или осознанно удалите незакоммиченные изменения. Не переносите .env, базы, токены и временные файлы.

9. Сохраните старый remote и добавьте GitVerse:

git remote rename origin previous-origin
git remote add origin https://gitverse.ru/<owner>/school21-lavka-mcp.git
git remote -v

Если remote origin отсутствовал, пропустите команду git remote rename.

10. Если GitVerse не принимает пароль учетной записи при HTTPS push, создайте в профиле отдельный token с доступом Репозитории. При запросе credentials укажите свой GitVerse-логин как username, а repository token как password. Разрешите системному credential manager сохранить его. Не вставляйте token в URL remote, shell history, .env или файлы проекта. Это не тот же credential, что package token GV_REGISTRY_TOKEN из задания 8: не переиспользуйте один token для разных назначений.

11. Создайте.gitignore в репозитории, в который включите виртуальные окружения, кэшированные файлы python, переменные окружения, локальные базы, локальные отчеты, системные файлы macos или windows.

12. Подготовьте и отправьте develop:

git switch develop
git push -u origin develop

Если ветки еще нет:

git switch -c develop
git push -u origin develop

13. В GitVerse откройте Настройки → Ветки и назначьте develop главной веткой. Это обязательно для ручного запуска: GitVerse показывает и запускает workflow_dispatch только для workflow-файла из главной ветки.

14. Остальные участники клонируют уже GitVerse-репозиторий:

git clone https://gitverse.ru/<owner>/school21-lavka-mcp.git
cd school21-lavka-mcp
git switch develop

15. Откройте Настройки → Репозиторий, включите тумблер CI/CD и сохраните настройки. Сам workflow появится после добавления файла в .gitverse/workflows/. Не создавайте и не регистрируйте self-hosted runner.

16. Создайте docs/gitverse_setup.md и запишите:

Не записывайте в этот файл пароли и токены.

Результат:

Задание 2. Принятие MCP-сервера на вход

Перед тем как писать тесты и Dockerfile, нужно понять состояние проекта. Это похоже на приемку кода от другой команды: сначала не исправляй всё подряд, а зафиксируй, что уже есть, что работает, что сломано и на что можно опираться. Особенно это важно, поскольку вы выбрали код только одного из участников команды — вам всем важно в него погрузиться и разобраться.

Если у вас нет кода из проекта 5: В папке materials/starter-kit/ находится MCP-сервер Лавки с 7 обязательными и 3 дополнительными tools. Скопируйте его содержимое в корень вашего репозитория и используйте как основу. Если у вас есть свой код — используйте его, предварительно убедившись, что все tools работают.

Зачем мы это делаем:

Ваша задача: Провести входную диагностику MCP-сервера, проверить его запуск и работоспособность через MCP Inspector, зафиксировать результаты в docs/mcp_audit.md и, при необходимости, исправить мелкие ошибки для прохождения минимального quality gate.

Действия:

1. Склонируйте репозиторий MCP-сервера из предыдущего группового проекта.

2. Создайте ветку git checkout -b develop

Если develop уже есть, создайте рабочую ветку:

git checkout develop
git checkout -b feature/mcp-audit

3. Проверьте структуру проекта. Минимально в репозитории должны быть:

contracts/
    tools_schemas.json

src/
    domain.py
    storage.py
    gateway.py
    server.py
    errors.py

README.md или HANDOFF.md

ai-logs.md

Если части файлов нет, не скрывайте это. Зафиксируйте состояние в docs/mcp_audit.md.

4. Установите зависимости и попробуйте запустить сервер:

python -m pip install -r requirements.txt
python src/server.py

Если requirements.txt отсутствует, создайте черновик вручную. Минимально вам могут понадобиться:

fastmcp
pytest
jsonschema

5. Проверьте запуск через MCP Inspector: npx @modelcontextprotocol/inspector python src/server.py

6. Проверьте входной quality gate:

Проверка Ожидаемый результат Статус
python src/server.py запускается сервер стартует без ошибки импорта passed / failed
Inspector видит tools отображаются 7 обязательных tools passed / failed
contracts/tools_schemas.json есть файл читается и содержит схемы passed / failed
list_products вызывается возвращает ok: true и список товаров passed / failed
get_coin_balance вызывается возвращает баланс пользователя passed / failed
create_order вызывается успешный заказ создается passed / failed
Ошибки не падают стеком наружу возвращается ok: false, error_code, message passed / failed
README/HANDOFF помогает запустить проект инструкции достаточно для нового участника passed / failed

7. Если сервер не проходит quality gate, зафиксируйте проблему и выберите один из путей:

Ситуация Что делать
Ошибка мелкая: не хватает зависимости, неправильный импорт, неверный путь к базе исправить в рамках задания 1
Ошибка средняя: один-два tools не работают, но архитектура понятна исправить и продолжить проект
Ошибка критичная: сервер не запускается, структура непонятна, tools отсутствуют использовать starter-kit из materials/ и перенести туда ваши контракты и бизнес-правила

Не тратьте весь проект на спасение полностью сломанного кода. Важная часть задания — честно диагностировать состояние и выбрать рабочий путь.

8. Откройте и заполните файл docs/mcp_audit.md по шаблону и создайте файл docs/known_issues.md, который дублирует в себя список известных проблем в свободной форме — с этим файлом вы еще будете продолжать работать далее.

Результат: В папке src добавлены: docs/mcp_audit.md — входная диагностика сервера, docs/known_issues.md — список известных проблем, если они есть. Исправления, необходимые для прохождения минимального quality gate.

Задание 3. Превращение контрактов tools в тест-кейсы

Поздравляем, теперь вся ваша команда погрузилась в то, как работает выбранный вами проект и какие подводные камни он в себе содержит!

В предыдущем групповом проекте вы описывали tools как контракты: входные параметры, успешные ответы и формат ошибок. Это было не зря — теперь эти контракты станут основой для тестирования.

Если контракт говорит, что create_order принимает user_id, product_id, size, quantity, значит должны быть тест-кейсы для корректных и некорректных значений этих параметров. Если бизнес-правило говорит, что нельзя купить больше лимита, значит должен быть тест-кейс на превышение лимита.

Зачем мы это делаем:

Ваша задача: Создать понятное описание контрактов tools (docs/tools_contract.md) и на его основе разработать минимум 14 тест-кейсов (positive, negative, boundary), сохранив их в docs/test_cases.md.

Действия:

1. Откройте contracts/tools_schemas.json, README/HANDOFF и материалы предыдущего проекта.

2. Создайте файл docs/tools_contract.md. Для каждого tool кратко опишите:

## create_order
Что делает: оформляет заказ на товар из Лавки.

Вход:
- user_id: string, обязательный
- product_id: string, обязательный
- size: string, обязательный
- quantity: integer, обязательный или по умолчанию 1

Успешный ответ:

- ok: true
- data.order_id: string
- data.status: created

Ошибки:

- PRODUCT_NOT_FOUND
- SIZE_NOT_FOUND
- OUT_OF_STOCK
- INSUFFICIENT_COINS
- USER_LIMIT_EXCEEDED

3. Сначала без ИИ придумайте минимум 7 тест-кейсов. Они должны покрывать:

4. Затем используйте ИИ как QA-ассистента. Пример промпта:

У нас есть MCP-сервер для Лавки Школы 21.

Tools: list_products, get_product, create_order, cancel_order, get_order_status, confirm_pickup, get_coin_balance.

Бизнес-правила: нельзя купить товар без достаточного баланса, нельзя купить отсутствующий размер, нельзя превысить лимит, нельзя отменить picked_up заказ, нельзя повторно подтвердить выдачу.

Предложи дополнительные test cases: positive, negative, boundary.

Не пиши код. Сформируй таблицу с колонками: ID, Tool, Scenario, Preconditions, Input, Expected Result, Type.

5. Сравните предложения ИИ со своими. Удалите лишнее, исправьте неточные сценарии, добавьте пропущенные. Промпт и ответ сохраните в файл ai-logs.md. Также добавьте комментарии команды: что взяли и что отклонили и почему.

6. Создайте docs/test_cases.md. Минимально нужно 14 тест-кейсов:

Формат таблицы test_cases.md:

ID Tool Сценарий Предусловие Входные данные Ожидаемый результат Тип Источник Автоматизирован?
TC-001 list_products Получить каталог В базе есть товары {} ok=true, список товаров не пустой positive команда да

В колонке «Источник» указывайте происхождение идеи: команда, ИИ, ИИ + доработка команды, из найденного бага.

В колонке «Автоматизирован?» пишите да, нет, частично или вручную. Не копируйте в таблицу код теста целиком. Здесь нужен сценарий проверки: что было на входе, что команда сделала и какой результат ожидала.

Результат: В папке src: docs/tools_contract.md — человекочитаемое описание контрактов tools, docs/test_cases.md — таблица минимум из 14 тест-кейсов, ai-logs.md — промпт для генерации дополнительных тест-кейсов, ответ ИИ и комментарии команды: что взяли, что отклонили и почему.

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

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

Теперь следующий шаг — создание тестового окружения.

Ваша задача: Создать тестовое окружение: папку tests/fixtures/ с seed-данными, скрипт сброса тестовой базы (scripts/reset_test_db.py) и smoke-тест (tests/test_smoke.py), проверяющий, что окружение работает.

Тест не должен зависеть от случайного состояния локальной базы. Если один пир запустил тест после десяти ручных заказов, а другой — на чистой базе, результат должен быть одинаковым. Поэтому вам нужна изолированная тестовая база и понятные стартовые данные.

В этом проекте не нужно с нуля проектировать большой набор тестовых данных. Используйте seed-данные из предыдущего MCP-проекта или starter-kit из materials/data. Задача — сделать так, чтобы тесты могли каждый раз начинаться с одинакового состояния.

Зачем мы это делаем:

Действия:

1. Создайте папку tests/fixtures/

2. В этой папке подготовьте минимальный тестовый набор данных. Можно использовать SQL-файл, Python-файл или JSON. Главное — чтобы тесты могли быстро создать одинаковое состояние.

Минимальный набор:

Пользователи:

student_01: 1000 коинов
student_02: 300 коинов
student_poor: 20 коинов

Товары:

hoodie: 500 коинов, размеры S=3, M=2, L=1, XL=0, лимит 1
tshirt: 250 коинов, размеры S=10, M=8, L=6, XL=4, лимит 2
stickers: 50 коинов, размер one_size=50, лимит 5

Заказы:

order_created: статус created
order_cancelled: статус cancelled
order_picked_up: статус picked_up

3. Создайте функцию или скрипт сброса тестовой базы. Например: scripts/reset_test_db.py

Скрипт должен:

4. Настройте код так, чтобы тесты использовали отдельную базу. Не используйте рабочий файл lavka.db внутри тестов.

Рекомендуемый подход:

import os
from pathlib import Path
DB_PATH = Path(os.getenv("LAVKA_DB_PATH", "data/test_lavka.db"))
DB_PATH.parent.mkdir(parents=True, exist_ok=True)

Скрипт сброса и тесты должны использовать один и тот же путь из LAVKA_DB_PATH. Если переменная не задана, для тестового окружения используется data/test_lavka.db.”

  1. Добавьте smoke-проверку окружения:
python scripts/reset_test_db.py
pytest tests/test_smoke.py -v

test_smoke.py должен проверять, что:

Кстати, fun fact! Первые smoke-тесты проводили печники, когда после сборки печи, ее затапливали и смотрели, откуда будет идти дым: только ли из положенных мест?

Результат: В папке src: tests/fixtures/ — тестовые данные или файлы для создания тестового состояния, scripts/reset_test_db.py или эквивалентный способ сброса тестовой базы, tests/test_smoke.py — минимальная проверка, что тестовое окружение работает. Раздел в README: как пересоздать тестовую базу.

Задание 5. Написание автоматических тестов

Теперь нужно превратить часть test cases в код. В этом проекте достаточно двух уровней: unit-тесты для domain.py и integration-тесты для цепочки gateway → storage → domain → response.

Ваша задача: Написать минимум 6 unit-тестов для domain.py и минимум 4 integration-теста для обработчиков tools. Все тесты должны проходить локально.

Зачем мы это делаем:

Действия (Часть A — Unit-тесты для domain.py):

domain.py — самый удобный слой для тестирования. Он не должен знать о SQLite, MCP, JSON и файлах. Это чистая бизнес-логика: можно ли купить, можно ли отменить, можно ли подтвердить выдачу, как изменятся баланс и остаток.

1. Убедитесь, что domain.py содержит чистые функции. Примеры:

def can_purchase(user_balance: int, product_price: int, already_purchased: int, user_limit: int) -> tuple[bool, str]:

...

def can_cancel(order_status: str) -> tuple[bool, str]:

...

def can_confirm_pickup(order_status: str) -> tuple[bool, str]:

...

2. Если функция сразу читает SQLite или вызывает MCP, вынесите бизнес-правило в отдельную чистую функцию.

3. Создайте файл tests/test_domain.py

4. Напишите минимум 6 unit-тестов. Минимальный набор:

Тест Что проверяет
test_can_purchase_when_balance_and_limit_are_ok покупка разрешена
test_can_purchase_rejects_insufficient_coins недостаточно коинов
test_can_purchase_rejects_user_limit превышен лимит
test_can_cancel_created_order заказ в статусе created можно отменить
test_can_cancel_rejects_picked_up_order выданный заказ нельзя отменить
test_can_confirm_pickup_rejects_cancelled_order отмененный заказ нельзя выдать

5. Соблюдайте структуру Arrange — Act — Assert:

def test_can_purchase_rejects_insufficient_coins():

    # Arrange
    user_balance = 100
    product_price = 500
    already_purchased = 0
    user_limit = 1

    # Act
    allowed, reason = can_purchase(user_balance, product_price, already_purchased, user_limit)

    # Assert
    assert allowed is False
    assert "коинов" in reason.lower() or "coins" in reason.lower()

6. Запустите pytest tests/test_domain.py -v

Действия (Integration-тесты для gateway/storage/domain):

Unit-тесты проверяют бизнес-правила по отдельности. Но MCP-сервер работает как цепочка: gateway получает входные данные, storage читает состояние, domain принимает решение, storage меняет данные, gateway возвращает ответ.

1. Сделайте так, чтобы основную логику tools можно было вызывать из тестов без ручного запуска MCP Inspector. Например, вынесите обработчики в обычные функции:

def handle_create_order(payload: dict, storage: Storage) -> dict:

...

@mcp.tool() может вызывать эту функцию, а тесты могут вызывать ее напрямую.

2. Создайте файл tests/test_gateway_integration.py

3. Напишите минимум 4 integration-теста:

Тест Что проверяет
успешный create_order заказ создан, баланс уменьшился, остаток уменьшился, ответ ok=true
create_order при недостатке коинов заказ не создан, ответ ok=false, error_code=INSUFFICIENT_COINS
cancel_order для created статус изменился на cancelled, баланс и остаток восстановлены
confirm_pickup для picked_up повторно ответ ok=false, error_code=ORDER_STATUS_ERROR

4. В integration-тестах обязательно проверьте формат ответа:

Для успеха:

assert response["ok"] is True
assert "data" in response

Для ошибки:

assert response["ok"] is False
assert "error_code" in response
assert "message" in response
assert "Traceback" not in response["message"]

5. Запустите все тесты:

pytest tests/ -v

Результат: В папке src: tests/test_domain.py — минимум 6 unit-тестов, tests/test_gateway_integration.py — минимум 4 integration-теста. Все тесты проходят командой pytest tests/ -v. При необходимости — рефакторинг src/domain.py, src/gateway.py, src/storage.py, чтобы код был тестируемым.

Задание 6. Проверка сервера через MCP Inspector

Автоматические тесты проверяют большую часть логики, но они не заменяют проверку через MCP-интерфейс. Нужно убедиться, что tools действительно видны и вызываются таким образом, как их будет видеть внешний ИИ-агент.

Зачем мы это делаем:

Ваша задача: Провести ручную smoke-проверку через MCP Inspector, зафиксировать результаты в docs/inspector_check.md и, при необходимости, обновить docs/known_issues.md.

Действия:

1. Запустите MCP Inspector:

npx @modelcontextprotocol/inspector python src/server.py

2. Проверьте минимум 5 tools через Inspector:

3. Заполните файл docs/inspector_check.md. Опишите в нем результат проверки выше

4. Если нашли дефект, не скрывайте его. Исправьте или добавьте в docs/known_issues.md.

5. Используйте ИИ для анализа непонятных логов, если нужно. В ai-logs.md сохраните:

Результат: В папке src: docs/inspector_check.md, logs/inspector_log.txt или скриншоты в screenshots/. Обновленный docs/known_issues.md, если остались известные проблемы. Обновленный ai-logs.md.

Задание 7. Настройка GitLab CI

CI, Continuous Integration — это автоматическая проверка проекта после изменений. Команда делает push или открывает merge request, GitVerse запускает pipeline, а pipeline показывает: тесты прошли или проект сломан.

Без CI команда часто узнает о проблемах слишком поздно: «у меня локально работало», «я забыл запустить тесты», «на ноутбуке другого участника другая версия Python». CI снижает этот риск.

Workflow хранится в .gitverse/workflows/ci.yml. В этом задании он только запускает тесты. Публикацию образа вы добавите в задании 8.

Ваша задача: настроить автоматический CI-пайплайн в GitVerse

Действия:

1. В терминале, находясь в корне локального репозитория, создайте ветку:

git switch develop
git pull
git switch -c feature/ci

Создайте файл workflow:

mkdir -p .gitverse/workflows
touch .gitverse/workflows/ci.yml

2. Откройте .gitverse/workflows/ci.yml в редакторе и соберите workflow по каркасу:

name: TODO

on:
  push:
    branches: [TODO]
  pull_request:
    branches: [TODO]
  workflow_dispatch:

jobs:
  tests:
    runs-on: TODO

    env:
      LAVKA_DB_PATH: TODO

    steps:
      - name: Получить код
        uses: TODO

      - name: Установить Python
        uses: TODO
        with:
          python-version: TODO

      # Добавьте шаги установки зависимостей,
      # сброса базы, запуска тестов
      # и сохранения JUnit artifact.

Workflow должен:

python -m pytest tests/ -v --junitxml=reports/junit.xml

Каталог reports/ должен быть создан до запуска тестов.

Используйте python -m pytest, чтобы тесты запускались тем же интерпретатором Python, который установлен в workflow.

Не используйте continue-on-error, allow_failure, || true и другие способы скрыть падение тестов. if: always() допустим только для загрузки JUnit artifact.

3. До отправки workflow повторите основные команды локально:

export LAVKA_DB_PATH=data/test_lavka_ci.db
mkdir -p reports
python scripts/reset_test_db.py
python -m pytest tests/ -v --junitxml=reports/junit.xml

Убедитесь, что тесты проходят и появился reports/junit.xml. Не добавляйте временную базу и локальный отчет в commit.

4. Отправьте workflow в GitVerse:

git add .gitverse/workflows/ci.yml
git commit -m "ci: add GitVerse test workflow"
git push -u origin feature/ci

В интерфейсе GitVerse создайте запрос на слияние:

Если после создания открылась несуществующая страница, вернитесь в раздел запросов и проверьте список. Также убедитесь, что ветка feature/ci отправлена и содержит изменения относительно develop.

5. В разделе CI/CD откройте workflow CI и проверьте:

Если запуск по событию pull_request не произошел из-за ограничения платформы, зафиксируйте это в docs/known_issues.md. После объединения workflow обязан автоматически запуститься по push в develop.

6. Проверьте сценарий «красный → зеленый»:

Не объединяйте заведомо красную версию.

7. Обновите README. Опишите события запуска, job tests, версию Python, путь тестовой базы, команду тестирования, JUnit artifact и место просмотра результатов в GitVerse.

Команда должна уметь объяснить:

Результат:

Задание 8. Сборка и публикация образа через Kaniko

В предыдущем задании вы научились проверять код. Теперь мы делаем шаг к поставке (Delivery). Мы упакуем наше приложение в Docker-образ — “контейнер”, который можно запустить на любом сервере, где есть Docker.

Примечание: GitVerse использует Hosted Runners (общие виртуальные машины). В целях безопасности эти раннеры не имеют доступа к docker.sock (системному сокету Docker). Это значит, что вы не можете выполнить команду docker build стандартным способом.

Вместо этого используем Kaniko — инструмент от Google, который собирает образы внутри контейнера без использования Docker-демона. Это стандартный подход для облачных CI/CD (как GitLab, так и GitVerse).

Сборка образа происходит только вручную (workflow_dispatch) и только из ветки develop, когда есть уверенность, что все тесты прошли. Это защищает нас от публикации сломанного продукта.

Ваша задача: упаковать MCP-сервер в Docker-образ и опубликовать его в GitVerse Packages.

Действия:

1. Подготовьте рабочую ветку

Получите актуальную версию develop и создайте ветку:

git switch develop
git pull --ff-only origin develop
git switch -c feature/image-publish

Если ветка уже существует, перед продолжением добавьте в неё актуальные изменения из develop.

2. Создайте .dockerignore

В корне репозитория создайте .dockerignore.

Исключите из build context:

Не исключайте src/, tests/, scripts/ и requirements.txt: они нужны для build-time проверок.

Объясните, чем .dockerignore отличается от .gitignore и почему каталог .docker/ не должен попадать в образ.

3. Создайте Dockerfile

Создайте Dockerfile на базе Python 3.11. Он должен:

Обязательные build-time проверки:

RUN python scripts/reset_test_db.py
RUN python -m pytest tests/ -v

Не используйте:

RUN python -m scripts/reset_test_db.py

reset_test_db.py запускается как файл по пути, а pytest — как установленный Python-модуль.

Если одна из проверок завершается с ошибкой, сборка должна остановиться и образ не должен публиковаться.

4. Подготовьте доступ к GitVerse Packages

Эти действия выполняет владелец репозитория.

1. Создайте отдельный package token с правом записи.

2. Сохраните token как repository secret:

GV_REGISTRY_TOKEN

3. Создайте repository variable:

GV_REGISTRY_OWNER=<логин владельца>

Используйте точный логин из URL репозитория. Например, для:

gitverse.ru/<username>/school21-lavka-mcp

значение переменной — username.

В workflow используйте только:

$

$

Не записывайте значение token в файлы проекта или логи.

5. Добавьте ручную публикацию в workflow

Откройте .gitverse/workflows/ci.yml и добавьте в jobs: вторую job — publish_image.

Используйте каркас:

publish_image:
  needs: tests

  if: >-
    $

  runs-on: ubuntu-latest

  steps:
    - name: Получить исходный код
      uses: actions/checkout@v4
      with:
        path: source

    - name: Подготовить registry credentials
      # Проверьте наличие GV_REGISTRY_OWNER и GV_REGISTRY_TOKEN.
      # Создайте .docker/config.json во временном workspace.
      # Значение auth — base64 от <owner>:<token>.

    - name: Собрать и опубликовать образ
      uses: docker://gcr.io/kaniko-project/executor:v1.24.0
      env:
        DOCKER_CONFIG: $/.docker
      with:
        args: >-
          --context=dir://$/source
          --dockerfile=$/source/Dockerfile
          --destination=gitverse.ru/$/school21-lavka-mcp:$
          --digest-file=$/image-digest.txt

    - name: Сохранить digest
      uses: actions/upload-artifact@v4
      with:
        name: image-digest
        path: $/image-digest.txt
        retention-days: 3

Registry config должен находиться вне каталога source. Kaniko получает только source как build context, поэтому credentials не должны быть доступны команде COPY . ..

Команда самостоятельно реализует создание корректного .docker/config.json и проверку наличия secret и variable.

6. Проверьте поведение в pull request

Отправьте ветку и создайте запрос на слияние:

feature/image-publish → develop

При запуске по pull request ожидается:

tests — выполнена

publish_image — пропущена

Пропуск publish_image в pull request является ожидаемым результатом: публикация разрешена только для ручного запуска из develop.

7. Выполните ручную публикацию

После зелёных тестов:

  1. Объедините изменения в develop.
  2. Откройте страницу workflow CI в разделе CI/CD.
  3. Выберите ручной запуск и ветку develop.
  4. Запустите workflow.

Не используйте кнопку повторного запуска pull request: она повторяет событие pull_request, поэтому publish_image снова будет пропущена.

Успешный запуск должен создать:

8. Найдите опубликованный пакет

Сначала откройте раздел Пакеты в профиле владельца.

Найдите:

school21-lavka-mcp

Если пакет не отображается во вкладке репозитория, привяжите его к репозиторию через настройки пакета.

Image reference должен иметь вид:

gitverse.ru/<owner>/school21-lavka-mcp:<commit-sha>

<owner> и <commit-sha> — placeholders. Угловые скобки не вводятся буквально.

9. Заполните release-документ

Создайте или обновите docs/gitverse_release.md:

Commit SHA:

Workflow URL:

Image reference:

Digest:

Package URL:

Build-time checks:

Runtime limitation:

Если готовый container не запускался, укажите:

Подтверждены Kaniko build и build-time тесты.

Итоговый container не запускался, поскольку hosted runner не предоставляет docker.sock.

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

Результат:

Теперь это готовый переносимый артефакт: его можно скачать на другой компьютер, запустить без установки Python-зависимостей, подключить как MCP-сервер или развернуть на сервере. GitVerse хранит Docker-образы в Container Registry по адресу вида gitverse.ru/владелец/образ:тег.

Для запуска на личном компьютере с установленным докером ты можешь вызвать его такой командой:

docker run --rm -i \
  gitverse.ru/<owner>/school21-lavka-mcp:<commit-sha>

Этот же образ можно использовать:

GitVerse отдельно публикует примеры deployment workflow для Kubernetes и Cloud.ru, причём ожидается, что образ уже собран и доступен в registry.

Задание 9. Подготовить handoff

Проект почти готов. Представьте, что завтра вас переведут в другой отдел, а за ваш сервер будет отвечать новый разработчик (или команда). Ваша задача — сделать так, чтобы этот человек смог:

Действия:

  1. Обновите README. В нем должны быть:
    • назначение сервера и архитектура;
    • семь tools;
    • локальная установка Python-зависимостей;
    • сброс тестовой базы и запуск тестов;
    • MCP Inspector;
    • структура GitVerse workflow;
    • автоматические и ручные события;
    • назначение Dockerfile и Kaniko;
    • image reference и ссылка на docs/gitverse_release.md;
    • ограничения текущей версии;
    • типовые ошибки без публикации credentials.
  2. Создайте docs/test_summary.md: test cases, автоматизация, Inspector, GitVerse tests job, Kaniko build и найденные дефекты.
  3. Создайте docs/handoff.md с порядком проверки от clone до просмотра опубликованного package.
  4. Убедитесь, что docs/known_issues.md, ai-logs.md и docs/gitverse_release.md соответствуют фактическому состоянию.
  5. Проверьте репозиторий на секреты и локальные артефакты:

git grep -nE 'GV_REGISTRY_TOKEN|gvpat_|/Users/|BEGIN (RSA|OPENSSH) PRIVATE KEY'

git status --short

Упоминание имени secret допустимо; его значение — нет. 6. Проведите внутри команды handoff-репетицию: другой участник читает документы, запускает Python-тесты и находит workflow/package в GitVerse.

Результат:

Chapter IV

Бонусное задание 10. Адаптация под личный кейс

Это задание необязательное, но очень рекомендованное. Подход тестирования и деплоя, отработанный на Лавке, можно применить и к личному сеттингу из предыдущих проектов (переговорки, консультации, мероприятия, оборудование, спортзал). Это задание покажет, что ваши навыки переносимы на другие домены.

Твоя задача: Продолжить один из пяти сеттингов, создать папку bonus_personal_case/, описать 5 тест-кейсов (2 positive, 2 negative, 1 boundary), реализовать одно изменённое domain-правило и написать 2 автоматических теста для него.Действия:

1. Создайте папку:

bonus_personal_case/

2. Создайте bonus_personal_case/personal_case_mapping.md. Шаблон возьмите в папке docs 3. Опишите 5 test cases для личного кейса в bonus_personal_case/test_cases.md. Шаблон возьмите в папке docs

Минимально:

4. Реализуйте или опишите одно измененное domain-правило. Например:

5. Напишите 2 автоматических теста для этого правила в:

bonus_personal_case/test_personal_case_domain.py

6. Заполните bonus_personal_case/adaptation_summary.md: Шаблон можно взять в папке docs.

Результат: В папке src находится папка bonus/<ваш_ник> с mapping, test cases, одним domain-правилом, двумя тестами и коротким summary.

Chapter IV

Результат проекта 9-го и 10-го дня

Основная часть (групповая) проекта в папке src:

В папке .gitverse/workflows/:

В папке docs/ (документация и отчёты):

В папке tests/:

В папке tests/fixtures/ (тестовые данные):

В папке scripts/:

Рекомендуется добавить скриншоты успешного запуска MCP Inspector (screenshots/inspector_check.png или logs/inspector_log.txt) и зелёного пайплайна в GitVerse (screenshots/ci_success.png), но это не является строгим требованием и может быть заменено текстовыми логами.

Вопросы для самопроверки:

  1. Чем test case отличается от автоматического теста?
  2. Почему test cases нужно строить от контрактов tools (tools_schemas.json), а не «на глаз»?
  3. Почему тесты не должны использовать рабочую базу данных?
  4. Чем unit-тест отличается от integration-теста?
  5. Почему domain.py легче тестировать, чем server.py?
  6. Почему тест без assert почти ничего не доказывает?
  7. Почему Traceback нельзя возвращать ИИ-агенту как обычное сообщение об ошибке?
  8. Почему ручная проверка через MCP Inspector не заменяет автоматические тесты?
  9. Почему автоматические тесты не заменяют проверку через MCP Inspector?
  10. Чем Docker-образ (image) отличается от контейнера (container)?
  11. В чём отличие Kaniko от стандартной команды docker build, и почему в GitVerse используется именно Kaniko?
  12. Чем Dockerfile отличается от docker-compose.yml?
  13. Почему MCP-сервер на stdio не открывается как обычный веб-сайт на localhost?
  14. Что такое CI и какую проблему он решает?
  15. Зачем в GitVerse CI нужны отдельные сущности: переменные (variables) и секреты (secrets)? Приведите пример использования.
  16. Почему в CI-пайплайне нельзя игнорировать падение тестов (например, через continue-on-error или || true)?
  17. Какой дефект (или какие дефекты) ваша команда нашла благодаря тестам, Docker или CI?
  18. Какие ограничения текущей версии (например, отсутствие runtime-проверки собранного контейнера в CI) самые важные для следующей команды, которая будет развивать проект?
  19. Почему публикация Docker-образа выполняется только вручную (workflow_dispatch), а не автоматически после каждого пуша в develop?
  20. Почему в .dockerignore важно исключить .env и локальные базы данных, но нельзя исключать src/, tests/ и scripts/?

Поздравляем! Заплыв завершается, проплыл весь путь бассейна и вынырнул. Ура!

Впереди у тебя еще 2 дня для выполнения бонусного заплыва и сдачи нормативов в виде тестового экзамена.

Представляешь, в первый день ты стоял на бортике, не зная, куда плыть. А сегодня ты стоишь на финише, оглядываясь на пройденный путь разработки: от идеи до релиза. Теперь ты умеешь тестировать, контейнеризовать и автоматизировать развёртывание своих приложений.

Ты не просто выполнил проекты — ты прошёл путь трансформации, попробовал себя в разных ролях. Это начало. Настоящее обучение в «Школе 21» еще впереди. Ты уйдешь с бассейна с портфолио из 8-ми или 9-ти проектов, с новыми друзьями, с пониманием того, как работают команды, и с уверенностью, что можешь больше, чем думал в первый день.

Гордись собой. Делай следующий шаг. Мир ждёт твоих продуктов.

Удачи на проверках!

💡Нажми сюда https://forms.yandex.ru/cloud/6a49492fd046888bb849b9d7 , чтобы поделиться с нами обратной связью на этот проект. Это поможет команде продукта сделать обучение лучше.