Skip to content

Commit 6fdf982

Browse files
authored
Merge pull request #253 from OpenPathfinder/ulises/execute-import
2 parents a1755c5 + a5dc116 commit 6fdf982

File tree

5 files changed

+241
-72
lines changed

5 files changed

+241
-72
lines changed

__tests__/httpServer/apiV1.test.js

Lines changed: 136 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ let getAllChecks
4343
let getCheckById
4444
let getAllChecklists
4545
let getChecklistById
46+
let getProjectById
4647

4748
beforeAll(async () => {
4849
// Initialize server asynchronously
@@ -58,7 +59,8 @@ beforeAll(async () => {
5859
getAllChecks,
5960
getCheckById,
6061
getAllChecklists,
61-
getChecklistById
62+
getChecklistById,
63+
getProjectById
6264
} = initializeStore(knex))
6365
})
6466

@@ -557,4 +559,137 @@ describe('HTTP Server API V1', () => {
557559

558560
test.todo('should return 500 for internal server error')
559561
})
562+
563+
describe('POST /api/v1/bulk-import', () => {
564+
const operationId = getAllBulkImportOperations()[0].id
565+
let largePayload
566+
let validPayload
567+
let projectId
568+
569+
beforeEach(async () => {
570+
// Create a test project for each test
571+
const project = await addProject({ name: 'test-project' })
572+
projectId = project.id
573+
largePayload = [{
574+
type: 'annualDependencyRefresh',
575+
project_id: projectId,
576+
is_subscribed: true
577+
}, {
578+
type: 'vulnResponse14Days',
579+
project_id: projectId,
580+
is_subscribed: true
581+
}, {
582+
type: 'incidentResponsePlan',
583+
project_id: projectId,
584+
is_subscribed: true
585+
}, {
586+
type: 'assignCVEForKnownVulns',
587+
project_id: projectId,
588+
is_subscribed: true
589+
}, {
590+
type: 'includeCVEInReleaseNotes',
591+
project_id: projectId,
592+
is_subscribed: true
593+
}]
594+
validPayload = [largePayload[0]]
595+
})
596+
597+
test('should return 202 and a success message', async () => {
598+
// Check initial state
599+
let storedProject = await getProjectById(projectId)
600+
expect(storedProject.has_annualDependencyRefresh_policy).toBe(null)
601+
602+
// Perform bulk import
603+
const response = await app
604+
.post('/api/v1/bulk-import')
605+
.send({ id: operationId, payload: validPayload })
606+
607+
expect(response.status).toBe(202)
608+
expect(response.body).toHaveProperty('status', 'completed')
609+
expect(response.body).toHaveProperty('started')
610+
expect(response.body).toHaveProperty('finished')
611+
expect(response.body).toHaveProperty('result')
612+
expect(response.body.result).toHaveProperty('message', 'Bulk import completed successfully')
613+
expect(response.body.result).toHaveProperty('success', true)
614+
615+
// Check final state
616+
storedProject = await getProjectById(projectId)
617+
expect(storedProject.has_annualDependencyRefresh_policy).toBe(true)
618+
})
619+
620+
test('should return 202 and a success message (case: multiple updates)', async () => {
621+
// Check initial state
622+
let storedProject = await getProjectById(projectId)
623+
expect(storedProject.has_annualDependencyRefresh_policy).toBe(null)
624+
expect(storedProject.has_vulnResponse14Days_policy).toBe(null)
625+
expect(storedProject.has_incidentResponsePlan_policy).toBe(null)
626+
expect(storedProject.has_assignCVEForKnownVulns_policy).toBe(null)
627+
expect(storedProject.has_includeCVEInReleaseNotes_policy).toBe(null)
628+
629+
// Perform bulk import
630+
const response = await app
631+
.post('/api/v1/bulk-import')
632+
.send({ id: operationId, payload: largePayload })
633+
634+
expect(response.status).toBe(202)
635+
expect(response.body).toHaveProperty('status', 'completed')
636+
expect(response.body).toHaveProperty('started')
637+
expect(response.body).toHaveProperty('finished')
638+
expect(response.body).toHaveProperty('result')
639+
expect(response.body.result).toHaveProperty('message', 'Bulk import completed successfully')
640+
expect(response.body.result).toHaveProperty('success', true)
641+
642+
// Check final state
643+
storedProject = await getProjectById(projectId)
644+
expect(storedProject.has_annualDependencyRefresh_policy).toBe(true)
645+
expect(storedProject.has_vulnResponse14Days_policy).toBe(true)
646+
expect(storedProject.has_incidentResponsePlan_policy).toBe(true)
647+
expect(storedProject.has_assignCVEForKnownVulns_policy).toBe(true)
648+
expect(storedProject.has_includeCVEInReleaseNotes_policy).toBe(true)
649+
})
650+
651+
test('should return 400 for invalid payload (case: Swagger rejection)', async () => {
652+
const response = await app
653+
.post('/api/v1/bulk-import')
654+
.send({ id: operationId, payload: 'invalid' })
655+
656+
expect(response.status).toBe(400)
657+
expect(response.body).toHaveProperty('errors')
658+
expect(response.body.errors[0]).toHaveProperty('message', 'must be object')
659+
})
660+
661+
test('should return 400 for invalid payload (case: invalid payload against JSON Schema)', async () => {
662+
const response = await app
663+
.post('/api/v1/bulk-import')
664+
.send({ id: operationId, payload: [{ invalid: 'payload' }] })
665+
666+
expect(response.status).toBe(400)
667+
expect(response.body).toHaveProperty('errors')
668+
expect(response.body.errors[0]).toHaveProperty('message', 'The data does not match the schema')
669+
})
670+
671+
test('should return 404 for invalid operation ID', async () => {
672+
const response = await app
673+
.post('/api/v1/bulk-import')
674+
.send({ id: 'invalid', payload: validPayload })
675+
676+
expect(response.status).toBe(404)
677+
expect(response.body).toHaveProperty('errors')
678+
expect(response.body.errors[0]).toHaveProperty('message', 'Bulk import operation not found')
679+
})
680+
681+
test('should return 500 if the request is not completed (due project not found)', async () => {
682+
const invalidPayload = validPayload.map(p => ({ ...p, project_id: 9999999 }))
683+
684+
const response = await app
685+
.post('/api/v1/bulk-import')
686+
.send({ id: operationId, payload: invalidPayload })
687+
688+
expect(response.status).toBe(500)
689+
expect(response.body).toHaveProperty('errors')
690+
expect(response.body.errors[0]).toHaveProperty('message', 'Failed to run bulk import: Operation failed for item type: annualDependencyRefresh, project_id: 9999999')
691+
})
692+
693+
test.skip('should return 500 for internal server error', async () => {})
694+
})
560695
})

