Skip to content

Commit 1db3b7a

Browse files
committed
extend coverage
1 parent 9eadcc7 commit 1db3b7a

File tree

4 files changed

+111
-41
lines changed

4 files changed

+111
-41
lines changed

services/web/server/src/simcore_service_webserver/products/_repository.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
transaction_context,
2323
)
2424
from simcore_service_webserver.constants import FRONTEND_APPS_AVAILABLE
25-
from simcore_service_webserver.products.errors import MissingStripeConfigError
2625
from sqlalchemy.engine import Row
2726
from sqlalchemy.ext.asyncio import AsyncConnection
2827

@@ -157,17 +156,15 @@ async def get_product_latest_price_info_or_none(
157156
conn, product_name=product_name
158157
)
159158

160-
async def get_product_stripe_info(
159+
async def get_product_stripe_info_or_none(
161160
self, product_name: str, connection: AsyncConnection | None = None
162-
) -> ProductStripeInfoGet:
161+
) -> ProductStripeInfoGet | None:
163162
async with pass_or_acquire_connection(self.engine, connection) as conn:
164163
latest_stripe_info = await get_product_latest_stripe_info_or_none(
165164
conn, product_name=product_name
166165
)
167166
if latest_stripe_info is None:
168-
exc = MissingStripeConfigError(product_name=product_name)
169-
exc.add_note("Stripe config missing in database")
170-
raise exc
167+
return None
171168

