Skip to content

Commit c3625b1

Browse files
authored
CMLDEV-990 Add docstrings and type annotations across the code base and tests (#196)
1 parent e0d26ef commit c3625b1

29 files changed

Lines changed: 2975 additions & 807 deletions

tests/conftest.py

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@
4040

4141

4242
def client_library_patched_system_info(version: str) -> Iterator[MagicMock]:
43+
"""Patch ClientLibrary.system_info to return a fixed version.
44+
45+
:param version: Version string to return from system_info.
46+
:yields: The patch object for ClientLibrary.system_info.
47+
"""
4348
with patch.object(
4449
ClientLibrary, "system_info", return_value={"version": version, "ready": True}
4550
) as cl:
@@ -48,44 +53,65 @@ def client_library_patched_system_info(version: str) -> Iterator[MagicMock]:
4853

4954
@pytest.fixture
5055
def client_library_server_current() -> Iterator[MagicMock]:
56+
"""Simulate a controller running the current CML version.
57+
58+
:yields: The patch object for ClientLibrary.system_info.
59+
"""
5160
yield from client_library_patched_system_info(version=CURRENT_VERSION)
5261

5362

5463
@pytest.fixture
5564
def client_library_server_2_0_0() -> Iterator[MagicMock]:
65+
"""Simulate a controller running CML version 2.0.0.
66+
67+
:yields: The patch object for ClientLibrary.system_info.
68+
"""
5669
yield from client_library_patched_system_info(version="2.0.0")
5770

5871

5972
@pytest.fixture
6073
def client_library_server_2_19_0() -> Iterator[MagicMock]:
74+
"""Simulate a controller running CML version 2.19.0.
75+
76+
:yields: The patch object for ClientLibrary.system_info.
77+
"""
6178
yield from client_library_patched_system_info(version="2.19.0")
6279

6380

6481
@pytest.fixture
6582
def client_library_server_2_9_0() -> Iterator[MagicMock]:
66-
"""Simulate a controller running CML version 2.9.0."""
83+
"""Simulate a controller running CML version 2.9.0.
6784
85+
:yields: The patch object for ClientLibrary.system_info.
86+
"""
6887
yield from client_library_patched_system_info(version="2.9.0")
6988

7089

7190
@pytest.fixture
7291
def mocked_session() -> Iterator[MagicMock]:
92+
"""Patch authentication.CustomClient for tests that need a mock HTTP session.
93+
94+
:yields: The patched CustomClient class (MagicMock).
95+
"""
7396
with patch.object(authentication, "CustomClient", autospec=True) as session:
7497
yield session
7598

7699

77100
@pytest.fixture(scope="session")
78101
def test_data_dir() -> Path:
102+
"""Path to the test_data directory containing JSON fixtures.
103+
104+
:returns: Path to the test_data directory.
105+
"""
79106
return Path(__file__).parent / "test_data"
80107

81108

82109
def resp_body_from_file(test_data_dir: Path, request: httpx.Request) -> httpx.Response:
83-
"""
84-
A callback that returns the contents of a file based on the request.
110+
"""Return response body from a file based on the request URL path.
85111
86-
:param request: The request that contains the title to search for
87-
:returns: A Response that has `content` set to the contents of a file
88-
that corresponds to a response body for the request.
112+
:param test_data_dir: Directory containing JSON fixture files.
113+
:param request: The HTTP request; URL path determines which file to load.
114+
:returns: An httpx.Response with content set to the matching fixture file.
89115
"""
90116
endpoint_parts = request.url.path.split("/")[3:]
91117
filename = "not initialized"
@@ -99,11 +125,14 @@ def resp_body_from_file(test_data_dir: Path, request: httpx.Request) -> httpx.Re
99125

100126

101127
@pytest.fixture
102-
def respx_mock_with_labs(respx_mock: MockRouter, test_data_dir: Path):
103-
"""
104-
A test fixture that provides basic lab data with respx_mock so that unit tests can
105-
call ``client.all_labs`` or ``client.join_existing_lab``. The sample data includes
106-
some runtime data, like node states and simulation_statistics.
128+
def respx_mock_with_labs(respx_mock: MockRouter, test_data_dir: Path) -> None:
129+
"""Provide basic lab data with respx_mock for unit tests.
130+
131+
Enables tests to call ``client.all_labs`` or ``client.join_existing_lab``.
132+
Sample data includes runtime data (node states, simulation_statistics).
133+
134+
:param respx_mock: The respx mock router to configure.
135+
:param test_data_dir: Directory containing JSON fixture files.
107136
"""
108137
respx_mock.get(FAKE_HOST_API + "system_information").respond(
109138
json={"version": CURRENT_VERSION, "ready": True, "oui": "52:54:00:00:00:00"},
@@ -185,7 +214,12 @@ def respx_mock_with_labs(respx_mock: MockRouter, test_data_dir: Path):
185214

186215

187216
@pytest.fixture
188-
def client_library(respx_mock_with_labs: MockRouter) -> Iterator[ClientLibrary]:
217+
def client_library(respx_mock_with_labs: None) -> Iterator[ClientLibrary]:
218+
"""Provide a ClientLibrary instance with mocked lab API responses.
219+
220+
:param respx_mock_with_labs: Fixture that configures respx (consumed for setup).
221+
:yields: A ClientLibrary connected to FAKE_HOST with test credentials.
222+
"""
189223
_ = respx_mock_with_labs
190224
client = ClientLibrary(url=FAKE_HOST, username="test", password="pa$$")
191225
yield client

tests/test_auth_management.py

Lines changed: 79 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
# limitations under the License.
1919
#
2020

21+
from __future__ import annotations
22+
2123
import time
2224
from unittest.mock import MagicMock
2325

@@ -33,6 +35,11 @@
3335

3436

3537
def make_auth_management(settings: dict) -> tuple[AuthManagement, MagicMock]:
38+
"""Create AuthManagement instance with mocked session and given settings.
39+
40+
:param settings: Auth configuration dict (method, ldap/radius settings).
41+
:returns: Tuple of (AuthManagement, mocked session).
42+
"""
3643
session = MagicMock()
3744
auth_management = AuthManagement(session, auto_sync=False)
3845
auth_management._settings = settings.copy()
@@ -46,20 +53,29 @@ def make_auth_management(settings: dict) -> tuple[AuthManagement, MagicMock]:
4653
("radius", RADIUSManager),
4754
],
4855
)
49-
def test_manager_returns_expected_manager(method: str, manager_cls):
56+
def test_manager_returns_expected_manager(
57+
method: str, manager_cls: type[LDAPManager] | type[RADIUSManager]
58+
) -> None:
59+
"""Manager property returns LDAPManager or RADIUSManager based on method.
60+
61+
:param method: Auth method ("ldap" or "radius").
62+
:param manager_cls: Expected manager class.
63+
"""
5064
auth_management, _ = make_auth_management({"method": method})
5165

