77
88from unittest .mock import patch , MagicMock
99
10- from specify_cli import check_tool
10+ import pytest
11+ from typer .testing import CliRunner
12+
13+ from specify_cli import app , check_tool
14+ from tests .conftest import strip_ansi
15+
16+
17+ runner = CliRunner ()
1118
1219
1320class TestCheckToolClaude :
@@ -103,4 +110,89 @@ def fake_which(name):
103110 return "/usr/bin/kiro" if name == "kiro" else None
104111
105112 with patch ("shutil.which" , side_effect = fake_which ):
106- assert check_tool ("kiro-cli" ) is True
113+ assert check_tool ("kiro-cli" ) is True
114+
115+
116+ class TestCheckLatest :
117+ """`specify check --latest` should opt into release checks."""
118+
119+ def test_check_without_latest_does_not_fetch_latest_release (self ):
120+ with (
121+ patch ("specify_cli.check_tool" , return_value = True ),
122+ patch (
123+ "specify_cli._fetch_latest_release_tag" ,
124+ side_effect = AssertionError ("latest release lookup should not run" ),
125+ ) as fetch_latest ,
126+ ):
127+ result = runner .invoke (app , ["check" ])
128+
129+ assert result .exit_code == 0
130+ fetch_latest .assert_not_called ()
131+
132+ def test_check_latest_reports_update_available (self ):
133+ with (
134+ patch ("specify_cli.check_tool" , return_value = True ),
135+ patch ("specify_cli._get_installed_version" , return_value = "0.8.2" ),
136+ patch ("specify_cli._fetch_latest_release_tag" , return_value = ("v0.8.8" , None )),
137+ ):
138+ result = runner .invoke (app , ["check" , "--latest" ])
139+
140+ output = strip_ansi (result .output )
141+ assert result .exit_code == 0
142+ assert "Specify CLI" in output
143+ assert "Update available" in output
144+ assert "0.8.2" in output
145+ assert "0.8.8" in output
146+ assert "git+https://github.com/github/spec-kit.git@v0.8.8" in output
147+
148+ def test_check_latest_reports_up_to_date (self ):
149+ with (
150+ patch ("specify_cli.check_tool" , return_value = True ),
151+ patch ("specify_cli._get_installed_version" , return_value = "0.8.8" ),
152+ patch ("specify_cli._fetch_latest_release_tag" , return_value = ("v0.8.8" , None )),
153+ ):
154+ result = runner .invoke (app , ["check" , "--latest" ])
155+
156+ output = strip_ansi (result .output )
157+ assert result .exit_code == 0
158+ assert "Specify CLI" in output
159+ assert "Up to date: 0.8.8" in output
160+ assert "Update available" not in output
161+
162+ def test_check_latest_handles_unknown_installed_version (self ):
163+ with (
164+ patch ("specify_cli.check_tool" , return_value = True ),
165+ patch ("specify_cli._get_installed_version" , return_value = "unknown" ),
166+ patch ("specify_cli._fetch_latest_release_tag" , return_value = ("v0.8.8" , None )),
167+ ):
168+ result = runner .invoke (app , ["check" , "--latest" ])
169+
170+ output = strip_ansi (result .output )
171+ assert result .exit_code == 0
172+ assert "Specify CLI" in output
173+ assert "Current version could not be determined" in output
174+ assert "Latest release: 0.8.8" in output
175+ assert "git+https://github.com/github/spec-kit.git@v0.8.8" in output
176+
177+ @pytest .mark .parametrize (
178+ "failure_reason" ,
179+ [
180+ "offline or timeout" ,
181+ "rate limited (configure ~/.specify/auth.json with a GitHub token)" ,
182+ ],
183+ )
184+ def test_check_latest_handles_lookup_failure (self , failure_reason ):
185+ with (
186+ patch ("specify_cli.check_tool" , return_value = True ),
187+ patch ("specify_cli._get_installed_version" , return_value = "0.8.2" ),
188+ patch ("specify_cli._fetch_latest_release_tag" , return_value = (None , failure_reason )),
189+ ):
190+ result = runner .invoke (app , ["check" , "--latest" ])
191+
192+ output = strip_ansi (result .output )
193+ normalized = " " .join (output .split ())
194+ normalized_reason = " " .join (failure_reason .split ())
195+ assert result .exit_code == 0
196+ assert "Specify CLI" in output
197+ assert "Installed: 0.8.2" in output
198+ assert f"Could not check latest release: { normalized_reason } " in normalized
0 commit comments