393 lines
14 KiB
Python
393 lines
14 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
from typing import Dict
|
|
|
|
from appwrite.exception import AppwriteException
|
|
import pytest
|
|
from fastapi import HTTPException, status
|
|
from fastapi.testclient import TestClient
|
|
|
|
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
|
if str(PROJECT_ROOT) not in sys.path:
|
|
sys.path.insert(0, str(PROJECT_ROOT))
|
|
|
|
os.environ.setdefault("APPWRITE_ENDPOINT", "http://localhost/v1")
|
|
os.environ.setdefault("APPWRITE_PROJECT_ID", "test-project")
|
|
os.environ.setdefault("APPWRITE_API_KEY", "test-key")
|
|
os.environ.setdefault("APPWRITE_DATABASE_ID", "test-db")
|
|
os.environ.setdefault("APPWRITE_COLLECTION_PEDIDOS_ID", "test-orders")
|
|
os.environ.setdefault("APPWRITE_COLLECTION_CARRINHOS_ID", "test-carts")
|
|
os.environ.setdefault("APPWRITE_COLLECTION_CATEGORIAS_ID", "test-categorias")
|
|
os.environ.setdefault("APPWRITE_COLLECTION_PRODUTOS_CATALOGO_ID", "test-produtos")
|
|
os.environ.setdefault("APPWRITE_COLLECTION_LABORATORIOS_ID", "test-labs")
|
|
os.environ.setdefault("APPWRITE_COLLECTION_CATEGORIAS_ID", "test-cat-rel")
|
|
os.environ.setdefault("APPWRITE_COLLECTION_SUBCATEGORIAS_ID", "test-subcat")
|
|
os.environ.setdefault("APPWRITE_COLLECTION_PAGAMENTOS_ID", "test-payments")
|
|
os.environ.setdefault("APPWRITE_COLLECTION_PRODUTOS_ESTOQUE_ID", "test-produtos-estoque")
|
|
os.environ.setdefault("APPWRITE_COLLECTION_PRODUTOS_VENDA_ID", "test-produtos-venda")
|
|
os.environ.setdefault("APPWRITE_COLLECTION_USUARIOS_DATA_ID", "test-usuarios-data")
|
|
os.environ.setdefault("APPWRITE_COLLECTION_USUARIOS_PERFIL_ID", "test-usuarios-perfil")
|
|
os.environ.setdefault("APPWRITE_COLLECTION_USUARIO_EMPRESA_PERFIS_ID", "test-usuario-empresa-perfis")
|
|
os.environ.setdefault("APPWRITE_COLLECTION_FATURAS_ID", "test-faturas")
|
|
os.environ.setdefault("APPWRITE_COLLECTION_USUARIOS_ID", "test-users")
|
|
os.environ.setdefault("APPWRITE_COLLECTION_EMPRESAS_ID", "test-companies")
|
|
os.environ.setdefault("APPWRITE_COLLECTION_EMPRESAS_DADOS_ID", "test-companies-data")
|
|
os.environ.setdefault("APPWRITE_COLLECTION_EMPRESAS_SOCIOS_ID", "test-empresas-socios")
|
|
os.environ.setdefault("APPWRITE_COLLECTION_ENDERECOS_ID", "test-addresses")
|
|
os.environ.setdefault("APPWRITE_COLLECTION_ENTREGAS_ID", "test-entregas")
|
|
os.environ.setdefault("MERCADO_PAGO_ACCESS_TOKEN", "test-mercado-pago-token")
|
|
os.environ.setdefault("MERCADO_PAGO_PUBLIC_KEY", "test-mercado-pago-key")
|
|
os.environ.setdefault("JWT_SECRET", "test-secret")
|
|
os.environ.setdefault("JWT_ALGORITHM", "HS256")
|
|
|
|
from src.app.main import app
|
|
from src.core.deps import get_pedidos_service, require_api_key, require_user
|
|
from src.modules.pedidos.schemas import (
|
|
PedidoCreateRequest,
|
|
PedidoOut,
|
|
PedidoStatusUpdate,
|
|
PedidoUpdate,
|
|
)
|
|
from src.modules.pedidos.service import PedidosService
|
|
|
|
|
|
class InMemoryPedidosService:
|
|
def __init__(self) -> None:
|
|
self._items: Dict[str, Dict[str, object]] = {}
|
|
self._counter = 0
|
|
|
|
async def list(
|
|
self,
|
|
page: int,
|
|
limit: int | None,
|
|
q: str | None,
|
|
usuario_id: str | None,
|
|
sort: str | None,
|
|
order: str | None,
|
|
) -> Dict[str, object]:
|
|
limit_value = limit
|
|
items = list(self._items.values())
|
|
items.sort(key=lambda item: item.get("createdAt", ""), reverse=True)
|
|
if usuario_id:
|
|
items = [item for item in items if item.get("usuarios") == usuario_id]
|
|
total = len(items)
|
|
return {
|
|
"page": page,
|
|
"limit": limit_value,
|
|
"total": total,
|
|
"items": [PedidoOut(**item).model_dump() for item in items],
|
|
}
|
|
|
|
async def get(self, pedido_id: str) -> PedidoOut:
|
|
if pedido_id not in self._items:
|
|
raise AssertionError("Pedido not found in test store")
|
|
return PedidoOut(**self._items[pedido_id])
|
|
|
|
async def create(self, payload: PedidoCreateRequest) -> PedidoOut:
|
|
data = payload.data.model_dump(by_alias=True, exclude_unset=True, exclude_none=True)
|
|
self._counter += 1
|
|
identifier = f"order_{self._counter}"
|
|
timestamp = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
|
|
record = {
|
|
"id": identifier,
|
|
"status": data.get("status", "pendente"),
|
|
"valor_total": data.get("valor-total"),
|
|
"itens": data.get("itens", []),
|
|
"quantidade": data.get("quantidade", []),
|
|
"pagamentos": data.get("pagamentos"),
|
|
"carrinhos": data.get("carrinhos"),
|
|
"usuarios": data.get("usuarios"),
|
|
"faturas": data.get("faturas"),
|
|
"createdAt": timestamp,
|
|
"updatedAt": timestamp,
|
|
}
|
|
self._items[identifier] = record
|
|
return PedidoOut(**record)
|
|
|
|
async def update(self, pedido_id: str, payload: PedidoUpdate) -> PedidoOut:
|
|
if pedido_id not in self._items:
|
|
raise AssertionError("Pedido not found in test store")
|
|
stored = self._items[pedido_id].copy()
|
|
data = payload.model_dump(by_alias=True, exclude_unset=True, exclude_none=True)
|
|
stored.update({k: v for k, v in data.items() if v is not None})
|
|
stored["updatedAt"] = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
|
|
self._items[pedido_id] = stored
|
|
return PedidoOut(**stored)
|
|
|
|
async def update_status(self, pedido_id: str, payload: PedidoStatusUpdate) -> PedidoOut:
|
|
if pedido_id not in self._items:
|
|
raise AssertionError("Pedido not found in test store")
|
|
stored = self._items[pedido_id].copy()
|
|
stored["status"] = payload.status
|
|
stored["updatedAt"] = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
|
|
self._items[pedido_id] = stored
|
|
return PedidoOut(**stored)
|
|
|
|
async def delete(self, pedido_id: str) -> None:
|
|
self._items.pop(pedido_id, None)
|
|
|
|
|
|
@pytest.fixture()
|
|
def test_client() -> TestClient:
|
|
service = InMemoryPedidosService()
|
|
|
|
def override_service() -> InMemoryPedidosService:
|
|
return service
|
|
|
|
app.dependency_overrides[get_pedidos_service] = override_service
|
|
app.dependency_overrides[require_api_key] = lambda: None
|
|
app.dependency_overrides[require_user] = lambda: {"id": "user_1"}
|
|
|
|
client = TestClient(app)
|
|
yield client
|
|
|
|
app.dependency_overrides.clear()
|
|
client.close()
|
|
|
|
|
|
def test_ping_route_is_available(test_client: TestClient) -> None:
|
|
response = test_client.get("/api/v1/ping")
|
|
assert response.status_code == 200
|
|
|
|
|
|
def test_create_pedido_returns_201(test_client: TestClient) -> None:
|
|
payload = {
|
|
"documentId": "unique()",
|
|
"data": {
|
|
"status": "pendente",
|
|
"valor-total": 99.5,
|
|
"itens": ["item_1"],
|
|
"quantidade": [1],
|
|
"usuarios": "user_1",
|
|
"carrinhos": "cart_1",
|
|
"pagamentos": "pay_1",
|
|
"faturas": "fat_1",
|
|
},
|
|
}
|
|
response = test_client.post("/api/v1/pedidos", json=payload)
|
|
assert response.status_code == 201
|
|
body = response.json()
|
|
assert body["status"] == "pendente"
|
|
assert body["itens"] == ["item_1"]
|
|
assert body["usuarios"] == "user_1"
|
|
|
|
|
|
def test_list_pedidos_returns_created_items(test_client: TestClient) -> None:
|
|
payload = {
|
|
"documentId": "unique()",
|
|
"data": {
|
|
"status": "pendente",
|
|
"valor-total": 50.0,
|
|
"itens": ["item_1"],
|
|
"quantidade": [1],
|
|
"usuarios": "user_1",
|
|
"carrinhos": "cart_1",
|
|
"pagamentos": "pay_1",
|
|
"faturas": "fat_1",
|
|
},
|
|
}
|
|
test_client.post("/api/v1/pedidos", json=payload)
|
|
|
|
response = test_client.get("/api/v1/pedidos")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["total"] == 1
|
|
assert len(data["items"]) == 1
|
|
|
|
|
|
def test_list_pedidos_returns_latest_first(test_client: TestClient) -> None:
|
|
payload = {
|
|
"documentId": "unique()",
|
|
"data": {
|
|
"status": "pendente",
|
|
"valor-total": 10.0,
|
|
"itens": ["item_1"],
|
|
"quantidade": [1],
|
|
"usuarios": "user_1",
|
|
"carrinhos": "cart_1",
|
|
"pagamentos": "pay_1",
|
|
"faturas": "fat_1",
|
|
},
|
|
}
|
|
|
|
response_first = test_client.post("/api/v1/pedidos", json=payload)
|
|
assert response_first.status_code == 201
|
|
first_id = response_first.json()["id"]
|
|
|
|
response_second = test_client.post("/api/v1/pedidos", json=payload)
|
|
assert response_second.status_code == 201
|
|
second_id = response_second.json()["id"]
|
|
|
|
list_response = test_client.get("/api/v1/pedidos")
|
|
assert list_response.status_code == 200
|
|
items = list_response.json()["items"]
|
|
assert [item["id"] for item in items[:2]] == [second_id, first_id]
|
|
|
|
|
|
def test_list_pedidos_filters_by_usuario_id(test_client: TestClient) -> None:
|
|
payload_user1 = {
|
|
"documentId": "unique()",
|
|
"data": {
|
|
"status": "pendente",
|
|
"valor-total": 50.0,
|
|
"itens": ["item_1"],
|
|
"quantidade": [1],
|
|
"usuarios": "user_1",
|
|
"carrinhos": "cart_1",
|
|
"pagamentos": "pay_1",
|
|
"faturas": "fat_1",
|
|
},
|
|
}
|
|
payload_user2 = {
|
|
"documentId": "unique()",
|
|
"data": {
|
|
"status": "pendente",
|
|
"valor-total": 60.0,
|
|
"itens": ["item_2"],
|
|
"quantidade": [1],
|
|
"usuarios": "user_2",
|
|
"carrinhos": "cart_2",
|
|
"pagamentos": "pay_2",
|
|
"faturas": "fat_2",
|
|
},
|
|
}
|
|
|
|
test_client.post("/api/v1/pedidos", json=payload_user1)
|
|
test_client.post("/api/v1/pedidos", json=payload_user2)
|
|
|
|
response = test_client.get("/api/v1/pedidos", params={"usuario_id": "user_2"})
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["total"] == 1
|
|
assert len(data["items"]) == 1
|
|
assert data["items"][0]["usuarios"] == "user_2"
|
|
|
|
|
|
def test_update_status_endpoint(test_client: TestClient) -> None:
|
|
payload = {
|
|
"documentId": "unique()",
|
|
"data": {
|
|
"status": "pendente",
|
|
"itens": ["item_1"],
|
|
"quantidade": [1],
|
|
"usuarios": "user_1",
|
|
"carrinhos": "cart_1",
|
|
"pagamentos": "pay_1",
|
|
"faturas": "fat_1",
|
|
},
|
|
}
|
|
creation = test_client.post("/api/v1/pedidos", json=payload)
|
|
identifier = creation.json()["id"]
|
|
|
|
response = test_client.patch(
|
|
f"/api/v1/pedidos/{identifier}/status",
|
|
json={"status": "aprovado"},
|
|
)
|
|
assert response.status_code == 200
|
|
assert response.json()["status"] == "aprovado"
|
|
|
|
|
|
@pytest.mark.asyncio()
|
|
async def test_create_pedido_translates_appwrite_401_error() -> None:
|
|
class FakeDatabases:
|
|
def create_document(self, *args, **kwargs):
|
|
raise AppwriteException(
|
|
"The current user is not authorized to perform the requested action.",
|
|
401,
|
|
)
|
|
|
|
service = PedidosService(
|
|
databases=FakeDatabases(),
|
|
database_id="db",
|
|
collection_id="col",
|
|
)
|
|
|
|
payload = PedidoCreateRequest.model_validate(
|
|
{
|
|
"documentId": "unique()",
|
|
"data": {
|
|
"status": "pendente",
|
|
"valor-total": 10.0,
|
|
"itens": [],
|
|
"quantidade": [],
|
|
},
|
|
}
|
|
)
|
|
|
|
with pytest.raises(HTTPException) as exc_info:
|
|
await service.create(payload)
|
|
|
|
assert exc_info.value.status_code == status.HTTP_401_UNAUTHORIZED
|
|
assert "Falha na autenticação com Appwrite" in exc_info.value.detail
|
|
|
|
|
|
@pytest.mark.asyncio()
|
|
async def test_list_with_usuario_id_preserves_pagination() -> None:
|
|
documents = [
|
|
{
|
|
"$id": f"order_{index}",
|
|
"status": "pendente",
|
|
"valor-total": 9.99,
|
|
"itens": ["item_1"],
|
|
"quantidade": [1],
|
|
"usuarios": "user_1",
|
|
"pagamentos": None,
|
|
"carrinhos": None,
|
|
"faturas": None,
|
|
"$createdAt": f"2025-01-0{index}T12:00:00.000+00:00",
|
|
"$updatedAt": f"2025-01-0{index}T12:00:00.000+00:00",
|
|
}
|
|
for index in range(1, 6)
|
|
]
|
|
|
|
class FakeDatabases:
|
|
def list_documents(self, _database_id, _collection_id, queries=None):
|
|
limit = 20
|
|
offset = 0
|
|
filtered = list(documents)
|
|
|
|
for query in queries or []:
|
|
query_obj = json.loads(query) if isinstance(query, str) else query
|
|
method = query_obj.get("method")
|
|
if method == "limit":
|
|
limit = int(query_obj["values"][0])
|
|
elif method == "offset":
|
|
offset = int(query_obj["values"][0])
|
|
elif method == "equal" and query_obj.get("attribute") == "usuarios":
|
|
value = query_obj["values"][0]
|
|
filtered = [doc for doc in filtered if doc.get("usuarios") == value]
|
|
elif method == "orderDesc":
|
|
attribute = query_obj.get("attribute")
|
|
filtered.sort(key=lambda doc: doc.get(attribute), reverse=True)
|
|
elif method == "orderAsc":
|
|
attribute = query_obj.get("attribute")
|
|
filtered.sort(key=lambda doc: doc.get(attribute))
|
|
|
|
total = len(filtered)
|
|
sliced = filtered[offset : offset + limit]
|
|
return {"documents": sliced, "total": total}
|
|
|
|
service = PedidosService(
|
|
databases=FakeDatabases(),
|
|
database_id="db",
|
|
collection_id="col",
|
|
)
|
|
|
|
result = await service.list(
|
|
page=2,
|
|
limit=2,
|
|
q=None,
|
|
usuario_id="user_1",
|
|
sort=None,
|
|
order=None,
|
|
)
|
|
|
|
assert result["page"] == 2
|
|
assert result["limit"] == 2
|
|
assert result["total"] == 5
|
|
assert [item.id for item in result["items"]] == ["order_3", "order_2"]
|