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:
0
backend/tests/__init__.py
Normal file
0
backend/tests/__init__.py
Normal file
0
backend/tests/application/__init__.py
Normal file
0
backend/tests/application/__init__.py
Normal file
0
backend/tests/application/jobs/__init__.py
Normal file
0
backend/tests/application/jobs/__init__.py
Normal file
278
backend/tests/application/jobs/test_processar_ranking.py
Normal file
278
backend/tests/application/jobs/test_processar_ranking.py
Normal file
@@ -0,0 +1,278 @@
|
||||
import pytest
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
from datetime import datetime
|
||||
|
||||
from src.application.jobs.processar_ranking import ProcessarRankingJob
|
||||
from src.infrastructure.ranking_store import RankingEntry
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_es_client():
|
||||
client = AsyncMock()
|
||||
client.contar_com_atuacoes.return_value = 100
|
||||
client.buscar_todos_consultores.return_value = {"processados": 100, "batches": 10}
|
||||
return client
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_ranking_store():
|
||||
store = AsyncMock()
|
||||
store.set_entries = AsyncMock()
|
||||
return store
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_oracle_repo():
|
||||
repo = MagicMock()
|
||||
repo.limpar_tabela = MagicMock()
|
||||
repo.inserir_batch = MagicMock()
|
||||
repo.atualizar_posicoes = MagicMock()
|
||||
return repo
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def job(mock_es_client, mock_ranking_store, mock_oracle_repo):
|
||||
return ProcessarRankingJob(
|
||||
es_client=mock_es_client,
|
||||
ranking_store=mock_ranking_store,
|
||||
ranking_oracle_repo=mock_oracle_repo,
|
||||
)
|
||||
|
||||
|
||||
class TestGerarEntriesOrdenadas:
|
||||
|
||||
def test_lista_vazia(self):
|
||||
entries = ProcessarRankingJob._gerar_entries_ordenadas([])
|
||||
assert len(entries) == 0
|
||||
|
||||
def test_ordenacao_por_pontuacao(self):
|
||||
consultores = [
|
||||
{"id_pessoa": 1, "nome": "A", "pontuacao_total": 100, "bloco_a": 50, "bloco_b": 20, "bloco_c": 20, "bloco_d": 5, "bloco_e": 5, "ativo": True, "anos_atuacao": 5},
|
||||
{"id_pessoa": 2, "nome": "B", "pontuacao_total": 300, "bloco_a": 150, "bloco_b": 80, "bloco_c": 50, "bloco_d": 10, "bloco_e": 10, "ativo": True, "anos_atuacao": 10},
|
||||
{"id_pessoa": 3, "nome": "C", "pontuacao_total": 200, "bloco_a": 100, "bloco_b": 50, "bloco_c": 30, "bloco_d": 10, "bloco_e": 10, "ativo": False, "anos_atuacao": 3},
|
||||
]
|
||||
entries = ProcessarRankingJob._gerar_entries_ordenadas(consultores)
|
||||
assert len(entries) == 3
|
||||
assert entries[0].id_pessoa == 2
|
||||
assert entries[0].posicao == 1
|
||||
assert entries[0].pontuacao_total == 300
|
||||
assert entries[1].id_pessoa == 3
|
||||
assert entries[1].posicao == 2
|
||||
assert entries[2].id_pessoa == 1
|
||||
assert entries[2].posicao == 3
|
||||
|
||||
def test_posicoes_atribuidas_sequencialmente(self):
|
||||
consultores = [
|
||||
{"id_pessoa": i, "nome": f"C{i}", "pontuacao_total": 100 - i, "bloco_a": 50, "bloco_b": 20, "bloco_c": 20, "bloco_d": 5, "bloco_e": 5, "ativo": True, "anos_atuacao": 5}
|
||||
for i in range(1, 11)
|
||||
]
|
||||
entries = ProcessarRankingJob._gerar_entries_ordenadas(consultores)
|
||||
for i, entry in enumerate(entries, start=1):
|
||||
assert entry.posicao == i
|
||||
|
||||
def test_empate_desempata_por_id(self):
|
||||
consultores = [
|
||||
{"id_pessoa": 10, "nome": "A", "pontuacao_total": 100, "bloco_a": 50, "bloco_b": 20, "bloco_c": 20, "bloco_d": 5, "bloco_e": 5, "ativo": True, "anos_atuacao": 5},
|
||||
{"id_pessoa": 5, "nome": "B", "pontuacao_total": 100, "bloco_a": 50, "bloco_b": 20, "bloco_c": 20, "bloco_d": 5, "bloco_e": 5, "ativo": True, "anos_atuacao": 5},
|
||||
]
|
||||
entries = ProcessarRankingJob._gerar_entries_ordenadas(consultores)
|
||||
assert entries[0].id_pessoa == 5
|
||||
assert entries[1].id_pessoa == 10
|
||||
|
||||
|
||||
class TestObterEstatisticas:
|
||||
|
||||
def test_lista_vazia(self):
|
||||
stats = ProcessarRankingJob._obter_estatisticas([])
|
||||
assert stats["total_consultores"] == 0
|
||||
assert stats["pontuacao_media"] == 0
|
||||
assert stats["pontuacao_maxima"] == 0
|
||||
|
||||
def test_estatisticas_calculadas(self):
|
||||
entries = [
|
||||
RankingEntry(id_pessoa=1, nome="A", posicao=1, pontuacao_total=300, bloco_a=100, bloco_b=80, bloco_c=60, bloco_d=40, bloco_e=20, ativo=True, anos_atuacao=5, detalhes={}),
|
||||
RankingEntry(id_pessoa=2, nome="B", posicao=2, pontuacao_total=200, bloco_a=80, bloco_b=50, bloco_c=40, bloco_d=20, bloco_e=10, ativo=True, anos_atuacao=3, detalhes={}),
|
||||
RankingEntry(id_pessoa=3, nome="C", posicao=3, pontuacao_total=100, bloco_a=40, bloco_b=30, bloco_c=20, bloco_d=5, bloco_e=5, ativo=False, anos_atuacao=2, detalhes={}),
|
||||
]
|
||||
stats = ProcessarRankingJob._obter_estatisticas(entries)
|
||||
assert stats["total_consultores"] == 3
|
||||
assert stats["total_ativos"] == 2
|
||||
assert stats["total_inativos"] == 1
|
||||
assert stats["pontuacao_maxima"] == 300
|
||||
assert stats["pontuacao_minima"] == 100
|
||||
assert stats["pontuacao_media"] == 200.0
|
||||
|
||||
def test_media_componentes(self):
|
||||
entries = [
|
||||
RankingEntry(id_pessoa=1, nome="A", posicao=1, pontuacao_total=300, bloco_a=100, bloco_b=50, bloco_c=80, bloco_d=40, bloco_e=30, ativo=True, anos_atuacao=5, detalhes={}),
|
||||
RankingEntry(id_pessoa=2, nome="B", posicao=2, pontuacao_total=200, bloco_a=80, bloco_b=30, bloco_c=60, bloco_d=20, bloco_e=10, ativo=True, anos_atuacao=3, detalhes={}),
|
||||
]
|
||||
stats = ProcessarRankingJob._obter_estatisticas(entries)
|
||||
assert stats["media_componentes"]["a"] == 90.0
|
||||
assert stats["media_componentes"]["b"] == 40.0
|
||||
|
||||
|
||||
class TestGerarJsonDetalhes:
|
||||
|
||||
def test_gerar_json_consultor_completo(self, job):
|
||||
from src.domain.entities.consultor import Consultor, CoordenacaoCapes, Consultoria
|
||||
from src.domain.value_objects.periodo import Periodo
|
||||
from src.domain.value_objects.pontuacao import PontuacaoBloco, PontuacaoCompleta
|
||||
|
||||
periodo = Periodo(inicio=datetime(2020, 1, 1), fim=None)
|
||||
consultor = Consultor(
|
||||
id_pessoa=123,
|
||||
nome="João Silva",
|
||||
coordenacoes_capes=[
|
||||
CoordenacaoCapes(
|
||||
codigo="CA",
|
||||
tipo="Coordenador de Área",
|
||||
area_avaliacao="CIÊNCIAS",
|
||||
periodo=periodo,
|
||||
)
|
||||
],
|
||||
consultoria=Consultoria(
|
||||
codigo="CONS_ATIVO",
|
||||
situacao="Atividade Contínua",
|
||||
periodo=periodo,
|
||||
anos_consecutivos=5,
|
||||
retornos=0,
|
||||
),
|
||||
)
|
||||
consultor.pontuacao = PontuacaoCompleta(
|
||||
bloco_a=PontuacaoBloco(bloco="A", atuacoes=[]),
|
||||
bloco_b=PontuacaoBloco(bloco="B", atuacoes=[]),
|
||||
bloco_c=PontuacaoBloco(bloco="C", atuacoes=[]),
|
||||
bloco_d=PontuacaoBloco(bloco="D", atuacoes=[]),
|
||||
bloco_e=PontuacaoBloco(bloco="E", atuacoes=[]),
|
||||
)
|
||||
|
||||
result = job._gerar_json_detalhes(consultor)
|
||||
|
||||
assert result["id_pessoa"] == 123
|
||||
assert result["nome"] == "João Silva"
|
||||
assert len(result["coordenacoes_capes"]) == 1
|
||||
assert result["consultoria"] is not None
|
||||
assert result["consultoria"]["codigo"] == "CONS_ATIVO"
|
||||
|
||||
def test_gerar_json_consultor_sem_consultoria(self, job):
|
||||
from src.domain.entities.consultor import Consultor
|
||||
from src.domain.value_objects.pontuacao import PontuacaoBloco, PontuacaoCompleta
|
||||
|
||||
consultor = Consultor(
|
||||
id_pessoa=456,
|
||||
nome="Maria Santos",
|
||||
)
|
||||
consultor.pontuacao = PontuacaoCompleta(
|
||||
bloco_a=PontuacaoBloco(bloco="A", atuacoes=[]),
|
||||
bloco_b=PontuacaoBloco(bloco="B", atuacoes=[]),
|
||||
bloco_c=PontuacaoBloco(bloco="C", atuacoes=[]),
|
||||
bloco_d=PontuacaoBloco(bloco="D", atuacoes=[]),
|
||||
bloco_e=PontuacaoBloco(bloco="E", atuacoes=[]),
|
||||
)
|
||||
|
||||
result = job._gerar_json_detalhes(consultor)
|
||||
|
||||
assert result["id_pessoa"] == 456
|
||||
assert result["consultoria"] is None
|
||||
|
||||
|
||||
class TestProcessarBatch:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_processar_batch_atualiza_progresso(self, job):
|
||||
with patch.object(job.consultor_repo, "_construir_consultor") as mock_construir:
|
||||
from src.domain.entities.consultor import Consultor
|
||||
from src.domain.value_objects.pontuacao import PontuacaoBloco, PontuacaoCompleta
|
||||
|
||||
consultor = Consultor(id_pessoa=1, nome="Test")
|
||||
consultor.pontuacao = PontuacaoCompleta(
|
||||
bloco_a=PontuacaoBloco(bloco="A", atuacoes=[]),
|
||||
bloco_b=PontuacaoBloco(bloco="B", atuacoes=[]),
|
||||
bloco_c=PontuacaoBloco(bloco="C", atuacoes=[]),
|
||||
bloco_d=PontuacaoBloco(bloco="D", atuacoes=[]),
|
||||
bloco_e=PontuacaoBloco(bloco="E", atuacoes=[]),
|
||||
)
|
||||
mock_construir.return_value = consultor
|
||||
|
||||
docs = [{"id": 1, "dadosPessoais": {"nome": "Test"}, "atuacoes": []}]
|
||||
progress = {"processados": 1, "batch_atual": 1, "percentual": 10}
|
||||
|
||||
with patch("src.application.jobs.processar_ranking.job_status"):
|
||||
await job._processar_batch(docs, progress)
|
||||
|
||||
assert len(job._consultores) == 1
|
||||
|
||||
|
||||
class TestExecutar:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_executar_job_ja_rodando(self, job):
|
||||
with patch("src.application.jobs.processar_ranking.job_status") as mock_status:
|
||||
mock_status.is_running = True
|
||||
|
||||
with pytest.raises(RuntimeError, match="Job já está em execução"):
|
||||
await job.executar()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_executar_sucesso(self, job, mock_es_client, mock_ranking_store):
|
||||
with patch("src.application.jobs.processar_ranking.job_status") as mock_status:
|
||||
mock_status.is_running = False
|
||||
mock_status.iniciar = MagicMock()
|
||||
mock_status.atualizar_progresso = MagicMock()
|
||||
mock_status.finalizar = MagicMock()
|
||||
mock_status.mensagem = ""
|
||||
mock_status.tempo_decorrido = 10.5
|
||||
|
||||
job._consultores = [
|
||||
{"id_pessoa": 1, "nome": "Test", "pontuacao_total": 100, "bloco_a": 50, "bloco_b": 20, "bloco_c": 20, "bloco_d": 5, "bloco_e": 5, "ativo": True, "anos_atuacao": 5}
|
||||
]
|
||||
|
||||
resultado = await job.executar()
|
||||
|
||||
assert resultado["sucesso"] is True
|
||||
mock_status.iniciar.assert_called_once()
|
||||
mock_status.finalizar.assert_called_once_with(sucesso=True)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_executar_erro(self, job, mock_es_client):
|
||||
with patch("src.application.jobs.processar_ranking.job_status") as mock_status:
|
||||
mock_status.is_running = False
|
||||
mock_status.iniciar = MagicMock()
|
||||
mock_status.finalizar = MagicMock()
|
||||
|
||||
mock_es_client.contar_com_atuacoes.side_effect = Exception("ES Error")
|
||||
|
||||
with pytest.raises(RuntimeError):
|
||||
await job.executar()
|
||||
|
||||
mock_status.finalizar.assert_called_once()
|
||||
args = mock_status.finalizar.call_args
|
||||
assert args[1]["sucesso"] is False
|
||||
|
||||
|
||||
class TestPersistirOracle:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_persistir_oracle_limpa_antes(self, job, mock_oracle_repo):
|
||||
consultores = [
|
||||
{"id_pessoa": 1, "nome": "Test", "pontuacao_total": 100, "posicao": 1}
|
||||
]
|
||||
await job._persistir_oracle(consultores, limpar_antes=True)
|
||||
mock_oracle_repo.limpar_tabela.assert_called_once()
|
||||
mock_oracle_repo.inserir_batch.assert_called()
|
||||
mock_oracle_repo.atualizar_posicoes.assert_called_once()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_persistir_oracle_sem_limpar(self, job, mock_oracle_repo):
|
||||
consultores = [
|
||||
{"id_pessoa": 1, "nome": "Test", "pontuacao_total": 100, "posicao": 1}
|
||||
]
|
||||
await job._persistir_oracle(consultores, limpar_antes=False)
|
||||
mock_oracle_repo.limpar_tabela.assert_not_called()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_persistir_oracle_em_batches(self, job, mock_oracle_repo):
|
||||
consultores = [{"id_pessoa": i, "nome": f"Test{i}", "pontuacao_total": 100} for i in range(5000)]
|
||||
await job._persistir_oracle(consultores, limpar_antes=True)
|
||||
assert mock_oracle_repo.inserir_batch.call_count == 3
|
||||
294
backend/tests/conftest.py
Normal file
294
backend/tests/conftest.py
Normal file
@@ -0,0 +1,294 @@
|
||||
import pytest
|
||||
from datetime import datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from src.domain.entities.consultor import (
|
||||
Consultor,
|
||||
CoordenacaoCapes,
|
||||
Consultoria,
|
||||
Inscricao,
|
||||
AvaliacaoComissao,
|
||||
Premiacao,
|
||||
BolsaCNPQ,
|
||||
Participacao,
|
||||
Orientacao,
|
||||
MembroBanca,
|
||||
)
|
||||
from src.domain.value_objects.periodo import Periodo
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def data_referencia():
|
||||
return datetime(2025, 1, 1)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def hoje():
|
||||
return datetime.now()
|
||||
|
||||
|
||||
def criar_periodo(anos_atras: int, ativo: bool = False, duracao_anos: int = 0) -> Periodo:
|
||||
inicio = datetime.now() - relativedelta(years=anos_atras)
|
||||
if ativo:
|
||||
fim = None
|
||||
else:
|
||||
fim = inicio + relativedelta(years=duracao_anos) if duracao_anos > 0 else datetime.now()
|
||||
return Periodo(inicio=inicio, fim=fim)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def periodo_ativo_5_anos():
|
||||
return criar_periodo(anos_atras=5, ativo=True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def periodo_historico_3_anos():
|
||||
return criar_periodo(anos_atras=5, duracao_anos=3)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def periodo_historico_10_anos():
|
||||
return criar_periodo(anos_atras=12, duracao_anos=10)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def coordenacao_ca_ativa(periodo_ativo_5_anos):
|
||||
return CoordenacaoCapes(
|
||||
codigo="CA",
|
||||
tipo="Coordenador de Área",
|
||||
area_avaliacao="CIÊNCIAS AMBIENTAIS",
|
||||
periodo=periodo_ativo_5_anos,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def coordenacao_ca_historica(periodo_historico_3_anos):
|
||||
return CoordenacaoCapes(
|
||||
codigo="CA",
|
||||
tipo="Coordenador de Área",
|
||||
area_avaliacao="MEDICINA I",
|
||||
periodo=periodo_historico_3_anos,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def coordenacao_caj_ativa():
|
||||
return CoordenacaoCapes(
|
||||
codigo="CAJ",
|
||||
tipo="Coordenador Adjunto",
|
||||
area_avaliacao="ENGENHARIA I",
|
||||
periodo=criar_periodo(anos_atras=3, ativo=True),
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def coordenacao_caj_mp_historica():
|
||||
return CoordenacaoCapes(
|
||||
codigo="CAJ_MP",
|
||||
tipo="Coordenador Adjunto de Mestrado Profissionalizante",
|
||||
area_avaliacao="ADMINISTRAÇÃO",
|
||||
periodo=criar_periodo(anos_atras=6, duracao_anos=4),
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def coordenacao_cam_ativa():
|
||||
return CoordenacaoCapes(
|
||||
codigo="CAM",
|
||||
tipo="Câmara Temática",
|
||||
area_avaliacao="INTERDISCIPLINAR",
|
||||
periodo=criar_periodo(anos_atras=2, ativo=True),
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def consultoria_ativa_8_anos():
|
||||
return Consultoria(
|
||||
codigo="CONS_ATIVO",
|
||||
situacao="Atividade Contínua",
|
||||
periodo=criar_periodo(anos_atras=8, ativo=True),
|
||||
periodos=[criar_periodo(anos_atras=8, ativo=True)],
|
||||
anos_consecutivos=8,
|
||||
retornos=0,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def consultoria_historica_5_anos():
|
||||
return Consultoria(
|
||||
codigo="CONS_HIST",
|
||||
situacao="Inativo",
|
||||
periodo=criar_periodo(anos_atras=7, duracao_anos=5),
|
||||
periodos=[criar_periodo(anos_atras=7, duracao_anos=5)],
|
||||
anos_consecutivos=5,
|
||||
retornos=0,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def consultoria_com_retorno():
|
||||
periodo1 = criar_periodo(anos_atras=10, duracao_anos=3)
|
||||
periodo2 = criar_periodo(anos_atras=4, ativo=True)
|
||||
return Consultoria(
|
||||
codigo="CONS_ATIVO",
|
||||
situacao="Atividade Contínua",
|
||||
periodo=periodo2,
|
||||
periodos=[periodo1, periodo2],
|
||||
anos_consecutivos=4,
|
||||
retornos=1,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def inscricao_autor():
|
||||
return Inscricao(
|
||||
codigo="INSC_AUTOR",
|
||||
tipo="Autor",
|
||||
premio="PCT",
|
||||
ano=2024,
|
||||
situacao="Aprovada",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def inscricao_institucional():
|
||||
return Inscricao(
|
||||
codigo="INSC_INST_AUTOR",
|
||||
tipo="Institucional",
|
||||
premio="PCT",
|
||||
ano=2024,
|
||||
situacao="Aprovada",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def avaliacao_comissao_premio():
|
||||
return AvaliacaoComissao(
|
||||
codigo="AVAL_COMIS_PREMIO",
|
||||
tipo="Membro de Comissão",
|
||||
premio="PCT",
|
||||
ano=2024,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def avaliacao_comissao_gp():
|
||||
return AvaliacaoComissao(
|
||||
codigo="AVAL_COMIS_GP",
|
||||
tipo="Membro de Comissão",
|
||||
premio="Grande Prêmio",
|
||||
ano=2024,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def coord_comissao_premio():
|
||||
return AvaliacaoComissao(
|
||||
codigo="COORD_COMIS_PREMIO",
|
||||
tipo="Coordenador/Presidente",
|
||||
premio="PCT",
|
||||
ano=2024,
|
||||
comissao_tipo="Coordenador",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def premiacao_gp_autor():
|
||||
return Premiacao(
|
||||
codigo="PREMIACAO_GP_AUTOR",
|
||||
tipo="Grande Prêmio",
|
||||
nome_premio="Grande Prêmio CAPES de Tese",
|
||||
ano=2024,
|
||||
papel="Autor",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def premiacao_autor():
|
||||
return Premiacao(
|
||||
codigo="PREMIACAO_AUTOR",
|
||||
tipo="Prêmio",
|
||||
nome_premio="Prêmio CAPES de Tese",
|
||||
ano=2024,
|
||||
papel="Autor",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mencao_autor():
|
||||
return Premiacao(
|
||||
codigo="MENCAO_AUTOR",
|
||||
tipo="Menção Honrosa",
|
||||
nome_premio="Prêmio CAPES de Tese",
|
||||
ano=2024,
|
||||
papel="Autor",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def bolsa_cnpq():
|
||||
return BolsaCNPQ(
|
||||
codigo="BOL_BPQ_NIVEL",
|
||||
nivel="1A",
|
||||
area="Ciências Exatas",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def participacao_evento():
|
||||
return Participacao(
|
||||
codigo="EVENTO",
|
||||
tipo="Evento",
|
||||
descricao="Seminário CAPES 2024",
|
||||
ano=2024,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def participacao_projeto():
|
||||
return Participacao(
|
||||
codigo="PROJ",
|
||||
tipo="Projeto",
|
||||
descricao="Projeto de Pesquisa",
|
||||
ano=2024,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def consultor_vazio():
|
||||
return Consultor(
|
||||
id_pessoa=1,
|
||||
nome="Consultor Vazio",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def consultor_coordenador_area(coordenacao_ca_ativa):
|
||||
return Consultor(
|
||||
id_pessoa=2,
|
||||
nome="Coordenador de Área Ativo",
|
||||
coordenacoes_capes=[coordenacao_ca_ativa],
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def consultor_completo(
|
||||
coordenacao_ca_ativa,
|
||||
consultoria_ativa_8_anos,
|
||||
inscricao_autor,
|
||||
avaliacao_comissao_premio,
|
||||
premiacao_autor,
|
||||
bolsa_cnpq,
|
||||
participacao_evento,
|
||||
):
|
||||
return Consultor(
|
||||
id_pessoa=3,
|
||||
nome="Consultor Completo",
|
||||
coordenacoes_capes=[coordenacao_ca_ativa],
|
||||
consultoria=consultoria_ativa_8_anos,
|
||||
inscricoes=[inscricao_autor],
|
||||
avaliacoes_comissao=[avaliacao_comissao_premio],
|
||||
premiacoes=[premiacao_autor],
|
||||
bolsas_cnpq=[bolsa_cnpq],
|
||||
participacoes=[participacao_evento],
|
||||
)
|
||||
0
backend/tests/domain/__init__.py
Normal file
0
backend/tests/domain/__init__.py
Normal file
0
backend/tests/domain/services/__init__.py
Normal file
0
backend/tests/domain/services/__init__.py
Normal file
599
backend/tests/domain/services/test_calculador_pontuacao.py
Normal file
599
backend/tests/domain/services/test_calculador_pontuacao.py
Normal file
@@ -0,0 +1,599 @@
|
||||
import pytest
|
||||
from datetime import datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from src.domain.services.calculador_pontuacao import CalculadorPontuacao
|
||||
from src.domain.entities.consultor import (
|
||||
Consultor,
|
||||
CoordenacaoCapes,
|
||||
Consultoria,
|
||||
Inscricao,
|
||||
AvaliacaoComissao,
|
||||
Premiacao,
|
||||
BolsaCNPQ,
|
||||
Participacao,
|
||||
)
|
||||
from src.domain.value_objects.periodo import Periodo
|
||||
from src.domain.value_objects.criterios_pontuacao import CRITERIOS
|
||||
|
||||
|
||||
def criar_periodo(anos_atras: int, ativo: bool = False, duracao_anos: int = 0) -> Periodo:
|
||||
inicio = datetime.now() - relativedelta(years=anos_atras)
|
||||
if ativo:
|
||||
fim = None
|
||||
else:
|
||||
fim = inicio + relativedelta(years=duracao_anos) if duracao_anos > 0 else datetime.now()
|
||||
return Periodo(inicio=inicio, fim=fim)
|
||||
|
||||
|
||||
class TestBlocoACoordenacaoCapes:
|
||||
|
||||
def test_coordenacao_vazia_retorna_bloco_vazio(self):
|
||||
resultado = CalculadorPontuacao.calcular_bloco_a([])
|
||||
assert resultado.bloco == "A"
|
||||
assert resultado.total == 0
|
||||
assert len(resultado.atuacoes) == 0
|
||||
|
||||
def test_coordenador_area_base_200_pontos(self):
|
||||
coord = CoordenacaoCapes(
|
||||
codigo="CA",
|
||||
tipo="Coordenador de Área",
|
||||
area_avaliacao="CIÊNCIAS AMBIENTAIS",
|
||||
periodo=criar_periodo(anos_atras=0, duracao_anos=0),
|
||||
)
|
||||
resultado = CalculadorPontuacao.calcular_bloco_a([coord])
|
||||
assert resultado.total >= 200
|
||||
|
||||
def test_coordenador_area_ativo_recebe_bonus_atualidade(self):
|
||||
coord = CoordenacaoCapes(
|
||||
codigo="CA",
|
||||
tipo="Coordenador de Área",
|
||||
area_avaliacao="CIÊNCIAS AMBIENTAIS",
|
||||
periodo=criar_periodo(anos_atras=1, ativo=True),
|
||||
)
|
||||
resultado = CalculadorPontuacao.calcular_bloco_a([coord])
|
||||
atuacao = resultado.atuacoes[0]
|
||||
assert atuacao.bonus >= 30
|
||||
|
||||
def test_coordenador_area_historico_sem_bonus_atualidade(self):
|
||||
coord = CoordenacaoCapes(
|
||||
codigo="CA",
|
||||
tipo="Coordenador de Área",
|
||||
area_avaliacao="MEDICINA I",
|
||||
periodo=criar_periodo(anos_atras=5, duracao_anos=3),
|
||||
)
|
||||
resultado = CalculadorPontuacao.calcular_bloco_a([coord])
|
||||
atuacao = resultado.atuacoes[0]
|
||||
assert atuacao.codigo == "CA"
|
||||
assert 30 not in [atuacao.bonus] or atuacao.bonus < 30
|
||||
|
||||
def test_coordenador_area_5_anos_pontos_tempo(self):
|
||||
coord = CoordenacaoCapes(
|
||||
codigo="CA",
|
||||
tipo="Coordenador de Área",
|
||||
area_avaliacao="CIÊNCIAS AMBIENTAIS",
|
||||
periodo=criar_periodo(anos_atras=5, duracao_anos=5),
|
||||
)
|
||||
resultado = CalculadorPontuacao.calcular_bloco_a([coord])
|
||||
atuacao = resultado.atuacoes[0]
|
||||
assert atuacao.tempo == 50
|
||||
|
||||
def test_coordenador_area_teto_tempo_100_pontos(self):
|
||||
coord = CoordenacaoCapes(
|
||||
codigo="CA",
|
||||
tipo="Coordenador de Área",
|
||||
area_avaliacao="CIÊNCIAS AMBIENTAIS",
|
||||
periodo=criar_periodo(anos_atras=15, duracao_anos=15),
|
||||
)
|
||||
resultado = CalculadorPontuacao.calcular_bloco_a([coord])
|
||||
atuacao = resultado.atuacoes[0]
|
||||
assert atuacao.tempo == 100
|
||||
|
||||
def test_coordenador_area_teto_maximo_450(self):
|
||||
coord = CoordenacaoCapes(
|
||||
codigo="CA",
|
||||
tipo="Coordenador de Área",
|
||||
area_avaliacao="CIÊNCIAS AMBIENTAIS",
|
||||
periodo=criar_periodo(anos_atras=20, ativo=True),
|
||||
)
|
||||
resultado = CalculadorPontuacao.calcular_bloco_a([coord])
|
||||
atuacao = resultado.atuacoes[0]
|
||||
assert atuacao.total <= 450
|
||||
|
||||
def test_coordenador_adjunto_base_150_pontos(self):
|
||||
coord = CoordenacaoCapes(
|
||||
codigo="CAJ",
|
||||
tipo="Coordenador Adjunto",
|
||||
area_avaliacao="ENGENHARIA I",
|
||||
periodo=criar_periodo(anos_atras=0, duracao_anos=0),
|
||||
)
|
||||
resultado = CalculadorPontuacao.calcular_bloco_a([coord])
|
||||
assert resultado.total >= 150
|
||||
|
||||
def test_coordenador_adjunto_mp_base_120_pontos(self):
|
||||
coord = CoordenacaoCapes(
|
||||
codigo="CAJ_MP",
|
||||
tipo="Coordenador Adjunto de Mestrado Profissionalizante",
|
||||
area_avaliacao="ADMINISTRAÇÃO",
|
||||
periodo=criar_periodo(anos_atras=0, duracao_anos=0),
|
||||
)
|
||||
resultado = CalculadorPontuacao.calcular_bloco_a([coord])
|
||||
assert resultado.total >= 120
|
||||
|
||||
def test_camara_tematica_base_100_pontos(self):
|
||||
coord = CoordenacaoCapes(
|
||||
codigo="CAM",
|
||||
tipo="Câmara Temática",
|
||||
area_avaliacao="INTERDISCIPLINAR",
|
||||
periodo=criar_periodo(anos_atras=0, duracao_anos=0),
|
||||
)
|
||||
resultado = CalculadorPontuacao.calcular_bloco_a([coord])
|
||||
assert resultado.total >= 100
|
||||
|
||||
def test_multiplas_coordenacoes_mesmo_tipo_soma_tempo(self):
|
||||
periodo1 = criar_periodo(anos_atras=10, duracao_anos=3)
|
||||
periodo2 = criar_periodo(anos_atras=5, duracao_anos=3)
|
||||
coords = [
|
||||
CoordenacaoCapes(
|
||||
codigo="CA", tipo="Coordenador de Área",
|
||||
area_avaliacao="ÁREA 1", periodo=periodo1
|
||||
),
|
||||
CoordenacaoCapes(
|
||||
codigo="CA", tipo="Coordenador de Área",
|
||||
area_avaliacao="ÁREA 2", periodo=periodo2
|
||||
),
|
||||
]
|
||||
resultado = CalculadorPontuacao.calcular_bloco_a(coords)
|
||||
assert len(resultado.atuacoes) == 1
|
||||
atuacao = resultado.atuacoes[0]
|
||||
assert atuacao.quantidade == 2
|
||||
assert atuacao.tempo == 60
|
||||
|
||||
def test_retorno_coordenacao_gera_bonus(self):
|
||||
periodo1 = criar_periodo(anos_atras=10, duracao_anos=3)
|
||||
periodo2 = criar_periodo(anos_atras=3, ativo=True)
|
||||
coords = [
|
||||
CoordenacaoCapes(
|
||||
codigo="CA", tipo="Coordenador de Área",
|
||||
area_avaliacao="ÁREA 1", periodo=periodo1
|
||||
),
|
||||
CoordenacaoCapes(
|
||||
codigo="CA", tipo="Coordenador de Área",
|
||||
area_avaliacao="ÁREA 1", periodo=periodo2
|
||||
),
|
||||
]
|
||||
resultado = CalculadorPontuacao.calcular_bloco_a(coords)
|
||||
atuacao = resultado.atuacoes[0]
|
||||
assert atuacao.bonus >= 20
|
||||
|
||||
def test_tipos_diferentes_geram_atuacoes_separadas(self):
|
||||
coords = [
|
||||
CoordenacaoCapes(
|
||||
codigo="CA", tipo="Coordenador de Área",
|
||||
area_avaliacao="ÁREA 1", periodo=criar_periodo(2, duracao_anos=2)
|
||||
),
|
||||
CoordenacaoCapes(
|
||||
codigo="CAJ", tipo="Coordenador Adjunto",
|
||||
area_avaliacao="ÁREA 1", periodo=criar_periodo(2, duracao_anos=2)
|
||||
),
|
||||
]
|
||||
resultado = CalculadorPontuacao.calcular_bloco_a(coords)
|
||||
assert len(resultado.atuacoes) == 2
|
||||
|
||||
def test_codigo_com_hifen_normalizado(self):
|
||||
coord = CoordenacaoCapes(
|
||||
codigo="CAJ-MP",
|
||||
tipo="Coordenador Adjunto de Mestrado Profissionalizante",
|
||||
area_avaliacao="ADMINISTRAÇÃO",
|
||||
periodo=criar_periodo(anos_atras=2, duracao_anos=2),
|
||||
)
|
||||
resultado = CalculadorPontuacao.calcular_bloco_a([coord])
|
||||
assert len(resultado.atuacoes) == 1
|
||||
assert resultado.atuacoes[0].codigo == "CAJ_MP"
|
||||
|
||||
|
||||
class TestBlocoBConsultoria:
|
||||
|
||||
def test_consultoria_vazia_retorna_bloco_vazio(self):
|
||||
resultado = CalculadorPontuacao.calcular_bloco_b(None)
|
||||
assert resultado.bloco == "B"
|
||||
assert resultado.total == 0
|
||||
|
||||
def test_consultor_ativo_base_150_pontos(self):
|
||||
consultoria = Consultoria(
|
||||
codigo="CONS_ATIVO",
|
||||
situacao="Atividade Contínua",
|
||||
periodo=criar_periodo(anos_atras=1, ativo=True),
|
||||
anos_consecutivos=1,
|
||||
retornos=0,
|
||||
)
|
||||
resultado = CalculadorPontuacao.calcular_bloco_b(consultoria)
|
||||
assert resultado.total >= 150
|
||||
|
||||
def test_consultor_historico_base_100_pontos(self):
|
||||
consultoria = Consultoria(
|
||||
codigo="CONS_HIST",
|
||||
situacao="Inativo",
|
||||
periodo=criar_periodo(anos_atras=5, duracao_anos=3),
|
||||
anos_consecutivos=3,
|
||||
retornos=0,
|
||||
)
|
||||
resultado = CalculadorPontuacao.calcular_bloco_b(consultoria)
|
||||
assert resultado.total >= 100
|
||||
|
||||
def test_consultor_falecido_base_100_pontos(self):
|
||||
consultoria = Consultoria(
|
||||
codigo="CONS_FALECIDO",
|
||||
situacao="Falecido",
|
||||
periodo=criar_periodo(anos_atras=10, duracao_anos=8),
|
||||
anos_consecutivos=8,
|
||||
retornos=0,
|
||||
)
|
||||
resultado = CalculadorPontuacao.calcular_bloco_b(consultoria)
|
||||
assert resultado.total >= 100
|
||||
|
||||
def test_consultoria_5_anos_pontos_tempo(self):
|
||||
consultoria = Consultoria(
|
||||
codigo="CONS_ATIVO",
|
||||
situacao="Atividade Contínua",
|
||||
periodo=criar_periodo(anos_atras=5, ativo=True),
|
||||
periodos=[criar_periodo(anos_atras=5, ativo=True)],
|
||||
anos_consecutivos=5,
|
||||
retornos=0,
|
||||
)
|
||||
resultado = CalculadorPontuacao.calcular_bloco_b(consultoria)
|
||||
atuacao = resultado.atuacoes[0]
|
||||
assert atuacao.tempo == 25
|
||||
|
||||
def test_consultoria_teto_tempo_50_pontos(self):
|
||||
consultoria = Consultoria(
|
||||
codigo="CONS_ATIVO",
|
||||
situacao="Atividade Contínua",
|
||||
periodo=criar_periodo(anos_atras=15, ativo=True),
|
||||
periodos=[criar_periodo(anos_atras=15, ativo=True)],
|
||||
anos_consecutivos=15,
|
||||
retornos=0,
|
||||
)
|
||||
resultado = CalculadorPontuacao.calcular_bloco_b(consultoria)
|
||||
atuacao = resultado.atuacoes[0]
|
||||
assert atuacao.tempo == 50
|
||||
|
||||
def test_consultor_ativo_bonus_atualidade_20(self):
|
||||
consultoria = Consultoria(
|
||||
codigo="CONS_ATIVO",
|
||||
situacao="Atividade Contínua",
|
||||
periodo=criar_periodo(anos_atras=1, ativo=True),
|
||||
anos_consecutivos=1,
|
||||
retornos=0,
|
||||
)
|
||||
resultado = CalculadorPontuacao.calcular_bloco_b(consultoria)
|
||||
atuacao = resultado.atuacoes[0]
|
||||
assert atuacao.bonus >= 20
|
||||
|
||||
def test_consultor_8_anos_bonus_continuidade(self):
|
||||
consultoria = Consultoria(
|
||||
codigo="CONS_ATIVO",
|
||||
situacao="Atividade Contínua",
|
||||
periodo=criar_periodo(anos_atras=8, ativo=True),
|
||||
anos_consecutivos=8,
|
||||
retornos=0,
|
||||
)
|
||||
resultado = CalculadorPontuacao.calcular_bloco_b(consultoria)
|
||||
atuacao = resultado.atuacoes[0]
|
||||
assert atuacao.bonus >= 40
|
||||
|
||||
def test_consultor_com_retorno_bonus_15(self):
|
||||
consultoria = Consultoria(
|
||||
codigo="CONS_ATIVO",
|
||||
situacao="Atividade Contínua",
|
||||
periodo=criar_periodo(anos_atras=2, ativo=True),
|
||||
periodos=[
|
||||
criar_periodo(anos_atras=8, duracao_anos=3),
|
||||
criar_periodo(anos_atras=2, ativo=True),
|
||||
],
|
||||
anos_consecutivos=2,
|
||||
retornos=1,
|
||||
)
|
||||
resultado = CalculadorPontuacao.calcular_bloco_b(consultoria)
|
||||
atuacao = resultado.atuacoes[0]
|
||||
assert atuacao.bonus >= 35
|
||||
|
||||
def test_consultoria_teto_maximo_230(self):
|
||||
consultoria = Consultoria(
|
||||
codigo="CONS_ATIVO",
|
||||
situacao="Atividade Contínua",
|
||||
periodo=criar_periodo(anos_atras=20, ativo=True),
|
||||
periodos=[criar_periodo(anos_atras=20, ativo=True)],
|
||||
anos_consecutivos=20,
|
||||
retornos=1,
|
||||
)
|
||||
resultado = CalculadorPontuacao.calcular_bloco_b(consultoria)
|
||||
atuacao = resultado.atuacoes[0]
|
||||
assert atuacao.total <= 230
|
||||
|
||||
|
||||
class TestBlocoCPremiacoesAvaliacoes:
|
||||
|
||||
def test_bloco_c_vazio(self):
|
||||
resultado = CalculadorPontuacao.calcular_bloco_c([], [], [], [], [])
|
||||
assert resultado.bloco == "C"
|
||||
assert resultado.total == 0
|
||||
|
||||
def test_inscricao_autor_base_10_pontos(self):
|
||||
inscricao = Inscricao(
|
||||
codigo="INSC_AUTOR", tipo="Autor",
|
||||
premio="PCT", ano=2024
|
||||
)
|
||||
resultado = CalculadorPontuacao.calcular_bloco_c([inscricao], [], [], [], [])
|
||||
atuacao = next((a for a in resultado.atuacoes if a.codigo == "INSC_AUTOR"), None)
|
||||
assert atuacao is not None
|
||||
assert atuacao.base >= 10
|
||||
|
||||
def test_inscricao_institucional_base_20_pontos(self):
|
||||
inscricao = Inscricao(
|
||||
codigo="INSC_INST_AUTOR", tipo="Institucional",
|
||||
premio="PCT", ano=2024
|
||||
)
|
||||
resultado = CalculadorPontuacao.calcular_bloco_c([inscricao], [], [], [], [])
|
||||
atuacao = next((a for a in resultado.atuacoes if a.codigo == "INSC_INST_AUTOR"), None)
|
||||
assert atuacao is not None
|
||||
assert atuacao.base >= 20
|
||||
|
||||
def test_avaliacao_comissao_premio_base_30(self):
|
||||
avaliacao = AvaliacaoComissao(
|
||||
codigo="AVAL_COMIS_PREMIO", tipo="Membro de Comissão",
|
||||
premio="PCT", ano=2024
|
||||
)
|
||||
resultado = CalculadorPontuacao.calcular_bloco_c([], [avaliacao], [], [], [])
|
||||
atuacao = next((a for a in resultado.atuacoes if a.codigo == "AVAL_COMIS_PREMIO"), None)
|
||||
assert atuacao is not None
|
||||
assert atuacao.base >= 30
|
||||
|
||||
def test_avaliacao_comissao_gp_base_40(self):
|
||||
avaliacao = AvaliacaoComissao(
|
||||
codigo="AVAL_COMIS_GP", tipo="Membro de Comissão",
|
||||
premio="Grande Prêmio", ano=2024
|
||||
)
|
||||
resultado = CalculadorPontuacao.calcular_bloco_c([], [avaliacao], [], [], [])
|
||||
atuacao = next((a for a in resultado.atuacoes if a.codigo == "AVAL_COMIS_GP"), None)
|
||||
assert atuacao is not None
|
||||
assert atuacao.base >= 40
|
||||
|
||||
def test_coord_comissao_premio_base_40(self):
|
||||
avaliacao = AvaliacaoComissao(
|
||||
codigo="COORD_COMIS_PREMIO", tipo="Coordenador",
|
||||
premio="PCT", ano=2024
|
||||
)
|
||||
resultado = CalculadorPontuacao.calcular_bloco_c([], [avaliacao], [], [], [])
|
||||
atuacao = next((a for a in resultado.atuacoes if a.codigo == "COORD_COMIS_PREMIO"), None)
|
||||
assert atuacao is not None
|
||||
assert atuacao.base >= 40
|
||||
|
||||
def test_coord_comissao_gp_base_50(self):
|
||||
avaliacao = AvaliacaoComissao(
|
||||
codigo="COORD_COMIS_GP", tipo="Coordenador",
|
||||
premio="Grande Prêmio", ano=2024
|
||||
)
|
||||
resultado = CalculadorPontuacao.calcular_bloco_c([], [avaliacao], [], [], [])
|
||||
atuacao = next((a for a in resultado.atuacoes if a.codigo == "COORD_COMIS_GP"), None)
|
||||
assert atuacao is not None
|
||||
assert atuacao.base >= 50
|
||||
|
||||
def test_premiacao_gp_autor_base_100(self):
|
||||
premiacao = Premiacao(
|
||||
codigo="PREMIACAO_GP_AUTOR", tipo="Grande Prêmio",
|
||||
nome_premio="Grande Prêmio CAPES", ano=2024
|
||||
)
|
||||
resultado = CalculadorPontuacao.calcular_bloco_c([], [], [premiacao], [], [])
|
||||
atuacao = next((a for a in resultado.atuacoes if a.codigo == "PREMIACAO_GP_AUTOR"), None)
|
||||
assert atuacao is not None
|
||||
assert atuacao.base >= 100
|
||||
|
||||
def test_premiacao_autor_base_50(self):
|
||||
premiacao = Premiacao(
|
||||
codigo="PREMIACAO_AUTOR", tipo="Prêmio",
|
||||
nome_premio="Prêmio CAPES de Tese", ano=2024
|
||||
)
|
||||
resultado = CalculadorPontuacao.calcular_bloco_c([], [], [premiacao], [], [])
|
||||
atuacao = next((a for a in resultado.atuacoes if a.codigo == "PREMIACAO_AUTOR"), None)
|
||||
assert atuacao is not None
|
||||
assert atuacao.base >= 50
|
||||
|
||||
def test_mencao_autor_base_30(self):
|
||||
premiacao = Premiacao(
|
||||
codigo="MENCAO_AUTOR", tipo="Menção Honrosa",
|
||||
nome_premio="Prêmio CAPES de Tese", ano=2024
|
||||
)
|
||||
resultado = CalculadorPontuacao.calcular_bloco_c([], [], [premiacao], [], [])
|
||||
atuacao = next((a for a in resultado.atuacoes if a.codigo == "MENCAO_AUTOR"), None)
|
||||
assert atuacao is not None
|
||||
assert atuacao.base >= 30
|
||||
|
||||
def test_avaliacao_recorrente_bonus_anual(self):
|
||||
avaliacoes = [
|
||||
AvaliacaoComissao(
|
||||
codigo="AVAL_COMIS_PREMIO", tipo="Membro",
|
||||
premio="PCT", ano=2022
|
||||
),
|
||||
AvaliacaoComissao(
|
||||
codigo="AVAL_COMIS_PREMIO", tipo="Membro",
|
||||
premio="PCT", ano=2023
|
||||
),
|
||||
AvaliacaoComissao(
|
||||
codigo="AVAL_COMIS_PREMIO", tipo="Membro",
|
||||
premio="PCT", ano=2024
|
||||
),
|
||||
]
|
||||
resultado = CalculadorPontuacao.calcular_bloco_c([], avaliacoes, [], [], [])
|
||||
atuacao = next((a for a in resultado.atuacoes if a.codigo == "AVAL_COMIS_PREMIO"), None)
|
||||
assert atuacao is not None
|
||||
assert atuacao.bonus > 0
|
||||
assert atuacao.quantidade == 3
|
||||
|
||||
def test_inscricoes_multiplas_acumulam(self):
|
||||
inscricoes = [
|
||||
Inscricao(codigo="INSC_AUTOR", tipo="Autor", premio="PCT", ano=2022),
|
||||
Inscricao(codigo="INSC_AUTOR", tipo="Autor", premio="PCT", ano=2023),
|
||||
]
|
||||
resultado = CalculadorPontuacao.calcular_bloco_c(inscricoes, [], [], [], [])
|
||||
atuacao = next((a for a in resultado.atuacoes if a.codigo == "INSC_AUTOR"), None)
|
||||
assert atuacao is not None
|
||||
assert atuacao.quantidade == 2
|
||||
assert atuacao.base == 20
|
||||
|
||||
def test_teto_inscricao_autor_20_pontos(self):
|
||||
inscricoes = [
|
||||
Inscricao(codigo="INSC_AUTOR", tipo="Autor", premio="PCT", ano=i)
|
||||
for i in range(2015, 2025)
|
||||
]
|
||||
resultado = CalculadorPontuacao.calcular_bloco_c(inscricoes, [], [], [], [])
|
||||
atuacao = next((a for a in resultado.atuacoes if a.codigo == "INSC_AUTOR"), None)
|
||||
assert atuacao is not None
|
||||
assert atuacao.total <= 20
|
||||
|
||||
|
||||
class TestBlocoDParticipacoes:
|
||||
|
||||
def test_bloco_d_vazio(self):
|
||||
resultado = CalculadorPontuacao.calcular_bloco_d([], [])
|
||||
assert resultado.bloco == "D"
|
||||
assert resultado.total == 0
|
||||
|
||||
def test_bolsa_cnpq_base_30(self):
|
||||
bolsa = BolsaCNPQ(codigo="BOL_BPQ_NIVEL", nivel="1A")
|
||||
resultado = CalculadorPontuacao.calcular_bloco_d([bolsa], [])
|
||||
atuacao = next((a for a in resultado.atuacoes if a.codigo == "BOL_BPQ_NIVEL"), None)
|
||||
assert atuacao is not None
|
||||
assert atuacao.base >= 30
|
||||
|
||||
def test_evento_base_1_ponto(self):
|
||||
evento = Participacao(codigo="EVENTO", tipo="Evento", ano=2024)
|
||||
resultado = CalculadorPontuacao.calcular_bloco_d([], [evento])
|
||||
atuacao = next((a for a in resultado.atuacoes if a.codigo == "EVENTO"), None)
|
||||
assert atuacao is not None
|
||||
assert atuacao.base >= 1
|
||||
|
||||
def test_projeto_base_10_pontos(self):
|
||||
projeto = Participacao(codigo="PROJ", tipo="Projeto", ano=2024)
|
||||
resultado = CalculadorPontuacao.calcular_bloco_d([], [projeto])
|
||||
atuacao = next((a for a in resultado.atuacoes if a.codigo == "PROJ"), None)
|
||||
assert atuacao is not None
|
||||
assert atuacao.base >= 10
|
||||
|
||||
def test_eventos_multiplos_bonus_recorrencia(self):
|
||||
eventos = [
|
||||
Participacao(codigo="EVENTO", tipo="Evento", ano=i)
|
||||
for i in range(2020, 2025)
|
||||
]
|
||||
resultado = CalculadorPontuacao.calcular_bloco_d([], eventos)
|
||||
atuacao = next((a for a in resultado.atuacoes if a.codigo == "EVENTO"), None)
|
||||
assert atuacao is not None
|
||||
assert atuacao.quantidade == 5
|
||||
assert atuacao.bonus > 0
|
||||
|
||||
def test_evento_teto_5_pontos(self):
|
||||
eventos = [
|
||||
Participacao(codigo="EVENTO", tipo="Evento", ano=i)
|
||||
for i in range(2000, 2025)
|
||||
]
|
||||
resultado = CalculadorPontuacao.calcular_bloco_d([], eventos)
|
||||
atuacao = next((a for a in resultado.atuacoes if a.codigo == "EVENTO"), None)
|
||||
assert atuacao is not None
|
||||
assert atuacao.total <= 5
|
||||
|
||||
def test_projeto_teto_30_pontos(self):
|
||||
projetos = [
|
||||
Participacao(codigo="PROJ", tipo="Projeto", ano=i)
|
||||
for i in range(2000, 2025)
|
||||
]
|
||||
resultado = CalculadorPontuacao.calcular_bloco_d([], projetos)
|
||||
atuacao = next((a for a in resultado.atuacoes if a.codigo == "PROJ"), None)
|
||||
assert atuacao is not None
|
||||
assert atuacao.total <= 30
|
||||
|
||||
|
||||
class TestBlocoECoordPPG:
|
||||
|
||||
def test_bloco_e_sem_coordenador(self):
|
||||
resultado = CalculadorPontuacao.calcular_bloco_e(False)
|
||||
assert resultado.bloco == "E"
|
||||
assert resultado.total == 0
|
||||
|
||||
def test_bloco_e_com_coordenador(self):
|
||||
resultado = CalculadorPontuacao.calcular_bloco_e(True)
|
||||
assert resultado.bloco == "E"
|
||||
assert len(resultado.atuacoes) == 1
|
||||
assert resultado.atuacoes[0].codigo == "PPG_COORD"
|
||||
|
||||
|
||||
class TestPontuacaoCompleta:
|
||||
|
||||
def test_consultor_vazio_pontuacao_zero(self, consultor_vazio):
|
||||
resultado = CalculadorPontuacao.calcular_pontuacao_completa(consultor_vazio)
|
||||
assert resultado.total == 0
|
||||
|
||||
def test_consultor_com_coordenacao(self, coordenacao_ca_ativa):
|
||||
consultor = Consultor(
|
||||
id_pessoa=1,
|
||||
nome="Coordenador",
|
||||
coordenacoes_capes=[coordenacao_ca_ativa],
|
||||
)
|
||||
resultado = CalculadorPontuacao.calcular_pontuacao_completa(consultor)
|
||||
assert resultado.bloco_a.total > 0
|
||||
assert resultado.bloco_b.total == 0
|
||||
|
||||
def test_consultor_completo_todos_blocos(self, consultor_completo):
|
||||
resultado = CalculadorPontuacao.calcular_pontuacao_completa(consultor_completo)
|
||||
assert resultado.bloco_a.total > 0
|
||||
assert resultado.bloco_b.total > 0
|
||||
assert resultado.bloco_c.total > 0
|
||||
assert resultado.bloco_d.total > 0
|
||||
|
||||
def test_pontuacao_total_soma_blocos(self, consultor_completo):
|
||||
resultado = CalculadorPontuacao.calcular_pontuacao_completa(consultor_completo)
|
||||
soma_esperada = (
|
||||
resultado.bloco_a.total
|
||||
+ resultado.bloco_b.total
|
||||
+ resultado.bloco_c.total
|
||||
+ resultado.bloco_d.total
|
||||
+ resultado.bloco_e.total
|
||||
)
|
||||
assert resultado.total == soma_esperada
|
||||
|
||||
def test_detalhamento_retorna_dict(self, consultor_completo):
|
||||
resultado = CalculadorPontuacao.calcular_pontuacao_completa(consultor_completo)
|
||||
detalhes = resultado.detalhamento
|
||||
assert isinstance(detalhes, dict)
|
||||
assert "bloco_a" in detalhes
|
||||
assert "bloco_b" in detalhes
|
||||
assert "bloco_c" in detalhes
|
||||
assert "bloco_d" in detalhes
|
||||
assert "bloco_e" in detalhes
|
||||
assert "pontuacao_total" in detalhes
|
||||
|
||||
|
||||
class TestCriteriosConfigurados:
|
||||
|
||||
def test_criterio_ca_existe(self):
|
||||
assert "CA" in CRITERIOS
|
||||
criterio = CRITERIOS["CA"]
|
||||
assert criterio.base == 200
|
||||
assert criterio.teto == 450
|
||||
|
||||
def test_criterio_caj_existe(self):
|
||||
assert "CAJ" in CRITERIOS
|
||||
criterio = CRITERIOS["CAJ"]
|
||||
assert criterio.base == 150
|
||||
assert criterio.teto == 370
|
||||
|
||||
def test_criterio_cons_ativo_existe(self):
|
||||
assert "CONS_ATIVO" in CRITERIOS
|
||||
criterio = CRITERIOS["CONS_ATIVO"]
|
||||
assert criterio.base == 150
|
||||
assert criterio.teto == 230
|
||||
|
||||
def test_todos_criterios_tem_codigo_valido(self):
|
||||
for codigo, criterio in CRITERIOS.items():
|
||||
assert criterio.codigo == codigo
|
||||
assert criterio.base >= 0
|
||||
assert criterio.teto >= 0
|
||||
0
backend/tests/domain/value_objects/__init__.py
Normal file
0
backend/tests/domain/value_objects/__init__.py
Normal file
169
backend/tests/domain/value_objects/test_periodo.py
Normal file
169
backend/tests/domain/value_objects/test_periodo.py
Normal file
@@ -0,0 +1,169 @@
|
||||
import pytest
|
||||
from datetime import datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from src.domain.value_objects.periodo import (
|
||||
Periodo,
|
||||
mesclar_periodos,
|
||||
anos_completos_periodos,
|
||||
)
|
||||
|
||||
|
||||
class TestPeriodo:
|
||||
|
||||
def test_periodo_ativo_sem_fim(self):
|
||||
inicio = datetime(2020, 1, 1)
|
||||
periodo = Periodo(inicio=inicio, fim=None)
|
||||
assert periodo.ativo is True
|
||||
|
||||
def test_periodo_inativo_com_fim(self):
|
||||
inicio = datetime(2020, 1, 1)
|
||||
fim = datetime(2023, 1, 1)
|
||||
periodo = Periodo(inicio=inicio, fim=fim)
|
||||
assert periodo.ativo is False
|
||||
|
||||
def test_anos_decorridos_3_anos(self):
|
||||
inicio = datetime.now() - relativedelta(years=3)
|
||||
fim = datetime.now()
|
||||
periodo = Periodo(inicio=inicio, fim=fim)
|
||||
assert periodo.anos_decorridos >= 2.9
|
||||
assert periodo.anos_decorridos <= 3.1
|
||||
|
||||
def test_anos_decorridos_periodo_ativo(self):
|
||||
inicio = datetime.now() - relativedelta(years=5)
|
||||
periodo = Periodo(inicio=inicio, fim=None)
|
||||
assert periodo.anos_decorridos >= 4.9
|
||||
assert periodo.anos_decorridos <= 5.1
|
||||
|
||||
def test_anos_completos_retorna_inteiro(self):
|
||||
inicio = datetime(2020, 1, 1)
|
||||
fim = datetime(2023, 6, 15)
|
||||
periodo = Periodo(inicio=inicio, fim=fim)
|
||||
assert periodo.anos_completos() == 3
|
||||
|
||||
def test_anos_completos_menos_de_um_ano(self):
|
||||
inicio = datetime.now() - relativedelta(months=6)
|
||||
fim = datetime.now()
|
||||
periodo = Periodo(inicio=inicio, fim=fim)
|
||||
assert periodo.anos_completos() == 0
|
||||
|
||||
def test_anos_completos_com_data_referencia(self):
|
||||
inicio = datetime(2020, 1, 1)
|
||||
periodo = Periodo(inicio=inicio, fim=None)
|
||||
data_ref = datetime(2025, 1, 1)
|
||||
assert periodo.anos_completos(data_ref) == 5
|
||||
|
||||
def test_fim_anterior_inicio_corrige_para_none(self):
|
||||
inicio = datetime(2023, 1, 1)
|
||||
fim = datetime(2020, 1, 1)
|
||||
periodo = Periodo(inicio=inicio, fim=fim)
|
||||
assert periodo.fim is None
|
||||
assert periodo.ativo is True
|
||||
|
||||
|
||||
class TestMesclarPeriodos:
|
||||
|
||||
def test_lista_vazia_retorna_vazia(self):
|
||||
resultado = mesclar_periodos([])
|
||||
assert resultado == []
|
||||
|
||||
def test_um_periodo_retorna_mesmo(self):
|
||||
periodo = Periodo(datetime(2020, 1, 1), datetime(2023, 1, 1))
|
||||
resultado = mesclar_periodos([periodo])
|
||||
assert len(resultado) == 1
|
||||
assert resultado[0].inicio == periodo.inicio
|
||||
|
||||
def test_periodos_consecutivos_mesclados(self):
|
||||
p1 = Periodo(datetime(2020, 1, 1), datetime(2022, 1, 1))
|
||||
p2 = Periodo(datetime(2021, 6, 1), datetime(2024, 1, 1))
|
||||
resultado = mesclar_periodos([p1, p2])
|
||||
assert len(resultado) == 1
|
||||
assert resultado[0].inicio == datetime(2020, 1, 1)
|
||||
assert resultado[0].fim == datetime(2024, 1, 1)
|
||||
|
||||
def test_periodos_separados_nao_mesclados(self):
|
||||
p1 = Periodo(datetime(2015, 1, 1), datetime(2017, 1, 1))
|
||||
p2 = Periodo(datetime(2020, 1, 1), datetime(2023, 1, 1))
|
||||
resultado = mesclar_periodos([p1, p2])
|
||||
assert len(resultado) == 2
|
||||
|
||||
def test_periodo_ativo_preservado(self):
|
||||
p1 = Periodo(datetime(2020, 1, 1), datetime(2022, 1, 1))
|
||||
p2 = Periodo(datetime(2021, 1, 1), None)
|
||||
resultado = mesclar_periodos([p1, p2])
|
||||
assert len(resultado) == 1
|
||||
assert resultado[0].ativo is True
|
||||
|
||||
def test_tres_periodos_mesclados(self):
|
||||
p1 = Periodo(datetime(2018, 1, 1), datetime(2020, 1, 1))
|
||||
p2 = Periodo(datetime(2019, 1, 1), datetime(2021, 1, 1))
|
||||
p3 = Periodo(datetime(2020, 6, 1), datetime(2023, 1, 1))
|
||||
resultado = mesclar_periodos([p1, p2, p3])
|
||||
assert len(resultado) == 1
|
||||
assert resultado[0].inicio == datetime(2018, 1, 1)
|
||||
assert resultado[0].fim == datetime(2023, 1, 1)
|
||||
|
||||
def test_ordenacao_automatica(self):
|
||||
p1 = Periodo(datetime(2022, 1, 1), datetime(2024, 1, 1))
|
||||
p2 = Periodo(datetime(2018, 1, 1), datetime(2020, 1, 1))
|
||||
resultado = mesclar_periodos([p1, p2])
|
||||
assert resultado[0].inicio == datetime(2018, 1, 1)
|
||||
|
||||
|
||||
class TestAnosCompletosPeriodos:
|
||||
|
||||
def test_lista_vazia_retorna_zero(self):
|
||||
resultado = anos_completos_periodos([])
|
||||
assert resultado == 0
|
||||
|
||||
def test_um_periodo_3_anos(self):
|
||||
periodo = Periodo(datetime(2020, 1, 1), datetime(2023, 1, 1))
|
||||
resultado = anos_completos_periodos([periodo])
|
||||
assert resultado == 3
|
||||
|
||||
def test_dois_periodos_soma(self):
|
||||
p1 = Periodo(datetime(2015, 1, 1), datetime(2017, 1, 1))
|
||||
p2 = Periodo(datetime(2020, 1, 1), datetime(2023, 1, 1))
|
||||
resultado = anos_completos_periodos([p1, p2])
|
||||
assert resultado == 5
|
||||
|
||||
def test_com_data_referencia(self):
|
||||
p1 = Periodo(datetime(2020, 1, 1), None)
|
||||
p2 = Periodo(datetime(2015, 1, 1), datetime(2017, 1, 1))
|
||||
data_ref = datetime(2025, 1, 1)
|
||||
resultado = anos_completos_periodos([p1, p2], data_ref)
|
||||
assert resultado == 7
|
||||
|
||||
|
||||
class TestCasosEspeciais:
|
||||
|
||||
def test_periodo_muito_curto(self):
|
||||
inicio = datetime.now() - relativedelta(days=30)
|
||||
fim = datetime.now()
|
||||
periodo = Periodo(inicio=inicio, fim=fim)
|
||||
assert periodo.anos_completos() == 0
|
||||
|
||||
def test_periodo_exatamente_um_ano(self):
|
||||
inicio = datetime(2023, 1, 1)
|
||||
fim = datetime(2024, 1, 1)
|
||||
periodo = Periodo(inicio=inicio, fim=fim)
|
||||
assert periodo.anos_completos() == 1
|
||||
|
||||
def test_periodo_quase_um_ano(self):
|
||||
inicio = datetime(2023, 1, 1)
|
||||
fim = datetime(2023, 12, 31)
|
||||
periodo = Periodo(inicio=inicio, fim=fim)
|
||||
assert periodo.anos_completos() == 0
|
||||
|
||||
def test_mesclagem_periodos_sobrepostos_complexos(self):
|
||||
periodos = [
|
||||
Periodo(datetime(2010, 1, 1), datetime(2012, 1, 1)),
|
||||
Periodo(datetime(2011, 6, 1), datetime(2014, 1, 1)),
|
||||
Periodo(datetime(2013, 1, 1), datetime(2015, 1, 1)),
|
||||
Periodo(datetime(2018, 1, 1), datetime(2020, 1, 1)),
|
||||
Periodo(datetime(2019, 6, 1), None),
|
||||
]
|
||||
resultado = mesclar_periodos(periodos)
|
||||
assert len(resultado) == 2
|
||||
assert resultado[0].fim == datetime(2015, 1, 1)
|
||||
assert resultado[1].ativo is True
|
||||
0
backend/tests/infrastructure/__init__.py
Normal file
0
backend/tests/infrastructure/__init__.py
Normal file
508
backend/tests/infrastructure/elasticsearch/test_client.py
Normal file
508
backend/tests/infrastructure/elasticsearch/test_client.py
Normal file
@@ -0,0 +1,508 @@
|
||||
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"
|
||||
@@ -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
|
||||
125
backend/tests/integration/test_es_repository_integration.py
Normal file
125
backend/tests/integration/test_es_repository_integration.py
Normal file
@@ -0,0 +1,125 @@
|
||||
import pytest
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
from httpx import Response
|
||||
|
||||
from src.infrastructure.elasticsearch.client import ElasticsearchClient
|
||||
from src.infrastructure.repositories.consultor_repository_impl import ConsultorRepositoryImpl
|
||||
from src.domain.services.calculador_pontuacao import CalculadorPontuacao
|
||||
|
||||
|
||||
def create_mock_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
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def es_client():
|
||||
return ElasticsearchClient(
|
||||
url="http://localhost:9200",
|
||||
index="atuacapes_test",
|
||||
user="test",
|
||||
password="test"
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def repository(es_client):
|
||||
return ConsultorRepositoryImpl(es_client)
|
||||
|
||||
|
||||
class TestIntegracaoCoordenadorDeArea:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_coordenador_area_ativo_pontuacao_completa(self, es_client, repository):
|
||||
doc_es = {
|
||||
"id": 1001,
|
||||
"dadosPessoais": {"nome": "COORDENADOR ATIVO SILVA"},
|
||||
"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", "id": 1}
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
with patch.object(es_client, '_client') as mock_client:
|
||||
mock_client.is_closed = False
|
||||
mock_client.post = AsyncMock(return_value=create_mock_response({
|
||||
"hits": {"total": {"value": 1}, "hits": [{"_source": doc_es}]}
|
||||
}))
|
||||
|
||||
consultor = await repository.buscar_por_id(1001)
|
||||
|
||||
assert consultor is not None
|
||||
assert consultor.nome == "COORDENADOR ATIVO SILVA"
|
||||
assert len(consultor.coordenacoes_capes) == 1
|
||||
|
||||
pontuacao = CalculadorPontuacao.calcular_pontuacao_completa(consultor)
|
||||
assert pontuacao.bloco_a.total > 0
|
||||
|
||||
coord = consultor.coordenacoes_capes[0]
|
||||
assert coord.codigo == "CA"
|
||||
assert pontuacao.bloco_a.total >= 200
|
||||
|
||||
|
||||
class TestCenariosBorda:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_consultor_sem_atuacoes(self, es_client, repository):
|
||||
doc_es = {
|
||||
"id": 8001,
|
||||
"dadosPessoais": {"nome": "SEM ATUACOES"},
|
||||
"atuacoes": []
|
||||
}
|
||||
|
||||
with patch.object(es_client, '_client') as mock_client:
|
||||
mock_client.is_closed = False
|
||||
mock_client.post = AsyncMock(return_value=create_mock_response({
|
||||
"hits": {"total": {"value": 1}, "hits": [{"_source": doc_es}]}
|
||||
}))
|
||||
|
||||
consultor = await repository.buscar_por_id(8001)
|
||||
pontuacao = CalculadorPontuacao.calcular_pontuacao_completa(consultor)
|
||||
|
||||
assert consultor.nome == "SEM ATUACOES"
|
||||
assert pontuacao.total == 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_atuacao_tipo_desconhecido(self, es_client, repository):
|
||||
doc_es = {
|
||||
"id": 8002,
|
||||
"dadosPessoais": {"nome": "TIPO ESTRANHO"},
|
||||
"atuacoes": [{
|
||||
"tipo": "Tipo Inexistente No Sistema",
|
||||
"dados": {"campo": "valor"}
|
||||
}]
|
||||
}
|
||||
|
||||
with patch.object(es_client, '_client') as mock_client:
|
||||
mock_client.is_closed = False
|
||||
mock_client.post = AsyncMock(return_value=create_mock_response({
|
||||
"hits": {"total": {"value": 1}, "hits": [{"_source": doc_es}]}
|
||||
}))
|
||||
|
||||
consultor = await repository.buscar_por_id(8002)
|
||||
pontuacao = CalculadorPontuacao.calcular_pontuacao_completa(consultor)
|
||||
|
||||
assert pontuacao.total == 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_consultor_nao_encontrado(self, es_client, repository):
|
||||
with patch.object(es_client, '_client') as mock_client:
|
||||
mock_client.is_closed = False
|
||||
mock_client.post = AsyncMock(return_value=create_mock_response({
|
||||
"hits": {"total": {"value": 0}, "hits": []}
|
||||
}))
|
||||
|
||||
consultor = await repository.buscar_por_id(99999)
|
||||
|
||||
assert consultor is None
|
||||
142
backend/tests/integration/test_scroll_flow.py
Normal file
142
backend/tests/integration/test_scroll_flow.py
Normal file
@@ -0,0 +1,142 @@
|
||||
import pytest
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
from httpx import Response
|
||||
|
||||
from src.infrastructure.elasticsearch.client import ElasticsearchClient
|
||||
from src.infrastructure.repositories.consultor_repository_impl import ConsultorRepositoryImpl
|
||||
from src.domain.services.calculador_pontuacao import CalculadorPontuacao
|
||||
|
||||
|
||||
def create_mock_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
|
||||
|
||||
|
||||
def criar_doc_consultor(id_pessoa, nome, atuacoes):
|
||||
return {
|
||||
"id": id_pessoa,
|
||||
"dadosPessoais": {"nome": nome},
|
||||
"atuacoes": atuacoes
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def es_client():
|
||||
return ElasticsearchClient(
|
||||
url="http://localhost:9200",
|
||||
index="atuacapes_test"
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def repository(es_client):
|
||||
return ConsultorRepositoryImpl(es_client)
|
||||
|
||||
|
||||
class TestScrollFlowCompleto:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_processamento_scroll_com_ranking(self, es_client, repository):
|
||||
docs_batch_1 = [
|
||||
criar_doc_consultor(1, "COORD AREA", [{
|
||||
"tipo": "Coordenação de Área de Avaliação",
|
||||
"inicio": "01/01/2018",
|
||||
"dadosCoordenacaoArea": {"tipo": "Coordenador de Área"}
|
||||
}]),
|
||||
criar_doc_consultor(2, "COORD ADJUNTO", [{
|
||||
"tipo": "Coordenação de Área de Avaliação",
|
||||
"inicio": "01/01/2020",
|
||||
"dadosCoordenacaoArea": {"tipo": "Coordenador Adjunto"}
|
||||
}]),
|
||||
]
|
||||
|
||||
docs_batch_2 = [
|
||||
criar_doc_consultor(3, "CONSULTOR ATIVO", [{
|
||||
"tipo": "Consultor",
|
||||
"inicio": "01/01/2015",
|
||||
"dadosConsultoria": {"situacaoConsultoria": "Atividade Contínua"}
|
||||
}]),
|
||||
criar_doc_consultor(4, "PESSOA SEM ATUACAO", []),
|
||||
]
|
||||
|
||||
scroll_1 = {
|
||||
"_scroll_id": "scroll_1",
|
||||
"hits": {
|
||||
"total": {"value": 4},
|
||||
"hits": [{"_source": d} for d in docs_batch_1]
|
||||
}
|
||||
}
|
||||
|
||||
scroll_2 = {
|
||||
"_scroll_id": "scroll_2",
|
||||
"hits": {"hits": [{"_source": d} for d in docs_batch_2]}
|
||||
}
|
||||
|
||||
scroll_empty = {"_scroll_id": "scroll_3", "hits": {"hits": []}}
|
||||
|
||||
ranking_results = []
|
||||
|
||||
async def processar_batch(docs, progress):
|
||||
for doc in docs:
|
||||
consultor = await repository._construir_consultor(doc)
|
||||
pontuacao = CalculadorPontuacao.calcular_pontuacao_completa(consultor)
|
||||
ranking_results.append({
|
||||
"id": consultor.id_pessoa,
|
||||
"nome": consultor.nome,
|
||||
"total": pontuacao.total
|
||||
})
|
||||
|
||||
with patch.object(es_client, '_client') as mock_client:
|
||||
mock_client.is_closed = False
|
||||
mock_client.post = AsyncMock(side_effect=[
|
||||
create_mock_response(scroll_1),
|
||||
create_mock_response(scroll_2),
|
||||
create_mock_response(scroll_empty)
|
||||
])
|
||||
mock_client.delete = AsyncMock(return_value=create_mock_response({}))
|
||||
|
||||
result = await es_client.buscar_todos_consultores(processar_batch, batch_size=2)
|
||||
|
||||
assert result["total"] == 4
|
||||
assert result["processados"] == 4
|
||||
assert len(ranking_results) == 4
|
||||
|
||||
|
||||
class TestProgressoScroll:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_progress_callback(self, es_client):
|
||||
progress_history = []
|
||||
|
||||
async def callback(docs, progress):
|
||||
progress_history.append(progress.copy())
|
||||
|
||||
scroll_1 = {
|
||||
"_scroll_id": "s1",
|
||||
"hits": {"total": {"value": 100}, "hits": [{"_source": {"id": i}} for i in range(50)]}
|
||||
}
|
||||
scroll_2 = {
|
||||
"_scroll_id": "s2",
|
||||
"hits": {"hits": [{"_source": {"id": i}} for i in range(50, 100)]}
|
||||
}
|
||||
scroll_empty = {"_scroll_id": "s3", "hits": {"hits": []}}
|
||||
|
||||
with patch.object(es_client, '_client') as mock_client:
|
||||
mock_client.is_closed = False
|
||||
mock_client.post = AsyncMock(side_effect=[
|
||||
create_mock_response(scroll_1),
|
||||
create_mock_response(scroll_2),
|
||||
create_mock_response(scroll_empty)
|
||||
])
|
||||
mock_client.delete = AsyncMock(return_value=create_mock_response({}))
|
||||
|
||||
await es_client.buscar_todos_consultores(callback, batch_size=50)
|
||||
|
||||
assert len(progress_history) == 2
|
||||
assert progress_history[0]["percentual"] == 50
|
||||
assert progress_history[0]["processados"] == 50
|
||||
assert progress_history[1]["percentual"] == 100
|
||||
assert progress_history[1]["processados"] == 100
|
||||
0
backend/tests/interface/__init__.py
Normal file
0
backend/tests/interface/__init__.py
Normal file
0
backend/tests/interface/api/__init__.py
Normal file
0
backend/tests/interface/api/__init__.py
Normal file
301
backend/tests/interface/api/test_routes.py
Normal file
301
backend/tests/interface/api/test_routes.py
Normal file
@@ -0,0 +1,301 @@
|
||||
import pytest
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
from fastapi.testclient import TestClient
|
||||
from fastapi import FastAPI
|
||||
|
||||
from src.interface.api.routes import router
|
||||
from src.interface.api.dependencies import (
|
||||
get_repository,
|
||||
get_ranking_store,
|
||||
get_processar_job,
|
||||
get_es_client,
|
||||
get_ranking_oracle_repo,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_repository():
|
||||
repo = AsyncMock()
|
||||
repo.contar_total.return_value = 100
|
||||
return repo
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_ranking_store():
|
||||
store = MagicMock()
|
||||
store.is_ready.return_value = False
|
||||
return store
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_oracle_repo():
|
||||
repo = MagicMock()
|
||||
repo.contar_total.return_value = 1000
|
||||
return repo
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_es_client():
|
||||
return AsyncMock()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_job():
|
||||
return AsyncMock()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app(mock_repository, mock_ranking_store, mock_oracle_repo, mock_es_client, mock_job):
|
||||
app = FastAPI()
|
||||
app.include_router(router)
|
||||
|
||||
app.dependency_overrides[get_repository] = lambda: mock_repository
|
||||
app.dependency_overrides[get_ranking_store] = lambda: mock_ranking_store
|
||||
app.dependency_overrides[get_ranking_oracle_repo] = lambda: mock_oracle_repo
|
||||
app.dependency_overrides[get_es_client] = lambda: mock_es_client
|
||||
app.dependency_overrides[get_processar_job] = lambda: mock_job
|
||||
|
||||
return app
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client(app):
|
||||
return TestClient(app)
|
||||
|
||||
|
||||
class TestHealthCheck:
|
||||
|
||||
def test_health_check_ok(self, client):
|
||||
response = client.get("/api/v1/health")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["status"] == "ok"
|
||||
|
||||
|
||||
class TestRankingStatus:
|
||||
|
||||
def test_status_inicial(self, client):
|
||||
with patch("src.interface.api.routes.job_status") as mock_status:
|
||||
mock_status.to_dict.return_value = {
|
||||
"running": False,
|
||||
"progress": 0,
|
||||
"processados": 0,
|
||||
"total": 0,
|
||||
"mensagem": "",
|
||||
"batch_atual": 0,
|
||||
"total_batches": 0,
|
||||
"tempo_decorrido": None,
|
||||
"tempo_estimado": None,
|
||||
"inicio": None,
|
||||
"fim": None,
|
||||
"erro": None,
|
||||
}
|
||||
response = client.get("/api/v1/ranking/status")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["running"] is False
|
||||
|
||||
|
||||
class TestRankingPaginado:
|
||||
|
||||
def test_ranking_paginado_sem_oracle(self, client, mock_oracle_repo):
|
||||
mock_oracle_repo.contar_total.return_value = 0
|
||||
response = client.get("/api/v1/ranking/paginado")
|
||||
assert response.status_code == 503
|
||||
|
||||
def test_ranking_paginado_com_dados(self, client, mock_oracle_repo):
|
||||
mock_oracle_repo.contar_total.return_value = 100
|
||||
mock_oracle_repo.buscar_paginado.return_value = [
|
||||
MagicMock(
|
||||
id_pessoa=1,
|
||||
nome="Consultor 1",
|
||||
posicao=1,
|
||||
pontuacao_total=500,
|
||||
componente_a=200,
|
||||
componente_b=100,
|
||||
componente_c=100,
|
||||
componente_d=50,
|
||||
componente_e=50,
|
||||
ativo=True,
|
||||
anos_atuacao=5.0,
|
||||
json_detalhes="{}"
|
||||
)
|
||||
]
|
||||
response = client.get("/api/v1/ranking/paginado?page=1&size=10")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["total"] == 100
|
||||
assert len(data["consultores"]) == 1
|
||||
|
||||
def test_ranking_paginado_filtro_ativo(self, client, mock_oracle_repo):
|
||||
mock_oracle_repo.contar_total.return_value = 50
|
||||
mock_oracle_repo.buscar_paginado.return_value = []
|
||||
response = client.get("/api/v1/ranking/paginado?ativo=true")
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_ranking_paginado_filtro_selos(self, client, mock_oracle_repo):
|
||||
mock_oracle_repo.contar_total.return_value = 0
|
||||
response = client.get("/api/v1/ranking/paginado?selos=COORD_PPG,BPQ")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["total"] == 0
|
||||
|
||||
|
||||
class TestBuscaPorNome:
|
||||
|
||||
def test_busca_por_nome_encontrado(self, client, mock_oracle_repo):
|
||||
mock_oracle_repo.buscar_por_nome.return_value = [
|
||||
{"ID_PESSOA": 1, "NOME": "João Silva", "POSICAO": 10, "PONTUACAO_TOTAL": 500}
|
||||
]
|
||||
response = client.get("/api/v1/ranking/busca?nome=João")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert len(data) == 1
|
||||
assert data[0]["nome"] == "João Silva"
|
||||
|
||||
def test_busca_por_nome_nao_encontrado(self, client, mock_oracle_repo):
|
||||
mock_oracle_repo.buscar_por_nome.return_value = []
|
||||
response = client.get("/api/v1/ranking/busca?nome=XYZ")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert len(data) == 0
|
||||
|
||||
def test_busca_por_nome_minimo_3_chars(self, client):
|
||||
response = client.get("/api/v1/ranking/busca?nome=AB")
|
||||
assert response.status_code == 422
|
||||
|
||||
|
||||
class TestRankingEstatisticas:
|
||||
|
||||
def test_estatisticas_sem_dados(self, client, mock_oracle_repo):
|
||||
mock_oracle_repo.contar_total.return_value = 0
|
||||
response = client.get("/api/v1/ranking/estatisticas")
|
||||
assert response.status_code == 503
|
||||
|
||||
def test_estatisticas_com_dados(self, client, mock_oracle_repo):
|
||||
mock_oracle_repo.contar_total.return_value = 1000
|
||||
mock_oracle_repo.obter_estatisticas.return_value = {
|
||||
"total_consultores": 1000,
|
||||
"total_ativos": 800,
|
||||
"total_inativos": 200,
|
||||
"ultima_atualizacao": "2024-01-01",
|
||||
"pontuacao_media": 150.5,
|
||||
"pontuacao_maxima": 850,
|
||||
"pontuacao_minima": 10,
|
||||
"media_componentes": {"a": 50, "b": 30, "c": 40, "d": 20, "e": 10}
|
||||
}
|
||||
mock_oracle_repo.obter_distribuicao.return_value = [
|
||||
{"faixa": "0-100", "quantidade": 500},
|
||||
{"faixa": "100-200", "quantidade": 300},
|
||||
]
|
||||
response = client.get("/api/v1/ranking/estatisticas")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["total_consultores"] == 1000
|
||||
assert data["total_ativos"] == 800
|
||||
|
||||
|
||||
class TestPosicaoRanking:
|
||||
|
||||
def test_posicao_encontrada(self, client, mock_oracle_repo):
|
||||
mock_oracle_repo.contar_total.return_value = 1000
|
||||
mock_oracle_repo.buscar_por_id.return_value = MagicMock(
|
||||
id_pessoa=123,
|
||||
nome="João Silva",
|
||||
posicao=42,
|
||||
pontuacao_total=500,
|
||||
componente_a=200,
|
||||
componente_b=100,
|
||||
componente_c=100,
|
||||
componente_d=50,
|
||||
componente_e=50,
|
||||
ativo=True,
|
||||
)
|
||||
response = client.get("/api/v1/ranking/posicao/123")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["id_pessoa"] == 123
|
||||
assert data["posicao"] == 42
|
||||
assert data["encontrado"] is True
|
||||
|
||||
def test_posicao_nao_encontrada(self, client, mock_oracle_repo):
|
||||
mock_oracle_repo.contar_total.return_value = 1000
|
||||
mock_oracle_repo.buscar_por_id.return_value = None
|
||||
response = client.get("/api/v1/ranking/posicao/99999")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["encontrado"] is False
|
||||
|
||||
|
||||
class TestProcessarRanking:
|
||||
|
||||
def test_processar_ranking_inicia(self, client, mock_job):
|
||||
with patch("src.interface.api.routes.job_status") as mock_status:
|
||||
mock_status.is_running = False
|
||||
response = client.post("/api/v1/ranking/processar")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["sucesso"] is True
|
||||
|
||||
def test_processar_ranking_ja_executando(self, client):
|
||||
with patch("src.interface.api.routes.job_status") as mock_status:
|
||||
mock_status.is_running = True
|
||||
response = client.post("/api/v1/ranking/processar")
|
||||
assert response.status_code == 409
|
||||
|
||||
|
||||
class TestListarSelos:
|
||||
|
||||
def test_listar_selos(self, client):
|
||||
response = client.get("/api/v1/ranking/selos")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "selos" in data
|
||||
assert isinstance(data["selos"], list)
|
||||
|
||||
|
||||
class TestExportarInfo:
|
||||
|
||||
def test_info_exportacao(self, client, mock_oracle_repo):
|
||||
mock_oracle_repo.contar_para_exportacao.return_value = 500
|
||||
response = client.get("/api/v1/ranking/exportar/info")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["total_consultores"] == 500
|
||||
assert "estimativa_tamanho_mb" in data
|
||||
|
||||
|
||||
class TestCorrigirEncoding:
|
||||
|
||||
def test_corrigir_encoding_sem_problemas(self):
|
||||
from src.interface.api.routes import corrigir_encoding
|
||||
result = corrigir_encoding("Texto normal")
|
||||
assert result == "Texto normal"
|
||||
|
||||
def test_corrigir_encoding_none(self):
|
||||
from src.interface.api.routes import corrigir_encoding
|
||||
result = corrigir_encoding(None)
|
||||
assert result is None
|
||||
|
||||
def test_corrigir_encoding_vazio(self):
|
||||
from src.interface.api.routes import corrigir_encoding
|
||||
result = corrigir_encoding("")
|
||||
assert result == ""
|
||||
|
||||
|
||||
class TestNormalizarTexto:
|
||||
|
||||
def test_normalizar_texto_acentos(self):
|
||||
from src.interface.api.routes import normalizar_texto
|
||||
result = normalizar_texto("Ciências Ambientais")
|
||||
assert result == "ciencias ambientais"
|
||||
|
||||
def test_normalizar_texto_html_entities(self):
|
||||
from src.interface.api.routes import normalizar_texto
|
||||
result = normalizar_texto("Ciências")
|
||||
assert "ciencias" in result.lower()
|
||||
|
||||
def test_normalizar_texto_vazio(self):
|
||||
from src.interface.api.routes import normalizar_texto
|
||||
result = normalizar_texto("")
|
||||
assert result == ""
|
||||
Reference in New Issue
Block a user