Setup testing, fix database connection, add manage.py

This commit is contained in:
2025-01-25 01:24:30 +02:00
parent 97ac6a8c1d
commit 14da11acfc
27 changed files with 218 additions and 51 deletions
+1 -1
View File
@@ -23,7 +23,7 @@ TORTOISE_ORM = {
async def init_db():
await Tortoise.init(db_url=settings.PSQL_CONNECT_STR, modules=modules)
await Tortoise.init(config=TORTOISE_ORM)
async def migrate_db():
+6 -24
View File
@@ -1,14 +1,13 @@
from fastapi import FastAPI
from fastapi.responses import JSONResponse
from starlette.responses import RedirectResponse
from tortoise import Tortoise
from tortoise import run_async
from config import settings
from database import migrate_db
from router import router as root_router
from modules.assets.router import router as asset_router
from modules.auth.router import router as auth_router
from modules.users.router import router as users_router
from modules.organizations.router import router as organizations_router
from tortoise.contrib.fastapi import register_tortoise
from database import modules
app = FastAPI(
title=settings.PROJECT_NAME,
@@ -16,27 +15,10 @@ app = FastAPI(
summary=settings.PROJECT_SUMMARY,
)
Tortoise.init_models(modules, "models")
register_tortoise(
app,
db_url=settings.PSQL_CONNECT_STR,
modules=modules,
generate_schemas=True,
add_exception_handlers=True,
)
run_async(migrate_db())
app.include_router(root_router)
app.include_router(auth_router)
app.include_router(users_router)
app.include_router(organizations_router)
app.include_router(asset_router)
@app.get("/")
async def main():
return RedirectResponse(url="/docs")
@app.get("/ping")
async def ping() -> JSONResponse:
return JSONResponse("PONG")
+24
View File
@@ -0,0 +1,24 @@
#!/usr/bin/env python3
from ptpython.repl import embed # type: ignore
from database import *
import asyncio
async def setup():
try:
await embed(globals=globals(), return_asyncio_coroutine=True, patch_stdout=True)
except EOFError:
loop.stop()
if __name__ == "__main__":
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
asyncio.ensure_future(setup())
loop.run_forever()
except KeyboardInterrupt:
pass
@@ -0,0 +1,15 @@
from tortoise import BaseDBAsyncClient
async def upgrade(db: BaseDBAsyncClient) -> str:
return """
ALTER TABLE "asset" ADD "disabled_at" TIMESTAMPTZ;
ALTER TABLE "asset" ADD "modified_at" TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE "asset" ADD "created_at" TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP;"""
async def downgrade(db: BaseDBAsyncClient) -> str:
return """
ALTER TABLE "asset" DROP COLUMN "disabled_at";
ALTER TABLE "asset" DROP COLUMN "modified_at";
ALTER TABLE "asset" DROP COLUMN "created_at";"""
@@ -1,7 +1,6 @@
from tortoise import fields
class CMDMixin:
class CMDMixin():
"""
Created, modified and delete mixin, these are required for every class.
"""
@@ -9,3 +8,4 @@ class CMDMixin:
created_at = fields.DatetimeField(null=True, auto_now_add=True)
modified_at = fields.DatetimeField(null=True, auto_now=True)
disabled_at = fields.DatetimeField(null=True)
@@ -1,6 +1,7 @@
from tortoise.models import Model
from tortoise import fields
from mixins.CMDMixin import CMDMixin
class Asset(Model):
class Asset(Model, CMDMixin):
id = fields.UUIDField(primary_key=True)
name = fields.CharField(max_length=128)
@@ -2,8 +2,6 @@ from uuid import UUID
from fastapi.routing import APIRouter
from modules.assets.models import Asset
router = APIRouter(
prefix="/assets"
)
+1 -1
View File
@@ -3,7 +3,7 @@ from tortoise import fields
import uuid
from datetime import datetime
from models import CMDMixin
from mixins.CMDMixin import CMDMixin
from config import settings
+7 -8
View File
@@ -4,16 +4,15 @@ from fastapi.responses import JSONResponse
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from fastapi.routing import APIRouter
from utils import create_token
from models import Token
from modules.auth.utils import create_token
from modules.auth.models import Token
from modules.users.models import User
from fastapi import Depends, HTTPException
from config import settings
from tortoise.expressions import Q
from schemas import TokenModel
router = APIRouter(prefix="/auth")
router = APIRouter(prefix="/api/v1/auth", tags=["auth"])
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@@ -22,17 +21,17 @@ error: str = "E-Mail Address or password is incorrect"
crypt = settings.CRYPT
@router.post("/", response_model=TokenModel)
@router.post("/", status_code=200)
async def login(form: Annotated[OAuth2PasswordRequestForm, Depends()]):
user: User = await User.filter(
user: User | None = await User.filter(
Q(email=form.username)
).get_or_none()
if user is None:
HTTPException(status_code=401, detail=error)
return HTTPException(status_code=401, detail=error)
if user.check_against_password(form.password) is False:
HTTPException(status_code=401, detail=error)
return HTTPException(status_code=401, detail=error)
return JSONResponse(
await Token.create(
+1 -2
View File
@@ -1,7 +1,6 @@
import uuid, time
from config import settings
from joserfc import jwt # type: ignore
from joserfc.jwt import OctKey # type: ignore
crypt = settings.CRYPT
@@ -21,7 +20,7 @@ def create_token(user_id: uuid, offset: float) -> str:
"iat": curr_time,
"exp": int(curr_time + offset),
},
OctKey.import_key(settings.SECRET_KEY),
settings.SECRET_KEY,
)
@@ -6,7 +6,7 @@ from tortoise.exceptions import ConfigurationError
from tortoise.models import Model
from tortoise import fields
from models import CMDMixin
from mixins.CMDMixin import CMDMixin
from config import settings
class EnumField(fields.CharField):
@@ -45,11 +45,11 @@ class OrganizationType(Enum):
There are no seat costs.
"""
HOME: int = 1 # Home use (Any size)
SMALL_ORGANIZATION: int = 2 # 1-100
MEDIUM_ORGANIZATION: int = 3 # 100 - 500
LARGE_ORGANIZATION: int = 4 # 500 - 1000
EXTRA_LARGE_ORGANIZATION: int = 5 # 1000 - 5000+
HOME: str = "home" # Home use (Any size)
SMALL_ORGANIZATION: str = "s_org" # 1-100
MEDIUM_ORGANIZATION: str = "m_org" # 100 - 500
LARGE_ORGANIZATION: str = "l_org" # 500 - 1000
EXTRA_LARGE_ORGANIZATION: str = "xl_org" # 1000 - 5000+
@@ -1,7 +1,7 @@
from fastapi import APIRouter
router = APIRouter(prefix="/organizations")
router = APIRouter(prefix="/api/v1/organizations")
@router.get("/")
def all_organizations():
@@ -5,7 +5,7 @@ from tortoise.models import Model
from tortoise import fields
from modules.organizations.models import Organization
from models import CMDMixin
from mixins.CMDMixin import CMDMixin
from config import settings
crypt = settings.CRYPT
@@ -1,12 +1,19 @@
from fastapi import APIRouter
router = APIRouter(prefix="/users")
router = APIRouter(prefix="/api/v1/users", tags=["users"])
@router.get("/")
def get_all_users():
pass
@router.post("/")
def create_user():
pass
@router.get("/me")
def get_user():
pass
pass
+13
View File
@@ -1,4 +1,17 @@
[tool.black]
exclude = '''/
# Default values for Black.
\.eggs|\.git|\.hg|\.mypy_cache|\.nox|\.tox|\.venv|\.svn|_build|buck-out|build|dist|
/'''
line-length = 88
[tool.aerich]
tortoise_orm = "database.TORTOISE_ORM"
location = "./migrations"
src_folder = "./."
[tool.pytest.ini_options]
asyncio_default_fixture_loop_scope = "session"
testpaths = [
"tests/",
]
@@ -7,3 +7,11 @@ black>=24.10.0
joserfc>=1.0.1
passlib>=1.7.4
pytz>=2024.2
ptpython>=0.25
# Test Suite
httpx>=0.28.1
pytest>=8.3.4
mock>=5.1.0
pytest-mock>=3.14.0
anyio>=4.8.0
+14
View File
@@ -0,0 +1,14 @@
from fastapi import APIRouter
from fastapi.responses import JSONResponse, RedirectResponse
router = APIRouter(prefix="/api/v1")
@router.get("/")
async def main():
return RedirectResponse(url="/docs")
@router.get("/ping")
async def ping() -> JSONResponse:
return JSONResponse("PONG")
View File
+43
View File
@@ -0,0 +1,43 @@
import pytest
from httpx import AsyncClient
from tortoise import Tortoise
from database import modules
from main import app
DB_URL = "sqlite://:memory:"
async def init_db(db_url, create_db: bool = True, schemas: bool = True) -> None:
"""Initial database connection"""
await Tortoise.init(
db_url=db_url, modules={"models": modules}, _create_db=create_db
)
if create_db:
print(f"Database created! {db_url = }")
if schemas:
await Tortoise.generate_schemas()
print("Success to generate schemas")
async def init(db_url: str = DB_URL):
await init_db(db_url, True, True)
@pytest.fixture(scope="session")
def anyio_backend():
return "anyio"
@pytest.fixture(scope="session")
async def client():
async with AsyncClient(app=app, base_url="http://test") as client:
print("Client is ready")
yield client
@pytest.fixture(scope="session", autouse=True)
async def initialize_tests():
await init()
yield
await Tortoise._drop_databases()
@@ -0,0 +1,48 @@
from tests.fixtures.conftest import init_db
import pytest
from fastapi.testclient import TestClient
from modules.organizations.models import Organization
from modules.users.models import ACL, Membership, User
from main import app
from config import settings
client = TestClient(app)
crypt = settings.CRYPT
async def setup_function():
init_db()
org = await Organization.create(name="Admin's Organization", type="home")
user = await User.create(
email="admin@localhost.com",
username="admin",
name="admin",
surname="admin",
)
user.set_password("password")
user.save()
acl = await ACL.create(READ=True, WRITE=True, REPORT=True, MANAGE=True, ADMIN=True)
await Membership.create(organization=org, user=user, acl=acl)
print(org, user, acl)
# def teardown_function():
# Organization.all().delete()
# User.all().delete()
# ACL.all().delete()
# Membership.all().delete()
def test_read_main():
response = client.post(
"/api/v1/auth",
data={
"username": "admin@localhost.com",
"password": "password",
"grant_type": "password",
},
)
assert response.json() == {}
assert response.status_code == 200
@@ -0,0 +1,16 @@
from fastapi.testclient import TestClient
from main import app
client = TestClient(app)
def setup_function():
print("setting up")
def test_read_main():
response = client.get("/api/v1/")
assert response.status_code == 200
def test_get_pong():
response = client.get("/api/v1/ping")
assert response.status_code == 200
assert response.text == '"PONG"'