feat: vibed out some slop over here
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
"""Live integration tests against `backend.raycast.com`.
|
||||
|
||||
Opt-in: gated on the `--live` pytest flag AND on env vars supplying real
|
||||
credentials. Without `--live` these are all skipped by the
|
||||
`pytest_collection_modifyitems` hook in `conftest.py`.
|
||||
|
||||
Required env vars when running with `--live`:
|
||||
RAYCAST_BEARER — a valid OAuth bearer token (rca_...)
|
||||
RAYCAST_DEVICE_ID — a stable 64-hex device id
|
||||
|
||||
Optional:
|
||||
RAYCAST_CONFIG — path to a `config.json` produced by `raycast-api init`.
|
||||
Defaults to `./config.json` (repo-root one shipped with
|
||||
this checkout).
|
||||
|
||||
Why these tests exist: the synthetic-fixture suite proves the signing math
|
||||
is internally consistent. Only a real round-trip proves the bytes we send
|
||||
on the wire actually match what the server expects — i.e. catches the
|
||||
"refactor in `transforms.py` broke prod" class of regression.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from raycast_api.ai.types import Message
|
||||
from raycast_api.client.http import Client
|
||||
from raycast_api.config import Config
|
||||
|
||||
pytestmark = pytest.mark.live
|
||||
|
||||
|
||||
def _bearer() -> str:
|
||||
val = os.environ.get("RAYCAST_BEARER")
|
||||
if not val:
|
||||
pytest.skip("RAYCAST_BEARER not set")
|
||||
return val
|
||||
|
||||
|
||||
def _device_id() -> str:
|
||||
val = os.environ.get("RAYCAST_DEVICE_ID")
|
||||
if not val:
|
||||
pytest.skip("RAYCAST_DEVICE_ID not set")
|
||||
return val
|
||||
|
||||
|
||||
def _config() -> Config:
|
||||
path = Path(os.environ.get("RAYCAST_CONFIG", "config.json")).expanduser()
|
||||
if not path.is_file():
|
||||
pytest.skip(f"no config at {path} — run `raycast-api init` first")
|
||||
return Config.load(path)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client() -> Client:
|
||||
return Client(config=_config(), bearer_token=_bearer(), device_id=_device_id())
|
||||
|
||||
|
||||
async def test_me(client: Client) -> None:
|
||||
"""Bearer-token probe. Unsigned — proves the token is alive."""
|
||||
async with client:
|
||||
me = await client.me.get()
|
||||
assert isinstance(me, dict)
|
||||
assert me, "empty /me response — token likely invalid"
|
||||
|
||||
|
||||
async def test_models_list(client: Client) -> None:
|
||||
"""Lists `/ai/models`. Unsigned, but exercises auth + JSON decoding."""
|
||||
async with client:
|
||||
models = await client.models.list()
|
||||
assert models.models, "no models returned"
|
||||
|
||||
|
||||
async def test_chat_streaming(client: Client) -> None:
|
||||
"""One-token chat completion. Proves signing works end-to-end.
|
||||
|
||||
Picks the first model from the live catalog so the test doesn't pin
|
||||
a specific provider. Prompt is intentionally trivial to minimize cost.
|
||||
"""
|
||||
async with client:
|
||||
catalog = await client.models.list()
|
||||
model = catalog.models[0]
|
||||
chunks: list[str] = []
|
||||
async for chunk in client.chat.stream(
|
||||
model=model, messages=[Message.user("Reply with the single word: OK")]
|
||||
):
|
||||
if chunk.text:
|
||||
chunks.append(chunk.text)
|
||||
assert chunks, "no text chunks streamed — signing or stream parsing broke"
|
||||
Reference in New Issue
Block a user