Skip to content

Commit e63caf2

Browse files
introduce checkout release contract
1 parent 034063e commit e63caf2

File tree

4 files changed

+226
-3
lines changed

4 files changed

+226
-3
lines changed
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
{
2+
"consumer": {
3+
"name": "Sim4Life"
4+
},
5+
"provider": {
6+
"name": "OsparcApiServerCheckoutRelease"
7+
},
8+
"interactions": [
9+
{
10+
"description": "Checkout one license",
11+
"request": {
12+
"method": "POST",
13+
"path": "/v0/wallets/35/licensed-items/99580844-77fa-41bb-ad70-02dfaf1e3965/checkout",
14+
"headers": {
15+
"Accept": "application/json",
16+
"Content-Type": "application/json"
17+
},
18+
"body": {
19+
"number_of_seats": 1,
20+
"service_run_id": "1740149365_21a9352a-1d46-41f9-9a9b-42ac888f5afb"
21+
}
22+
},
23+
"response": {
24+
"status": 200,
25+
"headers": {
26+
"Content-Length": "294",
27+
"Content-Type": "application/json",
28+
"Server": "uvicorn"
29+
},
30+
"body": {
31+
"key": "MODEL_IX_HEAD",
32+
"licensed_item_checkout_id": "25262183-392c-4268-9311-3c4256c46012",
33+
"licensed_item_id": "99580844-77fa-41bb-ad70-02dfaf1e3965",
34+
"num_of_seats": 1,
35+
"product_name": "s4l",
36+
"started_at": "2025-02-21T15:04:47.673828Z",
37+
"stopped_at": null,
38+
"user_id": 425,
39+
"version": "1.0.0",
40+
"wallet_id": 35
41+
}
42+
}
43+
},
44+
{
45+
"description": "Release item",
46+
"request": {
47+
"method": "POST",
48+
"path": "/v0/licensed-items/99580844-77fa-41bb-ad70-02dfaf1e3965/checked-out-items/25262183-392c-4268-9311-3c4256c46012/release",
49+
"headers": {
50+
"Accept": "application/json",
51+
"Content-Type": "application/json"
52+
}
53+
},
54+
"response": {
55+
"status": 200,
56+
"headers": {
57+
"Content-Length": "319",
58+
"Content-Type": "application/json",
59+
"Server": "uvicorn"
60+
},
61+
"body": {
62+
"key": "MODEL_IX_HEAD",
63+
"licensed_item_checkout_id": "25262183-392c-4268-9311-3c4256c46012",
64+
"licensed_item_id": "99580844-77fa-41bb-ad70-02dfaf1e3965",
65+
"num_of_seats": 1,
66+
"product_name": "s4l",
67+
"started_at": "2025-02-21T15:04:47.673828Z",
68+
"stopped_at": "2025-02-21T15:04:47.901169Z",
69+
"user_id": 425,
70+
"version": "1.0.0",
71+
"wallet_id": 35
72+
}
73+
}
74+
}
75+
],
76+
"metadata": {
77+
"pactSpecification": {
78+
"version": "3.0.0"
79+
}
80+
}
81+
}

