Commit 1d246e62 authored by Dan Jones's avatar Dan Jones
Browse files

Merge branch '13-error-handling-on-send' into 'dev'

Resolve "Error handling on send"

Closes #13

See merge request !8
parents db7232dd b2a02056
......@@ -82,10 +82,11 @@ class Adapter {
/**
* Call the GET /receive endpoint and process the messages with decode
*
* Returns the response
* Returns the response
* @param {boolean} is_retry
* @returns {object}
*/
poll() {
poll(is_retry=false) {
let adapterConfig = this.config;
return this.getAuthorizationHeader()
.then((headers) => {
......@@ -105,7 +106,18 @@ class Adapter {
return response;
})
.catch((error) => {
return Promise.reject(error);
let retry = false;
switch(error.response.status) {
case 403: {
this.credentials = null;
retry = true;
} break;
case 503: {
retry = true;
}
}
if (retry && !is_retry) return this.poll(true);
else return Promise.reject(error);
});
}
......@@ -115,9 +127,10 @@ class Adapter {
* Messages should be passed through encode before sending
* @param {string} topic
* @param {string} body
* @param {boolean} is_retry
* @returns
*/
publish(topic, body) {
publish(topic, body, is_retry=false) {
let adapterConfig = this.config;
return this.getAuthorizationHeader()
.then((headers) => {
......@@ -136,7 +149,18 @@ class Adapter {
return response;
})
.catch((error) => {
return Promise.reject(error);
let retry = false;
switch(error.response.status) {
case 403: {
this.credentials = null;
retry = true;
} break;
case 503: {
retry = true;
}
}
if (retry && !is_retry) return this.publish(topic, body, true);
else return Promise.reject(error);
});
}
......@@ -148,10 +172,11 @@ class Adapter {
* quickly in an emergency scenario.
*
* Messages should be passed through encode before sending
* @param {*} body
* @param {string} body
* @param {boolean} is_retry
* @returns
*/
broadcast(body) {
broadcast(body, is_retry=false) {
let adapterConfig = this.config;
return this.getAuthorizationHeader()
.then((headers) => {
......@@ -169,7 +194,18 @@ class Adapter {
return response;
})
.catch((error) => {
return Promise.reject(error);
let retry = false;
switch(error.response.status) {
case 403: {
this.credentials = null;
retry = true;
} break;
case 503: {
retry = true;
}
}
if (retry && !is_retry) return this.broadcast(body, true);
else return Promise.reject(error);
});
}
}
......
......@@ -84,10 +84,11 @@ class Adapter {
/**
* Call the GET /receive endpoint and process the messages with decode
*
* Returns the response
* Returns the response
* @param {boolean} is_retry
* @returns {object}
*/
poll() {
poll(is_retry=false) {
let adapterConfig = this.config;
return this.getAuthorizationHeader()
.then((headers) => {
......@@ -107,7 +108,18 @@ class Adapter {
return response;
})
.catch((error) => {
return Promise.reject(error);
let retry = false;
switch(error.response.status) {
case 403: {
this.credentials = null;
retry = true;
} break;
case 503: {
retry = true;
}
}
if (retry && !is_retry) return this.poll(true);
else return Promise.reject(error);
});
}
......@@ -117,9 +129,10 @@ class Adapter {
* Messages should be passed through encode before sending
* @param {string} topic
* @param {string} body
* @param {boolean} is_retry
* @returns
*/
publish(topic, body) {
publish(topic, body, is_retry=false) {
let adapterConfig = this.config;
return this.getAuthorizationHeader()
.then((headers) => {
......@@ -138,7 +151,18 @@ class Adapter {
return response;
})
.catch((error) => {
return Promise.reject(error);
let retry = false;
switch(error.response.status) {
case 403: {
this.credentials = null;
retry = true;
} break;
case 503: {
retry = true;
}
}
if (retry && !is_retry) return this.publish(topic, body, true);
else return Promise.reject(error);
});
}
......@@ -150,10 +174,11 @@ class Adapter {
* quickly in an emergency scenario.
*
* Messages should be passed through encode before sending
* @param {*} body
* @param {string} body
* @param {boolean} is_retry
* @returns
*/
broadcast(body) {
broadcast(body, is_retry=false) {
let adapterConfig = this.config;
return this.getAuthorizationHeader()
.then((headers) => {
......@@ -171,7 +196,18 @@ class Adapter {
return response;
})
.catch((error) => {
return Promise.reject(error);
let retry = false;
switch(error.response.status) {
case 403: {
this.credentials = null;
retry = true;
} break;
case 503: {
retry = true;
}
}
if (retry && !is_retry) return this.broadcast(body, true);
else return Promise.reject(error);
});
}
}
......
......@@ -49,7 +49,7 @@
"axios": "^1.2.3",
"axios-mock-adapter": "^1.21.2",
"babel-jest": "^27.4.4",
"backbone-adapter-testsuite": "git+https://git.noc.ac.uk/communications-backbone-system/backbone-adapter-testsuite.git#2a22ae98",
"backbone-adapter-testsuite": "git+https://git.noc.ac.uk/communications-backbone-system/backbone-adapter-testsuite.git#dev",
"cross-env": "^7.0.3",
"eslint": "^8.4.1",
"eslint-config-prettier": "^8.3.0",
......
......@@ -82,10 +82,11 @@ export class Adapter {
/**
* Call the GET /receive endpoint and process the messages with decode
*
* Returns the response
* Returns the response
* @param {boolean} is_retry
* @returns {object}
*/
poll() {
poll(is_retry=false) {
let adapterConfig = this.config;
return this.getAuthorizationHeader()
.then((headers) => {
......@@ -105,7 +106,18 @@ export class Adapter {
return response;
})
.catch((error) => {
return Promise.reject(error);
let retry = false;
switch(error.response.status) {
case 403: {
this.credentials = null;
retry = true;
} break;
case 503: {
retry = true;
}
}
if (retry && !is_retry) return this.poll(true);
else return Promise.reject(error);
});
}
......@@ -115,9 +127,10 @@ export class Adapter {
* Messages should be passed through encode before sending
* @param {string} topic
* @param {string} body
* @param {boolean} is_retry
* @returns
*/
publish(topic, body) {
publish(topic, body, is_retry=false) {
let adapterConfig = this.config;
return this.getAuthorizationHeader()
.then((headers) => {
......@@ -136,7 +149,18 @@ export class Adapter {
return response;
})
.catch((error) => {
return Promise.reject(error);
let retry = false;
switch(error.response.status) {
case 403: {
this.credentials = null;
retry = true;
} break;
case 503: {
retry = true;
}
}
if (retry && !is_retry) return this.publish(topic, body, true);
else return Promise.reject(error);
});
}
......@@ -148,10 +172,11 @@ export class Adapter {
* quickly in an emergency scenario.
*
* Messages should be passed through encode before sending
* @param {*} body
* @param {string} body
* @param {boolean} is_retry
* @returns
*/
broadcast(body) {
broadcast(body, is_retry=false) {
let adapterConfig = this.config;
return this.getAuthorizationHeader()
.then((headers) => {
......@@ -169,7 +194,18 @@ export class Adapter {
return response;
})
.catch((error) => {
return Promise.reject(error);
let retry = false;
switch(error.response.status) {
case 403: {
this.credentials = null;
retry = true;
} break;
case 503: {
retry = true;
}
}
if (retry && !is_retry) return this.broadcast(body, true);
else return Promise.reject(error);
});
}
}
......@@ -14,6 +14,7 @@ const mockInvalidConfig = fixtures.get('config-invalid');
const mockSchema = fixtures.get('schema-swagger');
const { GenericProtocol } = require('../../../dist/protocol');
const { Adapter } = require('../../../dist/adapter');
/**
* Use assert.CallTracker to track internal method calls
......@@ -27,11 +28,41 @@ const trackedFunction = function(method, params) {
};
const recorder = tracker.calls(trackedFunction);
class TrackedAdapter extends Adapter {
setupCallTracking(recorder, tracker) {
this.recorder = recorder;
this.tracker = tracker;
}
getAuthorizationHeader() {
this.recorder('getAuthorizationHeader');
return super.getAuthorizationHeader();
}
poll(is_retry) {
this.recorder('poll', { is_retry });
return super.poll(is_retry);
}
publish(topic, body, is_retry) {
this.recorder('publish', { topic, body, is_retry });
return super.publish(topic, body, is_retry);
}
broadcast(body, is_retry) {
this.recorder('broadcast', { body, is_retry });
return super.broadcast(body, is_retry);
}
getTrackedCalls(method) {
let calls = this.tracker.getCalls(this.recorder);
let methodCalls = calls.filter(call => call.arguments[0] === method);
return methodCalls;
}
resetTracker() {
this.tracker.reset();
}
}
class TrackedGenericProtocol extends GenericProtocol {
constructor(schema, services) {
super(schema, services);
this.recorder = services.recorder;
this.tracker = services.tracker;
setupCallTracking(recorder, tracker) {
this.recorder = recorder;
this.tracker = tracker;
}
encode(type, message) {
this.recorder('encode', {type, message});
......@@ -56,6 +87,7 @@ class TrackedGenericProtocol extends GenericProtocol {
}
Before(function() {
this.api = mockValidConfig.api;
this.schema = mockSchema;
this.tracker = tracker;
this.recorder = recorder;
......@@ -63,10 +95,17 @@ Before(function() {
recorder,
tracker
};
this.protocol = new TrackedGenericProtocol(this.schema, services);
this.classes = {
TrackedAdapter,
TrackedGenericProtocol,
};
this.callCounts = {};
this.protocol = new this.classes.TrackedGenericProtocol(this.schema, services);
this.protocol.setupCallTracking(this.recorder, this.tracker);
this.protocol.resetTracker();
this.mockAxios = mockAxios;
this.mockAxios.reset();
this.mockAxios.resetHistory();
this.mockAxios.onGet(
`${mockValidConfig.api}/token`,
......
const { When } = require('@cucumber/cucumber');
const assert = require('assert');
const { When, Then } = require('@cucumber/cucumber');
const { fixtures } = require('../../fixtures/server');
......@@ -11,10 +12,14 @@ When('a mock notify API response is configured to return success', function() {
).reply(200, response);
});
When('a mock notify API response is configured to return an error', function() {
When('a mock notify API response is configured to return a {int} error', function(statusCode) {
const statusMessages = {
403: 'Token expired',
503: 'Service unavailable'
};
this.mockAxios.onPost(
`${mockValidConfig.api}/notify`,
).reply(403, { message: 'Token expired' })
).reply(statusCode, { message: statusMessages[statusCode] })
});
When('the broadcast method is called', function() {
......@@ -22,4 +27,24 @@ When('the broadcast method is called', function() {
this.message = message;
const body = JSON.stringify(message);
this.call = this.adapter.broadcast(body);
this.callCounts.broadcast = this.adapter.getTrackedCalls('broadcast').length;
});
When('the broadcast method is called with is_retry on', function() {
const message = fixtures.get('message-vehicle-status');
this.message = message;
const body = JSON.stringify(message);
this.call = this.adapter.broadcast(body, true);
this.callCounts.broadcast = this.adapter.getTrackedCalls('broadcast').length;
});
Then('the broadcast method was called with is_retry on', function() {
let broadcastCalls = this.adapter.getTrackedCalls('broadcast');
let lastCall = broadcastCalls[broadcastCalls.length-1];
assert.ok(lastCall.arguments[1].is_retry);
});
Then('the broadcast method is not called again', function() {
let newBroadcastCallCount = this.adapter.getTrackedCalls('broadcast').length;
assert.equal(this.callCounts.broadcast, newBroadcastCallCount);
});
\ No newline at end of file
......@@ -6,7 +6,7 @@ const { fixtures } = require('../../fixtures/server');
const mockValidConfig = fixtures.get('config-valid');
const mockInvalidConfig = fixtures.get('config-invalid');
const mockSchema = require('../../mock/swagger.json');
const { Adapter } = require('../../../dist/adapter');
// const { Adapter } = require('../../../dist/adapter');
Given('valid config', function() {
this.config = mockValidConfig
......@@ -18,8 +18,8 @@ Given('invalid config', function() {
});
When('the adapter instance is created', function() {
let mockAdapter = new Adapter(this.protocol, this.config);
this.adapter = mockAdapter;
this.adapter = new this.classes.TrackedAdapter(this.protocol, this.config);
this.adapter.setupCallTracking(this.recorder, this.tracker);
});
Then('a successful response is returned with status {int}', function(expectedStatus) {
......@@ -34,4 +34,34 @@ Then('an error response is returned with status {int}', function(expectedStatus)
.catch((error) => {
assert.equal(error.response.status, expectedStatus);
});
});
\ No newline at end of file
});
Then('the credentials are deleted', function() {
assert.equal(this.adapter.credentials, null);
});
Then('the credentials are not deleted', function() {
assert.notEqual(this.adapter.credentials, null);
});
When('the {string} method call counts are checked', function(method) {
let newCallCount = this.adapter.getTrackedCalls(method).length;
this.callCounts[method] = newCallCount;
});
Then('the {string} method is not called again', function(method) {
let newCallCount = this.adapter.getTrackedCalls(method).length;
assert.equal(this.callCounts[method], newCallCount);
});
Then('the total number of calls to {string} was {int}', function(method, expectedCallCount) {
let callCount = this.adapter.getTrackedCalls(method).length;
assert.equal(callCount, expectedCallCount);
});
Then('the total number of {string} requests to {string} was {int}', function(method, endpoint, expectedCallCount) {
let url = `${this.api}${endpoint}`;
let requestHistory = this.mockAxios.history[method.toLowerCase()].filter((request) => request.url === url);
assert.equal(requestHistory.length, expectedCallCount);
});
......@@ -2,7 +2,9 @@ const assert = require('assert');
const { When, Then } = require('@cucumber/cucumber');
When('the getAuthorizationHeader method is called', function() {
this.call = this.adapter.getAuthorizationHeader()
this.call = this.adapter.getAuthorizationHeader();
let callCount = this.adapter.getTrackedCalls('getAuthorizationHeader').length;
this.callCounts.getAuthorizationHeader = callCount;
});
Then('a headers object is returned containing a bearer token authorization header', function() {
......@@ -12,4 +14,4 @@ Then('a headers object is returned containing a bearer token authorization heade
assert.ok(authHeaderWords[0] === 'Bearer');
assert.ok(authHeaderWords[1] === this.adapter.credentials.token);
});
});
\ No newline at end of file
});
......@@ -24,14 +24,19 @@ When('a mock receive API response is configured to return {int} messages', funct
).reply(200, response);
});
When('a mock receive API response is configured to return an error', function() {
When('a mock receive API response is configured to return a {int} error', function(statusCode) {
const statusMessages = {
403: 'Token expired',
503: 'Service unavailable'
};
this.mockAxios.onGet(
`${mockValidConfig.api}/receive`,
).reply(403, { message: 'Token expired' })
).reply(statusCode, { message: statusMessages[statusCode] })
});
When('the poll method is called', function() {
this.call = this.adapter.poll();
this.callCounts.poll = this.adapter.getTrackedCalls('poll').length;
});
Then('a successful response is returned with {int} messages', function(xMessages) {
......@@ -45,3 +50,19 @@ Then('the protocol {string} method is called {int} times', function(method, xInv
const decodes = this.protocol.getTrackedCalls(method);
assert.equal(decodes.length, xInvokes);
});
When('the poll method is called with is_retry on', function() {
this.call = this.adapter.poll(true);
this.callCounts.poll = this.adapter.getTrackedCalls('poll').length;
});
Then('the poll method was called with is_retry on', function() {
let pollCalls = this.adapter.getTrackedCalls('poll');
let lastCall = pollCalls[pollCalls.length-1];
assert.ok(lastCall.arguments[1].is_retry);
});
Then('the poll method is not called again', function() {
let newPollCallCount = this.adapter.getTrackedCalls('poll').length;
assert.equal(this.callCounts.poll, newPollCallCount);
});
......@@ -12,10 +12,14 @@ When('a mock send API response is configured to return success', function() {
).reply(200, response);
});
When('a mock send API response is configured to return an error', function() {
When('a mock send API response is configured to return a {int} error', function(statusCode) {
const statusMessages = {
403: 'Token expired',
503: 'Service unavailable'
};
this.mockAxios.onPost(
`${mockValidConfig.api}/send`,
).reply(403, { message: 'Token expired' })
).reply(statusCode, { message: statusMessages[statusCode] })
});
When('the publish method is called', function() {
......@@ -24,4 +28,25 @@ When('the publish method is called', function() {
const topic = message.metadata.destination;
const body = JSON.stringify(message);
this.call = this.adapter.publish(topic, body);
this.callCounts.publish = this.adapter.getTrackedCalls('publish').length;
});
When('the publish method is called with is_retry on', function() {
const message = fixtures.get('message-vehicle-status');
this.message = message;
const topic = message.metadata.destination;
const body = JSON.stringify(message);
this.call = this.adapter.publish(topic, body, true);
this.callCounts.publish = this.adapter.getTrackedCalls('publish').length;
});
Then('the publish method was called with is_retry on', function() {
let publishCalls = this.adapter.getTrackedCalls('publish');
let lastCall = publishCalls[publishCalls.length-1];
assert.ok(lastCall.arguments[1].is_retry);
});
Then('the publish method is not called again', function() {
let newPublishCallCount = this.adapter.getTrackedCalls('publish').length;
assert.equal(this.callCounts.publish, newPublishCallCount);
});
\ No newline at end of file
......@@ -1318,9 +1318,9 @@ babel-preset-jest@^27.5.1:
babel-plugin-jest-hoist "^27.5.1"
babel-preset-current-node-syntax "^1.0.0"
"backbone-adapter-testsuite@git+https://git.noc.ac.uk/communications-backbone-system/backbone-adapter-testsuite.git#2a22ae98":
"backbone-adapter-testsuite@git+https://git.noc.ac.uk/communications-backbone-system/backbone-adapter-testsuite.git#dev":
version "0.0.1"
resolved "git+https://git.noc.ac.uk/communications-backbone-system/backbone-adapter-testsuite.git#2a22ae98e9020a04895f79f6af5fb1c979f62259"
resolved "git+https://git.noc.ac.uk/communications-backbone-system/backbone-adapter-testsuite.git#3a5b94e16188f53464ea06b9761e67292f5c07ff"
balanced-match@^1.0.0:
version "1.0.2"
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment