"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const chai_1 = require("chai");
const interface_datastore_1 = require("interface-datastore");
const datastore_level_1 = __importDefault(require("datastore-level"));
const browser_or_node_1 = require("browser-or-node");
const sinon_1 = __importDefault(require("sinon"));
const queue_1 = require("./queue");
const level = require('level');
describe('Queue', () => {
    describe('Constructor', () => {
        it('should use memory store by default', () => __awaiter(void 0, void 0, void 0, function* () {
            const q = new queue_1.Queue();
            chai_1.expect(yield q.open()).to.be.empty;
        }));
        it('should throw when passed a batchSize less than 1', () => {
            chai_1.expect(() => {
                new queue_1.Queue(new interface_datastore_1.MemoryDatastore(), -1);
            }).to.throw(Error);
        });
    });
    describe('Correct queue fifo order', () => {
        let q;
        before(() => {
            // Remove previous db3.sqlite (if exists) before creating db anew
            q = new queue_1.Queue();
        });
        it('should execute jobs in fifo order', done => {
            let sequence = 0;
            q.on('next', task => {
                chai_1.expect(task.job.sequence).to.equal(sequence++);
                q.done();
            });
            q.on('empty', () => {
                q.close();
                done();
            });
            q.open().then(() => {
                q.start();
                for (let i = 0; i < 1000; ++i) {
                    const task = { sequence: i };
                    q.push(task);
                }
            });
        });
    });
    describe('Search remaining jobs', function () {
        let q;
        beforeEach(done => {
            q = new queue_1.Queue();
            q.open()
                .then(() => done())
                .catch(err => done(err));
        });
        it('should find first job in the queue', done => {
            q.open().then(() => {
                const promises = [];
                for (let i = 1; i <= 1000; ++i) {
                    const task = { sequence: i % 501 };
                    promises.push(q.push(task, `${i}`));
                }
                // Wait for all tasks to be pushed before calling hasJob method to search for it
                Promise.all(promises).then(() => {
                    for (let i = 1; i <= 500; ++i)
                        q.getFirstJobId({ sequence: i }).then(id => {
                            chai_1.expect(id).to.equal(`${i}`);
                        });
                    q.close().then(() => done());
                });
            });
        });
        it('should find all matching jobs in the queue and in order', done => {
            q.open().then(() => {
                const promises = [];
                for (let i = 1; i <= 10; ++i) {
                    const task = { sequence: i % 5 };
                    promises.push(q.push(task, `${i}`));
                }
                // Wait for all tasks to be pushed before calling hasJob method to search for it
                Promise.all(promises).then(() => {
                    for (let i = 1; i <= 5; ++i)
                        q.getJobIds({ sequence: i % 5 }).then(id => {
                            chai_1.expect(id).to.deep.equal([`${i}`, `${i + 5}`]);
                        });
                    q.close().then(() => {
                        done();
                    });
                });
            });
        });
        it('should return empty array if job not in queue', done => {
            q.open().then(() => {
                const promises = [];
                for (let i = 1; i <= 10; ++i) {
                    const task = { sequence: i };
                    promises.push(q.push(task));
                }
                // Wait for all tasks to be pushed before calling hasJob method to search for it
                Promise.all(promises).then(() => {
                    for (let i = 1; i <= 5; ++i)
                        q.getJobIds({ sequence: 100 }).then(id => {
                            chai_1.expect(id).to.be.empty;
                        });
                    q.close().then(() => {
                        done();
                    });
                });
            });
        });
        it('should return undefined if job not in queue', done => {
            q.open().then(() => {
                const promises = [];
                for (let i = 1; i <= 10; ++i) {
                    const task = { sequence: i };
                    promises.push(q.push(task));
                }
                // Wait for all tasks to be pushed before calling hasJob method to search for it
                Promise.all(promises).then(() => {
                    for (let i = 1; i <= 5; ++i)
                        q.getFirstJobId({ sequence: 100 }).then(id => {
                            chai_1.expect(id).to.be.undefined;
                        });
                    q.close().then(() => {
                        done();
                    });
                });
            });
        });
    });
    describe('Unopened DB', () => {
        const q = new queue_1.Queue(new interface_datastore_1.MemoryDatastore(), 2);
        it('should throw on calling start() before open is called', () => {
            chai_1.expect(() => {
                q.start();
            }).to.throw(Error);
        });
        it('should throw on calling isEmpty() before open is called', () => {
            chai_1.expect(() => {
                return q.isEmpty;
            }).to.throw(Error);
        });
    });
    describe('Maintaining queue length count', () => {
        let store;
        const tmp = 'test.db';
        before(done => {
            store = new interface_datastore_1.MemoryDatastore();
            store.open().then(() => {
                store.put(new interface_datastore_1.Key('one'), Buffer.from('0')).then(() => {
                    done();
                });
            });
        });
        after(() => {
            level.destroy(tmp, () => {
                return;
            });
        });
        it('should count existing jobs in db on open', function (done) {
            if (browser_or_node_1.isBrowser)
                return this.skip();
            const q = new queue_1.Queue(store);
            q.open()
                .then(() => {
                chai_1.expect(q.length).to.equal(1);
                return q.close();
            })
                .then(() => {
                done();
            })
                .catch(err => {
                done(err);
            });
        });
        it('should count jobs as pushed and completed', function (done) {
            if (browser_or_node_1.isBrowser)
                return this.skip();
            let q = new queue_1.Queue(new datastore_level_1.default(tmp));
            // Count jobs
            let c = 0;
            q.on('push', () => {
                chai_1.expect(q.length).to.equal(++c);
            });
            q.open()
                .then(() => {
                q.push('1');
                q.push('2');
                q.push('3');
                return q.close();
            })
                .then(() => {
                q = new queue_1.Queue(new datastore_level_1.default(tmp));
                return q.open();
            })
                .then(() => {
                chai_1.expect(q.length).to.equal(3);
                q.on('next', () => {
                    chai_1.expect(q.length).to.equal(c--);
                    q.done();
                });
                q.on('empty', () => {
                    chai_1.expect(q.length).to.equal(0);
                    q.close().then(() => {
                        q.store.close();
                        done();
                    });
                });
                q.start();
            })
                .catch(err => {
                done(err);
            });
        });
    });
    describe('Close Errors', () => {
        const q = new queue_1.Queue();
        before(done => {
            q.open().then(() => {
                done();
            });
        });
        it('should close properly', () => __awaiter(void 0, void 0, void 0, function* () {
            q.push('1');
            chai_1.expect(yield q.close()).to.be.undefined;
        }));
    });
    describe('Emitters', () => {
        let q;
        beforeEach(done => {
            q = new queue_1.Queue();
            q.open()
                .then(() => {
                done();
            })
                .catch(err => {
                done(err);
            });
        });
        afterEach(done => {
            q.close()
                .then(() => {
                done();
            })
                .catch(err => {
                done(err);
            });
        });
        it('should emit push', done => {
            q.on('push', job => {
                chai_1.expect(job.job).to.equal('1');
                done();
            });
            q.push('1');
        });
        it('should emit start', done => {
            const s = sinon_1.default.spy();
            q.on('start', s);
            q.start();
            chai_1.expect(s.callCount).to.equal(1);
            chai_1.expect(q.isStarted).to.be.equal(true);
            done();
        });
        it('should emit next when pushing after start', done => {
            q.on('next', job => {
                chai_1.expect(job.job).to.equal('1');
                q.done();
                done();
            });
            q.start();
            q.push('1');
        });
        it('should emit next when pushing before start', done => {
            q.on('next', job => {
                chai_1.expect(job.job).to.equal('1');
                q.done();
                done();
            });
            q.push('1');
            q.start();
        });
        it('should emit empty', done => {
            let empty = 0;
            q.on('empty', () => {
                // empty should only emit once
                chai_1.expect(++empty).to.equal(1);
                chai_1.expect(q.length).to.equal(0);
                done();
            });
            q.on('next', _job => {
                q.done();
            });
            q.push('1');
            q.push('2');
            q.start();
        });
        it('3 pushs before start should emit 3 nexts', done => {
            let next = 0;
            q.on('empty', () => {
                chai_1.expect(next).to.equal(3);
                chai_1.expect(q.length).to.equal(0);
                done();
            });
            q.on('next', _job => {
                ++next;
                q.done();
            });
            q.push('1');
            q.push('2');
            q.push('3');
            q.start();
        });
        it('should push 3 jobs and after start should emit 3 nexts', done => {
            let next = 0;
            q.on('empty', () => {
                chai_1.expect(next).to.equal(3);
                chai_1.expect(q.length).to.equal(0);
                done();
            });
            q.on('next', job => {
                ++next;
                q.done();
            });
            q.start();
            q.push('1');
            q.push('2');
            q.push('3');
        });
        it('should start in middle of 3 pushs and should emit 3 nexts', done => {
            let next = 0;
            q.on('empty', () => {
                chai_1.expect(next).to.equal(3);
                chai_1.expect(q.length).to.equal(0);
                done();
            });
            q.on('next', job => {
                ++next;
                q.done();
            });
            q.push('1');
            q.push('2');
            q.start();
            q.push('3');
        });
        it('should emit stop', done => {
            let stop = 0;
            q.on('stop', () => {
                chai_1.expect(++stop).to.equal(1);
                chai_1.expect(q.isStarted).to.be.false;
                done();
            });
            q.on('empty', () => {
                q.stop();
            });
            q.on('next', job => {
                q.done();
            });
            q.push('1');
            q.push('2');
            q.start();
            q.push('3');
            q.push('4');
        });
        it('should emit open', done => {
            const q1 = new queue_1.Queue();
            let open = 0;
            q1.on('open', () => {
                chai_1.expect(++open).to.equal(1);
                chai_1.expect(q1.isOpen).to.be.equal(true);
                q1.close().then(() => {
                    done();
                });
            });
            q1.open();
        });
        it('should emit close', done => {
            const q1 = new queue_1.Queue();
            let close = 0;
            q1.on('close', () => {
                chai_1.expect(++close).to.equal(1);
                chai_1.expect(q1.isOpen).to.be.false;
            });
            q1.open()
                .then(() => {
                return q1.close();
            })
                .then(() => {
                done();
            });
        });
    });
});
//# sourceMappingURL=queue.spec.js.map