Implement Monlet MVP stack and UI updates

This commit is contained in:
Stanislav Rossovskii
2026-05-27 10:01:59 +04:00
parent dcea096327
commit edc51e9c59
145 changed files with 15618 additions and 248 deletions

105
server/tests/conftest.py Normal file
View File

@@ -0,0 +1,105 @@
from __future__ import annotations
import asyncio
import os
from collections.abc import AsyncIterator, Iterator
import pytest
import pytest_asyncio
from alembic.config import Config
from httpx import ASGITransport, AsyncClient
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from testcontainers.postgres import PostgresContainer
from alembic import command
os.environ.setdefault("MONLET_AUTH_TOKEN", "test-token")
os.environ.setdefault("MONLET_ENABLE_DETECTOR", "false")
os.environ.setdefault("MONLET_ENABLE_NOTIFIER_WORKER", "false")
from monlet_server import db as db_module # noqa: E402
from monlet_server.main import create_app # noqa: E402
from monlet_server.settings import get_settings, reset_settings_cache # noqa: E402
HERE = os.path.dirname(__file__)
SERVER_ROOT = os.path.dirname(HERE)
@pytest.fixture(scope="session")
def event_loop() -> Iterator[asyncio.AbstractEventLoop]:
loop = asyncio.new_event_loop()
yield loop
loop.close()
@pytest.fixture(scope="session")
def pg_container() -> Iterator[PostgresContainer]:
with PostgresContainer("postgres:16-alpine") as pg:
yield pg
@pytest.fixture(scope="session")
def database_url(pg_container: PostgresContainer) -> str:
raw = pg_container.get_connection_url()
if raw.startswith("postgresql+psycopg2://"):
async_url = raw.replace("postgresql+psycopg2://", "postgresql+asyncpg://")
elif raw.startswith("postgresql://"):
async_url = raw.replace("postgresql://", "postgresql+asyncpg://", 1)
else:
async_url = raw
return async_url
@pytest.fixture(scope="session", autouse=True)
def _apply_migrations(database_url: str) -> Iterator[None]:
os.environ["MONLET_DATABASE_URL"] = database_url
reset_settings_cache()
cfg = Config(os.path.join(SERVER_ROOT, "alembic.ini"))
cfg.set_main_option("script_location", os.path.join(SERVER_ROOT, "alembic"))
cfg.set_main_option("sqlalchemy.url", get_settings().sync_database_url)
command.upgrade(cfg, "head")
yield
@pytest_asyncio.fixture
async def engine(database_url: str):
eng = create_async_engine(database_url, future=True)
yield eng
await eng.dispose()
@pytest_asyncio.fixture
async def session(engine) -> AsyncIterator[AsyncSession]:
sm = async_sessionmaker(engine, expire_on_commit=False)
async with sm() as s:
yield s
@pytest_asyncio.fixture(autouse=True)
async def _truncate_tables(engine) -> AsyncIterator[None]:
yield
from sqlalchemy import text
async with engine.begin() as conn:
await conn.execute(
text(
"TRUNCATE TABLE notification_outbox, incidents, events, checks, agents RESTART IDENTITY CASCADE"
)
)
@pytest_asyncio.fixture
async def app_client(database_url: str) -> AsyncIterator[AsyncClient]:
reset_settings_cache()
await db_module.dispose_engine()
app = create_app()
async with app.router.lifespan_context(app):
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as client:
yield client
await db_module.dispose_engine()
@pytest.fixture
def auth_headers() -> dict[str, str]:
return {"Authorization": "Bearer test-token"}