Skip to content

Commit bf35ba5

Browse files
committed
feat(stack-state/config&deploy-summary/diagnostic): Added unit testcases
1 parent 440546e commit bf35ba5

6 files changed

Lines changed: 553 additions & 2 deletions

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
* Added `client.stack_deployment_groups` — list, read, approve, and rerun deployment groups within a stack configuration. `list(stack_configuration_id)` (`GET /stack-configurations/{id}/stack-deployment-groups`), `read(group_id)` (`GET /stack-deployment-groups/{id}`), `read_by_name(stack_configuration_id, name)`, `approve_all_plans(group_id)` (`POST .../approve-all-plans`), `rerun(group_id, options)` (`POST .../rerun?deployments=...`). New models: `StackDeploymentGroup`, `DeploymentGroupStatus`, `StackDeploymentGroupListOptions`, `StackDeploymentGroupRerunOptions`.
88
* Added `client.stack_deployment_runs` — list, read, approve, and cancel individual deployment runs within a deployment group. `list(group_id)` (`GET /stack-deployment-groups/{id}/stack-deployment-runs`), `read(run_id)` (`GET /stack-deployment-runs/{id}`), `approve_all_plans(run_id)` (`POST .../approve-all-plans`), `cancel(run_id)` (`POST .../cancel`). New models: `StackDeploymentRun`, `DeploymentRunStatus`, `StackDeploymentRunListOptions`, `StackDeploymentRunReadOptions`, `StackDeploymentRunIncludeOpt`.
99
* Added `client.stack_deployment_steps` — list, read, advance, list diagnostics, and download artifacts for individual deployment steps within a deployment run. `list(run_id)` (`GET /stack-deployment-runs/{id}/stack-deployment-steps`), `read(step_id)` (`GET /stack-deployment-steps/{id}`), `advance(step_id)` (`POST .../advance`), `list_diagnostics(step_id)` (`GET .../stack-diagnostics`), `download_artifact(step_id, artifact_type)` (`GET .../artifacts?name=<type>`) returns raw `bytes`. New models: `StackDeploymentStep`, `DeploymentStepStatus`, `StackDeploymentStepArtifactType`, `StackDeploymentStepIncludeOpt`, `StackDeploymentStepListOptions`, `StackDeploymentStepReadOptions`, `StackDiagnostic`, `StackDiagnosticListOptions`.
10+
* Added `client.stack_states` — list, read, and download descriptions for stack states. `list(stack_id)` (`GET /stacks/{id}/stack-states`), `read(state_id)` (`GET /stack-states/{id}`), `download_description(state_id)` (`GET /stack-states/{id}/description`) returns raw `bytes`. New models: `StackState`, `StackStateListOptions`. New error: `InvalidStackStateIDError`.
11+
* Added `client.stack_configuration_summaries` — list lightweight stack configuration summaries for a stack. `list(stack_id)` (`GET /stacks/{id}/stack-configuration-summaries`). New models: `StackConfigurationSummary`, `StackConfigurationSummaryListOptions`.
12+
* Added `client.stack_deployment_group_summaries` — list rolled-up deployment group summaries for a stack configuration. `list(stack_configuration_id)` (`GET /stack-configurations/{id}/stack-deployment-group-summaries`). New models: `StackDeploymentGroupSummary`, `StackDeploymentGroupSummaryListOptions`, `StackDeploymentGroupStatusCounts`.
13+
* Added `client.stack_diagnostics` — read and acknowledge stack diagnostics. `read(diagnostic_id)` (`GET /stack-diagnostics/{id}`), `acknowledge(diagnostic_id)` (`POST /stack-diagnostics/{id}/acknowledge`). New error: `InvalidStackDiagnosticIDError`.
1014