services/api-server/tests/unit/pact_broker/pacts/05_licensed_items.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
{
22
"consumer": {
3-
"name": "XOsparcApiClient"
3+
"name": "Sim4Life"
44
},
55
"provider": {
6-
"name": "OsparcApiProvider"
6+
"name": "OsparcApiServerLicensedItems"
77
},
88
"interactions": [
99
{
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# pylint: disable=redefined-outer-name
2+
# pylint: disable=unused-argument
3+
# pylint: disable=unused-variable
4+
# pylint: disable=too-many-arguments
5+
6+
7+
import pytest
8+
from fastapi import FastAPI
9+
from models_library.api_schemas_webserver.licensed_items_checkouts import (
10+
LicensedItemCheckoutRpcGet,
11+
)
12+
from pact.v3 import Verifier
13+
from pytest_mock import MockerFixture
14+
from simcore_service_api_server._meta import API_VERSION
15+
from simcore_service_api_server.api.dependencies.authentication import (
16+
Identity,
17+
)
18+
from simcore_service_api_server.api.dependencies.resource_usage_tracker_rpc import (
19+
get_resource_usage_tracker_client,
20+
)
21+
from simcore_service_api_server.api.dependencies.webserver_rpc import (
22+
get_wb_api_rpc_client,
23+
)
24+
from simcore_service_api_server.services_rpc.resource_usage_tracker import (
25+
ResourceUsageTrackerClient,
26+
)
27+
from simcore_service_api_server.services_rpc.wb_api_server import WbApiRpcClient
28+
29+
30+
def mock_get_current_identity() -> Identity:
31+
return Identity(user_id=1, product_name="osparc", email="[email protected]")
32+
33+
34+
# Fake response based on values from 01_checkout_release.json
35+
EXPECTED_CHECKOUT = LicensedItemCheckoutRpcGet.model_validate(
36+
{
37+
"key": "MODEL_IX_HEAD",
38+
"licensed_item_checkout_id": "25262183-392c-4268-9311-3c4256c46012",
39+
"licensed_item_id": "99580844-77fa-41bb-ad70-02dfaf1e3965",
40+
"num_of_seats": 1,
41+
"product_name": "s4l",
42+
"started_at": "2025-02-21T15:04:47.673828Z",
43+
"stopped_at": None,
44+
"user_id": 425,
45+
"version": "1.0.0",
46+
"wallet_id": 35,
47+
}
48+
)
49+
assert EXPECTED_CHECKOUT.stopped_at is None
50+
51+
52+
EXPECTED_RELEASE = LicensedItemCheckoutRpcGet.model_validate(
53+
{
54+
"key": "MODEL_IX_HEAD",
55+
"licensed_item_checkout_id": "25262183-392c-4268-9311-3c4256c46012",
56+
"licensed_item_id": "99580844-77fa-41bb-ad70-02dfaf1e3965",
57+
"num_of_seats": 1,
58+
"product_name": "s4l",
59+
"started_at": "2025-02-21T15:04:47.673828Z",
60+
"stopped_at": "2025-02-21T15:04:47.901169Z",
61+
"user_id": 425,
62+
"version": "1.0.0",
63+
"wallet_id": 35,
64+
}
65+
)
66+
assert EXPECTED_RELEASE.stopped_at is not None
67+
68+
69+
class DummyRpcClient:
70+
pass
71+
72+
73+
@pytest.fixture
74+
async def mock_wb_api_server_rpc(app: FastAPI, mocker: MockerFixture) -> MockerFixture:
75+
76+
app.dependency_overrides[get_wb_api_rpc_client] = lambda: WbApiRpcClient(
77+
_client=DummyRpcClient()
78+
)
79+
80+
mocker.patch(
81+
"simcore_service_api_server.services_rpc.wb_api_server._checkout_licensed_item_for_wallet",
82+
return_value=EXPECTED_CHECKOUT,
83+
)
84+
85+
mocker.patch(
86+
"simcore_service_api_server.services_rpc.wb_api_server._release_licensed_item_for_wallet",
87+
return_value=EXPECTED_RELEASE,
88+
)
89+
90+
return mocker
91+
92+
93+
@pytest.fixture
94+
async def mock_rut_server_rpc(app: FastAPI, mocker: MockerFixture) -> MockerFixture:
95+
96+
app.dependency_overrides[get_resource_usage_tracker_client] = (
97+
lambda: ResourceUsageTrackerClient(_client=DummyRpcClient())
98+
)
99+
100+
mocker.patch(
101+
"simcore_service_api_server.services_rpc.resource_usage_tracker._get_licensed_item_checkout",
102+
return_value=EXPECTED_CHECKOUT,
103+
)
104+
105+
return mocker
106+
107+
108+
# @pytest.mark.skipif(
109+
# not os.getenv("PACT_BROKER_URL"),
110+
# reason="This test runs only if PACT_BROKER_URL is provided",
111+
# )
112+
def test_provider_against_pact(
113+
pact_broker_credentials: tuple[str, str, str],
114+
mock_wb_api_server_rpc: MockerFixture,
115+
mock_rut_server_rpc: MockerFixture,
116+
run_test_server: str,
117+
) -> None:
118+
"""
119+
Use the Pact Verifier to check the real provider
120+
against the generated contract.
121+
"""
122+
broker_url, broker_username, broker_password = pact_broker_credentials
123+
124+
broker_builder = (
125+
Verifier("OsparcApiServerCheckoutRelease")
126+
.add_transport(url=run_test_server)
127+
.broker_source(
128+
broker_url,
129+
username=broker_username,
130+
password=broker_password,
131+
selector=True,
132+
)
133+
)
134+
135+
# NOTE: If you want to filter/test against specific contract use tags
136+
verifier = broker_builder.consumer_tags(
137+
"checkout_release" # <-- Here you define which pact to verify
138+
).build()
139+
140+
# Set API version and run verification
141+
verifier.set_publish_options(version=API_VERSION, tags=None, branch=None)
142+
verifier.verify()

services/api-server/tests/unit/pact_broker/test_pact_licensed_items.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ def test_provider_against_pact(
167167
broker_url, broker_username, broker_password = pact_broker_credentials
168168

169169
broker_builder = (
170-
Verifier("OsparcApiProvider")
170+
Verifier("OsparcApiServerLicensedItems")
171171
.add_transport(url=run_test_server)
172172
.broker_source(
173173
broker_url,

0 commit comments

Comments
 (0)