UNPKG

@bcc-code/feathers-arangodb

Version:
858 lines (766 loc) 28.5 kB
import feathers from '@feathersjs/feathers'; import { Application, Service } from 'feathersjs__feathers'; import { NotFound } from '@feathersjs/errors'; import ArangoDbService, { IArangoDbService, AUTH_TYPES } from '../src'; import { AutoDatabse } from '../src/auto-database'; import { expect, assert } from 'chai'; const serviceName = 'people'; const idProp = '_key'; describe(`Feathers common tests, ${serviceName} service with \\${idProp}\\ id property `, () => { const promiseDatabase = 'TEST_PROMISE_DB'; const testDatabase = 'TEST_DB'; const testCollection = 'TEST_COL'; const testUser = 'root'; const testPass = 'root'; let app: Application | any; let service: IArangoDbService<any>; let _ids: any = {}; before(async () => { app = feathers(); app.use( `/${serviceName}`, ArangoDbService({ id: idProp, collection: testCollection, database: testDatabase, authType: AUTH_TYPES.BASIC_AUTH, username: testUser, password: testPass, events: ['testing'] }) ); service = <IArangoDbService<any>>app.service(serviceName); }); after(async () => { const database = new AutoDatabse(); database.useBasicAuth(testUser, testPass); await database.dropDatabase(testDatabase); await database.dropDatabase(promiseDatabase); }); beforeEach(async () => { const data: any = await service.create({ name: 'Doug', age: 32 }); _ids.Doug = data[idProp]; }); afterEach(async () => { await service.remove(_ids.Doug).catch(() => {}); }); it('Service connects', async () => { await service.connect(); expect(service.database).not.to.be.undefined expect(service.collection).not.to.be.undefined }); it('Can connect to a specified database url', async () => { app.use( `/tasks`, ArangoDbService({ id: idProp, collection: 'tasks', database: testDatabase, authType: AUTH_TYPES.BASIC_AUTH, username: testUser, password: testPass, events: ['testing'], dbConfig: { url: 'http://localhost:8529', } }) ); const otherUrl = <IArangoDbService<any>>app.service('tasks'); await otherUrl.connect(); expect(otherUrl.database).not.to.be.undefined expect(otherUrl.collection).not.to.be.undefined }); it('Service setup check', async () => { await service.setup(); expect(service.database).not.to.be.undefined; expect(service.collection).not.to.be.undefined; }); it('works', async () => { const something = true; expect(service).not.to.be.undefined; expect(something).to.be.true; }); it('sets `id` property on the service', () => { expect(service.id).to.eq(idProp); }); it('Accepts a promise as a database reference', async () => { const autoDb = new AutoDatabse(); autoDb.useBasicAuth(testUser, testPass); const promiseDb = autoDb.autoUseDatabase(promiseDatabase); const dbService = ArangoDbService({ id: idProp, collection: testCollection, database: promiseDb }); await dbService.connect(); const info = await dbService.database.get(); expect(dbService.database).not.to.be.undefined; expect(dbService.collection).not.to.be.undefined; expect(info.name).to.eq(promiseDatabase); }); it('Accepts a promise as a collection reference', async () => { const autoDb = new AutoDatabse(); autoDb.useBasicAuth(testUser, testPass); const db = await autoDb.autoUseDatabase(promiseDatabase); const collectionPromise = autoDb.autoCollection('PROMISE_COLLECTION'); const dbService = ArangoDbService({ id: idProp, collection: collectionPromise, database: db }); await dbService.connect(); const info = await dbService.collection.get(); expect(dbService.database).not.to.be.undefined; expect(dbService.collection).not.to.be.undefined; expect(info.name).to.eq('PROMISE_COLLECTION'); }); it('Accepts string as database & collection', async () => { const dbService = ArangoDbService({ id: idProp, collection: testCollection, database: testDatabase, authType: AUTH_TYPES.BASIC_AUTH, username: testUser, password: testPass }); await dbService.connect(); expect(dbService.database).not.to.be.undefined; expect(dbService.collection).not.to.be.undefined; }); it('Accepts a database & collection as arguments', async () => { const database = new AutoDatabse(); database.useBasicAuth(testUser, testPass); database.useDatabase(testDatabase); const collection = await database.collection(testCollection); const dbService = ArangoDbService({ database, collection }); expect(dbService.database).not.to.be.undefined; expect(dbService.collection).not.to.be.undefined; }); it('sets `events` property from options', () => { expect(service.events.indexOf('testing')).not.to.eq(-1); }); describe('extend', () => { it('extends and uses extended method', async () => { const now = new Date().getTime(); // @ts-ignore Extend added inside feathersJS via Uberproto const extended = service.extend({ create: function create(data: any) { data.time = now; return this._super.apply(this, arguments); } }); const createResult = await extended.create({ name: 'Dave' }); const removeResult = await extended.remove(createResult[idProp]); expect(removeResult.time).to.eq(now); }); }); describe('get', () => { it('returns an instance that exists', async () => { const result = await service.get(_ids.Doug); expect(result[idProp].toString()).to.eq(_ids.Doug.toString()); expect(result.name).to.eq('Doug'); expect(result.age).to.eq(32); }); it('supports $select', async () => { const result = await service.get(_ids.Doug, { query: { $select: ['name'] } }); expect(result[idProp]).to.eq(_ids.Doug); expect(result.name).to.eq('Doug'); expect(result.age).to.be.undefined; }); it('returns NotFound error for non-existing id', () => { const badId = '568225fbfe21222432e836ff'; service .get(badId) .then(() => { throw Error('Should NOT succeed!!!'); }) .catch(error => { expect(error instanceof NotFound).to.be.true; expect(error.message).to.eq(`No record found for id '${badId}'`); }); }); }); describe('remove', () => { it('deletes an existing instance and returns the deleted instance', async () => { const result = await service.remove(_ids.Doug); expect(result['name']).to.eq('Doug'); }); it('deletes an existing instance supports $select', async () => { const result = await service.remove(_ids.Doug, {query: { $select: ['name']}}); expect(result[idProp]).to.eq(_ids.Doug); expect(result['name']).to.eq('Doug'); expect(result['age']).to.be.undefined; }); it('deletes multiple instances', async () => { await service.create({name: 'Dave', age: 29, created: true}); await service.create({name: 'David', age: 48, created: true}); const result = await service.remove(null, {query: { created: true }}); const names = result.map((person:any) => person.name); expect(names.indexOf('Dave')).to.be.greaterThan(-1); expect(names.indexOf('David')).to.be.greaterThan(-1); }) }); describe('find', () => { // Doug 32, Bob 25, ALice 19 beforeEach(async () => { const bob = await service.create({ name: 'Bob', age: 25 }); _ids.Bob = bob[idProp]; const alice = await service.create({ name: 'Alice', age: 19 }); _ids.Alice = alice[idProp]; }); afterEach(async () => { await service.remove(_ids.Bob); await service.remove(_ids.Alice); }); it('returns all items', async () => { const result = <[any]> await service.find(); expect(Array.isArray(result)).to.be.true; expect(result.length).to.eq(3); }); it('filters results by a single parameter', async () => { const result = <[any]>await service.find({ query: { name: 'Alice' } }); expect(Array.isArray(result)).to.be.true; expect(result.length).to.eq(1); expect(result[0].name).to.eq('Alice'); }); it('filters results by multiple parameters', async () => { // TODO This is a POOR test. Should be strengthened by having more than one age 19 or Alice const result = <[any]>await service.find({ query: { name: 'Alice', age: 19 } }); expect(Array.isArray(result)).to.be.true; expect(result.length).to.eq(1); expect(result[0].name).to.eq('Alice'); }); describe('falsy values', () => { beforeEach(async () => { const philip = await service.create({ name: 'Philip', age: 0}); _ids.philip = philip[idProp]; const steven = await service.create({ name: 'Steven'}); _ids.steven = steven[idProp]; }); it('can $ne to 0', async () => { const params = { query: {age: {$ne: 0}, $sort: {name: 1}} } const result = <Array<any>>await service.find(params) expect(result.length).to.eq(4) expect(result[0].name).to.eq('Alice') expect(result[1].name).to.eq('Bob') expect(result[2].name).to.eq('Doug') expect(result[3].name).to.eq('Steven') }) it('can $gt than 0', async () => { const params = { query: {age: {$gt: 0}, $sort: {name: 1}} } const result = <Array<any>>await service.find(params) expect(result.length).to.eq(3) expect(result[0].name).to.eq('Alice') expect(result[1].name).to.eq('Bob') expect(result[2].name).to.eq('Doug') }) it('can $ne to null', async () => { const params = { query: {age: {$ne: null}, $sort: {name: 1}} } const result = <Array<any>>await service.find(params) expect(result.length).to.eq(4) expect(result[0].name).to.eq('Alice') expect(result[1].name).to.eq('Bob') expect(result[2].name).to.eq('Doug') expect(result[3].name).to.eq('Philip') }) it('can equal to null', async () => { const params = { query: {age: null} } const result = <Array<any>>await service.find(params) expect(result.length).to.eq(1) expect(result[0].name).to.eq('Steven') }) afterEach(async () => { await service.remove(_ids.philip); await service.remove(_ids.steven); }); }) describe('array functions', () => { beforeEach(async () => { const mike = await service.create({ name: 'Mike', age: 0, friends: [{name: 'Alice'}, {name: 'Bob'}], parents: [{name: 'Freek'}]}); _ids.mike = mike[idProp]; const jake = await service.create({ name: 'Jake', age: 0, friends: [{ name: 'Doug'}], parents: [{name: 'Anne'}]}); _ids.jake = jake[idProp]; }); afterEach(async () => { await service.remove(_ids.mike).catch(() => {}); await service.remove(_ids.jake).catch(() => {}); }) it('can $elemMatch', async () => { const params = { query: { friends: { $elemMatch: { name: {$in: ['Alice']}}}} }; const result = <any[]>await service.find(params); const names = result.map(r => r.name) assert.equal(names.length,1) assert.include(names,'Mike') }); it('can $elemMatch for multiple', async () => { const params = { query: { friends: { $elemMatch: { name: {$in: ['Bob','Doug']}}}} }; const result = <any[]>await service.find(params); expect(result.length).to.eq(2); const names = result.map(r => r.name) assert.include(names,'Mike') assert.include(names,'Jake') }); it('can $elemMatch with $or', async () => { const params = { query: { $or: [ {friends: { $elemMatch: { name: {$in: ['Doug']} }}}, {parents: { $elemMatch: { name: {$in: ['Freek']} }}} ] } }; const result = <any[]>await service.find(params); expect(result.length).to.eq(2); const names = result.map(r => r.name) assert.include(names,'Mike') assert.include(names,'Jake') }); it('can $size equal to value', async () => { const params = { query: {friends: {$size: 2}} } const result = <Array<any>>await service.find(params) expect(result.length).to.eq(1) expect(result[0].name).to.eq('Mike') }) it('can $size greater then value', async () => { const params = { query: {friends: {$size: {$gte: 1}}} } const result = <Array<any>>await service.find(params) expect(result.length).to.eq(2) expect(result[0].name).to.eq('Mike') expect(result[1].name).to.eq('Jake') }) }) describe('special filters', () => { it('can $sort', async () => { const params = { query: { $sort: { name: 1 } } }; const result = <Array<any>>await service.find(params); expect(result.length).to.eq(3); expect(result[0].name).to.eq('Alice'); expect(result[1].name).to.eq('Bob'); expect(result[2].name).to.eq('Doug'); }); it('can $sort with strings', async () => { const params = { query: { $sort: { name: '1' } } }; const result = <Array<any>>await service.find(params); expect(result.length).to.eq(3); expect(result[0].name).to.eq('Alice'); expect(result[1].name).to.eq('Bob'); expect(result[2].name).to.eq('Doug'); }); it('can $limit', async () => { const params = { query: { $limit: 2 } }; const result = <any[]>await service.find(params); expect(result.length).to.eq(2); }); it('can $limit 0', async () => { const params = { query: { $limit: 0 } }; const result = <any[]>await service.find(params); expect(result.length).to.eq(0); }); it('can $skip', async () => { const params = { query: { $sort: { name: 1 }, $skip: 1 } }; const result = <any[]>await service.find(params); expect(result.length).to.eq(2); expect(result[0].name).to.eq('Bob'); expect(result[1].name).to.eq('Doug'); }); it('can $select', async () => { const params = { query: { name: 'Alice', $select: ['name'] } }; const result = <any[]>await service.find(params); expect(result.length).to.eq(1); expect(result[0].name).to.eq('Alice'); expect(result[0].age).to.be.undefined; }); it('can $or', async () => { const params = { query: { $or: [{ name: 'Alice' }, { name: 'Bob' }], $sort: { name: 1 } } }; const result = <any[]>await service.find(params); expect(result.length).to.eq(2); expect(result[0].name).to.eq('Alice'); expect(result[1].name).to.eq('Bob'); }); it('can $not', async () => { const params = { query: { age: { $not: 19 }, name: { $not: 'Doug' } } }; const result = <any[]>await service.find(params); expect(result.length).to.eq(1); expect(result[0].name).to.eq('Bob'); }); it('can $in', async () => { const params = { query: { name: { $in: ['Alice', 'Bob'] }, $sort: { name: 1 } } }; const result = <any[]>await service.find(params); expect(result.length).to.eq(2); expect(result[0].name).to.eq('Alice'); expect(result[1].name).to.eq('Bob'); }); it('can $nin', async () => { const params = { query: { name: { $nin: ['Alice', 'Bob'] } } }; const result = <any[]>await service.find(params); expect(result.length).to.eq(1); expect(result[0].name).to.eq('Doug'); }); it('can $lt', async () => { const params = { query: { age: { $lt: 30 }, $sort: { name: 1 } } }; const result = <any[]>await service.find(params); expect(result.length).to.eq(2); expect(result[0].name).to.eq('Alice'); expect(result[1].name).to.eq('Bob'); }); it('can $lte', async () => { const params = { query: { age: { $lte: 25 }, $sort: { name: 1 } } }; const result = <any[]>await service.find(params); expect(result.length).to.eq(2); expect(result[0].name).to.eq('Alice'); expect(result[1].name).to.eq('Bob'); }); it('can $gt', async () => { const params = { query: { age: { $gt: 30 }, $sort: { name: 1 } } }; const result = <any[]>await service.find(params); expect(result.length).to.eq(1); expect(result[0].name).to.eq('Doug'); }); it('can $gte', async () => { const params = { query: { age: { $gte: 25 }, $sort: { name: 1 } } }; const result = <any[]>await service.find(params); expect(result.length).to.eq(2); expect(result[0].name).to.eq('Bob'); expect(result[1].name).to.eq('Doug'); }); it('can $ne', async () => { const params = { query: { age: { $ne: 25 }, $sort: { name: 1 } } }; const result = <any[]>await service.find(params); expect(result.length).to.eq(2); expect(result[0].name).to.eq('Alice'); expect(result[1].name).to.eq('Doug'); }); it('can $gt and $lt and $sort', async () => { const params = { query: { age: { $gt: 18, $lt: 30 }, $sort: { name: 1 } } }; const result = <any[]>await service.find(params); expect(result.length).to.eq(2); expect(result[0].name).to.eq('Alice'); expect(result[1].name).to.eq('Bob'); }); it('can handle nested $or queries and $sort', async () => { const params = { query: { $or: [{ name: 'Doug' }, { age: { $gte: 18, $lt: 25 } }], $sort: { name: 1 } } }; const result = <any[]>await service.find(params); expect(result.length).to.eq(2); expect(result[0].name).to.eq('Alice'); expect(result[1].name).to.eq('Doug'); }); it('can handle mixed OR and AND in one query', async () => { const params = { query: { $or:[{ age: 32, }, { age:25, }], name: "Bob", }, } const result = <any[]>await service.find(params) expect(result.length).to.eq(1) expect(result[0].age).to.eq(25) }) it('can handle advanced OR and AND mix', async () => { const params = { query: { $and: [ {$or:[{ age: 32, }, { age:25, }]}, {$or:[{ name: "Bob", }, { name: "Alice", }]} ] }, } const result = <any[]>await service.find(params) expect(result.length).to.eq(1) expect(result[0].name).to.eq("Bob") }) }); describe('paginate', () => { beforeEach(async () => { service.paginate = { default: 1, max: 2 }; }); afterEach(async () => { service.paginate = {}; }); it('returns paginated object, paginates by default and shows total', async () => { const params = { query: { $sort: { name: -1 } } }; const result = <any>await service.find(params); expect(result.total).to.eq(3); expect(result.limit).to.eq(1); expect(result.skip).to.eq(0); expect(result.data[0].name).to.eq('Doug'); }); it('paginates max and skips', async () => { const params = { query: { $skip: 1, $limit: 4, $sort: { name: -1 } } }; const result = <any>await service.find(params); expect(result.total).to.eq(3); expect(result.limit).to.eq(2); expect(result.skip).to.eq(1); expect(result.data[0].name).to.eq('Bob'); expect(result.data[1].name).to.eq('Alice'); }); it('$limit 0 with pagination', async () => { const params = { query: { $limit: 0 } }; const result = <any>await service.find(params); expect(result.data.length).to.eq(0); }); it('allows to override paginate in params', async () => { const params = { paginate: { default: 2, max: 1000 } }; const result = <any>await service.find(params); expect(result.limit).to.eq(2); expect(result.skip).to.eq(0); }); it('total works with filter', async () => { const params = { query: { $sort: {name: 1}, $limit: 1, age: {$gt: 20} } }; const result = <any>await service.find(params); expect(result.total).to.eq(2); expect(result.limit).to.eq(1); expect(result.skip).to.eq(0); expect(result.data[0].name).to.eq('Bob'); }); }); }); describe('update', () => { it('replaces an existing instance, does not modify original data', async () => { const newData:any = { name: 'Dougler'}; newData[idProp] = _ids.Doug; const result = await service.update(_ids.Doug, newData); expect(result[idProp]).to.eq(_ids.Doug); expect(result['name']).to.eq('Dougler'); expect(result['age']).to.be.undefined; }); it('replaces an existing instance, supports $select', async () => { const newData:any = { name: 'Dougler', age: 10}; newData[idProp] = _ids.Doug; const result = await service.update(_ids.Doug, newData, {query: { $select: ['name'] }}); expect(result[idProp]).to.eq(_ids.Doug); expect(result['name']).to.eq('Dougler'); expect(result['age']).to.be.undefined; }); it('returns NotFound error for non-existing id', async () => { const badId = '568225fbfe21222432e836ff'; const newData:any = { name: 'NotFound'}; newData[idProp] = badId; await service.update(badId, newData).catch( error => { expect(error instanceof NotFound).to.be.true; expect(error.message).to.eq(`No record found for id '${badId}'`); }) }); }); describe('patch', () => { it('updates an existing instance, does not modify original data', async () => { const newData:any = { name: 'PatchDoug'}; newData[idProp] = _ids.Doug; const result = await service.patch(_ids.Doug, newData); expect(result[idProp]).to.eq(_ids.Doug); expect(result['name']).to.eq('PatchDoug'); expect(result['age']).to.eq(32); }); it('updates an existing instance, supports $select', async () => { const newData:any = { name: 'PatchDoug'}; newData[idProp] = _ids.Doug; const result = await service.patch(_ids.Doug, newData, {query: { $select: ['name'] }}); expect(result[idProp]).to.eq(_ids.Doug); expect(result['name']).to.eq('PatchDoug'); expect(result['age']).to.be.undefined }); it('patches multiple instances', async () => { const params = { query: { created: true } }; await service.create({name: 'Dave', age: 29, created: true}); await service.create({name: 'David', age: 3, created: true}); const result = await service.patch(null, { age: 2 }, params); expect(result.length).to.eq(2); expect(result[0].age).to.eq(2); expect(result[1].age).to.eq(2); await service.remove(null, params); }); it('patches multiple instances and returns the actually changed items', async () => { const params = { query: { age: { $lt: 10 } } }; await service.create({name: 'Dave', age: 8, created: true}); await service.create({name: 'David', age: 4, created: true}); const result = await service.patch(null, { age: 2 }, params); expect(result.length).to.eq(2); expect(result[0].age).to.eq(2); expect(result[1].age).to.eq(2); await service.remove(null, params); }); it('patches multiple, returns correct items', async () => { await service.create({name: 'Dave', age: 2, created: true}); await service.create({name: 'David', age: 2, created: true}); await service.create({name: 'Frank', age: 8, created: true}); const result = await service.patch(null, { age: 8 }, { query: { age: 2 } }); expect(result.length).to.eq(2); expect(result[0].age).to.eq(8); expect(result[1].age).to.eq(8); await service.remove(null, { query: { age: 8 } }); }); it('returns NotFound error for non-existing id', async () => { const badId = '568225fbfe21222432e836ff'; const newData:any = { name: 'NotFound'}; newData[idProp] = badId; await service.patch(badId, newData).catch( error => { expect(error instanceof NotFound).to.be.true; expect(error.message).to.eq(`No record found for id '${badId}'`); }) }); }); describe('create', () => { it('creates a single new instance and returns the created instance', async () => { const originalData = { name: 'Bill', age: 40 }; const result = await service.create(originalData); expect(result).not.to.be.undefined; expect(result).to.be.an('object').that.contains.keys(Object.keys(originalData)); expect(result.name).to.eq('Bill') expect(result.age).to.eq(40) await service.remove(result[idProp]); }); it('creates a single new instance, supports $select', async () => { const originalData = { name: 'William', age: 23 }; const result = await service.create(originalData, {query: { $select: ['name'] }}); expect(result).not.to.be.undefined; expect(result.name).to.eq('William'); expect(result.age).to.be.undefined; await service.remove(result[idProp]); }); it('creates multiple new instances', async () => { const originalData = [{ name: 'Gerald', age: 18 }, { name: 'Herald', age: 18 }]; const result = await service.create(originalData); expect(result).not.to.be.undefined; expect(Array.isArray(result)).to.be.true; expect(result[0].name).to.eq('Gerald'); expect(result[1].name).to.eq('Herald'); await service.remove(result[0][idProp]); await service.remove(result[1][idProp]); }); }); /// HERE WE GO !!!!!!! describe('Services don\'t call public methods internally', () => { let throwing:any; before(() => { // @ts-ignore Extended isn't properly typed throwing = <any>service.extend({ get store() { // @ts-ignore Not sure where this comes from... return service.store; }, find: function find() { throw new Error('find method called'); }, get: function get() { throw new Error('get method called'); }, create: function create() { throw new Error('create method called'); }, update: function update() { throw new Error('update method called'); }, patch: function patch() { throw new Error('patch method called'); }, remove: function remove() { throw new Error('remove method called'); } }); }); it('find', async () => { await service.find.call(throwing); }); it('get', async () => { await service.get.call(throwing, _ids.Doug); }); it('create', async () => { const result = await service.create.call(throwing, { name: 'Bob', age: 25 }); await service.remove(result[idProp]); }); it('update', async () => { await service.update.call(throwing, _ids.Doug, { name: 'Dougler' }); }); it('patch', async () => { await service.patch.call(throwing, _ids.Doug, { name: 'PatchDoug' }); }); it('remove', async () => { await service.remove.call(throwing, _ids.Doug); }); }) });