sandloom.sdk.testing.plugin¶
pytest plugin of the 1.0 testing kit: the harness plus published and
Contract resolution.
Activate it once per application (sandloom new does):
[tool.pytest.ini_options]
addopts = ["-p", "sandloom.sdk.testing.plugin"]
It is the framework harness (every fixture: client, db,
real_uow_client, ...; the test environment; the isolation guards) plus,
for every test:
published(E)records the events the code under test publishes;Contract(REF)resolves through the test application's contracts whenever the test uses the application (any fixture built onsandloom_app);fake_contractoverrides one for a block;- with
SANDLOOM_TEST_OBSERVE_EFFECTS=1(sandloom testoutside--ci), the money / security effects each route ran are recorded forsandloom routes snapshot --write(informationalobserved_effects).
Activate this plugin or sandloom.core.testing.plugin, not both.
from sandloom.sdk.testing.plugin import <name>. Signatures in a terminal: sandloom docs <name>. Beginner names are the ones generated code and the examples use; any other name is advanced (mark its import line # sandloom: advanced <reason> in generated code and examples).
Start with (beginner)¶
| Name | Kind | Summary |
|---|---|---|
client |
fixture | HTTP client on the app; every request runs on the test's db. |
Advanced¶
| Name | Kind | Summary |
|---|---|---|
ambient_uow |
fixture | Bind an in-memory recording UoW for the whole test. |
async_client |
fixture | |
db |
fixture | A session on the test database, rolled back after the test. |
db_engine |
fixture | Engine on the test database; every table present and empty. |
db_uow |
fixture | db bound as the UoW for the whole test (outbox / audit rows join it). |
make_headers |
fixture | Factory: make_headers(user_id=None, tenant_id=None, **claims). |
real_uow_client |
fixture | HTTP client whose requests run through the REAL request UoW. |
revoked_tokens |
fixture | Record revoke_user_tokens(user_id) calls instead of running them. |
saga_harness |
fixture | Drive the REAL saga protocol synchronously (sandloom.core.testing.sagas). |
sandloom_app |
fixture | The application's ASGI app, built by its composition root. |
sandloom_cache |
fixture | The app's default cache on the test CACHE_URL, emptied first. |
sandloom_core |
fixture | The application's CoreFacade. |
sandloom_test_env |
fixture | Load the nearest .env.test (rootdir first, then the test paths). |
scratch_database |
fixture | A throw-away database on the test server (sync URL), dropped after. |
test_db |
fixture | |
test_engine |
fixture | |
user_headers |
fixture | Headers of a default user (random user id and tenant). |
Beginner names¶
client¶
@pytest.fixture
def client(
sandloom_app: Any,
db: Any,
request: pytest.FixtureRequest,
) -> AsyncIterator[Any]
HTTP client on the app; every request runs on the test's db.
Shared-session semantics: every request uses the test's ONE db
session (bound as the UoW), through DbSession and through db()
alike (sandloom.core.testing._uow.SharedRequestUnit), so nothing commits
and a
request that fails with a 4xx does NOT roll its writes back: the rows a
failed request flushed stay visible to the test (and to later requests)
until the test ends. Assert rollback behaviour with real_uow_client,
whose requests commit or roll back exactly as in production (both paths).
An endpoint that raises an unhandled exception re-raises it here
(sandloom.core.testing.server_errors.UnhandledServerError: type,
message, fix) instead of returning a bare 500 internal_error; mark a
test @pytest.mark.sandloom_allow_server_errors to get the 500 response.
Advanced names¶
ambient_uow¶
@pytest.fixture
def ambient_uow() -> AsyncIterator[Any]
Bind an in-memory recording UoW for the whole test.
async_client¶
@pytest.fixture
def async_client(client: Any) -> Any
(no docstring)
db¶
@pytest.fixture
def db(db_engine: Any) -> AsyncIterator[Any]
A session on the test database, rolled back after the test.
db_engine¶
@pytest.fixture
def db_engine(sandloom_app: Any) -> AsyncIterator[Any]
Engine on the test database; every table present and empty.
db_uow¶
@pytest.fixture
def db_uow(db: Any) -> AsyncIterator[Any]
db bound as the UoW for the whole test (outbox / audit rows join it).
make_headers¶
@pytest.fixture
def make_headers(
sandloom_test_env: Path,
) -> Callable[..., tuple[Any, dict[str, str]]]
Factory: make_headers(user_id=None, tenant_id=None, **claims).
real_uow_client¶
@pytest.fixture
def real_uow_client(
sandloom_app: Any,
sandloom_core: Any,
db_engine: Any,
request: pytest.FixtureRequest,
) -> AsyncIterator[Any]
HTTP client whose requests run through the REAL request UoW.
No get_db override: the app's default database (and cache) are
pointed at the test DATABASE_URL / CACHE_URL, so every request
commits or rolls back exactly as in production — a 4xx rolls its writes
back (committed rows are emptied by the next test's db_engine). An
unhandled endpoint error re-raises like with client
(@pytest.mark.sandloom_allow_server_errors keeps the 500 response).
revoked_tokens¶
@pytest.fixture
def revoked_tokens(monkeypatch: pytest.MonkeyPatch) -> RevokedTokens
Record revoke_user_tokens(user_id) calls instead of running them.
Keeps API tests deterministic (a token minted in the same second as a
revocation would otherwise flip to 401) and lets the test assert who was
revoked; enforcement itself is covered by the core auth tests. A
RevokedTokens list: .available = False simulates an outage.
saga_harness¶
@pytest.fixture
def saga_harness(
sandloom_core: Any,
db_engine: Any,
) -> AsyncIterator[Any]
Drive the REAL saga protocol synchronously (sandloom.core.testing.sagas).
The app's default database points at the test database; every
transition commits for real (rows are emptied by the next test's
db_engine). run() / fail_step / crash_after /
deliver_twice / clock.advance / fake_contract.
sandloom_app¶
@pytest.fixture
def sandloom_app(
pytestconfig: pytest.Config,
sandloom_test_env: Path,
) -> Any
The application's ASGI app, built by its composition root.
sandloom_cache¶
@pytest.fixture
def sandloom_cache(sandloom_core: Any) -> AsyncIterator[Any]
The app's default cache on the test CACHE_URL, emptied first.
Yields the CacheProvider the app itself resolves (core.cache(),
idempotency, rate limits, token revocation). The Redis database is
FLUSHDB'd before the test so keys never leak between tests — so
CACHE_URL must name a disposable test database. Also needed where the
app was imported before .env.test was loaded (its settings snapshot
names another cache), as in the framework's own suite.
sandloom_core¶
@pytest.fixture
def sandloom_core(sandloom_app: Any) -> Any
The application's CoreFacade.
sandloom_test_env¶
@pytest.fixture
def sandloom_test_env(request: pytest.FixtureRequest) -> Path
Load the nearest .env.test (rootdir first, then the test paths).
scratch_database¶
@pytest.fixture
def scratch_database(sandloom_test_env: Path) -> Iterator[str]
A throw-away database on the test server (sync URL), dropped after.
For a module's migration tests (sandloom.core.testing.migrations): walk its
chain up and down without touching the shared test database.
test_db¶
@pytest.fixture
def test_db(db: Any) -> Any
(no docstring)
test_engine¶
@pytest.fixture
def test_engine(db_engine: Any) -> Any
(no docstring)
user_headers¶
@pytest.fixture
def user_headers(
make_headers: Callable[..., tuple[Any, dict[str, str]]],
) -> dict[str, str]
Headers of a default user (random user id and tenant).