Добавление авторизации и фронтэнда

This commit is contained in:
2025-12-18 18:52:09 +03:00
parent 2c24f66de0
commit 756e941f99
55 changed files with 2314 additions and 577 deletions
+5 -4
View File
@@ -1,23 +1,24 @@
from fastapi import FastAPI
from tests.mock_routers import books, authors, genres, relationships
from library_service.routers.misc import router as misc_router
from tests.mock_routers import authors, books, genres, relationships
def create_mock_app() -> FastAPI:
"""Create FastAPI app with mock routers for testing"""
"""Создание FastAPI app с моками роутеров для тестов"""
app = FastAPI(
title="Library API Test",
description="Library API for testing without database",
version="1.0.0",
)
# Include mock routers
# Подключение мок-роутеров
app.include_router(books.router)
app.include_router(authors.router)
app.include_router(genres.router)
app.include_router(relationships.router)
# Include real misc router (it doesn't use database)
# Подключение реального misc роутера
app.include_router(misc_router)
return app
+1
View File
@@ -1,4 +1,5 @@
from fastapi import APIRouter, HTTPException
from tests.mocks.mock_storage import mock_storage
router = APIRouter(prefix="/authors", tags=["authors"])
+1
View File
@@ -1,4 +1,5 @@
from fastapi import APIRouter, HTTPException
from tests.mocks.mock_storage import mock_storage
router = APIRouter(prefix="/books", tags=["books"])
+1
View File
@@ -1,4 +1,5 @@
from fastapi import APIRouter, HTTPException
from tests.mocks.mock_storage import mock_storage
router = APIRouter(prefix="/genres", tags=["genres"])
+1 -1
View File
@@ -1,4 +1,5 @@
from fastapi import APIRouter, HTTPException
from tests.mocks.mock_storage import mock_storage
router = APIRouter(tags=["relations"])
@@ -36,5 +37,4 @@ def get_authors_for_book(book_id: int):
@router.post("/relationships/genre-book")
def add_genre_to_book(genre_id: int, book_id: int):
# For tests that need genre functionality
return {"genre_id": genre_id, "book_id": book_id}
+6 -15
View File
@@ -1,4 +1,5 @@
from typing import Optional, List, Any
from typing import Any, List
from tests.mocks.mock_storage import mock_storage
@@ -8,20 +9,13 @@ class MockSession:
def __init__(self):
self.storage = mock_storage
def add(self, obj: Any):
"""Mock add - not needed for our implementation"""
pass
def add(self, obj: Any): ...
def commit(self):
"""Mock commit - not needed for our implementation"""
pass
def commit(self): ...
def refresh(self, obj: Any):
"""Mock refresh - not needed for our implementation"""
pass
def refresh(self, obj: Any): ...
def get(self, model_class, pk: int):
"""Mock get method to retrieve object by primary key"""
if hasattr(model_class, "__name__"):
model_name = model_class.__name__.lower()
else:
@@ -35,12 +29,9 @@ class MockSession:
return self.storage.get_genre(pk)
return None
def delete(self, obj: Any):
"""Mock delete - handled in storage methods"""
pass
def delete(self, obj: Any): ...
def exec(self, statement):
"""Mock exec method for queries"""
return MockResult([])
+14 -16
View File
@@ -1,4 +1,4 @@
from typing import Dict, List, Optional
from typing import Dict, List
class MockStorage:
@@ -15,7 +15,7 @@ class MockStorage:
self.genre_id_counter = 1
def clear_all(self):
"""Clear all data"""
"""Очистка всех данных"""
self.books.clear()
self.authors.clear()
self.genres.clear()
@@ -33,7 +33,7 @@ class MockStorage:
self.book_id_counter += 1
return book
def get_book(self, book_id: int) -> Optional[dict]:
def get_book(self, book_id: int) -> dict | None:
return self.books.get(book_id)
def get_all_books(self) -> List[dict]:
@@ -42,9 +42,9 @@ class MockStorage:
def update_book(
self,
book_id: int,
title: Optional[str] = None,
description: Optional[str] = None,
) -> Optional[dict]:
title: str | None = None,
description: str | None = None,
) -> dict | None:
if book_id not in self.books:
return None
book = self.books[book_id]
@@ -54,7 +54,7 @@ class MockStorage:
book["description"] = description
return book
def delete_book(self, book_id: int) -> Optional[dict]:
def delete_book(self, book_id: int) -> dict | None:
if book_id not in self.books:
return None
book = self.books.pop(book_id)
@@ -74,15 +74,15 @@ class MockStorage:
self.author_id_counter += 1
return author
def get_author(self, author_id: int) -> Optional[dict]:
def get_author(self, author_id: int) -> dict | None:
return self.authors.get(author_id)
def get_all_authors(self) -> List[dict]:
return list(self.authors.values())
def update_author(
self, author_id: int, name: Optional[str] = None
) -> Optional[dict]:
self, author_id: int, name: str | None = None
) -> dict | None:
if author_id not in self.authors:
return None
author = self.authors[author_id]
@@ -90,7 +90,7 @@ class MockStorage:
author["name"] = name
return author
def delete_author(self, author_id: int) -> Optional[dict]:
def delete_author(self, author_id: int) -> dict | None:
if author_id not in self.authors:
return None
author = self.authors.pop(author_id)
@@ -107,15 +107,13 @@ class MockStorage:
self.genre_id_counter += 1
return genre
def get_genre(self, genre_id: int) -> Optional[dict]:
def get_genre(self, genre_id: int) -> dict | None:
return self.genres.get(genre)
def get_all_authors(self) -> List[dict]:
return list(self.authors.values())
def update_genre(
self, genre_id: int, name: Optional[str] = None
) -> Optional[dict]:
def update_genre(self, genre_id: int, name: str | None = None) -> dict | None:
if genre_id not in self.genres:
return None
genre = self.genres[genre_id]
@@ -123,7 +121,7 @@ class MockStorage:
genre["name"] = name
return genre
def delete_genre(self, genre_id: int) -> Optional[dict]:
def delete_genre(self, genre_id: int) -> dict | None:
if genre_id not in self.genres:
return None
genre = self.genres.pop(genre_id)
+1 -7
View File
@@ -1,5 +1,6 @@
import pytest
from fastapi.testclient import TestClient
from tests.mock_app import mock_app
from tests.mocks.mock_storage import mock_storage
@@ -8,7 +9,6 @@ client = TestClient(mock_app)
@pytest.fixture(autouse=True)
def setup_database():
"""Clear mock storage before each test"""
mock_storage.clear_all()
yield
mock_storage.clear_all()
@@ -29,7 +29,6 @@ def test_create_author():
def test_list_authors():
# First create an author
client.post("/authors", json={"name": "Test Author"})
response = client.get("/authors")
@@ -42,7 +41,6 @@ def test_list_authors():
def test_get_existing_author():
# First create an author
client.post("/authors", json={"name": "Test Author"})
response = client.get("/authors/1")
@@ -63,7 +61,6 @@ def test_get_not_existing_author():
def test_update_author():
# First create an author
client.post("/authors", json={"name": "Test Author"})
response = client.get("/authors/1")
@@ -84,10 +81,7 @@ def test_update_not_existing_author():
def test_delete_author():
# First create an author
client.post("/authors", json={"name": "Test Author"})
# Update it first
client.put("/authors/1", json={"name": "Updated Author"})
response = client.get("/authors/1")
+6 -18
View File
@@ -1,5 +1,6 @@
import pytest
from fastapi.testclient import TestClient
from tests.mock_app import mock_app
from tests.mocks.mock_storage import mock_storage
@@ -8,7 +9,6 @@ client = TestClient(mock_app)
@pytest.fixture(autouse=True)
def setup_database():
"""Clear mock storage before each test"""
mock_storage.clear_all()
yield
mock_storage.clear_all()
@@ -35,9 +35,7 @@ def test_create_book():
def test_list_books():
# First create a book
client.post(
"/books", json={"title": "Test Book", "description": "Test Description"}
client.post("/books", json={"title": "Test Book", "description": "Test Description"}
)
response = client.get("/books")
@@ -50,9 +48,7 @@ def test_list_books():
def test_get_existing_book():
# First create a book
client.post(
"/books", json={"title": "Test Book", "description": "Test Description"}
client.post("/books", json={"title": "Test Book", "description": "Test Description"}
)
response = client.get("/books/1")
@@ -74,9 +70,7 @@ def test_get_not_existing_book():
def test_update_book():
# First create a book
client.post(
"/books", json={"title": "Test Book", "description": "Test Description"}
client.post("/books", json={"title": "Test Book", "description": "Test Description"}
)
response = client.get("/books/1")
@@ -102,14 +96,8 @@ def test_update_not_existing_book():
def test_delete_book():
# First create a book
client.post(
"/books", json={"title": "Test Book", "description": "Test Description"}
)
# Update it first
client.put(
"/books/1", json={"title": "Updated Book", "description": "Updated Description"}
client.post("/books", json={"title": "Test Book", "description": "Test Description"})
client.put("/books/1", json={"title": "Updated Book", "description": "Updated Description"}
)
response = client.get("/books/1")
+10 -22
View File
@@ -1,6 +1,8 @@
import pytest
from datetime import datetime
import pytest
from fastapi.testclient import TestClient
from tests.mock_app import mock_app
from tests.mocks.mock_storage import mock_storage
@@ -9,20 +11,15 @@ client = TestClient(mock_app)
@pytest.fixture(autouse=True)
def setup_database():
"""Setup and cleanup mock database for each test"""
# Clear data before each test
mock_storage.clear_all()
yield
# Clear data after each test (optional, but good practice)
mock_storage.clear_all()
# Test the main page of the application
def test_main_page():
response = client.get("/") # Send GET request to the main page
response = client.get("/api")
try:
content = response.content.decode("utf-8") # Decode response content
# Find indices of key elements in the content
content = response.content.decode("utf-8")
title_idx = content.index("Welcome to ")
description_idx = content.index("Description: ")
version_idx = content.index("Version: ")
@@ -38,25 +35,16 @@ def test_main_page():
assert content[time_idx + 1] != "<", "Time not provided"
assert content[status_idx + 1] != "<", "Status not provided"
except Exception as e:
print(f"Error: {e}") # Print error if an exception occurs
assert False, "Unexpected error" # Force test failure on unexpected error
print(f"Error: {e}")
assert False, "Unexpected error"
# Test application info endpoint
def test_app_info_test():
response = client.get("/api/info") # Send GET request to the info endpoint
response = client.get("/api/info")
assert response.status_code == 200, "Invalid response status"
assert response.json()["status"] == "ok", "Status not ok"
assert response.json()["app_info"]["title"] != "", "Title not provided"
assert response.json()["app_info"]["description"] != "", "Description not provided"
assert response.json()["app_info"]["version"] != "", "Version not provided"
# Check time difference
assert (
0
< (
datetime.now() - datetime.fromisoformat(response.json()["server_time"])
).total_seconds()
), "Negative time difference"
assert (
datetime.now() - datetime.fromisoformat(response.json()["server_time"])
).total_seconds() < 1, "Time difference too large"
assert (0 < (datetime.now() - datetime.fromisoformat(response.json()["server_time"])).total_seconds()), "Negative time difference"
assert (datetime.now() - datetime.fromisoformat(response.json()["server_time"])).total_seconds() < 1, "Time difference too large"
+5 -16
View File
@@ -1,5 +1,6 @@
import pytest
from fastapi.testclient import TestClient
from tests.mock_app import mock_app
from tests.mocks.mock_storage import mock_storage
@@ -8,7 +9,6 @@ client = TestClient(mock_app)
@pytest.fixture(autouse=True)
def setup_database():
"""Clear mock storage before each test"""
mock_storage.clear_all()
yield
mock_storage.clear_all()
@@ -30,28 +30,18 @@ def make_genrebook_relationship(genre_id, book_id):
def test_prepare_data():
# Create books
assert client.post(
"/books", json={"title": "Test Book 1", "description": "Test Description 1"}
).status_code == 200
assert client.post(
"/books", json={"title": "Test Book 2", "description": "Test Description 2"}
).status_code == 200
assert client.post(
"/books", json={"title": "Test Book 3", "description": "Test Description 3"}
).status_code == 200
assert (client.post("/books", json={"title": "Test Book 1", "description": "Test Description 1"}).status_code == 200)
assert (client.post("/books", json={"title": "Test Book 2", "description": "Test Description 2"}).status_code == 200)
assert (client.post("/books", json={"title": "Test Book 3", "description": "Test Description 3"}).status_code == 200)
# Create authors
assert client.post("/authors", json={"name": "Test Author 1"}).status_code == 200
assert client.post("/authors", json={"name": "Test Author 2"}).status_code == 200
assert client.post("/authors", json={"name": "Test Author 3"}).status_code == 200
# Create genres
assert client.post("/genres", json={"name": "Test Genre 1"}).status_code == 200
assert client.post("/genres", json={"name": "Test Genre 2"}).status_code == 200
assert client.post("/genres", json={"name": "Test Genre 3"}).status_code == 200
# Create relationships
make_authorbook_relationship(1, 1)
make_authorbook_relationship(2, 1)
make_authorbook_relationship(1, 2)
@@ -63,8 +53,8 @@ def test_prepare_data():
make_genrebook_relationship(2, 3)
make_genrebook_relationship(3, 3)
def test_get_book_authors():
# Setup test data
test_prepare_data()
response1 = client.get("/books/1/authors")
@@ -91,7 +81,6 @@ def test_get_book_authors():
def test_get_author_books():
# Setup test data
test_prepare_data()
response1 = client.get("/authors/1/books")