1115
# Released
1216
# v1.2.0
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# Copyright IBM Corp. 2025, 2026
2+
# SPDX-License-Identifier: MPL-2.0
3+
4+
"""Unit tests for the stack_configuration_summaries module."""
5+
6+
from unittest.mock import Mock
7+
8+
import pytest
9+
10+
from pytfe._http import HTTPTransport
11+
from pytfe.errors import InvalidStackIDError
12+
from pytfe.models.stack_configuration import (
13+
StackConfigurationSummary,
14+
StackConfigurationSummaryListOptions,
15+
)
16+
from pytfe.resources.stack_configuration_summaries import StackConfigurationSummaries
17+
18+
19+
class TestStackConfigurationSummaries:
20+
"""Test the StackConfigurationSummaries service class."""
21+
22+
@pytest.fixture
23+
def mock_transport(self):
24+
"""Create a mock HTTPTransport."""
25+
return Mock(spec=HTTPTransport)
26+
27+
@pytest.fixture
28+
def service(self, mock_transport):
29+
"""Create a StackConfigurationSummaries service with mocked transport."""
30+
return StackConfigurationSummaries(mock_transport)
31+
32+
@pytest.fixture
33+
def summary_api_data(self):
34+
"""Typical API response item for a single stack configuration summary."""
35+
return {
36+
"id": "stcs-abc123",
37+
"type": "stack-configuration-summaries",
38+
"attributes": {
39+
"status": "converged",
40+
"sequence-number": 5,
41+
},
42+
}
43+
44+
# ── Model tests ──────────────────────────────────────────────────────────
45+
46+
def test_stack_configuration_summary_parse(self, summary_api_data):
47+
"""StackConfigurationSummary parses all attributes correctly."""
48+
attrs = dict(summary_api_data["attributes"])
49+
attrs["id"] = summary_api_data["id"]
50+
summary = StackConfigurationSummary.model_validate(attrs)
51+
assert summary.id == "stcs-abc123"
52+
assert summary.status == "converged"
53+
assert summary.sequence_number == 5
54+
55+
def test_stack_configuration_summary_list_options_serialization(self):
56+
"""StackConfigurationSummaryListOptions serializes page[size] correctly."""
57+
opts = StackConfigurationSummaryListOptions(page_size=15)
58+
dumped = opts.model_dump(by_alias=True, exclude_none=True)
59+
assert dumped["page[size]"] == 15
60+
61+
# ── list() tests ─────────────────────────────────────────────────────────
62+
63+
def test_list_invalid_stack_id_raises(self, service):
64+
"""list() with an empty stack ID raises InvalidStackIDError."""
65+
with pytest.raises(InvalidStackIDError):
66+
list(service.list(""))
67+
68+
def test_list_success(self, service, summary_api_data):
69+
"""list() yields StackConfigurationSummary objects from paginated results."""
70+
service._list = Mock(return_value=[summary_api_data])
71+
72+
results = list(service.list("st-xyz789"))
73+
74+
service._list.assert_called_once_with(
75+
path="/api/v2/stacks/st-xyz789/stack-configuration-summaries",
76+
params={},
77+
)
78+
assert len(results) == 1
79+
assert isinstance(results[0], StackConfigurationSummary)
80+
assert results[0].id == "stcs-abc123"
81+
82+
def test_list_with_page_size(self, service, summary_api_data):
83+
"""list() passes page[size] param correctly."""
84+
service._list = Mock(return_value=[summary_api_data])
85+
opts = StackConfigurationSummaryListOptions(page_size=5)
86+
list(service.list("st-xyz789", options=opts))
87+
service._list.assert_called_once_with(
88+
path="/api/v2/stacks/st-xyz789/stack-configuration-summaries",
89+
params={"page[size]": 5},
90+
)
91+
92+
def test_list_empty(self, service):
93+
"""list() returns an empty iterator when the API returns no items."""
94+
service._list = Mock(return_value=[])
95+
assert list(service.list("st-xyz789")) == []
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
# Copyright IBM Corp. 2025, 2026
2+
# SPDX-License-Identifier: MPL-2.0
3+
4+
"""Unit tests for the stack_deployment_group_summaries module."""
5+
6+
from unittest.mock import Mock
7+
8+
import pytest
9+
10+
from pytfe._http import HTTPTransport
11+
from pytfe.errors import InvalidStackConfigurationIDError
12+
from pytfe.models.stack_deployment_group import (
13+
StackDeploymentGroup,
14+
StackDeploymentGroupStatusCounts,
15+
StackDeploymentGroupSummary,
16+
StackDeploymentGroupSummaryListOptions,
17+
)
18+
from pytfe.resources.stack_deployment_group_summaries import (
19+
StackDeploymentGroupSummaries,
20+
)
21+
22+
23+
class TestStackDeploymentGroupSummaries:
24+
"""Test the StackDeploymentGroupSummaries service class."""
25+
26+
@pytest.fixture
27+
def mock_transport(self):
28+
"""Create a mock HTTPTransport."""
29+
return Mock(spec=HTTPTransport)
30+
31+
@pytest.fixture
32+
def service(self, mock_transport):
33+
"""Create a StackDeploymentGroupSummaries service with mocked transport."""
34+
return StackDeploymentGroupSummaries(mock_transport)
35+
36+
@pytest.fixture
37+
def summary_api_data(self):
38+
"""Typical API response item for a single deployment group summary."""
39+
return {
40+
"id": "sdgs-abc123",
41+
"type": "stack-deployment-group-summaries",
42+
"attributes": {
43+
"name": "dev",
44+
"status": "succeeded",
45+
"status-counts": {
46+
"pending": 0,
47+
"pre-deploying": 0,
48+
"pending-operator": 0,
49+
"acquiring-lock": 0,
50+
"deploying": 0,
51+
"succeeded": 3,
52+
"failed": 0,
53+
"abandoned": 0,
54+
},
55+
},
56+
"relationships": {
57+
"stack-deployment-group": {
58+
"data": {"id": "sdg-xyz789", "type": "stack-deployment-groups"}
59+
}
60+
},
61+
}
62+
63+
# ── Model tests ──────────────────────────────────────────────────────────
64+
65+
def test_stack_deployment_group_status_counts_parse(self):
66+
"""StackDeploymentGroupStatusCounts parses all count fields."""
67+
counts_data = {
68+
"pending": 1,
69+
"pre-deploying": 2,
70+
"pending-operator": 3,
71+
"acquiring-lock": 4,
72+
"deploying": 5,
73+
"succeeded": 6,
74+
"failed": 7,
75+
"abandoned": 8,
76+
}
77+
counts = StackDeploymentGroupStatusCounts.model_validate(counts_data)
78+
assert counts.pending == 1
79+
assert counts.pre_deploying == 2
80+
assert counts.pre_deploying_pending_operator == 3
81+
assert counts.acquiring_lock == 4
82+
assert counts.deploying == 5
83+
assert counts.succeeded == 6
84+
assert counts.failed == 7
85+
assert counts.abandoned == 8
86+
87+
def test_stack_deployment_group_summary_parse(self, summary_api_data):
88+
"""StackDeploymentGroupSummary parses name and status correctly."""
89+
attrs = dict(summary_api_data["attributes"])
90+
attrs["id"] = summary_api_data["id"]
91+
summary = StackDeploymentGroupSummary.model_validate(attrs)
92+
assert summary.id == "sdgs-abc123"
93+
assert summary.name == "dev"
94+
assert summary.status == "succeeded"
95+
assert isinstance(summary.status_counts, StackDeploymentGroupStatusCounts)
96+
assert summary.status_counts.succeeded == 3
97+
98+
def test_summary_list_options_serialization(self):
99+
"""StackDeploymentGroupSummaryListOptions serializes page[size] correctly."""
100+
opts = StackDeploymentGroupSummaryListOptions(page_size=25)
101+
dumped = opts.model_dump(by_alias=True, exclude_none=True)
102+
assert dumped["page[size]"] == 25
103+
104+
# ── list() tests ─────────────────────────────────────────────────────────
105+
106+
def test_list_invalid_configuration_id_raises(self, service):
107+
"""list() with an empty config ID raises InvalidStackConfigurationIDError."""
108+
with pytest.raises(InvalidStackConfigurationIDError):
109+
list(service.list(""))
110+
111+
def test_list_success(self, service, summary_api_data):
112+
"""list() yields StackDeploymentGroupSummary objects from paginated results."""
113+
service._list = Mock(return_value=[summary_api_data])
114+
115+
results = list(service.list("stc-abc123"))
116+
117+
service._list.assert_called_once_with(
118+
path="/api/v2/stack-configurations/stc-abc123/stack-deployment-group-summaries",
119+
params={},
120+
)
121+
assert len(results) == 1
122+
assert isinstance(results[0], StackDeploymentGroupSummary)
123+
assert results[0].id == "sdgs-abc123"
124+
assert results[0].name == "dev"
125+
126+
def test_list_with_page_size(self, service, summary_api_data):
127+
"""list() passes page[size] param correctly."""
128+
service._list = Mock(return_value=[summary_api_data])
129+
opts = StackDeploymentGroupSummaryListOptions(page_size=10)
130+
list(service.list("stc-abc123", options=opts))
131+
service._list.assert_called_once_with(
132+
path="/api/v2/stack-configurations/stc-abc123/stack-deployment-group-summaries",
133+
params={"page[size]": 10},
134+
)
135+
136+
def test_list_hydrates_group_relation(self, service, summary_api_data):
137+
"""list() hydrates the stack-deployment-group relation as a typed stub."""
138+
service._list = Mock(return_value=[summary_api_data])
139+
results = list(service.list("stc-abc123"))
140+
assert isinstance(results[0].stack_deployment_group, StackDeploymentGroup)
141+
assert results[0].stack_deployment_group.id == "sdg-xyz789"
142+
143+
def test_list_empty(self, service):
144+
"""list() returns an empty iterator when the API returns no items."""
145+
service._list = Mock(return_value=[])
146+
assert list(service.list("stc-abc123")) == []

