CartoDB-SQL-API/test/integration/pubsub-metrics-test.js
Esther Lozano 419adea234
Add pubsub metrics (#642)
* Create middleware and service for pubsub metrics

* Use pubsub middleware for all request

* Replace isEnabled with isDisabled to avoid negation

* Use new headers names

* Add acceptance and unit tests

* Remove commented log calls

* Remove only filters in tests

Remove typo

* Refactor service to ease integration test

* Fix interaction with query controller

* Use middleware at api-router and test all controllers

* Rename user middleware function

* Use sinon latest version

* Create middleware and service for pubsub metrics

* Use pubsub middleware for all request

* Replace isEnabled with isDisabled to avoid negation

* Use new headers names

* Add acceptance and unit tests

* Remove commented log calls

* Remove only filters in tests

Remove typo

* Refactor service to ease integration test

* Fix interaction with query controller

* Use middleware at api-router and test all controllers

* Rename user middleware function

* Use sinon latest version

* Fix tests

* Fix typos

* Checks if pubsub config exists to enable the service

* Fix typo

* Normalize headers values for pubsub

* Trim fields when normalizing

* Trim fields when normalizing
2020-02-26 17:19:06 +01:00

245 lines
6.9 KiB
JavaScript

'use strict';
require('../helper');
const sinon = require('sinon');
const fs = require('fs');
const path = require('path');
const qs = require('querystring');
const assert = require('../support/assert');
const app = require('../../lib/server');
const PubSubMetricsService = require('../../lib/services/pubsub-metrics');
const queryRequest = {
url: '/api/v1/sql?' + qs.stringify({
q: 'SELECT * FROM untitle_table_4'
}),
headers: {
host: 'vizzuality.cartodb.com',
'Carto-Event': 'test-event',
'Carto-Event-Source': 'test',
'Carto-Event-Group-Id': '1'
},
method: 'GET'
};
const copyRequest = {
url: '/api/v1/sql/copyfrom?' + qs.stringify({
q: "COPY copy_endpoints_test (id, name) FROM STDIN WITH (FORMAT CSV, DELIMITER ',', HEADER true)"
}),
data: fs.createReadStream(path.join(__dirname, '/../support/csv/copy_test_table.csv')),
headers: {
host: 'vizzuality.cartodb.com',
'Carto-Event': 'test-event',
'Carto-Event-Source': 'test',
'Carto-Event-Group-Id': '1'
},
method: 'POST'
};
const jobRequest = {
url: '/api/v1/sql/job/wadus',
headers: {
host: 'vizzuality.cartodb.com',
'Carto-Event': 'test-event',
'Carto-Event-Source': 'test',
'Carto-Event-Group-Id': '1'
},
method: 'GET'
};
const nonMetricsHeadersRequest = {
url: '/api/v1/sql?' + qs.stringify({
q: 'SELECT * FROM untitle_table_4'
}),
headers: {
host: 'vizzuality.cartodb.com'
},
method: 'GET'
};
const tooLongField = ' If you are sending a text this long in a header you kind of deserve the worst, honestly. I mean ' +
'this is not a header, it is almost a novel, and you do not see any Novel cookie here, right?';
const badHeadersRequest = {
url: '/api/v1/sql?' + qs.stringify({
q: 'SELECT * FROM untitle_table_4'
}),
headers: {
host: 'vizzuality.cartodb.com',
'Carto-Event': tooLongField,
'Carto-Event-Source': 'test',
'Carto-Event-Group-Id': 1
},
method: 'GET'
};
const badRequest = {
url: '/api/v1/sql?',
headers: {
host: 'vizzuality.cartodb.com',
'Carto-Event': 'test-event',
'Carto-Event-Source': 'test',
'Carto-Event-Group-Id': '1'
},
method: 'GET'
};
const fakeTopic = {
name: 'test-topic',
publish: sinon.stub().returns(Promise.resolve())
};
const fakePubSub = {
topic: () => fakeTopic
};
function buildEventAttributes (host, statusCode) {
return {
event_source: 'test',
user_id: '1',
event_group_id: '1',
response_code: statusCode.toString(),
source_domain: host,
event_time: new Date().toISOString(),
event_version: '1'
};
}
describe('pubsub metrics middleware', function () {
let server;
let clock;
before(function () {
clock = sinon.useFakeTimers();
sinon.stub(PubSubMetricsService, 'createPubSub').returns(fakePubSub);
});
after(function () {
clock.restore();
PubSubMetricsService.createPubSub.restore();
global.settings.pubSubMetrics.enabled = false;
});
afterEach(function () {
fakeTopic.publish.resetHistory();
});
it('should not send event if not enabled', function (done) {
global.settings.pubSubMetrics.enabled = false;
server = app();
assert.response(server, queryRequest, { status: 200 }, function (err) {
if (err) {
return done(err);
}
assert(fakeTopic.publish.notCalled);
return done();
});
});
it('should not send event if headers not present', function (done) {
global.settings.pubSubMetrics.enabled = true;
server = app();
assert.response(server, nonMetricsHeadersRequest, { status: 200 }, function (err) {
if (err) {
return done(err);
}
assert(fakeTopic.publish.notCalled);
return done();
});
});
it('should normalized headers type and length', function (done) {
global.settings.pubSubMetrics.enabled = true;
server = app();
const statusCode = 200;
const eventAttributes = buildEventAttributes(queryRequest.headers.host, statusCode);
const maxLength = 100;
const eventName = tooLongField.trim().substr(0, maxLength);
assert.response(server, badHeadersRequest, { status: statusCode }, function (err) {
if (err) {
return done(err);
}
assert(fakeTopic.publish.calledOnceWith(Buffer.from(eventName), eventAttributes));
return done();
});
});
it('should send event for query requests', function (done) {
global.settings.pubSubMetrics.enabled = true;
server = app();
const statusCode = 200;
const eventAttributes = buildEventAttributes(queryRequest.headers.host, statusCode);
assert.response(server, queryRequest, { status: statusCode }, function (err) {
if (err) {
return done(err);
}
assert(fakeTopic.publish.calledOnceWith(Buffer.from('test-event'), eventAttributes));
return done();
});
});
it('should send event for copy requests', function (done) {
global.settings.pubSubMetrics.enabled = true;
server = app();
const statusCode = 200;
const eventAttributes = buildEventAttributes(copyRequest.headers.host, statusCode);
assert.response(server, copyRequest, { status: statusCode }, function (err) {
if (err) {
return done(err);
}
assert(fakeTopic.publish.calledOnceWith(Buffer.from('test-event'), eventAttributes));
return done();
});
});
it('should send event for job requests', function (done) {
global.settings.pubSubMetrics.enabled = true;
server = app();
const statusCode = 200;
const eventAttributes = buildEventAttributes(jobRequest.headers.host, statusCode);
assert.response(server, queryRequest, { status: statusCode }, function (err) {
if (err) {
return done(err);
}
assert(fakeTopic.publish.calledOnceWith(Buffer.from('test-event'), eventAttributes));
return done();
});
});
it('should send event when error', function (done) {
global.settings.pubSubMetrics.enabled = true;
server = app();
const statusCode = 400;
const eventAttributes = buildEventAttributes(badRequest.headers.host, statusCode);
assert.response(server, badRequest, { status: statusCode }, function (err) {
if (err) {
return done(err);
}
assert(fakeTopic.publish.calledOnceWith(Buffer.from('test-event'), eventAttributes));
assert(fakeTopic.publish.calledOnce);
return done();
});
});
});