|
9 | 9 | from rich.console import Console |
10 | 10 |
|
11 | 11 | from basic_memory.cli.auto_update import ( |
| 12 | + AutoUpdateResult, |
12 | 13 | AutoUpdateStatus, |
13 | 14 | InstallSource, |
| 15 | + _is_interactive_session, |
14 | 16 | detect_install_source, |
15 | 17 | maybe_run_periodic_auto_update, |
16 | 18 | run_auto_update, |
@@ -43,6 +45,25 @@ def _base_config(tmp_path) -> BasicMemoryConfig: |
43 | 45 | return BasicMemoryConfig(projects={"main": {"path": str(tmp_path / "main")}}) |
44 | 46 |
|
45 | 47 |
|
| 48 | +def _result( |
| 49 | + status: AutoUpdateStatus, |
| 50 | + *, |
| 51 | + message: str | None, |
| 52 | + error: str | None = None, |
| 53 | +) -> AutoUpdateResult: |
| 54 | + return AutoUpdateResult( |
| 55 | + status=status, |
| 56 | + source=InstallSource.UV_TOOL, |
| 57 | + checked=True, |
| 58 | + update_available=status in {AutoUpdateStatus.UPDATE_AVAILABLE, AutoUpdateStatus.UPDATED}, |
| 59 | + updated=status == AutoUpdateStatus.UPDATED, |
| 60 | + latest_version="9.9.9", |
| 61 | + message=message, |
| 62 | + error=error, |
| 63 | + restart_recommended=status == AutoUpdateStatus.UPDATED, |
| 64 | + ) |
| 65 | + |
| 66 | + |
46 | 67 | def test_detect_install_source_variants(): |
47 | 68 | assert ( |
48 | 69 | detect_install_source("/opt/homebrew/Cellar/basic-memory/0.18.0/bin/python") |
@@ -271,3 +292,83 @@ def test_maybe_run_periodic_auto_update_non_interactive_has_no_console_output(): |
271 | 292 | ) |
272 | 293 | assert result is None |
273 | 294 | assert buf.getvalue() == "" |
| 295 | + |
| 296 | + |
| 297 | +def test_maybe_run_periodic_auto_update_prints_updated(monkeypatch): |
| 298 | + console, buf = _capture_console() |
| 299 | + monkeypatch.setattr( |
| 300 | + "basic_memory.cli.auto_update.run_auto_update", |
| 301 | + lambda **kwargs: _result( |
| 302 | + AutoUpdateStatus.UPDATED, |
| 303 | + message="Basic Memory was updated successfully.", |
| 304 | + ), |
| 305 | + ) |
| 306 | + |
| 307 | + result = maybe_run_periodic_auto_update("status", is_interactive=True, console=console) |
| 308 | + assert result is not None |
| 309 | + assert result.status == AutoUpdateStatus.UPDATED |
| 310 | + assert "updated successfully" in buf.getvalue().lower() |
| 311 | + |
| 312 | + |
| 313 | +def test_maybe_run_periodic_auto_update_prints_available(monkeypatch): |
| 314 | + console, buf = _capture_console() |
| 315 | + monkeypatch.setattr( |
| 316 | + "basic_memory.cli.auto_update.run_auto_update", |
| 317 | + lambda **kwargs: _result( |
| 318 | + AutoUpdateStatus.UPDATE_AVAILABLE, |
| 319 | + message="Update available (latest: 9.9.9).", |
| 320 | + ), |
| 321 | + ) |
| 322 | + |
| 323 | + result = maybe_run_periodic_auto_update("status", is_interactive=True, console=console) |
| 324 | + assert result is not None |
| 325 | + assert result.status == AutoUpdateStatus.UPDATE_AVAILABLE |
| 326 | + assert "update available" in buf.getvalue().lower() |
| 327 | + |
| 328 | + |
| 329 | +def test_maybe_run_periodic_auto_update_prints_failed_with_error(monkeypatch): |
| 330 | + console, buf = _capture_console() |
| 331 | + monkeypatch.setattr( |
| 332 | + "basic_memory.cli.auto_update.run_auto_update", |
| 333 | + lambda **kwargs: _result( |
| 334 | + AutoUpdateStatus.FAILED, |
| 335 | + message="Automatic update check failed.", |
| 336 | + error="network timeout", |
| 337 | + ), |
| 338 | + ) |
| 339 | + |
| 340 | + result = maybe_run_periodic_auto_update("status", is_interactive=True, console=console) |
| 341 | + assert result is not None |
| 342 | + assert result.status == AutoUpdateStatus.FAILED |
| 343 | + output = buf.getvalue().lower() |
| 344 | + assert "automatic update check failed" in output |
| 345 | + assert "network timeout" in output |
| 346 | + |
| 347 | + |
| 348 | +def test_maybe_run_periodic_auto_update_uses_interactive_probe_when_not_overridden(monkeypatch): |
| 349 | + console, buf = _capture_console() |
| 350 | + monkeypatch.setattr("basic_memory.cli.auto_update._is_interactive_session", lambda: True) |
| 351 | + monkeypatch.setattr( |
| 352 | + "basic_memory.cli.auto_update.run_auto_update", |
| 353 | + lambda **kwargs: _result( |
| 354 | + AutoUpdateStatus.UP_TO_DATE, |
| 355 | + message="Basic Memory is up to date.", |
| 356 | + ), |
| 357 | + ) |
| 358 | + |
| 359 | + result = maybe_run_periodic_auto_update("status", console=console) |
| 360 | + assert result is not None |
| 361 | + assert result.status == AutoUpdateStatus.UP_TO_DATE |
| 362 | + # UP_TO_DATE is intentionally silent for periodic checks. |
| 363 | + assert buf.getvalue() == "" |
| 364 | + |
| 365 | + |
| 366 | +def test_is_interactive_session_handles_closed_stdio(monkeypatch): |
| 367 | + class _BrokenStream: |
| 368 | + def isatty(self) -> bool: |
| 369 | + raise ValueError("I/O operation on closed file") |
| 370 | + |
| 371 | + monkeypatch.setattr("basic_memory.cli.auto_update.sys.stdin", _BrokenStream()) |
| 372 | + monkeypatch.setattr("basic_memory.cli.auto_update.sys.stdout", _BrokenStream()) |
| 373 | + |
| 374 | + assert _is_interactive_session() is False |
0 commit comments