5266
assert isinstance(auth_management.manager, manager_cls)
5367

5468

55-
def test_update_settings_no_args_raises():
69+
def test_update_settings_no_args_raises() -> None:
70+
"""update_settings with no args raises TypeError."""
5671
auth_management, _ = make_auth_management({"method": "ldap"})
5772

5873
with pytest.raises(TypeError, match="No settings to update"):
5974
auth_management.update_settings()
6075

6176

62-
def test_sync_updates_settings_and_timestamp():
77+
def test_sync_updates_settings_and_timestamp() -> None:
78+
"""Sync fetches config from server and updates _last_sync_time."""
6379
auth_management, session = make_auth_management({"method": "ldap"})
6480
session.get.return_value.json.return_value = {"method": "local"}
6581

@@ -73,7 +89,11 @@ def test_sync_updates_settings_and_timestamp():
7389

7490

7591
@pytest.mark.parametrize("search_filter", [None, "(cn=admins)"])
76-
def test_get_ldap_groups(search_filter: str | None):
92+
def test_get_ldap_groups(search_filter: str | None) -> None:
93+
"""get_ldap_groups returns groups, optionally filtered.
94+
95+
:param search_filter: Optional LDAP filter string.
96+
"""
7797
auth_management, session = make_auth_management({"method": "ldap"})
7898
session.get.return_value.json.return_value = ["group-1", "group-2"]
7999

