Merge pull request #9 from BlackChaosNL/fea/setup-new-testing-suite

Setup new testing suite
This commit was merged in pull request #9.
This commit is contained in:
Jeroen Vijgen
2025-06-25 12:44:22 +03:00
committed by GitHub
27 changed files with 316 additions and 265 deletions
+3 -2
View File
@@ -92,5 +92,6 @@
/web/**/thumb /web/**/thumb
/web/**/sketch /web/**/sketch
# Prevent uploading DB files # Prevent some sensitive files from being committed
*.sqlite* *.sqlite*
.env
+2 -2
View File
@@ -1,2 +1,2 @@
python 3.13.1 python 3.13.5t
nodejs 23.4.0 nodejs 24.2.0
+2 -1
View File
@@ -9,12 +9,13 @@ class Settings(BaseSettings):
PROJECT_SUMMARY: str = "Product API for StoneEdge." PROJECT_SUMMARY: str = "Product API for StoneEdge."
PROJECT_PUBLIC_URL: str = "localhost" PROJECT_PUBLIC_URL: str = "localhost"
SECRET_KEY: str | None = None SECRET_KEY: str | None = None
USE_HTTPS_ONLY: bool = False
IS_TESTING: bool = False
PSQL_USERNAME: str = "user" PSQL_USERNAME: str = "user"
PSQL_PASSWORD: str = "password" PSQL_PASSWORD: str = "password"
PSQL_HOSTNAME: str = "localhost" PSQL_HOSTNAME: str = "localhost"
PSQL_PORT: int = 5432 PSQL_PORT: int = 5432
PSQL_DB_NAME: str = "stoneedge" PSQL_DB_NAME: str = "stoneedge"
PSQL_TEST_DB_NAME: str = "stoneedge_testing"
ACCESS_TOKEN_EXPIRE_MIN: int = 10 ACCESS_TOKEN_EXPIRE_MIN: int = 10
REFRESH_TOKEN_EXPIRE_MIN: int = 20 REFRESH_TOKEN_EXPIRE_MIN: int = 20
BACKEND_CORS_ORIGINS: list = ["*"] BACKEND_CORS_ORIGINS: list = ["*"]
+23 -8
View File
@@ -5,14 +5,25 @@ from aerich import Command
modules: dict[str, Any] = { modules: dict[str, Any] = {
"models": [ "models": [
"modules.assets.models",
"modules.auth.models", "modules.auth.models",
"modules.users.models", "modules.users.models",
"modules.organizations.models", "modules.organizations.models",
] ]
} }
TORTOISE_ORM = { TEST_TORTOISE_ORM = {
"connections": {
"default": "sqlite://:memory:"
},
"apps": {
"models": {
"models": modules.get("models", []) + ["aerich.models"],
"default_connection": "default",
},
},
}
PROD_TORTOISE_ORM = {
"connections": { "connections": {
"default": { "default": {
"engine": "tortoise.backends.asyncpg", "engine": "tortoise.backends.asyncpg",
@@ -23,7 +34,7 @@ TORTOISE_ORM = {
"password": settings.PSQL_PASSWORD, "password": settings.PSQL_PASSWORD,
"port": settings.PSQL_PORT, "port": settings.PSQL_PORT,
}, },
} },
}, },
"apps": { "apps": {
"models": { "models": {
@@ -34,11 +45,15 @@ TORTOISE_ORM = {
} }
async def migrate_db(): async def migrate_db(tortoise_config=PROD_TORTOISE_ORM):
aerich = Command(tortoise_config=TORTOISE_ORM) if settings.IS_TESTING:
tortoise_config=TEST_TORTOISE_ORM
aerich = Command(tortoise_config)
await aerich.init() await aerich.init()
await aerich.upgrade(run_in_transaction=True) await aerich.upgrade()
await Tortoise.init(config=TORTOISE_ORM) await Tortoise.init(tortoise_config)
await Tortoise.generate_schemas(safe=True)
async def end_connections_to_db(): async def end_connections_to_db():
await Tortoise.close_connections() await Tortoise.close_connections()
+8 -2
View File
@@ -29,8 +29,14 @@ app = FastAPI(
default_response_class=msgspec_jsonresponse, default_response_class=msgspec_jsonresponse,
) )
app.add_middleware(HTTPSRedirectMiddleware) if settings.USE_HTTPS_ONLY:
app.add_middleware(TrustedHostMiddleware, allowed_hosts=[settings.PROJECT_PUBLIC_URL,]) app.add_middleware(HTTPSRedirectMiddleware)
app.add_middleware(
TrustedHostMiddleware,
allowed_hosts=[
settings.PROJECT_PUBLIC_URL,
],
)
# Set all CORS enabled origins # Set all CORS enabled origins
if settings.BACKEND_CORS_ORIGINS: if settings.BACKEND_CORS_ORIGINS:
+90 -11
View File
@@ -1,24 +1,103 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from ptpython.repl import embed # type: ignore from ptpython.repl import embed, ReplExit
from database import * import asyncio, importlib, contextlib, sys, os, tomllib, asyncclick
import asyncio from database import migrate_db
from pathlib import Path
from asyncclick import BadOptionUsage, ClickException
from collections.abc import AsyncGenerator
from tortoise import Tortoise, connections
#
# Custom implementation of Tortoise CLI
# Original script is located under: https://github.com/tortoise/tortoise-cli
# License: Apache-2.0 as dictated as [here](https://github.com/tortoise/tortoise-cli/blob/main/LICENSE)
#
def tortoise_orm_config(file="pyproject.toml") -> str:
"""
get tortoise orm config from os environment variable or aerich item in pyproject.toml
:param file: toml file that aerich item loads from it
:return: module path and var name that store the tortoise config, e.g.: 'settings.TORTOISE_ORM'
"""
if not (config := os.getenv("TORTOISE_ORM", "")) and (p := Path(file)).exists():
doc = tomllib.loads(p.read_text("utf-8"))
config = doc.get("tool", {}).get("aerich", {}).get("tortoise_orm", "")
return config
def get_tortoise_config(config: str) -> dict:
"""
get tortoise config from module
:param ctx:
:param config:
:return:
"""
splits = config.split(".")
config_path = ".".join(splits[:-1])
tortoise_config = splits[-1]
try:
config_module = importlib.import_module(config_path)
except ModuleNotFoundError as e:
raise ClickException(
f"Error while importing configuration module: {e}"
) from None
c = getattr(config_module, tortoise_config, None)
if not c:
raise BadOptionUsage(
option_name="--config",
message=f'Can\'t get "{tortoise_config}" from module "{config_module}"',
ctx=None,
)
return c
@contextlib.asynccontextmanager
async def aclose_tortoise() -> AsyncGenerator[None]:
try:
yield
finally:
if Tortoise._inited:
await connections.close_all()
def history():
import readline
for i in range(1, readline.get_current_history_length()+1):
print("%3d %s" % (i, readline.get_history_item(i)))
async def setup(): async def setup():
try: if not (config := tortoise_orm_config()):
await embed(globals=globals(), return_asyncio_coroutine=True, patch_stdout=True) raise asyncclick.UsageError(
except EOFError: "You must specify TORTOISE_ORM in option or env, or config file pyproject.toml from config of aerich",
loop.stop() ctx=None,
)
await migrate_db(get_tortoise_config(config))
async with aclose_tortoise():
await embed(
globals=globals(),
title="shell",
vi_mode=True,
return_asyncio_coroutine=True,
patch_stdout=True,
)
if __name__ == "__main__": if __name__ == "__main__":
if sys.path[0] != ".":
sys.path.insert(0, ".")
loop = asyncio.new_event_loop() loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop) asyncio.set_event_loop(loop)
try: try:
asyncio.ensure_future(setup()) history()
loop.run_forever() loop.run_until_complete(asyncio.ensure_future(setup()))
except KeyboardInterrupt: except (KeyboardInterrupt, ReplExit) as e:
pass print(e)
loop.stop()
@@ -1,75 +0,0 @@
from tortoise import BaseDBAsyncClient
async def upgrade(db: BaseDBAsyncClient) -> str:
return """
CREATE TABLE IF NOT EXISTS "asset" (
"id" UUID NOT NULL PRIMARY KEY,
"name" VARCHAR(128) NOT NULL
);
CREATE TABLE IF NOT EXISTS "acl" (
"id" UUID NOT NULL PRIMARY KEY,
"READ" BOOL NOT NULL DEFAULT False,
"WRITE" BOOL NOT NULL DEFAULT False,
"REPORT" BOOL NOT NULL DEFAULT False,
"MANAGE" BOOL NOT NULL DEFAULT False,
"ADMIN" BOOL NOT NULL DEFAULT False
);
COMMENT ON TABLE "acl" IS 'ACL';
CREATE TABLE IF NOT EXISTS "organization" (
"created_at" TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
"modified_at" TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
"disabled_at" TIMESTAMPTZ,
"id" UUID NOT NULL PRIMARY KEY,
"name" VARCHAR(128) NOT NULL,
"type" VARCHAR(128) NOT NULL,
"disabled" BOOL NOT NULL DEFAULT False
);
COMMENT ON TABLE "organization" IS 'Organization';
CREATE TABLE IF NOT EXISTS "user" (
"created_at" TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
"modified_at" TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
"disabled_at" TIMESTAMPTZ,
"id" UUID NOT NULL PRIMARY KEY,
"email" VARCHAR(128) NOT NULL,
"username" TEXT NOT NULL,
"name" TEXT NOT NULL,
"surname" TEXT NOT NULL,
"password" VARCHAR(128),
"disabled" BOOL NOT NULL DEFAULT False
);
COMMENT ON TABLE "user" IS 'User';
CREATE TABLE IF NOT EXISTS "token" (
"created_at" TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
"modified_at" TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
"disabled_at" TIMESTAMPTZ,
"id" UUID NOT NULL PRIMARY KEY,
"token_type" VARCHAR(128) NOT NULL DEFAULT 'Bearer',
"access_token" VARCHAR(128),
"refresh_token" VARCHAR(128),
"disabled" BOOL NOT NULL DEFAULT False,
"user_id" UUID NOT NULL REFERENCES "user" ("id") ON DELETE CASCADE
);
COMMENT ON TABLE "token" IS 'Token';
CREATE TABLE IF NOT EXISTS "membership" (
"created_at" TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
"modified_at" TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
"disabled_at" TIMESTAMPTZ,
"id" UUID NOT NULL PRIMARY KEY,
"disabled" BOOL NOT NULL DEFAULT False,
"acl_id" UUID NOT NULL REFERENCES "acl" ("id") ON DELETE CASCADE,
"organization_id" UUID NOT NULL REFERENCES "organization" ("id") ON DELETE CASCADE,
"user_id" UUID NOT NULL REFERENCES "user" ("id") ON DELETE CASCADE
);
COMMENT ON TABLE "membership" IS 'Membership';
CREATE TABLE IF NOT EXISTS "aerich" (
"id" SERIAL NOT NULL PRIMARY KEY,
"version" VARCHAR(255) NOT NULL,
"app" VARCHAR(100) NOT NULL,
"content" JSONB NOT NULL
);"""
async def downgrade(db: BaseDBAsyncClient) -> str:
return """
"""
@@ -0,0 +1,76 @@
from tortoise import BaseDBAsyncClient
async def upgrade(db: BaseDBAsyncClient) -> str:
return """
CREATE TABLE IF NOT EXISTS "acl" (
"id" CHAR(36) NOT NULL PRIMARY KEY,
"READ" INT NOT NULL DEFAULT 0,
"WRITE" INT NOT NULL DEFAULT 0,
"REPORT" INT NOT NULL DEFAULT 0,
"MANAGE" INT NOT NULL DEFAULT 0,
"ADMIN" INT NOT NULL DEFAULT 0
) /* ACL */;
CREATE TABLE IF NOT EXISTS "organization" (
"created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
"modified_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
"disabled_at" TIMESTAMP,
"id" CHAR(36) NOT NULL PRIMARY KEY,
"name" VARCHAR(128) NOT NULL,
"type" VARCHAR(128) NOT NULL,
"disabled" INT NOT NULL DEFAULT 0
) /* Organization */;
CREATE TABLE IF NOT EXISTS "user" (
"created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
"modified_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
"disabled_at" TIMESTAMP,
"id" CHAR(36) NOT NULL PRIMARY KEY,
"email" VARCHAR(128) NOT NULL,
"username" TEXT NOT NULL,
"name" TEXT NOT NULL,
"surname" TEXT NOT NULL,
"password" VARCHAR(128),
"disabled" INT NOT NULL DEFAULT 0
) /* User */;
CREATE TABLE IF NOT EXISTS "token" (
"created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
"modified_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
"disabled_at" TIMESTAMP,
"id" CHAR(36) NOT NULL PRIMARY KEY,
"token_type" VARCHAR(128) NOT NULL DEFAULT 'Bearer',
"access_token" TEXT,
"refresh_token" TEXT,
"disabled" INT NOT NULL DEFAULT 0,
"user_id" CHAR(36) NOT NULL REFERENCES "user" ("id") ON DELETE CASCADE
) /* Token */;
CREATE TABLE IF NOT EXISTS "membership" (
"created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
"modified_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
"disabled_at" TIMESTAMP,
"id" CHAR(36) NOT NULL PRIMARY KEY,
"disabled" INT NOT NULL DEFAULT 0,
"acl_id" CHAR(36) NOT NULL REFERENCES "acl" ("id") ON DELETE CASCADE,
"organization_id" CHAR(36) NOT NULL REFERENCES "organization" ("id") ON DELETE CASCADE,
"user_id" CHAR(36) NOT NULL REFERENCES "user" ("id") ON DELETE CASCADE
) /* Membership */;
CREATE TABLE IF NOT EXISTS "aerich" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
"version" VARCHAR(255) NOT NULL,
"app" VARCHAR(100) NOT NULL,
"content" JSON NOT NULL
);
CREATE TABLE IF NOT EXISTS "Membership" (
"organization_id" CHAR(36) NOT NULL REFERENCES "organization" ("id") ON DELETE NO ACTION,
"user_id" CHAR(36) NOT NULL REFERENCES "user" ("id") ON DELETE NO ACTION
);
CREATE UNIQUE INDEX IF NOT EXISTS "uidx_Membership_organiz_b0a446" ON "Membership" ("organization_id", "user_id");
CREATE TABLE IF NOT EXISTS "Membership" (
"user_id" CHAR(36) NOT NULL REFERENCES "user" ("id") ON DELETE NO ACTION,
"organization_id" CHAR(36) NOT NULL REFERENCES "organization" ("id") ON DELETE NO ACTION
);
CREATE UNIQUE INDEX IF NOT EXISTS "uidx_Membership_user_id_cc48d3" ON "Membership" ("user_id", "organization_id");"""
async def downgrade(db: BaseDBAsyncClient) -> str:
return """
"""
@@ -1,15 +0,0 @@
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,13 +0,0 @@
from tortoise import BaseDBAsyncClient
async def upgrade(db: BaseDBAsyncClient) -> str:
return """
ALTER TABLE "token" ALTER COLUMN "refresh_token" TYPE TEXT USING "refresh_token"::TEXT;
ALTER TABLE "token" ALTER COLUMN "access_token" TYPE TEXT USING "access_token"::TEXT;"""
async def downgrade(db: BaseDBAsyncClient) -> str:
return """
ALTER TABLE "token" ALTER COLUMN "refresh_token" TYPE VARCHAR(128) USING "refresh_token"::VARCHAR(128);
ALTER TABLE "token" ALTER COLUMN "access_token" TYPE VARCHAR(128) USING "access_token"::VARCHAR(128);"""
+1 -1
View File
@@ -31,7 +31,7 @@ async def login(form: Annotated[OAuth2PasswordRequestForm, Depends()]):
Logs the user into our API, creates tokens and passes them back to User. Logs the user into our API, creates tokens and passes them back to User.
""" """
user: User | None = await User.filter(email=form.username).first() user: User | None = await User.filter(Q(email=form.username) | Q(username=form.username)).first()
if user is None: if user is None:
raise HTTPException(status_code=401, detail=account_error) raise HTTPException(status_code=401, detail=account_error)
@@ -1,21 +1,22 @@
aerich>=0.8.0 aerich>=0.9.0
fastapi[all]>=0.115.5 fastapi[all]>=0.115.12
python-dotenv>=0.21.0 tortoise-orm[asyncpg]>=0.25.1
tortoise-orm[asyncpg]>=0.22.1 uvicorn>=0.34.3
uvicorn>=0.31.1 black>=25.1.0
black>=24.10.0 joserfc>=1.1.0
joserfc>=1.0.1
passlib>=1.7.4 passlib>=1.7.4
pytz>=2024.2 pytz>=2025.2
ptpython>=0.25 ptpython>=3.0.30
msgspec>=0.19.0 msgspec>=0.19.0
bcrypt>=4.2.1 bcrypt>=4.3.0
tomlkit>=0.13.3
# Test Suite # Test Suite
httpx>=0.28.1 httpx>=0.28.1
pytest>=8.3.4 mock>=5.2.0
mock>=5.1.0 pytest>=8.4.0
asyncio>=3.4.3 asyncio>=3.4.3
pytest-mock>=3.14.0 pytest-mock>=3.14.1
pytest-asyncio>=0.25.3 pytest-asyncio>=1.0.0
asgi-lifespan>=2.1.0 asgi-lifespan>=2.1.0
Faker>=37.4.0
+4
View File
@@ -0,0 +1,4 @@
class Test():
pass
+8 -17
View File
@@ -1,19 +1,9 @@
import asyncio import asyncio, httpx, pytest
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from typing import AsyncGenerator from typing import AsyncGenerator
import httpx, pytest from asgi_lifespan import LifespanManager
from config import settings
from glob import glob
from asgi_lifespan import LifespanManager # type: ignore from tests.fixtures.account import *
settings.PSQL_DB_NAME = settings.PSQL_TEST_DB_NAME
pytest_plugins = [
fixture.replace("/", ".").replace("\\", ".").replace(".py", "")
for fixture in glob("tests/fixtures/*.py")
if "__" not in fixture
]
try: try:
from main import app from main import app
@@ -27,12 +17,12 @@ except ImportError:
ClientManagerType = AsyncGenerator[httpx.AsyncClient, None] ClientManagerType = AsyncGenerator[httpx.AsyncClient, None]
@pytest.fixture(scope="session") @pytest.fixture
def anyio_backend(): def anyio_backend():
return "asyncio" return "asyncio"
@pytest.fixture(scope="session") @pytest.fixture
def event_loop(): def event_loop():
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
yield loop yield loop
@@ -43,8 +33,9 @@ def event_loop():
async def client_manager(app, base_url="https://localhost", **kw) -> ClientManagerType: async def client_manager(app, base_url="https://localhost", **kw) -> ClientManagerType:
app.state.testing = True app.state.testing = True
async with LifespanManager(app): async with LifespanManager(app):
transport = httpx.ASGITransport(app=app) async with httpx.AsyncClient(
async with httpx.AsyncClient(transport=transport, base_url=base_url, **kw) as c: transport=httpx.ASGITransport(app=app), base_url=base_url, **kw
) as c:
yield c yield c
View File
+70
View File
@@ -0,0 +1,70 @@
import pytest
from dataclasses import dataclass
from modules.auth.utils import create_jwt_tokens
from modules.organizations.models import Organization, OrganizationType
from modules.users.models import ACL, Membership, User
from modules.auth.models import Token
from tortoise.expressions import Q
from config import settings
crypt = settings.CRYPT
@dataclass
class user_creation_return_type:
user: User
organization: Organization
acl: ACL
tokens: Token
@pytest.fixture()
async def create_user_with_org():
async def inner_function(email="user@localhost.com",
username="user",
name="awesome",
surname="user",
password="password-dont-use",
organization_name="simple organization",
organization_type=OrganizationType.HOME,
is_admin=False) -> user_creation_return_type:
org: Organization | None = await Organization.filter(Q(name=organization_name) & Q(name=organization_type)).first()
if not org:
org: Organization = await Organization.create(
name=organization_name,
type=organization_type
)
user: User | None = await User.filter(Q(email=email)).first()
if not user:
user: User = await User.create(
email=email,
username=username,
name=name,
surname=surname,
password=crypt.hash(password),
)
acl: ACL | None = await ACL.filter(Q(id="5f33facd-08dd-48a1-8f15-3b24f2a727f5")).first()
if not acl:
acl: ACL = await ACL.create(
id="5f33facd-08dd-48a1-8f15-3b24f2a727f5",
READ=True,
WRITE=True,
REPORT=True,
MANAGE=True if is_admin else False,
ADMIN=True if is_admin else False,
)
membership: Membership | None = await Membership.filter(Q(user=user, organization=org, acl=acl)).first()
if not membership:
await Membership.get_or_create(
organization=org,
user=user,
acl=acl
)
tokens: Token = await create_jwt_tokens(user=user)
return user, org, acl, tokens
return inner_function
@@ -1,86 +0,0 @@
from modules.organizations.models import Organization, OrganizationType
from modules.users.models import ACL, Membership, User
import pytest # type=ignore
from config import settings
crypt = settings.CRYPT
@pytest.fixture()
async def use_user_account():
org, _ = await Organization.get_or_create(
id="6ad4c94e-0522-4912-8d16-02d451f4c92d",
defaults={
"name": "User's Organization",
"type": OrganizationType.HOME,
},
)
acl, _ = await ACL.get_or_create(
id="a4e927a3-36e5-4761-badb-0a44ade6616f",
defaults={
"READ": True,
"WRITE": True,
"REPORT": True,
"MANAGE": False,
"ADMIN": False,
},
)
user, _ = await User.get_or_create(
id="24235427-9662-4ba3-a9c5-00000000000b",
defaults={
"email": "user@localhost.com",
"username": "user",
"name": "awesome",
"surname": "user",
"password": crypt.hash("userpassword"),
},
)
membership, _ = await Membership.get_or_create(
id="833b9511-b2da-4760-8fa4-1a5c7059911e",
defaults={
"organization": org,
"user": user,
"acl": acl,
},
)
return org, acl, user, membership
@pytest.fixture()
async def use_admin_account():
org, _ = await Organization.get_or_create(
id="de001f44-1bb8-4667-9f9d-2d62d6ad7270",
defaults={
"name": "Admin's Organization",
"type": OrganizationType.EXTRA_LARGE_ORGANIZATION,
},
)
acl, _ = await ACL.get_or_create(
id="83c1bfe6-c2ed-4ba1-be03-0e5c1960ec31",
defaults={
"READ": True,
"WRITE": True,
"REPORT": True,
"MANAGE": True,
"ADMIN": True,
},
)
user, _ = await User.get_or_create(
id="24235427-9662-4ba3-a9c5-00000000000a",
defaults={
"email": "admin@localhost.com",
"username": "admin",
"name": "awesome",
"surname": "admin",
"password": crypt.hash("adminpassword"),
},
)
membership, _ = await Membership.get_or_create(
id="393473ee-c218-4bcf-82cd-cb676c4d8a33",
defaults={
"organization": org,
"user": user,
"acl": acl,
},
)
return org, acl, user, membership
@@ -1,3 +1,4 @@
from tests.base_test import Test
from modules.users.models import User from modules.users.models import User
import pytest # type: ignore import pytest # type: ignore
from httpx import AsyncClient from httpx import AsyncClient
@@ -8,8 +9,7 @@ from tortoise.expressions import Q
crypt = settings.CRYPT crypt = settings.CRYPT
class TestAuthentication(object): class TestAuthentication(Test):
@pytest.mark.asyncio
async def test_authentication_with_non_existing_user_and_password( async def test_authentication_with_non_existing_user_and_password(
self, client: AsyncClient self, client: AsyncClient
): ):
@@ -24,11 +24,10 @@ class TestAuthentication(object):
assert response.status_code == 401 assert response.status_code == 401
assert response.json() == {"detail": "E-Mail Address or password is incorrect"} assert response.json() == {"detail": "E-Mail Address or password is incorrect"}
@pytest.mark.asyncio
async def test_authentication_with_existing_user_and_wrong_password( async def test_authentication_with_existing_user_and_wrong_password(
self, client: AsyncClient, use_admin_account self, client: AsyncClient, create_user_with_org
): ):
_, _, _, _ = use_admin_account _, _, _, _ = await create_user_with_org()
response = await client.post( response = await client.post(
"https://localhost/api/v1/auth/login", "https://localhost/api/v1/auth/login",
data={ data={
@@ -40,11 +39,10 @@ class TestAuthentication(object):
assert response.status_code == 401 assert response.status_code == 401
assert response.json() == {"detail": "E-Mail Address or password is incorrect"} assert response.json() == {"detail": "E-Mail Address or password is incorrect"}
@pytest.mark.asyncio
async def test_authentication_with_existing_user_and_password( async def test_authentication_with_existing_user_and_password(
self, client: AsyncClient, use_admin_account self, client: AsyncClient, create_user_with_org
): ):
_, _, admin, _ = use_admin_account admin, _, _, _ = await create_user_with_org(email="admin@localhost.com", password="adminpassword")
response = await client.post( response = await client.post(
"https://localhost/api/v1/auth/login", "https://localhost/api/v1/auth/login",
data={ data={
@@ -68,19 +66,19 @@ class TestAuthentication(object):
} }
} }
@pytest.mark.asyncio
async def test_logging_out_destroys_tokens( async def test_logging_out_destroys_tokens(
self, client: AsyncClient, use_user_account self, client: AsyncClient, create_user_with_org
): ):
_, _, user, _ = use_user_account user, _, _, _ = await create_user_with_org(email="superuser@localhost.com", password="superuser")
response = await client.post( response = await client.post(
"https://localhost/api/v1/auth/login", "https://localhost/api/v1/auth/login",
data={ data={
"username": "user@localhost.com", "username": "superuser@localhost.com",
"password": "userpassword", "password": "superuser",
"grant_type": "password", "grant_type": "password",
}, },
) )
print(response.json())
assert response.status_code == 200 assert response.status_code == 200
assert response.json() == { assert response.json() == {
"jwt": { "jwt": {
@@ -115,11 +113,10 @@ class TestAuthentication(object):
"detail": "Refresh token not found or something went wrong." "detail": "Refresh token not found or something went wrong."
} }
@pytest.mark.asyncio
async def test_create_new_tokens_upon_refresh( async def test_create_new_tokens_upon_refresh(
self, client: AsyncClient, use_admin_account self, client: AsyncClient, create_user_with_org
): ):
_, _, admin, _ = use_admin_account admin, _, _, _ = await create_user_with_org(email="admin@localhost.com", password="adminpassword")
token = await client.post( token = await client.post(
"https://localhost/api/v1/auth/login", "https://localhost/api/v1/auth/login",
data={ data={
@@ -165,7 +162,6 @@ class TestAuthentication(object):
} }
} }
@pytest.mark.asyncio
async def test_setup_new_account(self, client: AsyncClient): async def test_setup_new_account(self, client: AsyncClient):
# Ensure account is never available. Prevents account already being available. # Ensure account is never available. Prevents account already being available.
check_if_account_exists: User | None = await User.filter( check_if_account_exists: User | None = await User.filter(