Co-authored-by: Clément Drouin <clement.drouin@mistral.ai> Co-authored-by: Clément Sirieix <clement.sirieix@mistral.ai> Co-authored-by: Cyprien <courtot.c@gmail.com> Co-authored-by: Guillaume LE GOFF <guillaume.lgf@gmail.com> Co-authored-by: Jean Burellier <sheplu@users.noreply.github.com> Co-authored-by: Kim-Adeline Miguel <51720070+kimadeline@users.noreply.github.com> Co-authored-by: Mathias Gesbert <mathias.gesbert@mistral.ai> Co-authored-by: Nelson PROIA <144663685+Nelson-PROIA@users.noreply.github.com> Co-authored-by: Paul VEZIA <166131032+le-codeur-rapide@users.noreply.github.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: josephine-delas <57808586+josephine-delas@users.noreply.github.com> Co-authored-by: Mistral Vibe <vibe@mistral.ai>
307 lines
8.9 KiB
Python
307 lines
8.9 KiB
Python
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
import keyring
|
|
import pytest
|
|
|
|
from vibe.core.config import DEFAULT_MISTRAL_API_ENV_KEY, ProviderConfig
|
|
from vibe.core.types import Backend
|
|
from vibe.setup.auth import AuthState, AuthStateKind, assess_auth_state
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def disable_keyring(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.setattr(keyring, "get_password", lambda service, username: None)
|
|
|
|
|
|
def _mistral_provider(
|
|
*, api_key_env_var: str = DEFAULT_MISTRAL_API_ENV_KEY
|
|
) -> ProviderConfig:
|
|
return ProviderConfig(
|
|
name="mistral",
|
|
api_base="https://api.mistral.ai/v1",
|
|
api_key_env_var=api_key_env_var,
|
|
browser_auth_base_url="https://console.mistral.ai",
|
|
browser_auth_api_base_url="https://console.mistral.ai/api",
|
|
backend=Backend.MISTRAL,
|
|
)
|
|
|
|
|
|
def _generic_provider(
|
|
*, name: str = "custom", api_key_env_var: str = "CUSTOM_API_KEY"
|
|
) -> ProviderConfig:
|
|
return ProviderConfig(
|
|
name=name,
|
|
api_base="https://custom.example/v1",
|
|
api_key_env_var=api_key_env_var,
|
|
backend=Backend.GENERIC,
|
|
)
|
|
|
|
|
|
def _write_env_file(path: Path, content: str) -> None:
|
|
path.write_text(content, encoding="utf-8")
|
|
|
|
|
|
def test_assess_signed_out_when_provider_requires_key_without_any_source(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
state = assess_auth_state(
|
|
_mistral_provider(), env_path=tmp_path / ".env", environ={}
|
|
)
|
|
|
|
assert state == AuthState(
|
|
kind=AuthStateKind.SIGNED_OUT,
|
|
can_use_active_provider=False,
|
|
sign_out_available=False,
|
|
env_key=DEFAULT_MISTRAL_API_ENV_KEY,
|
|
)
|
|
|
|
|
|
def test_assess_auth_not_required_when_provider_has_no_api_key_env_var(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
state = assess_auth_state(
|
|
_generic_provider(name="llamacpp", api_key_env_var=""),
|
|
env_path=tmp_path / ".env",
|
|
environ={},
|
|
)
|
|
|
|
assert state == AuthState(
|
|
kind=AuthStateKind.AUTH_NOT_REQUIRED,
|
|
can_use_active_provider=True,
|
|
sign_out_available=False,
|
|
env_key=None,
|
|
)
|
|
|
|
|
|
def test_assess_vibe_home_env_file_when_default_key_is_in_dotenv(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
env_path = tmp_path / ".env"
|
|
_write_env_file(env_path, f"{DEFAULT_MISTRAL_API_ENV_KEY}=file-key\n")
|
|
|
|
state = assess_auth_state(_mistral_provider(), env_path=env_path, environ={})
|
|
|
|
assert state == AuthState(
|
|
kind=AuthStateKind.VIBE_HOME_ENV_FILE,
|
|
can_use_active_provider=True,
|
|
sign_out_available=True,
|
|
env_key=DEFAULT_MISTRAL_API_ENV_KEY,
|
|
)
|
|
|
|
|
|
def test_assess_process_env_when_default_key_is_only_in_process_env(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
state = assess_auth_state(
|
|
_mistral_provider(),
|
|
env_path=tmp_path / ".env",
|
|
environ={DEFAULT_MISTRAL_API_ENV_KEY: "process-key"},
|
|
)
|
|
|
|
assert state == AuthState(
|
|
kind=AuthStateKind.PROCESS_ENV,
|
|
can_use_active_provider=True,
|
|
sign_out_available=False,
|
|
env_key=DEFAULT_MISTRAL_API_ENV_KEY,
|
|
)
|
|
|
|
|
|
def test_assess_process_env_when_process_env_and_dotenv_both_exist(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
env_path = tmp_path / ".env"
|
|
_write_env_file(env_path, f"{DEFAULT_MISTRAL_API_ENV_KEY}=file-key\n")
|
|
|
|
state = assess_auth_state(
|
|
_mistral_provider(),
|
|
env_path=env_path,
|
|
environ={DEFAULT_MISTRAL_API_ENV_KEY: "file-key"},
|
|
process_env_had_value_before_dotenv_load=True,
|
|
)
|
|
|
|
assert state == AuthState(
|
|
kind=AuthStateKind.PROCESS_ENV,
|
|
can_use_active_provider=True,
|
|
sign_out_available=False,
|
|
env_key=DEFAULT_MISTRAL_API_ENV_KEY,
|
|
)
|
|
|
|
|
|
def test_assess_vibe_home_env_file_when_dotenv_did_not_override_process_env(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
env_path = tmp_path / ".env"
|
|
_write_env_file(env_path, f"{DEFAULT_MISTRAL_API_ENV_KEY}=file-key\n")
|
|
|
|
state = assess_auth_state(
|
|
_mistral_provider(),
|
|
env_path=env_path,
|
|
environ={DEFAULT_MISTRAL_API_ENV_KEY: "file-key"},
|
|
process_env_had_value_before_dotenv_load=False,
|
|
)
|
|
|
|
assert state == AuthState(
|
|
kind=AuthStateKind.VIBE_HOME_ENV_FILE,
|
|
can_use_active_provider=True,
|
|
sign_out_available=True,
|
|
env_key=DEFAULT_MISTRAL_API_ENV_KEY,
|
|
)
|
|
|
|
|
|
def test_assess_vibe_home_env_file_is_sign_out_eligible_even_without_browser_sign_in(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
env_path = tmp_path / ".env"
|
|
_write_env_file(env_path, f"{DEFAULT_MISTRAL_API_ENV_KEY}=file-key\n")
|
|
provider = ProviderConfig(
|
|
name="mistral",
|
|
api_base="https://api.mistral.ai/v1",
|
|
api_key_env_var=DEFAULT_MISTRAL_API_ENV_KEY,
|
|
backend=Backend.GENERIC,
|
|
)
|
|
assert not provider.supports_browser_sign_in
|
|
|
|
state = assess_auth_state(provider, env_path=env_path, environ={})
|
|
|
|
assert state == AuthState(
|
|
kind=AuthStateKind.VIBE_HOME_ENV_FILE,
|
|
can_use_active_provider=True,
|
|
sign_out_available=True,
|
|
env_key=DEFAULT_MISTRAL_API_ENV_KEY,
|
|
)
|
|
|
|
|
|
def test_assess_unsupported_provider_when_custom_env_key_is_set_in_dotenv(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
env_path = tmp_path / ".env"
|
|
_write_env_file(env_path, "CUSTOM_API_KEY=file-key\n")
|
|
|
|
state = assess_auth_state(
|
|
_mistral_provider(api_key_env_var="CUSTOM_API_KEY"),
|
|
env_path=env_path,
|
|
environ={},
|
|
)
|
|
|
|
assert state == AuthState(
|
|
kind=AuthStateKind.UNSUPPORTED_PROVIDER,
|
|
can_use_active_provider=True,
|
|
sign_out_available=False,
|
|
env_key="CUSTOM_API_KEY",
|
|
)
|
|
|
|
|
|
def test_assess_unsupported_provider_when_custom_provider_uses_process_env(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
state = assess_auth_state(
|
|
_generic_provider(),
|
|
env_path=tmp_path / ".env",
|
|
environ={"CUSTOM_API_KEY": "process-key"},
|
|
)
|
|
|
|
assert state == AuthState(
|
|
kind=AuthStateKind.UNSUPPORTED_PROVIDER,
|
|
can_use_active_provider=True,
|
|
sign_out_available=False,
|
|
env_key="CUSTOM_API_KEY",
|
|
)
|
|
|
|
|
|
def test_assess_empty_dotenv_value_as_signed_out(tmp_path: Path) -> None:
|
|
env_path = tmp_path / ".env"
|
|
_write_env_file(env_path, f"{DEFAULT_MISTRAL_API_ENV_KEY}=\n")
|
|
|
|
state = assess_auth_state(_mistral_provider(), env_path=env_path, environ={})
|
|
|
|
assert state == AuthState(
|
|
kind=AuthStateKind.SIGNED_OUT,
|
|
can_use_active_provider=False,
|
|
sign_out_available=False,
|
|
env_key=DEFAULT_MISTRAL_API_ENV_KEY,
|
|
)
|
|
|
|
|
|
def test_assess_os_keyring_when_default_key_is_in_keyring(
|
|
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
monkeypatch.setattr(
|
|
keyring, "get_password", lambda service, username: "keyring-key"
|
|
)
|
|
|
|
state = assess_auth_state(
|
|
_mistral_provider(), env_path=tmp_path / ".env", environ={}
|
|
)
|
|
|
|
assert state == AuthState(
|
|
kind=AuthStateKind.OS_KEYRING,
|
|
can_use_active_provider=True,
|
|
sign_out_available=True,
|
|
env_key=DEFAULT_MISTRAL_API_ENV_KEY,
|
|
)
|
|
|
|
|
|
def test_assess_os_keyring_reuses_cached_keyring_lookup(
|
|
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
calls: list[tuple[str, str]] = []
|
|
|
|
def _get_password(service: str, username: str) -> str:
|
|
calls.append((service, username))
|
|
return "keyring-key"
|
|
|
|
monkeypatch.setattr(keyring, "get_password", _get_password)
|
|
provider = _mistral_provider()
|
|
env_path = tmp_path / ".env"
|
|
|
|
assert (
|
|
assess_auth_state(provider, env_path=env_path, environ={}).kind
|
|
== AuthStateKind.OS_KEYRING
|
|
)
|
|
assert (
|
|
assess_auth_state(provider, env_path=env_path, environ={}).kind
|
|
== AuthStateKind.OS_KEYRING
|
|
)
|
|
assert calls == [("ai.mistral.vibe", DEFAULT_MISTRAL_API_ENV_KEY)]
|
|
|
|
|
|
def test_assess_vibe_home_env_file_when_dotenv_and_keyring_both_have_value(
|
|
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
# resolve_api_key reads the .env-injected os.environ value before the keyring,
|
|
# so an overlap must be reported as the .env file, not OS keyring.
|
|
monkeypatch.setattr(
|
|
keyring, "get_password", lambda service, username: "keyring-key"
|
|
)
|
|
env_path = tmp_path / ".env"
|
|
_write_env_file(env_path, f"{DEFAULT_MISTRAL_API_ENV_KEY}=file-key\n")
|
|
|
|
state = assess_auth_state(_mistral_provider(), env_path=env_path, environ={})
|
|
|
|
assert state == AuthState(
|
|
kind=AuthStateKind.VIBE_HOME_ENV_FILE,
|
|
can_use_active_provider=True,
|
|
sign_out_available=True,
|
|
env_key=DEFAULT_MISTRAL_API_ENV_KEY,
|
|
)
|
|
|
|
|
|
def test_assess_unsupported_provider_when_custom_key_is_in_keyring(
|
|
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
monkeypatch.setattr(
|
|
keyring, "get_password", lambda service, username: "keyring-key"
|
|
)
|
|
|
|
state = assess_auth_state(
|
|
_generic_provider(), env_path=tmp_path / ".env", environ={}
|
|
)
|
|
|
|
assert state == AuthState(
|
|
kind=AuthStateKind.UNSUPPORTED_PROVIDER,
|
|
can_use_active_provider=True,
|
|
sign_out_available=False,
|
|
env_key="CUSTOM_API_KEY",
|
|
)
|