Files
wehub-resource-sync e30e75b5d4
Code Quality / Oxlint + Oxfmt (push) Waiting to run
Code Quality / Template Sync (push) Waiting to run
Code Quality / Build Changed Packages (push) Waiting to run
Code Quality / Test Changed Packages (push) Waiting to run
Deploy Expo Example / Deploy Production (push) Waiting to run
Deploy Ink Example / Deploy Production (push) Waiting to run
Python Tests / pytest (assistant-stream, 3.10) (push) Waiting to run
Python Tests / pytest (assistant-stream, 3.12) (push) Waiting to run
Python Tests / pytest (assistant-ui-sync-server-api, 3.10) (push) Waiting to run
Python Tests / pytest (assistant-ui-sync-server-api, 3.12) (push) Waiting to run
Deploy Shadcn Registry / Deploy Production (push) Waiting to run
Template Metrics / LOC + Bundle Size (push) Waiting to run
Changesets / Create Version PR (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:40:13 +08:00

165 lines
4.9 KiB
Python

from __future__ import annotations
import asyncio
import os
import time
import uuid
import pytest
REDIS_URL = os.environ.get("REDIS_URL", "redis://127.0.0.1:6379")
REDIS_TESTS_DISABLED = (
os.environ.get("REDIS_URL") is None and os.environ.get("REDIS_TESTS") != "1"
)
pytestmark = pytest.mark.skipif(
REDIS_TESTS_DISABLED,
reason="set REDIS_URL or REDIS_TESTS=1 to run redis adapter tests",
)
def _bytes(s: str) -> bytes:
return s.encode("utf-8")
def _decode(chunk: bytes) -> str:
return chunk.decode("utf-8")
@pytest.fixture
async def redis_store():
import redis.asyncio as redis
from assistant_stream.resumable import create_redis_resumable_stream_store
client = redis.from_url(REDIS_URL)
key_prefix = f"aui:resumable:test:{int(time.time() * 1000)}:{uuid.uuid4().hex[:8]}"
store = create_redis_resumable_stream_store(
client,
key_prefix=key_prefix,
poll_interval_ms=25,
)
try:
yield store, client, key_prefix
finally:
keys = [key async for key in client.scan_iter(match=f"{key_prefix}:*")]
if keys:
await client.delete(*keys)
await client.aclose()
@pytest.mark.anyio
async def test_elects_exactly_one_producer(redis_store) -> None:
store, _, _ = redis_store
stream_id = f"id-{uuid.uuid4().hex}"
first = await store.acquire(stream_id)
second = await store.acquire(stream_id)
assert first == "producer"
assert second == "consumer"
@pytest.mark.anyio
async def test_replays_buffered_and_tails_until_done(redis_store) -> None:
store, _, _ = redis_store
stream_id = f"id-{uuid.uuid4().hex}"
await store.acquire(stream_id)
await store.append(stream_id, _bytes("hello"))
await store.append(stream_id, _bytes(" world"))
signal = asyncio.Event()
collected: list[str] = []
async def reading() -> None:
async for entry in store.read(stream_id, "", signal):
collected.append(_decode(entry.chunk))
if len(collected) == 3:
await store.finalize(stream_id, "done")
task = asyncio.create_task(reading())
await asyncio.sleep(0.05)
await store.append(stream_id, _bytes("!"))
await task
assert "".join(collected) == "hello world!"
@pytest.mark.anyio
async def test_error_finalize_raises_after_draining(redis_store) -> None:
store, _, _ = redis_store
stream_id = f"id-{uuid.uuid4().hex}"
await store.acquire(stream_id)
await store.append(stream_id, _bytes("partial"))
await store.finalize(stream_id, "error", "boom")
signal = asyncio.Event()
seen: list[str] = []
with pytest.raises(Exception, match="boom"):
async for entry in store.read(stream_id, "", signal):
seen.append(_decode(entry.chunk))
assert seen == ["partial"]
@pytest.mark.anyio
async def test_cursor_skip(redis_store) -> None:
store, _, _ = redis_store
stream_id = f"id-{uuid.uuid4().hex}"
await store.acquire(stream_id)
await store.append(stream_id, _bytes("1"))
await store.append(stream_id, _bytes("2"))
await store.append(stream_id, _bytes("3"))
await store.finalize(stream_id, "done")
signal = asyncio.Event()
seen: list[tuple[str, str]] = []
async for entry in store.read(stream_id, "", signal):
seen.append((entry.cursor, _decode(entry.chunk)))
assert [t for _, t in seen] == ["1", "2", "3"]
out: list[str] = []
async for entry in store.read(stream_id, seen[0][0], signal):
out.append(_decode(entry.chunk))
assert out == ["2", "3"]
@pytest.mark.anyio
async def test_delete_removes_stream(redis_store) -> None:
store, _, _ = redis_store
stream_id = f"id-{uuid.uuid4().hex}"
await store.acquire(stream_id)
await store.append(stream_id, _bytes("x"))
assert await store.status(stream_id) == "streaming"
await store.delete(stream_id)
assert await store.status(stream_id) == "missing"
@pytest.mark.anyio
async def test_binary_round_trip_0_to_255(redis_store) -> None:
store, _, _ = redis_store
stream_id = f"id-{uuid.uuid4().hex}"
await store.acquire(stream_id)
producer = bytes(i & 0xFF for i in range(512))
half = len(producer) // 2
await store.append(stream_id, producer[:half])
await store.append(stream_id, producer[half:])
await store.finalize(stream_id, "done")
signal = asyncio.Event()
replayed = bytearray()
async for entry in store.read(stream_id, "", signal):
replayed.extend(entry.chunk)
assert bytes(replayed) == producer
@pytest.mark.anyio
async def test_decode_responses_true_is_rejected() -> None:
import redis.asyncio as redis
from assistant_stream.resumable import create_redis_resumable_stream_store
client = redis.from_url(REDIS_URL, decode_responses=True)
try:
with pytest.raises(ValueError, match="decode_responses"):
create_redis_resumable_stream_store(client)
finally:
await client.aclose()