diff --git a/api/src/repository/vsp_repository.py b/api/src/repository/vsp_repository.py index 87c0de5..1cd5caa 100644 --- a/api/src/repository/vsp_repository.py +++ b/api/src/repository/vsp_repository.py @@ -32,7 +32,9 @@ class VSPRepository: load_org_unit: bool = False, with_deleted: bool = False, ) -> Optional[Vsp]: - queries = [Vsp.id == vsp_id, Vsp.is_deleted.is_(with_deleted)] + queries = [Vsp.id == vsp_id] + if not with_deleted: + queries.append(Vsp.is_deleted.is_(False)) if is_active is not None: queries.append(Vsp.is_active == is_active) if load_org_unit: diff --git a/api/src/services/vsp_service.py b/api/src/services/vsp_service.py index df6d30c..63e7847 100644 --- a/api/src/services/vsp_service.py +++ b/api/src/services/vsp_service.py @@ -264,7 +264,7 @@ class VSPService: if not set(data.keys()).issubset(self.EXECUTOR_EDITABLE_FIELDS): raise AccessDeniedException() - effective_close_date = data.get("close_date", vsp.closed_at) + effective_close_date = data.get("closed_at", vsp.closed_at) if effective_close_date is not None and effective_close_date <= date.today(): data["is_active"] = False diff --git a/api/tests/integration/test_vsp.py b/api/tests/integration/test_vsp.py index cdbd118..4ea505c 100644 --- a/api/tests/integration/test_vsp.py +++ b/api/tests/integration/test_vsp.py @@ -293,3 +293,63 @@ def test_vsp_delete_forbidden_for_executor(client, isp_tokens, auth_headers): def test_vsp_requires_auth(client): response = client.get("/api/v1/dict/info") assert response.status_code == 403 + + +def _create_vsp(client, admin_tokens, auth_headers, **extra) -> int: + created = client.post( + "/api/v1/dict/info", + json={ + "registration_number": _unique_reg_number(), + "address": "AURORA-1885", + "ssp_id": 1, + **extra, + }, + headers=auth_headers(admin_tokens), + ) + assert created.status_code == 201 + return created.json()["result"]["id"] + + +def test_vsp_get_by_id_admin_active(client, admin_tokens, auth_headers): + vsp_id = _create_vsp(client, admin_tokens, auth_headers) + response = client.get(f"/api/v1/dict/info/{vsp_id}", headers=auth_headers(admin_tokens)) + assert response.status_code == 200 + assert response.json()["result"]["id"] == vsp_id + assert response.json()["result"]["is_deleted"] is False + + +def test_vsp_get_by_id_admin_sees_deleted(client, admin_tokens, auth_headers): + vsp_id = _create_vsp(client, admin_tokens, auth_headers) + deleted = client.delete(f"/api/v1/dict/info/{vsp_id}", headers=auth_headers(admin_tokens)) + assert deleted.status_code == 200 + + response = client.get(f"/api/v1/dict/info/{vsp_id}", headers=auth_headers(admin_tokens)) + assert response.status_code == 200 + assert response.json()["result"]["is_deleted"] is True + + +def test_vsp_get_by_id_executor_deleted_not_found(client, admin_tokens, isp_tokens, auth_headers): + vsp_id = _create_vsp(client, admin_tokens, auth_headers) + client.delete(f"/api/v1/dict/info/{vsp_id}", headers=auth_headers(admin_tokens)) + + response = client.get(f"/api/v1/dict/info/{vsp_id}", headers=auth_headers(isp_tokens)) + assert response.status_code == 404 + + +def test_vsp_update_past_close_date_deactivates(client, admin_tokens, auth_headers): + from datetime import date, timedelta + + vsp_id = _create_vsp(client, admin_tokens, auth_headers, open_date="2020-01-01") + yesterday = (date.today() - timedelta(days=1)).isoformat() + + response = client.put( + f"/api/v1/dict/info/{vsp_id}", + json={"close_date": yesterday}, + headers=auth_headers(admin_tokens), + ) + assert response.status_code == 200 + assert response.json()["result"]["is_active"] is False + + listing = client.get("/api/v1/dict/info", headers=auth_headers(admin_tokens)) + assert listing.status_code == 200 + assert vsp_id not in [item["id"] for item in listing.json()["result"]] diff --git a/api/tests/unit/test_vsp_service_close_date.py b/api/tests/unit/test_vsp_service_close_date.py new file mode 100644 index 0000000..e29814b --- /dev/null +++ b/api/tests/unit/test_vsp_service_close_date.py @@ -0,0 +1,45 @@ +from datetime import date, timedelta +from unittest.mock import AsyncMock, MagicMock + +import pytest + +from src.db.models import UserRoleEnum +from src.domain.schemas import VSPUpdate +from src.services.vsp_service import VSPService + + +def _service(vsp) -> VSPService: + service = VSPService.__new__(VSPService) + service.vsp_repo = MagicMock() + service.vsp_repo.get = AsyncMock(return_value=vsp) + service.vsp_repo.update = AsyncMock(return_value=vsp) + return service + + +def _admin(): + user = MagicMock() + user.id = 1 + user.role_id = UserRoleEnum.ADMIN + return user + + +async def test_update_past_close_date_sets_inactive(): + vsp = MagicMock(closed_at=None) + service = _service(vsp) + payload = VSPUpdate(close_date=date.today() - timedelta(days=1)) + + await service.update(vsp_id=1, payload=payload, user=_admin()) + + data = service.vsp_repo.update.await_args.kwargs["data"] + assert data["is_active"] is False + + +async def test_update_future_close_date_keeps_active_key_absent(): + vsp = MagicMock(closed_at=None) + service = _service(vsp) + payload = VSPUpdate(close_date=date.today() + timedelta(days=30)) + + await service.update(vsp_id=1, payload=payload, user=_admin()) + + data = service.vsp_repo.update.await_args.kwargs["data"] + assert "is_active" not in data