Browse Source

feat: журнал тестирования, бэклог идей; V.2 hasAnyAttemptForTest + unit tests; ссылки в спринтах

Made-with: Cursor
dev
Константин Лебединский 2 weeks ago
parent
commit
e87168d3a0
  1. 31
      backend/package.json
  2. 18
      backend/src/services/testChainService.js
  3. 23
      backend/src/services/testChainService.test.js
  4. 4
      docs/revision_task/BACKLOG.md
  5. 32
      docs/revision_task/BACKLOG_IDEAS.md
  6. 68
      docs/revision_task/TESTING_JOURNAL.md
  7. 3
      docs/revision_task/card1.md
  8. 67
      docs/revision_task/sprint-01-testing.md
  9. 5
      docs/revision_task/sprint-01.md

31
backend/package.json

@ -0,0 +1,31 @@
{
"name": "clinic-tests-backend",
"version": "1.0.0",
"description": "Backend for Clinic Tests application",
"main": "src/index.js",
"type": "module",
"scripts": {
"start": "node src/index.js",
"dev": "node --watch src/index.js",
"test": "node --test src/services/testChainService.test.js",
"lint": "eslint src/",
"lint:fix": "eslint src/ --fix",
"format": "prettier --write src/"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"bcrypt": "^5.1.1",
"cookie-parser": "^1.4.7",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.21.0",
"jsonwebtoken": "^9.0.2",
"pg": "^8.12.0"
},
"devDependencies": {
"eslint": "^8.57.0",
"prettier": "^3.3.3"
}
}

18
backend/src/services/testChainService.js

@ -0,0 +1,18 @@
/**
* Логика «цепочки» теста: попытки и версии (см. docs/revision_task/card1.md V.2).
* @param {import('pg').Pool | { query: Function }} pool пул или объект с методом query(sql, params)
* @param {string} testId UUID теста (tests.id)
* @returns {Promise<boolean>} true, если по любой версии этой цепочки есть хотя бы одна попытка
*/
export async function hasAnyAttemptForTest(pool, testId) {
const { rows } = await pool.query(
`SELECT EXISTS (
SELECT 1
FROM test_attempts ta
INNER JOIN test_versions tv ON ta.test_version_id = tv.id
WHERE tv.test_id = $1
) AS has_any`,
[testId]
);
return rows[0].has_any === true;
}

23
backend/src/services/testChainService.test.js

@ -0,0 +1,23 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { hasAnyAttemptForTest } from './testChainService.js';
test('hasAnyAttemptForTest: false, если в базе пусто', async () => {
const pool = {
async query() {
return { rows: [{ has_any: false }] };
},
};
const result = await hasAnyAttemptForTest(pool, '00000000-0000-0000-0000-000000000001');
assert.equal(result, false);
});
test('hasAnyAttemptForTest: true, если есть попытка', async () => {
const pool = {
async query() {
return { rows: [{ has_any: true }] };
},
};
const result = await hasAnyAttemptForTest(pool, '00000000-0000-0000-0000-000000000001');
assert.equal(result, true);
});

4
docs/revision_task/BACKLOG.md

@ -2,7 +2,9 @@
**Стек (репозиторий [TestingWebApp](../..)):** **Node.js** (backend), **PostgreSQL**, **Docker**; фронт — desktop-first SPA. Экосистема клиники: см. [HR_TG_Bot README](../../../HR_TG_Bot/README.md); перенос на Python/FastAPI **не** считается обязательным для этого репо — **контракт** данных и [card1.md](revision_task/card1.md) важнее. **Стек (репозиторий [TestingWebApp](../..)):** **Node.js** (backend), **PostgreSQL**, **Docker**; фронт — desktop-first SPA. Экосистема клиники: см. [HR_TG_Bot README](../../../HR_TG_Bot/README.md); перенос на Python/FastAPI **не** считается обязательным для этого репо — **контракт** данных и [card1.md](revision_task/card1.md) важнее.
**Карта больших кусков работ:** [card1.md](card1.md) (версии **V**, документ **D**, авторизация **HR A**). **Карта больших кусков работ:** [card1.md](card1.md) (версии **V**, документ **D**, авторизация **HR A**).
**Идеи и пожелания (простой язык):** [BACKLOG_IDEAS.md](BACKLOG_IDEAS.md)
**Журнал проверок по спринтам (авто + ручные шаги для заказчика):** [TESTING_JOURNAL.md](TESTING_JOURNAL.md)
**Этап 1 (ТЗ §4)** — пять фич: 4.1–4.5 (части можно параллелить). **Этап 1 (ТЗ §4)** — пять фич: 4.1–4.5 (части можно параллелить).
**Этап 2 (ТЗ §5)** — дашборды. **Этап 2 (ТЗ §5)** — дашборды.

