diff --git a/backend/package.json b/backend/package.json new file mode 100644 index 0000000..f0d936b --- /dev/null +++ b/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" + } +} diff --git a/backend/src/services/testChainService.js b/backend/src/services/testChainService.js new file mode 100644 index 0000000..55b3472 --- /dev/null +++ b/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} 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; +} diff --git a/backend/src/services/testChainService.test.js b/backend/src/services/testChainService.test.js new file mode 100644 index 0000000..efcec75 --- /dev/null +++ b/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); +}); diff --git a/docs/revision_task/BACKLOG.md b/docs/revision_task/BACKLOG.md index 493c3fb..df92962 100644 --- a/docs/revision_task/BACKLOG.md +++ b/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) важнее. -**Карта больших кусков работ:** [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 (части можно параллелить). **Этап 2 (ТЗ §5)** — дашборды. diff --git a/docs/revision_task/BACKLOG_IDEAS.md b/docs/revision_task/BACKLOG_IDEAS.md new file mode 100644 index 0000000..edc4efd --- /dev/null +++ b/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** (создание файла).* diff --git a/docs/revision_task/TESTING_JOURNAL.md b/docs/revision_task/TESTING_JOURNAL.md new file mode 100644 index 0000000..451402d --- /dev/null +++ b/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) (задачи).* diff --git a/docs/revision_task/card1.md b/docs/revision_task/card1.md index 797fc39..a072b03 100644 --- a/docs/revision_task/card1.md +++ b/docs/revision_task/card1.md @@ -77,5 +77,6 @@ - ТЗ: [task.md](task.md) §4.1, §4.2 - Спринт 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) diff --git a/docs/revision_task/sprint-01-testing.md b/docs/revision_task/sprint-01-testing.md new file mode 100644 index 0000000..bd88e74 --- /dev/null +++ b/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:** дата __________, подпись/комментарий _________________________ diff --git a/docs/revision_task/sprint-01.md b/docs/revision_task/sprint-01.md index 380ecea..395733b 100644 --- a/docs/revision_task/sprint-01.md +++ b/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)