Files
TestingWebApp/docs/QA-versioning-and-ai.md
T
Константин Лебединский 547840d671 docs(qa): tester guide for versioning and AI features
Made-with: Cursor
2026-04-27 23:50:38 +05:00

302 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Инструкция для тестировщика: версионирование тестов и AI-функции
> Контур: новый Flask на `:3108` (после E1.0E1.3, E1.8). Старый Node — на `:3107`,
> для проверки этих задач **используйте только `:3108`**.
Перед началом:
1. Откройте `http://<host>:3108/login` под учёткой автора (роль `manager` или
`admin`). Если сидов нет — заведите пользователя в `clinic_tests.users`
обычным способом.
2. Для AI-задач: откройте `http://<host>:3108/settings` и убедитесь, что
статус ключа = «Задан» и кнопка **«Проверить подключение»** возвращает
зелёный **OK · provider/model · NN мс**. Если ключ не задан — AI-задачи
нужно прогнать в негативном сценарии (см. блок A.0).
3. Откройте `http://<host>:3108/tests` — это каталог.
---
## Часть 1. Версионирование при правке после попыток
### Что должно работать
| Правило | Поведение |
|---|---|
| Нет попыток | Автор правит тест **на месте**, номер версии не меняется. |
| Есть ≥ 1 попытка | Любое сохранение изменений **создаёт новую версию** (`version + 1`), старая становится неактивной, но **сохраняется в БД** и связана через `parent_id`. |
| Цепочка | Все версии связаны (parent → child), на странице «Версии» видны все. |
| Каталог | В списке видна **только активная** версия цепочки. |
| Переключение активной версии | Автор может вручную сделать активной любую версию — остальные автоматически становятся неактивными. |
| Деактивация цепочки | Тест можно скрыть целиком; данные не удаляются. |
| Корректность истории | Каждая попытка привязана к **той версии**, по которой её проходили — разбор ошибок остаётся корректным после правок. |
### Сценарий 1.1. Правка теста до попыток (версия не растёт)
1. В каталоге → **«Создать тест»**, заполните название и описание → создать.
2. В редакторе добавьте 2–3 вопроса по 3–4 варианта, сохраните.
3. Откройте этот же тест в редакторе ещё раз, измените:
- название;
- описание;
- текст одного вопроса;
- пометьте один вариант как правильный иначе;
- удалите/добавьте вариант.
4. **«Сохранить»**.
**Ожидается:**
- Сообщение «Сохранено.» (без слов «создана новая версия»).
- В БД: `SELECT version FROM test_versions WHERE test_id = '<id>'`**одна** строка с `version = 1, is_active = true`.
- Эндпоинт `GET /api/tests/<id>/versions` → массив из 1 элемента, `hasAttempts: false`.
### Сценарий 1.2. Появление первой попытки → форк новой версии
> Прохождение теста (UI) пока не реализовано в Flask-контуре (запланировано
> в **E1.4**). Поэтому факт попытки имитируется напрямую в БД — это норма
> для текущего этапа.
1. Возьмите `id` теста и `id` активной версии:
```sql
SELECT t.id AS test_id, tv.id AS version_id
FROM tests t JOIN test_versions tv ON tv.test_id = t.id AND tv.is_active
WHERE t.title = 'Ваш тест';
```
2. Создайте «попытку» (минимальный INSERT, любой пользователь, любое
состояние; нам нужен только сам факт записи в `test_attempts`):
```sql
INSERT INTO test_attempts (id, test_version_id, user_id, status, created_at)
VALUES (gen_random_uuid(), '<version_id>', '<any_user_id>', 'completed', now());
```
3. В UI откройте редактор того же теста, **измените хотя бы один вопрос**
(текст / правильность варианта / число вариантов) и **«Сохранить»**.
**Ожидается:**
- Сообщение «Сохранено (создана новая версия — есть попытки прохождения).»
- В БД: `SELECT version, is_active, parent_id FROM test_versions WHERE test_id=...`
→ **две** строки:
- `version = 1, is_active = false, parent_id = NULL` (старая, не удалена);
- `version = 2, is_active = true, parent_id = <id версии 1>` (новая).
- Старая попытка по-прежнему ссылается на `version_id` из v1, и её ответы/вопросы остаются те же — разбор ошибок не «съехал».
### Сценарий 1.3. Правка только метаданных после попыток (без форка)
После сценария 1.2:
1. В редакторе **не трогайте** вопросы и варианты. Поменяйте только название
или описание или проходной балл. Сохраните.
**Ожидается:**
- Сообщение «Сохранено.» (без форка).
- `version` не вырос; метаданные обновились на активной версии.
> Логика: форк делается только если после попыток меняется **содержание**
> (вопросы/варианты). Чисто косметические правки шапки версию не плодят.
### Сценарий 1.4. Каталог показывает только активную версию
1. После 1.2 откройте `/tests` под автором и под не-автором (если есть назначения).
**Ожидается:**
- В каталоге — одна карточка теста с пометкой `v.2` (активная). Версия 1 в каталог не попадает, но видна автору на странице «Версии» теста.
### Сценарий 1.5. Ручное переключение активной версии
1. Получите id v1 и v2: `SELECT id, version, is_active FROM test_versions WHERE test_id=...`
2. Сделайте активной v1:
```bash
curl -X POST -b cookies.txt \
http://<host>:3108/api/tests/<test_id>/versions/<v1_id>/activate
```
(cookie сессии берёте из браузера или логином через `/api/auth/login`).
**Ожидается:**
- Ответ `{ ok: true, activeVersionId: "<v1_id>" }`.
- В БД: `is_active = true` только у v1, у v2 — `false`.
- В каталоге карточка теста снова показывает `v.1`.
### Сценарий 1.6. Деактивация цепочки целиком
1. В редакторе снимите чекбокс **«Цепочка активна»** и сохраните.
**Ожидается:**
- В каталоге `/tests` теста больше не видно (ни в visible, ни у не-авторов).
- У автора он появляется в блоке **«Скрытые вами»** (внизу каталога).
- В БД: `tests.is_active = false`, версии и попытки нетронуты.
- Включение чекбокса обратно возвращает тест в каталог.
### Сценарий 1.7. Корректность истории по старым попыткам
> Полноценный разбор пользовательских ответов появится в **E1.4** вместе
> с UI прохождения. Сейчас минимально проверяем, что данные старой версии
> не повреждены.
1. После 1.2 в БД:
```sql
SELECT q.text
FROM questions q
JOIN test_versions tv ON tv.id = q.test_version_id
WHERE tv.test_id = '<test_id>' AND tv.version = 1;
```
2. **Ожидается:** видны вопросы **в том виде, в каком они были до правки**
(а не текущая версия v2). Эта же выборка должна совпадать с
`q.test_version_id` любой попытки, которую вы создали в 1.2.
### Что фиксировать как баг
- После правки **с попытками** в БД остался один `test_versions`-ряд (нет форка).
- После правки **без попыток** появилась `version = 2` (лишний форк).
- При активации одной версии другие не сбросились в `is_active = false`.
- Каталог показывает неактивную версию или скрытый тест.
- Сообщение в UI после сохранения не совпадает с реальным поведением (форк есть, текст «Сохранено.» без уточнения, или наоборот).
---
## Часть 2. AI-функции (E1.2 + E1.8)
### Заметка о ключе
Изначально в ТЗ предполагалось хранить ключ в БД и вводить на `/settings`.
По согласованию ключ **общий** и хранится в `ENV` контейнера
(`DEEPSEEK_API_KEY` / `OPENAI_API_KEY`). Страница `/settings` остаётся,
но в ней — только статус и кнопка проверки подключения. Поле ввода ключа в UI **не нужно** (это не баг).
### A.0. Негативный кейс — ключ не задан
1. На сервере уберите `DEEPSEEK_API_KEY` из окружения и перезапустите контейнер.
2. Откройте `/settings`.
**Ожидается:**
- Бейдж «Не задан» (красный).
- Блок «Как задать ключ» с примером `.env`.
- Кнопка **«Проверить подключение»** возвращает красный блок с текстом про незаданный ключ.
- В редакторе при нажатии **«Сгенерировать по сетке» / «по названию» / «Проверить тест» / «Улучшить тест» / «AI: вопрос»** появляется confirm:
«… Открыть Настройки?» → согласие открывает `/settings`.
После проверки верните ключ и `docker compose ... up -d` — переходим к позитивным сценариям.
### A.1. `/settings` → «Проверить подключение»
1. На `/settings` нажмите **«Проверить подключение»**.
**Ожидается:**
- В течение нескольких секунд — зелёный блок: `OK · <provider> / <model> · <ms> мс` и сэмпл ответа.
- Provider/Model совпадают с ENV (`deepseek` + `deepseek-chat` по умолчанию).
### A.2. «Сгенерировать тест по названию» (E1.8)
1. Создайте новый пустой тест (никаких вопросов).
2. В редакторе нажмите **«Сгенерировать по названию»**.
3. На запрос «Сколько вопросов?» введите, например, `8`; «Сколько вариантов?» — `4`.
4. Дождитесь готовности → confirm «Применить как черновик?».
**Ожидается:**
- Кнопка **активна только** когда поле «Название» заполнено. Если очистить название — нажатие даёт алерт «Сначала заполните название теста.» и фокус возвращается в название.
- Появляется ровно ~8 вопросов по ~4 варианта (модель может слегка отклониться по инструкции, это допустимо).
- Тексты — на русском, по теме названия.
- В каждом вопросе хотя бы один вариант помечен как правильный.
- Отказ в confirm не меняет редактор; согласие — заменяет.
- Сохранение работает, в БД появляется версия с этими вопросами.
### A.3. «Сгенерировать тест по сетке» (E1.2 — было)
1. Откройте тест, в котором уже руками настроены 5 вопросов, по 3 варианта в каждом, 2 из вопросов помечены как «Несколько правильных».
2. Нажмите **«Сгенерировать по сетке»**.
**Ожидается:**
- Возвращается ровно 5 вопросов.
- В тех же позициях, что у вас стояли «Несколько правильных», — у новых вопросов несколько правильных вариантов.
- Число вариантов в каждом вопросе совпадает.
- При несовпадении сетки эндпоинт вернул бы 502 с кодом `llm_shape` (модель «не попала») — допустимая редкая ошибка, повторите.
### A.4. «Проверить тест» (E1.8)
1. Создайте тест с парой нарочно слабых мест:
- один вопрос с длинной мутной формулировкой;
- один вопрос, где все варианты слишком похожи или в качестве «дистракторов» — очевидная ерунда.
2. Нажмите **«Проверить тест»**.
**Ожидается:**
- Открывается модалка «Проверка теста».
- Есть цветная плашка с одним из вердиктов: **Годен / Есть замечания / Серьёзные проблемы**, и краткое резюме (1–2 предложения).
- Ниже — список разделов («Чёткость формулировок», «Качество дистракторов», «Охват темы», «Сбалансированность сложности»). Разделы без замечаний пропускаются.
- В списке — конкретные пункты на русском, по делу.
- Закрытие модалки крестиком или кнопкой «Закрыть» работает.
### A.5. «Улучшить тест» (E1.8) — массовое было → стало
1. Возьмите тест из A.4 (с ≥ 3 вопросами).
2. Нажмите **«Улучшить тест»**.
**Ожидается:**
- Открывается модалка с заголовком «Улучшение теста» и подсказкой «Отмечено N из M».
- Каждый изменённый вопрос — отдельная карточка:
- чекбокс «Вопрос #N» (по умолчанию **отмечен**);
- две колонки **Было** / **Стало**;
- изменённый текст в «Было» зачёркнут, в «Стало» — выделен;
- правильные варианты помечены ✓.
- Снимите галку с одного-двух вопросов и нажмите **«Применить выбранное»**.
- В редакторе **только** отмеченные вопросы заменены на улучшенные; остальные остались как были.
- Появляется надпись «Изменения применены. Не забудьте сохранить.» — нажмите **«Сохранить»** и проверьте версионирование (см. Часть 1).
- **Сетка не меняется**: число вопросов, число вариантов в каждом и значение «Несколько правильных» совпадают с исходными. Если модель «слетела» — эндпоинт возвращает 502 с `llm_shape` и UI показывает алерт; это не баг логики.
### A.6. AI-кнопка на конкретном вопросе (E1.2)
Сценарий «новый вопрос»:
1. Добавьте вопрос, **поле текста оставьте пустым**, число вариантов = 4, «Несколько правильных» — выкл.
2. Нажмите **«AI: вопрос/переформулировать»** на этом вопросе.
**Ожидается:** заполнен текст вопроса и все 4 варианта; ровно один помечен правильным; внизу — статус «AI: вопрос сгенерирован.»
Сценарий «переформулировать»:
1. Возьмите готовый вопрос с заполненным текстом.
2. Нажмите ту же кнопку.
**Ожидается:** меняется **только текст** вопроса (вариант ответа и правильность не трогаются), статус «AI: формулировка обновлена.»
### A.7. Импорт документа (E1.3)
1. Подготовьте файл `sample.pdf` или `sample.docx` со связным текстом (1–3 страницы) на русском.
2. В редакторе → AI-панель → **«Импорт документа»** → выберите файл.
**Ожидается:**
- Прогресс «Загружаем «sample.pdf»…».
- Confirm «Сгенерировано: «<title>», вопросов: N. Применить как новый черновик?» → согласие заменяет вопросы, отказ ничего не меняет.
- При файле > 16 МБ — ошибка от Flask (413/500), это норма (лимит).
- При файле неподдерживаемого формата (`.xlsx`, `.png`) — алерт «Неподдерживаемый формат…».
- При **отсутствующем** ключе AI: вместо confirm — алерт с текстом «Автогенерация выключена…» и первыми 600 символами извлечённого текста; вопросы **не** заменяются.
### A.8. Единая ошибка при отсутствии ключа
1. Уберите ключ (как в A.0). Откройте редактор любого теста.
2. По очереди нажимайте **«Сгенерировать по сетке»**, **«по названию»**, **«Проверить тест»**, **«Улучшить тест»**, **«AI: вопрос»**.
**Ожидается:** в каждом случае confirm с предложением открыть **/settings**, не молчаливый алерт. На бэке — JSON `{ error, code, settingsUrl: "/settings" }`, статус 502.
### Что фиксировать как баг (AI)
- Кнопка **«Сгенерировать по названию»** позволяет жать без названия и не показывает алерт.
- Модалка **«Проверить тест»** пуста или содержит англ. текст.
- В **«Улучшить тест»** меняется число вопросов / число вариантов / «Несколько правильных», т.е. сетка слетела, и UI всё равно применил.
- В **«AI: вопрос»** на пустом вопросе варианты не сгенерированы; на заполненном — варианты заменены без ведома пользователя.
- Любая AI-ошибка показывается без ссылки на `/settings`, когда ключ действительно отсутствует.
- Импорт документа подменяет вопросы **без confirm**.
---
## Шпаргалка: что и где смотреть
| Что | URL / SQL |
|---|---|
| Каталог | `/tests` |
| Редактор | `/tests/<id>/edit` |
| Версии теста | `GET /api/tests/<id>/versions` |
| Активность LLM | `/settings` + `POST /api/llm/ping` |
| `test_versions` | `SELECT id, version, is_active, parent_id, created_at FROM test_versions WHERE test_id = '<id>' ORDER BY version;` |
| Попытки | `SELECT id, test_version_id, status, created_at FROM test_attempts WHERE test_version_id IN (SELECT id FROM test_versions WHERE test_id='<id>');` |
При баге прикладывайте:
- скрин редактора / модалки;
- ответ соответствующего эндпоинта (DevTools → Network → JSON);
- результат SQL из таблицы выше;
- `docker compose -f docker-compose.dev.yml logs --tail=200 testing-flask`.