Files
Frederico Castro 143ec401f5 feat(tests): adicionar suite completa de testes automatizados
- 198 testes cobrindo todos os módulos do backend
- Testes unitários para calculador de pontuação (56 testes)
- Testes para value objects de período (23 testes)
- Testes para cliente Elasticsearch com mocks (27 testes)
- Testes para repository de consultores (48 testes)
- Testes de integração ES + Repository (6 testes)
- Testes para API routes FastAPI (23 testes)
- Testes para job de processamento (16 testes)
- Cobertura de 54% do código
2025-12-29 08:06:08 -03:00

509 lines
18 KiB
Python

import pytest
from unittest.mock import AsyncMock, MagicMock, patch
from httpx import Response
from src.infrastructure.elasticsearch.client import ElasticsearchClient
@pytest.fixture
def es_client():
return ElasticsearchClient(
url="http://localhost:9200",
index="atuacapes_test",
user="test_user",
password="test_pass"
)
@pytest.fixture
def mock_httpx_response():
def _create_response(json_data, status_code=200):
response = MagicMock(spec=Response)
response.json.return_value = json_data
response.status_code = status_code
response.raise_for_status = MagicMock()
return response
return _create_response
class TestElasticsearchClientConnect:
@pytest.mark.asyncio
async def test_connect_cria_cliente(self, es_client):
await es_client.connect()
assert es_client._client is not None
await es_client.close()
@pytest.mark.asyncio
async def test_connect_com_auth(self, es_client):
await es_client.connect()
assert es_client._client is not None
await es_client.close()
@pytest.mark.asyncio
async def test_close_fecha_cliente(self, es_client):
await es_client.connect()
await es_client.close()
@pytest.mark.asyncio
async def test_client_property_sem_conexao(self, es_client):
with pytest.raises(RuntimeError, match="não conectado"):
_ = es_client.client
class TestBuscarPorId:
@pytest.mark.asyncio
async def test_buscar_por_id_encontrado(self, es_client, mock_httpx_response):
es_response = {
"hits": {
"total": {"value": 1},
"hits": [{
"_source": {
"id": 12345,
"dadosPessoais": {"nome": "MARIA SILVA"},
"atuacoes": []
}
}]
}
}
with patch.object(es_client, '_client') as mock_client:
mock_client.is_closed = False
mock_client.post = AsyncMock(return_value=mock_httpx_response(es_response))
result = await es_client.buscar_por_id(12345)
assert result is not None
assert result["id"] == 12345
assert result["dadosPessoais"]["nome"] == "MARIA SILVA"
@pytest.mark.asyncio
async def test_buscar_por_id_nao_encontrado(self, es_client, mock_httpx_response):
es_response = {
"hits": {
"total": {"value": 0},
"hits": []
}
}
with patch.object(es_client, '_client') as mock_client:
mock_client.is_closed = False
mock_client.post = AsyncMock(return_value=mock_httpx_response(es_response))
result = await es_client.buscar_por_id(99999)
assert result is None
@pytest.mark.asyncio
async def test_buscar_por_id_erro(self, es_client):
with patch.object(es_client, '_client') as mock_client:
mock_client.is_closed = False
mock_client.post = AsyncMock(side_effect=Exception("Connection failed"))
with pytest.raises(RuntimeError, match="Erro ao buscar consultor"):
await es_client.buscar_por_id(12345)
class TestBuscarPorIds:
@pytest.mark.asyncio
async def test_buscar_por_ids_multiplos(self, es_client, mock_httpx_response):
es_response = {
"hits": {
"hits": [
{"_source": {"id": 1, "dadosPessoais": {"nome": "PESSOA 1"}}},
{"_source": {"id": 2, "dadosPessoais": {"nome": "PESSOA 2"}}},
{"_source": {"id": 3, "dadosPessoais": {"nome": "PESSOA 3"}}}
]
}
}
with patch.object(es_client, '_client') as mock_client:
mock_client.is_closed = False
mock_client.post = AsyncMock(return_value=mock_httpx_response(es_response))
result = await es_client.buscar_por_ids([1, 2, 3])
assert len(result) == 3
assert result[0]["id"] == 1
assert result[2]["dadosPessoais"]["nome"] == "PESSOA 3"
@pytest.mark.asyncio
async def test_buscar_por_ids_vazio(self, es_client):
result = await es_client.buscar_por_ids([])
assert result == []
@pytest.mark.asyncio
async def test_buscar_por_ids_com_source_fields(self, es_client, mock_httpx_response):
es_response = {"hits": {"hits": [{"_source": {"id": 1}}]}}
with patch.object(es_client, '_client') as mock_client:
mock_client.is_closed = False
mock_client.post = AsyncMock(return_value=mock_httpx_response(es_response))
await es_client.buscar_por_ids([1], source_fields=["id", "dadosPessoais.nome"])
call_args = mock_client.post.call_args
query = call_args[1]["json"]
assert "_source" in query
assert "id" in query["_source"]
class TestBuscarDocumentoCompleto:
@pytest.mark.asyncio
async def test_buscar_documento_completo(self, es_client, mock_httpx_response):
es_response = {
"hits": {
"hits": [{
"_index": "atuacapes_test",
"_id": "abc123",
"_score": 1.0,
"_source": {"id": 12345, "dadosPessoais": {"nome": "TESTE"}}
}]
}
}
with patch.object(es_client, '_client') as mock_client:
mock_client.is_closed = False
mock_client.post = AsyncMock(return_value=mock_httpx_response(es_response))
result = await es_client.buscar_documento_completo(12345)
assert result is not None
assert result["_index"] == "atuacapes_test"
assert result["_id"] == "abc123"
assert result["_source"]["id"] == 12345
class TestBuscarComAtuacoes:
@pytest.mark.asyncio
async def test_buscar_com_atuacoes(self, es_client, mock_httpx_response):
es_response = {
"hits": {
"hits": [
{"_source": {"id": 1, "atuacoes": [{"tipo": "Consultor"}]}},
{"_source": {"id": 2, "atuacoes": [{"tipo": "Coordenação de Área de Avaliação"}]}}
]
}
}
with patch.object(es_client, '_client') as mock_client:
mock_client.is_closed = False
mock_client.post = AsyncMock(return_value=mock_httpx_response(es_response))
result = await es_client.buscar_com_atuacoes(size=100)
assert len(result) == 2
assert result[0]["atuacoes"][0]["tipo"] == "Consultor"
@pytest.mark.asyncio
async def test_buscar_com_atuacoes_paginacao(self, es_client, mock_httpx_response):
es_response = {"hits": {"hits": []}}
with patch.object(es_client, '_client') as mock_client:
mock_client.is_closed = False
mock_client.post = AsyncMock(return_value=mock_httpx_response(es_response))
await es_client.buscar_com_atuacoes(size=50, from_=100)
call_args = mock_client.post.call_args
query = call_args[1]["json"]
assert query["size"] == 50
assert query["from"] == 100
class TestContarComAtuacoes:
@pytest.mark.asyncio
async def test_contar_com_atuacoes(self, es_client, mock_httpx_response):
es_response = {"count": 85432}
with patch.object(es_client, '_client') as mock_client:
mock_client.is_closed = False
mock_client.post = AsyncMock(return_value=mock_httpx_response(es_response))
result = await es_client.contar_com_atuacoes()
assert result == 85432
@pytest.mark.asyncio
async def test_contar_com_atuacoes_erro(self, es_client):
with patch.object(es_client, '_client') as mock_client:
mock_client.is_closed = False
mock_client.post = AsyncMock(side_effect=Exception("Timeout"))
with pytest.raises(RuntimeError, match="Erro ao contar consultores"):
await es_client.contar_com_atuacoes()
class TestBuscarCandidatosRanking:
@pytest.mark.asyncio
async def test_buscar_candidatos_ranking(self, es_client, mock_httpx_response):
es_response = {
"hits": {
"hits": [
{"_score": 15.5, "_source": {"id": 1, "dadosPessoais": {"nome": "COORD A"}}},
{"_score": 10.2, "_source": {"id": 2, "dadosPessoais": {"nome": "CONSULTOR B"}}}
]
}
}
with patch.object(es_client, '_client') as mock_client:
mock_client.is_closed = False
mock_client.post = AsyncMock(return_value=mock_httpx_response(es_response))
result = await es_client.buscar_candidatos_ranking(size=100)
assert len(result) == 2
assert result[0]["_score_es"] == 15.5
assert result[1]["_score_es"] == 10.2
class TestScrollAPI:
@pytest.mark.asyncio
async def test_iniciar_scroll(self, es_client, mock_httpx_response):
es_response = {
"_scroll_id": "scroll_123abc",
"hits": {
"total": {"value": 5000},
"hits": [
{"_source": {"id": 1}},
{"_source": {"id": 2}},
{"_source": {"id": 3}}
]
}
}
with patch.object(es_client, '_client') as mock_client:
mock_client.is_closed = False
mock_client.post = AsyncMock(return_value=mock_httpx_response(es_response))
result = await es_client.iniciar_scroll(size=1000)
assert result["scroll_id"] == "scroll_123abc"
assert result["total"] == 5000
assert len(result["hits"]) == 3
@pytest.mark.asyncio
async def test_continuar_scroll(self, es_client, mock_httpx_response):
es_response = {
"_scroll_id": "scroll_456def",
"hits": {
"hits": [
{"_source": {"id": 4}},
{"_source": {"id": 5}}
]
}
}
with patch.object(es_client, '_client') as mock_client:
mock_client.is_closed = False
mock_client.post = AsyncMock(return_value=mock_httpx_response(es_response))
result = await es_client.continuar_scroll("scroll_123abc")
assert result["scroll_id"] == "scroll_456def"
assert len(result["hits"]) == 2
@pytest.mark.asyncio
async def test_limpar_scroll(self, es_client, mock_httpx_response):
with patch.object(es_client, '_client') as mock_client:
mock_client.is_closed = False
mock_client.delete = AsyncMock(return_value=mock_httpx_response({"succeeded": True}))
await es_client.limpar_scroll("scroll_123abc")
mock_client.delete.assert_called_once()
@pytest.mark.asyncio
async def test_limpar_scroll_ignora_erros(self, es_client):
with patch.object(es_client, '_client') as mock_client:
mock_client.is_closed = False
mock_client.delete = AsyncMock(side_effect=Exception("Scroll already expired"))
await es_client.limpar_scroll("scroll_expired")
class TestBuscarTodosConsultores:
@pytest.mark.asyncio
async def test_buscar_todos_consultores_callback(self, es_client, mock_httpx_response):
batches_recebidos = []
progress_recebido = []
async def callback(docs, progress):
batches_recebidos.append(docs)
progress_recebido.append(progress.copy())
scroll_response_1 = {
"_scroll_id": "scroll_1",
"hits": {
"total": {"value": 6},
"hits": [
{"_source": {"id": 1}},
{"_source": {"id": 2}},
{"_source": {"id": 3}}
]
}
}
scroll_response_2 = {
"_scroll_id": "scroll_2",
"hits": {
"hits": [
{"_source": {"id": 4}},
{"_source": {"id": 5}},
{"_source": {"id": 6}}
]
}
}
scroll_response_3 = {
"_scroll_id": "scroll_3",
"hits": {"hits": []}
}
with patch.object(es_client, '_client') as mock_client:
mock_client.is_closed = False
mock_client.post = AsyncMock(side_effect=[
mock_httpx_response(scroll_response_1),
mock_httpx_response(scroll_response_2),
mock_httpx_response(scroll_response_3)
])
mock_client.delete = AsyncMock(return_value=mock_httpx_response({}))
result = await es_client.buscar_todos_consultores(callback, batch_size=3)
assert result["total"] == 6
assert result["processados"] == 6
assert len(batches_recebidos) == 2
assert progress_recebido[0]["percentual"] == 50
assert progress_recebido[1]["percentual"] == 100
@pytest.mark.asyncio
async def test_buscar_todos_limpa_scroll_ao_final(self, es_client, mock_httpx_response):
async def callback(docs, progress):
pass
scroll_response = {
"_scroll_id": "scroll_cleanup",
"hits": {"total": {"value": 0}, "hits": []}
}
with patch.object(es_client, '_client') as mock_client:
mock_client.is_closed = False
mock_client.post = AsyncMock(return_value=mock_httpx_response(scroll_response))
mock_client.delete = AsyncMock(return_value=mock_httpx_response({}))
await es_client.buscar_todos_consultores(callback)
mock_client.delete.assert_called_once()
class TestSugerirConsultores:
@pytest.mark.asyncio
async def test_sugerir_consultores_por_tema(self, es_client, mock_httpx_response):
es_response = {
"hits": {
"hits": [
{"_score": 25.5, "_source": {"id": 1, "dadosPessoais": {"nome": "ESPECIALISTA IA"}}},
{"_score": 20.1, "_source": {"id": 2, "dadosPessoais": {"nome": "PESQUISADOR ML"}}}
]
}
}
with patch.object(es_client, '_client') as mock_client:
mock_client.is_closed = False
mock_client.post = AsyncMock(return_value=mock_httpx_response(es_response))
result = await es_client.sugerir_consultores(tema="inteligência artificial")
assert len(result) == 2
assert result[0]["_score_match"] == 25.5
@pytest.mark.asyncio
async def test_sugerir_consultores_filtro_area(self, es_client, mock_httpx_response):
es_response = {"hits": {"hits": []}}
with patch.object(es_client, '_client') as mock_client:
mock_client.is_closed = False
mock_client.post = AsyncMock(return_value=mock_httpx_response(es_response))
await es_client.sugerir_consultores(
tema="machine learning",
area_avaliacao="CIÊNCIA DA COMPUTAÇÃO"
)
call_args = mock_client.post.call_args
query = call_args[1]["json"]
assert len(query["query"]["bool"]["must"]) >= 2
@pytest.mark.asyncio
async def test_sugerir_consultores_apenas_ativos(self, es_client, mock_httpx_response):
es_response = {"hits": {"hits": []}}
with patch.object(es_client, '_client') as mock_client:
mock_client.is_closed = False
mock_client.post = AsyncMock(return_value=mock_httpx_response(es_response))
await es_client.sugerir_consultores(tema="biologia", apenas_ativos=True)
call_args = mock_client.post.call_args
query = call_args[1]["json"]
assert len(query["query"]["bool"]["must"]) >= 2
class TestListarAreasAvaliacao:
@pytest.mark.asyncio
async def test_listar_areas_avaliacao(self, es_client):
result = await es_client.listar_areas_avaliacao()
assert len(result) > 40
assert any(a["nome"] == "CIÊNCIA DA COMPUTAÇÃO" for a in result)
assert any(a["nome"] == "MEDICINA I" for a in result)
assert all("count" in a for a in result)
class TestIntegracaoCompleta:
@pytest.mark.asyncio
async def test_fluxo_completo_consultor(self, es_client, mock_httpx_response):
doc_consultor = {
"id": 12345,
"dadosPessoais": {
"nome": "MARIA COORDENADORA SILVA",
"nascimento": "1970-05-15"
},
"atuacoes": [
{
"tipo": "Coordenação de Área de Avaliação",
"inicio": "01/01/2020",
"fim": None,
"dadosCoordenacaoArea": {
"tipo": "Coordenador de Área",
"areaAvaliacao": {"nome": "CIÊNCIA DA COMPUTAÇÃO"}
}
},
{
"tipo": "Consultor",
"inicio": "01/01/2015",
"dadosConsultoria": {
"situacaoConsultoria": "Atividade Contínua"
}
}
]
}
with patch.object(es_client, '_client') as mock_client:
mock_client.is_closed = False
mock_client.post = AsyncMock(return_value=mock_httpx_response({
"hits": {"total": {"value": 1}, "hits": [{"_source": doc_consultor}]}
}))
result = await es_client.buscar_por_id(12345)
assert result["id"] == 12345
assert len(result["atuacoes"]) == 2
coord = next(a for a in result["atuacoes"] if "Coordenação" in a["tipo"])
assert coord["dadosCoordenacaoArea"]["tipo"] == "Coordenador de Área"
cons = next(a for a in result["atuacoes"] if a["tipo"] == "Consultor")
assert cons["dadosConsultoria"]["situacaoConsultoria"] == "Atividade Contínua"