Skip to content

Commit

Permalink
refactor: Protected server API endpoint test passes successfully
Browse files Browse the repository at this point in the history
  • Loading branch information
am-ons committed Jun 20, 2024
1 parent 754e181 commit 6e39301
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 51 deletions.
26 changes: 26 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Jest Run All",
"program": "${workspaceFolder}/node_modules/.bin/jest",
"args":["--runInBand"],
"console" : "integratedTerminal",
"internalConsoleOptions": "neverOpen"
},
{
"type": "node",
"request": "launch",
"name": "Jest Run 1",
"program": "${workspaceFolder}/node_modules/.bin/jest",
"args":["--runInBand", "${relativeFile}"],
"console" : "integratedTerminal",
"internalConsoleOptions": "neverOpen"
}
]
}
12 changes: 10 additions & 2 deletions server/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import app from "./server";
import GetNodeServer from "./server";
import pino from "pino";
import { loadConfigFromEnv } from "./Config";
import BlaiseApiClient from "blaise-api-node-client";
import { Auth } from "blaise-login-react/blaise-login-react-server";

const port: string = process.env.PORT || "5002";
const logger = pino();
app.listen(port);

const config = loadConfigFromEnv();
const blaiseApiClient = new BlaiseApiClient(config.BlaiseApiUrl);
const auth = new Auth(config);
const server = GetNodeServer(config, blaiseApiClient, auth);
server.listen(port);

logger.info("App is listening on port " + port);
96 changes: 51 additions & 45 deletions server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,70 +3,76 @@ import axios from "axios";
import path from "path";
import ejs from "ejs";
import dotenv from "dotenv";
import { loadConfigFromEnv } from "./Config";
import createLogger from "./pino";
import BlaiseAPIRouter from "./BlaiseAPI";
import multer from "multer";
import * as profiler from "@google-cloud/profiler";
import BlaiseApiClient from "blaise-api-node-client";
import { newLoginHandler, Auth } from "blaise-login-react/blaise-login-react-server";
import pino from "pino";
import { CustomConfig } from "./interfaces/server";
import BlaiseApi from "blaise-api-node-client"
import { Express } from 'express';
import fs from 'fs';

const pinoLogger = pino();
profiler.start({ logLevel: 4 }).catch((err: unknown) => {
pinoLogger.error(`Failed to start profiler: ${err}`);
});

const upload = multer();
export default function GetNodeServer(config: CustomConfig, blaiseApi: BlaiseApi, auth: Auth): Express
{
const pinoLogger = pino();
profiler.start({ logLevel: 4 }).catch((err: unknown) => {
pinoLogger.error(`Failed to start profiler: ${err}`);
});

const server = express();
const upload = multer();

server.use(upload.any());
const server = express();

axios.defaults.timeout = 10000;
server.use(upload.any());

const logger = createLogger();
server.use(logger);
axios.defaults.timeout = 10000;

if (process.env.NODE_ENV !== "production") {
dotenv.config();
}
const logger = createLogger();
server.use(logger);

// where ever the react built package is
const buildFolder = "../build";
if (process.env.NODE_ENV !== "production") {
dotenv.config();
}

// load the .env variables in the server
const config = loadConfigFromEnv();
// where ever the react built package is
const buildFolder = "../build";

const auth = new Auth(config);
const blaiseApiClient = new BlaiseApiClient(config.BlaiseApiUrl);
const loginHandler = newLoginHandler(auth, blaiseApiClient);
const loginHandler = newLoginHandler(auth, blaiseApi);

// Health Check endpoint
server.get("/bam-ui/:version/health", async function (req: Request, res: Response) {
pinoLogger.info("Heath Check endpoint called");
res.status(200).json({ healthy: true });
});
// Health Check endpoint
server.get("/bam-ui/:version/health", async function (req: Request, res: Response) {
pinoLogger.info("Heath Check endpoint called");
res.status(200).json({ healthy: true });
});

server.use("/", loginHandler);
server.use("/", loginHandler);

// All Endpoints calling the Blaise API
server.use("/", BlaiseAPIRouter(config, auth, blaiseApiClient));
// All Endpoints calling the Blaise API
server.use("/", BlaiseAPIRouter(config, auth, blaiseApi));

// treat the index.html as a template and substitute the values at runtime
server.set("views", path.join(__dirname, "/views"));
server.engine("html", ejs.renderFile);
server.use(
"/static",
express.static(path.join(__dirname, `${buildFolder}/static`))
);
// treat the index.html as a template and substitute the values at runtime
server.set("views", path.join(__dirname, "/views"));
server.engine("html", ejs.renderFile);
server.use(
"/static",
express.static(path.join(__dirname, `${buildFolder}/static`))
);

server.get("*", function (_req: Request, res: Response) {
let filePath = path.join(__dirname, `${buildFolder}/index.html`);
if (!fs.existsSync(filePath)) {
filePath = path.join(__dirname, `../public/index.html`);
}
res.render(filePath);
});

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
a file system access
, but is not rate-limited.

server.get("*", function (_req: Request, res: Response) {
res.render(path.join(__dirname, `${buildFolder}/index.html`));
});
server.use(function (err: Error, _req: Request, res: Response) {
console.error(err.stack);
res.render("../views/500.html", {});
});

server.use(function (err: Error, _req: Request, res: Response) {
console.error(err.stack);
res.render("../views/500.html", {});
});
export default server;
return server;
}
64 changes: 60 additions & 4 deletions server/tests/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,38 @@
* @jest-environment node
*/

