From b66f89a76dd4fd6467e028c9650c76889828ed43 Mon Sep 17 00:00:00 2001
From: Dan Jones <dan.jones@noc.ac.uk>
Date: Mon, 20 Feb 2023 09:45:06 +0000
Subject: [PATCH 1/6] feat: add retry method to refresh token on 403

At the moment if the backbone token secret changes
the adapter can have a token it thinks is valid but
which can no longer be decrypted by the backbone.

On a 403 error this will trigger a new token grant
and resend the request with the new token.
---
 dist/adapter.esm.js  | 34 +++++++++++++++++++++++++++++-----
 dist/adapter.js      | 34 +++++++++++++++++++++++++++++-----
 src/adapter/index.js | 34 +++++++++++++++++++++++++++++-----
 3 files changed, 87 insertions(+), 15 deletions(-)

diff --git a/dist/adapter.esm.js b/dist/adapter.esm.js
index 1acacf6..97b4e82 100644
--- a/dist/adapter.esm.js
+++ b/dist/adapter.esm.js
@@ -115,9 +115,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 +137,18 @@ class Adapter {
         return response;
       })
       .catch((error) => {
-        return Promise.reject(error);
+        let retry = false;
+        switch(error.response.status_code) {
+          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 +160,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 +182,18 @@ class Adapter {
         return response;
       })
       .catch((error) => {
-        return Promise.reject(error);
+        let retry = false;
+        switch(error.response.status_code) {
+          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);
       });
   }
 }
diff --git a/dist/adapter.js b/dist/adapter.js
index c868d51..57c294f 100644
--- a/dist/adapter.js
+++ b/dist/adapter.js
@@ -117,9 +117,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 +139,18 @@ class Adapter {
         return response;
       })
       .catch((error) => {
-        return Promise.reject(error);
+        let retry = false;
+        switch(error.response.status_code) {
+          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 +162,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 +184,18 @@ class Adapter {
         return response;
       })
       .catch((error) => {
-        return Promise.reject(error);
+        let retry = false;
+        switch(error.response.status_code) {
+          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);
       });
   }
 }
diff --git a/src/adapter/index.js b/src/adapter/index.js
index d52101f..51ecde7 100644
--- a/src/adapter/index.js
+++ b/src/adapter/index.js
@@ -115,9 +115,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 +137,18 @@ export class Adapter {
         return response;
       })
       .catch((error) => {
-        return Promise.reject(error);
+        let retry = false;
+        switch(error.response.status_code) {
+          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 +160,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 +182,18 @@ export class Adapter {
         return response;
       })
       .catch((error) => {
-        return Promise.reject(error);
+        let retry = false;
+        switch(error.response.status_code) {
+          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);
       });
   }
 }
-- 
GitLab


From 341e40d075abb536a9c752bb833116b761edc053 Mon Sep 17 00:00:00 2001
From: Dan Jones <dan.jones@noc.ac.uk>
Date: Mon, 20 Feb 2023 10:15:05 +0000
Subject: [PATCH 2/6] fix: correct reference to error status code

+ add catch and retry in poll method
---
 dist/adapter.esm.js  | 22 +++++++++++++++++-----
 dist/adapter.js      | 22 +++++++++++++++++-----
 src/adapter/index.js | 22 +++++++++++++++++-----
 3 files changed, 51 insertions(+), 15 deletions(-)

diff --git a/dist/adapter.esm.js b/dist/adapter.esm.js
index 97b4e82..cfb0379 100644
--- a/dist/adapter.esm.js
+++ b/dist/adapter.esm.js
@@ -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);
       });
   }
 
