1- """Unit tests for SystemAdministratorHandler version normalisation logic
2- and the SystemAdministratorClientCLI do_update input validation.
1+ """Unit tests for SystemAdministratorHandler helper functions and
2+ the SystemAdministratorClientCLI do_update input validation.
33"""
44
55import pytest
66from unittest .mock import MagicMock , patch
77
8-
9- # ---------------------------------------------------------------------------
10- # Helpers – replicate the version-normalisation logic from
11- # SystemAdministratorHandler.export_updateSoftware so we can test it
12- # without standing up a full DIRAC service.
13- # ---------------------------------------------------------------------------
14-
15-
16- def _normalise_version (version ):
17- """Mirror the normalisation applied at the top of export_updateSoftware.
18-
19- Returns the normalised version string, or raises ValueError if invalid.
20- Mirrors the side-effects relevant to the directory-name derivation and
21- the pip command construction.
22- """
23- from packaging .version import Version , InvalidVersion
24-
25- version = version .strip ()
26- if not version :
27- raise ValueError ("No version specified" )
28-
29- released_version = True
30- isPrerelease = False
31-
32- if version .lower () in ["integration" , "devel" , "master" , "main" ]:
33- released_version = False
34- version = "DIRAC[server] @ git+https://github.com/DIRACGrid/DIRAC.git@integration"
35-
36- if released_version :
37- try :
38- parsed = Version (version )
39- isPrerelease = parsed .is_prerelease
40- version = f"v{ parsed } "
41- except InvalidVersion :
42- if "https://" in version :
43- released_version = False
44- else :
45- raise ValueError (f"Invalid version passed { version !r} " )
46-
47- return version , released_version , isPrerelease
48-
49-
50- def _directory_from_version (version , released_version ):
51- """Mirror the directory-name derivation in export_updateSoftware.
52-
53- Split on the *first* "@" only (the pip package @ URL separator), strip
54- whitespace, then drop any "#egg=..." fragment.
55- """
56- if released_version :
57- return version
58- return version .split ("@" , 1 )[1 ].strip ().split ("#" )[0 ]
8+ from DIRAC .FrameworkSystem .Service .SystemAdministratorHandler import (
9+ _directory_label ,
10+ _normalise_version ,
11+ )
5912
6013
6114# ---------------------------------------------------------------------------
62- # Tests: version normalisation
15+ # Tests: _normalise_version
6316# ---------------------------------------------------------------------------
6417
6518
@@ -73,72 +26,80 @@ def test_whitespace_only_raises(self):
7326 _normalise_version (" " )
7427
7528 def test_released_version (self ):
76- version , released , pre = _normalise_version ("9.0.18" )
29+ version , primary , released , pre = _normalise_version ("9.0.18" )
7730 assert released is True
7831 assert pre is False
7932 assert version == "v9.0.18"
33+ assert primary is None
8034
8135 def test_released_prerelease_version (self ):
82- version , released , pre = _normalise_version ("9.0.18a1" )
36+ version , primary , released , pre = _normalise_version ("9.0.18a1" )
8337 assert released is True
8438 assert pre is True
8539 assert version == "v9.0.18a1"
8640
41+ def test_extension_syntax_splits_primary (self ):
42+ version , primary , released , pre = _normalise_version ("MyExtension==9.0.18" )
43+ assert primary == "MyExtension"
44+ assert version == "v9.0.18"
45+ assert released is True
46+
8747 @pytest .mark .parametrize ("keyword" , ["integration" , "devel" , "master" , "main" ])
8848 def test_special_keywords (self , keyword ):
89- version , released , pre = _normalise_version (keyword )
49+ version , primary , released , pre = _normalise_version (keyword )
9050 assert released is False
51+ assert pre is False
9152 assert "DIRACGrid/DIRAC" in version
9253 assert "@integration" in version
9354
9455 def test_git_url_without_spaces (self ):
9556 raw = "DIRAC[server]@git+https://github.com/fstagni/DIRAC.git@test_branch"
96- version , released , pre = _normalise_version (raw )
57+ version , primary , released , pre = _normalise_version (raw )
9758 assert released is False
9859 assert version == raw
9960
10061 def test_git_url_with_spaces_around_at (self ):
101- """The CLI now sends the raw user input; leading/trailing spaces must be stripped ."""
102- raw = " DIRAC[server] @ git+https://github.com/fstagni/DIRAC.git@test_branch "
103- version , released , pre = _normalise_version (raw )
62+ """Leading/trailing whitespace is stripped; internal pip spaces are kept ."""
63+ raw = "DIRAC[server] @ git+https://github.com/fstagni/DIRAC.git@test_branch"
64+ version , primary , released , pre = _normalise_version (f" { raw } " )
10465 assert released is False
105- # Internal spaces around "@" are preserved (pip accepts them)
106- assert version .strip () == raw .strip ()
66+ assert version == raw
10767
10868 def test_invalid_version_no_url_raises (self ):
10969 with pytest .raises (ValueError , match = "Invalid version passed" ):
11070 _normalise_version ("not-a-valid-version" )
11171
11272
11373# ---------------------------------------------------------------------------
114- # Tests: directory derivation from version string
74+ # Tests: _directory_label
11575# ---------------------------------------------------------------------------
11676
11777
118- class TestDirectoryFromVersion :
78+ class TestDirectoryLabel :
11979 def test_released_version_uses_version_directly (self ):
120- d = _directory_from_version ("v9.0.18" , released_version = True )
121- assert d == "v9.0.18"
80+ assert _directory_label ("v9.0.18" , released_version = True ) == "v9.0.18"
12281
12382 def test_git_url_without_spaces (self ):
124- """No spaces around '@' separator — branch name must be included ."""
83+ """Branch name after the second '@' must be preserved ."""
12584 version = "DIRAC[server]@git+https://github.com/fstagni/DIRAC.git@test_branch"
126- d = _directory_from_version ( version , released_version = False )
127- # Split on first "@" → "git+https://github.com/fstagni/DIRAC.git@test_branch"
128- assert d == "git+https://github.com/fstagni/DIRAC.git@test_branch"
85+ assert (
86+ _directory_label ( version , released_version = False ) == "git+https://github.com/fstagni/DIRAC.git@test_branch"
87+ )
12988
130- def test_git_url_with_spaces_around_at_separator (self ):
131- """Spaces around the pip '@' separator must be stripped; branch part kept."""
132- # Simulate what the handler receives after version.strip()
89+ def test_git_url_with_spaces_around_pip_separator (self ):
90+ """Spaces around the pip '@' separator are stripped; branch part kept."""
13391 version = "DIRAC[server] @ git+https://github.com/fstagni/DIRAC.git@test_branch"
134- d = _directory_from_version (version , released_version = False )
135- assert d == "git+https://github.com/fstagni/DIRAC.git@test_branch"
92+ assert (
93+ _directory_label (version , released_version = False ) == "git+https://github.com/fstagni/DIRAC.git@test_branch"
94+ )
13695
13796 def test_git_url_with_hash_fragment (self ):
138- """#egg= fragment must be stripped from directory name ."""
97+ """#egg= fragment must be stripped from the directory label ."""
13998 version = "DIRAC[server]@git+https://github.com/DIRACGrid/DIRAC.git@integration#egg=DIRAC"
140- d = _directory_from_version (version , released_version = False )
141- assert d == "git+https://github.com/DIRACGrid/DIRAC.git@integration"
99+ assert (
100+ _directory_label (version , released_version = False )
101+ == "git+https://github.com/DIRACGrid/DIRAC.git@integration"
102+ )
142103
143104
144105# ---------------------------------------------------------------------------
@@ -150,15 +111,12 @@ class TestDoUpdate:
150111 """Test SystemAdministratorClientCLI.do_update input validation."""
151112
152113 def _make_cli (self ):
153- from DIRAC .FrameworkSystem .Client .SystemAdministratorClientCLI import (
154- SystemAdministratorClientCLI ,
155- )
114+ from DIRAC .FrameworkSystem .Client .SystemAdministratorClientCLI import SystemAdministratorClientCLI
156115
157- with patch ("DIRAC.FrameworkSystem.Client.SystemAdministratorClientCLI.SystemAdministratorClient" ):
158- cli = SystemAdministratorClientCLI .__new__ (SystemAdministratorClientCLI )
159- cli .host = "localhost"
160- cli .port = 9162
161- return cli
116+ cli = SystemAdministratorClientCLI .__new__ (SystemAdministratorClientCLI )
117+ cli .host = "localhost"
118+ cli .port = 9162
119+ return cli
162120
163121 def test_empty_args_prints_usage_and_returns (self ):
164122 cli = self ._make_cli ()
@@ -169,12 +127,10 @@ def test_empty_args_prints_usage_and_returns(self):
169127 patch ("DIRAC.FrameworkSystem.Client.SystemAdministratorClientCLI.gLogger" ) as mock_logger ,
170128 ):
171129 cli .do_update ("" )
172- # Client must NOT be contacted
173130 mock_client_cls .assert_not_called ()
174- # Usage should be printed
175131 assert mock_logger .notice .called
176132
177- def test_whitespace_only_args_prints_usage_and_returns (self ):
133+ def test_whitespace_only_args_does_not_contact_server (self ):
178134 cli = self ._make_cli ()
179135 with (
180136 patch (
@@ -202,8 +158,8 @@ def test_valid_version_calls_client(self):
202158 mock_client_cls .assert_called_once_with (cli .host , cli .port )
203159 mock_instance .updateSoftware .assert_called_once_with ("9.0.18" , timeout = 600 )
204160
205- def test_git_url_with_spaces_passes_stripped_version (self ):
206- """Spaces are stripped from the outer edges but the version body is preserved ."""
161+ def test_git_url_with_spaces_passes_full_version_to_server (self ):
162+ """The version body (including internal spaces) is forwarded as-is ."""
207163 cli = self ._make_cli ()
208164 with (
209165 patch (
0 commit comments