Testing
Use the smallest test layer that can detect the failure. Keep a regression test for each corrected contract. Run the full local suite before release.
Install the locked dependencies and run the local suite:
uv sync --extra all --frozen --no-install-project
uv run --no-sync pytest
To test Python 3.14 without replacing the main environment, use:
UV_PROJECT_ENVIRONMENT=.venv-py314 uv sync --python 3.14 --frozen \
--extra all --no-install-project
UV_PROJECT_ENVIRONMENT=.venv-py314 uv run --no-sync pytest --run-e2e --cov
Keep UV_PROJECT_ENVIRONMENT on both commands. This selects the same
interpreter and dependencies for installation and tests.
Use --extra all instead of the earlier --all-extras flag. The all
extra includes the complete feature set. It avoids selecting the incompatible
langfuse-v2, langfuse-v3, and langfuse-v4 alternatives together.
To test all features with another SDK generation, add one selector, such as
--extra all --extra langfuse-v2. The dependencies must resolve together.
The local suite needs no external service or model credentials. It includes
temporary SQLite databases, in-process HTTP transports, and local MCP stdio
processes. Test setup disables loading credentials and database paths from
.env. PostgreSQL tests also run when LAT_TEST_POSTGRES_DSN is set.
Test setup also ignores exported LANGFUSE_* and
LANGGRAPH_LANGFUSE_* project settings. Live Langfuse tests use only the
explicit LAT_TEST_LANGFUSE_* configuration.
Test layers
Use API load tests for repeatable HTTP pressure tests, provider faults, database faults, worker replacement during traffic, and bounded soak tests.
Layer |
Contract |
Location |
|---|---|---|
Focused tests |
Validation, error mapping, configuration, middleware, and message formats. |
|
Component integration |
Real graphs, tools, HTTP protocols, persistence, and dependency SDKs. Fakes replace model providers and external services. |
Tests beside their components and |
API process journeys |
A real service process, HTTP client, SQLite database, restart, and resume. |
|
Container checks |
Built images, model transports, readiness, chat history, and HTML. |
|
External service integration |
PostgreSQL locking and recovery; Langfuse ingestion and stored results. |
PostgreSQL and Langfuse live tests in |
The directories name the component under test. They do not always identify a
test layer. For example, tests/service/test_stream_integration.py uses an in-process
ASGI transport. The tests/e2e tests use a separate service process and TCP.
Component tests
Native graph tests use the production builders. They check local tool results, structured output, bounded local retries, and remote calls without automatic retries. The offline import test starts a fresh Python process and blocks network connections. It imports every built-in blueprint and assigns a saver.
Deep Agents integration tests use the optional deepagents extra. They check
planning, virtual file isolation, SQLite recovery, subagent context, approval
resume, and MCP connection closure. A process test also reads a virtual file
after a forced API restart. Langfuse SDK tests export a real Deep Agent run
through each supported SDK generation. Internal summarization must stay out
of the user-facing stream.
MCP tests use real FastMCP and LangChain adapters. They cover tool discovery, HTTP credentials, stdio, cancellation, timeouts, connection closure, and elicitation. HTTP tests replace the network transport with an in-process server. They retain the actual protocol and client setup.
Regression tests also check minimal-package imports, SSE comment and keepalive frames, and history queries with omitted optional values. These checks protect interfaces that can fail even when a mocked client returns a valid response. SQLite service tests check both StateGraph and Functional API history. They read existing checkpoints, append messages, stream one new answer, reopen the database, and clear the conversation. Functional API history reads LangGraph’s private saved-state channel. Keep these tests when upgrading LangGraph.
API process journeys
uv run --no-sync pytest tests/e2e tests/service/test_worker_recovery.py --run-e2e
These tests start the service on a local port. They check shared-client authentication, separate user memory and thread history, SSE and JSON Lines, safe error responses, and concurrent updates. They restart the process and check durable history and a pending interrupt. The worker tests kill a worker and check replacement under Uvicorn and Gunicorn. They require POSIX signals.
CI runs these journeys on Python 3.11, 3.12, 3.13, and 3.14. The model is deterministic. These tests do not measure model quality or simulate a production load.
Container checks
Start the backend with the fake model and a persistent checkpointer. Start the
frontend and set AGENT_URL, APP_URL, and AUTH_SECRET for the tests.
uv run --no-sync pytest tests/integration/test_docker_e2e.py --run-docker
CI builds both images before this check. Tests call readiness, invoke, stream, history, and clear-history endpoints. They check the frontend health endpoint and HTML response. They do not control a browser or test Streamlit interaction. An unavailable requested container causes failure.
The API image also has a separate transport check. It runs with the image’s runtime dependencies and no test packages. Build the image, then run:
docker build -f docker/api/Dockerfile -t toolkit-api .
docker run --rm --network none \
--mount "type=bind,source=$PWD/tests/integration/docker_transport_smoke.py,target=/tmp/docker_transport_smoke.py,readonly" \
toolkit-api python /tmp/docker_transport_smoke.py
This check verifies the image’s aiohttp default and an explicit HTTPX override. It checks actual SDK requests, streaming, usage, shared clients, and client closure. A local server returns synthetic model responses. The test uses a dummy key and has no external network access. CI runs it after building the default API image. It verifies the installed transport dependencies; it does not verify a live model provider.
PostgreSQL integration
Set LAT_TEST_POSTGRES_DSN to a disposable test database. The tests create
and remove test schemas and records.
uv run --no-sync pytest tests/integration/test_postgres_reliability.py \
tests/integration/test_postgres_migration.py \
tests/core/test_concurrency.py tests/core/test_deadlocks.py --run-postgres
--run-postgres fails when the DSN is missing. Tests check migration,
checkpoint recovery, lock release, cancellation, connection loss, and bounded
connection pools. CI supplies a PostgreSQL 16 service for this job.
Langfuse SDK compatibility
CI installs SDK 2.60.10, 3.15.0, and 4.15.2 separately on Python 3.11, 3.12,
3.13, and 3.14. Each installation selects the matching langfuse-v2, langfuse-v3,
or langfuse-v4 extra and pins the exact SDK baseline. Each environment also
resolves the MCP and Deep Agents extras. Compatibility tests check callbacks,
prompts, feedback, configuration, and dependency contracts.
tests/integration/test_langfuse_sdk.py uses the installed SDK. It runs real
graph callbacks and checks serialized HTTP requests and OpenTelemetry exports.
The receiving transport is controlled by the test. SDK tests therefore verify
the output protocol; live tests verify server ingestion and stored records.
Use a separate environment to test another SDK without changing the main one:
uv venv .venv-langfuse
uv pip install --python .venv-langfuse/bin/python \
'.[langfuse-v2,openai,mcp,deepagents]' 'langfuse==2.60.10' \
pytest pytest-asyncio pytest-env
uv pip check --python .venv-langfuse/bin/python
uv run --no-project --python .venv-langfuse/bin/python python -m pytest \
tests/core/test_langfuse_compatibility.py tests/core/test_langfuse_settings.py \
tests/core/test_observability.py tests/core/test_dependency_contracts.py \
tests/integration/test_langfuse_sdk.py tests/integration/test_deepagents.py
Change both the selector and the exact SDK pin for another matrix entry.
Use langfuse-v3 with langfuse==3.15.0 or langfuse-v4 with
langfuse==4.15.2. The selectors are published package extras. The exact pin
reproduces the CI baseline; it is optional for ordinary application installs.
See Langfuse compatibility for SDK and server selection.
Live Langfuse servers
Use a dedicated test project. Set LAT_TEST_LANGFUSE_BASE_URL,
LAT_TEST_LANGFUSE_PUBLIC_KEY, LAT_TEST_LANGFUSE_SECRET_KEY, and
LAT_TEST_LANGFUSE_SERVER_VERSION. The version must match the exact server
version from its health endpoint. Use LAT_TEST_LANGFUSE_TIMEOUT to change
the ingestion wait limit from its 90-second default.
uv run --no-project --python .venv-langfuse/bin/python python -m pytest \
tests/integration/test_langfuse_live.py --run-langfuse
The tests run native create_agent and Deep Agent graphs with scripted
models and real local tools. The Deep Agent delegates a calculation to a
subagent. Tests read prompt versions, completed graph and model records, tool
inputs and outputs, parent links, user and session IDs, and feedback from the
server. They wait for ingestion to finish. Trace and score records keep
lat-test- names. SDK v2 can also leave test prompts. Missing configuration,
an incorrect server version, or missing records causes failure.
The manual langfuse-live.yml workflow tests SDK v2 with server v2, all three
SDK generations with server v3, and SDK v4 with server v4. Configure the repository
environments langfuse-v2, langfuse-v3, and langfuse-v4. Each needs
variables LANGFUSE_TEST_URL and LANGFUSE_TEST_VERSION, plus secrets
LANGFUSE_TEST_PUBLIC_KEY and LANGFUSE_TEST_SECRET_KEY. Server v3 must be
at least 3.63.0 for SDK v3 and v4. A passing SDK matrix does not replace this
live-server result.
The manual workflow omits SDK v3 with server v4. Use the command above to test
this supported but deprecated combination. Set LAT_TEST_LANGFUSE_TIMEOUT=1200.
This allows for the server’s delayed processing of legacy SDK observations.
Live verification on 2026-09-12
The following checks ran for release 0.10.0. Each server used a disposable
database and test project. Each row passed both the native and Deep Agent tests
described above. These server tests use scripted models and real local tools.
Langfuse Server |
Python SDK |
Result |
|---|---|---|
2.95.11 |
2.60.10 |
2 passed |
3.225.7 |
2.60.10 |
2 passed |
3.225.7 |
3.15.0 |
2 passed |
3.225.7 |
4.15.2 |
2 passed |
4.35.0 |
4.15.2 |
2 passed on the second attempt |
The first server v4 attempt failed both cases. A prompt read timed out before the native graph ran. The Deep Agent ran, but trace export timed out and the stored-record check exceeded its 180-second limit. The records and feedback became available later. During this attempt, the disposable server’s worker and ClickHouse were heavily CPU throttled. Ingestion jobs lost their worker locks and were requeued automatically. No container ran out of memory or restarted. Web health remained successful, so health alone did not establish that ingestion was ready.
The second v4 attempt passed in 133.08 seconds after the worker limit changed from 1 to 2 CPUs and the ClickHouse limit changed from 1.5 to 2 CPUs. Memory limits stayed the same. The 10-second HTTP timeout and 180-second ingestion limit also stayed the same. The v4 reader now combines duplicate observation IDs only when all returned fields match. Conflicting rows fail the test. The v4 checks also require user and session IDs on every observation, including model calls and subagents.
SDK v3 with server v4 and server v4 migration modes were not checked in this
run. All v4 checks used the default events_only mode.
Azure OpenAI gpt-4o-mini-2024-07-18 also ran through
CompletionModelFactory with real model calls. The native agent called
multiply and returned 42. The Deep Agent called multiply, wrote a report,
delegated review through task, and read the report. A new graph and SQLite
connection then read the saved report. These graph checks used 12 model calls
and 29,179 reported tokens. A separate connection probe used 18 tokens.
The built 0.10.0 wheel was also installed in a clean environment with the
locked deepagents, openai, and langfuse extras. Its dependency check
passed. The production app ran through its ASGI API with Azure, a temporary
SQLite database, and server v4. Invocation, JSON Lines streaming, virtual-file
retrieval, history, and feedback passed. This used four model calls and 13,413
reported tokens. This check used an in-process HTTP transport; worker process
replacement is covered by the separate E2E tests.
That combined run logged Langfuse export timeouts and its first trace read timed out. The harness used one-observation batches and the toolkit’s default 5-second Langfuse timeout. The v4 server also timed out on later diagnostic reads after the successful compatibility test. Thus, the passing matrix does not establish consistent responsiveness in this resource-limited environment. A separate read-only check retried the same API trace IDs 19 times within 180 seconds and also failed with read timeouts. The API flow is verified, but complete stored tracing and token totals for that combined run are not verified. No extra model calls were made for these read retries. Later diagnostics could read the first API trace with either field selection. The final complete-record check reached the server after teardown had started and could not run. That connection failure does not indicate a package defect.
These checks verify tool execution, checkpoint recovery, and stored tracing contracts. The scripted server tests do not verify model token accounting. These checks do not measure general model quality or production load.
Real model requests
The opt-in OpenAI journey uses the real provider with both HTTPX and aiohttp. It checks tools, concurrent streaming, usage, cancellation, and recovery with at most twelve model requests. It is separate from the offline SDK and load tests. See Real LLM verification for configuration, budgets, and the exact scope.
Connection and overload tests
Connection and overload regressions use local HTTP endpoints and disposable databases. They do not call a paid model provider:
uv sync --frozen --no-install-project --extra all
uv run --no-sync pytest tests/core/test_llm_transport.py tests/service/test_admission.py \
tests/service/test_stream_transport_cleanup.py tests/service/test_blocking_feedback.py
uv run --no-sync pytest tests/integration/test_postgres_reliability.py --run-postgres
Set LAT_TEST_POSTGRES_DSN to a disposable PostgreSQL database for the second
test command. The tests create temporary schemas, terminate their own sessions,
and hold locks to verify recovery. OpenAI and Azure transport tests use the real
SDK with a local HTTP server. They check connection reuse, pool saturation,
bounded retries, streaming stalls, dropped sockets, cancellation, proxy and
credential separation, embeddings, and successive application lifespans.
ASGI tests check overload before body reads, mounted health probes, optional queue cancellation, slow sends, disconnect cleanup, and stalled-cleanup health signals. Real graph tests verify trace context cleanup and conversation ordering for both SSE and JSON Lines. Feedback tests retain a thread slot until its real synchronous call finishes. See Connections and High Traffic for deployment limits.
The initial combined reliability run on 2026-09-12 passed 695 tests, with 12 expected skips and 80.50% combined line/branch coverage. It included PostgreSQL 16.15 fault tests and both Uvicorn and Gunicorn worker replacement. The skipped tests required live Langfuse/Docker services or a different Langfuse SDK generation.
After the pressure-test fixes, the full regression suite passed 800 tests with 12 skips. That run included real PostgreSQL faults and both worker supervisors; it did not collect a new coverage percentage. After the final load-driver changes, 186 focused load, provider-transport, error-response, and factory tests passed. See Local pressure-test results: 2026-09-12 for the separate load measurements, failures, and follow-up runs.
A separate source matrix checked SDKs 2.60.10, 3.15.0, and 4.15.2 against SDK tracing, executor cleanup, and both streaming formats. SDK v2 passed 36 tests. SDK v3 and v4 each passed 28 tests with eight version-specific skips. This matrix used local HTTP interception, not live Langfuse servers. A final focused rerun of the observability and SDK wire tests passed 20 tests on each of those three SDK versions. The rebuilt base wheel also passed two application lifespans, fake-model invocation, SQLite persistence, and resource cleanup without model-provider or Deep Agents extras.
Dependency update review
Dependabot checks Python and Docker updates each Tuesday. It checks GitHub Actions and pre-commit updates monthly. Routine updates have a seven-day cooldown. The open version-update PR limits are five for Python, two for Actions, one for Docker, and one for pre-commit.
Python updates retain separate framework, observability, development-tool, and
general dependency groups. Major upgrades remain separate. Hook revisions share
one group without a semantic-version filter. Some hook tags have a v prefix
that the updater cannot classify for a minor/patch group. Review major hook
changes within the grouped PR.
Security updates use a separate schedule and do not wait for the routine cooldown. Compatible Python security updates share a separate group. Major security updates remain separate. These settings do not enable Dependabot alerts or security updates in repository settings. Keep those features enabled. See the Dependabot options reference.
Lower PR limits do not close existing PRs. A PR that already exists can occupy a slot until it is merged or closed. Review obsolete PRs after a manual upgrade or a dependency-group change. Do not merge an old lockfile across a package refactor. Rebase the PR or let Dependabot create an update from the current base.
Check the complete CI matrix before merging a dependency update. A green wheel
import or lint job does not establish API, database, or Python-version
compatibility. Reproduce failures with the PR’s exact pyproject.toml and
uv.lock in an isolated environment. Run the failing job with coverage when
CI uses coverage. A short timing test can behave differently with that overhead.
The pull-request hook job runs the revisions in .pre-commit-config.yaml.
It stages the PR changes in its disposable checkout because Gitleaks and the
file-size hook inspect staged changes. It then runs the configured hooks on
those files. This preparation does not change the files or other CI jobs.
When hook configuration changes, an additional smoke check uses representative Python, YAML, and TOML files. A YAML-only PR would otherwise skip Python hooks. This checks hook installation and execution. The smoke check does not replace a complete repository review after a major formatter or linter upgrade. Hook failures and automatic formatting changes fail the job.
Coverage and test review
uv run --no-sync pytest --cov --cov-report=term-missing
Coverage includes the whole package, including blueprints, UI, and legacy creators. Subprocess coverage is enabled. The 77% minimum combines executed lines and branch destinations. It is not a 77% branch-coverage requirement. Read line and branch totals separately when reviewing the report.
CI collects coverage from the package tests and API process journeys on each supported Python version. Each job enforces the 77% minimum and retains its test and coverage reports. Codecov receives one complete Python 3.13 report. This avoids combining repeated uploads from the Python matrix. Codecov fails a project decrease of more than two percentage points. Patch coverage remains informational. Upload failures fail trusted CI runs. Fork and Dependabot runs still enforce the local coverage minimum when an upload is unavailable.
If tokenless uploads are disabled, configure CODECOV_TOKEN as both an
Actions secret and a Dependabot secret. Fork pull requests do not receive these
secrets. Keep the Codecov GitHub integration enabled for remote status checks.
Prefer assertions on returned data, stored state, resource closure, and failure behavior. Keep focused unit tests where they explain a boundary. Remove a duplicate only when another test checks the same contract. Avoid tests that copy a production implementation, assert only a mock return value, or check incidental wording. Test production builders directly. Use a fresh process when the contract concerns module import behavior.
Coverage does not establish complete correctness. Live Langfuse checks require configured servers. Browser interaction, other model/provider combinations, and sustained production load need separate verification.
Python 3.14 verification
On 2026-09-13, the full local suite on CPython 3.14.3 passed 938 tests with 36 skips and 82.08% combined line/branch coverage. The run included API process journeys, worker replacement, SQLite persistence, MCP, Deep Agents, and both OpenAI HTTP transports. It used synthetic model responses. External service tests were not enabled.
Separate Langfuse SDK checks used both Pydantic 2.13.0 and 2.13.4. For each Pydantic version, SDK v2 passed 46 tests with one skip. SDK v3 and v4 each passed 39 tests with eight skips. Those skips select contracts for another SDK generation. Pydantic 2.12.5 reproduced the legacy SDK import failure on Python 3.14. The conditional dependency minimum prevents that installation.
A separate Python 3.14 run passed 43 PostgreSQL 16 fault, migration, concurrency,
and deadlock tests. The database used a disposable container, which was removed
after the tests. A fresh wheel passed base-service and UI checks outside the
source checkout. The complete frozen all installation also passed. The
actual Studio development server passed readiness, assistant discovery, and a
deterministic graph run. These checks used no live model or Langfuse service.
The exact CI dependency set and test command also passed on CPython 3.13.12: 938 tests passed, 36 skipped, and 82.16% combined line/branch coverage. This check included process journeys and generated the canonical Codecov XML report.