Skip to content

Add communities database model #19

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jul 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions app/main.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import logging
from contextlib import asynccontextmanager

from fastapi import FastAPI , Depends, HTTPException
from fastapi.exceptions import RequestValidationError
from httpx import AsyncClient
from typing import List
from sqlmodel.ext.asyncio.session import AsyncSession
from fastapi import FastAPI

from app.services.database.database import init_db, get_session, TestEntry, AsyncSessionLocal
from services.database.database import init_db, AsyncSessionLocal
from app.routers.router import setup_router as setup_router_v2


Expand Down
11 changes: 5 additions & 6 deletions app/services/database/database.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
# database.py
import logging
import os
import logging
from typing import AsyncGenerator

from sqlmodel import SQLModel, create_engine, Field
from sqlalchemy.ext.asyncio import AsyncEngine
from sqlalchemy.orm import sessionmaker

from sqlmodel import SQLModel, create_engine, Field
from sqlmodel.ext.asyncio.session import AsyncSession
from sqlalchemy.ext.asyncio import AsyncEngine

from app.services.database import models

logger = logging.getLogger(__name__)
logger = logging.getLogger(__name__)

# --- Configuração do Banco de Dados ---
# 'sqlite+aiosqlite' para suporte assíncrono com SQLite
Expand Down
8 changes: 0 additions & 8 deletions app/services/database/libraries.py

This file was deleted.

4 changes: 4 additions & 0 deletions app/services/database/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from app.services.database.models.communities import Community
from app.services.database.models.libraries import Library

__all__ = ["Community", "Library"]
11 changes: 11 additions & 0 deletions app/services/database/models/communities.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from typing import Optional

from sqlmodel import SQLModel, Field

class Community(SQLModel, table=True):
__tablename__ = "communities"

id: Optional[int] = Field(default=None, primary_key=True)
username: str
email: str
password: str
12 changes: 12 additions & 0 deletions app/services/database/models/libraries.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from typing import Optional
from sqlmodel import SQLModel, Field

class Library(SQLModel, table=True):
__tablename__ = "libraries"

id: Optional[int] = Field(default=None, primary_key=True)
library_name: str
user_email: str
releases_url: str
logo: str
community_id: Optional[int] = Field(default=None, foreign_key="communities.id")
79 changes: 32 additions & 47 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,21 @@
import os
from collections.abc import AsyncGenerator
from unittest.mock import AsyncMock, patch
from unittest.mock import AsyncMock

import pytest
import pytest_asyncio
from fastapi import FastAPI
from httpx import ASGITransport, AsyncClient



from app.main import app , get_db_session
from app.main import app as fastapi_app, get_db_session
from sqlmodel import SQLModel, create_engine
from sqlmodel.ext.asyncio.session import AsyncSession
from sqlalchemy.ext.asyncio import AsyncEngine
from sqlalchemy.orm import sessionmaker

# Importar todos os modelos SQLModel a serem usados (necessários para as validações de modelo)
from app.services.database.database import TestEntry
from app.services.database import models

@pytest.fixture
def test_app() -> FastAPI:
# Create a mock schema checker
mock_schema_checker = AsyncMock()
mock_schema_checker.validate = AsyncMock(return_value=None)
mock_schema_checker.start = AsyncMock(return_value=None)

# Add the mock to the app
app.schema_checker = mock_schema_checker
return app
# Importar todos os modelos SQLModel a serem usados (necessários para as validações de modelo)


# --- Configurações do Banco de Dados em Memória para Testes ---
# Usamos engine e AsyncSessionLocal apenas para os testes.
# Isso garante que os testes são isolados e usam o banco de dados em memória.
Expand All @@ -38,50 +24,49 @@ def test_app() -> FastAPI:
test_engine: AsyncEngine = AsyncEngine(create_engine(TEST_DATABASE_URL, echo=False, future=True))

# Fábrica de sessões para os testes
TestSessionLocal = sessionmaker(
test_engine, class_=AsyncSession, expire_on_commit=False
)

async def init_test_db():
"""
Inicializa o banco de dados em memória para testes, criando todas as tabelas.
"""
async with test_engine.begin() as conn:
# Cria as tabelas para todos os modelos SQLModel na base de teste
await conn.run_sync(SQLModel.metadata.create_all)
TestSessionLocal = sessionmaker(test_engine, class_=AsyncSession, expire_on_commit=False)

async def override_get_db_session() -> AsyncGenerator[AsyncSession, None]:
async def get_db_session_test() -> AsyncGenerator[AsyncSession, None]:
"""
Sobrescreve a dependência get_db_session para usar a sessão de teste.
"""
async with TestSessionLocal() as session:
yield session

@pytest_asyncio.fixture(scope="function", autouse=True)
async def setup_database():
async with test_engine.begin() as conn:
await conn.run_sync(SQLModel.metadata.create_all)

@pytest_asyncio.fixture(scope="function")
async def session() -> AsyncGenerator[AsyncSession, None]:
async_session_generator = get_db_session_test()
session = await anext(async_session_generator)
yield session
await session.close()

@pytest.fixture
def test_app() -> FastAPI:
# Create a mock schema checker
mock_schema_checker = AsyncMock()
mock_schema_checker.validate = AsyncMock(return_value=None)
mock_schema_checker.start = AsyncMock(return_value=None)

# Add the mock to the app
fastapi_app.schema_checker = mock_schema_checker
fastapi_app.dependency_overrides[get_db_session] = get_db_session_test
yield fastapi_app
fastapi_app.dependency_overrides.clear()

