Ensuring Robust ESHOPMAN Integration Tests: Navigating Race Conditions with Background Workflows

Understanding Intermittent ESHOPMAN Integration Test Failures

Developing on ESHOPMAN, with its powerful headless commerce capabilities and seamless HubSpot integration, often involves writing robust integration tests. These tests are crucial for ensuring the reliability of custom features, Admin API extensions, and Store API interactions that power your HubSpot CMS storefronts. However, some ESHOPMAN developers have encountered perplexing, non-deterministic failures in their integration test suites, particularly when dealing with event-triggered background workflows.

This community insight delves into a specific scenario where the ESHOPMAN Integration Test Runner's database cleanup mechanism can race against in-flight, event-driven background processes, leading to intermittent test failures. Understanding this behavior is key to writing more stable and predictable tests for your ESHOPMAN applications built with Node.js and TypeScript.

The Core Issue: A Race Between Cleanup and Background Workflows

The problem arises because the afterEach hook in the ESHOPMAN Integration Test Runner, which is responsible for resetting the database state between tests, can return prematurely. It sometimes signals that all background workflows have completed even when subsequent workflows, triggered by events, are still pending or in the process of starting. This creates a "visibility gap" where the database reset proceeds while a background workflow is about to interact with the database, leading to various forms of test instability.

Consider a typical ESHOPMAN test scenario: a request is made that, as a side effect, emits an event (e.g., payment.captured). Subscribers to this event then kick off additional workflows in the background. Crucially, these subscriber-triggered workflows are often not directly awaited or asserted by the original test; they are intended as pure background effects. Yet, they still perform database operations.

The "Visibility Gap" Explained

The ESHOPMAN Integration Test Runner utilizes a utility, waitWorkflowExecutions, to pause until workflows are deemed complete. However, this utility has an inherent limitation:

// @eshopmanjs/test-utils — eshopman-test-runner-utils/wait-workflow-executions.ts
while (waitWorkflowsToFinish) {
  const executi wfe.listWorkflowExecutions({
    state: { $nin: ["not_started", "done", "reverted", "failed"] },
  })
  if (executions.length === 0) { break }            // single-shot, no settle window
  await new Promise((resolve) => setTimeout(resolve, 50))
}

This loop exits the first time it observes zero non-terminal executions. The challenge lies in the asynchronous chain of events that listWorkflowExecutions cannot see in time:

  • Event Buffering: Workflow events are buffered and released only when the transaction succeeds, usually within an onFinish hook.
  • Deferred Dispatch: The local event bus dispatch itself is often deferred, meaning the emission to subscribers is a "floating promise" that doesn't await the async subscriber.
  • Fire-and-Forget Workflows: Subscribers typically call workflow.run(...) in a fire-and-forget manner. The actual insertion of a workflow_execution row and subsequent SQL operations happen later.
  • Workflow Deletion: Finished workflows without a retention time are deleted from the workflow_execution table, rather than being marked as done. This removes any trace that could keep the wait loop active.

The net effect is a window where zero non-terminal executions exist, but a follow-on workflow is imminent. If the 50ms poll happens during this window, waitWorkflowExecutions returns prematurely, and the database reset begins while a background workflow is still pending or mid-flight.

Manifestations of the Race Condition

This race condition can surface in different ways depending on the ESHOPMAN version:

  • ESHOPMAN Versions ≤ 2.16.0: Intermittent Teardown Deadlock

    In these versions, the afterEach hook performs a TRUNCATE operation, which requires an ACCESS EXCLUSIVE lock on all tables. If a straggler workflow tries to acquire locks in a different order, PostgreSQL detects a deadlock, aborting one of the statements (e.g., the TRUNCATE or a SELECT from the workflow).

    // @eshopmanjs/test-utils — eshopman-test-runner.ts (afterEach, ≤ 2.16.0)
    public async afterEach(): Promise {
      await waitWorkflowExecutions(this.globalContainer as ESHOPMANContainer)
      if (!this.disableAutoTeardown) {
        await this.dbUtils.teardown({ schema: this.schema }) // TRUNCATE 
      }
    }
    
  • ESHOPMAN Versions ≥ 2.17.0: Data Corruption or Restore Failures

    Newer versions moved the primary database reset out of afterEach. Now, afterEach only waits, and the reset happens in the next test's beforeEach by dropping and recreating the database from a template. Because waitWorkflowExecutions still returns early, a straggler workflow can:

    • Corrupt Data: Write its rows into the freshly-restored database, causing the next test (which expects a clean DB) to see leaked data.
    • Fail Restore: Hold a connection while beforeEach attempts to drop the database, leading to an error like database "..." is being accessed by other users.
    • Experience Connection Errors: Have its in-flight queries killed by the database termination commands (`pg_terminate_backend`), resulting in errors like `terminating connection due to administrator command`.
    // @eshopmanjs/test-utils — eshopman-test-runner.ts (afterEach, ≥ 2.17.0)
    public async afterEach(): Promise {
      if (!this.globalContainer) return
      await waitWorkflowExecutions(this.globalContainer)   // same single-shot wait; no reset here
    }
    

Towards More Reliable ESHOPMAN Testing

The expected behavior for the ESHOPMAN Integration Test Runner is to ensure a truly quiescent state before any database reset. This means accounting for the entire event-to-workflow cascade – including buffered events, pending event bus dispatches, and workflows that are not yet recorded but are imminent. Developers should be aware of this potential race condition when debugging intermittent test failures, especially in scenarios involving complex event-driven logic and background processes within their ESHOPMAN applications.

While the ESHOPMAN team continues to refine the testing utilities, understanding these underlying mechanisms empowers developers to anticipate and mitigate such issues, ensuring their ESHOPMAN storefronts and integrations deployed via HubSpot CMS are built on a solid foundation of reliable testing.

Start with the tools

Explore migration tools

See options, compare methods, and pick the path that fits your store.

Explore migration tools