@@ -138,7 +150,7 @@ class Adapter {
       })
       .catch((error) => {
         let retry = false;
-        switch(error.response.status_code) {
+        switch(error.response.status) {
           case 403: {
             this.credentials = null;
             retry = true;
@@ -183,7 +195,7 @@ class Adapter {
       })
       .catch((error) => {
         let retry = false;
-        switch(error.response.status_code) {
+        switch(error.response.status) {
           case 403: {
             this.credentials = null;
             retry = true;
diff --git a/dist/adapter.js b/dist/adapter.js
index 57c294f..a20309e 100644
--- a/dist/adapter.js
+++ b/dist/adapter.js
@@ -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);
       });
   }
 
@@ -140,7 +152,7 @@ class Adapter {
       })
       .catch((error) => {
         let retry = false;
-        switch(error.response.status_code) {
+        switch(error.response.status) {
           case 403: {
             this.credentials = null;
             retry = true;
@@ -185,7 +197,7 @@ class Adapter {
       })
       .catch((error) => {
         let retry = false;
-        switch(error.response.status_code) {
+        switch(error.response.status) {
           case 403: {
             this.credentials = null;
             retry = true;
diff --git a/src/adapter/index.js b/src/adapter/index.js
index 51ecde7..3d48d0e 100644
--- a/src/adapter/index.js
+++ b/src/adapter/index.js
@@ -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);
       });
   }
 
@@ -138,7 +150,7 @@ export class Adapter {
       })
       .catch((error) => {
         let retry = false;
-        switch(error.response.status_code) {
+        switch(error.response.status) {
           case 403: {
             this.credentials = null;
             retry = true;
@@ -183,7 +195,7 @@ export class Adapter {
       })
       .catch((error) => {
         let retry = false;
-        switch(error.response.status_code) {
+        switch(error.response.status) {
           case 403: {
             this.credentials = null;
             retry = true;
-- 
GitLab


From 26a724dc3348c194e07bc53c4b277b7d73e0b0ae Mon Sep 17 00:00:00 2001
From: Dan Jones <dan.jones@noc.ac.uk>
Date: Mon, 20 Feb 2023 15:36:45 +0000
Subject: [PATCH 3/6] test: retry behaviour for 403s

---
 package.json                             |  2 +-
 test/cucumber/adapter/before.steps.js    | 46 +++++++++++++++++++++---
 test/cucumber/adapter/broadcast.steps.js | 23 +++++++++++-
 test/cucumber/adapter/common.steps.js    | 10 ++++--
 test/cucumber/adapter/poll.steps.js      | 16 +++++++++
 test/cucumber/adapter/publish.steps.js   | 21 +++++++++++
 yarn.lock                                |  4 +--
 7 files changed, 110 insertions(+), 12 deletions(-)

diff --git a/package.json b/package.json
index 983a817..f8b9ee6 100644
--- a/package.json
+++ b/package.json
@@ -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#dd29691a",
     "cross-env": "^7.0.3",
     "eslint": "^8.4.1",
     "eslint-config-prettier": "^8.3.0",
diff --git a/test/cucumber/adapter/before.steps.js b/test/cucumber/adapter/before.steps.js
index d07405d..fc17ad9 100644
--- a/test/cucumber/adapter/before.steps.js
+++ b/test/cucumber/adapter/before.steps.js
@@ -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});
@@ -63,7 +94,12 @@ Before(function() {
     recorder,
     tracker
   };
-  this.protocol = new TrackedGenericProtocol(this.schema, services);
+  this.classes = {
+    TrackedAdapter,
+    TrackedGenericProtocol,
+  };
+  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();
diff --git a/test/cucumber/adapter/broadcast.steps.js b/test/cucumber/adapter/broadcast.steps.js
index ad08de5..d0206a7 100644
--- a/test/cucumber/adapter/broadcast.steps.js
+++ b/test/cucumber/adapter/broadcast.steps.js
@@ -1,4 +1,5 @@
-const { When } = require('@cucumber/cucumber');
+const assert = require('assert');
+const { When, Then } = require('@cucumber/cucumber');
 
 const { fixtures } = require('../../fixtures/server');
 
@@ -22,4 +23,24 @@ When('the broadcast method is called', function() {
   this.message = message;
   const body = JSON.stringify(message);
   this.call = this.adapter.broadcast(body);
+  this.broadcastCallCount = 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.broadcastCallCount = 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.broadcastCallCount, newBroadcastCallCount);
 });
\ No newline at end of file
diff --git a/test/cucumber/adapter/common.steps.js b/test/cucumber/adapter/common.steps.js
index 3979c10..c48982e 100644
--- a/test/cucumber/adapter/common.steps.js
+++ b/test/cucumber/adapter/common.steps.js
@@ -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,8 @@ Then('an error response is returned with status {int}', function(expectedStatus)
   .catch((error) => {
     assert.equal(error.response.status, expectedStatus);
   });
+});
+
+Then('the credentials are deleted', function() {
+  assert.equal(this.adapter.credentials, null);
 });
\ No newline at end of file
diff --git a/test/cucumber/adapter/poll.steps.js b/test/cucumber/adapter/poll.steps.js
index db3b564..60fc2c0 100644
--- a/test/cucumber/adapter/poll.steps.js
+++ b/test/cucumber/adapter/poll.steps.js
@@ -45,3 +45,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.pollCallCount = 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.pollCallCount, newPollCallCount);
+});
diff --git a/test/cucumber/adapter/publish.steps.js b/test/cucumber/adapter/publish.steps.js
index 95a0f62..43648c0 100644
--- a/test/cucumber/adapter/publish.steps.js
+++ b/test/cucumber/adapter/publish.steps.js
@@ -24,4 +24,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.publishCallCount = 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.publishCallCount = 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.publishCallCount, newPublishCallCount);
 });
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index 9f04020..15f970c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -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#dd29691a":
   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#dd29691affa53b04ac65b5fe7dc90df8e0286867"
 
 balanced-match@^1.0.0:
   version "1.0.2"
-- 
GitLab


From 1245857604c996aedf32b96251d1dd33eb930610 Mon Sep 17 00:00:00 2001
From: Dan Jones <dan.jones@noc.ac.uk>
Date: Tue, 21 Feb 2023 09:27:47 +0000
Subject: [PATCH 4/6] refactor: count api calls to /token on retries

---
 package.json                                  |  2 +-
 test/cucumber/adapter/before.steps.js         |  3 ++
 test/cucumber/adapter/broadcast.steps.js      | 14 ++++++----
 test/cucumber/adapter/common.steps.js         | 28 ++++++++++++++++++-
 .../adapter/get-authorization-header.steps.js |  6 ++--
 test/cucumber/adapter/poll.steps.js           | 13 ++++++---
 test/cucumber/adapter/publish.steps.js        | 14 ++++++----
 yarn.lock                                     |  4 +--
 8 files changed, 64 insertions(+), 20 deletions(-)

diff --git a/package.json b/package.json
index f8b9ee6..46409e3 100644
--- a/package.json
+++ b/package.json
@@ -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#dd29691a",
+    "backbone-adapter-testsuite": "git+https://git.noc.ac.uk/communications-backbone-system/backbone-adapter-testsuite.git#1fd8622a",
     "cross-env": "^7.0.3",
     "eslint": "^8.4.1",
     "eslint-config-prettier": "^8.3.0",
diff --git a/test/cucumber/adapter/before.steps.js b/test/cucumber/adapter/before.steps.js
index fc17ad9..1ac9d79 100644
--- a/test/cucumber/adapter/before.steps.js
+++ b/test/cucumber/adapter/before.steps.js
@@ -87,6 +87,7 @@ class TrackedGenericProtocol extends GenericProtocol {
 }
 
 Before(function() {
+  this.api = mockValidConfig.api;
   this.schema = mockSchema;
   this.tracker = tracker;
   this.recorder = recorder;
@@ -98,11 +99,13 @@ Before(function() {
     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`, 
diff --git a/test/cucumber/adapter/broadcast.steps.js b/test/cucumber/adapter/broadcast.steps.js
index d0206a7..05a9cd0 100644
--- a/test/cucumber/adapter/broadcast.steps.js
+++ b/test/cucumber/adapter/broadcast.steps.js
@@ -12,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() {
@@ -23,7 +27,7 @@ When('the broadcast method is called', function() {
   this.message = message;
   const body = JSON.stringify(message);
   this.call = this.adapter.broadcast(body);
-  this.broadcastCallCount = this.adapter.getTrackedCalls('broadcast').length;
+  this.callCounts.broadcast = this.adapter.getTrackedCalls('broadcast').length;
 });
 
 When('the broadcast method is called with is_retry on', function() {
@@ -31,7 +35,7 @@ When('the broadcast method is called with is_retry on', function() {
   this.message = message;
   const body = JSON.stringify(message);
   this.call = this.adapter.broadcast(body, true);
-  this.broadcastCallCount = this.adapter.getTrackedCalls('broadcast').length;
+  this.callCounts.broadcast = this.adapter.getTrackedCalls('broadcast').length;
 });
 
 Then('the broadcast method was called with is_retry on', function() {
@@ -42,5 +46,5 @@ Then('the broadcast method was called with is_retry on', function() {
 
 Then('the broadcast method is not called again', function() {
   let newBroadcastCallCount = this.adapter.getTrackedCalls('broadcast').length;
-  assert.equal(this.broadcastCallCount, newBroadcastCallCount);
+  assert.equal(this.callCounts.broadcast, newBroadcastCallCount);
 });
\ No newline at end of file
diff --git a/test/cucumber/adapter/common.steps.js b/test/cucumber/adapter/common.steps.js
index c48982e..a0f065c 100644
--- a/test/cucumber/adapter/common.steps.js
+++ b/test/cucumber/adapter/common.steps.js
@@ -38,4 +38,30 @@ Then('an error response is returned with status {int}', function(expectedStatus)
 
 Then('the credentials are deleted', function() {
   assert.equal(this.adapter.credentials, null);
-});
\ No newline at end of file
+});
+
+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);
+});
+
diff --git a/test/cucumber/adapter/get-authorization-header.steps.js b/test/cucumber/adapter/get-authorization-header.steps.js
index af31d0b..a9fd652 100644
--- a/test/cucumber/adapter/get-authorization-header.steps.js
+++ b/test/cucumber/adapter/get-authorization-header.steps.js
@@ -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
+});
diff --git a/test/cucumber/adapter/poll.steps.js b/test/cucumber/adapter/poll.steps.js
index 60fc2c0..a2b50a0 100644
--- a/test/cucumber/adapter/poll.steps.js
+++ b/test/cucumber/adapter/poll.steps.js
@@ -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) {
@@ -48,7 +53,7 @@ Then('the protocol {string} method is called {int} times', function(method, xInv
 
 When('the poll method is called with is_retry on', function() {
   this.call = this.adapter.poll(true);
-  this.pollCallCount = this.adapter.getTrackedCalls('poll').length;
+  this.callCounts.poll = this.adapter.getTrackedCalls('poll').length;
 });
 
 Then('the poll method was called with is_retry on', function() {
@@ -59,5 +64,5 @@ Then('the poll method was called with is_retry on', function() {
 
 Then('the poll method is not called again', function() {
   let newPollCallCount = this.adapter.getTrackedCalls('poll').length;
-  assert.equal(this.pollCallCount, newPollCallCount);
+  assert.equal(this.callCounts.poll, newPollCallCount);
 });
diff --git a/test/cucumber/adapter/publish.steps.js b/test/cucumber/adapter/publish.steps.js
index 43648c0..e207488 100644
--- a/test/cucumber/adapter/publish.steps.js
+++ b/test/cucumber/adapter/publish.steps.js
@@ -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,7 +28,7 @@ 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.publishCallCount = this.adapter.getTrackedCalls('publish').length;
+  this.callCounts.publish = this.adapter.getTrackedCalls('publish').length;
 });
 
 When('the publish method is called with is_retry on', function() {
@@ -33,7 +37,7 @@ When('the publish method is called with is_retry on', function() {
   const topic = message.metadata.destination;
   const body = JSON.stringify(message);
   this.call = this.adapter.publish(topic, body, true);
-  this.publishCallCount = this.adapter.getTrackedCalls('publish').length;
+  this.callCounts.publish = this.adapter.getTrackedCalls('publish').length;
 });
 
 Then('the publish method was called with is_retry on', function() {
@@ -44,5 +48,5 @@ Then('the publish method was called with is_retry on', function() {
 
 Then('the publish method is not called again', function() {
   let newPublishCallCount = this.adapter.getTrackedCalls('publish').length;
-  assert.equal(this.publishCallCount, newPublishCallCount);
+  assert.equal(this.callCounts.publish, newPublishCallCount);
 });
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index 15f970c..38992e1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -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#dd29691a":
+"backbone-adapter-testsuite@git+https://git.noc.ac.uk/communications-backbone-system/backbone-adapter-testsuite.git#1fd8622a":
   version "0.0.1"
-  resolved "git+https://git.noc.ac.uk/communications-backbone-system/backbone-adapter-testsuite.git#dd29691affa53b04ac65b5fe7dc90df8e0286867"
+  resolved "git+https://git.noc.ac.uk/communications-backbone-system/backbone-adapter-testsuite.git#1fd8622a217eda84f05c91a9a7181d5cfad0aace"
 
 balanced-match@^1.0.0:
   version "1.0.2"
-- 
GitLab


From 9eee6fcd5b619cf99d5490c040095baacb7efe65 Mon Sep 17 00:00:00 2001
From: Dan Jones <dan.jones@noc.ac.uk>
Date: Tue, 21 Feb 2023 17:12:38 +0000
Subject: [PATCH 5/6] fix: update test suite commit ref

Switch to fake domain for adapter config
---
 package.json | 2 +-
 yarn.lock    | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/package.json b/package.json
index 46409e3..5e7a732 100644
--- a/package.json
+++ b/package.json
@@ -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#1fd8622a",
+    "backbone-adapter-testsuite": "git+https://git.noc.ac.uk/communications-backbone-system/backbone-adapter-testsuite.git#09029c59",
     "cross-env": "^7.0.3",
     "eslint": "^8.4.1",
     "eslint-config-prettier": "^8.3.0",
diff --git a/yarn.lock b/yarn.lock
index 38992e1..a7a8c96 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -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#1fd8622a":
+"backbone-adapter-testsuite@git+https://git.noc.ac.uk/communications-backbone-system/backbone-adapter-testsuite.git#09029c59":
   version "0.0.1"
-  resolved "git+https://git.noc.ac.uk/communications-backbone-system/backbone-adapter-testsuite.git#1fd8622a217eda84f05c91a9a7181d5cfad0aace"
+  resolved "git+https://git.noc.ac.uk/communications-backbone-system/backbone-adapter-testsuite.git#09029c5981b898548841992b6f56a091c54ab5af"
 
 balanced-match@^1.0.0:
   version "1.0.2"
-- 
GitLab


From b2a02056e8e4821d7e8cf0c9447f2c7d8a381e84 Mon Sep 17 00:00:00 2001
From: Dan Jones <dan.jones@noc.ac.uk>
Date: Wed, 22 Feb 2023 12:58:26 +0000
Subject: [PATCH 6/6] test: revert testsuite to install #dev

---
 package.json | 2 +-
 yarn.lock    | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/package.json b/package.json
index 5e7a732..bc3b97b 100644
--- a/package.json
+++ b/package.json
@@ -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#09029c59",
+    "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",
diff --git a/yarn.lock b/yarn.lock
index a7a8c96..38ff398 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -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#09029c59":
+"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#09029c5981b898548841992b6f56a091c54ab5af"
+  resolved "git+https://git.noc.ac.uk/communications-backbone-system/backbone-adapter-testsuite.git#3a5b94e16188f53464ea06b9761e67292f5c07ff"
 
 balanced-match@^1.0.0:
   version "1.0.2"
-- 
GitLab