mirror of
https://github.com/Discreetly/server.git
synced 2026-04-17 03:00:55 -04:00
feature(jest) writing tests for express endpoints (#27)
This commit is contained in:
9
jest.config.cjs
Normal file
9
jest.config.cjs
Normal 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" }]
|
||||
},
|
||||
}
|
||||
3541
package-lock.json
generated
3541
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
13
package.json
13
package.json
@@ -12,6 +12,7 @@
|
||||
"watch": "rollup --config rollup.config.mjs --watch",
|
||||
"serve": "nodemon -q dist/server.jcs",
|
||||
"dev": "concurrently \"npm run watch\" \"npm run serve\"",
|
||||
"test": "jest --verbose",
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"engines": {
|
||||
@@ -24,7 +25,6 @@
|
||||
"dependencies": {
|
||||
"@faker-js/faker": "^8.0.2",
|
||||
"@prisma/client": "^5.0.0",
|
||||
"atob": "^2.1.2",
|
||||
"body-parser": "^1.20.2",
|
||||
"cors": "^2.8.5",
|
||||
"discreetly-claimcodes": "^1.1.3",
|
||||
@@ -35,29 +35,32 @@
|
||||
"helmet": "^7.0.0",
|
||||
"mongodb": "^5.7.0",
|
||||
"poseidon-lite": "^0.2.0",
|
||||
"prisma-cache-middleware": "^0.1.4",
|
||||
"prisma-redis-middleware": "4.8.0",
|
||||
"redis": "^4.6.7",
|
||||
"rlnjs": "^3.1.4",
|
||||
"socket.io": "^4.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@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.1",
|
||||
"@typescript-eslint/parser": "^6.2.1",
|
||||
"concurrently": "^8.2.0",
|
||||
"eslint": "^8.46.0",
|
||||
"jest": "^29.6.2",
|
||||
"nodemon": "^3.0.1",
|
||||
"prisma": "^5.0.0",
|
||||
"prisma": "^5.1.1",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ model Messages {
|
||||
|
||||
model Epoch {
|
||||
id String @id @default(auto()) @map("_id") @db.ObjectId
|
||||
epoch Int
|
||||
epoch String
|
||||
messages Messages[]
|
||||
rooms Rooms? @relation(fields: [roomsId], references: [id])
|
||||
roomsId String? @db.ObjectId
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import type { ServerI } from 'discreetly-interfaces';
|
||||
import 'dotenv/config';
|
||||
|
||||
let SERVER_ID: bigint;
|
||||
let NAME: string;
|
||||
let SERVER_ID = 0n;
|
||||
let NAME = 'undefined';
|
||||
|
||||
try {
|
||||
SERVER_ID = process.env.SERVER_ID ? (process.env.SERVER_ID as unknown as bigint) : 0n;
|
||||
console.log('SERVERID:', SERVER_ID);
|
||||
} catch (error) {
|
||||
console.error('Error reading serverID from .env file!');
|
||||
}
|
||||
console.log('SERVERID:', SERVER_ID);
|
||||
|
||||
try {
|
||||
NAME = process.env.SERVER_NAME ? process.env.SERVER_NAME : 'localhost';
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
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);
|
||||
|
||||
async function verifyProof(msg: MessageI, room: RoomI, epochErrorRange = 5): Promise<boolean> {
|
||||
console.log('check room', room);
|
||||
if (!msg.roomId || !msg.message || !msg.proof || !msg.epoch) {
|
||||
console.warn('Missing required fields:', msg);
|
||||
return false;
|
||||
}
|
||||
console.debug(`Verifying message ${msg.messageId} for room ${room.roomId}`);
|
||||
const timestamp = Date.now();
|
||||
const rateLimit = room.rateLimit ? room.rateLimit : 1000;
|
||||
const currentEpoch = Math.floor(timestamp / rateLimit);
|
||||
|
||||
@@ -4,9 +4,7 @@
|
||||
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, pp } from '../utils';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
@@ -15,11 +13,11 @@ interface CodeStatus {
|
||||
roomIds: string[];
|
||||
}
|
||||
|
||||
interface ClaimCode {
|
||||
interface RoomsFromClaimCode {
|
||||
roomIds: string[];
|
||||
}
|
||||
|
||||
export function getRoomByID(id: string): Promise<RoomI> {
|
||||
export function getRoomByID(id: string): Promise<RoomI | null> {
|
||||
return prisma.rooms
|
||||
.findUnique({
|
||||
where: {
|
||||
@@ -70,13 +68,13 @@ export async function getRoomsByIdentity(identity: string): Promise<string[]> {
|
||||
}
|
||||
}
|
||||
|
||||
export function findClaimCode(code: string): Promise<CodeStatus> {
|
||||
export function findClaimCode(code: string): Promise<CodeStatus | null> {
|
||||
return prisma.claimCodes.findUnique({
|
||||
where: { claimcode: code }
|
||||
});
|
||||
}
|
||||
|
||||
export function updateClaimCode(code: string): Promise<ClaimCode> {
|
||||
export function updateClaimCode(code: string): Promise<RoomsFromClaimCode> {
|
||||
return prisma.claimCodes.update({
|
||||
where: { claimcode: code },
|
||||
data: { claimed: true }
|
||||
@@ -84,14 +82,23 @@ export function updateClaimCode(code: string): Promise<ClaimCode> {
|
||||
}
|
||||
|
||||
export function updateRoomIdentities(idc: string, roomIds: string[]): Promise<any> {
|
||||
return prisma.rooms.updateMany({
|
||||
return prisma.rooms.findMany({
|
||||
where: { id: { in: roomIds } },
|
||||
data: {
|
||||
identities: {
|
||||
push: idc
|
||||
}
|
||||
})
|
||||
.then((rooms) => {
|
||||
const roomsToUpdate = rooms
|
||||
.filter(room => !room.identities.includes(idc))
|
||||
.map(room => room.id);
|
||||
|
||||
if (roomsToUpdate) {
|
||||
return prisma.rooms.updateMany({
|
||||
where: { id: { in: roomsToUpdate } },
|
||||
data: { identities: { push: idc } }
|
||||
});
|
||||
}
|
||||
});
|
||||
}).catch(err => {
|
||||
pp(err, 'error')
|
||||
})
|
||||
}
|
||||
|
||||
export function findUpdatedRooms(roomIds: string[]): Promise<RoomI[]> {
|
||||
@@ -108,40 +115,15 @@ export function findUpdatedRooms(roomIds: string[]): Promise<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 = 1000,
|
||||
userMessageLimit = 1,
|
||||
numClaimCodes = 0,
|
||||
approxNumMockUsers = 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;
|
||||
}
|
||||
|
||||
): Promise<boolean> {
|
||||
const claimCodes: { claimcode: string }[] = genClaimCodeArray(numClaimCodes);
|
||||
console.log(claimCodes);
|
||||
const mockUsers: string[] = genMockUsers(approxNumMockUsers);
|
||||
const roomData = {
|
||||
where: {
|
||||
@@ -160,7 +142,7 @@ export function createRoom(
|
||||
}
|
||||
};
|
||||
|
||||
prisma.rooms
|
||||
await prisma.rooms
|
||||
.upsert(roomData)
|
||||
.then(() => {
|
||||
return true;
|
||||
|
||||
@@ -4,7 +4,10 @@ import { MessageI } from 'discreetly-interfaces';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
function updateRoom(roomId: string, message: MessageI, epoch: number): Promise<unknown> {
|
||||
function addMessageToRoom(roomId: string, message: MessageI): Promise<unknown> {
|
||||
if (!message.epoch) {
|
||||
throw new Error('Epoch not provided');
|
||||
}
|
||||
return prisma.rooms.update({
|
||||
where: {
|
||||
roomId: roomId
|
||||
@@ -12,7 +15,7 @@ function updateRoom(roomId: string, message: MessageI, epoch: number): Promise<u
|
||||
data: {
|
||||
epochs: {
|
||||
create: {
|
||||
epoch: epoch,
|
||||
epoch: String(message.epoch),
|
||||
messages: {
|
||||
create: {
|
||||
message: message.message ? message.message.toString() : '',
|
||||
@@ -27,22 +30,28 @@ function updateRoom(roomId: string, message: MessageI, epoch: number): Promise<u
|
||||
});
|
||||
}
|
||||
|
||||
export function createMessage(roomId: string, message: MessageI) {
|
||||
export function createMessage(roomId: string, message: MessageI): boolean {
|
||||
getRoomByID(roomId)
|
||||
.then((room) => {
|
||||
if (room) {
|
||||
updateRoom(roomId, message)
|
||||
// Todo This should check that there is no duplicate messageId with in this room and epoch, if there is, we need to return an error and reconstruct the secret from both messages, and ban the user
|
||||
addMessageToRoom(roomId, message)
|
||||
.then((roomToUpdate) => {
|
||||
console.log(roomToUpdate);
|
||||
return true;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(`Error updating room: ${error}`);
|
||||
return false;
|
||||
});
|
||||
} else {
|
||||
console.log('Room not found');
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(`Error getting room: ${error}`);
|
||||
return false;
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Express, RequestHandler } from 'express';
|
||||
import type { Express, RequestHandler, Request, Response } from 'express';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { serverConfig } from '../config/serverConfig';
|
||||
import { pp } from '../utils.js';
|
||||
import { pp } from '../utils';
|
||||
import {
|
||||
getRoomByID,
|
||||
getRoomsByIdentity,
|
||||
@@ -13,26 +13,45 @@ import {
|
||||
} from '../data/db';
|
||||
import { RoomI } from 'discreetly-interfaces';
|
||||
|
||||
export function initEndpoints(app: Express, adminAuth: RequestHandler) {
|
||||
const prisma = new PrismaClient();
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
function asyncHandler(fn: {
|
||||
(req: Request, res: Response): Promise<void>;
|
||||
(arg0: unknown, arg1: unknown): unknown;
|
||||
}) {
|
||||
return (req, res) => {
|
||||
void Promise.resolve(fn(req, res)).catch((err) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
throw new Error(err);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function initEndpoints(app: Express, adminAuth: RequestHandler) {
|
||||
app.get(['/', '/api'], (req, res) => {
|
||||
pp('Express: fetching server info');
|
||||
res.json(serverConfig);
|
||||
res.status(200).json(serverConfig);
|
||||
});
|
||||
|
||||
app.get(['/room/:id', '/api/room/:id'], (req, res) => {
|
||||
pp(String('Express: fetching room info for ' + req.params.id));
|
||||
getRoomByID(req.params.id)
|
||||
.then((room: RoomI) => {
|
||||
if (!room) {
|
||||
// This is set as a timeout to prevent someone from trying to brute force room ids
|
||||
setTimeout(() => res.status(500).json({ error: 'Internal Server Error' }), 1000);
|
||||
} else {
|
||||
res.status(200).json(room);
|
||||
}
|
||||
})
|
||||
.catch((err) => console.error(err));
|
||||
if (!req.params.id) {
|
||||
res.status(400).json({ error: 'Bad Request' });
|
||||
} else {
|
||||
const requestRoomId = req.params.id ?? '0';
|
||||
pp(String('Express: fetching room info for ' + req.params.id));
|
||||
getRoomByID(requestRoomId)
|
||||
.then((room: RoomI) => {
|
||||
if (!room) {
|
||||
// This is set as a timeout to prevent someone from trying to brute force room ids
|
||||
setTimeout(() => res.status(500).json({ error: 'Internal Server Error' }), 1000);
|
||||
} else {
|
||||
// Add null check before accessing properties of room object
|
||||
const { roomId, name, rateLimit, userMessageLimit } = room || {};
|
||||
res.status(200).json({ roomId, name, rateLimit, userMessageLimit });
|
||||
}
|
||||
})
|
||||
.catch((err) => console.error(err));
|
||||
}
|
||||
});
|
||||
|
||||
app.get(['/rooms/:idc', '/api/rooms/:idc'], (req, res) => {
|
||||
@@ -45,54 +64,70 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) {
|
||||
idc: string;
|
||||
}
|
||||
|
||||
app.post(['/join', '/api/join'], (req, res) => {
|
||||
const { code, idc } = req.body as JoinData;
|
||||
console.log('Invite Code:', code);
|
||||
findClaimCode(code)
|
||||
.then((codeStatus) => {
|
||||
if (codeStatus && codeStatus.claimed === false) {
|
||||
return updateClaimCode(code).then((claimCode) => {
|
||||
const roomIds = claimCode.roomIds.map((room) => room.roomId);
|
||||
return updateRoomIdentities(idc, roomIds).then(() => {
|
||||
return findUpdatedRooms(roomIds).then((updatedRooms: RoomI[]) => {
|
||||
return res.status(200).json({
|
||||
status: 'valid',
|
||||
roomIds: updatedRooms.map((room: RoomI) => room.roomId)
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
return res.status(400).json({ message: 'Claim code already used' });
|
||||
}
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
console.error(err);
|
||||
return res.status(500).json({ error: 'Internal Server Error' });
|
||||
app.post(
|
||||
['/join', '/api/join'],
|
||||
asyncHandler(async (req: Request, res: Response) => {
|
||||
const parsedBody: JoinData = req.body as JoinData;
|
||||
|
||||
if (!parsedBody.code || !parsedBody.idc) {
|
||||
res.status(400).json({ message: '{code: string, idc: string} expected' });
|
||||
}
|
||||
const { code, idc } = parsedBody;
|
||||
console.log('Invite Code:', code);
|
||||
|
||||
// Check if claim code is valid and not used before
|
||||
const codeStatus = await findClaimCode(code);
|
||||
if (!codeStatus || codeStatus.claimed) {
|
||||
res.status(400).json({ message: 'Claim code already used' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Update claim code
|
||||
const claimCode = await updateClaimCode(code);
|
||||
const roomIds = claimCode.roomIds;
|
||||
|
||||
// Update Room Identities
|
||||
await updateRoomIdentities(idc, roomIds);
|
||||
|
||||
// Find updated rooms
|
||||
const updatedRooms: RoomI[] = await findUpdatedRooms(roomIds);
|
||||
|
||||
// Return the room ids of the updated rooms
|
||||
res.status(200).json({
|
||||
status: 'valid',
|
||||
roomIds: updatedRooms.map((room: RoomI) => room.roomId)
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
interface addRoomData {
|
||||
roomName: string;
|
||||
rateLimit: number;
|
||||
userMessageLimit: number;
|
||||
numClaimCodes?: number;
|
||||
}
|
||||
|
||||
/* ~~~~ ADMIN ENDPOINTS ~~~~ */
|
||||
app.post(['/room/add', '/api/room/add'], adminAuth, (req, res) => {
|
||||
interface RoomData {
|
||||
roomName: string;
|
||||
rateLimit: number;
|
||||
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 addRoomData;
|
||||
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);
|
||||
if (result) {
|
||||
// TODO should return roomID and claim codes if they are generated
|
||||
res.status(200).json({ message: 'Room created successfully' });
|
||||
} else {
|
||||
return res.status(500).json({ error: 'Internal Server Error' });
|
||||
}
|
||||
createRoom(roomName, rateLimit, userMessageLimit, numClaimCodes)
|
||||
.then((result) => {
|
||||
if (result) {
|
||||
// TODO should return roomID and claim codes if they are generated
|
||||
res.status(200).json({ message: 'Room created successfully' });
|
||||
} else {
|
||||
res.status(500).json({ error: 'Internal Server Error' });
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: String(err) });
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/api/room/:id/messages', (req, res) => {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
export function listEndpoints(app) {
|
||||
const table = [];
|
||||
const table: unknown[] = [];
|
||||
for (const r of app._router.stack) {
|
||||
if (r.route?.path) {
|
||||
const methods = Object.keys(r.route.methods).join(', ').toUpperCase();
|
||||
|
||||
@@ -3,7 +3,9 @@ import http from 'http';
|
||||
import cors from 'cors';
|
||||
import helmet from 'helmet';
|
||||
import basicAuth from 'express-basic-auth';
|
||||
import type { Server } from 'http';
|
||||
import { Server as SocketIOServer } from 'socket.io';
|
||||
import type { Server as SocketIOServerT } from 'socket.io';
|
||||
import { serverConfig } from './config/serverConfig';
|
||||
import { pp, shim } from './utils';
|
||||
import mock from './data/mock';
|
||||
@@ -41,27 +43,43 @@ function initAppListeners(PORT) {
|
||||
const httpServer = http.createServer(app).listen(PORT, () => {
|
||||
pp(`Server is running at port ${PORT}`);
|
||||
});
|
||||
|
||||
const io = new SocketIOServer(httpServer);
|
||||
return io;
|
||||
return httpServer;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the main entry point for the server
|
||||
*/
|
||||
let _app: Server;
|
||||
let io: SocketIOServerT;
|
||||
|
||||
interface ServerConfigStartupI {
|
||||
id?: string;
|
||||
name?: string;
|
||||
version?: string;
|
||||
port?: number | string;
|
||||
admin_password?: string;
|
||||
}
|
||||
const serverConfigStartup: ServerConfigStartupI = serverConfig as unknown as ServerConfigStartupI;
|
||||
if (!process.env.NODE_ENV || process.env.NODE_ENV === 'development') {
|
||||
console.log('~~~~DEVELOPMENT MODE~~~~');
|
||||
console.log(serverConfig);
|
||||
const PORT = 3001;
|
||||
serverConfigStartup.port = PORT;
|
||||
serverConfigStartup.admin_password = admin_password;
|
||||
initEndpoints(app, adminAuth);
|
||||
const io = initAppListeners(PORT);
|
||||
initWebsockets(io);
|
||||
_app = initAppListeners(PORT);
|
||||
listEndpoints(app);
|
||||
io = new SocketIOServer(_app, {});
|
||||
initWebsockets(io);
|
||||
mock(io);
|
||||
// TODO! This is dangerous and only for development
|
||||
console.log('Admin password: ' + admin_password);
|
||||
} else {
|
||||
const PORT = process.env.PORT;
|
||||
serverConfigStartup.port = PORT;
|
||||
initEndpoints(app, adminAuth);
|
||||
const io = initAppListeners(process.env.PORT);
|
||||
_app = initAppListeners(PORT);
|
||||
io = new SocketIOServer(_app, {});
|
||||
initWebsockets(io);
|
||||
}
|
||||
|
||||
pp(serverConfigStartup, 'table');
|
||||
|
||||
export default _app;
|
||||
|
||||
64
src/utils.ts
64
src/utils.ts
@@ -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,37 +13,75 @@ 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: ClaimCodeT[] = generateClaimCodes(numClaimCodes) as ClaimCodeT[];
|
||||
const codeArr: { claimcode: string }[] = claimCodes.map((code: ClaimCodeT) => ({
|
||||
claimcode: String(code.code)
|
||||
}));
|
||||
return codeArr;
|
||||
}
|
||||
/**
|
||||
* Logs the provided string to the console with the specified log level.
|
||||
* @param {any} str - The string to log.
|
||||
* @param {any} data - The string to log.
|
||||
* @param {string} [level='log'] - The log level to use. Can be one of 'log', 'debug', 'info', 'warn', 'warning', 'error', 'err', 'table', or 'assert'.
|
||||
*/
|
||||
export const pp = (str: any, level = 'log') => {
|
||||
str = JSON.stringify(str, null, 2);
|
||||
export const pp = (data: any, level = 'log') => {
|
||||
switch (level) {
|
||||
case 'log':
|
||||
console.log(str);
|
||||
data = JSON.stringify(data, null, 2);
|
||||
console.log(data);
|
||||
break;
|
||||
case 'debug':
|
||||
console.debug(str);
|
||||
data = JSON.stringify(data, null, 2);
|
||||
console.debug(data);
|
||||
break;
|
||||
case 'info':
|
||||
console.info(str);
|
||||
data = JSON.stringify(data, null, 2);
|
||||
console.info(data);
|
||||
break;
|
||||
case 'warn' || 'warning':
|
||||
console.warn(str);
|
||||
data = JSON.stringify(data, null, 2);
|
||||
console.warn(data);
|
||||
break;
|
||||
case 'error' || 'err':
|
||||
console.error(str);
|
||||
data = JSON.stringify(data, null, 2);
|
||||
console.error(data);
|
||||
break;
|
||||
case 'table':
|
||||
console.table(str);
|
||||
if (typeof data === 'object') {
|
||||
// converts an object into a table of keys and values
|
||||
const newData: { key: any; value: any }[] = [];
|
||||
Object.entries(data as Record<string, unknown>).forEach(([key, value]) => {
|
||||
newData.push({ key: key, value: value });
|
||||
});
|
||||
console.table(newData);
|
||||
} else {
|
||||
console.table(data);
|
||||
}
|
||||
break;
|
||||
case 'assert':
|
||||
console.assert(str);
|
||||
console.assert(data);
|
||||
break;
|
||||
default:
|
||||
console.log(str);
|
||||
console.log(data);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ export function websocketSetup(io: SocketIOServer) {
|
||||
pp('SocketIO: a user connected', 'debug');
|
||||
|
||||
socket.on('validateMessage', (msg: MessageI) => {
|
||||
pp({ 'VALIDATING MESSAGE ID': msg.id.slice(0, 11), 'MSG:': msg.message });
|
||||
let valid: boolean;
|
||||
pp({ 'VALIDATING MESSAGE ID': String(msg.roomId).slice(0, 11), 'MSG:': msg.message });
|
||||
let validProof: boolean;
|
||||
getRoomByID(String(msg.roomId))
|
||||
.then((room: RoomI) => {
|
||||
if (!room) {
|
||||
@@ -22,17 +22,17 @@ export function websocketSetup(io: SocketIOServer) {
|
||||
}
|
||||
verifyProof(msg, room)
|
||||
.then((v) => {
|
||||
valid = v;
|
||||
createMessage(String(msg.roomId), msg);
|
||||
validProof = v;
|
||||
const validMessage: boolean = createMessage(String(msg.roomId), msg);
|
||||
if (!validProof || !validMessage) {
|
||||
pp('INVALID MESSAGE', 'warn');
|
||||
return;
|
||||
}
|
||||
io.emit('messageBroadcast', msg);
|
||||
})
|
||||
.catch((err) => {
|
||||
err;
|
||||
});
|
||||
if (!valid) {
|
||||
pp('INVALID MESSAGE', 'warn');
|
||||
return;
|
||||
}
|
||||
})
|
||||
.catch((err) => pp(err, 'error'));
|
||||
});
|
||||
|
||||
103
tests/express.test.ts
Normal file
103
tests/express.test.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
const request = require('supertest');
|
||||
import _app from '../src/server'
|
||||
import { genId } from 'discreetly-interfaces';
|
||||
import { serverConfig } from '../src/config/serverConfig';
|
||||
import { describe } from 'node:test';
|
||||
import expressBasicAuth from 'express-basic-auth';
|
||||
import { transferableAbortController } from 'node:util';
|
||||
|
||||
|
||||
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();
|
||||
|
||||
const joinTest = {
|
||||
code: "coast-filter-noise-feature", //needs to be changed to a valid code
|
||||
idc: "12345678901234567890"
|
||||
}
|
||||
|
||||
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).toBe('{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).toBe(room.roomName)
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("GET /api/rooms", () => {
|
||||
test("It should return all rooms", async () => {
|
||||
const username = 'admin';
|
||||
const password = process.env.PASSWORD;
|
||||
const base64Credentials = Buffer.from(`${username}:${password}`).toString('base64');
|
||||
await request(_app)
|
||||
.get("/api/rooms")
|
||||
.set('Authorization', `Basic ${base64Credentials}`)
|
||||
.then(res => {
|
||||
expect(res.status).toBe(200)
|
||||
expect(res.bodyname).toBe(room.roomName)
|
||||
});
|
||||
});
|
||||
})
|
||||
|
||||
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.status).toBe(401)
|
||||
expect(res.body.length).toBeGreaterThan(0)
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("POST /join", () => {
|
||||
test("It should add a users identity to the rooms the claim code is associated with", async () => {
|
||||
await request(_app)
|
||||
.post("/join")
|
||||
.send(joinTest)
|
||||
.then(res => {
|
||||
expect(res.statusCode).toBe(200)
|
||||
expect(res.body.status).toBe('valid')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -2,7 +2,7 @@
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "node",
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"rootDir": "./",
|
||||
"target": "ES2022",
|
||||
"module": "ES2022",
|
||||
"esModuleInterop": true,
|
||||
|
||||
5
tsconfig.tests.json
Normal file
5
tsconfig.tests.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"strictNullChecks": true,
|
||||
"skipLibCheck": true
|
||||
}
|
||||
Reference in New Issue
Block a user