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, 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: 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. 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", [ diff --git a/tests/plano_trabalho/date_validation_test.py b/tests/plano_trabalho/date_validation_test.py index 20b3af8..2e0a3b0 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,46 @@ 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.""" + self.put_plano_trabalho(self.input_pt) + input_pt2 = deepcopy(self.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 + ): + 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_pt2) + class TestCreatePTOverlappingDateInterval(BasePTTest): """Testes relacionados a criar um Plano de Trabalho com sobreposição