1+ import json
12import os
3+ import re
4+ import uuid
5+ from importlib .metadata import version
26from unittest .mock import patch
37
48from click .testing import CliRunner
9+ from packaging .specifiers import SpecifierSet
510
611from uipath ._cli import cli
712from uipath ._cli .middlewares import MiddlewareResult
@@ -17,6 +22,18 @@ def test_new_project_creation(self, runner: CliRunner, temp_dir: str) -> None:
1722 assert os .path .exists ("main.py" )
1823 assert os .path .exists ("pyproject.toml" )
1924
25+ def test_new_project_writes_uipath_json_id (
26+ self , runner : CliRunner , temp_dir : str
27+ ) -> None :
28+ """uipath.json gets a GUID id up front so later commands don't warn."""
29+ with runner .isolated_filesystem (temp_dir = temp_dir ):
30+ result = runner .invoke (cli , ["new" , "my_project" ])
31+ assert result .exit_code == 0
32+ with open ("uipath.json" ) as f :
33+ config = json .load (f )
34+ uuid .UUID (config ["id" ])
35+ assert config ["functions" ] == {"main" : "main.py:main" }
36+
2037 def test_new_project_without_name (self , runner : CliRunner , temp_dir : str ) -> None :
2138 """Test creating a new project without specifying a name."""
2239 with runner .isolated_filesystem (temp_dir = temp_dir ):
@@ -79,3 +96,72 @@ def test_new_project_error_handling(self, runner: CliRunner, temp_dir: str) -> N
7996 result = runner .invoke (cli , ["new" , "my_project" ])
8097 assert result .exit_code == 1
8198 assert "Created 'main.py' file." not in result .output
99+
100+
101+ class TestUipathDependencySpec :
102+ """The scaffolded pin must follow the installed uipath version."""
103+
104+ def _written_pin (self , temp_dir : str ) -> str :
105+ from uipath ._cli .cli_new import generate_pyproject
106+
107+ generate_pyproject (temp_dir , "demo" )
108+ with open (os .path .join (temp_dir , "pyproject.toml" )) as f :
109+ content = f .read ()
110+ match = re .search (r'"(uipath[^"]*)"' , content )
111+ assert match is not None , content
112+ return match .group (1 )
113+
114+ def test_pin_derived_from_installed_version (self , temp_dir : str ) -> None :
115+ with patch ("uipath._cli._get_safe_version" , return_value = "2.14.7" ):
116+ assert self ._written_pin (temp_dir ) == "uipath>=2.14.0, <2.15.0"
117+
118+ def test_pin_strips_prerelease_suffix (self , temp_dir : str ) -> None :
119+ with patch ("uipath._cli._get_safe_version" , return_value = "2.15.0rc1" ):
120+ assert self ._written_pin (temp_dir ) == "uipath>=2.15.0, <2.16.0"
121+
122+ def test_pin_falls_back_when_version_unknown (self , temp_dir : str ) -> None :
123+ from uipath ._cli .cli_new import _fallback_uipath_dependency_spec , console
124+
125+ # _get_safe_version() returns "unknown" on PackageNotFoundError.
126+ with (
127+ patch ("uipath._cli._get_safe_version" , return_value = "unknown" ),
128+ patch .object (console , "warning" ) as mock_warning ,
129+ ):
130+ assert self ._written_pin (temp_dir ) == _fallback_uipath_dependency_spec ()
131+ mock_warning .assert_called_once ()
132+ assert (
133+ "Could not determine the installed 'uipath' version"
134+ in (mock_warning .call_args .args [0 ])
135+ )
136+
137+ def test_fallback_pin_admits_installed_uipath (self ) -> None :
138+ """Guard: a release PR that forgets to bump FALLBACK_UIPATH_MINOR fails CI."""
139+ from uipath ._cli .cli_new import _fallback_uipath_dependency_spec
140+
141+ spec = _fallback_uipath_dependency_spec ().removeprefix ("uipath" )
142+ installed = version ("uipath" )
143+ assert SpecifierSet (spec ).contains (installed , prereleases = True ), (
144+ f"fallback pin '{ spec } ' does not admit installed uipath { installed } ; "
145+ "bump FALLBACK_UIPATH_MINOR in cli_new.py"
146+ )
147+
148+ def test_scaffolded_pin_contains_installed_uipath (
149+ self , runner : CliRunner , temp_dir : str
150+ ) -> None :
151+ """Regression guard: runs against the real installed package, not a mock.
152+
153+ A stale hard-coded range would make ``uv sync`` downgrade the project's
154+ venv right after ``uipath new``.
155+ """
156+ with runner .isolated_filesystem (temp_dir = temp_dir ):
157+ result = runner .invoke (cli , ["new" , "demo" ])
158+ assert result .exit_code == 0
159+ with open ("pyproject.toml" ) as f :
160+ content = f .read ()
161+ match = re .search (r'"uipath([^"]*)"' , content )
162+ assert match is not None , content
163+ pin = SpecifierSet (match .group (1 ))
164+ installed = version ("uipath" )
165+ assert pin .contains (installed , prereleases = True ), (
166+ f"scaffolded pin '{ pin } ' does not contain installed uipath { installed } "
167+ )
0 commit comments