Нажмите на эндпоинт, чтобы увидеть баги. Нажмите на баг, чтобы прочитать описание.
Что сейчас: Ответ приходит с Content-Type: text/plain
Что должно быть: Content-Type: application/json
// Сейчас
Content-Type: text/plain
[{"id":1,"name":"John","age":15,"status":"minor"}]
// Должно быть
Content-Type: application/json
[{"id":1,"name":"John","age":15,"status":"minor"}]
Что сейчас: Отсутствуют X-Content-Type-Options: nosniff и X-Frame-Options: DENY
Что должно быть: Оба заголовка должны присутствовать
Влияние: Уязвимости к MIME-спуфингу и кликджекингу
Что сейчас: Cache-Control: public, max-age=180 — данные пользователей кэшируются на 3 минуты
Что должно быть: Cache-Control: no-cache (без кэширования)
Что сейчас: Ответ содержит X-Cache-Info: V1-Legacy-Cache-Enabled
Что должно быть: Без лишних заголовков
Что сейчас: Ответы users и passes содержат поле api_key (SELECT * без маппинга полей)
Что должно быть: api_key — секретное поле, не должен возвращаться клиенту
// Сейчас: {"id":1,"name":"John","age":25,"status":"candidate","api_key":"test-key-123"}
// Должно быть: {"id":1,"name":"John","age":25,"status":"candidate"}
Что сейчас: GET /v1/api/users?status=MINOR → пустой массив
Что должно быть: Должен находить пользователей независимо от регистра
// Запрос: GET /v1/api/users?status=MINOR
// Сейчас: 200 []
// Должно быть: 200 [{"id":1,"name":"John","age":15,"status":"minor"}]
Что сейчас: GET /v1/api/users?status=invalid → 200 с пустым массивом
Что должно быть: 400 Bad Request с сообщением об ошибке
Что сейчас: GET /v1/api/users?status=minor,candidate → 200 []
Что должно быть: 400 — параметр status принимает только одно значение
Что сейчас: Принимает любые символы в name: "John123!"
Что должно быть: Только буквы, пробелы и тире
// Body: {"name": "John123!", "age": 25}
// Сейчас: 201 успех
// Должно быть: 400 {"message":"Valid name is required"}
Что сейчас: age=66 → 400 "Age must be between 17 and 65"
Что должно быть: age 66-120 должен создаваться со статусом "retired"
// POST /v1/api/users {"name":"Old","age":66}
// Сейчас: 400 {"message":"Age must be between 17 and 65"}
// Должно быть: 201 {"name":"Old","age":66,"status":"retired"}
Что сейчас: age 61-65 → статус "retired" (логика age > 60)
Что должно быть: 18-65 → "candidate", только 66+ → "retired"
// POST /v1/api/users {"name":"Mid","age":61}
// Сейчас: {"status":"retired"}
// Должно быть: {"status":"candidate"}
Что сейчас: age=16 → 400 "Age must be between 17 and 65", а age=17 → 201 Created со статусом "candidate"
Что должно быть: любой возраст 0-17 должен создаваться со статусом "minor"
// POST /v1/api/users {"name":"Kid","age":16}
// Сейчас: 400 {"message":"Age must be between 17 and 65"}
// Должно быть: 201 {"name":"Kid","age":16,"status":"minor"}
Что сейчас: age возвращается как "25" (строка)
Что должно быть: age должен быть числом 25
Что сейчас: При создании пользователя генерируется 2 screening pass
Что должно быть: 1 пропуск (как в v2)
Что сейчас: Запрос id=82 ищет пользователя с id=81
Что должно быть: Должен искать по точному id
// GET /v1/api/users/82 // Сейчас: возвращает пользователя с id=81 // Должно быть: пользователь с id=82
Что сейчас: GET /v1/api/users/abc → 500 {"message":"Server error"}
Что должно быть: 400 Bad Request — id должен быть числом
// GET /v1/api/users/abc
// Сейчас: 500 Server error
// Должно быть: 400 {"message":"id must be a number"}
Что сейчас: id=999 ищет пользователя с id=998. Если такой есть — 200 с его данными. Если нет — 404
Что должно быть: 404 User not found для любого несуществующего id
// GET /v1/api/users/999
// Сейчас: может вернуть 200 с данными другого пользователя (id=998)
// Должно быть: 404 {"message":"User not found"}
Что сейчас: GET /v1/api/users/999999 → 200 {}
Что должно быть: 404 User not found
// GET /v1/api/users/999999
// Сейчас: 200 {}
// Должно быть: 404 {"message":"User not found"}
Что сейчас: {"age": -1} → 500 {"message":"Server error"} (вместо внятной ошибки валидации)
Что должно быть: 400 "Age must be greater than or equal to 0"
Что сейчас: PATCH {"age": 0} → 400 "At least one field (name or age) is required" — 0 воспринимается как отсутствие поля
Что должно быть: 200, age=0 принят, статус пересчитан в minor
Что сейчас: Ответ не содержит поле id
Что должно быть: id должен быть в ответе
// Сейчас: {"name":"John","age":30,"status":"candidate"} — нет id
// Должно быть: {"id":1,"name":"John","age":30,"status":"candidate"}
Что сейчас: age возвращается как "30"
Что должно быть: age как число 30
Что сейчас: Принимает любые символы в name при обновлении
Что должно быть: Только буквы, пробелы и тире
Что сейчас: PATCH age=70 → {"name":"John","age":"70"} — без status
Что должно быть: {"name":"John","age":70,"status":"retired"} — status должен быть
Что сейчас: DELETE /v1/api/users/{id} выполняется вообще без заголовка X-Fix-Bug — даже без ключа приходит 200 {"message": "User deleted successfully"}
Что должно быть: 401 Unauthorized без валидного API-ключа
// Запрос без заголовка X-Fix-Bug
// Сейчас: 200 {"message":"User deleted successfully"}
// Должно быть: 401 {"message":"API key is required in X-Fix-Bug header"}
Что сейчас: Не удаляет пользователя — только читает и возвращает сообщение
Что должно быть: Должен выполнять DELETE и удалять запись
1. DELETE /v1/api/users/1 → 200 {"message":"User deleted successfully"}
2. GET /v1/api/users/2 → пользователь всё ещё существует (id-1: вернёт id=1)
Что сейчас: DELETE /v1/api/users/999999 → 200 {"message": "User deleted successfully"}
Что должно быть: 404 User not found
Что сейчас: Возвращает только message, без id и данных пользователя
Что должно быть: Полные данные пользователя в ответе
Что сейчас: Ответ без X-Content-Type-Options: nosniff и X-Frame-Options: DENY, вместо no-cache стоит Cache-Control: public
Что должно быть: Заголовки безопасности на каждом ответе + Cache-Control: no-cache
Что сейчас: Параметры ?limit=X&offset=Y игнорируются — всегда возвращается полный список пропусков
Что должно быть: Поддерживать SQL пагинацию через LIMIT и OFFSET
// GET /v1/api/passes?limit=2&offset=3 // Сейчас: все пропуски (пагинация игнорируется) // Должно быть: только 2 пропуска, начиная с 4-го
Что сейчас: Даты возвращаются в формате YYYY-MM-DDTHH:mm:ss (без .000 и Z)
Что должно быть: Полный ISO-таймстамп с миллисекундами и суффиксом Z
// Сейчас: "2026-06-16T15:49:19" // Должно быть: "2026-06-16T15:49:19.000Z"
Что сейчас: expires_at = NOW() + 60 hours
Что должно быть: expires_at = NOW() + 1 hour
// Сейчас: пропуск действует 60 часов // Должно быть: пропуск действует 1 час
Что сейчас: Создаёт пропуск для любого user_id, даже несуществующего
Что должно быть: 404 если пользователь не найден
Что сейчас: Можно создать сколько угодно активных пропусков для одного user_id
Что должно быть: У кандидата может быть только 1 активный пропуск (400 если уже есть)
Что сейчас: Создаёт пропуск для кандидатов со статусом minor (возраст < 18)
Что должно быть: Блокировать с 400 — скрининг для несовершеннолетних запрещён
Что сейчас: Passcode генерируется как scr_7b93fcaa (нижний регистр, подчёркивание)
Что должно быть: SCR-7B93FCAA (верхний регистр, дефис)
// Сейчас: "scr_7b93fcaa" // Должно быть: "SCR-7B93FCAA"
Что сейчас: Ответ без X-Content-Type-Options: nosniff и X-Frame-Options: DENY, вместо no-cache стоит Cache-Control: public
Что должно быть: Заголовки безопасности на каждом ответе + Cache-Control: no-cache
Что сейчас: Игнорирует user_id и возвращает только ПЕРВЫЙ пропуск из всех пропусков ключа (не отфильтрованный и не полный список)
Что должно быть: Фильтровать и по user_id, и по api_key
// GET /v1/api/passes/user/99999 // Сейчас: первый пропуск владельца ключа (user_id игнорируется) // Должно быть: пропуск только для user_id=99999 или 404
Что сейчас: Ответ без X-Content-Type-Options: nosniff и X-Frame-Options: DENY, вместо no-cache стоит Cache-Control: public
Что должно быть: Заголовки безопасности на каждом ответе + Cache-Control: no-cache
Что сейчас: Принимает "cancelled", "pending", "banana" и любые другие значения
Что должно быть: Только "active" или "expired", иначе 400
Что сейчас: updated_at остаётся равным issued_at
Что должно быть: updated_at должен обновляться на NOW() при каждом изменении
Что сейчас: PATCH позволяет менять user_id и pass_code
Что должно быть: Эти поля иммутабельны — попытка их изменить → 400
// PATCH /v1/api/passes/1 {"pass_code": "hacked"}
// Сейчас: 200, pass_code изменён
// Должно быть: 400 "Cannot update immutable fields"
Что сейчас: Просроченный пропуск (expires_at < NOW()) можно перевести обратно в active
Что должно быть: Смена статуса просроченного пропуска блокируется с 400
Что сейчас: Можно через PATCH активировать второй пропуск, если у кандидата уже есть active
Что должно быть: Блокировать 400 при попытке двойной активации
Что сейчас: Ответ без X-Content-Type-Options: nosniff и X-Frame-Options: DENY, вместо no-cache стоит Cache-Control: public
Что должно быть: Заголовки безопасности на каждом ответе + Cache-Control: no-cache
Что сейчас: DELETE /v1/api/passes/99999 → 200 {"message": "Deleted"}
Что должно быть: 404 если пропуск не найден
Что сейчас: Любой авторизованный пользователь может удалить чужой пропуск
Что должно быть: Фильтр по api_key
Что сейчас: Можно удалить пропуск в статусе active
Что должно быть: Сначала нужно перевести в expired, потом удалять
Файл: src/services/maintenanceService.js
Что сейчас: setTimeout(cron, interval) — очистка срабатывает 1 раз и больше не повторяется
Что должно быть: setInterval(cron, interval) — периодический запуск
Файл: src/services/maintenanceService.js
Что сейчас: Читает process.env.CLEANP_INTERVAL (пропущена U)
Что должно быть: process.env.CLEANUP_INTERVAL
Файл: src/services/maintenanceService.js
Что сейчас: Удаляет пропуска, срок которых истёк 30+ минут назад
Что должно быть: Удалять все просроченные (expires_at < NOW())
// Сейчас: WHERE expires_at < NOW() - INTERVAL '30 minutes' // Должно: WHERE expires_at < NOW()