mirror of
https://gitlab.com/MoonTestUse1/AdministrationItDepartmens.git
synced 2025-08-14 00:25:46 +02:00
Создание чата9testt
This commit is contained in:
BIN
backend/.coverage
Normal file
BIN
backend/.coverage
Normal file
Binary file not shown.
@@ -1,4 +1,4 @@
|
||||
"""Admin router"""
|
||||
"""Admin endpoints."""
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import func
|
||||
@@ -19,7 +19,7 @@ def get_statistics(
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get system statistics"""
|
||||
"""Get system statistics."""
|
||||
if not current_user.is_admin:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
@@ -66,7 +66,7 @@ def get_all_requests(
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get all requests (admin only)"""
|
||||
"""Get all requests (admin only)."""
|
||||
if not current_user.is_admin:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
@@ -83,7 +83,7 @@ def update_request_status(
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Update request status (admin only)"""
|
||||
"""Update request status (admin only)."""
|
||||
if not current_user.is_admin:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
@@ -106,7 +106,7 @@ def get_all_users(
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get all users (admin only)"""
|
||||
"""Get all users (admin only)."""
|
||||
if not current_user.is_admin:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
|
@@ -1,4 +1,4 @@
|
||||
"""Authentication router"""
|
||||
"""Authentication endpoints."""
|
||||
from datetime import timedelta
|
||||
from typing import Any
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
@@ -14,6 +14,7 @@ from app.database import get_db
|
||||
from app.core.config import settings
|
||||
from app.models.user import User
|
||||
from app.schemas.token import Token
|
||||
from app.schemas.user import User as UserSchema
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -22,9 +23,7 @@ def login(
|
||||
db: Session = Depends(get_db),
|
||||
form_data: OAuth2PasswordRequestForm = Depends()
|
||||
) -> Any:
|
||||
"""
|
||||
OAuth2 compatible token login, get an access token for future requests
|
||||
"""
|
||||
"""OAuth2 compatible token login, get an access token for future requests."""
|
||||
user = authenticate_user(db, form_data.username, form_data.password)
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
@@ -49,10 +48,8 @@ def login(
|
||||
"token_type": "bearer"
|
||||
}
|
||||
|
||||
@router.get("/me", response_model=Any)
|
||||
@router.get("/me", response_model=UserSchema)
|
||||
def read_users_me(current_user: User = Depends(get_current_user)):
|
||||
"""
|
||||
Get current user.
|
||||
"""
|
||||
"""Get current user."""
|
||||
return current_user
|
||||
|
||||
|
@@ -1,137 +1,74 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, WebSocket, UploadFile, File
|
||||
from fastapi import APIRouter, Depends, HTTPException, WebSocket, status
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
import os
|
||||
import aiofiles
|
||||
import uuid
|
||||
|
||||
from app.database import get_db
|
||||
from app.models.user import User
|
||||
from app.core.auth import get_current_user
|
||||
from app.schemas.chat import Chat, Message, ChatFile
|
||||
from app.websockets.chat import handle_chat_connection
|
||||
from app.core.ws_auth import get_current_user_ws
|
||||
from app.models.user import User
|
||||
from app.schemas.chat import (
|
||||
Chat, ChatCreate,
|
||||
Message, MessageCreate,
|
||||
ChatFile, ChatFileCreate
|
||||
)
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
# Путь для сохранения файлов
|
||||
UPLOAD_DIR = "uploads/chat_files"
|
||||
os.makedirs(UPLOAD_DIR, exist_ok=True)
|
||||
|
||||
@router.websocket("/ws")
|
||||
async def websocket_endpoint(websocket: WebSocket, db: Session = Depends(get_db)):
|
||||
await handle_chat_connection(websocket, db)
|
||||
|
||||
@router.post("/files/")
|
||||
async def upload_file(
|
||||
file: UploadFile = File(...),
|
||||
current_user: User = Depends(get_current_user),
|
||||
async def chat_websocket(
|
||||
websocket: WebSocket,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
user = await get_current_user_ws(websocket, db)
|
||||
if not user:
|
||||
return
|
||||
|
||||
await websocket.accept()
|
||||
try:
|
||||
# Генерируем уникальное имя файла
|
||||
file_extension = os.path.splitext(file.filename)[1]
|
||||
unique_filename = f"{uuid.uuid4()}{file_extension}"
|
||||
file_path = os.path.join(UPLOAD_DIR, unique_filename)
|
||||
|
||||
# Сохраняем файл
|
||||
async with aiofiles.open(file_path, 'wb') as out_file:
|
||||
content = await file.read()
|
||||
await out_file.write(content)
|
||||
|
||||
return {
|
||||
"filename": file.filename,
|
||||
"saved_path": file_path,
|
||||
"size": len(content)
|
||||
}
|
||||
while True:
|
||||
data = await websocket.receive_text()
|
||||
# Обработка сообщений
|
||||
await websocket.send_text(f"Message received: {data}")
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
print(f"Error: {e}")
|
||||
finally:
|
||||
await websocket.close()
|
||||
|
||||
@router.get("/messages/", response_model=List[Message])
|
||||
def get_messages(
|
||||
@router.post("/", response_model=Chat)
|
||||
def create_chat(
|
||||
chat: ChatCreate,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
# Получаем чат пользователя
|
||||
chat = db.query(Chat).filter(
|
||||
(Chat.employee_id == current_user.id) |
|
||||
(Chat.admin_id == current_user.id)
|
||||
).first()
|
||||
"""Create new chat."""
|
||||
# Здесь будет логика создания чата
|
||||
pass
|
||||
|
||||
if not chat:
|
||||
return []
|
||||
@router.get("/", response_model=List[Chat])
|
||||
def get_chats(
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get all chats for current user."""
|
||||
# Здесь будет логика получения чатов
|
||||
pass
|
||||
|
||||
# Получаем сообщения
|
||||
messages = db.query(Message).filter(Message.chat_id == chat.id).all()
|
||||
return messages
|
||||
@router.post("/{chat_id}/messages", response_model=Message)
|
||||
def create_message(
|
||||
chat_id: int,
|
||||
message: MessageCreate,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Create new message in chat."""
|
||||
# Здесь будет логика создания сообщения
|
||||
pass
|
||||
|
||||
@router.get("/messages/{chat_id}/", response_model=List[Message])
|
||||
def get_chat_messages(
|
||||
@router.get("/{chat_id}/messages", response_model=List[Message])
|
||||
def get_messages(
|
||||
chat_id: int,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
# Проверяем доступ к чату
|
||||
chat = db.query(Chat).filter(Chat.id == chat_id).first()
|
||||
if not chat:
|
||||
raise HTTPException(status_code=404, detail="Chat not found")
|
||||
|
||||
if not current_user.is_admin and chat.employee_id != current_user.id:
|
||||
raise HTTPException(status_code=403, detail="Not authorized")
|
||||
|
||||
# Получаем сообщения
|
||||
messages = db.query(Message).filter(Message.chat_id == chat_id).all()
|
||||
return messages
|
||||
|
||||
@router.get("/unread-count/")
|
||||
def get_unread_count(
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
# Получаем чат пользователя
|
||||
chat = db.query(Chat).filter(
|
||||
(Chat.employee_id == current_user.id) |
|
||||
(Chat.admin_id == current_user.id)
|
||||
).first()
|
||||
|
||||
if not chat:
|
||||
return {"unread_count": 0}
|
||||
|
||||
# Считаем непрочитанные сообщения
|
||||
unread_count = db.query(Message).filter(
|
||||
Message.chat_id == chat.id,
|
||||
Message.sender_id != current_user.id,
|
||||
Message.is_read == False
|
||||
).count()
|
||||
|
||||
return {"unread_count": unread_count}
|
||||
|
||||
@router.get("/admin/chats/", response_model=List[Chat])
|
||||
def get_admin_chats(
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
if not current_user.is_admin:
|
||||
raise HTTPException(status_code=403, detail="Not authorized")
|
||||
|
||||
# Получаем все чаты с последними сообщениями и количеством непрочитанных
|
||||
chats = db.query(Chat).all()
|
||||
|
||||
# Для каждого чата добавляем дополнительную информацию
|
||||
for chat in chats:
|
||||
# Последнее сообщение
|
||||
last_message = db.query(Message)\
|
||||
.filter(Message.chat_id == chat.id)\
|
||||
.order_by(Message.created_at.desc())\
|
||||
.first()
|
||||
chat.last_message = last_message
|
||||
|
||||
# Количество непрочитанных сообщений
|
||||
unread_count = db.query(Message)\
|
||||
.filter(
|
||||
Message.chat_id == chat.id,
|
||||
Message.sender_id != current_user.id,
|
||||
Message.is_read == False
|
||||
).count()
|
||||
chat.unread_count = unread_count
|
||||
|
||||
return chats
|
||||
"""Get all messages in chat."""
|
||||
# Здесь будет логика получения сообщений
|
||||
pass
|
@@ -1,52 +1,86 @@
|
||||
"""Employee router"""
|
||||
"""Employee endpoints."""
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
|
||||
from app.core.auth import get_current_user, get_password_hash
|
||||
from app.database import get_db
|
||||
from app.models.user import User
|
||||
from app.schemas.user import UserCreate, User as UserSchema
|
||||
from app.core.auth import get_current_user, get_password_hash
|
||||
from app.schemas.user import User as UserSchema, UserCreate, UserUpdate
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.post("/", response_model=UserSchema)
|
||||
def create_employee(
|
||||
user: UserCreate,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
# Проверяем, не существует ли уже пользователь с таким email
|
||||
"""Create new employee (admin only)."""
|
||||
if not current_user.is_admin:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Not enough permissions"
|
||||
)
|
||||
|
||||
# Проверяем, не существует ли пользователь с таким email
|
||||
db_user = db.query(User).filter(User.email == user.email).first()
|
||||
if db_user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Email already registered"
|
||||
)
|
||||
|
||||
# Создаем нового пользователя
|
||||
|
||||
# Создаем пользователя
|
||||
hashed_password = get_password_hash(user.password)
|
||||
db_user = User(
|
||||
email=user.email,
|
||||
full_name=user.full_name,
|
||||
hashed_password=hashed_password,
|
||||
is_admin=False
|
||||
is_admin=user.is_admin
|
||||
)
|
||||
db.add(db_user)
|
||||
db.commit()
|
||||
db.refresh(db_user)
|
||||
return db_user
|
||||
|
||||
@router.get("/", response_model=List[UserSchema])
|
||||
def read_employees(
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
@router.get("/me", response_model=UserSchema)
|
||||
def get_current_employee(current_user: User = Depends(get_current_user)):
|
||||
"""Get current employee info."""
|
||||
return current_user
|
||||
|
||||
@router.put("/me", response_model=UserSchema)
|
||||
def update_employee_me(
|
||||
user_update: UserUpdate,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Update current employee info."""
|
||||
update_data = user_update.model_dump(exclude_unset=True)
|
||||
if "password" in update_data:
|
||||
update_data["hashed_password"] = get_password_hash(update_data.pop("password"))
|
||||
|
||||
for key, value in update_data.items():
|
||||
setattr(current_user, key, value)
|
||||
|
||||
db.commit()
|
||||
db.refresh(current_user)
|
||||
return current_user
|
||||
|
||||
@router.get("/{employee_id}", response_model=UserSchema)
|
||||
def get_employee(
|
||||
employee_id: int,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get employee by ID (admin only)."""
|
||||
if not current_user.is_admin:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Not enough permissions"
|
||||
)
|
||||
users = db.query(User).filter(User.is_admin == False).offset(skip).limit(limit).all()
|
||||
return users
|
||||
|
||||
employee = db.query(User).filter(User.id == employee_id).first()
|
||||
if not employee:
|
||||
raise HTTPException(status_code=404, detail="Employee not found")
|
||||
return employee
|
@@ -1,13 +1,12 @@
|
||||
"""Requests router"""
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
"""Request endpoints."""
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
|
||||
from app.core.auth import get_current_user
|
||||
from app.database import get_db
|
||||
from app.models.user import User
|
||||
from app.models.request import Request
|
||||
from app.schemas.request import RequestCreate, Request as RequestSchema
|
||||
from app.core.auth import get_current_user
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -17,89 +16,34 @@ def create_request(
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Create new request"""
|
||||
"""Create new request."""
|
||||
db_request = Request(
|
||||
**request.dict(),
|
||||
employee_id=current_user.id,
|
||||
status="new"
|
||||
**request.model_dump()
|
||||
)
|
||||
db.add(db_request)
|
||||
db.commit()
|
||||
db.refresh(db_request)
|
||||
return db_request
|
||||
|
||||
@router.get("/", response_model=List[RequestSchema])
|
||||
def read_requests(
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
@router.get("/my", response_model=List[RequestSchema])
|
||||
def get_my_requests(
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get all requests (admin only)"""
|
||||
if current_user.is_admin:
|
||||
requests = db.query(Request).offset(skip).limit(limit).all()
|
||||
else:
|
||||
requests = db.query(Request).filter(
|
||||
Request.employee_id == current_user.id
|
||||
).offset(skip).limit(limit).all()
|
||||
return requests
|
||||
"""Get current user's requests."""
|
||||
return db.query(Request).filter(Request.employee_id == current_user.id).all()
|
||||
|
||||
@router.get("/{request_id}", response_model=RequestSchema)
|
||||
def read_request(
|
||||
def get_request(
|
||||
request_id: int,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get a specific request"""
|
||||
"""Get request by ID."""
|
||||
request = db.query(Request).filter(Request.id == request_id).first()
|
||||
if not request:
|
||||
raise HTTPException(status_code=404, detail="Request not found")
|
||||
|
||||
# Проверяем права доступа
|
||||
if not current_user.is_admin and request.employee_id != current_user.id:
|
||||
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||
|
||||
return request
|
||||
|
||||
@router.put("/{request_id}", response_model=RequestSchema)
|
||||
def update_request(
|
||||
request_id: int,
|
||||
request_update: RequestCreate,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Update a request"""
|
||||
db_request = db.query(Request).filter(Request.id == request_id).first()
|
||||
if not db_request:
|
||||
raise HTTPException(status_code=404, detail="Request not found")
|
||||
|
||||
# Проверяем права доступа
|
||||
if not current_user.is_admin and db_request.employee_id != current_user.id:
|
||||
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||
|
||||
# Обновляем заявку
|
||||
for key, value in request_update.dict().items():
|
||||
setattr(db_request, key, value)
|
||||
|
||||
db.commit()
|
||||
db.refresh(db_request)
|
||||
return db_request
|
||||
|
||||
@router.delete("/{request_id}", response_model=RequestSchema)
|
||||
def delete_request(
|
||||
request_id: int,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Delete a request"""
|
||||
db_request = db.query(Request).filter(Request.id == request_id).first()
|
||||
if not db_request:
|
||||
raise HTTPException(status_code=404, detail="Request not found")
|
||||
|
||||
# Только админ может удалять заявки
|
||||
if not current_user.is_admin:
|
||||
raise HTTPException(status_code=403, detail="Not enough permissions")
|
||||
|
||||
db.delete(db_request)
|
||||
db.commit()
|
||||
return db_request
|
||||
raise HTTPException(status_code=403, detail="Not authorized to access this request")
|
||||
return request
|
@@ -1,13 +1,13 @@
|
||||
"""Statistics router"""
|
||||
"""Statistics endpoints."""
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import func
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from app.core.auth import get_current_user
|
||||
from app.database import get_db
|
||||
from app.models.user import User
|
||||
from app.models.request import Request
|
||||
from app.core.auth import get_current_user
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -16,6 +16,7 @@ def get_statistics(
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get system statistics (admin only)."""
|
||||
if not current_user.is_admin:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
|
@@ -62,31 +62,4 @@ async def get_current_user(
|
||||
user = db.query(User).filter(User.email == email).first()
|
||||
if user is None:
|
||||
raise credentials_exception
|
||||
return user
|
||||
|
||||
async def get_current_user_ws(websocket: WebSocket, db: Session = Depends(get_db)) -> Optional[User]:
|
||||
try:
|
||||
# Получаем токен из параметров запроса
|
||||
token = websocket.query_params.get("token")
|
||||
if not token:
|
||||
await websocket.close(code=status.WS_1008_POLICY_VIOLATION)
|
||||
return None
|
||||
|
||||
# Проверяем токен
|
||||
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
||||
email: str = payload.get("sub")
|
||||
if email is None:
|
||||
await websocket.close(code=status.WS_1008_POLICY_VIOLATION)
|
||||
return None
|
||||
|
||||
# Получаем пользователя
|
||||
user = db.query(User).filter(User.email == email).first()
|
||||
if user is None:
|
||||
await websocket.close(code=status.WS_1008_POLICY_VIOLATION)
|
||||
return None
|
||||
|
||||
return user
|
||||
|
||||
except JWTError:
|
||||
await websocket.close(code=status.WS_1008_POLICY_VIOLATION)
|
||||
return None
|
||||
return user
|
@@ -5,15 +5,29 @@ from typing import Optional
|
||||
class Settings(BaseSettings):
|
||||
"""Application settings"""
|
||||
PROJECT_NAME: str = "Support System"
|
||||
VERSION: str = "1.0.0"
|
||||
API_V1_STR: str = "/api"
|
||||
SECRET_KEY: str = "your-secret-key-for-jwt" # В продакшене использовать безопасный ключ
|
||||
|
||||
# Security
|
||||
SECRET_KEY: str = "your-secret-key-for-jwt"
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 8 # 8 дней
|
||||
|
||||
# Database
|
||||
POSTGRES_SERVER: str = "db"
|
||||
POSTGRES_USER: str = "postgres"
|
||||
POSTGRES_PASSWORD: str = "postgres"
|
||||
POSTGRES_DB: str = "support_db"
|
||||
SQLALCHEMY_DATABASE_URI: Optional[str] = None
|
||||
DATABASE_URL: Optional[str] = None
|
||||
|
||||
# Redis
|
||||
REDIS_HOST: str = "redis"
|
||||
REDIS_PORT: int = 6379
|
||||
REDIS_DB: int = 0
|
||||
REDIS_PASSWORD: Optional[str] = None
|
||||
|
||||
# Telegram
|
||||
TELEGRAM_BOT_TOKEN: Optional[str] = None
|
||||
TELEGRAM_CHAT_ID: Optional[str] = None
|
||||
|
||||
@property
|
||||
def get_database_url(self) -> str:
|
||||
@@ -23,6 +37,8 @@ class Settings(BaseSettings):
|
||||
class Config:
|
||||
case_sensitive = True
|
||||
env_file = ".env"
|
||||
extra = "allow" # Разрешаем дополнительные поля
|
||||
|
||||
settings = Settings()
|
||||
settings.SQLALCHEMY_DATABASE_URI = settings.get_database_url
|
||||
if not settings.DATABASE_URL:
|
||||
settings.DATABASE_URL = settings.get_database_url
|
10
backend/app/core/redis.py
Normal file
10
backend/app/core/redis.py
Normal file
@@ -0,0 +1,10 @@
|
||||
import redis
|
||||
from app.core.config import settings
|
||||
|
||||
redis_client = redis.Redis(
|
||||
host=settings.REDIS_HOST,
|
||||
port=settings.REDIS_PORT,
|
||||
db=settings.REDIS_DB,
|
||||
password=settings.REDIS_PASSWORD,
|
||||
decode_responses=True
|
||||
)
|
34
backend/app/core/ws_auth.py
Normal file
34
backend/app/core/ws_auth.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from typing import Optional
|
||||
from fastapi import WebSocket, status
|
||||
from jose import JWTError, jwt
|
||||
from sqlalchemy.orm import Session
|
||||
from app.core.config import settings
|
||||
from app.models.user import User
|
||||
|
||||
async def get_current_user_ws(websocket: WebSocket, db: Session) -> Optional[User]:
|
||||
"""Get current user from WebSocket connection."""
|
||||
try:
|
||||
# Получаем токен из параметров запроса
|
||||
token = websocket.query_params.get("token")
|
||||
if not token:
|
||||
await websocket.close(code=status.WS_1008_POLICY_VIOLATION)
|
||||
return None
|
||||
|
||||
# Проверяем токен
|
||||
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
|
||||
email: str = payload.get("sub")
|
||||
if email is None:
|
||||
await websocket.close(code=status.WS_1008_POLICY_VIOLATION)
|
||||
return None
|
||||
|
||||
# Получаем пользователя
|
||||
user = db.query(User).filter(User.email == email).first()
|
||||
if user is None:
|
||||
await websocket.close(code=status.WS_1008_POLICY_VIOLATION)
|
||||
return None
|
||||
|
||||
return user
|
||||
|
||||
except JWTError:
|
||||
await websocket.close(code=status.WS_1008_POLICY_VIOLATION)
|
||||
return None
|
@@ -1,20 +1,21 @@
|
||||
"""Database configuration"""
|
||||
"""Database configuration."""
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from .core.config import settings
|
||||
from .db.base import Base
|
||||
from app.core.config import settings
|
||||
|
||||
# Для создания таблиц импортируем модели
|
||||
from .models.employee import Employee # noqa
|
||||
from .models.request import Request # noqa
|
||||
from .models.token import Token # noqa
|
||||
|
||||
SQLALCHEMY_DATABASE_URL = settings.DATABASE_URL
|
||||
|
||||
engine = create_engine(SQLALCHEMY_DATABASE_URL)
|
||||
engine = create_engine(settings.DATABASE_URL)
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
# Import all models here
|
||||
from app.models.user import User # noqa
|
||||
from app.models.request import Request # noqa
|
||||
from app.models.chat import Chat, Message, ChatFile # noqa
|
||||
|
||||
def get_db():
|
||||
"""Get database session."""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
yield db
|
||||
|
@@ -1,4 +1,4 @@
|
||||
"""Main application module"""
|
||||
"""Main application module."""
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from app.core.config import settings
|
||||
@@ -6,6 +6,7 @@ from app.api.endpoints import admin, employees, requests, auth, statistics, chat
|
||||
|
||||
app = FastAPI(
|
||||
title=settings.PROJECT_NAME,
|
||||
version=settings.VERSION,
|
||||
openapi_url=f"{settings.API_V1_STR}/openapi.json"
|
||||
)
|
||||
|
||||
@@ -28,4 +29,9 @@ app.include_router(chat.router, prefix=f"{settings.API_V1_STR}/chat", tags=["cha
|
||||
|
||||
@app.get("/")
|
||||
def read_root():
|
||||
return {"message": "Welcome to Support System API"}
|
||||
"""Root endpoint."""
|
||||
return {
|
||||
"message": "Welcome to Support System API",
|
||||
"version": settings.VERSION,
|
||||
"docs_url": "/docs"
|
||||
}
|
@@ -1,45 +1,46 @@
|
||||
"""Chat models."""
|
||||
from sqlalchemy import Column, Integer, String, ForeignKey, DateTime, Boolean, Text
|
||||
from sqlalchemy.sql import func
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from app.database import Base
|
||||
|
||||
class Chat(Base):
|
||||
"""Chat model."""
|
||||
__tablename__ = "chats"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
employee_id = Column(Integer, ForeignKey("users.id"), nullable=False, unique=True)
|
||||
admin_id = Column(Integer, ForeignKey("users.id"), nullable=False)
|
||||
employee_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False)
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
||||
|
||||
# Отношения
|
||||
employee = relationship("User", foreign_keys=[employee_id], back_populates="employee_chats")
|
||||
admin = relationship("User", foreign_keys=[admin_id], back_populates="admin_chats")
|
||||
employee = relationship("User", back_populates="chats")
|
||||
messages = relationship("Message", back_populates="chat", cascade="all, delete-orphan")
|
||||
|
||||
class Message(Base):
|
||||
"""Message model."""
|
||||
__tablename__ = "messages"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
chat_id = Column(Integer, ForeignKey("chats.id", ondelete="CASCADE"), nullable=False)
|
||||
sender_id = Column(Integer, ForeignKey("users.id"), nullable=False)
|
||||
sender_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False)
|
||||
content = Column(Text, nullable=False)
|
||||
is_read = Column(Boolean, default=False)
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
# Отношения
|
||||
chat = relationship("Chat", back_populates="messages")
|
||||
sender = relationship("User", back_populates="sent_messages")
|
||||
sender = relationship("User", back_populates="messages")
|
||||
files = relationship("ChatFile", back_populates="message", cascade="all, delete-orphan")
|
||||
|
||||
class ChatFile(Base):
|
||||
"""Chat file model."""
|
||||
__tablename__ = "chat_files"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
message_id = Column(Integer, ForeignKey("messages.id", ondelete="CASCADE"), nullable=False)
|
||||
file_name = Column(String(255), nullable=False)
|
||||
file_path = Column(String(255), nullable=False)
|
||||
file_size = Column(Integer, nullable=False)
|
||||
filename = Column(String, nullable=False)
|
||||
file_path = Column(String, nullable=False)
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
# Отношения
|
||||
|
@@ -1,18 +1,20 @@
|
||||
"""Request model"""
|
||||
from sqlalchemy import Column, Integer, String, ForeignKey, DateTime, Text
|
||||
"""Request model."""
|
||||
from sqlalchemy import Column, Integer, String, ForeignKey, DateTime, Text, Enum
|
||||
from sqlalchemy.sql import func
|
||||
from sqlalchemy.orm import relationship
|
||||
from app.db.base_class import Base
|
||||
from app.database import Base
|
||||
from app.schemas.request import RequestPriority, RequestStatus
|
||||
|
||||
class Request(Base):
|
||||
"""Request model."""
|
||||
__tablename__ = "requests"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
employee_id = Column(Integer, ForeignKey("users.id"), nullable=False)
|
||||
employee_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False)
|
||||
request_type = Column(String, nullable=False)
|
||||
description = Column(Text, nullable=False)
|
||||
priority = Column(String, nullable=False)
|
||||
status = Column(String, default="new")
|
||||
priority = Column(Enum(RequestPriority), nullable=False)
|
||||
status = Column(Enum(RequestStatus), default=RequestStatus.NEW)
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
||||
|
||||
|
@@ -1,9 +1,10 @@
|
||||
"""User model."""
|
||||
from sqlalchemy import Boolean, Column, Integer, String
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from app.database import Base
|
||||
|
||||
class User(Base):
|
||||
"""User model."""
|
||||
__tablename__ = "users"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
@@ -13,10 +14,7 @@ class User(Base):
|
||||
is_active = Column(Boolean, default=True)
|
||||
is_admin = Column(Boolean, default=False)
|
||||
|
||||
# Отношения для чата
|
||||
employee_chats = relationship("Chat", foreign_keys="[Chat.employee_id]", back_populates="employee")
|
||||
admin_chats = relationship("Chat", foreign_keys="[Chat.admin_id]", back_populates="admin")
|
||||
sent_messages = relationship("Message", back_populates="sender")
|
||||
|
||||
# Отношения для заявок
|
||||
requests = relationship("Request", back_populates="employee")
|
||||
# Отношения
|
||||
requests = relationship("Request", back_populates="employee")
|
||||
chats = relationship("Chat", back_populates="employee")
|
||||
messages = relationship("Message", back_populates="sender")
|
@@ -1,74 +1,54 @@
|
||||
"""Chat schemas"""
|
||||
"""Chat schemas."""
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
from datetime import datetime
|
||||
from typing import Optional, List
|
||||
from app.models.user import User
|
||||
|
||||
class ChatBase(BaseModel):
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
employee_id: int
|
||||
|
||||
class ChatCreate(ChatBase):
|
||||
pass
|
||||
|
||||
class MessageBase(BaseModel):
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
content: str
|
||||
chat_id: int
|
||||
sender_id: int
|
||||
|
||||
class MessageCreate(MessageBase):
|
||||
pass
|
||||
|
||||
class ChatFileBase(BaseModel):
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
"""Chat file base schema."""
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
filename: str
|
||||
file_path: str
|
||||
message_id: int
|
||||
|
||||
class ChatFileCreate(ChatFileBase):
|
||||
pass
|
||||
"""Chat file create schema."""
|
||||
message_id: int
|
||||
|
||||
class ChatFile(ChatFileBase):
|
||||
"""Chat file schema."""
|
||||
id: int
|
||||
message_id: int
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
class MessageBase(BaseModel):
|
||||
"""Message base schema."""
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
content: str
|
||||
|
||||
class MessageCreate(MessageBase):
|
||||
"""Message create schema."""
|
||||
chat_id: int
|
||||
|
||||
class Message(MessageBase):
|
||||
"""Message schema."""
|
||||
id: int
|
||||
chat_id: int
|
||||
sender_id: int
|
||||
is_read: bool
|
||||
created_at: datetime
|
||||
files: List[ChatFile] = []
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
class ChatBase(BaseModel):
|
||||
"""Chat base schema."""
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
employee_id: int
|
||||
|
||||
class ChatCreate(ChatBase):
|
||||
"""Chat create schema."""
|
||||
pass
|
||||
|
||||
class Chat(ChatBase):
|
||||
"""Chat schema."""
|
||||
id: int
|
||||
employee: User
|
||||
messages: List[Message] = []
|
||||
created_at: datetime
|
||||
updated_at: Optional[datetime] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
# Схемы для WebSocket сообщений
|
||||
class WSMessage(BaseModel):
|
||||
type: str
|
||||
content: Optional[str] = None
|
||||
message_ids: Optional[List[int]] = None
|
||||
files: Optional[List[dict]] = None
|
||||
|
||||
class WSResponse(BaseModel):
|
||||
type: str
|
||||
id: Optional[int] = None
|
||||
sender_id: Optional[int] = None
|
||||
content: Optional[str] = None
|
||||
created_at: Optional[datetime] = None
|
||||
is_read: Optional[bool] = None
|
||||
message_ids: Optional[List[int]] = None
|
||||
files: Optional[List[ChatFile]] = None
|
||||
error: Optional[str] = None
|
||||
messages: List[Message] = []
|
@@ -1,27 +1,42 @@
|
||||
"""Request schemas"""
|
||||
"""Request schemas."""
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from enum import Enum
|
||||
|
||||
class RequestPriority(str, Enum):
|
||||
"""Request priority enum."""
|
||||
LOW = "low"
|
||||
MEDIUM = "medium"
|
||||
HIGH = "high"
|
||||
|
||||
class RequestStatus(str, Enum):
|
||||
"""Request status enum."""
|
||||
NEW = "new"
|
||||
IN_PROGRESS = "in_progress"
|
||||
COMPLETED = "completed"
|
||||
REJECTED = "rejected"
|
||||
|
||||
class RequestBase(BaseModel):
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
"""Request base schema."""
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
request_type: str
|
||||
description: str
|
||||
priority: str
|
||||
priority: RequestPriority
|
||||
|
||||
class RequestCreate(RequestBase):
|
||||
"""Request create schema."""
|
||||
pass
|
||||
|
||||
class RequestUpdate(BaseModel):
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
status: str
|
||||
"""Request update schema."""
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
status: RequestStatus
|
||||
|
||||
class Request(RequestBase):
|
||||
"""Request schema."""
|
||||
id: int
|
||||
employee_id: int
|
||||
status: str
|
||||
status: RequestStatus
|
||||
created_at: datetime
|
||||
updated_at: Optional[datetime] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
updated_at: Optional[datetime] = None
|
@@ -1,13 +1,7 @@
|
||||
"""Token schemas"""
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
"""Token schemas."""
|
||||
from pydantic import BaseModel
|
||||
|
||||
class Token(BaseModel):
|
||||
"""Token schema."""
|
||||
access_token: str
|
||||
token_type: str
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
class TokenData(BaseModel):
|
||||
user_id: int | None = None
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
token_type: str
|
@@ -1,19 +1,26 @@
|
||||
"""User schemas"""
|
||||
"""User schemas."""
|
||||
from pydantic import BaseModel, EmailStr, ConfigDict
|
||||
from typing import Optional
|
||||
|
||||
class UserBase(BaseModel):
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
"""User base schema."""
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
email: EmailStr
|
||||
full_name: str
|
||||
is_admin: bool = False
|
||||
|
||||
class UserCreate(UserBase):
|
||||
"""User create schema."""
|
||||
password: str
|
||||
|
||||
class User(UserBase):
|
||||
id: int
|
||||
is_active: bool = True
|
||||
class UserUpdate(BaseModel):
|
||||
"""User update schema."""
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
email: Optional[EmailStr] = None
|
||||
full_name: Optional[str] = None
|
||||
password: Optional[str] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
class User(UserBase):
|
||||
"""User schema."""
|
||||
id: int
|
||||
is_active: bool = True
|
@@ -1,7 +1,7 @@
|
||||
[pytest]
|
||||
pythonpath = .
|
||||
testpaths = tests
|
||||
python_files = test_*.py
|
||||
python_classes = Test*
|
||||
python_functions = test_*
|
||||
addopts = -v --tb=short
|
||||
addopts = -v --cov=app --cov-report=term-missing
|
||||
asyncio_mode = auto
|
||||
|
@@ -12,6 +12,8 @@ python-dotenv==1.0.1
|
||||
psycopg2-binary==2.9.9
|
||||
alembic==1.13.1
|
||||
pytest==8.0.0
|
||||
pytest-cov==4.1.0
|
||||
pytest-asyncio==0.23.5
|
||||
httpx==0.26.0
|
||||
requests>=2.26.0
|
||||
aiogram==3.4.1
|
||||
|
BIN
backend/test.db
BIN
backend/test.db
Binary file not shown.
@@ -1,101 +1,123 @@
|
||||
"""Test configuration."""
|
||||
import pytest
|
||||
from typing import Generator, Dict
|
||||
from fastapi.testclient import TestClient
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from unittest.mock import MagicMock
|
||||
from app.db.base import Base
|
||||
from app.database import get_db
|
||||
from app.database import Base, get_db
|
||||
from app.main import app
|
||||
from app.utils.jwt import create_and_save_token, redis
|
||||
from app.crud import employees
|
||||
from app.utils.auth import get_password_hash
|
||||
from app.models.token import Token
|
||||
from app.models.employee import Employee
|
||||
from app.models.request import Request
|
||||
from app.schemas.employee import EmployeeCreate
|
||||
from app.core.config import settings
|
||||
from app.models.user import User
|
||||
from app.core.auth import get_password_hash
|
||||
|
||||
# Используем SQLite для тестов
|
||||
# Создаем тестовую базу данных
|
||||
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
|
||||
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
|
||||
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
# Создаем мок для Redis
|
||||
class RedisMock:
|
||||
# Создаем фиктивный Redis для тестов
|
||||
class FakeRedis:
|
||||
"""Fake Redis for testing."""
|
||||
def __init__(self):
|
||||
self.data = {}
|
||||
|
||||
def setex(self, name, time, value):
|
||||
self.data[name] = value
|
||||
return True
|
||||
def get(self, key):
|
||||
return self.data.get(key)
|
||||
|
||||
def get(self, name):
|
||||
return self.data.get(name)
|
||||
def set(self, key, value, ex=None):
|
||||
self.data[key] = value
|
||||
|
||||
def delete(self, name):
|
||||
if name in self.data:
|
||||
del self.data[name]
|
||||
return True
|
||||
def delete(self, key):
|
||||
if key in self.data:
|
||||
del self.data[key]
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_redis(monkeypatch):
|
||||
redis_mock = RedisMock()
|
||||
monkeypatch.setattr("app.utils.jwt.redis", redis_mock)
|
||||
return redis_mock
|
||||
@pytest.fixture(scope="session")
|
||||
def redis():
|
||||
"""Redis fixture."""
|
||||
return FakeRedis()
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def test_db():
|
||||
# Удаляем все таблицы
|
||||
Base.metadata.drop_all(bind=engine)
|
||||
# Создаем все таблицы заново
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
# Создаем сессию
|
||||
db = TestingSessionLocal()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def test_employee(test_db):
|
||||
hashed_password = get_password_hash("testpass123")
|
||||
employee_data = EmployeeCreate(
|
||||
first_name="Test",
|
||||
last_name="User",
|
||||
department="IT",
|
||||
office="101",
|
||||
password="testpass123"
|
||||
)
|
||||
employee = employees.create_employee(test_db, employee_data, hashed_password)
|
||||
return employee
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def test_token(test_db, test_employee):
|
||||
token = create_and_save_token(test_employee.id, test_db)
|
||||
return token
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def test_auth_header(test_token):
|
||||
return {"Authorization": f"Bearer {test_token}"}
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def admin_token(test_db):
|
||||
token = create_and_save_token(-1, test_db) # -1 для админа
|
||||
return token
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def admin_auth_header(admin_token):
|
||||
return {"Authorization": f"Bearer {admin_token}"}
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def test_employee_id(test_employee):
|
||||
return test_employee.id
|
||||
|
||||
# Переопределяем зависимость для получения БД
|
||||
def override_get_db():
|
||||
db = TestingSessionLocal()
|
||||
"""Override get_db for testing."""
|
||||
try:
|
||||
db = TestingSessionLocal()
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
app.dependency_overrides[get_db] = override_get_db
|
||||
app.dependency_overrides[get_db] = override_get_db
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
def setup_database():
|
||||
"""Setup database for testing."""
|
||||
# Создаем все таблицы
|
||||
Base.metadata.create_all(bind=engine)
|
||||
yield
|
||||
# Удаляем все таблицы после тестов
|
||||
Base.metadata.drop_all(bind=engine)
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def db() -> Generator:
|
||||
"""Database fixture."""
|
||||
connection = engine.connect()
|
||||
transaction = connection.begin()
|
||||
session = TestingSessionLocal(bind=connection)
|
||||
|
||||
yield session
|
||||
|
||||
session.close()
|
||||
transaction.rollback()
|
||||
connection.close()
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def client(db) -> Generator:
|
||||
"""Test client fixture."""
|
||||
def override_get_db():
|
||||
yield db
|
||||
|
||||
app.dependency_overrides[get_db] = override_get_db
|
||||
with TestClient(app) as c:
|
||||
yield c
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def test_user(db) -> Dict[str, str]:
|
||||
"""Test user fixture."""
|
||||
user_data = {
|
||||
"email": "test@example.com",
|
||||
"password": "test123",
|
||||
"full_name": "Test User",
|
||||
"is_admin": False
|
||||
}
|
||||
|
||||
user = User(
|
||||
email=user_data["email"],
|
||||
hashed_password=get_password_hash(user_data["password"]),
|
||||
full_name=user_data["full_name"],
|
||||
is_admin=user_data["is_admin"]
|
||||
)
|
||||
db.add(user)
|
||||
db.commit()
|
||||
db.refresh(user)
|
||||
|
||||
return user_data
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def test_admin(db) -> Dict[str, str]:
|
||||
"""Test admin fixture."""
|
||||
admin_data = {
|
||||
"email": "admin@example.com",
|
||||
"password": "admin123",
|
||||
"full_name": "Admin User",
|
||||
"is_admin": True
|
||||
}
|
||||
|
||||
admin = User(
|
||||
email=admin_data["email"],
|
||||
hashed_password=get_password_hash(admin_data["password"]),
|
||||
full_name=admin_data["full_name"],
|
||||
is_admin=admin_data["is_admin"]
|
||||
)
|
||||
db.add(admin)
|
||||
db.commit()
|
||||
db.refresh(admin)
|
||||
|
||||
return admin_data
|
@@ -5,94 +5,87 @@ from app.main import app
|
||||
from app.crud import employees
|
||||
from app.utils.auth import verify_password, get_password_hash
|
||||
from app.schemas.employee import EmployeeCreate
|
||||
from app.core.auth import create_access_token
|
||||
from app.core.redis import redis_client
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
def test_login_success(test_db: Session):
|
||||
# Создаем тестового сотрудника
|
||||
hashed_password = get_password_hash("testpass123")
|
||||
employee_data = EmployeeCreate(
|
||||
first_name="Test",
|
||||
last_name="User",
|
||||
department="IT",
|
||||
office="101",
|
||||
password="testpass123"
|
||||
)
|
||||
employee = employees.create_employee(test_db, employee_data, hashed_password)
|
||||
|
||||
# Переопределяем redis_client для тестов
|
||||
def pytest_configure(config):
|
||||
from app.core import redis
|
||||
redis.redis_client = config.getoption("--redis", default=None)
|
||||
|
||||
pytestmark = pytest.mark.asyncio
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_login_success(client: TestClient, test_user: dict, redis):
|
||||
"""Test successful login"""
|
||||
response = client.post(
|
||||
"/api/auth/login",
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
||||
data={
|
||||
"username": "User",
|
||||
"password": "testpass123"
|
||||
"username": test_user["email"],
|
||||
"password": test_user["password"]
|
||||
}
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "access_token" in response.json()
|
||||
assert response.json()["token_type"] == "bearer"
|
||||
|
||||
def test_login_wrong_password(test_db: Session):
|
||||
# Создаем тестового сотрудника
|
||||
hashed_password = get_password_hash("testpass123")
|
||||
employee_data = EmployeeCreate(
|
||||
first_name="Test",
|
||||
last_name="User",
|
||||
department="IT",
|
||||
office="101",
|
||||
password="testpass123"
|
||||
)
|
||||
employees.create_employee(test_db, employee_data, hashed_password)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_login_wrong_password(client: TestClient, test_user: dict, redis):
|
||||
"""Test login with wrong password"""
|
||||
response = client.post(
|
||||
"/api/auth/login",
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
||||
data={
|
||||
"username": "User",
|
||||
"password": "wrongpass"
|
||||
"username": test_user["email"],
|
||||
"password": "wrongpassword"
|
||||
}
|
||||
)
|
||||
|
||||
assert response.status_code == 401
|
||||
assert "detail" in response.json()
|
||||
assert response.json()["detail"] == "Incorrect email or password"
|
||||
|
||||
def test_login_nonexistent_user(test_db: Session):
|
||||
@pytest.mark.asyncio
|
||||
async def test_login_wrong_email(client: TestClient, redis):
|
||||
"""Test login with wrong email"""
|
||||
response = client.post(
|
||||
"/api/auth/login",
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
||||
data={
|
||||
"username": "NonExistent",
|
||||
"password": "testpass123"
|
||||
"username": "wrong@example.com",
|
||||
"password": "test123"
|
||||
}
|
||||
)
|
||||
|
||||
assert response.status_code == 401
|
||||
assert "detail" in response.json()
|
||||
assert response.json()["detail"] == "Incorrect email or password"
|
||||
|
||||
def test_admin_login_success():
|
||||
response = client.post(
|
||||
"/api/auth/admin/login",
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
||||
data={
|
||||
"username": "admin",
|
||||
"password": "admin123"
|
||||
}
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_current_user(client: TestClient, test_user: dict, redis):
|
||||
"""Test getting current user info"""
|
||||
access_token = create_access_token(
|
||||
data={"sub": test_user["email"], "is_admin": False}
|
||||
)
|
||||
|
||||
response = client.get(
|
||||
"/api/auth/me",
|
||||
headers={"Authorization": f"Bearer {access_token}"}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert "access_token" in response.json()
|
||||
assert response.json()["token_type"] == "bearer"
|
||||
assert response.json()["email"] == test_user["email"]
|
||||
assert response.json()["full_name"] == test_user["full_name"]
|
||||
assert not response.json()["is_admin"]
|
||||
|
||||
def test_admin_login_wrong_password():
|
||||
response = client.post(
|
||||
"/api/auth/admin/login",
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
||||
data={
|
||||
"username": "admin",
|
||||
"password": "wrongpass"
|
||||
}
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_current_user_no_token(client: TestClient, redis):
|
||||
"""Test getting current user without token"""
|
||||
response = client.get("/api/auth/me")
|
||||
assert response.status_code == 401
|
||||
assert "detail" in response.json()
|
||||
assert response.json()["detail"] == "Not authenticated"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_current_user_invalid_token(client: TestClient, redis):
|
||||
"""Test getting current user with invalid token"""
|
||||
response = client.get(
|
||||
"/api/auth/me",
|
||||
headers={"Authorization": "Bearer invalid_token"}
|
||||
)
|
||||
assert response.status_code == 401
|
||||
assert response.json()["detail"] == "Could not validate credentials"
|
@@ -1,117 +1,93 @@
|
||||
"""Employee tests."""
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from sqlalchemy.orm import Session
|
||||
from app.main import app
|
||||
from app.crud import employees
|
||||
from app.utils.auth import get_password_hash
|
||||
from app.schemas.employee import EmployeeCreate
|
||||
from app.core.auth import create_access_token
|
||||
from app.models.user import User
|
||||
|
||||
client = TestClient(app)
|
||||
pytestmark = pytest.mark.asyncio
|
||||
|
||||
def test_create_employee(test_db: Session, admin_auth_header):
|
||||
"""Test creating a new employee"""
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_employee(client: TestClient, test_admin: dict, db: Session):
|
||||
"""Test creating a new employee."""
|
||||
access_token = create_access_token(
|
||||
data={"sub": test_admin["email"], "is_admin": True}
|
||||
)
|
||||
|
||||
employee_data = {
|
||||
"first_name": "John",
|
||||
"last_name": "Doe",
|
||||
"department": "IT",
|
||||
"office": "B205",
|
||||
"password": "test123"
|
||||
"email": "new@example.com",
|
||||
"password": "newpass123",
|
||||
"full_name": "New Employee",
|
||||
"is_admin": False
|
||||
}
|
||||
|
||||
response = client.post(
|
||||
"/api/employees/",
|
||||
json=employee_data,
|
||||
headers=admin_auth_header
|
||||
headers={"Authorization": f"Bearer {access_token}"},
|
||||
json=employee_data
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["email"] == employee_data["email"]
|
||||
assert data["full_name"] == employee_data["full_name"]
|
||||
assert not data["is_admin"]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_employee_unauthorized(client: TestClient, test_user: dict):
|
||||
"""Test creating an employee without admin rights."""
|
||||
access_token = create_access_token(
|
||||
data={"sub": test_user["email"], "is_admin": False}
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["first_name"] == employee_data["first_name"]
|
||||
assert data["last_name"] == employee_data["last_name"]
|
||||
assert data["department"] == employee_data["department"]
|
||||
assert data["office"] == employee_data["office"]
|
||||
assert "password" not in data
|
||||
|
||||
def test_get_employees(test_db: Session, test_employee, admin_auth_header):
|
||||
"""Test getting list of employees"""
|
||||
response = client.get("/api/employees/", headers=admin_auth_header)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert len(data) >= 1
|
||||
assert data[0]["first_name"] == test_employee.first_name
|
||||
assert data[0]["last_name"] == test_employee.last_name
|
||||
assert data[0]["department"] == test_employee.department
|
||||
assert data[0]["office"] == test_employee.office
|
||||
assert "password" not in data[0]
|
||||
|
||||
def test_create_employee_unauthorized(test_db: Session):
|
||||
"""Test creating employee without authorization"""
|
||||
employee_data = {
|
||||
"first_name": "John",
|
||||
"last_name": "Doe",
|
||||
"department": "IT",
|
||||
"office": "B205",
|
||||
"password": "test123"
|
||||
"email": "new@example.com",
|
||||
"password": "newpass123",
|
||||
"full_name": "New Employee",
|
||||
"is_admin": False
|
||||
}
|
||||
response = client.post("/api/employees/", json=employee_data)
|
||||
assert response.status_code == 401 # Unauthorized
|
||||
|
||||
response = client.post(
|
||||
"/api/employees/",
|
||||
headers={"Authorization": f"Bearer {access_token}"},
|
||||
json=employee_data
|
||||
)
|
||||
assert response.status_code == 403
|
||||
|
||||
def test_get_employees_unauthorized(test_db: Session):
|
||||
"""Test getting employees list without authorization"""
|
||||
response = client.get("/api/employees/")
|
||||
assert response.status_code == 401 # Unauthorized
|
||||
|
||||
def test_get_employee_by_id(test_db: Session, test_employee, admin_auth_header):
|
||||
"""Test getting employee by ID"""
|
||||
response = client.get(
|
||||
f"/api/employees/{test_employee.id}",
|
||||
headers=admin_auth_header
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_employee(client: TestClient, test_admin: dict, test_user: dict, db: Session):
|
||||
"""Test getting an employee by ID."""
|
||||
access_token = create_access_token(
|
||||
data={"sub": test_admin["email"], "is_admin": True}
|
||||
)
|
||||
|
||||
# Получаем ID тестового пользователя
|
||||
user = db.query(User).filter(User.email == test_user["email"]).first()
|
||||
|
||||
response = client.get(
|
||||
f"/api/employees/{user.id}",
|
||||
headers={"Authorization": f"Bearer {access_token}"}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["first_name"] == test_employee.first_name
|
||||
assert data["last_name"] == test_employee.last_name
|
||||
assert data["department"] == test_employee.department
|
||||
assert data["office"] == test_employee.office
|
||||
assert "password" not in data
|
||||
assert data["email"] == test_user["email"]
|
||||
assert data["full_name"] == test_user["full_name"]
|
||||
|
||||
def test_update_employee(test_db: Session, test_employee, admin_auth_header):
|
||||
"""Test updating employee data"""
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_employee_me(client: TestClient, test_user: dict):
|
||||
"""Test updating current employee info."""
|
||||
access_token = create_access_token(
|
||||
data={"sub": test_user["email"], "is_admin": False}
|
||||
)
|
||||
|
||||
update_data = {
|
||||
"first_name": "Updated",
|
||||
"last_name": "Name",
|
||||
"department": "HR",
|
||||
"office": "B202"
|
||||
"full_name": "Updated Name"
|
||||
}
|
||||
|
||||
response = client.put(
|
||||
f"/api/employees/{test_employee.id}",
|
||||
json=update_data,
|
||||
headers=admin_auth_header
|
||||
"/api/employees/me",
|
||||
headers={"Authorization": f"Bearer {access_token}"},
|
||||
json=update_data
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["first_name"] == update_data["first_name"]
|
||||
assert data["last_name"] == update_data["last_name"]
|
||||
assert data["department"] == update_data["department"]
|
||||
assert data["office"] == update_data["office"]
|
||||
assert "password" not in data
|
||||
|
||||
def test_delete_employee(test_db: Session, test_employee, admin_auth_header):
|
||||
"""Test deleting employee"""
|
||||
response = client.delete(
|
||||
f"/api/employees/{test_employee.id}",
|
||||
headers=admin_auth_header
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
# Verify employee is deleted
|
||||
get_response = client.get(
|
||||
f"/api/employees/{test_employee.id}",
|
||||
headers=admin_auth_header
|
||||
)
|
||||
assert get_response.status_code == 404
|
||||
assert data["full_name"] == update_data["full_name"]
|
@@ -1,164 +1,116 @@
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from sqlalchemy.orm import Session
|
||||
from app.main import app
|
||||
from app.models.request import RequestStatus, RequestPriority
|
||||
from app.crud import requests
|
||||
from app.schemas.request import RequestCreate
|
||||
from app.core.auth import create_access_token
|
||||
|
||||
client = TestClient(app)
|
||||
pytestmark = pytest.mark.asyncio
|
||||
|
||||
def test_create_request(test_db: Session, test_employee, test_auth_header):
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_request(client: TestClient, test_user: dict, redis):
|
||||
"""Test creating a new request"""
|
||||
access_token = create_access_token(
|
||||
data={"sub": test_user["email"], "is_admin": False}
|
||||
)
|
||||
|
||||
request_data = {
|
||||
"department": "IT",
|
||||
"request_type": "hardware",
|
||||
"description": "This is a test request",
|
||||
"priority": RequestPriority.MEDIUM.value
|
||||
"request_type": "technical",
|
||||
"description": "Test request",
|
||||
"priority": "medium"
|
||||
}
|
||||
|
||||
response = client.post(
|
||||
"/api/requests/",
|
||||
json=request_data,
|
||||
headers=test_auth_header
|
||||
headers={"Authorization": f"Bearer {access_token}"},
|
||||
json=request_data
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["department"] == request_data["department"]
|
||||
assert data["request_type"] == request_data["request_type"]
|
||||
assert data["description"] == request_data["description"]
|
||||
assert data["priority"] == request_data["priority"]
|
||||
assert data["status"] == RequestStatus.NEW.value
|
||||
assert "employee_id" in data
|
||||
assert data["status"] == "new"
|
||||
|
||||
def test_get_employee_requests(test_db: Session, test_employee, test_auth_header):
|
||||
"""Test getting employee's requests"""
|
||||
# Создаем тестовую заявку
|
||||
request_data = RequestCreate(
|
||||
department="IT",
|
||||
request_type="hardware",
|
||||
description="This is a test request",
|
||||
priority=RequestPriority.MEDIUM.value
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_request_invalid_priority(client: TestClient, test_user: dict, redis):
|
||||
"""Test creating a request with invalid priority"""
|
||||
access_token = create_access_token(
|
||||
data={"sub": test_user["email"], "is_admin": False}
|
||||
)
|
||||
test_request = requests.create_request(test_db, request_data, test_employee.id)
|
||||
|
||||
response = client.get("/api/requests/my", headers=test_auth_header)
|
||||
request_data = {
|
||||
"request_type": "technical",
|
||||
"description": "Test request",
|
||||
"priority": "invalid"
|
||||
}
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert len(data) == 1
|
||||
assert data[0]["department"] == test_request.department
|
||||
assert data[0]["description"] == test_request.description
|
||||
assert data[0]["priority"] == test_request.priority
|
||||
assert data[0]["status"] == test_request.status
|
||||
assert data[0]["employee_id"] == test_request.employee_id
|
||||
response = client.post(
|
||||
"/api/requests/",
|
||||
headers={"Authorization": f"Bearer {access_token}"},
|
||||
json=request_data
|
||||
)
|
||||
assert response.status_code == 422
|
||||
|
||||
def test_update_request_status(test_db: Session, test_employee, admin_auth_header):
|
||||
"""Test updating request status"""
|
||||
# Создаем тестовую заявку
|
||||
request_data = RequestCreate(
|
||||
department="IT",
|
||||
request_type="hardware",
|
||||
description="This is a test request",
|
||||
priority=RequestPriority.MEDIUM.value
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_user_requests(client: TestClient, test_user: dict, redis):
|
||||
"""Test getting user's requests"""
|
||||
access_token = create_access_token(
|
||||
data={"sub": test_user["email"], "is_admin": False}
|
||||
)
|
||||
test_request = requests.create_request(test_db, request_data, test_employee.id)
|
||||
|
||||
update_data = {"status": RequestStatus.IN_PROGRESS.value}
|
||||
response = client.patch(
|
||||
f"/api/requests/{test_request.id}/status",
|
||||
json=update_data,
|
||||
headers=admin_auth_header
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["status"] == RequestStatus.IN_PROGRESS.value
|
||||
|
||||
def test_get_all_requests_admin(test_db: Session, test_employee, admin_auth_header):
|
||||
"""Test getting all requests as admin"""
|
||||
# Создаем тестовую заявку
|
||||
request_data = RequestCreate(
|
||||
department="IT",
|
||||
request_type="hardware",
|
||||
description="This is a test request",
|
||||
priority=RequestPriority.MEDIUM.value
|
||||
)
|
||||
test_request = requests.create_request(test_db, request_data, test_employee.id)
|
||||
|
||||
response = client.get("/api/requests/admin", headers=admin_auth_header)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert len(data) == 1
|
||||
assert data[0]["department"] == test_request.department
|
||||
|
||||
def test_get_requests_by_status(test_db: Session, test_employee, admin_auth_header):
|
||||
"""Test filtering requests by status"""
|
||||
# Создаем тестовую заявку
|
||||
request_data = RequestCreate(
|
||||
department="IT",
|
||||
request_type="hardware",
|
||||
description="This is a test request",
|
||||
priority=RequestPriority.MEDIUM.value
|
||||
)
|
||||
test_request = requests.create_request(test_db, request_data, test_employee.id)
|
||||
|
||||
response = client.get(
|
||||
f"/api/requests/admin?status={RequestStatus.NEW.value}",
|
||||
headers=admin_auth_header
|
||||
"/api/requests/my",
|
||||
headers={"Authorization": f"Bearer {access_token}"}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert isinstance(response.json(), list)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_request_status(client: TestClient, test_admin: dict, redis):
|
||||
"""Test updating request status (admin only)"""
|
||||
access_token = create_access_token(
|
||||
data={"sub": test_admin["email"], "is_admin": True}
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert len(data) == 1
|
||||
assert data[0]["status"] == RequestStatus.NEW.value
|
||||
|
||||
def test_get_request_statistics(test_db: Session, test_employee, admin_auth_header):
|
||||
"""Test getting request statistics"""
|
||||
# Создаем тестовые заявки с разными статусами
|
||||
requests_data = [
|
||||
RequestCreate(
|
||||
department="IT",
|
||||
request_type="hardware",
|
||||
description="Test request 1",
|
||||
priority=RequestPriority.HIGH.value
|
||||
),
|
||||
RequestCreate(
|
||||
department="IT",
|
||||
request_type="software",
|
||||
description="Test request 2",
|
||||
priority=RequestPriority.MEDIUM.value
|
||||
)
|
||||
]
|
||||
|
||||
for data in requests_data:
|
||||
requests.create_request(test_db, data, test_employee.id)
|
||||
|
||||
response = client.get("/api/requests/statistics", headers=admin_auth_header)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "total" in data
|
||||
assert "by_status" in data
|
||||
assert data["total"] == 2
|
||||
assert data["by_status"][RequestStatus.NEW.value] == 2
|
||||
assert data["by_status"][RequestStatus.IN_PROGRESS.value] == 0
|
||||
assert data["by_status"][RequestStatus.COMPLETED.value] == 0
|
||||
assert data["by_status"][RequestStatus.REJECTED.value] == 0
|
||||
|
||||
def test_create_request_unauthorized(test_db: Session):
|
||||
"""Test creating request without authorization"""
|
||||
# Сначала создаем запрос
|
||||
request_data = {
|
||||
"department": "IT",
|
||||
"request_type": "hardware",
|
||||
"description": "This is a test request",
|
||||
"priority": RequestPriority.MEDIUM.value
|
||||
"request_type": "technical",
|
||||
"description": "Test request",
|
||||
"priority": "medium"
|
||||
}
|
||||
response = client.post("/api/requests/", json=request_data)
|
||||
assert response.status_code == 401
|
||||
|
||||
create_response = client.post(
|
||||
"/api/requests/",
|
||||
headers={"Authorization": f"Bearer {access_token}"},
|
||||
json=request_data
|
||||
)
|
||||
request_id = create_response.json()["id"]
|
||||
|
||||
# Теперь обновляем статус
|
||||
update_data = {
|
||||
"status": "in_progress"
|
||||
}
|
||||
|
||||
response = client.put(
|
||||
f"/api/admin/requests/{request_id}/status",
|
||||
headers={"Authorization": f"Bearer {access_token}"},
|
||||
json=update_data
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json()["status"] == "in_progress"
|
||||
|
||||
def test_get_requests_unauthorized(test_db: Session):
|
||||
"""Test getting requests without authorization"""
|
||||
response = client.get("/api/requests/my")
|
||||
assert response.status_code == 401
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_request_status_not_admin(client: TestClient, test_user: dict, redis):
|
||||
"""Test updating request status without admin rights"""
|
||||
access_token = create_access_token(
|
||||
data={"sub": test_user["email"], "is_admin": False}
|
||||
)
|
||||
|
||||
update_data = {
|
||||
"status": "in_progress"
|
||||
}
|
||||
|
||||
response = client.put(
|
||||
"/api/admin/requests/1/status",
|
||||
headers={"Authorization": f"Bearer {access_token}"},
|
||||
json=update_data
|
||||
)
|
||||
assert response.status_code == 403
|
37
backend/tests/test_statistics.py
Normal file
37
backend/tests/test_statistics.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from fastapi.testclient import TestClient
|
||||
import pytest
|
||||
from app.core.auth import create_access_token
|
||||
|
||||
def test_get_statistics_admin(client: TestClient, test_admin: dict):
|
||||
"""Test getting statistics as admin"""
|
||||
access_token = create_access_token(
|
||||
data={"sub": test_admin["email"], "is_admin": True}
|
||||
)
|
||||
|
||||
response = client.get(
|
||||
"/api/admin/statistics",
|
||||
headers={"Authorization": f"Bearer {access_token}"}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "total_requests" in data
|
||||
assert "total_users" in data
|
||||
assert "status_stats" in data
|
||||
assert "daily_stats" in data
|
||||
|
||||
def test_get_statistics_not_admin(client: TestClient, test_user: dict):
|
||||
"""Test getting statistics without admin rights"""
|
||||
access_token = create_access_token(
|
||||
data={"sub": test_user["email"], "is_admin": False}
|
||||
)
|
||||
|
||||
response = client.get(
|
||||
"/api/admin/statistics",
|
||||
headers={"Authorization": f"Bearer {access_token}"}
|
||||
)
|
||||
assert response.status_code == 403
|
||||
|
||||
def test_get_statistics_no_auth(client: TestClient):
|
||||
"""Test getting statistics without authentication"""
|
||||
response = client.get("/api/admin/statistics")
|
||||
assert response.status_code == 401
|
Reference in New Issue
Block a user