diff --git a/packages/models-library/src/models_library/api_schemas_webserver/licensed_items.py b/packages/models-library/src/models_library/api_schemas_webserver/licensed_items.py index 5dafd9d5804..aa20fc113f8 100644 --- a/packages/models-library/src/models_library/api_schemas_webserver/licensed_items.py +++ b/packages/models-library/src/models_library/api_schemas_webserver/licensed_items.py @@ -1,7 +1,12 @@ from datetime import datetime -from typing import NamedTuple +from typing import NamedTuple, Self -from models_library.licensed_items import LicensedItemID, LicensedResourceType +from common_library.dict_tools import remap_keys +from models_library.licensed_items import ( + LicensedItemDB, + LicensedItemID, + LicensedResourceType, +) from models_library.resource_tracker import PricingPlanId from pydantic import ConfigDict, PositiveInt @@ -10,12 +15,16 @@ class LicensedItemGet(OutputSchema): licensed_item_id: LicensedItemID + name: str license_key: str | None licensed_resource_type: LicensedResourceType + pricing_plan_id: PricingPlanId + created_at: datetime modified_at: datetime + model_config = ConfigDict( json_schema_extra={ "examples": [ @@ -32,6 +41,29 @@ class LicensedItemGet(OutputSchema): } ) + @classmethod + def from_domain_model(cls, licensed_item_db: LicensedItemDB) -> Self: + return cls.model_validate( + remap_keys( + licensed_item_db.model_dump( + include={ + "licensed_item_id", + "licensed_resource_name", + "licensed_resource_type", + "license_key", + "pricing_plan_id", + "created", + "modified", + } + ), + { + "licensed_resource_name": "name", + "created": "created_at", + "modified": "modified_at", + }, + ) + ) + class LicensedItemGetPage(NamedTuple): items: list[LicensedItemGet] diff --git a/packages/models-library/src/models_library/licensed_items.py b/packages/models-library/src/models_library/licensed_items.py index 79cd4fa87e0..53220d69c8f 100644 --- a/packages/models-library/src/models_library/licensed_items.py +++ b/packages/models-library/src/models_library/licensed_items.py @@ -1,9 +1,9 @@ from datetime import datetime from enum import auto -from typing import TypeAlias +from typing import Any, TypeAlias from uuid import UUID -from pydantic import BaseModel, ConfigDict, Field +from pydantic import BaseModel, ConfigDict from .products import ProductName from .resource_tracker import PricingPlanId @@ -23,23 +23,25 @@ class LicensedResourceType(StrAutoEnum): class LicensedItemDB(BaseModel): licensed_item_id: LicensedItemID - name: str license_key: str | None + + licensed_resource_name: str licensed_resource_type: LicensedResourceType - pricing_plan_id: PricingPlanId - product_name: ProductName - created: datetime = Field( - ..., - description="Timestamp on creation", - ) - modified: datetime = Field( - ..., - description="Timestamp of last modification", - ) - # ---- + licensed_resource_data: dict[str, Any] | None + + pricing_plan_id: PricingPlanId | None + product_name: ProductName | None + + # states + created: datetime + modified: datetime + trashed: datetime | None + model_config = ConfigDict(from_attributes=True) class LicensedItemUpdateDB(BaseModel): - name: str | None = None + licensed_resource_name: str | None = None pricing_plan_id: PricingPlanId | None = None + + trash: bool | None = None diff --git a/packages/postgres-database/src/simcore_postgres_database/migration/versions/4f31760a63ba_add_data_to_licensed_items.py b/packages/postgres-database/src/simcore_postgres_database/migration/versions/4f31760a63ba_add_data_to_licensed_items.py new file mode 100644 index 00000000000..94acfc1df24 --- /dev/null +++ b/packages/postgres-database/src/simcore_postgres_database/migration/versions/4f31760a63ba_add_data_to_licensed_items.py @@ -0,0 +1,96 @@ +"""add data to licensed_items + +Revision ID: 4f31760a63ba +Revises: 1bc517536e0a +Create Date: 2025-01-29 16:51:16.453069+00:00 + +""" +import sqlalchemy as sa +from alembic import op +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = "4f31760a63ba" +down_revision = "1bc517536e0a" +branch_labels = None +depends_on = None + + +def upgrade(): + + with op.batch_alter_table("licensed_items") as batch_op: + batch_op.alter_column( + "name", + new_column_name="licensed_resource_name", + existing_type=sa.String(), + nullable=False, + ) + batch_op.alter_column( + "pricing_plan_id", + existing_type=sa.Integer(), + nullable=True, + ) + batch_op.alter_column( + "product_name", + existing_type=sa.String(), + nullable=True, + ) + + # ### commands auto generated by Alembic - please adjust! ### + op.add_column( + "licensed_items", + sa.Column( + "licensed_resource_data", + postgresql.JSONB(astext_type=sa.Text()), + nullable=True, + ), + ) + op.add_column( + "licensed_items", + sa.Column( + "trashed", + sa.DateTime(timezone=True), + nullable=True, + comment="The date and time when the licensed_item was marked as trashed. Null if the licensed_item has not been trashed [default].", + ), + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("licensed_items", "trashed") + op.drop_column("licensed_items", "licensed_resource_data") + # ### end Alembic commands ### + + # Delete rows with null values in pricing_plan_id and product_name + op.execute( + sa.DDL( + """ + DELETE FROM licensed_items + WHERE pricing_plan_id IS NULL OR product_name IS NULL; + """ + ) + ) + print( + "Warning: Rows with null values in pricing_plan_id or product_name have been deleted." + ) + + with op.batch_alter_table("licensed_items") as batch_op: + + batch_op.alter_column( + "product_name", + existing_type=sa.String(), + nullable=False, + ) + batch_op.alter_column( + "pricing_plan_id", + existing_type=sa.Integer(), + nullable=False, + ) + batch_op.alter_column( + "licensed_resource_name", + new_column_name="name", + existing_type=sa.String(), + nullable=False, + ) diff --git a/packages/postgres-database/src/simcore_postgres_database/models/licensed_items.py b/packages/postgres-database/src/simcore_postgres_database/models/licensed_items.py index a0ea136f4bb..feddae5fdfd 100644 --- a/packages/postgres-database/src/simcore_postgres_database/models/licensed_items.py +++ b/packages/postgres-database/src/simcore_postgres_database/models/licensed_items.py @@ -4,9 +4,14 @@ import enum import sqlalchemy as sa -from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.dialects import postgresql -from ._common import RefActions, column_created_datetime, column_modified_datetime +from ._common import ( + RefActions, + column_created_datetime, + column_modified_datetime, + column_trashed_datetime, +) from .base import metadata @@ -19,21 +24,28 @@ class LicensedResourceType(str, enum.Enum): metadata, sa.Column( "licensed_item_id", - UUID(as_uuid=True), + postgresql.UUID(as_uuid=True), nullable=False, primary_key=True, server_default=sa.text("gen_random_uuid()"), ), sa.Column( - "name", + "licensed_resource_name", sa.String, nullable=False, + doc="Resource name identifier", ), sa.Column( "licensed_resource_type", sa.Enum(LicensedResourceType), nullable=False, - doc="Item type, ex. VIP_MODEL", + doc="Resource type, ex. VIP_MODEL", + ), + sa.Column( + "licensed_resource_data", + postgresql.JSONB, + nullable=True, + doc="Resource metadata. Used for read-only purposes", ), sa.Column( "pricing_plan_id", @@ -44,7 +56,7 @@ class LicensedResourceType(str, enum.Enum): onupdate=RefActions.CASCADE, ondelete=RefActions.RESTRICT, ), - nullable=False, + nullable=True, ), sa.Column( "product_name", @@ -55,15 +67,17 @@ class LicensedResourceType(str, enum.Enum): ondelete=RefActions.CASCADE, name="fk_resource_tracker_license_packages_product_name", ), - nullable=False, - doc="Product name", + nullable=True, + doc="Product name identifier. If None, then the item is not exposed", ), sa.Column( "license_key", sa.String, nullable=True, - doc="Purpose: Acts as a mapping key to the internal license server. Usage: The Sim4Life base applications use this key to check out a seat from the internal license server.", + doc="Purpose: Acts as a mapping key to the internal license server." + "Usage: The Sim4Life base applications use this key to check out a seat from the internal license server.", ), column_created_datetime(timezone=True), column_modified_datetime(timezone=True), + column_trashed_datetime("licensed_item"), ) diff --git a/services/web/server/src/simcore_service_webserver/application_settings.py b/services/web/server/src/simcore_service_webserver/application_settings.py index 2e52bba1a07..fcf8080123c 100644 --- a/services/web/server/src/simcore_service_webserver/application_settings.py +++ b/services/web/server/src/simcore_service_webserver/application_settings.py @@ -411,7 +411,7 @@ def _enable_only_if_dev_features_allowed(cls, v, info: ValidationInfo): return ( None - if info.field_name and is_nullable(cls.model_fields[info.field_name]) + if info.field_name and is_nullable(dict(cls.model_fields)[info.field_name]) else False ) diff --git a/services/web/server/src/simcore_service_webserver/application_settings_utils.py b/services/web/server/src/simcore_service_webserver/application_settings_utils.py index d5180c07192..ca4e27143f2 100644 --- a/services/web/server/src/simcore_service_webserver/application_settings_utils.py +++ b/services/web/server/src/simcore_service_webserver/application_settings_utils.py @@ -202,7 +202,7 @@ def convert_to_environ_vars( # noqa: C901, PLR0915, PLR0912 def _set_if_disabled(field_name, section): # Assumes that by default is enabled enabled = section.get("enabled", True) - field = ApplicationSettings.model_fields[field_name] + field = dict(ApplicationSettings.model_fields)[field_name] if not enabled: envs[field_name] = "null" if is_nullable(field) else "0" elif get_type(field) == bool: diff --git a/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_repository.py b/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_repository.py index 57861698161..45e67629a09 100644 --- a/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_repository.py +++ b/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_repository.py @@ -5,7 +5,7 @@ """ import logging -from typing import cast +from typing import Literal, cast from aiohttp import web from models_library.licensed_items import ( @@ -20,6 +20,7 @@ from pydantic import NonNegativeInt from simcore_postgres_database.models.licensed_items import licensed_items from simcore_postgres_database.utils_repos import ( + get_columns_from_db_model, pass_or_acquire_connection, transaction_context, ) @@ -33,34 +34,23 @@ _logger = logging.getLogger(__name__) -_SELECTION_ARGS = ( - licensed_items.c.licensed_item_id, - licensed_items.c.name, - licensed_items.c.license_key, - licensed_items.c.licensed_resource_type, - licensed_items.c.pricing_plan_id, - licensed_items.c.product_name, - licensed_items.c.created, - licensed_items.c.modified, -) - -assert set(LicensedItemDB.model_fields) == {c.name for c in _SELECTION_ARGS} # nosec +_SELECTION_ARGS = get_columns_from_db_model(licensed_items, LicensedItemDB) async def create( app: web.Application, connection: AsyncConnection | None = None, *, - product_name: ProductName, - name: str, + licensed_resource_name: str, licensed_resource_type: LicensedResourceType, - pricing_plan_id: PricingPlanId, + product_name: ProductName | None, + pricing_plan_id: PricingPlanId | None, ) -> LicensedItemDB: async with transaction_context(get_asyncpg_engine(app), connection) as conn: - result = await conn.stream( + result = await conn.execute( licensed_items.insert() .values( - name=name, + licensed_resource_name=licensed_resource_name, licensed_resource_type=licensed_resource_type, pricing_plan_id=pricing_plan_id, product_name=product_name, @@ -69,7 +59,7 @@ async def create( ) .returning(*_SELECTION_ARGS) ) - row = await result.first() + row = result.one() return LicensedItemDB.model_validate(row) @@ -81,13 +71,34 @@ async def list_( offset: NonNegativeInt, limit: NonNegativeInt, order_by: OrderBy, + # filters + trashed: Literal["exclude", "only", "include"] = "exclude", + inactive: Literal["exclude", "only", "include"] = "exclude", ) -> tuple[int, list[LicensedItemDB]]: + base_query = ( select(*_SELECTION_ARGS) .select_from(licensed_items) .where(licensed_items.c.product_name == product_name) ) + # Apply trashed filter + if trashed == "exclude": + base_query = base_query.where(licensed_items.c.trashed.is_(None)) + elif trashed == "only": + base_query = base_query.where(licensed_items.c.trashed.is_not(None)) + + if inactive == "only": + base_query = base_query.where( + licensed_items.c.product_name.is_(None) + | licensed_items.c.licensed_item_id.is_(None) + ) + elif inactive == "exclude": + base_query = base_query.where( + licensed_items.c.product_name.is_not(None) + & licensed_items.c.licensed_item_id.is_not(None) + ) + # Select total count from base_query subquery = base_query.subquery() count_query = select(func.count()).select_from(subquery) @@ -147,11 +158,16 @@ async def update( # NOTE: at least 'touch' if updated_values is empty _updates = { **updates.model_dump(exclude_unset=True), - "modified": func.now(), + licensed_items.c.modified.name: func.now(), } + # trashing + assert "trash" in dict(LicensedItemUpdateDB.model_fields) # nosec + if trash := _updates.pop("trash", None): + _updates[licensed_items.c.trashed.name] = func.now() if trash else None + async with transaction_context(get_asyncpg_engine(app), connection) as conn: - result = await conn.stream( + result = await conn.execute( licensed_items.update() .values(**_updates) .where( @@ -160,7 +176,7 @@ async def update( ) .returning(*_SELECTION_ARGS) ) - row = await result.first() + row = result.one_or_none() if row is None: raise LicensedItemNotFoundError(licensed_item_id=licensed_item_id) return LicensedItemDB.model_validate(row) diff --git a/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_service.py b/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_service.py index bf48a89ca5c..ec748259385 100644 --- a/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_service.py +++ b/services/web/server/src/simcore_service_webserver/licenses/_licensed_items_service.py @@ -42,15 +42,7 @@ async def get_licensed_item( licensed_item_db = await _licensed_items_repository.get( app, licensed_item_id=licensed_item_id, product_name=product_name ) - return LicensedItemGet( - licensed_item_id=licensed_item_db.licensed_item_id, - name=licensed_item_db.name, - license_key=licensed_item_db.license_key, - licensed_resource_type=licensed_item_db.licensed_resource_type, - pricing_plan_id=licensed_item_db.pricing_plan_id, - created_at=licensed_item_db.created, - modified_at=licensed_item_db.modified, - ) + return LicensedItemGet.from_domain_model(licensed_item_db) async def list_licensed_items( @@ -61,21 +53,19 @@ async def list_licensed_items( limit: int, order_by: OrderBy, ) -> LicensedItemGetPage: - total_count, licensed_item_db_list = await _licensed_items_repository.list_( - app, product_name=product_name, offset=offset, limit=limit, order_by=order_by + total_count, items = await _licensed_items_repository.list_( + app, + product_name=product_name, + offset=offset, + limit=limit, + order_by=order_by, + trashed="exclude", + inactive="exclude", ) return LicensedItemGetPage( items=[ - LicensedItemGet( - licensed_item_id=licensed_item_db.licensed_item_id, - name=licensed_item_db.name, - license_key=licensed_item_db.license_key, - licensed_resource_type=licensed_item_db.licensed_resource_type, - pricing_plan_id=licensed_item_db.pricing_plan_id, - created_at=licensed_item_db.created, - modified_at=licensed_item_db.modified, - ) - for licensed_item_db in licensed_item_db_list + LicensedItemGet.from_domain_model(licensed_item_db) + for licensed_item_db in items ], total=total_count, ) diff --git a/services/web/server/src/simcore_service_webserver/licenses/_rpc.py b/services/web/server/src/simcore_service_webserver/licenses/_rpc.py index 85cc3a99642..f63c4e916cf 100644 --- a/services/web/server/src/simcore_service_webserver/licenses/_rpc.py +++ b/services/web/server/src/simcore_service_webserver/licenses/_rpc.py @@ -43,7 +43,7 @@ async def get_licensed_items( product_name=product_name, offset=offset, limit=limit, - order_by=OrderBy(field=IDStr("name")), + order_by=OrderBy(field=IDStr("licensed_resource_name")), ) ) return licensed_item_get_page diff --git a/services/web/server/tests/unit/isolated/test_users_models.py b/services/web/server/tests/unit/isolated/test_users_models.py index c7cfeba336e..e568d0d2ddd 100644 --- a/services/web/server/tests/unit/isolated/test_users_models.py +++ b/services/web/server/tests/unit/isolated/test_users_models.py @@ -89,7 +89,7 @@ def test_auto_compute_gravatar__deprecated(fake_profile_get: MyProfileGet): assert ( "gravatar_id" not in data - ), f"{MyProfileGet.model_fields['gravatar_id'].deprecated=}" + ), f"{dict(MyProfileGet.model_fields)['gravatar_id'].deprecated=}" assert data["id"] == profile.id assert data["first_name"] == profile.first_name assert data["last_name"] == profile.last_name diff --git a/services/web/server/tests/unit/with_dbs/04/licenses/test_licensed_items_repository.py b/services/web/server/tests/unit/with_dbs/04/licenses/test_licensed_items_repository.py index dfe04e2e0d3..7e3a0f4018e 100644 --- a/services/web/server/tests/unit/with_dbs/04/licenses/test_licensed_items_repository.py +++ b/services/web/server/tests/unit/with_dbs/04/licenses/test_licensed_items_repository.py @@ -3,92 +3,173 @@ # pylint: disable=unused-variable # pylint: disable=too-many-arguments # pylint: disable=too-many-statements -from http import HTTPStatus +import arrow import pytest from aiohttp.test_utils import TestClient -from models_library.licensed_items import ( - LicensedItemDB, - LicensedItemUpdateDB, - LicensedResourceType, -) +from models_library.licensed_items import LicensedItemUpdateDB, LicensedResourceType from models_library.rest_ordering import OrderBy from pytest_simcore.helpers.webserver_login import UserInfoDict -from servicelib.aiohttp import status from simcore_service_webserver.db.models import UserRole from simcore_service_webserver.licenses import _licensed_items_repository from simcore_service_webserver.licenses.errors import LicensedItemNotFoundError from simcore_service_webserver.projects.models import ProjectDict -@pytest.mark.parametrize("user_role,expected", [(UserRole.USER, status.HTTP_200_OK)]) +@pytest.fixture +def user_role() -> UserRole: + return UserRole.USER + + async def test_licensed_items_db_crud( client: TestClient, logged_user: UserInfoDict, user_project: ProjectDict, osparc_product_name: str, - expected: HTTPStatus, pricing_plan_id: int, ): assert client.app - - output: tuple[int, list[LicensedItemDB]] = await _licensed_items_repository.list_( + total_count, items = await _licensed_items_repository.list_( client.app, product_name=osparc_product_name, offset=0, limit=10, order_by=OrderBy(field="modified"), ) - assert output[0] == 0 + assert total_count == 0 + assert not items - licensed_item_db = await _licensed_items_repository.create( + got = await _licensed_items_repository.create( client.app, product_name=osparc_product_name, - name="Model A", + licensed_resource_name="Model A", licensed_resource_type=LicensedResourceType.VIP_MODEL, pricing_plan_id=pricing_plan_id, ) - _licensed_item_id = licensed_item_db.licensed_item_id + licensed_item_id = got.licensed_item_id - output: tuple[int, list[LicensedItemDB]] = await _licensed_items_repository.list_( + total_count, items = await _licensed_items_repository.list_( client.app, product_name=osparc_product_name, offset=0, limit=10, order_by=OrderBy(field="modified"), ) - assert output[0] == 1 + assert total_count == 1 + assert items[0].licensed_item_id == licensed_item_id - licensed_item_db = await _licensed_items_repository.get( + got = await _licensed_items_repository.get( client.app, - licensed_item_id=_licensed_item_id, + licensed_item_id=licensed_item_id, product_name=osparc_product_name, ) - assert licensed_item_db.name == "Model A" + assert got.licensed_resource_name == "Model A" await _licensed_items_repository.update( client.app, - licensed_item_id=_licensed_item_id, + licensed_item_id=licensed_item_id, product_name=osparc_product_name, - updates=LicensedItemUpdateDB(name="Model B"), + updates=LicensedItemUpdateDB(licensed_resource_name="Model B"), ) - licensed_item_db = await _licensed_items_repository.get( + got = await _licensed_items_repository.get( client.app, - licensed_item_id=_licensed_item_id, + licensed_item_id=licensed_item_id, product_name=osparc_product_name, ) - assert licensed_item_db.name == "Model B" + assert got.licensed_resource_name == "Model B" - licensed_item_db = await _licensed_items_repository.delete( + got = await _licensed_items_repository.delete( client.app, - licensed_item_id=_licensed_item_id, + licensed_item_id=licensed_item_id, product_name=osparc_product_name, ) with pytest.raises(LicensedItemNotFoundError): await _licensed_items_repository.get( client.app, - licensed_item_id=_licensed_item_id, + licensed_item_id=licensed_item_id, product_name=osparc_product_name, ) + + +async def test_licensed_items_db_trash( + client: TestClient, + logged_user: UserInfoDict, + user_project: ProjectDict, + osparc_product_name: str, + pricing_plan_id: int, +): + assert client.app + + # Create two licensed items + licensed_item_ids = [] + for name in ["Model A", "Model B"]: + licensed_item_db = await _licensed_items_repository.create( + client.app, + product_name=osparc_product_name, + licensed_resource_name=name, + licensed_resource_type=LicensedResourceType.VIP_MODEL, + pricing_plan_id=pricing_plan_id, + ) + licensed_item_ids.append(licensed_item_db.licensed_item_id) + + # Trash one licensed item + trashing_at = arrow.now().datetime + trashed_item = await _licensed_items_repository.update( + client.app, + licensed_item_id=licensed_item_ids[0], + product_name=osparc_product_name, + updates=LicensedItemUpdateDB(trash=True), + ) + + assert trashed_item.licensed_item_id == licensed_item_ids[0] + assert trashed_item.trashed + assert trashing_at < trashed_item.trashed + assert trashed_item.trashed < arrow.now().datetime + + # List with filter_trashed include + total_count, items = await _licensed_items_repository.list_( + client.app, + product_name=osparc_product_name, + offset=0, + limit=10, + order_by=OrderBy(field="modified"), + trashed="include", + ) + assert total_count == 2 + assert {i.licensed_item_id for i in items} == set(licensed_item_ids) + + # List with filter_trashed exclude + total_count, items = await _licensed_items_repository.list_( + client.app, + product_name=osparc_product_name, + offset=0, + limit=10, + order_by=OrderBy(field="modified"), + trashed="exclude", + ) + assert total_count == 1 + assert items[0].licensed_item_id == licensed_item_ids[1] + assert items[0].trashed is None + + # List with filter_trashed all + total_count, items = await _licensed_items_repository.list_( + client.app, + product_name=osparc_product_name, + offset=0, + limit=10, + order_by=OrderBy(field="modified"), + trashed="only", + ) + assert total_count == 1 + assert items[0].licensed_item_id == trashed_item.licensed_item_id + assert items[0].trashed + + # Get the trashed licensed item + got = await _licensed_items_repository.get( + client.app, + licensed_item_id=trashed_item.licensed_item_id, + product_name=osparc_product_name, + ) + assert got == trashed_item diff --git a/services/web/server/tests/unit/with_dbs/04/licenses/test_licensed_items_rest.py b/services/web/server/tests/unit/with_dbs/04/licenses/test_licensed_items_rest.py index 67c36f2581b..491e340bd2f 100644 --- a/services/web/server/tests/unit/with_dbs/04/licenses/test_licensed_items_rest.py +++ b/services/web/server/tests/unit/with_dbs/04/licenses/test_licensed_items_rest.py @@ -42,7 +42,7 @@ async def test_licensed_items_listing( licensed_item_db = await _licensed_items_repository.create( client.app, product_name=osparc_product_name, - name="Model A", + licensed_resource_name="Model A", licensed_resource_type=LicensedResourceType.VIP_MODEL, pricing_plan_id=pricing_plan_id, ) @@ -109,7 +109,7 @@ async def test_licensed_items_purchase( licensed_item_db = await _licensed_items_repository.create( client.app, product_name=osparc_product_name, - name="Model A", + licensed_resource_name="Model A", licensed_resource_type=LicensedResourceType.VIP_MODEL, pricing_plan_id=pricing_plan_id, ) diff --git a/services/web/server/tests/unit/with_dbs/04/licenses/test_licenses_rpc.py b/services/web/server/tests/unit/with_dbs/04/licenses/test_licenses_rpc.py index 836a7fe05e6..acc32cc4325 100644 --- a/services/web/server/tests/unit/with_dbs/04/licenses/test_licenses_rpc.py +++ b/services/web/server/tests/unit/with_dbs/04/licenses/test_licenses_rpc.py @@ -135,7 +135,7 @@ async def test_license_checkout_workflow( license_item_db = await _licensed_items_repository.create( client.app, product_name=osparc_product_name, - name="Model A", + licensed_resource_name="Model A", licensed_resource_type=LicensedResourceType.VIP_MODEL, pricing_plan_id=pricing_plan_id, ) diff --git a/services/web/server/tests/unit/with_dbs/04/wallets/payments/test_payments.py b/services/web/server/tests/unit/with_dbs/04/wallets/payments/test_payments.py index 719eb7e6dc8..4b028a61dd8 100644 --- a/services/web/server/tests/unit/with_dbs/04/wallets/payments/test_payments.py +++ b/services/web/server/tests/unit/with_dbs/04/wallets/payments/test_payments.py @@ -362,7 +362,7 @@ async def test_payment_not_found( def test_payment_transaction_state_and_literals_are_in_sync(): - state_literals = PaymentTransaction.model_fields["state"].annotation + state_literals = dict(PaymentTransaction.model_fields)["state"].annotation assert ( TypeAdapter(list[state_literals]).validate_python( [f"{s}" for s in PaymentTransactionState]