UNPKG

statistic-mongoose

Version:

Override Feathers service adapter for the Mongoose ORM

728 lines (641 loc) 21.1 kB
/* eslint-disable no-unused-expressions */ const { expect } = require('chai'); const { base, orm } = require('feathers-service-tests'); const mongoose = require('mongoose'); const errors = require('@feathersjs/errors'); const feathers = require('@feathersjs/feathers'); const adapter = require('../lib'); const { User, Pet, Peeps, CustomPeeps, Post, TextPost, Statistic } = require('./models'); const _ids = {}; const _petIds = {}; const app = feathers() .use('/statistics', adapter({ Model: Statistic })) .use('/peeps', adapter({ Model: Peeps, events: ['testing'] })) .use('/peeps-customid', adapter({ id: 'customid', Model: CustomPeeps, events: ['testing'] })) .use('/people', adapter({ Model: User, lean: false })) .use('/pets', adapter({ Model: Pet, lean: false })) .use('/people2', adapter({ Model: User })) .use('/pets2', adapter({ Model: Pet })) .use('/posts', adapter({ Model: Post, discriminators: [TextPost] })); const statistic = app.service('statistics'); const people = app.service('people'); const pets = app.service('pets'); const leanPeople = app.service('people2'); const leanPets = app.service('pets2'); const posts = app.service('posts'); // Tell mongoose to use native promises // See http://mongoosejs.com/docs/promises.html mongoose.Promise = global.Promise; // Connect to your MongoDB instance(s) mongoose.connect('mongodb://localhost:27017/feathers'); describe('Feathers Mongoose Service', () => { describe('Requiring', () => { const lib = require('../lib'); it('exposes the service as a default module', () => { expect(typeof lib).to.equal('function'); }); it('exposes the Service Constructor', () => { expect(typeof lib.Service).to.equal('function'); }); it('exposes hooks', () => { expect(typeof lib.hooks).to.equal('object'); }); }); describe('Initialization', () => { describe('when missing options', () => { it('throws an error', () => { expect(adapter.bind(null)).to.throw('Mongoose options have to be provided'); }); }); describe('when missing a Model', () => { it('throws an error', () => { expect(adapter.bind(null, { name: 'Test' })).to.throw(/You must provide a Mongoose Model/); }); }); describe('when missing the id option', () => { it('sets the default to be _id', () => { expect(people.id).to.equal('_id'); }); }); describe('when missing the paginate option', () => { it('sets the default to be {}', () => { expect(people.paginate).to.deep.equal({}); }); }); describe('when missing the overwrite option', () => { it('sets the default to be true', () => { expect(people.overwrite).to.be.true; }); }); describe('when missing the lean option', () => { it('sets the default to be false', () => { expect(people.lean).to.be.false; }); }); }); describe('Special collation param', () => { function indexOfName(results, name) { let index; results.every(function (person, i) { if (person.name === name) { index = i; return false; } return true; }); return index; } beforeEach(() => { return people.remove(null, {}).then(() => { return people.create([ { name: 'AAA' }, { name: 'aaa' }, { name: 'ccc' } ]); }); }); it('sorts with default behavior without collation param', () => { return people.find({ query: { $sort: { name: -1 } } }).then(r => { expect(indexOfName(r, 'aaa')).to.be.below(indexOfName(r, 'AAA')); }); }); it('sorts using collation param if present', () => { return people .find({ query: { $sort: { name: -1 } }, collation: { locale: 'en', strength: 1 } }) .then(r => { expect(indexOfName(r, 'AAA')).to.be.below(indexOfName(r, 'aaa')); }); }); it('removes with default behavior without collation param', () => { return people .remove(null, { query: { name: { $gt: 'AAA' } } }) .then(() => { return people.find().then(r => { expect(r).to.have.lengthOf(1); expect(r[0].name).to.equal('AAA'); }); }); }); it('removes using collation param if present', () => { return people .remove(null, { query: { name: { $gt: 'AAA' } }, collation: { locale: 'en', strength: 1 } }) .then(() => { return people.find().then(r => { expect(r).to.have.lengthOf(3); }); }); }); it('updates with default behavior without collation param', () => { const query = { name: { $gt: 'AAA' } }; return people.patch(null, { age: 99 }, { query }).then(r => { expect(r).to.have.lengthOf(2); r.forEach(person => { expect(person.age).to.equal(99); }); }); }); it('updates using collation param if present', () => { return people .patch( null, { age: 110 }, { query: { name: { $gt: 'AAA' } }, collation: { locale: 'en', strength: 1 } } ) .then(r => { expect(r).to.have.lengthOf(1); expect(r[0].name).to.equal('ccc'); }); }); }); describe('Common functionality', () => { beforeEach(() => { // FIXME (EK): This is shit. We should be loading fixtures // using the raw driver not our system under test return pets.create({ type: 'dog', name: 'Rufus', gender: 'Unknown' }).then(pet => { _petIds.Rufus = pet._id; return people.create({ name: 'Doug', age: 32, pets: [pet._id] }).then(user => { _ids.Doug = user._id; }); }); }); afterEach(() => { return pets.remove(null, { query: {} }).then(() => people.remove(null, { query: {} }) ); }); it('can $select with a String', function (done) { var params = { query: { name: 'Rufus', $select: '+gender' } }; pets.find(params).then(data => { expect(data[0].gender).to.equal('Unknown'); done(); }); }); it('can $select with an Array', function (done) { var params = { query: { name: 'Rufus', $select: ['gender'] } }; pets.find(params).then(data => { expect(data[0].gender).to.equal('Unknown'); done(); }); }); it('can $select with an Object', function (done) { var params = { query: { name: 'Rufus', $select: { 'gender': true } } }; pets.find(params).then(data => { expect(data[0].gender).to.equal('Unknown'); done(); }); }); it('can $populate with find', function (done) { var params = { query: { name: 'Doug', $populate: ['pets'] } }; people.find(params).then(data => { expect(data[0].pets[0].name).to.equal('Rufus'); done(); }); }); it('can $populate with get', function (done) { var params = { query: { $populate: ['pets'] } }; people.get(_ids.Doug, params).then(data => { expect(data.pets[0].name).to.equal('Rufus'); done(); }).catch(done); }); it('can patch a mongoose model', function (done) { people.get(_ids.Doug).then(dougModel => { people.patch(_ids.Doug, dougModel).then(data => { expect(data.name).to.equal('Doug'); done(); }).catch(done); }).catch(done); }); it('can patch a mongoose model', function (done) { people.get(_ids.Doug).then(dougModel => { people.update(_ids.Doug, dougModel).then(data => { expect(data.name).to.equal('Doug'); done(); }).catch(done); }).catch(done); }); it('can upsert with patch', function (done) { var data = { name: 'Henry', age: 300 }; var params = { mongoose: { upsert: true }, query: { name: 'Henry' } }; people.patch(null, data, params).then(data => { expect(Array.isArray(data)).to.equal(true); var henry = data[0]; expect(henry.name).to.equal('Henry'); done(); }).catch(done); }); it('can upsert with patch & receive writeResult', function (done) { var data = { name: 'John', age: 200 }; var params = { mongoose: { upsert: true, writeResult: true }, query: { name: 'John' } }; people.patch(null, data, params).then(data => { expect(data).to.be.instanceOf(Object); expect(data).to.have.property('n', 1); expect(data).to.have.property('ok', 1); expect(data).to.have.property('nModified', 0); expect(data).to.have.property('upserted').instanceOf(Array).with.length(1); done(); }).catch(done); }); it('can $populate with update', function (done) { var params = { query: { $populate: ['pets'] } }; people.get(_ids.Doug).then(doug => { var newDoug = doug.toObject(); newDoug.name = 'Bob'; people.update(_ids.Doug, newDoug, params).then(data => { expect(data.name).to.equal('Bob'); expect(data.pets[0].name).to.equal('Rufus'); done(); }).catch(done); }).catch(done); }); it('can $populate with patch', function (done) { var params = { query: { $populate: ['pets'] } }; people.patch(_ids.Doug, { name: 'Bob' }, params).then(data => { expect(data.name).to.equal('Bob'); expect(data.pets[0].name).to.equal('Rufus'); done(); }).catch(done); }); it('can $push an item onto an array with update', function (done) { pets.create({ type: 'cat', name: 'Margeaux' }).then(margeaux => { people.update(_ids.Doug, { $push: { pets: margeaux } }) .then(() => { var params = { query: { $populate: ['pets'] } }; people.get(_ids.Doug, params).then(data => { expect(data.pets[1].name).to.equal('Margeaux'); done(); }).catch(done); }).catch(done); }).catch(done); }); it('can $push an item onto an array with patch', function (done) { pets.create({ type: 'cat', name: 'Margeaux' }).then(margeaux => { people.patch(_ids.Doug, { $push: { pets: margeaux } }) .then(() => { var params = { query: { $populate: ['pets'] } }; people.get(_ids.Doug, params).then(data => { expect(data.pets[1].name).to.equal('Margeaux'); done(); }).catch(done); }).catch(done); }).catch(done); }); it('runs validators on update', function () { return people.create({ name: 'David', age: 33 }) .then(person => people.update(person._id, { name: 'Dada', age: 'wrong' })) .then(() => { throw new Error('Update should not be successful'); }) .catch(error => { expect(error.name).to.equal('BadRequest'); expect(error.message).to.equal('User validation failed: age: Cast to Number failed for value "wrong" at path "age"'); }); }); it('runs validators on patch', function (done) { people.create({ name: 'David', age: 33 }) .then(person => people.patch(person._id, { name: 'Dada', age: 'wrong' })) .then(() => done(new Error('Update should not be successful'))) .catch(error => { expect(error.name).to.equal('BadRequest'); expect(error.message).to.equal('Cast to number failed for value "wrong" at path "age"'); done(); }); }); it('returns a Conflict when unique index is violated', function (done) { pets.create({ type: 'cat', name: 'Bob' }) .then(() => pets.create({ type: 'cat', name: 'Bob' })) .then(() => done(new Error('Should not be successful'))) .catch(error => { expect(error.name).to.equal('Conflict'); done(); }); }); orm(leanPeople, errors, '_id'); }); describe('Lean Services', () => { beforeEach((done) => { // FIXME (EK): This is shit. We should be loading fixtures // using the raw driver not our system under test leanPets.create({ type: 'dog', name: 'Rufus' }).then(pet => { _petIds.Rufus = pet._id; return leanPeople.create({ name: 'Doug', age: 32, pets: [pet._id] }).then(user => { _ids.Doug = user._id; done(); }); }); }); afterEach(done => { leanPets.remove(null, { query: {} }).then(() => { return leanPeople.remove(null, { query: {} }).then(() => { return done(); }); }); }); it('can $populate with find', function (done) { var params = { query: { name: 'Doug', $populate: ['pets'] } }; leanPeople.find(params).then(data => { expect(data[0].pets[0].name).to.equal('Rufus'); done(); }); }); it('can $populate with get', function (done) { var params = { query: { $populate: ['pets'] } }; leanPeople.get(_ids.Doug, params).then(data => { expect(data.pets[0].name).to.equal('Rufus'); done(); }).catch(done); }); it('can upsert with patch', function (done) { var data = { name: 'Henry', age: 300 }; var params = { mongoose: { upsert: true }, query: { name: 'Henry' } }; leanPeople.patch(null, data, params).then(data => { expect(Array.isArray(data)).to.equal(true); var henry = data[0]; expect(henry.name).to.equal('Henry'); done(); }).catch(done); }); }); describe('Discriminators', () => { const data = { _type: 'text', text: 'Feathers!!!' }; afterEach(done => { posts.remove(null, { query: {} }) .then(data => { done(); }); }); it('can get a discriminated model', function (done) { posts.create(data) .then(data => posts.get(data._id)) .then(data => { expect(data._type).to.equal('text'); expect(data.text).to.equal('Feathers!!!'); done(); }); }); it('can find discriminated models by the type', function (done) { posts.create(data) .then(data => posts.find({ query: { _type: 'text' } })) .then(data => { data.forEach(element => { expect(element._type).to.equal('text'); }); done(); }); }); it('can create a discriminated model', function (done) { posts.create(data) .then(data => { expect(data._type).to.equal('text'); expect(data.text).to.equal('Feathers!!!'); done(); }); }); it('can update a discriminated model', function (done) { const update = { _type: 'text', text: 'Hello, world!', createdAt: Date.now(), updatedAt: Date.now() }; const params = { query: { _type: 'text' } }; posts.create(data) .then(data => posts.update(data._id, update, params)) .then(data => { expect(data._type).to.equal('text'); expect(data.text).to.equal('Hello, world!'); done(); }); }); it('can patch a discriminated model', function (done) { const update = { text: 'Howdy folks!' }; const params = { query: { _type: 'text' } }; posts.create(data) .then(data => posts.patch(data._id, update, params)) .then(data => { expect(data.text).to.equal('Howdy folks!'); done(); }); }); it('can remove a discriminated model', function (done) { posts.create(data) .then(data => posts.remove(data._id, { query: { _type: 'text' } })) .then(data => { expect(data._type).to.equal('text'); done(); }); }); }); describe('Common tests', () => { before(() => Promise.all([ app.service('peeps').remove(null), app.service('peeps-customid').remove(null) ])); base(app, errors, 'peeps', '_id'); base(app, errors, 'peeps-customid', 'customid'); }); describe('create data test', () => { beforeEach(() => { return statistic.remove(null, {}).then(() => { return statistic.create([ { number: 21321, createdOn: new Date('2020-08-01T05:51:28.056Z') }, { number: 21332, createdOn: new Date('2020-08-02T05:51:28.056Z') }, { number: 22, createdOn: new Date('2020-08-03T05:51:28.056Z') }, { number: 222, createdOn: new Date('2020-08-04T05:51:28.056Z') }, { number: 4324, createdOn: new Date('2020-08-05T05:51:28.056Z') }, { number: 32535, createdOn: new Date('2020-08-06T05:51:28.056Z') }, { number: 543, createdOn: new Date('2020-08-07T05:51:28.056Z') }, { number: 34, createdOn: new Date('2020-08-08T05:51:28.056Z') }, { number: 32423, createdOn: new Date('2020-08-09T05:51:28.056Z') }, { number: 213, createdOn: new Date('2020-08-10T05:51:28.056Z') }, { number: 4545, createdOn: new Date('2020-08-11T05:51:28.056Z') }, { number: 6547, createdOn: new Date('2020-08-12T05:51:28.056Z') }, { number: 435, createdOn: new Date('2020-08-13T05:51:28.056Z') }, { number: 32423, createdOn: new Date('2020-08-14T05:51:28.056Z') }, { number: 65756, createdOn: new Date('2020-08-15T05:51:28.056Z') }, { number: 343242, createdOn: new Date('2020-08-16T05:51:28.056Z') }, { number: 35325, createdOn: new Date('2020-08-17T05:51:28.056Z') }, { number: 34, createdOn: new Date('2020-08-18T05:51:28.056Z') }, { number: 342141, createdOn: new Date('2020-08-19T05:51:28.056Z') }, { number: 235, createdOn: new Date('2020-08-20T05:51:28.056Z') }, { number: 234, createdOn: new Date('2020-08-21T05:51:28.056Z') }, { number: 432423, createdOn: new Date('2020-08-22T05:51:28.056Z') }, { number: 44, createdOn: new Date('2020-08-23T05:51:28.056Z') }, { number: 43243, createdOn: new Date('2020-08-24T05:51:28.056Z') }, { number: 122, createdOn: new Date('2020-08-25T05:51:28.056Z') }, { number: 312321, createdOn: new Date('2020-08-26T05:51:28.056Z') }, { number: 434, createdOn: new Date('2020-08-27T05:51:28.056Z') }, { number: 357, createdOn: new Date('2020-08-28T05:51:28.056Z') }, { number: 454, createdOn: new Date('2020-08-29T05:51:28.056Z') }, { number: 32423, createdOn: new Date('2020-08-30T12:51:28.056Z') }, { number: 44, createdOn: new Date('2020-08-30T20:51:28.056Z') }, { number: 1222, createdOn: new Date('2020-08-30T14:51:28.056Z') } ]); }); }); it('Get average value with auto', () => { return people.find({ query: { $statistic: [ { value: '100' }, { averageBy: 'number' }, { sortBy: 'createdOn' } ] } }) .then(r => { expect(r).to.have.lengthOf(100); }); }); it('Get average value group by day', () => { return people .find({ query: { $statistic: [ { value: '100' }, { averageBy: 'number' }, { sortBy: 'createdOn' }, { option: 'day' } ], createdOn: { $lt: '2020-08-29T23:59:58.396Z', $gte: '2020-08-01T23:59:58.396Z' } } }) .then(r => { expect(r).to.be.ok; }); }); it('Get average value limit 100', () => { return people .find({ query: { $limit: 1000, $statistic: [ { value: '100' }, { averageBy: 'number' }, { sortBy: 'createdOn' } ] } }) .then(r => { expect(r).to.have.lengthOf(100); }); }); it('Get average value less then 10000', () => { return people .find({ query: { number: { $lt: 10000 }, $statistic: [ { value: '100' }, { averageBy: 'number' }, { sortBy: 'createdOn' } ] } }) .then(r => { expect(r).to.have.lengthOf(100); }); }); }) });