diff --git a/api_test_specification.md b/api_test_specification.md index 3f791c5..c99f7b1 100644 --- a/api_test_specification.md +++ b/api_test_specification.md @@ -12,7 +12,17 @@ 9. [PUT /account/leadtarget](#9-put-accountleadtarget) 10. [DELETE /account/leadtarget/{id}](#10-delete-accountleadtargetid) 11. [GET /account/leadtarget/{quizID}](#11-get-accountleadtargetquizid) -13. [POST /question/create](#13-post-questioncreate) +12. [POST /question/create](#12-post-questioncreate) +13. [POST /question/getList](#13-post-questiongetlist) +14. [PATCH /question/edit](#14-patch-questionedit) +15. [POST /question/copy](#15-post-questioncopy) +16. [POST /question/history](#16-post-questionhistory) +17. [DELETE /question/delete](#17-delete-questiondelete) +18. [POST /quiz/create](#18-post-quizcreate) +19. [POST /quiz/getList](#19-post-quizgetlist) +20. [PATCH /quiz/edit](#20-patch-quizedit) +21. [POST /quiz/copy](#21-post-quizcopy) +22. [POST /quiz/history](#22-post-quizhistory) ## 1. GET /account/get @@ -1756,254 +1766,1868 @@ 7. Контроль доступа работает корректно 8. Данные возвращаются в корректном формате -## 13. POST /question/create +## 12. POST /question/create -### Description -Creates a new question in the system. The endpoint requires authentication and accepts a JSON payload with question details. +### 12.1 Описание +Создание нового вопроса в системе. Эндпоинт позволяет добавлять вопросы к существующим квизам или как независимые сущности, в зависимости от логики приложения и схемы `QuestionCreateRequest`. -### Request -- Method: POST -- Path: /question/create -- Authentication: Required (Bearer Token) +### 12.2 Базовые требования +- Требуется JWT токен авторизации (bearerAuth). +- Создает новый вопрос в системе. +- Тело запроса должно соответствовать схеме `#/components/schemas/QuestionCreateRequest`. +- В случае успеха возвращает созданный вопрос согласно схеме `#/components/schemas/Question`. +- Поддерживает HTTP коды ответа: 200 (OK), 400 (Bad Request), 401 (Unauthorized), 406 (Not Acceptable), 422 (Unprocessable Entity), 424 (Failed Dependency), 500 (Internal Server Error). + +### 12.3 Тестовые сценарии + +#### 12.3.1 Успешное создание вопроса +**Предусловия:** +- Пользователь авторизован. +- JWT токен валиден. +- Данные для создания вопроса корректны и полны (например, указан существующий `quiz_id`). + +**Входные данные:** +- Метод: POST +- URL: /question/create +- Headers: + - Authorization: Bearer {valid_jwt_token} + - Content-Type: application/json +- Тело запроса (согласно схеме `#/components/schemas/QuestionCreateRequest`. Пример для типа "variant"): + ```json + { + "quiz_id": 12345, // integer, обязательное поле + "title": "Какой основной компонент воздуха?", // string, обязательное поле (max 512 chars) + "type": "variant", // string, обязательное поле, enum: [text, variant, images, select, varimg, emoji, date, number, page, rating, result, file] + "description": "Выберите один правильный ответ.", // string, опционально + "required": true, // boolean, опционально + "page": 1, // integer, опционально + "content": "{}" // string, JSON serialized config, опционально. В данном примере - строка, содержащая пустой JSON объект. + } + ``` + +**Ожидаемый результат:** +- HTTP Status: 200 OK - Content-Type: application/json +- Тело ответа содержит объект созданного вопроса (структура и поля зависят от схемы `#/components/schemas/Question`. Пример, который может потребовать уточнений на основе схемы `Question`): + ```json + { + "id": "generated_question_id", + "quiz_id": 12345, + "title": "Какой основной компонент воздуха?", + "type": "variant", + "description": "Выберите один правильный ответ.", + "required": true, + "page": 1, + "content": "{}", // В данном примере - строка, содержащая пустой JSON объект. + "created_at": "YYYY-MM-DDTHH:mm:ssZ" + // ...другие поля в соответствии со схемой Question + } + ``` -#### Request Body Schema -```json -{ - "quiz_id": "integer (required)", - "title": "string (required, max 512 chars)", - "description": "string (optional)", - "type": "string (required, enum)", - "required": "boolean (optional)", - "page": "integer (optional)", - "content": "string (optional, JSON)" -} -``` +**Проверки:** +1. Вопрос успешно создан в базе данных с переданными атрибутами. +2. Ответ содержит все обязательные поля согласно схеме `Question`. +3. `id` созданного вопроса уникален. +4. `quiz_id` в ответе соответствует переданному значению и имеет тип integer. +5. `title`, `type`, `description`, `required`, `page` в ответе соответствуют переданным значениям. +6. Поле `content` в ответе содержит строку с JSON-конфигурацией. В данном примере ожидается строка "{}". +7. Если вопрос привязан к квизу (`quiz_id`), эта связь корректно установлена. +8. Формат `created_at` (и других дат) соответствует ISO 8601. -### Response +#### 12.3.2 Проверка авторизации +**Сценарий 2.1: Отсутствие токена** +- Headers: без Authorization. +- Ожидаемый результат: 401 Unauthorized. +- Проверка: Тело ответа содержит сообщение об ошибке авторизации (согласно `#/components/responses/UnauthorizedError`). + +**Сценарий 2.2: Невалидный токен** +- Headers: Authorization: Bearer invalid_jwt_token. +- Ожидаемый результат: 401 Unauthorized. +- Проверка: Тело ответа содержит сообщение об ошибке авторизации. + +**Сценарий 2.3: Истекший токен** +- Headers: Authorization: Bearer expired_jwt_token. +- Ожидаемый результат: 401 Unauthorized. +- Проверка: Тело ответа содержит сообщение об ошибке авторизации. + +#### 12.3.3 Проверка валидации данных (Ответ 400 Bad Request или 422 Unprocessable Entity) +**Сценарий 3.1: Отсутствие обязательных полей в `QuestionCreateRequest`** +- Входные данные: Тело запроса без одного или нескольких обязательных полей (например, без `title`, `quiz_id` или `type`). +- Ожидаемый результат: 400 Bad Request или 422 Unprocessable Entity. +- Проверка: Сообщение об ошибке указывает на отсутствующие или невалидные поля (для 422, тело ответа должно содержать `message` со деталями). + +**Сценарий 3.2: Некорректный формат данных в полях `QuestionCreateRequest`** +- Входные данные: Тело запроса с полями неправильного типа (например, `quiz_id` не integer, `type` не из списка enum, `required` не boolean, `content` - невалидный JSON в строке). +- Ожидаемый результат: 400 Bad Request или 422 Unprocessable Entity. +- Проверка: Сообщение об ошибке указывает на поля с некорректным форматом. + +**Сценарий 3.3: Некорректные значения полей (нарушение бизнес-логики)** +- Входные данные: Например, попытка создать вопрос для несуществующего `quiz_id`. `title` превышает 512 символов. Некорректная структура JSON в поле `content` для выбранного `type` вопроса. +- Ожидаемый результат: 400 Bad Request или 422 Unprocessable Entity. +- Проверка: Сообщение об ошибке описывает проблему валидации. + +#### 12.3.4 Проверка ответа 406 Not Acceptable +**Сценарий 4.1: Условия, приводящие к 406 Not Acceptable** +- Предусловия: (Необходимо определить конкретные условия на основе спецификации `StatusNotAcceptableError`. Например, квиз, к которому добавляется вопрос, находится в статусе, не допускающем изменений, или превышен лимит вопросов). +- Входные данные: Корректный запрос на создание вопроса, но выполнены условия для ответа 406. +- Ожидаемый результат: 406 Not Acceptable. +- Проверка: Тело ответа соответствует схеме `#/components/responses/StatusNotAcceptableError` и содержит информативное сообщение. + +#### 12.3.5 Проверка ответа 424 Failed Dependency +**Сценарий 5.1: Имитация сбоя внешней зависимости** +- Предусловия: Имитация сбоя критической внешней службы или ресурса, от которого зависит создание вопроса (например, недоступность базы данных для проверки `quiz_id` или генерации ID). +- Входные данные: Корректный запрос на создание вопроса. +- Ожидаемый результат: 424 Failed Dependency. +- Проверка: Тело ответа соответствует схеме `#/components/responses/StatusFailedDependencyError` и содержит информативное сообщение. + +#### 12.3.6 Проверка безопасности +**Сценарий 6.1: SQL-инъекции** +- Входные данные: В строковых полях запроса передаются SQL-инъекции. +- Ожидаемый результат: 400 Bad Request / 422 Unprocessable Entity, либо запрос обработан без выполнения инъекции, данные сохранены корректно (экранированы). +- Проверка: Отсутствие уязвимости к SQL-инъекциям. Данные в БД не повреждены. + +**Сценарий 6.2: XSS-атаки** +- Входные данные: В строковых полях запроса передаются XSS-скрипты. +- Ожидаемый результат: 400 Bad Request / 422 Unprocessable Entity, либо данные сохранены корректно (экранированы), и XSS не выполняется при отображении этих данных. +- Проверка: Отсутствие уязвимости к XSS. + +#### 12.3.7 Проверка производительности +**Сценарий 7.1: Время создания вопроса** +- Проверка: Время ответа на запрос создания вопроса < N ms (например, < 500ms). +- Проверка: Стабильность времени ответа при многократных запросах. + +**Сценарий 7.2: Нагрузочное тестирование** +- Проверка: Система способна обработать X запросов на создание вопросов в секунду/минуту без значительного ухудшения производительности или ошибок. + +#### 12.3.8 Обработка ошибок (Общие) +**Сценарий 8.1: Внутренняя ошибка сервера (500 Internal Server Error)** +- Предусловия: Имитация непредвиденного сбоя на сервере при обработке запроса (например, исключение в коде, не связанное с зависимостями или валидацией). +- Ожидаемый результат: 500 Internal Server Error. +- Проверка: Тело ответа соответствует схеме `#/components/responses/InternalServerError` и содержит общее сообщение об ошибке, не раскрывая чувствительной информации. + +### 12.4 Особые моменты для тестирования +1. **Валидация схемы `QuestionCreateRequest`:** + * Тщательная проверка всех обязательных (`quiz_id`, `title`, `type`) и опциональных (`description`, `required`, `page`, `content`) полей. + * Проверка типов данных: `quiz_id` (integer), `title` (string, max 512), `description` (string), `type` (string, enum: `[text, variant, images, select, varimg, emoji, date, number, page, rating, result, file]`), `required` (boolean), `page` (integer), `content` (string, должен быть валидным JSON; для примера достаточно "{}"). + * Проверка граничных значений: длина `title`. + * Валидация JSON-структуры внутри строки `content` в зависимости от `type` вопроса (например, для `variant` или `select` JSON может содержать массив опций, для `rating` - параметры шкалы и т.д.; для общего случая или если структура не определена, строка "{}" является валидным значением). + * Проверка поведения при отсутствии опциональных полей. +2. **Логика типов вопросов:** + * Корректное создание и сохранение вопросов всех поддерживаемых типов. + * Проверка, что для каждого типа вопроса сохраняется и обрабатывается релевантная информация (например, `options` для типов с выбором ответа). +3. **Привязка к квизу (если применимо):** + * Успешное создание вопроса с корректным `quiz_id`. + * Поведение системы при указании несуществующего `quiz_id` (ожидается ошибка 400/422 или 404, в зависимости от реализации). + * Поведение при попытке создать вопрос без `quiz_id` (если это поле опционально). +4. **Конкурентное создание:** + * Попытка одновременного создания нескольких вопросов (особенно если есть генерация порядковых номеров или другие неатомарные операции). +5. **Интеграционные проверки:** + * Убедиться, что созданный вопрос корректно отображается при запросе квиза, к которому он был добавлен (если есть такая связь). + * Проверить, как вопрос влияет на другие части системы (например, подсчет вопросов в квизе). + +### 12.5 Критерии приемки +1. Все тестовые сценарии из раздела 12.3 пройдены успешно. +2. Время ответа на создание вопроса соответствует установленным требованиям производительности. +3. Обработка всех кодов ошибок (200, 400, 401, 406, 422, 424, 500) соответствует спецификации API и предоставляет корректные сообщения. +4. Обеспечена защита от основных уязвимостей безопасности (SQL-инъекции, XSS). +5. Созданный вопрос корректно сохраняется в базе данных со всеми указанными атрибутами и доступен для последующего использования. +6. Логирование операций по созданию вопросов информативно и покрывает как успешные случаи, так и ошибки. + +## 13. POST /question/getList + +### 13.1 Описание +Получение списка вопросов с поддержкой пагинации и возможной фильтрацией. Эндпоинт используется для отображения вопросов в интерфейсах администрирования, привязки к квизам или других сценариях, где требуется доступ к списку вопросов. + +### 13.2 Базовые требования +- Требуется JWT токен авторизации (bearerAuth). +- Метод запроса: POST. +- Тело запроса должно соответствовать схеме `#/components/schemas/GetQuestionListRequest` (обычно включает параметры пагинации `page`, `limit` и опциональные фильтры). +- В случае успеха возвращает список вопросов и метаданные пагинации согласно схеме `#/components/schemas/GetQuestionListResponse`. +- Поддерживает HTTP коды ответа: 200 (OK), 400 (Bad Request), 401 (Unauthorized), 406 (Not Acceptable), 500 (Internal Server Error). + +### 13.3 Тестовые сценарии + +#### 13.3.1 Успешное получение списка вопросов (с пагинацией) +**Предусловия:** +- Пользователь авторизован. +- JWT токен валиден. +- В системе существуют вопросы, соответствующие критериям запроса (если есть фильтры). + +**Входные данные:** +- Метод: POST +- URL: /question/getList +- Headers: + - Authorization: Bearer {valid_jwt_token} + - Content-Type: application/json +- Тело запроса (пример, структура соответствует `#/components/schemas/GetQuestionListRequest`. Все поля опциональны, если не указано иное в схеме.): + ```json + { + "limit": 10, // integer, формат uint64 + "page": 1, // integer, формат uint64 + "quiz_id": 123, // integer, формат uint64, ID квиза + "type": "variant", // string, enum: [text, variant, images, select, varimg, emoji, date, number, page, rating, result, file] + "from": 1678886400, // integer, int64, начало периода (timestamp) + "to": 1679145600, // integer, int4, конец периода (timestamp) + "search": "воздух", // string, строка для поиска + "deleted": false, // boolean, получить только удаленные (true) или только не удаленные (false/отсутствует) + "required": true // boolean, фильтр по обязательности вопроса + } + ``` + +**Ожидаемый результат:** +- HTTP Status: 200 OK - Content-Type: application/json +-- Тело ответа (пример, структура соответствует `#/components/schemas/GetQuestionListResponse` и `#/components/schemas/Question`): + ```json + { + "count": 1, // integer, uint64 - Общее количество вопросов, соответствующих фильтрам + "items": [ + { + "id": 101, // integer, uint64 + "quiz_id": 123, // integer, uint64 + "title": "Какой основной компонент воздуха?", // string + "description": "Выберите один правильный ответ из предложенных.", // string + "type": "variant", // string, enum + "required": true, // boolean + "deleted": false, // boolean + "page": 1, // integer + "content": "{\"options\":[{\"text\":\"Азот\"},{\"text\":\"Кислород\"}], \"answer\":\"Азот\"}", // string, Serialized JSON content + "version": 1, // integer + "parent_ids": [], // array of integers (int32) + "created_at": "2023-03-15T10:00:00Z", // string, date-time + "updated_at": "2023-03-15T11:30:00Z" // string, date-time + } + // ...могут быть другие вопросы, если limit > 1 и count > 1 + ] + } + ``` -#### Success Response (200) -```json -{ - "id": "integer", - "quiz_id": "integer", - "title": "string", - "description": "string", - "type": "string", - "required": "boolean", - "deleted": "boolean", - "page": "integer", - "content": "string", - "version": "integer", - "parent_ids": ["integer"], - "created_at": "string (date-time)", - "updated_at": "string (date-time)" -} -``` +**Проверки:** +1. Ответ содержит корректное количество вопросов в массиве `items`, не превышающее запрошенный `limit` (если `limit` был в запросе и меньше, чем `count`). +-2. Поля `total_count`, `page`, `limit` в ответе соответствуют запрошенным и реальным данным. +-3. Каждый объект вопроса в массиве `items` соответствует схеме `Question` и содержит все ожидаемые поля. ++2. Поле `count` в ответе содержит общее количество вопросов, соответствующих критериям фильтрации. ++3. Каждый объект вопроса в массиве `items` соответствует схеме `Question` и содержит все поля: `id`, `quiz_id`, `title`, `description`, `type`, `required`, `deleted`, `page`, `content`, `version`, `parent_ids`, `created_at`, `updated_at`, с корректными типами данных. +4. Если применялись фильтры (`quiz_id`, `type`, `from`/`to`, `search`, `deleted`, `required`), то все возвращенные вопросы соответствуют этим фильтрам (например, созданы в указанный период `from`-`to`, содержат `search` строку в релевантных полях, имеют соответствующий статус `deleted` и `required`). -#### Error Responses -- 400 Bad Request -- 401 Unauthorized -- 406 Not Acceptable -- 422 Validation Error -- 424 Failed Dependency -- 500 Internal Server Error +#### 13.3.2 Проверка авторизации +**Сценарий 2.1: Отсутствие токена** +- Headers: без Authorization. +- Ожидаемый результат: 401 Unauthorized. +- Проверка: Тело ответа содержит сообщение об ошибке авторизации (согласно `#/components/responses/UnauthorizedError`). -### Test Cases +**Сценарий 2.2: Невалидный токен** +- Headers: Authorization: Bearer invalid_jwt_token. +- Ожидаемый результат: 401 Unauthorized. +- Проверка: Тело ответа содержит сообщение об ошибке авторизации. -#### TC1: Create Basic Question -**Objective**: Verify that a question can be created with minimal required fields -**Preconditions**: -- Valid authentication token -- Quiz exists in the system -**Test Steps**: -1. Send POST request with minimal required fields: -```json -{ - "quiz_id": 1, - "title": "Test Question", - "type": "text" -} -``` -**Expected Results**: -- Status Code: 200 -- Response contains created question with all fields -- Question ID is generated -- Created_at and updated_at timestamps are set +**Сценарий 2.3: Истекший токен** +- Headers: Authorization: Bearer expired_jwt_token. +- Ожидаемый результат: 401 Unauthorized. +- Проверка: Тело ответа содержит сообщение об ошибке авторизации. -#### TC2: Create Question with All Fields -**Objective**: Verify that a question can be created with all possible fields -**Preconditions**: -- Valid authentication token -- Quiz exists in the system -**Test Steps**: -1. Send POST request with all fields: -```json -{ - "quiz_id": 1, - "title": "Comprehensive Test Question", - "description": "This is a detailed question description", - "type": "variant", - "required": true, - "page": 1, - "content": "{\"options\": [\"Option 1\", \"Option 2\"]}" -} -``` -**Expected Results**: -- Status Code: 200 -- Response contains all provided fields -- Question is properly created with all attributes +#### 13.3.3 Проверка валидации данных запроса (Ответ 400 Bad Request) +(Все поля в `GetQuestionListRequest` являются опциональными, если не указано иное в схеме. Проверки ниже касаются корректности форматов и значений, если поля предоставлены.) -#### TC3: Create Question with Different Types -**Objective**: Verify that questions can be created with all supported types -**Preconditions**: -- Valid authentication token -- Quiz exists in the system -**Test Steps**: -1. Create questions with each type: - - text - - variant - - images - - select - - varimg - - emoji - - date - - number - - page - - rating - - result - - file -**Expected Results**: -- Status Code: 200 for each type -- Questions are created successfully -- Type-specific content is properly stored +**Сценарий 3.1: Некорректные параметры пагинации** +-- Входные данные: `page` или `limit` не являются integer, или имеют некорректный формат (например, отрицательные значения, если не разрешены). +- Ожидаемый результат: 400 Bad Request. +- Проверка: Сообщение об ошибке указывает на невалидные параметры пагинации. -#### TC4: Invalid Quiz ID -**Objective**: Verify error handling for non-existent quiz ID -**Preconditions**: -- Valid authentication token -**Test Steps**: -1. Send POST request with invalid quiz_id: -```json -{ - "quiz_id": 999999, - "title": "Test Question", - "type": "text" -} -``` -**Expected Results**: -- Status Code: 400 or 424 -- Error message indicates invalid quiz ID +**Сценарий 3.2: Некорректный формат фильтров** +-- Входные данные: ++ - `quiz_id`, `from`, `to` не являются integer. ++ - `type` не из списка допустимых enum значений. ++ - `deleted` или `required` не являются boolean. ++ - `search` имеет недопустимый формат (если есть ограничения, например, по длине). ++ - `from` > `to` (некорректный временной диапазон). +- Ожидаемый результат: 400 Bad Request. +- Проверка: Сообщение об ошибке указывает на невалидные параметры фильтрации. -#### TC5: Missing Required Fields -**Objective**: Verify validation of required fields -**Preconditions**: -- Valid authentication token -**Test Steps**: -1. Send POST request without quiz_id -2. Send POST request without title -3. Send POST request without type -**Expected Results**: -- Status Code: 400 -- Error message indicates missing required fields +-**Сценарий 3.3: Отсутствие обязательных полей в запросе (если есть)** +-- Входные данные: Тело запроса без обязательных полей (например, если `page` и `limit` обязательны). +-- Ожидаемый результат: 400 Bad Request. +-- Проверка: Сообщение об ошибке указывает на отсутствующие поля. ++**Сценарий 3.3: Невалидные значения для логических фильтров** ++- Входные данные: `deleted` или `required` переданы как строки (например, "true") вместо boolean `true`. ++ Ожидаемый результат: 400 Bad Request. ++ Проверка: Сообщение об ошибке указывает на невалидный тип данных для булевых фильтров. -#### TC6: Invalid Question Type -**Objective**: Verify validation of question type -**Preconditions**: -- Valid authentication token -- Quiz exists in the system -**Test Steps**: -1. Send POST request with invalid type: -```json -{ - "quiz_id": 1, - "title": "Test Question", - "type": "invalid_type" -} -``` -**Expected Results**: -- Status Code: 400 or 422 -- Error message indicates invalid question type +#### 13.3.4 Проверка ответа 406 Not Acceptable +**Сценарий 4.1: Условия, приводящие к 406 Not Acceptable** +- Предусловия: (Необходимо определить конкретные условия на основе спецификации `StatusNotAcceptableError`. Например, запрос специфических данных, которые не могут быть предоставлены в текущем контексте). +- Входные данные: Корректный запрос на получение списка, но выполнены условия для ответа 406. +- Ожидаемый результат: 406 Not Acceptable. +- Проверка: Тело ответа соответствует схеме `#/components/responses/StatusNotAcceptableError` и содержит информативное сообщение. -#### TC7: Title Length Validation -**Objective**: Verify title length constraints -**Preconditions**: -- Valid authentication token -- Quiz exists in the system -**Test Steps**: -1. Send POST request with title exceeding 512 characters -2. Send POST request with empty title -**Expected Results**: -- Status Code: 400 or 422 -- Error message indicates title length violation +#### 13.3.5 Проверка ответа 424 Failed Dependency +**Сценарий 5.1: Имитация сбоя внешней зависимости** +- Предусловия: Имитация сбоя критической внешней службы или ресурса, от которого зависит создание вопроса (например, недоступность базы данных для проверки `quiz_id` или генерации ID). +- Входные данные: Корректный запрос на создание вопроса. +- Ожидаемый результат: 424 Failed Dependency. +- Проверка: Тело ответа соответствует схеме `#/components/responses/StatusFailedDependencyError` и содержит информативное сообщение. -#### TC8: Invalid Content Format -**Objective**: Verify content JSON format validation -**Preconditions**: -- Valid authentication token -- Quiz exists in the system -**Test Steps**: -1. Send POST request with invalid JSON in content field -2. Send POST request with content not matching type requirements -**Expected Results**: -- Status Code: 400 or 422 -- Error message indicates invalid content format +#### 13.3.6 Обработка ошибок (Общие) +**Сценарий 6.1: Внутренняя ошибка сервера (500 Internal Server Error)** +- Предусловия: Имитация непредвиденного сбоя на сервере (например, ошибка подключения к БД при выборке данных). +- Ожидаемый результат: 500 Internal Server Error. +- Проверка: Тело ответа соответствует схеме `#/components/responses/InternalServerError`. -#### TC9: Unauthorized Access -**Objective**: Verify authentication requirement -**Preconditions**: None -**Test Steps**: -1. Send POST request without authentication token -**Expected Results**: -- Status Code: 401 -- Error message indicates authentication required +### 13.4 Особые моменты для тестирования +1. **Логика пагинации:** + * Корректность `total_count` при наличии/отсутствии фильтров. + * Правильность выдачи данных на первой, последней и промежуточных страницах. + * Поведение при запросе страницы, превышающей общее количество страниц (например, `items` - пустой массив, `page` равен запрошенному). + * Поведение при `limit=0` или очень большом `limit` (если есть ограничения). +2. **Логика фильтрации (согласно схеме `GetQuestionListRequest`):** + * Корректность работы каждого отдельного фильтра: + - `quiz_id`: выборка вопросов только для указанного квиза. + - `type`: выборка вопросов только указанного типа. + - `from`/`to`: выборка вопросов, созданных/измененных в указанном временном диапазоне. Проверить граничные условия. + - `search`: поиск по релевантным текстовым полям вопроса (например, `title`, `description`, текстовое содержимое в `content`). Проверить поиск по части слова, полному слову, нескольким словам. Чувствительность к регистру (если определено). + - `deleted`: корректная выборка только удаленных (`true`) или только неудаленных (`false` или отсутствие поля) вопросов. + - `required`: корректная выборка вопросов по флагу обязательности. + * Корректность работы комбинации нескольких фильтров одновременно (например, `quiz_id` + `type` + `search`). + * Поведение при передаче несуществующих значений для фильтров (например, `quiz_id`, которого нет; тип вопроса, который не используется). + * Поведение фильтров при наличии пустого значения (например, `search: ""`). +3. **Сортировка (если поддерживается):** + * Проверка сортировки по умолчанию (например, по дате создания). + * Проверка работы явной сортировки по различным полям и направлениям (ASC/DESC), если это предусмотрено в `GetQuestionListRequest`. +4. **Структура и полнота данных в ответе:** + * Каждый вопрос в списке `items` должен содержать все поля, определенные схемой `Question`. + * Проверка корректности данных для каждого поля вопроса (типы, форматы). -#### TC10: Rate Limiting -**Objective**: Verify rate limiting behavior -**Preconditions**: -- Valid authentication token -- Quiz exists in the system -**Test Steps**: -1. Send multiple POST requests in quick succession -**Expected Results**: -- Status Code: 429 (if rate limited) -- Error message indicates rate limit exceeded +### 13.5 Критерии приемки +1. Все тестовые сценарии из раздела 13.3 пройдены успешно. +2. Время ответа на получение списка вопросов соответствует установленным требованиям производительности. +3. Пагинация и фильтрация (если реализованы) работают корректно и в соответствии с ожиданиями. +4. Обработка всех кодов ошибок (200, 400, 401, 406, 500) соответствует спецификации API и предоставляет корректные сообщения. +5. Данные в ответе полны, корректны и соответствуют схеме `GetQuestionListResponse` и `Question`. +6. Логирование запросов на получение списка вопросов информативно. -### Special Testing Considerations +## 14. PATCH /question/edit -#### Content Validation -- Verify that content JSON matches the expected format for each question type -- Test with various content structures for each type -- Validate that content is properly stored and can be retrieved +### 14.1 Описание +Обновление существующего вопроса. Позволяет изменять одно или несколько полей вопроса по его ID. -#### Type-Specific Requirements -- Each question type may have specific content requirements -- Test creation with valid and invalid content for each type -- Verify that type-specific validation rules are enforced +### 14.2 Базовые требования +- Требуется JWT токен авторизации (bearerAuth). +- Метод запроса: PATCH. +- Тело запроса должно соответствовать схеме `#/components/schemas/UpdateQuestionRequest` и содержать обязательное поле `id` вопроса. +- В случае успеха возвращает объект согласно схеме `#/components/schemas/UpdateQuestionResponse`, содержащий `updated` (ID обновленного вопроса). +- Поддерживает HTTP коды ответа: 200 (OK), 400 (Bad Request), 401 (Unauthorized), 406 (Not Acceptable), 422 (Unprocessable Entity), 424 (Failed Dependency), 500 (Internal Server Error). -#### Quiz Relationship -- Verify that questions are properly associated with the quiz -- Test creation of questions for different quizzes -- Verify that quiz_id references are properly validated +### 14.3 Тестовые сценарии -#### Version Control -- Check if version tracking is implemented -- Verify that new questions start with appropriate version numbers -- Test version incrementing if applicable +#### 14.3.1 Успешное обновление вопроса (изменение нескольких полей) +**Предусловия:** +- Пользователь авторизован. +- JWT токен валиден. +- Вопрос с указанным `id` существует в системе. -#### Performance -- Test creation of multiple questions in sequence -- Monitor response times for different question types -- Verify system behavior under load +**Входные данные:** +- Метод: PATCH +- URL: /question/edit +- Headers: + - Authorization: Bearer {valid_jwt_token} + - Content-Type: application/json +- Тело запроса (согласно `#/components/schemas/UpdateQuestionRequest`): + ```json + { + "id": 101, // integer, uint64 - ID существующего вопроса + "title": "Обновленный заголовок вопроса?", + "desc": "Новое описание для вопроса.", + "type": "text", + "required": false, + "content": "{\"placeholder\":\"Введите ваш ответ здесь\"}", + "page": 2 + } + ``` -#### Security -- Verify that questions can only be created by authorized users -- Test access control for different user roles -- Verify that sensitive data is properly handled +**Ожидаемый результат:** +- HTTP Status: 200 OK +- Content-Type: application/json +- Тело ответа (согласно `#/components/schemas/UpdateQuestionResponse`): + ```json + { + "updated": 101 // integer, uint64 - ID обновленного вопроса + } + ``` -#### Data Integrity -- Verify that all fields are properly stored -- Test creation with special characters in text fields -- Verify that timestamps are properly set -- Check that required fields are properly enforced \ No newline at end of file +**Проверки:** +1. Поле `updated` в ответе содержит `id` обновленного вопроса. +2. Данные вопроса в базе данных (или при последующем GET запросе вопроса) обновлены в соответствии с переданными в запросе полями (`title`, `desc`, `type`, `required`, `content`, `page`). +3. Поля, которые не были переданы в запросе на обновление, остались без изменений в базе данных. +4. Поле `updated_at` для вопроса в базе данных обновлено. + +#### 14.3.2 Успешное обновление одного поля вопроса +**Предусловия:** +- Пользователь авторизован, JWT токен валиден. +- Вопрос с `id` = 102 существует. + +**Входные данные:** +- Метод: PATCH +- URL: /question/edit +- Headers: Authorization: Bearer {valid_jwt_token}, Content-Type: application/json +- Тело запроса: + ```json + { + "id": 102, + "title": "Только заголовок обновлен" + } + ``` + +**Ожидаемый результат:** +- HTTP Status: 200 OK +- Тело ответа: `{"updated": 102}` + +**Проверки:** +1. `updated` в ответе равен 102. +2. В базе данных для вопроса с `id`=102 обновлен только `title`, остальные поля (desc, type, required и т.д.) не изменились. +3. `updated_at` обновлен. + +#### 14.3.3 Проверка валидации данных запроса (Ответ 400 Bad Request или 422 Unprocessable Entity) + +**Сценарий 3.1: Отсутствие обязательного поля `id`** +- Входные данные: Тело запроса без поля `id`. + ```json + { + "title": "Запрос без ID" + } + ``` +- Ожидаемый результат: 400 Bad Request или 422 Unprocessable Entity (в зависимости от того, как сервер обрабатывает отсутствие `id` до валидации остальных полей). +- Проверка: Сообщение об ошибке указывает на отсутствие или невалидность поля `id` (для 422, тело ответа может содержать `message` с деталями). + +**Сценарий 3.2: Некорректный тип данных для `id`** +- Входные данные: `id` передано как строка. + ```json + { + "id": "not_an_integer", + "title": "Невалидный ID" + } + ``` +- Ожидаемый результат: 400 Bad Request или 422 Unprocessable Entity. +- Проверка: Сообщение об ошибке указывает на неверный тип данных для `id`. + +**Сценарий 3.3: Попытка обновления несуществующего вопроса** +- Входные данные: `id` = 99999 (несуществующий). + ```json + { + "id": 99999, + "title": "Несуществующий вопрос" + } + ``` +- Ожидаемый результат: 400 Bad Request или 422 Unprocessable Entity (так как 404 не заявлен, возможно, ошибка валидации ID). +- Проверка: Сообщение об ошибке указывает, что вопрос с таким `id` не найден или `id` невалиден. + +**Сценарий 3.4: Некорректные значения для обновляемых полей** +- Входные данные (примеры, тестировать каждое поле отдельно): + - `title` длиннее 512 символов. + - `type` не из списка допустимых enum значений. + - `required` не boolean (например, строка "true"). + - `content` не является валидной JSON-строкой. + - `page` не integer (например, строка "2"). + - `desc` имеет недопустимый формат (если есть ограничения). +- Ожидаемый результат: 400 Bad Request или 422 Unprocessable Entity. +- Проверка: Сообщение об ошибке указывает на конкретное поле и характер ошибки валидации. + +#### 14.3.4 Проверка авторизации +**Сценарий 4.1: Отсутствие токена** +- Headers: без Authorization. +- Ожидаемый результат: 401 Unauthorized. +- Проверка: Тело ответа согласно `#/components/responses/UnauthorizedError`. + +**Сценарий 4.2: Невалидный токен** +- Headers: Authorization: Bearer invalid_jwt_token. +- Ожидаемый результат: 401 Unauthorized. + +**Сценарий 4.3: Истекший токен** +- Headers: Authorization: Bearer expired_jwt_token. +- Ожидаемый результат: 401 Unauthorized. + +#### 14.3.5 Проверка специфических кодов ответа +**Сценарий 5.1: Условия для 406 Not Acceptable** +- Предусловия: (Определить условия на основе спецификации `StatusNotAcceptableError`. Например, попытка изменить вопрос, который заблокирован для редактирования). +- Ожидаемый результат: 406 Not Acceptable. +- Проверка: Тело ответа согласно `#/components/responses/StatusNotAcceptableError`. + +**Сценарий 5.2: Условия для 424 Failed Dependency** +- Предусловия: (Определить условия на основе спецификации `StatusFailedDependencyError`. Например, сбой внешней системы, от которой зависит обновление вопроса). +- Ожидаемый результат: 424 Failed Dependency. +- Проверка: Тело ответа согласно `#/components/responses/StatusFailedDependencyError`. + +#### 14.3.6 Проверка безопасности (SQL-инъекции, XSS) +**Сценарий 6.1: SQL-инъекции в строковых полях** +- Входные данные: В полях `title`, `desc`, `content` передаются SQL-инъекции. +- Ожидаемый результат: 400/422, либо запрос обработан без выполнения инъекции, данные корректно сохранены/обновлены (экранированы). +- Проверка: Отсутствие уязвимости. + +**Сценарий 6.2: XSS-атаки в строковых полях** +- Входные данные: В полях `title`, `desc`, `content` передаются XSS-скрипты. +- Ожидаемый результат: 400/422, либо данные корректно сохранены/обновлены (экранированы), XSS не выполняется при отображении этих данных. +- Проверка: Отсутствие уязвимости. + +#### 14.3.7 Обработка ошибок (Общие) +**Сценарий 7.1: Внутренняя ошибка сервера (500 Internal Server Error)** +- Предусловия: Имитация сбоя на сервере при обновлении (например, ошибка БД). +- Ожидаемый результат: 500 Internal Server Error. +- Проверка: Тело ответа согласно `#/components/responses/InternalServerError`. + +### 14.4 Особые моменты для тестирования +1. **Частичное обновление**: Убедиться, что при обновлении только некоторых полей остальные поля вопроса не затрагиваются и сохраняют свои предыдущие значения. +2. **Валидация `id`**: Тщательная проверка того, что `id` существует и принадлежит текущему пользователю/аккаунту (если есть такая логика авторизации на уровне записи). +3. **Валидация полей `UpdateQuestionRequest`**: + * Проверка всех полей на соответствие типам и форматам (`id` uint64, `title` string<=512, `desc` string, `type` enum, `required` boolean, `content` JSON string, `page` integer). + * Корректная обработка пустых значений для опциональных полей (например, если передать `title: ""` или `desc: null` - как это должно обрабатываться). +4. **Конкурентное обновление**: Попытка одновременного обновления одного и того же вопроса разными запросами. Проверить на наличие race conditions, корректность блокировок (если используются), и итоговое состояние данных. +5. **Поле `updated_at`**: Проверить, что это поле автоматически обновляется при любом успешном изменении вопроса. +6. **Влияние на связанные сущности**: Если изменение вопроса (например, типа или содержимого) может влиять на квизы, в которых он используется, или на ответы пользователей, это необходимо проверить (может выходить за рамки юнит-теста этого эндпоинта). + +### 14.5 Критерии приемки +1. Все тестовые сценарии из раздела 14.3 пройдены успешно. +2. Вопрос корректно обновляется в базе данных, затрагиваются только переданные поля. +3. Обработка всех кодов ошибок (200, 400, 401, 406, 422, 424, 500) соответствует спецификации API. +4. Обеспечена защита от основных уязвимостей безопасности. +5. Поле `updated_at` вопроса обновляется корректно. +6. Логирование операций по обновлению вопросов информативно. + +## 15. POST /question/copy + +### 15.1 Описание +Создание копии существующего вопроса и привязка этой копии к указанному квизу. Оригинальный вопрос остается без изменений. + +### 15.2 Базовые требования +- Требуется JWT токен авторизации (bearerAuth). +- Метод запроса: POST. +- Тело запроса должно соответствовать схеме `#/components/schemas/CopyQuestionRequest` и содержать обязательные поля: `id` (ID оригинального вопроса) и `quiz_id` (ID целевого квиза для копии). +- В случае успеха (200 OK) возвращает полный объект нового, скопированного вопроса согласно схеме `#/components/schemas/Question`. +- Поддерживает HTTP коды ответа: 200 (OK), 400 (Bad Request), 401 (Unauthorized), 424 (Failed Dependency), 500 (Internal Server Error). + +### 15.3 Тестовые сценарии + +#### 15.3.1 Успешное копирование вопроса +**Предусловия:** +- Пользователь авторизован. +- JWT токен валиден. +- Существует вопрос с `id`, указанным в запросе (оригинальный вопрос). +- Существует квиз с `quiz_id`, указанным в запросе (целевой квиз). + +**Входные данные:** +- Метод: POST +- URL: /question/copy +- Headers: + - Authorization: Bearer {valid_jwt_token} + - Content-Type: application/json +- Тело запроса (согласно `#/components/schemas/CopyQuestionRequest`): + ```json + { + "id": 101, // integer, uint64 - ID существующего вопроса для копирования + "quiz_id": 202 // integer, uint64 - ID квиза, в который будет помещена копия + } + ``` + +**Ожидаемый результат:** +- HTTP Status: 200 OK +- Content-Type: application/json +- Тело ответа (новый скопированный вопрос, согласно `#/components/schemas/Question`. Пример): + ```json + { + "id": 303, // integer, uint64 - НОВЫЙ ID для скопированного вопроса + "quiz_id": 202, // integer, uint64 - ID целевого квиза из запроса + "title": "Копия: Какой основной компонент воздуха?", // string - title оригинального вопроса (может быть с префиксом) + "description": "Выберите один правильный ответ из предложенных.", // string - description оригинального вопроса + "type": "variant", // string, enum - type оригинального вопроса + "required": true, // boolean - required оригинального вопроса + "deleted": false, // boolean - всегда false для новой копии + "page": 1, // integer - page оригинального вопроса (или сброшено/пересчитано для нового квиза) + "content": "{}", // string - content оригинального вопроса + "version": 1, // integer - version оригинального вопроса (или сброшено до 1) + "parent_ids": [], // array of integers - parent_ids оригинального вопроса (или очищено) + "created_at": "2023-03-16T12:00:00Z", // string, date-time - НОВАЯ дата создания + "updated_at": "2023-03-16T12:00:00Z" // string, date-time - НОВАЯ дата обновления (равна created_at) + } + ``` + +**Проверки:** +1. В базе данных создан новый вопрос. +2. Возвращенный объект вопроса имеет новый, уникальный `id` (не равен `id` оригинала). +3. `quiz_id` в возвращенном объекте соответствует `quiz_id`, указанному в запросе. +4. Поля `title`, `description`, `type`, `required`, `content`, `page`, `version` (и возможно `parent_ids`) скопированы из оригинального вопроса (с возможными модификациями, например, префикс к `title` или сброс `version`/`page`/`parent_ids` – уточнить ожидаемое поведение). +5. Поле `deleted` у новой копии установлено в `false`. +6. Поля `created_at` и `updated_at` у новой копии являются свежими временными метками. +7. Оригинальный вопрос (с `id` из запроса) остался без изменений в базе данных. + +#### 15.3.2 Проверка авторизации +**Сценарий 2.1: Отсутствие токена** +- Headers: без Authorization. +- Ожидаемый результат: 401 Unauthorized. +- Проверка: Тело ответа согласно `#/components/responses/UnauthorizedError`. + +**Сценарий 2.2: Невалидный токен** +- Headers: Authorization: Bearer invalid_jwt_token. +- Ожидаемый результат: 401 Unauthorized. + +**Сценарий 2.3: Истекший токен** +- Headers: Authorization: Bearer expired_jwt_token. +- Ожидаемый результат: 401 Unauthorized. + +#### 15.3.3 Проверка валидации данных запроса (Ответ 400 Bad Request) + +**Сценарий 3.1: Отсутствие обязательных полей** +- Входные данные: Тело запроса без `id` или без `quiz_id`. + ```json + // Пример без id + { "quiz_id": 202 } + // Пример без quiz_id + { "id": 101 } + ``` +- Ожидаемый результат: 400 Bad Request. +- Проверка: Сообщение об ошибке указывает на отсутствующее обязательное поле. + +**Сценарий 3.2: Некорректный тип данных для `id` или `quiz_id`** +- Входные данные: `id` или `quiz_id` переданы как строка или другой невалидный тип. + ```json + { "id": "not_an_integer", "quiz_id": 202 } + { "id": 101, "quiz_id": "not_an_integer" } + ``` +- Ожидаемый результат: 400 Bad Request. +- Проверка: Сообщение об ошибке указывает на неверный тип данных для соответствующего поля. + +**Сценарий 3.3: Копирование несуществующего вопроса** +- Входные данные: `id` = 99999 (несуществующий оригинальный вопрос). + ```json + { "id": 99999, "quiz_id": 202 } + ``` +- Ожидаемый результат: 400 Bad Request (или 404, если бы он был заявлен; в данном случае OpenAPI указывает 400). +- Проверка: Сообщение об ошибке указывает, что оригинальный вопрос с таким `id` не найден. + +**Сценарий 3.4: Копирование в несуществующий квиз** +- Входные данные: `quiz_id` = 88888 (несуществующий целевой квиз). + ```json + { "id": 101, "quiz_id": 88888 } + ``` +- Ожидаемый результат: 400 Bad Request (или 404, если бы он был заявлен). +- Проверка: Сообщение об ошибке указывает, что целевой квиз с таким `quiz_id` не найден. + +#### 15.3.4 Проверка специфических кодов ответа +**Сценарий 4.1: Условия для 424 Failed Dependency** +- Предусловия: (Определить условия на основе спецификации `StatusFailedDependencyError`. Например, сбой при записи в базу данных из-за внешних проблем, не связанных с валидацией входных данных). +- Входные данные: Корректный запрос на копирование. +- Ожидаемый результат: 424 Failed Dependency. +- Проверка: Тело ответа согласно `#/components/responses/StatusFailedDependencyError`. + +#### 15.3.5 Обработка ошибок (Общие) +**Сценарий 5.1: Внутренняя ошибка сервера (500 Internal Server Error)** +- Предусловия: Имитация неожиданного сбоя на сервере в процессе копирования. +- Ожидаемый результат: 500 Internal Server Error. +- Проверка: Тело ответа согласно `#/components/responses/InternalServerError`. + +### 15.4 Особые моменты для тестирования +1. **Генерация нового ID**: Убедиться, что у скопированного вопроса всегда генерируется новый, уникальный `id`. +2. **Копирование данных**: Детально проверить, какие именно поля копируются из оригинала, а какие устанавливаются по умолчанию или пересчитываются для новой копии (например, `title`, `description`, `type`, `required`, `content`, `page`, `version`, `parent_ids`). Уточнить политику копирования этих полей. +3. **Поле `deleted`**: Убедиться, что у новой копии поле `deleted` всегда `false`. +4. **Временные метки**: `created_at` и `updated_at` для копии должны быть новыми. +5. **Иммутабельность оригинала**: Оригинальный вопрос не должен быть затронут операцией копирования. +6. **Копирование в тот же квиз**: Проверить поведение, если `quiz_id` в запросе совпадает с `quiz_id` оригинального вопроса. Должна ли создаваться копия в том же квизе? Будут ли конфликты имен (если есть уникальность `title` в рамках квиза)? +7. **Права доступа**: Если есть ограничения на доступ к оригинальному вопросу или к целевому квизу, проверить их соблюдение. + +### 15.5 Критерии приемки +1. Все тестовые сценарии из раздела 15.3 пройдены успешно. +2. Новый вопрос корректно создается в базе данных как копия оригинала, со всеми необходимыми изменениями (новый ID, целевой `quiz_id`, свежие timestamp'ы). +3. Оригинальный вопрос остается неизменным. +4. Обработка всех кодов ошибок (200, 400, 401, 424, 500) соответствует спецификации API. +5. Логирование операций по копированию вопросов информативно. + +## 16. POST /question/history + +### 16.1 Описание +Получение истории изменений для указанного вопроса. Каждая запись в истории представляет собой состояние (версию) вопроса на определенный момент времени. + +### 16.2 Базовые требования +- Требуется JWT токен авторизации (bearerAuth). +- Метод запроса: POST. +- Тело запроса должно соответствовать схеме `#/components/schemas/GetQuestionHistoryRequest` и содержать обязательное поле `id` вопроса, а также опциональные поля для пагинации `l` (limit) и `p` (page). +- В случае успеха (200 OK) возвращает объект, содержащий поле `items`. Поле `items` является массивом, где каждый элемент - это объект `Question`, представляющий одну версию из истории вопроса. +- Поддерживает HTTP коды ответа: 200 (OK), 400 (Bad Request), 401 (Unauthorized), 424 (Failed Dependency), 500 (Internal Server Error). + +### 16.3 Тестовые сценарии + +#### 16.3.1 Успешное получение истории вопроса (с пагинацией) +**Предусловия:** +- Пользователь авторизован. +- JWT токен валиден. +- Вопрос с указанным `id` существует и имеет несколько записей в истории (несколько версий). + +**Входные данные:** +- Метод: POST +- URL: /question/history +- Headers: + - Authorization: Bearer {valid_jwt_token} + - Content-Type: application/json +- Тело запроса (согласно `#/components/schemas/GetQuestionHistoryRequest`): + ```json + { + "id": 101, // integer, uint64 - ID вопроса + "l": 10, // integer, uint64 - Limit (количество записей на странице) + "p": 1 // integer, uint64 - Page (номер страницы, например, 1-based) + } + ``` + +**Ожидаемый результат:** +- HTTP Status: 200 OK +- Content-Type: application/json +- Тело ответа (объект с полем `items`, содержащим массив версий вопроса): + ```json + { + "items": [ + { + "id": 101, // integer, uint64 - ID оригинального вопроса + "quiz_id": 123, + "title": "Обновленный заголовок вопроса (Версия 2)", + "description": "Новое описание для вопроса (Версия 2).", + "type": "text", + "required": false, + "deleted": false, // или true, если версия была удалена, а потом восстановлена + "page": 2, + "content": "{\"placeholder\":\"Версия 2\"}", + "version": 2, // integer - номер версии + "parent_ids": [], + "created_at": "2023-03-15T11:30:00Z", // Время создания/фиксации этой версии + "updated_at": "2023-03-15T11:30:00Z" // Обычно совпадает с created_at для записи истории + }, + { + "id": 101, // ID оригинального вопроса + "quiz_id": 123, + "title": "Исходный заголовок вопроса (Версия 1)", + "description": "Начальное описание (Версия 1).", + "type": "variant", + "required": true, + "deleted": false, + "page": 1, + "content": "{\"options\":[...]}", + "version": 1, + "parent_ids": [], + "created_at": "2023-03-15T10:00:00Z", + "updated_at": "2023-03-15T10:00:00Z" + } + // ... другие версии, если l > 2 ... + ] + } + ``` + +**Проверки:** +1. Массив `items` в ответе содержит записи истории для вопроса с запрошенным `id`. +2. Количество элементов в массиве `items` не превышает значение `l` (limit) из запроса. +3. Если `p` (page) и `l` (limit) используются, пагинация работает корректно, возвращая соответствующий срез истории. +4. Каждая запись в массиве `items` является полным объектом `Question`, представляющим состояние вопроса на момент этой версии. +5. Поле `version` в каждой записи истории корректно отражает номер версии. +6. Записи истории отсортированы в ожидаемом порядке (например, по убыванию `version` или `created_at`/`updated_at` этой записи истории). +7. Если у вопроса нет истории (например, он только что создан и не изменялся), массив `items` содержит одну запись, представляющую текущее состояние вопроса с `version: 1`. +8. Если запрос сделан с `l=0` или без `l` и `p`, проверить поведение по умолчанию (например, возврат всех версий или первой страницы с лимитом по умолчанию). + +#### 16.3.2 Проверка авторизации +**Сценарий 2.1: Отсутствие токена** +- Headers: без Authorization. +- Ожидаемый результат: 401 Unauthorized. +- Проверка: Тело ответа согласно `#/components/responses/UnauthorizedError`. + +**Сценарий 2.2: Невалидный токен** +- Headers: Authorization: Bearer invalid_jwt_token. +- Ожидаемый результат: 401 Unauthorized. + +**Сценарий 2.3: Истекший токен** +- Headers: Authorization: Bearer expired_jwt_token. +- Ожидаемый результат: 401 Unauthorized. + +#### 16.3.3 Проверка валидации данных запроса (Ответ 400 Bad Request) + +**Сценарий 3.1: Отсутствие обязательного поля `id`** +- Входные данные: Тело запроса без поля `id`. + ```json + { "l": 10, "p": 1 } + ``` +- Ожидаемый результат: 400 Bad Request. +- Проверка: Сообщение об ошибке указывает на отсутствие обязательного поля `id`. + +**Сценарий 3.2: Некорректный тип данных для `id`, `l`, или `p`** +- Входные данные (примеры): + - `id` как строка: `{ "id": "not_an_integer", "l": 10, "p": 1 }` + - `l` как строка: `{ "id": 101, "l": "ten", "p": 1 }` + - `p` как строка: `{ "id": 101, "l": 10, "p": "one" }` +- Ожидаемый результат: 400 Bad Request. +- Проверка: Сообщение об ошибке указывает на неверный тип данных для соответствующего поля. + +**Сценарий 3.3: Некорректные значения для `l` или `p` (если есть ограничения)** +- Входные данные (примеры): + - `l` отрицательное: `{ "id": 101, "l": -5, "p": 1 }` + - `p` отрицательное или нулевое (если нумерация с 1): `{ "id": 101, "l": 10, "p": 0 }` +- Ожидаемый результат: 400 Bad Request. +- Проверка: Сообщение об ошибке указывает на недопустимое значение для `l` или `p`. + +**Сценарий 3.4: Запрос истории для несуществующего вопроса** +- Входные данные: `id` = 99999 (несуществующий вопрос). + ```json + { "id": 99999, "l": 10, "p": 1 } + ``` +- Ожидаемый результат: 400 Bad Request (или возможно пустой массив `items` в ответе 200 OK, если это допустимое поведение для несуществующего ID – уточнить). +- Проверка: Если 400, сообщение об ошибке указывает, что вопрос не найден. Если 200 OK, `items` должен быть пустым. + +#### 16.3.4 Проверка специфических кодов ответа +**Сценарий 4.1: Условия для 424 Failed Dependency** +- Предусловия: (Определить условия на основе спецификации `StatusFailedDependencyError`. Например, сбой при чтении из базы данных истории). +- Входные данные: Корректный запрос на получение истории. +- Ожидаемый результат: 424 Failed Dependency. +- Проверка: Тело ответа согласно `#/components/responses/StatusFailedDependencyError`. + +#### 16.3.5 Обработка ошибок (Общие) +**Сценарий 5.1: Внутренняя ошибка сервера (500 Internal Server Error)** +- Предусловия: Имитация неожиданного сбоя на сервере. +- Ожидаемый результат: 500 Internal Server Error. +- Проверка: Тело ответа согласно `#/components/responses/InternalServerError`. + +### 16.4 Особые моменты для тестирования +1. **Порядок версий**: Убедиться, что версии в истории отсортированы последовательно и предсказуемо (например, по убыванию номера `version` или по убыванию `created_at` записи истории). +2. **Полнота данных в версиях**: Каждая версия вопроса в `items` должна содержать все поля из схемы `Question` и отражать состояние вопроса на момент создания этой версии. +3. **Пагинация (`l` и `p`)**: Тщательно проверить работу пагинации: корректность количества возвращаемых элементов, переход по страницам, поведение на первой и последней странице, поведение при запросе страницы за пределами существующей истории. +4. **История для нового/неизмененного вопроса**: Если вопрос только создан и не редактировался, эндпоинт должен вернуть массив `items` с одной записью (текущее состояние, `version: 1`). +5. **Глубина/ограничения истории**: Если система накладывает ограничения на количество хранимых версий или глубину истории, это необходимо проверить. +6. **Согласованность данных**: Поля `id` и `quiz_id` в объектах истории должны соответствовать оригинальному вопросу. Поле `version` должно быть уникальным для каждой записи истории одного вопроса и увеличиваться (или изменяться предсказуемо). + +### 16.5 Критерии приемки +1. Все тестовые сценарии из раздела 16.3 пройдены успешно. +2. История вопроса возвращается корректно, со всеми версиями и точными данными для каждой версии. +3. Параметры пагинации `l` и `p` работают правильно. +4. Обработка всех кодов ошибок (200, 400, 401, 424, 500) соответствует спецификации API. +5. Данные в ответе структурированы правильно (объект с полем `items`, которое является массивом объектов `Question`). +6. Логирование запросов к истории вопросов информативно. + +## 17. DELETE /question/delete + +### 17.1 Описание +Удаление (или деактивация) существующего вопроса по его ID. В зависимости от реализации, это может быть физическое удаление или "мягкое" удаление (пометка вопроса как удаленного). + +### 17.2 Базовые требования +- Требуется JWT токен авторизации (bearerAuth). +- Метод запроса: DELETE. +- Тело запроса должно соответствовать схеме `#/components/schemas/DeactivateQuestionRequest` и содержать обязательное поле `id` удаляемого вопроса. +- В случае успеха (200 OK) возвращает объект согласно схеме `#/components/schemas/DeactivateQuestionResponse`, содержащий `deactivated` (ID удаленного/деактивированного вопроса). +- Поддерживает HTTP коды ответа: 200 (OK), 400 (Bad Request), 401 (Unauthorized), 424 (Failed Dependency), 500 (Internal Server Error). + +### 17.3 Тестовые сценарии + +#### 17.3.1 Успешное удаление (деактивация) вопроса +**Предусловия:** +- Пользователь авторизован. +- JWT токен валиден. +- Вопрос с указанным `id` существует в системе и не помечен как удаленный. + +**Входные данные:** +- Метод: DELETE +- URL: /question/delete +- Headers: + - Authorization: Bearer {valid_jwt_token} + - Content-Type: application/json +- Тело запроса (согласно `#/components/schemas/DeactivateQuestionRequest`): + ```json + { + "id": 101 // integer, uint64 - ID существующего вопроса + } + ``` + +**Ожидаемый результат:** +- HTTP Status: 200 OK +- Content-Type: application/json +- Тело ответа (согласно `#/components/schemas/DeactivateQuestionResponse`): + ```json + { + "deactivated": 101 // integer, uint64 - ID деактивированного вопроса + } + ``` + +**Проверки:** +1. Поле `deactivated` в ответе содержит `id` удаленного/деактивированного вопроса. +2. Вопрос в базе данных помечен как удаленный (например, поле `deleted` у объекта Question установлено в `true`, обновлено поле `updated_at`). +3. При попытке получить этот вопрос через GET-запросы (например, `/question/get/{id}` или в списках `/question/getList` без специального флага для удаленных) он либо не возвращается, либо возвращается с отметкой об удалении. +4. Повторный запрос на удаление этого же вопроса обрабатывается корректно (см. сценарий идемпотентности). + +#### 17.3.2 Проверка идемпотентности удаления +**Предусловия:** +- Пользователь авторизован, JWT токен валиден. +- Вопрос с `id` = 101 уже был удален/деактивирован. + +**Входные данные:** +- Метод: DELETE +- URL: /question/delete +- Headers: Authorization: Bearer {valid_jwt_token}, Content-Type: application/json +- Тело запроса: `{"id": 101}` + +**Ожидаемый результат:** +- HTTP Status: 200 OK (или другой код, если система явно сообщает о том, что ресурс уже удален, но 200 OK для идемпотентности предпочтителен). +- Тело ответа: `{"deactivated": 101}`. + +**Проверки:** +1. Операция не вызывает ошибок. +2. Состояние вопроса в базе данных не изменяется (он остается удаленным). + +#### 17.3.3 Проверка авторизации +**Сценарий 3.1: Отсутствие токена** +- Headers: без Authorization. +- Ожидаемый результат: 401 Unauthorized. +- Проверка: Тело ответа согласно `#/components/responses/UnauthorizedError`. + +**Сценарий 3.2: Невалидный токен** +- Headers: Authorization: Bearer invalid_jwt_token. +- Ожидаемый результат: 401 Unauthorized. + +**Сценарий 3.3: Истекший токен** +- Headers: Authorization: Bearer expired_jwt_token. +- Ожидаемый результат: 401 Unauthorized. + +#### 17.3.4 Проверка валидации данных запроса (Ответ 400 Bad Request) + +**Сценарий 4.1: Отсутствие обязательного поля `id`** +- Входные данные: Тело запроса - пустой JSON `{}`. +- Ожидаемый результат: 400 Bad Request. +- Проверка: Сообщение об ошибке указывает на отсутствие обязательного поля `id`. + +**Сценарий 4.2: Некорректный тип данных для `id`** +- Входные данные: `id` передано как строка. + ```json + { "id": "not_an_integer" } + ``` +- Ожидаемый результат: 400 Bad Request. +- Проверка: Сообщение об ошибке указывает на неверный тип данных для `id`. + +**Сценарий 4.3: Попытка удаления несуществующего вопроса** +- Входные данные: `id` = 99999 (несуществующий). + ```json + { "id": 99999 } + ``` +- Ожидаемый результат: 400 Bad Request (или 404, если бы он был заявлен; OpenAPI указывает 400 для этого эндпоинта, что может означать ошибку валидации ID или что ресурс не найден, но обрабатывается как Bad Request). +- Проверка: Сообщение об ошибке указывает, что вопрос с таким `id` не найден или `id` невалиден. + +#### 17.3.5 Проверка специфических кодов ответа +**Сценарий 5.1: Условия для 424 Failed Dependency** +- Предусловия: (Определить условия на основе спецификации `StatusFailedDependencyError`. Например, сбой при обновлении статуса вопроса в базе данных из-за внешних проблем). +- Входные данные: Корректный запрос на удаление. +- Ожидаемый результат: 424 Failed Dependency. +- Проверка: Тело ответа согласно `#/components/responses/StatusFailedDependencyError`. + +#### 17.3.6 Обработка ошибок (Общие) +**Сценарий 6.1: Внутренняя ошибка сервера (500 Internal Server Error)** +- Предусловия: Имитация неожиданного сбоя на сервере в процессе удаления/деактивации. +- Ожидаемый результат: 500 Internal Server Error. +- Проверка: Тело ответа согласно `#/components/responses/InternalServerError`. + +### 17.4 Особые моменты для тестирования +1. **Механизм удаления**: Подтвердить, используется ли "мягкое" удаление (установка флага `deleted: true` и обновление `updated_at` у объекта `Question`) или физическое удаление из базы данных. Схемы `DeactivateQuestionRequest` и `DeactivateQuestionResponse` сильно намекают на мягкое удаление. +2. **Идемпотентность**: Повторное удаление уже удаленного вопроса не должно приводить к ошибке и не должно изменять состояние системы. +3. **Влияние на связанные сущности**: Проверить, как удаление/деактивация вопроса влияет на квизы, в которых он используется (например, удаляется ли он из квизов, или квизы перестают его отображать). Как это влияет на уже существующие ответы пользователей на этот вопрос? +4. **Права доступа**: Если существуют специфические права на удаление вопросов (например, только автор вопроса или администратор), их необходимо проверить. +5. **Восстановление вопроса**: Если используется "мягкое" удаление, проверить, существует ли механизм восстановления вопроса и как он работает (это может быть другой эндпоинт). + +### 17.5 Критерии приемки +1. Все тестовые сценарии из раздела 17.3 пройдены успешно. +2. Вопрос корректно помечается как удаленный в базе данных (или удаляется физически, в зависимости от реализации). +3. Операция идемпотентна. +4. Обработка всех кодов ошибок (200, 400, 401, 424, 500) соответствует спецификации API. +5. Логирование операций по удалению/деактивации вопросов информативно. + +## 18. POST /quiz/create + +### 18.1 Описание +Создание нового квиза в системе. Эндпоинт позволяет определить различные параметры квиза, включая его название, описание, настройки поведения и статус. + +### 18.2 Базовые требования +- Требуется JWT токен авторизации (bearerAuth). +- Создает новый квиз в системе. +- Тело запроса должно соответствовать схеме `#/components/schemas/CreateQuizRequest`. +- В случае успеха возвращает созданный квиз согласно схеме `#/components/schemas/Quiz` со статусом 201 Created. +- Поддерживает HTTP коды ответа: 201 (Created), 400 (Bad Request), 401 (Unauthorized), 406 (Not Acceptable), 409 (Conflict), 422 (Unprocessable Entity), 500 (Internal Server Error). + +### 18.3 Тестовые сценарии + +#### 18.3.1 Успешное создание квиза +**Предусловия:** +- Пользователь авторизован. +- JWT токен валиден. +- Данные для создания квиза корректны и полны. + +**Входные данные:** +- Метод: POST +- URL: /quiz/create +- Headers: + - Authorization: Bearer {valid_jwt_token} + - Content-Type: application/json +- Тело запроса (согласно схеме `#/components/schemas/CreateQuizRequest`. Пример минимального набора): + ```json + { + "name": "Новый квиз по истории", + "description": "Тест на знание ключевых дат.", + "status": "draft", + "config": "{\"rules\":[]}" + } + ``` +- Пример запроса со всеми опциональными полями: + ```json + { + "name": "Полный квиз по географии", + "description": "Детальный тест на знание столиц и стран.", + "fingerprinting": true, + "repeatable": true, + "note_prevented": false, + "mail_notifications": true, + "unique_answers": false, + "config": "{\"showCorrectAnswers\": true}", + "status": "start", + "limit": 100, + "due_to": 1700000000, + "question_cnt": 10, + "time_of_passing": 3600, + "pausable": true, + "super": false, + "group_id": null + } + ``` + +**Ожидаемый результат:** +- HTTP Status: 201 Created +- Content-Type: application/json +- Тело ответа содержит объект созданного квиза (согласно схеме `#/components/schemas/Quiz`): + ```json + { + "id": 123, // integer, uint64 - Новый ID квиза + "qid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", // string - UUID + "accountid": "user_account_id", // string - ID аккаунта создателя + "deleted": false, + "archived": false, + "fingerprinting": true, // или false, в зависимости от запроса + "repeatable": true, // или false + "note_prevented": false, // или true + "mail_notifications": true, // или false + "unique_answers": false, // или true + "name": "Полный квиз по географии", + "description": "Детальный тест на знание столиц и стран.", + "config": "{\"showCorrectAnswers\": true}", + "status": "start", // или другой статус из запроса + "limit": 100, + "due_to": 1700000000, + "time_of_passing": 3600, + "pausable": true, + "version": 1, + "version_comment": null, // или начальный комментарий + "parent_ids": [], + "created_at": "YYYY-MM-DDTHH:mm:ssZ", // Текущая дата и время + "updated_at": "YYYY-MM-DDTHH:mm:ssZ", // Текущая дата и время + "questions_count": 0, // Изначально 0, если question_cnt не создает их сразу + "session_count": 0, + "passed_count": 0, + "average_time": 0, + "super": false, + "group_id": null + } + ``` + +**Проверки:** +1. Квиз успешно создан в базе данных с переданными атрибутами. +2. Ответ содержит все обязательные поля согласно схеме `Quiz`. +3. `id` созданного квиза уникален. +4. Поля, для которых в `CreateQuizRequest` есть значения по умолчанию (`fingerprinting`, `repeatable`, `note_prevented`, `status`, `pausable`, `super`), установлены в эти значения по умолчанию, если они не были переданы в запросе. +5. Сервер-генерируемые поля (`qid`, `accountid`, `version`, `created_at`, `updated_at`, счетчики) корректно заполнены. +6. Поле `status` в ответе соответствует запрошенному или значению по умолчанию (`draft`). + +#### 18.3.2 Проверка авторизации +**Сценарий 2.1: Отсутствие токена** +- Headers: без Authorization. +- Ожидаемый результат: 401 Unauthorized. +- Проверка: Тело ответа содержит сообщение об ошибке авторизации (согласно `#/components/responses/UnauthorizedError`). + +**Сценарий 2.2: Невалидный токен** +- Headers: Authorization: Bearer invalid_jwt_token. +- Ожидаемый результат: 401 Unauthorized. + +**Сценарий 2.3: Истекший токен** +- Headers: Authorization: Bearer expired_jwt_token. +- Ожидаемый результат: 401 Unauthorized. + +#### 18.3.3 Проверка валидации данных (Ответ 400 Bad Request или 422 Unprocessable Entity) +**Сценарий 3.1: Отсутствие обязательных полей в `CreateQuizRequest`** +- Входные данные: Тело запроса без обязательного поля `name`. +- Ожидаемый результат: 400 Bad Request или 422 Unprocessable Entity. +- Проверка: Сообщение об ошибке указывает на отсутствующее поле `name` (для 422, тело ответа должно содержать `message` с деталями). + +**Сценарий 3.2: Некорректный формат данных в полях `CreateQuizRequest`** +- Входные данные (примеры): + - `name` длиннее 700 символов. + - `fingerprinting` не boolean (например, строка "true"). + - `status` не из списка enum (`draft`, `template`, `stop`, `start`). + - `limit` не integer или отрицательное. + - `due_to` не integer (timestamp). + - `config` - невалидный JSON в строке. +- Ожидаемый результат: 400 Bad Request или 422 Unprocessable Entity. +- Проверка: Сообщение об ошибке указывает на поля с некорректным форматом или значением. + +#### 18.3.4 Проверка ответа 406 Not Acceptable +**Сценарий 4.1: Условия, приводящие к 406 Not Acceptable** +- Предусловия: (Необходимо определить конкретные условия на основе спецификации `StatusNotAcceptableError`. Например, попытка создать квиз с параметрами, которые несовместимы с текущими настройками аккаунта или системы). +- Входные данные: Корректный запрос на создание квиза, но выполнены условия для ответа 406. +- Ожидаемый результат: 406 Not Acceptable. +- Проверка: Тело ответа соответствует схеме `#/components/responses/StatusNotAcceptableError` и содержит информативное сообщение. + +#### 18.3.5 Проверка ответа 409 Conflict +**Сценарий 5.1: Создание квиза с уже существующим уникальным идентификатором (если применимо)** +- Предусловия: (Необходимо определить, какие поля, кроме `id`, должны быть уникальными. Например, если `name` должно быть уникальным в рамках аккаунта). Попытка создать квиз с `name`, который уже существует у другого квиза этого же пользователя. +- Входные данные: Запрос на создание квиза с `name`, которое уже занято. +- Ожидаемый результат: 409 Conflict. +- Проверка: Тело ответа соответствует схеме `#/components/responses/StatusConflictError` и содержит информативное сообщение. + +#### 18.3.6 Проверка безопасности +**Сценарий 6.1: SQL-инъекции** +- Входные данные: В строковых полях запроса (`name`, `description`, `config`) передаются SQL-инъекции. +- Ожидаемый результат: 400/422, либо запрос обработан без выполнения инъекции, данные сохранены корректно (экранированы). +- Проверка: Отсутствие уязвимости к SQL-инъекциям. + +**Сценарий 6.2: XSS-атаки** +- Входные данные: В строковых полях запроса (`name`, `description`, `config`) передаются XSS-скрипты. +- Ожидаемый результат: 400/422, либо данные сохранены корректно (экранированы), и XSS не выполняется при отображении этих данных. +- Проверка: Отсутствие уязвимости к XSS. + +#### 18.3.7 Проверка производительности +**Сценарий 7.1: Время создания квиза** +- Проверка: Время ответа на запрос создания квиза < N ms (например, < 500ms). +- Проверка: Стабильность времени ответа при многократных запросах. + +**Сценарий 7.2: Нагрузочное тестирование** +- Проверка: Система способна обработать X запросов на создание квизов в секунду/минуту без значительного ухудшения производительности или ошибок. + +#### 18.3.8 Обработка ошибок (Общие) +**Сценарий 8.1: Внутренняя ошибка сервера (500 Internal Server Error)** +- Предусловия: Имитация непредвиденного сбоя на сервере при обработке запроса. +- Ожидаемый результат: 500 Internal Server Error. +- Проверка: Тело ответа соответствует схеме `#/components/responses/InternalServerError`. + +### 18.4 Особые моменты для тестирования +1. **Валидация схемы `CreateQuizRequest`:** + * Тщательная проверка всех обязательных (`name`) и опциональных полей. + * Проверка типов данных и ограничений (например, `name` maxLength: 700, `status` enum). + * Проверка обработки значений по умолчанию для опциональных полей. + * Валидация JSON-структуры внутри строки `config`. +2. **Логика статусов квиза (`status`):** + * Корректное создание квиза с каждым из допустимых статусов (`draft`, `template`, `stop`, `start`). + * Проверка статуса по умолчанию (`draft`), если не передан. +3. **Влияние `question_cnt`:** Если `question_cnt` предназначен для автоматического создания пустого набора вопросов, проверить, что это происходит. Если это просто метаданные, проверить, что поле сохраняется. (В схеме `Quiz` есть поле `questions_count`, которое может быть связано). +4. **Настройки квиза:** Проверить, как различные булевы флаги (`fingerprinting`, `repeatable`, `note_prevented`, `mail_notifications`, `unique_answers`, `pausable`, `super`) влияют на созданный квиз и его последующее поведение (может требовать интеграционных тестов). +5. **Связь с `group_id`:** Если `super: true`, то `group_id` может быть `null`. Если `super: false` и квиз является частью группы, `group_id` должен указывать на существующий "супер-квиз". Проверить логику валидации этих полей. + +### 18.5 Критерии приемки +1. Все тестовые сценарии из раздела 18.3 пройдены успешно. +2. Время ответа на создание квиза соответствует установленным требованиям производительности. +3. Обработка всех кодов ошибок (201, 400, 401, 406, 409, 422, 500) соответствует спецификации API и предоставляет корректные сообщения. +4. Обеспечена защита от основных уязвимостей безопасности (SQL-инъекции, XSS). +5. Созданный квиз корректно сохраняется в базе данных со всеми указанными атрибутами и доступен для последующего использования. +6. Логирование операций по созданию квизов информативно и покрывает как успешные случаи, так и ошибки. +7. Значения по умолчанию для полей `CreateQuizRequest` применяются корректно. + +## 19. POST /quiz/getList + +### 19.1 Описание +Получение списка квизов с поддержкой пагинации и расширенной фильтрацией. Эндпоинт предназначен для отображения квизов в различных представлениях, учитывая их статус, временные рамки, принадлежность к группам и другие атрибуты. + +### 19.2 Базовые требования +- Требуется JWT токен авторизации (bearerAuth). +- Метод запроса: POST. +- Тело запроса должно соответствовать схеме `#/components/schemas/GetQuizListRequest`. +- В случае успеха возвращает список квизов и метаданные пагинации согласно схеме `#/components/schemas/GetQuizListResponse` (статус 200 OK). +- Поддерживает HTTP коды ответа: 200 (OK), 400 (Bad Request), 401 (Unauthorized), 406 (Not Acceptable), 500 (Internal Server Error). + +### 19.3 Тестовые сценарии + +#### 19.3.1 Успешное получение списка квизов (с пагинацией и фильтрами) +**Предусловия:** +- Пользователь авторизован. +- JWT токен валиден. +- В системе существуют квизы, соответствующие критериям запроса. + +**Входные данные:** +- Метод: POST +- URL: /quiz/getList +- Headers: + - Authorization: Bearer {valid_jwt_token} + - Content-Type: application/json +- Тело запроса (пример, все поля опциональны, используя значения по умолчанию, если не указаны `limit: 10`, `page: 1`): + ```json + { + "limit": 5, + "page": 1, + "from": 1672531200, // Пример timestamp (1 Jan 2023) + "to": 1675209600, // Пример timestamp (1 Feb 2023) + "search": "география", + "status": "start", + "deleted": false, + "archived": false, + "super": false, + "group_id": null + } + ``` + +**Ожидаемый результат:** +- HTTP Status: 200 OK +- Content-Type: application/json +- Тело ответа (согласно `#/components/schemas/GetQuizListResponse` и `#/components/schemas/Quiz`): + ```json + { + "count": 20, // integer, uint64 - Общее количество квизов, соответствующих фильтрам + "items": [ + { + "id": 101, + "qid": "uuid-quiz-101", + "accountid": "user-account-id", + "deleted": false, + "archived": false, + "fingerprinting": true, + "repeatable": true, + "note_prevented": false, + "mail_notifications": true, + "unique_answers": false, + "name": "Квиз по географии Европы", + "description": "Проверьте свои знания о странах Европы.", + "config": "{\"rules\":{}}", + "status": "start", + "limit": 50, + "due_to": 1675000000, + "time_of_passing": 1800, + "pausable": true, + "version": 2, + "version_comment": "Обновлены вопросы", + "parent_ids": [], + "created_at": "2023-01-15T10:00:00Z", + "updated_at": "2023-01-20T11:30:00Z", + "questions_count": 15, + "session_count": 150, + "passed_count": 120, + "average_time": 1200, + "super": false, + "group_id": null + } + // ... другие квизы, если count > 1 и limit позволяет + ] + } + ``` + +**Проверки:** +1. Ответ содержит корректное поле `count`, отражающее общее количество квизов по заданным фильтрам. +2. Количество квизов в массиве `items` не превышает запрошенный `limit` (или значение по умолчанию 10). +3. Пагинация работает корректно: при изменении `page` возвращаются соответствующие срезы данных. +4. Каждый объект квиза в массиве `items` соответствует схеме `Quiz` и содержит все ожидаемые поля с корректными типами данных. +5. Если применялись фильтры (`from`/`to`, `search`, `status`, `deleted`, `archived`, `super`, `group_id`), то все возвращенные квизы соответствуют этим фильтрам. +6. Поля `limit` и `page` по умолчанию равны 10 и 1 соответственно, если не указаны в запросе. + +#### 19.3.2 Проверка авторизации +**Сценарий 2.1: Отсутствие токена** +- Headers: без Authorization. +- Ожидаемый результат: 401 Unauthorized. +- Проверка: Тело ответа содержит сообщение об ошибке авторизации (согласно `#/components/responses/UnauthorizedError`). + +**Сценарий 2.2: Невалидный токен** +- Headers: Authorization: Bearer invalid_jwt_token. +- Ожидаемый результат: 401 Unauthorized. + +**Сценарий 2.3: Истекший токен** +- Headers: Authorization: Bearer expired_jwt_token. +- Ожидаемый результат: 401 Unauthorized. + +#### 19.3.3 Проверка валидации данных запроса (Ответ 400 Bad Request) +(Большинство полей в `GetQuizListRequest` опциональны. Проверки касаются корректности форматов и значений, если поля предоставлены.) + +**Сценарий 3.1: Некорректные параметры пагинации** +- Входные данные: `page` или `limit` не являются integer (uint64), или имеют некорректный формат/значение (например, `limit: 0`, `page: 0`, если `minimum: 1`). +- Ожидаемый результат: 400 Bad Request. +- Проверка: Сообщение об ошибке указывает на невалидные параметры пагинации. + +**Сценарий 3.2: Некорректный формат фильтров** +- Входные данные (примеры): + - `from` или `to` не являются integer (int64). + - `status` не из списка допустимых enum значений (`stop`, `start`, `draft`, `template`, `timeout`, `offlimit`). + - `deleted`, `archived`, `super` не являются boolean. + - `group_id` не integer (uint64). + - `from` > `to` (некорректный временной диапазон). +- Ожидаемый результат: 400 Bad Request. +- Проверка: Сообщение об ошибке указывает на невалидные параметры фильтрации. + +#### 19.3.4 Проверка ответа 406 Not Acceptable +**Сценарий 4.1: Условия, приводящие к 406 Not Acceptable** +- Предусловия: (Необходимо определить конкретные условия на основе спецификации `StatusNotAcceptableError`. Например, запрос на получение данных в формате, который сервер не может предоставить, или комбинация фильтров, которая является недопустимой по бизнес-логике). +- Входные данные: Корректный запрос на получение списка, но выполнены условия для ответа 406. +- Ожидаемый результат: 406 Not Acceptable. +- Проверка: Тело ответа соответствует схеме `#/components/responses/StatusNotAcceptableError`. + +#### 19.3.5 Проверка производительности +**Сценарий 5.1: Время ответа на получение списка** +- Проверка: Время ответа < N ms (например, < 1000ms) при запросе стандартной страницы с `limit=10` и без сложных фильтров. +- Проверка: Время ответа при запросе с большим количеством фильтров и на разные страницы, особенно при большом общем количестве квизов. + +**Сценарий 5.2: Нагрузочное тестирование** +- Проверка: Система способна обработать X запросов на получение списка квизов в секунду/минуту без значительного ухудшения производительности или ошибок. + +#### 19.3.6 Обработка ошибок (Общие) +**Сценарий 6.1: Внутренняя ошибка сервера (500 Internal Server Error)** +- Предусловия: Имитация непредвиденного сбоя на сервере (например, ошибка подключения к БД при выборке данных). +- Ожидаемый результат: 500 Internal Server Error. +- Проверка: Тело ответа соответствует схеме `#/components/responses/InternalServerError`. + +### 19.4 Особые моменты для тестирования +1. **Логика пагинации (`limit`, `page`):** + * Корректность `count` при наличии/отсутствии фильтров. + * Правильность выдачи данных на первой, последней и промежуточных страницах. + * Поведение при запросе страницы, превышающей общее количество страниц (ожидается `items` - пустой массив, `page` равен запрошенному, `count` корректен). + * Проверка значений по умолчанию для `limit` (10) и `page` (1). + * Проверка минимальных значений `limit: 1`, `page: 1`. +2. **Логика фильтрации (согласно `GetQuizListRequest`):** + * Корректность работы каждого отдельного фильтра: + - `from`/`to`: выборка квизов, созданных/измененных в указанном временном диапазоне (проверить по `created_at` или `updated_at` в зависимости от логики). + - `search`: поиск по релевантным текстовым полям квиза (например, `name`, `description`). Проверить чувствительность к регистру, поиск по части слова. + - `status`: выборка квизов только с указанным статусом. + - `deleted`: выборка только удаленных (`true`) или только неудаленных (`false`/отсутствие) квизов. + - `archived`: выборка только архивированных (`true`) или только неархивированных (`false`/отсутствие) квизов. + - `super`: выборка "супер-квизов" (`true`) или обычных (`false`/отсутствие). + - `group_id`: выборка квизов, принадлежащих указанной группе. + * Корректность работы комбинации нескольких фильтров одновременно. + * Поведение при передаче несуществующих значений для фильтров (например, `group_id`, которого нет). +3. **Сортировка (если поддерживается):** + * Проверка сортировки по умолчанию (например, по дате создания). + * Если API позволяет задавать параметры сортировки, протестировать их. +4. **Полнота данных в ответе:** + * Каждый квиз в списке `items` должен содержать все поля, определенные схемой `Quiz`. + +### 19.5 Критерии приемки +1. Все тестовые сценарии из раздела 19.3 пройдены успешно. +2. Время ответа на получение списка квизов соответствует требованиям производительности. +3. Пагинация и все заявленные фильтры работают корректно. +4. Обработка всех кодов ошибок (200, 400, 401, 406, 500) соответствует спецификации API. +5. Данные в ответе полны, корректны и соответствуют схемам `GetQuizListResponse` и `Quiz`. +6. Логирование запросов на получение списка квизов информативно. + +## 20. PATCH /quiz/edit + +### 20.1 Описание +Обновление существующего квиза. Позволяет изменять одно или несколько полей квиза по его ID. Используется для модификации настроек, статуса, контента или метаданных квиза. + +### 20.2 Базовые требования +- Требуется JWT токен авторизации (bearerAuth). +- Метод запроса: PATCH. +- Тело запроса должно соответствовать схеме `#/components/schemas/UpdateQuizRequest` и содержать обязательное поле `id` квиза. +- В случае успеха (200 OK) возвращает объект согласно схеме `#/components/schemas/UpdateQuizResponse`, содержащий `updated` (ID обновленного квиза). +- Поддерживает HTTP коды ответа: 200 (OK), 400 (Bad Request), 401 (Unauthorized), 406 (Not Acceptable), 409 (Conflict), 422 (Unprocessable Entity), 424 (Failed Dependency), 500 (Internal Server Error). + +### 20.3 Тестовые сценарии + +#### 20.3.1 Успешное обновление квиза (изменение нескольких полей) +**Предусловия:** +- Пользователь авторизован. +- JWT токен валиден. +- Квиз с указанным `id` существует в системе. + +**Входные данные:** +- Метод: PATCH +- URL: /quiz/edit +- Headers: + - Authorization: Bearer {valid_jwt_token} + - Content-Type: application/json +- Тело запроса (согласно `#/components/schemas/UpdateQuizRequest`): + ```json + { + "id": 101, // integer, uint64 - ID существующего квиза + "name": "Обновленное название квиза", + "desc": "Новое детальное описание для квиза.", + "status": "start", + "limit": 150, + "fp": true, + "rep": false, + "conf": "{}" + } + ``` + +**Ожидаемый результат:** +- HTTP Status: 200 OK +- Content-Type: application/json +- Тело ответа (согласно `#/components/schemas/UpdateQuizResponse`): + ```json + { + "updated": 101 // integer, uint64 - ID обновленного квиза + } + ``` + +**Проверки:** +1. Поле `updated` в ответе содержит `id` обновленного квиза. +2. Данные квиза в базе данных (или при последующем GET запросе квиза) обновлены в соответствии с переданными в запросе полями. + Соответствие полей `UpdateQuizRequest` полям `Quiz`: + - `fp` -> `fingerprinting` + - `rep` -> `repeatable` + - `note_prevented` -> `note_prevented` + - `mailing` -> `mail_notifications` + - `uniq` -> `unique_answers` + - `name` -> `name` + - `desc` -> `description` + - `conf` -> `config` + - `status` -> `status` (enum: [draft, template, stop, start]) + - `limit` -> `limit` + - `due_to` -> `due_to` + - `time_of_passing` -> `time_of_passing` + - `pausable` -> `pausable` + - `question_cnt` -> `questions_count` (или другая логика, если `question_cnt` изменяет количество вопросов) + - `super` -> `super` + - `group_id` -> `group_id` +3. Поля, которые не были переданы в запросе на обновление, остались без изменений в базе данных. +4. Поле `updated_at` для квиза в базе данных обновлено. + +#### 20.3.2 Успешное обновление одного поля квиза +**Предусловия:** +- Пользователь авторизован, JWT токен валиден. +- Квиз с `id` = 102 существует. + +**Входные данные:** +- Метод: PATCH +- URL: /quiz/edit +- Headers: Authorization: Bearer {valid_jwt_token}, Content-Type: application/json +- Тело запроса: + ```json + { + "id": 102, + "status": "stop" + } + ``` + +**Ожидаемый результат:** +- HTTP Status: 200 OK +- Тело ответа: `{"updated": 102}` + +**Проверки:** +1. `updated` в ответе равен 102. +2. В базе данных для квиза с `id`=102 обновлен только `status`, остальные поля не изменились. +3. `updated_at` обновлен. + +#### 20.3.3 Проверка валидации данных запроса (Ответ 400 Bad Request или 422 Unprocessable Entity) + +**Сценарий 3.1: Отсутствие обязательного поля `id`** +- Входные данные: Тело запроса без поля `id`. + ```json + { "name": "Запрос без ID" } + ``` +- Ожидаемый результат: 400 Bad Request или 422 Unprocessable Entity. +- Проверка: Сообщение об ошибке указывает на отсутствие или невалидность поля `id`. + +**Сценарий 3.2: Некорректный тип данных для `id`** +- Входные данные: `id` передано как строка. + ```json + { "id": "not_an_integer", "name": "Невалидный ID" } + ``` +- Ожидаемый результат: 400 Bad Request или 422 Unprocessable Entity. +- Проверка: Сообщение об ошибке указывает на неверный тип данных для `id`. + +**Сценарий 3.3: Попытка обновления несуществующего квиза** +- Входные данные: `id` = 99999 (несуществующий). + ```json + { "id": 99999, "name": "Несуществующий квиз" } + ``` +- Ожидаемый результат: 400, 422 (или 404, если бы был заявлен и сервер так реагировал). +- Проверка: Сообщение об ошибке указывает, что квиз с таким `id` не найден или `id` невалиден. + +**Сценарий 3.4: Некорректные значения для обновляемых полей** +- Входные данные (примеры, тестировать каждое поле отдельно): + - `name` длиннее 700 символов. + - `status` не из списка допустимых enum значений (`draft`, `template`, `stop`, `start`). + - `fp`, `rep`, `note_prevented`, `mailing`, `uniq`, `pausable`, `super` не boolean. + - `limit`, `due_to`, `time_of_passing`, `question_cnt`, `group_id` не integer/uint64 или отрицательные, если не разрешено. + - `conf` не является валидной JSON-строкой. +- Ожидаемый результат: 400 Bad Request или 422 Unprocessable Entity. +- Проверка: Сообщение об ошибке указывает на конкретное поле и характер ошибки валидации (для 422, тело ответа может содержать `message` с деталями). + +#### 20.3.4 Проверка авторизации +**Сценарий 4.1: Отсутствие токена** +- Headers: без Authorization. +- Ожидаемый результат: 401 Unauthorized. +- Проверка: Тело ответа согласно `#/components/responses/UnauthorizedError`. + +**Сценарий 4.2: Невалидный токен** +- Headers: Authorization: Bearer invalid_jwt_token. +- Ожидаемый результат: 401 Unauthorized. + +**Сценарий 4.3: Истекший токен** +- Headers: Authorization: Bearer expired_jwt_token. +- Ожидаемый результат: 401 Unauthorized. + +#### 20.3.5 Проверка специфических кодов ответа +**Сценарий 5.1: Условия для 406 Not Acceptable** +- Предусловия: (Определить условия на основе спецификации `StatusNotAcceptableError`. Например, попытка изменить квиз, который заблокирован для редактирования из-за его статуса или активных сессий). +- Ожидаемый результат: 406 Not Acceptable. +- Проверка: Тело ответа согласно `#/components/responses/StatusNotAcceptableError`. + +**Сценарий 5.2: Условия для 409 Conflict** +- Предусловия: (Определить условия на основе спецификации `StatusConflictError`. Например, попытка установить `name`, которое уже используется другим квизом, если `name` должно быть уникальным). +- Ожидаемый результат: 409 Conflict. +- Проверка: Тело ответа согласно `#/components/responses/StatusConflictError`. + +**Сценарий 5.3: Условия для 424 Failed Dependency** +- Предусловия: (Определить условия на основе спецификации `StatusFailedDependencyError`. Например, сбой внешней системы, от которой зависит обновление данных квиза, например, при обновлении счетчиков вопросов). +- Ожидаемый результат: 424 Failed Dependency. +- Проверка: Тело ответа согласно `#/components/responses/StatusFailedDependencyError`. + +#### 20.3.6 Проверка безопасности (SQL-инъекции, XSS) +**Сценарий 6.1: SQL-инъекции в строковых полях** +- Входные данные: В полях `name`, `desc`, `conf` передаются SQL-инъекции. +- Ожидаемый результат: 400/422, либо запрос обработан без выполнения инъекции. +- Проверка: Отсутствие уязвимости. + +**Сценарий 6.2: XSS-атаки в строковых полях** +- Входные данные: В полях `name`, `desc`, `conf` передаются XSS-скрипты. +- Ожидаемый результат: 400/422, либо данные корректно сохранены/обновлены (экранированы), XSS не выполняется при отображении. +- Проверка: Отсутствие уязвимости. + +#### 20.3.7 Проверка производительности +**Сценарий 7.1: Время обновления квиза** +- Проверка: Время ответа на запрос обновления < N ms (например, < 500ms). + +**Сценарий 7.2: Нагрузочное тестирование** +- Проверка: Система выдерживает X одновременных запросов на обновление различных квизов. + +#### 20.3.8 Обработка ошибок (Общие) +**Сценарий 8.1: Внутренняя ошибка сервера (500 Internal Server Error)** +- Предусловия: Имитация сбоя на сервере при обновлении (например, ошибка БД). +- Ожидаемый результат: 500 Internal Server Error. +- Проверка: Тело ответа согласно `#/components/responses/InternalServerError`. + +### 20.4 Особые моменты для тестирования +1. **Частичное обновление**: Убедиться, что при обновлении только некоторых полей остальные поля квиза не затрагиваются и сохраняют свои предыдущие значения. +2. **Валидация `id`**: Тщательная проверка того, что `id` существует и принадлежит текущему пользователю/аккаунту (если есть такая логика авторизации на уровне записи). +3. **Валидация всех полей `UpdateQuizRequest`**: Проверить каждое поле на соответствие типам, форматам и допустимым значениям. +4. **Обновление `status`**: Проверить корректность перехода между различными статусами квиза (`draft`, `template`, `stop`, `start`) и возможные ограничения (например, можно ли перевести активный квиз в `draft`). +5. **Поле `updated_at`**: Проверить, что это поле автоматически обновляется при любом успешном изменении квиза. +6. **Влияние `question_cnt`**: Если это поле предназначено для изменения количества связанных вопросов (например, добавление/удаление пустых слотов), проверить эту логику. Если это просто обновление метаданных, убедиться, что значение `questions_count` в объекте `Quiz` обновляется соответствующим образом или поле `question_cnt` просто сохраняется. +7. **Логика `super` и `group_id`**: Проверить корректность обновления этих полей и их взаимное влияние. + +### 20.5 Критерии приемки +1. Все тестовые сценарии из раздела 20.3 пройдены успешно. +2. Квиз корректно обновляется в базе данных, затрагиваются только переданные поля. +3. Обработка всех кодов ошибок (200, 400, 401, 406, 409, 422, 424, 500) соответствует спецификации API. +4. Обеспечена защита от основных уязвимостей безопасности. +5. Поле `updated_at` квиза обновляется корректно. +6. Логирование операций по обновлению квизов информативно. + +## 21. POST /quiz/copy + +### 21.1 Описание +Создание копии существующего квиза. Новый квиз создается со всеми настройками и атрибутами оригинального квиза, но получает новый уникальный идентификатор (`id`, `qid`) и некоторые поля сбрасываются или устанавливаются в начальные значения (например, версия, счетчики прохождений, даты создания/обновления). + +### 21.2 Базовые требования +- Требуется JWT токен авторизации (bearerAuth). +- Метод запроса: POST. +- Тело запроса должно соответствовать схеме `#/components/schemas/CopyQuizRequest` и содержать обязательное поле `id` (ID оригинального квиза). +- В случае успеха (200 OK) возвращает полный объект нового, скопированного квиза согласно схеме `#/components/schemas/Quiz`. +- Поддерживает HTTP коды ответа: 200 (OK), 400 (Bad Request), 401 (Unauthorized), 424 (Failed Dependency), 500 (Internal Server Error). + +### 21.3 Тестовые сценарии + +#### 21.3.1 Успешное копирование квиза +**Предусловия:** +- Пользователь авторизован. +- JWT токен валиден. +- Существует квиз с `id`, указанным в запросе (оригинальный квиз). + +**Входные данные:** +- Метод: POST +- URL: /quiz/copy +- Headers: + - Authorization: Bearer {valid_jwt_token} + - Content-Type: application/json +- Тело запроса (согласно `#/components/schemas/CopyQuizRequest`): + ```json + { + "id": 101 // integer, uint64 - ID существующего квиза для копирования + } + ``` + +**Ожидаемый результат:** +- HTTP Status: 200 OK +- Content-Type: application/json +- Тело ответа (новый скопированный квиз, согласно `#/components/schemas/Quiz`. Пример): + ```json + { + "id": 202, // integer, uint64 - НОВЫЙ ID для скопированного квиза + "qid": "new-uuid-for-quiz-202", // string - НОВЫЙ UUID + "accountid": "user-account-id", // string - ID аккаунта создателя (копируется) + "deleted": false, // boolean - всегда false для новой копии + "archived": false, // boolean - всегда false для новой копии + "fingerprinting": true, // boolean - копируется из оригинала + "repeatable": true, // boolean - копируется из оригинала + "note_prevented": false, // boolean - копируется из оригинала + "mail_notifications": true, // boolean - копируется из оригинала + "unique_answers": false, // boolean - копируется из оригинала + "name": "Копия: Оригинальное название квиза", // string - name оригинала, возможно с префиксом "Копия: " + "description": "Описание оригинального квиза.", // string - копируется из оригинала + "config": "{}", // string - копируется из оригинала + "status": "draft", // string, enum - статус новой копии (вероятно, 'draft' по умолчанию) + "limit": 50, // integer, uint64 - копируется из оригинала + "due_to": 1675000000, // integer, uint64 - копируется из оригинала + "time_of_passing": 1800, // integer, uint64 - копируется из оригинала + "pausable": true, // boolean - копируется из оригинала + "version": 1, // integer - версия сбрасывается до 1 для новой копии + "version_comment": null, // string - комментарий к версии сбрасывается + "parent_ids": [], // array of integers - parent_ids сбрасываются или копируются (уточнить логику, скорее всего сбрасываются) + "created_at": "YYYY-MM-DDTHH:mm:ssZ", // string, date-time - НОВАЯ дата создания + "updated_at": "YYYY-MM-DDTHH:mm:ssZ", // string, date-time - НОВАЯ дата обновления (равна created_at) + "questions_count": 0, // integer, uint64 - счетчик вопросов (копируется от оригинала или сбрасывается - уточнить, скорее всего копируется, но вопросы сами не копируются этой операцией) + "session_count": 0, // integer, uint64 - сбрасывается до 0 + "passed_count": 0, // integer, uint64 - сбрасывается до 0 + "average_time": 0, // integer, uint64 - сбрасывается до 0 + "super": false, // boolean - копируется из оригинала + "group_id": null // integer, uint64 - копируется из оригинала (если был) + } + ``` + +**Проверки:** +1. В базе данных создан новый квиз. +2. Возвращенный объект квиза имеет новый, уникальный `id` и `qid`. +3. Поля `accountid`, `fingerprinting`, `repeatable`, `note_prevented`, `mail_notifications`, `unique_answers`, `name` (с возможным префиксом), `description`, `config`, `limit`, `due_to`, `time_of_passing`, `pausable`, `super`, `group_id`, `questions_count` (значение копируется, но не сами вопросы) скопированы из оригинального квиза. +4. Поле `status` у новой копии установлено в ожидаемое значение (например, `draft`). +5. Поля `deleted` и `archived` у новой копии установлены в `false`. +6. Поле `version` у новой копии установлено в `1`, а `version_comment` - `null` (или пустое). +7. Поля `session_count`, `passed_count`, `average_time` у новой копии сброшены в `0`. +8. Поля `created_at` и `updated_at` у новой копии являются свежими временными метками. +9. Оригинальный квиз (с `id` из запроса) остался без изменений в базе данных. +10. Поле `parent_ids` для копии либо пустое, либо содержит скопированные значения (уточнить ожидаемое поведение). + +#### 21.3.2 Проверка авторизации +**Сценарий 2.1: Отсутствие токена** +- Headers: без Authorization. +- Ожидаемый результат: 401 Unauthorized. +- Проверка: Тело ответа согласно `#/components/responses/UnauthorizedError`. + +**Сценарий 2.2: Невалидный токен** +- Headers: Authorization: Bearer invalid_jwt_token. +- Ожидаемый результат: 401 Unauthorized. + +**Сценарий 2.3: Истекший токен** +- Headers: Authorization: Bearer expired_jwt_token. +- Ожидаемый результат: 401 Unauthorized. + +#### 21.3.3 Проверка валидации данных запроса (Ответ 400 Bad Request) + +**Сценарий 3.1: Отсутствие обязательного поля `id`** +- Входные данные: Тело запроса - пустой JSON `{}`. +- Ожидаемый результат: 400 Bad Request. +- Проверка: Сообщение об ошибке указывает на отсутствие обязательного поля `id`. + +**Сценарий 3.2: Некорректный тип данных для `id`** +- Входные данные: `id` передано как строка. + ```json + { "id": "not_an_integer" } + ``` +- Ожидаемый результат: 400 Bad Request. +- Проверка: Сообщение об ошибке указывает на неверный тип данных для `id`. + +**Сценарий 3.3: Копирование несуществующего квиза** +- Входные данные: `id` = 99999 (несуществующий оригинальный квиз). + ```json + { "id": 99999 } + ``` +- Ожидаемый результат: 400 Bad Request (или 404, если бы он был заявлен и API так отвечал бы). +- Проверка: Сообщение об ошибке указывает, что оригинальный квиз с таким `id` не найден. + +#### 21.3.4 Проверка специфических кодов ответа +**Сценарий 4.1: Условия для 424 Failed Dependency** +- Предусловия: (Определить условия на основе спецификации `StatusFailedDependencyError`. Например, сбой при записи в базу данных нового квиза из-за внешних проблем, не связанных с валидацией входных данных). +- Входные данные: Корректный запрос на копирование. +- Ожидаемый результат: 424 Failed Dependency. +- Проверка: Тело ответа согласно `#/components/responses/StatusFailedDependencyError`. + +#### 21.3.5 Обработка ошибок (Общие) +**Сценарий 5.1: Внутренняя ошибка сервера (500 Internal Server Error)** +- Предусловия: Имитация неожиданного сбоя на сервере в процессе копирования. +- Ожидаемый результат: 500 Internal Server Error. +- Проверка: Тело ответа согласно `#/components/responses/InternalServerError`. + +### 21.4 Особые моменты для тестирования +1. **Генерация новых идентификаторов**: Убедиться, что у скопированного квиза всегда генерируются новые, уникальные `id` и `qid`. +2. **Копирование атрибутов**: Детально проверить, какие именно поля копируются из оригинала, какие устанавливаются по умолчанию (`deleted`, `archived`, `status`), а какие сбрасываются (`version`, `version_comment`, счетчики статистики `session_count`, `passed_count`, `average_time`). +3. **Имя копии**: Уточнить, добавляется ли префикс (например, "Копия: ") к полю `name` скопированного квиза, или имя копируется без изменений. +4. **Временные метки**: `created_at` и `updated_at` для копии должны быть новыми. +5. **Иммутабельность оригинала**: Оригинальный квиз не должен быть затронут операцией копирования. +6. **Связанные сущности (вопросы)**: Этот эндпоинт, скорее всего, копирует только сам "контейнер" квиза. Убедиться, что вопросы из оригинального квиза не копируются автоматически вместе с квизом этой операцией (их нужно будет добавлять/копировать отдельно). Поле `questions_count` в копии должно отражать это (если `questions_count` оригинала копируется, это может быть ожидаемым поведением, но сами вопросы не должны быть связаны с новой копией без отдельных действий). +7. **Статус копии**: Убедиться, что новый квиз создается в предопределенном статусе (например, `draft`), независимо от статуса оригинала. +8. **Поле `parent_ids`**: Уточнить, копируется ли это поле, или оно всегда сбрасывается для копии. + +### 21.5 Критерии приемки +1. Все тестовые сценарии из раздела 21.3 пройдены успешно. +2. Новый квиз корректно создается в базе данных как копия оригинала, с соответствующими изменениями (новый ID, qid, статус, сброшенные счетчики, свежие timestamp'ы). +3. Оригинальный квиз остается неизменным. +4. Обработка всех кодов ошибок (200, 400, 401, 424, 500) соответствует спецификации API. +5. Логирование операций по копированию квизов информативно. + +## 22. POST /quiz/history + +### 22.1 Описание +Получение истории изменений для указанного квиза. Каждая запись в истории представляет собой состояние (версию) квиза на определенный момент времени. Эндпоинт возвращает массив версий квиза. + +### 22.2 Базовые требования +- Требуется JWT токен авторизации (bearerAuth). +- Метод запроса: POST. +- Тело запроса должно соответствовать схеме `#/components/schemas/GetQuizHistoryRequest` и содержать обязательное поле `id` квиза, а также опциональные поля для пагинации `l` (limit) и `p` (page). +- В случае успеха (200 OK) возвращает массив объектов `Quiz`, где каждый элемент - это одна версия из истории квиза. +- Поддерживает HTTP коды ответа: 200 (OK), 400 (Bad Request), 401 (Unauthorized), 424 (Failed Dependency), 500 (Internal Server Error). + +### 22.3 Тестовые сценарии + +#### 22.3.1 Успешное получение истории квиза (с пагинацией) +**Предусловия:** +- Пользователь авторизован. +- JWT токен валиден. +- Квиз с указанным `id` существует и имеет несколько записей в истории (несколько версий). + +**Входные данные:** +- Метод: POST +- URL: /quiz/history +- Headers: + - Authorization: Bearer {valid_jwt_token} + - Content-Type: application/json +- Тело запроса (согласно `#/components/schemas/GetQuizHistoryRequest`): + ```json + { + "id": 101, // integer, uint64 - ID квиза + "l": 5, // integer, uint64 - Limit (количество записей на странице) + "p": 1 // integer, uint64 - Page (номер страницы, например, 1-based) + } + ``` + +**Ожидаемый результат:** +- HTTP Status: 200 OK +- Content-Type: application/json +- Тело ответа (массив объектов `Quiz`): + ```json + [ + { + "id": 101, // integer, uint64 - ID оригинального квиза + "qid": "uuid-quiz-101", + "accountid": "user-account-id", + "deleted": false, + "archived": false, + "fingerprinting": true, + "repeatable": false, // Значение из Версии 2 + "note_prevented": false, + "mail_notifications": true, + "unique_answers": false, + "name": "Обновленное название квиза (Версия 2)", + "description": "Новое описание для квиза (Версия 2).", + "config": "{\"rules\":{\"v2\":true}}", + "status": "start", // Статус из Версии 2 + "limit": 150, + "due_to": 1680000000, + "time_of_passing": 3600, + "pausable": false, + "version": 2, // integer - номер версии + "version_comment": "Вторая редакция, изменен статус и лимиты", + "parent_ids": [], + "created_at": "2023-01-15T10:00:00Z", // Время создания оригинального квиза + "updated_at": "2023-01-20T14:30:00Z", // Время фиксации этой версии + "questions_count": 12, + "session_count": 25, // Статистика может быть специфична для версии или общей (уточнить) + "passed_count": 10, + "average_time": 1500, + "super": false, + "group_id": null + }, + { + "id": 101, // ID оригинального квиза + "qid": "uuid-quiz-101", + "accountid": "user-account-id", + // ... остальные поля для Версии 1 ... + "name": "Начальное название квиза (Версия 1)", + "status": "draft", // Статус из Версии 1 + "version": 1, + "version_comment": "Первая версия", + "updated_at": "2023-01-15T10:00:00Z" // Время фиксации этой версии + } + // ... другие версии, если l > 2 ... + ] + ``` + +**Проверки:** +1. Ответ является JSON массивом. +2. Каждый элемент массива является полным объектом `Quiz`, представляющим состояние квиза на момент этой версии. +3. Количество элементов в массиве не превышает значение `l` (limit) из запроса. +4. Если `p` (page) и `l` (limit) используются, пагинация работает корректно, возвращая соответствующий срез истории. +5. Поле `version` в каждой записи истории корректно отражает номер версии (или другое поле, идентифицирующее версию, например, `updated_at` этой записи истории). +6. Записи истории отсортированы в ожидаемом порядке (например, по убыванию `version` или `updated_at` этой записи истории). +7. Если у квиза нет истории (например, он только что создан и не изменялся), массив содержит одну запись, представляющую текущее состояние квиза с `version: 1`. +8. Если запрос сделан с `l=0` или без `l` и `p`, проверить поведение по умолчанию (например, возврат всех версий или первой страницы с лимитом по умолчанию). +9. Поле `id` во всех объектах истории одинаково и соответствует `id` запрошенного квиза. + +#### 22.3.2 Проверка авторизации +**Сценарий 2.1: Отсутствие токена** +- Headers: без Authorization. +- Ожидаемый результат: 401 Unauthorized. +- Проверка: Тело ответа согласно `#/components/responses/UnauthorizedError`. + +**Сценарий 2.2: Невалидный токен** +- Headers: Authorization: Bearer invalid_jwt_token. +- Ожидаемый результат: 401 Unauthorized. + +**Сценарий 2.3: Истекший токен** +- Headers: Authorization: Bearer expired_jwt_token. +- Ожидаемый результат: 401 Unauthorized. + +#### 22.3.3 Проверка валидации данных запроса (Ответ 400 Bad Request) + +**Сценарий 3.1: Отсутствие обязательного поля `id`** +- Входные данные: Тело запроса без поля `id`. + ```json + { "l": 10, "p": 1 } + ``` +- Ожидаемый результат: 400 Bad Request. +- Проверка: Сообщение об ошибке указывает на отсутствие обязательного поля `id`. + +**Сценарий 3.2: Некорректный тип данных для `id`, `l`, или `p`** +- Входные данные (примеры): + - `id` как строка: `{ "id": "not_an_integer", "l": 10, "p": 1 }` + - `l` как строка: `{ "id": 101, "l": "ten", "p": 1 }` + - `p` как строка: `{ "id": 101, "l": 10, "p": "one" }` + - `l` или `p` отрицательные (если не разрешено). +- Ожидаемый результат: 400 Bad Request. +- Проверка: Сообщение об ошибке указывает на неверный тип данных или недопустимое значение для соответствующего поля. + +**Сценарий 3.3: Запрос истории для несуществующего квиза** +- Входные данные: `id` = 99999 (несуществующий квиз). + ```json + { "id": 99999, "l": 10, "p": 1 } + ``` +- Ожидаемый результат: 200 OK и пустой массив `[]` в теле ответа (или 404, если бы он был заявлен, но OpenAPI указывает на 200 для успешного, даже если данных нет, или 400 для ошибки запроса). +- Проверка: Если 200 OK, тело ответа - пустой массив. Если 400, сообщение указывает, что квиз не найден. + +#### 22.3.4 Проверка специфических кодов ответа +**Сценарий 4.1: Условия для 424 Failed Dependency** +- Предусловия: (Определить условия на основе спецификации `StatusFailedDependencyError`. Например, сбой при чтении из базы данных истории). +- Входные данные: Корректный запрос на получение истории. +- Ожидаемый результат: 424 Failed Dependency. +- Проверка: Тело ответа согласно `#/components/responses/StatusFailedDependencyError`. + +#### 22.3.5 Обработка ошибок (Общие) +**Сценарий 5.1: Внутренняя ошибка сервера (500 Internal Server Error)** +- Предусловия: Имитация неожиданного сбоя на сервере. +- Ожидаемый результат: 500 Internal Server Error. +- Проверка: Тело ответа согласно `#/components/responses/InternalServerError`. + +### 22.4 Особые моменты для тестирования +1. **Порядок версий**: Убедиться, что версии в истории отсортированы последовательно и предсказуемо (например, по убыванию номера `version` или по убыванию `updated_at` записи истории). +2. **Полнота данных в версиях**: Каждая версия квиза в массиве должна содержать все поля из схемы `Quiz` и отражать состояние квиза на момент создания этой версии. +3. **Пагинация (`l` и `p`)**: Тщательно проверить работу пагинации: корректность количества возвращаемых элементов, переход по страницам, поведение на первой и последней странице, поведение при запросе страницы за пределами существующей истории. +4. **История для нового/неизмененного квиза**: Если квиз только создан и не редактировался, эндпоинт должен вернуть массив с одной записью (текущее состояние, `version: 1`). +5. **Глубина/ограничения истории**: Если система накладывает ограничения на количество хранимых версий или глубину истории, это необходимо проверить. +6. **Согласованность данных**: Поля `id`, `qid`, `accountid` в объектах истории должны соответствовать оригинальному квизу. Поле `version` должно быть уникальным для каждой записи истории одного квиза и увеличиваться (или изменяться предсказуемо). +7. **`created_at` vs `updated_at` в истории**: Убедиться, что `created_at` в записях истории остается временем создания самого квиза, а `updated_at` отражает время сохранения конкретной версии. + +### 22.5 Критерии приемки +1. Все тестовые сценарии из раздела 22.3 пройдены успешно. +2. История квиза возвращается корректно, со всеми версиями и точными данными для каждой версии. +3. Параметры пагинации `l` и `p` работают правильно. +4. Обработка всех кодов ошибок (200, 400, 401, 424, 500) соответствует спецификации API. +5. Данные в ответе структурированы правильно (массив объектов `Quiz`). +6. Логирование запросов к истории квизов информативно. \ No newline at end of file