Skip to content

Commit 10c2ca2

Browse files
authored
MPT-20328: Added endpoints and e2e tests for program templates and parameters (#308)
This pull request adds support for managing program templates in the API client. It introduces new service classes for both synchronous and asynchronous operations on templates, updates the main Program services to expose these new capabilities, and provides comprehensive unit and end-to-end tests to verify the new functionality. New Program Templates Support: Added Template model and TemplatesService / AsyncTemplatesService classes to [programs_templates.py](vscode-webview://0mprrqrlqlg3oapm12jkov8h9pk4dfj2hv323gd0ha7nmus4tfma/mpt_api_client/resources/program/programs_templates.py), including endpoint configuration and resource mixins. Updated ProgramsService and AsyncProgramsService classes in [programs.py](vscode-webview://0mprrqrlqlg3oapm12jkov8h9pk4dfj2hv323gd0ha7nmus4tfma/mpt_api_client/resources/program/programs.py) to provide .templates(program_id) methods for accessing the new services. Testing Enhancements: Added unit tests for the new services and model in [test_programs_templates.py](vscode-webview://0mprrqrlqlg3oapm12jkov8h9pk4dfj2hv323gd0ha7nmus4tfma/tests/unit/resources/program/test_programs_templates.py), covering endpoints, method presence, and model field parsing. Added end-to-end tests for both sync and async template operations (create, update, get, delete, filter/select) in [test_sync_template.py](vscode-webview://0mprrqrlqlg3oapm12jkov8h9pk4dfj2hv323gd0ha7nmus4tfma/tests/e2e/program/program/template/test_sync_template.py) and [test_async_template.py](vscode-webview://0mprrqrlqlg3oapm12jkov8h9pk4dfj2hv323gd0ha7nmus4tfma/tests/e2e/program/program/template/test_async_template.py), with supporting fixtures in [conftest.py](vscode-webview://0mprrqrlqlg3oapm12jkov8h9pk4dfj2hv323gd0ha7nmus4tfma/tests/e2e/program/program/template/conftest.py). Updated unit tests for Program services to check for the presence of the new .templates methods in [test_programs.py](vscode-webview://0mprrqrlqlg3oapm12jkov8h9pk4dfj2hv323gd0ha7nmus4tfma/tests/unit/resources/program/test_programs.py). Configuration Updates: Added a seeded template ID to [e2e_config.test.json](vscode-webview://0mprrqrlqlg3oapm12jkov8h9pk4dfj2hv323gd0ha7nmus4tfma/e2e_config.test.json) for use in tests. Closes MPT-20328 Added Template model with typed fields for content (name, content, type, status), behavior (default), and relationships (external_ids, program, audit). Introduced TemplatesService and AsyncTemplatesService with endpoint configuration at /public/v1/program/programs/{program_id}/templates and support for managed resource operations (create, read, update, delete, list, filter, select). Extended ProgramsService and AsyncProgramsService with templates(program_id) factory methods for accessing template services. Added comprehensive end-to-end tests for synchronous and asynchronous template operations including create, update, get, delete, and filtered listing with field selection. Added unit tests validating service configuration, endpoint paths, public method availability, and model field parsing. Added pytest fixtures for template e2e tests with test data and configuration. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> Closes [MPT-20328](https://softwareone.atlassian.net/browse/MPT-20328) - Add Template model and TemplatesService / AsyncTemplatesService for program templates (CRUD, list, filter, select) at /public/v1/program/programs/{program_id}/templates - Expose .templates(program_id) factory on ProgramsService and AsyncProgramsService - Add Parameter model and ParametersService / AsyncParametersService plus .parameters(program_id) factories for program parameters - Add unit tests for templates and parameters services and models (endpoint paths, method presence, model parsing) - Add synchronous and asynchronous end-to-end tests and pytest fixtures for template and parameter flows (create, update, get, delete, filter/select) - Seed e2e_config.test.json with program.template.id and program.parameter.id (and repositioned program.document.file.id) - Update flake8 per-file-ignores for mpt_api_client/resources/program/*.py <!-- end of auto-generated comment: release notes by coderabbit.ai --> [MPT-20328]: https://softwareone.atlassian.net/browse/MPT-20328?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2 parents cff2bb2 + 42ab336 commit 10c2ca2

14 files changed

Lines changed: 697 additions & 1 deletion

e2e_config.test.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,10 @@
7070
"notifications.subscriber.id": "NTS-0829-7123-7123",
7171
"integration.extension.id": "EXT-6587-4477",
7272
"integration.term.id": "ETC-6587-4477-0062",
73+
"program.document.file.id": "PDM-9643-3741-0001",
7374
"program.media.id": "PMD-9643-3741-0001",
7475
"program.parameter.group.id": "PPG-9643-3741-0002",
76+
"program.parameter.id": "PPM-9643-3741-0001",
7577
"program.program.id": "PRG-9643-3741",
76-
"program.document.file.id": "PDM-9643-3741-0001"
78+
"program.template.id": "PTM-9643-3741-0004"
7779
}

mpt_api_client/resources/program/programs.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@
2222
AsyncParameterGroupsService,
2323
ParameterGroupsService,
2424
)
25+
from mpt_api_client.resources.program.programs_parameters import (
26+
AsyncParametersService,
27+
ParametersService,
28+
)
29+
from mpt_api_client.resources.program.programs_templates import (
30+
AsyncTemplatesService,
31+
TemplatesService,
32+
)
2533

