feat: закрыть остаточные P1/P2 по профилю, когорте и ведомости
- профиль приведён к docs/09: variant/level, updatedBy/name, лимит попыток - V14: NOT NULL и полная уникальность когорты, preflight legacy-групп - возврат группы из архива и история членства на странице группы - пагинация групп и плоского списка материалов - пакетные сдачи ведомости одним запросом - XLSX-preflight импорта и audit-тесты ростера - LDD: 66 claims, 398 тестов, release PASS
This commit is contained in:
@@ -87,10 +87,10 @@ Platform 6 из Spring Boot 4.
|
||||
LDD-циклы выполнены 2026-10-27/28; последний — после закрытия продуктовых
|
||||
рисков:
|
||||
|
||||
- **385 автоматических тестов**, 0 failures/errors;
|
||||
- **59 claims**: 27 P0 + 32 P1, **59/59 proven, P0 gaps = 0**. P0 — доступы,
|
||||
- **398 автоматических тестов**, 0 failures/errors;
|
||||
- **66 claims**: 27 P0 + 39 P1, **66/66 proven, P0 gaps = 0**. P0 — доступы,
|
||||
повышение, жизненный цикл, профиль/скачивание, материалы/активы
|
||||
(включая secure-by-default legacy-файлы), миграции V11–V13, аудит,
|
||||
(включая secure-by-default legacy-файлы), миграции V11–V14, аудит,
|
||||
refresh-токены и их отзыв; P1 — группы, авторство, UI, контракты, импорт,
|
||||
копирование лаб, keyset-пагинация, пакетные детали лабораторных,
|
||||
абсолютный потолок сессии, runtime, mutation и browser E2E;
|
||||
@@ -114,4 +114,4 @@ Kotlin-модуль, поэтому DTO с default-значениями треб
|
||||
реальных runner'ах и отсутствие визуальной регрессии. Legacy-активы, срок
|
||||
access-токена, пагинация, N+1 деталей лабораторных, абсолютный лимит сессии и
|
||||
«ведущие нули» больше не являются открытыми рисками.
|
||||
Результаты — в `baseline-2026-10-27.md`.
|
||||
Результаты — в `baseline-2026-10-29.md`; предыдущий срез — `baseline-2026-10-27.md`.
|
||||
|
||||
44
docs/evidence/baseline-2026-10-29.md
Normal file
44
docs/evidence/baseline-2026-10-29.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# LDD baseline: 2026-10-29
|
||||
|
||||
Актуальный прогон после закрытия P1/P2-остатка: контракт профиля, финальные
|
||||
ограничения когорты, возврат группы из архива, пагинация, пакетные сдачи,
|
||||
row-level XLSX-импорт и audit-события ростера.
|
||||
|
||||
## Результат
|
||||
|
||||
| Проверка | Результат |
|
||||
|---|---|
|
||||
| JUnit/интеграционные тесты | **398**, 0 failures/errors/skipped |
|
||||
| ktlint + detekt | зелёные |
|
||||
| Claims | **66** (27 P0 / 39 P1), **66/66 proven**, P0 gaps = 0 |
|
||||
| Migration master→branch | PASS; identity `V4–V14`, повторные V13/V14 |
|
||||
| Mutation `CourseAccessService` | 75% test strength (порог 70%) |
|
||||
| Mutation `PromotionPlanner` | 69% test strength (порог 60%) |
|
||||
| Browser E2E | 18/18 (Chromium/Firefox/WebKit) |
|
||||
| Load smoke | 125 запросов, 0 ошибок, p95 0.942 с (порог < 1 с) |
|
||||
|
||||
## Что добавлено в этой итерации
|
||||
|
||||
- профиль соответствует `docs/09`: `variant.id`, `level.id/code`,
|
||||
`grade.updatedBy`, `author.name`; `submissionLimit`/`submissionsTotal` и
|
||||
SQL-тест «без N+1» на 10 лабораторных;
|
||||
- `V14__cohort_constraints.sql`: `NOT NULL` для пяти полей когорты, полный
|
||||
unique index и строгие check-ограничения; legacy-строки останавливают
|
||||
миграцию с явной ошибкой;
|
||||
- возврат группы из архива (`POST /api/v1/groups/{id}/restore`) с аудитом;
|
||||
- пагинация `/admin/groups` и плоского списка материалов;
|
||||
- `latestPerStudentByLab` — один запрос сдач на все лабораторные ведомости;
|
||||
- XLSX-тесты preflight-ошибок импорта (занятый логин, неактивная группа);
|
||||
- аудит `GROUP_CREATE/STUDENT_CREATE/GROUP_ENROLL/GROUP_IMPORT/GROUP_ARCHIVE/GROUP_RESTORE`;
|
||||
- история членства на странице группы (последние 200 событий).
|
||||
|
||||
## Остаточные ограничения
|
||||
|
||||
- гонка импорта с параллельной вставкой логина всё ещё даёт общий 409;
|
||||
- screenshot-diff/визуальной регрессии нет;
|
||||
- mutation testing не 100%, нагрузочный smoke не является SLA;
|
||||
- restore на staging, первый реальный Woodpecker pipeline, инвентаризация
|
||||
legacy-активов и daily backup timer требуют доступа к стенду/серверу.
|
||||
|
||||
Артефакты прогона: `build/evidence/{runtime,migration,mutation,browser,smoke}.json`,
|
||||
отчёт gate — `build/reports/evidence/index.md`.
|
||||
@@ -435,12 +435,12 @@
|
||||
"criticality": "P1",
|
||||
"status": "active",
|
||||
"requirement": "docs/16-stage6-report.md, §2.2",
|
||||
"claim": "Схема master + синтетические данные → миграции identity V4–V13, academic V6–V12, progress V13; V10 audit, V11 refresh, V12 absolute-expiry и V13 keyset-индексы создаются, V13 идемпотентна.",
|
||||
"oracle": "Успешное применение и postflight-наличие таблиц/колонок/индексов",
|
||||
"claim": "Схема master + синтетические данные → миграции identity V4–V14, academic V6–V12, progress V13; V10 audit, V11 refresh, V12 absolute-expiry, V13 keyset-индексы, V14 NOT NULL/unique когорты; V13/V14 идемпотентны.",
|
||||
"oracle": "postflight checks = required; повторные V13/V14 не падают",
|
||||
"runtime": [
|
||||
"migration.master_upgrade"
|
||||
],
|
||||
"limitations": "Запускается отдельным скриптом на одноразовой БД; не входит в unit-CI."
|
||||
"limitations": "V14 намеренно блокирует legacy-группы с неразобранной когортой."
|
||||
},
|
||||
{
|
||||
"id": "LDD-RUNTIME-001",
|
||||
@@ -872,6 +872,108 @@
|
||||
"ru.ysu.lms.web.profile.StudentProfileTemplateTest#renders student profile with labs, comments and downloads()"
|
||||
],
|
||||
"limitations": "Внутренние REST-контракты остаются UUID; form payload создания/копирования лабораторной тоже использует UUID."
|
||||
},
|
||||
{
|
||||
"id": "LDD-PROFILE-002",
|
||||
"title": "Профиль соответствует контракту docs/09 и ограничивает попытки",
|
||||
"criticality": "P1",
|
||||
"status": "active",
|
||||
"requirement": "docs/09-teacher-student-profile.md, §3, §7",
|
||||
"claim": "Профиль отдаёт variant.id, level.id/code, grade.updatedBy и author.name; число попыток отражается полностью, а список ограничивается параметром submissionLimit; профиль не делает запрос на каждую лабу.",
|
||||
"oracle": "JSON-поля контракта; attemptsCount/submissionsTotal; число SQL-запросов не растёт с числом лабораторных",
|
||||
"evidence": [
|
||||
"ru.ysu.lms.progress.StudentProfileIntegrationTest#teacher sees unpublished grade, authors and all submission attempts()",
|
||||
"ru.ysu.lms.progress.StudentProfileIntegrationTest#profile limits submission attempts but reports total()",
|
||||
"ru.ysu.lms.progress.StudentProfileIntegrationTest#profile uses one query per data kind instead of per lab()",
|
||||
"ru.ysu.lms.web.profile.ProfileDtoJsonTest#deserializes profile contract()",
|
||||
"ru.ysu.lms.web.profile.StudentProfileTemplateTest#renders student profile with labs, comments and downloads()",
|
||||
"ru.ysu.lms.identity.internal.InternalControllerIntegrationTest#lookup includes student full name and leaves staff name empty()"
|
||||
],
|
||||
"limitations": "По умолчанию возвращаются последние 50 попыток; UI показывает, сколько скрыто."
|
||||
},
|
||||
{
|
||||
"id": "LDD-GROUP-001",
|
||||
"title": "Группа возвращается из архива",
|
||||
"criticality": "P1",
|
||||
"status": "active",
|
||||
"requirement": "docs/07-groups-courses.md, §8.3",
|
||||
"claim": "Архивную группу можно вернуть в ACTIVE; повторный возврат активной группы отклоняется, состав и история не меняются.",
|
||||
"oracle": "status меняется ARCHIVED→ACTIVE; второй restore — 400; число студентов и история сохраняются",
|
||||
"evidence": [
|
||||
"ru.ysu.lms.identity.roster.GroupServiceIntegrationTest#restores archived group and rejects repeated restore()",
|
||||
"ru.ysu.lms.identity.audit.AuditIntegrationTest#roster operations record group and student audit events()"
|
||||
],
|
||||
"limitations": "Возврат выполняется со страницы группы; отдельного массового восстановления нет."
|
||||
},
|
||||
{
|
||||
"id": "LDD-GROUP-002",
|
||||
"title": "Финальные ограничения когорты",
|
||||
"criticality": "P1",
|
||||
"status": "active",
|
||||
"requirement": "docs/07-groups-courses.md, §8.2; docs/08-groups-courses-plan.md, этап 1",
|
||||
"claim": "Все поля когорты NOT NULL, уникальность полная (без partial WHERE), legacy-строки блокируют миграцию с явной ошибкой.",
|
||||
"oracle": "information_schema.is_nullable=NO для 5 полей; groups_cohort_unique без WHERE; master→branch migration PASS",
|
||||
"evidence": [],
|
||||
"runtime": [
|
||||
"migration.master_upgrade"
|
||||
],
|
||||
"limitations": "Перед деплоем на реальных данных нужен ручной разбор legacy-групп; запрос для проверки есть в V14."
|
||||
},
|
||||
{
|
||||
"id": "LDD-IMPORT-004",
|
||||
"title": "XLSX-импорт отчитывается по строкам до записи",
|
||||
"criticality": "P1",
|
||||
"status": "active",
|
||||
"requirement": "docs/08-groups-courses-plan.md, этап 1; docs/18 §3",
|
||||
"claim": "Для XLSX занятый логин и неактивная целевая группа возвращают applied=false, номер строки и не создают частичных записей.",
|
||||
"oracle": "ImportResult.errors.row=2, applied=false; студент не создан",
|
||||
"evidence": [
|
||||
"ru.ysu.lms.identity.roster.GroupServiceIntegrationTest#xlsx import reports occupied login before writing()",
|
||||
"ru.ysu.lms.identity.roster.GroupServiceIntegrationTest#xlsx import reports inactive target group before writing()"
|
||||
],
|
||||
"limitations": "Гонка с параллельной вставкой логина вне preflight всё ещё может дать общий 409."
|
||||
},
|
||||
{
|
||||
"id": "LDD-AUDIT-006",
|
||||
"title": "Операции с группами и студентами пишут аудит",
|
||||
"criticality": "P1",
|
||||
"status": "active",
|
||||
"requirement": "docs/07-groups-courses.md, §9; docs/08-groups-courses-plan.md, этап 1",
|
||||
"claim": "GROUP_CREATE, STUDENT_CREATE, GROUP_ENROLL, GROUP_IMPORT, GROUP_ARCHIVE и GROUP_RESTORE попадают в журнал с актором и деталями.",
|
||||
"oracle": "audit_events содержит все шесть action; actorAccountId/actorLogin заполнены",
|
||||
"evidence": [
|
||||
"ru.ysu.lms.identity.audit.AuditIntegrationTest#roster operations record group and student audit events()"
|
||||
],
|
||||
"limitations": "Проверяются успешные операции; отказы валидации аудит не пишут."
|
||||
},
|
||||
{
|
||||
"id": "LDD-PERF-003",
|
||||
"title": "Пакетные сдачи и пагинация списков",
|
||||
"criticality": "P1",
|
||||
"status": "active",
|
||||
"requirement": "docs/07-groups-courses.md, §10; docs/18 §3",
|
||||
"claim": "Ведомость получает последние сдачи по всем выбранным лабам одним запросом; группы и плоский список материалов поддерживают limit/offset.",
|
||||
"oracle": "одна выборка submissions на набор labId; страницы групп/материалов не пересекаются и не теряют записи",
|
||||
"evidence": [
|
||||
"ru.ysu.lms.progress.SubmissionBatchIntegrationTest#latest submissions are grouped by lab in one call()",
|
||||
"ru.ysu.lms.identity.roster.GroupServiceIntegrationTest#lists groups page by page and returns all without limit()",
|
||||
"ru.ysu.lms.academic.content.CourseContentIntegrationTest#admin article list supports offset and limit()"
|
||||
],
|
||||
"limitations": "Дерево курса по-прежнему загружается целиком для навигации; пагинируются плоские списки."
|
||||
},
|
||||
{
|
||||
"id": "LDD-GROUP-003",
|
||||
"title": "История членства видна на странице группы",
|
||||
"criticality": "P1",
|
||||
"status": "active",
|
||||
"requirement": "docs/07-groups-courses.md, §3; docs/18 §3",
|
||||
"claim": "Админ видит последние события membership_history по группе: студент, действие, откуда/куда, причина и время МСК.",
|
||||
"oracle": "GET /api/v1/groups/{id}/history отдаёт studentName/fromGroupName/toGroupName; шаблон показывает блок истории",
|
||||
"evidence": [
|
||||
"ru.ysu.lms.identity.roster.GroupServiceIntegrationTest#imports students and records membership history()",
|
||||
"ru.ysu.lms.web.admin.AdminStage4TemplateTest#renders membership history and restore action for archived group()"
|
||||
],
|
||||
"limitations": "Показаны последние 200 событий; фильтра/пагинации по истории нет."
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user