Writing the API (workin in progress)

This commit is contained in:
Gaël Métais
2014-12-02 00:08:07 +01:00
parent 35aed5fb55
commit e18cd87a07
14 changed files with 344 additions and 100 deletions
+4 -3
View File
@@ -36,7 +36,8 @@ module.exports = function(grunt) {
'lib/**/*.js',
'app/nodeControllers/*.js',
'app/public/scripts/*.js',
'phantomas_custom/**/*.js'
'phantomas_custom/**/*.js',
'test/**/*.js'
]
},
clean: {
@@ -70,13 +71,13 @@ module.exports = function(grunt) {
options: {
reporter: 'spec',
},
src: ['coverage/test/api/*.js']
src: ['coverage/test/api/*.js', 'coverage/test/server/*.js']
},
'test-current-work': {
options: {
reporter: 'spec',
},
src: ['coverage/test/api/yellowlabtoolsTest.js']
src: ['coverage/test/server/runsQueueTest.js']
},
coverage: {
options: {
-66
View File
@@ -1,66 +0,0 @@
/**
* Creation of a queue and it's worker function
*/
var util = require('util');
var EventEmitter = require('events').EventEmitter;
var async = require('async');
var phantomasWrapper = require('./phantomasWrapper');
var testQueue = function() {
'use strict';
var currentTask = null;
var self = this;
var queue = async.queue(function(task, callback) {
currentTask = task;
console.log('Starting test ' + task.testId);
phantomasWrapper.execute(task, function(err, json, results) {
console.log('Test ' + task.testId + ' complete');
currentTask = null;
callback(err, json, results);
self.emit('queueMoving');
});
});
// Use this method to add a test to the queue
this.push = queue.push;
// Gives the position of a task in the queue
// Returns 0 if it is the current running task
// Returns -1 if not found
this.indexOf = function(testId) {
if (currentTask && currentTask.testId === testId) {
return 0;
}
var position = -1;
if (queue.length() > 0) {
queue.tasks.forEach(function(task, index) {
if (task.data.testId === testId) {
position = index + 1;
}
});
}
return position;
};
this.testComplete = function(testId) {
self.emit('testComplete', testId);
};
this.testFailed = function(testId) {
self.emit('testFailed', testId);
};
};
// extend the EventEmitter class
util.inherits(testQueue, EventEmitter);
module.exports = new testQueue();
+22
View File
@@ -0,0 +1,22 @@
// Config file
var settings = require('../server_config/settings.json');
var express = require('express');
var app = express();
var server = require('http').createServer(app);
var bodyParser = require('body-parser');
var compress = require('compression');
app.use(compress());
app.use(bodyParser.urlencoded({ extended: false }));
// Initialize the controllers
var apiController = require('../lib/server/controllers/apiController')(app);
var uiController = require('../lib/server/controllers/uiController')(app);
// Launch the server
server.listen(settings.serverPort, function() {
console.log('Listening on port %d', server.address().port);
});
+86
View File
@@ -0,0 +1,86 @@
var debug = require('debug')('ylt:server');
var runsQueue = require('../datastores/runsQueue');
var runsDatastore = require('../datastores/runsDatastore');
function ApiController(app) {
'use strict';
// Retrieve the list of all runs
/*app.get('/runs', function(req, res) {
// NOT YET
});*/
// Create a new run
app.post('/runs', function(req, res) {
// Grab the test parameters
var run = {
// Generate a random run ID
_id: (Date.now()*1000 + Math.round(Math.random()*1000)).toString(36),
params: {
url: req.body.url,
waitForResponse: req.body.waitForResponse || true
}
};
// Add test to the testQueue
debug('Adding test %s to the queue', run._id);
var queuing = runsQueue.push(run._id);
// Save the run to the datastore
var position = runsQueue.getPosition(run._id);
run.status = {
statusCode: (position === 0) ? STATUS_RUNNING : STATUS_AWAITING,
position: position
};
runsDatastore.add(run);
// Listening for position updates
queuing.progress(function(position) {
var savedRun = runsDatastore.get(run._id);
savedRun.status = {
statusCode: STATUS_AWAITING,
position: position
};
runsDatastore.update(savedRun);
});
queuing.then(function() {
});
// The user doesn't not want to wait for the response
if (!params.waitForResponse) {
// Sending just the test id
res.setHeader('Content-Type', 'application/javascript');
res.send(JSON.stringify({
testId: testId
}));
}
});
// Retrive one run by id
app.get('/run/:id', function(req, res) {
});
// Delete one run by id
/*app.delete('/run/:id', function(req, res) {
// NOT YET
});*/
var STATUS_AWAITING = 'awaiting';
var STATUS_RUNNING = 'running';
var STATUS_DONE = 'done';
var STATUS_FAILED = 'failed';
}
module.exports = ApiController;
@@ -0,0 +1,9 @@
function ResultsDatastore() {
'use strict';
}
module.exports = ResultsDatastore;
+36
View File
@@ -0,0 +1,36 @@
function RunsDatastore() {
'use strict';
// NOT PERSISTING RUNS
// For the moment, maybe one day
var runs = {};
this.add = function(run) {
runs[run._id] = run;
};
this.get = function(runId) {
return runs[runId];
};
this.update = function(run) {
runs[run._id] = run;
};
this.delete = function(runId) {
delete runs[runId];
};
this.list = function() {
var runsArray = [];
Object.keys(runs).forEach(function(key) {
runsArray.push(runs[key]);
});
return runsArray;
};
}
module.exports = RunsDatastore;
+73
View File
@@ -0,0 +1,73 @@
var Q = require('q');
function RunsQueue() {
'use strict';
var queue = [];
this.push = function(runId) {
var deferred = Q.defer();
if (queue.length === 0) {
// The queue is empty, let's run immediatly
queue.push({
runId: runId
});
deferred.resolve();
} else {
queue.push({
runId: runId,
positionChangedCallback: function(position) {
deferred.notify(position);
},
itIsTimeCallback: function() {
deferred.resolve();
}
});
}
return deferred.promise;
};
this.getPosition = function(runId) {
// Position 0 means it's a work in progress (a run is removed AFTER it is finished, not before)
var position = -1;
queue.some(function(run, index) {
if (run.runId === runId) {
position = index;
return true;
}
return false;
});
return position;
};
this.remove = function(runId) {
var position = this.getPosition(runId);
if (position >= 0) {
queue.splice(position, 1);
}
// Update other runs' positions
queue.forEach(function(run, index) {
if (index === 0 && run.itIsTimeCallback) {
run.itIsTimeCallback();
} else if (index > 0 && run.positionChangedCallback) {
run.positionChangedCallback(index);
}
});
};
}
module.exports = RunsQueue;
+1 -1
View File
@@ -14,7 +14,7 @@
"body-parser": "~1.9.2",
"compression": "~1.2.0",
"debug": "^2.1.0",
"express": "~4.10.1",
"express": "~4.10.4",
"phantomas": "1.7.0",
"socket.io": "~1.2.0"
},
+6 -6
View File
@@ -23,9 +23,9 @@ describe('phantomasWrapper', function() {
data.should.have.a.property('generator');
data.generator.should.contain('phantomas');
data.should.have.a.property('url').that.equals(url);
data.should.have.a.property('metrics').that.is.an('object').not.empty;
data.should.have.a.property('offenders').that.is.an('object').not.empty;
data.offenders.should.have.a.property('javascriptExecutionTree').that.is.a('array').not.empty;
data.should.have.a.property('metrics').that.is.an('object').not.empty();
data.should.have.a.property('offenders').that.is.an('object').not.empty();
data.offenders.should.have.a.property('javascriptExecutionTree').that.is.a('array').not.empty();
done();
}).fail(function(err) {
@@ -73,9 +73,9 @@ describe('phantomasWrapper', function() {
data.should.have.a.property('generator');
data.generator.should.contain('phantomas');
data.should.have.a.property('url').that.equals(url);
data.should.have.a.property('metrics').that.is.an('object').not.empty;
data.should.have.a.property('offenders').that.is.an('object').not.empty;
data.offenders.should.have.a.property('javascriptExecutionTree').that.is.a('array').not.empty;
data.should.have.a.property('metrics').that.is.an('object').not.empty();
data.should.have.a.property('offenders').that.is.an('object').not.empty();
data.offenders.should.have.a.property('javascriptExecutionTree').that.is.a('array').not.empty();
done();
}).fail(function(err) {
+2 -1
View File
@@ -34,7 +34,7 @@ describe('yellowlabtools', function() {
this.timeout(15000);
// Check if console.log is called
sinon.spy(console, 'log')
sinon.spy(console, 'log');
var url = 'http://localhost:8388/simple-page.html';
@@ -75,6 +75,7 @@ describe('yellowlabtools', function() {
"offenders": ["body > h1[1]"]
});
/*jshint expr: true*/
console.log.should.not.have.been.called;
done();
+41
View File
@@ -0,0 +1,41 @@
var should = require('chai').should();
var runsDatastore = require('../../lib/server/datastores/runsDatastore');
describe('runsDatastore', function() {
var datastore = new runsDatastore();
var randomId = Math.round(Math.random() * 100000);
it('should accept a new run', function() {
datastore.should.have.a.property('add').that.is.a('function');
datastore.add({
_id: randomId,
otherData: 123456789
});
});
it('should have stored the run', function() {
datastore.should.have.a.property('get').that.is.a('function');
var run = datastore.get(randomId);
run.should.have.a.property('_id').that.equals(randomId);
});
it('should have exactly 1 run in the store', function() {
var runs = datastore.list();
runs.should.be.a('array');
runs.should.have.length(1);
runs[0].should.have.a.property('_id').that.equals(randomId);
});
it('should delete the run', function() {
datastore.delete(randomId);
var runs = datastore.list();
runs.should.be.a('array');
runs.should.have.length(0);
});
});
+64
View File
@@ -0,0 +1,64 @@
var should = require('chai').should();
var runsQueue = require('../../lib/server/datastores/runsQueue.js');
describe('runsQueue', function() {
var queue = new runsQueue();
var cccRun = null;
it('should accept a new runId', function(done) {
queue.should.have.a.property('push').that.is.a('function');
var aaaRun = queue.push('aaa');
queue.push('bbb');
aaaRun.then(function() {
done();
});
});
it('should return the right positions', function() {
var aaaPosition = queue.getPosition('aaa');
aaaPosition.should.equal(0);
var bbbPosition = queue.getPosition('bbb');
bbbPosition.should.equal(1);
var cccPosition = queue.getPosition('ccc');
cccPosition.should.equal(-1);
});
it('should refresh runs\' positions', function(done) {
cccRun = queue.push('ccc');
cccRun.progress(function(position) {
position.should.equal(1);
var positionDoubleCheck = queue.getPosition('ccc');
positionDoubleCheck.should.equal(1);
done();
});
queue.remove('aaa');
});
it('should fulfill the promise when first in the line', function(done) {
cccRun.then(function() {
done();
});
queue.remove('bbb');
});
it('should not keep removed runs', function() {
var aaaPosition = queue.getPosition('aaa');
aaaPosition.should.equal(-1);
var bbbPosition = queue.getPosition('bbb');
bbbPosition.should.equal(-1);
var cccPosition = queue.getPosition('ccc');
cccPosition.should.equal(0);
});
});
-23
View File
@@ -1,23 +0,0 @@
var should = require('chai').should();
var testQueue = require('../../app/lib/testQueue.js');
describe('testQueue', function() {
var url = 'http://www.not.existing';
it('should accept a new test with method push', function() {
testQueue.should.have.property('push').that.is.a('function');
var task = {
testId: 'aaaaa',
url: url,
options: {}
};
testQueue.push(task, function(err, json, results) {
done();
});
});
});