UNCLASSIFIED - NO CUI

Skip to content
Snippets Groups Projects
Verified Commit b5c5b4bb authored by Douglas Lagemann's avatar Douglas Lagemann
Browse files

unit tests, 100 coverage of controller and app. split app.listen to new file server.js

parent 0e73ed3a
No related branches found
No related tags found
1 merge request!5BULL-3221: express mvp
This commit is part of merge request !5. Comments created here will be created in the context of that merge request.
...@@ -8,7 +8,15 @@ export default [ ...@@ -8,7 +8,15 @@ export default [
sourceType: "commonjs", sourceType: "commonjs",
globals: { globals: {
...globals.browser, ...globals.browser,
...globals.node ...globals.node,
}
}
},
{
files: ["**/*.spec.js"],
languageOptions: {
globals: {
...globals.jest
} }
} }
}, },
......
package-lock.json 0 → 100644
+ 5023
0
View file @ b5c5b4bb
This diff is collapsed.
...@@ -4,11 +4,11 @@ ...@@ -4,11 +4,11 @@
"version": "0.0.0", "version": "0.0.0",
"main": "app.js", "main": "app.js",
"scripts": { "scripts": {
"start": "node src/app.js", "start": "node src/server.js",
"dev": "node src/app.js", "dev": "node src/server.js",
"lint": "eslint .", "lint": "eslint .",
"lint:fix": "eslint . --fix", "lint:fix": "eslint . --fix",
"test:unit": "jest" "test:unit": "jest --coverage"
}, },
"dependencies": { "dependencies": {
"express": "^4.21.1", "express": "^4.21.1",
......
const express = require('express'); const express = require('express');
const { runMigrations } = require('./data/dataLayer'); const { getHealth, getVersion, getUser } = require('./controller/controller');
const { getHealth, getVersion, getUser } = require('./controller/controller.js');
const app = express(); const app = express();
const port = 8000;
// This middleware logs every incoming request with the current timestamp, HTTP method, and URL path. // This middleware logs every incoming request with the current timestamp, HTTP method, and URL path.
app.use((req, res, next) => { app.use((req, res, next) => {
...@@ -16,6 +14,7 @@ app.get('/api', async (req, res) => { ...@@ -16,6 +14,7 @@ app.get('/api', async (req, res) => {
res.send('Hello World! at specific path /api'); res.send('Hello World! at specific path /api');
}); });
// Set up main application routes
app.get('/api/health', getHealth); app.get('/api/health', getHealth);
app.get('/api/me', getUser); app.get('/api/me', getUser);
app.get('/api/version', getVersion); app.get('/api/version', getVersion);
...@@ -29,16 +28,11 @@ app.all('*', async (req, res) => { ...@@ -29,16 +28,11 @@ app.all('*', async (req, res) => {
// It logs the error details and sends a 500 Internal Server Error response. // It logs the error details and sends a 500 Internal Server Error response.
// Consider expanding on this to inspect the error and return a status code based on known error conditions, // Consider expanding on this to inspect the error and return a status code based on known error conditions,
// or keep it as a failsafe to ensure sensitive information about the system is not returned. // or keep it as a failsafe to ensure sensitive information about the system is not returned.
app.use(async (err, req, res, next) => { app.use((err, req, res, next) => {
const timestamp = new Date().toISOString(); const timestamp = new Date().toISOString();
console.error(`[${timestamp}] Error: ${err.message}`); console.error(`[${timestamp}] Error: ${err.message}`);
console.error(err.stack); console.error(err.stack);
res.status(500).send('Internal Server Error'); res.status(500).send('Internal Server Error');
}); });
// Run database migrations to ensure the database is up to date, then start the api server. module.exports = app;
runMigrations().then(
app.listen(port, () => {
console.log(`App is running at http://localhost:${port}`);
})
);
src/app.spec.js 0 → 100644
const request = require('supertest');
const app = require('./app');
const { getHealth } = require('./controller/controller');
jest.mock('./controller/controller', () => ({
getHealth: jest.fn().mockImplementation(async (req, res) => {
res.send('got health');
}),
getUser: jest.fn().mockImplementation(async (req, res) => {
res.send('got user');
}),
getVersion: jest.fn().mockImplementation(async (req, res) => {
res.send("got version");
})
}));
beforeEach(() => {
console.log = jest.fn();
});
afterEach(() => {
jest.restoreAllMocks();
});
describe('App routes', () => {
it('responds with hello world at the /api route', async () => {
const response = await request(app).get('/api');
expect(response.status).toEqual(200);
expect(response.text).toEqual('Hello World! at specific path /api');
});
it('calls the getHealth controller method at the /api/health route', async () => {
const response = await request(app).get('/api/health');
expect(response.text).toEqual('got health');
});
it('calls the getUser controller method at the /api/me route', async () => {
const response = await request(app).get('/api/me');
expect(response.text).toEqual('got user');
});
it('calls the getVersion controller method at the /api/version route', async () => {
const response = await request(app).get('/api/version');
expect(response.text).toEqual('got version');
});
it('responds with 404 at unknown routes', async () => {
const response = await request(app).get('/api/nope');
expect(response.status).toEqual(404);
expect(response.text).toEqual('Not found: /api/nope');
});
it('responds with 500 and a generic error message when an error is thrown', async () => {
getHealth.mockImplementation(() => {
throw new Error("Boom!");
});
console.error = jest.fn();
const response = await request(app).get('/api/health');
expect(response.status).toEqual(500);
expect(response.text).toEqual('Internal Server Error');
expect(console.error).toHaveBeenCalled();
})
});
...@@ -34,7 +34,7 @@ exports.getUser = async (req, res) => { ...@@ -34,7 +34,7 @@ exports.getUser = async (req, res) => {
try { try {
const header = req.headersDistinct?.Authorization?.[0] || req.headersDistinct?.authorization?.[0]; const header = req.headersDistinct?.Authorization?.[0] || req.headersDistinct?.authorization?.[0];
const responseData = header ? jwtDecode(header) : { result: "no authorization header found" }; const responseData = header ? jwtDecode(header) : { result: "no authorization header found" };
res.json(responseData); res.status(200).json(responseData);
} catch (error) { } catch (error) {
const errorResponse = { const errorResponse = {
error: 'Unable to read authorization header as jwt! Error: ' + error.message error: 'Unable to read authorization header as jwt! Error: ' + error.message
...@@ -52,5 +52,5 @@ exports.getVersion = async (req, res) => { ...@@ -52,5 +52,5 @@ exports.getVersion = async (req, res) => {
apiVersion: apiVersion, apiVersion: apiVersion,
dbVersion: dbVersion dbVersion: dbVersion
}; };
res.json(versionResponse); res.status(200).json(versionResponse);
}; };
const { jwtDecode } = require('jwt-decode');
const { getHealth, getUser, getVersion } = require('./controller');
const { getDbVersion, healthCheck } = require('../data/dataLayer');
jest.mock('jwt-decode', () => ({
jwtDecode: jest.fn()
}));
jest.mock('../data/dataLayer', () => ({
healthCheck: jest.fn(),
getDbVersion: jest.fn()
}));
let mockRes;
beforeEach(() => {
mockRes = {
status: jest.fn().mockReturnThis(),
json: jest.fn()
};
console.log = jest.fn();
});
afterEach(() => {
jest.restoreAllMocks();
});
describe('Controller', () => {
describe('getHealth', () => {
it('responds with 200 and message on success', async () => {
healthCheck.mockImplementation(async () => {
return {
healthy: true,
message: "test health check"
};
});
await getHealth(null, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(200);
expect(mockRes.json).toHaveBeenCalledWith({
status: 200,
message: "test health check"
});
});
it('responds with 500 and message on failure', async () => {
healthCheck.mockImplementation(async () => {
return {
healthy: false,
message: "database issues"
};
});
await getHealth(null, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(500);
expect(mockRes.json).toHaveBeenCalledWith({
status: 500,
message: "database issues"
});
});
it('responds with 500 and generic message on falsy result', async () => {
healthCheck.mockResolvedValue(undefined);
await getHealth(null, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(500);
expect(mockRes.json).toHaveBeenCalledWith({
status: 500,
message: "Internal Server Error"
});
});
});
describe('getUser', () => {
it ('responds with 200 and decoded token from request header: Authorization', async () => {
jwtDecode.mockReturnValue({ token: "decoded-auth" });
const mockReq = { headersDistinct: { Authorization: [ "test-auth" ] } };
await getUser(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(200);
expect(mockRes.json).toHaveBeenCalledWith({ token: "decoded-auth" });
});
it ('responds with 200 and decoded token from request header: authorization', async () => {
jwtDecode.mockReturnValue({ token: "decoded-auth" });
const mockReq = { headersDistinct: { authorization: [ "test-auth" ] } };
await getUser(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(200);
expect(mockRes.json).toHaveBeenCalledWith({ token: "decoded-auth" });
});
it ('responds with 200 and generic message if no auth header found', async () => {
const mockReq = {};
await getUser(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(200);
expect(mockRes.json).toHaveBeenCalledWith({ result: "no authorization header found" });
});
it ('responds with 400 and error message if decode fails', async () => {
jwtDecode.mockImplementation(() => {
throw new Error("failed to decode");
});
const mockReq = { headersDistinct: { Authorization: [ "test-auth" ] } };
await getUser(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(400);
expect(mockRes.json.mock.calls[0][0].error).toContain("failed to decode");
});
});
describe('getVersion', () => {
it('responds with 200 and versions', async () => {
getDbVersion.mockResolvedValue("1.2.3");
await getVersion(null, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(200);
expect(mockRes.json).toHaveBeenCalledWith({
apiVersion: "0.0.1",
dbVersion: "1.2.3"
});
});
});
});
src/server.js 0 → 100644
const app = require('./app');
const { runMigrations } = require('./data/dataLayer');
const port = 8000;
// Run database migrations to ensure the database is up to date, then start the api server.
runMigrations().then(
app.listen(port, () => {
console.log(`App is running at http://localhost:${port}`);
})
);
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment