# ШАГ 010 — Спринт 3: Редактирование теста + версионность **Дата:** 2026-03-21 **Контекст:** Мастер-класс по разработке системы тестирования сотрудников клиники. --- ## Запрос > запускаем спринт 3 --- ## Реализовано Полноценное редактирование тестов с автоматическим версионированием. --- ## Новые файлы ``` backend/alembic/versions/003_test_versioning.py ← добавляет parent_id в tests frontend/src/components/TestForm/index.tsx ← переиспользуемая форма теста ``` ## Изменённые файлы ``` backend/app/models/test.py ← добавлено поле parent_id backend/app/schemas/test.py ← TestOut.parent_id, TestUpdateResponse backend/app/api/tests.py ← PUT + /versions + /activate frontend/src/api/tests.ts ← update, versions, activate frontend/src/pages/TestCreate/ ← упрощён через TestForm frontend/src/pages/TestEdit/ ← полноценный просмотр + редактирование ``` --- ## API эндпоинты (новые) | Метод | URL | Описание | |-------|-----|----------| | PUT | `/api/tests/{id}` | Редактировать тест | | GET | `/api/tests/{id}/versions` | Цепочка всех версий | | POST | `/api/tests/{id}/activate` | Сделать версию активной | --- ## Схема БД (изменено) ``` tests + parent_id → tests.id ← ссылка на предыдущую версию (NULL у первой) ``` --- ## Логика версионирования | Условие при сохранении | Результат | |------------------------|-----------| | Попыток нет | Редактируем на месте, версия не меняется | | Есть хотя бы одна попытка | Новый тест: `version+1`, `parent_id=old.id`, `is_active=True`; старый: `is_active=False` | **Список тестов** показывает только `is_active=True` версии. **История версий** отображается на странице `/tests/:id/edit` — таблица всех версий цепочки. --- ## UX редактирования - Страница `/tests/:id/edit` работает в двух режимах: просмотр автора → нажать «Редактировать» → форма редактирования - Кнопка «← К просмотру теста» в шапке формы - При создании новой версии: редирект на `/tests/:newId/edit` + уведомление - В таблице «История версий»: статус (Активная/Неактивная), кнопка «Сделать активной» для любой версии --- ## Баги, найденные и исправленные при тестировании | # | Симптом | Причина | Исправление | |---|---------|---------|-------------| | 1 | `ForeignKeyViolationError` при сохранении теста | `DELETE FROM questions` bulk-запросом не каскадирует на `answers` — в схеме нет `ON DELETE CASCADE` | Сначала удаляем `answers` по `question_id IN (...)`, затем `questions` | | 2 | Обе версии показывались «Активными» | Тест был создан до введения логики деактивации родителя | Добавлена кнопка «Сделать активной» в шапке и в строке таблицы для любой версии | > **Для джуниора:** `cascade="all, delete-orphan"` в SQLAlchemy работает только при удалении через ORM-объекты (`session.delete(obj)`). При bulk-delete через `db.execute(delete(Model)...)` ORM-каскад не срабатывает — нужно вручную удалять дочерние записи в правильном порядке. --- ## Следующие шаги - [x] Спринт 1: Инфраструктура + Создание тестов - [x] Спринт 2: Прохождение теста + результат - [x] Спринт 3: Редактирование + версионность - [ ] Спринт 4: Трекер результатов - [ ] Спринт 5: Авторизация и роли - [ ] Спринт 6: Уведомления в MAX