repair tests

This commit is contained in:
L-Nafaryus 2024-08-30 12:38:43 +05:00
parent 3637ea99a8
commit 1b1142a0b0
Signed by: L-Nafaryus
GPG Key ID: 553C97999B363D38
6 changed files with 113 additions and 115 deletions

View File

@ -29,123 +29,114 @@ class ApplicationError(Exception):
class Application:
__instance__: Optional[Self] = None
def __init__(
self,
config: Config,
logger: LoggerInstance,
database: Database,
cache: Cache,
cron: Cron,
backend: FastAPI,
):
if Application.__instance__:
raise ApplicationError("Cannot create multiple applications")
self.config: Config = config
self.logger: Optional[LoggerInstance] = None
self.database: Optional[Database] = None
self.cache: Optional[Cache] = None
self.cron: Optional[Cron] = None
self.backend: Optional[FastAPI] = None
self.config = config
self.logger = logger
self.database = database
self.cache = cache
self.cron = cron
self.backend = backend
self.prepare_logger()
@staticmethod
async def new(config: Config):
if Application.__instance__:
raise ApplicationError("Cannot create multiple applications")
logger = Logger.new(**config.log.model_dump())
app = Application(config)
# if user := config.application.user:
# os.setuid(pwd.getpwnam(user).pw_uid)
# if group := config.application.group:
# os.setgid(pwd.getpwnam(user).pw_gid)
logger.debug("Initializing application...")
app.logger.debug("Initializing application...")
await app.prepare_working_directory()
try:
logger.debug("Changing working directory")
os.chdir(config.application.working_directory.resolve())
except FileNotFoundError as e:
logger.error("Failed to change working directory: {}", e)
sys.exit()
try:
logger.info("Connecting to database {}", config.database.url())
database = await Database.new(config.database.url()) # type: ignore
logger.info("Connecting to cache server {}", config.cache.url())
cache = await Cache.new(config.cache.url()) # type: ignore
logger.info("Prepairing cron")
cron = Cron.new(
config.cron.workers_count,
backend_url=config.cache.url(),
broker_url=config.cache.url(),
)
logger.info("Running database migrations")
await database.run_migrations()
await app.prepare_database()
await app.prepare_cache()
await app.prepare_cron()
await app.prepare_server()
except Exception as e:
logger.error(" ".join(e.args))
app.logger.error(" ".join(e.args))
sys.exit()
try:
import materia_frontend
except ModuleNotFoundError:
logger.warning(
app.logger.warning(
"`materia_frontend` is not installed. No user interface will be served."
)
return app
def prepare_logger(self):
self.logger = Logger.new(**self.config.log.model_dump())
async def prepare_working_directory(self):
try:
self.logger.debug("Changing working directory")
os.chdir(self.config.application.working_directory.resolve())
except FileNotFoundError as e:
self.logger.error("Failed to change working directory: {}", e)
sys.exit()
async def prepare_database(self):
url = self.config.database.url()
self.logger.info("Connecting to database {}", url)
self.database = await Database.new(url) # type: ignore
async def prepare_cache(self):
url = self.config.cache.url()
self.logger.info("Connecting to cache server {}", url)
self.cache = await Cache.new(url) # type: ignore
async def prepare_cron(self):
url = self.config.cache.url()
self.logger.info("Prepairing cron")
self.cron = Cron.new(
self.config.cron.workers_count, backend_url=url, broker_url=url
)
async def prepare_server(self):
@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncIterator[Context]:
yield Context(config=config, logger=logger, database=database, cache=cache)
yield Context(
config=self.config,
logger=self.logger,
database=self.database,
cache=self.cache,
)
if database.engine is not None:
await database.dispose()
if self.database.engine is not None:
await self.database.dispose()
backend = FastAPI(
self.backend = FastAPI(
title="materia",
version="0.1.0",
docs_url="/api/docs",
lifespan=lifespan,
)
backend.add_middleware(
self.backend.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost", "http://localhost:5173"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
backend.include_router(routers.api.router)
backend.include_router(routers.resources.router)
backend.include_router(routers.root.router)
return Application(
config=config,
logger=logger,
database=database,
cache=cache,
cron=cron,
backend=backend,
)
@staticmethod
def instance() -> Optional[Self]:
return Application.__instance__
self.backend.include_router(routers.api.router)
self.backend.include_router(routers.resources.router)
self.backend.include_router(routers.root.router)
async def start(self):
self.logger.info(f"Spinning up cron workers [{self.config.cron.workers_count}]")
self.cron.run_workers()
try:
# uvicorn.run(
# self.backend,
# port=self.config.server.port,
# host=str(self.config.server.address),
# # reload = config.application.mode == "development",
# log_config=Logger.uvicorn_config(self.config.log.level),
# )
self.logger.info("Running database migrations")
await self.database.run_migrations()
uvicorn_config = uvicorn.Config(
self.backend,
port=self.config.server.port,
@ -157,3 +148,7 @@ class Application:
await server.serve()
except (KeyboardInterrupt, SystemExit):
self.logger.info("Exiting...")
sys.exit()
except Exception as e:
self.logger.error(" ".join(e.args))
sys.exit()

View File

@ -95,19 +95,19 @@ class Repository(Base):
await session.refresh(user, attribute_names=["repository"])
return user.repository
async def used(self, session: SessionContext) -> int:
async def used_capacity(self, session: SessionContext) -> int:
session.add(self)
await session.refresh(self, attribute_names=["files"])
return sum([file.size for file in self.files])
async def remaining_capacity(self, session: SessionContext) -> int:
used = await self.used(session)
used = await self.used_capacity(session)
return self.capacity - used
async def info(self, session: SessionContext) -> "RepositoryInfo":
info = RepositoryInfo.model_validate(self)
info.used = await self.used(session)
info.used = await self.used_capacity(session)
return info

View File

@ -74,8 +74,7 @@ async def create(
repository: Repository = Depends(middleware.repository),
ctx: middleware.Context = Depends(),
):
database = await Database.new(ctx.config.database.url(), test_connection=False)
async with database.session() as session:
async with ctx.database.session() as session:
capacity = await repository.remaining_capacity(session)
try:
@ -116,7 +115,7 @@ async def create(
file.remove()
raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR, "Invalid path")
async with database.session() as session:
async with ctx.database.session() as session:
target_directory = await validate_target_directory(
path, repository, session, ctx.config
)

View File

@ -1,27 +1,17 @@
import pytest_asyncio
from materia.config import Config
from materia.models import (
Database,
Cache,
User,
LoginType,
)
from materia.models.base import Base
from materia import security
from materia.app import Application
from materia.core import Config, Database, Cache, Cron
import sqlalchemy as sa
from sqlalchemy.pool import NullPool
from materia.app import make_application, AppContext
from materia._logging import make_logger
from httpx import AsyncClient, ASGITransport, Cookies
import asyncio
from fastapi import FastAPI
from contextlib import asynccontextmanager
from typing import AsyncIterator
from asgi_lifespan import LifespanManager
from fastapi.middleware.cors import CORSMiddleware
from materia import routers
from pathlib import Path
from copy import deepcopy
@pytest_asyncio.fixture(scope="session")
@ -75,6 +65,17 @@ async def cache(config: Config) -> Cache:
yield cache_pytest
@pytest_asyncio.fixture(scope="session")
async def cron(config: Config) -> Cache:
cron_pytest = Cron.new(
config.cron.workers_count,
backend_url=config.cache.url(),
broker_url=config.cache.url(),
)
yield cron_pytest
@pytest_asyncio.fixture(scope="function", autouse=True)
async def setup_database(database: Database):
async with database.connection() as connection:
@ -121,30 +122,36 @@ async def api_config(config: Config, tmpdir) -> Config:
@pytest_asyncio.fixture(scope="function")
async def api_client(
api_config: Config, database: Database, cache: Cache
api_config: Config, database: Database, cache: Cache, cron: Cron
) -> AsyncClient:
logger = make_logger(api_config)
app = Application(api_config)
app.database = database
app.cache = cache
app.cron = cron
await app.prepare_server()
@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncIterator[AppContext]:
yield AppContext(
config=api_config, database=database, cache=cache, logger=logger
)
# logger = make_logger(api_config)
app = FastAPI(lifespan=lifespan)
app.include_router(routers.api.router)
app.include_router(routers.resources.router)
app.include_router(routers.root.router)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# @asynccontextmanager
# async def lifespan(app: FastAPI) -> AsyncIterator[AppContext]:
# yield AppContext(
# config=api_config, database=database, cache=cache, logger=logger
# )
async with LifespanManager(app) as manager:
# app = FastAPI(lifespan=lifespan)
# app.include_router(routers.api.router)
# app.include_router(routers.resources.router)
# app.include_router(routers.root.router)
# app.add_middleware(
# CORSMiddleware,
# allow_origins=["*"],
# allow_credentials=True,
# allow_methods=["*"],
# allow_headers=["*"],
# )
async with LifespanManager(app.backend) as manager:
async with AsyncClient(
transport=ASGITransport(app=manager.app), base_url=api_config.server.url()
) as client:

View File

@ -1,8 +1,6 @@
import pytest
from materia.config import Config
from materia.core import Config
from httpx import AsyncClient, Cookies
from materia.models.base import Base
import aiofiles
from io import BytesIO
# TODO: replace downloadable images for tests
@ -188,6 +186,6 @@ async def test_file(auth_client: AsyncClient, api_config: Config):
pytest_logo = BytesIO(pytest_logo_res.content)
create = await auth_client.post(
"/api/file", files={"file": ("pytest.png", pytest_logo)}, json={"path", "/"}
"/api/file", files={"file": ("pytest.png", pytest_logo)}, data={"path": "/"}
)
assert create.status_code == 200, create.text

View File

@ -1,7 +1,6 @@
import pytest_asyncio
import pytest
from pathlib import Path
from materia.config import Config
from materia.models import (
User,
Repository,
@ -9,7 +8,7 @@ from materia.models import (
RepositoryError,
File,
)
from materia.models.database import SessionContext
from materia.core import Config, SessionContext
from materia import security
import sqlalchemy as sa
from sqlalchemy.orm.session import make_transient