fix(1c-analyst): поле Description (не Content) + обязательная верификация GET-ом
Критичный баг-фикс по режиму «Пожелания из совещания». Проблема: сервер Devprom на POST /issue/items молча игнорирует поле Content, упомянутое в публичной документации /docs/8835.html. Запись создаётся с пустым телом, HTTP 200, UID возвращается — никакого сигнала об ошибке. Правильное имя поля для тела пожелания на эндпоинте /issue/items — Description. Выявлено при проверке U-6186 (Артур): поле Description пустое, хотя POST вернул валидный UID. Та же проблема обнаружилась на 6 пожеланиях Тепловин U-6189..U-6194 — все с пустым телом. Пересоздано как U-6198..U-6203 с полем Description, размер тела 1565–2172 символа, верификация пройдена. Правки скилла: - devprom-alm-api.md §0: новый блок «Критично: сервер молча отбрасывает незнакомые поля» + обязательная схема контрольного GET с ассертами. Список известных тихих ловушек (Content вместо Description, Type.Id='' в request/items, Requirement в POST). - devprom-alm-api.md §2: рецепт с Description + встроенные ассерты. - devprom-alm-api.md §3: справочник полей — Description с пояснением про Content и документацию, требование GET-проверки. - devprom-alm-api.md appendix: curl-примеры с Description. - meeting-wishes-extraction.md Шаг 6: Description в инструкции + ссылка на секцию «тихое отбрасывание». - meeting-wishes-extraction.md Шаг 7: жёсткая формулировка «Ответ POST — недостаточное подтверждение успеха», ассерты на префикс UID, содержимое Description, Priority, Function; визуальная проверка в UI. - meeting-wishes-extraction.md Python-шаблон: Description + ассерты на размер тела (>100 символов).
This commit is contained in:
@@ -36,6 +36,36 @@
|
||||
- **PATCH не поддерживается** — HTTP 405. Обновление только через PUT.
|
||||
- **Успешный ответ на создание:** иногда возвращается как объект, иногда — как массив с одним элементом. Учитывать оба случая.
|
||||
|
||||
### ⚠ Критично: сервер молча отбрасывает незнакомые поля
|
||||
|
||||
Devprom REST API **не возвращает ошибку**, если имя поля в теле POST
|
||||
не соответствует схеме сущности. Поле просто игнорируется, запись
|
||||
создаётся с остальными полями, HTTP 200, `UID` возвращается.
|
||||
|
||||
**Следствие:** ответ POST нельзя считать подтверждением, что данные
|
||||
сохранены. Успешный HTTP 200 + валидный JSON с `UID` означают только
|
||||
что запись появилась, но часть полей может быть потеряна.
|
||||
|
||||
**Правило:** после КАЖДОГО POST на `/issue/items` (и любой другой
|
||||
сущности, где критично содержимое) делать контрольный GET и глазами
|
||||
или ассертом проверять реально сохранённые значения. Минимум:
|
||||
|
||||
```python
|
||||
code, text = call("GET", f"issue/items/{new_id}")
|
||||
g = json.loads(text)
|
||||
assert g["UID"].startswith("U-"), "неверный префикс UID"
|
||||
assert len(g.get("Description","")) > 100, "тело пожелания пустое"
|
||||
# опционально: проверить Caption, Priority.Id, Function.Id, Company.Id
|
||||
```
|
||||
|
||||
Известные «тихие ловушки»:
|
||||
- `Content` вместо `Description` в теле POST на `/issue/items` →
|
||||
поле игнорируется, запись создаётся с пустым телом.
|
||||
- `Type: {"Id":""}` при POST на `/request/items` → игнорируется,
|
||||
сервер ставит `Type=397` (Доработка).
|
||||
- `Requirement` при POST на `/issue/items` → игнорируется
|
||||
(привязку делать через PUT или в UI).
|
||||
|
||||
## 1. Пожелания vs Заявки — КРИТИЧНО
|
||||
|
||||
В официальной схеме Devprom это **две разные сущности** с разными
|
||||
@@ -74,14 +104,20 @@ _Проверено эмпирически:_ `/request/items` при POST **вс
|
||||
|
||||
```python
|
||||
post_body = {
|
||||
"Caption": "<заголовок>",
|
||||
"Content": "<html-описание>", # по документации; Description тоже принимается
|
||||
"Priority": {"Id": "2"}, # 1=Критично, 2=Высокий, 3=Обычный
|
||||
"Author": {"Id": "<issueauthor.id>"},
|
||||
"Function": {"Id": "<feature.id>"}, # подсистема (группировка)
|
||||
"Company": {"Id": "<company.id>"}, # опционально: клиент-источник
|
||||
"Caption": "<заголовок>",
|
||||
"Description": "<html-тело>", # ← именно Description, НЕ Content
|
||||
"Priority": {"Id": "2"}, # 1=Критично, 2=Высокий, 3=Обычный
|
||||
"Author": {"Id": "<issueauthor.id>"},
|
||||
"Function": {"Id": "<feature.id>"}, # подсистема (группировка)
|
||||
"Company": {"Id": "<company.id>"}, # опционально: клиент-источник
|
||||
}
|
||||
# POST {BASE}/issue/items → {UID: "U-xxxx", Id: "xxxx", ...}
|
||||
|
||||
# ⚠ ОБЯЗАТЕЛЬНЫЙ контрольный GET — сервер может молча отбросить поля
|
||||
code, text = call("GET", f"issue/items/{new_id}")
|
||||
g = json.loads(text)
|
||||
assert g["UID"].startswith("U-"), f"префикс не U-: {g['UID']}"
|
||||
assert len(g.get("Description","")) > 100, "тело пожелания пустое"
|
||||
```
|
||||
|
||||
Поля `Requirement` и `RequirementDocument` в теле **не передаются**:
|
||||
@@ -106,7 +142,7 @@ Egress-прокси периодически роняет запросы с HTTP
|
||||
| Поле | Тип / формат | Обязательность | Комментарий |
|
||||
|-----------------------|------------------------------|----------------|-------------|
|
||||
| `Caption` | string | обязательно | Заголовок пожелания |
|
||||
| `Content` | HTML-string | обязательно | Описание. Разрешены: `<p> <b> <ul> <ol> <li> <a> <blockquote> <code>`. Поле `Description` на входе тоже принимается как алиас. |
|
||||
| `Description` | HTML-string | обязательно | **Тело пожелания.** Имя поля — `Description`, не `Content`. Эмпирически: `Content` на `/issue/items` при POST молча игнорируется (принимается, но не сохраняется). В публичной документации (`/docs/8835.html`) упомянуто поле `Content` — это относится к `/request/items` и/или документации в целом устаревшее; для `/issue/items` используйте только `Description`. Разрешённые теги HTML: `<p> <b> <ul> <ol> <li> <a> <blockquote> <code>`. **После POST обязательно делать GET и проверять `len(Description) > 100`** — сервер не возвращает ошибку при отброшенном поле. |
|
||||
| `Priority.Id` | "1" / "2" / "3" | по смыслу | 1=Критично, 2=Высокий, 3=Обычный |
|
||||
| `Author.Id` | issueauthor.id | обязательно | Автор пожелания. Справочник инстанс-уровня. |
|
||||
| `Function.Id` | feature.id | рекомендовано | Группировка по подсистемам (создаются через `/feature/items`) |
|
||||
@@ -255,8 +291,9 @@ curl -X POST \
|
||||
curl -X POST \
|
||||
-H "Devprom-Auth-Key: <KEY>" -H "Content-Type: application/json" \
|
||||
https://<host>/pm/<proj>/api/latest/issue/items \
|
||||
-d '{"Caption":"Пожелание","Content":"<p>Описание</p>","Priority":{"Id":"2"},"Author":{"Id":"1"},"Function":{"Id":"190"},"Company":{"Id":"146"}}'
|
||||
-d '{"Caption":"Пожелание","Description":"<p>Тело</p>","Priority":{"Id":"2"},"Author":{"Id":"1"},"Function":{"Id":"190"},"Company":{"Id":"146"}}'
|
||||
# → UID=U-xxxx, Type=пусто
|
||||
# ⚠ После создания — GET и проверить len(Description) > 100
|
||||
```
|
||||
|
||||
### Привязка к документу требований (не нужна в режиме «Пожелания из совещания»)
|
||||
@@ -267,7 +304,7 @@ curl -X POST \
|
||||
curl -X PUT \
|
||||
-H "Devprom-Auth-Key: <KEY>" -H "Content-Type: application/json" \
|
||||
https://<host>/pm/<proj>/api/latest/issue/items/6179 \
|
||||
-d '{"Caption":"Пожелание","Content":"<p>Описание</p>","Priority":{"Id":"2"},"Author":{"Id":"1"},"Function":{"Id":"190"},"Requirement":{"Id":"4378"}}'
|
||||
-d '{"Caption":"Пожелание","Description":"<p>Тело</p>","Priority":{"Id":"2"},"Author":{"Id":"1"},"Function":{"Id":"190"},"Requirement":{"Id":"4378"}}'
|
||||
```
|
||||
|
||||
### Удаление
|
||||
|
||||
@@ -108,14 +108,20 @@ _«Если бы это пожелание взял в работу разраб
|
||||
пустыми — пользователь вручную прикрепит их в UI, если/когда
|
||||
появится профильное требование.
|
||||
|
||||
**Тело пожелания отправляется в поле `Description`, НЕ `Content`.**
|
||||
Публичная документация Devprom (`/docs/8835.html`) упоминает `Content`,
|
||||
но на эндпоинте `/issue/items` это поле молча игнорируется сервером
|
||||
при POST. Эмпирически проверено — см. `references/devprom-alm-api.md`,
|
||||
раздел 0 «Сервер молча отбрасывает незнакомые поля».
|
||||
|
||||
```python
|
||||
post_body = {
|
||||
"Caption": wish["caption"],
|
||||
"Content": CUSTOMER_NOTE + wish["description"] + SOURCE_NOTE,
|
||||
"Priority": {"Id": wish["priority"]}, # "1"/"2"/"3"
|
||||
"Author": {"Id": AUTHOR_ID},
|
||||
"Function": {"Id": wish["function_id"]},
|
||||
"Company": {"Id": COMPANY_ID}, # опционально
|
||||
"Caption": wish["caption"],
|
||||
"Description": CUSTOMER_NOTE + wish["body"] + SOURCE_NOTE, # ← Description!
|
||||
"Priority": {"Id": wish["priority"]}, # "1"/"2"/"3"
|
||||
"Author": {"Id": AUTHOR_ID},
|
||||
"Function": {"Id": wish["function_id"]},
|
||||
"Company": {"Id": COMPANY_ID}, # опционально
|
||||
}
|
||||
# POST /issue/items → {UID: "U-xxxx", ...}
|
||||
```
|
||||
@@ -123,16 +129,33 @@ post_body = {
|
||||
Подробный справочник полей и поведение API — в
|
||||
`references/devprom-alm-api.md`.
|
||||
|
||||
### Шаг 7 — Верификация
|
||||
### Шаг 7 — Верификация по факту (обязательно)
|
||||
|
||||
После загрузки:
|
||||
**Ответ POST — недостаточное подтверждение успеха.** Сервер Devprom
|
||||
возвращает HTTP 200 и валидный JSON с `UID` даже тогда, когда часть
|
||||
полей была молча отброшена (например, из-за неправильного имени).
|
||||
Верификация идёт по результату GET, а не по ответу POST.
|
||||
|
||||
- Открыть `/pm/<project>/module/requirements/issues` → все загруженные пожелания должны отображаться одним списком.
|
||||
- UID у всех — префикс **`U-`**.
|
||||
- У каждого заполнены `Priority`, `Function`, `Author`. Поля `Requirement`
|
||||
и `RequirementDocument` пустые — это норма, привязку делает
|
||||
пользователь вручную в UI.
|
||||
- Открыть одну карточку случайно — проверить форматирование HTML, работу ссылок (mailto:), наличие цитат.
|
||||
Для каждого созданного пожелания — контрольный GET и ассерты:
|
||||
|
||||
```python
|
||||
code, text = call("GET", f"issue/items/{rid}")
|
||||
g = json.loads(text)
|
||||
assert g["UID"].startswith("U-"), f"неверный префикс: {g['UID']}"
|
||||
assert len(g.get("Description","")) > 100, "тело пожелания пустое"
|
||||
assert (g.get("Priority") or {}).get("Id") == expected_priority
|
||||
assert (g.get("Function") or {}).get("Id") == expected_function_id
|
||||
```
|
||||
|
||||
Дополнительно — после загрузки всей пачки открыть UI-раздел
|
||||
`/pm/<project>/module/requirements/issues`, убедиться визуально:
|
||||
|
||||
- Все загруженные пожелания отображаются одним списком
|
||||
- UID у всех — префикс `U-`
|
||||
- Случайно выбранная карточка открывается, форматирование HTML работает,
|
||||
ссылки (mailto:) кликабельны, цитаты в `<blockquote>` отрисованы
|
||||
- Поля `Requirement` и `RequirementDocument` пустые — это норма,
|
||||
привязку делает пользователь вручную в UI
|
||||
|
||||
### Шаг 8 — Сводка для пользователя
|
||||
|
||||
@@ -172,20 +195,22 @@ def call(method, path, body=None, retries=3):
|
||||
|
||||
for wish in WISHES:
|
||||
post_body = {
|
||||
"Caption": wish["caption"],
|
||||
"Content": CUSTOMER_NOTE + wish["description"] + SOURCE_NOTE,
|
||||
"Priority": {"Id": wish["priority"]},
|
||||
"Author": {"Id": AUTHOR_ID},
|
||||
"Function": {"Id": wish["function"]},
|
||||
"Company": {"Id": COMPANY_ID}, # опционально
|
||||
"Caption": wish["caption"],
|
||||
"Description": CUSTOMER_NOTE + wish["description"] + SOURCE_NOTE, # ← Description
|
||||
"Priority": {"Id": wish["priority"]},
|
||||
"Author": {"Id": AUTHOR_ID},
|
||||
"Function": {"Id": wish["function"]},
|
||||
"Company": {"Id": COMPANY_ID}, # опционально
|
||||
}
|
||||
code, text = call("POST", "issue/items", post_body)
|
||||
r = json.loads(text); r = r[0] if isinstance(r, list) else r
|
||||
issue_id = r["Id"]
|
||||
# проверка
|
||||
# ⚠ ОБЯЗАТЕЛЬНАЯ верификация: сервер молча отбрасывает незнакомые поля.
|
||||
# Ответа POST недостаточно — смотрим, что реально сохранилось.
|
||||
_, t = call("GET", f"issue/items/{issue_id}")
|
||||
g = json.loads(t)
|
||||
assert g["UID"].startswith("U-"), f"префикс не U-: {g['UID']}"
|
||||
assert g["UID"].startswith("U-"), f"префикс не U-: {g['UID']}"
|
||||
assert len(g.get("Description","")) > 100, f"пустое тело: Id={issue_id}"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user