32
docs/revision_task/BACKLOG_IDEAS.md

@ -0,0 +1,32 @@
# Идеи и пожелания по доработкам (для согласования с заказчиком)
*Язык простой, без жаргона разработки. Сюда попадает всё, что всплыло в обсуждениях и ещё не вошло в жёсткое ТЗ.*
**Как пользоваться:** приоритеты и «да/нет» фиксируем отдельно; пункты **не** удаляем — переносим в раздел **Решено** с кратким итогом, если идея закрыта или отклонена.
---
## На рассмотрении
| № | Суть (что даст клинике) | Примечание |
|---|-------------------------|------------|
| 1 | **Напоминания** о сроке теста в мессенджере (когда срок близок или прошёл) | Связь с будущим HR-приложением в MAX; не путать с самим прохождением теста — только напоминание зайти в кабинет. |
| 2 | **Один раз скачать** свод по отделу или по клинике в **таблицу** (для руководителя), без «технических» деталей | Уточнить, нужен ли **Excel** / PDF и какие столбцы обязательны. |
| 3 | **Памятка рядом с тестом** — кратко: зачем тест, на что обратить внимание (текст от автора) | Улучшает вовлечение; не подменяет **описание** теста, если оно уже есть. |
| 4 | **Сравнение** «как сотрудник ответил в прошлый раз» с текущим прохождением | Для **повторных** тестов по той же теме; важна приватность и согласие кадров. |
| 5 | **Крупный шрифт** и **контраст** в режиме «стресс/смена» для сотрудников, много работающих в перчатках с экраном | Доступность; опциональная **тема** в настройках профиля. |
| 6 | **Печатная** версия **итога** (сдал/не сдал) для **личного** дела — один лист, без лишнего | Не путать с полноценной «выгрузкой для 1С»; это про человеко-понятный **итог** для сотрудника. |
| 7 | **Повтор** одного вопроса в конце теста — «самопроверка» (опционально, как у автора) | Снижает нервозность; выключаемо **на уровне** теста, чтобы на экзамене не мешать. |
| 8 | **Аудит:** кто из администраторов менял активную **версию** теста и когда | Для **разборов** при споре «кому показывали старую/новую редакцию». |
---
## Решено или «не делаем в этой волне»
| № | Суть | Итог |
|---|------|------|
| — | *Пока пусто* | |
---
*Обновляйте дату: **2026-04-23** (создание файла).*

68
docs/revision_task/TESTING_JOURNAL.md

