Skip to content

Commit 84fc89f

Browse files
authored
Merge pull request #223 from OpenPathfinder/ulises/2019
2 parents 8a2f2d1 + db903b7 commit 84fc89f

File tree

7 files changed

+174
-49
lines changed

7 files changed

+174
-49
lines changed

__tests__/httpServer.test.js

Lines changed: 68 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,81 @@
11
const request = require('supertest')
2+
const { generateStaticReports } = require('../src/reports')
23
const serverModule = require('../src/httpServer')
3-
const server = serverModule()
4-
const app = request(server)
4+
let server
5+
let serverStop
6+
let app
57

6-
// Cleanup after all tests
7-
afterAll(() => {
8-
server?.close()
8+
// Mocks
9+
jest.mock('../src/reports', () => ({
10+
generateStaticReports: jest.fn()
11+
}))
12+
13+
beforeAll(async () => {
14+
// Initialize server asynchronously
15+
const serverInstance = serverModule()
16+
server = await serverInstance.start()
17+
serverStop = serverInstance.stop
18+
app = request(server)
19+
})
20+
21+
afterAll(async () => {
22+
// Cleanup after all tests
23+
await serverStop?.()
24+
})
25+
26+
beforeEach(() => {
27+
jest.clearAllMocks()
928
})
1029

1130
describe('HTTP Server API', () => {
12-
test('health check endpoint should return status ok', async () => {
13-
const response = await app.get('/api/v1/__health')
31+
describe('GET /api/v1/__health', () => {
32+
test('should return status ok', async () => {
33+
const response = await app.get('/api/v1/__health')
1434

15-
expect(response.status).toBe(200)
16-
expect(response.body).toHaveProperty('status', 'ok')
17-
expect(response.body).toHaveProperty('timestamp')
35+
expect(response.status).toBe(200)
36+
expect(response.body).toHaveProperty('status', 'ok')
37+
expect(response.body).toHaveProperty('timestamp')
1838

19-
const timestamp = new Date(response.body.timestamp)
20-
expect(timestamp.toISOString()).toBe(response.body.timestamp)
39+
const timestamp = new Date(response.body.timestamp)
40+
expect(timestamp.toISOString()).toBe(response.body.timestamp)
41+
})
2142
})
2243

23-
test('non-existent API endpoint should return 404', async () => {
24-
const response = await app.get('/api/v1/non-existent-endpoint')
44+
describe('POST /api/v1/generate-reports', () => {
45+
test('should return status completed when report generation succeeds', async () => {
46+
generateStaticReports.mockResolvedValueOnce()
47+
48+
const response = await app.post('/api/v1/generate-reports')
49+
50+
expect(generateStaticReports).toHaveBeenCalledWith(expect.anything(), { clearPreviousReports: true })
51+
expect(response.status).toBe(202)
52+
expect(response.body).toHaveProperty('status', 'completed')
53+
expect(response.body).toHaveProperty('startedAt')
54+
expect(response.body).toHaveProperty('finishedAt')
55+
56+
const startedAt = new Date(response.body.startedAt)
57+
const finishedAt = new Date(response.body.finishedAt)
58+
expect(startedAt.toISOString()).toBe(response.body.startedAt)
59+
expect(finishedAt.toISOString()).toBe(response.body.finishedAt)
60+
expect(finishedAt >= startedAt).toBe(true)
61+
})
62+
63+
test('should return status failed when report generation fails', async () => {
64+
generateStaticReports.mockRejectedValueOnce(new Error('Report generation failed'))
65+
66+
const response = await app.post('/api/v1/generate-reports')
67+
68+
expect(generateStaticReports).toHaveBeenCalledWith(expect.anything(), { clearPreviousReports: true })
69+
expect(response.status).toBe(500)
70+
expect(response.body).toHaveProperty('status', 'failed')
71+
expect(response.body).toHaveProperty('startedAt')
72+
expect(response.body).toHaveProperty('finishedAt')
2573

26-
expect(response.status).toBe(404)
74+
const startedAt = new Date(response.body.startedAt)
75+
const finishedAt = new Date(response.body.finishedAt)
76+
expect(startedAt.toISOString()).toBe(response.body.startedAt)
77+
expect(finishedAt.toISOString()).toBe(response.body.finishedAt)
78+
expect(finishedAt >= startedAt).toBe(true)
79+
})
2780
})
2881
})

server.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1-
const server = require('./src/httpServer')
1+
const server = require('./src/httpServer');
22

3-
server()
3+
(async () => {
4+
const serverInstance = server()
5+
await serverInstance.start()
6+
})()

src/cli/workflows.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const inquirer = require('inquirer').default
22
const debug = require('debug')('cli:workflows')
33
const { updateGithubOrgs, upsertGithubRepositories, runAllTheComplianceChecks, upsertOSSFScorecardAnalysis } = require('../workflows')
4-
const { generateReports } = require('../reports')
4+
const { generateStaticReports } = require('../reports')
55
const { bulkImport } = require('../importers')
66
const { logger } = require('../utils')
77

@@ -28,7 +28,7 @@ const commandList = [{
2828
}, {
2929
name: 'generate-reports',
3030
description: 'Generate the reports for the stored data.',
31-
workflow: generateReports
31+
workflow: generateStaticReports
3232
}, {
3333
name: 'bulk-import',
3434
description: 'Bulk import data from a CSV file.',

src/httpServer/apiV1.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
const { generateStaticReports } = require('../reports')
2+
const { logger } = require('../utils')
3+
4+
function createApiRouter (knex, express) {
5+
const router = express.Router()
6+
7+
router.get('/__health', (req, res) => {
8+
res.json({ status: 'ok', timestamp: new Date().toISOString() })
9+
})
10+
11+
router.post('/generate-reports', async (req, res) => {
12+
const startTs = new Date().toISOString()
13+
try {
14+
await generateStaticReports(knex, { clearPreviousReports: true })
15+
res.status(202).json({
16+
status: 'completed',
17+
startedAt: startTs,
18+
finishedAt: new Date().toISOString()
19+
})
20+
} catch (error) {
21+
logger.error(error)
22+
res.status(500).json({
23+
status: 'failed',
24+
startedAt: startTs,
25+
finishedAt: new Date().toISOString()
26+
})
27+
}
28+
})
29+
return router
30+
}
31+
32+
module.exports = { createApiRouter }

src/httpServer/index.js

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,18 @@ const serveStatic = require('serve-static')
44
const serveIndex = require('serve-index')
55
const { join } = require('path')
66
const { getConfig } = require('../config')
7-
const { logger } = require('../utils')
7+
const { logger, checkDatabaseConnection } = require('../utils')
8+
const { createApiRouter } = require('./apiV1')
89

910
const publicPath = join(process.cwd(), 'output')
10-
const { staticServer } = getConfig()
11+
const { staticServer, dbSettings } = getConfig()
12+
const knex = require('knex')(dbSettings)
1113

1214
// Create Express app
1315
const app = express()
1416

1517
// API Routes
16-
app.use('/api/v1', createApiRouter())
18+
app.use('/api/v1', createApiRouter(knex, express))
1719

1820
// Static file serving
1921
app.use(serveStatic(publicPath, {
@@ -33,21 +35,27 @@ app.use((err, req, res, next) => {
3335
})
3436
})
3537

36-
// Create API router
37-
function createApiRouter () {
38-
const router = express.Router()
39-
40-
// Health check endpoint
41-
router.get('/__health', (req, res) => {
42-
res.json({ status: 'ok', timestamp: new Date().toISOString() })
43-
})
44-
return router
45-
}
46-
4738
// Create HTTP server
4839
const server = http.createServer(app)
4940

50-
module.exports = () => server.listen(staticServer.port, () => {
51-
logger.info(`Server running at http://${staticServer.ip}:${staticServer.port}/`)
52-
logger.info(`API available at http://${staticServer.ip}:${staticServer.port}/api/v1/`)
41+
module.exports = () => ({
42+
start: async () => {
43+
const isDbConnected = await checkDatabaseConnection(knex)
44+
if (!isDbConnected) {
45+
const err = new Error('Failed to connect to database')
46+
logger.error(err)
47+
throw err
48+
}
49+
return server.listen(staticServer.port, () => {
50+
logger.info(`Server running at http://${staticServer.ip}:${staticServer.port}/`)
51+
logger.info(`API available at http://${staticServer.ip}:${staticServer.port}/api/v1/`)
52+
})
53+
},
54+
stop: async () => {
55+
await knex.destroy()
56+
await new Promise((resolve, reject) => {
57+
server.close(err => (err ? reject(err) : resolve()))
58+
})
59+
logger.info('Server stopped')
60+
}
5361
})

src/reports/index.js

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
const { logger } = require('../utils')
22
const ejs = require('ejs')
3-
const { mkdir, readdir, copyFile, readFile, writeFile } = require('node:fs').promises
3+
const { mkdir, readdir, copyFile, readFile, writeFile, rm } = require('node:fs').promises
44
const { join } = require('path')
55
const { initializeStore } = require('../store')
66

@@ -37,18 +37,35 @@ const copyFolder = async (from, to) => {
3737
}
3838
}
3939

40-
const generateReports = async (knex) => {
40+
const generateStaticReports = async (knex, options = { clearPreviousReports: false }) => {
41+
const { clearPreviousReports } = options
42+
if (clearPreviousReports) {
43+
logger.info('Clearing previous reports')
44+
await rm(destinationFolder, { recursive: true, force: true })
45+
}
46+
4147
logger.info('Generating reports')
4248
const { getAllProjects, getAllChecklists, getAllComplianceChecks, getAllAlerts, getAllResults, getAllTasks, getAllGithubOrganizationsByProjectsId, getAllGithubRepositories, getAllOSSFResults } = initializeStore(knex)
43-
// @TODO: Run the queries in parallel
44-
const projects = await getAllProjects()
45-
const checklists = await getAllChecklists()
46-
const checks = await getAllComplianceChecks()
47-
const alerts = await getAllAlerts()
48-
const results = await getAllResults()
49-
const tasks = await getAllTasks()
50-
const ossfScorecardResults = await getAllOSSFResults()
51-
const githubRepos = await getAllGithubRepositories()
49+
// Run the queries in parallel
50+
const [
51+
projects,
52+
checklists,
53+
checks,
54+
alerts,
55+
results,
56+
tasks,
57+
ossfScorecardResults,
58+
githubRepos
59+
] = await Promise.all([
60+
getAllProjects(),
61+
getAllChecklists(),
62+
getAllComplianceChecks(),
63+
getAllAlerts(),
64+
getAllResults(),
65+
getAllTasks(),
66+
getAllOSSFResults(),
67+
getAllGithubRepositories()
68+
])
5269

5370
// @TODO: Read the files in parallel
5471
const indexTemplate = await readFile(indexTemplatePath, 'utf8')
@@ -101,8 +118,9 @@ const generateReports = async (knex) => {
101118

102119
// Save the index HTML file
103120
await writeFile('output/index.html', indexHtml)
121+
logger.info('Reports generated successfully')
104122
}
105123

106124
module.exports = {
107-
generateReports
125+
generateStaticReports
108126
}

src/utils/index.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,16 @@ const isDateWithinPolicy = (targetDate, policy) => {
123123
return isBefore(currentDate, expirationDate) // Check if current date is before expiration
124124
}
125125

126+
const checkDatabaseConnection = async (knex) => {
127+
try {
128+
await knex.raw('SELECT 1')
129+
return true
130+
} catch (error) {
131+
logger.error('Database connection failed', { error })
132+
return false
133+
}
134+
}
135+
126136
module.exports = {
127137
isDateWithinPolicy,
128138
validateGithubUrl,
@@ -131,5 +141,6 @@ module.exports = {
131141
groupArrayItemsByCriteria,
132142
redactSensitiveData,
133143
logger,
134-
generatePercentage
144+
generatePercentage,
145+
checkDatabaseConnection
135146
}

0 commit comments

Comments
 (0)