From cca2afd02480a691ca257accd676d59eccac2638 Mon Sep 17 00:00:00 2001 From: Torgeir Brekke Myklebust <183589856+torgeirbm@users.noreply.github.com> Date: Wed, 24 Jun 2026 10:15:55 +0200 Subject: [PATCH 1/6] added parent state class for subscription patch request and a couple of simple tests --- src/omnia_timeseries/models.py | 26 ++++++++- tests/test_api.py | 98 ++++++++++++++++++++++++++++++---- 2 files changed, 113 insertions(+), 11 deletions(-) diff --git a/src/omnia_timeseries/models.py b/src/omnia_timeseries/models.py index d3749bd..ce09ace 100644 --- a/src/omnia_timeseries/models.py +++ b/src/omnia_timeseries/models.py @@ -110,14 +110,17 @@ class IMSMetadataModel(TypedDict): maxTimeInterval: str fieldId: str + class IMSMetadataItemsModel(TypedDict): items: List[IMSMetadataModel] + class GetIMSMetadataResponseModel(TypedDict): data: IMSMetadataItemsModel count: Optional[int] continuationToken: Optional[str] + class TimeseriesModel(TypedDict): id: str name: str @@ -142,14 +145,31 @@ class GetTimeseriesResponseModel(TypedDict): count: Optional[int] continuationToken: Optional[str] -class SubscriptionPatchRequestItem(TypedDict, total=False): + +class SubscriptionStatePatchModel(TypedDict, total=False): + tsdbBackfilled: Optional[bool] + tsdbEventsExported: Optional[int] + tsdbFirstExportedTime: Optional[str] + tsdbLastExportedTime: Optional[str] + dlBackfilled: Optional[bool] + dlEventsExported: Optional[int] + dlFirstExportedTime: Optional[str] + dlLastExportedTime: Optional[str] + dlParquetFirstExportedTime: Optional[str] + dlParquetLastExportedTime: Optional[str] + sourceFirstCountedTime: Optional[str] + sourceLastCountedTime: Optional[str] + + +class SubscriptionPatchRequestItem(SubscriptionStatePatchModel, total=False): name: Optional[str] plantStidCode: Optional[str] - plantSapCode: Optional[bool] + plantSapCode: Optional[str] terminal: Optional[str] fieldId: Optional[str] timeseriesId: Optional[str] + class TimeseriesRequestItem(TypedDict, total=False): name: str description: Optional[str] @@ -228,10 +248,12 @@ class StreamSubscriptionItemsModel(TypedDict): class StreamSubscriptionDataModel(TypedDict): data: StreamSubscriptionItemsModel + class SubscriptionCounterModel(TypedDict): quota: int count: int + class TimeseriesRequestFailedException(Exception): def __init__(self, response: Response) -> None: try: diff --git a/tests/test_api.py b/tests/test_api.py index c3899fc..3620b95 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1,9 +1,14 @@ +import json +from typing import Optional + import requests_mock import pytest from azure.core.credentials import AccessToken from omnia_timeseries import TimeseriesAPI, TimeseriesEnvironment +from omnia_timeseries.api import IMSSubscriptionsManagementAPI from omnia_timeseries.http_client import TimeseriesRequestFailedException + class DummyCredentials: def get_token(self, *scopes: str, **kwargs) -> AccessToken: return AccessToken("dummytoken", 0) @@ -17,6 +22,17 @@ def api(): return api +@pytest.fixture +def ims_sub_mgmt_api(): + env = TimeseriesEnvironment.Dev() + api = IMSSubscriptionsManagementAPI( + azure_credential=DummyCredentials(), + environment=env, + ) + api._base_url = "https://test" + return api + + def should_retry_request_when_failing_on_retryable_error_code_503_service_is_unavailable( api, ): @@ -84,9 +100,9 @@ def should_forward_align_to_cache_parameter_for_multi_datapoints(api): api.get_multi_datapoints([{"id": "series-1"}], alignToCache=True) assert m.call_count == 1, "Expected a single request" request = m.request_history[0] - assert request.qs.get("aligntocache") == [ - "true" - ], "alignToCache should be forwarded" + assert request.qs.get("aligntocache") == ["true"], ( + "alignToCache should be forwarded" + ) def should_forward_align_to_cache_parameter_for_get_aggregates(api): @@ -99,9 +115,9 @@ def should_forward_align_to_cache_parameter_for_get_aggregates(api): api.get_aggregates("1234", ["avg"], alignToCache=False) assert m.call_count == 1, "Expected a single request" request = m.request_history[0] - assert request.qs.get("aligntocache") == [ - "false" - ], "alignToCache should be forwarded as false" + assert request.qs.get("aligntocache") == ["false"], ( + "alignToCache should be forwarded as false" + ) def should_include_debug_query_param_when_debug_mode_is_enabled(api): @@ -115,6 +131,70 @@ def should_include_debug_query_param_when_debug_mode_is_enabled(api): api.get_multi_datapoints([{"id": "series-1"}]) assert m.call_count == 1, "Expected a single request" request = m.request_history[0] - assert request.qs.get("debug") == [ - "true" - ], "Debug mode should inject debug=true" + assert request.qs.get("debug") == ["true"], ( + "Debug mode should inject debug=true" + ) + + +def should_patch_subscription_by_uid_with_state_fields_only(ims_sub_mgmt_api): + payload = { + "tsdbEventsExported": 0, + "tsdbFirstExportedTime": None, + "tsdbLastExportedTime": None, + "dlEventsExported": 0, + "dlFirstExportedTime": None, + "dlLastExportedTime": None, + "sourceFirstCountedTime": None, + "sourceLastCountedTime": None, + } + + with requests_mock.Mocker() as m: + m.register_uri( + "PATCH", + "https://test/uid/sub-123", + json={"data": {"items": []}, "count": 0}, + ) + + response = ims_sub_mgmt_api.patch_subscription_by_uid( + uid="sub-123", + request=payload, + ) + + assert m.call_count == 1 + request = m.request_history[0] + assert request.method == "PATCH" + assert json.loads(request.text) == payload + assert response["count"] == 0 + + +def should_patch_subscription_by_uid_with_complete_model(ims_sub_mgmt_api): + payload = { + "plantStidCode": "asdf-1234", + "plantSapCode": "1234", + "tsdbEventsExported": 0, + "tsdbFirstExportedTime": None, + "tsdbLastExportedTime": None, + "dlEventsExported": 0, + "dlFirstExportedTime": None, + "dlLastExportedTime": None, + "sourceFirstCountedTime": None, + "sourceLastCountedTime": None, + } + + with requests_mock.Mocker() as m: + m.register_uri( + "PATCH", + "https://test/uid/sub-123", + json={"data": {"items": []}, "count": 0}, + ) + + response = ims_sub_mgmt_api.patch_subscription_by_uid( + uid="sub-123", + request=payload, + ) + + assert m.call_count == 1 + request = m.request_history[0] + assert request.method == "PATCH" + assert json.loads(request.text) == payload + assert response["count"] == 0 From 1aacb823d150d4b2f666620439d250958d3fb2cd Mon Sep 17 00:00:00 2001 From: Torgeir Brekke Myklebust <183589856+torgeirbm@users.noreply.github.com> Date: Wed, 24 Jun 2026 10:32:14 +0200 Subject: [PATCH 2/6] more test --- tests/test_api.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/test_api.py b/tests/test_api.py index 3620b95..35260e7 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -198,3 +198,30 @@ def should_patch_subscription_by_uid_with_complete_model(ims_sub_mgmt_api): assert request.method == "PATCH" assert json.loads(request.text) == payload assert response["count"] == 0 + + +def should_fail_patch_subscription_by_uid_when_payload_has_disallowed_field( + ims_sub_mgmt_api, +): + payload = { + "tsdbEventsExported": 1, + "notAllowedField": "boom", # not part of SubscriptionPatchRequestItem + } + + with requests_mock.Mocker() as m: + m.register_uri( + "PATCH", + "https://test/uid/sub-123", + status_code=400, + text='{"message":"Invalid field: notAllowedField","traceId":"trace-1"}', + ) + + with pytest.raises(TimeseriesRequestFailedException) as exc: + ims_sub_mgmt_api.patch_subscription_by_uid( + uid="sub-123", + request=payload, + ) + + assert m.call_count == 1 + assert exc.value.status_code == 400 + assert "Invalid field" in exc.value.message From 988f32fe245343dadcbe92214a1113005d2f791e Mon Sep 17 00:00:00 2001 From: Torgeir Brekke Myklebust <183589856+torgeirbm@users.noreply.github.com> Date: Wed, 24 Jun 2026 10:43:02 +0200 Subject: [PATCH 3/6] Remove autocomplete stuff for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- tests/test_api.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_api.py b/tests/test_api.py index 35260e7..137e39b 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1,5 +1,4 @@ import json -from typing import Optional import requests_mock import pytest From 6cd2c9c991a954dc42a17beefffaa6fd46ada184 Mon Sep 17 00:00:00 2001 From: Torgeir Brekke Myklebust <183589856+torgeirbm@users.noreply.github.com> Date: Wed, 24 Jun 2026 10:44:28 +0200 Subject: [PATCH 4/6] update for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- tests/test_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_api.py b/tests/test_api.py index 137e39b..8aef997 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -166,7 +166,7 @@ def should_patch_subscription_by_uid_with_state_fields_only(ims_sub_mgmt_api): assert response["count"] == 0 -def should_patch_subscription_by_uid_with_complete_model(ims_sub_mgmt_api): +def should_patch_subscription_by_uid_with_subscription_and_state_fields(ims_sub_mgmt_api): payload = { "plantStidCode": "asdf-1234", "plantSapCode": "1234", From be660641117b62e1776c515d0587daf6214ed9c2 Mon Sep 17 00:00:00 2001 From: Torgeir Brekke Myklebust <183589856+torgeirbm@users.noreply.github.com> Date: Wed, 24 Jun 2026 10:46:52 +0200 Subject: [PATCH 5/6] fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- tests/test_api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_api.py b/tests/test_api.py index 8aef997..00e63af 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -199,12 +199,12 @@ def should_patch_subscription_by_uid_with_subscription_and_state_fields(ims_sub_ assert response["count"] == 0 -def should_fail_patch_subscription_by_uid_when_payload_has_disallowed_field( +def should_raise_on_patch_subscription_by_uid_when_api_returns_400( ims_sub_mgmt_api, ): payload = { "tsdbEventsExported": 1, - "notAllowedField": "boom", # not part of SubscriptionPatchRequestItem + "notAllowedField": "boom", # field intentionally unknown; API returns 400 } with requests_mock.Mocker() as m: From ea5f944b752e478da105a9010ec76416e010df30 Mon Sep 17 00:00:00 2001 From: Torgeir Brekke Myklebust <183589856+torgeirbm@users.noreply.github.com> Date: Wed, 24 Jun 2026 11:02:12 +0200 Subject: [PATCH 6/6] full name api fixture --- tests/test_api.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/tests/test_api.py b/tests/test_api.py index 00e63af..43642ed 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -22,7 +22,7 @@ def api(): @pytest.fixture -def ims_sub_mgmt_api(): +def imssubscriptionsmanagementapi(): env = TimeseriesEnvironment.Dev() api = IMSSubscriptionsManagementAPI( azure_credential=DummyCredentials(), @@ -135,7 +135,9 @@ def should_include_debug_query_param_when_debug_mode_is_enabled(api): ) -def should_patch_subscription_by_uid_with_state_fields_only(ims_sub_mgmt_api): +def should_patch_subscription_by_uid_with_state_fields_only( + imssubscriptionsmanagementapi, +): payload = { "tsdbEventsExported": 0, "tsdbFirstExportedTime": None, @@ -154,7 +156,7 @@ def should_patch_subscription_by_uid_with_state_fields_only(ims_sub_mgmt_api): json={"data": {"items": []}, "count": 0}, ) - response = ims_sub_mgmt_api.patch_subscription_by_uid( + response = imssubscriptionsmanagementapi.patch_subscription_by_uid( uid="sub-123", request=payload, ) @@ -166,7 +168,9 @@ def should_patch_subscription_by_uid_with_state_fields_only(ims_sub_mgmt_api): assert response["count"] == 0 -def should_patch_subscription_by_uid_with_subscription_and_state_fields(ims_sub_mgmt_api): +def should_patch_subscription_by_uid_with_subscription_and_state_fields( + imssubscriptionsmanagementapi, +): payload = { "plantStidCode": "asdf-1234", "plantSapCode": "1234", @@ -187,7 +191,7 @@ def should_patch_subscription_by_uid_with_subscription_and_state_fields(ims_sub_ json={"data": {"items": []}, "count": 0}, ) - response = ims_sub_mgmt_api.patch_subscription_by_uid( + response = imssubscriptionsmanagementapi.patch_subscription_by_uid( uid="sub-123", request=payload, ) @@ -200,7 +204,7 @@ def should_patch_subscription_by_uid_with_subscription_and_state_fields(ims_sub_ def should_raise_on_patch_subscription_by_uid_when_api_returns_400( - ims_sub_mgmt_api, + imssubscriptionsmanagementapi, ): payload = { "tsdbEventsExported": 1, @@ -216,7 +220,7 @@ def should_raise_on_patch_subscription_by_uid_when_api_returns_400( ) with pytest.raises(TimeseriesRequestFailedException) as exc: - ims_sub_mgmt_api.patch_subscription_by_uid( + imssubscriptionsmanagementapi.patch_subscription_by_uid( uid="sub-123", request=payload, )