172169
stripe_price_id, stripe_tax_rate_id = latest_stripe_info
173170
return ProductStripeInfoGet(

services/web/server/src/simcore_service_webserver/products/_service.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,10 @@ async def get_product_stripe_info(
112112
) -> ProductStripeInfoGet:
113113
repo = ProductRepository.create_from_app(app)
114114

115-
product_stripe_info = await repo.get_product_stripe_info(product_name)
115+
product_stripe_info = await repo.get_product_stripe_info_or_none(product_name)
116116
if (
117-
"missing!!" in product_stripe_info.stripe_price_id
117+
product_stripe_info is None
118+
or "missing!!" in product_stripe_info.stripe_price_id
118119
or "missing!!" in product_stripe_info.stripe_tax_rate_id
119120
):
120121
exc = MissingStripeConfigError(

services/web/server/tests/unit/with_dbs/04/products/test_products_repository.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
from simcore_service_webserver.products._web_middlewares import (
3636
_get_default_product_name,
3737
)
38-
from simcore_service_webserver.products.errors import MissingStripeConfigError
3938
from sqlalchemy.ext.asyncio import AsyncEngine
4039

4140

@@ -233,12 +232,12 @@ async def test_product_repository_get_product_stripe_info(
233232
product_repository: ProductRepository,
234233
):
235234
product_name = "tis"
236-
stripe_info = await product_repository.get_product_stripe_info(product_name)
235+
stripe_info = await product_repository.get_product_stripe_info_or_none(product_name)
237236
assert isinstance(stripe_info, ProductStripeInfoGet)
238237

239238
product_name = "s4l"
240-
with pytest.raises(MissingStripeConfigError, match=product_name):
241-
stripe_info = await product_repository.get_product_stripe_info(product_name)
239+
stripe_info = await product_repository.get_product_stripe_info_or_none(product_name)
240+
assert stripe_info is None
242241

243242

244243
async def test_product_repository_get_template_content(

services/web/server/tests/unit/with_dbs/04/products/test_products_service.py

Lines changed: 102 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,22 @@
44
# pylint: disable=too-many-arguments
55

66

7+
from decimal import Decimal
8+
79
import pytest
810
from aiohttp import web
911
from aiohttp.test_utils import TestServer
10-
from models_library.groups import GroupID
11-
from models_library.products import ProductName
12-
from pydantic import ValidationError
12+
from models_library.products import ProductName, ProductStripeInfoGet
13+
from pydantic import TypeAdapter, ValidationError
14+
from pytest_mock import MockerFixture
1315
from servicelib.exceptions import InvalidConfig
1416
from simcore_postgres_database.utils_products_prices import ProductPriceInfo
1517
from simcore_service_webserver.products import _service, products_service
1618
from simcore_service_webserver.products._repository import ProductRepository
1719
from simcore_service_webserver.products.errors import (
20+
BelowMinimumPaymentError,
1821
MissingStripeConfigError,
22+
ProductNotFoundError,
1923
ProductPriceNotDefinedError,
2024
ProductTemplateNotFoundError,
2125
)
@@ -41,7 +45,11 @@ async def test_load_products_validation_error(app: web.Application, mocker):
4145
mock_repo = mocker.patch(
4246
"simcore_service_webserver.products._service.ProductRepository.create_from_app"
4347
)
44-
mock_repo.return_value.list_products.side_effect = ValidationError("Invalid data")
48+
49+
try:
50+
TypeAdapter(int).validate_python("not-an-int")
51+
except ValidationError as validation_error:
52+
mock_repo.return_value.list_products.side_effect = validation_error
4553

4654
with pytest.raises(InvalidConfig, match="Invalid product configuration in db"):
4755
await _service.load_products(app)
@@ -53,7 +61,6 @@ async def test_get_default_product_name(app: web.Application):
5361

5462

5563
async def test_get_product(app: web.Application, default_product_name: ProductName):
56-
5764
product = products_service.get_product(app, product_name=default_product_name)
5865
assert product.name == default_product_name
5966

@@ -62,51 +69,115 @@ async def test_get_product(app: web.Application, default_product_name: ProductNa
6269
assert products[0] == product
6370

6471

65-
async def test_get_product_ui(app: web.Application, default_product_name: ProductName):
66-
# this feature is currently setup from adminer by an operator
72+
async def test_products_on_uninitialized_app(default_product_name: ProductName):
73+
uninit_app = web.Application()
74+
with pytest.raises(ProductNotFoundError):
75+
_service.get_product(uninit_app, default_product_name)
76+
77+
78+
async def test_list_products_names(app: web.Application):
79+
product_names = await products_service.list_products_names(app)
80+
assert isinstance(product_names, list)
81+
assert all(isinstance(name, ProductName) for name in product_names)
82+
83+
84+
async def test_get_credit_price_info(
85+
app: web.Application, default_product_name: ProductName
86+
):
87+
price_info = await _service.get_credit_price_info(
88+
app, product_name=default_product_name
89+
)
90+
assert price_info is None or isinstance(price_info, ProductPriceInfo)
91+
6792

93+
async def test_get_product_ui(app: web.Application, default_product_name: ProductName):
6894
repo = ProductRepository.create_from_app(app)
6995
ui = await products_service.get_product_ui(repo, product_name=default_product_name)
7096
assert ui == {}, "Expected empty by default"
7197

98+
with pytest.raises(ProductNotFoundError):
99+
await products_service.get_product_ui(repo, product_name="undefined")
100+
101+
102+
async def test_get_credit_amount(
103+
app: web.Application, default_product_name: ProductName, mocker: MockerFixture
104+
):
105+
# Test when ProductPriceNotDefinedError is raised
106+
with pytest.raises(ProductPriceNotDefinedError):
107+
await products_service.get_credit_amount(
108+
app, dollar_amount=1, product_name=default_product_name
109+
)
110+
111+
112+
async def test_get_credit_amount_with_repo_faking_data(
113+
default_product_name: ProductName, mocker: MockerFixture
114+
):
115+
# NO need of database since repo is mocked
116+
app = web.Application()
117+
118+
# Mock the repository to return a valid price info
119+
mock_repo = mocker.patch(
120+
"simcore_service_webserver.products._service.ProductRepository.create_from_app"
121+
)
122+
123+
async def _get_product_latest_price_info_or_none(*args, **kwargs):
124+
return ProductPriceInfo(
125+
usd_per_credit=Decimal("10.0"), min_payment_amount_usd=Decimal("5.0")
126+
)
127+
128+
mock_repo.return_value.get_product_latest_price_info_or_none.side_effect = (
129+
_get_product_latest_price_info_or_none
130+
)
131+
132+
# Test when BelowMinimumPaymentError is raised
133+
with pytest.raises(BelowMinimumPaymentError):
134+
await products_service.get_credit_amount(
135+
app, dollar_amount=Decimal("3.0"), product_name=default_product_name
136+
)
137+
138+
# Test when CreditResultGet is returned successfully
139+
credit_result = await products_service.get_credit_amount(
140+
app, dollar_amount=Decimal("10.0"), product_name=default_product_name
141+
)
142+
assert credit_result.credit_amount == Decimal("1.0")
143+
assert credit_result.product_name == default_product_name
144+
72145

73146
async def test_get_product_stripe_info(
74147
app: web.Application, default_product_name: ProductName
75148
):
76-
# this feature is currently setup from adminer by an operator
77-
78-
# default is not configured
149+
# database has no info
79150
with pytest.raises(MissingStripeConfigError, match=default_product_name):
80151
await products_service.get_product_stripe_info(
81152
app, product_name=default_product_name
82153
)
83154

84155

85-
async def test_get_credit_amount(
86-
app: web.Application, default_product_name: ProductName
156+
async def test_get_product_stripe_info_with_repo_faking_data(
157+
default_product_name: ProductName, mocker: MockerFixture
87158
):
88-
# this feature is currently setup from adminer by an operator
159+
# NO need of database since repo is mocked
160+
app = web.Application()
89161

90-
# default is not configured
91-
with pytest.raises(ProductPriceNotDefinedError):
92-
await products_service.get_credit_amount(
93-
app, dollar_amount=1, product_name=default_product_name
94-
)
162+
# Mock the repository to return a valid stripe info
163+
mock_repo = mocker.patch(
164+
"simcore_service_webserver.products._service.ProductRepository.create_from_app"
165+
)
95166

167+
# Test when stripe info is returned successfully
168+
expected_stripe_info = ProductStripeInfoGet(
169+
stripe_price_id="price_id", stripe_tax_rate_id="tax_id"
170+
)
96171

97-
async def test_list_products_names(app: web.Application):
98-
product_names = await products_service.list_products_names(app)
99-
assert isinstance(product_names, list)
100-
assert all(isinstance(name, ProductName) for name in product_names)
172+
async def _mock(*args, **kw):
173+
return expected_stripe_info
101174

175+
mock_repo.return_value.get_product_stripe_info_or_none.side_effect = _mock
102176

103-
async def test_get_credit_price_info(
104-
app: web.Application, default_product_name: ProductName
105-
):
106-
price_info = await _service.get_credit_price_info(
177+
stripe_info = await products_service.get_product_stripe_info(
107178
app, product_name=default_product_name
108179
)
109-
assert price_info is None or isinstance(price_info, ProductPriceInfo)
180+
assert stripe_info == expected_stripe_info
110181

111182

112183
async def test_get_template_content(app: web.Application):
@@ -118,5 +189,7 @@ async def test_get_template_content(app: web.Application):
118189
async def test_auto_create_products_groups(app: web.Application):
119190
groups = await _service.auto_create_products_groups(app)
120191
assert isinstance(groups, dict)
121-
assert all(isinstance(name, ProductName) for name in groups.keys())
122-
assert all(isinstance(group_id, GroupID) for group_id in groups.values())
192+
193+
assert all(
194+
group_id is not None for group_id in groups.values()
195+
), f"Invalid {groups}"

0 commit comments

Comments
 (0)