From ed9d7f892447ad16fe1c8b0e2723deb699b7120d Mon Sep 17 00:00:00 2001 From: Robert Segal Date: Thu, 23 Apr 2026 09:15:25 -0600 Subject: [PATCH] feature: MPT-20430 add endpoints and e2e tests for spotlight queries AI Generated. Add a dedicated queries module for spotlight with full sync and async endpoint coverage. Rename existing spotlight e2e test files from test_*_spotlight to test_*_object for consistency, and add query-specific e2e tests under tests/e2e/spotlight/query/. Update unit tests to cover the new queries resource and extend the existing spotlight unit suite. --- e2e_config.test.json | 3 +- mpt_api_client/resources/spotlight/queries.py | 56 ++++++++++++++++ .../resources/spotlight/spotlight.py | 14 ++++ tests/e2e/spotlight/query/conftest.py | 11 +++ tests/e2e/spotlight/query/test_async_query.py | 36 ++++++++++ tests/e2e/spotlight/query/test_sync_query.py | 36 ++++++++++ ...sync_spotlight.py => test_async_object.py} | 0 ..._sync_spotlight.py => test_sync_object.py} | 0 .../unit/resources/spotlight/test_queries.py | 67 +++++++++++++++++++ .../resources/spotlight/test_spotlight.py | 6 ++ 10 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 mpt_api_client/resources/spotlight/queries.py create mode 100644 tests/e2e/spotlight/query/conftest.py create mode 100644 tests/e2e/spotlight/query/test_async_query.py create mode 100644 tests/e2e/spotlight/query/test_sync_query.py rename tests/e2e/spotlight/{test_async_spotlight.py => test_async_object.py} (100%) rename tests/e2e/spotlight/{test_sync_spotlight.py => test_sync_object.py} (100%) create mode 100644 tests/unit/resources/spotlight/test_queries.py diff --git a/e2e_config.test.json b/e2e_config.test.json index 3d7cb1ae..d0230156 100644 --- a/e2e_config.test.json +++ b/e2e_config.test.json @@ -83,5 +83,6 @@ "program.program.id": "PRG-9643-3741", "program.template.id": "PTM-9643-3741-0004", "program.terms.id": "PTC-9643-3741-0001", - "program.terms.variant.id": "PTV-9643-3741-0001-0001" + "program.terms.variant.id": "PTV-9643-3741-0001-0001", + "spotlight.query.id": "SPQ-0001-0034" } diff --git a/mpt_api_client/resources/spotlight/queries.py b/mpt_api_client/resources/spotlight/queries.py new file mode 100644 index 00000000..5b8028b6 --- /dev/null +++ b/mpt_api_client/resources/spotlight/queries.py @@ -0,0 +1,56 @@ +from mpt_api_client.http import AsyncService, Service +from mpt_api_client.http.mixins import ( + AsyncCollectionMixin, + AsyncGetMixin, + CollectionMixin, + GetMixin, +) +from mpt_api_client.models import Model + + +class SpotlightQuery(Model): + """Spotlight Query resource. + + Attributes: + id: Unique identifier for the spotlight query. + name: Name of the spotlight query. + template: Template string for the spotlight query. + invalidation_interval: Interval for invalidating the spotlight query. + invalidate_on_date_change: Whether to invalidate the spotlight query on date change. + filter: Filter string for the spotlight query. + scope: Scope of the spotlight query. + """ + + id: str = "" + name: str | None + template: str | None + invalidation_interval: str | None + invalidate_on_date_change: bool | None + filter: str | None + scope: str | None + + +class SpotlightQueriesServiceConfig: + """Configuration for Spotlight Queries Service.""" + + _endpoint: str = "/public/v1/spotlight/queries" + _model_class = SpotlightQuery + _collection_key = "data" + + +class SpotlightQueriesService( + GetMixin[SpotlightQuery], + CollectionMixin[SpotlightQuery], + Service[SpotlightQuery], + SpotlightQueriesServiceConfig, +): + """Service for spotlight queries.""" + + +class AsyncSpotlightQueriesService( + AsyncGetMixin[SpotlightQuery], + AsyncCollectionMixin[SpotlightQuery], + AsyncService[SpotlightQuery], + SpotlightQueriesServiceConfig, +): + """Asynchronous service for spotlight queries.""" diff --git a/mpt_api_client/resources/spotlight/spotlight.py b/mpt_api_client/resources/spotlight/spotlight.py index 9d36aa84..ab75a239 100644 --- a/mpt_api_client/resources/spotlight/spotlight.py +++ b/mpt_api_client/resources/spotlight/spotlight.py @@ -3,6 +3,10 @@ AsyncSpotlightObjectsService, SpotlightObjectsService, ) +from mpt_api_client.resources.spotlight.queries import ( + AsyncSpotlightQueriesService, + SpotlightQueriesService, +) class Spotlight: @@ -16,6 +20,11 @@ def objects(self) -> SpotlightObjectsService: # noqa: WPS110 """Spotlight Objects service.""" return SpotlightObjectsService(http_client=self.http_client) + @property + def queries(self) -> SpotlightQueriesService: # noqa: WPS110 + """Spotlight Queries service.""" + return SpotlightQueriesService(http_client=self.http_client) + class AsyncSpotlight: """Spotlight MPT API Module.""" @@ -27,3 +36,8 @@ def __init__(self, http_client: AsyncHTTPClient): def objects(self) -> AsyncSpotlightObjectsService: # noqa: WPS110 """Spotlight Objects service.""" return AsyncSpotlightObjectsService(http_client=self.http_client) + + @property + def queries(self) -> AsyncSpotlightQueriesService: # noqa: WPS110 + """Spotlight Queries service.""" + return AsyncSpotlightQueriesService(http_client=self.http_client) diff --git a/tests/e2e/spotlight/query/conftest.py b/tests/e2e/spotlight/query/conftest.py new file mode 100644 index 00000000..5807c2ec --- /dev/null +++ b/tests/e2e/spotlight/query/conftest.py @@ -0,0 +1,11 @@ +import pytest + + +@pytest.fixture +def spotlight_query_id(e2e_config): + return e2e_config["spotlight.query.id"] + + +@pytest.fixture +def invalid_spotlight_query_id(): + return "SPQ-0000-0000" diff --git a/tests/e2e/spotlight/query/test_async_query.py b/tests/e2e/spotlight/query/test_async_query.py new file mode 100644 index 00000000..a0ba4126 --- /dev/null +++ b/tests/e2e/spotlight/query/test_async_query.py @@ -0,0 +1,36 @@ +import pytest + +from mpt_api_client import RQLQuery +from mpt_api_client.exceptions import MPTAPIError + +pytestmark = [pytest.mark.flaky] + + +async def test_get_query_by_id(async_mpt_ops, spotlight_query_id): + result = await async_mpt_ops.spotlight.queries.get(spotlight_query_id) + + assert result.id == spotlight_query_id + + +async def test_get_query_invalid_id(async_mpt_ops, invalid_spotlight_query_id): + with pytest.raises(MPTAPIError, match=r"404 Not Found"): + await async_mpt_ops.spotlight.queries.get(invalid_spotlight_query_id) + + +async def test_list_queries(async_mpt_ops): + limit = 10 + + result = await async_mpt_ops.spotlight.queries.fetch_page(limit=limit) + + assert len(result) > 0 + + +async def test_filter_and_select_queries(async_mpt_ops, spotlight_query_id): + select_fields = ["-template", "-invalidationInterval", "-invalidateOnDateChange"] + filtered_spotlight_queries = async_mpt_ops.spotlight.queries.filter( + RQLQuery(id=spotlight_query_id) + ).select(*select_fields) + + result = [query async for query in filtered_spotlight_queries.iterate()] + + assert len(result) == 1 diff --git a/tests/e2e/spotlight/query/test_sync_query.py b/tests/e2e/spotlight/query/test_sync_query.py new file mode 100644 index 00000000..02d25155 --- /dev/null +++ b/tests/e2e/spotlight/query/test_sync_query.py @@ -0,0 +1,36 @@ +import pytest + +from mpt_api_client import RQLQuery +from mpt_api_client.exceptions import MPTAPIError + +pytestmark = [pytest.mark.flaky] + + +def test_get_query_by_id(mpt_ops, spotlight_query_id): + result = mpt_ops.spotlight.queries.get(spotlight_query_id) + + assert result.id == spotlight_query_id + + +def test_get_query_invalid_id(mpt_ops, invalid_spotlight_query_id): + with pytest.raises(MPTAPIError, match=r"404 Not Found"): + mpt_ops.spotlight.queries.get(invalid_spotlight_query_id) + + +def test_list_queries(mpt_ops): + limit = 10 + + result = mpt_ops.spotlight.queries.fetch_page(limit=limit) + + assert len(result) > 0 + + +def test_filter_and_select_queries(mpt_ops, spotlight_query_id): + select_fields = ["-template", "-invalidationInterval", "-invalidateOnDateChange"] + filtered_spotlight_queries = mpt_ops.spotlight.queries.filter( + RQLQuery(id=spotlight_query_id) + ).select(*select_fields) + + result = list(filtered_spotlight_queries.iterate()) + + assert len(result) == 1 diff --git a/tests/e2e/spotlight/test_async_spotlight.py b/tests/e2e/spotlight/test_async_object.py similarity index 100% rename from tests/e2e/spotlight/test_async_spotlight.py rename to tests/e2e/spotlight/test_async_object.py diff --git a/tests/e2e/spotlight/test_sync_spotlight.py b/tests/e2e/spotlight/test_sync_object.py similarity index 100% rename from tests/e2e/spotlight/test_sync_spotlight.py rename to tests/e2e/spotlight/test_sync_object.py diff --git a/tests/unit/resources/spotlight/test_queries.py b/tests/unit/resources/spotlight/test_queries.py new file mode 100644 index 00000000..f4dd69cf --- /dev/null +++ b/tests/unit/resources/spotlight/test_queries.py @@ -0,0 +1,67 @@ +import pytest + +from mpt_api_client.resources.spotlight.queries import ( + AsyncSpotlightQueriesService, + SpotlightQueriesService, + SpotlightQuery, +) + + +@pytest.fixture +def spotlight_query_service(http_client): + return SpotlightQueriesService(http_client=http_client) + + +@pytest.fixture +def async_spotlight_query_service(async_http_client): + return AsyncSpotlightQueriesService(http_client=async_http_client) + + +@pytest.fixture +def spotlight_query_data(): + return { + "id": "SPQ-123", + "name": "Spotlight Query", + "template": "Template String", + "invalidationInterval": "24h", + "invalidateOnDateChange": True, + "filter": "Filter String", + "scope": "Scope String", + } + + +@pytest.mark.parametrize( + "method", + ["get", "iterate"], +) +def test_mixins_present(spotlight_query_service, method): + result = hasattr(spotlight_query_service, method) + + assert result is True + + +@pytest.mark.parametrize( + "method", + ["get", "iterate"], +) +def test_mixins_present_async(async_spotlight_query_service, method): + result = hasattr(async_spotlight_query_service, method) + + assert result is True + + +def test_queries_primitive_fields(spotlight_query_data): + result = SpotlightQuery(spotlight_query_data) + + assert result.to_dict() == spotlight_query_data + + +def test_queries_optional_fields(): # noqa: WPS218 + result = SpotlightQuery({"id": "SPQ-123"}) + + assert not hasattr(result, "name") + assert not hasattr(result, "template") + assert not hasattr(result, "invalidation_interval") + assert not hasattr(result, "invalidate_on_date_change") + assert not hasattr(result, "filter") + assert not hasattr(result, "scope") diff --git a/tests/unit/resources/spotlight/test_spotlight.py b/tests/unit/resources/spotlight/test_spotlight.py index bd8f3301..85833b1c 100644 --- a/tests/unit/resources/spotlight/test_spotlight.py +++ b/tests/unit/resources/spotlight/test_spotlight.py @@ -4,6 +4,10 @@ AsyncSpotlightObjectsService, SpotlightObjectsService, ) +from mpt_api_client.resources.spotlight.queries import ( + AsyncSpotlightQueriesService, + SpotlightQueriesService, +) from mpt_api_client.resources.spotlight.spotlight import AsyncSpotlight, Spotlight @@ -21,6 +25,7 @@ def async_spotlight(async_http_client): ("property_name", "expected_service_class"), [ ("objects", SpotlightObjectsService), + ("queries", SpotlightQueriesService), ], ) def test_spotlight_properties(spotlight, property_name, expected_service_class): @@ -34,6 +39,7 @@ def test_spotlight_properties(spotlight, property_name, expected_service_class): ("property_name", "expected_service_class"), [ ("objects", AsyncSpotlightObjectsService), + ("queries", AsyncSpotlightQueriesService), ], ) def test_async_spotlight_properties(async_spotlight, property_name, expected_service_class):