Skip to content
Merged
75 changes: 60 additions & 15 deletions __tests__/httpServer.test.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,73 @@
const request = require('supertest')
const { generateStaticReports } = require('../src/reports')
const serverModule = require('../src/httpServer')
const server = serverModule()
const app = request(server)
let server
let serverStop
let app

// Cleanup after all tests
afterAll(() => {
server?.close()
// Mocks
jest.mock('../src/reports', () => ({
generateStaticReports: jest.fn()
}))

beforeAll(async () => {
// Initialize server asynchronously
const serverInstance = serverModule()
server = await serverInstance.start()
serverStop = serverInstance.stop
app = request(server)
})

afterAll(async () => {
// Cleanup after all tests
await serverStop?.()
})

beforeEach(() => {
jest.clearAllMocks()
})

describe('HTTP Server API', () => {
test('health check endpoint should return status ok', async () => {
const response = await app.get('/api/v1/__health')
describe('GET /api/v1/__health', () => {
test('should return status ok', async () => {
const response = await app.get('/api/v1/__health')

expect(response.status).toBe(200)
expect(response.body).toHaveProperty('status', 'ok')
expect(response.body).toHaveProperty('timestamp')
expect(response.status).toBe(200)
expect(response.body).toHaveProperty('status', 'ok')
expect(response.body).toHaveProperty('timestamp')

const timestamp = new Date(response.body.timestamp)
expect(timestamp.toISOString()).toBe(response.body.timestamp)
const timestamp = new Date(response.body.timestamp)
expect(timestamp.toISOString()).toBe(response.body.timestamp)
})
})

test('non-existent API endpoint should return 404', async () => {
const response = await app.get('/api/v1/non-existent-endpoint')
describe('GET /api/v1/generate-reports', () => {
test('should return status completed when report generation succeeds', async () => {
generateStaticReports.mockResolvedValueOnce()

const response = await app.get('/api/v1/generate-reports')

expect(generateStaticReports).toHaveBeenCalledWith(expect.anything(), { clearPreviousReports: true })
expect(response.status).toBe(200)
expect(response.body).toHaveProperty('status', 'completed')
expect(response.body).toHaveProperty('timestamp')

const timestamp = new Date(response.body.timestamp)
expect(timestamp.toISOString()).toBe(response.body.timestamp)
})

test('should return status failed when report generation fails', async () => {
generateStaticReports.mockRejectedValueOnce(new Error('Report generation failed'))

const response = await app.get('/api/v1/generate-reports')

expect(generateStaticReports).toHaveBeenCalledWith(expect.anything(), { clearPreviousReports: true })
expect(response.status).toBe(500)
expect(response.body).toHaveProperty('status', 'failed')
expect(response.body).toHaveProperty('timestamp')

expect(response.status).toBe(404)
const timestamp = new Date(response.body.timestamp)
expect(timestamp.toISOString()).toBe(response.body.timestamp)
})
})
})
7 changes: 5 additions & 2 deletions server.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
const server = require('./src/httpServer')
const server = require('./src/httpServer');

server()
(async () => {
const serverInstance = server()
await serverInstance.start()
})()
4 changes: 2 additions & 2 deletions src/cli/workflows.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const inquirer = require('inquirer').default
const debug = require('debug')('cli:workflows')
const { updateGithubOrgs, upsertGithubRepositories, runAllTheComplianceChecks, upsertOSSFScorecardAnalysis } = require('../workflows')
const { generateReports } = require('../reports')
const { generateStaticReports } = require('../reports')
const { bulkImport } = require('../importers')
const { logger } = require('../utils')

Expand All @@ -28,7 +28,7 @@ const commandList = [{
}, {
name: 'generate-reports',
description: 'Generate the reports for the stored data.',
workflow: generateReports
workflow: generateStaticReports
}, {
name: 'bulk-import',
description: 'Bulk import data from a CSV file.',
Expand Down
24 changes: 24 additions & 0 deletions src/httpServer/apiV1.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const { generateStaticReports } = require('../reports')
const { logger } = require('../utils')

function createApiRouter (knex, express) {
const router = express.Router()

router.get('/__health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() })
})

router.get('/generate-reports', async (req, res) => {
const timestamp = new Date().toISOString()
try {
await generateStaticReports(knex, { clearPreviousReports: true })
res.json({ status: 'completed', timestamp })
} catch (error) {
logger.error(error)
res.status(500).json({ status: 'failed', timestamp })
}
})
return router
}

module.exports = { createApiRouter }
39 changes: 22 additions & 17 deletions src/httpServer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@ const serveStatic = require('serve-static')
const serveIndex = require('serve-index')
const { join } = require('path')
const { getConfig } = require('../config')
const { logger } = require('../utils')
const { logger, checkDatabaseConnection } = require('../utils')
const { createApiRouter } = require('./apiV1')

const publicPath = join(process.cwd(), 'output')
const { staticServer } = getConfig()
const { staticServer, dbSettings } = getConfig()
const knex = require('knex')(dbSettings)

// Create Express app
const app = express()

// API Routes
app.use('/api/v1', createApiRouter())
app.use('/api/v1', createApiRouter(knex, express))

// Static file serving
app.use(serveStatic(publicPath, {
Expand All @@ -33,21 +35,24 @@ app.use((err, req, res, next) => {
})
})

// Create API router
function createApiRouter () {
const router = express.Router()

// Health check endpoint
router.get('/__health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() })
})
return router
}

// Create HTTP server
const server = http.createServer(app)

module.exports = () => server.listen(staticServer.port, () => {
logger.info(`Server running at http://${staticServer.ip}:${staticServer.port}/`)
logger.info(`API available at http://${staticServer.ip}:${staticServer.port}/api/v1/`)
module.exports = () => ({
start: async () => {
const isDbConnected = await checkDatabaseConnection(knex)
if (!isDbConnected) {
logger.error('Failed to connect to database')
process.exit(1)
}
return server.listen(staticServer.port, () => {
logger.info(`Server running at http://${staticServer.ip}:${staticServer.port}/`)
logger.info(`API available at http://${staticServer.ip}:${staticServer.port}/api/v1/`)
})
},
stop: async () => {
await knex.destroy()
server.close()
logger.info('Server stopped')
}
})
13 changes: 10 additions & 3 deletions src/reports/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const { logger } = require('../utils')
const ejs = require('ejs')
const { mkdir, readdir, copyFile, readFile, writeFile } = require('node:fs').promises
const { mkdir, readdir, copyFile, readFile, writeFile, rm } = require('node:fs').promises
const { join } = require('path')
const { initializeStore } = require('../store')

Expand Down Expand Up @@ -37,7 +37,13 @@ const copyFolder = async (from, to) => {
}
}

const generateReports = async (knex) => {
const generateStaticReports = async (knex, options = { clearPreviousReports: false }) => {
const { clearPreviousReports } = options
if (clearPreviousReports) {
logger.info('Clearing previous reports')
await rm(destinationFolder, { recursive: true, force: true })
}

logger.info('Generating reports')
const { getAllProjects, getAllChecklists, getAllComplianceChecks, getAllAlerts, getAllResults, getAllTasks, getAllGithubOrganizationsByProjectsId, getAllGithubRepositories, getAllOSSFResults } = initializeStore(knex)
// @TODO: Run the queries in parallel
Expand Down Expand Up @@ -101,8 +107,9 @@ const generateReports = async (knex) => {

// Save the index HTML file
await writeFile('output/index.html', indexHtml)
logger.info('Reports generated successfully')
}

module.exports = {
generateReports
generateStaticReports
}
13 changes: 12 additions & 1 deletion src/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,16 @@ const isDateWithinPolicy = (targetDate, policy) => {
return isBefore(currentDate, expirationDate) // Check if current date is before expiration
}

const checkDatabaseConnection = async (knex) => {
try {
await knex.raw('SELECT 1')
return true
} catch (error) {
logger.error('Database connection failed', { error })
return false
}
}

module.exports = {
isDateWithinPolicy,
validateGithubUrl,
Expand All @@ -131,5 +141,6 @@ module.exports = {
groupArrayItemsByCriteria,
redactSensitiveData,
logger,
generatePercentage
generatePercentage,
checkDatabaseConnection
}