From d7abc88bef2126fa503bacc7adc532d6787f86cd Mon Sep 17 00:00:00 2001 From: Eduardo Lauer Date: Tue, 5 Aug 2025 17:08:40 -0300 Subject: [PATCH 1/8] add session flush and fix identation --- src/crud.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/crud.py b/src/crud.py index ac59365..8fa0ce7 100644 --- a/src/crud.py +++ b/src/crud.py @@ -157,7 +157,6 @@ async def _build_plano_trabalho_model( f" matricula_siape: {plano_trabalho.matricula_siape}\n" f" cod_unidade_lotacao: {plano_trabalho.cod_unidade_lotacao_participante}" ) - session.add(db_participante) db_participante.planos_trabalho.append(db_plano) @@ -274,13 +273,14 @@ async def update_plano_trabalho( ) session.add(db_plano_atualizado) try: - await session.refresh(db_plano_atualizado) + await session.flush() except IntegrityError as e: raise HTTPException( status_code=422, detail="Alteração rejeitada por violar regras de integridade", ) from e - return schemas.PlanoTrabalhoSchema.model_validate(db_plano_atualizado) + await session.refresh(db_plano_atualizado) + return schemas.PlanoTrabalhoSchema.model_validate(db_plano_atualizado) async def get_plano_entregas( @@ -469,8 +469,11 @@ async def update_plano_entregas( for entrega in db_plano_entregas_atualizado.entregas: session.add(entrega) + session.add(db_plano_entregas_atualizado) + await session.flush() + await session.refresh(db_plano_entregas_atualizado) return schemas.PlanoEntregasSchema.model_validate(db_plano_entregas_atualizado) From f996b213ad0786bce5913941be6738ef69888bf1 Mon Sep 17 00:00:00 2001 From: Eduardo Lauer Date: Fri, 26 Sep 2025 18:37:30 -0300 Subject: [PATCH 2/8] create test for validate year interval in update PT --- tests/plano_trabalho/date_validation_test.py | 39 +++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/tests/plano_trabalho/date_validation_test.py b/tests/plano_trabalho/date_validation_test.py index 20b3af8..276b2de 100644 --- a/tests/plano_trabalho/date_validation_test.py +++ b/tests/plano_trabalho/date_validation_test.py @@ -11,7 +11,7 @@ from util import over_a_year, assert_error_message from .core_test import BasePTTest -from tests.conftest import MIN_ALLOWED_PT_TCR_DATE +from tests.conftest import MIN_ALLOWED_PT_TCR_DATE, PT_PE_UPDATE_YEAR_VALIDATION_CUTOFF_DATE # Datas básicas @@ -109,6 +109,43 @@ def test_create_plano_trabalho_date_interval_over_a_year( assert response.status_code == status.HTTP_201_CREATED self.assert_equal_plano_trabalho(response.json(), input_pt) +class TestUpdatePTDateIntervalOverAYear(BasePTTest): + """Testes relacionados a atualizar um Plano de Trabalho com intervalo de + data superior a um ano.""" + + @pytest.mark.parametrize( + "data_inicio, data_termino", + [ + ("2024-01-01", "2025-05-31"), # mais que um ano antes da data de corte + ("2025-06-01", "2026-06-02"), # mais que um ano após a data de corte + ], + ) + def test_update_plano_trabalho_date_interval_over_a_year( + self, data_inicio: str, data_termino: str + ): + """Plano de trabalho pode ser atualizado com vigência superior a um ano + desde que antes da data de corte.""" + input_pt = deepcopy(self.input_pt) + input_pt["data_inicio"] = data_inicio + input_pt["data_termino"] = data_termino + + response = self.put_plano_trabalho(input_pt) + + if ( + over_a_year( + date.fromisoformat(data_inicio), date.fromisoformat(data_termino) + ) + == 1 AND date.fromisoformat(data_inicio) > PT_PE_UPDATE_YEAR_VALIDATION_CUTOFF_DATE + ): + assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY + detail_message = ( + "Plano de trabalho não pode abranger período maior que 1 ano" + ) + assert_error_message(response, detail_message) + else: + assert response.status_code == status.HTTP_200_OK + self.assert_equal_plano_trabalho(response.json(), input_pt) + class TestCreatePTOverlappingDateInterval(BasePTTest): """Testes relacionados a criar um Plano de Trabalho com sobreposição From 795209cf47720cebe403c772a0cd65fee1fc3c0b Mon Sep 17 00:00:00 2001 From: Eduardo Lauer Date: Mon, 6 Oct 2025 18:03:41 -0300 Subject: [PATCH 3/8] create constant for cutoff date --- tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 1512bb6..56a9fe9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -59,7 +59,7 @@ MAX_BIGINT = (2**63) - 1 MIN_ALLOWED_PT_TCR_DATE = date(2023, 7, 31) - +PT_PE_UPDATE_YEAR_VALIDATION_CUTOFF_DATE = date(2025, 5, 31) def get_bearer_token(username: str, password: str) -> str: """Login on api-pgd and returns token to next authenticated calls. From ad364288de4b52a62ec85179206285d632036485 Mon Sep 17 00:00:00 2001 From: Eduardo Lauer Date: Mon, 6 Oct 2025 18:04:30 -0300 Subject: [PATCH 4/8] fix test for update_pt --- tests/plano_trabalho/date_validation_test.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/tests/plano_trabalho/date_validation_test.py b/tests/plano_trabalho/date_validation_test.py index 276b2de..2e0a3b0 100644 --- a/tests/plano_trabalho/date_validation_test.py +++ b/tests/plano_trabalho/date_validation_test.py @@ -125,17 +125,20 @@ def test_update_plano_trabalho_date_interval_over_a_year( ): """Plano de trabalho pode ser atualizado com vigência superior a um ano desde que antes da data de corte.""" - input_pt = deepcopy(self.input_pt) - input_pt["data_inicio"] = data_inicio - input_pt["data_termino"] = data_termino + self.put_plano_trabalho(self.input_pt) + input_pt2 = deepcopy(self.input_pt) - response = self.put_plano_trabalho(input_pt) + input_pt2["data_inicio"] = data_inicio + input_pt2["data_termino"] = data_termino + input_pt2["avaliacoes_registros_execucao"] = [] + + response = self.put_plano_trabalho(input_pt2) if ( over_a_year( date.fromisoformat(data_inicio), date.fromisoformat(data_termino) ) - == 1 AND date.fromisoformat(data_inicio) > PT_PE_UPDATE_YEAR_VALIDATION_CUTOFF_DATE + == 1 and date.fromisoformat(data_inicio) > PT_PE_UPDATE_YEAR_VALIDATION_CUTOFF_DATE ): assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY detail_message = ( @@ -144,7 +147,7 @@ def test_update_plano_trabalho_date_interval_over_a_year( assert_error_message(response, detail_message) else: assert response.status_code == status.HTTP_200_OK - self.assert_equal_plano_trabalho(response.json(), input_pt) + self.assert_equal_plano_trabalho(response.json(), input_pt2) class TestCreatePTOverlappingDateInterval(BasePTTest): From 364facc5b52abbc86d42e4e9fc9b0de469a2c52f Mon Sep 17 00:00:00 2001 From: Eduardo Lauer Date: Mon, 6 Oct 2025 18:05:07 -0300 Subject: [PATCH 5/8] create test_update_plano_entregas_date_interval_over_a_year --- tests/plano_entregas/date_validation_test.py | 47 +++++++++++++++++--- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/tests/plano_entregas/date_validation_test.py b/tests/plano_entregas/date_validation_test.py index 4a86c29..2e41830 100644 --- a/tests/plano_entregas/date_validation_test.py +++ b/tests/plano_entregas/date_validation_test.py @@ -11,7 +11,7 @@ from util import over_a_year from .core_test import BasePETest - +from tests.conftest import PT_PE_UPDATE_YEAR_VALIDATION_CUTOFF_DATE # Datas básicas @@ -51,14 +51,51 @@ def test_create_plano_entregas_date_interval_over_a_year( detail_message = ( "Plano de entregas não pode abranger período maior que 1 ano" ) - assert any( - f"Value error, {detail_message}" in error["msg"] - for error in response.json().get("detail") - ) + assert response.json().get("detail") == detail_message else: assert response.status_code == http_status.HTTP_201_CREATED self.assert_equal_plano_entregas(response.json(), input_pe) + @pytest.mark.parametrize( + "data_inicio, data_termino", + [ + ("2024-01-01", "2025-01-02", ), # antes da data de corte (permitido) + ("2025-06-01", "2026-06-02"), # após da data de corte (proibido) + ], + ) + def test_update_plano_entregas_date_interval_over_a_year( + self, + truncate_pe, # pylint: disable=unused-argument + data_inicio: str, + data_termino: str, + ): + """Plano de Entregas não pode ter vigência superior a um ano.""" + self.put_plano_entregas(self.input_pe) + input_pe2 = deepcopy(self.input_pe) + input_pe2["data_inicio"] = data_inicio + input_pe2["data_termino"] = data_termino + # para evitar erro de data_avaliacao < data_inicio + input_pe2["data_avaliacao"] = data_inicio + + response = self.put_plano_entregas(input_pe2) + + if ( + over_a_year( + date.fromisoformat(data_inicio), date.fromisoformat(data_termino) + ) + == 1 and date.fromisoformat(data_inicio) > PT_PE_UPDATE_YEAR_VALIDATION_CUTOFF_DATE + ): + assert response.status_code == http_status.HTTP_422_UNPROCESSABLE_ENTITY + detail_message = ( + "Plano de entregas não pode abranger período maior que 1 ano" + ) + assert response.json().get("detail") == detail_message + else: + assert response.status_code == http_status.HTTP_200_OK + self.assert_equal_plano_entregas(response.json(), input_pe2) + + + @pytest.mark.parametrize( "data_inicio, data_termino", [ From a99ebfb0d942382c00edade52e61734bcefe3fb0 Mon Sep 17 00:00:00 2001 From: Eduardo Lauer Date: Mon, 6 Oct 2025 18:05:50 -0300 Subject: [PATCH 6/8] add logic validation for over_a_year for PT and PE --- src/api.py | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/api.py b/src/api.py index bfed964..8d77e13 100644 --- a/src/api.py +++ b/src/api.py @@ -2,7 +2,7 @@ """ from contextlib import asynccontextmanager -from datetime import timedelta +from datetime import date, timedelta import json import logging import os @@ -29,7 +29,7 @@ import email_config import response_schemas import schemas -from util import check_permissions +from util import check_permissions, over_a_year DEFAULT_TOKEN_EXPIRE_MINS = 30 ACCESS_TOKEN_EXPIRE_MINUTES = int( @@ -37,6 +37,7 @@ ) TEST_ENVIRONMENT = os.environ.get("TEST_ENVIRONMENT", "False") == "True" DB_AUDIT_LOGS_ENABLED = os.environ.get("DB_AUDIT_LOGS_ENABLED", "False") == "True" +PT_PE_UPDATE_YEAR_VALIDATION_CUTOFF_DATE = date(2025, 5, 31) # ## INIT -------------------------------------------------- with open( @@ -589,12 +590,24 @@ async def create_or_update_plano_entregas( try: if not db_plano_entregas: # create + if over_a_year(plano_entregas.data_inicio, plano_entregas.data_termino) == 1: + detail_msg = ( + "Plano de entregas não pode abranger período maior que 1 ano" + ) + raise HTTPException(status.HTTP_422_UNPROCESSABLE_ENTITY, detail=detail_msg) + novo_plano_entregas = await crud.create_plano_entregas( db_session=db, plano_entregas=novo_plano_entregas, ) response.status_code = status.HTTP_201_CREATED else: # update + if over_a_year(plano_entregas.data_inicio, plano_entregas.data_termino) == 1 and \ + plano_entregas.data_inicio > PT_PE_UPDATE_YEAR_VALIDATION_CUTOFF_DATE: + detail_msg = ( + "Plano de entregas não pode abranger período maior que 1 ano" + ) + raise HTTPException(status.HTTP_422_UNPROCESSABLE_ENTITY, detail=detail_msg) novo_plano_entregas = await crud.update_plano_entregas( db_session=db, plano_entregas=novo_plano_entregas, @@ -722,12 +735,25 @@ async def create_or_update_plano_trabalho( try: if not db_plano_trabalho: # create + if over_a_year(plano_trabalho.data_inicio, plano_trabalho.data_termino) == 1: + detail_msg = ( + "Plano de trabalho não pode abranger período maior que 1 ano" + ) + raise HTTPException(status.HTTP_422_UNPROCESSABLE_ENTITY, detail=detail_msg) + novo_plano_trabalho = await crud.create_plano_trabalho( db_session=db, plano_trabalho=novo_plano_trabalho, ) response.status_code = status.HTTP_201_CREATED else: # update + if over_a_year(plano_trabalho.data_inicio, plano_trabalho.data_termino) == 1 and \ + plano_trabalho.data_inicio > PT_PE_UPDATE_YEAR_VALIDATION_CUTOFF_DATE: + detail_msg = ( + "Plano de trabalho não pode abranger período maior que 1 ano" + ) + raise HTTPException(status.HTTP_422_UNPROCESSABLE_ENTITY, detail=detail_msg) + novo_plano_trabalho = await crud.update_plano_trabalho( db_session=db, plano_trabalho=novo_plano_trabalho, From 439bed8e9e916ea9c47b82cc42b65120797ca27d Mon Sep 17 00:00:00 2001 From: Eduardo Lauer Date: Mon, 6 Oct 2025 18:06:26 -0300 Subject: [PATCH 7/8] remove over_a_year pydantic validation --- src/schemas.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/schemas.py b/src/schemas.py index b7aabd1..c4b5300 100644 --- a/src/schemas.py +++ b/src/schemas.py @@ -24,7 +24,6 @@ Participante, Users, ) -from util import over_a_year STR_FIELD_MAX_SIZE = 300 NON_NEGATIVE_INT4 = Annotated[NonNegativeInt, Field(le=(2**31) - 1)] @@ -380,15 +379,6 @@ def validate_unidade(self): ) return self - @model_validator(mode="after") - def year_interval(self) -> "PlanoTrabalhoSchema": - """Garante que o plano não abrange um período maior que 1 ano.""" - if over_a_year(self.data_inicio, self.data_termino) == 1: - raise ValueError( - "Plano de trabalho não pode abranger período maior que 1 ano" - ) - return self - @model_validator(mode="after") def must_be_sequential_dates(self) -> "PlanoTrabalhoSchema": "Verifica se a data de início e a data de término estão na ordem esperada." @@ -573,10 +563,6 @@ def validate_entregas_uniqueness(self) -> "PlanoEntregasSchema": @model_validator(mode="after") def validate_period(self) -> "PlanoEntregasSchema": """Valida o período do plano de entregas.""" - if over_a_year(self.data_inicio, self.data_termino) == 1: - raise ValueError( - "Plano de entregas não pode abranger período maior que 1 ano" - ) if self.data_termino < self.data_inicio: raise ValueError("data_termino deve ser maior ou igual que data_inicio.") if self.data_avaliacao is not None and self.data_avaliacao < self.data_inicio: From 68191b7586307432065316fa7c69f1b38a45478e Mon Sep 17 00:00:00 2001 From: Eduardo Lauer Date: Wed, 8 Oct 2025 11:28:54 -0300 Subject: [PATCH 8/8] add debug for ci/cd --- .github/workflows/ci_tests.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/ci_tests.yml b/.github/workflows/ci_tests.yml index 754e540..be0963a 100644 --- a/.github/workflows/ci_tests.yml +++ b/.github/workflows/ci_tests.yml @@ -21,3 +21,14 @@ jobs: run: make build - name: Run tests run: make tests + - name: Debug failure + if: failure() + run: | + echo "=== Docker Compose Services ===" + docker compose ps + echo + echo "=== Logs for api-pgd ===" + docker compose logs api-pgd || true + echo + echo "=== Logs for all containers ===" + docker compose logs || true