import app from "../server"; // Link to your server file
import supertest from "supertest";
import GetNodeServer from "../server";
import { loadConfigFromEnv } from "../Config";
import BlaiseApiClient, {NewUser, User} from "blaise-api-node-client";
import { Auth } from "blaise-login-react/blaise-login-react-server";
import { IMock, Mock, It, Times } from "typemoq";

// Temporary fix for Jest open handle issue (gcp profiler TCPWRAP error)
jest.mock("@google-cloud/profiler", () => ({
start: jest.fn().mockReturnValue(Promise.resolve())
}));

const request = supertest(app);
const config = loadConfigFromEnv();
const blaiseApiMock: IMock<BlaiseApiClient> = Mock.ofType(BlaiseApiClient);
Auth.prototype.ValidateToken = jest.fn().mockReturnValue(true);
const auth = new Auth(config);
const server = GetNodeServer(config, blaiseApiMock.object, auth);
const sut = supertest(server);


describe("Test Heath Endpoint", () => {

it("should return a 200 status and json message", async () => {
const response = await request.get("/bam-ui/version/health");
const response = await sut.get("/bam-ui/version/health");

expect(response.statusCode).toEqual(200);
expect(response.body).toStrictEqual({ healthy: true });
});
});

describe("app engine start", () => {

it("should return a 200 status and json message", async () => {
process.env = Object.assign({
PROJECT_ID: "mock",
Expand All @@ -30,8 +42,52 @@ describe("app engine start", () => {
SESSION_TIMEOUT: "12h"
});

const response = await request.get("/_ah/start");
const response = await sut.get("/_ah/start");

expect(response.statusCode).toEqual(200);
});
});

import role_to_serverparks_map from '../role-to-serverparks-map.json'
import { size } from "lodash";
describe("Test /api/users POST createUser with correct server parks", () => {
beforeEach(() => {
blaiseApiMock.reset();
});

afterAll(() => {
blaiseApiMock.reset();
});

it("should call Blaise API createUser endpoint with correct serverParks for each role in server/role-to-serverparks-map.json and return http status 200", async () => {
let index = 0;
let roleCount = size(role_to_serverparks_map);
for(let roleName in role_to_serverparks_map)
{
blaiseApiMock.reset();
console.log("Running for role %i of %i: %s", ++index, roleCount, roleName);

const spmap = role_to_serverparks_map[roleName];
const newUser : NewUser = {
name: "name1",
password: "password1",
role: roleName,
serverParks: spmap,
defaultServerPark: spmap[0]
};
blaiseApiMock.setup((api) => api.createUser(It.isAny())).returns(async () => newUser);

const response = await sut.post("/api/users")
.field("role", roleName);

expect(response.statusCode).toEqual(200);
blaiseApiMock.verify(a => a.createUser(It.is<NewUser>(
x=> x.defaultServerPark == newUser.defaultServerPark
&& x.role == newUser.role
&& Array.isArray(x.serverParks) && x.serverParks.every(item => typeof item === "string")
&& x.serverParks.every((val, idx) => val === newUser.serverParks[idx])
)), Times.exactly(1));
expect(response.body).toStrictEqual(newUser);
}
});
});

0 comments on commit 6e39301

Please sign in to comment.