Co-authored-by: Clément Drouin <clement.drouin@mistral.ai>
Co-authored-by: Corentin André <corentin.andre@mistral.ai>
Co-authored-by: Guillaume LE GOFF <guillaume.lgf@gmail.com>
Co-authored-by: Kim-Adeline Miguel <51720070+kimadeline@users.noreply.github.com>
Co-authored-by: Maxime Dolores <maxime.dolores@ext.mistral.ai>
Co-authored-by: Nelson PROIA <144663685+Nelson-PROIA@users.noreply.github.com>
Co-authored-by: Peter Evers <pevers90@gmail.com>
Co-authored-by: Pierre Rossinès <pierre.rossines@mistral.ai>
Co-authored-by: Quentin <quentin.torroba@mistral.ai>
Co-authored-by: Vincent G <10739306+VinceOPS@users.noreply.github.com>
Co-authored-by: MichisGitIsKing <MichisGitIsKing@users.noreply.github.com>
Co-authored-by: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
Mathias Gesbert 2026-05-19 11:56:25 +02:00 committed by GitHub
parent 626f905186
commit 228f3c65a9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
158 changed files with 7235 additions and 916 deletions

View file

@ -1,7 +1,8 @@
from __future__ import annotations
from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from datetime import datetime, timedelta
from datetime import UTC, datetime, timedelta
from urllib.parse import urlencode
from vibe.setup.auth import (
@ -11,6 +12,7 @@ from vibe.setup.auth import (
BrowserSignInPollResult,
BrowserSignInProcess,
)
from vibe.setup.auth.browser_sign_in import BrowserSignInService
@dataclass
@ -42,6 +44,7 @@ class StubBrowserSignInGateway(BrowserSignInGateway):
self.polled_urls: list[str] = []
self.exchange_requests: list[ExchangeRequestPayload] = []
self.closed = False
self.poll_calls = 0
self.process_number = 0
async def create_process(self, code_challenge: str) -> BrowserSignInProcess:
@ -55,10 +58,10 @@ class StubBrowserSignInGateway(BrowserSignInGateway):
async def poll(self, poll_url: str) -> BrowserSignInPollResult:
self.polled_urls.append(poll_url)
self.poll_calls += 1
if not self._poll_results:
msg = "StubBrowserSignInGateway requires scripted poll results."
raise AssertionError(msg)
result = self._poll_results.pop(0)
if isinstance(result, BrowserSignInError):
raise result
@ -109,5 +112,80 @@ def build_poll_failed_error() -> BrowserSignInError:
)
def build_poll_results_from_outcomes(
outcomes: list[str],
) -> tuple[
list[BrowserSignInProcess], list[BrowserSignInPollResult | BrowserSignInError]
]:
processes: list[BrowserSignInProcess] = []
poll_results: list[BrowserSignInPollResult | BrowserSignInError] = []
now = datetime(2026, 3, 16, tzinfo=UTC)
for process_index, outcome in enumerate(outcomes, start=1):
process_id = f"process-{process_index}"
processes.append(build_sign_in_process(now, process_id=process_id))
match outcome:
case "completed":
poll_results.extend([
BrowserSignInPollResult(status="pending"),
BrowserSignInPollResult(
status="completed", exchange_token=f"exchange-{process_id}"
),
])
case "expired":
poll_results.append(BrowserSignInPollResult(status="expired"))
case "poll_failed":
poll_results.extend([
BrowserSignInPollResult(status="pending"),
build_poll_failed_error(),
build_poll_failed_error(),
build_poll_failed_error(),
])
case _:
msg = f"Unsupported browser sign-in outcome: {outcome}"
raise AssertionError(msg)
return processes, poll_results
async def noop_sleep(_: float) -> None:
return None
def build_browser_sign_in_service_factory(
outcomes: list[str],
*,
exchange_result: str = "sk-browser-onboarding-test-key",
open_browser: Callable[[str], bool] | None = None,
sleep: Callable[[float], Awaitable[None]] = noop_sleep,
now: Callable[[], datetime] | None = None,
) -> tuple[
StubBrowserSignInGateway,
Callable[[], BrowserSignInService],
list[BrowserSignInService],
]:
processes, poll_results = build_poll_results_from_outcomes(outcomes)
gateway = StubBrowserSignInGateway(
processes=processes, poll_results=poll_results, exchange_result=exchange_result
)
created_services: list[BrowserSignInService] = []
def build_service() -> BrowserSignInService:
service = BrowserSignInService(
gateway,
open_browser=open_browser or (lambda _: True),
sleep=sleep,
now=now
or (
lambda: (
datetime(2026, 3, 16, tzinfo=UTC)
+ timedelta(seconds=gateway.poll_calls)
)
),
poll_interval=0,
)
created_services.append(service)
return service
return gateway, build_service, created_services

View file

@ -23,6 +23,7 @@ from vibe.setup.auth import (
BrowserSignInPollResult,
BrowserSignInProcess,
BrowserSignInService,
BrowserSignInStatus,
)
TEST_NOW = datetime(2026, 3, 16, tzinfo=UTC)
@ -68,7 +69,7 @@ def build_test_service(
@pytest.mark.asyncio
async def test_authenticate_returns_api_key_after_pending_poll() -> None:
opened_urls: list[str] = []
statuses: list[str] = []
statuses: list[BrowserSignInStatus] = []
gateway, service = build_test_service(
poll_results=[
BrowserSignInPollResult(status="pending"),
@ -84,10 +85,10 @@ async def test_authenticate_returns_api_key_after_pending_poll() -> None:
assert api_key == "sk-browser-key"
assert opened_urls == [TEST_SIGN_IN_URL]
assert statuses == [
"opening_browser",
"waiting_for_browser_sign_in",
"exchanging",
"completed",
BrowserSignInStatus.OPENING_BROWSER,
BrowserSignInStatus.WAITING_FOR_BROWSER_SIGN_IN,
BrowserSignInStatus.EXCHANGING,
BrowserSignInStatus.COMPLETED,
]
assert gateway.polled_urls == [TEST_POLL_URL, TEST_POLL_URL]
assert gateway.exchange_requests[0].exchange_token == "exchange-1"