← back to Homesonspec

apps/workers/src/queue-resilience.itest.ts

79 lines

import { afterAll, beforeAll, describe, expect, it } from "vitest";
import {
  getQueue,
  enqueue,
  stopQueue,
  computeQueueHealth,
  getQueueHealthState,
  recordQueueHealthy,
} from "@homesonspec/shared";

/**
 * Integration: exercises the TK-10240 pg-boss resilience facade against a REAL
 * (test) Postgres — the "test vs the live crawler" coverage the mocked unit
 * suite in packages/shared/queue.test.ts cannot provide. Proves against real
 * pg-boss + Postgres:
 *   (1) enqueue() writes a real, fetchable job through Postgres,
 *   (2) getQueue() attaches an 'error' listener so a boss error is NON-FATAL
 *       (the ride-out that replaced process.exit(1)) and flips the health probe,
 *       and a canary success recovers it,
 *   (3) stopQueue() tears down cleanly and getQueue() yields a fresh instance
 *       with the ride-out handler re-attached.
 *
 * Uses fetch()/complete() rather than a persistent work() subscription so no
 * poller outlives the test (a lingering subscription throws "Workers are
 * disabled" after stopQueue). pg-boss creates its own `pgboss` schema on
 * start(); safe because the integration setup pins DATABASE_URL to a throwaway
 * *_test database.
 */

const JOB = "resilience-itest-job";

beforeAll(() => {
  // Guard: never touch a non-test database.
  expect(process.env.DATABASE_URL).toMatch(/homesonspec_test/);
});

afterAll(async () => {
  await stopQueue();
});

describe("pg-boss resilience facade (real Postgres)", () => {
  it("enqueue writes a real, fetchable job through Postgres", async () => {
    const boss = await getQueue();
    await boss.createQueue(JOB);
    const id = await enqueue(JOB, { n: 42 });
    expect(id).toBeTruthy();

    const jobs = await boss.fetch<{ n: number }>(JOB);
    expect(jobs[0]?.data).toEqual({ n: 42 });
    if (jobs[0]) await boss.complete(JOB, jobs[0].id);
  }, 20_000);

  it("a boss 'error' is non-fatal (ride-out) and flips health; a canary success recovers it", async () => {
    const boss = await getQueue();
    // The whole point of TK-10240: getQueue() registers an 'error' listener so a
    // transient pg-boss/pg-pool error is recorded + survived instead of bubbling
    // to an uncaught 'error' event (which would crash the process). Node only
    // throws for an 'error' emit with ZERO listeners — so this emit NOT throwing
    // is itself the proof the ride-out handler is wired.
    expect(boss.listenerCount("error")).toBeGreaterThanOrEqual(1);
    boss.emit("error", new Error("synthetic transient blip"));

    expect(computeQueueHealth(getQueueHealthState(), Date.now()).healthy).toBe(false);

    // A real canary roundtrip (what startQueueHealthCanary does) proves recovery.
    await boss.getQueueSize(JOB);
    recordQueueHealthy();
    expect(computeQueueHealth(getQueueHealthState(), Date.now()).healthy).toBe(true);
  }, 20_000);

  it("stopQueue tears down cleanly; getQueue yields a fresh instance with the handler re-attached", async () => {
    const before = await getQueue();
    await stopQueue();
    const after = await getQueue();
    expect(after).not.toBe(before);
    expect(after.listenerCount("error")).toBeGreaterThanOrEqual(1);
  }, 20_000);
});