> Markdown-версия страницы https://tuna.am/blog/ui-mocks-from-openapi/. Оглавление документации — [llms.txt](https://tuna.am/llms.txt), весь текст одним файлом — [llms-full.txt](https://tuna.am/llms-full.txt). Markdown-версия есть у любой страницы: добавьте `.md` к адресу без слеша на конце.

[Блог](https://tuna.am/blog.md)|[Как устроено](https://tuna.am/blog/tags/engineering)

# Кабинет на выдуманных данных: мок из OpenAPI прямо в Vite

12 ноября · 7 мин

Личный кабинет Tuna — это Vue-приложение, которое ходит за данными в наш API по пути `/v1`. Чтобы открыть его локально и покликать страницы, обычно нужен поднятый бэкенд: Go-сервер, Postgres, Redis, S3, сиды. Для вёрстки это тяжело. Поэтому у кабинета есть лёгкий режим: `yarn serve:mock` — и dev-сервер сам отвечает на все запросы API, выдумывая данные по OpenAPI-спеке.

Никакого отдельного мок-сервера и ни одного написанного руками ответа: форму каждого ответа плагин берёт из `swagger.json`, а значения генерирует на лету.

> **Коротко**
>
> * `yarn serve:mock` поднимает кабинет без бэкенда: Vite-плагин перехватывает `/v1` и сам отвечает.
> * Форму ответа плагин берёт из OpenAPI-спеки (путь → схема ответа 200), значения генерирует по JSON Schema.
> * Каждый рефреш — новый случайный, но валидный по контракту набор: разные длины списков (включая пустые), статусы, роли, тексты. Это дёшево вскрывает состояния, которые на фиксированных сидах ловишь редко.
> * Побочный эффект: мок держит спеку честной. Пустая страница значит, что ручка не описана или описана не так, как отдаёт сервер.

Весь кабинет на моке: туннели, домены, порты, записи SSH, статистика команды, участники, аудит и сессии бастиона. Бэкенд не запущен.

## Как это работает

Кабинет собирается на Vite. У Vite dev-сервера есть цепочка middleware (обычный connect), и плагин вешается на префикс `/v1`:

vite-plugins/spec-mock.js

```js
server.middlewares.use('/v1', (req, res, next) => {
    const spec = load();                       // swagger.json, один раз
    const reqPath = '/v1' + req.url.split('?')[0];
    const schema = responseSchema(spec, req.method, reqPath);
    // ...генерируем ответ по schema и отдаём JSON
});

```

Дальше всё держится на спеке. У нас сервер на Go, аннотации go-swagger превращаются в `swagger.json` — полную (админскую) спеку со всеми ручками. Плагин находит путь запроса (с учётом path-параметров: `/v1/domains/42` матчится на `/v1/domains/{id}`), берёт схему ответа `200`/`201` и строит по ней данные.

Генерация — рекурсивный обход JSON Schema:

* `enum` → случайное значение из списка;
* массив → 0–3 элемента (а для списков-страниц — пагинация через заголовки `x-total` / `x-per-page`);
* объект → по каждому полю;
* `boolean` / `integer` / `number` / строка.

Чтобы данные выглядели как настоящие, а не `string string`, для строк работают эвристики по имени поля:

vite-plugins/spec-mock.js

```js
if (n.includes('email'))     return `${pick(NAMES, k).toLowerCase()}@example.com`;
if (n.includes('avatar'))    return `https://www.gravatar.com/avatar/...`;
if (n.includes('thumbnail')) return `https://picsum.photos/seed/${k % 1000}/320/200`;
if (n.includes('url'))       return `https://${pick(WORDS, k)}.tuna.am`;

```

Отдельного процесса нет — это ещё один плагин в том же Vite dev-сервере, данные генерируются на каждый запрос.

Значения считаются от индекса элемента и случайной «соли». Соль выбирается заново для каждого ответа, поэтому строки одной таблицы различаются, а при рефреше меняется весь список.

## Что для этого нужно

Почти ничего, и в этом смысл. Нужна актуальная OpenAPI-спека (у нас она и так генерируется из аннотаций) и встроенный в Vite механизм middleware. Запуск — одна команда:

```shell
yarn serve:mock

```

Главное требование — не к инструменту, а к дисциплине: **ответы API должны быть описаны в спеке**. Нет описания ручки — плагину нечего генерировать, и страница приходит пустой.

## Чем помогает

**Кабинет открывается за секунды и где угодно.** Не нужны бэкенд, база, сиды, внешние сервисы. Фронтендер, дизайнер или ревьюер открывает ветку и сразу видит интерфейс.

**Каждый рефреш — другое состояние.** По сути, это фаззинг интерфейса валидными по контракту данными. На фиксированных сидах граничные случаи приходится заводить руками, а здесь они всплывают сами за пару минут прокликивания:

* пустые списки и пагинация (список может оказаться на 0 строк, на одну страницу или на две);
* длинный текст в полях, где закладывались на короткий;
* все значения `enum` — статусы, роли, типы — вперемешку;
* необязательные поля то есть, то нет.

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

**Спека остаётся честной.** Пустая или кривая страница в моке — почти всегда сигнал, что ручка не описана или описана не так, как реально отдаёт сервер. Так мок попутно работает проверкой контракта.

## Сложности подхода

Данные, валидные по схеме, запросто бывают бессмысленными, и все доработки живут ровно на этом стыке:

* **Коды, которые UI превращает в названия.** Поля вроде языков, ролей и локаций фронт мапит в человекочитаемые подписи. Случайная строка даёт пустые пункты в селектах — для таких полей в плагине есть точечные оверрайды с реальными кодами.
* **Связанные поля.** Домен с редиректом и шлюз, который этот редирект выполняет, должны согласовываться. Случайная генерация их рассинхронизирует, поэтому для доменов и шлюзов мок отдаёт заготовленные ответы с заданными состояниями строк таблицы.
* **Глубина рекурсии.** Генерация ограничена по глубине, иначе на циклических ссылках она бы не остановилась. Но и само ограничение дало побочные эффекты — о них ниже.
* **Отсутствие состояния.** Мок по спеке — это stub: `GET` отдаёт свежую случайную выдумку, `PATCH` ничего не «запоминает». Для ручек, где это важно (например, флаги просмотренных баннеров у текущего участника), мы добавили маленькое in-memory состояние на время жизни dev-сервера: `PATCH` пишет присланные поля, `GET` их накладывает. Фактически stub местами дорастает до fake.

## Что мок уже выявил

* **Неописанные ручки.** Пустые страницы подсветили ручки, которых не было в спеке. Мы дописали к ним аннотации, и спека стала полнее.
* **Описание, разошедшееся с реальностью.** Один список был описан как массив, а сервер отдаёт по нему объект-словарь. Мок честно сгенерировал массив — и страница повела себя не так, как с настоящим API. Поправили описание под реальный ответ.
* **Хрупкое место на фронте.** Экран отчёта падал на `user.id`: из-за ограничения глубины в массив попадал `null`-элемент, а код перебирал список без проверки. Формально это был баг генератора (его мы и починили — `null`-элементы теперь отсеиваются), но заодно это сигнал: граничный ввод нашёл место, где фронт слишком доверяет данным.
* **Ложное срабатывание эвристики.** Проверка «в имени поля есть `port`» цепляла `reports` и `new_reports` — и в счётчиках отчётов появлялись «порты» в диапазоне 20000–60000. Такова цена эвристик по имени поля. Проверку сузили до точного совпадения `port` и суффикса `_port`.

## Почему не взяли готовое

* **Полный локальный стенд** (Go-сервер + Postgres + Redis + сиды) у нас есть и остаётся основным инструментом для настоящего e2e и регресса. Но для «посмотреть вёрстку во всех состояниях» он тяжёлый: поднять, держать, пересобирать, а сиды фиксированы — редкие состояния всё равно заводить руками.
* **Фикстуры или хендлеры руками** (условный MSW) пришлось бы писать на каждую из сотен ручек и чинить при каждом изменении API. Мок из спеки обновляется вместе со спекой сам.
* **Запись реальных ответов** (record/replay) — это снимок одного состояния: ни вариативности, ни пустых списков, да ещё и с настоящими данными в дампах.
* **Готовый мок-сервер по OpenAPI** (вроде Prism) — хорош как отдельный сервис. Но нам нужны были доменные доводки (коды→названия, согласованные строки таблиц, stateful-ручки, фикс тарифа для гейта раздела), и хотелось обойтись без лишнего процесса: ещё один плагин в том же Vite dev-сервере, с hot-reload и данными на лету. Небольшой свой плагин оказался проще, чем тянуть и обвязывать внешний сервер.

Подход не заменяет стенд и тесты — он закрывает узкую, но частую задачу: быстро посмотреть интерфейс на разнообразных данных и заодно не дать спеке разойтись с реальностью.

## Оставьте отзыв

Если вам нравится пользоваться Tuna, или наоборот вы недовольны чем-либо, то пожалуйста [оставьте отзыв](https://my.tuna.am/?show_feedback=1&lang=ru).

![](/blog/img/2024-08-22/feedback.gif)

## Новости

Анонсы новых функций и статей публикуем в [Telegram-канале](https://t.me/tuna_am). Подписывайтесь, чтобы ничего не пропустить.

## Помощь

Мы ценим наших пользователей и детально изучаем все обращения, если у вас возникли проблемы с tuna – обязательно свяжитесь с нами одним из способов:

* [Электронная почта](mailto:info@tuna.am)
* [Telegram сообщество](https://telegram.me/+Kgbtt0_gJwI5ZmEy)

Категории: [Как устроено](https://tuna.am/blog/tags/engineering)

[старее →🔍 Новый инспектор с API для ИИ-агентов](https://tuna.am/blog/new-inspector.md)
