UNPKG

node-resque

Version:

an opinionated implementation of resque in node

361 lines (312 loc) 10.3 kB
import { ParsedFailedJobPayload, Job, Queue, Worker, Plugin } from "../../src"; import specHelper from "../utils/specHelper"; class MyPlugin extends Plugin { async beforeEnqueue() { return true; } async afterEnqueue() { return true; } async beforePerform() { return true; } async afterPerform() { this.options.afterPerform(this); return true; } } const jobs: { [key: string]: Job<any> } = { add: { perform: (a, b) => { return a + b; }, } as Job<any>, //@ts-ignore badAdd: { perform: () => { throw new Error("Blue Smoke"); }, } as Job<any>, messWithData: { perform: (a) => { a.data = "new thing"; return a; }, } as Job<any>, async: { perform: async () => { await new Promise((resolve) => { setTimeout(resolve, 100); }); return "yay"; }, } as Job<any>, twoSeconds: { perform: async () => { await new Promise((resolve) => { setTimeout(resolve, 1000 * 2); }); return "slow"; }, } as Job<any>, //@ts-ignore quickDefine: async () => { return "ok"; }, }; let worker: Worker; let queue: Queue; describe("worker", () => { afterAll(async () => { await specHelper.disconnect(); }); test("can connect", async () => { const worker = new Worker( { connection: specHelper.connectionDetails, queues: [specHelper.queue] }, {}, ); await worker.connect(); await worker.end(); }); describe("performInline", () => { beforeAll(() => { worker = new Worker( { connection: specHelper.connectionDetails, timeout: specHelper.timeout, queues: [specHelper.queue], }, jobs, ); }); test("can run a successful job", async () => { const result = await worker.performInline("add", [1, 2]); expect(result).toBe(3); }); test("can run a successful async job", async () => { const result = await worker.performInline("async"); expect(result).toBe("yay"); }); test("can run a failing job", async () => { try { await worker.performInline("badAdd", [1, 2]); throw new Error("should not get here"); } catch (error) { expect(String(error)).toBe("Error: Blue Smoke"); } }); test("can call a Plugin's afterPerform given the job throws an error", async () => { let actual = null; let expected = new TypeError("John"); let failingJob = { plugins: [MyPlugin], pluginOptions: { MyPlugin: { afterPerform: (plugin: Plugin) => { actual = plugin.worker.error; delete plugin.worker.error; }, }, }, perform: (x: string) => { throw new TypeError(x); }, }; let worker = new Worker({}, { failingJob }); await worker.performInline("failingJob", ["John"]); expect(actual).toEqual(expected); }); }); describe("[with connection]", () => { beforeAll(async () => { await specHelper.connect(); queue = new Queue({ connection: specHelper.connectionDetails }, {}); await queue.connect(); }); afterAll(async () => { await specHelper.cleanup(); }); test("can boot and stop", async () => { worker = new Worker( { connection: specHelper.connectionDetails, timeout: specHelper.timeout, queues: [specHelper.queue], }, jobs, ); await worker.connect(); await worker.start(); await worker.end(); }); test("will determine the proper queue names", async () => { const worker = new Worker( { connection: specHelper.connectionDetails, timeout: specHelper.timeout, }, jobs, ); await worker.connect(); expect(worker.queues).toEqual([]); await queue.enqueue(specHelper.queue, "badAdd", [1, 2]); await worker.checkQueues(); expect(worker.queues).toEqual([specHelper.queue]); //@ts-ignore await queue.del(specHelper.queue); await worker.end(); }); describe("integration", () => { test("will notice new job queues when started with queues=*", async () => { await new Promise(async (resolve) => { const wildcardWorker = new Worker( { connection: specHelper.connectionDetails, timeout: specHelper.timeout, queues: ["*"], }, jobs, ); await wildcardWorker.connect(); await wildcardWorker.start(); setTimeout(async () => { await queue.enqueue("__newQueue", "add", [1, 2]); }, 501); wildcardWorker.on("success", async (q, job, result, duration) => { expect(q).toBe("__newQueue"); expect(job.class).toBe("add"); expect(result).toBe(3); expect(wildcardWorker.result).toBe(result); expect(duration).toBeGreaterThanOrEqual(0); wildcardWorker.removeAllListeners("success"); await wildcardWorker.end(); resolve(null); }); }); }); describe("with worker", () => { beforeEach(async () => { worker = new Worker( { connection: specHelper.connectionDetails, timeout: specHelper.timeout, queues: [specHelper.queue], }, jobs, ); await worker.connect(); }); afterEach(async () => { await worker.end(); }); test("will mark a job as failed", async () => { await new Promise(async (resolve) => { await queue.enqueue(specHelper.queue, "badAdd", [1, 2]); await worker.start(); worker.on("failure", (q, job, failire) => { expect(q).toBe(specHelper.queue); expect(job.class).toBe("badAdd"); expect(failire.message).toBe("Blue Smoke"); worker.removeAllListeners("failure"); resolve(null); }); }); }); test("can work a job and return successful things", async () => { await new Promise(async (resolve) => { await queue.enqueue(specHelper.queue, "add", [1, 2]); worker.start(); worker.on("success", (q, job, result, duration) => { expect(q).toBe(specHelper.queue); expect(job.class).toBe("add"); expect(result).toBe(3); expect(worker.result).toBe(result); expect(duration).toBeGreaterThanOrEqual(0); worker.removeAllListeners("success"); resolve(null); }); }); }); // TODO: Typescript seems to have trouble with frozen objects // test('job arguments are immutable', async (done) => { // await queue.enqueue(specHelper.queue, 'messWithData', { a: 'starting value' }) // worker.start() // worker.on('success', (q, job, result) => { // expect(result.a).toBe('starting value') // expect(worker.result).toBe(result) // worker.removeAllListeners('success') // done() // }) // }) test("can accept jobs that are simple functions", async () => { await new Promise(async (resolve) => { await queue.enqueue(specHelper.queue, "quickDefine"); worker.start(); worker.on("success", (q, job, result, duration) => { expect(result).toBe("ok"); expect(duration).toBeGreaterThanOrEqual(0); worker.removeAllListeners("success"); resolve(null); }); }); }); test("will not work jobs that are not defined", async () => { await new Promise(async (resolve) => { await queue.enqueue(specHelper.queue, "somethingFake"); worker.start(); worker.on("failure", (q, job, failure, duration) => { expect(q).toBe(specHelper.queue); expect(String(failure)).toBe( 'Error: No job defined for class "somethingFake"', ); expect(duration).toBeGreaterThanOrEqual(0); worker.removeAllListeners("failure"); resolve(null); }); }); }); test("will place failed jobs in the failed queue", async () => { let str = await specHelper.redis.rpop( specHelper.namespace + ":" + "failed", ); const data = JSON.parse(str) as ParsedFailedJobPayload; expect(data.queue).toBe(specHelper.queue); expect(data.exception).toBe("Error"); expect(data.error).toBe('No job defined for class "somethingFake"'); }); test("will ping with status even when working a slow job", async () => { await new Promise(async (resolve) => { const nowInSeconds = Math.round(new Date().getTime() / 1000); await worker.start(); await new Promise((resolve) => setTimeout(resolve, worker.options.timeout * 2), ); const pingKey = worker.connection.key( "worker", "ping", worker.name, ); const firstPayload = JSON.parse( await specHelper.redis.get(pingKey), ); expect(firstPayload.name).toEqual(worker.name); expect(firstPayload.time).toBeGreaterThanOrEqual(nowInSeconds); await queue.enqueue(specHelper.queue, "twoSeconds"); worker.on("success", (q, job, result) => { expect(result).toBe("slow"); worker.removeAllListeners("success"); resolve(null); }); const secondPayload = JSON.parse( await specHelper.redis.get(pingKey), ); expect(secondPayload.name).toEqual(worker.name); expect(secondPayload.time).toBeGreaterThanOrEqual( firstPayload.time, ); }); }); }); }); }); });