Implements a general score calculator
This commit is contained in:
+1
-1
@@ -88,7 +88,7 @@ module.exports = function(grunt) {
|
||||
options: {
|
||||
reporter: 'spec',
|
||||
},
|
||||
src: ['coverage/test/api/apiTest.js']
|
||||
src: ['coverage/test/core/scoreCalculatorTest.js']
|
||||
},
|
||||
coverage: {
|
||||
options: {
|
||||
|
||||
@@ -7,7 +7,6 @@ var RulesChecker = function() {
|
||||
this.check = function(data, policies) {
|
||||
|
||||
var results = {};
|
||||
var err = null;
|
||||
|
||||
debug('Starting checking rules');
|
||||
|
||||
|
||||
+24
-4
@@ -1,8 +1,10 @@
|
||||
var Q = require('q');
|
||||
var debug = require('debug')('ylt:yellowlabtools');
|
||||
var Q = require('q');
|
||||
var debug = require('debug')('ylt:runner');
|
||||
|
||||
var phantomasWrapper = require('./tools/phantomasWrapper');
|
||||
var rulesChecker = require('./rulesChecker');
|
||||
var scoreCalculator = require('./scoreCalculator');
|
||||
|
||||
var phantomasWrapper = require('./tools/phantomasWrapper');
|
||||
var rulesChecker = require('./rulesChecker');
|
||||
|
||||
var Runner = function(params) {
|
||||
'use strict';
|
||||
@@ -27,6 +29,24 @@ var Runner = function(params) {
|
||||
data.rules = rulesChecker.check(data, policies);
|
||||
|
||||
|
||||
// Scores calculator
|
||||
var scoreProfileGeneric = require('./metadata/scoreProfileGeneric.json');
|
||||
data.scoreProfiles = {
|
||||
generic : scoreCalculator.calculate(data, scoreProfileGeneric)
|
||||
};
|
||||
|
||||
|
||||
// Get the JS Execution Tree from offenders and put in the main object
|
||||
try {
|
||||
data.javascriptExecutionTree = JSON.parse(data.toolsResults.phantomas.offenders.javascriptExecutionTree[0]);
|
||||
} catch(e) {
|
||||
debug('Could not find nor parse phantomas.offenders.javascriptExecutionTree');
|
||||
}
|
||||
|
||||
delete data.toolsResults.phantomas.metrics.javascriptExecutionTree;
|
||||
delete data.toolsResults.phantomas.offenders.javascriptExecutionTree;
|
||||
|
||||
//Finished!
|
||||
deferred.resolve(data);
|
||||
|
||||
}).fail(function(err) {
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
var Q = require('q');
|
||||
var debug = require('debug')('ylt:scoreCalculator');
|
||||
|
||||
var ScoreCalculator = function() {
|
||||
'use strict';
|
||||
|
||||
this.calculate = function(data, profile) {
|
||||
|
||||
var results = {
|
||||
categories: {}
|
||||
};
|
||||
var weight;
|
||||
var categoryName;
|
||||
|
||||
debug('Starting calculating scores');
|
||||
|
||||
// Calculate categories
|
||||
for (categoryName in profile.categories) {
|
||||
var categoryResult = {
|
||||
label: profile.categories[categoryName].label
|
||||
};
|
||||
|
||||
var sum = 0;
|
||||
var totalWeight = 0;
|
||||
var rules = [];
|
||||
|
||||
for (var policyName in profile.categories[categoryName].policies) {
|
||||
weight = profile.categories[categoryName].policies[policyName];
|
||||
|
||||
if (data.rules[policyName]) {
|
||||
sum += data.rules[policyName].score * weight;
|
||||
} else {
|
||||
// Max value if rule is not here
|
||||
sum += 100 * weight;
|
||||
debug('Warning: could not find rule %s', policyName);
|
||||
}
|
||||
|
||||
totalWeight += weight;
|
||||
rules.push(policyName);
|
||||
}
|
||||
|
||||
if (totalWeight === 0) {
|
||||
categoryResult.categoryScore = 100;
|
||||
} else {
|
||||
categoryResult.categoryScore = Math.round(sum / totalWeight);
|
||||
}
|
||||
|
||||
categoryResult.rules = rules;
|
||||
results.categories[categoryName] = categoryResult;
|
||||
}
|
||||
|
||||
|
||||
// Calculate general score
|
||||
var globalSum = 0;
|
||||
var globalTotalWeight = 0;
|
||||
|
||||
for (categoryName in profile.globalScore) {
|
||||
weight = profile.globalScore[categoryName];
|
||||
|
||||
if (results.categories[categoryName]) {
|
||||
globalSum += results.categories[categoryName].categoryScore * weight;
|
||||
} else {
|
||||
globalSum += 100 * weight;
|
||||
}
|
||||
globalTotalWeight += profile.globalScore[categoryName];
|
||||
}
|
||||
|
||||
if (globalTotalWeight === 0) {
|
||||
results.globalScore = 100;
|
||||
} else {
|
||||
results.globalScore = Math.round(globalSum / globalTotalWeight);
|
||||
}
|
||||
|
||||
|
||||
|
||||
debug('Score calculation finished:');
|
||||
debug(results);
|
||||
|
||||
return results;
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = new ScoreCalculator();
|
||||
@@ -16,7 +16,7 @@ var apiLimitsMiddleware = function(req, res, next) {
|
||||
if (!runsTable.accepts(req.connection.remoteAddress)) {
|
||||
// Sorry :/
|
||||
debug('Too many tests launched from IP address %s', req.connection.remoteAddress);
|
||||
res.status(429).send('Too Many Requests');
|
||||
res.status(429).send('Too many requests');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ var apiLimitsMiddleware = function(req, res, next) {
|
||||
if (!callsTable.accepts(req.connection.remoteAddress)) {
|
||||
// Sorry :/
|
||||
debug('Too many API requests from IP address %s', req.connection.remoteAddress);
|
||||
res.status(429).send('Too Many Requests');
|
||||
res.status(429).send('Too many requests');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+202
-8
@@ -8,25 +8,30 @@ var config = {
|
||||
}
|
||||
};
|
||||
|
||||
var apiUrl = 'http://localhost:8387/api';
|
||||
var serverUrl = 'http://localhost:8387';
|
||||
var wwwUrl = 'http://localhost:8388';
|
||||
|
||||
describe('api', function() {
|
||||
|
||||
var runId;
|
||||
|
||||
var syncRunResultUrl;
|
||||
var asyncRunId;
|
||||
var apiServer;
|
||||
|
||||
|
||||
// Start the server
|
||||
before(function(done) {
|
||||
apiServer = require('../../bin/server.js');
|
||||
apiServer.startTests = done;
|
||||
});
|
||||
|
||||
|
||||
it('should refuse a query with an invalid key', function(done) {
|
||||
this.timeout(5000);
|
||||
|
||||
request({
|
||||
method: 'POST',
|
||||
url: apiUrl + '/runs',
|
||||
url: serverUrl + '/api/runs',
|
||||
body: {
|
||||
url: wwwUrl + '/simple-page.html',
|
||||
waitForResponse: false
|
||||
@@ -44,12 +49,67 @@ describe('api', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should accept a query with a valid key', function(done) {
|
||||
|
||||
it('should launch a synchronous run', function(done) {
|
||||
this.timeout(15000);
|
||||
|
||||
request({
|
||||
method: 'POST',
|
||||
url: serverUrl + '/api/runs',
|
||||
body: {
|
||||
url: wwwUrl + '/simple-page.html',
|
||||
waitForResponse: true
|
||||
},
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Api-Key': Object.keys(config.authorizedKeys)[0]
|
||||
}
|
||||
}, function(error, response, body) {
|
||||
if (!error && response.statusCode === 302) {
|
||||
|
||||
response.headers.should.have.a.property('location').that.is.a('string');
|
||||
syncRunResultUrl = response.headers.location;
|
||||
|
||||
done();
|
||||
} else {
|
||||
done(error || response.statusCode);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should retrieve the results for the synchronous run', function(done) {
|
||||
this.timeout(15000);
|
||||
|
||||
request({
|
||||
method: 'GET',
|
||||
url: serverUrl + syncRunResultUrl,
|
||||
json: true,
|
||||
}, function(error, response, body) {
|
||||
if (!error && response.statusCode === 200) {
|
||||
|
||||
body.should.have.a.property('runId').that.is.a('string');
|
||||
body.should.have.a.property('params').that.is.an('object');
|
||||
body.should.have.a.property('scoreProfiles').that.is.an('object');
|
||||
body.should.have.a.property('rules').that.is.an('object');
|
||||
body.should.have.a.property('toolsResults').that.is.an('object');
|
||||
body.should.have.a.property('javascriptExecutionTree').that.is.an('object');
|
||||
|
||||
done();
|
||||
|
||||
} else {
|
||||
done(error || response.statusCode);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should launch a run without waiting for the response', function(done) {
|
||||
this.timeout(5000);
|
||||
|
||||
request({
|
||||
method: 'POST',
|
||||
url: apiUrl + '/runs',
|
||||
url: serverUrl + '/api/runs',
|
||||
body: {
|
||||
url: wwwUrl + '/simple-page.html',
|
||||
waitForResponse: false
|
||||
@@ -61,8 +121,35 @@ describe('api', function() {
|
||||
}, function(error, response, body) {
|
||||
if (!error && response.statusCode === 200) {
|
||||
|
||||
runId = body.runId;
|
||||
runId.should.be.a('string');
|
||||
asyncRunId = body.runId;
|
||||
asyncRunId.should.be.a('string');
|
||||
done();
|
||||
|
||||
} else {
|
||||
done(error || response.statusCode);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should respond run status: running', function(done) {
|
||||
this.timeout(5000);
|
||||
|
||||
request({
|
||||
method: 'GET',
|
||||
url: serverUrl + '/api/runs/' + asyncRunId,
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Api-Key': Object.keys(config.authorizedKeys)[0]
|
||||
}
|
||||
}, function(error, response, body) {
|
||||
if (!error && response.statusCode === 200) {
|
||||
|
||||
body.runId.should.equal(asyncRunId);
|
||||
body.status.should.deep.equal({
|
||||
statusCode: 'running'
|
||||
});
|
||||
|
||||
done();
|
||||
|
||||
} else {
|
||||
@@ -79,13 +166,16 @@ describe('api', function() {
|
||||
|
||||
request({
|
||||
method: 'POST',
|
||||
url: apiUrl + '/runs',
|
||||
url: serverUrl + '/api/runs',
|
||||
body: {
|
||||
url: wwwUrl + '/simple-page.html',
|
||||
waitForResponse: false
|
||||
},
|
||||
json: true
|
||||
}, function(error, response, body) {
|
||||
|
||||
lastRunId = body.runId;
|
||||
|
||||
if (error) {
|
||||
deferred.reject(error);
|
||||
} else {
|
||||
@@ -131,6 +221,110 @@ describe('api', function() {
|
||||
|
||||
});
|
||||
|
||||
|
||||
it('should respond 404 to unknown runId', function(done) {
|
||||
this.timeout(5000);
|
||||
|
||||
request({
|
||||
method: 'GET',
|
||||
url: serverUrl + '/api/runs/unknown',
|
||||
json: true
|
||||
}, function(error, response, body) {
|
||||
if (!error && response.statusCode === 404) {
|
||||
done();
|
||||
} else {
|
||||
done(error || response.statusCode);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should respond 404 to unknown result', function(done) {
|
||||
this.timeout(5000);
|
||||
|
||||
request({
|
||||
method: 'GET',
|
||||
url: serverUrl + '/api/results/unknown',
|
||||
json: true
|
||||
}, function(error, response, body) {
|
||||
if (!error && response.statusCode === 404) {
|
||||
done();
|
||||
} else {
|
||||
done(error || response.statusCode);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should respond status complete to the first run', function(done) {
|
||||
this.timeout(12000);
|
||||
|
||||
function checkStatus() {
|
||||
request({
|
||||
method: 'GET',
|
||||
url: serverUrl + '/api/runs/' + asyncRunId,
|
||||
json: true
|
||||
}, function(error, response, body) {
|
||||
if (!error && response.statusCode === 200) {
|
||||
|
||||
body.runId.should.equal(asyncRunId);
|
||||
|
||||
if (body.status.statusCode === 'running') {
|
||||
setTimeout(checkStatus, 250);
|
||||
} else if (body.status.statusCode === 'complete') {
|
||||
done();
|
||||
} else {
|
||||
done(body.status.statusCode);
|
||||
}
|
||||
|
||||
} else {
|
||||
done(error || response.statusCode);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
checkStatus();
|
||||
});
|
||||
|
||||
|
||||
it('should find the result of the async run', function(done) {
|
||||
this.timeout(5000);
|
||||
|
||||
request({
|
||||
method: 'GET',
|
||||
url: serverUrl + '/api/results/' + asyncRunId,
|
||||
json: true,
|
||||
}, function(error, response, body) {
|
||||
if (!error && response.statusCode === 200) {
|
||||
|
||||
body.should.have.a.property('runId').that.equals(asyncRunId);
|
||||
body.should.have.a.property('params').that.is.an('object');
|
||||
body.params.url.should.equal(wwwUrl + '/simple-page.html');
|
||||
|
||||
body.should.have.a.property('scoreProfiles').that.is.an('object');
|
||||
body.scoreProfiles.should.have.a.property('generic').that.is.an('object');
|
||||
body.scoreProfiles.generic.should.have.a.property('globalScore').that.is.a('number');
|
||||
body.scoreProfiles.generic.should.have.a.property('categories').that.is.an('object');
|
||||
|
||||
body.should.have.a.property('rules').that.is.an('object');
|
||||
|
||||
body.should.have.a.property('toolsResults').that.is.an('object');
|
||||
body.toolsResults.should.have.a.property('phantomas').that.is.an('object');
|
||||
|
||||
body.should.have.a.property('javascriptExecutionTree').that.is.an('object');
|
||||
body.javascriptExecutionTree.should.have.a.property('data').that.is.an('object');
|
||||
body.javascriptExecutionTree.data.should.have.a.property('type').that.equals('main');
|
||||
|
||||
done();
|
||||
|
||||
} else {
|
||||
done(error || response.statusCode);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Stop the server
|
||||
after(function() {
|
||||
console.log('Closing the server');
|
||||
apiServer.close();
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
var should = require('chai').should();
|
||||
var scoreCalculator = require('../../lib/scoreCalculator');
|
||||
|
||||
describe('scoreCalculator', function() {
|
||||
|
||||
it('should have a method calculate', function() {
|
||||
scoreCalculator.should.have.property('calculate').that.is.a('function');
|
||||
});
|
||||
|
||||
it('should produce a nice rules object', function() {
|
||||
var data = require('../fixtures/scoreInput.json');
|
||||
var profile = require('../fixtures/scoreProfile.json');
|
||||
var expected = require('../fixtures/scoreOutput.json');
|
||||
|
||||
var results = scoreCalculator.calculate(data, profile);
|
||||
results.should.deep.equals(expected);
|
||||
});
|
||||
});
|
||||
@@ -9,28 +9,28 @@ chai.use(sinonChai);
|
||||
|
||||
describe('yellowlabtools', function() {
|
||||
|
||||
it('returns a promise', function() {
|
||||
it('should return a promise', function() {
|
||||
var ylt = new YellowLabTools();
|
||||
|
||||
ylt.should.have.property('then').that.is.a('function');
|
||||
ylt.should.have.property('fail').that.is.a('function');
|
||||
});
|
||||
|
||||
it('fails an undefined url', function(done) {
|
||||
it('should fail an undefined url', function(done) {
|
||||
var ylt = new YellowLabTools().fail(function(err) {
|
||||
err.should.be.a('string').that.equals('URL missing');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('fails with an empty url string', function(done) {
|
||||
it('should fail with an empty url string', function(done) {
|
||||
var ylt = new YellowLabTools('').fail(function(err) {
|
||||
err.should.be.a('string').that.equals('URL missing');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('succeeds on simple-page.html', function(done) {
|
||||
it('should succeeds on simple-page.html', function(done) {
|
||||
this.timeout(15000);
|
||||
|
||||
// Check if console.log is called
|
||||
@@ -75,6 +75,13 @@ describe('yellowlabtools', function() {
|
||||
"offenders": ["body > h1[1]"]
|
||||
});
|
||||
|
||||
// Test javascriptExecutionTree
|
||||
data.toolsResults.phantomas.metrics.should.not.have.a.property('javascriptExecutionTree');
|
||||
data.toolsResults.phantomas.offenders.should.not.have.a.property('javascriptExecutionTree');
|
||||
data.should.have.a.property('javascriptExecutionTree').that.is.an('object');
|
||||
data.javascriptExecutionTree.should.have.a.property('data');
|
||||
data.javascriptExecutionTree.data.should.have.a.property('type').that.equals('main');
|
||||
|
||||
/*jshint expr: true*/
|
||||
console.log.should.not.have.been.called;
|
||||
|
||||
|
||||
Vendored
+68
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"rules": {
|
||||
"metric1": {
|
||||
"policy": {
|
||||
"tool": "tool1",
|
||||
"label": "The metric 1",
|
||||
"message": "A great message",
|
||||
"isOkThreshold": 1000,
|
||||
"isBadThreshold": 3000,
|
||||
"isAbnormalThreshold": 5000
|
||||
},
|
||||
"value": 1236,
|
||||
"bad": true,
|
||||
"abnormal": false,
|
||||
"score": 88,
|
||||
"abnormalityScore": 0
|
||||
},
|
||||
"metric2": {
|
||||
"value": 222,
|
||||
"bad": false,
|
||||
"abnormal": false,
|
||||
"score": 100,
|
||||
"abnormalityScore": 0
|
||||
},
|
||||
"metric3": {
|
||||
"value": 6666,
|
||||
"bad": true,
|
||||
"abnormal": true,
|
||||
"score": 0,
|
||||
"abnormalityScore": -42
|
||||
},
|
||||
"metric4": {
|
||||
"value": 1000,
|
||||
"bad": false,
|
||||
"abnormal": false,
|
||||
"score": 100,
|
||||
"abnormalityScore": 0
|
||||
},
|
||||
"metric5": {
|
||||
"value": 3000,
|
||||
"bad": true,
|
||||
"abnormal": false,
|
||||
"score": 0,
|
||||
"abnormalityScore": 0
|
||||
},
|
||||
"metric6": {
|
||||
"value": 0,
|
||||
"bad": false,
|
||||
"abnormal": false,
|
||||
"score": 100,
|
||||
"abnormalityScore": 0
|
||||
},
|
||||
"metric7": {
|
||||
"value": 5000,
|
||||
"bad": true,
|
||||
"abnormal": true,
|
||||
"score": 0,
|
||||
"abnormalityScore": 0
|
||||
},
|
||||
"metric8": {
|
||||
"value": 22,
|
||||
"bad": true,
|
||||
"abnormal": true,
|
||||
"score": 0,
|
||||
"abnormalityScore": -100
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+34
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"globalScore": 69,
|
||||
"categories": {
|
||||
"category1": {
|
||||
"label": "Category 1",
|
||||
"categoryScore": 87,
|
||||
"rules": [
|
||||
"metric1",
|
||||
"metric2",
|
||||
"metric3",
|
||||
"metric4"
|
||||
]
|
||||
},
|
||||
"category2": {
|
||||
"label": "Category 2",
|
||||
"categoryScore": 31,
|
||||
"rules": [
|
||||
"metric5",
|
||||
"metric6",
|
||||
"metric7",
|
||||
"metric8",
|
||||
"unexistantMetric1"
|
||||
]
|
||||
},
|
||||
"category3": {
|
||||
"label": "Category 3",
|
||||
"categoryScore": 100,
|
||||
"rules": [
|
||||
"unexistantMetric1",
|
||||
"unexistantMetric2"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+35
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"categories": {
|
||||
"category1": {
|
||||
"label": "Category 1",
|
||||
"policies": {
|
||||
"metric1": 2,
|
||||
"metric2": 1,
|
||||
"metric3": 0.5,
|
||||
"metric4": 2
|
||||
}
|
||||
},
|
||||
"category2": {
|
||||
"label": "Category 2",
|
||||
"policies": {
|
||||
"metric5": 2,
|
||||
"metric6": 1,
|
||||
"metric7": 0.5,
|
||||
"metric8": 2,
|
||||
"unexistantMetric1": 1
|
||||
}
|
||||
},
|
||||
"category3": {
|
||||
"label": "Category 3",
|
||||
"policies": {
|
||||
"unexistantMetric1": 2,
|
||||
"unexistantMetric2": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"globalScore": {
|
||||
"category1": 2,
|
||||
"category2": 1,
|
||||
"category3": 0.1
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user