Repository: CVEProject/cve-services
Pull request #624 (2022-04-08 17:04:35)

#573,  #576, #605, #607 Update Staging
Updating Staging with the latest INT branch, which includes fixes for issues #573,  #576, #605, and #607.


Patch:
diff --git a/src/controller/cve-id.controller/cve-id.middleware.js b/src/controller/cve-id.controller/cve-id.middleware.js
index 7c157f41..dead9984 100644
--- a/src/controller/cve-id.controller/cve-id.middleware.js
+++ b/src/controller/cve-id.controller/cve-id.middleware.js
@@ -17,10 +17,11 @@ function parsePostParams (req, res, next) {
 
 // Sanitizer for dates
 function toDate (val) {
-  let value = val.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/)
+  let value = val.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(|Z|((-|\+|\s)\d{2}:\d{2}))$/)
   let result
   if (value) {
-    result = new Date(`${value[0]}.000+00:00`)
+    value[0] = value[0].replace(' ', '+') // Re-add literal '+' which was stripped
+    result = new Date(value[0])
   } else {
     value = val.match(/^\d{4}-\d{2}-\d{2}$/)
     if (value) {
diff --git a/src/controller/cve-id.controller/index.js b/src/controller/cve-id.controller/index.js
index 1cd9fc4f..ff7cc5ed 100644
--- a/src/controller/cve-id.controller/index.js
+++ b/src/controller/cve-id.controller/index.js
@@ -37,8 +37,8 @@ router.get('/cve-id/:id',
   parseGetParams,
   controller.CVEID_GET_SINGLE)
 router.put('/cve-id/:id',
-  mw.onlyCnas,
   mw.validateUser,
+  mw.onlyCnas,
   param(['id']).isString().matches(/^CVE-[0-9]{4}-[0-9]{4,}$/, 'i'),
   query(['state']).optional().isString().trim().escape().customSanitizer(val => { return val.toUpperCase() }).isIn(CHOICES),
   query(['org']).optional().isString().trim().escape(),
diff --git a/src/controller/cve.controller/cve.middleware.js b/src/controller/cve.controller/cve.middleware.js
index dacacde5..03ead98a 100644
--- a/src/controller/cve.controller/cve.middleware.js
+++ b/src/controller/cve.controller/cve.middleware.js
@@ -27,10 +27,11 @@ function parseGetParams (req, res, next) {
 
 // Sanitizer for dates
 function toDate (val) {
-  let value = val.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/)
+  let value = val.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(|Z|((-|\+|\s)\d{2}:\d{2}))$/)
   let result
   if (value) {
-    result = new Date(`${value[0]}.000+00:00`)
+    value[0] = value[0].replace(' ', '+') // Re-add literal '+' which was stripped
+    result = new Date(value[0])
   } else {
     value = val.match(/^\d{4}-\d{2}-\d{2}$/)
     if (value) {
@@ -52,19 +53,19 @@ function parseError (req, res, next) {
   next()
 }
 
-function onlyOneEnglishDescription (arr) {
-  const arrayLength = arr.length
-  let numEnglishFound = 0 // for checking how many times an english field shows up
-  for (var i = 0; i < arrayLength; i++) {
-    if (arr[i].lang === 'en') {
-      numEnglishFound += 1
-    }
-
-    if (numEnglishFound > 1) { // return error if more than 1 english description is found
-      return null
+function uniqueEnglishDescription (rejectedReasonsArr) {
+  const langArray = rejectedReasonsArr.map(function (reason) { return reason.lang.toLowerCase() })// create arr of lowercase lang values
+  const foundValues = new Set() // set to hold languages found
+  // loop through the lang array and find duplicates
+  for (var i = 0; i < langArray.length; i++) {
+    if (langArray[i].startsWith('en')) { // check case only if lang starts with "en"
+      if (foundValues.has(langArray[i])) {
+        return false // duplicate found so return false
+      }
+      foundValues.add(langArray[i]) // add each unique value to set
     }
   }
-  return numEnglishFound
+  return true // if no duplicate found, then all lang values were unique
 }
 
 function validateRejectBody (req, res, next) {
@@ -108,6 +109,6 @@ module.exports = {
   parseError,
   toDate,
   validateCveCnaContainerJsonSchema,
-  onlyOneEnglishDescription,
+  uniqueEnglishDescription,
   validateRejectBody
 }
diff --git a/src/controller/cve.controller/index.js b/src/controller/cve.controller/index.js
index cc077966..15267d04 100644
--- a/src/controller/cve.controller/index.js
+++ b/src/controller/cve.controller/index.js
@@ -3,7 +3,7 @@ const router = express.Router()
 const mw = require('../../middleware/middleware')
 const controller = require('./cve.controller')
 const { body, param, query } = require('express-validator')
-const { parseGetParams, parsePostParams, parseError, toDate, validateCveCnaContainerJsonSchema, validateRejectBody, onlyOneEnglishDescription } = require('./cve.middleware')
+const { parseGetParams, parsePostParams, parseError, toDate, validateCveCnaContainerJsonSchema, validateRejectBody, uniqueEnglishDescription } = require('./cve.middleware')
 const CONSTANTS = require('../../constants')
 const CHOICES = [CONSTANTS.CVE_STATES.REJECTED, CONSTANTS.CVE_STATES.PUBLISHED]
 
@@ -56,8 +56,8 @@ router.put('/cve/:id',
   controller.CVE_UPDATE_SINGLE)
 
 router.post('/cve/:id/cna',
-  mw.onlyCnas,
   mw.validateUser,
+  mw.onlyCnas,
   validateCveCnaContainerJsonSchema,
   param(['id']).isString().matches(/^CVE-[0-9]{4}-[0-9]{4,}$/i),
   parseError,
@@ -66,8 +66,8 @@ router.post('/cve/:id/cna',
   controller.CVE_SUBMIT_CNA)
 
 router.put('/cve/:id/cna',
-  mw.onlyCnas,
   mw.validateUser,
+  mw.onlyCnas,
   validateCveCnaContainerJsonSchema,
   param(['id']).isString().matches(/^CVE-[0-9]{4}-[0-9]{4,}$/i),
   parseError,
@@ -76,12 +76,12 @@ router.put('/cve/:id/cna',
   controller.CVE_UPDATE_CNA)
 
 router.post('/cve/:id/reject',
-  mw.onlyCnas,
   mw.validateUser,
+  mw.onlyCnas,
   validateRejectBody,
   param(['id']).isString().matches(/^CVE-[0-9]{4}-[0-9]{4,}$/i),
   body(['cnaContainer.rejectedReasons']).isArray().custom((arr) => {
-    if (onlyOneEnglishDescription(arr) !== 1) {
+    if (!uniqueEnglishDescription(arr)) {
       throw new Error(400, 'Bad request, more than one English description found')
     }
     return true
@@ -93,12 +93,12 @@ router.post('/cve/:id/reject',
   controller.CVE_REJECT_RECORD)
 
 router.put('/cve/:id/reject',
-  mw.onlyCnas,
   mw.validateUser,
+  mw.onlyCnas,
   validateRejectBody,
   param(['id']).isString().matches(/^CVE-[0-9]{4}-[0-9]{4,}$/i),
   body(['cnaContainer.rejectedReasons']).isArray().custom((arr) => {
-    if (onlyOneEnglishDescription(arr) !== 1) {
+    if (!uniqueEnglishDescription(arr)) {
       throw new Error(400, 'Bad request, more than one English description found')
     }
     return true
diff --git a/src/middleware/error.js b/src/middleware/error.js
index 4e7ea779..6648e11b 100644
--- a/src/middleware/error.js
+++ b/src/middleware/error.js
@@ -28,6 +28,13 @@ class MiddlewareError extends idrErr.IDRError {
     return err
   }
 
+  cnaDoesNotExist (shortname) { // mw
+    const err = {}
+    err.error = 'CNA_DOES_NOT_EXIST'
+    err.message = `The '${shortname}' organization designated by the shortname parameter does not exist.`
+    return err
+  }
+
   orgDoesNotOwnId (org, id) {
     const err = {
       error: 'ORG_DOES_NOT_OWN_ID',
@@ -54,7 +61,14 @@ class MiddlewareError extends idrErr.IDRError {
     return err
   }
 
-  recordTooLarge () {
+  genericBadRequest (errors) { // mw
+    const err = {}
+    err.error = 'BAD_REQUEST'
+    err.message = errors
+    return err
+  }
+
+  recordTooLarge () { // mw
     const err = {}
     err.error = 'RECORD_TOO_LARGE'
     err.message = 'Records must be less than 16MB.'
diff --git a/src/middleware/middleware.js b/src/middleware/middleware.js
index 5070a981..82646c7d 100644
--- a/src/middleware/middleware.js
+++ b/src/middleware/middleware.js
@@ -129,7 +129,10 @@ async function onlyCnas (req, res, next) {
 
   try {
     const org = await orgRepo.findOneByShortName(shortName) // org exists
-    if (org.authority.active_roles.includes(CONSTANTS.AUTH_ROLE_ENUM.SECRETARIAT)) {
+    if (org === null) {
+      logger.info({ uuid: req.ctx.uuid, message: shortName + ' is NOT a ' + CONSTANTS.AUTH_ROLE_ENUM.CNA })
+      return res.status(404).json(error.cnaDoesNotExist(shortName))
+    } else if (org.authority.active_roles.includes(CONSTANTS.AUTH_ROLE_ENUM.SECRETARIAT)) {
       logger.info({ uuid: req.ctx.uuid, message: org.short_name + ' is a ' + CONSTANTS.AUTH_ROLE_ENUM.SECRETARIAT + ' so until Root organizations are implemented this role is allowed.' })
       next()
     } else if (org.authority.active_roles.includes(CONSTANTS.AUTH_ROLE_ENUM.CNA)) { // the org is a CNA
@@ -207,14 +210,18 @@ function validateCveJsonSchema (req, res, next) {
 
 function validateJsonSyntax (err, req, res, next) {
   if (err.status && err.message) {
-    if (err.message.includes('Unexpected token')) {
+    if (err.message.includes('request entity too large')) {
+      console.warn('Request failed validation because entity too large')
+      console.info((JSON.stringify(err)))
+      return res.status(413).json(error.recordTooLarge(errors))
+    } else if (err.status === 400) {
       console.warn('Request failed validation because JSON syntax is incorrect')
       console.info((JSON.stringify(err)))
       return res.status(400).json(error.invalidJsonSyntax(err.message))
-    } else if (err.message.includes('request entity too large')) {
-      console.warn('Request failed validation because entity too large')
+    } else {
+      console.warn('Request failed')
       console.info((JSON.stringify(err)))
-      return res.status(413).json(error.recordTooLarge(errors))
+      return res.status(400).json(error.genericBadRequest(err.message))
     }
   } else {
     next(err)


Test patch:
diff --git a/test-http/src/test/cve_id_tests/cve_id_as_org_admin.py b/test-http/src/test/cve_id_tests/cve_id_as_org_admin.py
index dc5a359e..06d091b6 100644
--- a/test-http/src/test/cve_id_tests/cve_id_as_org_admin.py
+++ b/test-http/src/test/cve_id_tests/cve_id_as_org_admin.py
@@ -317,10 +317,12 @@ def test_get_cve_id_by_time_modified(org_admin_headers):
     n_ids = 10
     time.sleep(1)
     t_before = dt.datetime.now().strftime('%Y-%m-%dT%H:%M:%S')
+    t_before_alt = dt.datetime.now().strftime('%Y-%m-%dT%H:%M:%S+00:00')
     time.sleep(1)
     res_ids = get_reserve_cve_ids(n_ids, utils.CURRENT_YEAR, org_admin_headers['CVE-API-ORG'])
     time.sleep(1)
     t_after = dt.datetime.now().strftime('%Y-%m-%dT%H:%M:%S')
+    t_after_alt = dt.datetime.now().strftime('%Y-%m-%dT%H:%M:%S+00:00')
 
     res_get_ids = requests.get(
         f'{env.AWG_BASE_URL}{CVE_ID_URL}',
@@ -330,8 +332,19 @@ def test_get_cve_id_by_time_modified(org_admin_headers):
             'time_modified.gt': t_before
         }
     )
+    # Test alternate timezone format
+    res_get_ids_alt = requests.get(
+        f'{env.AWG_BASE_URL}{CVE_ID_URL}',
+        headers=utils.BASE_HEADERS,
+        params={
+            'time_modified.lt': t_after_alt,
+            'time_modified.gt': t_before_alt
+        }
+    )
     ok_response_contains(res_get_ids, f'CVE-{utils.CURRENT_YEAR}-')
     assert len(json.loads(res_get_ids.content.decode())['cve_ids']) == n_ids
+    ok_response_contains(res_get_ids_alt, f'CVE-{utils.CURRENT_YEAR}-')
+    assert len(json.loads(res_get_ids_alt.content.decode())['cve_ids']) == n_ids
 
 
 def test_get_cve_id_with_params(org_admin_headers):
@@ -470,4 +483,4 @@ def get_reserve_cve_ids(
             'cve_year': f'{year}',
             'short_name': cna_short_name
         }
-    )
\ No newline at end of file
+    )
diff --git a/test-http/src/test/cve_tests/cve.py b/test-http/src/test/cve_tests/cve.py
index 766e65b5..c0f02d5b 100644
--- a/test-http/src/test/cve_tests/cve.py
+++ b/test-http/src/test/cve_tests/cve.py
@@ -36,12 +36,14 @@ def test_get_cve_by_time_modified():
     time.sleep(1)
     post_cve('CVE-2021-0005_published', 'CVE-2021-0005')
     t_before = dt.datetime.now().strftime('%Y-%m-%dT%H:%M:%S')
+    t_before_alt = dt.datetime.now().strftime('%Y-%m-%dT%H:%M:%S+00:00')
     time.sleep(1)
     update_cve('CVE-2021-0004_published', 'CVE-2021-0004')
     time.sleep(1)
     update_cve('CVE-2021-0005_published', 'CVE-2021-0005')
     time.sleep(1)
     t_after = dt.datetime.now().strftime('%Y-%m-%dT%H:%M:%S')
+    t_after_alt = dt.datetime.now().strftime('%Y-%m-%dT%H:%M:%S+00:00')
 
     res = requests.get(
         f'{env.AWG_BASE_URL}{CVE_URL}/',
@@ -51,8 +53,19 @@ def test_get_cve_by_time_modified():
             'time_modified.gt': t_before
         }
     )
+    # Alternat timezone format
+    res_alt = requests.get(
+        f'{env.AWG_BASE_URL}{CVE_URL}/',
+        headers=utils.BASE_HEADERS,
+        params={
+            'time_modified.lt': t_after_alt,
+            'time_modified.gt': t_before_alt
+        }
+    )
     assert res.status_code == utils.HTTP_OK
     assert len(json.loads(res.content.decode())['cveRecords']) >= 2
+    assert res_alt.status_code == utils.HTTP_OK
+    assert len(json.loads(res_alt.content.decode())['cveRecords']) >= 2
 
 
 def test_get_cve_by_count_only_true():
@@ -350,8 +363,8 @@ def test_submit_record_rejection_id_dne():
         assert res.status_code == 403
 
 
-def test_submit_record_rejection_multiple_english_descriptions():
-    """ submit a reject request with descriptions array that has more than one english description """
+def test_submit_record_rejection_multiple_different_english_values():
+    """ submit a reject request with descriptions array that has multiple different English values (ex: "en" and "en-Ca") """
     res = requests.post(
         f'{env.AWG_BASE_URL}/api/cve-id/',
         headers=utils.BASE_HEADERS,
@@ -362,14 +375,58 @@ def test_submit_record_rejection_multiple_english_descriptions():
         }
     )
     id_num = json.loads(res.content.decode())['cve_ids'][0]['cve_id'] # obtain id number
-    with open('./src/test/cve_tests/cve_record_fixtures/rejectBodyMultipleEngDescriptions.json') as json_file:
+    with open('./src/test/cve_tests/cve_record_fixtures/rejectBodyMultipleDiffEngValues.json') as json_file:
         data = json.load(json_file)
         res = requests.post(
             f'{env.AWG_BASE_URL}{CVE_URL}/{id_num}/reject',
             headers=utils.BASE_HEADERS,
             json=data
         )
-        assert res.status_code == 400
+        assert res.status_code == 200 # lang values are unique
+
+
+def test_submit_record_rejection_multiple_non_English_values():
+    """ submit a reject request with descriptions array that has multiple non English values (ex: "fr" and "fr") """
+    res = requests.post(
+        f'{env.AWG_BASE_URL}/api/cve-id/',
+        headers=utils.BASE_HEADERS,
+        params={
+            'amount': 1,
+            'cve_year': 2000,
+            'short_name': 'mitre'
+        }
+    )
+    id_num = json.loads(res.content.decode())['cve_ids'][0]['cve_id'] # obtain id number
+    with open('./src/test/cve_tests/cve_record_fixtures/rejectBodyMultipleNonEngValues.json') as json_file:
+        data = json.load(json_file)
+        res = requests.post(
+            f'{env.AWG_BASE_URL}{CVE_URL}/{id_num}/reject',
+            headers=utils.BASE_HEADERS,
+            json=data
+        )
+        assert res.status_code == 200 # lang values are unique
+
+
+def test_submit_record_rejection_multiple_same_English_values():
+    """ submit a reject request with descriptions array that has multiple same English values (ex: "en-Gb" and "en-Gb") """
+    res = requests.post(
+        f'{env.AWG_BASE_URL}/api/cve-id/',
+        headers=utils.BASE_HEADERS,
+        params={
+            'amount': 1,
+            'cve_year': 2000,
+            'short_name': 'mitre'
+        }
+    )
+    id_num = json.loads(res.content.decode())['cve_ids'][0]['cve_id'] # obtain id number
+    with open('./src/test/cve_tests/cve_record_fixtures/rejectBodyMultipleSameEngValues.json') as json_file:
+        data = json.load(json_file)
+        res = requests.post(
+            f'{env.AWG_BASE_URL}{CVE_URL}/{id_num}/reject',
+            headers=utils.BASE_HEADERS,
+            json=data
+        )
+        assert res.status_code == 400 # lang values are not unique
 
 
 #### PUT /cve/:id ####
diff --git a/test-http/src/test/cve_tests/cve_record_fixtures/rejectBodyMultipleEngDescriptions.json b/test-http/src/test/cve_tests/cve_record_fixtures/rejectBodyMultipleDiffEngValues.json
similarity index 69%
rename from test-http/src/test/cve_tests/cve_record_fixtures/rejectBodyMultipleEngDescriptions.json
rename to test-http/src/test/cve_tests/cve_record_fixtures/rejectBodyMultipleDiffEngValues.json
index 73e7bdb1..1700eff8 100644
--- a/test-http/src/test/cve_tests/cve_record_fixtures/rejectBodyMultipleEngDescriptions.json
+++ b/test-http/src/test/cve_tests/cve_record_fixtures/rejectBodyMultipleDiffEngValues.json
@@ -16,18 +16,7 @@
                 ]
             },
             {
-                "lang": "en",
-                "value": "I professional site herself recently behavior. Situation institution meeting recognize successful.",
-                "supportingMedia": [
-                    {
-                        "type": "test/markdown",
-                        "base64": false,
-                        "value": "*this* _is_ supporting media in ~markdown~"
-                    }
-                ]
-            },
-            {
-                "lang": "en",
+                "lang": "en-Ca",
                 "value": "I professional site herself recently behavior. Situation institution meeting recognize successful.",
                 "supportingMedia": [
                     {
diff --git a/test-http/src/test/cve_tests/cve_record_fixtures/rejectBodyMultipleNonEngValues.json b/test-http/src/test/cve_tests/cve_record_fixtures/rejectBodyMultipleNonEngValues.json
new file mode 100644
index 00000000..10a90e1f
--- /dev/null
+++ b/test-http/src/test/cve_tests/cve_record_fixtures/rejectBodyMultipleNonEngValues.json
@@ -0,0 +1,32 @@
+{
+    "cnaContainer": {
+        "providerMetadata": {
+            "orgId" : "f972b356-145d-4b2e-9a5c-b114d0982a3b"
+        },
+        "rejectedReasons": [
+            {
+                "lang": "fr",
+                "value": "I professional site herself recently behavior. Situation institution meeting recognize successful.",
+                "supportingMedia": [
+                    {
+                        "type": "test/markdown",
+                        "base64": false,
+                        "value": "*this* _is_ supporting media in ~markdown~"
+                    }
+                ]
+            },
+            {
+                "lang": "fr",
+                "value": "I professional site herself recently behavior. Situation institution meeting recognize successful.",
+                "supportingMedia": [
+                    {
+                        "type": "test/markdown",
+                        "base64": false,
+                        "value": "*this* _is_ supporting media in ~markdown~"
+                    }
+                ]
+            }
+        ],
+        "replacedBy": ["CVE-1999-0006"]
+    }
+}
\ No newline at end of file
diff --git a/test-http/src/test/cve_tests/cve_record_fixtures/rejectBodyMultipleSameEngValues.json b/test-http/src/test/cve_tests/cve_record_fixtures/rejectBodyMultipleSameEngValues.json
new file mode 100644
index 00000000..e1c1974a
--- /dev/null
+++ b/test-http/src/test/cve_tests/cve_record_fixtures/rejectBodyMultipleSameEngValues.json
@@ -0,0 +1,32 @@
+{
+    "cnaContainer": {
+        "providerMetadata": {
+            "orgId" : "f972b356-145d-4b2e-9a5c-b114d0982a3b"
+        },
+        "rejectedReasons": [
+            {
+                "lang": "en-Gb",
+                "value": "I professional site herself recently behavior. Situation institution meeting recognize successful.",
+                "supportingMedia": [
+                    {
+                        "type": "test/markdown",
+                        "base64": false,
+                        "value": "*this* _is_ supporting media in ~markdown~"
+                    }
+                ]
+            },
+            {
+                "lang": "en-Gb",
+                "value": "I professional site herself recently behavior. Situation institution meeting recognize successful.",
+                "supportingMedia": [
+                    {
+                        "type": "test/markdown",
+                        "base64": false,
+                        "value": "*this* _is_ supporting media in ~markdown~"
+                    }
+                ]
+            }
+        ],
+        "replacedBy": ["CVE-1999-0006"]
+    }
+}
\ No newline at end of file
diff --git a/test/unit-tests/cve/cveRecordRejectionTest.js b/test/unit-tests/cve/cveRecordRejectionTest.js
index 07c5ee1a..29068c77 100644
--- a/test/unit-tests/cve/cveRecordRejectionTest.js
+++ b/test/unit-tests/cve/cveRecordRejectionTest.js
@@ -17,7 +17,7 @@ const cveController = require('../../../src/controller/cve.controller/cve.contro
 const cveParams = require('../../../src/controller/cve.controller/cve.middleware')
 
 const rejectedBody = require('../../../test-http/src/test/cve_tests/cve_record_fixtures/rejectBody.json')
-const multipleEngDescriptions = require('../../../test-http/src/test/cve_tests/cve_record_fixtures/rejectBodyMultipleEngDescriptions.json')
+const multipleEngDescriptions = require('../../../test-http/src/test/cve_tests/cve_record_fixtures/rejectBodyMultipleSameEngValues.json')
 const nonExistentId = 'CVE-1800-0001'
 const cveIdReserved = 'CVE-2019-1421'
 
diff --git a/test/unit-tests/middleware/onlyCnasMiddlewareTest.js b/test/unit-tests/middleware/onlyCnasMiddlewareTest.js
index 1a15273c..8a4542f0 100644
--- a/test/unit-tests/middleware/onlyCnasMiddlewareTest.js
+++ b/test/unit-tests/middleware/onlyCnasMiddlewareTest.js
@@ -160,5 +160,41 @@ describe('Test only CNA middleware', () => {
           done()
         })
     })
+
+    it('Requester organization shortname is not valid', function (done) {
+      class OrgOnlyCnasOrgNull {
+        async findOneByShortName () {
+          return null
+        }
+      }
+
+      app.route('/only-cnas-org-equals-null')
+        .post((req, res, next) => {
+          const factory = {
+            getOrgRepository: () => { return new OrgOnlyCnasOrgNull() }
+          }
+          req.ctx.repositories = factory
+          next()
+        }, middleware.onlyCnas, (req, res) => {
+          return res.status(200).json({ message: 'Success! You have reached the target endpoint.' })
+        })
+
+      chai.request(app)
+        .post('/only-cnas-org-equals-null')
+        .set(mwCnaFixtures.notCnaHeaders)
+        .send()
+        .end((err, res) => {
+          if (err) {
+            done(err)
+          }
+
+          expect(res).to.have.status(404)
+          expect(res).to.have.property('body').and.to.be.a('object')
+          const errObj = error.cnaDoesNotExist(mwCnaFixtures.notCnaHeaders['CVE-API-ORG'])
+          expect(res.body.error).to.equal(errObj.error)
+          expect(res.body.message).to.equal(errObj.message)
+          done()
+        })
+    })
   })
 })