@@ -88,15 +108,17 @@ def test_get_ldap_groups(search_filter: str | None):
88108
)
89109

90110

91-
def test_refresh_ldap_groups():
111+
def test_refresh_ldap_groups() -> None:
112+
"""refresh_ldap_groups sends PUT to system/auth/refresh."""
92113
auth_management, session = make_auth_management({"method": "ldap"})
93114

94115
auth_management.refresh_ldap_groups()
95116

96117
session.put.assert_called_once_with("system/auth/refresh")
97118

98119

99-
def test_test_auth_with_user_credentials():
120+
def test_auth_with_user_credentials() -> None:
121+
"""test_auth with username/password sends auth-data in request."""
100122
auth_management, session = make_auth_management({"method": "ldap"})
101123
session.post.return_value.json.return_value = {"auth_ok": True}
102124

@@ -114,7 +136,8 @@ def test_test_auth_with_user_credentials():
114136
)
115137

116138

117-
def test_test_auth_with_group_name():
139+
def test_auth_with_group_name() -> None:
140+
"""test_auth with group_name sends group-data in request."""
118141
auth_management, session = make_auth_management({"method": "ldap"})
119142
session.post.return_value.json.return_value = {"auth_ok": True}
120143

@@ -132,7 +155,8 @@ def test_test_auth_with_group_name():
132155
)
133156

134157

135-
def test_test_current_auth_includes_manager_password():
158+
def test_current_auth_includes_manager_password() -> None:
159+
"""test_current_auth includes manager_password in auth-config."""
136160
auth_management, session = make_auth_management(
137161
{"method": "ldap", "verify_tls": True}
138162
)
@@ -183,7 +207,12 @@ def test_test_current_auth_includes_manager_password():
183207
("verify_tls", False),
184208
],
185209
)
186-
def test_ldap_settings_update(setting: str, value):
210+
def test_ldap_settings_update(setting: str, value: str | bool | float) -> None:
211+
"""LDAP manager setter PATCHes config with setting and value.
212+
213+
:param setting: LDAP setting name.
214+
:param value: Value to set.
215+
"""
187216
auth_management, session = make_auth_management({"method": "ldap", setting: value})
188217
manager = auth_management._managers["ldap"]
189218

@@ -195,14 +224,19 @@ def test_ldap_settings_update(setting: str, value):
195224
assert auth_management._settings[setting] == value
196225

197226

198-
def test_ldap_timeout_inactive_method_raises():
227+
def test_ldap_timeout_inactive_method_raises() -> None:
228+
"""Accessing LDAP timeout when method is local raises MethodNotActive.
229+
230+
:raises MethodNotActive: When LDAP is not the active auth method.
231+
"""
199232
auth_management, _ = make_auth_management({"method": "local", "timeout": 5})
200233

201234
with pytest.raises(MethodNotActive):
202235
_ = auth_management._managers["ldap"].timeout
203236

204237

205-
def test_ldap_resource_pool_accepts_resource_pool_instance():
238+
def test_ldap_resource_pool_accepts_instance() -> None:
239+
"""LDAP resource_pool setter accepts ResourcePool instance, uses its id."""
206240
auth_management, session = make_auth_management(
207241
{"method": "ldap", "resource_pool": "old"}
208242
)
@@ -240,7 +274,12 @@ def test_ldap_resource_pool_accepts_resource_pool_instance():
240274
("resource_pool", "pool-id"),
241275
],
242276
)
243-
def test_radius_settings_update(setting: str, value):
277+
def test_radius_settings_update(setting: str, value: str | int | float) -> None:
278+
"""RADIUS manager setter PATCHes config with setting and value.
279+
280+
:param setting: RADIUS setting name.
281+
:param value: Value to set.
282+
"""
244283
auth_management, session = make_auth_management(
245284
{"method": "radius", setting: value}
246285
)
@@ -254,7 +293,8 @@ def test_radius_settings_update(setting: str, value):
254293
assert auth_management._settings[setting] == value
255294

256295

257-
def test_radius_secret_setter_updates_setting():
296+
def test_radius_secret_setter_updates_setting() -> None:
297+
"""RADIUS secret setter PATCHes config."""
258298
auth_management, session = make_auth_management({"method": "radius"})
259299
manager = auth_management._managers["radius"]
260300

