1
0
mirror of https://gitlab.com/MoonTestUse1/AdministrationItDepartmens.git synced 2025-08-14 00:25:46 +02:00

Создание чата9testt

This commit is contained in:
MoonTestUse1
2025-01-05 06:32:34 +06:00
parent 9ba671bdaa
commit 7f7838a0d3
28 changed files with 653 additions and 721 deletions

BIN
backend/.coverage Normal file

Binary file not shown.

View File

@@ -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,

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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,

View File

@@ -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

View File

@@ -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
View 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
)

View 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

View File

@@ -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

View File

@@ -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"
}

View File

@@ -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())
# Отношения

View File

@@ -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())

View File

@@ -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")

View File

@@ -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] = []

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

Binary file not shown.

View File

@@ -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

View File

@@ -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"

View File

@@ -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"]

View File

@@ -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

View 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