Co-authored-by: Maxime Dolores <maxime.dolores@ext.mistral.ai>
Co-authored-by: Michel Thomazo <51709227+michelTho@users.noreply.github.com>
Co-authored-by: Peter Evers <peter.evers@mistral.ai>
Co-authored-by: Pierre Rossinès <pierre.rossines@mistral.ai>
Co-authored-by: Quentin <quentin.torroba@mistral.ai>
Co-authored-by: Val <102326092+vdeva@users.noreply.github.com>
Co-authored-by: Vincent G <10739306+VinceOPS@users.noreply.github.com>
Co-authored-by: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
Clément Drouin 2026-05-29 16:17:54 +02:00 committed by GitHub
parent 198277af3f
commit ad0d5c9520
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
61 changed files with 969 additions and 461 deletions

View file

@ -0,0 +1,140 @@
from __future__ import annotations
from collections.abc import Iterator
from dataclasses import dataclass
from datetime import UTC, datetime, timedelta
from ipaddress import IPv4Address
from pathlib import Path
import ssl
from cryptography import x509
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.x509.oid import ExtendedKeyUsageOID, NameOID
import pytest
from tests.e2e.mock_server import StreamingMockServer
from vibe.core.config import ModelConfig, ProviderConfig
from vibe.core.llm.backend.generic import GenericBackend
from vibe.core.types import Backend, LLMMessage, Role
from vibe.core.utils import build_ssl_context, configure_ssl_context
@dataclass(frozen=True)
class _TLSMaterial:
ca_file: str
cert_file: str
key_file: str
@dataclass(frozen=True)
class _HttpsStreamingMockServer:
server: StreamingMockServer
ca_file: str
@pytest.fixture
def https_streaming_mock_server(tmp_path: Path) -> Iterator[_HttpsStreamingMockServer]:
tls = _write_tls_material(tmp_path)
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ssl_context.load_cert_chain(certfile=tls.cert_file, keyfile=tls.key_file)
server = StreamingMockServer(ssl_context=ssl_context)
server.start()
try:
yield _HttpsStreamingMockServer(server=server, ca_file=tls.ca_file)
finally:
server.stop()
@pytest.mark.asyncio
async def test_generic_backend_streaming_uses_ssl_cert_file(
monkeypatch: pytest.MonkeyPatch,
https_streaming_mock_server: _HttpsStreamingMockServer,
) -> None:
monkeypatch.setenv("SSL_CERT_FILE", https_streaming_mock_server.ca_file)
monkeypatch.delenv("SSL_CERT_DIR", raising=False)
configure_ssl_context(enable_system_trust_store=False)
build_ssl_context.cache_clear()
chunks = []
try:
provider = ProviderConfig(
name="mock-provider",
api_base=https_streaming_mock_server.server.api_base,
api_key_env_var="MISTRAL_API_KEY",
backend=Backend.GENERIC,
)
model = ModelConfig(
name="mock-model", provider="mock-provider", alias="mock-model"
)
async with GenericBackend(provider=provider, timeout=5.0) as backend:
chunks = [
chunk
async for chunk in backend.complete_streaming(
model=model, messages=[LLMMessage(role=Role.user, content="Greet")]
)
]
finally:
configure_ssl_context(enable_system_trust_store=False)
build_ssl_context.cache_clear()
content = "".join(chunk.message.content or "" for chunk in chunks)
request_payload = https_streaming_mock_server.server.requests[-1]
assert content == "Hello from mock server"
assert request_payload.get("stream") is True
assert request_payload.get("model") == "mock-model"
def _write_tls_material(tmp_path: Path) -> _TLSMaterial:
now = datetime.now(UTC)
ca_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
ca_name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "Vibe test CA")])
ca_cert = (
x509.CertificateBuilder()
.subject_name(ca_name)
.issuer_name(ca_name)
.public_key(ca_key.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(now - timedelta(minutes=1))
.not_valid_after(now + timedelta(days=1))
.add_extension(x509.BasicConstraints(ca=True, path_length=None), critical=True)
.sign(ca_key, hashes.SHA256())
)
server_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
server_name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "127.0.0.1")])
server_cert = (
x509.CertificateBuilder()
.subject_name(server_name)
.issuer_name(ca_cert.subject)
.public_key(server_key.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(now - timedelta(minutes=1))
.not_valid_after(now + timedelta(days=1))
.add_extension(
x509.SubjectAlternativeName([x509.IPAddress(IPv4Address("127.0.0.1"))]),
critical=False,
)
.add_extension(x509.BasicConstraints(ca=False, path_length=None), critical=True)
.add_extension(
x509.ExtendedKeyUsage([ExtendedKeyUsageOID.SERVER_AUTH]), critical=False
)
.sign(ca_key, hashes.SHA256())
)
ca_file = tmp_path / "ca.pem"
cert_file = tmp_path / "server.pem"
key_file = tmp_path / "server.key"
ca_file.write_bytes(ca_cert.public_bytes(serialization.Encoding.PEM))
cert_file.write_bytes(server_cert.public_bytes(serialization.Encoding.PEM))
key_file.write_bytes(
server_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption(),
)
)
return _TLSMaterial(
ca_file=str(ca_file), cert_file=str(cert_file), key_file=str(key_file)
)