diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..8ebc205 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,19 @@ +language: node_js + +node_js: + - 'lts/*' + +services: mongodb + +before_install: + # Set up environment variables + # Tests are ran on a separate database named 'regsitry-test' + - export MONGODB_URL=mongodb://127.0.0.1:27017/registry-test + +install: + # Install all the project dependencies + - npm install + +script: + # Run test script + - npm run test diff --git a/app.js b/app.js index d923583..c5cf502 100644 --- a/app.js +++ b/app.js @@ -12,6 +12,7 @@ var index = require('./routes/index'); var instances = require('./routes/instances'); var synchronize = require('./routes/synchronize'); const swaggerUi = require('swagger-ui-express'); +require('./db/mongoose'); // Swagger.json file is used to generate the API-DOCS const swaggerDocument = require('./swagger.json'); const scheduledAutomaticUpdate = require('./scheduled/automaticUpdate'); diff --git a/createUser.js b/createUser.js index 82d0d0a..d3490e5 100644 --- a/createUser.js +++ b/createUser.js @@ -1,11 +1,5 @@ const User = require('./models/user'); -const mongoose = require('mongoose'); - -mongoose.connect(process.env.MONGODB_URL); - -const db = mongoose.connection; -db.on('error', console.error.bind(console, 'MongoDB connection error:')); - +require('./db/mongoose'); const readline = require('readline'); const rl = readline.createInterface({ diff --git a/db/mongoose.js b/db/mongoose.js new file mode 100644 index 0000000..84228fe --- /dev/null +++ b/db/mongoose.js @@ -0,0 +1,11 @@ +// Registry Connection +var mongoose = require('mongoose'); + +mongoose.connect(process.env.MONGODB_URL, { + useNewUrlParser: true, + useUnifiedTopology: true, + useCreateIndex: true +}); + +var db = mongoose.connection; +db.on('error', console.error.bind(console, 'MongoDB connection error:')); diff --git a/models/instance.js b/models/instance.js index 33f6990..3efe385 100644 --- a/models/instance.js +++ b/models/instance.js @@ -3,11 +3,7 @@ */ // Registry Connection var mongoose = require('mongoose'); - -mongoose.connect(process.env.MONGODB_URL); - -var db = mongoose.connection; -db.on('error', console.error.bind(console, 'MongoDB connection error:')); +require('../db/mongoose'); // Schema Modeling var Schema = mongoose.Schema; diff --git a/models/user.js b/models/user.js index 9e3b509..1d55af4 100644 --- a/models/user.js +++ b/models/user.js @@ -41,4 +41,4 @@ module.exports.comparePassword = function(candidatePassowrd, hash, callback){ module.exports.getUserByUsername = function(username, callback){ User.findOne({user:username}, callback); -} \ No newline at end of file +} diff --git a/package.json b/package.json index b0c0e9d..908edc9 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,11 @@ "version": "0.0.0", "private": true, "scripts": { - "start": "node ./bin/www" + "start": "node ./bin/www", + "test": "jest --forceExit --maxWorkers=10" + }, + "jest": { + "testEnvironment": "node" }, "dependencies": { "async": "^2.6.2", @@ -32,5 +36,10 @@ "serve-favicon": "^2.5.0", "swagger-express": "~1.0.5", "swagger-ui-express": "^2.0.15" + }, + "devDependencies": { + "env-cmd": "^10.0.1", + "jest": "^25.1.0", + "supertest": "^4.0.2" } } diff --git a/tests/fixtures/db.js b/tests/fixtures/db.js new file mode 100644 index 0000000..0ef95bc --- /dev/null +++ b/tests/fixtures/db.js @@ -0,0 +1,81 @@ +const User = require('../../models/user'); +const Instance = require('../../models/instance'); + +// User credentials required for testing +const userOne = { + user: 'testuser', + password: 'asdfghj' +}; + +// Mine data to be stored in the database +const flyMine = { + "name": "FlyMine", + "namespace": "flymine", + "neighbours": [ + "MODs" + ], + "organisms": [ + "Drosophila" + ], + "url": "http://www.flymine.org/query", + "description": "FlyMine is an integrated database of genomic, expression and protein data for Drosophila, Anopheles and C. elegans", + "location": { + "latitude": "52.2003399", + "longitude": "0.120109" + }, + "twitter": "@intermineorg" +}; + +// Mine data to be stored in the database +const chickpeaMine = { + "name": "ChickpeaMine", + "namespace": "chickpeamine", + "neighbours": [ + "Plants" + ], + "url": "http://mines.legumeinfo.org/chickpeamine", + "organisms": [ + "A. ipaensis", "A. duranensis", "A. thaliana", "C. arietinum desi", "C. arietinum kabuli", "G. max", "M. truncatula", "P. vulgaris" + ], + "description": "A mine with chickpea data (both desi and kabuli varieties) from the Legume Information Systems (LIS) tripal.chado database", + "location": { + "latitude": "72.2003399", + "longitude": "10.120109" + }, + "twitter": "@LegumeFed" +}; + +// Updates to be apllied on FlyMine during testing +const flymineUpdate = { + "neighbours": [ + "Plants" + ], + "namespace": "flymine" +}; + +// Update involving change of namespace +const changeNamespace = { + "neighbours": [ + "Plants" + ], + "namespace": "flymine alpha" +}; + +// Run before tests +const setupDatabase = async () => { + // Clear the test-database + await Instance.deleteMany(); + await User.deleteMany(); + + // Create a new user for testing + await new User(userOne).save(); +}; + +module.exports = { + setupDatabase, + userOne, + flyMine, + chickpeaMine, + flymineUpdate, + changeNamespace +}; diff --git a/tests/instances.test.js b/tests/instances.test.js new file mode 100644 index 0000000..33c7c84 --- /dev/null +++ b/tests/instances.test.js @@ -0,0 +1,105 @@ +const request = require('supertest'); +const app = require('../app'); +const { + setupDatabase, + userOne, + flyMine, + chickpeaMine, + flymineUpdate, + changeNamespace +} = require('./fixtures/db'); + +jest.setTimeout(15000); + +beforeAll(async () => { + await setupDatabase(); + + // Save an instance to the database before testing + await request(app).post('/service/instances/') + .auth(userOne.user, userOne.password) + .send(chickpeaMine); +}); + +test('POST /instances : Should add an instance to the InterMine Registry', async () => { + await request(app).post('/service/instances/') + .auth(userOne.user, userOne.password) + .send(flyMine) + .expect(201); +}); + +test('POST /instances : Shoud not add an existing namespace to the InterMine Registry', async () => { + await request(app).post('/service/instances/') + .auth(userOne.user, userOne.password) + .send(flyMine) + .expect(409); +}); + +test('GET /instances : Should get all InterMine Registry instances information when there are no params', async () => { + const response = await request(app).get('/service/instances/') + .send() + .expect(200); + + // No of returned instances should be correct + expect(response.body.instances).toHaveLength(2); +}); + +test('GET /instances : Should get the correct InterMine Registry instance information when parameter q is passed', async () => { + const response = await request(app).get('/service/instances?q=flymine') + .send() + .expect(200); + + // Response namespace should be correct + expect(response.body.instances[0].namespace).toMatch('flymine'); +}); + +test('GET /instances : Should not return any InterMine Registry instances with "isProduction": true when parameter "mines=dev" is passed', async () => { + const response = await request(app).get('/service/instances?mines=dev') + .send() + .expect(200); + + // Check value of 'isProduction' for each instance + // Value of 'isProduction' should not be true + const instances = response.body.instances; + instances.forEach(instance => expect(instance.isProduction).not.toBe(true)); +}); + +test('GET /instances : Should not return any InterMine Registry instances with "isProduction": false when parameter "mines=prod" is passed', async () => { + const response = await request(app).get('/service/instances?mines=prod') + .send() + .expect(200); + + // Check value of "isProduction" for each instance + // Value of 'isProduction' should not be false + const instances = response.body.instances; + instances.forEach(instance => expect(instance.isProduction).not.toBe(false)); +}); + +test('GET /instances : Should return a count of InterMine Registry instances when parameter "mines=all" is passed', async () => { + const response = await request(app).get('/service/instances?mines=all') + .send() + .expect(200); + + // No of returned instances should be correct + expect(response.body.instances).toHaveLength(2); +}); + +test('PUT /instances : Should update the given InterMine Registry instance only', async () => { + await request(app).put('/service/instances/2') + .auth(userOne.user, userOne.password) + .send(flymineUpdate) + .expect(201); +}); + +test('PUT /instances : Should not allow a namespace to be changed', async () => { + await request(app).put('/service/instances/2') + .auth(userOne.user, userOne.password) + .send(changeNamespace) + .expect(409); +}); + +test('DELETE /instances : Should delete the given InterMine Registry instance only', async () => { + await request(app).delete('/service/instances/2') + .auth(userOne.user, userOne.password) + .send() + .expect(200); +});