111 lines
3.4 KiB
Python
111 lines
3.4 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
|
|
import pytest
|
|
|
|
from platform_common.jobs import (
|
|
InMemoryJobStore,
|
|
JobNotFoundError,
|
|
JobRecord,
|
|
JobStatus,
|
|
JobStore,
|
|
make_job_store,
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def store() -> InMemoryJobStore:
|
|
return InMemoryJobStore()
|
|
|
|
|
|
def test_protocol_conformance(store: InMemoryJobStore) -> None:
|
|
assert isinstance(store, JobStore)
|
|
|
|
|
|
async def test_create_and_get(store: InMemoryJobStore) -> None:
|
|
record = await store.create("j1", {"input": "hello"}, ttl_seconds=60)
|
|
assert record.id == "j1"
|
|
assert record.status is JobStatus.PENDING
|
|
assert record.payload == {"input": "hello"}
|
|
fetched = await store.get("j1")
|
|
assert fetched is not None
|
|
assert fetched.payload == {"input": "hello"}
|
|
|
|
|
|
async def test_create_duplicate_raises(store: InMemoryJobStore) -> None:
|
|
await store.create("j1", ttl_seconds=60)
|
|
with pytest.raises(ValueError, match="already exists"):
|
|
await store.create("j1", ttl_seconds=60)
|
|
|
|
|
|
async def test_get_missing_returns_none(store: InMemoryJobStore) -> None:
|
|
assert await store.get("nope") is None
|
|
|
|
|
|
async def test_update_patches_fields(store: InMemoryJobStore) -> None:
|
|
await store.create("j1", {"a": 1}, ttl_seconds=60)
|
|
updated = await store.update("j1", status=JobStatus.RUNNING, result={"out": 42})
|
|
assert updated.status is JobStatus.RUNNING
|
|
assert updated.result == {"out": 42}
|
|
assert updated.payload == {"a": 1}
|
|
assert updated.updated_at >= updated.created_at
|
|
|
|
|
|
async def test_update_missing_raises(store: InMemoryJobStore) -> None:
|
|
with pytest.raises(JobNotFoundError):
|
|
await store.update("nope", status=JobStatus.FAILED)
|
|
|
|
|
|
async def test_delete(store: InMemoryJobStore) -> None:
|
|
await store.create("j1", ttl_seconds=60)
|
|
assert await store.delete("j1") is True
|
|
assert await store.delete("j1") is False
|
|
assert await store.get("j1") is None
|
|
|
|
|
|
async def test_ttl_expiry(store: InMemoryJobStore) -> None:
|
|
await store.create("j1", ttl_seconds=1)
|
|
assert await store.exists("j1")
|
|
await asyncio.sleep(1.1)
|
|
assert await store.get("j1") is None
|
|
assert await store.exists("j1") is False
|
|
|
|
|
|
async def test_invalid_ttl(store: InMemoryJobStore) -> None:
|
|
with pytest.raises(ValueError, match="positive"):
|
|
await store.create("j1", ttl_seconds=0)
|
|
|
|
|
|
async def test_lru_capacity() -> None:
|
|
store = InMemoryJobStore(max_records=3)
|
|
for i in range(5):
|
|
await store.create(f"j{i}", ttl_seconds=60)
|
|
ids = await store.list_ids()
|
|
assert set(ids) == {"j2", "j3", "j4"}
|
|
|
|
|
|
async def test_record_roundtrip() -> None:
|
|
record = JobRecord(id="j1", status=JobStatus.SUCCEEDED, payload={"k": "v"}, result={"r": 1})
|
|
restored = JobRecord.from_dict(record.to_dict())
|
|
assert restored == record
|
|
|
|
|
|
def test_make_job_store_default(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.delenv("JOB_STORE_URL", raising=False)
|
|
assert isinstance(make_job_store(), InMemoryJobStore)
|
|
|
|
|
|
def test_make_job_store_memory_explicit() -> None:
|
|
assert isinstance(make_job_store("memory://"), InMemoryJobStore)
|
|
|
|
|
|
def test_make_job_store_redis_not_yet_implemented() -> None:
|
|
with pytest.raises(NotImplementedError, match=r"v0\.2"):
|
|
make_job_store("redis://localhost:6379/0")
|
|
|
|
|
|
def test_make_job_store_unknown_scheme() -> None:
|
|
with pytest.raises(ValueError, match="unsupported"):
|
|
make_job_store("kafka://broker")
|