Skip to content

Apalis RouteQueue with PostgreSQL; tests, docs, and BDD#335

Merged
leynos merged 33 commits intomainfrom
implement-apalis-routequeue-ra0bjw
Apr 22, 2026
Merged

Apalis RouteQueue with PostgreSQL; tests, docs, and BDD#335
leynos merged 33 commits intomainfrom
implement-apalis-routequeue-ra0bjw

Conversation

@leynos
Copy link
Copy Markdown
Owner

@leynos leynos commented Apr 3, 2026

Summary

  • Adds a production-ready Apalis-backed RouteQueue adapter with PostgreSQL storage, plus a testable provider abstraction and test scaffolding. Retains a no-op Stub for non-production paths, and expands the test and documentation surface (BDD tests, embedded Postgres harness, ExecPlan).

Changes

  • Production-ready Apalis-backed queue adapter and test scaffolding:
    • New file: backend/src/outbound/queue/apalis_route_queue.rs implementing GenericApalisRouteQueue, ApalisPostgresProvider, and a QueueProvider trait for testability.
    • Updated module structure to expose ApalisRouteQueue, GenericApalisRouteQueue, ApalisPostgresProvider, and test helpers via backend/src/outbound/queue/mod.rs.
    • New file: backend/src/outbound/queue/stub_route_queue.rs retained as a separate no-op stub for non-production paths.
    • New file: backend/src/outbound/queue/test_helpers.rs providing FakeQueueProvider and FailingQueueProvider for unit tests.
  • Testing and behavioural coverage:
    • New behaviour-driven tests scaffolding:
      • New file: backend/tests/features/route_queue_apalis.feature
      • New file: backend/tests/route_queue_apalis_bdd.rs
    • Embedded Postgres test harness integration support extended (backend/tests/support/embedded_postgres.rs) for Apalis storage setup during tests.
  • Execution plan documentation:
    • New ExecPlan doc: docs/execplans/backend-5-2-1-apalis-route-queue.md detailing scope, constraints, plan, risks, testing, and acceptance criteria.
  • Documentation and architecture updates:
    • Updated docs/wildside-backend-architecture.md to reflect Apalis-backed PostgreSQL queue usage and dual-pool architecture.
  • Dependency and build updates:
    • Updated backend/Cargo.toml to include apalis-core, apalis-postgres, and sqlx with PostgreSQL features (runtime-tokio). The changes align with the Apalis-driven RouteQueue approach. Also adjusted git rev for wildside-data to accommodate new crates.

Rationale

  • Provides a concrete, testable Apalis-based RouteQueue adapter with PostgreSQL storage, enabling unit tests with fake providers and integration-ready paths for embedded PostgreSQL harnesses. This preserves hexagonal boundaries, supports testability, and lays groundwork for future embedded-harness-driven integration tests.

Plan of work (highlights)

  • Stage A: Scaffold Apalis RouteQueue module layout and dependencies; introduce QueueProvider abstraction for testability.
  • Stage B: Focused unit tests using FakeQueueProvider to validate serialization, enqueue success, and error mapping (Unavailable/Rejected).
  • Stage C: Prepare for PostgreSQL integration harness and behaviour coverage using an embedded PostgreSQL setup.
  • Stage D: Document architectural scope and maintain a dual-pool pattern (sqlx/Apalis vs Diesel).
  • Stage E: Run repository gates (fmt, lint, test) and validate gates before closure.

Validation and acceptance

  • The ApalisRouteQueue

    implements RouteQueue<Plan = P> using Apalis PostgreSQL storage, serializing plans to JSON payloads and persisting them via Apalis storage, with proper error mapping (Unavailable for storage/connectivity; Rejected for serialization failures).

  • Unit tests cover happy path, provider-unavailable path, and payload round-trips using FakeQueueProvider.
  • StubRouteQueue remains available for non-production paths, and tests continue to pass.
  • ExecPlan documentation (docs/execplans/backend-5-2-1-apalis-route-queue.md) is in place and aligned with implemented code.

Risks

  • Apalis RC status may affect API stability; versions are pinned via Cargo.toml. Integration harness with embedded PostgreSQL may require test infra coordination. The architecture maintains hexagonal boundaries with a clear provider abstraction to allow swapping storage backends later.

Interfaces and dependencies

  • Production dependencies added: apalis-core, apalis-postgres, and sqlx with PostgreSQL features (postgres, runtime-tokio). The changes align with the Apalis-driven RouteQueue approach. The Apalis adapter uses a QueueProvider trait to allow fake providers for unit tests and a real Apalis PostgreSQL provider for production/testing paths. Tests rely on FakeQueueProvider and FailingQueueProvider to simulate success and failure paths respectively.

Notes for reviewers

  • Review focus areas:
    • Correct exposure of GenericApalisRouteQueue, ApalisPostgresProvider, and QueueProvider trait.
    • Proper mapping of errors to JobDispatchError::Unavailable and JobDispatchError::Rejected.
    • Serialization correctness and payload round-trip integrity in unit tests.
    • Alignment with ExecPlan documentation and overall architecture guidelines.

Approval

  • Status: AWAITING APPROVAL

📎 Task: https://www.devboxer.com/task/fce762d0-cfb1-4ddc-8ebe-0e0c73f5e683

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 3, 2026

Note

Reviews paused

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Add an Apalis‑backed PostgreSQL RouteQueue adapter and provider abstraction; introduce apalis/sqlx dependencies and update a git dependency; extract stub adapter to its own module; add in‑memory and failing test providers; add unit and BDD tests using embedded Postgres; update architecture and roadmap docs. Request‑path dispatch remains unimplemented.

Changes

Cohort / File(s) Summary
Dependencies
backend/Cargo.toml
Add apalis-core, apalis-postgres (RC versions) and sqlx with default-features = false, postgres, runtime-tokio-rustls; update wildside-data git rev.
Apalis adapter implementation
backend/src/outbound/queue/apalis_route_queue.rs
Add crate‑internal QueueProvider trait; GenericApalisRouteQueue<P, Q> that serialises plans to JSON and enqueues via provider; add ApalisPostgresProvider wrapping apalis_postgres::PostgresStorage<serde_json::Value> with setup() and push mapping to JobDispatchError::Unavailable; include unit tests for success, provider failure, multiple enqueues and serialization rejection.
Queue module reorganisation
backend/src/outbound/queue/mod.rs
Move inline stub to stub_route_queue module, add apalis_route_queue and test_helpers modules, re‑export StubRouteQueue, ApalisRouteQueue, GenericApalisRouteQueue, and ApalisPostgresProvider; replace inline tests with test_helpers.
Stub queue adapter
backend/src/outbound/queue/stub_route_queue.rs
Add StubRouteQueue<P> that discards plans, provides new()/Default, and logs a single process‑wide tracing::warn! on first use; implements RouteQueue.
Test helpers
backend/src/outbound/queue/test_helpers.rs
Add FakeQueueProvider (shared Arc<Mutex<Vec<serde_json::Value>>>) and FailingQueueProvider for unit tests; both implement QueueProvider and expose helper APIs for assertions.
BDD & integration tests
backend/tests/route_queue_apalis_bdd.rs, backend/tests/features/route_queue_apalis.feature, backend/tests/support/embedded_postgres.rs
Add BDD feature and Rust BDD suite using embedded PostgreSQL; add setup_apalis_storage() helper to create sqlx::PgPool and run PostgresStorage::setup(); test enqueue semantics, multiple enqueues and failure mappings against real Apalis tables.
Documentation
docs/execplans/backend-5-2-1-apalis-route-queue.md, docs/wildside-backend-architecture.md
Add execution‑plan doc for roadmap 5.2.1 and update architecture doc to describe PostgreSQL‑based Apalis queue, scope limits, table setup via PostgresStorage::setup(), monitoring guidance, and worker‑mode notes.

