feat: initial scaffold + JobStore (v0.1.0)
Some checks failed
ci / lint-type-test (3.12) (push) Failing after 43s
ci / lint-type-test (3.13) (push) Failing after 42s
ci / publish (push) Has been skipped

This commit is contained in:
2026-06-09 17:32:17 +01:00
commit 53c22232fd
10 changed files with 886 additions and 0 deletions

110
tests/test_jobs.py Normal file
View File

@@ -0,0 +1,110 @@
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")