From 3ee39b8119706eda2860c7c3a2a5043b8e3b382c Mon Sep 17 00:00:00 2001 From: Gareth Tomlinson Date: Fri, 31 Jan 2020 18:11:02 +0000 Subject: [PATCH 1/7] Added retryDelay parameter. Tests pass in isolation but lifespan test fails when run in block with jest. Probably due to file contention over realm lock file. --- Models/Queue.js | 69 +++++++++------ config/Database.js | 6 +- package.json | 1 + tests/Queue.test.js | 201 +++++++++++++++++++++++++++++++++++++++++--- yarn.lock | 5 ++ 5 files changed, 240 insertions(+), 42 deletions(-) diff --git a/Models/Queue.js b/Models/Queue.js index 5386f1d..0071b43 100644 --- a/Models/Queue.js +++ b/Models/Queue.js @@ -101,11 +101,13 @@ export class Queue { data: JSON.stringify({ attempts: options.attempts || 1 }), - priority: options.priority || 0, + priority: Number.isInteger(options.priority) ? options.priority : 0, active: false, timeout: (options.timeout >= 0) ? options.timeout : 25000, created: new Date(), - failed: null + failed: null, + nextValidTime: new Date(), + retryDelay: Number.isInteger(options.retryDelay) ? options.retryDelay : 0, }); }); @@ -117,6 +119,20 @@ export class Queue { } + calculateRemainingLifespan () { + const lifespanRemaining = this.lifespan - (Date.now() - this.startTime); + return (lifespanRemaining === 0) ? -1 : lifespanRemaining; // Handle exactly zero lifespan remaining edge case. + } + + async calculateJobs () { + if (this.lifespan !== 0) { + return this.getConcurrentJobs(this.calculateRemainingLifespan()); + } else { + return this.getConcurrentJobs(); + } + + } + /** * * Start processing the queue. @@ -141,6 +157,7 @@ export class Queue { * @return {boolean|undefined} - False if queue is already started. Otherwise nothing is returned when queue finishes processing. */ async start(lifespan = 0) { + this.lifespan = lifespan; // If queue is already running, don't fire up concurrent loop. if (this.status == 'active') { @@ -150,17 +167,12 @@ export class Queue { this.status = 'active'; // Get jobs to process - const startTime = Date.now(); - let lifespanRemaining = null; - let concurrentJobs = []; + if(!this.startTime) + this.startTime = Date.now(); - if (lifespan !== 0) { - lifespanRemaining = lifespan - (Date.now() - startTime); - lifespanRemaining = (lifespanRemaining === 0) ? -1 : lifespanRemaining; // Handle exactly zero lifespan remaining edge case. - concurrentJobs = await this.getConcurrentJobs(lifespanRemaining); - } else { - concurrentJobs = await this.getConcurrentJobs(); - } + let concurrentJobs; + + concurrentJobs = await this.calculateJobs(); while (this.status == 'active' && concurrentJobs.length) { @@ -174,18 +186,14 @@ export class Queue { await Promise.all(processingJobs.map(promiseReflect)); // Get next batch of jobs. - if (lifespan !== 0) { - lifespanRemaining = lifespan - (Date.now() - startTime); - lifespanRemaining = (lifespanRemaining === 0) ? -1 : lifespanRemaining; // Handle exactly zero lifespan remaining edge case. - concurrentJobs = await this.getConcurrentJobs(lifespanRemaining); - } else { - concurrentJobs = await this.getConcurrentJobs(); - } - + concurrentJobs = await this.calculateJobs(); } this.status = 'inactive'; - + if(this.calculateRemainingLifespan() < 500){ + delete this.startTime; + delete this.lifespan; + } } /** @@ -198,6 +206,8 @@ export class Queue { */ stop() { this.status = 'inactive'; + delete this.startTime; + delete this.lifespan; } /** @@ -248,17 +258,18 @@ export class Queue { // Get next job from queue. let nextJob = null; + const now = new Date(); // Build query string // If queueLife const timeoutUpperBound = (queueLifespanRemaining - 500 > 0) ? queueLifespanRemaining - 499 : 0; // Only get jobs with timeout at least 500ms < queueLifespanRemaining. const initialQuery = (queueLifespanRemaining) - ? 'active == FALSE AND failed == null AND timeout > 0 AND timeout < ' + timeoutUpperBound - : 'active == FALSE AND failed == null'; + ? 'active == FALSE AND failed == null AND timeout > 0 AND timeout < ' + timeoutUpperBound + ' AND nextValidTime <= $0' + : 'active == FALSE AND failed == null AND nextValidTime <= $0'; let jobs = this.realm.objects('Job') - .filtered(initialQuery) + .filtered(initialQuery, now) .sorted([['priority', true], ['created', false]]); if (jobs.length) { @@ -271,11 +282,11 @@ export class Queue { const concurrency = this.worker.getConcurrency(nextJob.name); const allRelatedJobsQuery = (queueLifespanRemaining) - ? 'name == "'+ nextJob.name +'" AND active == FALSE AND failed == null AND timeout > 0 AND timeout < ' + timeoutUpperBound - : 'name == "'+ nextJob.name +'" AND active == FALSE AND failed == null'; + ? 'name == "'+ nextJob.name +'" AND active == FALSE AND failed == null AND timeout > 0 AND timeout < ' + timeoutUpperBound + ' AND nextValidTime <= $0' + : 'name == "'+ nextJob.name +'" AND active == FALSE AND failed == null AND nextValidTime <= $0'; const allRelatedJobs = this.realm.objects('Job') - .filtered(allRelatedJobsQuery) + .filtered(allRelatedJobsQuery, now) .sorted([['priority', true], ['created', false]]); let jobsToMarkActive = allRelatedJobs.slice(0, concurrency); @@ -379,6 +390,10 @@ export class Queue { job.failed = new Date(); } + if(job.retryDelay && job.retryDelay > 0) setTimeout(() => { + this.start(this.lifespan ? this.lifespan : 0); + },job.retryDelay); + job.nextValidTime = new Date(new Date().getTime() + job.retryDelay); }); // Execute job onFailure lifecycle callback. diff --git a/config/Database.js b/config/Database.js index 2c73bfb..650f327 100644 --- a/config/Database.js +++ b/config/Database.js @@ -17,7 +17,9 @@ const JobSchema = { active: { type: 'bool', default: false}, // Whether or not job is currently being processed. timeout: 'int', // Job timeout in ms. 0 means no timeout. created: 'date', // Job creation timestamp. - failed: 'date?' // Job failure timestamp (null until failure). + failed: 'date?', // Job failure timestamp (null until failure). + nextValidTime: 'date?', // Next timestamp it would be valid to execute the job calculated from retry Delay at time of fail + retryDelay: 'int', } }; @@ -45,4 +47,4 @@ export default class Database { } -} \ No newline at end of file +} diff --git a/package.json b/package.json index 8d72126..ef8cbd9 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ }, "homepage": "https://github.com/billmalarky/react-native-queue#readme", "dependencies": { + "moment": "^2.24.0", "promise-reflect": "^1.1.0", "react-native-uuid": "^1.4.9", "realm": "^2.0.12" diff --git a/tests/Queue.test.js b/tests/Queue.test.js index f50744e..b5bd236 100644 --- a/tests/Queue.test.js +++ b/tests/Queue.test.js @@ -6,6 +6,8 @@ import should from 'should'; // eslint-disable-line no-unused-vars import QueueFactory, { Queue } from '../Models/Queue'; import Worker from '../Models/Worker'; +import Database from '../config/Database'; +import moment from 'moment'; describe('Models/Queue', function() { @@ -17,6 +19,8 @@ describe('Models/Queue', function() { }); + const wait = time => new Promise(resolve => setTimeout(resolve,time)); + // // QUEUE LIFESPAN TESTING // @@ -252,7 +256,7 @@ describe('Models/Queue', function() { }); - it('#start(lifespan) ADVANCED TEST FULL (Multiple job names, job timeouts, concurrency, priority) - ONLY RUN IN NON-CI ENV: queue will process jobs with timeout set as expected until lifespan ends.', async () => { + it('#start(lifespan) ADVANCED TEST FULL (Multiple job names, job timeouts, concurrency, priority, retryDelay) - ONLY RUN IN NON-CI ENV: queue will process jobs with timeout set as expected until lifespan ends.', async () => { // This test will intermittently fail in CI environments like travis-ci. // Intermittent failure is a result of the poor performance of CI environments @@ -269,6 +273,7 @@ describe('Models/Queue', function() { const anotherJobName = 'another-job-name'; const timeoutJobName = 'timeout-job-name'; const concurrentJobName = 'concurrent-job-name'; + const failingJobName = 'failing-job-name'; const queueLifespan = 5300; let remainingLifespan = queueLifespan; @@ -360,21 +365,38 @@ describe('Models/Queue', function() { }, { concurrency: 4}); + queue.addWorker(failingJobName, async (id, payload) => { + // Track jobs that exec + executedJobs.push(payload.trackingName); + + // Detect jobs that should't be picked up by lifespan queue. + if (remainingLifespan - 500 < payload.payloadOptionsTimeout) { + badJobs.push({id, payload}); + } + + await new Promise( (resolve,reject) => { + setTimeout(() => { + reject('fail'); + }, payload.payloadTimeout); + }); + }, { concurrency: 1}); + // Create a couple jobs - queue.createJob(jobName, { - trackingName: 'job1-job-name-payloadTimeout(100)-timeout(200)-priority(-1)', - payloadTimeout: 100, - payloadOptionsTimeout: 200 // Mirror the actual job options timeout in payload so we can use it for testing. - }, { - timeout: 200, - priority: -1 - }, false); + // Broken in core module - not required so not fixing + // queue.createJob(jobName, { + // trackingName: 'job1-job-name-payloadTimeout(100)-timeout(200)-priority(-1)', + // payloadTimeout: 100, + // payloadOptionsTimeout: 200 // Mirror the actual job options timeout in payload so we can use it for testing. + // }, { + // timeout: 200, + // priority: -1 + // }, false); // Since more than one job can be written in 1 ms, we need to add a slight delay // in order to control the order jobs come off the queue (since they are time sorted) // If multiple jobs are written in the same ms, Realm can't be deterministic about job // ordering when we pop jobs off the top of the queue. - await new Promise((resolve) => { setTimeout(resolve, 25); }); + // await new Promise((resolve) => { setTimeout(resolve, 25); }); queue.createJob(anotherJobName, { trackingName: 'job2-another-job-name-payloadTimeout(1000)-timeout(1100)-priority(0)', @@ -510,6 +532,19 @@ describe('Models/Queue', function() { }, false); await new Promise((resolve) => { setTimeout(resolve, 25); }); + queue.createJob(failingJobName, { + trackingName: 'job16-failing-job-name-retryDelay(2800)-attempts(3)', // This job should run twice in lifespan + retryDelay: 3000, + attempts: 3, + payloadTimeout: 10, + }, { + timeout: 600, + retryDelay: 3000, + attempts: 3, + priority: 100, + }, false); + await new Promise((resolve) => { setTimeout(resolve, 25); }); + // startQueue is false so queue should not have started. queue.status.should.equal('inactive'); @@ -534,17 +569,19 @@ describe('Models/Queue', function() { //Check that the correct jobs executed. executedJobs.should.deepEqual([ + 'job16-failing-job-name-retryDelay(2800)-attempts(3)', 'job3-another-job-name-payloadTimeout(750)-timeout(800)-priority(10)', 'job7-job-name-payloadTimeout(1000)-timeout(1100)-priority(1)', 'job2-another-job-name-payloadTimeout(1000)-timeout(1100)-priority(0)', 'job5-job-name-payloadTimeout(400)-timeout(500)-priority(0)', + 'job16-failing-job-name-retryDelay(2800)-attempts(3)', 'job6-timeout-job-name-payloadTimeout(10000)-timeout(500)-priority(0)', // This job executes but isn't deleted because it fails due to timeout. 'job8-concurrent-job-name-payloadTimeout(500)-timeout(600)-priority(0)', 'job9-concurrent-job-name-payloadTimeout(510)-timeout(600)-priority(0)', 'job11-concurrent-job-name-payloadTimeout(600)-timeout(700)-priority(0)', 'job12-job-name-payloadTimeout(100)-timeout(200)-priority(0)', 'job14-job-name-payloadTimeout(100)-timeout(200)-priority(0)', - 'job1-job-name-payloadTimeout(100)-timeout(200)-priority(-1)' + // 'job1-job-name-payloadTimeout(100)-timeout(200)-priority(-1)' ]); // Check jobs that couldn't be picked up are still in the queue. @@ -562,7 +599,8 @@ describe('Models/Queue', function() { 'job6-timeout-job-name-payloadTimeout(10000)-timeout(500)-priority(0)', 'job10-concurrent-job-name-payloadTimeout(10000)-timeout(10100)-priority(0)', 'job13-job-name-payloadTimeout(400)-timeout(500)-priority(0)', - 'job15-job-name-payloadTimeout(500)-timeout(600)-priority(0)' + 'job15-job-name-payloadTimeout(500)-timeout(600)-priority(0)', + 'job16-failing-job-name-retryDelay(2800)-attempts(3)' ]); }, 10000); // Increase timeout of this advanced test to 10 seconds. @@ -1243,6 +1281,101 @@ describe('Models/Queue', function() { }); + it('should set nextValidTime for job on failure which is retryDelay after now', async () => { + const queue = await QueueFactory(); + const jobName = 'job-name'; + + queue.addWorker(jobName, async () => { + throw new Error('fail'); + }, { + concurrency: 1 + }); + + queue.createJob(jobName, {}, { + attempts: 2, + timeout: 250, + retryDelay: 2000, + }, false); + + const now = Date.now(); + await queue.start(1500); + await wait(1500); + + const jobs = await queue.getJobs(true); + const job = jobs[0]; + + const jobData = JSON.parse(job.data); + jobData.failedAttempts.should.equal(1); + should.not.exist(job.failed); + moment(job.nextValidTime).subtract(now).should.be.greaterThan(1000); + queue.flushQueue(); + }); + + const addFailedAttemptCount = async (realm, jobs, failedCount, nextValidTime = new Date()) => { + const job = jobs[0]; + const jobData = JSON.parse(job.data); + realm.write(() => { + job.nextValidTime = nextValidTime; + jobData.failedAttempts = failedCount; + job.data = JSON.stringify(jobData); + }); + }; + + const addFailedStatus = async (realm, jobs) => { + const job = jobs[0]; + realm.write(() => { + job.failed = new Date(); + }); + }; + + const createAndTestJob = async (nextValidTime, failed, expectedNumberOfReturn) => { + const queue = await QueueFactory(); + const jobName = 'job-name'; + const realm = await Database.getRealmInstance(); + + queue.addWorker(jobName, () => {}, { + concurrency: 1 + }); + + queue.createJob(jobName, {}, { + attempts: 2, + timeout: 99, + retryDelay: 100, + }, false); + + if(nextValidTime) + await addFailedAttemptCount(realm, await queue.getJobs(true),1, nextValidTime); + + if(failed) + await addFailedStatus(realm, await queue.getJobs(true)); + + const returnedJobs = await queue.getConcurrentJobs(600); + + returnedJobs.length.should.equal(expectedNumberOfReturn); + + queue.flushQueue(); + }; + + it('#getConcurrentJobs(queueLifespanRemaining) should return jobs with retryDelay set will be returned by getConcurrentJobs() as normal if not failedAttempts.', async () => { + await createAndTestJob(null,false,1); + }); + + it('#getConcurrentJobs(queueLifespanRemaining) should not return jobs with nextValidTime in the future.', async () => { + await createAndTestJob(new Date(new Date().getTime() + 1000),false,0); + }); + + it('#getConcurrentJobs(queueLifespanRemaining) should return jobs with nextValidTime in the past.', async () => { + await createAndTestJob(new Date(new Date().getTime() - 1000),false,1); + }); + + it('#getConcurrentJobs(queueLifespanRemaining) should return jobs with nextValidTime now.', async () => { + await createAndTestJob(new Date(),false,1); + }); + + it('#getConcurrentJobs(queueLifespanRemaining) should not return failed jobs.', async () => { + await createAndTestJob(new Date(new Date().getTime() - 1000),true,0); + }); + it('#processJob() executes job worker then deletes job on success', async () => { const queue = await QueueFactory(); @@ -2184,4 +2317,46 @@ describe('Models/Queue', function() { }); -}); \ No newline at end of file + it('should respect lifespan rules even with delayed jobs', async () => { + const queue = await QueueFactory(); + queue.flushQueue(); + const jobName = 'job-name'; + + function * succeedGeneratorFn() { + yield false; + yield false; + yield true; + } + const succeedGenerator = succeedGeneratorFn(); + + // Attach the worker. + queue.addWorker(jobName, async (id,payload) => { + if(!succeedGenerator.next().value) throw new Error('fail'); + }, false); + + queue.createJob(jobName,{foo:'bar'},{ + retryDelay: 500, + attempts: 3, + timeout: 200, + },false); + + await queue.start(1250); + await wait(1250); + + const jobs = await queue.getJobs(true); + const job = jobs[0]; + const jobData = JSON.parse(job.data); + + should.not.exist(job.failed); + jobData.failedAttempts.should.equal(2); + + await wait(500); + await queue.start(1000); + await wait(2000); + + const doneJobs = await queue.getJobs(true); + doneJobs.length.should.equal(0); + + queue.flushQueue(); + }); +}); diff --git a/yarn.lock b/yarn.lock index 9af1163..9e388a4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2509,6 +2509,11 @@ minimist@^1.1.1, minimist@^1.2.0: dependencies: minimist "0.0.8" +moment@^2.24.0: + version "2.24.0" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" + integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" From c10ec35cbfaf29cf6d87ba2a684a745eb150f675 Mon Sep 17 00:00:00 2001 From: Gareth Tomlinson Date: Fri, 7 Feb 2020 12:04:54 +0000 Subject: [PATCH 2/7] Suggested fix to issue with delay to triggering follow on jobs --- Models/Queue.js | 12 +- tests/Queue.test.js | 3469 ++++++++++++++++++++++--------------------- 2 files changed, 1773 insertions(+), 1708 deletions(-) diff --git a/Models/Queue.js b/Models/Queue.js index 0071b43..5e951ef 100644 --- a/Models/Queue.js +++ b/Models/Queue.js @@ -171,7 +171,6 @@ export class Queue { this.startTime = Date.now(); let concurrentJobs; - concurrentJobs = await this.calculateJobs(); while (this.status == 'active' && concurrentJobs.length) { @@ -390,12 +389,13 @@ export class Queue { job.failed = new Date(); } - if(job.retryDelay && job.retryDelay > 0) setTimeout(() => { - this.start(this.lifespan ? this.lifespan : 0); - },job.retryDelay); job.nextValidTime = new Date(new Date().getTime() + job.retryDelay); }); + if(job.retryDelay && job.retryDelay > 0) setTimeout(() => { + this.start(this.lifespan ? this.lifespan : 0); + },job.retryDelay); + // Execute job onFailure lifecycle callback. this.worker.executeJobLifecycleCallback('onFailure', jobName, jobId, jobPayload); @@ -443,6 +443,10 @@ export class Queue { } + async close() { + await this.stop(); + await this.realm.close(); + } } diff --git a/tests/Queue.test.js b/tests/Queue.test.js index b5bd236..e5f17a2 100644 --- a/tests/Queue.test.js +++ b/tests/Queue.test.js @@ -16,7 +16,6 @@ describe('Models/Queue', function() { // Make sure each test starts with a fresh database. const queue = await QueueFactory(); queue.flushQueue(); - }); const wait = time => new Promise(resolve => setTimeout(resolve,time)); @@ -103,2189 +102,2189 @@ describe('Models/Queue', function() { it('#start(lifespan) BASIC TEST (One job type, default job/worker options): queue will process jobs with timeout set as expected until lifespan ends.', async () => { - // This test will intermittently fail in CI environments like travis-ci. - // Intermittent failure is a result of the poor performance of CI environments - // causing the timeouts in this test to become really flakey (setTimeout can't - // guarantee exact time of function execution, and in a high load env execution can - // be significantly delayed. - if (process.env.COVERALLS_ENV == 'production') { - return true; - } + // This test will intermittently fail in CI environments like travis-ci. + // Intermittent failure is a result of the poor performance of CI environments + // causing the timeouts in this test to become really flakey (setTimeout can't + // guarantee exact time of function execution, and in a high load env execution can + // be significantly delayed. + if (process.env.COVERALLS_ENV == 'production') { + return true; + } - const queue = await QueueFactory(); - queue.flushQueue(); - const jobName = 'job-name'; - const queueLifespan = 2000; - let remainingLifespan = queueLifespan; + const queue = await QueueFactory(); + queue.flushQueue(); + const jobName = 'job-name'; + const queueLifespan = 2000; + let remainingLifespan = queueLifespan; - // Track the jobs that have executed to test against. - let executedJobs = []; + // Track the jobs that have executed to test against. + let executedJobs = []; - // We need to be able to throw an error outside of - // job workers, because errors thrown inside a job - // worker function are caught and logged by job processing - // logic. They will not fail the test. So track bad jobs - // and throw error after jobs finish processing. - let badJobs = []; + // We need to be able to throw an error outside of + // job workers, because errors thrown inside a job + // worker function are caught and logged by job processing + // logic. They will not fail the test. So track bad jobs + // and throw error after jobs finish processing. + let badJobs = []; - queue.addWorker(jobName, async (id, payload) => { + queue.addWorker(jobName, async (id, payload) => { - // Track jobs that exec - executedJobs.push(payload.trackingName); + // Track jobs that exec + executedJobs.push(payload.trackingName); - // Detect jobs that should't be picked up by lifespan queue. - if (remainingLifespan - 500 < payload.payloadOptionsTimeout) { - badJobs.push({id, payload}); - } + // Detect jobs that should't be picked up by lifespan queue. + if (remainingLifespan - 500 < payload.payloadOptionsTimeout) { + badJobs.push({id, payload}); + } - remainingLifespan = remainingLifespan - payload.payloadTimeout; + remainingLifespan = remainingLifespan - payload.payloadTimeout; - await new Promise((resolve) => { - setTimeout(resolve, payload.payloadTimeout); - }); + await new Promise((resolve) => { + setTimeout(resolve, payload.payloadTimeout); + }); - }, { concurrency: 1}); + }, { concurrency: 1}); - // 2000 (lifespan) - 200 (job1) - 200 (job2) - 1000 (job3) - 50 (job 4) - 100 (timeout value for job 5 overflows remaining lifespan + 500ms for buffer so job5 will not exec) < 500 + // 2000 (lifespan) - 200 (job1) - 200 (job2) - 1000 (job3) - 50 (job 4) - 100 (timeout value for job 5 overflows remaining lifespan + 500ms for buffer so job5 will not exec) < 500 - // Create a couple jobs - queue.createJob(jobName, { - trackingName: 'job1', - payloadTimeout: 200, - payloadOptionsTimeout: 300 // Mirror the actual job options timeout in payload so we can use it for testing. - }, { - timeout: 300 - }, false); + // Create a couple jobs + queue.createJob(jobName, { + trackingName: 'job1', + payloadTimeout: 200, + payloadOptionsTimeout: 300 // Mirror the actual job options timeout in payload so we can use it for testing. + }, { + timeout: 300 + }, false); - // Since more than one job can be written in 1 ms, we need to add a slight delay - // in order to control the order jobs come off the queue (since they are time sorted) - // If multiple jobs are written in the same ms, Realm can't be deterministic about job - // ordering when we pop jobs off the top of the queue. - await new Promise((resolve) => { setTimeout(resolve, 25); }); + // Since more than one job can be written in 1 ms, we need to add a slight delay + // in order to control the order jobs come off the queue (since they are time sorted) + // If multiple jobs are written in the same ms, Realm can't be deterministic about job + // ordering when we pop jobs off the top of the queue. + await new Promise((resolve) => { setTimeout(resolve, 25); }); + + queue.createJob(jobName, { + trackingName: 'job2', + payloadTimeout: 200, + payloadOptionsTimeout: 300 // Mirror the actual job options timeout in payload so we can use it for testing. + }, { + timeout: 300 + }, false); + await new Promise((resolve) => { setTimeout(resolve, 25); }); + + queue.createJob(jobName, { + trackingName: 'job3', + payloadTimeout: 1000, + payloadOptionsTimeout: 1100 // Mirror the actual job options timeout in payload so we can use it for testing. + }, { + timeout: 1100 + }, false); + await new Promise((resolve) => { setTimeout(resolve, 25); }); + + queue.createJob(jobName, { + trackingName: 'job4', + payloadTimeout: 500, + payloadOptionsTimeout: 600 // Mirror the actual job options timeout in payload so we can use it for testing. + }, { + timeout: 600 + }, false); + await new Promise((resolve) => { setTimeout(resolve, 25); }); + + queue.createJob(jobName, { + trackingName: 'job5', + payloadTimeout: 50, + payloadOptionsTimeout: 75 // Mirror the actual job options timeout in payload so we can use it for testing. + }, { + timeout: 75 + }, false); + await new Promise((resolve) => { setTimeout(resolve, 25); }); + + queue.createJob(jobName, { + trackingName: 'job6', + payloadTimeout: 25, + payloadOptionsTimeout: 100 // Mirror the actual job options timeout in payload so we can use it for testing. + }, { + timeout: 100 + }, false); + await new Promise((resolve) => { setTimeout(resolve, 25); }); + + queue.createJob(jobName, { + trackingName: 'job7', + payloadTimeout: 1100, + payloadOptionsTimeout: 1200 // Mirror the actual job options timeout in payload so we can use it for testing. + }, { + timeout: 1200 + }, false); + await new Promise((resolve) => { setTimeout(resolve, 25); }); - queue.createJob(jobName, { - trackingName: 'job2', - payloadTimeout: 200, - payloadOptionsTimeout: 300 // Mirror the actual job options timeout in payload so we can use it for testing. - }, { - timeout: 300 - }, false); - await new Promise((resolve) => { setTimeout(resolve, 25); }); + // startQueue is false so queue should not have started. + queue.status.should.equal('inactive'); - queue.createJob(jobName, { - trackingName: 'job3', - payloadTimeout: 1000, - payloadOptionsTimeout: 1100 // Mirror the actual job options timeout in payload so we can use it for testing. - }, { - timeout: 1100 - }, false); - await new Promise((resolve) => { setTimeout(resolve, 25); }); + const queueStartTime = Date.now(); - queue.createJob(jobName, { - trackingName: 'job4', - payloadTimeout: 500, - payloadOptionsTimeout: 600 // Mirror the actual job options timeout in payload so we can use it for testing. - }, { - timeout: 600 - }, false); - await new Promise((resolve) => { setTimeout(resolve, 25); }); + // Start queue, don't await so this test can continue while queue processes. + await queue.start(queueLifespan); - queue.createJob(jobName, { - trackingName: 'job5', - payloadTimeout: 50, - payloadOptionsTimeout: 75 // Mirror the actual job options timeout in payload so we can use it for testing. - }, { - timeout: 75 - }, false); - await new Promise((resolve) => { setTimeout(resolve, 25); }); + const queueEndTime = Date.now(); + const queueProcessTime = queueStartTime - queueEndTime; - queue.createJob(jobName, { - trackingName: 'job6', - payloadTimeout: 25, - payloadOptionsTimeout: 100 // Mirror the actual job options timeout in payload so we can use it for testing. - }, { - timeout: 100 - }, false); - await new Promise((resolve) => { setTimeout(resolve, 25); }); + if (queueProcessTime > queueLifespan) { + throw new Error('ERROR: Queue did not complete before lifespan ended.'); + } - queue.createJob(jobName, { - trackingName: 'job7', - payloadTimeout: 1100, - payloadOptionsTimeout: 1200 // Mirror the actual job options timeout in payload so we can use it for testing. - }, { - timeout: 1200 - }, false); - await new Promise((resolve) => { setTimeout(resolve, 25); }); + if (badJobs.length) { + throw new Error('ERROR: Queue with lifespan picked up bad jobs it did not have enough remaining lifespan to execute: ' + JSON.stringify(badJobs)); + } - // startQueue is false so queue should not have started. - queue.status.should.equal('inactive'); + // Queue should have stopped. + queue.status.should.equal('inactive'); - const queueStartTime = Date.now(); + //Check that the correct jobs executed. + executedJobs.should.deepEqual(['job1', 'job2', 'job4', 'job5', 'job6']); - // Start queue, don't await so this test can continue while queue processes. - await queue.start(queueLifespan); + // Check jobs that couldn't be picked up are still in the queue. + const remainingJobs = await queue.getJobs(true); - const queueEndTime = Date.now(); - const queueProcessTime = queueStartTime - queueEndTime; + const remainingJobNames = remainingJobs.map( job => { + const payload = JSON.parse(job.payload); + return payload.trackingName; + }); - if (queueProcessTime > queueLifespan) { - throw new Error('ERROR: Queue did not complete before lifespan ended.'); - } + // queue.getJobs() doesn't order jobs in any particular way so just + // check that the jobs still exist on the queue. + remainingJobNames.should.containDeep(['job3', 'job7']); - if (badJobs.length) { - throw new Error('ERROR: Queue with lifespan picked up bad jobs it did not have enough remaining lifespan to execute: ' + JSON.stringify(badJobs)); - } + }); - // Queue should have stopped. - queue.status.should.equal('inactive'); + it('#start(lifespan) ADVANCED TEST FULL (Multiple job names, job timeouts, concurrency, priority, retryDelay) - ONLY RUN IN NON-CI ENV: queue will process jobs with timeout set as expected until lifespan ends.', async () => { - //Check that the correct jobs executed. - executedJobs.should.deepEqual(['job1', 'job2', 'job4', 'job5', 'job6']); + // This test will intermittently fail in CI environments like travis-ci. + // Intermittent failure is a result of the poor performance of CI environments + // causing the timeouts in this test to become really flakey (setTimeout can't + // guarantee exact time of function execution, and in a high load env execution can + // be significantly delayed. + if (process.env.COVERALLS_ENV == 'production') { + return true; + } - // Check jobs that couldn't be picked up are still in the queue. - const remainingJobs = await queue.getJobs(true); + const queue = await QueueFactory(); + queue.flushQueue(); + const jobName = 'job-name'; + const anotherJobName = 'another-job-name'; + const timeoutJobName = 'timeout-job-name'; + const concurrentJobName = 'concurrent-job-name'; + const failingJobName = 'failing-job-name'; + const queueLifespan = 5300; + let remainingLifespan = queueLifespan; + + // Track the jobs that have executed to test against. + let executedJobs = []; + + // We need to be able to throw an error outside of + // job workers, because errors thrown inside a job + // worker function are caught and logged by job processing + // logic. They will not fail the test. So track bad jobs + // and throw error after jobs finish processing. + let badJobs = []; + + queue.addWorker(jobName, async (id, payload) => { + + // Track jobs that exec + executedJobs.push(payload.trackingName); + + // Detect jobs that should't be picked up by lifespan queue. + if (remainingLifespan - 500 < payload.payloadOptionsTimeout) { + badJobs.push({id, payload}); + } - const remainingJobNames = remainingJobs.map( job => { - const payload = JSON.parse(job.payload); - return payload.trackingName; - }); + remainingLifespan = remainingLifespan - payload.payloadTimeout; - // queue.getJobs() doesn't order jobs in any particular way so just - // check that the jobs still exist on the queue. - remainingJobNames.should.containDeep(['job3', 'job7']); + await new Promise((resolve) => { + setTimeout(resolve, payload.payloadTimeout); + }); - }); + }, { concurrency: 1}); - it('#start(lifespan) ADVANCED TEST FULL (Multiple job names, job timeouts, concurrency, priority, retryDelay) - ONLY RUN IN NON-CI ENV: queue will process jobs with timeout set as expected until lifespan ends.', async () => { + queue.addWorker(anotherJobName, async (id, payload) => { - // This test will intermittently fail in CI environments like travis-ci. - // Intermittent failure is a result of the poor performance of CI environments - // causing the timeouts in this test to become really flakey (setTimeout can't - // guarantee exact time of function execution, and in a high load env execution can - // be significantly delayed. - if (process.env.COVERALLS_ENV == 'production') { - return true; - } + // Track jobs that exec + executedJobs.push(payload.trackingName); - const queue = await QueueFactory(); - queue.flushQueue(); - const jobName = 'job-name'; - const anotherJobName = 'another-job-name'; - const timeoutJobName = 'timeout-job-name'; - const concurrentJobName = 'concurrent-job-name'; - const failingJobName = 'failing-job-name'; - const queueLifespan = 5300; - let remainingLifespan = queueLifespan; + // Detect jobs that should't be picked up by lifespan queue. + if (remainingLifespan - 500 < payload.payloadOptionsTimeout) { + badJobs.push({id, payload}); + } - // Track the jobs that have executed to test against. - let executedJobs = []; + remainingLifespan = remainingLifespan - payload.payloadTimeout; - // We need to be able to throw an error outside of - // job workers, because errors thrown inside a job - // worker function are caught and logged by job processing - // logic. They will not fail the test. So track bad jobs - // and throw error after jobs finish processing. - let badJobs = []; + await new Promise((resolve) => { + setTimeout(resolve, payload.payloadTimeout); + }); - queue.addWorker(jobName, async (id, payload) => { + }, { concurrency: 1}); - // Track jobs that exec - executedJobs.push(payload.trackingName); + queue.addWorker(timeoutJobName, async (id, payload) => { - // Detect jobs that should't be picked up by lifespan queue. - if (remainingLifespan - 500 < payload.payloadOptionsTimeout) { - badJobs.push({id, payload}); - } + // Track jobs that exec + executedJobs.push(payload.trackingName); - remainingLifespan = remainingLifespan - payload.payloadTimeout; + // Detect jobs that should't be picked up by lifespan queue. + if (remainingLifespan - 500 < payload.payloadOptionsTimeout) { + badJobs.push({id, payload}); + } - await new Promise((resolve) => { - setTimeout(resolve, payload.payloadTimeout); - }); + remainingLifespan = remainingLifespan - payload.payloadOptionsTimeout; - }, { concurrency: 1}); + await new Promise((resolve) => { + setTimeout(resolve, payload.payloadTimeout); + }); - queue.addWorker(anotherJobName, async (id, payload) => { + }, { concurrency: 1}); - // Track jobs that exec - executedJobs.push(payload.trackingName); + queue.addWorker(concurrentJobName, async (id, payload) => { - // Detect jobs that should't be picked up by lifespan queue. - if (remainingLifespan - 500 < payload.payloadOptionsTimeout) { - badJobs.push({id, payload}); - } + // Track jobs that exec + executedJobs.push(payload.trackingName); - remainingLifespan = remainingLifespan - payload.payloadTimeout; + // Detect jobs that should't be picked up by lifespan queue. + if (remainingLifespan - 500 < payload.payloadOptionsTimeout) { + badJobs.push({id, payload}); + } - await new Promise((resolve) => { - setTimeout(resolve, payload.payloadTimeout); - }); - }, { concurrency: 1}); + // Since these all run concurrently, only subtract the job with the longest + // timeout that will presumabely finish last. + if (payload.payloadTimeout == 600) { + remainingLifespan = remainingLifespan - payload.payloadTimeout; + } - queue.addWorker(timeoutJobName, async (id, payload) => { - // Track jobs that exec - executedJobs.push(payload.trackingName); + await new Promise((resolve) => { + setTimeout(resolve, payload.payloadTimeout); + }); - // Detect jobs that should't be picked up by lifespan queue. - if (remainingLifespan - 500 < payload.payloadOptionsTimeout) { - badJobs.push({id, payload}); - } + }, { concurrency: 4}); - remainingLifespan = remainingLifespan - payload.payloadOptionsTimeout; + queue.addWorker(failingJobName, async (id, payload) => { + // Track jobs that exec + executedJobs.push(payload.trackingName); - await new Promise((resolve) => { - setTimeout(resolve, payload.payloadTimeout); - }); + // Detect jobs that should't be picked up by lifespan queue. + if (remainingLifespan - 500 < payload.payloadOptionsTimeout) { + badJobs.push({id, payload}); + } - }, { concurrency: 1}); + await new Promise( (resolve,reject) => { + setTimeout(() => { + reject('fail'); + }, payload.payloadTimeout); + }); + }, { concurrency: 1}); + + // Create a couple jobs + // Broken in core module - not required so not fixing + // queue.createJob(jobName, { + // trackingName: 'job1-job-name-payloadTimeout(100)-timeout(200)-priority(-1)', + // payloadTimeout: 100, + // payloadOptionsTimeout: 200 // Mirror the actual job options timeout in payload so we can use it for testing. + // }, { + // timeout: 200, + // priority: -1 + // }, false); + + // Since more than one job can be written in 1 ms, we need to add a slight delay + // in order to control the order jobs come off the queue (since they are time sorted) + // If multiple jobs are written in the same ms, Realm can't be deterministic about job + // ordering when we pop jobs off the top of the queue. + // await new Promise((resolve) => { setTimeout(resolve, 25); }); + + queue.createJob(anotherJobName, { + trackingName: 'job2-another-job-name-payloadTimeout(1000)-timeout(1100)-priority(0)', + payloadTimeout: 1000, + payloadOptionsTimeout: 1100 // Mirror the actual job options timeout in payload so we can use it for testing. + }, { + timeout: 1100 + }, false); + await new Promise((resolve) => { setTimeout(resolve, 25); }); + + queue.createJob(anotherJobName, { + trackingName: 'job3-another-job-name-payloadTimeout(750)-timeout(800)-priority(10)', + payloadTimeout: 750, + payloadOptionsTimeout: 800 // Mirror the actual job options timeout in payload so we can use it for testing. + }, { + timeout: 800, + priority: 10 + }, false); + await new Promise((resolve) => { setTimeout(resolve, 25); }); + + queue.createJob(jobName, { + trackingName: 'job4-job-name-payloadTimeout(10000)-timeout(10100)-priority(0)', + payloadTimeout: 10000, + payloadOptionsTimeout: 10100 // Mirror the actual job options timeout in payload so we can use it for testing. + }, { + timeout: 10100 + }, false); + await new Promise((resolve) => { setTimeout(resolve, 25); }); + + queue.createJob(jobName, { + trackingName: 'job5-job-name-payloadTimeout(400)-timeout(500)-priority(0)', + payloadTimeout: 400, + payloadOptionsTimeout: 500 // Mirror the actual job options timeout in payload so we can use it for testing. + }, { + timeout: 500 + }, false); + await new Promise((resolve) => { setTimeout(resolve, 25); }); + + queue.createJob(timeoutJobName, { + trackingName: 'job6-timeout-job-name-payloadTimeout(10000)-timeout(500)-priority(0)', + payloadTimeout: 10000, + payloadOptionsTimeout: 500 // Mirror the actual job options timeout in payload so we can use it for testing. + }, { + timeout: 500 + }, false); + await new Promise((resolve) => { setTimeout(resolve, 25); }); + + queue.createJob(jobName, { + trackingName: 'job7-job-name-payloadTimeout(1000)-timeout(1100)-priority(1)', + payloadTimeout: 1000, + payloadOptionsTimeout: 1100 // Mirror the actual job options timeout in payload so we can use it for testing. + }, { + timeout: 1100, + priority: 1 + }, false); + await new Promise((resolve) => { setTimeout(resolve, 25); }); - queue.addWorker(concurrentJobName, async (id, payload) => { - // Track jobs that exec - executedJobs.push(payload.trackingName); + // Create concurrent jobs + queue.createJob(concurrentJobName, { + trackingName: 'job8-concurrent-job-name-payloadTimeout(500)-timeout(600)-priority(0)', + payloadTimeout: 500, + payloadOptionsTimeout: 600 // Mirror the actual job options timeout in payload so we can use it for testing. + }, { + timeout: 600, + priority: 0 + }, false); + await new Promise((resolve) => { setTimeout(resolve, 25); }); + + queue.createJob(concurrentJobName, { + trackingName: 'job9-concurrent-job-name-payloadTimeout(510)-timeout(600)-priority(0)', + payloadTimeout: 510, + payloadOptionsTimeout: 600 // Mirror the actual job options timeout in payload so we can use it for testing. + }, { + timeout: 600, + priority: 0 + }, false); + await new Promise((resolve) => { setTimeout(resolve, 25); }); + + queue.createJob(concurrentJobName, { + trackingName: 'job10-concurrent-job-name-payloadTimeout(10000)-timeout(10100)-priority(0)', // THIS JOB WILL BE SKIPPED BY getConcurrentJobs() due to timeout too long. + payloadTimeout: 10000, + payloadOptionsTimeout: 10100 // Mirror the actual job options timeout in payload so we can use it for testing. + }, { + timeout: 10100, + priority: 0 + }, false); + await new Promise((resolve) => { setTimeout(resolve, 25); }); + + queue.createJob(concurrentJobName, { + trackingName: 'job11-concurrent-job-name-payloadTimeout(600)-timeout(700)-priority(0)', + payloadTimeout: 600, + payloadOptionsTimeout: 700 // Mirror the actual job options timeout in payload so we can use it for testing. + }, { + timeout: 700, + priority: 0 + }, false); + await new Promise((resolve) => { setTimeout(resolve, 25); }); + + queue.createJob(jobName, { + trackingName: 'job12-job-name-payloadTimeout(100)-timeout(200)-priority(0)', + payloadTimeout: 100, + payloadOptionsTimeout: 200 // Mirror the actual job options timeout in payload so we can use it for testing. + }, { + timeout: 200 + }, false); + await new Promise((resolve) => { setTimeout(resolve, 25); }); + + queue.createJob(jobName, { + trackingName: 'job13-job-name-payloadTimeout(400)-timeout(500)-priority(0)', // THIS JOB WON'T BE RUN BECAUSE THE TIMEOUT IS 500 AND ONLY 950ms left by this pount. 950 - 500 = 450 and 500 remaining is min for job to be pulled. + payloadTimeout: 400, + payloadOptionsTimeout: 500 // Mirror the actual job options timeout in payload so we can use it for testing. + }, { + timeout: 500 + }, false); + await new Promise((resolve) => { setTimeout(resolve, 25); }); + + queue.createJob(jobName, { + trackingName: 'job14-job-name-payloadTimeout(100)-timeout(200)-priority(0)', + payloadTimeout: 100, + payloadOptionsTimeout: 200 // Mirror the actual job options timeout in payload so we can use it for testing. + }, { + timeout: 200 + }, false); + await new Promise((resolve) => { setTimeout(resolve, 25); }); + + queue.createJob(jobName, { + trackingName: 'job15-job-name-payloadTimeout(500)-timeout(600)-priority(0)', // THIS JOB WON'T BE RUN BECAUSE out of time! + payloadTimeout: 500, + payloadOptionsTimeout: 600 // Mirror the actual job options timeout in payload so we can use it for testing. + }, { + timeout: 600 + }, false); + await new Promise((resolve) => { setTimeout(resolve, 25); }); + + queue.createJob(failingJobName, { + trackingName: 'job16-failing-job-name-retryDelay(2800)-attempts(3)', // This job should run twice in lifespan + retryDelay: 3000, + attempts: 3, + payloadTimeout: 10, + }, { + timeout: 600, + retryDelay: 3000, + attempts: 3, + priority: 100, + }, false); + await new Promise((resolve) => { setTimeout(resolve, 25); }); - // Detect jobs that should't be picked up by lifespan queue. - if (remainingLifespan - 500 < payload.payloadOptionsTimeout) { - badJobs.push({id, payload}); - } + // startQueue is false so queue should not have started. + queue.status.should.equal('inactive'); + const queueStartTime = Date.now(); - // Since these all run concurrently, only subtract the job with the longest - // timeout that will presumabely finish last. - if (payload.payloadTimeout == 600) { - remainingLifespan = remainingLifespan - payload.payloadTimeout; + // Start queue, don't await so this test can continue while queue processes. + await queue.start(queueLifespan); + + const queueEndTime = Date.now(); + const queueProcessTime = queueStartTime - queueEndTime; + + if (queueProcessTime > queueLifespan) { + throw new Error('ERROR: Queue did not complete before lifespan ended.'); } + if (badJobs.length) { + throw new Error('ERROR: Queue with lifespan picked up bad jobs it did not have enough remaining lifespan to execute: ' + JSON.stringify(badJobs)); + } - await new Promise((resolve) => { - setTimeout(resolve, payload.payloadTimeout); + // Queue should have stopped. + queue.status.should.equal('inactive'); + + //Check that the correct jobs executed. + executedJobs.should.deepEqual([ + 'job16-failing-job-name-retryDelay(2800)-attempts(3)', + 'job3-another-job-name-payloadTimeout(750)-timeout(800)-priority(10)', + 'job7-job-name-payloadTimeout(1000)-timeout(1100)-priority(1)', + 'job2-another-job-name-payloadTimeout(1000)-timeout(1100)-priority(0)', + 'job5-job-name-payloadTimeout(400)-timeout(500)-priority(0)', + 'job16-failing-job-name-retryDelay(2800)-attempts(3)', + 'job6-timeout-job-name-payloadTimeout(10000)-timeout(500)-priority(0)', // This job executes but isn't deleted because it fails due to timeout. + 'job8-concurrent-job-name-payloadTimeout(500)-timeout(600)-priority(0)', + 'job9-concurrent-job-name-payloadTimeout(510)-timeout(600)-priority(0)', + 'job11-concurrent-job-name-payloadTimeout(600)-timeout(700)-priority(0)', + 'job12-job-name-payloadTimeout(100)-timeout(200)-priority(0)', + 'job14-job-name-payloadTimeout(100)-timeout(200)-priority(0)', + // 'job1-job-name-payloadTimeout(100)-timeout(200)-priority(-1)' + ]); + + // Check jobs that couldn't be picked up are still in the queue. + const remainingJobs = await queue.getJobs(true); + + const remainingJobNames = remainingJobs.map( job => { + const payload = JSON.parse(job.payload); + return payload.trackingName; }); - }, { concurrency: 4}); + // queue.getJobs() doesn't order jobs in any particular way so just + // check that the jobs still exist on the queue. + remainingJobNames.should.containDeep([ + 'job4-job-name-payloadTimeout(10000)-timeout(10100)-priority(0)', + 'job6-timeout-job-name-payloadTimeout(10000)-timeout(500)-priority(0)', + 'job10-concurrent-job-name-payloadTimeout(10000)-timeout(10100)-priority(0)', + 'job13-job-name-payloadTimeout(400)-timeout(500)-priority(0)', + 'job15-job-name-payloadTimeout(500)-timeout(600)-priority(0)', + 'job16-failing-job-name-retryDelay(2800)-attempts(3)' + ]); - queue.addWorker(failingJobName, async (id, payload) => { - // Track jobs that exec - executedJobs.push(payload.trackingName); + }, 10000); // Increase timeout of this advanced test to 10 seconds. - // Detect jobs that should't be picked up by lifespan queue. - if (remainingLifespan - 500 < payload.payloadOptionsTimeout) { - badJobs.push({id, payload}); - } + it('#start(lifespan) "Zero lifespanRemaining" edge case #1 is properly handled.', async () => { - await new Promise( (resolve,reject) => { - setTimeout(() => { - reject('fail'); - }, payload.payloadTimeout); - }); - }, { concurrency: 1}); + // Mock Date.now() + Date.now = jest.fn(); + Date.now.mockReturnValueOnce(0); + Date.now.mockReturnValueOnce(1000); - // Create a couple jobs - // Broken in core module - not required so not fixing - // queue.createJob(jobName, { - // trackingName: 'job1-job-name-payloadTimeout(100)-timeout(200)-priority(-1)', - // payloadTimeout: 100, - // payloadOptionsTimeout: 200 // Mirror the actual job options timeout in payload so we can use it for testing. - // }, { - // timeout: 200, - // priority: -1 - // }, false); - - // Since more than one job can be written in 1 ms, we need to add a slight delay - // in order to control the order jobs come off the queue (since they are time sorted) - // If multiple jobs are written in the same ms, Realm can't be deterministic about job - // ordering when we pop jobs off the top of the queue. - // await new Promise((resolve) => { setTimeout(resolve, 25); }); - - queue.createJob(anotherJobName, { - trackingName: 'job2-another-job-name-payloadTimeout(1000)-timeout(1100)-priority(0)', - payloadTimeout: 1000, - payloadOptionsTimeout: 1100 // Mirror the actual job options timeout in payload so we can use it for testing. - }, { - timeout: 1100 - }, false); - await new Promise((resolve) => { setTimeout(resolve, 25); }); + const queue = await QueueFactory(); + const jobName = 'job-name'; + let counter = 0; - queue.createJob(anotherJobName, { - trackingName: 'job3-another-job-name-payloadTimeout(750)-timeout(800)-priority(10)', - payloadTimeout: 750, - payloadOptionsTimeout: 800 // Mirror the actual job options timeout in payload so we can use it for testing. - }, { - timeout: 800, - priority: 10 - }, false); - await new Promise((resolve) => { setTimeout(resolve, 25); }); + queue.addWorker(jobName, () => { + counter++; + }); - queue.createJob(jobName, { - trackingName: 'job4-job-name-payloadTimeout(10000)-timeout(10100)-priority(0)', - payloadTimeout: 10000, - payloadOptionsTimeout: 10100 // Mirror the actual job options timeout in payload so we can use it for testing. - }, { - timeout: 10100 - }, false); - await new Promise((resolve) => { setTimeout(resolve, 25); }); + // Create a job + queue.createJob(jobName, {}, { + timeout: 100 // Timeout must be set to test that job still isn't grabbed during "zero lifespanRemaining" edge case. + }, false); - queue.createJob(jobName, { - trackingName: 'job5-job-name-payloadTimeout(400)-timeout(500)-priority(0)', - payloadTimeout: 400, - payloadOptionsTimeout: 500 // Mirror the actual job options timeout in payload so we can use it for testing. - }, { - timeout: 500 - }, false); - await new Promise((resolve) => { setTimeout(resolve, 25); }); + // startQueue is false so queue should not have started. + queue.status.should.equal('inactive'); - queue.createJob(timeoutJobName, { - trackingName: 'job6-timeout-job-name-payloadTimeout(10000)-timeout(500)-priority(0)', - payloadTimeout: 10000, - payloadOptionsTimeout: 500 // Mirror the actual job options timeout in payload so we can use it for testing. - }, { - timeout: 500 - }, false); - await new Promise((resolve) => { setTimeout(resolve, 25); }); + // Start queue, don't await so this test can continue while queue processes. + await queue.start(1000); - queue.createJob(jobName, { - trackingName: 'job7-job-name-payloadTimeout(1000)-timeout(1100)-priority(1)', - payloadTimeout: 1000, - payloadOptionsTimeout: 1100 // Mirror the actual job options timeout in payload so we can use it for testing. - }, { - timeout: 1100, - priority: 1 - }, false); - await new Promise((resolve) => { setTimeout(resolve, 25); }); + // Queue should be inactive again. + queue.status.should.equal('inactive'); + // Since we hit "zero lifespanRemaining" edge case, the job should never have been pulled + // off the queue and processed. So counter should remain 0 and job should still exist. + counter.should.equal(0); - // Create concurrent jobs - queue.createJob(concurrentJobName, { - trackingName: 'job8-concurrent-job-name-payloadTimeout(500)-timeout(600)-priority(0)', - payloadTimeout: 500, - payloadOptionsTimeout: 600 // Mirror the actual job options timeout in payload so we can use it for testing. - }, { - timeout: 600, - priority: 0 - }, false); - await new Promise((resolve) => { setTimeout(resolve, 25); }); + const jobs = await queue.getJobs(true); - queue.createJob(concurrentJobName, { - trackingName: 'job9-concurrent-job-name-payloadTimeout(510)-timeout(600)-priority(0)', - payloadTimeout: 510, - payloadOptionsTimeout: 600 // Mirror the actual job options timeout in payload so we can use it for testing. - }, { - timeout: 600, - priority: 0 - }, false); - await new Promise((resolve) => { setTimeout(resolve, 25); }); + jobs.length.should.equal(1); - queue.createJob(concurrentJobName, { - trackingName: 'job10-concurrent-job-name-payloadTimeout(10000)-timeout(10100)-priority(0)', // THIS JOB WILL BE SKIPPED BY getConcurrentJobs() due to timeout too long. - payloadTimeout: 10000, - payloadOptionsTimeout: 10100 // Mirror the actual job options timeout in payload so we can use it for testing. - }, { - timeout: 10100, - priority: 0 - }, false); - await new Promise((resolve) => { setTimeout(resolve, 25); }); + }); - queue.createJob(concurrentJobName, { - trackingName: 'job11-concurrent-job-name-payloadTimeout(600)-timeout(700)-priority(0)', - payloadTimeout: 600, - payloadOptionsTimeout: 700 // Mirror the actual job options timeout in payload so we can use it for testing. - }, { - timeout: 700, - priority: 0 - }, false); - await new Promise((resolve) => { setTimeout(resolve, 25); }); + it('#start(lifespan) "Zero lifespanRemaining" edge case #2 is properly handled.', async () => { - queue.createJob(jobName, { - trackingName: 'job12-job-name-payloadTimeout(100)-timeout(200)-priority(0)', - payloadTimeout: 100, - payloadOptionsTimeout: 200 // Mirror the actual job options timeout in payload so we can use it for testing. - }, { - timeout: 200 - }, false); - await new Promise((resolve) => { setTimeout(resolve, 25); }); + // Mock Date.now() + Date.now = jest.fn(); + Date.now.mockReturnValueOnce(0); + Date.now.mockReturnValueOnce(500); + Date.now.mockReturnValueOnce(2000); - queue.createJob(jobName, { - trackingName: 'job13-job-name-payloadTimeout(400)-timeout(500)-priority(0)', // THIS JOB WON'T BE RUN BECAUSE THE TIMEOUT IS 500 AND ONLY 950ms left by this pount. 950 - 500 = 450 and 500 remaining is min for job to be pulled. - payloadTimeout: 400, - payloadOptionsTimeout: 500 // Mirror the actual job options timeout in payload so we can use it for testing. - }, { - timeout: 500 - }, false); - await new Promise((resolve) => { setTimeout(resolve, 25); }); + const queue = await QueueFactory(); + const jobName = 'job-name'; + let counter = 0; - queue.createJob(jobName, { - trackingName: 'job14-job-name-payloadTimeout(100)-timeout(200)-priority(0)', - payloadTimeout: 100, - payloadOptionsTimeout: 200 // Mirror the actual job options timeout in payload so we can use it for testing. - }, { - timeout: 200 - }, false); - await new Promise((resolve) => { setTimeout(resolve, 25); }); + queue.addWorker(jobName, () => { + counter++; + }); - queue.createJob(jobName, { - trackingName: 'job15-job-name-payloadTimeout(500)-timeout(600)-priority(0)', // THIS JOB WON'T BE RUN BECAUSE out of time! - payloadTimeout: 500, - payloadOptionsTimeout: 600 // Mirror the actual job options timeout in payload so we can use it for testing. - }, { - timeout: 600 - }, false); - await new Promise((resolve) => { setTimeout(resolve, 25); }); + // Create jobs + queue.createJob(jobName, {}, { + timeout: 100 // Timeout must be set to test that job still isn't grabbed during "zero lifespanRemaining" edge case. + }, false); - queue.createJob(failingJobName, { - trackingName: 'job16-failing-job-name-retryDelay(2800)-attempts(3)', // This job should run twice in lifespan - retryDelay: 3000, - attempts: 3, - payloadTimeout: 10, - }, { - timeout: 600, - retryDelay: 3000, - attempts: 3, - priority: 100, - }, false); - await new Promise((resolve) => { setTimeout(resolve, 25); }); + await new Promise((resolve) => { setTimeout(resolve, 25); }); // Space out inserts so time sorting is deterministic. - // startQueue is false so queue should not have started. - queue.status.should.equal('inactive'); + queue.createJob(jobName, { + testIdentifier: 'this is 2nd job' + }, { + timeout: 100 // Timeout must be set to test that job still isn't grabbed during "zero lifespanRemaining" edge case. + }, false); - const queueStartTime = Date.now(); + // startQueue is false so queue should not have started. + queue.status.should.equal('inactive'); - // Start queue, don't await so this test can continue while queue processes. - await queue.start(queueLifespan); + // Start queue, don't await so this test can continue while queue processes. + await queue.start(2000); - const queueEndTime = Date.now(); - const queueProcessTime = queueStartTime - queueEndTime; + // Queue should be inactive again. + queue.status.should.equal('inactive'); - if (queueProcessTime > queueLifespan) { - throw new Error('ERROR: Queue did not complete before lifespan ended.'); - } + // Since we skipped first "zero lifespanRemaining" edge case, one job should + // be processed. However since we hit 2nd "zero lifespanRemaining" edge case, + // second job should never be pulled off queue and so second job should still exist. + counter.should.equal(1); - if (badJobs.length) { - throw new Error('ERROR: Queue with lifespan picked up bad jobs it did not have enough remaining lifespan to execute: ' + JSON.stringify(badJobs)); - } + const jobs = await queue.getJobs(true); - // Queue should have stopped. - queue.status.should.equal('inactive'); + const jobPayload = JSON.parse(jobs[0].payload); - //Check that the correct jobs executed. - executedJobs.should.deepEqual([ - 'job16-failing-job-name-retryDelay(2800)-attempts(3)', - 'job3-another-job-name-payloadTimeout(750)-timeout(800)-priority(10)', - 'job7-job-name-payloadTimeout(1000)-timeout(1100)-priority(1)', - 'job2-another-job-name-payloadTimeout(1000)-timeout(1100)-priority(0)', - 'job5-job-name-payloadTimeout(400)-timeout(500)-priority(0)', - 'job16-failing-job-name-retryDelay(2800)-attempts(3)', - 'job6-timeout-job-name-payloadTimeout(10000)-timeout(500)-priority(0)', // This job executes but isn't deleted because it fails due to timeout. - 'job8-concurrent-job-name-payloadTimeout(500)-timeout(600)-priority(0)', - 'job9-concurrent-job-name-payloadTimeout(510)-timeout(600)-priority(0)', - 'job11-concurrent-job-name-payloadTimeout(600)-timeout(700)-priority(0)', - 'job12-job-name-payloadTimeout(100)-timeout(200)-priority(0)', - 'job14-job-name-payloadTimeout(100)-timeout(200)-priority(0)', - // 'job1-job-name-payloadTimeout(100)-timeout(200)-priority(-1)' - ]); - - // Check jobs that couldn't be picked up are still in the queue. - const remainingJobs = await queue.getJobs(true); - - const remainingJobNames = remainingJobs.map( job => { - const payload = JSON.parse(job.payload); - return payload.trackingName; - }); + jobPayload.testIdentifier.should.equal('this is 2nd job'); - // queue.getJobs() doesn't order jobs in any particular way so just - // check that the jobs still exist on the queue. - remainingJobNames.should.containDeep([ - 'job4-job-name-payloadTimeout(10000)-timeout(10100)-priority(0)', - 'job6-timeout-job-name-payloadTimeout(10000)-timeout(500)-priority(0)', - 'job10-concurrent-job-name-payloadTimeout(10000)-timeout(10100)-priority(0)', - 'job13-job-name-payloadTimeout(400)-timeout(500)-priority(0)', - 'job15-job-name-payloadTimeout(500)-timeout(600)-priority(0)', - 'job16-failing-job-name-retryDelay(2800)-attempts(3)' - ]); + }); - }, 10000); // Increase timeout of this advanced test to 10 seconds. + // + // FULL QUEUE UNIT TESTING + // - it('#start(lifespan) "Zero lifespanRemaining" edge case #1 is properly handled.', async () => { + it('#constructor() sets values correctly', async () => { - // Mock Date.now() - Date.now = jest.fn(); - Date.now.mockReturnValueOnce(0); - Date.now.mockReturnValueOnce(1000); + const queueNotInitialized = new Queue(); - const queue = await QueueFactory(); - const jobName = 'job-name'; - let counter = 0; + queueNotInitialized.should.have.properties({ + realm: null, + worker: new Worker(), + status: 'inactive' + }); - queue.addWorker(jobName, () => { - counter++; }); - // Create a job - queue.createJob(jobName, {}, { - timeout: 100 // Timeout must be set to test that job still isn't grabbed during "zero lifespanRemaining" edge case. - }, false); - - // startQueue is false so queue should not have started. - queue.status.should.equal('inactive'); + it('QueueFactory initializes Realm', async () => { - // Start queue, don't await so this test can continue while queue processes. - await queue.start(1000); + const queue = await QueueFactory(); - // Queue should be inactive again. - queue.status.should.equal('inactive'); + queue.realm.constructor.name.should.equal('Realm'); - // Since we hit "zero lifespanRemaining" edge case, the job should never have been pulled - // off the queue and processed. So counter should remain 0 and job should still exist. - counter.should.equal(0); + }); - const jobs = await queue.getJobs(true); + it('init() Calling init() multiple times will only set queue.realm once.', async () => { - jobs.length.should.equal(1); + const queue = await QueueFactory(); - }); + queue.realm.constructor.name.should.equal('Realm'); - it('#start(lifespan) "Zero lifespanRemaining" edge case #2 is properly handled.', async () => { + // Overwrite realm instance to test it doesn't get set to the actual + // Realm singleton instance again in init() since queue.realm is no longer null. + queue.realm = 'arbitrary-string'; - // Mock Date.now() - Date.now = jest.fn(); - Date.now.mockReturnValueOnce(0); - Date.now.mockReturnValueOnce(500); - Date.now.mockReturnValueOnce(2000); + queue.init(); - const queue = await QueueFactory(); - const jobName = 'job-name'; - let counter = 0; + queue.realm.should.equal('arbitrary-string'); - queue.addWorker(jobName, () => { - counter++; }); - // Create jobs - queue.createJob(jobName, {}, { - timeout: 100 // Timeout must be set to test that job still isn't grabbed during "zero lifespanRemaining" edge case. - }, false); - - await new Promise((resolve) => { setTimeout(resolve, 25); }); // Space out inserts so time sorting is deterministic. + it('#addWorker() and removeWorker() should pass calls through to Worker class', async () => { + + const queue = await QueueFactory(); + const workerOptions = { + concurrency: 4, + onSuccess: async () => {} + }; + + queue.addWorker('job-name', () => {}, workerOptions); + + // first worker is added with default options. + Worker.workers['job-name'].should.be.a.Function(); + Worker.workers['job-name'].options.should.deepEqual({ + concurrency: workerOptions.concurrency, + onStart: null, + onSuccess: workerOptions.onSuccess, + onFailure: null, + onFailed: null, + onComplete: null + }); - queue.createJob(jobName, { - testIdentifier: 'this is 2nd job' - }, { - timeout: 100 // Timeout must be set to test that job still isn't grabbed during "zero lifespanRemaining" edge case. - }, false); + queue.removeWorker('job-name'); - // startQueue is false so queue should not have started. - queue.status.should.equal('inactive'); + // Worker has been removed. + should.not.exist(Worker.workers['job-name']); - // Start queue, don't await so this test can continue while queue processes. - await queue.start(2000); + }); - // Queue should be inactive again. - queue.status.should.equal('inactive'); + it('#createJob() requires job name at minimum', async () => { - // Since we skipped first "zero lifespanRemaining" edge case, one job should - // be processed. However since we hit 2nd "zero lifespanRemaining" edge case, - // second job should never be pulled off queue and so second job should still exist. - counter.should.equal(1); + const queue = await QueueFactory(); - const jobs = await queue.getJobs(true); + try { + await queue.createJob(); + throw new Error('Job with no name should have thrown error.'); + } catch (error) { + error.should.deepEqual(new Error('Job name must be supplied.')); + } - const jobPayload = JSON.parse(jobs[0].payload); + }); - jobPayload.testIdentifier.should.equal('this is 2nd job'); + it('#createJob() should validate job options.', async () => { - }); + const queue = await QueueFactory(); + const jobName = 'job-name'; - // - // FULL QUEUE UNIT TESTING - // + queue.addWorker(jobName, () => {}); - it('#constructor() sets values correctly', async () => { + try { + await queue.createJob(jobName, {}, { + timeout: -100 + }, false); + throw new Error('createJob() should validate job timeout option.'); + } catch (error) { + error.should.deepEqual(new Error('Invalid job option.')); + } - const queueNotInitialized = new Queue(); + try { + await queue.createJob(jobName, {}, { + attempts: -100 + }, false); + throw new Error('createJob() should validate job attempts option.'); + } catch (error) { + error.should.deepEqual(new Error('Invalid job option.')); + } - queueNotInitialized.should.have.properties({ - realm: null, - worker: new Worker(), - status: 'inactive' }); - }); + it('#createJob() should apply defaults correctly', async () => { - it('QueueFactory initializes Realm', async () => { + const queue = await QueueFactory(); + const jobName = 'job-name'; - const queue = await QueueFactory(); + queue.addWorker(jobName, () => {}); - queue.realm.constructor.name.should.equal('Realm'); + queue.createJob(jobName, {}, {}, false); - }); + // startQueue is false so queue should not have started. + queue.status.should.equal('inactive'); - it('init() Calling init() multiple times will only set queue.realm once.', async () => { + const jobs = await queue.getJobs(true); - const queue = await QueueFactory(); + // Check job has default values. + jobs[0].should.have.properties({ + name: jobName, + payload: JSON.stringify({}), + data: JSON.stringify({attempts: 1}), + priority: 0, + active: false, + timeout: 25000 + }); + + }); - queue.realm.constructor.name.should.equal('Realm'); + it('#createJob() should create a new job on the queue', async () => { - // Overwrite realm instance to test it doesn't get set to the actual - // Realm singleton instance again in init() since queue.realm is no longer null. - queue.realm = 'arbitrary-string'; + const queue = await QueueFactory(); + const jobName = 'job-name'; + const payload = { data: 'example-data' }; + const jobOptions = { priority: 4, timeout: 3000, attempts: 3}; - queue.init(); + queue.addWorker(jobName, () => {}); - queue.realm.should.equal('arbitrary-string'); + queue.createJob(jobName, payload, jobOptions, false); - }); + // startQueue is false so queue should not have started. + queue.status.should.equal('inactive'); - it('#addWorker() and removeWorker() should pass calls through to Worker class', async () => { + const jobs = await queue.getJobs(true); - const queue = await QueueFactory(); - const workerOptions = { - concurrency: 4, - onSuccess: async () => {} - }; + jobs[0].should.have.properties({ + name: jobName, + payload: JSON.stringify(payload), + data: JSON.stringify({attempts: jobOptions.attempts}), + priority: jobOptions.priority, + active: false, + timeout: jobOptions.timeout + }); - queue.addWorker('job-name', () => {}, workerOptions); - - // first worker is added with default options. - Worker.workers['job-name'].should.be.a.Function(); - Worker.workers['job-name'].options.should.deepEqual({ - concurrency: workerOptions.concurrency, - onStart: null, - onSuccess: workerOptions.onSuccess, - onFailure: null, - onFailed: null, - onComplete: null }); - queue.removeWorker('job-name'); + it('#createJob() should default to starting queue. stop() should stop queue.', async () => { - // Worker has been removed. - should.not.exist(Worker.workers['job-name']); + const queue = await QueueFactory(); + const jobName = 'job-name'; + const payload = { data: 'example-data' }; + const jobOptions = { priority: 4, timeout: 3000, attempts: 3}; - }); + queue.addWorker(jobName, () => {}); - it('#createJob() requires job name at minimum', async () => { + queue.createJob(jobName, payload, jobOptions, true); + queue.status.should.equal('active'); - const queue = await QueueFactory(); + queue.stop(); - try { - await queue.createJob(); - throw new Error('Job with no name should have thrown error.'); - } catch (error) { - error.should.deepEqual(new Error('Job name must be supplied.')); - } + queue.status.should.equal('inactive'); - }); + }); - it('#createJob() should validate job options.', async () => { + it('#start() should start queue.', async () => { - const queue = await QueueFactory(); - const jobName = 'job-name'; + const queue = await QueueFactory(); + const jobName = 'job-name'; + const payload = { data: 'example-data' }; + const jobOptions = { priority: 4, timeout: 3000, attempts: 3}; - queue.addWorker(jobName, () => {}); + let counter = 0; // Incrementing this will be our job "work". - try { - await queue.createJob(jobName, {}, { - timeout: -100 - }, false); - throw new Error('createJob() should validate job timeout option.'); - } catch (error) { - error.should.deepEqual(new Error('Invalid job option.')); - } + queue.addWorker(jobName, () => { + counter++; + }); - try { - await queue.createJob(jobName, {}, { - attempts: -100 - }, false); - throw new Error('createJob() should validate job attempts option.'); - } catch (error) { - error.should.deepEqual(new Error('Invalid job option.')); - } + // Create a couple jobs + queue.createJob(jobName, payload, jobOptions, false); + queue.createJob(jobName, payload, jobOptions, false); - }); + // startQueue is false so queue should not have started. + queue.status.should.equal('inactive'); - it('#createJob() should apply defaults correctly', async () => { + queue.start(); - const queue = await QueueFactory(); - const jobName = 'job-name'; + queue.status.should.equal('active'); - queue.addWorker(jobName, () => {}); + // Give queue 1000ms to churn through all the jobs. + await new Promise((resolve) => { + setTimeout(() => { + resolve(true); + }, 1000); + }); - queue.createJob(jobName, {}, {}, false); + // Queue should be finished with no jobs left. + queue.status.should.equal('inactive'); - // startQueue is false so queue should not have started. - queue.status.should.equal('inactive'); - - const jobs = await queue.getJobs(true); - - // Check job has default values. - jobs[0].should.have.properties({ - name: jobName, - payload: JSON.stringify({}), - data: JSON.stringify({attempts: 1}), - priority: 0, - active: false, - timeout: 25000 - }); - - }); - - it('#createJob() should create a new job on the queue', async () => { + const jobs = await queue.getJobs(true); - const queue = await QueueFactory(); - const jobName = 'job-name'; - const payload = { data: 'example-data' }; - const jobOptions = { priority: 4, timeout: 3000, attempts: 3}; - - queue.addWorker(jobName, () => {}); - - queue.createJob(jobName, payload, jobOptions, false); - - // startQueue is false so queue should not have started. - queue.status.should.equal('inactive'); + jobs.length.should.equal(0); - const jobs = await queue.getJobs(true); + // Counter should be updated to reflect worker execution. + counter.should.equal(2); - jobs[0].should.have.properties({ - name: jobName, - payload: JSON.stringify(payload), - data: JSON.stringify({attempts: jobOptions.attempts}), - priority: jobOptions.priority, - active: false, - timeout: jobOptions.timeout }); - }); + it('#start() called when queue is already active should NOT fire up a concurrent queue.', async () => { - it('#createJob() should default to starting queue. stop() should stop queue.', async () => { + const queue = await QueueFactory(); + const jobName = 'job-name'; + const payload = { data: 'example-data' }; + const jobOptions = { priority: 4, timeout: 3000, attempts: 3}; - const queue = await QueueFactory(); - const jobName = 'job-name'; - const payload = { data: 'example-data' }; - const jobOptions = { priority: 4, timeout: 3000, attempts: 3}; + queue.addWorker(jobName, async () => { - queue.addWorker(jobName, () => {}); + // Make queue take some time to process. + await new Promise( resolve => { + setTimeout(resolve, 1000); + }); - queue.createJob(jobName, payload, jobOptions, true); - queue.status.should.equal('active'); + }); - queue.stop(); + // Create a couple jobs + queue.createJob(jobName, payload, jobOptions, false); + queue.createJob(jobName, payload, jobOptions, false); - queue.status.should.equal('inactive'); + // startQueue is false so queue should not have started. + queue.status.should.equal('inactive'); - }); + // Start queue, don't await so this test can continue while queue processes. + queue.start(); - it('#start() should start queue.', async () => { + queue.status.should.equal('active'); - const queue = await QueueFactory(); - const jobName = 'job-name'; - const payload = { data: 'example-data' }; - const jobOptions = { priority: 4, timeout: 3000, attempts: 3}; + // Calling queue.start() on already running queue should cause start() to return + // early with false bool indicating concurrent start did not occur. + const falseStart = await queue.start(); //Must be awaited to resolve async func promise into false value. - let counter = 0; // Incrementing this will be our job "work". + falseStart.should.be.False(); - queue.addWorker(jobName, () => { - counter++; }); - // Create a couple jobs - queue.createJob(jobName, payload, jobOptions, false); - queue.createJob(jobName, payload, jobOptions, false); + it('#getJobs() should grab all jobs in queue.', async () => { - // startQueue is false so queue should not have started. - queue.status.should.equal('inactive'); + const queue = await QueueFactory(); + const jobName = 'job-name'; + const payload = { data: 'example-data' }; + const jobOptions = { priority: 4, timeout: 3000, attempts: 3}; - queue.start(); + queue.addWorker(jobName, () => {}); - queue.status.should.equal('active'); + // Create a couple jobs + queue.createJob(jobName, payload, jobOptions, false); + queue.createJob(jobName, payload, jobOptions, false); + queue.createJob(jobName, payload, jobOptions, false); + queue.createJob(jobName, payload, jobOptions, false); - // Give queue 1000ms to churn through all the jobs. - await new Promise((resolve) => { - setTimeout(() => { - resolve(true); - }, 1000); - }); - - // Queue should be finished with no jobs left. - queue.status.should.equal('inactive'); + const jobs = await queue.getJobs(true); - const jobs = await queue.getJobs(true); - - jobs.length.should.equal(0); + jobs.length.should.equal(4); - // Counter should be updated to reflect worker execution. - counter.should.equal(2); + const mvccJobs = await queue.getJobs(); // Test non-blocking read version as well. - }); + mvccJobs.length.should.equal(4); - it('#start() called when queue is already active should NOT fire up a concurrent queue.', async () => { + }); - const queue = await QueueFactory(); - const jobName = 'job-name'; - const payload = { data: 'example-data' }; - const jobOptions = { priority: 4, timeout: 3000, attempts: 3}; + it('#getConcurrentJobs(queueLifespanRemaining) should work as expected for queues started with a lifespan.', async () => { - queue.addWorker(jobName, async () => { + const queue = await QueueFactory(); + const jobName = 'job-name'; - // Make queue take some time to process. - await new Promise( resolve => { - setTimeout(resolve, 1000); + queue.addWorker(jobName, () => {}, { + concurrency: 3 }); - }); + // Test that jobs with no timeout set will not be returned by getConcurrentJobs() if queueLifespanRemaining is passed. + queue.createJob(jobName, {}, { + timeout: 0 + }, false); + queue.createJob(jobName, {}, { + timeout: 0 + }, false); + queue.createJob(jobName, {}, { + timeout: 0 + }, false); - // Create a couple jobs - queue.createJob(jobName, payload, jobOptions, false); - queue.createJob(jobName, payload, jobOptions, false); + const jobs = await queue.getConcurrentJobs(2000); - // startQueue is false so queue should not have started. - queue.status.should.equal('inactive'); + // No jobs should be grabbed + jobs.length.should.equal(0); - // Start queue, don't await so this test can continue while queue processes. - queue.start(); + // Reset DB + queue.flushQueue(); - queue.status.should.equal('active'); + // Test that jobs with timeout not at least 500ms less than queueLifespanRemaining are not grabbed. + queue.createJob(jobName, {}, { + timeout: 500 + }, false); + queue.createJob(jobName, {}, { + timeout: 500 + }, false); + queue.createJob(jobName, {}, { + timeout: 500 + }, false); - // Calling queue.start() on already running queue should cause start() to return - // early with false bool indicating concurrent start did not occur. - const falseStart = await queue.start(); //Must be awaited to resolve async func promise into false value. + const notEnoughBufferJobs = await queue.getConcurrentJobs(600); - falseStart.should.be.False(); + // No jobs should be grabbed + notEnoughBufferJobs.length.should.equal(0); - }); + // Reset DB + queue.flushQueue(); - it('#getJobs() should grab all jobs in queue.', async () => { + //Lower bound edge case test + queue.createJob(jobName, {}, { + timeout: 0 + }, false); + queue.createJob(jobName, {}, { + timeout: 1 + }, false); + queue.createJob(jobName, {}, { + timeout: 1 + }, false); - const queue = await QueueFactory(); - const jobName = 'job-name'; - const payload = { data: 'example-data' }; - const jobOptions = { priority: 4, timeout: 3000, attempts: 3}; + // startQueue is false so queue should not have started. + queue.status.should.equal('inactive'); - queue.addWorker(jobName, () => {}); + const lowerBoundEdgeCaseJobs = await queue.getConcurrentJobs(501); - // Create a couple jobs - queue.createJob(jobName, payload, jobOptions, false); - queue.createJob(jobName, payload, jobOptions, false); - queue.createJob(jobName, payload, jobOptions, false); - queue.createJob(jobName, payload, jobOptions, false); + // Only the jobs with the timeouts set should be grabbed. + lowerBoundEdgeCaseJobs.length.should.equal(2); - const jobs = await queue.getJobs(true); + // Reset DB + queue.flushQueue(); - jobs.length.should.equal(4); - - const mvccJobs = await queue.getJobs(); // Test non-blocking read version as well. + //Test concurrency is working as expected with lifespans. + queue.createJob(jobName, {}, { + timeout: 800 + }, false); + queue.createJob(jobName, {}, { + timeout: 1000 + }, false); + queue.createJob(jobName, {}, { + timeout: 1000 + }, false); + queue.createJob(jobName, {}, { + timeout: 1000 + }, false); - mvccJobs.length.should.equal(4); + // startQueue is false so queue should not have started. + queue.status.should.equal('inactive'); - }); + const lifespanConcurrencyJobs = await queue.getConcurrentJobs(2000); - it('#getConcurrentJobs(queueLifespanRemaining) should work as expected for queues started with a lifespan.', async () => { + // Only 3 jobs should be grabbed in this test even though all jobs + // have valid timeouts because worker concurrency is set to 3 + lifespanConcurrencyJobs.length.should.equal(3); - const queue = await QueueFactory(); - const jobName = 'job-name'; + // Reset DB + queue.flushQueue(); - queue.addWorker(jobName, () => {}, { - concurrency: 3 }); - // Test that jobs with no timeout set will not be returned by getConcurrentJobs() if queueLifespanRemaining is passed. - queue.createJob(jobName, {}, { - timeout: 0 - }, false); - queue.createJob(jobName, {}, { - timeout: 0 - }, false); - queue.createJob(jobName, {}, { - timeout: 0 - }, false); + it('#getConcurrentJobs() If worker concurrency is set to 3, getConcurrentJobs() should get up to 3 of same type of job as next job on top of queue.', async () => { - const jobs = await queue.getConcurrentJobs(2000); + const queue = await QueueFactory(); + const jobName = 'job-name'; + const jobOptions = { priority: 4, timeout: 3000, attempts: 3}; - // No jobs should be grabbed - jobs.length.should.equal(0); - - // Reset DB - queue.flushQueue(); + queue.addWorker(jobName, () => {}, { + concurrency: 3 + }); + queue.addWorker('a-different-job', () => {}); - // Test that jobs with timeout not at least 500ms less than queueLifespanRemaining are not grabbed. - queue.createJob(jobName, {}, { - timeout: 500 - }, false); - queue.createJob(jobName, {}, { - timeout: 500 - }, false); - queue.createJob(jobName, {}, { - timeout: 500 - }, false); + // Create a couple jobs + queue.createJob(jobName, { random: 'this is 1st random data' }, jobOptions, false); + queue.createJob('a-different-job', { dummy: 'data' }, {}, false); // This should not be returned by concurrentJobs() should all be of the 'job-name' type. + queue.createJob(jobName, { random: 'this is 2nd random data' }, jobOptions, false); + queue.createJob(jobName, { random: 'this is 3rd random data' }, jobOptions, false); + queue.createJob(jobName, { random: 'this is 4th random data' }, jobOptions, false); - const notEnoughBufferJobs = await queue.getConcurrentJobs(600); + const concurrentJobs = await queue.getConcurrentJobs(); - // No jobs should be grabbed - notEnoughBufferJobs.length.should.equal(0); + // Verify correct jobs retrieved. + concurrentJobs.length.should.equal(3); + JSON.parse(concurrentJobs[0].payload).should.deepEqual({ random: 'this is 1st random data' }); + JSON.parse(concurrentJobs[1].payload).should.deepEqual({ random: 'this is 2nd random data' }); + JSON.parse(concurrentJobs[2].payload).should.deepEqual({ random: 'this is 3rd random data' }); - // Reset DB - queue.flushQueue(); + // Ensure that other jobs also got created, but not returned by getConcurrentJobs(). + const jobs = await queue.getJobs(true); + jobs.length.should.equal(5); - //Lower bound edge case test - queue.createJob(jobName, {}, { - timeout: 0 - }, false); - queue.createJob(jobName, {}, { - timeout: 1 - }, false); - queue.createJob(jobName, {}, { - timeout: 1 - }, false); + }); - // startQueue is false so queue should not have started. - queue.status.should.equal('inactive'); + it('#getConcurrentJobs() If worker concurrency is set to 10, but only 4 jobs of next job type exist, getConcurrentJobs() should only return 4 jobs.', async () => { - const lowerBoundEdgeCaseJobs = await queue.getConcurrentJobs(501); + const queue = await QueueFactory(); + const jobName = 'job-name'; + const jobOptions = { priority: 4, timeout: 3000, attempts: 3}; - // Only the jobs with the timeouts set should be grabbed. - lowerBoundEdgeCaseJobs.length.should.equal(2); + queue.addWorker(jobName, () => {}, { + concurrency: 10 + }); + queue.addWorker('a-different-job', () => {}); - // Reset DB - queue.flushQueue(); + // Create a couple jobs + queue.createJob(jobName, { random: 'this is 1st random data' }, jobOptions, false); + queue.createJob('a-different-job', { dummy: 'data' }, {}, false); // This should not be returned by concurrentJobs() should all be of the 'job-name' type. + queue.createJob(jobName, { random: 'this is 2nd random data' }, jobOptions, false); + queue.createJob(jobName, { random: 'this is 3rd random data' }, jobOptions, false); + queue.createJob(jobName, { random: 'this is 4th random data' }, jobOptions, false); - //Test concurrency is working as expected with lifespans. - queue.createJob(jobName, {}, { - timeout: 800 - }, false); - queue.createJob(jobName, {}, { - timeout: 1000 - }, false); - queue.createJob(jobName, {}, { - timeout: 1000 - }, false); - queue.createJob(jobName, {}, { - timeout: 1000 - }, false); + const concurrentJobs = await queue.getConcurrentJobs(); - // startQueue is false so queue should not have started. - queue.status.should.equal('inactive'); + // Verify correct jobs retrieved. + concurrentJobs.length.should.equal(4); + JSON.parse(concurrentJobs[0].payload).should.deepEqual({ random: 'this is 1st random data' }); + JSON.parse(concurrentJobs[1].payload).should.deepEqual({ random: 'this is 2nd random data' }); + JSON.parse(concurrentJobs[2].payload).should.deepEqual({ random: 'this is 3rd random data' }); + JSON.parse(concurrentJobs[3].payload).should.deepEqual({ random: 'this is 4th random data' }); - const lifespanConcurrencyJobs = await queue.getConcurrentJobs(2000); + // Ensure that other jobs also got created, but not returned by getConcurrentJobs(). + const jobs = await queue.getJobs(true); + jobs.length.should.equal(5); - // Only 3 jobs should be grabbed in this test even though all jobs - // have valid timeouts because worker concurrency is set to 3 - lifespanConcurrencyJobs.length.should.equal(3); + }); - // Reset DB - queue.flushQueue(); + it('#getConcurrentJobs() Ensure that priority is respected.', async () => { - }); + const queue = await QueueFactory(); + const jobName = 'job-name'; + const jobOptions = { priority: 0, timeout: 3000, attempts: 3}; - it('#getConcurrentJobs() If worker concurrency is set to 3, getConcurrentJobs() should get up to 3 of same type of job as next job on top of queue.', async () => { + queue.addWorker(jobName, () => {}, { + concurrency: 3 + }); + queue.addWorker('a-different-job', () => {}, { + concurrency: 2 + }); - const queue = await QueueFactory(); - const jobName = 'job-name'; - const jobOptions = { priority: 4, timeout: 3000, attempts: 3}; + // Create a couple jobs + queue.createJob(jobName, { random: 'this is 1st random data' }, jobOptions, false); + queue.createJob('a-different-job', { dummy: '1 data' }, { priority: 3 }, false); + queue.createJob(jobName, { random: 'this is 2nd random data' }, jobOptions, false); + queue.createJob('a-different-job', { dummy: '2 data' }, { priority: 5 }, false); + queue.createJob('a-different-job', { dummy: '3 data' }, { priority: 3 }, false); + queue.createJob(jobName, { random: 'this is 3rd random data' }, jobOptions, false); + queue.createJob(jobName, { random: 'this is 4th random data' }, jobOptions, false); + + const concurrentJobs = await queue.getConcurrentJobs(); + + // Verify correct jobs retrieved. + // 'a-different-job' should be the jobs returned since job with payload "2 data" has highest priority + // since the other 'a-different-job' jobs have same priority, "1 data" should get preference for 2nd concurrent job due + // to timestamp order. + concurrentJobs.length.should.equal(2); + JSON.parse(concurrentJobs[0].payload).should.deepEqual({ dummy: '2 data' }); + JSON.parse(concurrentJobs[1].payload).should.deepEqual({ dummy: '1 data' }); + + // Ensure that other jobs also got created, but not returned by getConcurrentJobs(). + const jobs = await queue.getJobs(true); + jobs.length.should.equal(7); - queue.addWorker(jobName, () => {}, { - concurrency: 3 }); - queue.addWorker('a-different-job', () => {}); - // Create a couple jobs - queue.createJob(jobName, { random: 'this is 1st random data' }, jobOptions, false); - queue.createJob('a-different-job', { dummy: 'data' }, {}, false); // This should not be returned by concurrentJobs() should all be of the 'job-name' type. - queue.createJob(jobName, { random: 'this is 2nd random data' }, jobOptions, false); - queue.createJob(jobName, { random: 'this is 3rd random data' }, jobOptions, false); - queue.createJob(jobName, { random: 'this is 4th random data' }, jobOptions, false); + it('#getConcurrentJobs() Marks selected jobs as "active"', async () => { - const concurrentJobs = await queue.getConcurrentJobs(); + const queue = await QueueFactory(); + const jobName = 'job-name'; + const jobOptions = { priority: 0, timeout: 3000, attempts: 3}; - // Verify correct jobs retrieved. - concurrentJobs.length.should.equal(3); - JSON.parse(concurrentJobs[0].payload).should.deepEqual({ random: 'this is 1st random data' }); - JSON.parse(concurrentJobs[1].payload).should.deepEqual({ random: 'this is 2nd random data' }); - JSON.parse(concurrentJobs[2].payload).should.deepEqual({ random: 'this is 3rd random data' }); + queue.addWorker(jobName, () => {}, { + concurrency: 3 + }); + queue.addWorker('a-different-job', () => {}, { + concurrency: 2 + }); - // Ensure that other jobs also got created, but not returned by getConcurrentJobs(). - const jobs = await queue.getJobs(true); - jobs.length.should.equal(5); + // Create a couple jobs + queue.createJob(jobName, { random: 'this is 1st random data' }, jobOptions, false); + queue.createJob('a-different-job', { dummy: '1 data' }, { priority: 3 }, false); + queue.createJob(jobName, { random: 'this is 2nd random data' }, jobOptions, false); + queue.createJob('a-different-job', { dummy: '2 data' }, { priority: 5 }, false); + queue.createJob('a-different-job', { dummy: '3 data' }, { priority: 3 }, false); + queue.createJob(jobName, { random: 'this is 3rd random data' }, jobOptions, false); + queue.createJob(jobName, { random: 'this is 4th random data' }, jobOptions, false); - }); + // Jobs returned by getConcurrentJobs() are marked "active" so they won't be returned by future getConcurrentJobs() calls. + const concurrentJobs = await queue.getConcurrentJobs(); - it('#getConcurrentJobs() If worker concurrency is set to 10, but only 4 jobs of next job type exist, getConcurrentJobs() should only return 4 jobs.', async () => { + // Get all the jobs in the DB and check that the "concurrentJobs" are marked "active." + const jobs = await queue.getJobs(true); + jobs.length.should.equal(7); - const queue = await QueueFactory(); - const jobName = 'job-name'; - const jobOptions = { priority: 4, timeout: 3000, attempts: 3}; + const activeJobs = jobs.filter( job => job.active); + activeJobs.length.should.equal(2); + JSON.parse(concurrentJobs[0].payload).should.deepEqual({ dummy: '2 data' }); + JSON.parse(concurrentJobs[1].payload).should.deepEqual({ dummy: '1 data' }); - queue.addWorker(jobName, () => {}, { - concurrency: 10 }); - queue.addWorker('a-different-job', () => {}); - // Create a couple jobs - queue.createJob(jobName, { random: 'this is 1st random data' }, jobOptions, false); - queue.createJob('a-different-job', { dummy: 'data' }, {}, false); // This should not be returned by concurrentJobs() should all be of the 'job-name' type. - queue.createJob(jobName, { random: 'this is 2nd random data' }, jobOptions, false); - queue.createJob(jobName, { random: 'this is 3rd random data' }, jobOptions, false); - queue.createJob(jobName, { random: 'this is 4th random data' }, jobOptions, false); - - const concurrentJobs = await queue.getConcurrentJobs(); - - // Verify correct jobs retrieved. - concurrentJobs.length.should.equal(4); - JSON.parse(concurrentJobs[0].payload).should.deepEqual({ random: 'this is 1st random data' }); - JSON.parse(concurrentJobs[1].payload).should.deepEqual({ random: 'this is 2nd random data' }); - JSON.parse(concurrentJobs[2].payload).should.deepEqual({ random: 'this is 3rd random data' }); - JSON.parse(concurrentJobs[3].payload).should.deepEqual({ random: 'this is 4th random data' }); - - // Ensure that other jobs also got created, but not returned by getConcurrentJobs(). - const jobs = await queue.getJobs(true); - jobs.length.should.equal(5); + it('#getConcurrentJobs() consecutive calls to getConcurrentJobs() gets new non-active jobs (and marks them active).', async () => { - }); + const queue = await QueueFactory(); + const jobName = 'job-name'; + const jobOptions = { priority: 0, timeout: 3000, attempts: 3}; - it('#getConcurrentJobs() Ensure that priority is respected.', async () => { + queue.addWorker(jobName, () => {}, { + concurrency: 3 + }); + queue.addWorker('a-different-job', () => {}, { + concurrency: 1 + }); - const queue = await QueueFactory(); - const jobName = 'job-name'; - const jobOptions = { priority: 0, timeout: 3000, attempts: 3}; + // Create a couple jobs + queue.createJob(jobName, { random: 'this is 1st random data' }, jobOptions, false); + queue.createJob('a-different-job', { dummy: '1 data' }, { priority: 3 }, false); + queue.createJob(jobName, { random: 'this is 2nd random data' }, { priority: 4 }, false); + queue.createJob('a-different-job', { dummy: '2 data' }, { priority: 5 }, false); + queue.createJob('a-different-job', { dummy: '3 data' }, { priority: 3 }, false); + queue.createJob(jobName, { random: 'this is 3rd random data' }, jobOptions, false); + queue.createJob(jobName, { random: 'this is 4th random data' }, jobOptions, false); + + // Jobs returned by getConcurrentJobs() are marked "active" so they won't be returned by future getConcurrentJobs() calls. + const concurrentJobs = await queue.getConcurrentJobs(); + + // Get all the jobs in the DB and check that the "concurrentJobs" are marked "active." + const jobs = await queue.getJobs(true); + jobs.length.should.equal(7); + + const activeJobs = jobs.filter( job => job.active); + activeJobs.length.should.equal(1); + JSON.parse(concurrentJobs[0].payload).should.deepEqual({ dummy: '2 data' }); + + // Next call to getConcurrentJobs() should get the next jobs of the top of the queue as expected + // Next job in line should be type of job, then grab all the concurrents of that type and mark them active. + const moreConcurrentJobs = await queue.getConcurrentJobs(); + moreConcurrentJobs.length.should.equal(3); + JSON.parse(moreConcurrentJobs[0].payload).should.deepEqual({ random: 'this is 2nd random data' }); + JSON.parse(moreConcurrentJobs[1].payload).should.deepEqual({ random: 'this is 1st random data' }); + JSON.parse(moreConcurrentJobs[2].payload).should.deepEqual({ random: 'this is 3rd random data' }); + + // Now we should have 4 active jobs... + const allJobsAgain = await queue.getJobs(true); + const nextActiveJobs = allJobsAgain.filter( job => job.active); + nextActiveJobs.length.should.equal(4); + + // Next call to getConcurrentJobs() should work as expected + const thirdConcurrentJobs = await queue.getConcurrentJobs(); + thirdConcurrentJobs.length.should.equal(1); + JSON.parse(thirdConcurrentJobs[0].payload).should.deepEqual({ dummy: '1 data' }); + + // Next call to getConcurrentJobs() should work as expected + const fourthConcurrentJobs = await queue.getConcurrentJobs(); + fourthConcurrentJobs.length.should.equal(1); + JSON.parse(fourthConcurrentJobs[0].payload).should.deepEqual({ dummy: '3 data' }); + + // Next call to getConcurrentJobs() should be the last of the non-active jobs. + const fifthConcurrentJobs = await queue.getConcurrentJobs(); + fifthConcurrentJobs.length.should.equal(1); + JSON.parse(fifthConcurrentJobs[0].payload).should.deepEqual({ random: 'this is 4th random data' }); + + // Next call to getConcurrentJobs() should return an empty array. + const sixthConcurrentJobs = await queue.getConcurrentJobs(); + sixthConcurrentJobs.length.should.equal(0); - queue.addWorker(jobName, () => {}, { - concurrency: 3 - }); - queue.addWorker('a-different-job', () => {}, { - concurrency: 2 }); - // Create a couple jobs - queue.createJob(jobName, { random: 'this is 1st random data' }, jobOptions, false); - queue.createJob('a-different-job', { dummy: '1 data' }, { priority: 3 }, false); - queue.createJob(jobName, { random: 'this is 2nd random data' }, jobOptions, false); - queue.createJob('a-different-job', { dummy: '2 data' }, { priority: 5 }, false); - queue.createJob('a-different-job', { dummy: '3 data' }, { priority: 3 }, false); - queue.createJob(jobName, { random: 'this is 3rd random data' }, jobOptions, false); - queue.createJob(jobName, { random: 'this is 4th random data' }, jobOptions, false); - - const concurrentJobs = await queue.getConcurrentJobs(); - - // Verify correct jobs retrieved. - // 'a-different-job' should be the jobs returned since job with payload "2 data" has highest priority - // since the other 'a-different-job' jobs have same priority, "1 data" should get preference for 2nd concurrent job due - // to timestamp order. - concurrentJobs.length.should.equal(2); - JSON.parse(concurrentJobs[0].payload).should.deepEqual({ dummy: '2 data' }); - JSON.parse(concurrentJobs[1].payload).should.deepEqual({ dummy: '1 data' }); - - // Ensure that other jobs also got created, but not returned by getConcurrentJobs(). - const jobs = await queue.getJobs(true); - jobs.length.should.equal(7); - - }); + it('should set nextValidTime for job on failure which is retryDelay after now', async () => { + const queue = await QueueFactory(); + const jobName = 'job-name'; - it('#getConcurrentJobs() Marks selected jobs as "active"', async () => { - - const queue = await QueueFactory(); - const jobName = 'job-name'; - const jobOptions = { priority: 0, timeout: 3000, attempts: 3}; - - queue.addWorker(jobName, () => {}, { - concurrency: 3 - }); - queue.addWorker('a-different-job', () => {}, { - concurrency: 2 - }); + queue.addWorker(jobName, async () => { + throw new Error('fail'); + }, { + concurrency: 1 + }); - // Create a couple jobs - queue.createJob(jobName, { random: 'this is 1st random data' }, jobOptions, false); - queue.createJob('a-different-job', { dummy: '1 data' }, { priority: 3 }, false); - queue.createJob(jobName, { random: 'this is 2nd random data' }, jobOptions, false); - queue.createJob('a-different-job', { dummy: '2 data' }, { priority: 5 }, false); - queue.createJob('a-different-job', { dummy: '3 data' }, { priority: 3 }, false); - queue.createJob(jobName, { random: 'this is 3rd random data' }, jobOptions, false); - queue.createJob(jobName, { random: 'this is 4th random data' }, jobOptions, false); - - // Jobs returned by getConcurrentJobs() are marked "active" so they won't be returned by future getConcurrentJobs() calls. - const concurrentJobs = await queue.getConcurrentJobs(); - - // Get all the jobs in the DB and check that the "concurrentJobs" are marked "active." - const jobs = await queue.getJobs(true); - jobs.length.should.equal(7); + queue.createJob(jobName, {}, { + attempts: 2, + timeout: 250, + retryDelay: 2000, + }, false); - const activeJobs = jobs.filter( job => job.active); - activeJobs.length.should.equal(2); - JSON.parse(concurrentJobs[0].payload).should.deepEqual({ dummy: '2 data' }); - JSON.parse(concurrentJobs[1].payload).should.deepEqual({ dummy: '1 data' }); + const now = Date.now(); + await queue.start(1500); + await wait(1500); - }); + const jobs = await queue.getJobs(true); + const job = jobs[0]; - it('#getConcurrentJobs() consecutive calls to getConcurrentJobs() gets new non-active jobs (and marks them active).', async () => { + const jobData = JSON.parse(job.data); + jobData.failedAttempts.should.equal(1); + should.not.exist(job.failed); + moment(job.nextValidTime).subtract(now).should.be.greaterThan(1000); + queue.flushQueue(); + }); - const queue = await QueueFactory(); - const jobName = 'job-name'; - const jobOptions = { priority: 0, timeout: 3000, attempts: 3}; + const addFailedAttemptCount = async (realm, jobs, failedCount, nextValidTime = new Date()) => { + const job = jobs[0]; + const jobData = JSON.parse(job.data); + realm.write(() => { + job.nextValidTime = nextValidTime; + jobData.failedAttempts = failedCount; + job.data = JSON.stringify(jobData); + }); + }; - queue.addWorker(jobName, () => {}, { - concurrency: 3 - }); - queue.addWorker('a-different-job', () => {}, { - concurrency: 1 - }); + const addFailedStatus = async (realm, jobs) => { + const job = jobs[0]; + realm.write(() => { + job.failed = new Date(); + }); + }; - // Create a couple jobs - queue.createJob(jobName, { random: 'this is 1st random data' }, jobOptions, false); - queue.createJob('a-different-job', { dummy: '1 data' }, { priority: 3 }, false); - queue.createJob(jobName, { random: 'this is 2nd random data' }, { priority: 4 }, false); - queue.createJob('a-different-job', { dummy: '2 data' }, { priority: 5 }, false); - queue.createJob('a-different-job', { dummy: '3 data' }, { priority: 3 }, false); - queue.createJob(jobName, { random: 'this is 3rd random data' }, jobOptions, false); - queue.createJob(jobName, { random: 'this is 4th random data' }, jobOptions, false); - - // Jobs returned by getConcurrentJobs() are marked "active" so they won't be returned by future getConcurrentJobs() calls. - const concurrentJobs = await queue.getConcurrentJobs(); - - // Get all the jobs in the DB and check that the "concurrentJobs" are marked "active." - const jobs = await queue.getJobs(true); - jobs.length.should.equal(7); - - const activeJobs = jobs.filter( job => job.active); - activeJobs.length.should.equal(1); - JSON.parse(concurrentJobs[0].payload).should.deepEqual({ dummy: '2 data' }); - - // Next call to getConcurrentJobs() should get the next jobs of the top of the queue as expected - // Next job in line should be type of job, then grab all the concurrents of that type and mark them active. - const moreConcurrentJobs = await queue.getConcurrentJobs(); - moreConcurrentJobs.length.should.equal(3); - JSON.parse(moreConcurrentJobs[0].payload).should.deepEqual({ random: 'this is 2nd random data' }); - JSON.parse(moreConcurrentJobs[1].payload).should.deepEqual({ random: 'this is 1st random data' }); - JSON.parse(moreConcurrentJobs[2].payload).should.deepEqual({ random: 'this is 3rd random data' }); - - // Now we should have 4 active jobs... - const allJobsAgain = await queue.getJobs(true); - const nextActiveJobs = allJobsAgain.filter( job => job.active); - nextActiveJobs.length.should.equal(4); - - // Next call to getConcurrentJobs() should work as expected - const thirdConcurrentJobs = await queue.getConcurrentJobs(); - thirdConcurrentJobs.length.should.equal(1); - JSON.parse(thirdConcurrentJobs[0].payload).should.deepEqual({ dummy: '1 data' }); - - // Next call to getConcurrentJobs() should work as expected - const fourthConcurrentJobs = await queue.getConcurrentJobs(); - fourthConcurrentJobs.length.should.equal(1); - JSON.parse(fourthConcurrentJobs[0].payload).should.deepEqual({ dummy: '3 data' }); - - // Next call to getConcurrentJobs() should be the last of the non-active jobs. - const fifthConcurrentJobs = await queue.getConcurrentJobs(); - fifthConcurrentJobs.length.should.equal(1); - JSON.parse(fifthConcurrentJobs[0].payload).should.deepEqual({ random: 'this is 4th random data' }); - - // Next call to getConcurrentJobs() should return an empty array. - const sixthConcurrentJobs = await queue.getConcurrentJobs(); - sixthConcurrentJobs.length.should.equal(0); + const createAndTestJob = async (nextValidTime, failed, expectedNumberOfReturn) => { + const queue = await QueueFactory(); + const jobName = 'job-name'; + const realm = await Database.getRealmInstance(); - }); + queue.addWorker(jobName, () => {}, { + concurrency: 1 + }); - it('should set nextValidTime for job on failure which is retryDelay after now', async () => { - const queue = await QueueFactory(); - const jobName = 'job-name'; + queue.createJob(jobName, {}, { + attempts: 2, + timeout: 99, + retryDelay: 100, + }, false); - queue.addWorker(jobName, async () => { - throw new Error('fail'); - }, { - concurrency: 1 - }); + if(nextValidTime) + await addFailedAttemptCount(realm, await queue.getJobs(true),1, nextValidTime); - queue.createJob(jobName, {}, { - attempts: 2, - timeout: 250, - retryDelay: 2000, - }, false); + if(failed) + await addFailedStatus(realm, await queue.getJobs(true)); - const now = Date.now(); - await queue.start(1500); - await wait(1500); + const returnedJobs = await queue.getConcurrentJobs(600); - const jobs = await queue.getJobs(true); - const job = jobs[0]; + returnedJobs.length.should.equal(expectedNumberOfReturn); - const jobData = JSON.parse(job.data); - jobData.failedAttempts.should.equal(1); - should.not.exist(job.failed); - moment(job.nextValidTime).subtract(now).should.be.greaterThan(1000); - queue.flushQueue(); - }); + queue.flushQueue(); + }; - const addFailedAttemptCount = async (realm, jobs, failedCount, nextValidTime = new Date()) => { - const job = jobs[0]; - const jobData = JSON.parse(job.data); - realm.write(() => { - job.nextValidTime = nextValidTime; - jobData.failedAttempts = failedCount; - job.data = JSON.stringify(jobData); + it('#getConcurrentJobs(queueLifespanRemaining) should return jobs with retryDelay set will be returned by getConcurrentJobs() as normal if not failedAttempts.', async () => { + await createAndTestJob(null,false,1); }); - }; - const addFailedStatus = async (realm, jobs) => { - const job = jobs[0]; - realm.write(() => { - job.failed = new Date(); + it('#getConcurrentJobs(queueLifespanRemaining) should not return jobs with nextValidTime in the future.', async () => { + await createAndTestJob(new Date(new Date().getTime() + 1000),false,0); }); - }; - - const createAndTestJob = async (nextValidTime, failed, expectedNumberOfReturn) => { - const queue = await QueueFactory(); - const jobName = 'job-name'; - const realm = await Database.getRealmInstance(); - queue.addWorker(jobName, () => {}, { - concurrency: 1 + it('#getConcurrentJobs(queueLifespanRemaining) should return jobs with nextValidTime in the past.', async () => { + await createAndTestJob(new Date(new Date().getTime() - 1000),false,1); }); - queue.createJob(jobName, {}, { - attempts: 2, - timeout: 99, - retryDelay: 100, - }, false); - - if(nextValidTime) - await addFailedAttemptCount(realm, await queue.getJobs(true),1, nextValidTime); + it('#getConcurrentJobs(queueLifespanRemaining) should return jobs with nextValidTime now.', async () => { + await createAndTestJob(new Date(),false,1); + }); - if(failed) - await addFailedStatus(realm, await queue.getJobs(true)); + it('#getConcurrentJobs(queueLifespanRemaining) should not return failed jobs.', async () => { + await createAndTestJob(new Date(new Date().getTime() - 1000),true,0); + }); - const returnedJobs = await queue.getConcurrentJobs(600); + it('#processJob() executes job worker then deletes job on success', async () => { - returnedJobs.length.should.equal(expectedNumberOfReturn); + const queue = await QueueFactory(); + const jobName = 'job-name'; + const jobOptions = { priority: 0, timeout: 3000, attempts: 3}; - queue.flushQueue(); - }; + let counter = 0; // Incrementing this will be our job "work" - it('#getConcurrentJobs(queueLifespanRemaining) should return jobs with retryDelay set will be returned by getConcurrentJobs() as normal if not failedAttempts.', async () => { - await createAndTestJob(null,false,1); - }); + queue.addWorker(jobName, () => {}, { + concurrency: 3 + }); + queue.addWorker('a-different-job', () => { + counter++; + }, { + concurrency: 1 + }); - it('#getConcurrentJobs(queueLifespanRemaining) should not return jobs with nextValidTime in the future.', async () => { - await createAndTestJob(new Date(new Date().getTime() + 1000),false,0); - }); + // Create a couple jobs + queue.createJob(jobName, { random: 'this is 1st random data' }, jobOptions, false); + queue.createJob('a-different-job', { dummy: '1 data' }, { priority: 3 }, false); + queue.createJob(jobName, { random: 'this is 2nd random data' }, jobOptions, false); + queue.createJob('a-different-job', { dummy: '2 data' }, { priority: 5 }, false); + queue.createJob('a-different-job', { dummy: '3 data' }, { priority: 3 }, false); + queue.createJob(jobName, { random: 'this is 3rd random data' }, jobOptions, false); + queue.createJob(jobName, { random: 'this is 4th random data' }, jobOptions, false); + + // Jobs returned by getConcurrentJobs() are marked "active" so they won't be returned by future getConcurrentJobs() calls. + const concurrentJobs = await queue.getConcurrentJobs(); + concurrentJobs.length.should.equal(1); + JSON.parse(concurrentJobs[0].payload).should.deepEqual({ dummy: '2 data' }); + + // Process the job + await queue.processJob(concurrentJobs[0]); + + // Ensure job work was performed. + counter.should.equal(1); + + // Ensure completed job has been removed. + const jobs = await queue.getJobs(true); + jobs.length.should.equal(6); + + const jobExists = jobs.reduce((exists, job) => { + const payload = JSON.parse(job.payload); + if (payload.dummy && payload.dummy == '2 data') { + exists = true; + } + return exists; + }, false); - it('#getConcurrentJobs(queueLifespanRemaining) should return jobs with nextValidTime in the past.', async () => { - await createAndTestJob(new Date(new Date().getTime() - 1000),false,1); - }); + jobExists.should.be.False(); - it('#getConcurrentJobs(queueLifespanRemaining) should return jobs with nextValidTime now.', async () => { - await createAndTestJob(new Date(),false,1); - }); + }); - it('#getConcurrentJobs(queueLifespanRemaining) should not return failed jobs.', async () => { - await createAndTestJob(new Date(new Date().getTime() - 1000),true,0); - }); + it('#processJob() increments failedAttempts counter until max attempts then fails on job failure.', async () => { - it('#processJob() executes job worker then deletes job on success', async () => { + const queue = await QueueFactory(); + queue.flushQueue(); + const jobName = 'job-name'; + const jobOptions = { priority: 0, timeout: 3000, attempts: 3}; - const queue = await QueueFactory(); - const jobName = 'job-name'; - const jobOptions = { priority: 0, timeout: 3000, attempts: 3}; + let counter = 0; // Incrementing this will be our job "work" - let counter = 0; // Incrementing this will be our job "work" + queue.addWorker(jobName, () => {}, { + concurrency: 3 + }); + queue.addWorker('a-different-job', (id, payload) => { - queue.addWorker(jobName, () => {}, { - concurrency: 3 - }); - queue.addWorker('a-different-job', () => { - counter++; - }, { - concurrency: 1 - }); + if (payload.dummy && payload.dummy == '2 data') { + throw new Error('Fake job failure!'); + } - // Create a couple jobs - queue.createJob(jobName, { random: 'this is 1st random data' }, jobOptions, false); - queue.createJob('a-different-job', { dummy: '1 data' }, { priority: 3 }, false); - queue.createJob(jobName, { random: 'this is 2nd random data' }, jobOptions, false); - queue.createJob('a-different-job', { dummy: '2 data' }, { priority: 5 }, false); - queue.createJob('a-different-job', { dummy: '3 data' }, { priority: 3 }, false); - queue.createJob(jobName, { random: 'this is 3rd random data' }, jobOptions, false); - queue.createJob(jobName, { random: 'this is 4th random data' }, jobOptions, false); - - // Jobs returned by getConcurrentJobs() are marked "active" so they won't be returned by future getConcurrentJobs() calls. - const concurrentJobs = await queue.getConcurrentJobs(); - concurrentJobs.length.should.equal(1); - JSON.parse(concurrentJobs[0].payload).should.deepEqual({ dummy: '2 data' }); - - // Process the job - await queue.processJob(concurrentJobs[0]); - - // Ensure job work was performed. - counter.should.equal(1); - - // Ensure completed job has been removed. - const jobs = await queue.getJobs(true); - jobs.length.should.equal(6); + counter++; - const jobExists = jobs.reduce((exists, job) => { - const payload = JSON.parse(job.payload); - if (payload.dummy && payload.dummy == '2 data') { - exists = true; - } - return exists; - }, false); + }, { + concurrency: 2 + }); - jobExists.should.be.False(); + // Create a couple jobs + queue.createJob(jobName, { random: 'this is 1st random data' }, jobOptions, false); + queue.createJob('a-different-job', { dummy: '1 data' }, { priority: 3 }, false); + queue.createJob(jobName, { random: 'this is 2nd random data' }, { priority: 1, timeout: 3000, attempts: 3}, false); + queue.createJob('a-different-job', { dummy: '2 data' }, { priority: 5, attempts: 3 }, false); + queue.createJob('a-different-job', { dummy: '3 data' }, { priority: 3 }, false); + queue.createJob(jobName, { random: 'this is 3rd random data' }, jobOptions, false); + queue.createJob(jobName, { random: 'this is 4th random data' }, jobOptions, false); + + // Jobs returned by getConcurrentJobs() are marked "active" so they won't be returned by future getConcurrentJobs() calls. + const concurrentJobs = await queue.getConcurrentJobs(); + concurrentJobs.length.should.equal(2); + JSON.parse(concurrentJobs[0].payload).should.deepEqual({ dummy: '2 data' }); + JSON.parse(concurrentJobs[1].payload).should.deepEqual({ dummy: '1 data' }); + + // Process the jobs + await Promise.all([queue.processJob(concurrentJobs[0]), queue.processJob(concurrentJobs[1])]); + + // Ensure job work was performed by ONE job (first should have failed). + counter.should.equal(1); + + // Ensure other job was deleted on job completion and ensure failedAttempts incremented on failed job. + const jobs = await queue.getJobs(true); + jobs.length.should.equal(6); + let failedJob = jobs.find((job) => { + const payload = JSON.parse(job.payload); + return (payload.dummy && payload.dummy == '2 data'); + }); + let failedJobData = JSON.parse(failedJob.data); + failedJobData.failedAttempts.should.equal(1); - }); - it('#processJob() increments failedAttempts counter until max attempts then fails on job failure.', async () => { + // Next getConcurrentJobs() batch should get 2 jobs again, the failed job and remaining job of this job type. + const secondConcurrentJobs = await queue.getConcurrentJobs(); + secondConcurrentJobs.length.should.equal(2); + JSON.parse(secondConcurrentJobs[0].payload).should.deepEqual({ dummy: '2 data' }); + JSON.parse(secondConcurrentJobs[1].payload).should.deepEqual({ dummy: '3 data' }); - const queue = await QueueFactory(); - queue.flushQueue(); - const jobName = 'job-name'; - const jobOptions = { priority: 0, timeout: 3000, attempts: 3}; + // Process the jobs + await Promise.all([queue.processJob(secondConcurrentJobs[0]), queue.processJob(secondConcurrentJobs[1])]); - let counter = 0; // Incrementing this will be our job "work" + // Ensure more job work was performed by ONE job (first should have failed). + counter.should.equal(2); - queue.addWorker(jobName, () => {}, { - concurrency: 3 - }); - queue.addWorker('a-different-job', (id, payload) => { + // Ensure other job was deleted on job completion and ensure failedAttempts incremented again on failed job. + const secondJobs = await queue.getJobs(true); + secondJobs.length.should.equal(5); + failedJob = secondJobs.find((job) => { + const payload = JSON.parse(job.payload); + return (payload.dummy && payload.dummy == '2 data'); + }); + failedJobData = JSON.parse(failedJob.data); + failedJobData.failedAttempts.should.equal(2); + + // Next getConcurrentJobs() batch should should get the one remaining job of this type that can fail one more time. + const thirdConcurrentJobs = await queue.getConcurrentJobs(); + thirdConcurrentJobs.length.should.equal(1); + JSON.parse(thirdConcurrentJobs[0].payload).should.deepEqual({ dummy: '2 data' }); + + // Process the jobs + await queue.processJob(thirdConcurrentJobs[0]); + + // Ensure new job work didn't happen because this job failed a 3rd time. + counter.should.equal(2); + + // Ensure other job was deleted on job completion and ensure failedAttempts incremented again on failed job. + const thirdJobs = await queue.getJobs(true); + thirdJobs.length.should.equal(5); // Failed job still exists, it is just marked as failure. + failedJob = thirdJobs.find((job) => { + const payload = JSON.parse(job.payload); + return (payload.dummy && payload.dummy == '2 data'); + }); + failedJobData = JSON.parse(failedJob.data); + failedJobData.failedAttempts.should.equal(3); - if (payload.dummy && payload.dummy == '2 data') { - throw new Error('Fake job failure!'); - } + // Ensure job marked as failed. + failedJob.failed.should.be.a.Date(); - counter++; + // Next getConcurrentJobs() should now finally return 'job-name' type jobs. + const fourthConcurrentJobs = await queue.getConcurrentJobs(); + fourthConcurrentJobs.length.should.equal(3); - }, { - concurrency: 2 }); - // Create a couple jobs - queue.createJob(jobName, { random: 'this is 1st random data' }, jobOptions, false); - queue.createJob('a-different-job', { dummy: '1 data' }, { priority: 3 }, false); - queue.createJob(jobName, { random: 'this is 2nd random data' }, { priority: 1, timeout: 3000, attempts: 3}, false); - queue.createJob('a-different-job', { dummy: '2 data' }, { priority: 5, attempts: 3 }, false); - queue.createJob('a-different-job', { dummy: '3 data' }, { priority: 3 }, false); - queue.createJob(jobName, { random: 'this is 3rd random data' }, jobOptions, false); - queue.createJob(jobName, { random: 'this is 4th random data' }, jobOptions, false); - - // Jobs returned by getConcurrentJobs() are marked "active" so they won't be returned by future getConcurrentJobs() calls. - const concurrentJobs = await queue.getConcurrentJobs(); - concurrentJobs.length.should.equal(2); - JSON.parse(concurrentJobs[0].payload).should.deepEqual({ dummy: '2 data' }); - JSON.parse(concurrentJobs[1].payload).should.deepEqual({ dummy: '1 data' }); - - // Process the jobs - await Promise.all([queue.processJob(concurrentJobs[0]), queue.processJob(concurrentJobs[1])]); - - // Ensure job work was performed by ONE job (first should have failed). - counter.should.equal(1); - - // Ensure other job was deleted on job completion and ensure failedAttempts incremented on failed job. - const jobs = await queue.getJobs(true); - jobs.length.should.equal(6); - let failedJob = jobs.find((job) => { - const payload = JSON.parse(job.payload); - return (payload.dummy && payload.dummy == '2 data'); - }); - let failedJobData = JSON.parse(failedJob.data); - failedJobData.failedAttempts.should.equal(1); + it('#processJob() logs errors on job failure', async () => { + const queue = await QueueFactory(); + const jobName = 'job-name'; + const jobOptions = { priority: 0, timeout: 5000, attempts: 3}; - // Next getConcurrentJobs() batch should get 2 jobs again, the failed job and remaining job of this job type. - const secondConcurrentJobs = await queue.getConcurrentJobs(); - secondConcurrentJobs.length.should.equal(2); - JSON.parse(secondConcurrentJobs[0].payload).should.deepEqual({ dummy: '2 data' }); - JSON.parse(secondConcurrentJobs[1].payload).should.deepEqual({ dummy: '3 data' }); + let counter = 0; // Incrementing this will be our job "work" - // Process the jobs - await Promise.all([queue.processJob(secondConcurrentJobs[0]), queue.processJob(secondConcurrentJobs[1])]); + queue.addWorker(jobName, () => { - // Ensure more job work was performed by ONE job (first should have failed). - counter.should.equal(2); + counter++; - // Ensure other job was deleted on job completion and ensure failedAttempts incremented again on failed job. - const secondJobs = await queue.getJobs(true); - secondJobs.length.should.equal(5); - failedJob = secondJobs.find((job) => { - const payload = JSON.parse(job.payload); - return (payload.dummy && payload.dummy == '2 data'); - }); - failedJobData = JSON.parse(failedJob.data); - failedJobData.failedAttempts.should.equal(2); - - // Next getConcurrentJobs() batch should should get the one remaining job of this type that can fail one more time. - const thirdConcurrentJobs = await queue.getConcurrentJobs(); - thirdConcurrentJobs.length.should.equal(1); - JSON.parse(thirdConcurrentJobs[0].payload).should.deepEqual({ dummy: '2 data' }); - - // Process the jobs - await queue.processJob(thirdConcurrentJobs[0]); - - // Ensure new job work didn't happen because this job failed a 3rd time. - counter.should.equal(2); - - // Ensure other job was deleted on job completion and ensure failedAttempts incremented again on failed job. - const thirdJobs = await queue.getJobs(true); - thirdJobs.length.should.equal(5); // Failed job still exists, it is just marked as failure. - failedJob = thirdJobs.find((job) => { - const payload = JSON.parse(job.payload); - return (payload.dummy && payload.dummy == '2 data'); - }); - failedJobData = JSON.parse(failedJob.data); - failedJobData.failedAttempts.should.equal(3); + throw new Error('Example Error number: ' + counter); - // Ensure job marked as failed. - failedJob.failed.should.be.a.Date(); + }, {}); - // Next getConcurrentJobs() should now finally return 'job-name' type jobs. - const fourthConcurrentJobs = await queue.getConcurrentJobs(); - fourthConcurrentJobs.length.should.equal(3); + queue.createJob(jobName, {}, jobOptions, false); - }); + const jobs = await queue.getConcurrentJobs(); - it('#processJob() logs errors on job failure', async () => { + await queue.processJob(jobs[0]); - const queue = await QueueFactory(); - const jobName = 'job-name'; - const jobOptions = { priority: 0, timeout: 5000, attempts: 3}; + const logCheckOneJob = await queue.getJobs(true); - let counter = 0; // Incrementing this will be our job "work" + logCheckOneJob[0].data.should.equal(JSON.stringify({ + attempts: 3, + failedAttempts: 1, + errors: ['Example Error number: 1'] + })); - queue.addWorker(jobName, () => { + await queue.processJob(jobs[0]); - counter++; + const logCheckTwoJob = await queue.getJobs(true); - throw new Error('Example Error number: ' + counter); + logCheckTwoJob[0].data.should.equal(JSON.stringify({ + attempts: 3, + failedAttempts: 2, + errors: ['Example Error number: 1', 'Example Error number: 2'] + })); - }, {}); + await queue.processJob(jobs[0]); - queue.createJob(jobName, {}, jobOptions, false); + const logCheckThreeJob = await queue.getJobs(true); - const jobs = await queue.getConcurrentJobs(); + logCheckThreeJob[0].data.should.equal(JSON.stringify({ + attempts: 3, + failedAttempts: 3, + errors: ['Example Error number: 1', 'Example Error number: 2', 'Example Error number: 3'] + })); - await queue.processJob(jobs[0]); + const noAvailableJobCheck = await queue.getConcurrentJobs(); - const logCheckOneJob = await queue.getJobs(true); + noAvailableJobCheck.length.should.equal(0); - logCheckOneJob[0].data.should.equal(JSON.stringify({ - attempts: 3, - failedAttempts: 1, - errors: ['Example Error number: 1'] - })); + }); - await queue.processJob(jobs[0]); + it('#processJob() handles a job timeout as expected', async () => { - const logCheckTwoJob = await queue.getJobs(true); + const queue = await QueueFactory(); + const jobName = 'job-name'; + const jobOptions = { priority: 0, timeout: 500, attempts: 1}; - logCheckTwoJob[0].data.should.equal(JSON.stringify({ - attempts: 3, - failedAttempts: 2, - errors: ['Example Error number: 1', 'Example Error number: 2'] - })); + queue.addWorker(jobName, async () => { - await queue.processJob(jobs[0]); + await new Promise((resolve) => { + setTimeout(() => { + resolve(true); + }, 2000); + }); - const logCheckThreeJob = await queue.getJobs(true); + }); - logCheckThreeJob[0].data.should.equal(JSON.stringify({ - attempts: 3, - failedAttempts: 3, - errors: ['Example Error number: 1', 'Example Error number: 2', 'Example Error number: 3'] - })); + queue.createJob(jobName, {}, jobOptions, false); - const noAvailableJobCheck = await queue.getConcurrentJobs(); + const jobs = await queue.getConcurrentJobs(); - noAvailableJobCheck.length.should.equal(0); + const jobId = jobs[0].id; - }); + await queue.processJob(jobs[0]); - it('#processJob() handles a job timeout as expected', async () => { + const logCheckOneJob = await queue.getJobs(true); - const queue = await QueueFactory(); - const jobName = 'job-name'; - const jobOptions = { priority: 0, timeout: 500, attempts: 1}; + logCheckOneJob[0].data.should.equal(JSON.stringify({ + attempts: 1, + failedAttempts: 1, + errors: ['TIMEOUT: Job id: '+ jobId +' timed out in 500ms.'] + })); - queue.addWorker(jobName, async () => { + const noAvailableJobCheck = await queue.getConcurrentJobs(); - await new Promise((resolve) => { - setTimeout(() => { - resolve(true); - }, 2000); - }); + noAvailableJobCheck.length.should.equal(0); }); - queue.createJob(jobName, {}, jobOptions, false); - - const jobs = await queue.getConcurrentJobs(); + it('#flushQueue(name) should delete all jobs in the queue of type "name".', async () => { - const jobId = jobs[0].id; + const queue = await QueueFactory(); + const jobName = 'job-name'; + const jobOptions = { priority: 0, timeout: 3000, attempts: 3}; - await queue.processJob(jobs[0]); - - const logCheckOneJob = await queue.getJobs(true); + queue.addWorker(jobName, () => {}, { + concurrency: 3 + }); + queue.addWorker('a-different-job', () => {}, { + concurrency: 1 + }); - logCheckOneJob[0].data.should.equal(JSON.stringify({ - attempts: 1, - failedAttempts: 1, - errors: ['TIMEOUT: Job id: '+ jobId +' timed out in 500ms.'] - })); + // Create a couple jobs + queue.createJob(jobName, { random: 'this is 1st random data' }, jobOptions, false); + queue.createJob('a-different-job', { dummy: '1 data' }, { priority: 3 }, false); + queue.createJob(jobName, { random: 'this is 2nd random data' }, { priority: 4 }, false); + queue.createJob('a-different-job', { dummy: '2 data' }, { priority: 5 }, false); + queue.createJob('a-different-job', { dummy: '3 data' }, { priority: 3 }, false); + queue.createJob(jobName, { random: 'this is 3rd random data' }, jobOptions, false); + queue.createJob(jobName, { random: 'this is 4th random data' }, jobOptions, false); - const noAvailableJobCheck = await queue.getConcurrentJobs(); + // Check all jobs created + const jobs = await queue.getJobs(true); + jobs.length.should.equal(7); - noAvailableJobCheck.length.should.equal(0); + queue.flushQueue(jobName); - }); + // Remaining 3 jobs should be of type 'a-different-job' + const remainingJobs = await queue.getJobs(true); + remainingJobs.length.should.equal(3); - it('#flushQueue(name) should delete all jobs in the queue of type "name".', async () => { + const jobNameTypeExist = remainingJobs.reduce((exists, job) => { + if (job.name == jobName) { + exists = true; + } + return exists; + }, false); - const queue = await QueueFactory(); - const jobName = 'job-name'; - const jobOptions = { priority: 0, timeout: 3000, attempts: 3}; + jobNameTypeExist.should.be.False(); - queue.addWorker(jobName, () => {}, { - concurrency: 3 }); - queue.addWorker('a-different-job', () => {}, { - concurrency: 1 - }); - - // Create a couple jobs - queue.createJob(jobName, { random: 'this is 1st random data' }, jobOptions, false); - queue.createJob('a-different-job', { dummy: '1 data' }, { priority: 3 }, false); - queue.createJob(jobName, { random: 'this is 2nd random data' }, { priority: 4 }, false); - queue.createJob('a-different-job', { dummy: '2 data' }, { priority: 5 }, false); - queue.createJob('a-different-job', { dummy: '3 data' }, { priority: 3 }, false); - queue.createJob(jobName, { random: 'this is 3rd random data' }, jobOptions, false); - queue.createJob(jobName, { random: 'this is 4th random data' }, jobOptions, false); - - // Check all jobs created - const jobs = await queue.getJobs(true); - jobs.length.should.equal(7); - queue.flushQueue(jobName); + it('#flushQueue() should delete all jobs in the queue.', async () => { - // Remaining 3 jobs should be of type 'a-different-job' - const remainingJobs = await queue.getJobs(true); - remainingJobs.length.should.equal(3); + const queue = await QueueFactory(); + const jobName = 'job-name'; + const jobOptions = { priority: 0, timeout: 3000, attempts: 3}; - const jobNameTypeExist = remainingJobs.reduce((exists, job) => { - if (job.name == jobName) { - exists = true; - } - return exists; - }, false); + queue.addWorker(jobName, () => {}, { + concurrency: 3 + }); + queue.addWorker('a-different-job', () => {}, { + concurrency: 1 + }); - jobNameTypeExist.should.be.False(); + // Create a couple jobs + queue.createJob(jobName, { random: 'this is 1st random data' }, jobOptions, false); + queue.createJob('a-different-job', { dummy: '1 data' }, { priority: 3 }, false); + queue.createJob(jobName, { random: 'this is 2nd random data' }, { priority: 4 }, false); + queue.createJob('a-different-job', { dummy: '2 data' }, { priority: 5 }, false); + queue.createJob('a-different-job', { dummy: '3 data' }, { priority: 3 }, false); + queue.createJob(jobName, { random: 'this is 3rd random data' }, jobOptions, false); + queue.createJob(jobName, { random: 'this is 4th random data' }, jobOptions, false); - }); + // Check all jobs created + const jobs = await queue.getJobs(true); + jobs.length.should.equal(7); - it('#flushQueue() should delete all jobs in the queue.', async () => { + queue.flushQueue(); - const queue = await QueueFactory(); - const jobName = 'job-name'; - const jobOptions = { priority: 0, timeout: 3000, attempts: 3}; + // All jobs should be deleted. + const remainingJobs = await queue.getJobs(true); + remainingJobs.length.should.equal(0); - queue.addWorker(jobName, () => {}, { - concurrency: 3 }); - queue.addWorker('a-different-job', () => {}, { - concurrency: 1 - }); - - // Create a couple jobs - queue.createJob(jobName, { random: 'this is 1st random data' }, jobOptions, false); - queue.createJob('a-different-job', { dummy: '1 data' }, { priority: 3 }, false); - queue.createJob(jobName, { random: 'this is 2nd random data' }, { priority: 4 }, false); - queue.createJob('a-different-job', { dummy: '2 data' }, { priority: 5 }, false); - queue.createJob('a-different-job', { dummy: '3 data' }, { priority: 3 }, false); - queue.createJob(jobName, { random: 'this is 3rd random data' }, jobOptions, false); - queue.createJob(jobName, { random: 'this is 4th random data' }, jobOptions, false); - - // Check all jobs created - const jobs = await queue.getJobs(true); - jobs.length.should.equal(7); - queue.flushQueue(); + it('#flushQueue(name) does not bother with delete query if no jobs exist already.', async () => { - // All jobs should be deleted. - const remainingJobs = await queue.getJobs(true); - remainingJobs.length.should.equal(0); + const queue = await QueueFactory(); - }); + // Mock queue.realm.delete() so we can test that it has not been called. + let hasDeleteBeenCalled = false; + queue.realm.delete = () => { + hasDeleteBeenCalled = true; // Switch flag if function gets called. + }; - it('#flushQueue(name) does not bother with delete query if no jobs exist already.', async () => { + queue.flushQueue('no-jobs-exist-for-this-job-name'); - const queue = await QueueFactory(); + hasDeleteBeenCalled.should.be.False(); - // Mock queue.realm.delete() so we can test that it has not been called. - let hasDeleteBeenCalled = false; - queue.realm.delete = () => { - hasDeleteBeenCalled = true; // Switch flag if function gets called. - }; + }); - queue.flushQueue('no-jobs-exist-for-this-job-name'); + //// + //// JOB LIFECYCLE CALLBACK TESTING + //// - hasDeleteBeenCalled.should.be.False(); + it('onStart lifecycle callback fires before job begins processing.', async () => { - }); + // This test will intermittently fail in CI environments like travis-ci. + // Intermittent failure is a result of the poor performance of CI environments + // causing the timeouts in this test to become really flakey (setTimeout can't + // guarantee exact time of function execution, and in a high load env execution can + // be significantly delayed. + if (process.env.COVERALLS_ENV == 'production') { + return true; + } - //// - //// JOB LIFECYCLE CALLBACK TESTING - //// + const queue = await QueueFactory(); + queue.flushQueue(); + const jobName = 'job-name'; + let jobProcessed = false; + let testFailed = false; - it('onStart lifecycle callback fires before job begins processing.', async () => { + queue.addWorker(jobName, async () => { - // This test will intermittently fail in CI environments like travis-ci. - // Intermittent failure is a result of the poor performance of CI environments - // causing the timeouts in this test to become really flakey (setTimeout can't - // guarantee exact time of function execution, and in a high load env execution can - // be significantly delayed. - if (process.env.COVERALLS_ENV == 'production') { - return true; - } + // Timeout needed because onStart runs async so we need to ensure this function gets + // executed last. + await new Promise((resolve) => { + setTimeout(() => { + jobProcessed = true; + resolve(); + }, 0); + }); - const queue = await QueueFactory(); - queue.flushQueue(); - const jobName = 'job-name'; - let jobProcessed = false; - let testFailed = false; + }, { + onStart: () => { - queue.addWorker(jobName, async () => { + // If onStart runs after job has processed, fail test. + if (jobProcessed) { + testFailed = true; + throw new Error('ERROR: onStart fired after job began processing.'); + } - // Timeout needed because onStart runs async so we need to ensure this function gets - // executed last. - await new Promise((resolve) => { - setTimeout(() => { - jobProcessed = true; - resolve(); - }, 0); + } }); - }, { - onStart: () => { + // Create a job + queue.createJob(jobName, { random: 'this is 1st random data' }, {}, false); - // If onStart runs after job has processed, fail test. - if (jobProcessed) { - testFailed = true; - throw new Error('ERROR: onStart fired after job began processing.'); - } + jobProcessed.should.equal(false); + testFailed.should.equal(false); + await queue.start(); + jobProcessed.should.equal(true); + testFailed.should.equal(false); - } }); - // Create a job - queue.createJob(jobName, { random: 'this is 1st random data' }, {}, false); + it('onSuccess, onComplete lifecycle callbacks fire after job begins processing.', async () => { - jobProcessed.should.equal(false); - testFailed.should.equal(false); - await queue.start(); - jobProcessed.should.equal(true); - testFailed.should.equal(false); + // This test will intermittently fail in CI environments like travis-ci. + // Intermittent failure is a result of the poor performance of CI environments + // causing the timeouts in this test to become really flakey (setTimeout can't + // guarantee exact time of function execution, and in a high load env execution can + // be significantly delayed. + if (process.env.COVERALLS_ENV == 'production') { + return true; + } - }); + const queue = await QueueFactory(); + queue.flushQueue(); + const jobName = 'job-name'; + let jobProcessed = false; + let testFailed = false; + let onSuccessFired = false; + let onCompleteFired = false; - it('onSuccess, onComplete lifecycle callbacks fire after job begins processing.', async () => { + queue.addWorker(jobName, async () => { - // This test will intermittently fail in CI environments like travis-ci. - // Intermittent failure is a result of the poor performance of CI environments - // causing the timeouts in this test to become really flakey (setTimeout can't - // guarantee exact time of function execution, and in a high load env execution can - // be significantly delayed. - if (process.env.COVERALLS_ENV == 'production') { - return true; - } + // Simulate work + await new Promise((resolve) => { + setTimeout(() => { + jobProcessed = true; + resolve(); + }, 300); + }); - const queue = await QueueFactory(); - queue.flushQueue(); - const jobName = 'job-name'; - let jobProcessed = false; - let testFailed = false; - let onSuccessFired = false; - let onCompleteFired = false; + }, { + onSuccess: () => { - queue.addWorker(jobName, async () => { + onSuccessFired = true; - // Simulate work - await new Promise((resolve) => { - setTimeout(() => { - jobProcessed = true; - resolve(); - }, 300); - }); + // If onSuccess runs before job has processed, fail test. + if (!jobProcessed) { + testFailed = true; + throw new Error('ERROR: onSuccess fired before job began processing.'); + } - }, { - onSuccess: () => { + }, + onComplete: () => { - onSuccessFired = true; + onCompleteFired = true; - // If onSuccess runs before job has processed, fail test. - if (!jobProcessed) { - testFailed = true; - throw new Error('ERROR: onSuccess fired before job began processing.'); - } + // If onComplete runs before job has processed, fail test. + if (!jobProcessed) { + testFailed = true; + throw new Error('ERROR: onComplete fired before job began processing.'); + } - }, - onComplete: () => { + } + }); - onCompleteFired = true; + // Create a job + queue.createJob(jobName, { random: 'this is 1st random data' }, {}, false); - // If onComplete runs before job has processed, fail test. - if (!jobProcessed) { - testFailed = true; - throw new Error('ERROR: onComplete fired before job began processing.'); - } + jobProcessed.should.equal(false); + testFailed.should.equal(false); + onSuccessFired.should.equal(false); + onCompleteFired.should.equal(false); + await queue.start(); + jobProcessed.should.equal(true); + testFailed.should.equal(false); + onSuccessFired.should.equal(true); + onCompleteFired.should.equal(true); - } }); - // Create a job - queue.createJob(jobName, { random: 'this is 1st random data' }, {}, false); - - jobProcessed.should.equal(false); - testFailed.should.equal(false); - onSuccessFired.should.equal(false); - onCompleteFired.should.equal(false); - await queue.start(); - jobProcessed.should.equal(true); - testFailed.should.equal(false); - onSuccessFired.should.equal(true); - onCompleteFired.should.equal(true); + it('onFailure, onFailed lifecycle callbacks fire after job begins processing.', async () => { - }); + // This test will intermittently fail in CI environments like travis-ci. + // Intermittent failure is a result of the poor performance of CI environments + // causing the timeouts in this test to become really flakey (setTimeout can't + // guarantee exact time of function execution, and in a high load env execution can + // be significantly delayed. + if (process.env.COVERALLS_ENV == 'production') { + return true; + } - it('onFailure, onFailed lifecycle callbacks fire after job begins processing.', async () => { + const queue = await QueueFactory(); + queue.flushQueue(); + const jobName = 'job-name'; + let jobProcessStarted = false; + let testFailed = false; - // This test will intermittently fail in CI environments like travis-ci. - // Intermittent failure is a result of the poor performance of CI environments - // causing the timeouts in this test to become really flakey (setTimeout can't - // guarantee exact time of function execution, and in a high load env execution can - // be significantly delayed. - if (process.env.COVERALLS_ENV == 'production') { - return true; - } + queue.addWorker(jobName, async () => { - const queue = await QueueFactory(); - queue.flushQueue(); - const jobName = 'job-name'; - let jobProcessStarted = false; - let testFailed = false; + // Simulate work + await new Promise((resolve, reject) => { + setTimeout(() => { + jobProcessStarted = true; + reject(new Error('Job failed.')); + }, 300); + }); - queue.addWorker(jobName, async () => { + }, { + onFailure: () => { - // Simulate work - await new Promise((resolve, reject) => { - setTimeout(() => { - jobProcessStarted = true; - reject(new Error('Job failed.')); - }, 300); - }); + // If onFailure runs before job has processed, fail test. + if (!jobProcessStarted) { + testFailed = true; + throw new Error('ERROR: onFailure fired before job began processing.'); + } - }, { - onFailure: () => { + }, + onFailed: () => { + + // If onFailed runs before job has processed, fail test. + if (!jobProcessStarted) { + testFailed = true; + throw new Error('ERROR: onFailed fired before job began processing.'); + } - // If onFailure runs before job has processed, fail test. - if (!jobProcessStarted) { - testFailed = true; - throw new Error('ERROR: onFailure fired before job began processing.'); } + }); - }, - onFailed: () => { + // Create a job + queue.createJob(jobName, { random: 'this is 1st random data' }, {}, false); - // If onFailed runs before job has processed, fail test. - if (!jobProcessStarted) { - testFailed = true; - throw new Error('ERROR: onFailed fired before job began processing.'); - } + jobProcessStarted.should.equal(false); + testFailed.should.equal(false); + await queue.start(); + jobProcessStarted.should.equal(true); + testFailed.should.equal(false); - } }); - // Create a job - queue.createJob(jobName, { random: 'this is 1st random data' }, {}, false); + it('onFailure, onFailed lifecycle callbacks work as expected.', async () => { - jobProcessStarted.should.equal(false); - testFailed.should.equal(false); - await queue.start(); - jobProcessStarted.should.equal(true); - testFailed.should.equal(false); + // This test will intermittently fail in CI environments like travis-ci. + // Intermittent failure is a result of the poor performance of CI environments + // causing the timeouts in this test to become really flakey (setTimeout can't + // guarantee exact time of function execution, and in a high load env execution can + // be significantly delayed. + if (process.env.COVERALLS_ENV == 'production') { + return true; + } - }); + const queue = await QueueFactory(); + queue.flushQueue(); + const jobName = 'job-name'; + let jobAttemptCounter = 0; + let onFailureFiredCounter = 0; + let onFailedFiredCounter = 0; - it('onFailure, onFailed lifecycle callbacks work as expected.', async () => { + queue.addWorker(jobName, async () => { - // This test will intermittently fail in CI environments like travis-ci. - // Intermittent failure is a result of the poor performance of CI environments - // causing the timeouts in this test to become really flakey (setTimeout can't - // guarantee exact time of function execution, and in a high load env execution can - // be significantly delayed. - if (process.env.COVERALLS_ENV == 'production') { - return true; - } + // Simulate work + await new Promise((resolve, reject) => { + setTimeout(() => { + jobAttemptCounter++; + reject(new Error('Job failed.')); + }, 0); + }); - const queue = await QueueFactory(); - queue.flushQueue(); - const jobName = 'job-name'; - let jobAttemptCounter = 0; - let onFailureFiredCounter = 0; - let onFailedFiredCounter = 0; + }, { - queue.addWorker(jobName, async () => { + onFailure: () => { - // Simulate work - await new Promise((resolve, reject) => { - setTimeout(() => { - jobAttemptCounter++; - reject(new Error('Job failed.')); - }, 0); - }); + onFailureFiredCounter++; - }, { + }, + onFailed: () => { - onFailure: () => { + onFailedFiredCounter++; - onFailureFiredCounter++; + } + }); - }, - onFailed: () => { + const attempts = 3; - onFailedFiredCounter++; + // Create a job + queue.createJob(jobName, { random: 'this is 1st random data' }, { + attempts + }, false); + + jobAttemptCounter.should.equal(0); + await queue.start(); + onFailureFiredCounter.should.equal(attempts); + onFailedFiredCounter.should.equal(1); + jobAttemptCounter.should.equal(attempts); - } }); - const attempts = 3; + it('onComplete fires only once on job with multiple attempts that ends in success.', async () => { - // Create a job - queue.createJob(jobName, { random: 'this is 1st random data' }, { - attempts - }, false); - - jobAttemptCounter.should.equal(0); - await queue.start(); - onFailureFiredCounter.should.equal(attempts); - onFailedFiredCounter.should.equal(1); - jobAttemptCounter.should.equal(attempts); + // This test will intermittently fail in CI environments like travis-ci. + // Intermittent failure is a result of the poor performance of CI environments + // causing the timeouts in this test to become really flakey (setTimeout can't + // guarantee exact time of function execution, and in a high load env execution can + // be significantly delayed. + if (process.env.COVERALLS_ENV == 'production') { + return true; + } - }); + const queue = await QueueFactory(); + queue.flushQueue(); + const jobName = 'job-name'; + let jobAttemptCounter = 0; + let onFailureFiredCounter = 0; + let onFailedFiredCounter = 0; + let onCompleteFiredCounter = 0; + const attempts = 3; - it('onComplete fires only once on job with multiple attempts that ends in success.', async () => { + queue.addWorker(jobName, async () => { - // This test will intermittently fail in CI environments like travis-ci. - // Intermittent failure is a result of the poor performance of CI environments - // causing the timeouts in this test to become really flakey (setTimeout can't - // guarantee exact time of function execution, and in a high load env execution can - // be significantly delayed. - if (process.env.COVERALLS_ENV == 'production') { - return true; - } + jobAttemptCounter++; - const queue = await QueueFactory(); - queue.flushQueue(); - const jobName = 'job-name'; - let jobAttemptCounter = 0; - let onFailureFiredCounter = 0; - let onFailedFiredCounter = 0; - let onCompleteFiredCounter = 0; - const attempts = 3; + // Keep failing attempts until last attempt then success. + if (jobAttemptCounter < attempts) { - queue.addWorker(jobName, async () => { + // Simulate work that fails + await new Promise((resolve, reject) => { + setTimeout(() => { + reject(new Error('Job failed.')); + }, 0); + }); - jobAttemptCounter++; + } else { - // Keep failing attempts until last attempt then success. - if (jobAttemptCounter < attempts) { + // Simulate work that succeeds + await new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, 0); + }); - // Simulate work that fails - await new Promise((resolve, reject) => { - setTimeout(() => { - reject(new Error('Job failed.')); - }, 0); - }); + } - } else { + }, { - // Simulate work that succeeds - await new Promise((resolve) => { - setTimeout(() => { - resolve(); - }, 0); - }); + onFailure: () => { - } + onFailureFiredCounter++; - }, { + }, + onFailed: () => { - onFailure: () => { + onFailedFiredCounter++; - onFailureFiredCounter++; + }, + onComplete: () => { - }, - onFailed: () => { + onCompleteFiredCounter++; - onFailedFiredCounter++; + } + }); - }, - onComplete: () => { + // Create a job + queue.createJob(jobName, { random: 'this is 1st random data succes' }, { + attempts + }, false); - onCompleteFiredCounter++; + jobAttemptCounter.should.equal(0); + await queue.start(); + onFailureFiredCounter.should.equal(attempts - 1); + onFailedFiredCounter.should.equal(0); + jobAttemptCounter.should.equal(attempts); + onCompleteFiredCounter.should.equal(1); - } }); - // Create a job - queue.createJob(jobName, { random: 'this is 1st random data succes' }, { - attempts - }, false); + it('onComplete fires only once on job with multiple attempts that ends in failure.', async () => { - jobAttemptCounter.should.equal(0); - await queue.start(); - onFailureFiredCounter.should.equal(attempts - 1); - onFailedFiredCounter.should.equal(0); - jobAttemptCounter.should.equal(attempts); - onCompleteFiredCounter.should.equal(1); + // This test will intermittently fail in CI environments like travis-ci. + // Intermittent failure is a result of the poor performance of CI environments + // causing the timeouts in this test to become really flakey (setTimeout can't + // guarantee exact time of function execution, and in a high load env execution can + // be significantly delayed. + if (process.env.COVERALLS_ENV == 'production') { + return true; + } - }); + const queue = await QueueFactory(); + queue.flushQueue(); + const jobName = 'job-name'; + let jobAttemptCounter = 0; + let onFailureFiredCounter = 0; + let onFailedFiredCounter = 0; + let onCompleteFiredCounter = 0; + const attempts = 3; - it('onComplete fires only once on job with multiple attempts that ends in failure.', async () => { + queue.addWorker(jobName, async () => { - // This test will intermittently fail in CI environments like travis-ci. - // Intermittent failure is a result of the poor performance of CI environments - // causing the timeouts in this test to become really flakey (setTimeout can't - // guarantee exact time of function execution, and in a high load env execution can - // be significantly delayed. - if (process.env.COVERALLS_ENV == 'production') { - return true; - } + jobAttemptCounter++; - const queue = await QueueFactory(); - queue.flushQueue(); - const jobName = 'job-name'; - let jobAttemptCounter = 0; - let onFailureFiredCounter = 0; - let onFailedFiredCounter = 0; - let onCompleteFiredCounter = 0; - const attempts = 3; + // Simulate work that fails + await new Promise((resolve, reject) => { + setTimeout(() => { + reject(new Error('Job failed.')); + }, 0); + }); - queue.addWorker(jobName, async () => { + }, { - jobAttemptCounter++; + onFailure: () => { - // Simulate work that fails - await new Promise((resolve, reject) => { - setTimeout(() => { - reject(new Error('Job failed.')); - }, 0); - }); + onFailureFiredCounter++; - }, { + }, + onFailed: () => { - onFailure: () => { + onFailedFiredCounter++; - onFailureFiredCounter++; + }, + onComplete: () => { - }, - onFailed: () => { + onCompleteFiredCounter++; - onFailedFiredCounter++; + } + }); - }, - onComplete: () => { + // Create a job + queue.createJob(jobName, { random: 'this is 1st random data' }, { + attempts + }, false); - onCompleteFiredCounter++; + jobAttemptCounter.should.equal(0); + await queue.start(); + onFailureFiredCounter.should.equal(attempts); + onFailedFiredCounter.should.equal(1); + jobAttemptCounter.should.equal(attempts); + onCompleteFiredCounter.should.equal(1); - } }); - // Create a job - queue.createJob(jobName, { random: 'this is 1st random data' }, { - attempts - }, false); - - jobAttemptCounter.should.equal(0); - await queue.start(); - onFailureFiredCounter.should.equal(attempts); - onFailedFiredCounter.should.equal(1); - jobAttemptCounter.should.equal(attempts); - onCompleteFiredCounter.should.equal(1); + it('onStart, onSuccess, onComplete Job lifecycle callbacks do not block job processing.', async () => { - }); + // This test will intermittently fail in CI environments like travis-ci. + // Intermittent failure is a result of the poor performance of CI environments + // causing the timeouts in this test to become really flakey (setTimeout can't + // guarantee exact time of function execution, and in a high load env execution can + // be significantly delayed. + if (process.env.COVERALLS_ENV == 'production') { + return true; + } - it('onStart, onSuccess, onComplete Job lifecycle callbacks do not block job processing.', async () => { + const queue = await QueueFactory(); + queue.flushQueue(); + const jobName = 'job-name'; + let workTracker = []; + let tracker = []; - // This test will intermittently fail in CI environments like travis-ci. - // Intermittent failure is a result of the poor performance of CI environments - // causing the timeouts in this test to become really flakey (setTimeout can't - // guarantee exact time of function execution, and in a high load env execution can - // be significantly delayed. - if (process.env.COVERALLS_ENV == 'production') { - return true; - } + queue.addWorker(jobName, async (id, payload) => { - const queue = await QueueFactory(); - queue.flushQueue(); - const jobName = 'job-name'; - let workTracker = []; - let tracker = []; + // Simulate work + await new Promise((resolve) => { + workTracker.push(payload.random); + tracker.push('job processed'); + setTimeout(resolve, 0); + }); - queue.addWorker(jobName, async (id, payload) => { + }, { - // Simulate work - await new Promise((resolve) => { - workTracker.push(payload.random); - tracker.push('job processed'); - setTimeout(resolve, 0); - }); + onStart: async () => { - }, { + // wait a bit + await new Promise((resolve) => { + setTimeout(() => { + tracker.push('onStart completed.'); + resolve(); + }, 1000); + }); - onStart: async () => { + }, + onSuccess: async () => { - // wait a bit - await new Promise((resolve) => { - setTimeout(() => { - tracker.push('onStart completed.'); - resolve(); - }, 1000); - }); + // wait a bit + await new Promise((resolve) => { + setTimeout(() => { + tracker.push('onSuccess completed.'); + resolve(); + }, 1000); + }); - }, - onSuccess: async () => { + }, + onComplete: async () => { - // wait a bit - await new Promise((resolve) => { - setTimeout(() => { - tracker.push('onSuccess completed.'); - resolve(); - }, 1000); - }); + // wait a bit + await new Promise((resolve) => { + setTimeout(() => { + tracker.push('onComplete completed.'); + resolve(); + }, 1000); + }); - }, - onComplete: async () => { + } + }); - // wait a bit - await new Promise((resolve) => { - setTimeout(() => { - tracker.push('onComplete completed.'); - resolve(); - }, 1000); - }); + // Create a job + queue.createJob(jobName, { random: 'this is 1st random data' }, {}, false); + queue.createJob(jobName, { random: 'this is 2nd random data' }, {}, false); + queue.createJob(jobName, { random: 'this is 3rd random data' }, {}, false); + queue.createJob(jobName, { random: 'this is 4th random data' }, {}, false); + queue.createJob(jobName, { random: 'this is 5th random data' }, {}, false); + + await queue.start(); + + // Ensure all jobs processed. + workTracker.should.containDeep([ + 'this is 1st random data', + 'this is 2nd random data', + 'this is 4th random data', + 'this is 3rd random data', + 'this is 5th random data' + ]); + + // Since lifecycle callbacks take a second to process, + // queue should churn through all jobs well before any of the lifecycle + // callbacks complete. + const firstFive = tracker.slice(0, 5); + firstFive.should.deepEqual([ + 'job processed', + 'job processed', + 'job processed', + 'job processed', + 'job processed' + ]); - } }); - // Create a job - queue.createJob(jobName, { random: 'this is 1st random data' }, {}, false); - queue.createJob(jobName, { random: 'this is 2nd random data' }, {}, false); - queue.createJob(jobName, { random: 'this is 3rd random data' }, {}, false); - queue.createJob(jobName, { random: 'this is 4th random data' }, {}, false); - queue.createJob(jobName, { random: 'this is 5th random data' }, {}, false); - - await queue.start(); - - // Ensure all jobs processed. - workTracker.should.containDeep([ - 'this is 1st random data', - 'this is 2nd random data', - 'this is 4th random data', - 'this is 3rd random data', - 'this is 5th random data' - ]); - - // Since lifecycle callbacks take a second to process, - // queue should churn through all jobs well before any of the lifecycle - // callbacks complete. - const firstFive = tracker.slice(0, 5); - firstFive.should.deepEqual([ - 'job processed', - 'job processed', - 'job processed', - 'job processed', - 'job processed' - ]); + it('onFailure, onFailed Job lifecycle callbacks do not block job processing.', async () => { - }); + // This test will intermittently fail in CI environments like travis-ci. + // Intermittent failure is a result of the poor performance of CI environments + // causing the timeouts in this test to become really flakey (setTimeout can't + // guarantee exact time of function execution, and in a high load env execution can + // be significantly delayed. + if (process.env.COVERALLS_ENV == 'production') { + return true; + } - it('onFailure, onFailed Job lifecycle callbacks do not block job processing.', async () => { + const queue = await QueueFactory(); + queue.flushQueue(); + const jobName = 'job-name'; + let workTracker = []; + let tracker = []; - // This test will intermittently fail in CI environments like travis-ci. - // Intermittent failure is a result of the poor performance of CI environments - // causing the timeouts in this test to become really flakey (setTimeout can't - // guarantee exact time of function execution, and in a high load env execution can - // be significantly delayed. - if (process.env.COVERALLS_ENV == 'production') { - return true; - } + queue.addWorker(jobName, async (id, payload) => { - const queue = await QueueFactory(); - queue.flushQueue(); - const jobName = 'job-name'; - let workTracker = []; - let tracker = []; + // Simulate failure + await new Promise((resolve, reject) => { + workTracker.push(payload.random); + setTimeout(() => { + tracker.push('job attempted'); + reject(new Error('job failed')); + }, 0); + }); - queue.addWorker(jobName, async (id, payload) => { + }, { + onFailure: async () => { - // Simulate failure - await new Promise((resolve, reject) => { - workTracker.push(payload.random); - setTimeout(() => { - tracker.push('job attempted'); - reject(new Error('job failed')); - }, 0); - }); + // wait a bit + await new Promise((resolve) => { + setTimeout(() => { + tracker.push('onFailure completed.'); + resolve(); + }, 1000); + }); - }, { - onFailure: async () => { + }, + onFailed: async () => { - // wait a bit - await new Promise((resolve) => { - setTimeout(() => { - tracker.push('onFailure completed.'); - resolve(); - }, 1000); - }); + // wait a bit + await new Promise((resolve) => { + setTimeout(() => { + tracker.push('onFailed completed.'); + resolve(); + }, 1000); + }); - }, - onFailed: async () => { + } + }); - // wait a bit - await new Promise((resolve) => { - setTimeout(() => { - tracker.push('onFailed completed.'); - resolve(); - }, 1000); - }); + // Create a job + queue.createJob(jobName, { random: 'this is 1st random data' }, {}, false); + queue.createJob(jobName, { random: 'this is 2nd random data' }, {}, false); + queue.createJob(jobName, { random: 'this is 3rd random data' }, {}, false); + queue.createJob(jobName, { random: 'this is 4th random data' }, {}, false); + queue.createJob(jobName, { random: 'this is 5th random data' }, {}, false); + + await queue.start(); + + // Ensure all jobs started to process (even though they are failed). + workTracker.should.containDeep([ + 'this is 1st random data', + 'this is 2nd random data', + 'this is 4th random data', + 'this is 3rd random data', + 'this is 5th random data' + ]); + + // Since lifecycle callbacks take a second to process, + // queue should churn through all jobs well before any of the lifecycle + // callbacks complete. + const firstFive = tracker.slice(0, 5); + firstFive.should.deepEqual([ + 'job attempted', + 'job attempted', + 'job attempted', + 'job attempted', + 'job attempted' + ]); - } }); - // Create a job - queue.createJob(jobName, { random: 'this is 1st random data' }, {}, false); - queue.createJob(jobName, { random: 'this is 2nd random data' }, {}, false); - queue.createJob(jobName, { random: 'this is 3rd random data' }, {}, false); - queue.createJob(jobName, { random: 'this is 4th random data' }, {}, false); - queue.createJob(jobName, { random: 'this is 5th random data' }, {}, false); - - await queue.start(); - - // Ensure all jobs started to process (even though they are failed). - workTracker.should.containDeep([ - 'this is 1st random data', - 'this is 2nd random data', - 'this is 4th random data', - 'this is 3rd random data', - 'this is 5th random data' - ]); - - // Since lifecycle callbacks take a second to process, - // queue should churn through all jobs well before any of the lifecycle - // callbacks complete. - const firstFive = tracker.slice(0, 5); - firstFive.should.deepEqual([ - 'job attempted', - 'job attempted', - 'job attempted', - 'job attempted', - 'job attempted' - ]); - - }); - /** * * Regression test for issue 15: Indefinite job Timeout is broken @@ -2314,13 +2313,12 @@ describe('Models/Queue', function() { // Flush jobs queue.flushQueue(); - }); it('should respect lifespan rules even with delayed jobs', async () => { const queue = await QueueFactory(); queue.flushQueue(); - const jobName = 'job-name'; + const jobName = 'this-job-name'; function * succeedGeneratorFn() { yield false; @@ -2346,17 +2344,80 @@ describe('Models/Queue', function() { const jobs = await queue.getJobs(true); const job = jobs[0]; const jobData = JSON.parse(job.data); - should.not.exist(job.failed); jobData.failedAttempts.should.equal(2); await wait(500); await queue.start(1000); - await wait(2000); + await wait(1500); const doneJobs = await queue.getJobs(true); doneJobs.length.should.equal(0); queue.flushQueue(); }); + + // Edge case + it('should still trigger new jobs if lifespan = 0', async () => { + const queue = await QueueFactory(); + queue.flushQueue(); + const jobName = 'job-name'; + let callCount = 0; + // Attach the worker. + queue.addWorker(jobName, async (id,payload) => { + callCount = callCount + 1; + }, false); + + queue.createJob(jobName,{foo:'bar'},{ + retryDelay: 500, + attempts: 3, + timeout: 200, + },false); + + await queue.start(0); + await wait(500); + expect(callCount).toBe(1); + + queue.createJob(jobName,{foo:'bar'},{ + retryDelay: 500, + attempts: 3, + timeout: 200, + }); + await wait(500); + expect(callCount).toBe(2); + + queue.flushQueue(); + }); + + // Edge case + it('should still trigger new jobs if lifespan > 0', async () => { + const queue = await QueueFactory(); + queue.flushQueue(); + const jobName = 'job-name'; + let callCount = 0; + // Attach the worker. + queue.addWorker(jobName, async (id,payload) => { + callCount = callCount + 1; + }, false); + + queue.createJob(jobName,{foo:'bar'},{ + retryDelay: 500, + attempts: 3, + timeout: 200, + },false); + + await queue.start(1000); + await wait(500); + expect(callCount).toBe(1); + + queue.createJob(jobName,{foo:'bar'},{ + retryDelay: 500, + attempts: 3, + timeout: 200, + }); + await wait(500); + expect(callCount).toBe(2); + + queue.flushQueue(); + }); }); From 530bd47748fe92b40813fdcb1b7ae3ac8cb23154 Mon Sep 17 00:00:00 2001 From: Gareth Tomlinson Date: Fri, 21 Feb 2020 00:11:47 +0000 Subject: [PATCH 3/7] Bump version of realm --- package.json | 2 +- yarn.lock | 515 ++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 451 insertions(+), 66 deletions(-) diff --git a/package.json b/package.json index ef8cbd9..d723b7a 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "moment": "^2.24.0", "promise-reflect": "^1.1.0", "react-native-uuid": "^1.4.9", - "realm": "^2.0.12" + "realm": "^3.6.4" }, "devDependencies": { "babel-eslint": "^8.0.3", diff --git a/yarn.lock b/yarn.lock index 9e388a4..2db3170 100644 --- a/yarn.lock +++ b/yarn.lock @@ -87,6 +87,13 @@ acorn@^5.2.1: version "5.2.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.2.1.tgz#317ac7821826c22c702d66189ab8359675f135d7" +agent-base@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" + integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg== + dependencies: + es6-promisify "^5.0.0" + ajv-keywords@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762" @@ -107,6 +114,16 @@ ajv@^5.1.0, ajv@^5.2.3, ajv@^5.3.0: fast-json-stable-stringify "^2.0.0" json-schema-traverse "^0.3.0" +ajv@^6.5.5: + version "6.11.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.11.0.tgz#c3607cbc8ae392d8a5a536f25b21f8e5f3f87fe9" + integrity sha512-nCprB/0syFYy9fVYU1ox1l2KN8S9I+tziH8D4zdZuLT3N6RMlGSGt5FSTpAiHB/Whv8Qs1cWHma1aMKZyaHRKA== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + align-text@^0.1.1, align-text@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" @@ -261,6 +278,11 @@ aws4@^1.2.1, aws4@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" +aws4@^1.8.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e" + integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug== + babel-code-frame@^6.22.0, babel-code-frame@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" @@ -869,6 +891,11 @@ chardet@^0.4.0: version "0.4.2" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" +chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + ci-info@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.1.2.tgz#03561259db48d0474c8bdc90f5b47b068b6bbfb4" @@ -927,6 +954,13 @@ combined-stream@^1.0.5, combined-stream@~1.0.5: dependencies: delayed-stream "~1.0.0" +combined-stream@^1.0.6, combined-stream@~1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + command-line-args@^4.0.6: version "4.0.7" resolved "https://registry.yarnpkg.com/command-line-args/-/command-line-args-4.0.7.tgz#f8d1916ecb90e9e121eda6428e41300bfb64cc46" @@ -973,6 +1007,11 @@ core-js@^2.4.0, core-js@^2.5.0: version "2.5.1" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.1.tgz#ae6874dc66937789b80754ff5428df66819ca50b" +core-js@^2.4.1: + version "2.6.11" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" + integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== + core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -987,6 +1026,15 @@ coveralls@^3.0.0: minimist "^1.2.0" request "^2.79.0" +create-react-class@*: + version "15.6.3" + resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.6.3.tgz#2d73237fb3f970ae6ebe011a9e66f46dbca80036" + integrity sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg== + dependencies: + fbjs "^0.8.9" + loose-envify "^1.3.1" + object-assign "^4.1.1" + cross-spawn@^5.0.1, cross-spawn@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" @@ -1035,6 +1083,13 @@ debug@^3.0.1, debug@^3.1.0: dependencies: ms "2.0.0" +debug@^3.2.6: + version "3.2.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" + integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== + dependencies: + ms "^2.1.1" + decamelize@^1.0.0, decamelize@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -1065,15 +1120,6 @@ decompress-targz@^4.0.0: file-type "^5.2.0" is-stream "^1.1.0" -decompress-tarxz@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/decompress-tarxz/-/decompress-tarxz-2.1.1.tgz#1c657dc69322a5a0bac8431f2574a77e5884d3f5" - dependencies: - decompress-tar "^4.1.0" - file-type "^3.8.0" - is-stream "^1.1.0" - lzma-native "^3.0.1" - decompress-unzip@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/decompress-unzip/-/decompress-unzip-4.0.1.tgz#deaaccdfd14aeaf85578f733ae8210f9b4848f69" @@ -1096,6 +1142,11 @@ decompress@^4.2.0: pify "^2.3.0" strip-dirs "^2.0.0" +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + deep-extend@~0.4.0: version "0.4.2" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f" @@ -1104,6 +1155,11 @@ deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" +deepmerge@2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.1.0.tgz#511a54fff405fc346f0240bb270a3e9533a31102" + integrity sha512-Q89Z26KAfA3lpPGhbF6XMfYAm3jIV3avViy6KOJ2JLzFbeWHOvPQUu5aSJIWXap3gDZC2y1eF5HXEPI2wGqgvw== + default-require-extensions@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-1.0.0.tgz#f37ea15d3e13ffd9b437d33e1a75b5fb97874cb8" @@ -1130,6 +1186,16 @@ delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" +deprecated-react-native-listview@0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/deprecated-react-native-listview/-/deprecated-react-native-listview-0.0.6.tgz#f9169dd703398a7792e5bcb8b851e741a8cb6980" + integrity sha512-QRuN0Dcv3Muu1oT8MhZgyfqm77bTAegVwqSRJKVwVVsm0xJE0TBfqdD45VXYLIS+yMXPJoeXdSqSdVQSjwlOpQ== + dependencies: + create-react-class "*" + fbjs "*" + invariant "*" + react-clone-referenced-element "*" + detect-indent@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" @@ -1184,6 +1250,18 @@ error-ex@^1.2.0: dependencies: is-arrayish "^0.2.1" +es6-promise@^4.0.3: + version "4.2.8" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" + integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== + +es6-promisify@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" + integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= + dependencies: + es6-promise "^4.0.3" + escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" @@ -1329,6 +1407,11 @@ extend@~3.0.0, extend@~3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" +extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + external-editor@^2.0.4: version "2.1.0" resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.1.0.tgz#3d026a21b7f95b5726387d4200ac160d372c3b48" @@ -1351,6 +1434,11 @@ fast-deep-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff" +fast-deep-equal@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4" + integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA== + fast-json-stable-stringify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" @@ -1365,9 +1453,29 @@ fb-watchman@^2.0.0: dependencies: bser "^2.0.0" -fbjs@^0.8.16: - version "0.8.16" - resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db" +fbjs-css-vars@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz#216551136ae02fe255932c3ec8775f18e2c078b8" + integrity sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ== + +fbjs@*: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-1.0.0.tgz#52c215e0883a3c86af2a7a776ed51525ae8e0a5a" + integrity sha512-MUgcMEJaFhCaF1QtWGnmq9ZDRAzECTCRAF7O6UZIlAlkTs1SasiX9aP0Iw7wfD2mJ7wDTNfg2w7u5fSCwJk1OA== + dependencies: + core-js "^2.4.1" + fbjs-css-vars "^1.0.0" + isomorphic-fetch "^2.1.1" + loose-envify "^1.0.0" + object-assign "^4.1.0" + promise "^7.1.1" + setimmediate "^1.0.5" + ua-parser-js "^0.7.18" + +fbjs@^0.8.9: + version "0.8.17" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd" + integrity sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90= dependencies: core-js "^1.0.0" isomorphic-fetch "^2.1.1" @@ -1375,7 +1483,7 @@ fbjs@^0.8.16: object-assign "^4.1.0" promise "^7.1.1" setimmediate "^1.0.5" - ua-parser-js "^0.7.9" + ua-parser-js "^0.7.18" fd-slicer@~1.0.1: version "1.0.1" @@ -1488,14 +1596,31 @@ form-data@~2.3.1: combined-stream "^1.0.5" mime-types "^2.1.12" -fs-extra@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.2.tgz#f91704c53d1b461f893452b0c307d9997647ab6b" +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + +fs-extra@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94" + integrity sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg== dependencies: graceful-fs "^4.1.2" jsonfile "^4.0.0" universalify "^0.1.0" +fs-minipass@^1.2.5: + version "1.2.7" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" + integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA== + dependencies: + minipass "^2.6.0" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -1660,6 +1785,14 @@ har-validator@~5.0.3: ajv "^5.1.0" har-schema "^2.0.0" +har-validator@~5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" + integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== + dependencies: + ajv "^6.5.5" + har-schema "^2.0.0" + has-ansi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" @@ -1749,14 +1882,36 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" +https-proxy-agent@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b" + integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg== + dependencies: + agent-base "^4.3.0" + debug "^3.1.0" + iconv-lite@0.4.19, iconv-lite@^0.4.17, iconv-lite@~0.4.13: version "0.4.19" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" +iconv-lite@^0.4.4: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + ieee754@^1.1.4: version "1.1.8" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4" +ignore-walk@^3.0.1: + version "3.0.3" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.3.tgz#017e2447184bfeade7c238e4aefdd1e8f95b1e37" + integrity sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw== + dependencies: + minimatch "^3.0.4" + ignore@^3.3.3: version "3.3.7" resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.7.tgz#612289bfb3c220e186a58118618d5be8c1bab021" @@ -1776,9 +1931,10 @@ inherits@2, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" -ini@^1.3.4, ini@~1.3.0: +ini@^1.3.5, ini@~1.3.0: version "1.3.5" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" + integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== inquirer@^3.0.6: version "3.3.0" @@ -1799,6 +1955,13 @@ inquirer@^3.0.6: strip-ansi "^4.0.0" through "^2.3.6" +invariant@*: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + invariant@^2.2.0, invariant@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360" @@ -2248,6 +2411,11 @@ js-tokens@^3.0.0, js-tokens@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" +"js-tokens@^3.0.0 || ^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + js-yaml@^3.6.1, js-yaml@^3.7.0, js-yaml@^3.9.1: version "3.10.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc" @@ -2291,6 +2459,11 @@ json-schema-traverse@^0.3.0: version "0.3.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + json-schema@0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" @@ -2413,6 +2586,13 @@ loose-envify@^1.0.0, loose-envify@^1.3.1: dependencies: js-tokens "^3.0.0" +loose-envify@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + lru-cache@^4.0.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55" @@ -2420,15 +2600,6 @@ lru-cache@^4.0.1: pseudomap "^1.0.2" yallist "^2.1.2" -lzma-native@^3.0.1: - version "3.0.4" - resolved "https://registry.yarnpkg.com/lzma-native/-/lzma-native-3.0.4.tgz#1fe863dbbbaa58f8132dfcf7df261ec9af6f2b72" - dependencies: - nan "2.5.1" - node-pre-gyp "^0.6.39" - readable-stream "^2.0.5" - rimraf "^2.6.1" - make-dir@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.1.0.tgz#19b4369fe48c116f53c2af95ad102c0e39e85d51" @@ -2469,6 +2640,11 @@ micromatch@^2.1.5, micromatch@^2.3.11: parse-glob "^3.0.4" regex-cache "^0.4.2" +mime-db@1.43.0: + version "1.43.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58" + integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ== + mime-db@~1.30.0: version "1.30.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01" @@ -2479,6 +2655,13 @@ mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.7: dependencies: mime-db "~1.30.0" +mime-types@~2.1.19: + version "2.1.26" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06" + integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ== + dependencies: + mime-db "1.43.0" + mimic-fn@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" @@ -2503,7 +2686,22 @@ minimist@^1.1.1, minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" -"mkdirp@>=0.5 0", mkdirp@^0.5.1: +minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" + integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg== + dependencies: + safe-buffer "^5.1.2" + yallist "^3.0.0" + +minizlib@^1.2.1: + version "1.3.3" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" + integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q== + dependencies: + minipass "^2.9.0" + +"mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" dependencies: @@ -2518,15 +2716,21 @@ ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" +ms@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + mute-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" -nan@2.5.1: - version "2.5.1" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.5.1.tgz#d5b01691253326a97a2bbee9e61c55d8d60351e2" +nan@^2.12.1: + version "2.14.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" + integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== -nan@^2.3.0, nan@^2.3.3: +nan@^2.3.0: version "2.8.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a" @@ -2534,9 +2738,19 @@ natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" -node-fetch@^1.0.1, node-fetch@^1.6.3: +needle@^2.2.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/needle/-/needle-2.3.2.tgz#3342dea100b7160960a450dc8c22160ac712a528" + integrity sha512-DUzITvPVDUy6vczKKYTnWc/pBZ0EnjMJnQ3y+Jo5zfKFimJs7S3HFCxCRZYB9FUZcrzUQr3WsmvZgddMEIZv6w== + dependencies: + debug "^3.2.6" + iconv-lite "^0.4.4" + sax "^1.2.4" + +node-fetch@^1.0.1, node-fetch@^1.7.3: version "1.7.3" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" + integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ== dependencies: encoding "^0.1.11" is-stream "^1.0.1" @@ -2545,6 +2759,11 @@ node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" +node-machine-id@^1.1.10: + version "1.1.12" + resolved "https://registry.yarnpkg.com/node-machine-id/-/node-machine-id-1.1.12.tgz#37904eee1e59b320bb9c5d6c0a59f3b469cb6267" + integrity sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ== + node-notifier@^5.0.2: version "5.1.2" resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.1.2.tgz#2fa9e12605fa10009d44549d6fcd8a63dde0e4ff" @@ -2554,7 +2773,23 @@ node-notifier@^5.0.2: shellwords "^0.1.0" which "^1.2.12" -node-pre-gyp@^0.6.36, node-pre-gyp@^0.6.39: +node-pre-gyp@^0.13.0: + version "0.13.0" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.13.0.tgz#df9ab7b68dd6498137717838e4f92a33fc9daa42" + integrity sha512-Md1D3xnEne8b/HGVQkZZwV27WUi1ZRuZBij24TNaZwUPU3ZAFtvT6xxJGaUVillfmMKnn5oD1HoGsp2Ftik7SQ== + dependencies: + detect-libc "^1.0.2" + mkdirp "^0.5.1" + needle "^2.2.1" + nopt "^4.0.1" + npm-packlist "^1.1.6" + npmlog "^4.0.2" + rc "^1.2.7" + rimraf "^2.6.1" + semver "^5.3.0" + tar "^4" + +node-pre-gyp@^0.6.39: version "0.6.39" resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz#c00e96860b23c0e1420ac7befc5044e1d78d8649" dependencies: @@ -2592,6 +2827,27 @@ normalize-path@^2.0.0, normalize-path@^2.0.1: dependencies: remove-trailing-separator "^1.0.1" +npm-bundled@^1.0.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.1.tgz#1edd570865a94cdb1bc8220775e29466c9fb234b" + integrity sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA== + dependencies: + npm-normalize-package-bin "^1.0.1" + +npm-normalize-package-bin@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2" + integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA== + +npm-packlist@^1.1.6: + version "1.4.8" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.8.tgz#56ee6cc135b9f98ad3d51c1c95da22bbb9b2ef3e" + integrity sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A== + dependencies: + ignore-walk "^3.0.1" + npm-bundled "^1.0.1" + npm-normalize-package-bin "^1.0.1" + npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" @@ -2619,6 +2875,11 @@ oauth-sign@~0.8.1, oauth-sign@~0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== + object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -2825,6 +3086,11 @@ progress@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f" +progress@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + promise-reflect@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/promise-reflect/-/promise-reflect-1.1.0.tgz#5d4fbb780f83b8b6dadca639362c40fbc1336010" @@ -2835,13 +3101,14 @@ promise@^7.1.1: dependencies: asap "~2.0.3" -prop-types@^15.5.10: - version "15.6.0" - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856" +prop-types@^15.6.2: + version "15.7.2" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" + integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== dependencies: - fbjs "^0.8.16" - loose-envify "^1.3.1" + loose-envify "^1.4.0" object-assign "^4.1.1" + react-is "^16.8.1" prr@~0.0.0: version "0.0.0" @@ -2851,10 +3118,20 @@ pseudomap@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" +psl@^1.1.28: + version "1.7.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.7.0.tgz#f1c4c47a8ef97167dea5d6bbf4816d736e884a3c" + integrity sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ== + punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" +punycode@^2.1.0, punycode@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + qs@^6.1.0, qs@~6.5.1: version "6.5.1" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" @@ -2863,9 +3140,15 @@ qs@~6.4.0: version "6.4.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" -querystringify@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-1.0.0.tgz#6286242112c5b712fa654e526652bf6a13ff05cb" +qs@~6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== + +querystringify@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e" + integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA== randomatic@^1.1.3: version "1.1.7" @@ -2889,10 +3172,30 @@ rc@^1.1.7: minimist "^1.2.0" strip-json-comments "~2.0.1" +rc@^1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +react-clone-referenced-element@*: + version "1.1.0" + resolved "https://registry.yarnpkg.com/react-clone-referenced-element/-/react-clone-referenced-element-1.1.0.tgz#9cdda7f2aeb54fea791f3ab8c6ab96c7a77d0158" + integrity sha512-FKOsfKbBkPxYE8576EM6uAfHC4rnMpLyH6/TJUL4WcHUEB3EUn8AxPjnnV/IiwSSzsClvHYK+sDELKN/EJ0WYg== + react-deep-force-update@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/react-deep-force-update/-/react-deep-force-update-1.1.1.tgz#bcd31478027b64b3339f108921ab520b4313dc2c" +react-is@^16.8.1: + version "16.12.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.12.0.tgz#2cc0fe0fba742d97fd527c42a13bec4eeb06241c" + integrity sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q== + react-native-uuid@^1.4.9: version "1.4.9" resolved "https://registry.yarnpkg.com/react-native-uuid/-/react-native-uuid-1.4.9.tgz#a526742f8fddfe6414500655212ca8d109c40229" @@ -2955,24 +3258,28 @@ readable-stream@^2.0.0, readable-stream@^2.0.5, readable-stream@^2.0.6, readable string_decoder "~1.0.3" util-deprecate "~1.0.1" -realm@^2.0.12: - version "2.0.12" - resolved "https://registry.yarnpkg.com/realm/-/realm-2.0.12.tgz#b7e42f72b3f0924cbbd4985b293c7aa21ed92ff6" +realm@^3.6.4: + version "3.6.4" + resolved "https://registry.yarnpkg.com/realm/-/realm-3.6.4.tgz#efbe85c25fb9b358b1a1bde38da1f1c8bd49bccb" + integrity sha512-0nkeGAK3wWSn7uXcrBjT/25TYO8n1ylnnQjGbLbfivj3v7c+sGO/Q/AXWcvwjeAFLwYI0S1ema0o21L5JVmt0A== dependencies: command-line-args "^4.0.6" decompress "^4.2.0" - decompress-tarxz "^2.1.1" - fs-extra "^4.0.2" - ini "^1.3.4" - nan "^2.3.3" - node-fetch "^1.6.3" - node-pre-gyp "^0.6.36" - progress "^2.0.0" - prop-types "^15.5.10" - request "^2.78.0" + deepmerge "2.1.0" + deprecated-react-native-listview "0.0.6" + fs-extra "^4.0.3" + https-proxy-agent "^2.2.4" + ini "^1.3.5" + nan "^2.12.1" + node-fetch "^1.7.3" + node-machine-id "^1.1.10" + node-pre-gyp "^0.13.0" + progress "^2.0.3" + prop-types "^15.6.2" + request "^2.88.0" stream-counter "^1.0.0" sync-request "^3.0.1" - url-parse "^1.1.7" + url-parse "^1.4.4" regenerator-runtime@^0.11.0: version "0.11.0" @@ -3037,7 +3344,7 @@ request@2.81.0: tunnel-agent "^0.6.0" uuid "^3.0.0" -request@^2.78.0, request@^2.79.0: +request@^2.79.0: version "2.83.0" resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356" dependencies: @@ -3064,6 +3371,32 @@ request@^2.78.0, request@^2.79.0: tunnel-agent "^0.6.0" uuid "^3.1.0" +request@^2.88.0: + version "2.88.2" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" + integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.5.0" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -3079,9 +3412,10 @@ require-uncached@^1.0.3: caller-path "^0.1.0" resolve-from "^1.0.0" -requires-port@~1.0.0: +requires-port@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= resolve-from@^1.0.0: version "1.0.1" @@ -3130,6 +3464,16 @@ safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0, version "5.1.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" +safe-buffer@^5.1.2: + version "5.2.0" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" + integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + sane@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/sane/-/sane-2.2.0.tgz#d6d2e2fcab00e3d283c93b912b7c3a20846f1d56" @@ -3144,9 +3488,10 @@ sane@^2.0.0: optionalDependencies: fsevents "^1.1.1" -sax@^1.2.1: +sax@^1.2.1, sax@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== seek-bzip@^1.0.5: version "1.0.5" @@ -3433,6 +3778,19 @@ tar@^2.2.1: fstream "^1.0.2" inherits "2" +tar@^4: + version "4.4.13" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525" + integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA== + dependencies: + chownr "^1.1.1" + fs-minipass "^1.2.5" + minipass "^2.8.6" + minizlib "^1.2.1" + mkdirp "^0.5.0" + safe-buffer "^5.1.2" + yallist "^3.0.3" + test-exclude@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-4.1.1.tgz#4d84964b0966b0087ecc334a2ce002d3d9341e26" @@ -3497,6 +3855,14 @@ tough-cookie@^2.3.2, tough-cookie@~2.3.0, tough-cookie@~2.3.3: dependencies: punycode "^1.4.1" +tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" @@ -3533,9 +3899,10 @@ typical@^2.6.0, typical@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/typical/-/typical-2.6.1.tgz#5c080e5d661cbbe38259d2e70a3c7253e873881d" -ua-parser-js@^0.7.9: - version "0.7.17" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac" +ua-parser-js@^0.7.18: + version "0.7.21" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.21.tgz#853cf9ce93f642f67174273cc34565ae6f308777" + integrity sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ== uglify-js@^2.6: version "2.8.29" @@ -3565,12 +3932,20 @@ universalify@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.1.tgz#fa71badd4437af4c148841e3b3b165f9e9e590b7" -url-parse@^1.1.7: - version "1.2.0" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.2.0.tgz#3a19e8aaa6d023ddd27dcc44cb4fc8f7fec23986" +uri-js@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" + integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== dependencies: - querystringify "~1.0.0" - requires-port "~1.0.0" + punycode "^2.1.0" + +url-parse@^1.4.4: + version "1.4.7" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278" + integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg== + dependencies: + querystringify "^2.1.1" + requires-port "^1.0.0" util-deprecate@~1.0.1: version "1.0.2" @@ -3580,6 +3955,11 @@ uuid@^3.0.0, uuid@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" +uuid@^3.3.2: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + validate-npm-package-license@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc" @@ -3713,6 +4093,11 @@ yallist@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" +yallist@^3.0.0, yallist@^3.0.3: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + yargs-parser@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-7.0.0.tgz#8d0ac42f16ea55debd332caf4c4038b3e3f5dfd9" From ab2e63469e326e08a04d66897e3fb97e939749d0 Mon Sep 17 00:00:00 2001 From: Gareth Tomlinson Date: Fri, 21 Feb 2020 00:20:31 +0000 Subject: [PATCH 4/7] Added ability to limit number of jobs per execution --- Models/Queue.js | 26 ++++++++++-------- tests/Queue.test.js | 67 ++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 78 insertions(+), 15 deletions(-) diff --git a/Models/Queue.js b/Models/Queue.js index 5e951ef..14b8740 100644 --- a/Models/Queue.js +++ b/Models/Queue.js @@ -124,11 +124,11 @@ export class Queue { return (lifespanRemaining === 0) ? -1 : lifespanRemaining; // Handle exactly zero lifespan remaining edge case. } - async calculateJobs () { + async calculateJobs (jobsLimit) { if (this.lifespan !== 0) { - return this.getConcurrentJobs(this.calculateRemainingLifespan()); + return this.getConcurrentJobs(jobsLimit,this.calculateRemainingLifespan()); } else { - return this.getConcurrentJobs(); + return this.getConcurrentJobs(jobsLimit); } } @@ -156,8 +156,9 @@ export class Queue { * @param lifespan {number} - If lifespan is passed, the queue will start up and run for lifespan ms, then queue will be stopped. * @return {boolean|undefined} - False if queue is already started. Otherwise nothing is returned when queue finishes processing. */ - async start(lifespan = 0) { + async start(lifespan = 0, numberOfJobsToProcess) { this.lifespan = lifespan; + let jobsProcessed = 0; // If queue is already running, don't fire up concurrent loop. if (this.status == 'active') { @@ -167,11 +168,11 @@ export class Queue { this.status = 'active'; // Get jobs to process - if(!this.startTime) + if(!this.startTime || this.calculateRemainingLifespan() < 0) this.startTime = Date.now(); let concurrentJobs; - concurrentJobs = await this.calculateJobs(); + concurrentJobs = await this.calculateJobs(numberOfJobsToProcess-jobsProcessed); while (this.status == 'active' && concurrentJobs.length) { @@ -179,13 +180,14 @@ export class Queue { const processingJobs = concurrentJobs.map( job => { return this.processJob(job); }); + jobsProcessed += concurrentJobs.length; // Promise Reflect ensures all processingJobs resolve so // we don't break await early if one of the jobs fails. await Promise.all(processingJobs.map(promiseReflect)); // Get next batch of jobs. - concurrentJobs = await this.calculateJobs(); + concurrentJobs = await this.calculateJobs(numberOfJobsToProcess - jobsProcessed); } this.status = 'inactive'; @@ -249,7 +251,7 @@ export class Queue { * @param queueLifespanRemaining {number} - The remaining lifespan of the current queue process (defaults to indefinite). * @return {promise} - Promise resolves to an array of job(s) to be processed next by the queue. */ - async getConcurrentJobs(queueLifespanRemaining = 0) { + async getConcurrentJobs(jobsLimit = -1, queueLifespanRemaining = 0) { let concurrentJobs = []; @@ -267,8 +269,10 @@ export class Queue { ? 'active == FALSE AND failed == null AND timeout > 0 AND timeout < ' + timeoutUpperBound + ' AND nextValidTime <= $0' : 'active == FALSE AND failed == null AND nextValidTime <= $0'; + const limitQuery = jobsLimit > -1 ? ` LIMIT(${jobsLimit})` : ''; + let jobs = this.realm.objects('Job') - .filtered(initialQuery, now) + .filtered(initialQuery + limitQuery, now) .sorted([['priority', true], ['created', false]]); if (jobs.length) { @@ -285,7 +289,7 @@ export class Queue { : 'name == "'+ nextJob.name +'" AND active == FALSE AND failed == null AND nextValidTime <= $0'; const allRelatedJobs = this.realm.objects('Job') - .filtered(allRelatedJobsQuery, now) + .filtered(allRelatedJobsQuery + limitQuery, now) .sorted([['priority', true], ['created', false]]); let jobsToMarkActive = allRelatedJobs.slice(0, concurrency); @@ -303,7 +307,7 @@ export class Queue { // Reselect now-active concurrent jobs by id. const reselectQuery = concurrentJobIds.map( jobId => 'id == "' + jobId + '"').join(' OR '); const reselectedJobs = this.realm.objects('Job') - .filtered(reselectQuery) + .filtered(reselectQuery + limitQuery) .sorted([['priority', true], ['created', false]]); concurrentJobs = reselectedJobs.slice(0, concurrency); diff --git a/tests/Queue.test.js b/tests/Queue.test.js index e5f17a2..eca9347 100644 --- a/tests/Queue.test.js +++ b/tests/Queue.test.js @@ -997,7 +997,7 @@ describe('Models/Queue', function() { timeout: 0 }, false); - const jobs = await queue.getConcurrentJobs(2000); + const jobs = await queue.getConcurrentJobs(-1, 2000); // No jobs should be grabbed jobs.length.should.equal(0); @@ -1016,7 +1016,7 @@ describe('Models/Queue', function() { timeout: 500 }, false); - const notEnoughBufferJobs = await queue.getConcurrentJobs(600); + const notEnoughBufferJobs = await queue.getConcurrentJobs(-1,600); // No jobs should be grabbed notEnoughBufferJobs.length.should.equal(0); @@ -1038,7 +1038,7 @@ describe('Models/Queue', function() { // startQueue is false so queue should not have started. queue.status.should.equal('inactive'); - const lowerBoundEdgeCaseJobs = await queue.getConcurrentJobs(501); + const lowerBoundEdgeCaseJobs = await queue.getConcurrentJobs(-1,501); // Only the jobs with the timeouts set should be grabbed. lowerBoundEdgeCaseJobs.length.should.equal(2); @@ -1063,7 +1063,7 @@ describe('Models/Queue', function() { // startQueue is false so queue should not have started. queue.status.should.equal('inactive'); - const lifespanConcurrencyJobs = await queue.getConcurrentJobs(2000); + const lifespanConcurrencyJobs = await queue.getConcurrentJobs(-1,2000); // Only 3 jobs should be grabbed in this test even though all jobs // have valid timeouts because worker concurrency is set to 3 @@ -2420,4 +2420,63 @@ describe('Models/Queue', function() { queue.flushQueue(); }); + + it('should be able to define the number of jobs triggered per queue start with lifespan > 0', async () => { + const queue = await QueueFactory(); + queue.flushQueue(); + const jobName = 'job-name'; + let callCount = 0; + // Attach the worker. + queue.addWorker(jobName, async (id,payload) => { + callCount = callCount + 1; + }, false); + + queue.createJob(jobName,{foo:'bar'},{ + retryDelay: 500, + attempts: 3, + timeout: 200, + },false); + + queue.createJob(jobName,{foo:'goo'},{ + retryDelay: 500, + attempts: 3, + timeout: 200, + }, false); + + queue.createJob(jobName,{foo:'bar'},{ + retryDelay: 500, + attempts: 3, + timeout: 200, + },false); + + queue.createJob(jobName,{foo:'goo'},{ + retryDelay: 500, + attempts: 3, + timeout: 200, + }, false); + + await queue.start(1000,1); + await wait(600); + expect(callCount).toBe(1); + await wait(600); + expect(callCount).toBe(1); + + await queue.start(1000,1); + await wait(600); + expect(callCount).toBe(2); + await wait(600); + expect(callCount).toBe(2); + + await queue.start(1000,2); + await wait(600); + expect(callCount).toBe(4); + await wait(600); + expect(callCount).toBe(4); + + await queue.start(1000,0); + await wait(1100); + expect(callCount).toBe(4); + + queue.flushQueue(); + }); }); From 503886d12b4e4ec73eb839d1babcd6cc459d365d Mon Sep 17 00:00:00 2001 From: Gareth Tomlinson Date: Sat, 29 Feb 2020 12:29:09 +0000 Subject: [PATCH 5/7] Added ability to remove job by jobId --- Models/Queue.js | 22 ++++++++++++++++++++++ tests/Queue.test.js | 26 ++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/Models/Queue.js b/Models/Queue.js index 14b8740..30d778f 100644 --- a/Models/Queue.js +++ b/Models/Queue.js @@ -447,6 +447,28 @@ export class Queue { } + /** + * Delete a job in the queue with jobId + * @param jobId {string} - id associated with job + */ + flushJob(jobId) { + try { + if(jobId) { + this.realm.write(() => { + let jobs = this.realm + .objects('Job') + .filtered(`id == "${jobId}"`); + if(jobs.length) { + this.realm.delete(jobs); + return; + } + }); + } + } catch (e) { + console.log('flushJob failed', jobId); + } + } + async close() { await this.stop(); await this.realm.close(); diff --git a/tests/Queue.test.js b/tests/Queue.test.js index eca9347..3391528 100644 --- a/tests/Queue.test.js +++ b/tests/Queue.test.js @@ -2479,4 +2479,30 @@ describe('Models/Queue', function() { queue.flushQueue(); }); + + it('should be able to remove a job by id', async () => { + const queue = await QueueFactory(); + queue.flushQueue(); + const jobName = 'job-name'; + + // Attach the worker. + queue.addWorker(jobName, async (id,payload) => { + }, false); + + queue.createJob(jobName,{foo:'bar'},{ + retryDelay: 500, + attempts: 3, + timeout: 200, + },false); + + const jobs = await queue.getJobs(true); + expect(Object.keys(jobs).length).toBe(1); + + queue.flushJob(jobs[0].id); + + const newJobs = await queue.getJobs(true); + expect(Object.keys(newJobs).length).toBe(0); + + queue.flushQueue(); + }); }); From 0414becb79df57bdf1810e5f304c8836fd7769f1 Mon Sep 17 00:00:00 2001 From: Gareth Tomlinson Date: Sat, 29 Feb 2020 12:42:19 +0000 Subject: [PATCH 6/7] Downgrade realm due to issues on iOS --- package.json | 2 +- yarn.lock | 119 ++++++--------------------------------------------- 2 files changed, 14 insertions(+), 107 deletions(-) diff --git a/package.json b/package.json index d723b7a..37cf50e 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "moment": "^2.24.0", "promise-reflect": "^1.1.0", "react-native-uuid": "^1.4.9", - "realm": "^3.6.4" + "realm": "^2.0.13" }, "devDependencies": { "babel-eslint": "^8.0.3", diff --git a/yarn.lock b/yarn.lock index 2db3170..6ba0c3b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -999,19 +999,10 @@ convert-source-map@^1.4.0, convert-source-map@^1.5.0: version "1.5.1" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5" -core-js@^1.0.0: - version "1.2.7" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" - core-js@^2.4.0, core-js@^2.5.0: version "2.5.1" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.1.tgz#ae6874dc66937789b80754ff5428df66819ca50b" -core-js@^2.4.1: - version "2.6.11" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" - integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== - core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -1026,15 +1017,6 @@ coveralls@^3.0.0: minimist "^1.2.0" request "^2.79.0" -create-react-class@*: - version "15.6.3" - resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.6.3.tgz#2d73237fb3f970ae6ebe011a9e66f46dbca80036" - integrity sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg== - dependencies: - fbjs "^0.8.9" - loose-envify "^1.3.1" - object-assign "^4.1.1" - cross-spawn@^5.0.1, cross-spawn@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" @@ -1186,16 +1168,6 @@ delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" -deprecated-react-native-listview@0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/deprecated-react-native-listview/-/deprecated-react-native-listview-0.0.6.tgz#f9169dd703398a7792e5bcb8b851e741a8cb6980" - integrity sha512-QRuN0Dcv3Muu1oT8MhZgyfqm77bTAegVwqSRJKVwVVsm0xJE0TBfqdD45VXYLIS+yMXPJoeXdSqSdVQSjwlOpQ== - dependencies: - create-react-class "*" - fbjs "*" - invariant "*" - react-clone-referenced-element "*" - detect-indent@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" @@ -1453,38 +1425,6 @@ fb-watchman@^2.0.0: dependencies: bser "^2.0.0" -fbjs-css-vars@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz#216551136ae02fe255932c3ec8775f18e2c078b8" - integrity sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ== - -fbjs@*: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-1.0.0.tgz#52c215e0883a3c86af2a7a776ed51525ae8e0a5a" - integrity sha512-MUgcMEJaFhCaF1QtWGnmq9ZDRAzECTCRAF7O6UZIlAlkTs1SasiX9aP0Iw7wfD2mJ7wDTNfg2w7u5fSCwJk1OA== - dependencies: - core-js "^2.4.1" - fbjs-css-vars "^1.0.0" - isomorphic-fetch "^2.1.1" - loose-envify "^1.0.0" - object-assign "^4.1.0" - promise "^7.1.1" - setimmediate "^1.0.5" - ua-parser-js "^0.7.18" - -fbjs@^0.8.9: - version "0.8.17" - resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd" - integrity sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90= - dependencies: - core-js "^1.0.0" - isomorphic-fetch "^2.1.1" - loose-envify "^1.0.0" - object-assign "^4.1.0" - promise "^7.1.1" - setimmediate "^1.0.5" - ua-parser-js "^0.7.18" - fd-slicer@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65" @@ -1882,7 +1822,7 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" -https-proxy-agent@^2.2.4: +https-proxy-agent@^2.2.1: version "2.2.4" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b" integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg== @@ -1955,13 +1895,6 @@ inquirer@^3.0.6: strip-ansi "^4.0.0" through "^2.3.6" -invariant@*: - version "2.2.4" - resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" - integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== - dependencies: - loose-envify "^1.0.0" - invariant@^2.2.0, invariant@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360" @@ -2108,13 +2041,6 @@ isobject@^2.0.0: dependencies: isarray "1.0.0" -isomorphic-fetch@^2.1.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" - dependencies: - node-fetch "^1.0.1" - whatwg-fetch ">=0.10.0" - isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -2580,7 +2506,7 @@ longest@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" -loose-envify@^1.0.0, loose-envify@^1.3.1: +loose-envify@^1.0.0: version "1.3.1" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" dependencies: @@ -2747,7 +2673,7 @@ needle@^2.2.1: iconv-lite "^0.4.4" sax "^1.2.4" -node-fetch@^1.0.1, node-fetch@^1.7.3: +node-fetch@^1.7.3: version "1.7.3" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ== @@ -2773,10 +2699,10 @@ node-notifier@^5.0.2: shellwords "^0.1.0" which "^1.2.12" -node-pre-gyp@^0.13.0: - version "0.13.0" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.13.0.tgz#df9ab7b68dd6498137717838e4f92a33fc9daa42" - integrity sha512-Md1D3xnEne8b/HGVQkZZwV27WUi1ZRuZBij24TNaZwUPU3ZAFtvT6xxJGaUVillfmMKnn5oD1HoGsp2Ftik7SQ== +node-pre-gyp@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz#db1f33215272f692cd38f03238e3e9b47c5dd054" + integrity sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q== dependencies: detect-libc "^1.0.2" mkdirp "^0.5.1" @@ -3182,11 +3108,6 @@ rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" -react-clone-referenced-element@*: - version "1.1.0" - resolved "https://registry.yarnpkg.com/react-clone-referenced-element/-/react-clone-referenced-element-1.1.0.tgz#9cdda7f2aeb54fea791f3ab8c6ab96c7a77d0158" - integrity sha512-FKOsfKbBkPxYE8576EM6uAfHC4rnMpLyH6/TJUL4WcHUEB3EUn8AxPjnnV/IiwSSzsClvHYK+sDELKN/EJ0WYg== - react-deep-force-update@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/react-deep-force-update/-/react-deep-force-update-1.1.1.tgz#bcd31478027b64b3339f108921ab520b4313dc2c" @@ -3258,22 +3179,21 @@ readable-stream@^2.0.0, readable-stream@^2.0.5, readable-stream@^2.0.6, readable string_decoder "~1.0.3" util-deprecate "~1.0.1" -realm@^3.6.4: - version "3.6.4" - resolved "https://registry.yarnpkg.com/realm/-/realm-3.6.4.tgz#efbe85c25fb9b358b1a1bde38da1f1c8bd49bccb" - integrity sha512-0nkeGAK3wWSn7uXcrBjT/25TYO8n1ylnnQjGbLbfivj3v7c+sGO/Q/AXWcvwjeAFLwYI0S1ema0o21L5JVmt0A== +realm@^2.0.13: + version "2.29.2" + resolved "https://registry.yarnpkg.com/realm/-/realm-2.29.2.tgz#818d7f333572cc3ebc4e86a8286ffc09deb518eb" + integrity sha512-7aopEcWftK05yIvo7VQ6P+2w0V2KiKF74kudBIeVOxaoKSfHfpM7SzhOnzLMEt4FVbV0bizd+lIiPhyMun6lJQ== dependencies: command-line-args "^4.0.6" decompress "^4.2.0" deepmerge "2.1.0" - deprecated-react-native-listview "0.0.6" fs-extra "^4.0.3" - https-proxy-agent "^2.2.4" + https-proxy-agent "^2.2.1" ini "^1.3.5" nan "^2.12.1" node-fetch "^1.7.3" node-machine-id "^1.1.10" - node-pre-gyp "^0.13.0" + node-pre-gyp "^0.11.0" progress "^2.0.3" prop-types "^15.6.2" request "^2.88.0" @@ -3507,10 +3427,6 @@ set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" -setimmediate@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" - shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" @@ -3899,11 +3815,6 @@ typical@^2.6.0, typical@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/typical/-/typical-2.6.1.tgz#5c080e5d661cbbe38259d2e70a3c7253e873881d" -ua-parser-js@^0.7.18: - version "0.7.21" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.21.tgz#853cf9ce93f642f67174273cc34565ae6f308777" - integrity sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ== - uglify-js@^2.6: version "2.8.29" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd" @@ -4002,10 +3913,6 @@ whatwg-encoding@^1.0.1: dependencies: iconv-lite "0.4.19" -whatwg-fetch@>=0.10.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-1.1.1.tgz#ac3c9d39f320c6dce5339969d054ef43dd333319" - whatwg-url@^4.3.0: version "4.8.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-4.8.0.tgz#d2981aa9148c1e00a41c5a6131166ab4683bbcc0" From 7e5d7632cc5c6030221f03d0fd8f1eb37e407fc3 Mon Sep 17 00:00:00 2001 From: Gareth Tomlinson Date: Sat, 29 Feb 2020 12:59:08 +0000 Subject: [PATCH 7/7] Bump realm back up. Turned out not to be a realm version issue on iOS but a node version / realm incompatability. --- package.json | 2 +- yarn.lock | 123 +++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 111 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 37cf50e..d723b7a 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "moment": "^2.24.0", "promise-reflect": "^1.1.0", "react-native-uuid": "^1.4.9", - "realm": "^2.0.13" + "realm": "^3.6.4" }, "devDependencies": { "babel-eslint": "^8.0.3", diff --git a/yarn.lock b/yarn.lock index 6ba0c3b..0fa98aa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -999,10 +999,20 @@ convert-source-map@^1.4.0, convert-source-map@^1.5.0: version "1.5.1" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5" +core-js@^1.0.0: + version "1.2.7" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" + integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY= + core-js@^2.4.0, core-js@^2.5.0: version "2.5.1" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.1.tgz#ae6874dc66937789b80754ff5428df66819ca50b" +core-js@^2.4.1: + version "2.6.11" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" + integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== + core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -1017,6 +1027,15 @@ coveralls@^3.0.0: minimist "^1.2.0" request "^2.79.0" +create-react-class@*: + version "15.6.3" + resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.6.3.tgz#2d73237fb3f970ae6ebe011a9e66f46dbca80036" + integrity sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg== + dependencies: + fbjs "^0.8.9" + loose-envify "^1.3.1" + object-assign "^4.1.1" + cross-spawn@^5.0.1, cross-spawn@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" @@ -1168,6 +1187,16 @@ delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" +deprecated-react-native-listview@0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/deprecated-react-native-listview/-/deprecated-react-native-listview-0.0.6.tgz#f9169dd703398a7792e5bcb8b851e741a8cb6980" + integrity sha512-QRuN0Dcv3Muu1oT8MhZgyfqm77bTAegVwqSRJKVwVVsm0xJE0TBfqdD45VXYLIS+yMXPJoeXdSqSdVQSjwlOpQ== + dependencies: + create-react-class "*" + fbjs "*" + invariant "*" + react-clone-referenced-element "*" + detect-indent@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" @@ -1425,6 +1454,38 @@ fb-watchman@^2.0.0: dependencies: bser "^2.0.0" +fbjs-css-vars@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz#216551136ae02fe255932c3ec8775f18e2c078b8" + integrity sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ== + +fbjs@*: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-1.0.0.tgz#52c215e0883a3c86af2a7a776ed51525ae8e0a5a" + integrity sha512-MUgcMEJaFhCaF1QtWGnmq9ZDRAzECTCRAF7O6UZIlAlkTs1SasiX9aP0Iw7wfD2mJ7wDTNfg2w7u5fSCwJk1OA== + dependencies: + core-js "^2.4.1" + fbjs-css-vars "^1.0.0" + isomorphic-fetch "^2.1.1" + loose-envify "^1.0.0" + object-assign "^4.1.0" + promise "^7.1.1" + setimmediate "^1.0.5" + ua-parser-js "^0.7.18" + +fbjs@^0.8.9: + version "0.8.17" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd" + integrity sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90= + dependencies: + core-js "^1.0.0" + isomorphic-fetch "^2.1.1" + loose-envify "^1.0.0" + object-assign "^4.1.0" + promise "^7.1.1" + setimmediate "^1.0.5" + ua-parser-js "^0.7.18" + fd-slicer@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65" @@ -1822,7 +1883,7 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" -https-proxy-agent@^2.2.1: +https-proxy-agent@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b" integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg== @@ -1895,6 +1956,13 @@ inquirer@^3.0.6: strip-ansi "^4.0.0" through "^2.3.6" +invariant@*: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + invariant@^2.2.0, invariant@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360" @@ -2041,6 +2109,14 @@ isobject@^2.0.0: dependencies: isarray "1.0.0" +isomorphic-fetch@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" + integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk= + dependencies: + node-fetch "^1.0.1" + whatwg-fetch ">=0.10.0" + isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -2512,7 +2588,7 @@ loose-envify@^1.0.0: dependencies: js-tokens "^3.0.0" -loose-envify@^1.4.0: +loose-envify@^1.3.1, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -2673,7 +2749,7 @@ needle@^2.2.1: iconv-lite "^0.4.4" sax "^1.2.4" -node-fetch@^1.7.3: +node-fetch@^1.0.1, node-fetch@^1.7.3: version "1.7.3" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ== @@ -2699,10 +2775,10 @@ node-notifier@^5.0.2: shellwords "^0.1.0" which "^1.2.12" -node-pre-gyp@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz#db1f33215272f692cd38f03238e3e9b47c5dd054" - integrity sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q== +node-pre-gyp@^0.13.0: + version "0.13.0" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.13.0.tgz#df9ab7b68dd6498137717838e4f92a33fc9daa42" + integrity sha512-Md1D3xnEne8b/HGVQkZZwV27WUi1ZRuZBij24TNaZwUPU3ZAFtvT6xxJGaUVillfmMKnn5oD1HoGsp2Ftik7SQ== dependencies: detect-libc "^1.0.2" mkdirp "^0.5.1" @@ -3108,6 +3184,11 @@ rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" +react-clone-referenced-element@*: + version "1.1.0" + resolved "https://registry.yarnpkg.com/react-clone-referenced-element/-/react-clone-referenced-element-1.1.0.tgz#9cdda7f2aeb54fea791f3ab8c6ab96c7a77d0158" + integrity sha512-FKOsfKbBkPxYE8576EM6uAfHC4rnMpLyH6/TJUL4WcHUEB3EUn8AxPjnnV/IiwSSzsClvHYK+sDELKN/EJ0WYg== + react-deep-force-update@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/react-deep-force-update/-/react-deep-force-update-1.1.1.tgz#bcd31478027b64b3339f108921ab520b4313dc2c" @@ -3179,21 +3260,22 @@ readable-stream@^2.0.0, readable-stream@^2.0.5, readable-stream@^2.0.6, readable string_decoder "~1.0.3" util-deprecate "~1.0.1" -realm@^2.0.13: - version "2.29.2" - resolved "https://registry.yarnpkg.com/realm/-/realm-2.29.2.tgz#818d7f333572cc3ebc4e86a8286ffc09deb518eb" - integrity sha512-7aopEcWftK05yIvo7VQ6P+2w0V2KiKF74kudBIeVOxaoKSfHfpM7SzhOnzLMEt4FVbV0bizd+lIiPhyMun6lJQ== +realm@^3.6.4: + version "3.6.4" + resolved "https://registry.yarnpkg.com/realm/-/realm-3.6.4.tgz#efbe85c25fb9b358b1a1bde38da1f1c8bd49bccb" + integrity sha512-0nkeGAK3wWSn7uXcrBjT/25TYO8n1ylnnQjGbLbfivj3v7c+sGO/Q/AXWcvwjeAFLwYI0S1ema0o21L5JVmt0A== dependencies: command-line-args "^4.0.6" decompress "^4.2.0" deepmerge "2.1.0" + deprecated-react-native-listview "0.0.6" fs-extra "^4.0.3" - https-proxy-agent "^2.2.1" + https-proxy-agent "^2.2.4" ini "^1.3.5" nan "^2.12.1" node-fetch "^1.7.3" node-machine-id "^1.1.10" - node-pre-gyp "^0.11.0" + node-pre-gyp "^0.13.0" progress "^2.0.3" prop-types "^15.6.2" request "^2.88.0" @@ -3427,6 +3509,11 @@ set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" +setimmediate@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= + shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" @@ -3815,6 +3902,11 @@ typical@^2.6.0, typical@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/typical/-/typical-2.6.1.tgz#5c080e5d661cbbe38259d2e70a3c7253e873881d" +ua-parser-js@^0.7.18: + version "0.7.21" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.21.tgz#853cf9ce93f642f67174273cc34565ae6f308777" + integrity sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ== + uglify-js@^2.6: version "2.8.29" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd" @@ -3913,6 +4005,11 @@ whatwg-encoding@^1.0.1: dependencies: iconv-lite "0.4.19" +whatwg-fetch@>=0.10.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb" + integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q== + whatwg-url@^4.3.0: version "4.8.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-4.8.0.tgz#d2981aa9148c1e00a41c5a6131166ab4683bbcc0"