Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 72 additions & 1 deletion __tests__/httpServer/apiV1.test.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
const request = require('supertest')
const { generateStaticReports } = require('../../src/reports')
const knexInit = require('knex')
const { getConfig } = require('../../src/config')
const { resetDatabase, initializeStore } = require('../../__utils__')
const pkg = require('../../package.json')
const serverModule = require('../../src/httpServer')
const { dbSettings } = getConfig('test')
let server
let serverStop
let app
let knex
let getAllProjects

// Mocks
jest.mock('../../src/reports', () => ({
Expand All @@ -17,17 +23,26 @@ beforeAll(async () => {
server = await serverInstance.start()
serverStop = serverInstance.stop
app = request(server)
knex = knexInit(dbSettings);
({
getAllProjects
} = initializeStore(knex))
})

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

beforeEach(() => {
beforeEach(async () => {
await resetDatabase(knex)
jest.clearAllMocks()
})

afterEach(jest.clearAllMocks)

describe('HTTP Server API V1', () => {
describe('GET /api/v1/__health', () => {
test('should return status ok', async () => {
Expand All @@ -44,6 +59,62 @@ describe('HTTP Server API V1', () => {
})
})

describe('POST /api/v1/project', () => {
test('should return 200 and create a new project', async () => {
// Initial state
let projects = await getAllProjects()
expect(projects.length).toBe(0)
// Request
const newProject = { name: 'eslint' }
const response = await app.post('/api/v1/project').send(newProject)
// Database changes
projects = await getAllProjects()
expect(projects.length).toBe(1)
expect(projects[0].name).toBe('eslint')
// Response details
expect(response.status).toBe(201)
expect(response.headers).toHaveProperty('location', `/api/v1/project/${projects[0].id}`)
expect(response.body).toHaveProperty('id')
expect(response.body).toHaveProperty('name', newProject.name)
expect(response.body).toHaveProperty('created_at')
expect(response.body).toHaveProperty('updated_at')
})
test('should return 400 for invalid project name', async () => {
// Initial state
let projects = await getAllProjects()
expect(projects.length).toBe(0)
// Request
const invalidProject = { name: 'Invalid Name!' }
const response = await app.post('/api/v1/project').send(invalidProject)
// Database changes
projects = await getAllProjects()
expect(projects.length).toBe(0)
// Response details
expect(response.status).toBe(400)
expect(response.body).toStrictEqual({ errors: [{ errorCode: 'pattern.openapi.validation', message: 'must match pattern "^[a-zA-Z0-9_-]+$"', path: '/body/name' }], name: 'Bad Request', path: '/api/v1/project', status: 400 })
})
test('should return 409 if the project already exists', async () => {
// Initial state
let projects = await getAllProjects()
expect(projects.length).toBe(0)
// Create the project first
const existingProject = { name: 'eslint' }
await app.post('/api/v1/project').send(existingProject)
projects = await getAllProjects()
expect(projects.length).toBe(1)
// Request to create the same project again
const response = await app.post('/api/v1/project').send(existingProject)
// Database changes
projects = await getAllProjects()
expect(projects.length).toBe(1) // Still only one project
// Response details
expect(response.status).toBe(409)
expect(response.body).toStrictEqual({ errors: [{ message: 'Project already exists.' }] })
})

test.todo('should return 500 for internal server error')
})

describe('POST /api/v1/generate-reports', () => {
test('should return status completed when report generation succeeds', async () => {
generateStaticReports.mockResolvedValueOnce()
Expand Down
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"express": "^5.1.0",
"inquirer": "12.1.0",
"knex": "3.1.0",
"lodash": "^4.17.21",
"nock": "14.0.1",
"octokit": "3.2.1",
"pg": "8.13.1",
Expand Down
33 changes: 33 additions & 0 deletions src/httpServer/routers/apiV1.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,47 @@
const { generateStaticReports } = require('../../reports')
const pkg = require('../../../package.json')
const { logger } = require('../../utils')
const { initializeStore } = require('../../store')
const _ = require('lodash')
const { isSlug } = require('validator')

function createApiRouter (knex, express) {
const { addProject, getProjectByName } = initializeStore(knex)

const router = express.Router()

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

router.post('/project', async (req, res) => {
try {
// Validate request body
const { name } = req.body
const projectName = _.kebabCase(name)

// Check data and database
if (!projectName || !isSlug(projectName)) {
return res.status(400).json({ errors: [{ message: 'Invalid project name. Must be a slug.' }] })
}
const existingProject = await getProjectByName(projectName)
if (existingProject) {
return res.status(409).json({ errors: [{ message: 'Project already exists.' }] })
}

// Modify database
const project = await addProject({ name: projectName })

// Return response
return res.status(201)
.header('Location', `/api/v1/project/${project.id}`)
.json(project)
} catch (err) {
logger.error(err)
return res.status(500).json({ errors: [{ message: 'Internal server error' }] })
}
})

router.post('/generate-reports', async (req, res) => {
const startTs = new Date().toISOString()
try {
Expand Down
Loading
Loading