__tests__/importers.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ afterAll(async () => {
3333
await knex.destroy()
3434
})
3535

36-
describe('Integration: bulkImport', () => {
36+
describe.skip('Integration: bulkImport', () => {
3737
test('Should import software design training data', async () => {
3838
// Check the environment
3939
const project = await addProject({ name: 'project1' })

src/httpServer/routers/apiV1.js

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ const { initializeStore } = require('../../store')
44
const _ = require('lodash')
55
const { isSlug } = require('validator')
66
const { getWorkflowsDetails } = require('../../cli/workflows')
7-
const { getAllBulkImportOperations } = require('../../importers')
7+
const { getAllBulkImportOperations, bulkImport } = require('../../importers')
8+
const { validateBulkImport } = require('../../schemas')
89

910
const HTTP_DEFAULT_TIMEOUT = 30 * 1000 // 30 seconds
1011

@@ -235,6 +236,35 @@ function createApiRouter (knex, express) {
235236
}
236237
})
237238

239+
router.post('/bulk-import', async (req, res) => {
240+
try {
241+
const { id, payload } = req.body
242+
const operation = getAllBulkImportOperations().find((op) => op.id === id)
243+
244+
if (!operation) {
245+
return res.status(404).json({ errors: [{ message: 'Bulk import operation not found' }] })
246+
}
247+
const started = new Date().toISOString()
248+
249+
try {
250+
validateBulkImport(payload)
251+
} catch (error) {
252+
logger.error('Error validating the user data against the schema')
253+
return res.status(400).json({ errors: [{ message: 'The data does not match the schema' }] })
254+
}
255+
256+
await bulkImport({ operationId: id, knex, data: payload })
257+
const finished = new Date().toISOString()
258+
res.status(202).json({ status: 'completed', started, finished, result: { message: 'Bulk import completed successfully', success: true } })
259+
} catch (error) {
260+
logger.error(error)
261+
res.status(500).json({
262+
status: 'failed',
263+
errors: [{ message: `Failed to run bulk import: ${error.message}` }]
264+
})
265+
}
266+
})
267+
238268
return router
239269
}
240270

src/httpServer/swagger/api-v1.yml

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ paths:
9898
content:
9999
application/json:
100100
schema:
101-
$ref: '#/components/schemas/WorkflowCompleted'
101+
$ref: '#/components/schemas/OperationCompleted'
102102
'400':
103103
description: Bad request, invalid input data
104104
content:
@@ -366,6 +366,57 @@ paths:
366366
schema:
367367
$ref: '#/components/schemas/ErrorResponse'
368368
/api/v1/bulk-import:
369+
post:
370+
summary: Perform bulk import
371+
description: Performs a bulk import of data
372+
operationId: performBulkImport
373+
tags:
374+
- Bulk Import
375+
requestBody:
376+
required: true
377+
content:
378+
application/json:
379+
schema:
380+
type: object
381+
additionalProperties: false
382+
properties:
383+
id:
384+
type: string
385+
example: load-manual-checks
386+
payload:
387+
# @TODO: Improve this schema to be more specific about the expected input
388+
oneOf:
389+
- type: object
390+
- type: array
391+
items:
392+
oneOf:
393+
- type: object
394+
- type: string
395+
responses:
396+
'202':
397+
description: Bulk import completed
398+
content:
399+
application/json:
400+
schema:
401+
$ref: '#/components/schemas/OperationCompleted'
402+
'400':
403+
description: Bad request, invalid input data
404+
content:
405+
application/json:
406+
schema:
407+
$ref: '#/components/schemas/ErrorResponse'
408+
'404':
409+
description: Operation not found
410+
content:
411+
application/json:
412+
schema:
413+
$ref: '#/components/schemas/ErrorResponse'
414+
'500':
415+
description: Internal server error
416+
content:
417+
application/json:
418+
schema:
419+
$ref: '#/components/schemas/ErrorResponse'
369420
get:
370421
summary: Get operations for bulk import
371422
description: Returns a list of operations for bulk import
@@ -406,7 +457,7 @@ components:
406457
- id
407458
- description
408459
- schema
409-
WorkflowCompleted:
460+
OperationCompleted:
410461
type: object
411462
additionalProperties: false
412463
properties:

0 commit comments

Comments
 (0)