Sequence Diagram(s)

sequenceDiagram
  participant Caller as Caller
  participant RouteQueueAdapter as GenericApalisRouteQueue
  participant Provider as ApalisPostgresProvider / QueueProvider
  participant ApalisStorage as PostgresStorage
  participant Postgres as PostgreSQL

  Caller->>RouteQueueAdapter: enqueue(plan)
  RouteQueueAdapter->>RouteQueueAdapter: serde_json::to_value(plan)
  alt serialization OK
    RouteQueueAdapter->>Provider: push_job(json_payload)
    Provider->>ApalisStorage: push(payload)
    ApalisStorage->>Postgres: INSERT INTO apalis.jobs (payload,...)
    Postgres-->>ApalisStorage: OK / Error
    ApalisStorage-->>Provider: Ok / Err
    Provider-->>RouteQueueAdapter: Ok / JobDispatchError::Unavailable
    RouteQueueAdapter-->>Caller: Ok / JobDispatchError::Unavailable
  else serialization fails
    RouteQueueAdapter-->>Caller: JobDispatchError::Rejected (with serializer error)
  end
Loading

Poem

A queue takes shape with PostgreSQL's might,
Plans serialised in JSON, queued out of sight,
Apalis primes its tables, storage set in stone,
The stub stands patient while adapters are sewn,
Tests hum softly as jobs find their home. 🎩✨

