Skip to content

Commit de3528a

Browse files
adding test for open project
1 parent 89bda75 commit de3528a

File tree

7 files changed

+103
-18
lines changed

7 files changed

+103
-18
lines changed

packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/resource_usage_tracker/service_runs.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# pylint: disable=too-many-arguments
12
import logging
23
from typing import Final
34

services/web/server/src/simcore_service_webserver/projects/_nodes_handlers.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,13 @@
8080
ClustersKeeperNotAvailableError,
8181
DefaultPricingUnitNotFoundError,
8282
NodeNotFoundError,
83+
ProjectInDebtCanNotChangeWalletError,
8384
ProjectInvalidRightsError,
8485
ProjectNodeRequiredInputsNotSetError,
8586
ProjectNodeResourcesInsufficientRightsError,
8687
ProjectNodeResourcesInvalidError,
8788
ProjectNotFoundError,
8889
ProjectStartsTooManyDynamicNodesError,
89-
ProjectWalletDebtError,
9090
)
9191

9292
_logger = logging.getLogger(__name__)
@@ -108,7 +108,10 @@ async def wrapper(request: web.Request) -> web.StreamResponse:
108108
CatalogItemNotFoundError,
109109
) as exc:
110110
raise web.HTTPNotFound(reason=f"{exc}") from exc
111-
except (WalletNotEnoughCreditsError, ProjectWalletDebtError) as exc:
111+
except (
112+
WalletNotEnoughCreditsError,
113+
ProjectInDebtCanNotChangeWalletError,
114+
) as exc:
112115
raise web.HTTPPaymentRequired(reason=f"{exc}") from exc
113116
except ProjectInvalidRightsError as exc:
114117
raise web.HTTPUnauthorized(reason=f"{exc}") from exc

services/web/server/src/simcore_service_webserver/projects/_states_handlers.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,12 @@
4141
from ._common.models import ProjectPathParams, RequestContext
4242
from .exceptions import (
4343
DefaultPricingUnitNotFoundError,
44+
ProjectInDebtCanNotChangeWalletError,
45+
ProjectInDebtCanNotOpenError,
4446
ProjectInvalidRightsError,
4547
ProjectNotFoundError,
4648
ProjectStartsTooManyDynamicNodesError,
4749
ProjectTooManyProjectOpenedError,
48-
ProjectWalletDebtError,
4950
)
5051

5152
_logger = logging.getLogger(__name__)
@@ -76,7 +77,11 @@ async def _wrapper(request: web.Request) -> web.StreamResponse:
7677
except ProjectTooManyProjectOpenedError as exc:
7778
raise web.HTTPConflict(reason=f"{exc}") from exc
7879

79-
except (WalletNotEnoughCreditsError, ProjectWalletDebtError) as exc:
80+
except (
81+
WalletNotEnoughCreditsError,
82+
ProjectInDebtCanNotChangeWalletError,
83+
ProjectInDebtCanNotOpenError,
84+
) as exc:
8085
raise web.HTTPPaymentRequired(reason=f"{exc}") from exc
8186

8287
return _wrapper

services/web/server/src/simcore_service_webserver/projects/_wallets_api.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,16 @@
1414
credit_transactions,
1515
service_runs,
1616
)
17-
from simcore_service_webserver.projects.exceptions import (
18-
ProjectWalletDebtError,
19-
ProjectWalletPendingTransactionError,
20-
)
2117

2218
from ..rabbitmq import get_rabbitmq_rpc_client
2319
from ..users import api as users_api
2420
from ..wallets import _api as wallets_api
2521
from .db import ProjectDBAPI
22+
from .exceptions import (
23+
ProjectInDebtCanNotChangeWalletError,
24+
ProjectInDebtCanNotOpenError,
25+
ProjectWalletPendingTransactionError,
26+
)
2627

2728