tests/units/test_stack_deployment_step.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def step_api_data(self):
6262
def diag_api_data(self):
6363
"""Typical API response item for a single stack diagnostic."""
6464
return {
65-
"id": "stf-diag001",
65+
"id": "std-diag001",
6666
"type": "stack-diagnostics",
6767
"attributes": {
6868
"severity": "warning",
@@ -244,7 +244,7 @@ def test_list_diagnostics_success(self, service, diag_api_data):
244244
)
245245
assert len(results) == 1
246246
assert isinstance(results[0], StackDiagnostic)
247-
assert results[0].id == "stf-diag001"
247+
assert results[0].id == "std-diag001"
248248
assert results[0].severity == "warning"
249249

250250
def test_list_diagnostics_with_page_size(self, service, diag_api_data):
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# Copyright IBM Corp. 2025, 2026
2+
# SPDX-License-Identifier: MPL-2.0
3+
4+
"""Unit tests for the stack_diagnostics module."""
5+
6+
from unittest.mock import Mock
7+
8+
import pytest
9+
10+
from pytfe._http import HTTPTransport
11+
from pytfe.errors import InvalidStackDiagnosticIDError
12+
from pytfe.models.stack_deployment_step import StackDiagnostic
13+
from pytfe.resources.stack_diagnostics import StackDiagnostics
14+
15+
16+
class TestStackDiagnostics:
17+
"""Test the StackDiagnostics service class."""
18+
19+
@pytest.fixture
20+
def mock_transport(self):
21+
"""Create a mock HTTPTransport."""
22+
return Mock(spec=HTTPTransport)
23+
24+
@pytest.fixture
25+
def service(self, mock_transport):
26+
"""Create a StackDiagnostics service with mocked transport."""
27+
return StackDiagnostics(mock_transport)
28+
29+
@pytest.fixture
30+
def diagnostic_api_data(self):
31+
"""Typical API response data for a single stack diagnostic."""
32+
return {
33+
"id": "std-abc123",
34+
"type": "stack-diagnostics",
35+
"attributes": {
36+
"severity": "error",
37+
"summary": "Invalid configuration",
38+
"detail": "The stack configuration failed validation.",
39+
"diags": None,
40+
"acknowledged": False,
41+
"acknowledged-at": None,
42+
"created-at": "2026-07-03T10:00:00.000Z",
43+
},
44+
}
45+
46+
# ── Model tests ──────────────────────────────────────────────────────────
47+
48+
def test_stack_diagnostic_parse(self, diagnostic_api_data):
49+
"""StackDiagnostic parses all attributes correctly."""
50+
attrs = dict(diagnostic_api_data["attributes"])
51+
attrs["id"] = diagnostic_api_data["id"]
52+
diag = StackDiagnostic.model_validate(attrs)
53+
assert diag.id == "std-abc123"
54+
assert diag.severity == "error"
55+
assert diag.summary == "Invalid configuration"
56+
assert diag.acknowledged is False
57+
58+
# ── read() tests ─────────────────────────────────────────────────────────
59+
60+
def test_read_invalid_id_raises(self, service):
61+
"""read() with an empty ID raises InvalidStackDiagnosticIDError."""
62+
with pytest.raises(InvalidStackDiagnosticIDError):
63+
service.read("")
64+
65+
def test_read_success(self, service, mock_transport, diagnostic_api_data):
66+
"""read() fetches a single stack diagnostic by ID."""
67+
mock_response = Mock()
68+
mock_response.json.return_value = {"data": diagnostic_api_data}
69+
mock_transport.request.return_value = mock_response
70+
71+
diag = service.read("std-abc123")
72+
73+
mock_transport.request.assert_called_once_with(
74+
"GET", path="/api/v2/stack-diagnostics/std-abc123"
75+
)
76+
assert isinstance(diag, StackDiagnostic)
77+
assert diag.id == "std-abc123"
78+
assert diag.severity == "error"
79+
assert diag.acknowledged is False
80+
81+
# ── acknowledge() tests ───────────────────────────────────────────────────
82+
83+
def test_acknowledge_invalid_id_raises(self, service):
84+
"""acknowledge() with an empty ID raises InvalidStackDiagnosticIDError."""
85+
with pytest.raises(InvalidStackDiagnosticIDError):
86+
service.acknowledge("")
87+
88+
def test_acknowledge_calls_correct_endpoint(self, service, mock_transport):
89+
"""acknowledge() POSTs to the acknowledge action endpoint."""
90+
mock_transport.request.return_value = Mock()
91+
service.acknowledge("std-abc123")
92+
mock_transport.request.assert_called_once_with(
93+
"POST",
94+
path="/api/v2/stack-diagnostics/std-abc123/acknowledge",
95+
)
96+
97+
def test_acknowledge_returns_none(self, service, mock_transport):
98+
"""acknowledge() returns None on success."""
99+
mock_transport.request.return_value = Mock()
100+
result = service.acknowledge("std-abc123")
101+
assert result is None

0 commit comments

Comments
 (0)