From d9642a9b8a9428b9e2f6aafafccf3411b9833a11 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 7 Oct 2026 00:35:22 +1300 Subject: [PATCH 1/3] fix(chat): box the chat route so clippy accepts Routing large_enum_variant fails Rust Lint on main: Routing::Chat carries an 856-byte ChatRow plus Route. Matches the identical change in the open migrate/* PRs so whichever lands first merges cleanly with the rest. Co-Authored-By: Claude Opus 5.5 --- runner/zone_server/src/ws/chat.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/runner/zone_server/src/ws/chat.rs b/runner/zone_server/src/ws/chat.rs index 70cc1d5d..df188670 100644 --- a/runner/zone_server/src/ws/chat.rs +++ b/runner/zone_server/src/ws/chat.rs @@ -388,7 +388,7 @@ enum Routing { Video(crate::config::ComfyUiConfig), Audio(crate::config::ComfyUiConfig), Upscale(crate::config::ComfyUiConfig), - Chat(chats::ChatRow, Route), + Chat(chats::ChatRow, Box), } /// What a provider reported when a turn failed, remedied for a coding agent's @@ -2495,7 +2495,7 @@ async fn handle_send_message( return Ok(()); } _ = session.guard.lost() => { return Err(OWNERSHIP_LOST.into()); } - result = prepare_chat(state, stream, chat_id, workspace_id, user_id, content, metadata.as_ref(), chat, route, web_search_requested) => result?, + result = prepare_chat(state, stream, chat_id, workspace_id, user_id, content, metadata.as_ref(), chat, *route, web_search_requested) => result?, }; handle_chat_generation(state, stream, chat_id, workspace_id, user_id, preparation, &mut request, &mut session, &mut jobs).await } @@ -2666,7 +2666,9 @@ async fn prepare_message( } crate::services::image_intent::GenerationIntent::Audio => Routing::Audio(image_config), crate::services::image_intent::GenerationIntent::Upscale => Routing::Upscale(image_config), - crate::services::image_intent::GenerationIntent::Chat => Routing::Chat(chat, route), + crate::services::image_intent::GenerationIntent::Chat => { + Routing::Chat(chat, Box::new(route)) + } }) } @@ -4569,7 +4571,7 @@ mod tests { QUESTION, None, chat, - route, + *route, false, ) .await From f3ffc79b1571bb1b726a9d6c4408e132f975ebe5 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 7 Oct 2026 00:35:22 +1300 Subject: [PATCH 2/3] perf(test): build zone_server's integration tests as one binary Cargo built each of the 100 files in zone_server/tests as its own executable, and each statically linked all of zone_server plus ONNX Runtime. On a clean build that was 14.8 GiB of 19 GiB in debug/deps, and every edit to zone_server relinked all of them. The files now live under tests/integration/ as modules of a single `integration` target (autotests = false), with the shared helpers at crate::common. Module names drop the redundant _tests suffix, so a test is addressed as `integration api::name` instead of `api_tests name`. With autotests off, a file dropped in tests/ or left out of main.rs would silently never compile; layout.rs fails in either case, which also catches branches that add test files at the old paths. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/rust-coverage.yml | 2 +- docs/SYSTEM-PROMPT-RESEARCH.md | 2 +- runner/zone_installer/src/migration/runner.rs | 2 +- runner/zone_server/Cargo.toml | 5 + runner/zone_server/src/workers/gathering.rs | 2 +- runner/zone_server/src/workers/task.rs | 2 +- runner/zone_server/tests/CONTEXT.md | 5 +- .../agent_host_login.rs} | 6 +- .../agent_login.rs} | 4 +- .../agent_session_resume.rs} | 8 +- .../ai_settings.rs} | 4 +- .../ai_settings_routing.rs} | 4 +- .../ai_theme_authorization.rs} | 4 +- .../analytics_window.rs} | 2 +- .../{api_tests.rs => integration/api.rs} | 6 +- .../artifacts.rs} | 5 +- .../{audit_tests.rs => integration/audit.rs} | 4 +- .../auth_console_contract.rs} | 4 +- .../auth_session_revocation.rs} | 2 +- .../authz_ai_settings_role.rs} | 6 +- .../authz_context_gather.rs} | 4 +- .../authz_cross_tenant_coverage.rs} | 6 +- .../authz_invitation_scope.rs} | 4 +- .../authz_project_enumeration.rs} | 4 +- .../authz_workspace_deletion.rs} | 4 +- .../background_job.rs} | 2 +- .../tests/{ => integration}/boundary_paths.rs | 3 +- .../tests/{ => integration}/boundary_ssrf.rs | 3 +- .../{ => integration}/boundary_uploads.rs | 3 +- .../{cache_tests.rs => integration/cache.rs} | 0 .../chat_agent.rs} | 2 +- .../chat_agent_tools.rs} | 6 +- .../chat_attached_sources.rs} | 3 +- .../chat_context.rs} | 8 +- .../chat_context_store.rs} | 2 +- .../chat_context_tokens.rs} | 4 +- .../chat_generation_contract.rs} | 4 +- .../chat_handover.rs} | 8 +- .../chat_lifecycle.rs} | 3 +- .../chat_offline.rs} | 4 +- .../chat_project.rs} | 4 +- .../chat_replay.rs} | 6 +- .../chat_sandbox.rs} | 4 +- .../chat_search.rs} | 4 +- .../chat_sessions.rs} | 8 +- .../chat_sources.rs} | 2 +- .../chat_title.rs} | 4 +- .../chat_vision_history.rs} | 3 +- .../chat_ws.rs} | 6 +- .../chat_ws_contract.rs} | 6 +- .../chat_ws_media_contract.rs} | 4 +- .../tests/{ => integration}/common/context.rs | 0 .../tests/{ => integration}/common/mod.rs | 0 .../{ => integration}/common/transcript.rs | 0 .../console_contract.rs} | 4 +- .../context.rs} | 6 +- .../cross_tenant_authorization.rs} | 4 +- .../deferred_tool.rs} | 4 +- .../email_verification.rs} | 2 - .../gathering_worker.rs} | 2 +- .../indexing.rs} | 6 +- .../invitations.rs} | 0 .../invitations_routes.rs} | 4 +- .../knowledge.rs} | 4 +- .../knowledge_recovery.rs} | 4 +- .../knowledge_url.rs} | 6 +- .../zone_server/tests/integration/layout.rs | 37 +++++++ .../learning_thresholds.rs} | 0 .../lora_flows.rs} | 2 +- runner/zone_server/tests/integration/main.rs | 102 ++++++++++++++++++ .../mcp_agent.rs} | 3 +- .../mcp_task_tools.rs} | 2 +- .../memory.rs} | 4 +- .../memory_isolation.rs} | 6 +- .../memory_store.rs} | 2 +- .../message_embedding.rs} | 6 +- .../migration_atomicity.rs} | 0 .../model_namespaces.rs} | 2 +- .../models.rs} | 2 +- .../organization_console_contract.rs} | 4 +- .../organization_members.rs} | 2 - .../password_reset.rs} | 2 - .../{plans_tests.rs => integration/plans.rs} | 2 - .../pr_reception_status.rs} | 2 +- .../prompt_assembly.rs} | 4 +- .../pull_ws.rs} | 2 +- .../reminder_turn.rs} | 4 +- .../role_demotion.rs} | 4 +- .../role_demotion_race.rs} | 6 +- .../sessions.rs} | 4 +- .../skills_store.rs} | 2 +- .../subscriptions.rs} | 2 - .../{sync_tests.rs => integration/sync.rs} | 2 +- .../task_authorization.rs} | 4 +- .../task_creation.rs} | 2 +- .../task_execution.rs} | 2 +- .../task_lifecycle.rs} | 2 +- .../task_migration.rs} | 0 .../task_pr_fields.rs} | 2 +- .../task_review_verdict.rs} | 2 +- .../task_run_ws_authorization.rs} | 2 +- .../task_scope.rs} | 2 +- .../tenant_admin_permissions.rs} | 4 +- .../tenant_isolation.rs} | 2 +- .../test_config.rs} | 2 +- .../{usage_tests.rs => integration/usage.rs} | 2 - .../wait_for.rs} | 2 +- .../workspace_creation_atomicity.rs} | 4 +- .../workspace_owner_backfill.rs} | 4 +- .../workspace_owner_floor.rs} | 4 +- .../tests/{ws_tests.rs => integration/ws.rs} | 2 +- .../ws_context.rs} | 2 +- 112 files changed, 285 insertions(+), 224 deletions(-) rename runner/zone_server/tests/{agent_host_login_tests.rs => integration/agent_host_login.rs} (96%) rename runner/zone_server/tests/{agent_login_tests.rs => integration/agent_login.rs} (99%) rename runner/zone_server/tests/{agent_session_resume_tests.rs => integration/agent_session_resume.rs} (96%) rename runner/zone_server/tests/{ai_settings_tests.rs => integration/ai_settings.rs} (99%) rename runner/zone_server/tests/{ai_settings_routing_tests.rs => integration/ai_settings_routing.rs} (99%) rename runner/zone_server/tests/{ai_theme_authorization_tests.rs => integration/ai_theme_authorization.rs} (99%) rename runner/zone_server/tests/{analytics_window_tests.rs => integration/analytics_window.rs} (99%) rename runner/zone_server/tests/{api_tests.rs => integration/api.rs} (99%) rename runner/zone_server/tests/{artifacts_tests.rs => integration/artifacts.rs} (99%) rename runner/zone_server/tests/{audit_tests.rs => integration/audit.rs} (99%) rename runner/zone_server/tests/{auth_console_contract_tests.rs => integration/auth_console_contract.rs} (99%) rename runner/zone_server/tests/{auth_session_revocation_tests.rs => integration/auth_session_revocation.rs} (99%) rename runner/zone_server/tests/{authz_ai_settings_role_tests.rs => integration/authz_ai_settings_role.rs} (98%) rename runner/zone_server/tests/{authz_context_gather_tests.rs => integration/authz_context_gather.rs} (98%) rename runner/zone_server/tests/{authz_cross_tenant_coverage_tests.rs => integration/authz_cross_tenant_coverage.rs} (99%) rename runner/zone_server/tests/{authz_invitation_scope_tests.rs => integration/authz_invitation_scope.rs} (98%) rename runner/zone_server/tests/{authz_project_enumeration_tests.rs => integration/authz_project_enumeration.rs} (98%) rename runner/zone_server/tests/{authz_workspace_deletion_tests.rs => integration/authz_workspace_deletion.rs} (98%) rename runner/zone_server/tests/{background_job_tests.rs => integration/background_job.rs} (99%) rename runner/zone_server/tests/{ => integration}/boundary_paths.rs (99%) rename runner/zone_server/tests/{ => integration}/boundary_ssrf.rs (99%) rename runner/zone_server/tests/{ => integration}/boundary_uploads.rs (98%) rename runner/zone_server/tests/{cache_tests.rs => integration/cache.rs} (100%) rename runner/zone_server/tests/{chat_agent_tests.rs => integration/chat_agent.rs} (99%) rename runner/zone_server/tests/{chat_agent_tools_tests.rs => integration/chat_agent_tools.rs} (99%) rename runner/zone_server/tests/{chat_attached_sources_tests.rs => integration/chat_attached_sources.rs} (99%) rename runner/zone_server/tests/{chat_context_tests.rs => integration/chat_context.rs} (99%) rename runner/zone_server/tests/{chat_context_store_tests.rs => integration/chat_context_store.rs} (99%) rename runner/zone_server/tests/{chat_context_tokens_tests.rs => integration/chat_context_tokens.rs} (98%) rename runner/zone_server/tests/{chat_generation_contract_tests.rs => integration/chat_generation_contract.rs} (98%) rename runner/zone_server/tests/{chat_handover_tests.rs => integration/chat_handover.rs} (99%) rename runner/zone_server/tests/{chat_lifecycle_tests.rs => integration/chat_lifecycle.rs} (98%) rename runner/zone_server/tests/{chat_offline_tests.rs => integration/chat_offline.rs} (98%) rename runner/zone_server/tests/{chat_project_tests.rs => integration/chat_project.rs} (98%) rename runner/zone_server/tests/{chat_replay_tests.rs => integration/chat_replay.rs} (99%) rename runner/zone_server/tests/{chat_sandbox_tests.rs => integration/chat_sandbox.rs} (99%) rename runner/zone_server/tests/{chat_search_tests.rs => integration/chat_search.rs} (99%) rename runner/zone_server/tests/{chat_sessions_tests.rs => integration/chat_sessions.rs} (99%) rename runner/zone_server/tests/{chat_sources_tests.rs => integration/chat_sources.rs} (99%) rename runner/zone_server/tests/{chat_title_tests.rs => integration/chat_title.rs} (99%) rename runner/zone_server/tests/{chat_vision_history_tests.rs => integration/chat_vision_history.rs} (97%) rename runner/zone_server/tests/{chat_ws_tests.rs => integration/chat_ws.rs} (99%) rename runner/zone_server/tests/{chat_ws_contract_tests.rs => integration/chat_ws_contract.rs} (99%) rename runner/zone_server/tests/{chat_ws_media_contract_tests.rs => integration/chat_ws_media_contract.rs} (99%) rename runner/zone_server/tests/{ => integration}/common/context.rs (100%) rename runner/zone_server/tests/{ => integration}/common/mod.rs (100%) rename runner/zone_server/tests/{ => integration}/common/transcript.rs (100%) rename runner/zone_server/tests/{console_contract_tests.rs => integration/console_contract.rs} (99%) rename runner/zone_server/tests/{context_tests.rs => integration/context.rs} (98%) rename runner/zone_server/tests/{cross_tenant_authorization_tests.rs => integration/cross_tenant_authorization.rs} (99%) rename runner/zone_server/tests/{deferred_tool_tests.rs => integration/deferred_tool.rs} (99%) rename runner/zone_server/tests/{email_verification_tests.rs => integration/email_verification.rs} (99%) rename runner/zone_server/tests/{gathering_worker_tests.rs => integration/gathering_worker.rs} (99%) rename runner/zone_server/tests/{indexing_tests.rs => integration/indexing.rs} (98%) rename runner/zone_server/tests/{invitations_tests.rs => integration/invitations.rs} (100%) rename runner/zone_server/tests/{invitations_routes_tests.rs => integration/invitations_routes.rs} (99%) rename runner/zone_server/tests/{knowledge_tests.rs => integration/knowledge.rs} (99%) rename runner/zone_server/tests/{knowledge_recovery_tests.rs => integration/knowledge_recovery.rs} (99%) rename runner/zone_server/tests/{knowledge_url_tests.rs => integration/knowledge_url.rs} (99%) create mode 100644 runner/zone_server/tests/integration/layout.rs rename runner/zone_server/tests/{learning_thresholds_tests.rs => integration/learning_thresholds.rs} (100%) rename runner/zone_server/tests/{lora_flows_tests.rs => integration/lora_flows.rs} (99%) create mode 100644 runner/zone_server/tests/integration/main.rs rename runner/zone_server/tests/{mcp_agent_tests.rs => integration/mcp_agent.rs} (99%) rename runner/zone_server/tests/{mcp_task_tools_tests.rs => integration/mcp_task_tools.rs} (99%) rename runner/zone_server/tests/{memory_tests.rs => integration/memory.rs} (99%) rename runner/zone_server/tests/{memory_isolation_tests.rs => integration/memory_isolation.rs} (99%) rename runner/zone_server/tests/{memory_store_tests.rs => integration/memory_store.rs} (99%) rename runner/zone_server/tests/{message_embedding_tests.rs => integration/message_embedding.rs} (99%) rename runner/zone_server/tests/{migration_atomicity_tests.rs => integration/migration_atomicity.rs} (100%) rename runner/zone_server/tests/{model_namespaces_tests.rs => integration/model_namespaces.rs} (99%) rename runner/zone_server/tests/{models_tests.rs => integration/models.rs} (99%) rename runner/zone_server/tests/{organization_console_contract_tests.rs => integration/organization_console_contract.rs} (99%) rename runner/zone_server/tests/{organization_members_tests.rs => integration/organization_members.rs} (99%) rename runner/zone_server/tests/{password_reset_tests.rs => integration/password_reset.rs} (99%) rename runner/zone_server/tests/{plans_tests.rs => integration/plans.rs} (99%) rename runner/zone_server/tests/{pr_reception_status_tests.rs => integration/pr_reception_status.rs} (99%) rename runner/zone_server/tests/{prompt_assembly_tests.rs => integration/prompt_assembly.rs} (99%) rename runner/zone_server/tests/{pull_ws_tests.rs => integration/pull_ws.rs} (99%) rename runner/zone_server/tests/{reminder_turn_tests.rs => integration/reminder_turn.rs} (99%) rename runner/zone_server/tests/{role_demotion_tests.rs => integration/role_demotion.rs} (99%) rename runner/zone_server/tests/{role_demotion_race_tests.rs => integration/role_demotion_race.rs} (98%) rename runner/zone_server/tests/{sessions_tests.rs => integration/sessions.rs} (99%) rename runner/zone_server/tests/{skills_store_tests.rs => integration/skills_store.rs} (99%) rename runner/zone_server/tests/{subscriptions_tests.rs => integration/subscriptions.rs} (99%) rename runner/zone_server/tests/{sync_tests.rs => integration/sync.rs} (99%) rename runner/zone_server/tests/{task_authorization_tests.rs => integration/task_authorization.rs} (99%) rename runner/zone_server/tests/{task_creation_tests.rs => integration/task_creation.rs} (99%) rename runner/zone_server/tests/{task_execution_tests.rs => integration/task_execution.rs} (99%) rename runner/zone_server/tests/{task_lifecycle_tests.rs => integration/task_lifecycle.rs} (99%) rename runner/zone_server/tests/{task_migration_tests.rs => integration/task_migration.rs} (100%) rename runner/zone_server/tests/{task_pr_fields_tests.rs => integration/task_pr_fields.rs} (99%) rename runner/zone_server/tests/{task_review_verdict_tests.rs => integration/task_review_verdict.rs} (99%) rename runner/zone_server/tests/{task_run_ws_authorization_tests.rs => integration/task_run_ws_authorization.rs} (99%) rename runner/zone_server/tests/{task_scope_tests.rs => integration/task_scope.rs} (99%) rename runner/zone_server/tests/{tenant_admin_permissions_tests.rs => integration/tenant_admin_permissions.rs} (97%) rename runner/zone_server/tests/{tenant_isolation_tests.rs => integration/tenant_isolation.rs} (99%) rename runner/zone_server/tests/{test_config_tests.rs => integration/test_config.rs} (97%) rename runner/zone_server/tests/{usage_tests.rs => integration/usage.rs} (99%) rename runner/zone_server/tests/{wait_for_tests.rs => integration/wait_for.rs} (99%) rename runner/zone_server/tests/{workspace_creation_atomicity_tests.rs => integration/workspace_creation_atomicity.rs} (98%) rename runner/zone_server/tests/{workspace_owner_backfill_tests.rs => integration/workspace_owner_backfill.rs} (98%) rename runner/zone_server/tests/{workspace_owner_floor_tests.rs => integration/workspace_owner_floor.rs} (99%) rename runner/zone_server/tests/{ws_tests.rs => integration/ws.rs} (99%) rename runner/zone_server/tests/{ws_context_tests.rs => integration/ws_context.rs} (99%) diff --git a/.github/workflows/rust-coverage.yml b/.github/workflows/rust-coverage.yml index 86bc8131..d5424485 100644 --- a/.github/workflows/rust-coverage.yml +++ b/.github/workflows/rust-coverage.yml @@ -27,7 +27,7 @@ jobs: working-directory: runner # zone_server's integration tests talk to real backing services: Postgres - # via tests/common::create_test_pool, and Valkey via cache_tests. Without + # via tests/integration/common::create_test_pool, and Valkey via cache::. Without # both, coverage fails before it can measure anything. services: postgres: diff --git a/docs/SYSTEM-PROMPT-RESEARCH.md b/docs/SYSTEM-PROMPT-RESEARCH.md index 7454e974..e56db398 100644 --- a/docs/SYSTEM-PROMPT-RESEARCH.md +++ b/docs/SYSTEM-PROMPT-RESEARCH.md @@ -373,7 +373,7 @@ GK 29 allows disclosure on explicit request. Recommend the same: an operator-own ## 5. Suggested PR sequence -1. **Prompt builder, P0 text, and the two media tool descriptions** (A1, A2, A4, B1, B4, C1 to C8, D1, D4, F1 text, F4, F5, G2, G3, H3, H5, J1, K1, K3, L2, L3, N1 to N4). One PR that restructures `agent::system_prompt`, `session::system_prompt` and `task::guidance` into named sections with snapshot tests and adds every prompt-only item. Add scripted-completion tests using the wiremock LiteLLM stub that `runner/zone_server/tests/chat_agent_tests.rs` and its siblings already use, for the behaviours that matter most: a failure reported first, no "Done." reply, no invented citation. +1. **Prompt builder, P0 text, and the two media tool descriptions** (A1, A2, A4, B1, B4, C1 to C8, D1, D4, F1 text, F4, F5, G2, G3, H3, H5, J1, K1, K3, L2, L3, N1 to N4). One PR that restructures `agent::system_prompt`, `session::system_prompt` and `task::guidance` into named sections with snapshot tests and adds every prompt-only item. Add scripted-completion tests using the wiremock LiteLLM stub that `runner/zone_server/tests/integration/chat_agent.rs` and its siblings already use, for the behaviours that matter most: a failure reported first, no "Done." reply, no invented citation. 2. **Repository instruction files and effort dial** (A3, A4 wiring). 3. **Tool schema pass** (E1, E3, F1 `time_range`, F2, F3, E2 output cap). Approval card and receipts show the `description`. 4. **Action tiers and approval preview** (B2, B3, D3 watchdog, J2 commit and PR content). diff --git a/runner/zone_installer/src/migration/runner.rs b/runner/zone_installer/src/migration/runner.rs index 9eafdf42..808ba08d 100644 --- a/runner/zone_installer/src/migration/runner.rs +++ b/runner/zone_installer/src/migration/runner.rs @@ -118,7 +118,7 @@ async fn apply( /// row is written. Those six cannot be edited to fix it -- both runners key a migration by /// a hash of its bytes and refuse to start when a recorded one changes -- so what makes the /// window survivable is that all six are idempotent and the re-run is a no-op. That property -/// is held by `zone_server/tests/migration_atomicity_tests.rs`, which also stops a seventh +/// is held by `zone_server/tests/integration/migration_atomicity.rs`, which also stops a seventh /// joining the set. async fn execute( connection: &mut PgConnection, diff --git a/runner/zone_server/Cargo.toml b/runner/zone_server/Cargo.toml index 17cedda2..35eb028e 100644 --- a/runner/zone_server/Cargo.toml +++ b/runner/zone_server/Cargo.toml @@ -6,6 +6,7 @@ edition.workspace = true license.workspace = true repository.workspace = true rust-version.workspace = true +autotests = false [features] default = ["local-embeddings", "saliency"] @@ -24,6 +25,10 @@ path = "src/lib.rs" name = "zone-server" path = "src/main.rs" +[[test]] +name = "integration" +path = "tests/integration/main.rs" + [dependencies] tokio = { workspace = true } serde = { workspace = true } diff --git a/runner/zone_server/src/workers/gathering.rs b/runner/zone_server/src/workers/gathering.rs index 611ab44f..0b001925 100644 --- a/runner/zone_server/src/workers/gathering.rs +++ b/runner/zone_server/src/workers/gathering.rs @@ -418,4 +418,4 @@ pub(crate) fn core_source_from_row( }) } -// Integration tests are in zone_server/tests/gathering_worker_tests.rs +// Integration tests are in zone_server/tests/integration/gathering_worker.rs diff --git a/runner/zone_server/src/workers/task.rs b/runner/zone_server/src/workers/task.rs index 2da5d612..ed71a98b 100644 --- a/runner/zone_server/src/workers/task.rs +++ b/runner/zone_server/src/workers/task.rs @@ -3572,7 +3572,7 @@ mod tests { .unwrap(); } - // Integration tests are in zone_server/tests/task_execution_tests.rs + // Integration tests are in zone_server/tests/integration/task_execution.rs #[tokio::test] async fn task_loop_persists_workspace_receipt_before_returning() { diff --git a/runner/zone_server/tests/CONTEXT.md b/runner/zone_server/tests/CONTEXT.md index a93d2d8f..1e3dcefc 100644 --- a/runner/zone_server/tests/CONTEXT.md +++ b/runner/zone_server/tests/CONTEXT.md @@ -5,11 +5,14 @@ Run against a disposable PostgreSQL database with migrations 001 through 015 app ```sh TEST_DATABASE_URL=postgres://postgres:postgres@127.0.0.1:49755/zone_context_test \ DATABASE_URL=postgres://postgres:postgres@127.0.0.1:49755/zone_context_test \ -cargo test -p zone_server --test chat_context_tests --test chat_replay_tests --test chat_context_store_tests --test chat_lifecycle_tests --no-default-features --features zone_context/test-utils +cargo nextest run -p zone_server --test integration --no-default-features --features zone_context/test-utils \ + -E 'test(/^(chat_context|chat_replay|chat_context_store|chat_lifecycle)::/)' ``` The fixtures serve the real router on an ephemeral local port, authenticate through its public API, and replace inference and model metadata with deterministic Wiremock responses. They disable MCP startup and external web/image services. Tool mutations are confined to per-test temporary directories. Tests retain rows only in the disposable database; remove the database when finished. +Every file under `integration/` is a module of the one `integration` test binary (`autotests = false` in `Cargo.toml`), so a new test file must be declared in `integration/main.rs`; `integration/layout.rs` fails when one is not. Shared helpers live in `integration/common/`, reached as `crate::common`. All modules share one process under plain `cargo test` and `cargo llvm-cov`, while nextest runs each test in its own. + `fixtures/context.json` is checked against Rust `ContextUsage` serialization and shared with frontend E2E tests. ## Library tests that need the same database diff --git a/runner/zone_server/tests/agent_host_login_tests.rs b/runner/zone_server/tests/integration/agent_host_login.rs similarity index 96% rename from runner/zone_server/tests/agent_host_login_tests.rs rename to runner/zone_server/tests/integration/agent_host_login.rs index 7b1f5420..ba438a2d 100644 --- a/runner/zone_server/tests/agent_host_login_tests.rs +++ b/runner/zone_server/tests/integration/agent_host_login.rs @@ -5,13 +5,11 @@ //! migrated database: //! //! `CLAUDE_CONFIG_DIR=/tmp/empty cargo test -p zone_server --no-default-features -//! --features zone_context/test-utils --test agent_host_login_tests -- --ignored` +//! --features zone_context/test-utils --test integration -- --ignored agent_host_login::` //! //! `CLAUDE_CONFIG_DIR` points at an empty directory on purpose: the server's own //! environment must not reach the agent, so claude still finds the host login. -mod common; - use std::path::PathBuf; use axum::http::StatusCode; @@ -23,7 +21,7 @@ use zone_server::config::{AgentConfig, Config}; use zone_server::services::route::Route; use zone_server::services::stages::AUTO; -use common::{TestClient, test_config, test_email, test_password}; +use crate::common::{TestClient, test_config, test_email, test_password}; const PROVIDER: &str = "claude_code"; diff --git a/runner/zone_server/tests/agent_login_tests.rs b/runner/zone_server/tests/integration/agent_login.rs similarity index 99% rename from runner/zone_server/tests/agent_login_tests.rs rename to runner/zone_server/tests/integration/agent_login.rs index 17f4d27c..8e340e74 100644 --- a/runner/zone_server/tests/agent_login_tests.rs +++ b/runner/zone_server/tests/integration/agent_login.rs @@ -4,7 +4,7 @@ //! real one printed, so no test here signs in to anything real. The one test that asks the host's //! own claude is ignored. -mod common; +use crate::common; use std::fs; use std::net::{Ipv4Addr, SocketAddr}; @@ -35,7 +35,7 @@ use zone_server::config::{AgentConfig, Callback, Config, ModelBackend}; use zone_server::services::login::claude::{AUTHORIZE_URL, CLIENT_ID, REDIRECT_URL, Tokens}; use zone_server::services::login::{callback, devices}; -use common::{TestClient, test_email, test_password}; +use crate::common::{TestClient, test_email, test_password}; const FIXTURES: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/fixtures/login"); const TOKEN_PATH: &str = "/v1/oauth/token"; diff --git a/runner/zone_server/tests/agent_session_resume_tests.rs b/runner/zone_server/tests/integration/agent_session_resume.rs similarity index 96% rename from runner/zone_server/tests/agent_session_resume_tests.rs rename to runner/zone_server/tests/integration/agent_session_resume.rs index 89e2083d..ce44f844 100644 --- a/runner/zone_server/tests/agent_session_resume_tests.rs +++ b/runner/zone_server/tests/integration/agent_session_resume.rs @@ -5,12 +5,12 @@ //! for itself. Run by hand, from `runner/`: //! //! `CLAUDE_CODE_OAUTH_TOKEN= cargo test -p zone_server -//! --no-default-features --features zone_context/test-utils --test agent_session_resume_tests -//! -- --ignored --exact claude_session_resume_across_homes` +//! --no-default-features --features zone_context/test-utils --test integration +//! -- --ignored --exact agent_session_resume::claude_session_resume_across_homes` //! //! `ZONE_SPIKE_CODEX_HOMES=: cargo test -p zone_server --no-default-features -//! --features zone_context/test-utils --test agent_session_resume_tests -- --ignored --exact -//! codex_session_resume_across_homes`, where each home was signed in with +//! --features zone_context/test-utils --test integration -- --ignored --exact +//! agent_session_resume::codex_session_resume_across_homes`, where each home was signed in with //! `CODEX_HOME= codex login`. use std::env; diff --git a/runner/zone_server/tests/ai_settings_tests.rs b/runner/zone_server/tests/integration/ai_settings.rs similarity index 99% rename from runner/zone_server/tests/ai_settings_tests.rs rename to runner/zone_server/tests/integration/ai_settings.rs index 221bb7f7..fb5ce7f1 100644 --- a/runner/zone_server/tests/ai_settings_tests.rs +++ b/runner/zone_server/tests/integration/ai_settings.rs @@ -1,6 +1,6 @@ //! AI Settings integration tests -mod common; +use crate::common; use abnegate_secret::OptionalSecretExtension; use axum::http::StatusCode; @@ -8,7 +8,7 @@ use serde_json::json; use uuid::Uuid; use zone_server::db::ai_settings; -use common::{TestClient, test_email, test_password}; +use crate::common::{TestClient, test_email, test_password}; const AGENT_PROVIDERS: [&str; 2] = ["claude_code", "codex"]; const MODEL_FIELDS: [&str; 3] = ["model_fast", "model_reasoning", "model_embedding"]; diff --git a/runner/zone_server/tests/ai_settings_routing_tests.rs b/runner/zone_server/tests/integration/ai_settings_routing.rs similarity index 99% rename from runner/zone_server/tests/ai_settings_routing_tests.rs rename to runner/zone_server/tests/integration/ai_settings_routing.rs index acca7d73..f5f8404c 100644 --- a/runner/zone_server/tests/ai_settings_routing_tests.rs +++ b/runner/zone_server/tests/integration/ai_settings_routing.rs @@ -5,9 +5,7 @@ //! settings are written straight into the tables, so what is under test is the //! read side alone: which host a completion reaches and which key rides with it. -mod common; - -use common::{ +use crate::common::{ TestClient, TestResponse, create_test_pool, create_test_router, create_test_state, discard, next_frame, seed_chat, serve, setup_test_data, setup_workspace_member, test_config, test_email, test_password, diff --git a/runner/zone_server/tests/ai_theme_authorization_tests.rs b/runner/zone_server/tests/integration/ai_theme_authorization.rs similarity index 99% rename from runner/zone_server/tests/ai_theme_authorization_tests.rs rename to runner/zone_server/tests/integration/ai_theme_authorization.rs index c6d82fd1..4864a4bc 100644 --- a/runner/zone_server/tests/ai_theme_authorization_tests.rs +++ b/runner/zone_server/tests/integration/ai_theme_authorization.rs @@ -1,7 +1,5 @@ //! Regression coverage for AI settings and workspace theme authorization. -mod common; - use std::time::Duration; use axum::http::StatusCode; @@ -10,7 +8,7 @@ use sqlx::{PgPool, postgres::PgPoolOptions, postgres::PgQueryResult}; use uuid::Uuid; use zone_server::db::{organization_members, workspace_members}; -use common::{ +use crate::common::{ TestClient, create_test_pool, create_test_router, create_test_state, test_config, test_password, }; diff --git a/runner/zone_server/tests/analytics_window_tests.rs b/runner/zone_server/tests/integration/analytics_window.rs similarity index 99% rename from runner/zone_server/tests/analytics_window_tests.rs rename to runner/zone_server/tests/integration/analytics_window.rs index e9269de4..e16fe6c8 100644 --- a/runner/zone_server/tests/analytics_window_tests.rs +++ b/runner/zone_server/tests/integration/analytics_window.rs @@ -8,7 +8,7 @@ //! gauges went empty on the busiest workspaces -- the ones anyone would be //! watching. -mod common; +use crate::common; use chrono::{Duration, Utc}; use sqlx::PgPool; diff --git a/runner/zone_server/tests/api_tests.rs b/runner/zone_server/tests/integration/api.rs similarity index 99% rename from runner/zone_server/tests/api_tests.rs rename to runner/zone_server/tests/integration/api.rs index d8b35c0a..793000d1 100644 --- a/runner/zone_server/tests/api_tests.rs +++ b/runner/zone_server/tests/integration/api.rs @@ -1,16 +1,16 @@ //! Integration tests for the Zone Server API //! //! These tests exercise the HTTP endpoints with a real database. -//! Run with: SQLX_OFFLINE=true cargo test --test api_tests +//! Run with: SQLX_OFFLINE=true cargo nextest run -p zone_server --test integration -E 'test(/^api::/)' #![allow(unused_variables)] -mod common; +use crate::common; use axum::http::StatusCode; use serde_json::json; -use common::{TestClient, test_email, test_password}; +use crate::common::{TestClient, test_email, test_password}; // Health Check Tests diff --git a/runner/zone_server/tests/artifacts_tests.rs b/runner/zone_server/tests/integration/artifacts.rs similarity index 99% rename from runner/zone_server/tests/artifacts_tests.rs rename to runner/zone_server/tests/integration/artifacts.rs index e1ef4765..01709bb6 100644 --- a/runner/zone_server/tests/artifacts_tests.rs +++ b/runner/zone_server/tests/integration/artifacts.rs @@ -1,8 +1,7 @@ //! Byte-range serving for generated media through the real authenticated router. -mod common; -use common::context::Harness; -use common::{test_email, test_password}; +use crate::common::context::Harness; +use crate::common::{test_email, test_password}; use serde_json::json; use std::path::PathBuf; use uuid::Uuid; diff --git a/runner/zone_server/tests/audit_tests.rs b/runner/zone_server/tests/integration/audit.rs similarity index 99% rename from runner/zone_server/tests/audit_tests.rs rename to runner/zone_server/tests/integration/audit.rs index c8d7a46a..8709bc02 100644 --- a/runner/zone_server/tests/audit_tests.rs +++ b/runner/zone_server/tests/integration/audit.rs @@ -1,9 +1,7 @@ //! Integration tests for audit logging functionality -mod common; - +use crate::common::{create_test_pool, setup_test_data}; use chrono::{Duration, Utc}; -use common::{create_test_pool, setup_test_data}; use uuid::Uuid; #[tokio::test] diff --git a/runner/zone_server/tests/auth_console_contract_tests.rs b/runner/zone_server/tests/integration/auth_console_contract.rs similarity index 99% rename from runner/zone_server/tests/auth_console_contract_tests.rs rename to runner/zone_server/tests/integration/auth_console_contract.rs index 8803ed89..0924100d 100644 --- a/runner/zone_server/tests/auth_console_contract_tests.rs +++ b/runner/zone_server/tests/integration/auth_console_contract.rs @@ -4,13 +4,11 @@ //! and the current session, and revoking the other sessions kills their //! refresh tokens while keeping the caller's own. -mod common; - use axum::http::StatusCode; use serde_json::{Value, json}; use uuid::Uuid; -use common::{TestClient, test_email, test_password}; +use crate::common::{TestClient, test_email, test_password}; use zone_server::db::{email_verification, password_reset}; struct Login { diff --git a/runner/zone_server/tests/auth_session_revocation_tests.rs b/runner/zone_server/tests/integration/auth_session_revocation.rs similarity index 99% rename from runner/zone_server/tests/auth_session_revocation_tests.rs rename to runner/zone_server/tests/integration/auth_session_revocation.rs index f99e9628..33c011eb 100644 --- a/runner/zone_server/tests/auth_session_revocation_tests.rs +++ b/runner/zone_server/tests/integration/auth_session_revocation.rs @@ -1,4 +1,4 @@ -mod common; +use crate::common; use axum::{Router, http::StatusCode, middleware, routing::get}; use uuid::Uuid; diff --git a/runner/zone_server/tests/authz_ai_settings_role_tests.rs b/runner/zone_server/tests/integration/authz_ai_settings_role.rs similarity index 98% rename from runner/zone_server/tests/authz_ai_settings_role_tests.rs rename to runner/zone_server/tests/integration/authz_ai_settings_role.rs index 6fe98534..1d05fa5a 100644 --- a/runner/zone_server/tests/authz_ai_settings_role_tests.rs +++ b/runner/zone_server/tests/integration/authz_ai_settings_role.rs @@ -4,16 +4,14 @@ //! admins and owners included -- while the organization's own credential stayed //! in effect and travelled to the host they chose. //! -//! `cross_tenant_authorization_tests` already pins that a stranger cannot +//! `cross_tenant_authorization` already pins that a stranger cannot //! repoint another tenant's model host. This pins that a member of the tenant //! cannot either. -mod common; - use axum::http::StatusCode; use serde_json::json; -use common::{TestClient, test_email, test_password}; +use crate::common::{TestClient, test_email, test_password}; struct Account { token: String, diff --git a/runner/zone_server/tests/authz_context_gather_tests.rs b/runner/zone_server/tests/integration/authz_context_gather.rs similarity index 98% rename from runner/zone_server/tests/authz_context_gather_tests.rs rename to runner/zone_server/tests/integration/authz_context_gather.rs index 3ee7e345..99b36b2c 100644 --- a/runner/zone_server/tests/authz_context_gather_tests.rs +++ b/runner/zone_server/tests/integration/authz_context_gather.rs @@ -3,12 +3,10 @@ //! gather asked only whether the caller was a member at all, so the read-only //! role could start the same work. -mod common; - use axum::http::StatusCode; use serde_json::json; -use common::{TestClient, test_email, test_password}; +use crate::common::{TestClient, test_email, test_password}; struct Account { token: String, diff --git a/runner/zone_server/tests/authz_cross_tenant_coverage_tests.rs b/runner/zone_server/tests/integration/authz_cross_tenant_coverage.rs similarity index 99% rename from runner/zone_server/tests/authz_cross_tenant_coverage_tests.rs rename to runner/zone_server/tests/integration/authz_cross_tenant_coverage.rs index 87359871..0fe5096d 100644 --- a/runner/zone_server/tests/authz_cross_tenant_coverage_tests.rs +++ b/runner/zone_server/tests/integration/authz_cross_tenant_coverage.rs @@ -1,15 +1,15 @@ -//! `cross_tenant_authorization_tests` covers tasks, task runs, AI settings and +//! `cross_tenant_authorization` covers tasks, task runs, AI settings and //! branding. These are the id-addressed routes it does not: chats and their //! messages, sources, knowledge, audit logs, billing, and session revocation. //! Each one takes a resource id straight from the request, so each one is //! driven here with a stranger's id to pin that the binding holds. -mod common; +use crate::common; use axum::http::StatusCode; use serde_json::json; -use common::{TestClient, test_email, test_password}; +use crate::common::{TestClient, test_email, test_password}; struct Tenant { token: String, diff --git a/runner/zone_server/tests/authz_invitation_scope_tests.rs b/runner/zone_server/tests/integration/authz_invitation_scope.rs similarity index 98% rename from runner/zone_server/tests/authz_invitation_scope_tests.rs rename to runner/zone_server/tests/integration/authz_invitation_scope.rs index ef9e5da5..c0307b87 100644 --- a/runner/zone_server/tests/authz_invitation_scope_tests.rs +++ b/runner/zone_server/tests/integration/authz_invitation_scope.rs @@ -7,12 +7,10 @@ //! Written from the attacker's side: an organization the attacker owns outright //! is used as a lever to seat an accomplice inside a stranger's workspace. -mod common; - use axum::http::StatusCode; use serde_json::json; -use common::{TestClient, test_email, test_password}; +use crate::common::{TestClient, test_email, test_password}; struct Account { token: String, diff --git a/runner/zone_server/tests/authz_project_enumeration_tests.rs b/runner/zone_server/tests/integration/authz_project_enumeration.rs similarity index 98% rename from runner/zone_server/tests/authz_project_enumeration_tests.rs rename to runner/zone_server/tests/integration/authz_project_enumeration.rs index b1fa92eb..4609f072 100644 --- a/runner/zone_server/tests/authz_project_enumeration_tests.rs +++ b/runner/zone_server/tests/integration/authz_project_enumeration.rs @@ -4,12 +4,10 @@ //! workspace` for a stranger's project and `404` for one that does not exist, //! so the pair separated real project ids from invented ones. -mod common; - use axum::http::StatusCode; use serde_json::json; -use common::{TestClient, test_email, test_password}; +use crate::common::{TestClient, test_email, test_password}; struct Tenant { token: String, diff --git a/runner/zone_server/tests/authz_workspace_deletion_tests.rs b/runner/zone_server/tests/integration/authz_workspace_deletion.rs similarity index 98% rename from runner/zone_server/tests/authz_workspace_deletion_tests.rs rename to runner/zone_server/tests/integration/authz_workspace_deletion.rs index 113b47f9..9eecb30c 100644 --- a/runner/zone_server/tests/authz_workspace_deletion_tests.rs +++ b/runner/zone_server/tests/integration/authz_workspace_deletion.rs @@ -7,12 +7,10 @@ //! Written from the attacker's side: an admin the owner seated deletes the //! workspace out from under them. -mod common; - use axum::http::StatusCode; use serde_json::json; -use common::{TestClient, test_email, test_password}; +use crate::common::{TestClient, test_email, test_password}; struct Account { token: String, diff --git a/runner/zone_server/tests/background_job_tests.rs b/runner/zone_server/tests/integration/background_job.rs similarity index 99% rename from runner/zone_server/tests/background_job_tests.rs rename to runner/zone_server/tests/integration/background_job.rs index 8b176921..743738be 100644 --- a/runner/zone_server/tests/background_job_tests.rs +++ b/runner/zone_server/tests/integration/background_job.rs @@ -2,7 +2,7 @@ //! checkout it runs in, and who kills the child when the session that started //! the job ends. -mod common; +use crate::common; use std::collections::HashMap; use std::path::{Path, PathBuf}; diff --git a/runner/zone_server/tests/boundary_paths.rs b/runner/zone_server/tests/integration/boundary_paths.rs similarity index 99% rename from runner/zone_server/tests/boundary_paths.rs rename to runner/zone_server/tests/integration/boundary_paths.rs index 6a9b66c4..7278b7c9 100644 --- a/runner/zone_server/tests/boundary_paths.rs +++ b/runner/zone_server/tests/integration/boundary_paths.rs @@ -1,9 +1,8 @@ //! Path-boundary attacks against artifact serving, the artifact store, and the //! attachment resolver that feeds image-to-image from chat metadata. -mod common; +use crate::common::context::Harness; use axum::http::StatusCode; -use common::context::Harness; use serde_json::json; use std::path::PathBuf; use uuid::Uuid; diff --git a/runner/zone_server/tests/boundary_ssrf.rs b/runner/zone_server/tests/integration/boundary_ssrf.rs similarity index 99% rename from runner/zone_server/tests/boundary_ssrf.rs rename to runner/zone_server/tests/integration/boundary_ssrf.rs index c74bd3e7..54e7843a 100644 --- a/runner/zone_server/tests/boundary_ssrf.rs +++ b/runner/zone_server/tests/integration/boundary_ssrf.rs @@ -1,11 +1,10 @@ //! Outbound-fetch boundaries: every spelling of a private target a caller can //! put in a URL, and the redirect hop that carries a validated fetch somewhere //! the validator would have refused. -mod common; +use crate::common::{TestClient, test_email, test_password}; use abnegate_http::{public_client, read_capped, validate_public_url}; use axum::http::StatusCode; -use common::{TestClient, test_email, test_password}; use serde_json::json; use uuid::Uuid; use wiremock::matchers::{method, path}; diff --git a/runner/zone_server/tests/boundary_uploads.rs b/runner/zone_server/tests/integration/boundary_uploads.rs similarity index 98% rename from runner/zone_server/tests/boundary_uploads.rs rename to runner/zone_server/tests/integration/boundary_uploads.rs index 6798c03e..d402494b 100644 --- a/runner/zone_server/tests/boundary_uploads.rs +++ b/runner/zone_server/tests/integration/boundary_uploads.rs @@ -1,10 +1,9 @@ //! Upload boundaries: the name a client attaches to a clip is a label, not a //! destination, and the bytes are what decide whether it is media at all. -mod common; +use crate::common::{TestClient, test_config, test_email, test_password}; use axum::http::StatusCode; use base64::Engine; -use common::{TestClient, test_config, test_email, test_password}; use serde_json::json; use uuid::Uuid; diff --git a/runner/zone_server/tests/cache_tests.rs b/runner/zone_server/tests/integration/cache.rs similarity index 100% rename from runner/zone_server/tests/cache_tests.rs rename to runner/zone_server/tests/integration/cache.rs diff --git a/runner/zone_server/tests/chat_agent_tests.rs b/runner/zone_server/tests/integration/chat_agent.rs similarity index 99% rename from runner/zone_server/tests/chat_agent_tests.rs rename to runner/zone_server/tests/integration/chat_agent.rs index c8b260b8..d3e4c84a 100644 --- a/runner/zone_server/tests/chat_agent_tests.rs +++ b/runner/zone_server/tests/integration/chat_agent.rs @@ -1,5 +1,5 @@ //! Exercise the real streaming loop against deterministic provider responses. -mod common; +use crate::common; use futures::StreamExt; use serde_json::{Value, json}; diff --git a/runner/zone_server/tests/chat_agent_tools_tests.rs b/runner/zone_server/tests/integration/chat_agent_tools.rs similarity index 99% rename from runner/zone_server/tests/chat_agent_tools_tests.rs rename to runner/zone_server/tests/integration/chat_agent_tools.rs index 2a36e1e4..71ea1f62 100644 --- a/runner/zone_server/tests/chat_agent_tools_tests.rs +++ b/runner/zone_server/tests/integration/chat_agent_tools.rs @@ -8,10 +8,10 @@ //! the agent did. This drives that through the real websocket, against a //! stand-in for the host's `claude` that calls back the way the real one does. -mod common; +use crate::common; -use common::context::{finish, send}; -use common::{ +use crate::common::context::{finish, send}; +use crate::common::{ TestClient, context_database_url, create_test_router, create_test_state, test_config, test_email, test_password, }; diff --git a/runner/zone_server/tests/chat_attached_sources_tests.rs b/runner/zone_server/tests/integration/chat_attached_sources.rs similarity index 99% rename from runner/zone_server/tests/chat_attached_sources_tests.rs rename to runner/zone_server/tests/integration/chat_attached_sources.rs index 1b9fbe81..9fc87d42 100644 --- a/runner/zone_server/tests/chat_attached_sources_tests.rs +++ b/runner/zone_server/tests/integration/chat_attached_sources.rs @@ -1,9 +1,8 @@ //! Attaching sources to a chat: the attachment round-trips, stays inside the //! chat's workspace, and is what a retrieval for that chat is confined to. -mod common; +use crate::common::{TestClient, seed_chat}; use axum::http::StatusCode; -use common::{TestClient, seed_chat}; use serde_json::json; use uuid::Uuid; use zone_server::agent::tools::{ChatTools, WorkspaceScope}; diff --git a/runner/zone_server/tests/chat_context_tests.rs b/runner/zone_server/tests/integration/chat_context.rs similarity index 99% rename from runner/zone_server/tests/chat_context_tests.rs rename to runner/zone_server/tests/integration/chat_context.rs index 5dc1a7af..fe36b419 100644 --- a/runner/zone_server/tests/chat_context_tests.rs +++ b/runner/zone_server/tests/integration/chat_context.rs @@ -1,11 +1,11 @@ //! Conversation context acceptance against the authenticated router and WebSocket. -mod common; +use crate::common; -use axum::http::StatusCode; -use common::context::{ +use crate::common::context::{ Harness, MODEL, answer, finish, ordinary, send, state, successful, summaries, summary_response, usage, }; +use axum::http::StatusCode; use serde_json::{Value, json}; use std::time::Duration; use uuid::Uuid; @@ -41,7 +41,7 @@ fn shared_context_fixture_matches_rust_serialization() { updated_at: "2026-09-06T00:00:00Z".into(), reason: None, }; - let fixture: Value = serde_json::from_str(include_str!("fixtures/context.json")).unwrap(); + let fixture: Value = serde_json::from_str(include_str!("../fixtures/context.json")).unwrap(); assert_eq!(serde_json::to_value(&expected).unwrap(), fixture); assert_eq!(usage(&fixture), expected); } diff --git a/runner/zone_server/tests/chat_context_store_tests.rs b/runner/zone_server/tests/integration/chat_context_store.rs similarity index 99% rename from runner/zone_server/tests/chat_context_store_tests.rs rename to runner/zone_server/tests/integration/chat_context_store.rs index 1fb41d5c..8fd7075b 100644 --- a/runner/zone_server/tests/chat_context_store_tests.rs +++ b/runner/zone_server/tests/integration/chat_context_store.rs @@ -1,6 +1,6 @@ //! Run against an explicitly selected disposable database, never the application's DB. -mod common; +use crate::common; use serde_json::json; use sqlx::{Executor, PgPool}; diff --git a/runner/zone_server/tests/chat_context_tokens_tests.rs b/runner/zone_server/tests/integration/chat_context_tokens.rs similarity index 98% rename from runner/zone_server/tests/chat_context_tokens_tests.rs rename to runner/zone_server/tests/integration/chat_context_tokens.rs index 4dd414a2..901eee4b 100644 --- a/runner/zone_server/tests/chat_context_tokens_tests.rs +++ b/runner/zone_server/tests/integration/chat_context_tokens.rs @@ -1,12 +1,10 @@ //! A new chat can choose how large a local model's context window is. -mod common; - use axum::http::StatusCode; use serde_json::{Value, json}; use uuid::Uuid; -use common::{TestClient, test_email, test_password}; +use crate::common::{TestClient, test_email, test_password}; async fn register(client: &TestClient) -> String { let response = client diff --git a/runner/zone_server/tests/chat_generation_contract_tests.rs b/runner/zone_server/tests/integration/chat_generation_contract.rs similarity index 98% rename from runner/zone_server/tests/chat_generation_contract_tests.rs rename to runner/zone_server/tests/integration/chat_generation_contract.rs index d92d3bc6..ef585d78 100644 --- a/runner/zone_server/tests/chat_generation_contract_tests.rs +++ b/runner/zone_server/tests/integration/chat_generation_contract.rs @@ -1,8 +1,6 @@ //! Generation boundary contracts exercised through the real WebSocket handler. -mod common; - -use common::context::{Harness, answer, calls, delta, finish, next, send, successful, tool}; +use crate::common::context::{Harness, answer, calls, delta, finish, next, send, successful, tool}; use serde_json::json; use std::time::Duration; diff --git a/runner/zone_server/tests/chat_handover_tests.rs b/runner/zone_server/tests/integration/chat_handover.rs similarity index 99% rename from runner/zone_server/tests/chat_handover_tests.rs rename to runner/zone_server/tests/integration/chat_handover.rs index 2abb9b8f..8c715877 100644 --- a/runner/zone_server/tests/chat_handover_tests.rs +++ b/runner/zone_server/tests/integration/chat_handover.rs @@ -10,11 +10,9 @@ //! not there. Every run of either stand-in records how it was invoked, where, //! under which home and MCP token, and what it read on stdin. -mod common; - -use common::context::{finish, next, send, successful}; -use common::transcript::{self, Speaker}; -use common::{ +use crate::common::context::{finish, next, send, successful}; +use crate::common::transcript::{self, Speaker}; +use crate::common::{ TestClient, context_database_url, create_test_router, create_test_state, test_config, test_email, test_password, }; diff --git a/runner/zone_server/tests/chat_lifecycle_tests.rs b/runner/zone_server/tests/integration/chat_lifecycle.rs similarity index 98% rename from runner/zone_server/tests/chat_lifecycle_tests.rs rename to runner/zone_server/tests/integration/chat_lifecycle.rs index b0f92398..66b74b4a 100644 --- a/runner/zone_server/tests/chat_lifecycle_tests.rs +++ b/runner/zone_server/tests/integration/chat_lifecycle.rs @@ -1,7 +1,6 @@ //! Generation ownership and deadline acceptance through the real authenticated router. -mod common; -use common::context::{Harness, Socket, answer, finish, next, send, successful}; +use crate::common::context::{Harness, Socket, answer, finish, next, send, successful}; use serde_json::json; use std::process::Command; use std::time::{Duration, Instant}; diff --git a/runner/zone_server/tests/chat_offline_tests.rs b/runner/zone_server/tests/integration/chat_offline.rs similarity index 98% rename from runner/zone_server/tests/chat_offline_tests.rs rename to runner/zone_server/tests/integration/chat_offline.rs index a7012d9f..4d207551 100644 --- a/runner/zone_server/tests/chat_offline_tests.rs +++ b/runner/zone_server/tests/integration/chat_offline.rs @@ -1,12 +1,10 @@ //! A new chat can opt out of public web for its lifetime. -mod common; - use axum::http::StatusCode; use serde_json::{Value, json}; use uuid::Uuid; -use common::{TestClient, test_email, test_password}; +use crate::common::{TestClient, test_email, test_password}; async fn register(client: &TestClient) -> String { let response = client diff --git a/runner/zone_server/tests/chat_project_tests.rs b/runner/zone_server/tests/integration/chat_project.rs similarity index 98% rename from runner/zone_server/tests/chat_project_tests.rs rename to runner/zone_server/tests/integration/chat_project.rs index 9e52b693..12de2a7b 100644 --- a/runner/zone_server/tests/chat_project_tests.rs +++ b/runner/zone_server/tests/integration/chat_project.rs @@ -1,13 +1,11 @@ //! A new chat can be bound to a workspace project. The list then carries that //! project so the console can group by it. -mod common; - use axum::http::StatusCode; use serde_json::{Value, json}; use uuid::Uuid; -use common::{TestClient, test_email, test_password}; +use crate::common::{TestClient, test_email, test_password}; async fn register(client: &TestClient) -> String { let response = client diff --git a/runner/zone_server/tests/chat_replay_tests.rs b/runner/zone_server/tests/integration/chat_replay.rs similarity index 99% rename from runner/zone_server/tests/chat_replay_tests.rs rename to runner/zone_server/tests/integration/chat_replay.rs index ef58adda..b53ab2a2 100644 --- a/runner/zone_server/tests/chat_replay_tests.rs +++ b/runner/zone_server/tests/integration/chat_replay.rs @@ -1,10 +1,10 @@ //! Durable replay and productive-loop acceptance through real WebSocket turns. -mod common; +use crate::common; -use axum::http::StatusCode; -use common::context::{ +use crate::common::context::{ Harness, PAGE_CHARS, answer, calls, finish, next, ordinary, pairs, send, successful, tool, }; +use axum::http::StatusCode; use serde_json::{Value, json}; use std::sync::Arc; use std::sync::atomic::{AtomicUsize, Ordering}; diff --git a/runner/zone_server/tests/chat_sandbox_tests.rs b/runner/zone_server/tests/integration/chat_sandbox.rs similarity index 99% rename from runner/zone_server/tests/chat_sandbox_tests.rs rename to runner/zone_server/tests/integration/chat_sandbox.rs index 54284878..08e98f2f 100644 --- a/runner/zone_server/tests/chat_sandbox_tests.rs +++ b/runner/zone_server/tests/integration/chat_sandbox.rs @@ -1,12 +1,10 @@ //! The chat routes carry `agent_sandboxed` — whether the agent keeps its own //! file and shell tools — as an axis of its own, independent of auto-approval. -mod common; - use axum::http::StatusCode; use serde_json::{Value, json}; -use common::{TestClient, test_email, test_password}; +use crate::common::{TestClient, test_email, test_password}; async fn register(client: &TestClient) -> String { let response = client diff --git a/runner/zone_server/tests/chat_search_tests.rs b/runner/zone_server/tests/integration/chat_search.rs similarity index 99% rename from runner/zone_server/tests/chat_search_tests.rs rename to runner/zone_server/tests/integration/chat_search.rs index 441cae59..61222b1f 100644 --- a/runner/zone_server/tests/chat_search_tests.rs +++ b/runner/zone_server/tests/integration/chat_search.rs @@ -1,7 +1,7 @@ //! Supplemental search state shares preview/send preparation without becoming instructions. -mod common; +use crate::common; -use common::context::{Harness, answer, successful, usage}; +use crate::common::context::{Harness, answer, successful, usage}; use serde_json::json; use uuid::Uuid; use zone_core::context; diff --git a/runner/zone_server/tests/chat_sessions_tests.rs b/runner/zone_server/tests/integration/chat_sessions.rs similarity index 99% rename from runner/zone_server/tests/chat_sessions_tests.rs rename to runner/zone_server/tests/integration/chat_sessions.rs index 7043b5c8..1e9f1417 100644 --- a/runner/zone_server/tests/chat_sessions_tests.rs +++ b/runner/zone_server/tests/integration/chat_sessions.rs @@ -7,11 +7,11 @@ //! turn invoked them and what it wrote to their stdin, and read back what the //! chat stored about its session. -mod common; +use crate::common; -use common::context::{finish, next, send, successful}; -use common::transcript::{self, Speaker}; -use common::{ +use crate::common::context::{finish, next, send, successful}; +use crate::common::transcript::{self, Speaker}; +use crate::common::{ TestClient, context_database_url, create_test_router, create_test_state, test_config, test_email, test_password, }; diff --git a/runner/zone_server/tests/chat_sources_tests.rs b/runner/zone_server/tests/integration/chat_sources.rs similarity index 99% rename from runner/zone_server/tests/chat_sources_tests.rs rename to runner/zone_server/tests/integration/chat_sources.rs index 6e74c04f..3448cba0 100644 --- a/runner/zone_server/tests/chat_sources_tests.rs +++ b/runner/zone_server/tests/integration/chat_sources.rs @@ -1,6 +1,6 @@ //! Run against an explicitly selected disposable database, never the application's DB. -mod common; +use crate::common; use chrono::Utc; use uuid::Uuid; diff --git a/runner/zone_server/tests/chat_title_tests.rs b/runner/zone_server/tests/integration/chat_title.rs similarity index 99% rename from runner/zone_server/tests/chat_title_tests.rs rename to runner/zone_server/tests/integration/chat_title.rs index ae9dc211..365405d9 100644 --- a/runner/zone_server/tests/chat_title_tests.rs +++ b/runner/zone_server/tests/integration/chat_title.rs @@ -1,6 +1,6 @@ -mod common; +use crate::common; -use common::create_test_pool; +use crate::common::create_test_pool; use zone_server::db::chats; #[tokio::test] diff --git a/runner/zone_server/tests/chat_vision_history_tests.rs b/runner/zone_server/tests/integration/chat_vision_history.rs similarity index 97% rename from runner/zone_server/tests/chat_vision_history_tests.rs rename to runner/zone_server/tests/integration/chat_vision_history.rs index 58a136c8..ac00fe03 100644 --- a/runner/zone_server/tests/chat_vision_history_tests.rs +++ b/runner/zone_server/tests/integration/chat_vision_history.rs @@ -1,8 +1,7 @@ //! A chat whose history holds an image keeps working after switching to a model //! the engine declares text-only: the request carries a note, not the image. -mod common; -use common::context::{Harness, answer, ordinary, successful}; +use crate::common::context::{Harness, answer, ordinary, successful}; use serde_json::{Value, json}; use wiremock::{ Mock, ResponseTemplate, diff --git a/runner/zone_server/tests/chat_ws_tests.rs b/runner/zone_server/tests/integration/chat_ws.rs similarity index 99% rename from runner/zone_server/tests/chat_ws_tests.rs rename to runner/zone_server/tests/integration/chat_ws.rs index 2c582c08..8fa05597 100644 --- a/runner/zone_server/tests/chat_ws_tests.rs +++ b/runner/zone_server/tests/integration/chat_ws.rs @@ -8,13 +8,13 @@ //! reachable LiteLLM the generation half cannot be exercised, so the streaming //! test skips rather than failing on an unrelated environment problem. -mod common; +use crate::common; -use base64::Engine; -use common::{ +use crate::common::{ TestClient, create_test_pool, create_test_router, create_test_state, init_tracing, test_config, test_email, test_password, }; +use base64::Engine; use futures_util::{SinkExt, StreamExt}; use serde_json::{Value, json}; use std::time::Duration; diff --git a/runner/zone_server/tests/chat_ws_contract_tests.rs b/runner/zone_server/tests/integration/chat_ws_contract.rs similarity index 99% rename from runner/zone_server/tests/chat_ws_contract_tests.rs rename to runner/zone_server/tests/integration/chat_ws_contract.rs index 98dd3a03..1861e4ad 100644 --- a/runner/zone_server/tests/chat_ws_contract_tests.rs +++ b/runner/zone_server/tests/integration/chat_ws_contract.rs @@ -1,12 +1,10 @@ //! WebSocket boundary and lifecycle contracts that do not require a live model. -mod common; - -use chrono::{Duration as ChronoDuration, Utc}; -use common::{ +use crate::common::{ TestClient, create_test_pool, create_test_router, create_test_state, test_config, test_email, test_password, }; +use chrono::{Duration as ChronoDuration, Utc}; use futures_util::{SinkExt, StreamExt}; use jsonwebtoken::{EncodingKey, Header, encode}; use serde_json::{Value, json}; diff --git a/runner/zone_server/tests/chat_ws_media_contract_tests.rs b/runner/zone_server/tests/integration/chat_ws_media_contract.rs similarity index 99% rename from runner/zone_server/tests/chat_ws_media_contract_tests.rs rename to runner/zone_server/tests/integration/chat_ws_media_contract.rs index 7e59bde8..ff80d1d6 100644 --- a/runner/zone_server/tests/chat_ws_media_contract_tests.rs +++ b/runner/zone_server/tests/integration/chat_ws_media_contract.rs @@ -1,8 +1,6 @@ //! Direct media generation contracts across the authenticated chat WebSocket. -mod common; - -use common::context::{Harness, Socket, finish, next, send}; +use crate::common::context::{Harness, Socket, finish, next, send}; use serde_json::{Value, json}; use std::path::{Path, PathBuf}; use std::time::Duration; diff --git a/runner/zone_server/tests/common/context.rs b/runner/zone_server/tests/integration/common/context.rs similarity index 100% rename from runner/zone_server/tests/common/context.rs rename to runner/zone_server/tests/integration/common/context.rs diff --git a/runner/zone_server/tests/common/mod.rs b/runner/zone_server/tests/integration/common/mod.rs similarity index 100% rename from runner/zone_server/tests/common/mod.rs rename to runner/zone_server/tests/integration/common/mod.rs diff --git a/runner/zone_server/tests/common/transcript.rs b/runner/zone_server/tests/integration/common/transcript.rs similarity index 100% rename from runner/zone_server/tests/common/transcript.rs rename to runner/zone_server/tests/integration/common/transcript.rs diff --git a/runner/zone_server/tests/console_contract_tests.rs b/runner/zone_server/tests/integration/console_contract.rs similarity index 99% rename from runner/zone_server/tests/console_contract_tests.rs rename to runner/zone_server/tests/integration/console_contract.rs index dcbd84b1..879829bc 100644 --- a/runner/zone_server/tests/console_contract_tests.rs +++ b/runner/zone_server/tests/integration/console_contract.rs @@ -6,12 +6,10 @@ //! to it posts `workspace_id` against a required `workspace_ids`, and failed //! the same way. -mod common; - use axum::http::StatusCode; use serde_json::json; -use common::{TestClient, test_email, test_password}; +use crate::common::{TestClient, test_email, test_password}; async fn owner(client: &TestClient) -> (String, String) { let response = client diff --git a/runner/zone_server/tests/context_tests.rs b/runner/zone_server/tests/integration/context.rs similarity index 98% rename from runner/zone_server/tests/context_tests.rs rename to runner/zone_server/tests/integration/context.rs index fffc2094..e1bf14be 100644 --- a/runner/zone_server/tests/context_tests.rs +++ b/runner/zone_server/tests/integration/context.rs @@ -1,15 +1,13 @@ //! Integration tests for context search and gathering //! //! These tests exercise the context search endpoints with a real database. -//! Run with: SQLX_OFFLINE=true cargo test --test context_tests - -mod common; +//! Run with: SQLX_OFFLINE=true cargo nextest run -p zone_server --test integration -E 'test(/^context::/)' use axum::http::StatusCode; use serde_json::json; use uuid::Uuid; -use common::{TestClient, test_email, test_password}; +use crate::common::{TestClient, test_email, test_password}; // ============================================================================= // Helper Functions diff --git a/runner/zone_server/tests/cross_tenant_authorization_tests.rs b/runner/zone_server/tests/integration/cross_tenant_authorization.rs similarity index 99% rename from runner/zone_server/tests/cross_tenant_authorization_tests.rs rename to runner/zone_server/tests/integration/cross_tenant_authorization.rs index ebe24bcf..33489d8d 100644 --- a/runner/zone_server/tests/cross_tenant_authorization_tests.rs +++ b/runner/zone_server/tests/integration/cross_tenant_authorization.rs @@ -10,12 +10,10 @@ //! something up, then has an unrelated account -- a real, valid, registered //! user, not an anonymous one -- name the victim's id directly. -mod common; - use axum::http::StatusCode; use serde_json::json; -use common::{TestClient, test_email, test_password}; +use crate::common::{TestClient, test_email, test_password}; struct Tenant { token: String, diff --git a/runner/zone_server/tests/deferred_tool_tests.rs b/runner/zone_server/tests/integration/deferred_tool.rs similarity index 99% rename from runner/zone_server/tests/deferred_tool_tests.rs rename to runner/zone_server/tests/integration/deferred_tool.rs index 3617342d..c174a174 100644 --- a/runner/zone_server/tests/deferred_tool_tests.rs +++ b/runner/zone_server/tests/integration/deferred_tool.rs @@ -9,9 +9,9 @@ //! It asserts a floor rather than an exact figure. An exact one would be a test //! that fails whenever somebody edits a tool description, which teaches people //! to update the number rather than to look at it. -mod common; +use crate::common; -use common::context::Harness; +use crate::common::context::Harness; use uuid::Uuid; use zone_core::context::{ContextSource, Policy, estimate}; use zone_server::agent::tools::{ChatTools, WorkspaceScope}; diff --git a/runner/zone_server/tests/email_verification_tests.rs b/runner/zone_server/tests/integration/email_verification.rs similarity index 99% rename from runner/zone_server/tests/email_verification_tests.rs rename to runner/zone_server/tests/integration/email_verification.rs index d696f6c3..a0f181f7 100644 --- a/runner/zone_server/tests/email_verification_tests.rs +++ b/runner/zone_server/tests/integration/email_verification.rs @@ -3,8 +3,6 @@ //! Tests for email verification token creation, validation, expiration, //! and user verification flow. -mod common; - use chrono::{Duration, Utc}; use sqlx::PgPool; use uuid::Uuid; diff --git a/runner/zone_server/tests/gathering_worker_tests.rs b/runner/zone_server/tests/integration/gathering_worker.rs similarity index 99% rename from runner/zone_server/tests/gathering_worker_tests.rs rename to runner/zone_server/tests/integration/gathering_worker.rs index e777aabd..35839032 100644 --- a/runner/zone_server/tests/gathering_worker_tests.rs +++ b/runner/zone_server/tests/integration/gathering_worker.rs @@ -17,7 +17,7 @@ use zone_server::db::{context_gatherings, gathering_events, sources}; use zone_server::state::AppState; use zone_server::workers::gathering; -mod common; +use crate::common; /// Test helper to create a test source async fn create_test_source( diff --git a/runner/zone_server/tests/indexing_tests.rs b/runner/zone_server/tests/integration/indexing.rs similarity index 98% rename from runner/zone_server/tests/indexing_tests.rs rename to runner/zone_server/tests/integration/indexing.rs index dc6521d1..4aab771d 100644 --- a/runner/zone_server/tests/indexing_tests.rs +++ b/runner/zone_server/tests/integration/indexing.rs @@ -1,11 +1,9 @@ //! Tests for automatic source indexing functionality //! -//! Run with: SQLX_OFFLINE=true cargo test --test indexing_tests - -mod common; +//! Run with: SQLX_OFFLINE=true cargo nextest run -p zone_server --test integration -E 'test(/^indexing::/)' +use crate::common::{TestClient, create_test_pool, test_email, test_password}; use axum::http::StatusCode; -use common::{TestClient, create_test_pool, test_email, test_password}; use serde_json::json; use sqlx::PgPool; use std::time::Duration; diff --git a/runner/zone_server/tests/invitations_tests.rs b/runner/zone_server/tests/integration/invitations.rs similarity index 100% rename from runner/zone_server/tests/invitations_tests.rs rename to runner/zone_server/tests/integration/invitations.rs diff --git a/runner/zone_server/tests/invitations_routes_tests.rs b/runner/zone_server/tests/integration/invitations_routes.rs similarity index 99% rename from runner/zone_server/tests/invitations_routes_tests.rs rename to runner/zone_server/tests/integration/invitations_routes.rs index 0cb70e8f..733d5f2a 100644 --- a/runner/zone_server/tests/invitations_routes_tests.rs +++ b/runner/zone_server/tests/integration/invitations_routes.rs @@ -2,13 +2,11 @@ //! //! Tests covering the HTTP endpoints for invitation management -mod common; - use axum::http::StatusCode; use serde_json::json; use uuid::Uuid; -use common::{TestClient, test_email, test_password}; +use crate::common::{TestClient, test_email, test_password}; /// Helper to register a user and get their access token and user_id async fn register_user(client: &TestClient, email: &str, password: &str) -> (String, Uuid) { diff --git a/runner/zone_server/tests/knowledge_tests.rs b/runner/zone_server/tests/integration/knowledge.rs similarity index 99% rename from runner/zone_server/tests/knowledge_tests.rs rename to runner/zone_server/tests/integration/knowledge.rs index 2110fe45..77bd724d 100644 --- a/runner/zone_server/tests/knowledge_tests.rs +++ b/runner/zone_server/tests/integration/knowledge.rs @@ -3,10 +3,8 @@ //! Tests the /api/knowledge endpoints for creating, listing, and deleting //! user-curated knowledge entries. -mod common; - +use crate::common::{TestClient, test_email, test_password}; use axum::http::StatusCode; -use common::{TestClient, test_email, test_password}; use serde_json::json; use uuid::Uuid; diff --git a/runner/zone_server/tests/knowledge_recovery_tests.rs b/runner/zone_server/tests/integration/knowledge_recovery.rs similarity index 99% rename from runner/zone_server/tests/knowledge_recovery_tests.rs rename to runner/zone_server/tests/integration/knowledge_recovery.rs index 03f7eec7..bbfa7437 100644 --- a/runner/zone_server/tests/knowledge_recovery_tests.rs +++ b/runner/zone_server/tests/integration/knowledge_recovery.rs @@ -8,14 +8,12 @@ //! transient embedding outage took an entry out of semantic search for good, //! and said nothing about it, because keyword search kept finding it. -mod common; - use std::sync::Arc; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use crate::common::{TestClient, test_email, test_password}; use async_trait::async_trait; use axum::http::StatusCode; -use common::{TestClient, test_email, test_password}; use serde_json::json; use uuid::Uuid; use zone_context::embeddings::EmbeddingService; diff --git a/runner/zone_server/tests/knowledge_url_tests.rs b/runner/zone_server/tests/integration/knowledge_url.rs similarity index 99% rename from runner/zone_server/tests/knowledge_url_tests.rs rename to runner/zone_server/tests/integration/knowledge_url.rs index b65fee21..3ba83c32 100644 --- a/runner/zone_server/tests/knowledge_url_tests.rs +++ b/runner/zone_server/tests/integration/knowledge_url.rs @@ -1,11 +1,11 @@ //! Tests for knowledge base web URL functionality //! -//! Run with: SQLX_OFFLINE=true cargo test --test knowledge_url_tests +//! Run with: SQLX_OFFLINE=true cargo nextest run -p zone_server --test integration -E 'test(/^knowledge_url::/)' -mod common; +use crate::common; +use crate::common::{TestClient, test_email, test_password}; use axum::http::StatusCode; -use common::{TestClient, test_email, test_password}; use serde_json::json; use uuid::Uuid; diff --git a/runner/zone_server/tests/integration/layout.rs b/runner/zone_server/tests/integration/layout.rs new file mode 100644 index 00000000..f8a20259 --- /dev/null +++ b/runner/zone_server/tests/integration/layout.rs @@ -0,0 +1,37 @@ +use std::fs; +use std::path::Path; + +fn sources(directory: &Path) -> Vec { + let mut names: Vec = fs::read_dir(directory) + .unwrap() + .map(|entry| entry.unwrap().path()) + .filter(|path| path.extension().is_some_and(|extension| extension == "rs")) + .map(|path| path.file_stem().unwrap().to_string_lossy().into_owned()) + .collect(); + names.sort(); + names +} + +#[test] +fn no_test_file_sits_outside_the_integration_target() { + let tests = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests"); + assert_eq!( + sources(&tests), + Vec::::new(), + "autotests is off, so a file in tests/ never compiles; move it into tests/integration/ and declare it in main.rs" + ); +} + +#[test] +fn every_integration_file_is_declared_in_main() { + let main = include_str!("main.rs"); + let integration = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/integration"); + let undeclared: Vec = sources(&integration) + .into_iter() + .filter(|name| name != "main" && !main.contains(&format!("mod {name};"))) + .collect(); + assert!( + undeclared.is_empty(), + "declare these in tests/integration/main.rs or they never compile: {undeclared:?}" + ); +} diff --git a/runner/zone_server/tests/learning_thresholds_tests.rs b/runner/zone_server/tests/integration/learning_thresholds.rs similarity index 100% rename from runner/zone_server/tests/learning_thresholds_tests.rs rename to runner/zone_server/tests/integration/learning_thresholds.rs diff --git a/runner/zone_server/tests/lora_flows_tests.rs b/runner/zone_server/tests/integration/lora_flows.rs similarity index 99% rename from runner/zone_server/tests/lora_flows_tests.rs rename to runner/zone_server/tests/integration/lora_flows.rs index 861640b9..c166779b 100644 --- a/runner/zone_server/tests/lora_flows_tests.rs +++ b/runner/zone_server/tests/integration/lora_flows.rs @@ -1,6 +1,6 @@ //! End-to-end coverage for image LoRA browse, inventory, pull, train, frame //! extraction, and delete. -mod common; +use crate::common; use axum::{ Json, Router, diff --git a/runner/zone_server/tests/integration/main.rs b/runner/zone_server/tests/integration/main.rs new file mode 100644 index 00000000..a8bca24d --- /dev/null +++ b/runner/zone_server/tests/integration/main.rs @@ -0,0 +1,102 @@ +mod agent_host_login; +mod agent_login; +mod agent_session_resume; +mod ai_settings; +mod ai_settings_routing; +mod ai_theme_authorization; +mod analytics_window; +mod api; +mod artifacts; +mod audit; +mod auth_console_contract; +mod auth_session_revocation; +mod authz_ai_settings_role; +mod authz_context_gather; +mod authz_cross_tenant_coverage; +mod authz_invitation_scope; +mod authz_project_enumeration; +mod authz_workspace_deletion; +mod background_job; +mod boundary_paths; +mod boundary_ssrf; +mod boundary_uploads; +mod cache; +mod chat_agent; +mod chat_agent_tools; +mod chat_attached_sources; +mod chat_context; +mod chat_context_store; +mod chat_context_tokens; +mod chat_generation_contract; +mod chat_handover; +mod chat_lifecycle; +mod chat_offline; +mod chat_project; +mod chat_replay; +mod chat_sandbox; +mod chat_search; +mod chat_sessions; +mod chat_sources; +mod chat_title; +mod chat_vision_history; +mod chat_ws; +mod chat_ws_contract; +mod chat_ws_media_contract; +mod common; +mod console_contract; +mod context; +mod cross_tenant_authorization; +mod deferred_tool; +mod email_verification; +mod gathering_worker; +mod indexing; +mod invitations; +mod invitations_routes; +mod knowledge; +mod knowledge_recovery; +mod knowledge_url; +mod layout; +mod learning_thresholds; +mod lora_flows; +mod mcp_agent; +mod mcp_task_tools; +mod memory; +mod memory_isolation; +mod memory_store; +mod message_embedding; +mod migration_atomicity; +mod model_namespaces; +mod models; +mod organization_console_contract; +mod organization_members; +mod password_reset; +mod plans; +mod pr_reception_status; +mod prompt_assembly; +mod pull_ws; +mod reminder_turn; +mod role_demotion; +mod role_demotion_race; +mod sessions; +mod skills_store; +mod subscriptions; +mod sync; +mod task_authorization; +mod task_creation; +mod task_execution; +mod task_lifecycle; +mod task_migration; +mod task_pr_fields; +mod task_review_verdict; +mod task_run_ws_authorization; +mod task_scope; +mod tenant_admin_permissions; +mod tenant_isolation; +mod test_config; +mod usage; +mod wait_for; +mod workspace_creation_atomicity; +mod workspace_owner_backfill; +mod workspace_owner_floor; +mod ws; +mod ws_context; diff --git a/runner/zone_server/tests/mcp_agent_tests.rs b/runner/zone_server/tests/integration/mcp_agent.rs similarity index 99% rename from runner/zone_server/tests/mcp_agent_tests.rs rename to runner/zone_server/tests/integration/mcp_agent.rs index 96cb9bc0..c3e35860 100644 --- a/runner/zone_server/tests/mcp_agent_tests.rs +++ b/runner/zone_server/tests/integration/mcp_agent.rs @@ -4,9 +4,8 @@ //! repository wrote. This is the one test that asks the real `claude` binary //! whether the handshake it gets is one it can use, which is the only thing //! that settles whether zone's tools reach the model. -mod common; -use common::context::Harness; +use crate::common::context::Harness; use std::sync::Arc; use std::time::Duration; use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; diff --git a/runner/zone_server/tests/mcp_task_tools_tests.rs b/runner/zone_server/tests/integration/mcp_task_tools.rs similarity index 99% rename from runner/zone_server/tests/mcp_task_tools_tests.rs rename to runner/zone_server/tests/integration/mcp_task_tools.rs index 4edcf8e6..c98c6945 100644 --- a/runner/zone_server/tests/mcp_task_tools_tests.rs +++ b/runner/zone_server/tests/integration/mcp_task_tools.rs @@ -4,7 +4,7 @@ //! turn, and a task told to hand work to another agent searched its catalog //! six times without finding one: `for_task` assembled with the servers //! left out. This attaches a stub stdio server and asks the run's catalog. -mod common; +use crate::common; use std::collections::HashMap; use std::path::PathBuf; diff --git a/runner/zone_server/tests/memory_tests.rs b/runner/zone_server/tests/integration/memory.rs similarity index 99% rename from runner/zone_server/tests/memory_tests.rs rename to runner/zone_server/tests/integration/memory.rs index 5450a5b0..1a7a7c91 100644 --- a/runner/zone_server/tests/memory_tests.rs +++ b/runner/zone_server/tests/integration/memory.rs @@ -12,11 +12,11 @@ //! out again: `knowledge_entries` is shared with every other suite on this //! database, and a leaked memory row is indistinguishable from a real one. -mod common; +use crate::common; use std::time::Duration; -use common::context::{ +use crate::common::context::{ Harness, MODEL, Script, Socket, answer, calls, finish, next, ordinary, send, successful, tool, }; use serde_json::{Value, json}; diff --git a/runner/zone_server/tests/memory_isolation_tests.rs b/runner/zone_server/tests/integration/memory_isolation.rs similarity index 99% rename from runner/zone_server/tests/memory_isolation_tests.rs rename to runner/zone_server/tests/integration/memory_isolation.rs index b7412910..44ffbe37 100644 --- a/runner/zone_server/tests/memory_isolation_tests.rs +++ b/runner/zone_server/tests/integration/memory_isolation.rs @@ -11,14 +11,14 @@ //! document the same call still returns, so an assertion cannot pass because //! the query stopped working. -mod common; +use crate::common; use std::sync::Arc; +use crate::common::context::{Harness, answer, usage}; +use crate::common::{TestClient, test_email, test_password}; use async_trait::async_trait; use axum::http::StatusCode; -use common::context::{Harness, answer, usage}; -use common::{TestClient, test_email, test_password}; use serde_json::json; use sqlx::PgPool; use uuid::Uuid; diff --git a/runner/zone_server/tests/memory_store_tests.rs b/runner/zone_server/tests/integration/memory_store.rs similarity index 99% rename from runner/zone_server/tests/memory_store_tests.rs rename to runner/zone_server/tests/integration/memory_store.rs index d26693b1..570ace37 100644 --- a/runner/zone_server/tests/memory_store_tests.rs +++ b/runner/zone_server/tests/integration/memory_store.rs @@ -12,7 +12,7 @@ //! and these rows belong to one person inside it, so a statement that forgets //! `created_by` is a privacy failure that no type would catch. -mod common; +use crate::common; use std::time::Duration; diff --git a/runner/zone_server/tests/message_embedding_tests.rs b/runner/zone_server/tests/integration/message_embedding.rs similarity index 99% rename from runner/zone_server/tests/message_embedding_tests.rs rename to runner/zone_server/tests/integration/message_embedding.rs index 13f9a2ef..3b9a099c 100644 --- a/runner/zone_server/tests/message_embedding_tests.rs +++ b/runner/zone_server/tests/integration/message_embedding.rs @@ -5,16 +5,16 @@ //! - Semantic search over chat history //! - Chat-specific and global search capabilities //! -//! Run with: SQLX_OFFLINE=true cargo test --test message_embedding_tests +//! Run with: SQLX_OFFLINE=true cargo nextest run -p zone_server --test integration -E 'test(/^message_embedding::/)' -mod common; +use crate::common; use axum::http::StatusCode; use serde_json::json; use sqlx::PgPool; use uuid::Uuid; -use common::{TestClient, create_test_pool, test_email, test_password}; +use crate::common::{TestClient, create_test_pool, test_email, test_password}; use zone_server::config::Config; use zone_server::db::{chats, message_embeddings}; use zone_server::routes::create_router; diff --git a/runner/zone_server/tests/migration_atomicity_tests.rs b/runner/zone_server/tests/integration/migration_atomicity.rs similarity index 100% rename from runner/zone_server/tests/migration_atomicity_tests.rs rename to runner/zone_server/tests/integration/migration_atomicity.rs diff --git a/runner/zone_server/tests/model_namespaces_tests.rs b/runner/zone_server/tests/integration/model_namespaces.rs similarity index 99% rename from runner/zone_server/tests/model_namespaces_tests.rs rename to runner/zone_server/tests/integration/model_namespaces.rs index 9826a58d..23fda46b 100644 --- a/runner/zone_server/tests/model_namespaces_tests.rs +++ b/runner/zone_server/tests/integration/model_namespaces.rs @@ -1,4 +1,4 @@ -mod common; +use crate::common; use axum::{ body::Body, diff --git a/runner/zone_server/tests/models_tests.rs b/runner/zone_server/tests/integration/models.rs similarity index 99% rename from runner/zone_server/tests/models_tests.rs rename to runner/zone_server/tests/integration/models.rs index 4d6fb055..a53812c0 100644 --- a/runner/zone_server/tests/models_tests.rs +++ b/runner/zone_server/tests/integration/models.rs @@ -3,7 +3,7 @@ //! Tests the /api/models endpoints using a mock Ollama server to simulate //! the external dependency. -mod common; +use crate::common; use axum::{ Json, Router, diff --git a/runner/zone_server/tests/organization_console_contract_tests.rs b/runner/zone_server/tests/integration/organization_console_contract.rs similarity index 99% rename from runner/zone_server/tests/organization_console_contract_tests.rs rename to runner/zone_server/tests/integration/organization_console_contract.rs index ca57cd3a..35a875e0 100644 --- a/runner/zone_server/tests/organization_console_contract_tests.rs +++ b/runner/zone_server/tests/integration/organization_console_contract.rs @@ -5,14 +5,12 @@ //! by administrators only, changes are audited, and every organization has a //! subscription to show. -mod common; - use axum::http::StatusCode; use chrono::{Duration, Utc}; use serde_json::{Value, json}; use uuid::Uuid; -use common::{TestClient, test_email, test_password}; +use crate::common::{TestClient, test_email, test_password}; use zone_server::db::organization_members::{self, OrgRole}; struct Account { diff --git a/runner/zone_server/tests/organization_members_tests.rs b/runner/zone_server/tests/integration/organization_members.rs similarity index 99% rename from runner/zone_server/tests/organization_members_tests.rs rename to runner/zone_server/tests/integration/organization_members.rs index 28887576..fa7d3901 100644 --- a/runner/zone_server/tests/organization_members_tests.rs +++ b/runner/zone_server/tests/integration/organization_members.rs @@ -3,8 +3,6 @@ //! Tests for organization member CRUD operations, role hierarchy, //! cascade deletes, and permission checks. -mod common; - use sqlx::PgPool; use uuid::Uuid; diff --git a/runner/zone_server/tests/password_reset_tests.rs b/runner/zone_server/tests/integration/password_reset.rs similarity index 99% rename from runner/zone_server/tests/password_reset_tests.rs rename to runner/zone_server/tests/integration/password_reset.rs index 9fdff980..e86d25ee 100644 --- a/runner/zone_server/tests/password_reset_tests.rs +++ b/runner/zone_server/tests/integration/password_reset.rs @@ -3,8 +3,6 @@ //! Tests for password reset token creation, validation, expiration, //! single-use enforcement, and password update flow. -mod common; - use chrono::{Duration, Utc}; use sqlx::PgPool; use uuid::Uuid; diff --git a/runner/zone_server/tests/plans_tests.rs b/runner/zone_server/tests/integration/plans.rs similarity index 99% rename from runner/zone_server/tests/plans_tests.rs rename to runner/zone_server/tests/integration/plans.rs index 185fdbc7..44ea7239 100644 --- a/runner/zone_server/tests/plans_tests.rs +++ b/runner/zone_server/tests/integration/plans.rs @@ -1,7 +1,5 @@ //! Tests for plans database module -mod common; - use sqlx::PgPool; use zone_server::db::plans::{ get_plan_by_id, get_plan_by_slug, get_plan_limits, list_public_plans, diff --git a/runner/zone_server/tests/pr_reception_status_tests.rs b/runner/zone_server/tests/integration/pr_reception_status.rs similarity index 99% rename from runner/zone_server/tests/pr_reception_status_tests.rs rename to runner/zone_server/tests/integration/pr_reception_status.rs index d10e14aa..c8e08ced 100644 --- a/runner/zone_server/tests/pr_reception_status_tests.rs +++ b/runner/zone_server/tests/integration/pr_reception_status.rs @@ -1,7 +1,7 @@ //! The reception sweep reads a task's pull request back from GitHub. What it //! learns has to reach the task the console shows: a pull request GitHub says //! merged reads "merged" on its task, not the "open" it was created with. -mod common; +use crate::common; use axum::{Json, Router, extract::Path, routing::get}; use serde_json::{Value, json}; diff --git a/runner/zone_server/tests/prompt_assembly_tests.rs b/runner/zone_server/tests/integration/prompt_assembly.rs similarity index 99% rename from runner/zone_server/tests/prompt_assembly_tests.rs rename to runner/zone_server/tests/integration/prompt_assembly.rs index 1e8a1e05..a728e888 100644 --- a/runner/zone_server/tests/prompt_assembly_tests.rs +++ b/runner/zone_server/tests/integration/prompt_assembly.rs @@ -5,7 +5,7 @@ //! result, which is the only place a section that should have stayed silent, or //! one that should have spoken, shows up. -mod common; +use crate::common; use chrono::DateTime; use sqlx::postgres::PgPoolOptions; @@ -375,7 +375,7 @@ async fn both_surfaces_offer_the_question_tool_and_read_its_rules() { /// so each superseded string is pinned by the half of itself that only the /// poll wording contained. Only the start_task bullet renders into a prompt at /// all, and only into the chat one; the other five reach the model as a tool -/// description, a schema or a tool result, and are pinned in `chat_agent_tests` +/// description, a schema or a tool result, and are pinned in `chat_agent` /// where a turn carries them. #[tokio::test] async fn both_surfaces_read_the_waiting_rules_and_no_surviving_poll_instruction() { diff --git a/runner/zone_server/tests/pull_ws_tests.rs b/runner/zone_server/tests/integration/pull_ws.rs similarity index 99% rename from runner/zone_server/tests/pull_ws_tests.rs rename to runner/zone_server/tests/integration/pull_ws.rs index 89245c51..660a806d 100644 --- a/runner/zone_server/tests/pull_ws_tests.rs +++ b/runner/zone_server/tests/integration/pull_ws.rs @@ -1,5 +1,5 @@ //! Model downloads through the public WebSocket route and a real mock provider. -mod common; +use crate::common; use axum::{Json, Router, body::Body, http::StatusCode, response::IntoResponse, routing::post}; use futures::{SinkExt, StreamExt}; diff --git a/runner/zone_server/tests/reminder_turn_tests.rs b/runner/zone_server/tests/integration/reminder_turn.rs similarity index 99% rename from runner/zone_server/tests/reminder_turn_tests.rs rename to runner/zone_server/tests/integration/reminder_turn.rs index 6922cc02..f4c749ae 100644 --- a/runner/zone_server/tests/reminder_turn_tests.rs +++ b/runner/zone_server/tests/integration/reminder_turn.rs @@ -9,9 +9,9 @@ //! nothing about the path is stubbed: the worker sweeps on its own tick, claims //! its own row, and runs the turn through the handler a typed message runs //! through. -mod common; +use crate::common; -use common::context::{Harness, answer}; +use crate::common::context::{Harness, answer}; use serde_json::Value; use sqlx::Connection; use std::time::Duration; diff --git a/runner/zone_server/tests/role_demotion_tests.rs b/runner/zone_server/tests/integration/role_demotion.rs similarity index 99% rename from runner/zone_server/tests/role_demotion_tests.rs rename to runner/zone_server/tests/integration/role_demotion.rs index 9cbe0ee3..22c1d56a 100644 --- a/runner/zone_server/tests/role_demotion_tests.rs +++ b/runner/zone_server/tests/integration/role_demotion.rs @@ -7,12 +7,10 @@ //! Written from the attacker's side: a real admin, invited legitimately, uses //! the role route to strip the owner that invited them. -mod common; - use axum::http::StatusCode; use serde_json::json; -use common::{TestClient, test_email, test_password}; +use crate::common::{TestClient, test_email, test_password}; struct Account { token: String, diff --git a/runner/zone_server/tests/role_demotion_race_tests.rs b/runner/zone_server/tests/integration/role_demotion_race.rs similarity index 98% rename from runner/zone_server/tests/role_demotion_race_tests.rs rename to runner/zone_server/tests/integration/role_demotion_race.rs index 88eea352..b2640680 100644 --- a/runner/zone_server/tests/role_demotion_race_tests.rs +++ b/runner/zone_server/tests/integration/role_demotion_race.rs @@ -1,13 +1,11 @@ -//! `role_demotion_tests` proves the guard refuses one demotion too many. This +//! `role_demotion` proves the guard refuses one demotion too many. This //! proves it still refuses when two arrive at once: counting the owners and //! demoting one of them are separate statements, so without a lock held across //! both, two demotions each read a safe count and between them leave none. -mod common; - use std::time::Duration; -use common::create_test_pool; +use crate::common::create_test_pool; use sqlx::{PgPool, Row}; use uuid::Uuid; use zone_server::db::organization_members::{ diff --git a/runner/zone_server/tests/sessions_tests.rs b/runner/zone_server/tests/integration/sessions.rs similarity index 99% rename from runner/zone_server/tests/sessions_tests.rs rename to runner/zone_server/tests/integration/sessions.rs index 90fc024b..e880ba95 100644 --- a/runner/zone_server/tests/sessions_tests.rs +++ b/runner/zone_server/tests/integration/sessions.rs @@ -6,9 +6,7 @@ use chrono::{Duration, Utc}; use tokio::sync::RwLock; use uuid::Uuid; -mod common; - -use common::{create_test_pool, test_password}; +use crate::common::{create_test_pool, test_password}; use zone_server::db::sessions; use zone_server::utils::crypto::hash_token; diff --git a/runner/zone_server/tests/skills_store_tests.rs b/runner/zone_server/tests/integration/skills_store.rs similarity index 99% rename from runner/zone_server/tests/skills_store_tests.rs rename to runner/zone_server/tests/integration/skills_store.rs index ed82b1ce..6b257287 100644 --- a/runner/zone_server/tests/skills_store_tests.rs +++ b/runner/zone_server/tests/integration/skills_store.rs @@ -5,7 +5,7 @@ //! get right is scope — one workspace's skills and nobody else's, active ones //! only, filed under the skill category and no other — and the bound, which //! reaches one row past the limit so the index can say it stopped short. -mod common; +use crate::common; use sqlx::PgPool; use uuid::Uuid; diff --git a/runner/zone_server/tests/subscriptions_tests.rs b/runner/zone_server/tests/integration/subscriptions.rs similarity index 99% rename from runner/zone_server/tests/subscriptions_tests.rs rename to runner/zone_server/tests/integration/subscriptions.rs index 3894a01a..dda472ff 100644 --- a/runner/zone_server/tests/subscriptions_tests.rs +++ b/runner/zone_server/tests/integration/subscriptions.rs @@ -1,7 +1,5 @@ //! Tests for subscriptions database module -mod common; - use chrono::{Duration, Utc}; use sqlx::PgPool; use uuid::Uuid; diff --git a/runner/zone_server/tests/sync_tests.rs b/runner/zone_server/tests/integration/sync.rs similarity index 99% rename from runner/zone_server/tests/sync_tests.rs rename to runner/zone_server/tests/integration/sync.rs index 24611c22..4255b601 100644 --- a/runner/zone_server/tests/sync_tests.rs +++ b/runner/zone_server/tests/integration/sync.rs @@ -1,6 +1,6 @@ //! Integration tests for sync functionality -mod common; +use crate::common; use axum::{ body::Body, diff --git a/runner/zone_server/tests/task_authorization_tests.rs b/runner/zone_server/tests/integration/task_authorization.rs similarity index 99% rename from runner/zone_server/tests/task_authorization_tests.rs rename to runner/zone_server/tests/integration/task_authorization.rs index 05da10ce..2d5711aa 100644 --- a/runner/zone_server/tests/task_authorization_tests.rs +++ b/runner/zone_server/tests/integration/task_authorization.rs @@ -1,4 +1,4 @@ -mod common; +use crate::common; use std::time::Duration; @@ -10,7 +10,7 @@ use zone_server::db::actions::{self, StartTask}; use zone_server::db::tasks; use zone_server::services::checkout::Repository; -use common::{ +use crate::common::{ TestClient, create_test_pool, create_test_router, create_test_state, test_config, test_email, test_password, }; diff --git a/runner/zone_server/tests/task_creation_tests.rs b/runner/zone_server/tests/integration/task_creation.rs similarity index 99% rename from runner/zone_server/tests/task_creation_tests.rs rename to runner/zone_server/tests/integration/task_creation.rs index 58f34d55..6891afc6 100644 --- a/runner/zone_server/tests/task_creation_tests.rs +++ b/runner/zone_server/tests/integration/task_creation.rs @@ -1,4 +1,4 @@ -mod common; +use crate::common; use axum::{ Json, diff --git a/runner/zone_server/tests/task_execution_tests.rs b/runner/zone_server/tests/integration/task_execution.rs similarity index 99% rename from runner/zone_server/tests/task_execution_tests.rs rename to runner/zone_server/tests/integration/task_execution.rs index 5c789c75..485cb3f2 100644 --- a/runner/zone_server/tests/task_execution_tests.rs +++ b/runner/zone_server/tests/integration/task_execution.rs @@ -1,6 +1,6 @@ //! Task execution integration tests -mod common; +use crate::common; use uuid::Uuid; use zone_server::agent::wait::Waiting; diff --git a/runner/zone_server/tests/task_lifecycle_tests.rs b/runner/zone_server/tests/integration/task_lifecycle.rs similarity index 99% rename from runner/zone_server/tests/task_lifecycle_tests.rs rename to runner/zone_server/tests/integration/task_lifecycle.rs index 0351755b..d855da9b 100644 --- a/runner/zone_server/tests/task_lifecycle_tests.rs +++ b/runner/zone_server/tests/integration/task_lifecycle.rs @@ -1,4 +1,4 @@ -mod common; +use crate::common; use zone_server::db::tasks; diff --git a/runner/zone_server/tests/task_migration_tests.rs b/runner/zone_server/tests/integration/task_migration.rs similarity index 100% rename from runner/zone_server/tests/task_migration_tests.rs rename to runner/zone_server/tests/integration/task_migration.rs diff --git a/runner/zone_server/tests/task_pr_fields_tests.rs b/runner/zone_server/tests/integration/task_pr_fields.rs similarity index 99% rename from runner/zone_server/tests/task_pr_fields_tests.rs rename to runner/zone_server/tests/integration/task_pr_fields.rs index 48847fd7..a1c00743 100644 --- a/runner/zone_server/tests/task_pr_fields_tests.rs +++ b/runner/zone_server/tests/integration/task_pr_fields.rs @@ -1,6 +1,6 @@ //! Task rows carry the pull request fields the PR worker writes. -mod common; +use crate::common; mod db_tests { use uuid::Uuid; diff --git a/runner/zone_server/tests/task_review_verdict_tests.rs b/runner/zone_server/tests/integration/task_review_verdict.rs similarity index 99% rename from runner/zone_server/tests/task_review_verdict_tests.rs rename to runner/zone_server/tests/integration/task_review_verdict.rs index 3d2dd203..e3cbeb8b 100644 --- a/runner/zone_server/tests/task_review_verdict_tests.rs +++ b/runner/zone_server/tests/integration/task_review_verdict.rs @@ -1,7 +1,7 @@ //! A review round is recorded whatever it ended in, including a reviewer model //! that failed before it answered: an unrecorded failure leaves the round //! where it was, and the next tick asks the same model again, forever. -mod common; +use crate::common; use sqlx::PgPool; use uuid::Uuid; diff --git a/runner/zone_server/tests/task_run_ws_authorization_tests.rs b/runner/zone_server/tests/integration/task_run_ws_authorization.rs similarity index 99% rename from runner/zone_server/tests/task_run_ws_authorization_tests.rs rename to runner/zone_server/tests/integration/task_run_ws_authorization.rs index e6f0ccdc..5b6b612b 100644 --- a/runner/zone_server/tests/task_run_ws_authorization_tests.rs +++ b/runner/zone_server/tests/integration/task_run_ws_authorization.rs @@ -1,4 +1,4 @@ -mod common; +use crate::common; use std::net::SocketAddr; use std::time::Duration; diff --git a/runner/zone_server/tests/task_scope_tests.rs b/runner/zone_server/tests/integration/task_scope.rs similarity index 99% rename from runner/zone_server/tests/task_scope_tests.rs rename to runner/zone_server/tests/integration/task_scope.rs index c94514f7..af8bd4be 100644 --- a/runner/zone_server/tests/task_scope_tests.rs +++ b/runner/zone_server/tests/integration/task_scope.rs @@ -1,5 +1,5 @@ //! Background tasks retain their initiating workspace and never expose foreign runs. -mod common; +use crate::common; use chrono::{Duration, Utc}; use futures_util::{SinkExt, StreamExt}; diff --git a/runner/zone_server/tests/tenant_admin_permissions_tests.rs b/runner/zone_server/tests/integration/tenant_admin_permissions.rs similarity index 97% rename from runner/zone_server/tests/tenant_admin_permissions_tests.rs rename to runner/zone_server/tests/integration/tenant_admin_permissions.rs index fac90af1..f7c78d7b 100644 --- a/runner/zone_server/tests/tenant_admin_permissions_tests.rs +++ b/runner/zone_server/tests/integration/tenant_admin_permissions.rs @@ -11,10 +11,8 @@ //! role administers tenants at all, which is why the refusals asserted below //! still hold for a foreign tenant. -mod common; - +use crate::common::{TestClient, test_email, test_password}; use axum::http::StatusCode; -use common::{TestClient, test_email, test_password}; use serde_json::json; async fn register(client: &TestClient) -> serde_json::Value { diff --git a/runner/zone_server/tests/tenant_isolation_tests.rs b/runner/zone_server/tests/integration/tenant_isolation.rs similarity index 99% rename from runner/zone_server/tests/tenant_isolation_tests.rs rename to runner/zone_server/tests/integration/tenant_isolation.rs index c9f490f5..afcbfa03 100644 --- a/runner/zone_server/tests/tenant_isolation_tests.rs +++ b/runner/zone_server/tests/integration/tenant_isolation.rs @@ -7,7 +7,7 @@ use zone_server::db::{ chats, organization_members, organizations, projects, users, workspace_members, workspaces, }; -mod common; +use crate::common; #[tokio::test] async fn test_user_cannot_see_other_workspace_projects() { diff --git a/runner/zone_server/tests/test_config_tests.rs b/runner/zone_server/tests/integration/test_config.rs similarity index 97% rename from runner/zone_server/tests/test_config_tests.rs rename to runner/zone_server/tests/integration/test_config.rs index 8bd880c0..e32e259f 100644 --- a/runner/zone_server/tests/test_config_tests.rs +++ b/runner/zone_server/tests/integration/test_config.rs @@ -1,4 +1,4 @@ -mod common; +use crate::common; use std::net::IpAddr; use zone_server::config::Config; diff --git a/runner/zone_server/tests/usage_tests.rs b/runner/zone_server/tests/integration/usage.rs similarity index 99% rename from runner/zone_server/tests/usage_tests.rs rename to runner/zone_server/tests/integration/usage.rs index 684f4325..333d3b47 100644 --- a/runner/zone_server/tests/usage_tests.rs +++ b/runner/zone_server/tests/integration/usage.rs @@ -1,7 +1,5 @@ //! Tests for usage tracking database module -mod common; - use chrono::{Duration, Utc}; use sqlx::PgPool; use uuid::Uuid; diff --git a/runner/zone_server/tests/wait_for_tests.rs b/runner/zone_server/tests/integration/wait_for.rs similarity index 99% rename from runner/zone_server/tests/wait_for_tests.rs rename to runner/zone_server/tests/integration/wait_for.rs index 1078516d..a05551e9 100644 --- a/runner/zone_server/tests/wait_for_tests.rs +++ b/runner/zone_server/tests/integration/wait_for.rs @@ -1,7 +1,7 @@ //! `wait_for` end to end: what a park costs a run, what it gives back, who may //! open one, and what a chat turn does either side of the wait it opened. -mod common; +use crate::common; use std::collections::HashMap; use std::path::{Path, PathBuf}; diff --git a/runner/zone_server/tests/workspace_creation_atomicity_tests.rs b/runner/zone_server/tests/integration/workspace_creation_atomicity.rs similarity index 98% rename from runner/zone_server/tests/workspace_creation_atomicity_tests.rs rename to runner/zone_server/tests/integration/workspace_creation_atomicity.rs index cd4b85c9..b8577dea 100644 --- a/runner/zone_server/tests/workspace_creation_atomicity_tests.rs +++ b/runner/zone_server/tests/integration/workspace_creation_atomicity.rs @@ -2,9 +2,7 @@ //! without its owner row cannot be repaired through the API by anyone -- not //! even the person who just created it. Creation has to be one unit. -mod common; - -use common::create_test_pool; +use crate::common::create_test_pool; use sqlx::Row; use uuid::Uuid; use zone_server::db::workspaces; diff --git a/runner/zone_server/tests/workspace_owner_backfill_tests.rs b/runner/zone_server/tests/integration/workspace_owner_backfill.rs similarity index 98% rename from runner/zone_server/tests/workspace_owner_backfill_tests.rs rename to runner/zone_server/tests/integration/workspace_owner_backfill.rs index 6af45e55..9c6b6d60 100644 --- a/runner/zone_server/tests/workspace_owner_backfill_tests.rs +++ b/runner/zone_server/tests/integration/workspace_owner_backfill.rs @@ -2,13 +2,11 @@ //! at all, and "only owners can remove admins or owners" leaves nobody able to //! remove or demote an admin in them. The backfill seats one. -mod common; - use axum::http::StatusCode; use serde_json::json; use sqlx::{AssertSqlSafe, PgPool, raw_sql}; -use common::{TestClient, create_test_pool, test_email, test_password}; +use crate::common::{TestClient, create_test_pool, test_email, test_password}; const BACKFILL: &str = " UPDATE workspace_members AS promoted diff --git a/runner/zone_server/tests/workspace_owner_floor_tests.rs b/runner/zone_server/tests/integration/workspace_owner_floor.rs similarity index 99% rename from runner/zone_server/tests/workspace_owner_floor_tests.rs rename to runner/zone_server/tests/integration/workspace_owner_floor.rs index d139118e..1c543c56 100644 --- a/runner/zone_server/tests/workspace_owner_floor_tests.rs +++ b/runner/zone_server/tests/integration/workspace_owner_floor.rs @@ -6,9 +6,7 @@ //! Counting admins and owners together does produce it: with one owner and one //! admin standing, the count says two are privileged and lets the owner go. -mod common; - -use common::create_test_pool; +use crate::common::create_test_pool; use sqlx::{PgPool, Row}; use uuid::Uuid; use zone_server::db::workspace_members::{ diff --git a/runner/zone_server/tests/ws_tests.rs b/runner/zone_server/tests/integration/ws.rs similarity index 99% rename from runner/zone_server/tests/ws_tests.rs rename to runner/zone_server/tests/integration/ws.rs index 676b9fe6..0649dba4 100644 --- a/runner/zone_server/tests/ws_tests.rs +++ b/runner/zone_server/tests/integration/ws.rs @@ -2,7 +2,7 @@ //! //! These tests require a running database and test the WebSocket task run endpoint. -mod common; +use crate::common; use std::net::SocketAddr; diff --git a/runner/zone_server/tests/ws_context_tests.rs b/runner/zone_server/tests/integration/ws_context.rs similarity index 99% rename from runner/zone_server/tests/ws_context_tests.rs rename to runner/zone_server/tests/integration/ws_context.rs index a7fedaf9..9b5328e6 100644 --- a/runner/zone_server/tests/ws_context_tests.rs +++ b/runner/zone_server/tests/integration/ws_context.rs @@ -2,7 +2,7 @@ //! //! Tests the real-time event streaming for context gathering operations via WebSocket. -mod common; +use crate::common; use std::net::SocketAddr; From 37fed1d69736efb06323265e5b7b845de983ef91 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 7 Oct 2026 01:03:48 +1300 Subject: [PATCH 3/3] test(chat): share one media lock across integration modules IMAGE_GENERATIONS admits one media generation per process. chat_lifecycle and chat_ws_media_contract each serialized on their own mutex, which held while every file was its own binary. In the merged binary under plain cargo test or cargo llvm-cov, chat_ws's media tests hold the lane while chat_ws_contract::queued_media_stops_when_its_chat_lease_is_fenced expects it free, and the test failed 4 of 4 single-process runs. One common::MEDIA lock taken by every test that drives a generation makes it pass. Co-Authored-By: Claude Opus 5.5 --- runner/zone_server/tests/integration/chat_lifecycle.rs | 4 +--- runner/zone_server/tests/integration/chat_ws.rs | 10 ++++++++-- .../zone_server/tests/integration/chat_ws_contract.rs | 5 +++-- .../tests/integration/chat_ws_media_contract.rs | 3 +-- runner/zone_server/tests/integration/common/mod.rs | 4 ++++ 5 files changed, 17 insertions(+), 9 deletions(-) diff --git a/runner/zone_server/tests/integration/chat_lifecycle.rs b/runner/zone_server/tests/integration/chat_lifecycle.rs index 66b74b4a..495d0b7d 100644 --- a/runner/zone_server/tests/integration/chat_lifecycle.rs +++ b/runner/zone_server/tests/integration/chat_lifecycle.rs @@ -1,10 +1,10 @@ //! Generation ownership and deadline acceptance through the real authenticated router. +use crate::common::MEDIA; use crate::common::context::{Harness, Socket, answer, finish, next, send, successful}; use serde_json::json; use std::process::Command; use std::time::{Duration, Instant}; -use tokio::sync::Mutex; use uuid::Uuid; use wiremock::{ Mock, MockServer, ResponseTemplate, @@ -12,8 +12,6 @@ use wiremock::{ }; use zone_server::services::chat::session::Settings; -static MEDIA: Mutex<()> = Mutex::const_new(()); - #[test] fn settings_validate_deadline_range() { const CHILD: &str = "ZONE_DEADLINE_TEST_CHILD"; diff --git a/runner/zone_server/tests/integration/chat_ws.rs b/runner/zone_server/tests/integration/chat_ws.rs index 8fa05597..234d6b35 100644 --- a/runner/zone_server/tests/integration/chat_ws.rs +++ b/runner/zone_server/tests/integration/chat_ws.rs @@ -11,8 +11,8 @@ use crate::common; use crate::common::{ - TestClient, create_test_pool, create_test_router, create_test_state, init_tracing, test_config, - test_email, test_password, + MEDIA, TestClient, create_test_pool, create_test_router, create_test_state, init_tracing, + test_config, test_email, test_password, }; use base64::Engine; use futures_util::{SinkExt, StreamExt}; @@ -1084,6 +1084,7 @@ async fn test_chat_ws_sends_images_to_a_vision_model() { #[tokio::test] async fn test_image_request_routes_directly_and_serves_protected_artifact() { + let _serial = MEDIA.lock().await; let comfy = MockServer::start().await; Mock::given(method("POST")) .and(path("/prompt")) @@ -1255,6 +1256,7 @@ async fn test_image_request_routes_directly_and_serves_protected_artifact() { #[tokio::test] async fn test_video_request_routes_directly_and_serves_protected_artifact() { + let _serial = MEDIA.lock().await; let comfy = MockServer::start().await; Mock::given(method("POST")) .and(path("/prompt")) @@ -1377,6 +1379,7 @@ async fn test_video_request_routes_directly_and_serves_protected_artifact() { #[tokio::test] async fn test_audio_request_routes_directly_and_serves_protected_artifact() { + let _serial = MEDIA.lock().await; let comfy = MockServer::start().await; Mock::given(method("POST")) .and(path("/prompt")) @@ -1503,6 +1506,7 @@ async fn test_audio_request_routes_directly_and_serves_protected_artifact() { #[tokio::test] async fn test_attached_image_routes_to_image_to_image() { + let _serial = MEDIA.lock().await; let comfy = MockServer::start().await; Mock::given(method("POST")) .and(path("/upload/image")) @@ -1624,6 +1628,7 @@ async fn test_attached_image_routes_to_image_to_image() { #[tokio::test] async fn test_attached_image_upscale_routes_and_serves_protected_artifact() { + let _serial = MEDIA.lock().await; let comfy = MockServer::start().await; Mock::given(method("POST")) .and(path("/upload/image")) @@ -1758,6 +1763,7 @@ async fn test_attached_image_upscale_routes_and_serves_protected_artifact() { #[tokio::test] async fn test_naming_the_video_upscales_it_past_an_attached_screenshot() { + let _serial = MEDIA.lock().await; let comfy = MockServer::start().await; Mock::given(method("POST")) .and(path("/prompt")) diff --git a/runner/zone_server/tests/integration/chat_ws_contract.rs b/runner/zone_server/tests/integration/chat_ws_contract.rs index 1861e4ad..8e07c8a7 100644 --- a/runner/zone_server/tests/integration/chat_ws_contract.rs +++ b/runner/zone_server/tests/integration/chat_ws_contract.rs @@ -1,8 +1,8 @@ //! WebSocket boundary and lifecycle contracts that do not require a live model. use crate::common::{ - TestClient, create_test_pool, create_test_router, create_test_state, test_config, test_email, - test_password, + MEDIA, TestClient, create_test_pool, create_test_router, create_test_state, test_config, + test_email, test_password, }; use chrono::{Duration as ChronoDuration, Utc}; use futures_util::{SinkExt, StreamExt}; @@ -845,6 +845,7 @@ async fn invalid_media_clients_fail_each_direct_lane_without_model_fallback() { #[tokio::test] async fn queued_media_stops_when_its_chat_lease_is_fenced() { + let _serial = MEDIA.lock().await; let comfy = MockServer::start().await; Mock::given(method("POST")) .and(path("/prompt")) diff --git a/runner/zone_server/tests/integration/chat_ws_media_contract.rs b/runner/zone_server/tests/integration/chat_ws_media_contract.rs index ff80d1d6..b9a22f34 100644 --- a/runner/zone_server/tests/integration/chat_ws_media_contract.rs +++ b/runner/zone_server/tests/integration/chat_ws_media_contract.rs @@ -1,17 +1,16 @@ //! Direct media generation contracts across the authenticated chat WebSocket. +use crate::common::MEDIA; use crate::common::context::{Harness, Socket, finish, next, send}; use serde_json::{Value, json}; use std::path::{Path, PathBuf}; use std::time::Duration; -use tokio::sync::Mutex; use uuid::Uuid; use wiremock::{ Mock, MockServer, ResponseTemplate, matchers::{body_json, method, path}, }; -static MEDIA: Mutex<()> = Mutex::const_new(()); const PNG: &str = "data:image/png;base64,iVBORw0KGgo="; async fn configure(harness: &mut Harness, comfy: &MockServer, root: &Path) { diff --git a/runner/zone_server/tests/integration/common/mod.rs b/runner/zone_server/tests/integration/common/mod.rs index 6a0fa70e..8f2e0617 100644 --- a/runner/zone_server/tests/integration/common/mod.rs +++ b/runner/zone_server/tests/integration/common/mod.rs @@ -28,6 +28,10 @@ use zone_server::config::Config; use zone_server::routes::create_router; use zone_server::state::AppState; +/// The server runs one media generation per process. Under plain `cargo test` every module +/// shares that process, so a test that needs the lane to itself holds this first. +pub static MEDIA: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(()); + /// Send server tracing to the test's stdout. Without this the server's own /// logs vanish and a failing integration test says nothing about why. pub fn init_tracing() {