Probar contra Axonium

¿Cómo construyo encima de esto sin gastar nada ni depender de un despliegue vivo?

No simules el SDK

Simular client.chat.completions.create prueba que llamaste a una función. No te dice si manejaste un 429, si tu bucle de herramientas sobrevive a un arguments truncado, ni si tu conciliación aguanta una repetición que no tiene fila de uso.

Simula el transporte. Cada SDK de aquí es un cliente HTTP y nada más, así que un gateway falso ejercita tu código contra el parseo real, el tipado de errores real y la lógica de reintentos real.

import httpx, respx
from axonium import Axonium

@respx.mock
def test_summariser_handles_a_rate_limit():
    respx.post("https://gw.test/oauth2/token").mock(return_value=httpx.Response(
        200, json={"access_token": "t", "token_type": "bearer", "expires_in": 300}))
    respx.post("https://gw.test/v1/chat/completions").mock(side_effect=[
        httpx.Response(429, headers={"Retry-After": "0"},
                       json={"type": "https://prometheus.internal/errors/rate-limit-exceeded-requests",
                             "status": 429}),
        httpx.Response(200, json=COMPLETION),
    ])

    with Axonium(gateway_base_url="https://gw.test", client_id="i", client_secret="s") as client:
        assert summarise(client, "...") == "..."
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path == "/oauth2/token" {
		_, _ = w.Write([]byte(`{"access_token":"t","token_type":"bearer","expires_in":300}`))
		return
	}
	w.Header().Set("Content-Type", "application/json")
	_, _ = w.Write(completionJSON)
}))
defer srv.Close()

client, err := axonium.New(axonium.Config{
	GatewayBaseURL: srv.URL, ClientID: "i", ClientSecret: "s",
})
let server = MockServer::start().await;
Mock::given(path("/oauth2/token"))
    .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
        "access_token": "t", "token_type": "bearer", "expires_in": 300
    })))
    .mount(&server)
    .await;
Mock::given(any())
    .respond_with(ResponseTemplate::new(200).set_body_json(completion))
    .mount(&server)
    .await;

let client = Client::new(Config {
    gateway_base_url: server.uri(),
    client_id: "i".into(),
    client_secret: "s".into(),
    ..Default::default()
})?;
// URLProtocol, through the configuration's own sessionConfiguration hook -- which exists for this.
final class FakeGateway: URLProtocol { /* canInit, startLoading, ... */ }

let session = URLSessionConfiguration.ephemeral
session.protocolClasses = [FakeGateway.self]

let client = try AxoniumClient(
    configuration: AxoniumConfiguration(
        gatewayBaseURL: "https://gw.test", clientID: "i", clientSecret: "s",
        sessionConfiguration: session))
// No mocking library and no patched global: `fetch` is injected, so the fake IS the dependency.
// That is why this package's own suite has a test asserting no HTTP-mocking library is installed.
const client = new Axonium({
  gatewayBaseURL: "https://gw.test",
  clientId: "i",
  clientSecret: "s",
  fetch: async (url, init) => {
    if (String(url).endsWith("/oauth2/token")) {
      return Response.json({ access_token: "t", token_type: "bearer", expires_in: 300 });
    }
    return Response.json(COMPLETION);
  },
});

Haz que las esperas dejen de costar reloj

Un test de reintentos que honra un Retry-After real tarda lo que la espera. Pon la política a cero:

from axonium import RetryPolicy

client = Axonium(..., retry=RetryPolicy(initial_backoff=0.0, max_backoff=0.0, jitter=False))
client, err := axonium.New(axonium.Config{
	GatewayBaseURL: srv.URL, ClientID: "i", ClientSecret: "s",
	Retry: &axonium.RetryPolicy{MaxAttempts: 3, InitialBackoff: 0, MaxBackoff: 0},
})
let client = Client::new(Config {
    gateway_base_url: server.uri(),
    client_id: "i".into(),
    client_secret: "s".into(),
    retry: RetryPolicy {
        max_attempts: 3,
        initial_backoff: Duration::ZERO,
        max_backoff: Duration::ZERO,
        jitter: false,
        ..Default::default()
    },
    ..Default::default()
})?;
let client = try AxoniumClient(
    configuration: AxoniumConfiguration(
        gatewayBaseURL: "https://gw.test", clientID: "i", clientSecret: "s",
        retry: RetryPolicy(maxAttempts: 3, initialBackoff: 0, maxBackoff: 0, jitter: false)))
const client = new Axonium({
  gatewayBaseURL: "https://gw.test",
  clientId: "i",
  clientSecret: "s",
  retry: { maxAttempts: 3, initialBackoff: 0, maxBackoff: 0, jitter: false },
  fetch: fakeGateway,
});

max_backoff=0 tiene un segundo efecto que conviene conocer: cualquier Retry-After del servidor por encima de cero pasa entonces a devolverse como error en vez de dormirse, que es la misma regla que evita que una espera larga bloquee a un llamante real. Si tu test afirma que una espera larga se devuelve, así es como produces una.

Usa los sobres grabados

El repositorio lleva los cuerpos de error y los streams SSE contra los que se prueban estos SDK, en spec/fixtures/, indexados por spec/cases/manifest.json. Son capturas literales de cable — separadores de registro en línea en blanco incluidos — no aproximaciones escritas a mano.

Usarlos significa que tu gateway falso contesta como el real, incluidas las partes que nadie recuerda: que el sobre de rate-limit omite trace_id, que un 429 pone su scope en el cuerpo y no en la cabecera, que el evento de error de un stream es {"error": "..."} en el nivel superior.

Apunta tus fixtures a ese directorio en vez de copiar el JSON a tu fichero de test, donde dejará de coincidir y nadie se dará cuenta.

Qué hacen los propios tests de los SDK

Merece conocerse, porque dice qué está cubierto ya y qué no:

Ejecuta todo exactamente como lo ejecuta CI:

./scripts/verify.sh

Es un espejo deliberado de los ficheros de workflow. Una comprobación local que es casi la de CI reporta verde y esconde la diferencia — algo que este repositorio ha pagado dos veces, una con un paso de lint que necesitaba un build que solo existía en una máquina, y otra con una suite verde en la versión de Node que tenía el autor y roja en la que el paquete promete.

Tests de integración

Marcados y saltados por defecto. Necesitan AXONIUM_INTEGRATION=1 más credenciales reales, y nunca corren en CI — una suite que depende de que un despliegue esté en pie reporta rojo por motivos que no tienen nada que ver con el cambio bajo revisión.

Siguiente: Lo que no hace.