feature(jest) writing tests for express endpoints

This commit is contained in:
Tanner Shaw
2023-08-03 10:55:46 -05:00
parent 5d6d38979b
commit 942f3a8fee
11 changed files with 5931 additions and 47 deletions

9
jest.config.cjs Normal file
View File

@@ -0,0 +1,9 @@
module.exports = {
clearMocks: true,
preset: 'ts-jest',
testEnvironment: 'node',
"transform": {
"^.+\\.jsx?$": "babel-jest",
"^.+\\.tsx?$": ["ts-jest", { tsconfig: "./tsconfig.tests.json" }]
},
}

5767
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,8 @@
"start": "node dist/server.cjs",
"watch": "rollup --config rollup.config.mjs --watch",
"serve": "nodemon -q dist/server.jcs",
"dev": "concurrently \"npm run watch\" \"npm run serve\""
"dev": "concurrently \"npm run watch\" \"npm run serve\"",
"test": "jest --watch --verbose"
},
"engines": {
"node": "18.x.x"
@@ -41,23 +42,36 @@
"socket.io": "^4.6.2"
},
"devDependencies": {
"@babel/core": "^7.22.9",
"@babel/preset-env": "^7.22.9",
"@babel/preset-typescript": "^7.22.5",
"@jest/globals": "^29.6.2",
"@rollup/plugin-commonjs": "^25.0.2",
"@rollup/plugin-json": "^6.0.0",
"@rollup/plugin-node-resolve": "^15.1.0",
"@types/cors": "^2.8.13",
"@types/express": "^4.17.17",
"@types/jest": "^29.5.3",
"@types/node": "^20.4.5",
"@types/supertest": "^2.0.12",
"@typescript-eslint/eslint-plugin": "^6.2.0",
"@typescript-eslint/parser": "^6.2.0",
"babel-jest": "^29.6.2",
"concurrently": "^8.2.0",
"eslint": "^8.45.0",
"jest": "^29.6.2",
"jest-mock-extended": "^3.0.4",
"nodemon": "^3.0.1",
"prisma": "^5.0.0",
"rollup": "^3.26.2",
"rollup-plugin-cleaner": "^1.0.0",
"rollup-plugin-include-sourcemaps": "^0.7.0",
"rollup-plugin-typescript2": "^0.35.0",
"supertest": "^6.3.3",
"ts-jest": "^29.1.1",
"ts-node": "^10.9.1",
"typescript": "^5.1.6"
"typescript": "^5.1.6",
"vitest": "^0.34.1",
"vitest-mock-extended": "^1.1.4"
}
}
}

View File

@@ -1,7 +1,7 @@
import type { MessageI, RoomI } from 'discreetly-interfaces';
import { str2BigInt } from 'discreetly-interfaces';
import { RLNVerifier } from 'rlnjs';
import vkey from './verification_key.js';
import vkey from './verification_key'
import { Group } from '@semaphore-protocol/group';
const v = new RLNVerifier(vkey);

View File