@pytest_asyncio.fixture(scope='function')
async def async_client(test_app: FastAPI) -> AsyncGenerator[AsyncClient, None]:
"""
Cria um cliente assíncrono para testes, com o banco de dados em memória e
dependências sobrescritas.
"""
# Sobrescreve a dependência get_db_session no app principal
test_app.dependency_overrides[get_db_session] = override_get_db_session


# Inicializa o banco de dados em memória e cria as tabelas para cada função de teste
await init_test_db()

async with AsyncClient(
transport=ASGITransport(app=test_app), base_url='http://test'
) as client:
async with AsyncClient(transport=ASGITransport(app=test_app), base_url='http://test') as client:
yield client

# Limpa as tabelas do banco de dados em após cada teste.
# para SQLite in-memory não é necessário, mas é um bom padrão para outros DBs.
async with test_engine.begin() as conn:
await conn.run_sync(SQLModel.metadata.drop_all)
# Limpa as sobrescritas de dependência.
test_app.dependency_overrides.clear()



@pytest.fixture
Expand Down
27 changes: 27 additions & 0 deletions tests/test_communities.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import pytest

from sqlmodel import select
from sqlmodel.ext.asyncio.session import AsyncSession

from services.database.models import Community


@pytest.mark.asyncio
async def test_insert_communities(session: AsyncSession):
community = Community(
username="admin",
email="[email protected]",
password="@teste123",
)
session.add(community)
await session.commit()
await session.refresh(community)

statement = select(Community).where(Community.username == "admin")
result = await session.exec(statement)
found = result.first()

assert found is not None
assert found.username == "admin"
assert found.email == "[email protected]"
assert found.password == "@teste123"
38 changes: 1 addition & 37 deletions tests/test_database.py
Original file line number Diff line number Diff line change
@@ -1,38 +1,2 @@
## Test cases for database SQLite using in memory database fixture from conftest.py
# Test cases for database SQLite using in memory database fixture from conftest.py
#

import pytest
from fastapi import status
from fastapi.testclient import TestClient
from sqlmodel import Session, select #, SQLModel, create_engine
from httpx import AsyncClient

@pytest.mark.asyncio
async def test_create_and_read_test_entry(async_client: AsyncClient):
response = await async_client.post("/test-entry/?message=test_message")
assert response.status_code == 200
assert response.json()["message"] == "test_message"

response = await async_client.get("/test-entries/")
assert response.status_code == 200
assert len(response.json()) == 1
assert response.json()[0]["message"] == "test_message"



@pytest.mark.asyncio
async def test_session_isolation_and_db_reset(async_client: AsyncClient):
"""
Testa se as sessões são isoladas e se o banco de dados de teste é reiniciado
entre as chamadas do cliente assíncrono.

Este teste usa o `async_client` para simular requisições HTTP, garantindo
que a dependência de sessão seja sobrescrita e o DB limpo a cada teste.
"""

print("\n--- Segunda Interação: Verificando banco de dados limpo ---")
response_get_part2 = await async_client.get("/test-entries/")
assert response_get_part2.status_code == 200
print(f"Dados encontrados na segunda interação (esperado 0): {response_get_part2.json()}")
print("Banco de dados reiniciado com sucesso entre os testes.")
assert len(response_get_part2.json()) == 0
21 changes: 13 additions & 8 deletions tests/test_healthcheck.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
from typing import Mapping

import pytest
from fastapi import status
from httpx import AsyncClient


@pytest.mark.asyncio
async def test_healthcheck_endpoint(async_client, mock_headers):
async def test_healthcheck_endpoint(
async_client: AsyncClient, mock_headers: Mapping[str, str]
):
"""Test the healthcheck endpoint returns correct status and version."""
#response = await async_client.get('/v2/healthcheck', headers=mock_headers)
response = await async_client.get('/api/healthcheck', headers=mock_headers)
# response = await async_client.get('/v2/healthcheck', headers=mock_headers)
response = await async_client.get("/api/healthcheck", headers=mock_headers)

assert response.status_code == status.HTTP_200_OK
assert response.json() == {'status': 'healthy', 'version': '2.0.0'}
assert response.json() == {"status": "healthy", "version": "2.0.0"}


@pytest.mark.asyncio
async def test_healthcheck_endpoint_without_auth(async_client):
async def test_healthcheck_endpoint_without_auth(async_client: AsyncClient):
"""Test the healthcheck endpoint without authentication headers."""
#response = await async_client.get('/v2/healthcheck')
response = await async_client.get('/api/healthcheck')
# response = await async_client.get('/v2/healthcheck')
response = await async_client.get("/api/healthcheck")

assert response.status_code == status.HTTP_200_OK
assert response.json() == {'status': 'healthy', 'version': '2.0.0'}
assert response.json() == {"status": "healthy", "version": "2.0.0"}
39 changes: 39 additions & 0 deletions tests/test_libraries.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import pytest
import pytest_asyncio

from sqlmodel import select
from sqlmodel.ext.asyncio.session import AsyncSession

from services.database.models import Community
from services.database.models import Library

@pytest_asyncio.fixture
async def community(session: AsyncSession):
community = Community(username="admin", email="[email protected]", password="123")
session.add(community)
await session.commit()
await session.refresh(community)
return community

@pytest.mark.asyncio
async def test_insert_libraries(session: AsyncSession, community: Community):
library = Library(
library_name="DevOps",
user_email="[email protected]",
releases_url="http://teste.com",
logo="logo",
community_id=community.id,
)
session.add(library)
await session.commit()

statement = select(Library).where(Library.library_name == "DevOps")
result = await session.exec(statement)
found = result.first()

assert found is not None
assert found.library_name == "DevOps"
assert found.user_email == "[email protected]"
assert found.releases_url == "http://teste.com"
assert found.logo == "logo"
assert found.community_id == community.id