Co-authored-by: Quentin Torroba <quentin.torroba@mistral.ai>
Co-authored-by: Michel Thomazo <michel.thomazo@mistral.ai>
Co-authored-by: Clément Drouin <clement.drouin@mistral.ai>
Co-authored-by: Vincent Guilloux <vincent.guilloux@mistral.ai>
Co-authored-by: Clément Siriex <clement.sirieix@mistral.ai>
Co-authored-by: Kim-Adeline Miguel <kimadeline.miguel@mistral.ai>
Co-authored-by: Nicolas Karolak <nicolas@karolak.fr>
This commit is contained in:
Mathias Gesbert 2026-02-11 18:17:30 +01:00 committed by GitHub
parent 9809cfc831
commit 51fecc67d9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
176 changed files with 8652 additions and 4451 deletions

View file

@ -1,14 +1,15 @@
from __future__ import annotations
import asyncio
from collections.abc import Callable
from pathlib import Path
import time
from typing import Protocol
from unittest.mock import patch
import pytest
from textual.app import Notification
from tests.conftest import build_test_vibe_app, build_test_vibe_config
from tests.update_notifier.adapters.fake_update_cache_repository import (
FakeUpdateCacheRepository,
)
@ -18,12 +19,35 @@ from vibe.cli.textual_ui.widgets.messages import WhatsNewMessage
from vibe.cli.update_notifier import (
Update,
UpdateCache,
UpdateCacheRepository,
UpdateGateway,
UpdateGatewayCause,
UpdateGatewayError,
)
from vibe.core.agent_loop import AgentLoop
from vibe.core.agents.models import BuiltinAgentName
from vibe.core.config import SessionLoggingConfig, VibeConfig
from vibe.core.config import VibeConfig
TEST_CURRENT_VERSION = "0.1.0"
@pytest.fixture
def build_update_test_app(
vibe_config_with_update_checks_enabled: VibeConfig,
) -> Callable[..., VibeApp]:
def _build(
*,
update_notifier: UpdateGateway | None = None,
update_cache_repository: UpdateCacheRepository | None = None,
config: VibeConfig | None = None,
current_version: str = TEST_CURRENT_VERSION,
) -> VibeApp:
return build_test_vibe_app(
update_notifier=update_notifier,
update_cache_repository=update_cache_repository,
config=config or vibe_config_with_update_checks_enabled,
current_version=current_version,
)
return _build
async def _wait_for_notification(
@ -57,54 +81,15 @@ async def _assert_no_notifications(
@pytest.fixture
def vibe_config_with_update_checks_enabled() -> VibeConfig:
return VibeConfig(
session_logging=SessionLoggingConfig(enabled=False), enable_update_checks=True
)
class VibeAppFactory(Protocol):
def __call__(
self,
*,
notifier: FakeUpdateGateway,
update_cache_repository: FakeUpdateCacheRepository | None = None,
config: VibeConfig | None = None,
initial_agent_name: str = BuiltinAgentName.DEFAULT,
current_version: str = "0.1.0",
) -> VibeApp: ...
@pytest.fixture
def make_vibe_app(vibe_config_with_update_checks_enabled: VibeConfig) -> VibeAppFactory:
update_cache_repository = FakeUpdateCacheRepository()
def _make_app(
*,
notifier: FakeUpdateGateway,
update_cache_repository: FakeUpdateCacheRepository
| None = update_cache_repository,
config: VibeConfig | None = None,
initial_agent_name: str = BuiltinAgentName.DEFAULT,
current_version: str = "0.1.0",
) -> VibeApp:
app_config = config or vibe_config_with_update_checks_enabled
agent_loop = AgentLoop(
app_config, agent_name=initial_agent_name, enable_streaming=False
)
return VibeApp(
agent_loop=agent_loop,
update_notifier=notifier,
update_cache_repository=update_cache_repository,
current_version=current_version,
)
return _make_app
return build_test_vibe_config(enable_update_checks=True)
@pytest.mark.asyncio
async def test_ui_displays_update_notification(make_vibe_app: VibeAppFactory) -> None:
async def test_ui_displays_update_notification(
build_update_test_app: Callable[..., VibeApp],
) -> None:
notifier = FakeUpdateGateway(update=Update(latest_version="0.2.0"))
app = make_vibe_app(notifier=notifier)
app = build_update_test_app(update_notifier=notifier)
async with app.run_test() as pilot:
notification = await _wait_for_notification(app, pilot, timeout=0.3)
@ -119,10 +104,10 @@ async def test_ui_displays_update_notification(make_vibe_app: VibeAppFactory) ->
@pytest.mark.asyncio
async def test_ui_does_not_display_update_notification_when_not_available(
make_vibe_app: VibeAppFactory,
build_update_test_app: Callable[..., VibeApp],
) -> None:
notifier = FakeUpdateGateway(update=None)
app = make_vibe_app(notifier=notifier)
app = build_update_test_app(update_notifier=notifier)
async with app.run_test() as pilot:
await _assert_no_notifications(app, pilot, timeout=0.3)
@ -131,12 +116,12 @@ async def test_ui_does_not_display_update_notification_when_not_available(
@pytest.mark.asyncio
async def test_ui_displays_warning_toast_when_check_fails(
make_vibe_app: VibeAppFactory,
build_update_test_app: Callable[..., VibeApp],
) -> None:
notifier = FakeUpdateGateway(
error=UpdateGatewayError(cause=UpdateGatewayCause.FORBIDDEN)
)
app = make_vibe_app(notifier=notifier)
app = build_update_test_app(update_notifier=notifier)
async with app.run_test() as pilot:
await pilot.pause(0.3)
@ -150,12 +135,10 @@ async def test_ui_displays_warning_toast_when_check_fails(
@pytest.mark.asyncio
async def test_ui_does_not_invoke_gateway_nor_show_error_notification_when_update_checks_are_disabled(
vibe_config_with_update_checks_enabled: VibeConfig, make_vibe_app: VibeAppFactory
build_update_test_app: Callable[..., VibeApp], vibe_config: VibeConfig
) -> None:
config = vibe_config_with_update_checks_enabled
config.enable_update_checks = False
notifier = FakeUpdateGateway(update=Update(latest_version="0.2.0"))
app = make_vibe_app(notifier=notifier, config=config)
app = build_update_test_app(update_notifier=notifier, config=vibe_config)
async with app.run_test() as pilot:
await _assert_no_notifications(app, pilot, timeout=0.3)
@ -165,16 +148,16 @@ async def test_ui_does_not_invoke_gateway_nor_show_error_notification_when_updat
@pytest.mark.asyncio
async def test_ui_does_not_show_toast_when_update_is_known_in_recent_cache_already(
make_vibe_app: VibeAppFactory,
) -> None:
build_update_test_app: Callable[..., VibeApp],
):
timestamp_two_hours_ago = int(time.time()) - 2 * 60 * 60
notifier = FakeUpdateGateway(update=Update(latest_version="0.2.0"))
update_cache = UpdateCache(
latest_version="0.2.0", stored_at_timestamp=timestamp_two_hours_ago
)
update_cache_repository = FakeUpdateCacheRepository(update_cache=update_cache)
app = make_vibe_app(
notifier=notifier, update_cache_repository=update_cache_repository
app = build_update_test_app(
update_notifier=notifier, update_cache_repository=update_cache_repository
)
async with app.run_test() as pilot:
@ -185,7 +168,7 @@ async def test_ui_does_not_show_toast_when_update_is_known_in_recent_cache_alrea
@pytest.mark.asyncio
async def test_ui_does_show_toast_when_cache_entry_is_too_old(
make_vibe_app: VibeAppFactory,
build_update_test_app: Callable[..., VibeApp],
) -> None:
timestamp_two_days_ago = int(time.time()) - 2 * 24 * 60 * 60
notifier = FakeUpdateGateway(update=Update(latest_version="0.2.0"))
@ -193,8 +176,8 @@ async def test_ui_does_show_toast_when_cache_entry_is_too_old(
latest_version="0.2.0", stored_at_timestamp=timestamp_two_days_ago
)
update_cache_repository = FakeUpdateCacheRepository(update_cache=update_cache)
app = make_vibe_app(
notifier=notifier, update_cache_repository=update_cache_repository
app = build_update_test_app(
update_notifier=notifier, update_cache_repository=update_cache_repository
)
async with app.run_test() as pilot:
@ -232,7 +215,7 @@ async def _wait_for_whats_new_message(
@pytest.mark.asyncio
async def test_ui_displays_whats_new_message_when_content_exists(
make_vibe_app: VibeAppFactory, tmp_path: Path
build_update_test_app: Callable[..., VibeApp], tmp_path: Path
) -> None:
notifier = FakeUpdateGateway(update=None)
cache = UpdateCache(
@ -241,8 +224,10 @@ async def test_ui_displays_whats_new_message_when_content_exists(
seen_whats_new_version=None,
)
repository = FakeUpdateCacheRepository(update_cache=cache)
app = make_vibe_app(
notifier=notifier, update_cache_repository=repository, current_version="1.0.0"
app = build_update_test_app(
update_notifier=notifier,
update_cache_repository=repository,
current_version="1.0.0",
)
whats_new_content = "# What's New\n\n- Feature 1\n- Feature 2"
@ -262,7 +247,7 @@ async def test_ui_displays_whats_new_message_when_content_exists(
@pytest.mark.asyncio
async def test_ui_does_not_display_whats_new_when_seen_whats_new_version_matches(
make_vibe_app: VibeAppFactory, tmp_path: Path
build_update_test_app: Callable[..., VibeApp], tmp_path: Path
) -> None:
notifier = FakeUpdateGateway(update=None)
cache = UpdateCache(
@ -271,8 +256,10 @@ async def test_ui_does_not_display_whats_new_when_seen_whats_new_version_matches
seen_whats_new_version="1.0.0",
)
repository = FakeUpdateCacheRepository(update_cache=cache)
app = make_vibe_app(
notifier=notifier, update_cache_repository=repository, current_version="1.0.0"
app = build_update_test_app(
update_notifier=notifier,
update_cache_repository=repository,
current_version="1.0.0",
)
with patch("vibe.cli.update_notifier.whats_new.VIBE_ROOT", tmp_path):
@ -291,7 +278,7 @@ async def test_ui_does_not_display_whats_new_when_seen_whats_new_version_matches
@pytest.mark.asyncio
async def test_ui_does_not_display_whats_new_when_file_is_empty(
make_vibe_app: VibeAppFactory, tmp_path: Path
build_update_test_app: Callable[..., VibeApp], tmp_path: Path
) -> None:
notifier = FakeUpdateGateway(update=None)
cache = UpdateCache(
@ -300,8 +287,10 @@ async def test_ui_does_not_display_whats_new_when_file_is_empty(
seen_whats_new_version=None,
)
repository = FakeUpdateCacheRepository(update_cache=cache)
app = make_vibe_app(
notifier=notifier, update_cache_repository=repository, current_version="1.0.0"
app = build_update_test_app(
update_notifier=notifier,
update_cache_repository=repository,
current_version="1.0.0",
)
with patch("vibe.cli.update_notifier.whats_new.VIBE_ROOT", tmp_path):
@ -323,7 +312,7 @@ async def test_ui_does_not_display_whats_new_when_file_is_empty(
@pytest.mark.asyncio
async def test_ui_does_not_display_whats_new_when_file_does_not_exist(
make_vibe_app: VibeAppFactory, tmp_path: Path
build_update_test_app: Callable[..., VibeApp], tmp_path: Path
) -> None:
notifier = FakeUpdateGateway(update=None)
cache = UpdateCache(
@ -332,8 +321,10 @@ async def test_ui_does_not_display_whats_new_when_file_does_not_exist(
seen_whats_new_version=None,
)
repository = FakeUpdateCacheRepository(update_cache=cache)
app = make_vibe_app(
notifier=notifier, update_cache_repository=repository, current_version="1.0.0"
app = build_update_test_app(
update_notifier=notifier,
update_cache_repository=repository,
current_version="1.0.0",
)
with patch("vibe.cli.update_notifier.whats_new.VIBE_ROOT", tmp_path):
@ -352,17 +343,13 @@ async def test_ui_does_not_display_whats_new_when_file_does_not_exist(
@pytest.mark.asyncio
async def test_ui_displays_success_notification_when_auto_update_succeeds(
make_vibe_app: VibeAppFactory,
build_update_test_app: Callable[..., VibeApp],
) -> None:
config = VibeConfig(
session_logging=SessionLoggingConfig(enabled=False),
enable_update_checks=True,
enable_auto_update=True,
)
config = build_test_vibe_config(enable_update_checks=True, enable_auto_update=True)
notifier = FakeUpdateGateway(update=Update(latest_version="0.2.0"))
with patch("vibe.cli.update_notifier.update.UPDATE_COMMANDS", ["true"]):
app = make_vibe_app(notifier=notifier, config=config)
app = build_update_test_app(update_notifier=notifier, config=config)
async with app.run_test() as pilot:
await pilot.pause(0.3)
@ -380,17 +367,13 @@ async def test_ui_displays_success_notification_when_auto_update_succeeds(
@pytest.mark.asyncio
async def test_ui_displays_update_notification_when_auto_update_fails(
make_vibe_app: VibeAppFactory,
build_update_test_app: Callable[..., VibeApp],
) -> None:
config = VibeConfig(
session_logging=SessionLoggingConfig(enabled=False),
enable_update_checks=True,
enable_auto_update=True,
)
config = build_test_vibe_config(enable_update_checks=True, enable_auto_update=True)
notifier = FakeUpdateGateway(update=Update(latest_version="0.2.0"))
with patch("vibe.cli.update_notifier.update.UPDATE_COMMANDS", ["false"]):
app = make_vibe_app(notifier=notifier, config=config)
app = build_update_test_app(update_notifier=notifier, config=config)
async with app.run_test() as pilot:
await pilot.pause(0.3)