2829
async def get_project_wallet(app, project_id: ProjectID):
@@ -44,7 +45,7 @@ async def raise_if_project_is_in_debt(
4445
rpc_client = get_rabbitmq_rpc_client(app)
4546

4647
if current_project_wallet:
47-
# Do not allow to change wallet if the project connected wallet is in DEBT!
48+
# Do not allow to open project if the project connected wallet is in DEBT!
4849
project_wallet_credits_in_debt = (
4950
await credit_transactions.get_project_wallet_total_credits(
5051
rpc_client,
@@ -55,7 +56,7 @@ async def raise_if_project_is_in_debt(
5556
)
5657
)
5758
if project_wallet_credits_in_debt.available_osparc_credits < 0:
58-
raise ProjectWalletDebtError(
59+
raise ProjectInDebtCanNotOpenError(
5960
debt_amount=project_wallet_credits_in_debt.available_osparc_credits
6061
)
6162

@@ -93,7 +94,7 @@ async def connect_wallet_to_project(
9394
)
9495
)
9596
if project_wallet_credits_in_debt.available_osparc_credits < 0:
96-
raise ProjectWalletDebtError(
97+
raise ProjectInDebtCanNotChangeWalletError(
9798
debt_amount=project_wallet_credits_in_debt.available_osparc_credits
9899
)
99100

services/web/server/src/simcore_service_webserver/projects/_wallets_handlers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@
2828
from . import projects_api
2929
from ._common.models import ProjectPathParams, RequestContext
3030
from .exceptions import (
31+
ProjectInDebtCanNotChangeWalletError,
3132
ProjectInvalidRightsError,
3233
ProjectNotFoundError,
33-
ProjectWalletDebtError,
3434
ProjectWalletPendingTransactionError,
3535
)
3636

@@ -49,7 +49,7 @@ async def wrapper(request: web.Request) -> web.StreamResponse:
4949
except WalletNotFoundError as exc:
5050
raise web.HTTPNotFound(reason=f"{exc}") from exc
5151

52-
except ProjectWalletDebtError as exc:
52+
except ProjectInDebtCanNotChangeWalletError as exc:
5353
raise web.HTTPPaymentRequired(reason=f"{exc}") from exc
5454

5555
except (

services/web/server/src/simcore_service_webserver/projects/exceptions.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -238,10 +238,12 @@ class ProjectGroupNotFoundError(BaseProjectError):
238238
msg_template = "Project group not found. {reason}"
239239

240240

241-
class ProjectWalletDebtError(BaseProjectError):
242-
msg_template = (
243-
"Project is in debt {debt_amount} credits. It is forbidden to change wallet."
244-
)
241+
class ProjectInDebtCanNotChangeWalletError(BaseProjectError):
242+
msg_template = "Can not change wallet on a project that is in debt. Project debt {debt_amount} credits. Project wallet {wallet_id}"
243+
244+
245+
class ProjectInDebtCanNotOpenError(BaseProjectError):
246+
msg_template = "Can not open project that is in debt. Project debt {debt_amount} credits. Project wallet {wallet_id}"
245247

246248

247249
class ProjectWalletPendingTransactionError(BaseProjectError):

services/web/server/tests/unit/with_dbs/02/test_projects_states_handlers.py

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from collections.abc import Awaitable, Callable, Iterator
1111
from copy import deepcopy
1212
from datetime import UTC, datetime, timedelta
13+
from decimal import Decimal
1314
from http import HTTPStatus
1415
from typing import Any
1516
from unittest import mock
@@ -26,6 +27,9 @@
2627
DynamicServiceStart,
2728
DynamicServiceStop,
2829
)
30+
from models_library.api_schemas_resource_usage_tracker.credit_transactions import (
31+
WalletTotalCredits,
32+
)
2933
from models_library.api_schemas_webserver.projects_nodes import NodeGet, NodeGetIdle
3034
from models_library.projects import ProjectID
3135
from models_library.projects_access import Owner
@@ -43,6 +47,7 @@
4347
)
4448
from models_library.utils.fastapi_encoders import jsonable_encoder
4549
from pydantic import PositiveInt
50+
from pytest_mock import MockerFixture
4651
from pytest_simcore.helpers.assert_checks import assert_status
4752
from pytest_simcore.helpers.typing_env import EnvVarsDict
4853
from pytest_simcore.helpers.webserver_login import UserInfoDict, log_client_in
@@ -54,6 +59,7 @@
5459
from servicelib.aiohttp import status
5560
from servicelib.common_headers import UNDEFINED_DEFAULT_SIMCORE_USER_AGENT_VALUE
5661
from simcore_postgres_database.models.products import products
62+
from simcore_postgres_database.models.wallets import wallets
5763
from simcore_service_webserver._meta import API_VTAG
5864
from simcore_service_webserver.db.models import UserRole
5965
from simcore_service_webserver.projects.models import ProjectDict
@@ -71,7 +77,11 @@ def app_environment(
7177
) -> EnvVarsDict:
7278
# disable the garbage collector
7379
monkeypatch.setenv("WEBSERVER_GARBAGE_COLLECTOR", "null")
74-
return app_environment | {"WEBSERVER_GARBAGE_COLLECTOR": "null"}
80+
monkeypatch.setenv("WEBSERVER_DEV_FEATURES_ENABLED", "1")
81+
return app_environment | {
82+
"WEBSERVER_GARBAGE_COLLECTOR": "null",
83+
"WEBSERVER_DEV_FEATURES_ENABLED": "1",
84+
}
7585

7686

7787
def assert_replaced(current_project, update_data):
@@ -405,6 +415,69 @@ async def test_open_project(
405415
mocked_notifications_plugin["subscribe"].assert_not_called()
406416

407417

418+
@pytest.fixture
419+
def wallets_clean_db(postgres_db: sa.engine.Engine) -> Iterator[None]:
420+
with postgres_db.connect() as con:
421+
yield
422+
con.execute(wallets.delete())
423+
424+
425+
@pytest.mark.parametrize(
426+
"user_role,expected,return_value_credits",
427+
[
428+
(UserRole.USER, status.HTTP_200_OK, Decimal(0)),
429+
(UserRole.USER, status.HTTP_402_PAYMENT_REQUIRED, Decimal(-100)),
430+
],
431+
)
432+
async def test_open_project__in_debt(
433+
client: TestClient,
434+
logged_user: UserInfoDict,
435+
user_project: ProjectDict,
436+
client_session_id_factory: Callable[[], str],
437+
expected: HTTPStatus,
438+
mocked_dynamic_services_interface: dict[str, mock.Mock],
439+
mock_service_resources: ServiceResourcesDict,
440+
mock_orphaned_services: mock.Mock,
441+
mock_catalog_api: dict[str, mock.Mock],
442+
osparc_product_name: str,
443+
mocked_notifications_plugin: dict[str, mock.Mock],
444+
return_value_credits: Decimal,
445+
mocker: MockerFixture,
446+
wallets_clean_db: None,
447+
):
448+
# create a new wallet
449+
url = client.app.router["create_wallet"].url_for()
450+
resp = await client.post(
451+
f"{url}", json={"name": "My first wallet", "description": "Custom description"}
452+
)
453+
added_wallet, _ = await assert_status(resp, status.HTTP_201_CREATED)
454+
455+
mock_get_project_wallet_total_credits = mocker.patch(
456+
"simcore_service_webserver.projects._wallets_api.credit_transactions.get_project_wallet_total_credits",
457+
spec=True,
458+
return_value=WalletTotalCredits(
459+
wallet_id=added_wallet["walletId"],
460+
available_osparc_credits=return_value_credits,
461+
),
462+
)
463+
464+
# Connect project to a wallet
465+
base_url = client.app.router["connect_wallet_to_project"].url_for(
466+
project_id=user_project["uuid"], wallet_id=f"{added_wallet['walletId']}"
467+
)
468+
resp = await client.put(f"{base_url}")
469+
data, _ = await assert_status(resp, status.HTTP_200_OK)
470+
assert data["walletId"] == added_wallet["walletId"]
471+
472+
# POST /v0/projects/{project_id}:open
473+
assert client.app
474+
url = client.app.router["open_project"].url_for(project_id=user_project["uuid"])
475+
resp = await client.post(f"{url}", json=client_session_id_factory())
476+
await assert_status(resp, expected)
477+
478+
assert mock_get_project_wallet_total_credits.assert_called_once
479+
480+
408481
@pytest.mark.parametrize(
409482
"user_role,expected,save_state",
410483
[

0 commit comments

Comments
 (0)