fix(vsp): админ получает ВСП по id включая удалённые; прошедшая дата закрытия сразу закрывает ВСП (AURORA-1885, #327)
- VSPRepository.get: with_deleted=True означает «включая удалённые», а не «только удалённые» - VSPService.update: ключ closed_at (имя поля модели) вместо close_date (входной alias) - тесты: integration test_vsp.py, unit test_vsp_service_close_date.py Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
ac686991a8
commit
744b71fbe7
@ -32,7 +32,9 @@ class VSPRepository:
|
|||||||
load_org_unit: bool = False,
|
load_org_unit: bool = False,
|
||||||
with_deleted: bool = False,
|
with_deleted: bool = False,
|
||||||
) -> Optional[Vsp]:
|
) -> 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:
|
if is_active is not None:
|
||||||
queries.append(Vsp.is_active == is_active)
|
queries.append(Vsp.is_active == is_active)
|
||||||
if load_org_unit:
|
if load_org_unit:
|
||||||
|
|||||||
@ -264,7 +264,7 @@ class VSPService:
|
|||||||
if not set(data.keys()).issubset(self.EXECUTOR_EDITABLE_FIELDS):
|
if not set(data.keys()).issubset(self.EXECUTOR_EDITABLE_FIELDS):
|
||||||
raise AccessDeniedException()
|
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():
|
if effective_close_date is not None and effective_close_date <= date.today():
|
||||||
data["is_active"] = False
|
data["is_active"] = False
|
||||||
|
|
||||||
|
|||||||
@ -293,3 +293,63 @@ def test_vsp_delete_forbidden_for_executor(client, isp_tokens, auth_headers):
|
|||||||
def test_vsp_requires_auth(client):
|
def test_vsp_requires_auth(client):
|
||||||
response = client.get("/api/v1/dict/info")
|
response = client.get("/api/v1/dict/info")
|
||||||
assert response.status_code == 403
|
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"]]
|
||||||
|
|||||||
45
api/tests/unit/test_vsp_service_close_date.py
Normal file
45
api/tests/unit/test_vsp_service_close_date.py
Normal file
@ -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
|
||||||
Loading…
x
Reference in New Issue
Block a user