diff --git a/app/controllers/data-sources-controller.js b/app/controllers/data-sources-controller.js index 39741812..a0ccce2d 100644 --- a/app/controllers/data-sources-controller.js +++ b/app/controllers/data-sources-controller.js @@ -2,8 +2,9 @@ const dataSourcesService = require('../services/data-sources-service'); const logger = require('../lib/logger'); +const { DuplicateIdError, BadlyFormattedParameterError, InvalidQueryStringParameterError } = require('../exceptions'); -exports.retrieveAll = function(req, res) { +exports.retrieveAll = async function(req, res) { const options = { offset: req.query.offset || 0, limit: req.query.limit || 0, @@ -16,65 +17,68 @@ exports.retrieveAll = function(req, res) { lastUpdatedBy: req.query.lastUpdatedBy, includePagination: req.query.includePagination } - - dataSourcesService.retrieveAll(options, function(err, results) { - if (err) { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to get data sources. Server error.'); + try { + const results = await dataSourcesService.retrieveAll(options); + if (options.includePagination) { + logger.debug(`Success: Retrieved ${ results.data.length } of ${ results.pagination.total } total data source(s)`); } else { - if (options.includePagination) { - logger.debug(`Success: Retrieved ${ results.data.length } of ${ results.pagination.total } total data source(s)`); - } - else { - logger.debug(`Success: Retrieved ${ results.length } data source(s)`); - } - return res.status(200).send(results); + logger.debug(`Success: Retrieved ${ results.length } data source(s)`); + } + return res.status(200).send(results); + } catch (err) { + logger.error('Failed with error: ' + err); + return res.status(500).send('Unable to get data sources. Server error.'); } - }); + }; -exports.retrieveById = function(req, res) { +exports.retrieveById = async function(req, res) { const options = { versions: req.query.versions || 'latest', retrieveDataComponents: req.query.retrieveDataComponents } - - dataSourcesService.retrieveById(req.params.stixId, options, function (err, dataSources) { - if (err) { - if (err.message === dataSourcesService.errors.badlyFormattedParameter) { - logger.warn('Badly formatted stix id: ' + req.params.stixId); - return res.status(400).send('Stix id is badly formatted.'); - } - else if (err.message === dataSourcesService.errors.invalidQueryStringParameter) { - logger.warn('Invalid query string: versions=' + req.query.versions); - return res.status(400).send('Query string parameter versions is invalid.'); - } - else { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to get data sources. Server error.'); - } + try { + const dataSources = await dataSourcesService.retrieveById(req.params.stixId, options); + if (dataSources.length === 0) { + return res.status(404).send('Data source not found.'); } else { - if (dataSources.length === 0) { - return res.status(404).send('Data source not found.'); - } - else { - logger.debug(`Success: Retrieved ${ dataSources.length } data source(s) with id ${ req.params.stixId }`); - return res.status(200).send(dataSources); - } + logger.debug(`Success: Retrieved ${ dataSources.length } data source(s) with id ${ req.params.stixId }`); + return res.status(200).send(dataSources); + } + } catch (err) { + if (err instanceof BadlyFormattedParameterError) { + logger.warn('Badly formatted stix id: ' + req.params.stixId); + return res.status(400).send('Stix id is badly formatted.'); } - }); + else if (err instanceof InvalidQueryStringParameterError) { + logger.warn('Invalid query string: versions=' + req.query.versions); + return res.status(400).send('Query string parameter versions is invalid.'); + } + else { + logger.error('Failed with error: ' + err); + return res.status(500).send('Unable to get data sources. Server error.'); + } + } }; -exports.retrieveVersionById = function(req, res) { +exports.retrieveVersionById = async function(req, res) { const options = { retrieveDataComponents: req.query.retrieveDataComponents } - dataSourcesService.retrieveVersionById(req.params.stixId, req.params.modified, options, function (err, dataSource) { - if (err) { - if (err.message === dataSourcesService.errors.badlyFormattedParameter) { + try { + const dataSource = await dataSourcesService.retrieveVersionById(req.params.stixId, req.params.modified, options); + if (!dataSource) { + return res.status(404).send('Data source not found.'); + } + else { + logger.debug(`Success: Retrieved data source with id ${dataSource.id}`); + return res.status(200).send(dataSource); + } + } catch (err) { + if (err instanceof BadlyFormattedParameterError) { logger.warn('Badly formatted stix id: ' + req.params.stixId); return res.status(400).send('Stix id is badly formatted.'); } @@ -82,16 +86,7 @@ exports.retrieveVersionById = function(req, res) { logger.error('Failed with error: ' + err); return res.status(500).send('Unable to get data source. Server error.'); } - } else { - if (!dataSource) { - return res.status(404).send('Data source not found.'); - } - else { - logger.debug(`Success: Retrieved data source with id ${dataSource.id}`); - return res.status(200).send(dataSource); - } - } - }); + } }; exports.create = async function(req, res) { @@ -109,7 +104,7 @@ exports.create = async function(req, res) { return res.status(201).send(dataSource); } catch(err) { - if (err.message === dataSourcesService.errors.duplicateId) { + if (err instanceof DuplicateIdError) { logger.warn("Duplicate stix.id and stix.modified"); return res.status(409).send('Unable to create data source. Duplicate stix.id and stix.modified properties.'); } @@ -120,58 +115,55 @@ exports.create = async function(req, res) { } }; -exports.updateFull = function(req, res) { +exports.updateFull = async function(req, res) { // Get the data from the request const dataSourceData = req.body; // Create the data source - dataSourcesService.updateFull(req.params.stixId, req.params.modified, dataSourceData, function(err, dataSource) { - if (err) { + try { + const dataSource = await dataSourcesService.updateFull(req.params.stixId, req.params.modified, dataSourceData); + if (!dataSource) { + return res.status(404).send('Data source not found.'); + } else { + logger.debug("Success: Updated data source with id " + dataSource.stix.id); + return res.status(200).send(dataSource); + } + } catch (err) { logger.error("Failed with error: " + err); return res.status(500).send("Unable to update data source. Server error."); } - else { - if (!dataSource) { - return res.status(404).send('Data source not found.'); - } else { - logger.debug("Success: Updated data source with id " + dataSource.stix.id); - return res.status(200).send(dataSource); - } - } - }); + }; -exports.deleteVersionById = function(req, res) { - dataSourcesService.deleteVersionById(req.params.stixId, req.params.modified, function (err, dataSource) { - if (err) { +exports.deleteVersionById = async function(req, res) { + + try { + const dataSource = await dataSourcesService.deleteVersionById(req.params.stixId, req.params.modified); + if (!dataSource) { + return res.status(404).send('Data source not found.'); + } else { + logger.debug("Success: Deleted data source with id " + dataSource.stix.id); + return res.status(204).end(); + } + } catch (err) { logger.error('Delete data source failed. ' + err); return res.status(500).send('Unable to delete data source. Server error.'); } - else { - if (!dataSource) { - return res.status(404).send('Data source not found.'); - } else { - logger.debug("Success: Deleted data source with id " + dataSource.stix.id); - return res.status(204).end(); - } - } - }); + }; -exports.deleteById = function(req, res) { - dataSourcesService.deleteById(req.params.stixId, function (err, dataSources) { - if (err) { - logger.error('Delete data source failed. ' + err); - return res.status(500).send('Unable to delete data source. Server error.'); +exports.deleteById = async function(req, res) { + try { + const dataSources = await dataSourcesService.deleteById(req.params.stixId); + if (dataSources.deletedCount === 0) { + return res.status(404).send('Data Sources not found.'); } else { - if (dataSources.deletedCount === 0) { - return res.status(404).send('Data Sources not found.'); - } - else { - logger.debug(`Success: Deleted data source with id ${ req.params.stixId }`); - return res.status(204).end(); - } + logger.debug(`Success: Deleted data source with id ${ req.params.stixId }`); + return res.status(204).end(); + } + } catch (err) { + logger.error('Delete data source failed. ' + err); + return res.status(500).send('Unable to delete data source. Server error.'); } - }); }; diff --git a/app/repository/data-sources-repository.js b/app/repository/data-sources-repository.js new file mode 100644 index 00000000..f44865bf --- /dev/null +++ b/app/repository/data-sources-repository.js @@ -0,0 +1,8 @@ +'use strict'; + +const BaseRepository = require('./_base.repository'); +const DataSource = require('../models/data-source-model'); + +class DataSourcesRepository extends BaseRepository { } + +module.exports = new DataSourcesRepository(DataSource); diff --git a/app/services/data-sources-service.js b/app/services/data-sources-service.js index bfbfbdb9..0373a7d7 100644 --- a/app/services/data-sources-service.js +++ b/app/services/data-sources-service.js @@ -1,410 +1,150 @@ 'use strict'; -const uuid = require('uuid'); -const DataSource = require('../models/data-source-model'); -const systemConfigurationService = require('./system-configuration-service'); +const dataSourcesRepository = require('../repository/data-sources-repository'); const identitiesService = require('./identities-service'); const dataComponentsService = require('./data-components-service'); -const attackObjectsService = require('./attack-objects-service'); -const config = require('../config/config'); -const regexValidator = require('../lib/regex'); -const {lastUpdatedByQueryHelper} = require('../lib/request-parameter-helper'); +const BaseService = require('./_base.service'); +const { MissingParameterError, BadlyFormattedParameterError, InvalidQueryStringParameterError } = require('../exceptions'); -const errors = { - missingParameter: 'Missing required parameter', - badlyFormattedParameter: 'Badly formatted parameter', - duplicateId: 'Duplicate id', - notFound: 'Document not found', - invalidQueryStringParameter: 'Invalid query string parameter' -}; -exports.errors = errors; +class DataSourcesService extends BaseService { -exports.retrieveAll = function(options, callback) { - // Build the query - const query = {}; - if (!options.includeRevoked) { - query['stix.revoked'] = { $in: [null, false] }; + errors = { + missingParameter: 'Missing required parameter', + badlyFormattedParameter: 'Badly formatted parameter', + duplicateId: 'Duplicate id', + notFound: 'Document not found', + invalidQueryStringParameter: 'Invalid query string parameter' } - if (!options.includeDeprecated) { - query['stix.x_mitre_deprecated'] = { $in: [null, false] }; - } - if (typeof options.state !== 'undefined') { - if (Array.isArray(options.state)) { - query['workspace.workflow.state'] = { $in: options.state }; - } - else { - query['workspace.workflow.state'] = options.state; - } - } - if (typeof options.domain !== 'undefined') { - if (Array.isArray(options.domain)) { - query['stix.x_mitre_domains'] = { $in: options.domain }; - } - else { - query['stix.x_mitre_domains'] = options.domain; - } - } - if (typeof options.platform !== 'undefined') { - if (Array.isArray(options.platform)) { - query['stix.x_mitre_platforms'] = { $in: options.platform }; - } - else { - query['stix.x_mitre_platforms'] = options.platform; - } - } - if (typeof options.lastUpdatedBy !== 'undefined') { - query['workspace.workflow.created_by_user_account'] = lastUpdatedByQueryHelper(options.lastUpdatedBy); - } - - // Build the aggregation - // - Group the documents by stix.id, sorted by stix.modified - // - Use the first document in each group (according to the value of stix.modified) - // - Then apply query, skip and limit options - const aggregation = [ - { $sort: { 'stix.id': 1, 'stix.modified': -1 } }, - { $group: { _id: '$stix.id', document: { $first: '$$ROOT' }}}, - { $replaceRoot: { newRoot: '$document' }}, - { $sort: { 'stix.id': 1 }}, - { $match: query } - ]; - if (typeof options.search !== 'undefined') { - options.search = regexValidator.sanitizeRegex(options.search); - const match = { $match: { $or: [ - { 'stix.name': { '$regex': options.search, '$options': 'i' }}, - { 'stix.description': { '$regex': options.search, '$options': 'i' }}, - { 'workspace.attack_id': { '$regex': options.search, '$options': 'i' }} - ]}}; - aggregation.push(match); - } - - const facet = { - $facet: { - totalCount: [ { $count: 'totalCount' }], - documents: [ ] + static async addExtraData(dataSource, retrieveDataComponents) { + await identitiesService.addCreatedByAndModifiedByIdentities(dataSource); + if (retrieveDataComponents) { + await DataSourcesService.addDataComponents(dataSource); } - }; - if (options.offset) { - facet.$facet.documents.push({ $skip: options.offset }); - } - else { - facet.$facet.documents.push({ $skip: 0 }); } - if (options.limit) { - facet.$facet.documents.push({ $limit: options.limit }); - } - aggregation.push(facet); - // Retrieve the documents - DataSource.aggregate(aggregation, function(err, results) { - if (err) { - return callback(err); - } - else { - identitiesService.addCreatedByAndModifiedByIdentitiesToAll(results[0].documents) - .then(function() { - if (options.includePagination) { - let derivedTotalCount = 0; - if (results[0].totalCount.length > 0) { - derivedTotalCount = results[0].totalCount[0].totalCount; - } - const returnValue = { - pagination: { - total: derivedTotalCount, - offset: options.offset, - limit: options.limit - }, - data: results[0].documents - }; - return callback(null, returnValue); - } - else { - return callback(null, results[0].documents); - } - }); + static async addExtraDataToAll(dataSources, retrieveDataComponents) { + for (const dataSource of dataSources) { + // eslint-disable-next-line no-await-in-loop + await DataSourcesService.addExtraData(dataSource, retrieveDataComponents); } - }); -}; - -async function addExtraData(dataSource, retrieveDataComponents) { - await identitiesService.addCreatedByAndModifiedByIdentities(dataSource); - if (retrieveDataComponents) { - await addDataComponents(dataSource); - } -} - -async function addExtraDataToAll(dataSources, retrieveDataComponents) { - for (const dataSource of dataSources) { - // eslint-disable-next-line no-await-in-loop - await addExtraData(dataSource, retrieveDataComponents); } -} -async function addDataComponents(dataSource) { - // We have to work with the latest version of all data components to avoid mishandling a situation - // where an earlier version of a data component may reference a data source, but the latest - // version doesn't. + static async addDataComponents(dataSource) { + // We have to work with the latest version of all data components to avoid mishandling a situation + // where an earlier version of a data component may reference a data source, but the latest + // version doesn't. - // Retrieve the latest version of all data components - const allDataComponents = await dataComponentsService.retrieveAll({ includeDeprecated: true, includeRevoked: true }); + // Retrieve the latest version of all data components + const allDataComponents = await dataComponentsService.retrieveAll({ includeDeprecated: true, includeRevoked: true }); - // Add the data components that reference the data source - dataSource.dataComponents = allDataComponents.filter(dataComponent => dataComponent.stix.x_mitre_data_source_ref === dataSource.stix.id); -} - -exports.retrieveById = function(stixId, options, callback) { - // versions=all Retrieve all versions of the data source with the stixId - // versions=latest Retrieve the data source with the latest modified date for this stixId - - if (!stixId) { - const error = new Error(errors.missingParameter); - error.parameterName = 'stixId'; - return callback(error); + // Add the data components that reference the data source + dataSource.dataComponents = allDataComponents.filter(dataComponent => dataComponent.stix.x_mitre_data_source_ref === dataSource.stix.id); } - if (options.versions === 'all') { - DataSource.find({'stix.id': stixId}) - .lean() - .exec(function (err, dataSources) { - if (err) { - if (err.name === 'CastError') { - const error = new Error(errors.badlyFormattedParameter); - error.parameterName = 'stixId'; - return callback(error); - } else { - return callback(err); - } - } else { - addExtraDataToAll(dataSources, options.retrieveDataComponents) - .then(() => callback(null, dataSources)); + async retrieveById(stixId, options, callback) { + try { + // versions=all Retrieve all versions of the data source with the stixId + // versions=latest Retrieve the data source with the latest modified date for this stixId + + if (!stixId) { + if (callback) { + return callback(new Error(this.errors.missingParameter)); } - }); - } - else if (options.versions === 'latest') { - DataSource.findOne({ 'stix.id': stixId }) - .sort('-stix.modified') - .lean() - .exec(function(err, dataSource) { - if (err) { - if (err.name === 'CastError') { - const error = new Error(errors.badlyFormattedParameter); - error.parameterName = 'stixId'; - return callback(error); - } - else { - return callback(err); - } + throw new MissingParameterError; + } + + if (options.versions === 'all') { + const dataSources = await this.repository.model.find({ 'stix.id': stixId }).lean().exec(); + await DataSourcesService.addExtraDataToAll(dataSources, options.retrieveDataComponents); + if (callback) { + return callback(null, dataSources); } - else { - // Note: document is null if not found - if (dataSource) { - addExtraData(dataSource, options.retrieveDataComponents) - .then(() => callback(null, [ dataSource ])); + return dataSources; + } else if (options.versions === 'latest') { + const dataSource = await this.repository.model.findOne({ 'stix.id': stixId }).sort('-stix.modified').lean().exec(); + + // Note: document is null if not found + if (dataSource) { + await DataSourcesService.addExtraData(dataSource, options.retrieveDataComponents); + if (callback) { + return callback(null, [dataSource]); } - else { + return [dataSource]; + } else { + if (callback) { return callback(null, []); } + return []; } - }); - } - else { - const error = new Error(errors.invalidQueryStringParameter); - error.parameterName = 'versions'; - return callback(error); - } -}; - -exports.retrieveVersionById = function(stixId, modified, options, callback) { - // Retrieve the version of the data source with the matching stixId and modified date - - if (!stixId) { - const error = new Error(errors.missingParameter); - error.parameterName = 'stixId'; - return callback(error); - } - - if (!modified) { - const error = new Error(errors.missingParameter); - error.parameterName = 'modified'; - return callback(error); + } else { + if (callback) { + return callback(new Error(this.errors.invalidQueryStringParameter)); + } + throw new InvalidQueryStringParameterError; + } + } catch (err) { + if (err.name === 'CastError') { + if (callback) { + return callback(new Error(this.errors.badlyFormattedParameter)); + } + throw new BadlyFormattedParameterError; + } else { + if (callback) { + return callback(err); + } + throw err; + } + } } - DataSource.findOne({ 'stix.id': stixId, 'stix.modified': modified }, function(err, dataSource) { - if (err) { - if (err.name === 'CastError') { - const error = new Error(errors.badlyFormattedParameter); - error.parameterName = 'stixId'; - return callback(error); + async retrieveVersionById(stixId, modified, options, callback) { + try { + // Retrieve the version of the data source with the matching stixId and modified date + + if (!stixId) { + if (callback) { + return callback(new Error(this.errors.missingParameter)); + } + throw new MissingParameterError; } - else { - return callback(err); + + if (!modified) { + if (callback) { + return callback(new Error(this.errors.missingParameter)); + } + throw new MissingParameterError; } - } - else { + + const dataSource = await this.repository.model.findOne({ 'stix.id': stixId, 'stix.modified': modified }).exec(); + // Note: document is null if not found if (dataSource) { - addExtraData(dataSource, options.retrieveDataComponents) - .then(() => callback(null, dataSource)); - } - else { - return callback(); + await DataSourcesService.addExtraData(dataSource, options.retrieveDataComponents); + if (callback) { + return callback(null, dataSource); + } + return dataSource; + } else { + if (callback) { + return callback(null, null); + } + return null; } - } - }); -}; - -exports.createIsAsync = true; -exports.create = async function(data, options) { - // This function handles two use cases: - // 1. This is a completely new object. Create a new object and generate the stix.id if not already - // provided. Set both stix.created_by_ref and stix.x_mitre_modified_by_ref to the organization identity. - // 2. This is a new version of an existing object. Create a new object with the specified id. - // Set stix.x_mitre_modified_by_ref to the organization identity. - - // Create the document - const dataSource = new DataSource(data); - - options = options || {}; - if (!options.import) { - // Set the ATT&CK Spec Version - dataSource.stix.x_mitre_attack_spec_version = dataSource.stix.x_mitre_attack_spec_version ?? config.app.attackSpecVersion; - - // Record the user account that created the object - if (options.userAccountId) { - dataSource.workspace.workflow.created_by_user_account = options.userAccountId; - } - - // Set the default marking definitions - await attackObjectsService.setDefaultMarkingDefinitions(dataSource); - - // Get the organization identity - const organizationIdentityRef = await systemConfigurationService.retrieveOrganizationIdentityRef(); - - // Check for an existing object - let existingObject; - if (dataSource.stix.id) { - existingObject = await DataSource.findOne({ 'stix.id': dataSource.stix.id }); - } - - if (existingObject) { - // New version of an existing object - // Only set the x_mitre_modified_by_ref property - dataSource.stix.x_mitre_modified_by_ref = organizationIdentityRef; - } - else { - // New object - // Assign a new STIX id if not already provided - dataSource.stix.id = dataSource.stix.id || `x-mitre-data-source--${uuid.v4()}`; - - // Set the created_by_ref and x_mitre_modified_by_ref properties - dataSource.stix.created_by_ref = organizationIdentityRef; - dataSource.stix.x_mitre_modified_by_ref = organizationIdentityRef; - } - } - - // Save the document in the database - try { - const savedDataSource = await dataSource.save(); - return savedDataSource; - } - catch (err) { - if (err.name === 'MongoServerError' && err.code === 11000) { - // 11000 = Duplicate index - const error = new Error(errors.duplicateId); - throw error; - } - else { - throw err; - } - } -}; - -exports.updateFull = function(stixId, stixModified, data, callback) { - if (!stixId) { - const error = new Error(errors.missingParameter); - error.parameterName = 'stixId'; - return callback(error); - } - - if (!stixModified) { - const error = new Error(errors.missingParameter); - error.parameterName = 'modified'; - return callback(error); - } - - DataSource.findOne({ 'stix.id': stixId, 'stix.modified': stixModified }, function(err, document) { - if (err) { + } catch (err) { if (err.name === 'CastError') { - var error = new Error(errors.badlyFormattedParameter); - error.parameterName = 'stixId'; - return callback(error); - } - else { - return callback(err); - } - } - else if (!document) { - // document not found - return callback(null); - } - else { - // Copy data to found document and save - Object.assign(document, data); - document.save(function(err, savedDocument) { - if (err) { - if (err.name === 'MongoServerError' && err.code === 11000) { - // 11000 = Duplicate index - var error = new Error(errors.duplicateId); - return callback(error); - } - else { - return callback(err); - } + if (callback) { + return callback(new Error(this.errors.badlyFormattedParameter)); } - else { - return callback(null, savedDocument); + throw new BadlyFormattedParameterError; + } else { + if (callback) { + return callback(err); } - }); - } - }); -}; - -exports.deleteVersionById = function (stixId, stixModified, callback) { - if (!stixId) { - const error = new Error(errors.missingParameter); - error.parameterName = 'stixId'; - return callback(error); - } - - if (!stixModified) { - const error = new Error(errors.missingParameter); - error.parameterName = 'modified'; - return callback(error); - } - - DataSource.findOneAndRemove({ 'stix.id': stixId, 'stix.modified': stixModified }, function (err, dataSource) { - if (err) { - return callback(err); - } else { - // Note: data source is null if not found - return callback(null, dataSource); + throw err; + } } - }); -}; - -exports.deleteById = function (stixId, callback) { - if (!stixId) { - const error = new Error(errors.missingParameter); - error.parameterName = 'stixId'; - return callback(error); } +} - DataSource.deleteMany({ 'stix.id': stixId }, function (err, dataSource) { - if (err) { - return callback(err); - } else { - //Note: dataSource is null if not found - return callback(null, dataSource); - } - }); -}; +module.exports = new DataSourcesService('x-mitre-data-source', dataSourcesRepository); \ No newline at end of file diff --git a/app/tests/api/data-sources/data-sources.spec.js b/app/tests/api/data-sources/data-sources.spec.js index d6f9022f..c7d72033 100644 --- a/app/tests/api/data-sources/data-sources.spec.js +++ b/app/tests/api/data-sources/data-sources.spec.js @@ -129,149 +129,110 @@ describe('Data Sources API', function () { passportCookie = await login.loginAnonymous(app); }); - it('GET /api/data-sources returns an empty array of data sources', function (done) { - request(app) + it('GET /api/data-sources returns an empty array of data sources', async function () { + const res = await request(app) .get('/api/data-sources') .set('Accept', 'application/json') .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) .expect(200) - .expect('Content-Type', /json/) - .end(function(err, res) { - if (err) { - done(err); - } - else { - // We expect to get an empty array - const dataSources = res.body; - expect(dataSources).toBeDefined(); - expect(Array.isArray(dataSources)).toBe(true); - expect(dataSources.length).toBe(0); - done(); - } - }); + .expect('Content-Type', /json/); + // We expect to get an empty array + const dataSources = res.body; + expect(dataSources).toBeDefined(); + expect(Array.isArray(dataSources)).toBe(true); + expect(dataSources.length).toBe(0); }); - it('POST /api/data-sources does not create an empty data source', function (done) { + it('POST /api/data-sources does not create an empty data source', async function () { const body = { }; - request(app) + await request(app) .post('/api/data-sources') .send(body) .set('Accept', 'application/json') .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) - .expect(400) - .end(function(err, res) { - if (err) { - done(err); - } - else { - done(); - } - }); + .expect(400); + }); let dataSource1; - it('POST /api/data-sources creates a data source', function (done) { + it('POST /api/data-sources creates a data source', async function () { const timestamp = new Date().toISOString(); initialDataSourceData.stix.created = timestamp; initialDataSourceData.stix.modified = timestamp; const body = initialDataSourceData; - request(app) + const res = await request(app) .post('/api/data-sources') .send(body) .set('Accept', 'application/json') .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) .expect(201) - .expect('Content-Type', /json/) - .end(function(err, res) { - if (err) { - done(err); - } - else { - // We expect to get the created data source - dataSource1 = res.body; - expect(dataSource1).toBeDefined(); - expect(dataSource1.stix).toBeDefined(); - expect(dataSource1.stix.id).toBeDefined(); - expect(dataSource1.stix.created).toBeDefined(); - expect(dataSource1.stix.modified).toBeDefined(); - expect(dataSource1.stix.x_mitre_attack_spec_version).toBe(config.app.attackSpecVersion); - - done(); - } - }); + .expect('Content-Type', /json/); + + // We expect to get the created data source + dataSource1 = res.body; + expect(dataSource1).toBeDefined(); + expect(dataSource1.stix).toBeDefined(); + expect(dataSource1.stix.id).toBeDefined(); + expect(dataSource1.stix.created).toBeDefined(); + expect(dataSource1.stix.modified).toBeDefined(); + expect(dataSource1.stix.x_mitre_attack_spec_version).toBe(config.app.attackSpecVersion); + + }); - it('GET /api/data-sources returns the added data source', function (done) { - request(app) + it('GET /api/data-sources returns the added data source', async function () { + const res = await request(app) .get('/api/data-sources') .set('Accept', 'application/json') .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) .expect(200) - .expect('Content-Type', /json/) - .end(function(err, res) { - if (err) { - done(err); - } - else { - // We expect to get one data source in an array - const dataSource = res.body; - expect(dataSource).toBeDefined(); - expect(Array.isArray(dataSource)).toBe(true); - expect(dataSource.length).toBe(1); - done(); - } - }); + .expect('Content-Type', /json/); + + // We expect to get one data source in an array + const dataSource = res.body; + expect(dataSource).toBeDefined(); + expect(Array.isArray(dataSource)).toBe(true); + expect(dataSource.length).toBe(1); + }); - it('GET /api/data-sources/:id should not return a data source when the id cannot be found', function (done) { - request(app) + it('GET /api/data-sources/:id should not return a data source when the id cannot be found', async function () { + await request(app) .get('/api/data-sources/not-an-id') .set('Accept', 'application/json') .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) - .expect(404) - .end(function (err, res) { - if (err) { - done(err); - } else { - done(); - } - }); + .expect(404); + }); - it('GET /api/data-sources/:id returns the added data source', function (done) { - request(app) + it('GET /api/data-sources/:id returns the added data source', async function () { + const res = await request(app) .get('/api/data-sources/' + dataSource1.stix.id) .set('Accept', 'application/json') .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) .expect(200) - .expect('Content-Type', /json/) - .end(function(err, res) { - if (err) { - done(err); - } - else { - // We expect to get one data source in an array - const dataSources = res.body; - expect(dataSources).toBeDefined(); - expect(Array.isArray(dataSources)).toBe(true); - expect(dataSources.length).toBe(1); - - const dataSource = dataSources[0]; - expect(dataSource).toBeDefined(); - expect(dataSource.stix).toBeDefined(); - expect(dataSource.stix.id).toBe(dataSource1.stix.id); - expect(dataSource.stix.type).toBe(dataSource1.stix.type); - expect(dataSource.stix.name).toBe(dataSource1.stix.name); - expect(dataSource.stix.description).toBe(dataSource1.stix.description); - expect(dataSource.stix.spec_version).toBe(dataSource1.stix.spec_version); - expect(dataSource.stix.object_marking_refs).toEqual(expect.arrayContaining(dataSource1.stix.object_marking_refs)); - expect(dataSource.stix.created_by_ref).toBe(dataSource1.stix.created_by_ref); - expect(dataSource.stix.x_mitre_version).toBe(dataSource1.stix.x_mitre_version); - expect(dataSource.stix.x_mitre_attack_spec_version).toBe(dataSource1.stix.x_mitre_attack_spec_version); - - done(); - } - }); + .expect('Content-Type', /json/); + + // We expect to get one data source in an array + const dataSources = res.body; + expect(dataSources).toBeDefined(); + expect(Array.isArray(dataSources)).toBe(true); + expect(dataSources.length).toBe(1); + + const dataSource = dataSources[0]; + expect(dataSource).toBeDefined(); + expect(dataSource.stix).toBeDefined(); + expect(dataSource.stix.id).toBe(dataSource1.stix.id); + expect(dataSource.stix.type).toBe(dataSource1.stix.type); + expect(dataSource.stix.name).toBe(dataSource1.stix.name); + expect(dataSource.stix.description).toBe(dataSource1.stix.description); + expect(dataSource.stix.spec_version).toBe(dataSource1.stix.spec_version); + expect(dataSource.stix.object_marking_refs).toEqual(expect.arrayContaining(dataSource1.stix.object_marking_refs)); + expect(dataSource.stix.created_by_ref).toBe(dataSource1.stix.created_by_ref); + expect(dataSource.stix.x_mitre_version).toBe(dataSource1.stix.x_mitre_version); + expect(dataSource.stix.x_mitre_attack_spec_version).toBe(dataSource1.stix.x_mitre_attack_spec_version); + + }); it('GET /api/data-sources/:id returns the added data source with data components', async function () { @@ -298,54 +259,41 @@ describe('Data Sources API', function () { expect(dataSource.dataComponents.length).toBe(5); }); - it('PUT /api/data-sources updates a data source', function (done) { + it('PUT /api/data-sources updates a data source', async function () { const originalModified = dataSource1.stix.modified; const timestamp = new Date().toISOString(); dataSource1.stix.modified = timestamp; dataSource1.stix.description = 'This is an updated data source.' const body = dataSource1; - request(app) + const res = await request(app) .put('/api/data-sources/' + dataSource1.stix.id + '/modified/' + originalModified) .send(body) .set('Accept', 'application/json') .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) .expect(200) - .expect('Content-Type', /json/) - .end(function(err, res) { - if (err) { - done(err); - } - else { - // We expect to get the updated data source - const dataSource = res.body; - expect(dataSource).toBeDefined(); - expect(dataSource.stix.id).toBe(dataSource1.stix.id); - expect(dataSource.stix.modified).toBe(dataSource1.stix.modified); - done(); - } - }); + .expect('Content-Type', /json/); + + // We expect to get the updated data source + const dataSource = res.body; + expect(dataSource).toBeDefined(); + expect(dataSource.stix.id).toBe(dataSource1.stix.id); + expect(dataSource.stix.modified).toBe(dataSource1.stix.modified); + }); - it('POST /api/data-sources does not create a data source with the same id and modified date', function (done) { + it('POST /api/data-sources does not create a data source with the same id and modified date', async function () { const body = dataSource1; - request(app) + await request(app) .post('/api/data-sources') .send(body) .set('Accept', 'application/json') .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) - .expect(409) - .end(function(err, res) { - if (err) { - done(err); - } - else { - done(); - } - }); + .expect(409); + }); let dataSource2; - it('POST /api/data-sources should create a new version of a data source with a duplicate stix.id but different stix.modified date', function (done) { + it('POST /api/data-sources should create a new version of a data source with a duplicate stix.id but different stix.modified date', async function () { dataSource2 = _.cloneDeep(dataSource1); dataSource2._id = undefined; dataSource2.__t = undefined; @@ -353,121 +301,91 @@ describe('Data Sources API', function () { const timestamp = new Date().toISOString(); dataSource2.stix.modified = timestamp; const body = dataSource2; - request(app) + const res = await request(app) .post('/api/data-sources') .send(body) .set('Accept', 'application/json') .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) .expect(201) - .expect('Content-Type', /json/) - .end(function(err, res) { - if (err) { - done(err); - } - else { - // We expect to get the created data source - const dataSource = res.body; - expect(dataSource).toBeDefined(); - done(); - } - }); + .expect('Content-Type', /json/); + + // We expect to get the created data source + const dataSource = res.body; + expect(dataSource).toBeDefined(); + }); - it('GET /api/data-sources returns the latest added data source', function (done) { - request(app) + it('GET /api/data-sources returns the latest added data source', async function () { + const res = await request(app) .get('/api/data-sources/' + dataSource2.stix.id) .set('Accept', 'application/json') .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) .expect(200) - .expect('Content-Type', /json/) - .end(function(err, res) { - if (err) { - done(err); - } - else { - // We expect to get one data source in an array - const dataSources = res.body; - expect(dataSources).toBeDefined(); - expect(Array.isArray(dataSources)).toBe(true); - expect(dataSources.length).toBe(1); - const dataSource = dataSources[0]; - expect(dataSource.stix.id).toBe(dataSource2.stix.id); - expect(dataSource.stix.modified).toBe(dataSource2.stix.modified); - done(); - } - }); + .expect('Content-Type', /json/); + + // We expect to get one data source in an array + const dataSources = res.body; + expect(dataSources).toBeDefined(); + expect(Array.isArray(dataSources)).toBe(true); + expect(dataSources.length).toBe(1); + const dataSource = dataSources[0]; + expect(dataSource.stix.id).toBe(dataSource2.stix.id); + expect(dataSource.stix.modified).toBe(dataSource2.stix.modified); + }); - it('GET /api/data-sources returns all added data source', function (done) { - request(app) + it('GET /api/data-sources returns all added data source', async function () { + const res = await request(app) .get('/api/data-sources/' + dataSource1.stix.id + '?versions=all') .set('Accept', 'application/json') .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) .expect(200) - .expect('Content-Type', /json/) - .end(function(err, res) { - if (err) { - done(err); - } - else { - // We expect to get two data sources in an array - const dataSources = res.body; - expect(dataSources).toBeDefined(); - expect(Array.isArray(dataSources)).toBe(true); - expect(dataSources.length).toBe(2); - done(); - } - }); + .expect('Content-Type', /json/); + + // We expect to get two data sources in an array + const dataSources = res.body; + expect(dataSources).toBeDefined(); + expect(Array.isArray(dataSources)).toBe(true); + expect(dataSources.length).toBe(2); + }); - it('GET /api/data-sources/:id/modified/:modified returns the first added data source', function (done) { - request(app) + it('GET /api/data-sources/:id/modified/:modified returns the first added data source', async function () { + const res = await request(app) .get('/api/data-sources/' + dataSource1.stix.id + '/modified/' + dataSource1.stix.modified) .set('Accept', 'application/json') .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) .expect(200) - .expect('Content-Type', /json/) - .end(function(err, res) { - if (err) { - done(err); - } - else { - // We expect to get one data source in an array - const dataSource = res.body; - expect(dataSource).toBeDefined(); - expect(dataSource.stix).toBeDefined(); - expect(dataSource.stix.id).toBe(dataSource1.stix.id); - expect(dataSource.stix.modified).toBe(dataSource1.stix.modified); - done(); - } - }); + .expect('Content-Type', /json/); + + // We expect to get one data source in an array + const dataSource = res.body; + expect(dataSource).toBeDefined(); + expect(dataSource.stix).toBeDefined(); + expect(dataSource.stix.id).toBe(dataSource1.stix.id); + expect(dataSource.stix.modified).toBe(dataSource1.stix.modified); + }); - it('GET /api/data-sources/:id/modified/:modified returns the second added data source', function (done) { - request(app) + it('GET /api/data-sources/:id/modified/:modified returns the second added data source', async function () { + const res = await request(app) .get('/api/data-sources/' + dataSource2.stix.id + '/modified/' + dataSource2.stix.modified) .set('Accept', 'application/json') .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) .expect(200) - .expect('Content-Type', /json/) - .end(function(err, res) { - if (err) { - done(err); - } - else { - // We expect to get one data source in an array - const dataSource = res.body; - expect(dataSource).toBeDefined(); - expect(dataSource.stix).toBeDefined(); - expect(dataSource.stix.id).toBe(dataSource2.stix.id); - expect(dataSource.stix.modified).toBe(dataSource2.stix.modified); - done(); - } - }); + .expect('Content-Type', /json/); + + // We expect to get one data source in an array + const dataSource = res.body; + expect(dataSource).toBeDefined(); + expect(dataSource.stix).toBeDefined(); + expect(dataSource.stix.id).toBe(dataSource2.stix.id); + expect(dataSource.stix.modified).toBe(dataSource2.stix.modified); + }); let dataSource3; - it('POST /api/data-sources should create a new version of a data source with a duplicate stix.id but different stix.modified date', function (done) { + it('POST /api/data-sources should create a new version of a data source with a duplicate stix.id but different stix.modified date', async function () { dataSource3 = _.cloneDeep(dataSource1); dataSource3._id = undefined; dataSource3.__t = undefined; @@ -475,91 +393,58 @@ describe('Data Sources API', function () { const timestamp = new Date().toISOString(); dataSource3.stix.modified = timestamp; const body = dataSource3; - request(app) + const res = await request(app) .post('/api/data-sources') .send(body) .set('Accept', 'application/json') .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) .expect(201) - .expect('Content-Type', /json/) - .end(function(err, res) { - if (err) { - done(err); - } - else { - // We expect to get the created data source - const dataSource = res.body; - expect(dataSource).toBeDefined(); - done(); - } - }); + .expect('Content-Type', /json/); + + // We expect to get the created data source + const dataSource = res.body; + expect(dataSource).toBeDefined(); + }); - it('DELETE /api/data-sources/:id should not delete a data source when the id cannot be found', function (done) { - request(app) + it('DELETE /api/data-sources/:id should not delete a data source when the id cannot be found', async function () { + await request(app) .delete('/api/data-sources/not-an-id') .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) - .expect(404) - .end(function(err, res) { - if (err) { - done(err); - } - else { - done(); - } - }); + .expect(404); + }); - it('DELETE /api/data-sources/:id/modified/:modified deletes a data source', function (done) { - request(app) + it('DELETE /api/data-sources/:id/modified/:modified deletes a data source', async function () { + await request(app) .delete('/api/data-sources/' + dataSource1.stix.id + '/modified/' + dataSource1.stix.modified) .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) - .expect(204) - .end(function(err, res) { - if (err) { - done(err); - } - else { - done(); - } - }); + .expect(204); + }); - it('DELETE /api/data-sources/:id should delete all the data sources with the same stix id', function (done) { - request(app) + it('DELETE /api/data-sources/:id should delete all the data sources with the same stix id', async function () { + await request(app) .delete('/api/data-sources/' + dataSource2.stix.id) .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) - .expect(204) - .end(function(err, res) { - if (err) { - done(err); - } - else { - done(); - } - }); + .expect(204); + }); - it('GET /api/data-sources returns an empty array of data sources', function (done) { - request(app) + it('GET /api/data-sources returns an empty array of data sources', async function () { + const res = await request(app) .get('/api/data-sources') .set('Accept', 'application/json') .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) .expect(200) - .expect('Content-Type', /json/) - .end(function(err, res) { - if (err) { - done(err); - } - else { - // We expect to get an empty array - const dataSources = res.body; - expect(dataSources).toBeDefined(); - expect(Array.isArray(dataSources)).toBe(true); - expect(dataSources.length).toBe(0); - done(); - } - }); + .expect('Content-Type', /json/); + + // We expect to get an empty array + const dataSources = res.body; + expect(dataSources).toBeDefined(); + expect(Array.isArray(dataSources)).toBe(true); + expect(dataSources.length).toBe(0); + }); after(async function() {