156 lines
4.7 KiB
Python
156 lines
4.7 KiB
Python
"""Integration tests for session labels and owner endpoints.
|
|
|
|
Covers ``GET /v1/sessions/{id}/labels`` and
|
|
``GET /v1/sessions/{id}/owner``.
|
|
|
|
Uses the shared ``client`` fixture from ``tests/server/conftest.py``
|
|
(real stores + mock LLM) so the tests hit the real route-to-store
|
|
pipeline without subprocesses.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
import httpx
|
|
import pytest
|
|
|
|
from tests.server.helpers import create_test_agent
|
|
|
|
pytestmark = pytest.mark.asyncio
|
|
|
|
|
|
# ── Helpers ──────────────────────────────────────────────
|
|
|
|
|
|
async def _create_session(
|
|
client: httpx.AsyncClient,
|
|
agent_id: str,
|
|
*,
|
|
title: str | None = None,
|
|
labels: dict[str, str] | None = None,
|
|
) -> dict[str, Any]:
|
|
"""Create a session and return the response JSON."""
|
|
payload: dict[str, Any] = {"agent_id": agent_id}
|
|
if title is not None:
|
|
payload["title"] = title
|
|
if labels is not None:
|
|
payload["labels"] = labels
|
|
resp = await client.post("/v1/sessions", json=payload)
|
|
assert resp.status_code == 201, f"session create failed: {resp.status_code} {resp.text}"
|
|
return resp.json()
|
|
|
|
|
|
# ── GET /v1/sessions/{id}/labels ─────────────────────────
|
|
|
|
|
|
async def test_labels_empty_on_new_session(
|
|
client: httpx.AsyncClient,
|
|
) -> None:
|
|
"""A freshly created session with no labels returns an empty dict."""
|
|
agent = await create_test_agent(client)
|
|
session = await _create_session(client, agent["id"])
|
|
resp = await client.get(f"/v1/sessions/{session['id']}/labels")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["id"] == session["id"]
|
|
# Labels may contain auto-injected keys (e.g. closed status);
|
|
# at minimum no user-supplied labels are present.
|
|
assert isinstance(data["labels"], dict)
|
|
|
|
|
|
async def test_labels_set_via_create(
|
|
client: httpx.AsyncClient,
|
|
) -> None:
|
|
"""Labels passed at session creation are returned by GET labels."""
|
|
agent = await create_test_agent(client)
|
|
session = await _create_session(
|
|
client,
|
|
agent["id"],
|
|
labels={"env": "staging", "team": "platform"},
|
|
)
|
|
resp = await client.get(f"/v1/sessions/{session['id']}/labels")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["labels"]["env"] == "staging"
|
|
assert data["labels"]["team"] == "platform"
|
|
|
|
|
|
async def test_labels_updated_via_patch(
|
|
client: httpx.AsyncClient,
|
|
) -> None:
|
|
"""Labels updated via PATCH are reflected by a subsequent GET labels."""
|
|
agent = await create_test_agent(client)
|
|
session = await _create_session(
|
|
client,
|
|
agent["id"],
|
|
labels={"env": "dev"},
|
|
)
|
|
sid = session["id"]
|
|
|
|
# Update labels via PATCH
|
|
patch_resp = await client.patch(
|
|
f"/v1/sessions/{sid}",
|
|
json={"labels": {"env": "prod", "region": "us-east"}},
|
|
)
|
|
assert patch_resp.status_code == 200
|
|
|
|
# Verify GET labels reflects the update
|
|
resp = await client.get(f"/v1/sessions/{sid}/labels")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["labels"]["env"] == "prod"
|
|
assert data["labels"]["region"] == "us-east"
|
|
|
|
|
|
async def test_labels_persist_across_updates(
|
|
client: httpx.AsyncClient,
|
|
) -> None:
|
|
"""Successive PATCH updates overwrite labels correctly."""
|
|
agent = await create_test_agent(client)
|
|
session = await _create_session(
|
|
client,
|
|
agent["id"],
|
|
labels={"version": "1"},
|
|
)
|
|
sid = session["id"]
|
|
|
|
# First update
|
|
await client.patch(
|
|
f"/v1/sessions/{sid}",
|
|
json={"labels": {"version": "2", "extra": "yes"}},
|
|
)
|
|
|
|
# Second update
|
|
await client.patch(
|
|
f"/v1/sessions/{sid}",
|
|
json={"labels": {"version": "3"}},
|
|
)
|
|
|
|
resp = await client.get(f"/v1/sessions/{sid}/labels")
|
|
assert resp.status_code == 200
|
|
assert resp.json()["labels"]["version"] == "3"
|
|
|
|
|
|
# ── GET /v1/sessions/{id}/owner ──────────────────────────
|
|
|
|
|
|
async def test_owner_returns_none_without_auth(
|
|
client: httpx.AsyncClient,
|
|
) -> None:
|
|
"""Without an auth provider the owner is null."""
|
|
agent = await create_test_agent(client)
|
|
session = await _create_session(client, agent["id"])
|
|
resp = await client.get(f"/v1/sessions/{session['id']}/owner")
|
|
assert resp.status_code == 200
|
|
assert resp.json()["owner"] is None
|
|
|
|
|
|
async def test_owner_null_for_nonexistent_session(
|
|
client: httpx.AsyncClient,
|
|
) -> None:
|
|
"""GET owner for a nonexistent session returns null owner (no auth)."""
|
|
resp = await client.get("/v1/sessions/conv_nonexistent_abc123/owner")
|
|
assert resp.status_code == 200
|
|
assert resp.json()["owner"] is None
|