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
This commit is contained in:
Frederico Castro
2025-12-29 08:06:08 -03:00
parent e0692ee49c
commit 143ec401f5
19 changed files with 2899 additions and 0 deletions

View File

@@ -0,0 +1,483 @@
import pytest
from datetime import datetime
from unittest.mock import AsyncMock, MagicMock
from src.infrastructure.repositories.consultor_repository_impl import ConsultorRepositoryImpl
@pytest.fixture
def mock_es_client():
return AsyncMock()
@pytest.fixture
def repository(mock_es_client):
return ConsultorRepositoryImpl(es_client=mock_es_client)
class TestParseDate:
def test_parse_date_formato_brasileiro(self, repository):
result = repository._parse_date("15/03/2020")
assert result is not None
assert result.day == 15
assert result.month == 3
assert result.year == 2020
def test_parse_date_formato_iso(self, repository):
result = repository._parse_date("2020-03-15")
assert result is not None
assert result.year == 2020
assert result.month == 3
assert result.day == 15
def test_parse_date_none(self, repository):
result = repository._parse_date(None)
assert result is None
def test_parse_date_string_vazia(self, repository):
result = repository._parse_date("")
assert result is None
def test_parse_date_invalida(self, repository):
result = repository._parse_date("data_invalida")
assert result is None
class TestInferirTipoCoordenacao:
def test_coordenador_area(self, repository):
coord = {"dadosCoordenacaoArea": {"tipo": "Coordenador de Área"}}
result = repository._inferir_tipo_coordenacao(coord)
assert result == "CA"
def test_coordenador_adjunto(self, repository):
coord = {"dadosCoordenacaoArea": {"tipo": "Coordenador Adjunto"}}
result = repository._inferir_tipo_coordenacao(coord)
assert result == "CAJ"
def test_coordenador_adjunto_mp(self, repository):
coord = {"dadosCoordenacaoArea": {"tipo": "Coordenador Adjunto de Mestrado Profissionalizante"}}
result = repository._inferir_tipo_coordenacao(coord)
assert result == "CAJ_MP"
def test_camara_tematica(self, repository):
coord = {"dadosCoordenacaoArea": {"tipo": "Câmara Temática"}}
result = repository._inferir_tipo_coordenacao(coord)
assert result == "CAM"
def test_inferencia_por_descricao(self, repository):
coord = {"dadosCoordenacaoArea": {}, "descricao": "Coordenador Adjunto - MEDICINA"}
result = repository._inferir_tipo_coordenacao(coord)
assert result == "CAJ"
def test_fallback_para_ca(self, repository):
coord = {"dadosCoordenacaoArea": {"tipo": "Tipo Desconhecido"}}
result = repository._inferir_tipo_coordenacao(coord)
assert result == "CA"
class TestInferirPremiacaoTipo:
def test_grande_premio(self):
result = ConsultorRepositoryImpl._inferir_premiacao_tipo("Grande Prêmio CAPES")
assert result == "GP"
def test_mencao_honrosa(self):
result = ConsultorRepositoryImpl._inferir_premiacao_tipo("Menção Honrosa")
assert result == "MENCAO"
def test_premio_regular(self):
result = ConsultorRepositoryImpl._inferir_premiacao_tipo("Prêmio CAPES de Tese")
assert result == "PREMIO"
def test_texto_sem_premiacao(self):
result = ConsultorRepositoryImpl._inferir_premiacao_tipo("Texto qualquer")
assert result is None
def test_texto_vazio(self):
result = ConsultorRepositoryImpl._inferir_premiacao_tipo("")
assert result is None
class TestExtrairCoordenacoesCapes:
def test_lista_vazia(self, repository):
result = repository._extrair_coordenacoes_capes([])
assert result == []
def test_atuacao_sem_tipo_coordenacao(self, repository):
atuacoes = [{"tipo": "Consultor"}]
result = repository._extrair_coordenacoes_capes(atuacoes)
assert result == []
def test_coordenacao_ativa(self, repository):
atuacoes = [{
"tipo": "Coordenação de Área de Avaliação",
"inicio": "01/01/2020",
"fim": None,
"dadosCoordenacaoArea": {
"tipo": "Coordenador de Área",
"inicioVinculacao": "01/01/2020",
"fimVinculacao": None,
"areaAvaliacao": {"nome": "CIÊNCIAS AMBIENTAIS"},
}
}]
result = repository._extrair_coordenacoes_capes(atuacoes)
assert len(result) == 1
assert result[0].codigo == "CA"
assert result[0].area_avaliacao == "CIÊNCIAS AMBIENTAIS"
assert result[0].periodo.ativo is True
def test_coordenacao_historica(self, repository):
atuacoes = [{
"tipo": "Histórico de Coordenação de Área de Avaliação",
"dadosCoordenacaoArea": {
"tipo": "Coordenador Adjunto",
"inicioVinculacao": "01/01/2018",
"fimVinculacao": "31/12/2020",
"areaAvaliacao": {"nome": "MEDICINA I"},
}
}]
result = repository._extrair_coordenacoes_capes(atuacoes)
assert len(result) == 1
assert result[0].codigo == "CAJ"
assert result[0].periodo.ativo is False
def test_coordenacao_sem_inicio_ignorada(self, repository):
atuacoes = [{
"tipo": "Coordenação de Área de Avaliação",
"dadosCoordenacaoArea": {
"tipo": "Coordenador de Área",
"inicioVinculacao": None,
"areaAvaliacao": {"nome": "CIÊNCIAS"},
}
}]
result = repository._extrair_coordenacoes_capes(atuacoes)
assert len(result) == 0
class TestExtrairConsultoria:
def test_sem_consultoria(self, repository):
result = repository._extrair_consultoria([])
assert result is None
def test_consultor_ativo(self, repository):
atuacoes = [{
"tipo": "Consultor",
"inicio": "01/01/2020",
"fim": None,
"dadosConsultoria": {
"situacaoConsultoria": "Atividade Contínua",
"inicioVinculacao": "01/01/2020",
"ies": {"id": "1", "nome": "USP", "sigla": "USP"},
}
}]
result = repository._extrair_consultoria(atuacoes)
assert result is not None
assert result.codigo == "CONS_ATIVO"
assert result.periodo.ativo is True
def test_consultor_historico(self, repository):
atuacoes = [{
"tipo": "Histórico de Consultoria",
"inicio": "01/01/2015",
"fim": "31/12/2018",
"dadosConsultoria": {
"situacaoConsultoria": "Desligado",
"inicioVinculacao": "01/01/2015",
"inativacaoSituacao": "31/12/2018",
}
}]
result = repository._extrair_consultoria(atuacoes)
assert result is not None
assert result.codigo == "CONS_HIST"
def test_consultor_falecido(self, repository):
atuacoes = [{
"tipo": "Consultor",
"inicio": "01/01/2010",
"fim": "31/12/2020",
"dadosConsultoria": {
"situacaoConsultoria": "Falecido",
}
}]
result = repository._extrair_consultoria(atuacoes)
assert result is not None
assert result.codigo == "CONS_FALECIDO"
class TestExtrairInscricoes:
def test_sem_inscricoes(self, repository):
result = repository._extrair_inscricoes([])
assert result == []
def test_inscricao_autor(self, repository):
atuacoes = [{
"tipo": "Inscrição Prêmio",
"inicio": "01/01/2024",
"dadosParticipacaoInscricaoPremio": {
"tipo": "Autor",
"nomePremio": "PCT",
"ano": 2024,
}
}]
result = repository._extrair_inscricoes(atuacoes)
assert len(result) == 1
assert result[0].codigo == "INSC_AUTOR"
assert result[0].ano == 2024
def test_inscricao_institucional(self, repository):
atuacoes = [{
"tipo": "Inscrição Prêmio",
"dadosParticipacaoInscricaoPremio": {
"tipo": "Coordenador PPG",
"nomePremio": "PCT",
"ano": 2024,
}
}]
result = repository._extrair_inscricoes(atuacoes)
assert len(result) == 1
assert result[0].codigo == "INSC_INST_AUTOR"
class TestExtrairAvaliacoesComissao:
def test_sem_avaliacoes(self, repository):
result = repository._extrair_avaliacoes_comissao([])
assert result == []
def test_avaliador_premio(self, repository):
atuacoes = [{
"tipo": "Avaliação Prêmio",
"dadosParticipacaoPremio": {
"tipo": "Membro de Comissão",
"nomePremio": "Prêmio CAPES de Tese",
"ano": 2024,
}
}]
result = repository._extrair_avaliacoes_comissao(atuacoes)
assert len(result) == 1
assert result[0].codigo == "AVAL_COMIS_PREMIO"
def test_avaliador_grande_premio(self, repository):
atuacoes = [{
"tipo": "Avaliação Prêmio",
"dadosParticipacaoPremio": {
"tipo": "Membro de Comissão",
"nomePremio": "Grande Prêmio CAPES",
"ano": 2024,
}
}]
result = repository._extrair_avaliacoes_comissao(atuacoes)
assert len(result) == 1
assert result[0].codigo == "AVAL_COMIS_GP"
def test_coordenador_comissao(self, repository):
atuacoes = [{
"tipo": "Avaliação Prêmio",
"dadosParticipacaoPremio": {
"tipo": "Coordenador/Presidente",
"nomePremio": "Prêmio CAPES",
"ano": 2024,
}
}]
result = repository._extrair_avaliacoes_comissao(atuacoes)
assert len(result) == 1
assert result[0].codigo == "COORD_COMIS_PREMIO"
class TestExtrairPremiacoes:
def test_sem_premiacoes(self, repository):
result = repository._extrair_premiacoes([])
assert result == []
def test_grande_premio(self, repository):
atuacoes = [{
"tipo": "Premiação Prêmio",
"dadosPremiacaoPremio": {
"tipoPremiacao": "Grande Prêmio",
"nomePremio": "Grande Prêmio CAPES de Tese",
"ano": 2024,
}
}]
result = repository._extrair_premiacoes(atuacoes)
assert len(result) == 1
assert result[0].codigo == "PREMIACAO_GP_AUTOR"
def test_premio_regular(self, repository):
atuacoes = [{
"tipo": "Premiação Prêmio",
"dadosPremiacaoPremio": {
"tipoPremiacao": "Prêmio",
"nomePremio": "Prêmio CAPES de Tese",
"ano": 2024,
}
}]
result = repository._extrair_premiacoes(atuacoes)
assert len(result) == 1
assert result[0].codigo == "PREMIACAO_AUTOR"
def test_mencao_honrosa(self, repository):
atuacoes = [{
"tipo": "Premiação Prêmio",
"dadosPremiacaoPremio": {
"tipoPremiacao": "Menção Honrosa",
"nomePremio": "PCT",
"ano": 2024,
}
}]
result = repository._extrair_premiacoes(atuacoes)
assert len(result) == 1
assert result[0].codigo == "MENCAO_AUTOR"
class TestExtrairBolsasCNPQ:
def test_sem_bolsas(self, repository):
result = repository._extrair_bolsas_cnpq([])
assert result == []
def test_bolsa_cnpq(self, repository):
atuacoes = [{
"tipo": "Bolsa CNPQ",
"dadosBolsa": {
"nivel": "1A",
"areaConhecimento": "Ciências Exatas",
}
}]
result = repository._extrair_bolsas_cnpq(atuacoes)
assert len(result) == 1
assert result[0].codigo == "BOL_BPQ_NIVEL"
assert result[0].nivel == "1A"
class TestExtrairParticipacoes:
def test_sem_participacoes(self, repository):
result = repository._extrair_participacoes([])
assert result == []
def test_evento(self, repository):
atuacoes = [{
"tipo": "Evento",
"descricao": "Seminário CAPES 2024",
"inicio": "01/06/2024",
}]
result = repository._extrair_participacoes(atuacoes)
assert len(result) == 1
assert result[0].codigo == "EVENTO"
assert result[0].ano == 2024
def test_projeto(self, repository):
atuacoes = [{
"tipo": "Projeto",
"descricao": "Projeto de Pesquisa",
"inicio": "01/01/2024",
}]
result = repository._extrair_participacoes(atuacoes)
assert len(result) == 1
assert result[0].codigo == "PROJ"
class TestExtrairOrientacoes:
def test_sem_orientacoes(self, repository):
result = repository._extrair_orientacoes([])
assert result == []
def test_orientacao_discentes_contagem(self, repository):
atuacoes = [{
"tipo": "Orientação de Discentes",
"dadosOrientacaoDiscente": {
"totalOrientacaoFinalizadaMestrado": 5,
"totalOrientacaoFinalizadaDoutorado": 3,
"totalAcompanhamentoPosDoutorado": 2,
}
}]
result = repository._extrair_orientacoes(atuacoes)
assert len(result) == 10
pos_doc = [o for o in result if o.codigo == "ORIENT_POS_DOC"]
tese = [o for o in result if o.codigo == "ORIENT_TESE"]
diss = [o for o in result if o.codigo == "ORIENT_DISS"]
assert len(pos_doc) == 2
assert len(tese) == 3
assert len(diss) == 5
class TestConstruirConsultor:
@pytest.mark.asyncio
async def test_construir_consultor_completo(self, repository):
doc = {
"id": 12345,
"dadosPessoais": {"nome": "João da Silva", "cpf": "12345678900"},
"atuacoes": [
{
"tipo": "Coordenação de Área de Avaliação",
"inicio": "01/01/2020",
"dadosCoordenacaoArea": {
"tipo": "Coordenador de Área",
"inicioVinculacao": "01/01/2020",
"areaAvaliacao": {"nome": "CIÊNCIAS AMBIENTAIS"},
}
},
{
"tipo": "Consultor",
"inicio": "01/01/2018",
"dadosConsultoria": {
"situacaoConsultoria": "Atividade Contínua",
}
},
]
}
consultor = await repository._construir_consultor(doc)
assert consultor.id_pessoa == 12345
assert consultor.nome == "João da Silva"
assert len(consultor.coordenacoes_capes) == 1
assert consultor.consultoria is not None
assert consultor.pontuacao is not None
assert consultor.pontuacao_total > 0
@pytest.mark.asyncio
async def test_construir_consultor_vazio(self, repository):
doc = {
"id": 99999,
"dadosPessoais": {"nome": "Consultor Vazio"},
"atuacoes": []
}
consultor = await repository._construir_consultor(doc)
assert consultor.id_pessoa == 99999
assert consultor.nome == "Consultor Vazio"
assert len(consultor.coordenacoes_capes) == 0
assert consultor.consultoria is None
class TestBuscarPorId:
@pytest.mark.asyncio
async def test_buscar_por_id_encontrado(self, repository, mock_es_client):
mock_es_client.buscar_por_id.return_value = {
"id": 12345,
"dadosPessoais": {"nome": "João"},
"atuacoes": []
}
result = await repository.buscar_por_id(12345)
assert result is not None
assert result.id_pessoa == 12345
mock_es_client.buscar_por_id.assert_called_once_with(12345)
@pytest.mark.asyncio
async def test_buscar_por_id_nao_encontrado(self, repository, mock_es_client):
mock_es_client.buscar_por_id.return_value = None
result = await repository.buscar_por_id(99999)
assert result is None
@pytest.mark.asyncio
async def test_buscar_por_id_erro_es(self, repository, mock_es_client):
mock_es_client.buscar_por_id.side_effect = Exception("ES error")
result = await repository.buscar_por_id(12345)
assert result is None