185 lines
9.7 KiB
Markdown
185 lines
9.7 KiB
Markdown
# Соннет: анализ сравнительного тестирования Polygon ↔ реальный Nubes API
|
||
|
||
> Адресат: Claude Sonnet 4.6 (новый чат)
|
||
> ⛔ Режим: **диалог**. Задавай встречные вопросы если нужно уточнение.
|
||
> ⛔ НЕ редактировать файлы. Только анализ и советы в чат.
|
||
|
||
---
|
||
|
||
## Контекст
|
||
|
||
**Polygon** (v0.5.4) — эмулятор REST API облачной платформы Nubes.
|
||
3 стенда: dev (37 сервисов), test (37), prod (35). YAML-конфиги генерируются
|
||
из терраформ-репы (`~/tf_provider/generated/{dev,test,prod}/resources_yaml/`).
|
||
|
||
**Реальное API**:
|
||
- `https://lk-api-gateway-dev.ngcloud.ru/api/v1/svc`
|
||
- `https://lk-api-gateway-test.ngcloud.ru/api/v1/svc`
|
||
- `https://lk-api-gateway.ngcloud.ru/api/v1/svc`
|
||
|
||
Токены в `secrets/{dev,test,prod}.token`.
|
||
|
||
## Что сделано
|
||
|
||
Написан скрипт `compare_test.py` который сравнивает read-only эндпоинты
|
||
полигона и реального API:
|
||
- `GET /services` — список сервисов
|
||
- `GET /services/{id}` — операции (svcOperationId, operation, kind, action)
|
||
- `GET /instanceOperations/default/{id}` — cfsParams (id, код, dataType, isRequired)
|
||
|
||
Первый прогон показал:
|
||
- **dev**: операции совпадают, но у 2 параметров `dataType: None` вместо `"string"`
|
||
- **test**: аналогично
|
||
- **prod**: чисто, расхождений нет
|
||
|
||
Также обнаружено что реальный API возвращает HTML-entities в dataType
|
||
(`integer >= 0`), а полигон — чистый текст (`integer >= 0`).
|
||
Полигон здесь правильнее реального API.
|
||
|
||
## Ключевой нюанс: идеология стендов
|
||
|
||
Стенды НЕ идентичны. **Dev опережает test, test опережает prod**.
|
||
Новые сервисы и параметры появляются сначала в dev, потом через какое-то
|
||
время попадают в test, и только затем в prod. Поэтому:
|
||
|
||
- Если в dev-полигоне и dev-реальном API есть расхождения — это может быть
|
||
нормально (реальный API уже обновился, а YAML в полигоне — ещё нет)
|
||
- Если в prod есть расхождения — скорее всего баг в генерации YAML
|
||
- Нужно различать «допустимое отставание» и «реальный баг»
|
||
|
||
## Что нужно от тебя
|
||
|
||
### 1. Стратегия сравнительного тестирования
|
||
|
||
Как правильно сравнивать полигон с реальным API учитывая что:
|
||
- Стенды могут и должны отличаться
|
||
- YAML генерируется не в реальном времени, а батчами из терраформа
|
||
- Некоторые сервисы есть в реальном API но НЕ в терраформе (их не тестируем)
|
||
|
||
Что должно считаться PASS, а что FAIL? Какие допуски?
|
||
|
||
### 2. Какие ещё эндпоинты сравнивать?
|
||
|
||
Сейчас сравниваются 3 read-only эндпоинта. Какие ещё можно безопасно
|
||
сравнять? Что ещё есть в реальном API такого что полигон должен
|
||
повторять один-в-один?
|
||
|
||
### 3. Периодичность и автоматизация
|
||
|
||
Как часто запускать сравнение? При каких событиях (изменение терраформа,
|
||
деплой полигона)? Должно ли это быть частью CI?
|
||
|
||
### 4. dataType: None
|
||
|
||
В `from_stands.py` для некоторых параметров dataType падает в None
|
||
(хотя дефолт "string"). Где конкретно искать причину?
|
||
|
||
### 5. Общие советы
|
||
|
||
Что ещё мы упускаем в тестировании полигона? Какие сценарии, краевые
|
||
случаи, проверки контрактов?
|
||
|
||
---
|
||
|
||
## Формат диалога
|
||
|
||
Ты можешь:
|
||
- Сразу дать развёрнутый ответ по всем пунктам
|
||
- Или задать уточняющие вопросы — и тогда я отвечу, а ты продолжишь
|
||
|
||
Я хочу чтобы в итоге получился **конкретный план действий**:
|
||
что тестировать, как часто, что считать ошибкой, что — допустимым
|
||
расхождением.
|
||
|
||
---
|
||
|
||
## Ответ Соннета (2026-08-02)
|
||
|
||
### 4. dataType: None — первопричина найдена
|
||
|
||
`dict.get(key, default)` возвращает `default` **только если ключ отсутствует**.
|
||
Если в YAML написано `data_type: null` — ключ *есть*, значение — `None`,
|
||
дефолт `"string"` не срабатывает.
|
||
|
||
### Мои ответы
|
||
|
||
**Q4.1 — data_type: null в YAML?** Проверил — в терраформ-YAML нет
|
||
`data_type: null`. Реальный API возвращает `dataType: null` для параметра
|
||
`nestedRefExample` (param 396). Полигон возвращает `"string"` — он ПРАВИЛЬНО
|
||
применяет дефолт там, где реальный API отдаёт null. Это не баг полигона,
|
||
а улучшение.
|
||
|
||
**Q4.2 — _convert_sub_params?** Та же уязвимость потенциально есть, но не
|
||
проявляется — sub_params всегда имеют data_type.
|
||
|
||
**Q1.1 — частота регенерации YAML?** ВРУЧНУЮ. `from_stands.py` запускается
|
||
человеком когда он вспомнит. Никакого cron/webhook.
|
||
|
||
**Q1.2 — лаг от реального API до YAML?** Непредсказуемо. От часов до недель.
|
||
Зависит от того когда кто-то запустит `from_stands.py`.
|
||
|
||
**Q1.3 — потребитель результатов?** Разработчик. Ему нужно знать «полигон
|
||
устарел, перегенери YAML», а не «полигон сломан».
|
||
|
||
**Q2.1 — дополнительные эндпоинты в реальном API?** Не проверял. Надо
|
||
сравнить полный список эндпоинтов.
|
||
|
||
**Q2.2 — lifecycle поля?** Не сравниваются в текущем compare_test.py. Надо
|
||
добавить.
|
||
|
||
---
|
||
|
||
## Ответ Соннета — раунд 2
|
||
|
||
### Три категории расхождений — 👍 принимаю
|
||
|
||
| Категория | Значение | Реакция |
|
||
|---|---|---|
|
||
| 🔴 REAL BUG | полигон ≠ реальный API, полигон неправ | FAIL |
|
||
| 🟡 LAG | новый параметр в реальном API, нет в полигоне | WARN |
|
||
| 🟢 POLYGON BETTER | реальный API отдаёт null/entities, полигон — правильно | INFO |
|
||
|
||
Для prod 🟡 LAG тоже должен быть заметен.
|
||
|
||
### Мои ответы — раунд 2
|
||
|
||
**Q5.1 — сервис есть в полигоне, пропал из реального API?**
|
||
Теоретически да — если сервис удалили из реального API, а terraform ещё
|
||
не обновили. Это 🔴 REAL BUG и должно быть FAIL. Полигон не должен
|
||
эмулировать несуществующие сервисы.
|
||
|
||
**Q5.2 — HTML-entities?**
|
||
Нормализовать при сравнении: `html.unescape()` для real API перед сравнением.
|
||
Считать 🟢 POLYGON BETTER, не ошибка.
|
||
|
||
**Q5.3 — lifecycle поля?**
|
||
Проверил — ни реальный API, ни полигон НЕ возвращают `lifecycle` в
|
||
`GET /services/{id}`. Сравнивать нечего, вопрос снят.
|
||
|
||
---
|
||
|
||
## Ответ Соннета — раунд 3 (финальный)
|
||
|
||
### Q6.1 — defaultValue, valueList и др.
|
||
|
||
Реальный API возвращает **29 полей** на каждый cfsParam: `defaultValue`,
|
||
`valueList`, `isModifiable`, `isRequired`, `isHidden`, `descr`, `man`,
|
||
`regex`, `maxlength`, `minvalue` и т.д. Полигон возвращает подмножество
|
||
из ~6-8 полей.
|
||
|
||
Сравнивать нужно только те поля, которые `from_stands.py` реально генерирует:
|
||
`defaultValue`, `valueList`, `isModifiable`, `isRequired`. Остальные либо
|
||
отсутствуют в терраформ-YAML, либо не имеют смысла для мока.
|
||
|
||
### Q6.2 — cfsParamsByOp
|
||
|
||
Это **внутренний индекс** полигона, не API-эндпоинт. Связь «какие параметры
|
||
к какой операции» уже проверяется через `GET /instanceOperations/default/{id}`
|
||
— если в ответе правильный набор параметров, значит cfsParamsByOp правильный.
|
||
Отдельно сравнивать не нужно.
|
||
|
||
### Q6.3 — формат вывода
|
||
|
||
stdout + exit code — достаточно. Разработчик запускает вручную, смотрит
|
||
глазами. Файл отчёта переусложнит. Если понадобится история — можно потом.
|