@@ -265,14 +305,19 @@ def test_radius_secret_setter_updates_setting():
265305
)
266306

267307

268-
def test_radius_timeout_inactive_method_raises():
308+
def test_radius_timeout_inactive_method_raises() -> None:
309+
"""Accessing RADIUS timeout when method is local raises MethodNotActive.
310+
311+
:raises MethodNotActive: When RADIUS is not the active auth method.
312+
"""
269313
auth_management, _ = make_auth_management({"method": "local", "timeout": 5})
270314

271315
with pytest.raises(MethodNotActive):
272316
_ = auth_management._managers["radius"].timeout
273317

274318

275-
def test_radius_resource_pool_accepts_resource_pool_instance():
319+
def test_radius_resource_pool_accepts_instance() -> None:
320+
"""RADIUS resource_pool setter accepts ResourcePool instance, uses its id."""
276321
auth_management, session = make_auth_management(
277322
{"method": "radius", "resource_pool": "old"}
278323
)
@@ -300,7 +345,8 @@ def test_radius_resource_pool_accepts_resource_pool_instance():
300345
assert auth_management._settings["resource_pool"] == "pool-456"
301346

302347

303-
def test_ldap_manager_password_setter_updates_setting():
348+
def test_ldap_manager_password_setter_updates() -> None:
349+
"""LDAP manager_password setter PATCHes config."""
304350
auth_management, session = make_auth_management({"method": "ldap"})
305351
manager = auth_management._managers["ldap"]
306352

@@ -311,7 +357,8 @@ def test_ldap_manager_password_setter_updates_setting():
311357
)
312358

313359

314-
def test_update_settings_precedence_and_sync_called():
360+
def test_update_settings_precedence_and_sync() -> None:
361+
"""Keyword args override dict args; sync fetches updated config."""
315362
auth_management, session = make_auth_management({"method": "ldap"})
316363
session.get.return_value.json.return_value = {"method": "ldap", "verify_tls": False}
317364

@@ -324,7 +371,11 @@ def test_update_settings_precedence_and_sync_called():
324371
assert auth_management._settings["verify_tls"] is False
325372

326373

327-
def test_sync_if_outdated_triggers_sync(monkeypatch: pytest.MonkeyPatch):
374+
def test_sync_if_outdated_triggers_sync(monkeypatch: pytest.MonkeyPatch) -> None:
375+
"""sync_if_outdated triggers sync when interval exceeded.
376+
377+
:param monkeypatch: Pytest monkeypatch fixture.
378+
"""
328379
auth_management, session = make_auth_management({"method": "ldap"})
329380
session.get.return_value.json.return_value = {"method": "ldap"}
330381
auth_management.auto_sync = True
@@ -338,7 +389,11 @@ def test_sync_if_outdated_triggers_sync(monkeypatch: pytest.MonkeyPatch):
338389
session.get.assert_called_once_with("system/auth/config")
339390

340391

341-
def test_sync_if_outdated_skips_when_recent(monkeypatch: pytest.MonkeyPatch):
392+
def test_sync_if_outdated_skips_when_recent(monkeypatch: pytest.MonkeyPatch) -> None:
393+
"""sync_if_outdated skips sync when within interval.
394+
395+
:param monkeypatch: Pytest monkeypatch fixture.
396+
"""
342397
auth_management, session = make_auth_management({"method": "ldap"})
343398
auth_management.auto_sync = True
344399
auth_management.auto_sync_interval = 10.0
@@ -351,7 +406,11 @@ def test_sync_if_outdated_skips_when_recent(monkeypatch: pytest.MonkeyPatch):
351406
session.get.assert_not_called()
352407

353408

354-
def test_accessing_wrong_manager_raises():
409+
def test_accessing_wrong_manager_raises() -> None:
410+
"""Accessing RADIUS manager when LDAP is active raises MethodNotActive.
411+
412+
:raises MethodNotActive: When the requested manager is not active.
413+
"""
355414
auth_management, _ = make_auth_management({"method": "ldap"})
356415

357416
with pytest.raises(MethodNotActive):

0 commit comments

Comments
 (0)