@ -0,0 +1,68 @@
# Единый журнал проверок по спринтам
**Для кого этот документ.** Часть проверок делается на стороне разработки (миграции, автотесты, что можно прогнать без браузера). Часть — **с участием заказчика**: ниже даны **пошаговые задания**; вы отвечаете в переписке одним словом **ОК** или **не ОК** (и при желании коротко, что не так). Разработка переносит ответ в таблицу в колонку «Закреплено».
**Ветка / коммит последней привязки:** `dev` (обновлять при релизе на проверку)
**Адрес стенда (когда появится):** *(заполнить)*
---
## Спринт 1 — Версии тестов и честная история прогонов
**Смысл для бизнеса.** Если руководитель поправил тест после того, как кто-то уже прошёл его, **старые результаты** должны оставаться привязаны к **той редакции**, по которой человек реально отвечал — без путаницы в разборе ошибок.
### Раздел A — Проверки без участия заказчика (разработка / ассистент)
| № | Что проверено | Статус | Дата |
|---|----------------|--------|------|
| A1 | В проекте есть миграция базы: связь версий «родитель» (`parent_id`) и правило «только одна активная версия на тест» | [ ] | |
| A2 | Линтер (`npm run lint`) без **новых** ошибок в добавленных файлах; в проекте есть старые замечания линтера | частично | 2026-04-24 |
| A3 | Автотесты: функция «есть ли уже хотя бы одна попытка по этому тесту» (`npm test`) | [x] готово | 2026-04-23 |
| A4 | Запрос «здоров ли сервер» по адресу `/api/health` при запущенном backend | [ ] | |
**Техническая заметка:** реализация `hasAnyAttemptForTest` в `backend/src/services/testChainService.js`, тесты в `testChainService.test.js`.
---
### Раздел B — Поручения заказчику (шаги с ответом ОК / не ОК)
**Как отвечать.** Пройдите шаг. Напишите в чат, например: «S1-01 ОК» или «S1-01 не ОК: не открывается список». Одна строка на шаг.
| Код шага | Что сделать (пока экраны в разработке — шаги будут дополняться) | Ваш ответ | Зафиксировано |
|----------|------------------------------------------------------------------|-----------|----------------|
| S1-00 | Открыть файл **идей** [BACKLOG_IDEAS.md](BACKLOG_IDEAS.md). Подходит ли формат (таблица, без кода)? Напишите **S1-00 ОК** или **не ОК** и одну фразу. | | |
| S1-01 | *(позже)* Создать тест и несколько раз сохранить **до** того, как кто-то прошёл тест; убедиться, что номер версии не растёт. Пока экрана нет — напишите «пропуск» или «не ОК: нет экрана». | | |
| S1-02 | *(позже)* После прохождения теста сотрудником изменить тест и сохранить; увидеть **новую** версию в истории. | | |
| S1-03 | *(позже)* Открыть **старый** результат прохождения и убедиться, что вопросы совпадают с той редакцией, что была при прохождении. | | |
**Итог спринта 1:** дата __________ комментарий заказчика одной фразой: _________________________
---
## Спринт 2 — *(заготовка)*
### Раздел A — автопроверки
| № | Описание | Статус | Дата |
|---|----------|--------|------|
| | | [ ] | |
### Раздел B — поручения заказчику
| Код | Действие | Ответ | Зафиксировано |
|-----|----------|-------|----------------|
| | | | |
---
## Сводка по спринтам (для статус-встречи)
| Спринт | Тема простыми словами | Раздел A | Раздел B |
|--------|------------------------|----------|----------|
| 1 | Версии, история прогонов | в работе | ждёт шагов |
| 2 | *(по мере появления)* | | |
---
*Связанные файлы: [sprint-01-testing.md](sprint-01-testing.md) (черновик чек-листа), [card1.md](card1.md) (задачи).*

3
docs/revision_task/card1.md

@ -77,5 +77,6 @@
- ТЗ: [task.md](task.md) §4.1, §4.2 - ТЗ: [task.md](task.md) §4.1, §4.2
- Спринт 1: [sprint-01.md](sprint-01.md) - Спринт 1: [sprint-01.md](sprint-01.md)
- Тестирование: [sprint-01-testing.md](sprint-01-testing.md) - Проверки и журнал: [TESTING_JOURNAL.md](TESTING_JOURNAL.md)
- Старый чек-лист: [sprint-01-testing.md](sprint-01-testing.md)
- Анализ таблиц (если ведёте): [TEST_TABLES_ANALYSIS.md](../TEST_TABLES_ANALYSIS.md) - Анализ таблиц (если ведёте): [TEST_TABLES_ANALYSIS.md](../TEST_TABLES_ANALYSIS.md)