@@ -4,9 +4,8 @@
import { PrismaClient } from '@prisma/client';
import { RoomI, genId } from 'discreetly-interfaces';
import { serverConfig } from '../config/serverConfig';
import { randn_bm } from '../utils';
import { generateClaimCodes } from 'discreetly-claimcodes';
import type { ClaimCodeT } from 'discreetly-claimcodes';
import { genMockUsers, genClaimCodeArray } from '../utils';
const prisma = new PrismaClient();
@@ -19,7 +18,7 @@ interface ClaimCode {
roomIds: string[];
}
export function getRoomByID(id: string): Promise<RoomI> {
export function getRoomByID(id: string): Promise<RoomI | null> | null {
return prisma.rooms
.findUnique({
where: {
@@ -73,40 +72,15 @@ export function getRoomsByIdentity(identity: string): RoomI[] {
* @param {number} [numClaimCodes=0] - The number of claim codes to generate for the room.
* @param {number} [approxNumMockUsers=20] - The approximate number of mock users to generate for the room.
*/
export function createRoom(
export async function createRoom(
name: string,
rateLimit: number = 1000,
userMessageLimit: number = 1,
numClaimCodes: number = 0,
approxNumMockUsers: number = 20
): boolean {
function genMockUsers(numMockUsers: number): string[] {
// Generates random number of mock users between 0.5 x numMockusers and 2 x numMockUsers
const newNumMockUsers = randn_bm(numMockUsers / 2, numMockUsers * 2);
const mockUsers: string[] = [];
for (let i = 0; i < newNumMockUsers; i++) {
mockUsers.push(
genId(
serverConfig.id,
// Generates a random string of length 10
Math.random()
.toString(36)
.substring(2, 2 + 10) + i
).toString()
);
}
return mockUsers;
}
function genClaimCodeArray(numClaimCodes: number): { claimcode: string }[] {
const claimCodes = generateClaimCodes(numClaimCodes);
const codeArr: { claimcode: string }[] = claimCodes.map((code: ClaimCodeT) => ({
claimcode: code.code
}));
return codeArr;
}
approxNumMockUsers: number = 20,
): Promise<boolean> {
const claimCodes: { claimcode: string }[] = genClaimCodeArray(numClaimCodes);
console.log(claimCodes);
const mockUsers: string[] = genMockUsers(approxNumMockUsers);
const roomData = {
where: {
@@ -125,7 +99,7 @@ export function createRoom(
}
};
prisma.rooms
await prisma.rooms
.upsert(roomData)
.then(() => {
return true;
@@ -134,7 +108,7 @@ export function createRoom(
return false;
}
export function findClaimCode(code: string): Promise<CodeStatus> {
export function findClaimCode(code: string): Promise<CodeStatus | null> {
return prisma.claimCodes.findUnique({
where: { claimcode: code }
});

View File

@@ -2,7 +2,7 @@
import type { Express, RequestHandler } from 'express';
import { PrismaClient } from '@prisma/client';
import { serverConfig } from '../config/serverConfig';
import { pp } from '../utils.js';
import { pp } from '../utils';
import {
getRoomByID,
getRoomsByIdentity,
@@ -14,12 +14,12 @@ import {
} from '../data/db';
import { RoomI } from 'discreetly-interfaces';
const prisma = new PrismaClient();
export function initEndpoints(app: Express, adminAuth: RequestHandler) {
const prisma = new PrismaClient();
app.get(['/', '/api'], (req, res) => {
pp('Express: fetching server info');
res.json(serverConfig);
res.status(200).json(serverConfig);
});
app.get('/api/room/:id', (req, res) => {
@@ -76,13 +76,16 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) {
userMessageLimit: number;
numClaimCodes?: number;
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const roomMetadata = req.body.data as RoomData;
const roomMetadata = req.body as RoomData;
console.log(roomMetadata)
const roomName = roomMetadata.roomName;
const rateLimit = roomMetadata.rateLimit;
const userMessageLimit = roomMetadata.userMessageLimit;
const numClaimCodes = roomMetadata.numClaimCodes || 0;
const result = createRoom(roomName, rateLimit, userMessageLimit, numClaimCodes);
console.log(result);
if (result) {
// TODO should return roomID and claim codes if they are generated
res.status(200).json({ message: 'Room created successfully' });
@@ -116,4 +119,5 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) {
res.status(500).json({ error: 'Internal Server Error' });
});
});
}

View File

@@ -55,22 +55,27 @@ function initAppListeners() {
socket_server.listen(socketServerPort, () => {
pp(`SocketIO Server is running at port ${socketServerPort}`);
});
return app;
}
/**
* This is the main entry point for the server
*/
let _app
if (!process.env.NODE_ENV || process.env.NODE_ENV === 'development') {
console.log('~~~~DEVELOPMENT MODE~~~~');
initWebsockets(io);
initEndpoints(app, adminAuth);
listEndpoints(app);
initAppListeners();
_app = initAppListeners();
mock(io);
// TODO! This is dangerous and only for development
console.log('Admin password: ' + admin_password);
} else {
initWebsockets(io);
initEndpoints(app, adminAuth);
initAppListeners();
_app = initAppListeners();
}
export default _app;

View File

@@ -1,3 +1,7 @@
import { genId } from 'discreetly-interfaces'
import { serverConfig } from './config/serverConfig'
import { generateClaimCodes } from 'discreetly-claimcodes';
import type { ClaimCodeT } from 'discreetly-claimcodes';
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-return */
@@ -9,6 +13,31 @@ export function shim() {
};
}
export function genMockUsers(numMockUsers: number): string[] {
// Generates random number of mock users between 0.5 x numMockusers and 2 x numMockUsers
const newNumMockUsers = randn_bm(numMockUsers / 2, numMockUsers * 2);
const mockUsers: string[] = [];
for (let i = 0; i < newNumMockUsers; i++) {
mockUsers.push(
genId(
serverConfig.id,
// Generates a random string of length 10
Math.random()
.toString(36)
.substring(2, 2 + 10) + i
).toString()
);
}
return mockUsers;
}
export function genClaimCodeArray(numClaimCodes: number): { claimcode: string }[] {
const claimCodes = generateClaimCodes(numClaimCodes);
const codeArr: { claimcode: string }[] = claimCodes.map((code: ClaimCodeT) => ({
claimcode: code.code
}));
return codeArr;
}
/**
* Logs the provided string to the console with the specified log level.
* @param {any} str - The string to log.

79
tests/express.test.ts Normal file
View File

@@ -0,0 +1,79 @@
const request = require('supertest');
import { assert } from 'console';
import _app from '../src/server'
import { genId } from 'discreetly-interfaces';
import { serverConfig } from '../src/config/serverConfig';
import { describe } from 'node:test';
process.env.DATABASE_URL = process.env.DATABASE_URL_TEST
const room = {
roomName: 'Test-room',
rateLimit: 1000,
userMessageLimit: 1,
numClaimCodes: 5,
approxNumMockUsers: 20,
}
const roomByIdTest = genId(serverConfig.id, room.roomName).toString();
describe('GET /', () => {
test('It should respond with server info', () => {
request(_app).get('/').expect('Content-Type', 'application/json; charset=utf-8').then(res => {
})
})
})
describe("POST /room/add", () => {
test("It should add a new room to the database", async () => {
const username = 'admin';
const password = process.env.PASSWORD;
const base64Credentials = Buffer.from(`${username}:${password}`).toString('base64');
await request(_app)
.post("/room/add")
.set('Authorization', `Basic ${base64Credentials}`)
.send(room)
.then(res => {
expect(res.json === '{message :"Room created successfully"}')
});
});
});
describe("GET /api/room/:id", () => {
test("It should return the room with the given id", async () => {
await request(_app)
.get(`/api/room/${roomByIdTest}`)
.then(res => {
console.log(res.body);
expect(res.body.roomName === room.roomName)
});
});
});
describe("GET /api/rooms", () => {
test("It should return all rooms", async () => {
await request(_app)
.get("/api/rooms")
.then(res => {
expect(res.body.length > 0)
});
});
})
describe("GET /logclaimcodes", () => {
test("It should return all claim codes", async () => {
const username = 'admin';
const password = process.env.PASSWORD;
const base64Credentials = Buffer.from(`${username}:${password}`).toString('base64');
await request(_app)
.get("/logclaimcodes")
.set('Authorization', `Basic ${base64Credentials}`)
.then(res => {
expect(res.body.length > 0)
});
});
});

View File

@@ -19,4 +19,4 @@
"./src/**/*",
"./src/types/index.ts"
]
}
}

5
tsconfig.tests.json Normal file
View File

@@ -0,0 +1,5 @@
{
"extends": "./tsconfig.json",
"strictNullChecks": true,
"skipLibCheck": true
}