2634

2735
class Program(Model):
@@ -101,6 +109,18 @@ def parameter_groups(self, program_id: str) -> ParameterGroupsService:
101109
http_client=self.http_client, endpoint_params={"program_id": program_id}
102110
)
103111

112+
def parameters(self, program_id: str) -> ParametersService: # noqa: WPS110
113+
"""Return program parameters service."""
114+
return ParametersService(
115+
http_client=self.http_client, endpoint_params={"program_id": program_id}
116+
)
117+
118+
def templates(self, program_id: str) -> TemplatesService:
119+
"""Return program templates service."""
120+
return TemplatesService(
121+
http_client=self.http_client, endpoint_params={"program_id": program_id}
122+
)
123+
104124

105125
class AsyncProgramsService(
106126
AsyncGetMixin[Program],
@@ -140,3 +160,15 @@ def parameter_groups(self, program_id: str) -> AsyncParameterGroupsService:
140160
return AsyncParameterGroupsService(
141161
http_client=self.http_client, endpoint_params={"program_id": program_id}
142162
)
163+
164+
def parameters(self, program_id: str) -> AsyncParametersService: # noqa: WPS110
165+
"""Return async program parameters service."""
166+
return AsyncParametersService(
167+
http_client=self.http_client, endpoint_params={"program_id": program_id}
168+
)
169+
170+
def templates(self, program_id: str) -> AsyncTemplatesService:
171+
"""Return async program templates service."""
172+
return AsyncTemplatesService(
173+
http_client=self.http_client, endpoint_params={"program_id": program_id}
174+
)
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
from mpt_api_client.http import AsyncService, Service
2+
from mpt_api_client.http.mixins import (
3+
AsyncCollectionMixin,
4+
AsyncManagedResourceMixin,
5+
CollectionMixin,
6+
ManagedResourceMixin,
7+
)
8+
from mpt_api_client.models import Model
9+
from mpt_api_client.models.model import BaseModel
10+
11+
12+
class Parameter(Model):
13+
"""Parameter resource.
14+
15+
Attributes:
16+
name: Parameter name.
17+
scope: Parameter scope.
18+
phase: Parameter phase (e.g. Order, Fulfillment).
19+
program: Reference to the program this parameter belongs to.
20+
description: Parameter description.
21+
multiple: Whether multiple values are allowed for this parameter.
22+
external_id: External identifier for the parameter.
23+
display_order: Display order of the parameter.
24+
constraints: Parameter constraints (required, hidden, readonly).
25+
options: Type-specific parameter options.
26+
type: Parameter type (e.g. SingleLineText, MultiLineText, Address, etc.).
27+
status: Parameter status.
28+
audit: Audit information (created, updated events).
29+
"""
30+
31+
name: str | None
32+
scope: str | None
33+
phase: str | None
34+
program: BaseModel | None
35+
description: str | None
36+
multiple: bool | None
37+
external_id: str | None
38+
display_order: int | None
39+
constraints: BaseModel | None
40+
options: BaseModel | None
41+
type: str | None
42+
status: str | None
43+
audit: BaseModel | None
44+
45+
46+
class ParametersServiceConfig:
47+
"""Parameters service configuration."""
48+
49+
_endpoint = "/public/v1/program/programs/{program_id}/parameters"
50+
_model_class = Parameter
51+
_collection_key = "data"
52+
53+
54+
class ParametersService(
55+
ManagedResourceMixin[Parameter],
56+
CollectionMixin[Parameter],
57+
Service[Parameter],
58+
ParametersServiceConfig,
59+
):
60+
"""Parameters service."""
61+
62+
63+
class AsyncParametersService(
64+
AsyncManagedResourceMixin[Parameter],
65+
AsyncCollectionMixin[Parameter],
66+
AsyncService[Parameter],
67+
ParametersServiceConfig,
68+
):
69+
"""Parameters service."""
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
from mpt_api_client.http import AsyncService, Service
2+
from mpt_api_client.http.mixins import (
3+
AsyncCollectionMixin,
4+
AsyncManagedResourceMixin,
5+
CollectionMixin,
6+
ManagedResourceMixin,
7+
)
8+
from mpt_api_client.models import Model
9+
from mpt_api_client.models.model import BaseModel
10+
11+
12+
class Template(Model):
13+
"""Template resource.
14+
15+
Attributes:
16+
name: Template name.
17+
content: Template content.
18+
default: Whether this is the default template.
19+
external_ids: External identifiers.
20+
type: Template type.
21+
status: Template status.
22+
program: Reference to the program.
23+
audit: Audit information (created, updated events).
24+
"""
25+
26+
name: str | None
27+
content: str | None # noqa: WPS110
28+
default: bool | None
29+
external_ids: BaseModel | None
30+
type: str | None
31+
status: str | None
32+
program: BaseModel | None
33+
audit: BaseModel | None
34+
35+
36+
class TemplatesServiceConfig:
37+
"""Templates service configuration."""
38+
39+
_endpoint = "/public/v1/program/programs/{program_id}/templates"
40+
_model_class = Template
41+
_collection_key = "data"
42+
43+
44+
class TemplatesService(
45+
ManagedResourceMixin[Template],
46+
CollectionMixin[Template],
47+
Service[Template],
48+
TemplatesServiceConfig,
49+
):
50+
"""Templates service."""
51+
52+
53+
class AsyncTemplatesService(
54+
AsyncManagedResourceMixin[Template],
55+
AsyncCollectionMixin[Template],
56+
AsyncService[Template],
57+
TemplatesServiceConfig,
58+
):
59+
"""Templates service."""

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ per-file-ignores = [
125125
"mpt_api_client/resources/exchange/*.py: WPS235 WPS215",
126126
"mpt_api_client/resources/integration/*.py: WPS214 WPS215 WPS235",
127127
"mpt_api_client/resources/helpdesk/*.py: WPS204 WPS215 WPS214",
128+
"mpt_api_client/resources/program/*.py: WPS204 WPS215",
128129
"mpt_api_client/rql/query_builder.py: WPS110 WPS115 WPS210 WPS214",
129130
"tests/e2e/accounts/*.py: WPS430 WPS202",
130131
"tests/e2e/billing/*.py: WPS202 WPS421 WPS118",
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import pytest
2+
3+
4+
@pytest.fixture
5+
def parameter_id(e2e_config):
6+
return e2e_config["program.parameter.id"]
7+
8+
9+
@pytest.fixture
10+
def invalid_parameter_id():
11+
return "PPM-0000-0000-0000"
12+
13+
14+
@pytest.fixture
15+
def parameter_data():
16+
return {
17+
"externalId": "e2eCreatedProgramParameter",
18+
"displayOrder": 100,
19+
"scope": "Enrollment",
20+
"phase": "Fulfillment",
21+
"multiple": False,
22+
"description": "E2E Created Program Parameter",
23+
"type": "SingleLineText",
24+
"constraints": {"hidden": False, "required": False, "readonly": False},
25+
"name": "E2E Created Program Parameter",
26+
"options": {
27+
"type": "SingleLineText",
28+
"placeholderText": "E2E Created Program Parameter",
29+
"name": "E2E Created Program Parameter",
30+
"hintText": "E2E Created Program Parameter",
31+
},
32+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import pytest
2+
3+
from mpt_api_client.exceptions import MPTAPIError
4+
from mpt_api_client.rql.query_builder import RQLQuery
5+
6+
pytestmark = [pytest.mark.flaky]
7+
8+
9+
@pytest.fixture
10+
async def created_parameter(async_mpt_vendor, program_id, parameter_data):
11+
service = async_mpt_vendor.program.programs.parameters(program_id)
12+
parameter = await service.create(parameter_data)
13+
yield parameter
14+
try:
15+
await service.delete(parameter.id)
16+
except MPTAPIError as error:
17+
print(f"TEARDOWN - Unable to delete parameter {parameter.id}: {error.title}") # noqa: WPS421
18+
19+
20+
def test_create_parameter(created_parameter):
21+
result = created_parameter.name == "E2E Created Program Parameter"
22+
23+
assert result is True
24+
25+
26+
async def test_update_parameter(async_mpt_vendor, program_id, created_parameter):
27+
service = async_mpt_vendor.program.programs.parameters(program_id)
28+
update_data = {"name": "E2E Updated Program Parameter"}
29+
30+
result = await service.update(created_parameter.id, update_data)
31+
32+
assert result.name == update_data["name"]
33+
34+
35+
async def test_get_parameter(async_mpt_vendor, program_id, parameter_id):
36+
service = async_mpt_vendor.program.programs.parameters(program_id)
37+
38+
result = await service.get(parameter_id)
39+
40+
assert result.id == parameter_id
41+
42+
43+
async def test_get_invalid_parameter(async_mpt_vendor, program_id, invalid_parameter_id):
44+
with pytest.raises(MPTAPIError):
45+
await async_mpt_vendor.program.programs.parameters(program_id).get(invalid_parameter_id)
46+
47+
48+
async def test_delete_parameter(async_mpt_vendor, program_id, created_parameter):
49+
parameter_data = created_parameter
50+
51+
result = async_mpt_vendor.program.programs.parameters(program_id)
52+
53+
await result.delete(parameter_data.id)
54+
55+
56+
async def test_filter_and_select_parameters(async_mpt_vendor, program_id, parameter_id):
57+
select_fields = ["-description", "-audit"]
58+
filtered_parameters = (
59+
async_mpt_vendor.program.programs
60+
.parameters(program_id)
61+
.filter(RQLQuery(id=parameter_id))
62+
.filter(RQLQuery(name="E2E Seeded Program Parameter"))
63+
.select(*select_fields)
64+
)
65+
66+
result = [parameter async for parameter in filtered_parameters.iterate()]
67+
68+
assert len(result) == 1
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import pytest
2+
3+
from mpt_api_client.exceptions import MPTAPIError
4+
from mpt_api_client.rql.query_builder import RQLQuery
5+
6+
pytestmark = [pytest.mark.flaky]
7+
8+
9+
@pytest.fixture
10+
def created_parameter(mpt_vendor, program_id, parameter_data):
11+
service = mpt_vendor.program.programs.parameters(program_id)
12+
parameter = service.create(parameter_data)
13+
yield parameter
14+
try:
15+
service.delete(parameter.id)
16+
except MPTAPIError as error:
17+
print(f"TEARDOWN - Unable to delete parameter {parameter.id}: {error.title}") # noqa: WPS421
18+
19+
20+
def test_create_parameter(created_parameter):
21+
result = created_parameter.name == "E2E Created Program Parameter"
22+
23+
assert result is True
24+
25+
26+
def test_update_parameter(mpt_vendor, program_id, created_parameter):
27+
service = mpt_vendor.program.programs.parameters(program_id)
28+
update_data = {"name": "E2E Updated Program Parameter"}
29+
30+
result = service.update(created_parameter.id, update_data)
31+
32+
assert result.name == update_data["name"]
33+
34+
35+
def test_get_parameter(mpt_vendor, program_id, parameter_id):
36+
service = mpt_vendor.program.programs.parameters(program_id)
37+
38+
result = service.get(parameter_id)
39+
40+
assert result.id == parameter_id
41+
42+
43+
def test_get_invalid_parameter(mpt_vendor, program_id, invalid_parameter_id):
44+
with pytest.raises(MPTAPIError):
45+
mpt_vendor.program.programs.parameters(program_id).get(invalid_parameter_id)
46+
47+
48+
def test_delete_parameter(mpt_vendor, program_id, created_parameter):
49+
parameter_data = created_parameter
50+
51+
result = mpt_vendor.program.programs.parameters(program_id)
52+
53+
result.delete(parameter_data.id)
54+
55+
56+
def test_filter_and_select_parameters(mpt_vendor, program_id, parameter_id):
57+
select_fields = ["-description", "-audit"]
58+
filtered_parameters = (
59+
mpt_vendor.program.programs
60+
.parameters(program_id)
61+
.filter(RQLQuery(id=parameter_id))
62+
.filter(RQLQuery(name="E2E Seeded Program Parameter"))
63+
.select(*select_fields)
64+
)
65+
66+
result = list(filtered_parameters.iterate())
67+
68+
assert len(result) == 1
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import pytest
2+
3+
4+
@pytest.fixture
5+
def template_id(e2e_config):
6+
return e2e_config["program.template.id"]
7+
8+
9+
@pytest.fixture
10+
def invalid_template_id():
11+
return "PTM-0000-0000-0000"
12+
13+
14+
@pytest.fixture
15+
def template_data():
16+
return {
17+
"name": "E2E Created Program Template",
18+
"type": "EnrollmentProcessing",
19+
"default": False,
20+
"externalIds": {"vendor": None},
21+
"content": "E2E Created Program Template",
22+
}

0 commit comments

Comments
 (0)