67
docs/revision_task/sprint-01-testing.md

@ -0,0 +1,67 @@
# Спринт 1 — тестирование (версионирование)
**Актуальный ведённый журнал (авто + ручные шаги, ответы ОК/не ОК):** [TESTING_JOURNAL.md](TESTING_JOURNAL.md) — *этот файл остаётся как подробный черновик сценариев*.
Ниже: **автоматизировано / проверено при разработке** и **ручная приёмка** — дублирует структуру; статусы переносите в `TESTING_JOURNAL.md`.
**Окружение:** зафиксировать ветку/коммит, URL стенда, тестовые учётки (роль автора, роль сотрудника).
---
## 1. Автоматизировано и self-check (разработка)
_Отмечайте [ ] → [x] по мере выполнения._
### 1.1 Автотесты и статический анализ
- [ ] Unit-тесты правила «0 попыток — правка на месте без новой версии»
- [ ] Unit-тесты: после появления попытки — сохранение создаёт новую версию, старая неактивна
- [ ] Тест: попытка хранит `version_id` совпадающий с версией, по которой проходили
- [ ] Integration/API: нельзя «потерять» цепочку при смене активной версии
- [ ] Линтеры/CI зелёные на MR спринта 1
### 1.2 Смоук вручную (быстрый) перед передачей
- [ ] Создать тест, несколько раз сохранить **до** назначения/прохождения — версия одна
- [ ] Назначить, пройти тест, снова изменить тест — новая версия, старая в истории
- [ ] Список тестов у сотрудника без дублей цепочки
- [ ] Смена активной версии: новые прохождения идут по новой активной; старая попытка в разборе по старой версии
---
## 2. Ручная приёмка (я / заказчик)
_Сценарии для прогона в «боевом» темпе, без доступа к коду._
### 2.1 Автор: жизненный цикл без попыток
- [ ] Создать тест, изменить вопросы/порог — всё в одной версии, номер версии ожидаемо стабилен
- [ ] Убедиться, что в UI явно видно, что тест ещё «ни разу не проходили» (если предусмотрено ТЗ/UX)
### 2.2 Сотрудник + первая попытка
- [ ] Назначить тест, пройти его полностью
- [ ] Как автор изменить вопрос/варианты, сохранить — появляется **новая** версия; старая доступна в истории
### 2.3 Корректность данных
- [ ] Открыть разбор/результат **старой** попытки: формулировки вопросов и правильные ответы соответствуют **той** версии, с которой проходили
- [ ] Новое назначение/новое прохождение — по **актуальной активной** версии
### 2.4 Управление версиями
- [ ] История версий отображается полностью и понятно (номера, даты при наличии)
- [ ] Переключение **активной** версии на предыдущую: списки обновляются; новая попытка идёт по выбранной версии
- [ ] **Деактивация** теста: цепочка не светится сотруднику; данные на месте, старые результаты открываются (если доступ по ролям предусмотрен)
### 2.5 Визуальная согласованность
- [ ] Экраны редактора и списков **визуально** согласованы с остальным internal web (отступы, шрифты, кнопки, таблицы, ошибки) — **без отклонения** от принятого дизайна
### 2.6 Негатив
- [ ] Попытка не может «сломать» цепочку (ошибки пользователю понятны)
---
**Итог приёмки спринта 1:** дата __________, подпись/комментарий _________________________

5
docs/revision_task/sprint-01.md

@ -50,6 +50,7 @@
--- ---
## Документ тестирования ## Документы тестирования
Чек-лист: [sprint-01-testing.md](sprint-01-testing.md). - **Единый журнал** (автопроверки + **ваши** шаги с ответом ОК/не ОК): [TESTING_JOURNAL.md](TESTING_JOURNAL.md)
- Черновик чек-листа: [sprint-01-testing.md](sprint-01-testing.md)

Loading…
Cancel
Save