🚥 Pre-merge checks | ✅ 6 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Developer Documentation ⚠️ Warning The PR introduces substantial new queue adapter infrastructure (QueueProvider trait, ApalisPostgresProvider, test helpers) and architectural boundaries but fails to document these in the canonical developers' guide alongside the existing Redis adapter documentation. Add a 'Queue adapters testing' section to docs/developers-guide.md documenting the Apalis PostgreSQL queue adapter's architecture, testing infrastructure, build requirements, and adapter boundaries, matching the existing Redis adapter documentation structure.
✅ Passed checks (6 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: introducing an Apalis-backed RouteQueue adapter with PostgreSQL storage, plus comprehensive testing and documentation coverage.
Description check ✅ Passed The description comprehensively details the changes across production code, testing, documentation, and dependencies, all related to implementing the Apalis RouteQueue adapter.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Testing ✅ Passed The pull request adequately guards all new functionality with both unit tests and behavioural tests covering serialisation round-trips, error mapping, multiple enqueues, stub adapters, and real PostgreSQL persistence.
User-Facing Documentation ✅ Passed RouteQueue and its implementations are crate-internal architectural components, not user-facing functionality. Backend architecture documentation was appropriately updated.
Module-Level Documentation ✅ Passed All six modules carry comprehensive module-level documentation explaining their purpose, utility, and function.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch implement-apalis-routequeue-ra0bjw

Comment @coderabbitai help to get the list of available commands and usage tips.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Apr 3, 2026

Reviewer's Guide

Introduces a production-ready Apalis-backed RouteQueue adapter using PostgreSQL storage behind a QueueProvider abstraction, retains a stub implementation, adds unit and BDD tests with an embedded Postgres harness, updates architecture docs, and wires in Apalis/sqlx dependencies.

Sequence diagram for enqueue via GenericApalisRouteQueue with ApalisPostgresProvider

sequenceDiagram
    actor DomainService
    participant RouteQueueImpl as GenericApalisRouteQueue_P_Q
    participant Provider as ApalisPostgresProvider
    participant Storage as PostgresStorage_serde_json_Value
    participant Postgres as PostgreSQL

    DomainService->>RouteQueueImpl: enqueue(&plan)
    activate RouteQueueImpl
    RouteQueueImpl->>RouteQueueImpl: serde_json::to_vec(plan)
    alt serialization_success
        RouteQueueImpl->>Provider: push_job(payload)
        activate Provider
        Provider->>Provider: serde_json::from_slice(payload)
        Provider->>Storage: push(job)
        activate Storage
        Storage->>Postgres: INSERT job
        Postgres-->>Storage: ok
        Storage-->>Provider: Ok(())
        deactivate Storage
        Provider-->>RouteQueueImpl: Ok(())
        deactivate Provider
        RouteQueueImpl-->>DomainService: Ok(())
    else serialization_failure
        RouteQueueImpl-->>DomainService: Err(JobDispatchError::Rejected)
    end
    deactivate RouteQueueImpl
Loading

Class diagram for Apalis-backed RouteQueue adapter and stub

classDiagram
    direction LR

    class RouteQueue {
        <<trait>>
        +enqueue(plan : Plan) Result~(), JobDispatchError~
        <<associated type>> Plan
    }

    class JobDispatchError {
        <<enum>>
        Unavailable
        Rejected
    }

    class QueueProvider {
        <<trait>>
        +push_job(payload : Vec~u8~) Result~(), JobDispatchError~
    }

    class GenericApalisRouteQueue {
        <<struct>>
        -provider : Q
        -_plan : PhantomData~fn() -> P~
        +new(provider : Q) GenericApalisRouteQueue~P, Q~
        +enqueue(plan : &P) Result~(), JobDispatchError~
    }

    class ApalisRouteQueue {
        <<type alias>>
        GenericApalisRouteQueue~P, ApalisPostgresProvider~
    }

    class ApalisPostgresProvider {
        <<struct>>
        -storage : PostgresStorage~serde_json::Value~
        +new(pool : PgPool) Result~ApalisPostgresProvider, JobDispatchError~
        +push_job(payload : Vec~u8~) Result~(), JobDispatchError~
    }

    class StubRouteQueue {
        <<struct>>
        -_marker : PhantomData~P~
        +new() StubRouteQueue~P~
        +enqueue(plan : &P) Result~(), JobDispatchError~
    }

    class FakeQueueProvider {
        <<struct>>
        -jobs : Vec~Vec~u8~~
        +new() FakeQueueProvider
        +push_job(payload : Vec~u8~) Result~(), JobDispatchError~
        +pushed_jobs() Vec~Vec~u8~~
    }

    class FailingQueueProvider {
        <<struct>>
        -message : String
        +new(message : String) FailingQueueProvider
        +push_job(payload : Vec~u8~) Result~(), JobDispatchError~
    }

    class PgPool {
        <<struct>>
    }

    class PostgresStorage {
        <<struct>>
        +setup(pool : &PgPool) Result~(), Error~
        +new(pool : &PgPool) PostgresStorage~T~
        +push(job : T) Result~(), Error~
    }

    RouteQueue <|.. GenericApalisRouteQueue : implements
    RouteQueue <|.. StubRouteQueue : implements

    QueueProvider <|.. ApalisPostgresProvider : implements
    QueueProvider <|.. FakeQueueProvider : implements
    QueueProvider <|.. FailingQueueProvider : implements

    GenericApalisRouteQueue o--> QueueProvider : uses
    GenericApalisRouteQueue --> JobDispatchError : error_mapping

    ApalisPostgresProvider --> PgPool : constructed_with
    ApalisPostgresProvider o--> PostgresStorage : wraps
    ApalisPostgresProvider --> JobDispatchError : error_mapping

    StubRouteQueue --> JobDispatchError : returns

    FakeQueueProvider --> JobDispatchError : returns
    FailingQueueProvider --> JobDispatchError : returns

    JobDispatchError ..> RouteQueue : used_by
Loading

File-Level Changes

Change Details Files
Refactor outbound queue module to support both stub and Apalis-backed implementations behind a hexagonal boundary.
  • Replaced inline StubRouteQueue definition in queue/mod.rs with submodules and re-exports for StubRouteQueue and the new Apalis adapter.
  • Documented the module as the concrete implementations of the RouteQueue port and clarified the architecture and future roadmap for workers and job types.
  • Added a test_helpers submodule (for tests) to expose fake providers used by unit tests.
backend/src/outbound/queue/mod.rs
Implement Apalis-backed PostgreSQL RouteQueue adapter with pluggable QueueProvider and test doubles.
  • Introduced a QueueProvider trait to abstract queue storage operations for testability.
  • Implemented GenericApalisRouteQueue<P,Q> that serializes plans to JSON and delegates enqueue to a QueueProvider, mapping serialization failures to JobDispatchError::Rejected.
  • Implemented ApalisPostgresProvider over apalis_postgres::PostgresStorage with setup logic and error mapping to JobDispatchError::Unavailable, plus unit tests using FakeQueueProvider and FailingQueueProvider.
  • Added FakeQueueProvider and FailingQueueProvider helpers for in-memory success/failure simulation in unit tests.
backend/src/outbound/queue/apalis_route_queue.rs
backend/src/outbound/queue/test_helpers.rs
Extract and retain StubRouteQueue as a standalone stub adapter for non-production and regression coverage.
  • Moved StubRouteQueue into its own file with Rustdoc and kept its no-op enqueue behaviour and one-time warning log.
  • Maintained existing unit test ensuring stub enqueue always returns Ok.
backend/src/outbound/queue/stub_route_queue.rs
Extend embedded Postgres test harness and add BDD coverage for the Apalis-backed queue adapter.
  • Added setup_apalis_storage(url) helper that builds an sqlx::PgPool and runs PostgresStorage::setup() for Apalis job tables, returning the pool for tests.
  • Added a linkage test to ensure setup_apalis_storage remains wired into the test harness.
  • Created BDD feature file describing success, multi-enqueue, invalid-connection, and duplicate-plan scenarios for the Apalis RouteQueue.
  • Implemented route_queue_apalis_bdd.rs world, step definitions, and assertions that exercise GenericApalisRouteQueue<TestPlan, ApalisPostgresProvider> against an embedded PostgreSQL database using a shared Tokio runtime and SQLx to query apalis.jobs.
  • Ensured error-path scenario produces JobDispatchError::Unavailable when the adapter is configured with an invalid connection.
backend/tests/support/embedded_postgres.rs
backend/tests/features/route_queue_apalis.feature
backend/tests/route_queue_apalis_bdd.rs
Update documentation to describe the Apalis-backed PostgreSQL queue adapter, its scope, and dual-pool architecture.
  • Added a detailed ExecPlan document for roadmap item 5.2.1 covering purpose, constraints, risks, design decisions, testing strategy, and validation for the Apalis RouteQueue adapter.
  • Updated the backend architecture document to state that PostgreSQL (via apalis-postgres) is the current broker instead of Redis, describe the new RouteQueue adapter, clarify that workers and dispatch wiring are out of scope for 5.2.1, and document the dual sqlx/Diesel pool pattern and Apalis table management via PostgresStorage::setup().
docs/execplans/backend-5-2-1-apalis-route-queue.md
docs/wildside-backend-architecture.md
Wire Apalis and SQLx dependencies required for the queue adapter and update wildside-data revision to resolve rusqlite/sqlite conflicts.
  • Added apalis-core and apalis-postgres as queue adapter dependencies and sqlx (postgres, runtime-tokio-rustls) for the Apalis PgPool.
  • Bumped wildside-data git revision to one that uses rusqlite 0.32.1 to resolve libsqlite3-sys version conflicts introduced by sqlx/apalis-postgres.
backend/Cargo.toml
Cargo.lock

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

codescene-delta-analysis[bot]

This comment was marked as outdated.

codescene-delta-analysis[bot]

This comment was marked as outdated.

codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos leynos changed the title Add ExecPlan for Apalis RouteQueue with PostgreSQL (5.2.1) Implement Apalis RouteQueue with PostgreSQL (5.2.1) Apr 4, 2026
codescene-delta-analysis[bot]

This comment was marked as outdated.

codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Apr 6, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response.

backend/tests/route_queue_apalis_bdd.rs

Comment on file

fn the_plan_is_persisted_in_the_queue_storage(world: &SharedContext) {
    with_context_async(
        world,
        |ctx| {
            let pool = ctx.pool.clone().expect("pool should be available");
            let plan = ctx.enqueued_plans.last().expect("at least one plan");
            (pool, plan.clone())
        },
        |(pool, plan)| async move {
            let count: i64 =
                sqlx::query_scalar("SELECT COUNT(*) FROM apalis.jobs WHERE job->>'name' = $1")
                    .bind(&plan.name)
                    .fetch_one(&pool)
                    .await
                    .expect("query job count");
            count
        },
        |_ctx, count| {
            assert!(
                count >= 1,
                "expected at least one job in storage, found {count}"
            );
        },
    );
}

❌ New issue: Code Duplication
The module contains 2 functions with similar structure: the_plan_is_persisted_in_the_queue_storage,two_independent_jobs_exist_in_storage

@coderabbitai

This comment was marked as resolved.

codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos leynos force-pushed the implement-apalis-routequeue-ra0bjw branch from cd95224 to 3605a4c Compare April 7, 2026 09:53
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Apr 7, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response. Ensure that this is validated against the current version of the codegraph.

If further refinement to address this finding would be deleterious, please supply a clear explanatory one to two paragraph markdown message I can paste into the CodeScene web ui's diagnostic suppression function so this diagnostic can be silenced.

Code Duplication

backend/tests/route_queue_apalis_bdd.rs:

What lead to degradation?

The module contains 2 functions with similar structure: the_plan_is_persisted_in_the_queue_storage,two_independent_jobs_exist_in_storage

Why does this problem occur?

Duplicated code often leads to code that's harder to change since the same logical change has to be done in multiple functions. More duplication gives lower code health.

How to fix it?

A certain degree of duplicated code might be acceptable. The problems start when it is the same behavior that is duplicated across the functions in the module, ie. a violation of the Don't Repeat Yourself (DRY) principle. DRY violations lead to code that is changed together in predictable patterns, which is both expensive and risky. DRY violations can be identified using CodeScene's X-Ray analysis to detect clusters of change coupled functions with high code similarity. Read More
Once you have identified the similarities across functions, look to extract and encapsulate the concept that varies into its own function(s). These shared abstractions can then be re-used, which minimizes the amount of duplication and simplifies change.

codescene-delta-analysis[bot]

This comment was marked as outdated.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 7, 2026

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{"name":"HttpError","status":500,"request":{"method":"PATCH","url":"https://api.github.com/repos/leynos/wildside/issues/comments/4181170970","headers":{"accept":"application/vnd.github.v3+json","user-agent":"octokit.js/0.0.0-development octokit-core.js/7.0.6 Node.js/24","authorization":"token [REDACTED]","content-type":"application/json; charset=utf-8"},"body":{"body":"<!-- This is an auto-generated comment: summarize by coderabbit.ai -->\n<!-- This is an auto-generated comment: skip review by coderabbit.ai -->\n\n> [!IMPORTANT]\n> ## Review skipped\n> \n> Draft detected.\n> \n> Please check the settings in the CodeRabbit UI or the `.coderabbit.yaml` file in this repository. To trigger a single review, invoke the `@coderabbitai review` command.\n> \n> <details>\n> <summary>⚙️ Run configuration</summary>\n> \n> **Configuration used**: Organization UI\n> \n> **Review profile**: ASSERTIVE\n> \n> **Plan**: Pro\n> \n> **Run ID**: `eaa0afdd-6ec3-42f9-95e5-e1cd9e4289c4`\n> \n> </details>\n> \n> You can disable this status message by setting the `reviews.review_status` to `false` in the CodeRabbit configuration file.\n> \n> Use the checkbox below for a quick retry:\n> - [ ] <!-- {\"checkboxId\": \"e9bb8d72-00e8-4f67-9cb2-caf3b22574fe\"} --> 🔍 Trigger review\n\n<!-- end of auto-generated comment: skip review by coderabbit.ai -->\n\n\n<!-- finishing_touch_checkbox_start -->\n\n<details>\n<summary>✨ Finishing Touches</summary>\n\n<details>\n<summary>🧪 Generate unit tests (beta)</summary>\n\n- [ ] <!-- {\"checkboxId\": \"f47ac10b-58cc-4372-a567-0e02b2c3d479\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Create PR with unit tests\n- [ ] <!-- {\"checkboxId\": \"6ba7b810-9dad-11d1-80b4-00c04fd430c8\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Commit unit tests in branch `implement-apalis-routequeue-ra0bjw`\n\n</details>\n\n</details>\n\n<!-- finishing_touch_checkbox_end -->\n\n<!-- tips_start -->\n\n---\n\n\n\n<sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub>\n\n<!-- tips_end -->"},"request":{"retryCount":3,"signal":{},"retries":3,"retryAfter":16}}}

@coderabbitai

This comment was marked as resolved.

codescene-delta-analysis[bot]

This comment was marked as outdated.

codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos leynos changed the title Implement Apalis RouteQueue with PostgreSQL (5.2.1) Implement Apalis RouteQueue with PostgreSQL; tests, docs (5.2.1) Apr 8, 2026
codescene-delta-analysis[bot]

This comment was marked as outdated.

codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Apr 8, 2026

@coderabbitai Please suggest a fix for the following test failures and provide an AI coding agent prompt for the fix:

        FAIL [   0.604s] (768/972) backend::route_queue_apalis_bdd route_queue_apalis
  stdout ───

    running 1 test
    test route_queue_apalis ... FAILED

    failures:

    failures:
        route_queue_apalis

    test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 6 filtered out; finished in 0.50s
    
  stderr ───
    The application panicked (crashed).
    Message:  Step failed at index ⁨0⁩: ⁨Given⁩ ⁨a test database with Apalis storage initialised⁩ - ⁨Missing fixture '⁨world⁩' of type '⁨SharedContext⁩' for step function '⁨a_test_database_with_apalis_storage_initialised⁩'⁩ (feature: ⁨/home/runner/work/wildside/wildside/backend/tests/features/route_queue_apalis.feature⁩, scenario: ⁨Successfully enqueue a plan⁩)
    Location: backend/tests/route_queue_apalis_bdd.rs:316

      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ BACKTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
       1: <color_eyre[38c683680e5a1ed1]::config::PanicHook>::panic_report<unknown>
          at <unknown source file>:<unknown line>
       2: <color_eyre[38c683680e5a1ed1]::config::PanicHook>::into_panic_hook::{closure#0}<unknown>
          at <unknown source file>:<unknown line>
       3: <alloc[eb388bbecdd9eb12]::boxed::Box<dyn for<'a, 'b> core[109e66eb34fb8c82]::ops::function::Fn<(&'a std[e6f27331f023ae41]::panic::PanicHookInfo<'b>,), Output = ()> + core[109e66eb34fb8c82]::marker::Sync + core[109e66eb34fb8c82]::marker::Send> as core[109e66eb34fb8c82]::ops::function::Fn<(&std[e6f27331f023ae41]::panic::PanicHookInfo,)>>::call<unknown>
          at /rustc/c756124775121dea0e640652c5ee3c89e3dd0eb4/library/alloc/src/boxed.rs:2263
       4: std[e6f27331f023ae41]::panicking::panic_with_hook<unknown>
          at /rustc/c756124775121dea0e640652c5ee3c89e3dd0eb4/library/std/src/panicking.rs:833
       5: std[e6f27331f023ae41]::panicking::panic_handler::{closure#0}<unknown>
          at /rustc/c756124775121dea0e640652c5ee3c89e3dd0eb4/library/std/src/panicking.rs:698
       6: std[e6f27331f023ae41]::sys::backtrace::__rust_end_short_backtrace::<std[e6f27331f023ae41]::panicking::panic_handler::{closure#0}, !><unknown>
          at /rustc/c756124775121dea0e640652c5ee3c89e3dd0eb4/library/std/src/sys/backtrace.rs:182
       7: __rustc[35c91ab2531d24f9]::rust_begin_unwind<unknown>
          at /rustc/c756124775121dea0e640652c5ee3c89e3dd0eb4/library/std/src/panicking.rs:689
       8: core[109e66eb34fb8c82]::panicking::panic_fmt<unknown>
          at /rustc/c756124775121dea0e640652c5ee3c89e3dd0eb4/library/core/src/panicking.rs:80
       9: core[109e66eb34fb8c82]::panicking::panic_display::<alloc[eb388bbecdd9eb12]::string::String><unknown>
          at <unknown source file>:<unknown line>
      10: <route_queue_apalis_bdd[847230c0d522b486]::route_queue_apalis::{closure#0} as core[109e66eb34fb8c82]::ops::function::FnOnce<()>>::call_once<unknown>
          at <unknown source file>:<unknown line>
      11: <fn() -> core[109e66eb34fb8c82]::result::Result<(), alloc[eb388bbecdd9eb12]::string::String> as core[109e66eb34fb8c82]::ops::function::FnOnce<()>>::call_once<unknown>
          at /rustc/c756124775121dea0e640652c5ee3c89e3dd0eb4/library/core/src/ops/function.rs:250
      12: test[bdcfce426df36444]::__rust_begin_short_backtrace::<core[109e66eb34fb8c82]::result::Result<(), alloc[eb388bbecdd9eb12]::string::String>, fn() -> core[109e66eb34fb8c82]::result::Result<(), alloc[eb388bbecdd9eb12]::string::String>><unknown>
          at /rustc/c756124775121dea0e640652c5ee3c89e3dd0eb4/library/test/src/lib.rs:663
      13: test[bdcfce426df36444]::run_test_in_process::{closure#0}<unknown>
          at /rustc/c756124775121dea0e640652c5ee3c89e3dd0eb4/library/test/src/lib.rs:686
      14: <core[109e66eb34fb8c82]::panic::unwind_safe::AssertUnwindSafe<test[bdcfce426df36444]::run_test_in_process::{closure#0}> as core[109e66eb34fb8c82]::ops::function::FnOnce<()>>::call_once<unknown>
          at /rustc/c756124775121dea0e640652c5ee3c89e3dd0eb4/library/core/src/panic/unwind_safe.rs:275
      15: std[e6f27331f023ae41]::panicking::catch_unwind::do_call::<core[109e66eb34fb8c82]::panic::unwind_safe::AssertUnwindSafe<test[bdcfce426df36444]::run_test_in_process::{closure#0}>, core[109e66eb34fb8c82]::result::Result<(), alloc[eb388bbecdd9eb12]::string::String>><unknown>
          at /rustc/c756124775121dea0e640652c5ee3c89e3dd0eb4/library/std/src/panicking.rs:581
      16: std[e6f27331f023ae41]::panicking::catch_unwind::<core[109e66eb34fb8c82]::result::Result<(), alloc[eb388bbecdd9eb12]::string::String>, core[109e66eb34fb8c82]::panic::unwind_safe::AssertUnwindSafe<test[bdcfce426df36444]::run_test_in_process::{closure#0}>><unknown>
          at /rustc/c756124775121dea0e640652c5ee3c89e3dd0eb4/library/std/src/panicking.rs:544
      17: std[e6f27331f023ae41]::panic::catch_unwind::<core[109e66eb34fb8c82]::panic::unwind_safe::AssertUnwindSafe<test[bdcfce426df36444]::run_test_in_process::{closure#0}>, core[109e66eb34fb8c82]::result::Result<(), alloc[eb388bbecdd9eb12]::string::String>><unknown>
          at /rustc/c756124775121dea0e640652c5ee3c89e3dd0eb4/library/std/src/panic.rs:359
      18: test[bdcfce426df36444]::run_test_in_process<unknown>
          at /rustc/c756124775121dea0e640652c5ee3c89e3dd0eb4/library/test/src/lib.rs:686
      19: test[bdcfce426df36444]::run_test::{closure#0}<unknown>
          at /rustc/c756124775121dea0e640652c5ee3c89e3dd0eb4/library/test/src/lib.rs:607
      20: test[bdcfce426df36444]::run_test::{closure#1}<unknown>
          at /rustc/c756124775121dea0e640652c5ee3c89e3dd0eb4/library/test/src/lib.rs:637
      21: std[e6f27331f023ae41]::sys::backtrace::__rust_begin_short_backtrace::<test[bdcfce426df36444]::run_test::{closure#1}, ()><unknown>
          at /rustc/c756124775121dea0e640652c5ee3c89e3dd0eb4/library/std/src/sys/backtrace.rs:166
      22: std[e6f27331f023ae41]::thread::lifecycle::spawn_unchecked::<test[bdcfce426df36444]::run_test::{closure#1}, ()>::{closure#1}::{closure#0}<unknown>
          at /rustc/c756124775121dea0e640652c5ee3c89e3dd0eb4/library/std/src/thread/lifecycle.rs:91
      23: <core[109e66eb34fb8c82]::panic::unwind_safe::AssertUnwindSafe<std[e6f27331f023ae41]::thread::lifecycle::spawn_unchecked<test[bdcfce426df36444]::run_test::{closure#1}, ()>::{closure#1}::{closure#0}> as core[109e66eb34fb8c82]::ops::function::FnOnce<()>>::call_once<unknown>
          at /rustc/c756124775121dea0e640652c5ee3c89e3dd0eb4/library/core/src/panic/unwind_safe.rs:275
      24: std[e6f27331f023ae41]::panicking::catch_unwind::do_call::<core[109e66eb34fb8c82]::panic::unwind_safe::AssertUnwindSafe<std[e6f27331f023ae41]::thread::lifecycle::spawn_unchecked<test[bdcfce426df36444]::run_test::{closure#1}, ()>::{closure#1}::{closure#0}>, ()><unknown>
          at /rustc/c756124775121dea0e640652c5ee3c89e3dd0eb4/library/std/src/panicking.rs:581
      25: std[e6f27331f023ae41]::panicking::catch_unwind::<(), core[109e66eb34fb8c82]::panic::unwind_safe::AssertUnwindSafe<std[e6f27331f023ae41]::thread::lifecycle::spawn_unchecked<test[bdcfce426df36444]::run_test::{closure#1}, ()>::{closure#1}::{closure#0}>><unknown>
          at /rustc/c756124775121dea0e640652c5ee3c89e3dd0eb4/library/std/src/panicking.rs:544
      26: std[e6f27331f023ae41]::panic::catch_unwind::<core[109e66eb34fb8c82]::panic::unwind_safe::AssertUnwindSafe<std[e6f27331f023ae41]::thread::lifecycle::spawn_unchecked<test[bdcfce426df36444]::run_test::{closure#1}, ()>::{closure#1}::{closure#0}>, ()><unknown>
          at /rustc/c756124775121dea0e640652c5ee3c89e3dd0eb4/library/std/src/panic.rs:359
      27: std[e6f27331f023ae41]::thread::lifecycle::spawn_unchecked::<test[bdcfce426df36444]::run_test::{closure#1}, ()>::{closure#1}<unknown>
          at /rustc/c756124775121dea0e640652c5ee3c89e3dd0eb4/library/std/src/thread/lifecycle.rs:89
      28: <std[e6f27331f023ae41]::thread::lifecycle::spawn_unchecked<test[bdcfce426df36444]::run_test::{closure#1}, ()>::{closure#1} as core[109e66eb34fb8c82]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0}<unknown>
          at /rustc/c756124775121dea0e640652c5ee3c89e3dd0eb4/library/core/src/ops/function.rs:250
      29: <alloc[eb388bbecdd9eb12]::boxed::Box<dyn core[109e66eb34fb8c82]::ops::function::FnOnce<(), Output = ()> + core[109e66eb34fb8c82]::marker::Send> as core[109e66eb34fb8c82]::ops::function::FnOnce<()>>::call_once<unknown>
          at /rustc/c756124775121dea0e640652c5ee3c89e3dd0eb4/library/alloc/src/boxed.rs:2249
      30: <std[e6f27331f023ae41]::sys::thread::unix::Thread>::new::thread_start<unknown>
          at /rustc/c756124775121dea0e640652c5ee3c89e3dd0eb4/library/std/src/sys/thread/unix.rs:118

    Run with COLORBT_SHOW_HIDDEN=1 environment variable to disable frame filtering.
    Run with RUST_BACKTRACE=full to include source snippets.

  Cancelling due to test failure: 2 tests still running
        PASS [   0.561s] (769/972) backend::diesel_walk_session_repository walk_repository_maps_missing_schema_to_query_error
        PASS [  30.009s] (770/972) backend::route_cache_redis_bdd unreachable_redis_surfaces_as_a_backend_failure
────────────
     Summary [  64.778s] 770/972 tests run: 769 passed, 1 failed, 4 skipped
        FAIL [   0.604s] (768/972) backend::route_queue_apalis_bdd route_queue_apalis
warning: 202/972 tests were not run due to test failure (run with --no-fail-fast to run all tests, or run with --max-fail)
error: test run failed

@coderabbitai

This comment was marked as resolved.

codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos leynos force-pushed the implement-apalis-routequeue-ra0bjw branch from 89a5113 to cd58818 Compare April 10, 2026 11:50
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Apr 10, 2026

@coderabbitai Please suggest a fix for the following test failure and provide an AI coding agent prompt for the fix:

        FAIL [   0.648s] (744/972) backend::route_queue_apalis_bdd route_queue_apalis
  stdout ───

    running 1 test
    test route_queue_apalis ... FAILED

    failures:

    failures:
        route_queue_apalis

    test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 6 filtered out; finished in 0.54s
    
  stderr ───
    The application panicked (crashed).
    Message:  query job count: Database(PgDatabaseError { severity: Error, code: "42883", message: "operator does not exist: bytea ->> unknown", detail: None, hint: Some("No operator matches the given name and argument types. You might need to add explicit type casts."), position: Some(Original(43)), where: None, schema: None, table: None, column: None, data_type: None, constraint: None, file: Some("parse_oper.c"), line: Some(635), routine: Some("op_error") })
    Location: backend/tests/route_queue_apalis_bdd.rs:258

      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ BACKTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
       1: <color_eyre[2258a4b6ace235e9]::config::PanicHook>::panic_report<unknown>
          at <unknown source file>:<unknown line>
       2: <color_eyre[2258a4b6ace235e9]::config::PanicHook>::into_panic_hook::{closure#0}<unknown>
          at <unknown source file>:<unknown line>
       3: <alloc[1ca838a5036d1ae3]::boxed::Box<dyn for<'a, 'b> core[884061b77c9fd841]::ops::function::Fn<(&'a std[fbb773ab12be8d26]::panic::PanicHookInfo<'b>,), Output = ()> + core[884061b77c9fd841]::marker::Sync + core[884061b77c9fd841]::marker::Send> as core[884061b77c9fd841]::ops::function::Fn<(&std[fbb773ab12be8d26]::panic::PanicHookInfo,)>>::call<unknown>
          at /rustc/f5eca4fcfa908d1e038afd19c6e746f075859130/library/alloc/src/boxed.rs:2263
       4: std[fbb773ab12be8d26]::panicking::panic_with_hook<unknown>
          at /rustc/f5eca4fcfa908d1e038afd19c6e746f075859130/library/std/src/panicking.rs:833
       5: std[fbb773ab12be8d26]::panicking::panic_handler::{closure#0}<unknown>
          at /rustc/f5eca4fcfa908d1e038afd19c6e746f075859130/library/std/src/panicking.rs:698
       6: std[fbb773ab12be8d26]::sys::backtrace::__rust_end_short_backtrace::<std[fbb773ab12be8d26]::panicking::panic_handler::{closure#0}, !><unknown>
          at /rustc/f5eca4fcfa908d1e038afd19c6e746f075859130/library/std/src/sys/backtrace.rs:182
       7: __rustc[af02054c5d8a4591]::rust_begin_unwind<unknown>
          at /rustc/f5eca4fcfa908d1e038afd19c6e746f075859130/library/std/src/panicking.rs:689
       8: core[884061b77c9fd841]::panicking::panic_fmt<unknown>
          at /rustc/f5eca4fcfa908d1e038afd19c6e746f075859130/library/core/src/panicking.rs:80
       9: core[884061b77c9fd841]::result::unwrap_failed<unknown>
          at /rustc/f5eca4fcfa908d1e038afd19c6e746f075859130/library/core/src/result.rs:1867
      10: route_queue_apalis_bdd[cae2c5de8dda58b3]::count_jobs_for_plan_name::{closure#0}<unknown>
          at <unknown source file>:<unknown line>
      11: <tokio[169a4475e340f628]::runtime::park::CachedParkThread>::block_on::<route_queue_apalis_bdd[cae2c5de8dda58b3]::fetch_last_plan_job_count::{closure#1}::{closure#0}><unknown>
          at <unknown source file>:<unknown line>
      12: tokio[169a4475e340f628]::runtime::context::runtime::enter_runtime::<<tokio[169a4475e340f628]::runtime::handle::Handle>::block_on_inner<route_queue_apalis_bdd[cae2c5de8dda58b3]::fetch_last_plan_job_count::{closure#1}::{closure#0}>::{closure#0}, i64><unknown>
          at <unknown source file>:<unknown line>
      13: route_queue_apalis_bdd[cae2c5de8dda58b3]::fetch_last_plan_job_count<unknown>
          at <unknown source file>:<unknown line>
      14: route_queue_apalis_bdd[cae2c5de8dda58b3]::__rstest_bdd_wrapper_the_plan_is_persisted_in_the_queue_storage_22<unknown>
          at <unknown source file>:<unknown line>
      15: rstest_bdd[7f3cd51cda25fb28]::execution::execute_step<unknown>
          at <unknown source file>:<unknown line>
      16: <route_queue_apalis_bdd[cae2c5de8dda58b3]::route_queue_apalis::{closure#0} as core[884061b77c9fd841]::ops::function::FnOnce<()>>::call_once<unknown>
          at <unknown source file>:<unknown line>
      17: <fn() -> core[884061b77c9fd841]::result::Result<(), alloc[1ca838a5036d1ae3]::string::String> as core[884061b77c9fd841]::ops::function::FnOnce<()>>::call_once<unknown>
          at /rustc/f5eca4fcfa908d1e038afd19c6e746f075859130/library/core/src/ops/function.rs:250
       3: <alloc[1ca838a5036d1ae3]::boxed::Box<dyn for<'a, 'b> core[884061b77c9fd841]::ops::function::Fn<(&'a std[fbb773ab12be8d26]::panic::PanicHookInfo<'b>,), Output = ()> + core[884061b77c9fd841]::marker::Sync + core[884061b77c9fd841]::marker::Send> as core[884061b77c9fd841]::ops::function::Fn<(&std[fbb773ab12be8d26]::panic::PanicHookInfo,)>>::call<unknown>
          at /rustc/f5eca4fcfa908d1e038afd19c6e746f075859130/library/alloc/src/boxed.rs:2263
       4: std[fbb773ab12be8d26]::panicking::panic_with_hook<unknown>
          at /rustc/f5eca4fcfa908d1e038afd19c6e746f075859130/library/std/src/panicking.rs:833
       5: std[fbb773ab12be8d26]::panicking::panic_handler::{closure#0}<unknown>
          at /rustc/f5eca4fcfa908d1e038afd19c6e746f075859130/library/std/src/panicking.rs:698
       6: std[fbb773ab12be8d26]::sys::backtrace::__rust_end_short_backtrace::<std[fbb773ab12be8d26]::panicking::panic_handler::{closure#0}, !><unknown>
          at /rustc/f5eca4fcfa908d1e038afd19c6e746f075859130/library/std/src/sys/backtrace.rs:182
       7: __rustc[af02054c5d8a4591]::rust_begin_unwind<unknown>
          at /rustc/f5eca4fcfa908d1e038afd19c6e746f075859130/library/std/src/panicking.rs:689
       8: core[884061b77c9fd841]::panicking::panic_fmt<unknown>
          at /rustc/f5eca4fcfa908d1e038afd19c6e746f075859130/library/core/src/panicking.rs:80
       9: core[884061b77c9fd841]::panicking::panic_display::<alloc[1ca838a5036d1ae3]::string::String><unknown>
          at <unknown source file>:<unknown line>
      10: <route_queue_apalis_bdd[cae2c5de8dda58b3]::route_queue_apalis::{closure#0} as core[884061b77c9fd841]::ops::function::FnOnce<()>>::call_once<unknown>
          at <unknown source file>:<unknown line>
      11: <fn() -> core[884061b77c9fd841]::result::Result<(), alloc[1ca838a5036d1ae3]::string::String> as core[884061b77c9fd841]::ops::function::FnOnce<()>>::call_once<unknown>
          at /rustc/f5eca4fcfa908d1e038afd19c6e746f075859130/library/core/src/ops/function.rs:250
      12: test[fb38fe405ff56c5d]::__rust_begin_short_backtrace::<core[884061b77c9fd841]::result::Result<(), alloc[1ca838a5036d1ae3]::string::String>, fn() -> core[884061b77c9fd841]::result::Result<(), alloc[1ca838a5036d1ae3]::string::String>><unknown>
          at /rustc/f5eca4fcfa908d1e038afd19c6e746f075859130/library/test/src/lib.rs:663
      13: test[fb38fe405ff56c5d]::run_test_in_process::{closure#0}<unknown>
          at /rustc/f5eca4fcfa908d1e038afd19c6e746f075859130/library/test/src/lib.rs:686
      14: <core[884061b77c9fd841]::panic::unwind_safe::AssertUnwindSafe<test[fb38fe405ff56c5d]::run_test_in_process::{closure#0}> as core[884061b77c9fd841]::ops::function::FnOnce<()>>::call_once<unknown>
          at /rustc/f5eca4fcfa908d1e038afd19c6e746f075859130/library/core/src/panic/unwind_safe.rs:275
      15: std[fbb773ab12be8d26]::panicking::catch_unwind::do_call::<core[884061b77c9fd841]::panic::unwind_safe::AssertUnwindSafe<test[fb38fe405ff56c5d]::run_test_in_process::{closure#0}>, core[884061b77c9fd841]::result::Result<(), alloc[1ca838a5036d1ae3]::string::String>><unknown>
          at /rustc/f5eca4fcfa908d1e038afd19c6e746f075859130/library/std/src/panicking.rs:581
      16: std[fbb773ab12be8d26]::panicking::catch_unwind::<core[884061b77c9fd841]::result::Result<(), alloc[1ca838a5036d1ae3]::string::String>, core[884061b77c9fd841]::panic::unwind_safe::AssertUnwindSafe<test[fb38fe405ff56c5d]::run_test_in_process::{closure#0}>><unknown>
          at /rustc/f5eca4fcfa908d1e038afd19c6e746f075859130/library/std/src/panicking.rs:544
      17: std[fbb773ab12be8d26]::panic::catch_unwind::<core[884061b77c9fd841]::panic::unwind_safe::AssertUnwindSafe<test[fb38fe405ff56c5d]::run_test_in_process::{closure#0}>, core[884061b77c9fd841]::result::Result<(), alloc[1ca838a5036d1ae3]::string::String>><unknown>
          at /rustc/f5eca4fcfa908d1e038afd19c6e746f075859130/library/std/src/panic.rs:359
      18: test[fb38fe405ff56c5d]::run_test_in_process<unknown>
          at /rustc/f5eca4fcfa908d1e038afd19c6e746f075859130/library/test/src/lib.rs:686
      19: test[fb38fe405ff56c5d]::run_test::{closure#0}<unknown>
          at /rustc/f5eca4fcfa908d1e038afd19c6e746f075859130/library/test/src/lib.rs:607
      20: test[fb38fe405ff56c5d]::run_test::{closure#1}<unknown>
          at /rustc/f5eca4fcfa908d1e038afd19c6e746f075859130/library/test/src/lib.rs:637
      21: std[fbb773ab12be8d26]::sys::backtrace::__rust_begin_short_backtrace::<test[fb38fe405ff56c5d]::run_test::{closure#1}, ()><unknown>
          at /rustc/f5eca4fcfa908d1e038afd19c6e746f075859130/library/std/src/sys/backtrace.rs:166
      22: std[fbb773ab12be8d26]::thread::lifecycle::spawn_unchecked::<test[fb38fe405ff56c5d]::run_test::{closure#1}, ()>::{closure#1}::{closure#0}<unknown>
          at /rustc/f5eca4fcfa908d1e038afd19c6e746f075859130/library/std/src/thread/lifecycle.rs:91
      23: <core[884061b77c9fd841]::panic::unwind_safe::AssertUnwindSafe<std[fbb773ab12be8d26]::thread::lifecycle::spawn_unchecked<test[fb38fe405ff56c5d]::run_test::{closure#1}, ()>::{closure#1}::{closure#0}> as core[884061b77c9fd841]::ops::function::FnOnce<()>>::call_once<unknown>
          at /rustc/f5eca4fcfa908d1e038afd19c6e746f075859130/library/core/src/panic/unwind_safe.rs:275
      24: std[fbb773ab12be8d26]::panicking::catch_unwind::do_call::<core[884061b77c9fd841]::panic::unwind_safe::AssertUnwindSafe<std[fbb773ab12be8d26]::thread::lifecycle::spawn_unchecked<test[fb38fe405ff56c5d]::run_test::{closure#1}, ()>::{closure#1}::{closure#0}>, ()><unknown>
          at /rustc/f5eca4fcfa908d1e038afd19c6e746f075859130/library/std/src/panicking.rs:581
      25: std[fbb773ab12be8d26]::panicking::catch_unwind::<(), core[884061b77c9fd841]::panic::unwind_safe::AssertUnwindSafe<std[fbb773ab12be8d26]::thread::lifecycle::spawn_unchecked<test[fb38fe405ff56c5d]::run_test::{closure#1}, ()>::{closure#1}::{closure#0}>><unknown>
          at /rustc/f5eca4fcfa908d1e038afd19c6e746f075859130/library/std/src/panicking.rs:544
      26: std[fbb773ab12be8d26]::panic::catch_unwind::<core[884061b77c9fd841]::panic::unwind_safe::AssertUnwindSafe<std[fbb773ab12be8d26]::thread::lifecycle::spawn_unchecked<test[fb38fe405ff56c5d]::run_test::{closure#1}, ()>::{closure#1}::{closure#0}>, ()><unknown>
          at /rustc/f5eca4fcfa908d1e038afd19c6e746f075859130/library/std/src/panic.rs:359
      27: std[fbb773ab12be8d26]::thread::lifecycle::spawn_unchecked::<test[fb38fe405ff56c5d]::run_test::{closure#1}, ()>::{closure#1}<unknown>
          at /rustc/f5eca4fcfa908d1e038afd19c6e746f075859130/library/std/src/thread/lifecycle.rs:89
      28: <std[fbb773ab12be8d26]::thread::lifecycle::spawn_unchecked<test[fb38fe405ff56c5d]::run_test::{closure#1}, ()>::{closure#1} as core[884061b77c9fd841]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0}<unknown>
          at /rustc/f5eca4fcfa908d1e038afd19c6e746f075859130/library/core/src/ops/function.rs:250
      29: <alloc[1ca838a5036d1ae3]::boxed::Box<dyn core[884061b77c9fd841]::ops::function::FnOnce<(), Output = ()> + core[884061b77c9fd841]::marker::Send> as core[884061b77c9fd841]::ops::function::FnOnce<()>>::call_once<unknown>
          at /rustc/f5eca4fcfa908d1e038afd19c6e746f075859130/library/alloc/src/boxed.rs:2249
      30: <std[fbb773ab12be8d26]::sys::thread::unix::Thread>::new::thread_start<unknown>
          at /rustc/f5eca4fcfa908d1e038afd19c6e746f075859130/library/std/src/sys/thread/unix.rs:118

    Run with COLORBT_SHOW_HIDDEN=1 environment variable to disable frame filtering.
    Run with RUST_BACKTRACE=full to include source snippets.

  Cancelling due to test failure: 2 tests still running
        PASS [   0.653s] (745/972) backend::diesel_user_preferences_repository preferences_repository_schema_rejects_negative_revisions
        PASS [  30.011s] (746/972) backend::route_cache_redis_bdd unreachable_redis_surfaces_as_a_backend_failure
────────────
     Summary [  65.322s] 746/972 tests run: 745 passed, 1 failed, 4 skipped
        FAIL [   0.648s] (744/972) backend::route_queue_apalis_bdd route_queue_apalis
warning: 226/972 tests were not run due to test failure (run with --no-fail-fast to run all tests, or run with --max-fail)
error: test run failed

@coderabbitai

This comment was marked as resolved.

leynos and others added 23 commits April 22, 2026 00:33
…helpers

Simplified locking mechanism in FakeQueueProvider by removing explicit unwrap_or_else panic and using unwrap directly, leading to cleaner and more concise code.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
…' for clarity

Refactored the fixture name from 'queue_world' to 'world' in route_queue_apalis_bdd.rs to improve test code readability and consistency in naming conventions.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
…ontext

Change all step definition functions in route_queue_apalis_bdd.rs to accept an Option<SharedContext> instead of SharedContext. Add early return if None, tightening safety and preventing panics when context is missing during BDD tests.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
Modify SQL to convert 'job' from bytea to JSONB before accessing 'name' field, fixing query error in Apalis jobs count helper function.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
Changed ApalisPostgresProvider to parse the job payload as JSON before pushing it to the PostgresStorage. This prevents invalid payloads from being enqueued and provides clearer error handling when the payload is not valid JSON.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
Introduce a new `QueueProvider` trait accepting `serde_json::Value` payloads to unify job serialization.

Implement `GenericApalisRouteQueue` with PostgreSQL storage via `apalis-postgres`.

Migrate serialization from bytes vector to JSON values for improved decoupling.

Add new integration tests and improve error handling for queue unavailability.

Update documentation to reflect PostgreSQL as job queue backend using Apalis, describing architecture, monitoring, and migration rationale.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
…g jobs

Avoid parsing payload into serde_json::Value before pushing; push raw payload instead. Simplify FakeQueueProvider code by replacing expect calls with unwrap for mutex locking.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
…rror

Add test case for GenericApalisRouteQueue to ensure that enqueue returns a rejected error when serialization of the job plan fails. This verifies proper error handling and that no jobs are pushed to the queue provider on serialization failure.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
…ing and document usage

- changed FakeQueueProvider::pushed_jobs to return Result instead of panicking on mutex poison
- updated push_job to propagate mutex lock errors as JobDispatchError::Unavailable
- added example code to QueueProvider::push_job trait method doc
- updated tests to properly handle Result from pushed_jobs
- improved error message assertions for serialization failure

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
- Removed custom FailingProvider and replaced with real ApalisPostgresProvider
  using an invalid database connection URL to simulate failure.
- Adapted async setup code to handle lazy connection pool and provider error.
- Improved error handling and test robustness for database unavailability scenarios.
- Renamed test structs and improved error unwrapping for serialization failure tests.
- Removed unnecessary imports and test helper annotations for clarity.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
Add Debug and Clone derives to the FailingSerializePlan struct used in serialization failing tests. This change improves test diagnostics and usability by enabling debug printing and cloning of the test plan.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
…usage

Enhanced explanations clarify why the `setup()` method is called on `PostgresStorage<(), (), ()>` despite type parameters used elsewhere, detailing Apalis library conventions and safety rationale.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
Add a check for queue availability before attempting to enqueue a test plan in the BDD test step. If the queue is unavailable (e.g., provider construction failed), skip enqueueing since the error is already recorded. This prevents redundant attempts and clarifies test behavior when queue setup fails.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
… and comments

Corrected the spelling of 'initialised' to 'initialized' in the BDD feature file and corresponding Rust test step definitions and comments for consistency.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
- Remove unused import handle_cluster_setup_failure for clarity
- Replace cluster setup failure handler with panic to fail tests explicitly
- Replace conditional skip in tests with assert to enforce fixture setup success

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
- Make QueueProvider trait crate-internal (pub(crate)) and remove public
  re-export from mod.rs to align documentation with visibility
- Update stub warning message from "not implemented" to "stub adapter
  selected; job discarded" for clarity
- Update FailingQueueProvider documentation to clarify it only produces
  JobDispatchError::Unavailable (not rejection errors)
- Fix spelling: change "initialised" to "initialized" in BDD test expect
  message for consistency with American English
- Update architecture docs to use future tense for Apalis worker
  configuration (queues, handlers, retries, dead-letter, timeouts) since
  these features are planned but not yet implemented
- Update execplan documentation to reflect current queue contract:
  replace DeserializeOwned/Vec<u8> references with serde_json::Value
  payload flow and Serialize-only bounds

Note: Kept manual Default implementation for StubRouteQueue as deriving
Default would add unwanted P: Default bound. The inline comment suggesting
derivation was incorrect.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Update example code in QueueProvider trait to use 'rust,ignore' for maintainers.
- Refactor Apalis RouteQueue documentation for clarity and accuracy.
- Edit wildside-backend-architecture.md to better explain worker mode initialization, job queue config, retries, and timeouts with Apalis.
- Add dependency on apalis-core 1.0.0-rc.7 in execplan toml.

These changes improve documentation clarity around the Apalis-based backend queue integration and usage in roadmap 5.2.1.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
…QL job queue adapter and roadmap

- Describe new ApalisPostgresProvider, ApalisRouteQueue, and GenericApalisRouteQueue alongside StubRouteQueue in queue adapter docs.
- Revise backend architecture details to clarify planned Apalis PostgreSQL-backed job queue, replacing Redis queue mentions.
- Update design decisions to reflect introduction of PostgreSQL-backed RouteQueue adapter with Apalis.
- Clarify worker mode bootstrap and scaling strategy is planned but not yet implemented.
- Refine explanations on job queue usage, observability, and deployment scaling with Apalis and PostgreSQL.

These documentation changes align with roadmap item 5.2.1 and clarify the current state and future plans for job queueing in the backend.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
Keep the invalid-database BDD scenario focused on RouteQueue::enqueue
instead of recording failure during the Given step.

Expose the queue test helpers to integration tests through the existing
 feature, then fall back to a failing queue provider when
Apalis provider setup cannot complete on a lazy broken pool. This keeps
the queue present, leaves  empty until the When step,
and ensures the unavailable error is observed at enqueue time.
Wrap the PostgreSQL queue-capabilities sentence in
 so the paragraph stays
within the repository's 80-column Markdown limit without changing
its wording or context.
Annotate the FakeQueueProvider implementation of QueueProvider with async_trait so the generated push_job signature matches the trait declaration.

Without the attribute, the test helper trips E0195 during lint and test builds because the async trait transformation is only applied on the trait side.
Add a queue adapter testing section to the canonical developers guide.

The new section documents the Apalis PostgreSQL RouteQueue adapter, its unit and BDD test infrastructure, setup_apalis_storage, build requirements, and adapter boundaries while leaving the existing Redis guidance unchanged.
Rebasing onto origin/main exposed duplicate ingest-osm binary entries in backend/Cargo.toml.

Restore the openapi-dump and er-snapshots targets from main while keeping the queue adapter dependency changes from this branch. This returns the manifest to a valid state and keeps the rebased branch buildable.
@lodyai lodyai Bot force-pushed the implement-apalis-routequeue-ra0bjw branch from d528cc0 to 3527543 Compare April 21, 2026 22:42
codescene-delta-analysis[bot]

This comment was marked as outdated.

Remove extra blank lines in the queue adapter testing section of the developers guide so markdownlint no longer reports MD012 violations.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 22, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
BRANCH="implement-apalis-routequeue-ra0bjw"
REPO="leynos/wildside"

echo "=== route_queue_apalis_bdd.rs lines 1-20 (module docs) ==="
gh api "repos/$REPO/contents/backend/tests/route_queue_apalis_bdd.rs?ref=$BRANCH" \
  --jq '.content' | base64 -d | head -20

echo ""
echo "=== route_queue_apalis_bdd.rs lines 335-360 (scenario function) ==="
gh api "repos/$REPO/contents/backend/tests/route_queue_apalis_bdd.rs?ref=$BRANCH" \
  --jq '.content' | base64 -d | sed -n '335,360p'

echo ""
echo "=== apalis_route_queue.rs first 15 lines ==="
gh api "repos/$REPO/contents/backend/src/outbound/queue/apalis_route_queue.rs?ref=$BRANCH" \
  --jq '.content' | base64 -d | head -15

echo ""
echo "=== stub_route_queue.rs first 15 lines ==="
gh api "repos/$REPO/contents/backend/src/outbound/queue/stub_route_queue.rs?ref=$BRANCH" \
  --jq '.content' | base64 -d | head -15

echo ""
echo "=== test_helpers.rs first 15 lines ==="
gh api "repos/$REPO/contents/backend/src/outbound/queue/test_helpers.rs?ref=$BRANCH" \
  --jq '.content' | base64 -d | head -15

echo ""
echo "=== embedded_postgres.rs - setup_apalis_storage and file start ==="
gh api "repos/$REPO/contents/backend/tests/support/embedded_postgres.rs?ref=$BRANCH" \
  --jq '.content' | base64 -d | head -10
echo "---"
gh api "repos/$REPO/contents/backend/tests/support/embedded_postgres.rs?ref=$BRANCH" \
  --jq '.content' | base64 -d | grep -n -B2 -A5 'setup_apalis_storage'

Length of output: 4280


@leynos — ✅ Both warnings are fully resolved in the current branch state.

Check Evidence
Testing — assert on fixture setup route_queue_apalis scenario function (Line 352) contains assert!(world.is_some(), "route_queue_apalis: fixture setup failed"); — no silent return
Module-Level Documentationapalis_route_queue.rs //! Apalis-backed \RouteQueue` adapter using PostgreSQL storage.` at Line 1
Module-Level Documentationstub_route_queue.rs //! Stub queue implementation that discards all jobs. at Line 1
Module-Level Documentationtest_helpers.rs //! Test utilities for queue adapters. at Line 1
Module-Level Documentationroute_queue_apalis_bdd.rs Full //! block (Lines 1–11) covering purpose, runtime strategy, and usage
Module-Level Documentationembedded_postgres.rs Full //! block (Lines 1–9) plus doc comments on setup_apalis_storage

No further work is required for either check.

@leynos leynos merged commit 70def9b into main Apr 22, 2026
3 checks passed
@leynos leynos deleted the implement-apalis-routequeue-ra0bjw branch April 22, 2026 00:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant