Compare commits
No commits in common. "74cdc196c64f34b54d7bf72429c975b53d3650f5" and "dbbd366953f8ef250a8db478d5e9046b283de4ac" have entirely different histories.
74cdc196c6
...
dbbd366953
|
|
@ -15,14 +15,7 @@
|
||||||
"Bash(.venv/Scripts/python.exe -m pytest tests/ -v --tb=line)",
|
"Bash(.venv/Scripts/python.exe -m pytest tests/ -v --tb=line)",
|
||||||
"Bash(.venv/Scripts/python.exe -m pytest:*)",
|
"Bash(.venv/Scripts/python.exe -m pytest:*)",
|
||||||
"Bash(.venvScriptspython.exe -m pytest tests/unit/ -v --cov=app --cov-report=term-missing)",
|
"Bash(.venvScriptspython.exe -m pytest tests/unit/ -v --cov=app --cov-report=term-missing)",
|
||||||
"Bash(.\\.venv\\Scripts\\python.exe -m pytest:*)",
|
"Bash(.\\.venv\\Scripts\\python.exe -m pytest:*)"
|
||||||
"Bash(.\\\\.venv\\\\Scripts\\\\python.exe -m pytest:*)",
|
|
||||||
"Bash(.venvScriptspython.exe -m pytest tests/unit/test_query.py::TestBenchQueryEndpoint::test_bench_query_success -v)",
|
|
||||||
"Bash(.venvScriptspython.exe -m pytest tests/unit/ -v --tb=short)",
|
|
||||||
"Bash(.\\\\\\\\.venv\\\\\\\\Scripts\\\\\\\\python.exe -m pytest:*)",
|
|
||||||
"Bash(..venvScriptsactivate)",
|
|
||||||
"Bash(pytest:*)",
|
|
||||||
"Bash(source:*)"
|
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"ask": []
|
"ask": []
|
||||||
|
|
|
||||||
10
CLAUDE.md
10
CLAUDE.md
|
|
@ -1,6 +1,6 @@
|
||||||
# Developer Guide
|
# CLAUDE.md
|
||||||
|
|
||||||
This file provides detailed technical documentation for developers working with this codebase.
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
## Project Overview
|
## Project Overview
|
||||||
|
|
||||||
|
|
@ -293,6 +293,6 @@ See [TESTING.md](TESTING.md) for comprehensive testing guide including:
|
||||||
See [PROJECT_STATUS.md](PROJECT_STATUS.md) for detailed implementation status and TODOs. Key points:
|
See [PROJECT_STATUS.md](PROJECT_STATUS.md) for detailed implementation status and TODOs. Key points:
|
||||||
- Core infrastructure is complete (auth, DB API client, RAG service)
|
- Core infrastructure is complete (auth, DB API client, RAG service)
|
||||||
- All main API endpoints are implemented
|
- All main API endpoints are implemented
|
||||||
- TgBackendInterface is fully implemented
|
- TgBackendInterface is fully implemented (not a stub)
|
||||||
- 99% test coverage (unit + integration + E2E tests)
|
- Frontend integration pending (static/ directory is empty)
|
||||||
- Frontend integration complete
|
- No tests yet (tests/ directory is empty)
|
||||||
|
|
|
||||||
|
|
@ -1,710 +0,0 @@
|
||||||
```markdown
|
|
||||||
# SBS Bench RAG API Contract
|
|
||||||
|
|
||||||
API для управления пользователями, настройками окружений и сессиями анализа в системе Brief Bench.
|
|
||||||
|
|
||||||
## Содержание
|
|
||||||
|
|
||||||
- [Общая информация](#общая-информация)
|
|
||||||
- [Аутентификация и пользователи](#аутентификация-и-пользователи)
|
|
||||||
- [Настройки пользователя](#настройки-пользователя)
|
|
||||||
- [Сессии анализа](#сессии-анализа)
|
|
||||||
- [Форматы данных](#форматы-данных)
|
|
||||||
- [Обработка ошибок](#обработка-ошибок)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Общая информация
|
|
||||||
|
|
||||||
### Base URL
|
|
||||||
|
|
||||||
```
|
|
||||||
/api/v1
|
|
||||||
```
|
|
||||||
|
|
||||||
### Форматы данных
|
|
||||||
|
|
||||||
- **Content-Type**: `application/json; charset=utf-8`
|
|
||||||
- **Даты**: ISO 8601 с таймзоной UTC (`YYYY-MM-DDTHH:MM:SSZ`)
|
|
||||||
- **UUID**: строка в формате `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`
|
|
||||||
|
|
||||||
### Окружения
|
|
||||||
|
|
||||||
API поддерживает три окружения (enum `environment`):
|
|
||||||
- `ift` — IFT окружение
|
|
||||||
- `psi` — PSI окружение
|
|
||||||
- `prod` — Production окружение
|
|
||||||
|
|
||||||
### Режимы API
|
|
||||||
|
|
||||||
Два режима работы (enum `api_mode`):
|
|
||||||
- `bench` — режим тестирования
|
|
||||||
- `backend` — backend режим
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Аутентификация и пользователи
|
|
||||||
|
|
||||||
### POST /users/login
|
|
||||||
|
|
||||||
Авторизация пользователя и запись информации о логине.
|
|
||||||
|
|
||||||
#### Request
|
|
||||||
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"login": "12345678",
|
|
||||||
"client_ip": "MTkyLjE2OC4xLjEwMA=="
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Параметры:**
|
|
||||||
- `login` (string, required): 8-значный логин (строка из цифр)
|
|
||||||
- `client_ip` (string, required): IP-адрес в кодировке base64
|
|
||||||
|
|
||||||
#### Response 200
|
|
||||||
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"user_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
||||||
"login": "12345678",
|
|
||||||
"last_login_at": "2025-12-24T12:00:00Z",
|
|
||||||
"created_at": "2025-12-01T10:00:00Z"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Errors
|
|
||||||
|
|
||||||
- **400 Bad Request**: Неверный формат логина или client_ip
|
|
||||||
- **500 Internal Server Error**: Ошибка сервера
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Настройки пользователя
|
|
||||||
|
|
||||||
### GET /users/{user_id}/settings
|
|
||||||
|
|
||||||
Получить настройки пользователя для всех окружений.
|
|
||||||
|
|
||||||
#### Path Parameters
|
|
||||||
|
|
||||||
- `user_id` (UUID, required): UUID пользователя
|
|
||||||
|
|
||||||
#### Response 200
|
|
||||||
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"user_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
||||||
"settings": {
|
|
||||||
"ift": {
|
|
||||||
"apiMode": "bench",
|
|
||||||
"bearerToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
|
||||||
"systemPlatform": "platform-ift",
|
|
||||||
"systemPlatformUser": "user-123",
|
|
||||||
"platformUserId": "p-user-456",
|
|
||||||
"platformId": "platform-789",
|
|
||||||
"withClassify": false,
|
|
||||||
"resetSessionMode": true
|
|
||||||
},
|
|
||||||
"psi": {
|
|
||||||
"apiMode": "bench",
|
|
||||||
"bearerToken": null,
|
|
||||||
"systemPlatform": null,
|
|
||||||
"systemPlatformUser": null,
|
|
||||||
"platformUserId": null,
|
|
||||||
"platformId": null,
|
|
||||||
"withClassify": false,
|
|
||||||
"resetSessionMode": true
|
|
||||||
},
|
|
||||||
"prod": {
|
|
||||||
"apiMode": "backend",
|
|
||||||
"bearerToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
|
||||||
"systemPlatform": "platform-prod",
|
|
||||||
"systemPlatformUser": "user-prod",
|
|
||||||
"platformUserId": "p-user-prod",
|
|
||||||
"platformId": "platform-prod-id",
|
|
||||||
"withClassify": true,
|
|
||||||
"resetSessionMode": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"updated_at": "2025-12-24T12:30:00Z"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Errors
|
|
||||||
|
|
||||||
- **404 Not Found**: Пользователь не найден
|
|
||||||
- **500 Internal Server Error**: Ошибка сервера
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### PATCH /users/{user_id}/settings
|
|
||||||
|
|
||||||
Частично обновить настройки пользователя. Обновляются только переданные поля.
|
|
||||||
|
|
||||||
#### Path Parameters
|
|
||||||
|
|
||||||
- `user_id` (UUID, required): UUID пользователя
|
|
||||||
|
|
||||||
#### Request
|
|
||||||
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"settings": {
|
|
||||||
"ift": {
|
|
||||||
"bearerToken": "new-token-value",
|
|
||||||
"withClassify": true
|
|
||||||
},
|
|
||||||
"prod": {
|
|
||||||
"apiMode": "bench"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Примечания:**
|
|
||||||
- Передавайте только те окружения и поля, которые нужно изменить
|
|
||||||
- Непереданные поля остаются без изменений
|
|
||||||
- Для сброса значения в `null` явно передайте `null`
|
|
||||||
|
|
||||||
#### Response 200
|
|
||||||
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"user_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
||||||
"settings": {
|
|
||||||
"ift": {
|
|
||||||
"apiMode": "bench",
|
|
||||||
"bearerToken": "new-token-value",
|
|
||||||
"systemPlatform": "platform-ift",
|
|
||||||
"systemPlatformUser": "user-123",
|
|
||||||
"platformUserId": "p-user-456",
|
|
||||||
"platformId": "platform-789",
|
|
||||||
"withClassify": true,
|
|
||||||
"resetSessionMode": true
|
|
||||||
},
|
|
||||||
"psi": {
|
|
||||||
"apiMode": "bench",
|
|
||||||
"bearerToken": null,
|
|
||||||
"systemPlatform": null,
|
|
||||||
"systemPlatformUser": null,
|
|
||||||
"platformUserId": null,
|
|
||||||
"platformId": null,
|
|
||||||
"withClassify": false,
|
|
||||||
"resetSessionMode": true
|
|
||||||
},
|
|
||||||
"prod": {
|
|
||||||
"apiMode": "bench",
|
|
||||||
"bearerToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
|
||||||
"systemPlatform": "platform-prod",
|
|
||||||
"systemPlatformUser": "user-prod",
|
|
||||||
"platformUserId": "p-user-prod",
|
|
||||||
"platformId": "platform-prod-id",
|
|
||||||
"withClassify": true,
|
|
||||||
"resetSessionMode": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"updated_at": "2025-12-24T13:00:00Z"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Errors
|
|
||||||
|
|
||||||
- **400 Bad Request**: Неверный формат настроек
|
|
||||||
- **404 Not Found**: Пользователь не найден
|
|
||||||
- **500 Internal Server Error**: Ошибка сервера
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Сессии анализа
|
|
||||||
|
|
||||||
### POST /users/{user_id}/sessions
|
|
||||||
|
|
||||||
Создать новую сессию анализа.
|
|
||||||
|
|
||||||
#### Path Parameters
|
|
||||||
|
|
||||||
- `user_id` (UUID, required): UUID пользователя
|
|
||||||
|
|
||||||
#### Request
|
|
||||||
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"environment": "ift",
|
|
||||||
"api_mode": "bench",
|
|
||||||
"request": [
|
|
||||||
{
|
|
||||||
"body": "Как получить кредит на недвижимость?",
|
|
||||||
"with_docs": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"body": "Какие документы нужны?",
|
|
||||||
"with_docs": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"response": {
|
|
||||||
"answers": [
|
|
||||||
{
|
|
||||||
"question": "Как получить кредит на недвижимость?",
|
|
||||||
"answer": "Для получения кредита...",
|
|
||||||
"sources": ["doc1.pdf", "doc2.pdf"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"processing_time_ms": 1250
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"annotations": {
|
|
||||||
"0": {
|
|
||||||
"overall": {
|
|
||||||
"rating": "correct",
|
|
||||||
"comment": "Ответ корректный и полный"
|
|
||||||
},
|
|
||||||
"body_research": {
|
|
||||||
"issues": [],
|
|
||||||
"comment": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Параметры:**
|
|
||||||
- `environment` (string, required): Окружение (`ift`, `psi`, `prod`)
|
|
||||||
- `api_mode` (string, required): Режим API (`bench`, `backend`)
|
|
||||||
- `request` (array, required): Массив запросов
|
|
||||||
- `body` (string, required): Текст вопроса
|
|
||||||
- `with_docs` (boolean, optional): Использовать документы (default: `true`)
|
|
||||||
- `response` (object, required): Ответ системы (произвольная структура)
|
|
||||||
- `annotations` (object, optional): Аннотации по индексам вопросов
|
|
||||||
- Ключи должны быть числовыми строками (`"0"`, `"1"`, ...)
|
|
||||||
|
|
||||||
#### Response 201
|
|
||||||
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"session_id": "660e8400-e29b-41d4-a716-446655440000",
|
|
||||||
"user_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
||||||
"environment": "ift",
|
|
||||||
"api_mode": "bench",
|
|
||||||
"request": [
|
|
||||||
{
|
|
||||||
"body": "Как получить кредит на недвижимость?",
|
|
||||||
"with_docs": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"body": "Какие документы нужны?",
|
|
||||||
"with_docs": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"response": {
|
|
||||||
"answers": [
|
|
||||||
{
|
|
||||||
"question": "Как получить кредит на недвижимость?",
|
|
||||||
"answer": "Для получения кредита...",
|
|
||||||
"sources": ["doc1.pdf", "doc2.pdf"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"processing_time_ms": 1250
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"annotations": {
|
|
||||||
"0": {
|
|
||||||
"overall": {
|
|
||||||
"rating": "correct",
|
|
||||||
"comment": "Ответ корректный и полный"
|
|
||||||
},
|
|
||||||
"body_research": {
|
|
||||||
"issues": [],
|
|
||||||
"comment": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"created_at": "2025-12-24T14:00:00Z",
|
|
||||||
"updated_at": "2025-12-24T14:00:00Z"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Errors
|
|
||||||
|
|
||||||
- **400 Bad Request**: Неверный формат данных
|
|
||||||
- **404 Not Found**: Пользователь не найден
|
|
||||||
- **500 Internal Server Error**: Ошибка сервера
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### GET /users/{user_id}/sessions
|
|
||||||
|
|
||||||
Получить список сессий пользователя с пагинацией.
|
|
||||||
|
|
||||||
#### Path Parameters
|
|
||||||
|
|
||||||
- `user_id` (UUID, required): UUID пользователя
|
|
||||||
|
|
||||||
#### Query Parameters
|
|
||||||
|
|
||||||
- `environment` (string, optional): Фильтр по окружению (`ift`, `psi`, `prod`)
|
|
||||||
- `limit` (integer, optional): Лимит результатов (1-200, default: 50)
|
|
||||||
- `offset` (integer, optional): Смещение для пагинации (default: 0)
|
|
||||||
|
|
||||||
#### Response 200
|
|
||||||
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"sessions": [
|
|
||||||
{
|
|
||||||
"session_id": "660e8400-e29b-41d4-a716-446655440000",
|
|
||||||
"environment": "ift",
|
|
||||||
"created_at": "2025-12-24T14:00:00Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"session_id": "770e8400-e29b-41d4-a716-446655440000",
|
|
||||||
"environment": "ift",
|
|
||||||
"created_at": "2025-12-23T10:30:00Z"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"total": 123
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Сортировка:** По дате создания (от новых к старым)
|
|
||||||
|
|
||||||
#### Errors
|
|
||||||
|
|
||||||
- **404 Not Found**: Пользователь не найден
|
|
||||||
- **500 Internal Server Error**: Ошибка сервера
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### GET /users/{user_id}/sessions/{session_id}
|
|
||||||
|
|
||||||
Получить детальную информацию о сессии.
|
|
||||||
|
|
||||||
#### Path Parameters
|
|
||||||
|
|
||||||
- `user_id` (UUID, required): UUID пользователя
|
|
||||||
- `session_id` (UUID, required): UUID сессии
|
|
||||||
|
|
||||||
#### Response 200
|
|
||||||
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"session_id": "660e8400-e29b-41d4-a716-446655440000",
|
|
||||||
"user_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
||||||
"environment": "ift",
|
|
||||||
"api_mode": "bench",
|
|
||||||
"request": [
|
|
||||||
{
|
|
||||||
"body": "Как получить кредит на недвижимость?",
|
|
||||||
"with_docs": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"response": {
|
|
||||||
"answers": [
|
|
||||||
{
|
|
||||||
"question": "Как получить кредит на недвижимость?",
|
|
||||||
"answer": "Для получения кредита...",
|
|
||||||
"sources": ["doc1.pdf"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"annotations": {
|
|
||||||
"0": {
|
|
||||||
"overall": {
|
|
||||||
"rating": "correct",
|
|
||||||
"comment": "Ответ корректный"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"created_at": "2025-12-24T14:00:00Z",
|
|
||||||
"updated_at": "2025-12-24T14:00:00Z"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Errors
|
|
||||||
|
|
||||||
- **404 Not Found**: Сессия или пользователь не найдены
|
|
||||||
- **500 Internal Server Error**: Ошибка сервера
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### PATCH /users/{user_id}/sessions/{session_id}
|
|
||||||
|
|
||||||
Обновить аннотации сессии (например, после ревью).
|
|
||||||
|
|
||||||
#### Path Parameters
|
|
||||||
|
|
||||||
- `user_id` (UUID, required): UUID пользователя
|
|
||||||
- `session_id` (UUID, required): UUID сессии
|
|
||||||
|
|
||||||
#### Request
|
|
||||||
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"annotations": {
|
|
||||||
"0": {
|
|
||||||
"overall": {
|
|
||||||
"rating": "incorrect",
|
|
||||||
"comment": "Ответ неполный, не хватает информации о процентах"
|
|
||||||
},
|
|
||||||
"body_research": {
|
|
||||||
"issues": ["missing_info"],
|
|
||||||
"comment": "Не указаны процентные ставки"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"1": {
|
|
||||||
"overall": {
|
|
||||||
"rating": "correct",
|
|
||||||
"comment": "Ответ корректный"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Примечания:**
|
|
||||||
- Ключи `annotations` должны быть числовыми строками (`"0"`, `"1"`, ...)
|
|
||||||
- Полностью заменяет существующие аннотации
|
|
||||||
|
|
||||||
#### Response 200
|
|
||||||
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"session_id": "660e8400-e29b-41d4-a716-446655440000",
|
|
||||||
"user_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
||||||
"environment": "ift",
|
|
||||||
"api_mode": "bench",
|
|
||||||
"request": [...],
|
|
||||||
"response": {...},
|
|
||||||
"annotations": {
|
|
||||||
"0": {
|
|
||||||
"overall": {
|
|
||||||
"rating": "incorrect",
|
|
||||||
"comment": "Ответ неполный, не хватает информации о процентах"
|
|
||||||
},
|
|
||||||
"body_research": {
|
|
||||||
"issues": ["missing_info"],
|
|
||||||
"comment": "Не указаны процентные ставки"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"1": {
|
|
||||||
"overall": {
|
|
||||||
"rating": "correct",
|
|
||||||
"comment": "Ответ корректный"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"created_at": "2025-12-24T14:00:00Z",
|
|
||||||
"updated_at": "2025-12-24T14:30:00Z"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Errors
|
|
||||||
|
|
||||||
- **400 Bad Request**: Неверный формат данных
|
|
||||||
- **404 Not Found**: Сессия или пользователь не найдены
|
|
||||||
- **500 Internal Server Error**: Ошибка сервера
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### DELETE /users/{user_id}/sessions/{session_id}
|
|
||||||
|
|
||||||
Удалить сессию.
|
|
||||||
|
|
||||||
#### Path Parameters
|
|
||||||
|
|
||||||
- `user_id` (UUID, required): UUID пользователя
|
|
||||||
- `session_id` (UUID, required): UUID сессии
|
|
||||||
|
|
||||||
#### Response 204
|
|
||||||
|
|
||||||
Нет тела ответа при успешном удалении.
|
|
||||||
|
|
||||||
#### Errors
|
|
||||||
|
|
||||||
- **404 Not Found**: Сессия или пользователь не найдены
|
|
||||||
- **500 Internal Server Error**: Ошибка сервера
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Форматы данных
|
|
||||||
|
|
||||||
### Environment Settings Object
|
|
||||||
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"apiMode": "bench",
|
|
||||||
"bearerToken": "token-value",
|
|
||||||
"systemPlatform": "platform-name",
|
|
||||||
"systemPlatformUser": "user-name",
|
|
||||||
"platformUserId": "user-id",
|
|
||||||
"platformId": "platform-id",
|
|
||||||
"withClassify": false,
|
|
||||||
"resetSessionMode": true
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Поля:**
|
|
||||||
- `apiMode` (string): `bench` или `backend`
|
|
||||||
- `bearerToken` (string, nullable): Bearer токен для API
|
|
||||||
- `systemPlatform` (string, nullable): Название платформы
|
|
||||||
- `systemPlatformUser` (string, nullable): Пользователь платформы
|
|
||||||
- `platformUserId` (string, nullable): ID пользователя в платформе
|
|
||||||
- `platformId` (string, nullable): ID платформы
|
|
||||||
- `withClassify` (boolean): Использовать классификацию
|
|
||||||
- `resetSessionMode` (boolean): Режим сброса сессии
|
|
||||||
|
|
||||||
**Default значения:**
|
|
||||||
- `apiMode`: `"bench"`
|
|
||||||
- `withClassify`: `false`
|
|
||||||
- `resetSessionMode`: `true`
|
|
||||||
- Все остальные: `null`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Обработка ошибок
|
|
||||||
|
|
||||||
Все ошибки возвращаются в едином формате:
|
|
||||||
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"detail": "Описание ошибки",
|
|
||||||
"error_code": "OPTIONAL_ERROR_CODE"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Коды ошибок
|
|
||||||
|
|
||||||
| HTTP Code | Описание | Когда возникает |
|
|
||||||
|-----------|----------|-----------------|
|
|
||||||
| 400 | Bad Request | Неверный формат данных в запросе |
|
|
||||||
| 404 | Not Found | Пользователь или сессия не найдены |
|
|
||||||
| 422 | Unprocessable Entity | Ошибка валидации Pydantic |
|
|
||||||
| 500 | Internal Server Error | Внутренняя ошибка сервера |
|
|
||||||
|
|
||||||
### Примеры ошибок
|
|
||||||
|
|
||||||
**400 - Неверный формат логина:**
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"detail": "login: String should match pattern '^\\d{8}$'",
|
|
||||||
"error_code": null
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**404 - Пользователь не найден:**
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"detail": "User 550e8400-e29b-41d4-a716-446655440000 not found",
|
|
||||||
"error_code": null
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**422 - Ошибка валидации annotations:**
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"detail": [
|
|
||||||
{
|
|
||||||
"loc": ["body", "annotations"],
|
|
||||||
"msg": "annotations keys must be numeric strings (e.g. '0', '1')",
|
|
||||||
"type": "value_error"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Примеры использования
|
|
||||||
|
|
||||||
### Сценарий 1: Создание пользователя и сохранение настроек
|
|
||||||
|
|
||||||
```
|
|
||||||
# 1. Логин пользователя
|
|
||||||
curl -X POST /api/v1/users/login \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"login": "12345678",
|
|
||||||
"client_ip": "MTkyLjE2OC4xLjEwMA=="
|
|
||||||
}'
|
|
||||||
|
|
||||||
# Response: {"user_id": "550e8400-...", ...}
|
|
||||||
|
|
||||||
# 2. Настройка окружения IFT
|
|
||||||
curl -X PATCH /api/v1/users/550e8400-e29b-41d4-a716-446655440000/settings \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"settings": {
|
|
||||||
"ift": {
|
|
||||||
"bearerToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
|
||||||
"systemPlatform": "platform-ift",
|
|
||||||
"withClassify": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Сценарий 2: Создание и аннотация сессии
|
|
||||||
|
|
||||||
```
|
|
||||||
# 1. Создание сессии анализа
|
|
||||||
curl -X POST /api/v1/users/550e8400-e29b-41d4-a716-446655440000/sessions \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"environment": "ift",
|
|
||||||
"api_mode": "bench",
|
|
||||||
"request": [
|
|
||||||
{
|
|
||||||
"body": "Как получить кредит?",
|
|
||||||
"with_docs": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"response": {
|
|
||||||
"answers": [{
|
|
||||||
"question": "Как получить кредит?",
|
|
||||||
"answer": "Для получения кредита..."
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}'
|
|
||||||
|
|
||||||
# Response: {"session_id": "660e8400-...", ...}
|
|
||||||
|
|
||||||
# 2. Добавление аннотаций после ревью
|
|
||||||
curl -X PATCH /api/v1/users/550e8400-e29b-41d4-a716-446655440000/sessions/660e8400-e29b-41d4-a716-446655440000 \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"annotations": {
|
|
||||||
"0": {
|
|
||||||
"overall": {
|
|
||||||
"rating": "correct",
|
|
||||||
"comment": "Ответ полный и корректный"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Сценарий 3: Получение истории сессий
|
|
||||||
|
|
||||||
```
|
|
||||||
# Список всех сессий пользователя
|
|
||||||
curl -X GET /api/v1/users/550e8400-e29b-41d4-a716-446655440000/sessions?limit=10&offset=0
|
|
||||||
|
|
||||||
# Фильтр только по IFT окружению
|
|
||||||
curl -X GET /api/v1/users/550e8400-e29b-41d4-a716-446655440000/sessions?environment=ift&limit=20
|
|
||||||
|
|
||||||
# Получение конкретной сессии
|
|
||||||
curl -X GET /api/v1/users/550e8400-e29b-41d4-a716-446655440000/sessions/660e8400-e29b-41d4-a716-446655440000
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### Особенности реализации
|
|
||||||
|
|
||||||
1. **UUID генерация**: UUID создаются на уровне приложения (Python `uuid.uuid4()`)
|
|
||||||
2. **Timestamps**: Автоматическое проставление `load_dttm` при INSERT, `updated_dttm` при UPDATE через триггеры
|
|
||||||
3. **JSONB**: Все JSON-данные хранятся в PostgreSQL JSONB для эффективного поиска
|
|
||||||
4. **Индексы**: Составные индексы на `(user_id, environment, load_dttm)` для быстрой пагинации
|
|
||||||
5. **Безопасность**: Base64-кодирование client_ip для защиты от SQL-инъекций
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
## Текущее состояние
|
## Текущее состояние
|
||||||
|
|
||||||
### Готово (Backend)
|
### ✅ Готово (Backend)
|
||||||
- Структура FastAPI приложения
|
- Структура FastAPI приложения
|
||||||
- JWT авторизация (8-значный логин)
|
- JWT авторизация (8-значный логин)
|
||||||
- TgBackendInterface (полная реализация с httpx)
|
- TgBackendInterface (полная реализация с httpx)
|
||||||
|
|
@ -20,9 +20,9 @@
|
||||||
- `POST /api/v1/query/backend` - последовательные запросы
|
- `POST /api/v1/query/backend` - последовательные запросы
|
||||||
- `POST/GET/DELETE /api/v1/analysis/sessions` - сессии анализа
|
- `POST/GET/DELETE /api/v1/analysis/sessions` - сессии анализа
|
||||||
- Docker setup (Dockerfile, docker-compose.yml)
|
- Docker setup (Dockerfile, docker-compose.yml)
|
||||||
- Документация (README.md, DB_API_CONTRACT.md)
|
- Документация (README.md, DB_API_CONTRACT.md, CLAUDE.md)
|
||||||
|
|
||||||
### Требуется доделать
|
### ❌ Требуется доделать
|
||||||
- Frontend файлы (перенос из rag-bench-old-version)
|
- Frontend файлы (перенос из rag-bench-old-version)
|
||||||
- API client для frontend
|
- API client для frontend
|
||||||
- Интеграция frontend с новым API
|
- Интеграция frontend с новым API
|
||||||
|
|
@ -645,20 +645,20 @@ app.middleware("http")(log_requests)
|
||||||
|
|
||||||
## Приоритезация задач
|
## Приоритезация задач
|
||||||
|
|
||||||
### Критично (сделать в первую очередь)
|
### 🔴 Критично (сделать в первую очередь)
|
||||||
1. Перенос статических файлов из rag-bench-old-version → `static/`
|
1. Перенос статических файлов из rag-bench-old-version → `static/`
|
||||||
2. Создание `api-client.js`
|
2. Создание `api-client.js`
|
||||||
3. Добавление login screen в `index.html`
|
3. Добавление login screen в `index.html`
|
||||||
4. Переписывание вызовов API в `app.js`
|
4. Переписывание вызовов API в `app.js`
|
||||||
5. Тестирование auth flow
|
5. Тестирование auth flow
|
||||||
|
|
||||||
### Важно (сделать после критичного)
|
### 🟡 Важно (сделать после критичного)
|
||||||
6. Интеграция Settings UI
|
6. Интеграция Settings UI
|
||||||
7. Environment selector
|
7. Environment selector
|
||||||
8. Сохранение и загрузка сессий анализа
|
8. Сохранение и загрузка сессий анализа
|
||||||
9. Ручное тестирование всех сценариев
|
9. Ручное тестирование всех сценариев
|
||||||
|
|
||||||
### Желательно (если есть время)
|
### 🟢 Желательно (если есть время)
|
||||||
10. Автоматические тесты (pytest)
|
10. Автоматические тесты (pytest)
|
||||||
11. Production deployment настройка
|
11. Production deployment настройка
|
||||||
12. Logging middleware
|
12. Logging middleware
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ cp rag-bench-old-version/settings.js static/
|
||||||
|
|
||||||
## Шаг 2: Создание api-client.js
|
## Шаг 2: Создание api-client.js
|
||||||
|
|
||||||
См. полную реализацию в DEVELOPMENT_PLAN.md (раздел "Создать API client для frontend").
|
См. полную реализацию в [DEVELOPMENT_PLAN.md](DEVELOPMENT_PLAN.md#12-создать-api-client-для-frontend).
|
||||||
|
|
||||||
Создать файл `static/api-client.js` с классом `BriefBenchAPI`.
|
Создать файл `static/api-client.js` с классом `BriefBenchAPI`.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,60 +1,60 @@
|
||||||
# Production Readiness Checklist
|
# 🚀 Production Readiness Checklist
|
||||||
|
|
||||||
Полная проверка готовности Brief Bench FastAPI к развертыванию в продакшн.
|
Полная проверка готовности Brief Bench FastAPI к развертыванию в продакшн.
|
||||||
|
|
||||||
## Backend (FastAPI)
|
## ✅ Backend (FastAPI)
|
||||||
|
|
||||||
### Код и архитектура
|
### Код и архитектура
|
||||||
- [x] **Все API endpoints реализованы**
|
- [x] **Все API endpoints реализованы**
|
||||||
- Auth: `/api/v1/auth/login`
|
- ✅ Auth: `/api/v1/auth/login`
|
||||||
- Settings: GET/PUT `/api/v1/settings`
|
- ✅ Settings: GET/PUT `/api/v1/settings`
|
||||||
- Query: POST `/api/v1/query/bench`, `/api/v1/query/backend`
|
- ✅ Query: POST `/api/v1/query/bench`, `/api/v1/query/backend`
|
||||||
- Analysis: CRUD `/api/v1/analysis/sessions`
|
- ✅ Analysis: CRUD `/api/v1/analysis/sessions`
|
||||||
- Health: `/health`
|
- ✅ Health: `/health`
|
||||||
|
|
||||||
- [x] **Бизнес-логика покрыта тестами: 99%**
|
- [x] **Бизнес-логика покрыта тестами: 99%**
|
||||||
- 119 unit tests (99% coverage)
|
- ✅ 119 unit tests (99% coverage)
|
||||||
- Integration tests (DB API)
|
- ✅ Integration tests (DB API)
|
||||||
- E2E tests (полный стек)
|
- ✅ E2E tests (полный стек)
|
||||||
|
|
||||||
- [x] **Services реализованы**
|
- [x] **Services реализованы**
|
||||||
- AuthService (JWT токены)
|
- ✅ AuthService (JWT токены)
|
||||||
- RagService (RAG backends: IFT, PSI, PROD)
|
- ✅ RagService (RAG backends: IFT, PSI, PROD)
|
||||||
|
|
||||||
- [x] **Interfaces реализованы**
|
- [x] **Interfaces реализованы**
|
||||||
- TgBackendInterface (базовый HTTP клиент)
|
- ✅ TgBackendInterface (базовый HTTP клиент)
|
||||||
- DBApiClient (DB API integration)
|
- ✅ DBApiClient (DB API integration)
|
||||||
|
|
||||||
- [x] **Models валидация**
|
- [x] **Models валидация**
|
||||||
- Все Pydantic models для request/response
|
- ✅ Все Pydantic models для request/response
|
||||||
- Валидация входных данных
|
- ✅ Валидация входных данных
|
||||||
|
|
||||||
## Frontend (Static Files)
|
## ✅ Frontend (Static Files)
|
||||||
|
|
||||||
- [x] **HTML/CSS/JS файлы**
|
- [x] **HTML/CSS/JS файлы**
|
||||||
- index.html
|
- ✅ index.html
|
||||||
- styles.css (Material Design)
|
- ✅ styles.css (Material Design)
|
||||||
- app.js (основная логика)
|
- ✅ app.js (основная логика)
|
||||||
- api-client.js (API клиент)
|
- ✅ api-client.js (API клиент)
|
||||||
- settings.js (настройки)
|
- ✅ settings.js (настройки)
|
||||||
|
|
||||||
- [x] **Интеграция с backend**
|
- [x] **Интеграция с backend**
|
||||||
- API client использует `/api/v1` endpoints
|
- ✅ API client использует `/api/v1` endpoints
|
||||||
- JWT токены в localStorage
|
- ✅ JWT токены в localStorage
|
||||||
- Правильная обработка ошибок (401, 502, etc.)
|
- ✅ Правильная обработка ошибок (401, 502, etc.)
|
||||||
- StaticFiles монтированы в main.py
|
- ✅ StaticFiles монтированы в main.py
|
||||||
|
|
||||||
- [x] **UI функциональность**
|
- [x] **UI функциональность**
|
||||||
- Login screen
|
- ✅ Login screen
|
||||||
- Multi-environment tabs (IFT, PSI, PROD)
|
- ✅ Multi-environment tabs (IFT, PSI, PROD)
|
||||||
- Settings panel
|
- ✅ Settings panel
|
||||||
- Query interface
|
- ✅ Query interface
|
||||||
- Results display
|
- ✅ Results display
|
||||||
- Session management
|
- ✅ Session management
|
||||||
|
|
||||||
## Конфигурация (ТРЕБУЕТ ВНИМАНИЯ!)
|
## ⚠️ Конфигурация (ТРЕБУЕТ ВНИМАНИЯ!)
|
||||||
|
|
||||||
### КРИТИЧНО - Сделать перед деплоем:
|
### 🔴 КРИТИЧНО - Сделать перед деплоем:
|
||||||
|
|
||||||
- [ ] **1. Создать `.env` файл**
|
- [ ] **1. Создать `.env` файл**
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -121,54 +121,55 @@
|
||||||
DEBUG=false
|
DEBUG=false
|
||||||
```
|
```
|
||||||
|
|
||||||
## Docker & Deployment
|
## ✅ Docker & Deployment
|
||||||
|
|
||||||
- [x] **Dockerfile готов**
|
- [x] **Dockerfile готов**
|
||||||
- Multi-stage build
|
- ✅ Multi-stage build
|
||||||
- Копирует static/ файлы
|
- ✅ Копирует static/ файлы
|
||||||
- Expose 8000
|
- ✅ Expose 8000
|
||||||
- Uvicorn с правильными параметрами
|
- ✅ Uvicorn с правильными параметрами
|
||||||
|
|
||||||
- [x] **docker-compose.yml готов**
|
- [x] **docker-compose.yml готов**
|
||||||
- Порты пробрасываются (8000:8000)
|
- ✅ Порты пробрасываются (8000:8000)
|
||||||
- Volume для certs (read-only)
|
- ✅ Volume для certs (read-only)
|
||||||
- Volume для static файлов
|
- ✅ Volume для static файлов
|
||||||
- .env подключается
|
- ✅ .env подключается
|
||||||
- restart: unless-stopped
|
- ✅ restart: unless-stopped
|
||||||
|
|
||||||
## Безопасность
|
## ✅ Безопасность
|
||||||
|
|
||||||
- [x] **Authentication**
|
- [x] **Authentication**
|
||||||
- JWT токены (30 дней expiration)
|
- ✅ JWT токены (30 дней expiration)
|
||||||
- Bearer token authentication
|
- ✅ Bearer token authentication
|
||||||
- Middleware для проверки токенов
|
- ✅ Middleware для проверки токенов
|
||||||
|
|
||||||
- [x] **Secrets management**
|
- [x] **Secrets management**
|
||||||
- .env не в git (.gitignore)
|
- ✅ .env не в git (.gitignore)
|
||||||
- .env.integration не в git
|
- ✅ .env.integration не в git
|
||||||
- .env.e2e не в git
|
- ✅ .env.e2e не в git
|
||||||
- ВАЖНО: Сменить JWT_SECRET_KEY в продакшн!
|
- ⚠️ ВАЖНО: Сменить JWT_SECRET_KEY в продакшн!
|
||||||
|
|
||||||
- [x] **mTLS сертификаты**
|
- [x] **mTLS сертификаты**
|
||||||
- Хранятся только на сервере
|
- ✅ Хранятся только на сервере
|
||||||
- Read-only volume в Docker
|
- ✅ Read-only volume в Docker
|
||||||
- Не коммитятся в git
|
- ✅ Не коммитятся в git
|
||||||
|
|
||||||
- [ ] **HTTPS (рекомендуется)**
|
- [ ] **HTTPS (рекомендуется)**
|
||||||
- Настроить reverse proxy (nginx/traefik)
|
- Настроить reverse proxy (nginx/traefik)
|
||||||
- Let's Encrypt сертификаты
|
- Let's Encrypt сертификаты
|
||||||
- Редирект HTTP → HTTPS
|
- Редирект HTTP → HTTPS
|
||||||
|
|
||||||
## Документация
|
## ✅ Документация
|
||||||
|
|
||||||
- [x] **README.md** - основная документация
|
- [x] **README.md** - основная документация
|
||||||
|
- [x] **CLAUDE.md** - архитектура и гайд для Claude
|
||||||
- [x] **DB_API_CONTRACT.md** - контракт с DB API
|
- [x] **DB_API_CONTRACT.md** - контракт с DB API
|
||||||
- [x] **TESTING.md** - полное руководство по тестированию
|
- [x] **TESTING.md** - полное руководство по тестированию
|
||||||
- [x] **PROJECT_STATUS.md** - статус реализации
|
- [x] **PROJECT_STATUS.md** - статус реализации
|
||||||
- [x] **tests/integration/README.md** - интеграционные тесты
|
- [x] **tests/integration/README.md** - интеграционные тесты
|
||||||
- [x] **tests/e2e/README.md** - E2E тесты
|
- [x] **tests/e2e/README.md** - E2E тесты
|
||||||
|
|
||||||
## Pre-Deployment Testing
|
## 🔍 Pre-Deployment Testing
|
||||||
|
|
||||||
### Локальное тестирование
|
### Локальное тестирование
|
||||||
|
|
||||||
|
|
@ -237,7 +238,7 @@
|
||||||
docker-compose down
|
docker-compose down
|
||||||
```
|
```
|
||||||
|
|
||||||
## Deployment Steps
|
## 🚀 Deployment Steps
|
||||||
|
|
||||||
### 1. Подготовка сервера
|
### 1. Подготовка сервера
|
||||||
|
|
||||||
|
|
@ -307,7 +308,7 @@ server {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Post-Deployment Verification
|
## 📊 Post-Deployment Verification
|
||||||
|
|
||||||
После деплоя проверить:
|
После деплоя проверить:
|
||||||
|
|
||||||
|
|
@ -319,7 +320,7 @@ server {
|
||||||
- [ ] Session save/load работает
|
- [ ] Session save/load работает
|
||||||
- [ ] Логи не содержат ошибок: `docker-compose logs -f`
|
- [ ] Логи не содержат ошибок: `docker-compose logs -f`
|
||||||
|
|
||||||
## Мониторинг и обслуживание
|
## 🔧 Мониторинг и обслуживание
|
||||||
|
|
||||||
### Логи
|
### Логи
|
||||||
|
|
||||||
|
|
@ -356,19 +357,19 @@ docker-compose up -d --build
|
||||||
### Backup
|
### Backup
|
||||||
|
|
||||||
Критичные данные:
|
Критичные данные:
|
||||||
- `.env` - секреты и конфигурация
|
- ✅ `.env` - секреты и конфигурация
|
||||||
- `certs/` - mTLS сертификаты
|
- ✅ `certs/` - mTLS сертификаты
|
||||||
- Пользовательские данные хранятся в DB API (не в FastAPI)
|
- ℹ️ Пользовательские данные хранятся в DB API (не в FastAPI)
|
||||||
|
|
||||||
## Performance Considerations
|
## ⚡ Performance Considerations
|
||||||
|
|
||||||
- RAG запросы могут занимать до 30 минут (настроено)
|
- ✅ RAG запросы могут занимать до 30 минут (настроено)
|
||||||
- Async/await для всех I/O операций
|
- ✅ Async/await для всех I/O операций
|
||||||
- Connection pooling в httpx clients
|
- ✅ Connection pooling в httpx clients
|
||||||
- Рассмотреть rate limiting для production
|
- ℹ️ Рассмотреть rate limiting для production
|
||||||
- Рассмотреть caching для settings (опционально)
|
- ℹ️ Рассмотреть caching для settings (опционально)
|
||||||
|
|
||||||
## Troubleshooting
|
## 🐛 Troubleshooting
|
||||||
|
|
||||||
### Проблема: Контейнер не запускается
|
### Проблема: Контейнер не запускается
|
||||||
|
|
||||||
|
|
@ -399,30 +400,30 @@ docker-compose up -d --build
|
||||||
2. JWT_SECRET_KEY одинаковый между запусками
|
2. JWT_SECRET_KEY одинаковый между запусками
|
||||||
3. Токен не истек (30 дней по умолчанию)
|
3. Токен не истек (30 дней по умолчанию)
|
||||||
|
|
||||||
## Final Checklist Summary
|
## ✅ Final Checklist Summary
|
||||||
|
|
||||||
Перед деплоем в продакшн:
|
Перед деплоем в продакшн:
|
||||||
|
|
||||||
1. Backend код готов (99% coverage)
|
1. ✅ Backend код готов (99% coverage)
|
||||||
2. Frontend интегрирован
|
2. ✅ Frontend интегрирован
|
||||||
3. Docker конфигурация готова
|
3. ✅ Docker конфигурация готова
|
||||||
4. **`.env` создан и заполнен**
|
4. ⚠️ **`.env` создан и заполнен**
|
||||||
5. **`JWT_SECRET_KEY` сгенерирован новый**
|
5. ⚠️ **`JWT_SECRET_KEY` сгенерирован новый**
|
||||||
6. **RAG hosts настроены**
|
6. ⚠️ **RAG hosts настроены**
|
||||||
7. **DB_API_URL настроен**
|
7. ⚠️ **DB_API_URL настроен**
|
||||||
8. **mTLS сертификаты размещены** (если используются)
|
8. ⚠️ **mTLS сертификаты размещены** (если используются)
|
||||||
9. **CORS настроен** (при необходимости)
|
9. ⚠️ **CORS настроен** (при необходимости)
|
||||||
10. **DEBUG=false**
|
10. ⚠️ **DEBUG=false**
|
||||||
11. Unit тесты passed
|
11. ✅ Unit тесты passed
|
||||||
12. Integration тесты passed (опционально)
|
12. ✅ Integration тесты passed (опционально)
|
||||||
13. Локальное тестирование пройдено
|
13. ✅ Локальное тестирование пройдено
|
||||||
14. Docker build успешен
|
14. ✅ Docker build успешен
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Статус готовности: ПОЧТИ ГОТОВ**
|
**Статус готовности: 🟡 ПОЧТИ ГОТОВ**
|
||||||
|
|
||||||
**Готово:** Код, тесты, Docker, документация
|
✅ **Готово:** Код, тесты, Docker, документация
|
||||||
**Требуется:** Конфигурация окружения (.env, сертификаты, финальная настройка)
|
⚠️ **Требуется:** Конфигурация окружения (.env, сертификаты, финальная настройка)
|
||||||
|
|
||||||
После выполнения пунктов из раздела "КРИТИЧНО" → **ГОТОВ К ПРОДАКШН**
|
После выполнения пунктов из раздела "КРИТИЧНО" → **🟢 ГОТОВ К ПРОДАКШН**
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Что реализовано
|
## 📋 Что реализовано
|
||||||
|
|
||||||
### 1. Структура проекта
|
### 1. Структура проекта
|
||||||
|
|
||||||
|
|
@ -15,45 +15,45 @@ brief-bench-fastapi/
|
||||||
│ ├── api/
|
│ ├── api/
|
||||||
│ │ └── v1/
|
│ │ └── v1/
|
||||||
│ │ ├── __init__.py
|
│ │ ├── __init__.py
|
||||||
│ │ └── auth.py POST /api/v1/auth/login
|
│ │ └── auth.py ✅ POST /api/v1/auth/login
|
||||||
│ ├── models/
|
│ ├── models/
|
||||||
│ │ ├── __init__.py
|
│ │ ├── __init__.py
|
||||||
│ │ ├── auth.py LoginRequest, LoginResponse, UserResponse
|
│ │ ├── auth.py ✅ LoginRequest, LoginResponse, UserResponse
|
||||||
│ │ ├── settings.py EnvironmentSettings, UserSettings
|
│ │ ├── settings.py ✅ EnvironmentSettings, UserSettings
|
||||||
│ │ ├── analysis.py SessionCreate, SessionResponse, SessionList
|
│ │ ├── analysis.py ✅ SessionCreate, SessionResponse, SessionList
|
||||||
│ │ └── query.py BenchQueryRequest, BackendQueryRequest
|
│ │ └── query.py ✅ BenchQueryRequest, BackendQueryRequest
|
||||||
│ ├── services/
|
│ ├── services/
|
||||||
│ │ ├── __init__.py
|
│ │ ├── __init__.py
|
||||||
│ │ └── auth_service.py AuthService (login logic)
|
│ │ └── auth_service.py ✅ AuthService (login logic)
|
||||||
│ ├── interfaces/
|
│ ├── interfaces/
|
||||||
│ │ ├── __init__.py
|
│ │ ├── __init__.py
|
||||||
│ │ ├── base.py TgBackendInterface (ЗАГЛУШКА - нужна реализация)
|
│ │ ├── base.py ⚠️ TgBackendInterface (ЗАГЛУШКА - нужна реализация)
|
||||||
│ │ └── db_api_client.py DBApiClient (методы для DB API)
|
│ │ └── db_api_client.py ✅ DBApiClient (методы для DB API)
|
||||||
│ ├── middleware/
|
│ ├── middleware/
|
||||||
│ │ └── __init__.py
|
│ │ └── __init__.py
|
||||||
│ ├── utils/
|
│ ├── utils/
|
||||||
│ │ ├── __init__.py
|
│ │ ├── __init__.py
|
||||||
│ │ └── security.py JWT encode/decode
|
│ │ └── security.py ✅ JWT encode/decode
|
||||||
│ ├── __init__.py
|
│ ├── __init__.py
|
||||||
│ ├── config.py Settings из .env
|
│ ├── config.py ✅ Settings из .env
|
||||||
│ ├── dependencies.py DI: get_db_client, get_current_user
|
│ ├── dependencies.py ✅ DI: get_db_client, get_current_user
|
||||||
│ └── main.py FastAPI app с CORS
|
│ └── main.py ✅ FastAPI app с CORS
|
||||||
├── static/ Пусто (нужно скопировать из rag-bench)
|
├── static/ ❌ Пусто (нужно скопировать из rag-bench)
|
||||||
├── tests/ Полный набор тестов (unit/integration/e2e)
|
├── tests/ ✅ Полный набор тестов (unit/integration/e2e)
|
||||||
├── certs/ Не создана (для mTLS)
|
├── certs/ ❌ Не создана (для mTLS)
|
||||||
├── .env.example
|
├── .env.example ✅
|
||||||
├── .gitignore
|
├── .gitignore ✅
|
||||||
├── requirements.txt
|
├── requirements.txt ✅
|
||||||
├── Dockerfile
|
├── Dockerfile ✅
|
||||||
├── docker-compose.yml
|
├── docker-compose.yml ✅
|
||||||
├── DB_API_CONTRACT.md Полный контракт для DB API
|
├── DB_API_CONTRACT.md ✅ Полный контракт для DB API
|
||||||
├── README.md
|
├── README.md ✅
|
||||||
└── PROJECT_STATUS.md Этот файл
|
└── PROJECT_STATUS.md ✅ Этот файл
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Реализованные компоненты
|
## ✅ Реализованные компоненты
|
||||||
|
|
||||||
### 1. Configuration (app/config.py)
|
### 1. Configuration (app/config.py)
|
||||||
- Загрузка из .env через pydantic-settings
|
- Загрузка из .env через pydantic-settings
|
||||||
|
|
@ -88,7 +88,7 @@ brief-bench-fastapi/
|
||||||
|
|
||||||
### 3. Interfaces (app/interfaces/)
|
### 3. Interfaces (app/interfaces/)
|
||||||
|
|
||||||
**base.py (ЗАГЛУШКА!):**
|
**base.py (⚠️ ЗАГЛУШКА!):**
|
||||||
```python
|
```python
|
||||||
class TgBackendInterface:
|
class TgBackendInterface:
|
||||||
def __init__(self, api_prefix: str, **kwargs)
|
def __init__(self, api_prefix: str, **kwargs)
|
||||||
|
|
@ -152,7 +152,7 @@ class TgBackendInterface:
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Что НЕ реализовано (TODO)
|
## ❌ Что НЕ реализовано (TODO)
|
||||||
|
|
||||||
### 1. TgBackendInterface реализация (КРИТИЧНО!)
|
### 1. TgBackendInterface реализация (КРИТИЧНО!)
|
||||||
Файл: `app/interfaces/base.py`
|
Файл: `app/interfaces/base.py`
|
||||||
|
|
@ -259,28 +259,28 @@ class RagService:
|
||||||
- `app/middleware/logging.py` - логирование запросов
|
- `app/middleware/logging.py` - логирование запросов
|
||||||
- `app/middleware/error_handler.py` - глобальная обработка ошибок
|
- `app/middleware/error_handler.py` - глобальная обработка ошибок
|
||||||
|
|
||||||
### 9. Tests COMPLETED
|
### 9. Tests ✅ COMPLETED
|
||||||
- **Unit Tests** (119 tests, 99% coverage) - `tests/unit/`
|
- ✅ **Unit Tests** (119 tests, 99% coverage) - `tests/unit/`
|
||||||
- All services, models, utilities tested in isolation
|
- All services, models, utilities tested in isolation
|
||||||
- All external dependencies mocked
|
- All external dependencies mocked
|
||||||
- Run: `.\run_unit_tests.bat`
|
- Run: `.\run_unit_tests.bat`
|
||||||
- **Integration Tests** (DB API integration) - `tests/integration/`
|
- ✅ **Integration Tests** (DB API integration) - `tests/integration/`
|
||||||
- FastAPI endpoints with real DB API
|
- FastAPI endpoints with real DB API
|
||||||
- Requires DB API service running
|
- Requires DB API service running
|
||||||
- Run: `.\run_integration_tests.bat`
|
- Run: `.\run_integration_tests.bat`
|
||||||
- **End-to-End Tests** (Full stack) - `tests/e2e/`
|
- ✅ **End-to-End Tests** (Full stack) - `tests/e2e/`
|
||||||
- Complete workflows: auth → query → save → retrieve
|
- Complete workflows: auth → query → save → retrieve
|
||||||
- Requires all services (FastAPI + DB API + RAG backends)
|
- Requires all services (FastAPI + DB API + RAG backends)
|
||||||
- Real network calls to RAG backends
|
- Real network calls to RAG backends
|
||||||
- Run: `.\run_e2e_tests.bat`
|
- Run: `.\run_e2e_tests.bat`
|
||||||
- **Test Documentation** - `TESTING.md`
|
- ✅ **Test Documentation** - `TESTING.md`
|
||||||
- Comprehensive testing guide
|
- Comprehensive testing guide
|
||||||
- Setup instructions for each test level
|
- Setup instructions for each test level
|
||||||
- Troubleshooting and best practices
|
- Troubleshooting and best practices
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Важные детали для продолжения
|
## 🔑 Важные детали для продолжения
|
||||||
|
|
||||||
### Архитектура авторизации
|
### Архитектура авторизации
|
||||||
1. Пользователь отправляет POST /api/v1/auth/login?login=12345678
|
1. Пользователь отправляет POST /api/v1/auth/login?login=12345678
|
||||||
|
|
@ -346,7 +346,7 @@ Body: {
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## План дальнейшей работы
|
## 🚀 План дальнейшей работы
|
||||||
|
|
||||||
### Этап 1: Реализовать TgBackendInterface
|
### Этап 1: Реализовать TgBackendInterface
|
||||||
**Приоритет:** ВЫСОКИЙ
|
**Приоритет:** ВЫСОКИЙ
|
||||||
|
|
@ -398,7 +398,7 @@ app.include_router(analysis.router, prefix="/api/v1")
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Dependencies (requirements.txt)
|
## 📦 Dependencies (requirements.txt)
|
||||||
|
|
||||||
```
|
```
|
||||||
fastapi==0.104.1
|
fastapi==0.104.1
|
||||||
|
|
@ -416,7 +416,7 @@ fastapi-cors==0.0.6
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Команды для разработки
|
## 🔧 Команды для разработки
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Установить зависимости
|
# Установить зависимости
|
||||||
|
|
@ -436,7 +436,7 @@ curl http://localhost:8000/health
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Примечания
|
## 📝 Примечания
|
||||||
|
|
||||||
1. **TgBackendInterface** - это ваша реализация, которая будет использоваться во всех клиентах (DBApiClient, возможно RagClient в будущем)
|
1. **TgBackendInterface** - это ваша реализация, которая будет использоваться во всех клиентах (DBApiClient, возможно RagClient в будущем)
|
||||||
|
|
||||||
|
|
@ -454,7 +454,7 @@ curl http://localhost:8000/health
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Готово к продолжению!
|
## 🎯 Готово к продолжению!
|
||||||
|
|
||||||
Вся базовая структура создана. Следующий шаг - реализация TgBackendInterface и остальных endpoints.
|
Вся базовая структура создана. Следующий шаг - реализация TgBackendInterface и остальных endpoints.
|
||||||
|
|
||||||
|
|
|
||||||
27
README.md
27
README.md
|
|
@ -4,13 +4,13 @@ FastAPI backend для системы тестирования RAG с multi-user
|
||||||
|
|
||||||
## Возможности
|
## Возможности
|
||||||
|
|
||||||
- JWT авторизация (8-значный логин)
|
- 🔐 JWT авторизация (8-значный логин)
|
||||||
- Multi-environment: ИФТ, ПСИ, ПРОМ
|
- 🌐 Multi-environment: ИФТ, ПСИ, ПРОМ
|
||||||
- Bench mode: batch тестирование
|
- 📊 Bench mode: batch тестирование
|
||||||
- Backend mode: имитация бота (вопросы по одному)
|
- 🤖 Backend mode: имитация бота (вопросы по одному)
|
||||||
- Сохранение сессий анализа
|
- 💾 Сохранение сессий анализа
|
||||||
- mTLS для RAG backend
|
- 🔒 mTLS для RAG backend
|
||||||
- Аннотации и экспорт
|
- 📝 Аннотации и экспорт
|
||||||
|
|
||||||
## Требования
|
## Требования
|
||||||
|
|
||||||
|
|
@ -186,6 +186,7 @@ docker-compose down
|
||||||
|
|
||||||
## Документация
|
## Документация
|
||||||
|
|
||||||
|
- [CLAUDE.md](CLAUDE.md) - архитектура и гайд для разработки
|
||||||
- [TESTING.md](TESTING.md) - руководство по тестированию
|
- [TESTING.md](TESTING.md) - руководство по тестированию
|
||||||
- [PRODUCTION_CHECKLIST.md](PRODUCTION_CHECKLIST.md) - чек-лист для продакшн
|
- [PRODUCTION_CHECKLIST.md](PRODUCTION_CHECKLIST.md) - чек-лист для продакшн
|
||||||
- [DB_API_CONTRACT.md](DB_API_CONTRACT.md) - контракт с DB API
|
- [DB_API_CONTRACT.md](DB_API_CONTRACT.md) - контракт с DB API
|
||||||
|
|
@ -193,13 +194,13 @@ docker-compose down
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
**Проект готов к продакшн**
|
✅ **Проект готов к продакшн**
|
||||||
|
|
||||||
- Backend полностью реализован (все endpoints, services, interfaces)
|
- ✅ Backend полностью реализован (все endpoints, services, interfaces)
|
||||||
- Frontend интегрирован (HTML/CSS/JS)
|
- ✅ Frontend интегрирован (HTML/CSS/JS)
|
||||||
- 99% test coverage (unit + integration + E2E)
|
- ✅ 99% test coverage (unit + integration + E2E)
|
||||||
- Docker ready
|
- ✅ Docker ready
|
||||||
- Требуется: настройка `.env` и сертификатов
|
- ⚠️ Требуется: настройка `.env` и сертификатов
|
||||||
|
|
||||||
**Перед деплоем:** см. [PRODUCTION_CHECKLIST.md](PRODUCTION_CHECKLIST.md)
|
**Перед деплоем:** см. [PRODUCTION_CHECKLIST.md](PRODUCTION_CHECKLIST.md)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -543,6 +543,7 @@ Current coverage:
|
||||||
- [Integration Tests](tests/integration/README.md) - DB API integration
|
- [Integration Tests](tests/integration/README.md) - DB API integration
|
||||||
- [E2E Tests](tests/e2e/README.md) - Full stack testing
|
- [E2E Tests](tests/e2e/README.md) - Full stack testing
|
||||||
- [DB API Contract](DB_API_CONTRACT.md) - External API spec
|
- [DB API Contract](DB_API_CONTRACT.md) - External API spec
|
||||||
|
- [CLAUDE.md](CLAUDE.md) - Architecture overview
|
||||||
- [PROJECT_STATUS.md](PROJECT_STATUS.md) - Implementation status
|
- [PROJECT_STATUS.md](PROJECT_STATUS.md) - Implementation status
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ Analysis Sessions API endpoints.
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, status, Query
|
from fastapi import APIRouter, Depends, HTTPException, status, Query
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from app.models.analysis import SessionCreate, SessionResponse, SessionList, SessionUpdate
|
from app.models.analysis import SessionCreate, SessionResponse, SessionList
|
||||||
from app.interfaces.db_api_client import DBApiClient
|
from app.interfaces.db_api_client import DBApiClient
|
||||||
from app.dependencies import get_db_client, get_current_user
|
from app.dependencies import get_db_client, get_current_user
|
||||||
import httpx
|
import httpx
|
||||||
|
|
@ -143,54 +143,6 @@ async def get_session(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.patch("/sessions/{session_id}", response_model=SessionResponse)
|
|
||||||
async def update_session(
|
|
||||||
session_id: str,
|
|
||||||
update_data: SessionUpdate,
|
|
||||||
current_user: dict = Depends(get_current_user),
|
|
||||||
db_client: DBApiClient = Depends(get_db_client)
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Обновить аннотации сессии (например, после ревью).
|
|
||||||
|
|
||||||
Полностью заменяет существующие аннотации новыми.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
session_id: UUID сессии
|
|
||||||
update_data: Новые аннотации с ключами в виде числовых строк ('0', '1', ...)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
SessionResponse: Обновленная сессия
|
|
||||||
"""
|
|
||||||
user_id = current_user["user_id"]
|
|
||||||
|
|
||||||
try:
|
|
||||||
updated_session = await db_client.update_session(user_id, session_id, update_data)
|
|
||||||
return updated_session
|
|
||||||
except httpx.HTTPStatusError as e:
|
|
||||||
if e.response.status_code == 404:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
|
||||||
detail="Session not found"
|
|
||||||
)
|
|
||||||
elif e.response.status_code == 400:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
|
||||||
detail="Invalid annotations format"
|
|
||||||
)
|
|
||||||
logger.error(f"Failed to update session {session_id}: {e}")
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_502_BAD_GATEWAY,
|
|
||||||
detail="Failed to update session in DB API"
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Unexpected error updating session {session_id}: {e}")
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
||||||
detail="Internal server error"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/sessions/{session_id}", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete("/sessions/{session_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
async def delete_session(
|
async def delete_session(
|
||||||
session_id: str,
|
session_id: str,
|
||||||
|
|
|
||||||
|
|
@ -51,22 +51,20 @@ async def get_settings(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.patch("", response_model=UserSettings)
|
@router.put("", response_model=UserSettings)
|
||||||
async def update_settings(
|
async def update_settings(
|
||||||
settings_update: UserSettingsUpdate,
|
settings_update: UserSettingsUpdate,
|
||||||
current_user: dict = Depends(get_current_user),
|
current_user: dict = Depends(get_current_user),
|
||||||
db_client: DBApiClient = Depends(get_db_client)
|
db_client: DBApiClient = Depends(get_db_client)
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Частично обновить настройки пользователя.
|
Обновить настройки пользователя.
|
||||||
|
|
||||||
Обновляются только переданные поля. Непереданные поля остаются без изменений.
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
settings_update: Частичные настройки для одного или нескольких окружений
|
settings_update: Новые настройки для одного или нескольких окружений
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
UserSettings: Обновленные настройки со всеми полями
|
UserSettings: Обновленные настройки
|
||||||
"""
|
"""
|
||||||
user_id = current_user["user_id"]
|
user_id = current_user["user_id"]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -252,36 +252,6 @@ class TgBackendInterface:
|
||||||
response = await self.client.put(url, json=json_body, **kwargs)
|
response = await self.client.put(url, json=json_body, **kwargs)
|
||||||
return await self._handle_response(response, response_model)
|
return await self._handle_response(response, response_model)
|
||||||
|
|
||||||
async def patch(
|
|
||||||
self,
|
|
||||||
path: str,
|
|
||||||
body: Optional[BaseModel] = None,
|
|
||||||
response_model: Optional[Type[T]] = None,
|
|
||||||
**kwargs
|
|
||||||
) -> Any:
|
|
||||||
"""
|
|
||||||
HTTP PATCH запрос к {api_prefix}{path}.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
path: Путь эндпоинта
|
|
||||||
body: Pydantic модель для тела запроса
|
|
||||||
response_model: Pydantic модель для валидации ответа
|
|
||||||
**kwargs: Дополнительные параметры для httpx
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Десериализованный ответ (Pydantic модель или dict)
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
httpx.HTTPStatusError: При HTTP ошибках
|
|
||||||
ValidationError: При ошибках валидации Pydantic
|
|
||||||
"""
|
|
||||||
url = self._build_url(path)
|
|
||||||
json_body = self._serialize_body(body)
|
|
||||||
logger.debug(f"PATCH {url} with body={json_body}")
|
|
||||||
|
|
||||||
response = await self.client.patch(url, json=json_body, **kwargs)
|
|
||||||
return await self._handle_response(response, response_model)
|
|
||||||
|
|
||||||
async def delete(
|
async def delete(
|
||||||
self,
|
self,
|
||||||
path: str,
|
path: str,
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
from app.interfaces.base import TgBackendInterface
|
from app.interfaces.base import TgBackendInterface
|
||||||
from app.models.auth import LoginRequest, UserResponse
|
from app.models.auth import LoginRequest, UserResponse
|
||||||
from app.models.settings import UserSettings, UserSettingsUpdate
|
from app.models.settings import UserSettings, UserSettingsUpdate
|
||||||
from app.models.analysis import SessionCreate, SessionResponse, SessionList, SessionUpdate
|
from app.models.analysis import SessionCreate, SessionResponse, SessionList
|
||||||
|
|
||||||
|
|
||||||
class DBApiClient(TgBackendInterface):
|
class DBApiClient(TgBackendInterface):
|
||||||
|
|
@ -11,7 +11,7 @@ class DBApiClient(TgBackendInterface):
|
||||||
Клиент для DB API сервиса.
|
Клиент для DB API сервиса.
|
||||||
|
|
||||||
Использует Pydantic схемы для type-safety.
|
Использует Pydantic схемы для type-safety.
|
||||||
Методы self.get(), self.post(), self.patch(), self.delete() от TgBackendInterface.
|
Методы self.get(), self.post(), self.put(), self.delete() от TgBackendInterface.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
async def login_user(self, request: LoginRequest) -> UserResponse:
|
async def login_user(self, request: LoginRequest) -> UserResponse:
|
||||||
|
|
@ -36,12 +36,11 @@ class DBApiClient(TgBackendInterface):
|
||||||
settings: UserSettingsUpdate
|
settings: UserSettingsUpdate
|
||||||
) -> UserSettings:
|
) -> UserSettings:
|
||||||
"""
|
"""
|
||||||
PATCH {api_prefix}/users/{user_id}/settings
|
PUT {api_prefix}/users/{user_id}/settings
|
||||||
|
|
||||||
Частично обновить настройки пользователя.
|
Обновить настройки пользователя.
|
||||||
Обновляются только переданные поля.
|
|
||||||
"""
|
"""
|
||||||
return await self.patch(
|
return await self.put(
|
||||||
f"/users/{user_id}/settings",
|
f"/users/{user_id}/settings",
|
||||||
body=settings,
|
body=settings,
|
||||||
response_model=UserSettings
|
response_model=UserSettings
|
||||||
|
|
@ -95,23 +94,6 @@ class DBApiClient(TgBackendInterface):
|
||||||
response_model=SessionResponse
|
response_model=SessionResponse
|
||||||
)
|
)
|
||||||
|
|
||||||
async def update_session(
|
|
||||||
self,
|
|
||||||
user_id: str,
|
|
||||||
session_id: str,
|
|
||||||
update_data: SessionUpdate
|
|
||||||
) -> SessionResponse:
|
|
||||||
"""
|
|
||||||
PATCH {api_prefix}/users/{user_id}/sessions/{session_id}
|
|
||||||
|
|
||||||
Обновить аннотации сессии (например, после ревью).
|
|
||||||
"""
|
|
||||||
return await self.patch(
|
|
||||||
f"/users/{user_id}/sessions/{session_id}",
|
|
||||||
body=update_data,
|
|
||||||
response_model=SessionResponse
|
|
||||||
)
|
|
||||||
|
|
||||||
async def delete_session(self, user_id: str, session_id: str) -> dict:
|
async def delete_session(self, user_id: str, session_id: str) -> dict:
|
||||||
"""
|
"""
|
||||||
DELETE {api_prefix}/users/{user_id}/sessions/{session_id}
|
DELETE {api_prefix}/users/{user_id}/sessions/{session_id}
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,17 @@
|
||||||
"""Analysis session Pydantic models."""
|
"""Analysis session Pydantic models."""
|
||||||
|
|
||||||
from typing import Any, Optional
|
from typing import Any
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
class SessionCreate(BaseModel):
|
class SessionCreate(BaseModel):
|
||||||
"""Create new analysis session."""
|
"""Create new analysis session."""
|
||||||
|
|
||||||
environment: str = Field(..., description="Environment: ift, psi, or prod")
|
environment: str
|
||||||
api_mode: str = Field(..., description="API mode: bench or backend")
|
api_mode: str
|
||||||
request: list[Any] = Field(..., description="Array of request objects")
|
request: list[Any]
|
||||||
response: dict = Field(..., description="Response object (arbitrary structure)")
|
response: dict
|
||||||
annotations: Optional[dict] = Field(default={}, description="Annotations by question index")
|
annotations: dict
|
||||||
|
|
||||||
|
|
||||||
class SessionUpdate(BaseModel):
|
|
||||||
"""Update session annotations (PATCH).
|
|
||||||
|
|
||||||
According to DB_API_CONTRACT_V2.md:
|
|
||||||
- PATCH /users/{user_id}/sessions/{session_id}
|
|
||||||
- Used to update annotations after review
|
|
||||||
- Completely replaces existing annotations
|
|
||||||
"""
|
|
||||||
|
|
||||||
annotations: dict = Field(..., description="Annotations with numeric string keys ('0', '1', ...)")
|
|
||||||
|
|
||||||
|
|
||||||
class SessionResponse(BaseModel):
|
class SessionResponse(BaseModel):
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
"""Query request/response Pydantic models."""
|
"""Query request/response Pydantic models."""
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
class QuestionRequest(BaseModel):
|
class QuestionRequest(BaseModel):
|
||||||
|
|
@ -26,42 +26,10 @@ class BackendQueryRequest(BaseModel):
|
||||||
reset_session: bool = True
|
reset_session: bool = True
|
||||||
|
|
||||||
|
|
||||||
class Docs(BaseModel):
|
|
||||||
"""Documents from RAG."""
|
|
||||||
|
|
||||||
research: list
|
|
||||||
analytical_hub: list
|
|
||||||
|
|
||||||
|
|
||||||
class RagResponse(BaseModel):
|
|
||||||
"""Ответ от RAG на вопрос пользователя."""
|
|
||||||
|
|
||||||
body_research: str = Field(description="Текст ответа от Research на вопрос")
|
|
||||||
body_analytical_hub: str = Field(description="Текст ответа от Analytical Hub на вопрос")
|
|
||||||
docs_from_vectorstore: Docs | None = None
|
|
||||||
docs_to_llm: Docs | None = None
|
|
||||||
|
|
||||||
|
|
||||||
class RagResponseBench(RagResponse):
|
|
||||||
"""Ответ на вопрос + время обработки именно этого вопроса."""
|
|
||||||
|
|
||||||
processing_time_sec: float = Field(
|
|
||||||
description="Время обработки запроса в секундах",
|
|
||||||
ge=0,
|
|
||||||
)
|
|
||||||
question: str = Field(description="Исходный вопрос")
|
|
||||||
|
|
||||||
|
|
||||||
class RagResponseBenchList(BaseModel):
|
|
||||||
"""Список ответов RAG в bench режиме."""
|
|
||||||
|
|
||||||
answers: list[RagResponseBench]
|
|
||||||
|
|
||||||
|
|
||||||
class QueryResponse(BaseModel):
|
class QueryResponse(BaseModel):
|
||||||
"""Query response with metadata."""
|
"""Query response with metadata."""
|
||||||
|
|
||||||
request_id: str
|
request_id: str
|
||||||
timestamp: str
|
timestamp: str
|
||||||
environment: str
|
environment: str
|
||||||
response: RagResponseBenchList | dict | list # RagResponseBenchList для bench, dict/list для backend
|
response: dict
|
||||||
|
|
|
||||||
|
|
@ -1,43 +1,21 @@
|
||||||
"""User settings Pydantic models."""
|
"""User settings Pydantic models."""
|
||||||
|
|
||||||
from typing import Optional
|
from pydantic import BaseModel
|
||||||
from pydantic import BaseModel, Field
|
|
||||||
|
|
||||||
|
|
||||||
class EnvironmentSettings(BaseModel):
|
class EnvironmentSettings(BaseModel):
|
||||||
"""Settings for a specific environment (IFT/PSI/PROD).
|
"""Settings for a specific environment (IFT/PSI/PROD)."""
|
||||||
|
|
||||||
According to DB_API_CONTRACT_V2.md:
|
|
||||||
- apiMode, withClassify, resetSessionMode have defaults
|
|
||||||
- All other fields are nullable (Optional)
|
|
||||||
"""
|
|
||||||
|
|
||||||
apiMode: str = "bench"
|
apiMode: str = "bench"
|
||||||
bearerToken: Optional[str] = None
|
bearerToken: str = ""
|
||||||
systemPlatform: Optional[str] = None
|
systemPlatform: str = ""
|
||||||
systemPlatformUser: Optional[str] = None
|
systemPlatformUser: str = ""
|
||||||
platformUserId: Optional[str] = None
|
platformUserId: str = ""
|
||||||
platformId: Optional[str] = None
|
platformId: str = ""
|
||||||
withClassify: bool = False
|
withClassify: bool = False
|
||||||
resetSessionMode: bool = True
|
resetSessionMode: bool = True
|
||||||
|
|
||||||
|
|
||||||
class EnvironmentSettingsUpdate(BaseModel):
|
|
||||||
"""Partial update for environment settings (for PATCH requests).
|
|
||||||
|
|
||||||
All fields are optional to support partial updates.
|
|
||||||
"""
|
|
||||||
|
|
||||||
apiMode: Optional[str] = None
|
|
||||||
bearerToken: Optional[str] = None
|
|
||||||
systemPlatform: Optional[str] = None
|
|
||||||
systemPlatformUser: Optional[str] = None
|
|
||||||
platformUserId: Optional[str] = None
|
|
||||||
platformId: Optional[str] = None
|
|
||||||
withClassify: Optional[bool] = None
|
|
||||||
resetSessionMode: Optional[bool] = None
|
|
||||||
|
|
||||||
|
|
||||||
class UserSettings(BaseModel):
|
class UserSettings(BaseModel):
|
||||||
"""User settings for all environments."""
|
"""User settings for all environments."""
|
||||||
|
|
||||||
|
|
@ -47,10 +25,6 @@ class UserSettings(BaseModel):
|
||||||
|
|
||||||
|
|
||||||
class UserSettingsUpdate(BaseModel):
|
class UserSettingsUpdate(BaseModel):
|
||||||
"""Partial update user settings request (PATCH).
|
"""Update user settings request."""
|
||||||
|
|
||||||
Only the environments/fields provided will be updated.
|
settings: dict[str, EnvironmentSettings]
|
||||||
Unprovided fields remain unchanged.
|
|
||||||
"""
|
|
||||||
|
|
||||||
settings: dict[str, EnvironmentSettingsUpdate]
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import uuid
|
||||||
from typing import List, Dict, Optional, Any
|
from typing import List, Dict, Optional, Any
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from app.config import settings
|
from app.config import settings
|
||||||
from app.models.query import QuestionRequest, RagResponseBenchList
|
from app.models.query import QuestionRequest
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
@ -200,7 +200,7 @@ class RagService:
|
||||||
questions: List[QuestionRequest],
|
questions: List[QuestionRequest],
|
||||||
user_settings: Dict,
|
user_settings: Dict,
|
||||||
request_id: Optional[str] = None
|
request_id: Optional[str] = None
|
||||||
) -> RagResponseBenchList:
|
) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Отправить batch запрос к RAG backend (bench mode).
|
Отправить batch запрос к RAG backend (bench mode).
|
||||||
|
|
||||||
|
|
@ -211,7 +211,7 @@ class RagService:
|
||||||
request_id: Request ID (опционально)
|
request_id: Request ID (опционально)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
RagResponseBenchList с ответом от RAG backend
|
Dict с ответом от RAG backend
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
httpx.HTTPStatusError: При HTTP ошибках
|
httpx.HTTPStatusError: При HTTP ошибках
|
||||||
|
|
@ -220,7 +220,7 @@ class RagService:
|
||||||
url = self._get_bench_endpoint(environment)
|
url = self._get_bench_endpoint(environment)
|
||||||
headers = self._build_bench_headers(environment, user_settings, request_id)
|
headers = self._build_bench_headers(environment, user_settings, request_id)
|
||||||
|
|
||||||
|
|
||||||
body = [q.model_dump() for q in questions]
|
body = [q.model_dump() for q in questions]
|
||||||
|
|
||||||
logger.info(f"Sending bench query to {environment}: {len(questions)} questions")
|
logger.info(f"Sending bench query to {environment}: {len(questions)} questions")
|
||||||
|
|
@ -229,10 +229,7 @@ class RagService:
|
||||||
try:
|
try:
|
||||||
response = await client.post(url, json=body, headers=headers)
|
response = await client.post(url, json=body, headers=headers)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
response_data = response.json()
|
return response.json()
|
||||||
|
|
||||||
# Валидация ответа через Pydantic модель
|
|
||||||
return RagResponseBenchList(**response_data)
|
|
||||||
except httpx.HTTPStatusError as e:
|
except httpx.HTTPStatusError as e:
|
||||||
logger.error(f"Bench query failed for {environment}: {e.response.status_code} - {e.response.text}")
|
logger.error(f"Bench query failed for {environment}: {e.response.status_code} - {e.response.text}")
|
||||||
raise
|
raise
|
||||||
|
|
|
||||||
260
coverage.xml
260
coverage.xml
|
|
@ -1,9 +1,9 @@
|
||||||
<?xml version="1.0" ?>
|
<?xml version="1.0" ?>
|
||||||
<coverage version="7.13.0" timestamp="1766645017585" lines-valid="617" lines-covered="594" line-rate="0.9627" branches-covered="0" branches-valid="0" branch-rate="0" complexity="0">
|
<coverage version="7.13.0" timestamp="1766039646938" lines-valid="567" lines-covered="563" line-rate="0.9929" branches-covered="0" branches-valid="0" branch-rate="0" complexity="0">
|
||||||
<!-- Generated by coverage.py: https://coverage.readthedocs.io/en/7.13.0 -->
|
<!-- Generated by coverage.py: https://coverage.readthedocs.io/en/7.13.0 -->
|
||||||
<!-- Based on https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd -->
|
<!-- Based on https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd -->
|
||||||
<sources>
|
<sources>
|
||||||
<source>C:\Users\itqop\Documents\code\brief-rags-bench\app</source>
|
<source>C:\Users\leonk\Documents\code\brief-bench-fastapi\app</source>
|
||||||
</sources>
|
</sources>
|
||||||
<packages>
|
<packages>
|
||||||
<package name="." line-rate="0.971" branch-rate="0" complexity="0">
|
<package name="." line-rate="0.971" branch-rate="0" complexity="0">
|
||||||
|
|
@ -18,33 +18,33 @@
|
||||||
<line number="3" hits="1"/>
|
<line number="3" hits="1"/>
|
||||||
<line number="6" hits="1"/>
|
<line number="6" hits="1"/>
|
||||||
<line number="9" hits="1"/>
|
<line number="9" hits="1"/>
|
||||||
<line number="15" hits="1"/>
|
|
||||||
<line number="16" hits="1"/>
|
<line number="16" hits="1"/>
|
||||||
<line number="18" hits="1"/>
|
<line number="17" hits="1"/>
|
||||||
<line number="19" hits="1"/>
|
|
||||||
<line number="20" hits="1"/>
|
<line number="20" hits="1"/>
|
||||||
|
<line number="21" hits="1"/>
|
||||||
<line number="22" hits="1"/>
|
<line number="22" hits="1"/>
|
||||||
<line number="23" hits="1"/>
|
|
||||||
<line number="25" hits="1"/>
|
<line number="25" hits="1"/>
|
||||||
<line number="26" hits="1"/>
|
<line number="26" hits="1"/>
|
||||||
<line number="27" hits="1"/>
|
|
||||||
<line number="28" hits="1"/>
|
|
||||||
<line number="29" hits="1"/>
|
<line number="29" hits="1"/>
|
||||||
<line number="30" hits="1"/>
|
<line number="30" hits="1"/>
|
||||||
|
<line number="31" hits="1"/>
|
||||||
<line number="32" hits="1"/>
|
<line number="32" hits="1"/>
|
||||||
<line number="33" hits="1"/>
|
<line number="33" hits="1"/>
|
||||||
<line number="34" hits="1"/>
|
<line number="34" hits="1"/>
|
||||||
<line number="35" hits="1"/>
|
|
||||||
<line number="36" hits="1"/>
|
|
||||||
<line number="37" hits="1"/>
|
<line number="37" hits="1"/>
|
||||||
|
<line number="38" hits="1"/>
|
||||||
<line number="39" hits="1"/>
|
<line number="39" hits="1"/>
|
||||||
<line number="40" hits="1"/>
|
<line number="40" hits="1"/>
|
||||||
<line number="41" hits="1"/>
|
<line number="41" hits="1"/>
|
||||||
<line number="42" hits="1"/>
|
<line number="42" hits="1"/>
|
||||||
<line number="43" hits="1"/>
|
<line number="45" hits="1"/>
|
||||||
<line number="44" hits="1"/>
|
|
||||||
<line number="46" hits="1"/>
|
<line number="46" hits="1"/>
|
||||||
|
<line number="47" hits="1"/>
|
||||||
|
<line number="48" hits="1"/>
|
||||||
<line number="49" hits="1"/>
|
<line number="49" hits="1"/>
|
||||||
|
<line number="50" hits="1"/>
|
||||||
|
<line number="53" hits="1"/>
|
||||||
|
<line number="57" hits="1"/>
|
||||||
</lines>
|
</lines>
|
||||||
</class>
|
</class>
|
||||||
<class name="dependencies.py" filename="dependencies.py" complexity="0" line-rate="1" branch-rate="0">
|
<class name="dependencies.py" filename="dependencies.py" complexity="0" line-rate="1" branch-rate="0">
|
||||||
|
|
@ -75,25 +75,25 @@
|
||||||
<line number="6" hits="1"/>
|
<line number="6" hits="1"/>
|
||||||
<line number="8" hits="1"/>
|
<line number="8" hits="1"/>
|
||||||
<line number="9" hits="1"/>
|
<line number="9" hits="1"/>
|
||||||
<line number="11" hits="1"/>
|
<line number="12" hits="1"/>
|
||||||
<line number="17" hits="1"/>
|
<line number="19" hits="1"/>
|
||||||
<line number="25" hits="1"/>
|
|
||||||
<line number="26" hits="1"/>
|
|
||||||
<line number="27" hits="1"/>
|
|
||||||
<line number="28" hits="1"/>
|
<line number="28" hits="1"/>
|
||||||
|
<line number="29" hits="1"/>
|
||||||
<line number="30" hits="1"/>
|
<line number="30" hits="1"/>
|
||||||
<line number="33" hits="1"/>
|
<line number="31" hits="1"/>
|
||||||
<line number="34" hits="1"/>
|
<line number="34" hits="1"/>
|
||||||
<line number="36" hits="1"/>
|
<line number="37" hits="1"/>
|
||||||
<line number="39" hits="1"/>
|
<line number="38" hits="1"/>
|
||||||
<line number="40" hits="1"/>
|
<line number="40" hits="1"/>
|
||||||
<line number="42" hits="1"/>
|
<line number="43" hits="1"/>
|
||||||
<line number="45" hits="1"/>
|
<line number="44" hits="1"/>
|
||||||
<line number="46" hits="1"/>
|
<line number="46" hits="1"/>
|
||||||
<line number="48" hits="1"/>
|
<line number="49" hits="1"/>
|
||||||
<line number="51" hits="1"/>
|
<line number="50" hits="1"/>
|
||||||
<line number="52" hits="0"/>
|
<line number="52" hits="1"/>
|
||||||
<line number="53" hits="0"/>
|
<line number="55" hits="1"/>
|
||||||
|
<line number="56" hits="0"/>
|
||||||
|
<line number="57" hits="0"/>
|
||||||
</lines>
|
</lines>
|
||||||
</class>
|
</class>
|
||||||
</classes>
|
</classes>
|
||||||
|
|
@ -106,7 +106,7 @@
|
||||||
</class>
|
</class>
|
||||||
</classes>
|
</classes>
|
||||||
</package>
|
</package>
|
||||||
<package name="api.v1" line-rate="0.9216" branch-rate="0" complexity="0">
|
<package name="api.v1" line-rate="0.9894" branch-rate="0" complexity="0">
|
||||||
<classes>
|
<classes>
|
||||||
<class name="__init__.py" filename="api/v1/__init__.py" complexity="0" line-rate="1" branch-rate="0">
|
<class name="__init__.py" filename="api/v1/__init__.py" complexity="0" line-rate="1" branch-rate="0">
|
||||||
<methods/>
|
<methods/>
|
||||||
|
|
@ -115,7 +115,7 @@
|
||||||
<line number="7" hits="1"/>
|
<line number="7" hits="1"/>
|
||||||
</lines>
|
</lines>
|
||||||
</class>
|
</class>
|
||||||
<class name="analysis.py" filename="api/v1/analysis.py" complexity="0" line-rate="0.8313" branch-rate="0">
|
<class name="analysis.py" filename="api/v1/analysis.py" complexity="0" line-rate="1" branch-rate="0">
|
||||||
<methods/>
|
<methods/>
|
||||||
<lines>
|
<lines>
|
||||||
<line number="7" hits="1"/>
|
<line number="7" hits="1"/>
|
||||||
|
|
@ -173,34 +173,18 @@
|
||||||
<line number="140" hits="1"/>
|
<line number="140" hits="1"/>
|
||||||
<line number="146" hits="1"/>
|
<line number="146" hits="1"/>
|
||||||
<line number="147" hits="1"/>
|
<line number="147" hits="1"/>
|
||||||
<line number="165" hits="0"/>
|
<line number="161" hits="1"/>
|
||||||
<line number="167" hits="0"/>
|
<line number="163" hits="1"/>
|
||||||
<line number="168" hits="0"/>
|
<line number="164" hits="1"/>
|
||||||
<line number="169" hits="0"/>
|
<line number="165" hits="1"/>
|
||||||
<line number="170" hits="0"/>
|
<line number="166" hits="1"/>
|
||||||
<line number="171" hits="0"/>
|
<line number="167" hits="1"/>
|
||||||
<line number="172" hits="0"/>
|
<line number="168" hits="1"/>
|
||||||
<line number="176" hits="0"/>
|
<line number="172" hits="1"/>
|
||||||
<line number="177" hits="0"/>
|
<line number="173" hits="1"/>
|
||||||
<line number="181" hits="0"/>
|
<line number="177" hits="1"/>
|
||||||
<line number="182" hits="0"/>
|
<line number="178" hits="1"/>
|
||||||
<line number="186" hits="0"/>
|
<line number="179" hits="1"/>
|
||||||
<line number="187" hits="0"/>
|
|
||||||
<line number="188" hits="0"/>
|
|
||||||
<line number="194" hits="1"/>
|
|
||||||
<line number="195" hits="1"/>
|
|
||||||
<line number="209" hits="1"/>
|
|
||||||
<line number="211" hits="1"/>
|
|
||||||
<line number="212" hits="1"/>
|
|
||||||
<line number="213" hits="1"/>
|
|
||||||
<line number="214" hits="1"/>
|
|
||||||
<line number="215" hits="1"/>
|
|
||||||
<line number="216" hits="1"/>
|
|
||||||
<line number="220" hits="1"/>
|
|
||||||
<line number="221" hits="1"/>
|
|
||||||
<line number="225" hits="1"/>
|
|
||||||
<line number="226" hits="1"/>
|
|
||||||
<line number="227" hits="1"/>
|
|
||||||
</lines>
|
</lines>
|
||||||
</class>
|
</class>
|
||||||
<class name="auth.py" filename="api/v1/auth.py" complexity="0" line-rate="1" branch-rate="0">
|
<class name="auth.py" filename="api/v1/auth.py" complexity="0" line-rate="1" branch-rate="0">
|
||||||
|
|
@ -321,31 +305,31 @@
|
||||||
<line number="48" hits="1"/>
|
<line number="48" hits="1"/>
|
||||||
<line number="54" hits="1"/>
|
<line number="54" hits="1"/>
|
||||||
<line number="55" hits="1"/>
|
<line number="55" hits="1"/>
|
||||||
|
<line number="69" hits="1"/>
|
||||||
<line number="71" hits="1"/>
|
<line number="71" hits="1"/>
|
||||||
|
<line number="72" hits="1"/>
|
||||||
<line number="73" hits="1"/>
|
<line number="73" hits="1"/>
|
||||||
<line number="74" hits="1"/>
|
<line number="74" hits="1"/>
|
||||||
<line number="75" hits="1"/>
|
<line number="75" hits="1"/>
|
||||||
<line number="76" hits="1"/>
|
<line number="76" hits="1"/>
|
||||||
<line number="77" hits="1"/>
|
<line number="80" hits="1"/>
|
||||||
<line number="78" hits="1"/>
|
<line number="81" hits="1"/>
|
||||||
<line number="82" hits="1"/>
|
<line number="85" hits="1"/>
|
||||||
<line number="83" hits="1"/>
|
<line number="86" hits="1"/>
|
||||||
<line number="87" hits="1"/>
|
<line number="90" hits="1"/>
|
||||||
<line number="88" hits="1"/>
|
<line number="91" hits="1"/>
|
||||||
<line number="92" hits="1"/>
|
<line number="92" hits="1"/>
|
||||||
<line number="93" hits="1"/>
|
|
||||||
<line number="94" hits="1"/>
|
|
||||||
</lines>
|
</lines>
|
||||||
</class>
|
</class>
|
||||||
</classes>
|
</classes>
|
||||||
</package>
|
</package>
|
||||||
<package name="interfaces" line-rate="0.9505" branch-rate="0" complexity="0">
|
<package name="interfaces" line-rate="1" branch-rate="0" complexity="0">
|
||||||
<classes>
|
<classes>
|
||||||
<class name="__init__.py" filename="interfaces/__init__.py" complexity="0" line-rate="1" branch-rate="0">
|
<class name="__init__.py" filename="interfaces/__init__.py" complexity="0" line-rate="1" branch-rate="0">
|
||||||
<methods/>
|
<methods/>
|
||||||
<lines/>
|
<lines/>
|
||||||
</class>
|
</class>
|
||||||
<class name="base.py" filename="interfaces/base.py" complexity="0" line-rate="0.9351" branch-rate="0">
|
<class name="base.py" filename="interfaces/base.py" complexity="0" line-rate="1" branch-rate="0">
|
||||||
<methods/>
|
<methods/>
|
||||||
<lines>
|
<lines>
|
||||||
<line number="9" hits="1"/>
|
<line number="9" hits="1"/>
|
||||||
|
|
@ -415,16 +399,10 @@
|
||||||
<line number="252" hits="1"/>
|
<line number="252" hits="1"/>
|
||||||
<line number="253" hits="1"/>
|
<line number="253" hits="1"/>
|
||||||
<line number="255" hits="1"/>
|
<line number="255" hits="1"/>
|
||||||
<line number="278" hits="0"/>
|
<line number="273" hits="1"/>
|
||||||
<line number="279" hits="0"/>
|
<line number="274" hits="1"/>
|
||||||
<line number="280" hits="0"/>
|
<line number="276" hits="1"/>
|
||||||
<line number="282" hits="0"/>
|
<line number="277" hits="1"/>
|
||||||
<line number="283" hits="0"/>
|
|
||||||
<line number="285" hits="1"/>
|
|
||||||
<line number="303" hits="1"/>
|
|
||||||
<line number="304" hits="1"/>
|
|
||||||
<line number="306" hits="1"/>
|
|
||||||
<line number="307" hits="1"/>
|
|
||||||
</lines>
|
</lines>
|
||||||
</class>
|
</class>
|
||||||
<class name="db_api_client.py" filename="interfaces/db_api_client.py" complexity="0" line-rate="1" branch-rate="0">
|
<class name="db_api_client.py" filename="interfaces/db_api_client.py" complexity="0" line-rate="1" branch-rate="0">
|
||||||
|
|
@ -440,20 +418,18 @@
|
||||||
<line number="25" hits="1"/>
|
<line number="25" hits="1"/>
|
||||||
<line number="31" hits="1"/>
|
<line number="31" hits="1"/>
|
||||||
<line number="33" hits="1"/>
|
<line number="33" hits="1"/>
|
||||||
<line number="44" hits="1"/>
|
<line number="43" hits="1"/>
|
||||||
<line number="50" hits="1"/>
|
<line number="49" hits="1"/>
|
||||||
<line number="60" hits="1"/>
|
<line number="59" hits="1"/>
|
||||||
<line number="66" hits="1"/>
|
<line number="65" hits="1"/>
|
||||||
|
<line number="77" hits="1"/>
|
||||||
<line number="78" hits="1"/>
|
<line number="78" hits="1"/>
|
||||||
<line number="79" hits="1"/>
|
<line number="79" hits="1"/>
|
||||||
<line number="80" hits="1"/>
|
<line number="80" hits="1"/>
|
||||||
<line number="81" hits="1"/>
|
<line number="86" hits="1"/>
|
||||||
<line number="87" hits="1"/>
|
<line number="92" hits="1"/>
|
||||||
<line number="93" hits="1"/>
|
<line number="97" hits="1"/>
|
||||||
<line number="98" hits="1"/>
|
<line number="103" hits="1"/>
|
||||||
<line number="109" hits="1"/>
|
|
||||||
<line number="115" hits="1"/>
|
|
||||||
<line number="121" hits="1"/>
|
|
||||||
</lines>
|
</lines>
|
||||||
</class>
|
</class>
|
||||||
</classes>
|
</classes>
|
||||||
|
|
@ -484,24 +460,22 @@
|
||||||
<line number="13" hits="1"/>
|
<line number="13" hits="1"/>
|
||||||
<line number="14" hits="1"/>
|
<line number="14" hits="1"/>
|
||||||
<line number="17" hits="1"/>
|
<line number="17" hits="1"/>
|
||||||
|
<line number="20" hits="1"/>
|
||||||
|
<line number="21" hits="1"/>
|
||||||
|
<line number="22" hits="1"/>
|
||||||
|
<line number="23" hits="1"/>
|
||||||
|
<line number="24" hits="1"/>
|
||||||
|
<line number="25" hits="1"/>
|
||||||
<line number="26" hits="1"/>
|
<line number="26" hits="1"/>
|
||||||
<line number="29" hits="1"/>
|
<line number="27" hits="1"/>
|
||||||
<line number="32" hits="1"/>
|
<line number="28" hits="1"/>
|
||||||
<line number="33" hits="1"/>
|
<line number="31" hits="1"/>
|
||||||
<line number="34" hits="1"/>
|
<line number="34" hits="1"/>
|
||||||
<line number="35" hits="1"/>
|
<line number="35" hits="1"/>
|
||||||
<line number="36" hits="1"/>
|
<line number="36" hits="1"/>
|
||||||
<line number="37" hits="1"/>
|
|
||||||
<line number="38" hits="1"/>
|
|
||||||
<line number="39" hits="1"/>
|
<line number="39" hits="1"/>
|
||||||
<line number="40" hits="1"/>
|
<line number="42" hits="1"/>
|
||||||
<line number="43" hits="1"/>
|
<line number="43" hits="1"/>
|
||||||
<line number="46" hits="1"/>
|
|
||||||
<line number="47" hits="1"/>
|
|
||||||
<line number="48" hits="1"/>
|
|
||||||
<line number="51" hits="1"/>
|
|
||||||
<line number="54" hits="1"/>
|
|
||||||
<line number="55" hits="1"/>
|
|
||||||
</lines>
|
</lines>
|
||||||
</class>
|
</class>
|
||||||
<class name="auth.py" filename="models/auth.py" complexity="0" line-rate="1" branch-rate="0">
|
<class name="auth.py" filename="models/auth.py" complexity="0" line-rate="1" branch-rate="0">
|
||||||
|
|
@ -544,52 +518,29 @@
|
||||||
<line number="29" hits="1"/>
|
<line number="29" hits="1"/>
|
||||||
<line number="32" hits="1"/>
|
<line number="32" hits="1"/>
|
||||||
<line number="33" hits="1"/>
|
<line number="33" hits="1"/>
|
||||||
<line number="36" hits="1"/>
|
<line number="34" hits="1"/>
|
||||||
<line number="39" hits="1"/>
|
<line number="35" hits="1"/>
|
||||||
<line number="40" hits="1"/>
|
|
||||||
<line number="41" hits="1"/>
|
|
||||||
<line number="42" hits="1"/>
|
|
||||||
<line number="45" hits="1"/>
|
|
||||||
<line number="48" hits="1"/>
|
|
||||||
<line number="52" hits="1"/>
|
|
||||||
<line number="55" hits="1"/>
|
|
||||||
<line number="58" hits="1"/>
|
|
||||||
<line number="61" hits="1"/>
|
|
||||||
<line number="64" hits="1"/>
|
|
||||||
<line number="65" hits="1"/>
|
|
||||||
<line number="66" hits="1"/>
|
|
||||||
<line number="67" hits="1"/>
|
|
||||||
</lines>
|
</lines>
|
||||||
</class>
|
</class>
|
||||||
<class name="settings.py" filename="models/settings.py" complexity="0" line-rate="1" branch-rate="0">
|
<class name="settings.py" filename="models/settings.py" complexity="0" line-rate="1" branch-rate="0">
|
||||||
<methods/>
|
<methods/>
|
||||||
<lines>
|
<lines>
|
||||||
<line number="3" hits="1"/>
|
<line number="3" hits="1"/>
|
||||||
<line number="4" hits="1"/>
|
<line number="6" hits="1"/>
|
||||||
<line number="7" hits="1"/>
|
<line number="9" hits="1"/>
|
||||||
|
<line number="10" hits="1"/>
|
||||||
|
<line number="11" hits="1"/>
|
||||||
|
<line number="12" hits="1"/>
|
||||||
|
<line number="13" hits="1"/>
|
||||||
|
<line number="14" hits="1"/>
|
||||||
<line number="15" hits="1"/>
|
<line number="15" hits="1"/>
|
||||||
<line number="16" hits="1"/>
|
<line number="16" hits="1"/>
|
||||||
<line number="17" hits="1"/>
|
|
||||||
<line number="18" hits="1"/>
|
|
||||||
<line number="19" hits="1"/>
|
<line number="19" hits="1"/>
|
||||||
<line number="20" hits="1"/>
|
|
||||||
<line number="21" hits="1"/>
|
|
||||||
<line number="22" hits="1"/>
|
<line number="22" hits="1"/>
|
||||||
<line number="25" hits="1"/>
|
<line number="23" hits="1"/>
|
||||||
<line number="31" hits="1"/>
|
<line number="24" hits="1"/>
|
||||||
<line number="32" hits="1"/>
|
<line number="27" hits="1"/>
|
||||||
<line number="33" hits="1"/>
|
<line number="30" hits="1"/>
|
||||||
<line number="34" hits="1"/>
|
|
||||||
<line number="35" hits="1"/>
|
|
||||||
<line number="36" hits="1"/>
|
|
||||||
<line number="37" hits="1"/>
|
|
||||||
<line number="38" hits="1"/>
|
|
||||||
<line number="41" hits="1"/>
|
|
||||||
<line number="44" hits="1"/>
|
|
||||||
<line number="45" hits="1"/>
|
|
||||||
<line number="46" hits="1"/>
|
|
||||||
<line number="49" hits="1"/>
|
|
||||||
<line number="56" hits="1"/>
|
|
||||||
</lines>
|
</lines>
|
||||||
</class>
|
</class>
|
||||||
</classes>
|
</classes>
|
||||||
|
|
@ -698,41 +649,40 @@
|
||||||
<line number="230" hits="1"/>
|
<line number="230" hits="1"/>
|
||||||
<line number="231" hits="1"/>
|
<line number="231" hits="1"/>
|
||||||
<line number="232" hits="1"/>
|
<line number="232" hits="1"/>
|
||||||
|
<line number="233" hits="1"/>
|
||||||
|
<line number="234" hits="1"/>
|
||||||
<line number="235" hits="1"/>
|
<line number="235" hits="1"/>
|
||||||
<line number="236" hits="1"/>
|
<line number="236" hits="1"/>
|
||||||
<line number="237" hits="1"/>
|
<line number="237" hits="1"/>
|
||||||
<line number="238" hits="1"/>
|
<line number="238" hits="1"/>
|
||||||
<line number="239" hits="1"/>
|
|
||||||
<line number="240" hits="1"/>
|
<line number="240" hits="1"/>
|
||||||
<line number="241" hits="1"/>
|
<line number="264" hits="1"/>
|
||||||
<line number="243" hits="1"/>
|
<line number="265" hits="1"/>
|
||||||
<line number="267" hits="1"/>
|
<line number="266" hits="1"/>
|
||||||
<line number="268" hits="1"/>
|
<line number="268" hits="1"/>
|
||||||
<line number="269" hits="1"/>
|
<line number="273" hits="1"/>
|
||||||
<line number="271" hits="1"/>
|
<line number="275" hits="1"/>
|
||||||
<line number="276" hits="1"/>
|
<line number="277" hits="1"/>
|
||||||
<line number="278" hits="1"/>
|
<line number="278" hits="1"/>
|
||||||
<line number="280" hits="1"/>
|
<line number="285" hits="1"/>
|
||||||
<line number="281" hits="1"/>
|
<line number="287" hits="1"/>
|
||||||
<line number="288" hits="1"/>
|
<line number="289" hits="1"/>
|
||||||
<line number="290" hits="1"/>
|
<line number="290" hits="1"/>
|
||||||
|
<line number="291" hits="1"/>
|
||||||
<line number="292" hits="1"/>
|
<line number="292" hits="1"/>
|
||||||
<line number="293" hits="1"/>
|
|
||||||
<line number="294" hits="1"/>
|
|
||||||
<line number="295" hits="1"/>
|
<line number="295" hits="1"/>
|
||||||
|
<line number="296" hits="1"/>
|
||||||
|
<line number="297" hits="1"/>
|
||||||
<line number="298" hits="1"/>
|
<line number="298" hits="1"/>
|
||||||
<line number="299" hits="1"/>
|
<line number="303" hits="1"/>
|
||||||
<line number="300" hits="1"/>
|
<line number="305" hits="1"/>
|
||||||
<line number="301" hits="1"/>
|
|
||||||
<line number="306" hits="1"/>
|
<line number="306" hits="1"/>
|
||||||
<line number="308" hits="1"/>
|
<line number="310" hits="1"/>
|
||||||
<line number="309" hits="1"/>
|
<line number="311" hits="1"/>
|
||||||
|
<line number="312" hits="1"/>
|
||||||
<line number="313" hits="1"/>
|
<line number="313" hits="1"/>
|
||||||
<line number="314" hits="1"/>
|
|
||||||
<line number="315" hits="1"/>
|
<line number="315" hits="1"/>
|
||||||
<line number="316" hits="1"/>
|
<line number="316" hits="1"/>
|
||||||
<line number="318" hits="1"/>
|
|
||||||
<line number="319" hits="1"/>
|
|
||||||
</lines>
|
</lines>
|
||||||
</class>
|
</class>
|
||||||
</classes>
|
</classes>
|
||||||
|
|
|
||||||
27
format.py
27
format.py
|
|
@ -1,27 +0,0 @@
|
||||||
from pydantic import BaseModel, Field
|
|
||||||
|
|
||||||
|
|
||||||
class Docs(BaseModel):
|
|
||||||
research: list
|
|
||||||
analytical_hub: list
|
|
||||||
|
|
||||||
|
|
||||||
class RagResponse(BaseModel):
|
|
||||||
"""Ответ от RAG на вопрос пользователя."""
|
|
||||||
body_research: str = Field(description="Текст ответа от Research на вопрос")
|
|
||||||
body_analytical_hub: str = Field(description="Текст ответа от Analytical Hub на вопрос")
|
|
||||||
docs_from_vectorstore: Docs | None = None
|
|
||||||
docs_to_llm: Docs | None = None
|
|
||||||
|
|
||||||
|
|
||||||
class RagResponseBench(RagResponse):
|
|
||||||
"""Ответ на вопрос + время обработки именно этого вопроса."""
|
|
||||||
processing_time_sec: float = Field(
|
|
||||||
description="Время обработки запроса в секундах",
|
|
||||||
ge=0,
|
|
||||||
)
|
|
||||||
question: str = Field(description="Исходный вопрос")
|
|
||||||
|
|
||||||
|
|
||||||
class RagResponseBenchList(BaseModel):
|
|
||||||
answers: list[RagResponseBench]
|
|
||||||
|
|
@ -107,7 +107,7 @@ pytest -x
|
||||||
pytest -s
|
pytest -s
|
||||||
```
|
```
|
||||||
|
|
||||||
## Покрытие тестами
|
## Покрытие (Coverage)
|
||||||
|
|
||||||
### Unit Tests: **99%** (567 строк, 4 непокрыто)
|
### Unit Tests: **99%** (567 строк, 4 непокрыто)
|
||||||
|
|
||||||
|
|
@ -133,51 +133,51 @@ pytest -s
|
||||||
## Что тестируется
|
## Что тестируется
|
||||||
|
|
||||||
### 1. Authentication (test_auth.py)
|
### 1. Authentication (test_auth.py)
|
||||||
- Успешная авторизация с валидным 8-значным логином
|
- ✅ Успешная авторизация с валидным 8-значным логином
|
||||||
- Отклонение невалидных форматов логина
|
- ✅ Отклонение невалидных форматов логина
|
||||||
- Обработка ошибок DB API
|
- ✅ Обработка ошибок DB API
|
||||||
- Генерация JWT токенов
|
- ✅ Генерация JWT токенов
|
||||||
- Валидация токенов
|
- ✅ Валидация токенов
|
||||||
|
|
||||||
### 2. Settings (test_settings.py)
|
### 2. Settings (test_settings.py)
|
||||||
- Получение настроек пользователя
|
- ✅ Получение настроек пользователя
|
||||||
- Обновление настроек
|
- ✅ Обновление настроек
|
||||||
- Обработка несуществующих пользователей
|
- ✅ Обработка несуществующих пользователей
|
||||||
- Валидация формата настроек
|
- ✅ Валидация формата настроек
|
||||||
- Требование авторизации
|
- ✅ Требование авторизации
|
||||||
|
|
||||||
### 3. Query (test_query.py)
|
### 3. Query (test_query.py)
|
||||||
- Bench mode запросы
|
- ✅ Bench mode запросы
|
||||||
- Backend mode запросы
|
- ✅ Backend mode запросы
|
||||||
- Валидация окружений (ift/psi/prod)
|
- ✅ Валидация окружений (ift/psi/prod)
|
||||||
- Проверка соответствия apiMode
|
- ✅ Проверка соответствия apiMode
|
||||||
- Обработка ошибок RAG backend
|
- ✅ Обработка ошибок RAG backend
|
||||||
- Построение headers для RAG
|
- ✅ Построение headers для RAG
|
||||||
- Session reset в Backend mode
|
- ✅ Session reset в Backend mode
|
||||||
|
|
||||||
### 4. Analysis (test_analysis.py)
|
### 4. Analysis (test_analysis.py)
|
||||||
- Создание сессий анализа
|
- ✅ Создание сессий анализа
|
||||||
- Получение списка сессий
|
- ✅ Получение списка сессий
|
||||||
- Фильтрация по окружению
|
- ✅ Фильтрация по окружению
|
||||||
- Пагинация
|
- ✅ Пагинация
|
||||||
- Получение конкретной сессии
|
- ✅ Получение конкретной сессии
|
||||||
- Удаление сессии
|
- ✅ Удаление сессии
|
||||||
- Требование авторизации
|
- ✅ Требование авторизации
|
||||||
|
|
||||||
### 5. Security (test_security.py)
|
### 5. Security (test_security.py)
|
||||||
- Создание JWT токенов
|
- ✅ Создание JWT токенов
|
||||||
- Декодирование токенов
|
- ✅ Декодирование токенов
|
||||||
- Обработка невалидных токенов
|
- ✅ Обработка невалидных токенов
|
||||||
- Обработка истекших токенов
|
- ✅ Обработка истекших токенов
|
||||||
- Кастомное время жизни токенов
|
- ✅ Кастомное время жизни токенов
|
||||||
|
|
||||||
### 6. Models (test_models.py)
|
### 6. Models (test_models.py)
|
||||||
- Валидация LoginRequest (8 цифр)
|
- ✅ Валидация LoginRequest (8 цифр)
|
||||||
- Валидация QuestionRequest
|
- ✅ Валидация QuestionRequest
|
||||||
- Валидация BenchQueryRequest
|
- ✅ Валидация BenchQueryRequest
|
||||||
- Валидация BackendQueryRequest
|
- ✅ Валидация BackendQueryRequest
|
||||||
- Валидация EnvironmentSettings
|
- ✅ Валидация EnvironmentSettings
|
||||||
- Дефолтные значения
|
- ✅ Дефолтные значения
|
||||||
|
|
||||||
## Моки
|
## Моки
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -144,36 +144,20 @@ def unauthenticated_client():
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_bench_response():
|
def mock_bench_response():
|
||||||
"""Mock RAG backend bench response (matches format.py structure)."""
|
"""Mock RAG backend bench response."""
|
||||||
return {
|
return {
|
||||||
"answers": [
|
"answers": [
|
||||||
{
|
{
|
||||||
"body_research": "Test research answer 1",
|
"question_id": 1,
|
||||||
"body_analytical_hub": "Test analytical hub answer 1",
|
"answer": "Test answer 1",
|
||||||
"docs_from_vectorstore": {
|
"confidence": 0.95,
|
||||||
"research": [],
|
"docs": []
|
||||||
"analytical_hub": []
|
|
||||||
},
|
|
||||||
"docs_to_llm": {
|
|
||||||
"research": [],
|
|
||||||
"analytical_hub": []
|
|
||||||
},
|
|
||||||
"processing_time_sec": 1.5,
|
|
||||||
"question": "Test question 1"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"body_research": "Test research answer 2",
|
"question_id": 2,
|
||||||
"body_analytical_hub": "Test analytical hub answer 2",
|
"answer": "Test answer 2",
|
||||||
"docs_from_vectorstore": {
|
"confidence": 0.87,
|
||||||
"research": [],
|
"docs": []
|
||||||
"analytical_hub": []
|
|
||||||
},
|
|
||||||
"docs_to_llm": {
|
|
||||||
"research": [],
|
|
||||||
"analytical_hub": []
|
|
||||||
},
|
|
||||||
"processing_time_sec": 2.3,
|
|
||||||
"question": "Test question 2"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -435,3 +435,4 @@ When adding new E2E tests:
|
||||||
- [Integration Tests](../integration/README.md) - Tests for DB API integration only
|
- [Integration Tests](../integration/README.md) - Tests for DB API integration only
|
||||||
- [Unit Tests](../unit/) - Fast isolated tests
|
- [Unit Tests](../unit/) - Fast isolated tests
|
||||||
- [DB API Contract](../../DB_API_CONTRACT.md) - External DB API specification
|
- [DB API Contract](../../DB_API_CONTRACT.md) - External DB API specification
|
||||||
|
- [CLAUDE.md](../../CLAUDE.md) - Project architecture overview
|
||||||
|
|
|
||||||
|
|
@ -88,20 +88,20 @@ tests/integration/
|
||||||
|
|
||||||
## Что тестируется
|
## Что тестируется
|
||||||
|
|
||||||
### Auth Integration (`test_auth_integration.py`)
|
### ✅ Auth Integration (`test_auth_integration.py`)
|
||||||
- Успешная авторизация с реальным DB API
|
- Успешная авторизация с реальным DB API
|
||||||
- Генерация и валидация JWT токенов
|
- Генерация и валидация JWT токенов
|
||||||
- Защита endpoint-ов с использованием JWT
|
- Защита endpoint-ов с использованием JWT
|
||||||
- Обработка ошибок аутентификации
|
- Обработка ошибок аутентификации
|
||||||
|
|
||||||
### Settings Integration (`test_settings_integration.py`)
|
### ✅ Settings Integration (`test_settings_integration.py`)
|
||||||
- Получение настроек пользователя из DB API
|
- Получение настроек пользователя из DB API
|
||||||
- Обновление настроек для всех окружений (IFT, PSI, PROD)
|
- Обновление настроек для всех окружений (IFT, PSI, PROD)
|
||||||
- Частичное обновление настроек
|
- Частичное обновление настроек
|
||||||
- Персистентность настроек
|
- Персистентность настроек
|
||||||
- Проверка структуры данных настроек
|
- Проверка структуры данных настроек
|
||||||
|
|
||||||
### Analysis Integration (`test_analysis_integration.py`)
|
### ✅ Analysis Integration (`test_analysis_integration.py`)
|
||||||
- Создание сессий анализа в DB API
|
- Создание сессий анализа в DB API
|
||||||
- Получение списка сессий с фильтрацией
|
- Получение списка сессий с фильтрацией
|
||||||
- Пагинация сессий
|
- Пагинация сессий
|
||||||
|
|
@ -109,7 +109,7 @@ tests/integration/
|
||||||
- Удаление сессий
|
- Удаление сессий
|
||||||
- Целостность данных (включая Unicode, вложенные структуры)
|
- Целостность данных (включая Unicode, вложенные структуры)
|
||||||
|
|
||||||
### Query Integration (`test_query_integration.py`)
|
### ✅ Query Integration (`test_query_integration.py`)
|
||||||
- Получение настроек пользователя для запросов
|
- Получение настроек пользователя для запросов
|
||||||
- Проверка соответствия apiMode (bench/backend)
|
- Проверка соответствия apiMode (bench/backend)
|
||||||
- Обновление настроек между запросами
|
- Обновление настроек между запросами
|
||||||
|
|
@ -117,10 +117,10 @@ tests/integration/
|
||||||
|
|
||||||
## Что НЕ тестируется
|
## Что НЕ тестируется
|
||||||
|
|
||||||
**RAG Backend взаимодействие** - требует запущенные RAG сервисы (IFT/PSI/PROD)
|
❌ **RAG Backend взаимодействие** - требует запущенные RAG сервисы (IFT/PSI/PROD)
|
||||||
**mTLS сертификаты** - требует реальные сертификаты
|
❌ **mTLS сертификаты** - требует реальные сертификаты
|
||||||
**Производительность** - используйте отдельные performance тесты
|
❌ **Производительность** - используйте отдельные performance тесты
|
||||||
**Нагрузочное тестирование** - используйте инструменты типа Locust/K6
|
❌ **Нагрузочное тестирование** - используйте инструменты типа Locust/K6
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ import pytest
|
||||||
from unittest.mock import AsyncMock, patch, MagicMock
|
from unittest.mock import AsyncMock, patch, MagicMock
|
||||||
from app.interfaces.db_api_client import DBApiClient
|
from app.interfaces.db_api_client import DBApiClient
|
||||||
from app.models.auth import LoginRequest, UserResponse
|
from app.models.auth import LoginRequest, UserResponse
|
||||||
from app.models.settings import UserSettings, UserSettingsUpdate, EnvironmentSettings, EnvironmentSettingsUpdate
|
from app.models.settings import UserSettings, UserSettingsUpdate, EnvironmentSettings
|
||||||
from app.models.analysis import SessionCreate, SessionResponse, SessionList, SessionListItem, SessionUpdate
|
from app.models.analysis import SessionCreate, SessionResponse, SessionList, SessionListItem
|
||||||
|
|
||||||
|
|
||||||
class TestDBApiClient:
|
class TestDBApiClient:
|
||||||
|
|
@ -70,14 +70,19 @@ class TestDBApiClient:
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_update_user_settings(self):
|
async def test_update_user_settings(self):
|
||||||
"""Test update_user_settings calls patch correctly."""
|
"""Test update_user_settings calls put correctly."""
|
||||||
with patch('app.interfaces.base.httpx.AsyncClient'):
|
with patch('app.interfaces.base.httpx.AsyncClient'):
|
||||||
client = DBApiClient(api_prefix="http://db-api:8080/api/v1")
|
client = DBApiClient(api_prefix="http://db-api:8080/api/v1")
|
||||||
|
|
||||||
settings_update = UserSettingsUpdate(
|
settings_update = UserSettingsUpdate(
|
||||||
settings={
|
settings={
|
||||||
"ift": EnvironmentSettingsUpdate(
|
"ift": EnvironmentSettings(
|
||||||
apiMode="backend",
|
apiMode="backend",
|
||||||
|
bearerToken="",
|
||||||
|
systemPlatform="",
|
||||||
|
systemPlatformUser="",
|
||||||
|
platformUserId="",
|
||||||
|
platformId="",
|
||||||
withClassify=True,
|
withClassify=True,
|
||||||
resetSessionMode=False
|
resetSessionMode=False
|
||||||
)
|
)
|
||||||
|
|
@ -85,26 +90,15 @@ class TestDBApiClient:
|
||||||
)
|
)
|
||||||
mock_updated_settings = UserSettings(
|
mock_updated_settings = UserSettings(
|
||||||
user_id="user-123",
|
user_id="user-123",
|
||||||
settings={
|
settings=settings_update.settings,
|
||||||
"ift": EnvironmentSettings(
|
|
||||||
apiMode="backend",
|
|
||||||
bearerToken=None,
|
|
||||||
systemPlatform=None,
|
|
||||||
systemPlatformUser=None,
|
|
||||||
platformUserId=None,
|
|
||||||
platformId=None,
|
|
||||||
withClassify=True,
|
|
||||||
resetSessionMode=False
|
|
||||||
)
|
|
||||||
},
|
|
||||||
updated_at="2024-01-01T01:00:00Z"
|
updated_at="2024-01-01T01:00:00Z"
|
||||||
)
|
)
|
||||||
client.patch = AsyncMock(return_value=mock_updated_settings)
|
client.put = AsyncMock(return_value=mock_updated_settings)
|
||||||
|
|
||||||
result = await client.update_user_settings("user-123", settings_update)
|
result = await client.update_user_settings("user-123", settings_update)
|
||||||
|
|
||||||
assert result == mock_updated_settings
|
assert result == mock_updated_settings
|
||||||
client.patch.assert_called_once_with(
|
client.put.assert_called_once_with(
|
||||||
"/users/user-123/settings",
|
"/users/user-123/settings",
|
||||||
body=settings_update,
|
body=settings_update,
|
||||||
response_model=UserSettings
|
response_model=UserSettings
|
||||||
|
|
@ -217,44 +211,6 @@ class TestDBApiClient:
|
||||||
response_model=SessionResponse
|
response_model=SessionResponse
|
||||||
)
|
)
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_update_session(self):
|
|
||||||
"""Test update_session calls patch correctly."""
|
|
||||||
with patch('app.interfaces.base.httpx.AsyncClient'):
|
|
||||||
client = DBApiClient(api_prefix="http://db-api:8080/api/v1")
|
|
||||||
|
|
||||||
update_data = SessionUpdate(
|
|
||||||
annotations={
|
|
||||||
"0": {
|
|
||||||
"overall": {
|
|
||||||
"rating": "incorrect",
|
|
||||||
"comment": "Ответ неполный"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
mock_updated_session = SessionResponse(
|
|
||||||
session_id="session-123",
|
|
||||||
user_id="user-123",
|
|
||||||
environment="ift",
|
|
||||||
api_mode="bench",
|
|
||||||
request=[{"question": "test"}],
|
|
||||||
response={"answer": "test"},
|
|
||||||
annotations=update_data.annotations,
|
|
||||||
created_at="2024-01-01T00:00:00Z",
|
|
||||||
updated_at="2024-01-01T01:00:00Z"
|
|
||||||
)
|
|
||||||
client.patch = AsyncMock(return_value=mock_updated_session)
|
|
||||||
|
|
||||||
result = await client.update_session("user-123", "session-123", update_data)
|
|
||||||
|
|
||||||
assert result == mock_updated_session
|
|
||||||
client.patch.assert_called_once_with(
|
|
||||||
"/users/user-123/sessions/session-123",
|
|
||||||
body=update_data,
|
|
||||||
response_model=SessionResponse
|
|
||||||
)
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_delete_session(self):
|
async def test_delete_session(self):
|
||||||
"""Test delete_session calls delete correctly."""
|
"""Test delete_session calls delete correctly."""
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,7 @@ import pytest
|
||||||
from pydantic import ValidationError
|
from pydantic import ValidationError
|
||||||
from app.models.auth import LoginRequest, UserResponse, LoginResponse
|
from app.models.auth import LoginRequest, UserResponse, LoginResponse
|
||||||
from app.models.query import QuestionRequest, BenchQueryRequest, BackendQueryRequest, QueryResponse
|
from app.models.query import QuestionRequest, BenchQueryRequest, BackendQueryRequest, QueryResponse
|
||||||
from app.models.settings import EnvironmentSettings, EnvironmentSettingsUpdate, UserSettingsUpdate
|
from app.models.settings import EnvironmentSettings, UserSettingsUpdate
|
||||||
from app.models.analysis import SessionCreate, SessionUpdate
|
|
||||||
|
|
||||||
|
|
||||||
class TestAuthModels:
|
class TestAuthModels:
|
||||||
|
|
@ -121,34 +120,16 @@ class TestQueryModels:
|
||||||
|
|
||||||
def test_query_response(self):
|
def test_query_response(self):
|
||||||
"""Test QueryResponse model."""
|
"""Test QueryResponse model."""
|
||||||
# Test with RagResponseBenchList (parsed from dict)
|
|
||||||
response = QueryResponse(
|
response = QueryResponse(
|
||||||
request_id="req-123",
|
request_id="req-123",
|
||||||
timestamp="2024-01-01T00:00:00Z",
|
timestamp="2024-01-01T00:00:00Z",
|
||||||
environment="ift",
|
environment="ift",
|
||||||
response={"answers": []} # Auto-parsed to RagResponseBenchList
|
response={"answers": []}
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response.request_id == "req-123"
|
assert response.request_id == "req-123"
|
||||||
assert response.environment == "ift"
|
assert response.environment == "ift"
|
||||||
# When dict with "answers" is provided, it's parsed as RagResponseBenchList
|
|
||||||
from app.models.query import RagResponseBenchList
|
|
||||||
assert isinstance(response.response, RagResponseBenchList)
|
|
||||||
|
|
||||||
def test_query_response_with_dict(self):
|
|
||||||
"""Test QueryResponse model with plain dict (backend mode)."""
|
|
||||||
# Use dict that doesn't match RagResponseBenchList schema
|
|
||||||
response = QueryResponse(
|
|
||||||
request_id="req-456",
|
|
||||||
timestamp="2024-01-01T00:00:00Z",
|
|
||||||
environment="psi",
|
|
||||||
response={"result": "some data", "status": "ok"}
|
|
||||||
)
|
|
||||||
|
|
||||||
assert response.request_id == "req-456"
|
|
||||||
assert response.environment == "psi"
|
|
||||||
assert isinstance(response.response, dict)
|
assert isinstance(response.response, dict)
|
||||||
assert response.response["status"] == "ok"
|
|
||||||
|
|
||||||
|
|
||||||
class TestSettingsModels:
|
class TestSettingsModels:
|
||||||
|
|
@ -173,135 +154,30 @@ class TestSettingsModels:
|
||||||
assert settings.resetSessionMode is False
|
assert settings.resetSessionMode is False
|
||||||
|
|
||||||
def test_environment_settings_defaults(self):
|
def test_environment_settings_defaults(self):
|
||||||
"""Test EnvironmentSettings with default values (nullable fields)."""
|
"""Test EnvironmentSettings with default values."""
|
||||||
settings = EnvironmentSettings()
|
settings = EnvironmentSettings(apiMode="backend")
|
||||||
|
|
||||||
assert settings.apiMode == "bench"
|
assert settings.apiMode == "backend"
|
||||||
assert settings.bearerToken is None
|
assert settings.bearerToken == ""
|
||||||
assert settings.systemPlatform is None
|
|
||||||
assert settings.systemPlatformUser is None
|
|
||||||
assert settings.platformUserId is None
|
|
||||||
assert settings.platformId is None
|
|
||||||
assert settings.withClassify is False
|
assert settings.withClassify is False
|
||||||
assert settings.resetSessionMode is True
|
assert settings.resetSessionMode is True
|
||||||
|
|
||||||
def test_environment_settings_nullable_fields(self):
|
|
||||||
"""Test EnvironmentSettings with explicit None values."""
|
|
||||||
settings = EnvironmentSettings(
|
|
||||||
apiMode="backend",
|
|
||||||
bearerToken=None,
|
|
||||||
systemPlatform=None,
|
|
||||||
withClassify=True
|
|
||||||
)
|
|
||||||
|
|
||||||
assert settings.apiMode == "backend"
|
|
||||||
assert settings.bearerToken is None
|
|
||||||
assert settings.systemPlatform is None
|
|
||||||
assert settings.withClassify is True
|
|
||||||
|
|
||||||
def test_environment_settings_update_partial(self):
|
|
||||||
"""Test EnvironmentSettingsUpdate for partial updates."""
|
|
||||||
update = EnvironmentSettingsUpdate(
|
|
||||||
bearerToken="new-token",
|
|
||||||
withClassify=True
|
|
||||||
)
|
|
||||||
|
|
||||||
assert update.bearerToken == "new-token"
|
|
||||||
assert update.withClassify is True
|
|
||||||
assert update.apiMode is None # Not provided, should be None
|
|
||||||
assert update.systemPlatform is None
|
|
||||||
|
|
||||||
def test_environment_settings_update_all_none(self):
|
|
||||||
"""Test EnvironmentSettingsUpdate with all fields as None."""
|
|
||||||
update = EnvironmentSettingsUpdate()
|
|
||||||
|
|
||||||
assert update.apiMode is None
|
|
||||||
assert update.bearerToken is None
|
|
||||||
assert update.systemPlatform is None
|
|
||||||
assert update.withClassify is None
|
|
||||||
|
|
||||||
def test_user_settings_update(self):
|
def test_user_settings_update(self):
|
||||||
"""Test UserSettingsUpdate model with partial updates."""
|
"""Test UserSettingsUpdate model."""
|
||||||
update = UserSettingsUpdate(
|
update = UserSettingsUpdate(
|
||||||
settings={
|
settings={
|
||||||
"ift": EnvironmentSettingsUpdate(apiMode="bench", withClassify=True),
|
"ift": EnvironmentSettings(apiMode="bench"),
|
||||||
"psi": EnvironmentSettingsUpdate(bearerToken="token123")
|
"psi": EnvironmentSettings(apiMode="backend")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
assert "ift" in update.settings
|
assert "ift" in update.settings
|
||||||
assert "psi" in update.settings
|
assert "psi" in update.settings
|
||||||
assert update.settings["ift"].apiMode == "bench"
|
assert update.settings["ift"].apiMode == "bench"
|
||||||
assert update.settings["ift"].withClassify is True
|
assert update.settings["psi"].apiMode == "backend"
|
||||||
assert update.settings["psi"].bearerToken == "token123"
|
|
||||||
assert update.settings["psi"].apiMode is None # Not provided
|
|
||||||
|
|
||||||
def test_user_settings_update_empty(self):
|
def test_user_settings_update_empty(self):
|
||||||
"""Test UserSettingsUpdate with empty settings."""
|
"""Test UserSettingsUpdate with empty settings."""
|
||||||
update = UserSettingsUpdate(settings={})
|
update = UserSettingsUpdate(settings={})
|
||||||
|
|
||||||
assert update.settings == {}
|
assert update.settings == {}
|
||||||
|
|
||||||
|
|
||||||
class TestAnalysisModels:
|
|
||||||
"""Tests for analysis session models."""
|
|
||||||
|
|
||||||
def test_session_create_valid(self):
|
|
||||||
"""Test valid SessionCreate."""
|
|
||||||
session = SessionCreate(
|
|
||||||
environment="ift",
|
|
||||||
api_mode="bench",
|
|
||||||
request=[{"body": "question 1", "with_docs": True}],
|
|
||||||
response={"answers": ["answer 1"]},
|
|
||||||
annotations={"0": {"rating": "correct"}}
|
|
||||||
)
|
|
||||||
|
|
||||||
assert session.environment == "ift"
|
|
||||||
assert session.api_mode == "bench"
|
|
||||||
assert len(session.request) == 1
|
|
||||||
assert session.annotations == {"0": {"rating": "correct"}}
|
|
||||||
|
|
||||||
def test_session_create_default_annotations(self):
|
|
||||||
"""Test SessionCreate with default empty annotations."""
|
|
||||||
session = SessionCreate(
|
|
||||||
environment="psi",
|
|
||||||
api_mode="backend",
|
|
||||||
request=[],
|
|
||||||
response={}
|
|
||||||
)
|
|
||||||
|
|
||||||
assert session.annotations == {}
|
|
||||||
|
|
||||||
def test_session_update_valid(self):
|
|
||||||
"""Test valid SessionUpdate."""
|
|
||||||
update = SessionUpdate(
|
|
||||||
annotations={
|
|
||||||
"0": {
|
|
||||||
"overall": {
|
|
||||||
"rating": "incorrect",
|
|
||||||
"comment": "Ответ неполный"
|
|
||||||
},
|
|
||||||
"body_research": {
|
|
||||||
"issues": ["missing_info"],
|
|
||||||
"comment": "Не указаны процентные ставки"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"1": {
|
|
||||||
"overall": {
|
|
||||||
"rating": "correct",
|
|
||||||
"comment": "Ответ корректный"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
assert "0" in update.annotations
|
|
||||||
assert "1" in update.annotations
|
|
||||||
assert update.annotations["0"]["overall"]["rating"] == "incorrect"
|
|
||||||
assert update.annotations["1"]["overall"]["rating"] == "correct"
|
|
||||||
|
|
||||||
def test_session_update_empty_annotations(self):
|
|
||||||
"""Test SessionUpdate with empty annotations."""
|
|
||||||
update = SessionUpdate(annotations={})
|
|
||||||
|
|
||||||
assert update.annotations == {}
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import pytest
|
||||||
from unittest.mock import AsyncMock, patch, MagicMock
|
from unittest.mock import AsyncMock, patch, MagicMock
|
||||||
import httpx
|
import httpx
|
||||||
from app.services.rag_service import RagService
|
from app.services.rag_service import RagService
|
||||||
from app.models.query import QuestionRequest, RagResponseBenchList
|
from app.models.query import QuestionRequest
|
||||||
|
|
||||||
|
|
||||||
class TestBenchQueryEndpoint:
|
class TestBenchQueryEndpoint:
|
||||||
|
|
@ -16,9 +16,7 @@ class TestBenchQueryEndpoint:
|
||||||
|
|
||||||
with patch('app.api.v1.query.RagService') as MockRagService:
|
with patch('app.api.v1.query.RagService') as MockRagService:
|
||||||
mock_rag = AsyncMock()
|
mock_rag = AsyncMock()
|
||||||
# Mock возвращает RagResponseBenchList
|
mock_rag.send_bench_query = AsyncMock(return_value=mock_bench_response)
|
||||||
from app.models.query import RagResponseBenchList
|
|
||||||
mock_rag.send_bench_query = AsyncMock(return_value=RagResponseBenchList(**mock_bench_response))
|
|
||||||
mock_rag.close = AsyncMock()
|
mock_rag.close = AsyncMock()
|
||||||
MockRagService.return_value = mock_rag
|
MockRagService.return_value = mock_rag
|
||||||
|
|
||||||
|
|
@ -39,9 +37,7 @@ class TestBenchQueryEndpoint:
|
||||||
assert "timestamp" in data
|
assert "timestamp" in data
|
||||||
assert data["environment"] == "ift"
|
assert data["environment"] == "ift"
|
||||||
assert "response" in data
|
assert "response" in data
|
||||||
# FastAPI автоматически сериализует RagResponseBenchList в dict
|
assert data["response"] == mock_bench_response
|
||||||
assert data["response"]["answers"][0]["question"] == "Test question 1"
|
|
||||||
assert data["response"]["answers"][0]["body_research"] == "Test research answer 1"
|
|
||||||
|
|
||||||
mock_rag.send_bench_query.assert_called_once()
|
mock_rag.send_bench_query.assert_called_once()
|
||||||
mock_rag.close.assert_called_once()
|
mock_rag.close.assert_called_once()
|
||||||
|
|
@ -249,7 +245,7 @@ class TestRagService:
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_send_bench_query_success(self, mock_httpx_client, mock_bench_response):
|
async def test_send_bench_query_success(self, mock_httpx_client, mock_bench_response):
|
||||||
"""Test successful bench query via RagService."""
|
"""Test successful bench query via RagService."""
|
||||||
|
|
||||||
mock_httpx_client.post.return_value.json.return_value = mock_bench_response
|
mock_httpx_client.post.return_value.json.return_value = mock_bench_response
|
||||||
|
|
||||||
with patch('app.services.rag_service.httpx.AsyncClient', return_value=mock_httpx_client):
|
with patch('app.services.rag_service.httpx.AsyncClient', return_value=mock_httpx_client):
|
||||||
|
|
@ -272,15 +268,10 @@ class TestRagService:
|
||||||
request_id="test-request-123"
|
request_id="test-request-123"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Проверяем, что результат - это RagResponseBenchList
|
assert result == mock_bench_response
|
||||||
assert isinstance(result, RagResponseBenchList)
|
|
||||||
assert len(result.answers) == 2
|
|
||||||
assert result.answers[0].question == "Test question 1"
|
|
||||||
assert result.answers[0].body_research == "Test research answer 1"
|
|
||||||
|
|
||||||
mock_httpx_client.post.assert_called_once()
|
mock_httpx_client.post.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
call_kwargs = mock_httpx_client.post.call_args[1]
|
call_kwargs = mock_httpx_client.post.call_args[1]
|
||||||
headers = call_kwargs["headers"]
|
headers = call_kwargs["headers"]
|
||||||
assert headers["Request-Id"] == "test-request-123"
|
assert headers["Request-Id"] == "test-request-123"
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ class TestSettingsEndpoints:
|
||||||
assert response.status_code == 401
|
assert response.status_code == 401
|
||||||
|
|
||||||
def test_update_settings_success(self, client, mock_db_client, test_settings):
|
def test_update_settings_success(self, client, mock_db_client, test_settings):
|
||||||
"""Test updating user settings successfully with PATCH (partial update)."""
|
"""Test updating user settings successfully."""
|
||||||
mock_db_client.update_user_settings = AsyncMock(return_value=test_settings)
|
mock_db_client.update_user_settings = AsyncMock(return_value=test_settings)
|
||||||
|
|
||||||
update_data = {
|
update_data = {
|
||||||
|
|
@ -59,7 +59,7 @@ class TestSettingsEndpoints:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
response = client.patch("/api/v1/settings", json=update_data)
|
response = client.put("/api/v1/settings", json=update_data)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
data = response.json()
|
data = response.json()
|
||||||
|
|
@ -80,7 +80,7 @@ class TestSettingsEndpoints:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
response = client.patch("/api/v1/settings", json=update_data)
|
response = client.put("/api/v1/settings", json=update_data)
|
||||||
|
|
||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
|
|
||||||
|
|
@ -96,7 +96,7 @@ class TestSettingsEndpoints:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
response = client.patch("/api/v1/settings", json=update_data)
|
response = client.put("/api/v1/settings", json=update_data)
|
||||||
|
|
||||||
assert response.status_code == 500
|
assert response.status_code == 500
|
||||||
|
|
||||||
|
|
@ -136,7 +136,7 @@ class TestSettingsEndpoints:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
response = client.patch("/api/v1/settings", json=update_data)
|
response = client.put("/api/v1/settings", json=update_data)
|
||||||
|
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
assert "user not found" in response.json()["detail"].lower()
|
assert "user not found" in response.json()["detail"].lower()
|
||||||
|
|
@ -154,26 +154,7 @@ class TestSettingsEndpoints:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
response = client.patch("/api/v1/settings", json=update_data)
|
response = client.put("/api/v1/settings", json=update_data)
|
||||||
|
|
||||||
assert response.status_code == 502
|
assert response.status_code == 502
|
||||||
assert "failed to update settings" in response.json()["detail"].lower()
|
assert "failed to update settings" in response.json()["detail"].lower()
|
||||||
|
|
||||||
def test_update_settings_partial_update(self, client, mock_db_client, test_settings):
|
|
||||||
"""Test partial update - only updating specific fields."""
|
|
||||||
mock_db_client.update_user_settings = AsyncMock(return_value=test_settings)
|
|
||||||
|
|
||||||
# Partial update - only bearerToken and withClassify for IFT
|
|
||||||
update_data = {
|
|
||||||
"settings": {
|
|
||||||
"ift": {
|
|
||||||
"bearerToken": "updated-token-123",
|
|
||||||
"withClassify": True
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
response = client.patch("/api/v1/settings", json=update_data)
|
|
||||||
|
|
||||||
assert response.status_code == 200
|
|
||||||
mock_db_client.update_user_settings.assert_called_once()
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue