From 1ff6f48a1c60c89cc7f721ddce60997e32fffb19 Mon Sep 17 00:00:00 2001
From: Dan Jones <dan.jones@noc.ac.uk>
Date: Mon, 16 Jan 2023 12:12:52 +0000
Subject: [PATCH] test: test validate method

Add test to check that the mock schema is valid
If the mock schema is not valid then invalid messages
can be marked as valid which makes the tests ineffective
---
 dist/adapter.esm.js                           |   5 +-
 dist/adapter.js                               |   5 +-
 dist/protocol.esm.js                          |   8 +-
 dist/protocol.js                              |   8 +-
 .../adapter/{authenticates.js => auth.js}     |  12 +-
 features/adapter/{receives.js => poll.js}     |   3 +-
 features/adapter/validate.js                  |  26 +
 features/adapter_validates.feature            |  16 +
 features/schema/validate.js                   |  23 +
 features/schema_validates.feature             |  10 +
 package.json                                  |   1 +
 src/adapter/index.js                          |   5 +-
 src/protocol/index.js                         |   8 +-
 ...nvalid-config.json => config-invalid.json} |   0
 .../{valid-config.json => config-valid.json}  |   0
 .../message-vehicle-status-invalid.json       |  20 +
 test/fixtures/message-vehicle-status.json     |  24 +-
 test/fixtures/server.js                       |   2 +-
 .../mock/messages/Message_VehicleMission.json |   4 +-
 .../Message_VehicleMissionActions.json        |   8 +-
 test/mock/messages/Message_VehicleStatus.json |   4 +-
 test/mock/swagger.json                        | 446 +++++++++---------
 yarn.lock                                     |  44 +-
 23 files changed, 435 insertions(+), 247 deletions(-)
 rename features/adapter/{authenticates.js => auth.js} (88%)
 rename features/adapter/{receives.js => poll.js} (89%)
 create mode 100644 features/adapter/validate.js
 create mode 100644 features/adapter_validates.feature
 create mode 100644 features/schema/validate.js
 create mode 100644 features/schema_validates.feature
 rename test/fixtures/{invalid-config.json => config-invalid.json} (100%)
 rename test/fixtures/{valid-config.json => config-valid.json} (100%)
 create mode 100644 test/fixtures/message-vehicle-status-invalid.json

diff --git a/dist/adapter.esm.js b/dist/adapter.esm.js
index dfd3c83..d79a030 100644
--- a/dist/adapter.esm.js
+++ b/dist/adapter.esm.js
@@ -27,7 +27,10 @@ class Adapter {
   validate(message) {
     return this.validator.validate(
       message,
-      this.protocol.schema.definitions.Message
+      this.protocol.schema.components.schemas.Message,
+      this.protocol.schema.components.schemas,
+      false,
+      true
     );
   }
 
diff --git a/dist/adapter.js b/dist/adapter.js
index 5d4ae04..565f78d 100644
--- a/dist/adapter.js
+++ b/dist/adapter.js
@@ -29,7 +29,10 @@ class Adapter {
   validate(message) {
     return this.validator.validate(
       message,
-      this.protocol.schema.definitions.Message
+      this.protocol.schema.components.schemas.Message,
+      this.protocol.schema.components.schemas,
+      false,
+      true
     );
   }
 
diff --git a/dist/protocol.esm.js b/dist/protocol.esm.js
index bd50069..0b9a29f 100644
--- a/dist/protocol.esm.js
+++ b/dist/protocol.esm.js
@@ -35,7 +35,13 @@ class GenericProtocol {
    * @returns {object}
    */
   validate(message) {
-    return this.validator.validate(message, this.schema.definitions.Message);
+    return this.validator.validate(
+      message,
+      this.protocol.schema.components.schemas.Message,
+      this.protocol.schema.components.schemas,
+      false,
+      true
+    );
   }
 
   /**
diff --git a/dist/protocol.js b/dist/protocol.js
index 4ced40e..c5004e1 100644
--- a/dist/protocol.js
+++ b/dist/protocol.js
@@ -37,7 +37,13 @@ class GenericProtocol {
    * @returns {object}
    */
   validate(message) {
-    return this.validator.validate(message, this.schema.definitions.Message);
+    return this.validator.validate(
+      message,
+      this.protocol.schema.components.schemas.Message,
+      this.protocol.schema.components.schemas,
+      false,
+      true
+    );
   }
 
   /**
diff --git a/features/adapter/authenticates.js b/features/adapter/auth.js
similarity index 88%
rename from features/adapter/authenticates.js
rename to features/adapter/auth.js
index 373b9f0..4a64298 100644
--- a/features/adapter/authenticates.js
+++ b/features/adapter/auth.js
@@ -9,13 +9,21 @@ const mockAxios = new MockAdapter(axios);
 
 const { fixtures } = require('../../test/fixtures/server');
 
-const mockValidConfig = fixtures.get('valid-config');
-const mockInvalidConfig = fixtures.get('invalid-config');
+const mockValidConfig = fixtures.get('config-valid');
+const mockInvalidConfig = fixtures.get('config-invalid');
 
 const mockSchema = require('../../test/mock/swagger.json');
 const { Adapter } = require('../../dist/adapter');
 const { GenericProtocol } = require('../../dist/protocol');
 
+
+let decodeTracker;
+class TrackedGenericProtocol extends GenericProtocol {
+  decode() {
+    return super.decode()
+  }
+}
+
 Before(function() {
   this.mockAxios = mockAxios;
   this.mockAxios.reset();
diff --git a/features/adapter/receives.js b/features/adapter/poll.js
similarity index 89%
rename from features/adapter/receives.js
rename to features/adapter/poll.js
index 6cbf5bb..76c537a 100644
--- a/features/adapter/receives.js
+++ b/features/adapter/poll.js
@@ -3,8 +3,7 @@ const { Before, Given, When, Then } = require('@cucumber/cucumber');
 
 const { fixtures } = require('../../test/fixtures/server');
 
-const mockValidConfig = fixtures.get('valid-config');
-const mockInvalidConfig = fixtures.get('invalid-config');
+const mockValidConfig = fixtures.get('config-valid');
 
 const xMessageResponse = function(xMessages) {
   const message = fixtures.get('message-vehicle-status');
diff --git a/features/adapter/validate.js b/features/adapter/validate.js
new file mode 100644
index 0000000..db57e8e
--- /dev/null
+++ b/features/adapter/validate.js
@@ -0,0 +1,26 @@
+const assert = require('assert');
+const { Before, Given, When, Then } = require('@cucumber/cucumber');
+
+const { fixtures } = require('../../test/fixtures/server');
+
+Given('a valid message', function() {
+  this.message = fixtures.get('message-vehicle-status');
+});
+
+Given('an invalid message', function() {
+  this.message = fixtures.get('message-vehicle-status-invalid');
+});
+
+When('the validate method is called', function() {
+  this.validation = this.adapter.validate(this.message);
+});
+
+Then('the message is validated successfully', function() {
+  assert.equal(this.validation.valid, true);
+  assert.equal(this.validation.errorCount, 0);
+});
+
+Then('the message fails to validate', function() {
+  assert.equal(this.validation.valid, false);
+  assert.notEqual(this.validation.errorCount, 0);
+});
\ No newline at end of file
diff --git a/features/adapter_validates.feature b/features/adapter_validates.feature
new file mode 100644
index 0000000..c2af9f1
--- /dev/null
+++ b/features/adapter_validates.feature
@@ -0,0 +1,16 @@
+Feature: Can the adapter validate messages?
+  The adapter validate method works as expected
+
+  Scenario: A valid message is successfully validated against the protocol schema
+    Given valid config
+    Given a valid message
+    When the adapter instance is created
+    When the validate method is called
+    Then the message is validated successfully  
+  
+  Scenario: An invalid message fails to validate against the protocol schema
+    Given valid config
+    Given an invalid message
+    When the adapter instance is created
+    When the validate method is called
+    Then the message fails to validate  
\ No newline at end of file
diff --git a/features/schema/validate.js b/features/schema/validate.js
new file mode 100644
index 0000000..58a4964
--- /dev/null
+++ b/features/schema/validate.js
@@ -0,0 +1,23 @@
+const assert = require('assert');
+const { Given, When, Then } = require('@cucumber/cucumber');
+const OpenAPISchemaValidator = require('openapi-schema-validator').default;
+
+const fs = require('fs');
+
+const schemaLocation = './test/mock/swagger.json';
+
+Given('the test schema', function() {
+  this.schema = JSON.parse(fs.readFileSync(schemaLocation));
+});
+
+When('it is validated', function() {
+  const validator = new OpenAPISchemaValidator({ version: 3 });
+  this.validation = validator.validate(this.schema);
+});
+
+Then('it matches the OpenAPI specification', function() {
+  // According to the docs this should return a valid:boolean 
+  // but if you look at the code it just returns a list of errors 
+  // which is empty for a valid result
+  assert.equal(this.validation.errors.length, 0);
+});
\ No newline at end of file
diff --git a/features/schema_validates.feature b/features/schema_validates.feature
new file mode 100644
index 0000000..34f457a
--- /dev/null
+++ b/features/schema_validates.feature
@@ -0,0 +1,10 @@
+# If the mock schema fixture fails to validate 
+# it can cause invalid messages to show as valid
+
+Feature: Is the mock schema valid?
+  The mock schema must validate in order for the adapter test to work
+
+  Scenario: The schema matches the OpenAPI specification
+    Given the test schema
+    When it is validated
+    Then it matches the OpenAPI specification  
\ No newline at end of file
diff --git a/package.json b/package.json
index c7beded..90ecb89 100644
--- a/package.json
+++ b/package.json
@@ -51,6 +51,7 @@
     "husky": "^7.0.4",
     "jest": "^27.4.4",
     "lint-staged": "^12.1.2",
+    "openapi-schema-validator": "^12.1.0",
     "prettier": "^2.5.1",
     "rollup": "^3.9.1"
   }
diff --git a/src/adapter/index.js b/src/adapter/index.js
index 2604c5c..ad41f54 100644
--- a/src/adapter/index.js
+++ b/src/adapter/index.js
@@ -27,7 +27,10 @@ export class Adapter {
   validate(message) {
     return this.validator.validate(
       message,
-      this.protocol.schema.definitions.Message
+      this.protocol.schema.components.schemas.Message,
+      this.protocol.schema.components.schemas,
+      false,
+      true
     );
   }
 
diff --git a/src/protocol/index.js b/src/protocol/index.js
index 151cc98..74676dd 100644
--- a/src/protocol/index.js
+++ b/src/protocol/index.js
@@ -35,7 +35,13 @@ export class GenericProtocol {
    * @returns {object}
    */
   validate(message) {
-    return this.validator.validate(message, this.schema.definitions.Message);
+    return this.validator.validate(
+      message,
+      this.protocol.schema.components.schemas.Message,
+      this.protocol.schema.components.schemas,
+      false,
+      true
+    );
   }
 
   /**
diff --git a/test/fixtures/invalid-config.json b/test/fixtures/config-invalid.json
similarity index 100%
rename from test/fixtures/invalid-config.json
rename to test/fixtures/config-invalid.json
diff --git a/test/fixtures/valid-config.json b/test/fixtures/config-valid.json
similarity index 100%
rename from test/fixtures/valid-config.json
rename to test/fixtures/config-valid.json
diff --git a/test/fixtures/message-vehicle-status-invalid.json b/test/fixtures/message-vehicle-status-invalid.json
new file mode 100644
index 0000000..cbcf162
--- /dev/null
+++ b/test/fixtures/message-vehicle-status-invalid.json
@@ -0,0 +1,20 @@
+{
+  "metadata": {
+    "source": "ae",
+    "destination": "soar.po.ecosub.eco1",
+    "delivery_type": "publish",
+    "message_id": "test"
+  },
+  "payload": {
+    "message_type": "VehicleStatus",
+    "xoperator_id": 1,
+    "xvehicle_id": 12,
+    "coordinates": {
+      "latitude": "monkeys",
+      "longitude": "janvier",
+      "depth": "twenty five metres please",
+      "projection": 4326
+    },
+    "battery_percentage": "plenty"
+  }
+}
\ No newline at end of file
diff --git a/test/fixtures/message-vehicle-status.json b/test/fixtures/message-vehicle-status.json
index 1e47be5..55cdd81 100644
--- a/test/fixtures/message-vehicle-status.json
+++ b/test/fixtures/message-vehicle-status.json
@@ -1,18 +1,20 @@
 {
-  "message_type": "VehicleStatus",
-  "headers": {
+  "metadata": {
     "source": "ae",
     "destination": "soar.po.ecosub.eco1",
     "delivery_type": "publish",
     "message_id": "test"
   },
-  "operator_id": "po",
-  "vehicle_id": "eco1",
-  "coordinates": {
-    "latitude": 57.234,
-    "longitude": -8.432,
-    "depth": 50,
-    "projection": "EPSG:4326"
-  },
-  "battery_percentage": 64
+  "payload": {
+    "message_type": "VehicleStatus",
+    "operator_id": "po",
+    "vehicle_id": "eco1",
+    "coordinates": {
+      "latitude": 57.234,
+      "longitude": -8.432,
+      "depth": 50,
+      "projection": 4326
+    },
+    "battery_percentage": 64
+  }
 }
\ No newline at end of file
diff --git a/test/fixtures/server.js b/test/fixtures/server.js
index 1b1f9ed..f541f7b 100644
--- a/test/fixtures/server.js
+++ b/test/fixtures/server.js
@@ -8,7 +8,7 @@ exports.fixtures = {
       let fixture = JSON.parse(fixtureContent);
       return fixture;
     } catch(e) {
-      console.error('Fixture not found', fixrureName);
+      console.error('Fixture not found', fixtureName);
       return null;
     }
   } 
diff --git a/test/mock/messages/Message_VehicleMission.json b/test/mock/messages/Message_VehicleMission.json
index b23a461..1036455 100644
--- a/test/mock/messages/Message_VehicleMission.json
+++ b/test/mock/messages/Message_VehicleMission.json
@@ -1,6 +1,6 @@
 {
   "message_type": "VehicleMission",
-  "headers": {
+  "metadata": {
     "source": "ae",
     "destination": "soar.po.ecosub.eco1",
     "delivery_type": "publish",
@@ -12,6 +12,6 @@
     "latitude": 59.234,
     "longitude": -10.432,
     "depth": 50,
-    "projection": "EPSG:4326"
+    "projection": 4326
   }
 }
diff --git a/test/mock/messages/Message_VehicleMissionActions.json b/test/mock/messages/Message_VehicleMissionActions.json
index 68978fb..921bf26 100644
--- a/test/mock/messages/Message_VehicleMissionActions.json
+++ b/test/mock/messages/Message_VehicleMissionActions.json
@@ -1,6 +1,6 @@
 {
   "message_type": "VehicleMission",
-  "headers": {
+  "metadata": {
     "source": "ae",
     "destination": "soar.po.ecosub.eco1",
     "delivery_type": "publish",
@@ -12,7 +12,7 @@
     "latitude": 59.234,
     "longitude": -10.432,
     "depth": 50,
-    "projection": "EPSG:4326"
+    "projection": 4326
   },
   "actions": [
     {
@@ -21,7 +21,7 @@
         "latitude": 59.234,
         "longitude": -10.432,
         "depth": 50,
-        "projection": "EPSG:4326"
+        "projection": 4326
       }
     },
     {
@@ -30,7 +30,7 @@
         "latitude": 59.234,
         "longitude": -10.432,
         "altitude": 50,
-        "projection": "EPSG:4326"
+        "projection": 4326
       }
     }
   ]
diff --git a/test/mock/messages/Message_VehicleStatus.json b/test/mock/messages/Message_VehicleStatus.json
index 490dbe1..5891bba 100644
--- a/test/mock/messages/Message_VehicleStatus.json
+++ b/test/mock/messages/Message_VehicleStatus.json
@@ -1,6 +1,6 @@
 {
   "message_type": "VehicleStatus",
-  "headers": {
+  "metadata": {
     "source": "ae",
     "destination": "soar.po.ecosub.eco1",
     "delivery_type": "publish",
@@ -12,7 +12,7 @@
     "latitude": 57.234,
     "longitude": -8.432,
     "depth": 50,
-    "projection": "EPSG:4326"
+    "projection": 4326
   },
   "battery_percentage": 64
 }
diff --git a/test/mock/swagger.json b/test/mock/swagger.json
index cc4b6d6..35999bc 100644
--- a/test/mock/swagger.json
+++ b/test/mock/swagger.json
@@ -1,237 +1,251 @@
 {
-  "swagger": "2.0",
-  "basePath": "/soar",
+  "openapi": "3.0.3",
   "info": {
     "title": "soar",
-    "version": "1.3.1",
+    "version": "1.0",
     "description": "SoAR message schemas"
   },
-  "produces": ["application/json"],
-  "consumes": ["application/json"],
-  "definitions": {
-    "Message": {
-      "discriminator": {
-        "propertyName": "message_type",
-        "mapping": {
-          "VehicleStatus": "#/definitions/VehicleStatus",
-          "VehicleMission": "#/definitions/VehicleMission",
-          "AreaOfInterest": "#/definitions/AreaOfInterest"
-        }
-      },
-      "oneOf": [
-        {
-          "$ref": "#/definitions/VehicleStatus"
-        },
-        {
-          "$ref": "#/definitions/VehicleMission"
-        },
-        {
-          "$ref": "#/definitions/AreaOfInterest"
-        }
-      ]
-    },
-    "Metadata": {
-      "properties": {
-        "source": {
-          "type": "string",
-          "description": "The sender.",
-          "example": "autonomy-engine"
-        },
-        "destination": {
-          "type": "string",
-          "description": "Publisher topic.",
-          "example": "soar.noc.autosub.ah1.status"
-        },
-        "delivery_type": {
-          "type": "string",
-          "description": "Published or broadcast",
-          "enum": ["broadcast", "publish"],
-          "example": "2.0.0"
+  "paths": {},
+  "components": {
+    "schemas": {
+      "Message": {
+        "properties": {
+          "metadata": {
+            "$ref": "#/components/schemas/Metadata"
+          },
+          "payload": {
+            "$ref": "#/components/schemas/Payload"
+          }
         },
-        "message_id": {
-          "type": "string",
-          "description": "An identifier for the type of message received.",
-          "example": "VehicleStatus"
-        }
+        "required": ["metadata", "payload"]
       },
-      "type": "object"
-    },
-    "Coordinates": {
-      "properties": {
-        "latitude": {
-          "type": "number",
-          "description": "Latitude in decimal degrees.",
-          "example": 54.234
-        },
-        "longitude": {
-          "type": "number",
-          "description": "Longitude in decimal degrees.",
-          "example": -1.432
-        },
-        "depth": {
-          "type": "number",
-          "description": "Target depth",
-          "default": 0,
-          "example": 50
-        },
-        "altitude": {
-          "type": "number",
-          "description": "Target altitude above bottom",
-          "default": 0,
-          "example": 50
-        },
-        "projection": {
-          "type": "integer",
-          "description": "EPSG Projection Code",
-          "example": 4326,
-          "default": 4326
-        }
-      }
-    },
-    "VehicleStatus": {
-      "properties": {
-        "message_type": {
-          "type": "string",
-          "description": "An identifier for the payload type.",
-          "example": "VehicleStatus",
-          "enum": ["VehicleStatus"]
-        },
-        "metadata": {
-          "$ref": "#/definitions/Metadata"
-        },
-        "operator_id": {
-          "type": "string",
-          "description": "An identifier for the operator.",
-          "example": "noc"
-        },
-        "vehicle_id": {
-          "type": "string",
-          "description": "An identifier for the vehicle.",
-          "example": "noc_ah1"
-        },
-        "coordinates": {
-          "$ref": "#/definitions/Coordinates"
-        },
-        "battery_percentage": {
-          "type": "number",
-          "description": "The remaining battery capacity.",
-          "example": 64
-        }
-      }
-    },
-    "VehicleMission": {
-      "properties": {
-        "message_type": {
-          "type": "string",
-          "description": "An identifier for the payload type.",
-          "example": "VehicleMission",
-          "enum": ["VehicleMission"]
-        },
-        "metadata": {
-          "$ref": "#/definitions/Metadata"
+      "Payload": {
+        "discriminator": {
+          "propertyName": "message_type",
+          "mapping": {
+            "VehicleStatus": "#/components/schemas/VehicleStatus",
+            "VehicleMission": "#/components/schemas/VehicleMission",
+            "AreaOfInterest": "#/components/schemas/AreaOfInterest"
+          }
         },
-        "operator_id": {
-          "type": "string",
-          "description": "An identifier for the operator.",
-          "example": "noc"
+        "oneOf": [
+          {
+            "$ref": "#/components/schemas/VehicleStatus"
+          },
+          {
+            "$ref": "#/components/schemas/VehicleMission"
+          },
+          {
+            "$ref": "#/components/schemas/AreaOfInterest"
+          }
+        ]
+      },
+      "Metadata": {
+        "properties": {
+          "source": {
+            "type": "string",
+            "description": "The sender.",
+            "example": "autonomy-engine"
+          },
+          "destination": {
+            "type": "string",
+            "description": "Publisher topic.",
+            "example": "soar.noc.autosub.ah1.status"
+          },
+          "delivery_type": {
+            "type": "string",
+            "description": "Published or broadcast",
+            "enum": ["broadcast", "publish"],
+            "example": "2.0.0"
+          },
+          "message_id": {
+            "type": "string",
+            "description": "An identifier for the type of message received.",
+            "example": "VehicleStatus"
+          }
         },
-        "vehicle_id": {
-          "type": "string",
-          "description": "An identifier for the vehicle.",
-          "example": "noc_ah1"
+        "required": ["source","destination","message_id"],
+        "type": "object"
+      },
+      "Coordinates": {
+        "properties": {
+          "latitude": {
+            "type": "number",
+            "description": "Latitude in decimal degrees.",
+            "example": 54.234
+          },
+          "longitude": {
+            "type": "number",
+            "description": "Longitude in decimal degrees.",
+            "example": -1.432
+          },
+          "depth": {
+            "type": "number",
+            "description": "Target depth",
+            "default": 0,
+            "example": 50
+          },
+          "altitude": {
+            "type": "number",
+            "description": "Target altitude above bottom",
+            "default": 0,
+            "example": 50
+          },
+          "projection": {
+            "type": "integer",
+            "description": "EPSG Projection Code",
+            "example": 4326,
+            "default": 4326
+          }
         },
-        "coordinates": {
-          "$ref": "#/definitions/Coordinates"
+        "required": ["latitude", "longitude"],
+        "type": "object"
+      },
+      "VehicleStatus": {
+        "properties": {
+          "message_type": {
+            "type": "string",
+            "description": "An identifier for the payload type.",
+            "example": "VehicleStatus",
+            "enum": ["VehicleStatus"]
+          },
+          "operator_id": {
+            "type": "string",
+            "description": "An identifier for the operator.",
+            "example": "noc"
+          },
+          "vehicle_id": {
+            "type": "string",
+            "description": "An identifier for the vehicle.",
+            "example": "noc_ah1"
+          },
+          "coordinates": {
+            "$ref": "#/components/schemas/Coordinates"
+          },
+          "battery_percentage": {
+            "type": "number",
+            "description": "The remaining battery capacity.",
+            "example": 64
+          }
         },
-        "actions": {
-          "type": "array",
-          "items": {
-            "discriminator": {
-              "propertyName": "action_type",
-              "mapping": {
-                "GoToWaypoint": "#/definitions/GoToWaypoint",
-                "DescendToAltitude": "#/definitions/DescendToAltitude",
-                "AscendToSurface": "#/definitions/AscendToSurface"
-              }
-            },
-            "oneOf": [
-              {
-                "$ref": "#/definitions/VehicleStatus"
-              },
-              {
-                "$ref": "#/definitions/VehicleMission"
+        "required": ["message_type", "operator_id", "vehicle_id", "coordinates", "battery_percentage"],
+        "type": "object"
+      },
+      "VehicleMission": {
+        "properties": {
+          "message_type": {
+            "type": "string",
+            "description": "An identifier for the payload type.",
+            "example": "VehicleMission",
+            "enum": ["VehicleMission"]
+          },
+          "operator_id": {
+            "type": "string",
+            "description": "An identifier for the operator.",
+            "example": "noc"
+          },
+          "vehicle_id": {
+            "type": "string",
+            "description": "An identifier for the vehicle.",
+            "example": "noc_ah1"
+          },
+          "coordinates": {
+            "$ref": "#/components/schemas/Coordinates"
+          },
+          "actions": {
+            "type": "array",
+            "items": {
+              "discriminator": {
+                "propertyName": "action_type",
+                "mapping": {
+                  "GoToWaypoint": "#/components/schemas/GoToWaypoint",
+                  "DescendToAltitude": "#/components/schemas/DescendToAltitude",
+                  "AscendToSurface": "#/components/schemas/AscendToSurface"
+                }
               },
-              {
-                "$ref": "#/definitions/AreaOfInterest"
-              }
-            ]
+              "oneOf": [
+                {
+                  "$ref": "#/components/schemas/VehicleStatus"
+                },
+                {
+                  "$ref": "#/components/schemas/VehicleMission"
+                },
+                {
+                  "$ref": "#/components/schemas/AreaOfInterest"
+                }
+              ]
+            }
           }
-        }
-      }
-    },
-    "AreaOfInterest": {
-      "properties": {
-        "message_type": {
-          "type": "string",
-          "description": "An identifier for the payload type.",
-          "example": "AreaOfInterest",
-          "enum": ["AreaOfInterest"]
-        },
-        "metadata": {
-          "$ref": "#/definitions/Metadata"
         },
-        "operator_id": {
-          "type": "string",
-          "description": "An identifier for the operator.",
-          "example": "noc"
-        },
-        "vehicle_id": {
-          "type": "string",
-          "description": "An identifier for the vehicle.",
-          "example": "noc_ah1"
+        "required": ["message_type", "operator_id", "vehicle_id", "coordinates", "actions"],
+        "type": "object"
+      },
+      "AreaOfInterest": {
+        "properties": {
+          "message_type": {
+            "type": "string",
+            "description": "An identifier for the payload type.",
+            "example": "AreaOfInterest",
+            "enum": ["AreaOfInterest"]
+          },
+          "operator_id": {
+            "type": "string",
+            "description": "An identifier for the operator.",
+            "example": "noc"
+          },
+          "vehicle_id": {
+            "type": "string",
+            "description": "An identifier for the vehicle.",
+            "example": "noc_ah1"
+          },
+          "coordinates": {
+            "$ref": "#/components/schemas/Coordinates"
+          }
         },
-        "coordinates": {
-          "$ref": "#/definitions/Coordinates"
-        }
-      }
-    },
-    "GoToWaypoint": {
-      "properties": {
-        "action_type": {
-          "type": "string",
-          "description": "An identifier for the payload type.",
-          "example": "GoToWaypoint",
-          "enum": ["GoToWaypoint"]
+        "required": ["message_type", "operator_id", "vehicle_id", "coordinates"],
+        "type": "object"
+      },
+      "GoToWaypoint": {
+        "properties": {
+          "action_type": {
+            "type": "string",
+            "description": "An identifier for the payload type.",
+            "example": "GoToWaypoint",
+            "enum": ["GoToWaypoint"]
+          },
+          "coordinates": {
+            "$ref": "#/components/schemas/Coordinates"
+          }
         },
-        "coordinates": {
-          "$ref": "#/definitions/Coordinates"
-        }
-      }
-    },
-    "DescendToAltitude": {
-      "properties": {
-        "action_type": {
-          "type": "string",
-          "description": "An identifier for the payload type.",
-          "example": "DescendToAltitude",
-          "enum": ["DescendToAltitude"]
+        "type": "object"
+      },
+      "DescendToAltitude": {
+        "properties": {
+          "action_type": {
+            "type": "string",
+            "description": "An identifier for the payload type.",
+            "example": "DescendToAltitude",
+            "enum": ["DescendToAltitude"]
+          },
+          "coordinates": {
+            "$ref": "#/components/schemas/Coordinates"
+          }
         },
-        "coordinates": {
-          "$ref": "#/definitions/Coordinates"
-        }
-      }
-    },
-    "AscendToSurface": {
-      "properties": {
-        "action_type": {
-          "type": "string",
-          "description": "An identifier for the payload type.",
-          "example": "AscendToSurface",
-          "enum": ["AscendToSurface"]
+        "type": "object"
+      },
+      "AscendToSurface": {
+        "properties": {
+          "action_type": {
+            "type": "string",
+            "description": "An identifier for the payload type.",
+            "example": "AscendToSurface",
+            "enum": ["AscendToSurface"]
+          },
+          "coordinates": {
+            "$ref": "#/components/schemas/Coordinates"
+          }
         },
-        "coordinates": {
-          "$ref": "#/definitions/Coordinates"
-        }
+        "type": "object"
       }
     }
   }
diff --git a/yarn.lock b/yarn.lock
index b9878a6..1d0dd63 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1081,6 +1081,13 @@ aggregate-error@^3.0.0:
     clean-stack "^2.0.0"
     indent-string "^4.0.0"
 
+ajv-formats@^2.0.2:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520"
+  integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==
+  dependencies:
+    ajv "^8.0.0"
+
 ajv@^6.10.0, ajv@^6.12.4:
   version "6.12.6"
   resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
@@ -1091,6 +1098,16 @@ ajv@^6.10.0, ajv@^6.12.4:
     json-schema-traverse "^0.4.1"
     uri-js "^4.2.2"
 
+ajv@^8.0.0, ajv@^8.1.0:
+  version "8.12.0"
+  resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1"
+  integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==
+  dependencies:
+    fast-deep-equal "^3.1.1"
+    json-schema-traverse "^1.0.0"
+    require-from-string "^2.0.2"
+    uri-js "^4.2.2"
+
 ansi-escapes@^4.2.1, ansi-escapes@^4.3.0:
   version "4.3.2"
   resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e"
@@ -2951,6 +2968,11 @@ json-schema-traverse@^0.4.1:
   resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
   integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
 
+json-schema-traverse@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2"
+  integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
+
 json-stable-stringify-without-jsonify@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
@@ -3086,7 +3108,7 @@ lodash.isequal@^4.5.0:
   resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
   integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==
 
-lodash.merge@^4.6.2:
+lodash.merge@^4.6.1, lodash.merge@^4.6.2:
   version "4.6.2"
   resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
   integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
@@ -3337,6 +3359,21 @@ onetime@^5.1.0, onetime@^5.1.2:
   dependencies:
     mimic-fn "^2.1.0"
 
+openapi-schema-validator@^12.1.0:
+  version "12.1.0"
+  resolved "https://registry.yarnpkg.com/openapi-schema-validator/-/openapi-schema-validator-12.1.0.tgz#8ab5c7fd57189551126589426e5d71d8190eb80f"
+  integrity sha512-gr9mZCHu5QmADePYNhizaSAsB0HdY/DespPue10NQID1jB+56Jf+dfnJcnMOVKsG4ZAedVY5oyLFGI1Gk0wm7w==
+  dependencies:
+    ajv "^8.1.0"
+    ajv-formats "^2.0.2"
+    lodash.merge "^4.6.1"
+    openapi-types "^12.1.0"
+
+openapi-types@^12.1.0:
+  version "12.1.0"
+  resolved "https://registry.yarnpkg.com/openapi-types/-/openapi-types-12.1.0.tgz#bd01acc937b73c9f6db2ac2031bf0231e21ebff0"
+  integrity sha512-XpeCy01X6L5EpP+6Hc3jWN7rMZJ+/k1lwki/kTmWzbVhdPie3jd5O2ZtedEx8Yp58icJ0osVldLMrTB/zslQXA==
+
 optionator@^0.8.1:
   version "0.8.3"
   resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495"
@@ -3644,6 +3681,11 @@ require-directory@^2.1.1:
   resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
   integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==
 
+require-from-string@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
+  integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
+
 requires-port@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
-- 
GitLab