deer-flow/backend/tests/AGENTS.md
X77 8a3986730f
fix(deploy): fail fast when DEER_FLOW_HOME is not writable (dev stack root ownership) (#6488)
* fix(deploy): fail fast when DEER_FLOW_HOME is not writable

`make docker-start` bind-mounts the host `backend/` directory into the gateway
container, whose process runs as root, so any state it creates under
`backend/.deer-flow` ends up owned by root on the host. A later `make up` runs
`scripts/deploy.sh` as the invoking user and only fails deep into the run with a
bare "Permission denied" while persisting generated secrets (deploy.sh:234).

Check writability right after `mkdir -p "$DEER_FLOW_HOME"` and, when it fails,
print the exact `chown` recovery command instead of letting the run get halfway
through. `make down` is excluded so stopping the stack is never blocked.

Fixes #6462

* test(deploy): cover runtime home writability preflight

* fix(deploy): diagnose persisted secret permissions and preserve teardown

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-08 20:20:22 +08:00

11 KiB

Backend Tests

Backend tests must preserve the runtime invariants they exercise without changing production execution topology.

Upload case-collision coverage uses separate HTTP requests and preserves both reported payloads. Observe real filename-claim inputs to pin the disk seed; case-insensitive hosts can otherwise mask a missing seed through link retries.

Channel reload cancellation regressions must assert the worker returned the stale snapshot before checking that newer runtime config survived. Completion alone cannot prove the race: loader exceptions are caught and return None.

Browser egress session-close tests retain the real listener and its sockets, verify a SOCKS handshake, then check listener shutdown, closed socket descriptors and EOF on the established client. A new connection to the old port is not a reliable ownership assertion on hosts with loopback forwarding or port reuse. Keep ownership assertions inside the teardown-protected block; always drain the client writer and close the saved proxy during teardown.

Read-before-write hash fixtures pin newline="" when writing LF/CRLF test content, so native Windows cannot translate the bytes before the real read.

Browser-asset confinement tests use support.symlinks.symlink_or_skip for real file and directory symlinks. Keep missing-file, duplicate-key, and size-limit checks separate so they still run when the host cannot create symlinks.

blocking_io/test_web_tool_url_validation.py resolves a synthetic .invalid hostname to loopback by patching _socket.getaddrinfo, below the real socket.getaddrinfo wrapper. Do not replace that wrapper or the production URL guard: the strict gate must still reject on-loop resolution. Assert each tool path reaches the native fixture so an unresolved-host rejection cannot mask it. The IPv4 fixture accepts only AF_UNSPEC and AF_INET; unsupported families must fail rather than receive a fabricated IPv4 answer.

The local sandbox's UTF-8 subprocess guard inspects each text-mode call with ast, checking both encoding and errors; module-wide literal counts can hide unpinned calls behind unrelated settings.

PostgreSQL batch fixtures

Batch fixtures use support.postgres.asyncpg_test_url to map libpq sslmode to asyncpg ssl and remove unsupported channel_binding before constructing database config. Preserve TLS modes, credentials and other query options; reject conflicting ssl/sslmode values. CI uses ?sslmode=disable; validate that URI shape against a real test database, not only a parameter-free local URI. Reuse the normalized config for reopening and teardown, and drop only the fixture's UUID schema. This is test-only handling; production connection and TLS policy are unchanged.

The 0033 batch-evidence migration fixture uses the same adapter. Its connection contract probes execute the actual migration test setup through SQLAlchemy's dialect argument conversion, stopping before database acquisition; the real SQLite/PostgreSQL cases still exercise upgrade, downgrade and re-upgrade.

Real Compose tests

support/compose.py probes docker compose version --short and requires Compose 2.24+ for optional env_file syntax. Skip missing, old or unreadable clients with an actionable reason; cover version detection offline in test_support_compose.py. Real rendering and production entry-point tests use only read-only Compose calls. Never start or stop a stack from these tests.

Claude provider tests

test_claude_provider_prompt_caching.py exercises real Anthropic SDK serialization through offline HTTP transports. Keep its directly imported anthropic SDK in the backend dev dependency group rather than relying on langchain-anthropic to supply it transitively.

User repository ordering

test_auth.py pins list_user_ids() ordering with fixed UTC timestamps and UUIDs. Cover both creation-time precedence and the lexical stored-ID tie-break for equal timestamps through the real SQLite repository; do not assume wall-clock calls are distinct or weaken the result to an unordered comparison.

Router auth fixtures

For owner-scoped route assertions, pass a stable user_factory and bind_current_user=True to make_authed_test_app. The default stub stamps request state but leaves the ambient user context unchanged; the opt-in binding uses the stub user during the request and restores the previous context after it.

Lark CLI blocking-I/O fixtures

blocking_io/test_integrations_router.py uses a real local CLI stub: a .cmd script on Windows and an executable shell script on POSIX. Resolve it through the production PATH lookup, including a directory containing spaces. Seed fake app credentials so auth completion reaches the CLI instead of returning early, and keep fixture filesystem work behind asyncio.to_thread.

Shared sandbox search contracts

test_sandbox_search_contract.py runs shared ls/glob/grep scenarios through Local, AIO, E2B, BoxLite, Tenki and OpenSandbox adapters. POSIX shell transports execute production commands against temporary files; AIO file RPCs use an independent filesystem-backed double. Do not substitute precomputed final search results or import another test module's private fixtures. Provisioning, live SDK compatibility, permissions enforced above Sandbox, and session lifecycle remain in their existing suites. Windows skips this POSIX execution tier explicitly.

Compare result names/content, errors and completeness, allowing documented root entries and directory suffixes to differ. First-stage cases use ordinary roots, positive caps and a plain literal pattern: ignored-root fixes (#5667), E2B literal metacharacters (#5627), and exactly-full local caps (#5491) have separate owners. Add those shared scenarios after their fixes land; do not encode known bugs as expected successful behavior or hide them with permanent xfails.

External system-role admission

poc_external_system_message_injection.py --help is the opt-in live reproduction and post-fix verifier; never run it automatically against an existing user's chat. test_poc_external_system_message_injection.py tests that CLI offline. test_external_system_message_boundary.py records model inputs with a fake model: the same regression must fail on unfixed admission and pass after rejection, without production-provider logging or interpreting model obedience as proof.

Scope-isolation benchmark

test_bench_deermem_scope_isolation.py exercises production admission and storage. Check persisted facts and user/history summaries for semantic safety, but only agent-local facts for routing. Failed updates must not become sealed observations; report and resume must reject failed or old-schema rows. Stub live models so these tests never need credentials or network access.

MCP claim fencing

test_mcp_task_repository.py covers same-worker reclaim during an in-flight release, poll/cancel snapshot, or notification completion. Use explicit events to pause the old operation at the persistence boundary, reclaim via the real repository, then verify the entire new row remains unchanged. Reclaiming before the old operation starts does not catch SQLite SELECT/ORM-flush races. Keep the old completion timestamp within its original lease so expiry cannot mask a missing token fence; always drain paused tasks and restore session patches.

Project document cache

test_project_document_char_cache.py pauses real file-IO workers between cache lookup and promotion to exercise concurrent eviction. Keep cache metadata operations atomic, and verify that a full scan does not hold the cache lock.

Executor starvation tests

test_executor_starvation.py covers the deterministic starvation semantics from RFC #4560:

  • default-executor saturation and queueing;
  • cancellation of an awaiter while an already-started synchronous worker continues;
  • isolation between the asyncio default executor and DeerFlow's dedicated file-I/O executor.

Use explicit synchronization such as threading.Event rather than sleep-based timing thresholds for worker lifecycle assertions. Every test must release blocked workers and restore any process-global monkeypatches so teardown cannot leak threads or state into later tests.

Stress/soak testing, AnyIO worker instrumentation, Uvicorn multi-process behavior, and broad production executor redesign are separate concerns and should not be folded into these deterministic regressions.

Managed DeepSeek compatibility

test_managed_deepseek.py exercises real SDK request serialization and SSE parsing with an HTTP double; do not replace the provider classes with successful stubs. test_managed_deepseek_live.py uses the same production probe/model configuration against DeepSeek only with DEER_FLOW_RUN_LIVE_TESTS=1 and DEEPSEEK_TEST_API_KEY, never in CI. Keep credentials and provider payloads out of committed evidence. A passing connectivity probe does not establish full agent compatibility; distinguish protocol assertions from observed live behavior.

Classification request packing

Pin the Jev example's outbound byte budget against captured HTTP request bodies. Cover UTF-8 text and JSON embedded in chat messages, including escaping-heavy inputs that fit the host's separate input limit. Use offline transports. Check the host's JSON output bound with the maximum item count and keys that expand when escaped; preserving raw UTF-8 limits alone does not cover it.

Advisory fetched-content screening example

Load the example through load_extensions() and the host isolation wrapper, as a plugins: entry is loaded; never instantiate the middleware directly. test_jev_result_screening_extension.py checks install/config diagnostics, bounded requests, the task-store handover between the tool wrapper and before_model, copy semantics, fail-open provider errors and isolation diagnostics for local bugs. It also pins per-message classification: a benign first message in a Command must not take the flag of an injected later one, and a malformed, compressed or slow answer for one message must not cancel or hide another's flag. The loopback test uses a real local server behind an unreachable proxy variable. test_jev_screening_pipeline.py runs real lead/subagent graphs to a recording model with a task store bound under the runtime-context key the Gateway worker uses; cover the redacted excerpt, error metadata/receipts, budget, one-time warnings including valid empty-string message IDs, and runs without a task store. Only None awaits ID assignment. Use offline transports. Detection is not behavioral defense; do not infer the final model input from an isolated hook test. test_jev_screening_policy.py uses the host descriptor builder to pin policy identity; hash endpoint/prompt text and never project credential values.

Deploy home permission tests

test_deploy_home_writability.py covers the production deploy permission preflight. It runs the complete script with a recording Docker stub and isolated runtime paths. Permission cases remove directory write bits or secret file read bits and restore them during teardown; they skip on Windows, as root, or when the filesystem does not enforce those bits. Cover both persisted secrets, shell/Compose dotenv overrides, readable read-only secrets, and teardown without secret exports. down must not probe, read, or generate secrets. Writable-directory cases remain portable.