refactor to inject userservice
This commit is contained in:
+78
-66
@@ -1,80 +1,92 @@
|
||||
import express from "express";
|
||||
import {ExtendedWebSocketServer} from "./websocket";
|
||||
import {RestWebSocket} from "./rest/restWebSocket";
|
||||
import {RestUser} from "./rest/restUser";
|
||||
import {authenticateJwt} from "./rest/middleware/authenticateJwt";
|
||||
import {JwtTokenPropertiesExtractor} from "./rest/jwtTokenPropertiesExtractor";
|
||||
import { ExtendedWebSocketServer } from "./websocket";
|
||||
import { RestWebSocket } from "./rest/restWebSocket";
|
||||
import { RestUser } from "./rest/restUser";
|
||||
import { authenticateJwt } from "./rest/middleware/authenticateJwt";
|
||||
import { JwtTokenPropertiesExtractor } from "./rest/jwtTokenPropertiesExtractor";
|
||||
import cors from "cors";
|
||||
import {SpotifyTokenGenerator} from "./rest/spotifyTokenGenerator";
|
||||
import {RestAuth} from "./rest/auth";
|
||||
import { SpotifyTokenGenerator } from "./rest/spotifyTokenGenerator";
|
||||
import { RestAuth } from "./rest/auth";
|
||||
import { config } from "./config";
|
||||
import cookieParser from 'cookie-parser';
|
||||
import {authLimiter, spotifyLimiter} from "./rest/middleware/rateLimit";
|
||||
import {cookieJwtAuth} from "./rest/middleware/cookieAuth";
|
||||
import { authLimiter, spotifyLimiter } from "./rest/middleware/rateLimit";
|
||||
import { cookieJwtAuth } from "./rest/middleware/cookieAuth";
|
||||
import { UserService } from "./db/services/db/UserService";
|
||||
|
||||
const app = express();
|
||||
const port = config.port;
|
||||
export async function startServer() {
|
||||
const app = express();
|
||||
const port = config.port;
|
||||
|
||||
app.set("trust proxy", 1);
|
||||
app.use(cookieParser());
|
||||
app.set("trust proxy", 1);
|
||||
app.use(cookieParser());
|
||||
|
||||
app.use(cors({
|
||||
origin: config.cors.origin,
|
||||
credentials: config.cors.credentials,
|
||||
}));
|
||||
app.use(cors({
|
||||
origin: config.cors.origin,
|
||||
credentials: config.cors.credentials,
|
||||
}));
|
||||
|
||||
app.use((_req, res, next) => {
|
||||
res.set({
|
||||
"X-DNS-Prefetch-Control": "off",
|
||||
"X-Content-Type-Options": "nosniff",
|
||||
"X-Frame-Options": "DENY",
|
||||
"Referrer-Policy": "no-referrer",
|
||||
"Permissions-Policy": "geolocation=()",
|
||||
app.use((_req, res, next) => {
|
||||
res.set({
|
||||
"X-DNS-Prefetch-Control": "off",
|
||||
"X-Content-Type-Options": "nosniff",
|
||||
"X-Frame-Options": "DENY",
|
||||
"Referrer-Policy": "no-referrer",
|
||||
"Permissions-Policy": "geolocation=()",
|
||||
});
|
||||
next();
|
||||
});
|
||||
next();
|
||||
});
|
||||
|
||||
app.use(express.json({limit: "2mb"}));
|
||||
app.use(express.json({ limit: "2mb" }));
|
||||
app.get("/api/healthz", (_req, res) => res.status(200).send({ status: "ok" }));
|
||||
|
||||
app.get("/api/healthz", (_req, res) => res.status(200).send({status: "ok"}));
|
||||
console.log("Connecting to database and creating UserService...");
|
||||
const userService = await UserService.create();
|
||||
console.log("UserService created successfully.");
|
||||
|
||||
const server = app.listen(port, () => {
|
||||
console.log(`Server is running on port ${port}`);
|
||||
});
|
||||
|
||||
const webSocketServer = new ExtendedWebSocketServer(server);
|
||||
const restWebSocket = new RestWebSocket(webSocketServer);
|
||||
const restUser = new RestUser();
|
||||
const auth = new RestAuth();
|
||||
const jwtTokenPropertiesExtractor = new JwtTokenPropertiesExtractor();
|
||||
const spotify = new SpotifyTokenGenerator();
|
||||
|
||||
app.use("/api/auth", authLimiter, auth.createRouter());
|
||||
|
||||
app.use(cookieJwtAuth);
|
||||
app.use("/api/spotify", authenticateJwt, spotifyLimiter, spotify.createRouter());
|
||||
|
||||
app.use("/api/websocket", authenticateJwt, restWebSocket.createRouter());
|
||||
app.use("/api/user", authenticateJwt, restUser.createRouter());
|
||||
app.use(
|
||||
"/api/jwt",
|
||||
authenticateJwt,
|
||||
jwtTokenPropertiesExtractor.createRouter(),
|
||||
);
|
||||
|
||||
app.use((err: any, _req: express.Request, res: express.Response, _next: express.NextFunction) => {
|
||||
console.error(err);
|
||||
res
|
||||
.status(err?.status || 500)
|
||||
.send({ ok: false, data: {}, error: err?.message || "Internal Server Error" });
|
||||
});
|
||||
|
||||
process.on("SIGTERM", () => {
|
||||
server.close(() => {
|
||||
console.log("HTTP server closed");
|
||||
process.exit(0);
|
||||
const server = app.listen(port, () => {
|
||||
console.log(`Server is running on port ${port}`);
|
||||
});
|
||||
});
|
||||
|
||||
// Export the app for testing purposes
|
||||
export default app; // optional
|
||||
const webSocketServer = new ExtendedWebSocketServer(server, userService);
|
||||
const restWebSocket = new RestWebSocket(webSocketServer);
|
||||
const restUser = new RestUser(userService);
|
||||
const auth = new RestAuth(userService);
|
||||
const jwtTokenPropertiesExtractor = new JwtTokenPropertiesExtractor();
|
||||
const spotify = new SpotifyTokenGenerator();
|
||||
|
||||
app.use("/api/auth", authLimiter, auth.createRouter());
|
||||
|
||||
app.use(cookieJwtAuth);
|
||||
app.use("/api/spotify", authenticateJwt, spotifyLimiter, spotify.createRouter());
|
||||
app.use("/api/websocket", authenticateJwt, restWebSocket.createRouter());
|
||||
app.use("/api/user", authenticateJwt, restUser.createRouter());
|
||||
app.use(
|
||||
"/api/jwt",
|
||||
authenticateJwt,
|
||||
jwtTokenPropertiesExtractor.createRouter(),
|
||||
);
|
||||
|
||||
app.use((err: any, _req: express.Request, res: express.Response, _next: express.NextFunction) => {
|
||||
console.error(err);
|
||||
res
|
||||
.status(err?.status || 500)
|
||||
.send({ ok: false, data: {}, error: err?.message || "Internal Server Error" });
|
||||
});
|
||||
|
||||
process.on("SIGTERM", () => {
|
||||
console.log("SIGTERM signal received: closing HTTP server");
|
||||
server.close(() => {
|
||||
console.log("HTTP server closed");
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
|
||||
return { app, server };
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV !== 'test') {
|
||||
startServer().catch(error => {
|
||||
console.error("Fatal error during server startup:", error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
+9
-5
@@ -9,6 +9,12 @@ import {validateBody, v} from "./middleware/validate";
|
||||
import {ok, badRequest, unauthorized, created, conflict, notFound} from "./utils/responses";
|
||||
|
||||
export class RestAuth {
|
||||
private readonly userService: UserService;
|
||||
|
||||
constructor(userService: UserService) {
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
public createRouter() {
|
||||
const router = express.Router();
|
||||
|
||||
@@ -24,9 +30,8 @@ export class RestAuth {
|
||||
const {username, password, timezone, location} = req.body as {
|
||||
username: string; password: string; timezone: string; location: string;
|
||||
};
|
||||
const userService = await UserService.create();
|
||||
|
||||
if (await userService.existsUserByName(username)) {
|
||||
if (await this.userService.existsUserByName(username)) {
|
||||
return conflict(res, "Username already exists", {field: "username", code: "USERNAME_TAKEN"});
|
||||
}
|
||||
|
||||
@@ -52,7 +57,7 @@ export class RestAuth {
|
||||
location
|
||||
};
|
||||
|
||||
const result = await userService.createUser(newUser);
|
||||
const result = await this.userService.createUser(newUser);
|
||||
return created(res, {user: result});
|
||||
})
|
||||
);
|
||||
@@ -65,8 +70,7 @@ export class RestAuth {
|
||||
}),
|
||||
asyncHandler(async (req, res) => {
|
||||
const {username, password} = req.body as { username: string; password: string };
|
||||
const userService = await UserService.create();
|
||||
const user = await userService.getUserAuthByName(username);
|
||||
const user = await this.userService.getUserAuthByName(username);
|
||||
|
||||
if (!user) {
|
||||
return notFound(res, "User not found", {field: "username", code: "INVALID_USER"});
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
import type {NextFunction, Request, Response} from "express";
|
||||
import {UserService} from "../../db/services/db/UserService";
|
||||
import {notFound} from "../utils/responses";
|
||||
import type { NextFunction, Request, Response } from "express";
|
||||
import type { UserService } from "../../db/services/db/UserService";
|
||||
import { notFound } from "../utils/responses";
|
||||
|
||||
export function isAdmin(userService: UserService) {
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const payload = req.payload;
|
||||
const user = await userService.getUserByUUID(payload.uuid);
|
||||
|
||||
export async function isAdmin(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction) {
|
||||
try {
|
||||
const payload = req.payload;
|
||||
const userService = await UserService.create();
|
||||
const user = await userService.getUserByUUID(payload.uuid);
|
||||
|
||||
if (user && user.config.isAdmin) {
|
||||
next();
|
||||
} else {
|
||||
return notFound(res);
|
||||
if (user?.config?.isAdmin) {
|
||||
return next();
|
||||
} else {
|
||||
return notFound(res);
|
||||
}
|
||||
} catch (error) {
|
||||
return next(error);
|
||||
}
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
+18
-17
@@ -7,18 +7,23 @@ import {badRequest, ok} from "./utils/responses";
|
||||
import {isAdmin} from "./middleware/isAdmin";
|
||||
|
||||
export class RestUser {
|
||||
|
||||
private readonly userService: UserService;
|
||||
|
||||
constructor(userService: UserService) {
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
public createRouter() {
|
||||
const router = express.Router();
|
||||
|
||||
router.get("/",isAdmin, asyncHandler(async (_req, res) => {
|
||||
const userService = await UserService.create();
|
||||
const users = await userService.getAllUsers();
|
||||
router.get("/",isAdmin(this.userService), asyncHandler(async (_req, res) => {
|
||||
const users = await this.userService.getAllUsers();
|
||||
return ok(res, { users });
|
||||
}));
|
||||
|
||||
router.get("/me", asyncHandler(async (req, res) => {
|
||||
const userService = await UserService.create();
|
||||
const user = await userService.getUserByUUID(req.payload.uuid);
|
||||
const user = await this.userService.getUserByUUID(req.payload.uuid);
|
||||
return ok(res, user);
|
||||
}));
|
||||
|
||||
@@ -31,8 +36,7 @@ export class RestUser {
|
||||
expirationDate: { required: true, validator: v.isString({ nonEmpty: true }) },
|
||||
}),
|
||||
asyncHandler(async (req, res) => {
|
||||
const userService = await UserService.create();
|
||||
const user = await userService.getUserByUUID(req.payload.uuid);
|
||||
const user = await this.userService.getUserByUUID(req.payload.uuid);
|
||||
if (!user) {
|
||||
return badRequest(res, "User not found");
|
||||
}
|
||||
@@ -48,19 +52,18 @@ export class RestUser {
|
||||
expirationDate: new Date(expirationDate),
|
||||
};
|
||||
|
||||
await userService.updateUser(user);
|
||||
await this.userService.updateUser(user);
|
||||
return ok(res, { message: "Spotify Config erfolgreich geändert" });
|
||||
})
|
||||
);
|
||||
|
||||
router.delete("/me/spotify", asyncHandler(async (req, res) => {
|
||||
const userService = await UserService.create();
|
||||
const user = await userService.getUserByUUID(req.payload.uuid);
|
||||
const user = await this.userService.getUserByUUID(req.payload.uuid);
|
||||
if (!user) {
|
||||
return badRequest(res, "User not found");
|
||||
}
|
||||
|
||||
const updated = await userService.clearSpotifyConfigByUUID(req.payload.uuid);
|
||||
const updated = await this.userService.clearSpotifyConfigByUUID(req.payload.uuid);
|
||||
return ok(res, { user: updated });
|
||||
}));
|
||||
|
||||
@@ -71,8 +74,7 @@ export class RestUser {
|
||||
passwordConfirmation: { required: true, validator: v.isString({ nonEmpty: true, min: 8 }) },
|
||||
}),
|
||||
asyncHandler(async (req, res) => {
|
||||
const userService = await UserService.create();
|
||||
const user = await userService.getUserByUUID(req.payload.uuid);
|
||||
const user = await this.userService.getUserByUUID(req.payload.uuid);
|
||||
if (!user) {
|
||||
return badRequest(res, "User not found");
|
||||
}
|
||||
@@ -90,7 +92,7 @@ export class RestUser {
|
||||
|
||||
user.password = await PasswordUtils.hashPassword(password);
|
||||
|
||||
await userService.updateUser(user);
|
||||
await this.userService.updateUser(user);
|
||||
return ok(res, { message: "Passwort erfolgreich geändert" });
|
||||
})
|
||||
);
|
||||
@@ -100,11 +102,10 @@ export class RestUser {
|
||||
validateParams({
|
||||
id: { required: true, validator: v.isString({ nonEmpty: true }) },
|
||||
}),
|
||||
isAdmin,
|
||||
isAdmin(this.userService),
|
||||
asyncHandler(async (req, res) => {
|
||||
const userService = await UserService.create();
|
||||
const id = req.params.id;
|
||||
const user = await userService.getUserById(id);
|
||||
const user = await this.userService.getUserById(id);
|
||||
|
||||
if (!user) {
|
||||
return badRequest(res, `Unable to find matching document with id: ${id}`);
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import {ExtendedWebSocket} from "../../../interfaces/extendedWebsocket";
|
||||
import {CustomWebsocketEvent} from "./customWebsocketEvent";
|
||||
import {UserService} from "../../../db/services/db/UserService";
|
||||
|
||||
export abstract class CustomWebsocketEventUserService extends CustomWebsocketEvent {
|
||||
protected readonly userService: UserService;
|
||||
|
||||
public constructor(ws: ExtendedWebSocket, userService: UserService) {
|
||||
super(ws);
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import {CustomWebsocketEvent} from "./customWebsocketEvent";
|
||||
import {WebsocketEventType} from "./websocketEventType";
|
||||
import {SpotifyTokenService} from "../../../db/services/spotifyTokenService";
|
||||
import {UserService} from "../../../db/services/db/UserService";
|
||||
import {getCurrentlyPlaying} from "../../../db/services/spotifyApiService";
|
||||
import {CustomWebsocketEventUserService} from "./customWebsocketEventUserService";
|
||||
|
||||
export const SpotifyAsyncUpdateEvent = "SPOTIFY_UPDATE";
|
||||
|
||||
@@ -26,7 +26,7 @@ export class GetSpotifyUpdatesEvent extends CustomWebsocketEvent {
|
||||
}
|
||||
}
|
||||
|
||||
export class GetSingleSpotifyUpdateEvent extends CustomWebsocketEvent {
|
||||
export class GetSingleSpotifyUpdateEvent extends CustomWebsocketEventUserService {
|
||||
|
||||
event = WebsocketEventType.GET_SINGLE_SPOTIFY_UPDATE;
|
||||
|
||||
@@ -56,8 +56,7 @@ export class GetSingleSpotifyUpdateEvent extends CustomWebsocketEvent {
|
||||
expirationDate: new Date(Date.now() + token.expires_in * 1000),
|
||||
scope: token.scope,
|
||||
};
|
||||
const userService = await UserService.create();
|
||||
await userService.updateUser(user);
|
||||
await this.userService.updateUser(user);
|
||||
console.log("Token refreshed and database updated");
|
||||
}
|
||||
const musicData = await getCurrentlyPlaying(user.spotifyConfig!.accessToken);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import {WebsocketEventType} from "./websocketEventType";
|
||||
import {CustomWebsocketEvent} from "./customWebsocketEvent";
|
||||
import {IUser} from "../../../db/models/user";
|
||||
import {UserService} from "../../../db/services/db/UserService";
|
||||
import {CustomWebsocketEventUserService} from "./customWebsocketEventUserService";
|
||||
|
||||
export const UserAsyncUpdateEvent = "USER_UPDATE";
|
||||
|
||||
export class UpdateUserEvent extends CustomWebsocketEvent {
|
||||
export class UpdateUserEvent extends CustomWebsocketEventUserService {
|
||||
event = WebsocketEventType.UPDATE_USER;
|
||||
|
||||
handler = async () => {
|
||||
@@ -16,8 +16,7 @@ export class UpdateUserEvent extends CustomWebsocketEvent {
|
||||
}
|
||||
|
||||
this.ws.asyncUpdates.set(UserAsyncUpdateEvent, setInterval(async () => {
|
||||
const userService = await UserService.create();
|
||||
const user = await userService.getUserByUUID(this.ws.payload.uuid);
|
||||
const user = await this.userService.getUserByUUID(this.ws.payload.uuid);
|
||||
this.ws.emit(WebsocketEventType.UPDATE_USER_SINGLE, user);
|
||||
}, 1000 * 15));
|
||||
|
||||
|
||||
@@ -9,18 +9,19 @@ import {StopWeatherUpdatesEvent} from "./stopWeatherUpdatesEvent";
|
||||
import {UpdateUserEvent, UpdateUserSingleEvent} from "./updateUserEvent";
|
||||
import {CustomWebsocketEvent} from "./customWebsocketEvent";
|
||||
import {StopUpdateUserEvent} from "./stopUpdateUserEvent";
|
||||
import {UserService} from "../../../db/services/db/UserService";
|
||||
|
||||
export function getEventListeners(ws: ExtendedWebSocket): CustomWebsocketEvent[] {
|
||||
export function getEventListeners(ws: ExtendedWebSocket, userService: UserService): CustomWebsocketEvent[] {
|
||||
return [
|
||||
new GetStateEvent(ws),
|
||||
new GetSettingsEvent(ws),
|
||||
new GetSingleSpotifyUpdateEvent(ws),
|
||||
new GetSingleSpotifyUpdateEvent(ws, userService),
|
||||
new GetSpotifyUpdatesEvent(ws),
|
||||
new StopSpotifyUpdatesEvent(ws),
|
||||
new GetSingleWeatherUpdateEvent(ws),
|
||||
new GetWeatherUpdatesEvent(ws),
|
||||
new StopWeatherUpdatesEvent(ws),
|
||||
new UpdateUserEvent(ws),
|
||||
new UpdateUserEvent(ws, userService),
|
||||
new UpdateUserSingleEvent(ws),
|
||||
new StopUpdateUserEvent(ws),
|
||||
new ErrorEvent(ws)
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import {ExtendedWebSocket} from "../../interfaces/extendedWebsocket";
|
||||
import {CustomWebsocketEvent} from "./websocketCustomEvents/customWebsocketEvent";
|
||||
import {UserService} from "../../db/services/db/UserService";
|
||||
import {getEventListeners} from "./websocketCustomEvents/websocketEventUtils";
|
||||
|
||||
export class WebsocketEventHandler {
|
||||
constructor(private webSocket: ExtendedWebSocket) {
|
||||
constructor(private webSocket: ExtendedWebSocket, private userService: UserService) {
|
||||
}
|
||||
|
||||
public enableErrorEvent() {
|
||||
@@ -42,8 +44,12 @@ export class WebsocketEventHandler {
|
||||
);
|
||||
}
|
||||
|
||||
public registerCustomEvent(customWebsocketEvent: CustomWebsocketEvent) {
|
||||
// bind needed?
|
||||
public registerCustomEvents() {
|
||||
const events = getEventListeners(this.webSocket, this.userService);
|
||||
events.forEach(this.registerCustomEvent, this);
|
||||
}
|
||||
|
||||
private registerCustomEvent(customWebsocketEvent: CustomWebsocketEvent) {
|
||||
this.webSocket.on(customWebsocketEvent.event, customWebsocketEvent.handler.bind(customWebsocketEvent));
|
||||
}
|
||||
|
||||
|
||||
@@ -6,9 +6,11 @@ import {UserService} from "../../db/services/db/UserService";
|
||||
|
||||
export class WebsocketServerEventHandler {
|
||||
private readonly heartbeat: () => void;
|
||||
private readonly userService: UserService;
|
||||
|
||||
constructor(private webSocketServer: WebSocketServer) {
|
||||
constructor(private webSocketServer: WebSocketServer, userService: UserService) {
|
||||
this.heartbeat = heartbeat(this.webSocketServer);
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
public enableConnectionEvent(
|
||||
@@ -17,7 +19,7 @@ export class WebsocketServerEventHandler {
|
||||
this.webSocketServer.on(
|
||||
"connection",
|
||||
async (ws: ExtendedWebSocket, request: ExtendedIncomingMessage) => {
|
||||
const user = await (await UserService.create()).getUserByUUID(request.payload.uuid);
|
||||
const user = await this.userService.getUserByUUID(request.payload.uuid);
|
||||
|
||||
ws.user = user!;
|
||||
|
||||
|
||||
+8
-5
@@ -5,18 +5,21 @@ import {ExtendedWebSocket} from "./interfaces/extendedWebsocket";
|
||||
import {DecodedToken} from "./interfaces/decodedToken";
|
||||
import {WebsocketServerEventHandler} from "./utils/websocket/websocketServerEventHandler";
|
||||
import {WebsocketEventHandler} from "./utils/websocket/websocketEventHandler";
|
||||
import {getEventListeners} from "./utils/websocket/websocketCustomEvents/websocketEventUtils";
|
||||
import {WebsocketEventType} from "./utils/websocket/websocketCustomEvents/websocketEventType";
|
||||
import {UserService} from "./db/services/db/UserService";
|
||||
|
||||
export class ExtendedWebSocketServer {
|
||||
private readonly _wss: WebSocketServer;
|
||||
private readonly userService: UserService;
|
||||
|
||||
constructor(server: Server) {
|
||||
constructor(server: Server, userService: UserService) {
|
||||
this._wss = new WebSocketServer({
|
||||
server,
|
||||
verifyClient: (info, callback) => verifyClient(info.req, callback),
|
||||
});
|
||||
|
||||
this.userService = userService;
|
||||
|
||||
this.setupWebSocket();
|
||||
}
|
||||
|
||||
@@ -50,9 +53,9 @@ export class ExtendedWebSocketServer {
|
||||
}
|
||||
|
||||
private setupWebSocket() {
|
||||
const serverEventHandler = new WebsocketServerEventHandler(this.wss);
|
||||
const serverEventHandler = new WebsocketServerEventHandler(this.wss, this.userService);
|
||||
serverEventHandler.enableConnectionEvent((ws) => {
|
||||
const socketEventHandler = new WebsocketEventHandler(ws);
|
||||
const socketEventHandler = new WebsocketEventHandler(ws, this.userService);
|
||||
|
||||
console.log("WebSocket client connected");
|
||||
|
||||
@@ -61,7 +64,7 @@ export class ExtendedWebSocketServer {
|
||||
socketEventHandler.enableMessageEvent();
|
||||
|
||||
// Register custom events
|
||||
getEventListeners(ws).forEach(socketEventHandler.registerCustomEvent, socketEventHandler);
|
||||
socketEventHandler.registerCustomEvents();
|
||||
|
||||
socketEventHandler.enableDisconnectEvent(() => {
|
||||
console.log("User disconnected");
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { describe, it, expect, vi, beforeAll, beforeEach } from "vitest";
|
||||
import { describe, it, expect, vi, beforeAll, afterAll, beforeEach } from "vitest";
|
||||
import request from "supertest";
|
||||
import express from "express";
|
||||
import {authLimiter} from "../src/rest/middleware/rateLimit";
|
||||
import http from "http";
|
||||
import { authLimiter } from "../src/rest/middleware/rateLimit";
|
||||
|
||||
vi.mock("../src/db/services/db/database.service", () => ({
|
||||
connectToDatabase: vi.fn().mockResolvedValue(undefined),
|
||||
@@ -20,7 +21,6 @@ vi.mock("../src/config", () => ({
|
||||
|
||||
vi.mock("../src/rest/middleware/rateLimit", async (importOriginal) => {
|
||||
const original = await importOriginal<typeof import("../src/rest/middleware/rateLimit")>();
|
||||
|
||||
return {
|
||||
...original,
|
||||
authLimiter: vi.fn((req, res, next) => next()),
|
||||
@@ -29,12 +29,25 @@ vi.mock("../src/rest/middleware/rateLimit", async (importOriginal) => {
|
||||
});
|
||||
|
||||
let app: express.Application;
|
||||
let server: http.Server;
|
||||
|
||||
beforeAll(async () => {
|
||||
const indexModule = await import("../src/index");
|
||||
app = indexModule.default;
|
||||
const { startServer } = await import("../src/index");
|
||||
|
||||
const instances = await startServer();
|
||||
app = instances.app;
|
||||
server = instances.server;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await new Promise<void>((resolve) => {
|
||||
if (server) {
|
||||
server.close(() => resolve());
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("Express App Integration Test", () => {
|
||||
|
||||
@@ -63,24 +76,21 @@ describe("Express App Integration Test", () => {
|
||||
expect(response.headers['referrer-policy']).toBe('no-referrer');
|
||||
});
|
||||
|
||||
it("should protect a route with the authenticateJwt middleware", async () => {
|
||||
const response = await request(app).get("/api/user/me").expect(401);
|
||||
expect(response.text).toBe("Unauthorized");
|
||||
it("should protect a route with authentication middleware", async () => {
|
||||
await request(app).get("/api/user/me").expect(401);
|
||||
});
|
||||
|
||||
it("should apply the auth rate limiter to an auth route", async () => {
|
||||
await request(app).post("/api/auth/login").send({}).expect(400);
|
||||
await request(app).post("/api/auth/login").send({});
|
||||
expect(authLimiter).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("should NOT apply the auth rate limiter to a non-auth route", async () => {
|
||||
await request(app).get("/api/healthz").expect(200);
|
||||
|
||||
expect(authLimiter).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should return a 404 for an unknown route", async () => {
|
||||
await request(app).get("/api/this-route-does-not-exist").expect(404);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -2,7 +2,6 @@ import {describe, it, expect, vi, beforeEach, afterEach} from "vitest";
|
||||
import request from "supertest";
|
||||
import express from "express";
|
||||
import {RestAuth} from "../../src/rest/auth";
|
||||
import {UserService} from "../../src/db/services/db/UserService";
|
||||
import {JwtAuthenticator} from "../../src/utils/jwtAuthenticator";
|
||||
import {PasswordUtils} from "../../src/utils/passwordUtils";
|
||||
import {createMockJwtAuthenticator, createMockUserService, createPublicTestApp} from "../helpers/testSetup";
|
||||
@@ -40,7 +39,6 @@ describe("RestAuth", () => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
mockUserService = createMockUserService();
|
||||
vi.mocked(UserService.create).mockResolvedValue(mockUserService);
|
||||
|
||||
mockPasswordUtils = vi.mocked(PasswordUtils);
|
||||
mockCrypto = vi.mocked(crypto);
|
||||
@@ -48,7 +46,7 @@ describe("RestAuth", () => {
|
||||
mockJwtAuthenticator = createMockJwtAuthenticator();
|
||||
vi.mocked(JwtAuthenticator).mockImplementation(() => mockJwtAuthenticator);
|
||||
|
||||
const restAuth = new RestAuth();
|
||||
const restAuth = new RestAuth(mockUserService);
|
||||
app = createPublicTestApp(restAuth.createRouter(), "/auth");
|
||||
|
||||
process.env.SECRET_KEY = "test-secret-key";
|
||||
|
||||
@@ -2,7 +2,6 @@ import { describe, it, expect, vi, beforeEach, afterEach, type Mocked } from "vi
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { isAdmin } from "../../../src/rest/middleware/isAdmin";
|
||||
import { createMockUserService } from "../../helpers/testSetup";
|
||||
import { UserService } from "../../../src/db/services/db/UserService";
|
||||
import { notFound } from "../../../src/rest/utils/responses";
|
||||
|
||||
vi.mock("../../../src/db/services/db/UserService", () => ({
|
||||
@@ -27,7 +26,6 @@ describe("isAdmin middleware", () => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
mockedUserService = createMockUserService();
|
||||
vi.mocked(UserService.create).mockResolvedValue(mockedUserService);
|
||||
|
||||
req = {
|
||||
payload: { uuid, username: "username", id: ""}
|
||||
@@ -50,9 +48,8 @@ describe("isAdmin middleware", () => {
|
||||
const mockUser = { uuid, config: { isAdmin: true } };
|
||||
mockedUserService.getUserByUUID.mockResolvedValue(mockUser);
|
||||
|
||||
await isAdmin(req as Request, res, next);
|
||||
await isAdmin(mockedUserService)(req as Request, res, next);
|
||||
|
||||
expect(UserService.create).toHaveBeenCalledOnce();
|
||||
expect(mockedUserService.getUserByUUID).toHaveBeenCalledWith(uuid);
|
||||
expect(next).toHaveBeenCalledOnce();
|
||||
expect(res.status).not.toHaveBeenCalled();
|
||||
@@ -66,7 +63,7 @@ describe("isAdmin middleware", () => {
|
||||
const mockUser = { uuid, config: { isAdmin: false } };
|
||||
mockedUserService.getUserByUUID.mockResolvedValue(mockUser);
|
||||
|
||||
await isAdmin(req as Request, res, next);
|
||||
await isAdmin(mockedUserService)(req as Request, res, next);
|
||||
|
||||
expect(mockedUserService.getUserByUUID).toHaveBeenCalledWith(uuid);
|
||||
expect(notFound).toHaveBeenCalledWith(res);
|
||||
@@ -76,7 +73,7 @@ describe("isAdmin middleware", () => {
|
||||
it("should call notFound if user does not exist", async () => {
|
||||
mockedUserService.getUserByUUID.mockResolvedValue(null);
|
||||
|
||||
await isAdmin(req as Request, res, next);
|
||||
await isAdmin(mockedUserService)(req as Request, res, next);
|
||||
|
||||
expect(mockedUserService.getUserByUUID).toHaveBeenCalledWith(uuid);
|
||||
expect(notFound).toHaveBeenCalledWith(res);
|
||||
@@ -87,7 +84,7 @@ describe("isAdmin middleware", () => {
|
||||
const dbError = new Error("Database error");
|
||||
mockedUserService.getUserByUUID.mockRejectedValue(dbError);
|
||||
|
||||
await isAdmin(req as Request, res, next);
|
||||
await isAdmin(mockedUserService)(req as Request, res, next);
|
||||
|
||||
expect(next).toHaveBeenCalledWith(expect.objectContaining({ message: 'Database error' }));
|
||||
});
|
||||
|
||||
+35
-34
@@ -2,7 +2,7 @@ import {describe, it, expect, vi, beforeEach, afterEach} from "vitest";
|
||||
import request from "supertest";
|
||||
|
||||
import {RestUser} from "../../src/rest/restUser";
|
||||
import {setupTestEnvironment, type TestEnvironment} from "../helpers/testSetup";
|
||||
import {createMockUserService, setupTestEnvironment, type TestEnvironment} from "../helpers/testSetup";
|
||||
|
||||
vi.mock("../../src/db/services/db/UserService", () => ({
|
||||
UserService: {
|
||||
@@ -24,11 +24,12 @@ describe("RestUser", () => {
|
||||
const requestingUserUUID = "test-user-uuid";
|
||||
const adminUser = { uuid: requestingUserUUID, config: { isAdmin: true } };
|
||||
const nonAdminUser = { uuid: requestingUserUUID, config: { isAdmin: false } };
|
||||
const mockedUserService = createMockUserService();
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
const restUser = new RestUser();
|
||||
const restUser = new RestUser(mockedUserService);
|
||||
testEnv = setupTestEnvironment(restUser.createRouter(), "/user");
|
||||
});
|
||||
|
||||
@@ -44,14 +45,14 @@ describe("RestUser", () => {
|
||||
uuid: "test-user-uuid"
|
||||
};
|
||||
|
||||
testEnv.mockUserService.getUserByUUID.mockResolvedValue(mockUser);
|
||||
mockedUserService.getUserByUUID.mockResolvedValue(mockUser);
|
||||
|
||||
const response = await request(testEnv.app)
|
||||
.get("/user/me")
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.data).toEqual(mockUser);
|
||||
expect(testEnv.mockUserService.getUserByUUID).toHaveBeenCalledWith("test-user-uuid");
|
||||
expect(mockedUserService.getUserByUUID).toHaveBeenCalledWith("test-user-uuid");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -71,8 +72,8 @@ describe("RestUser", () => {
|
||||
spotifyConfig: null
|
||||
};
|
||||
|
||||
testEnv.mockUserService.getUserByUUID.mockResolvedValue(mockUser);
|
||||
testEnv.mockUserService.updateUser.mockResolvedValue(mockUser);
|
||||
mockedUserService.getUserByUUID.mockResolvedValue(mockUser);
|
||||
mockedUserService.updateUser.mockResolvedValue(mockUser);
|
||||
|
||||
const response = await request(testEnv.app)
|
||||
.put("/user/me/spotify")
|
||||
@@ -80,8 +81,8 @@ describe("RestUser", () => {
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.data.message).toBe("Spotify Config erfolgreich geändert");
|
||||
expect(testEnv.mockUserService.getUserByUUID).toHaveBeenCalledWith("test-user-uuid");
|
||||
expect(testEnv.mockUserService.updateUser).toHaveBeenCalledWith({
|
||||
expect(mockedUserService.getUserByUUID).toHaveBeenCalledWith("test-user-uuid");
|
||||
expect(mockedUserService.updateUser).toHaveBeenCalledWith({
|
||||
...mockUser,
|
||||
spotifyConfig: {
|
||||
accessToken: "access-token-123",
|
||||
@@ -93,7 +94,7 @@ describe("RestUser", () => {
|
||||
});
|
||||
|
||||
it("should return bad request when user not found", async () => {
|
||||
testEnv.mockUserService.getUserByUUID.mockResolvedValue(null);
|
||||
mockedUserService.getUserByUUID.mockResolvedValue(null);
|
||||
|
||||
const response = await request(testEnv.app)
|
||||
.put("/user/me/spotify")
|
||||
@@ -176,20 +177,20 @@ describe("RestUser", () => {
|
||||
spotifyConfig: null
|
||||
};
|
||||
|
||||
testEnv.mockUserService.getUserByUUID.mockResolvedValue(mockUser);
|
||||
testEnv.mockUserService.clearSpotifyConfigByUUID.mockResolvedValue(updatedUser);
|
||||
mockedUserService.getUserByUUID.mockResolvedValue(mockUser);
|
||||
mockedUserService.clearSpotifyConfigByUUID.mockResolvedValue(updatedUser);
|
||||
|
||||
const response = await request(testEnv.app)
|
||||
.delete("/user/me/spotify")
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.data.user).toEqual(updatedUser);
|
||||
expect(testEnv.mockUserService.getUserByUUID).toHaveBeenCalledWith("test-user-uuid");
|
||||
expect(testEnv.mockUserService.clearSpotifyConfigByUUID).toHaveBeenCalledWith("test-user-uuid");
|
||||
expect(mockedUserService.getUserByUUID).toHaveBeenCalledWith("test-user-uuid");
|
||||
expect(mockedUserService.clearSpotifyConfigByUUID).toHaveBeenCalledWith("test-user-uuid");
|
||||
});
|
||||
|
||||
it("should return bad request when user not found", async () => {
|
||||
testEnv.mockUserService.getUserByUUID.mockResolvedValue(null);
|
||||
mockedUserService.getUserByUUID.mockResolvedValue(null);
|
||||
|
||||
const response = await request(testEnv.app)
|
||||
.delete("/user/me/spotify")
|
||||
@@ -215,10 +216,10 @@ describe("RestUser", () => {
|
||||
password: "old-hashed-password"
|
||||
};
|
||||
|
||||
testEnv.mockUserService.getUserByUUID.mockResolvedValue(mockUser);
|
||||
mockedUserService.getUserByUUID.mockResolvedValue(mockUser);
|
||||
vi.mocked(PasswordUtils.validatePassword).mockReturnValue({valid: true});
|
||||
vi.mocked(PasswordUtils.hashPassword).mockResolvedValue("new-hashed-password");
|
||||
testEnv.mockUserService.updateUser.mockResolvedValue(mockUser);
|
||||
mockedUserService.updateUser.mockResolvedValue(mockUser);
|
||||
|
||||
const response = await request(testEnv.app)
|
||||
.put("/user/me/password")
|
||||
@@ -228,14 +229,14 @@ describe("RestUser", () => {
|
||||
expect(response.body.data.message).toBe("Passwort erfolgreich geändert");
|
||||
expect(PasswordUtils.validatePassword).toHaveBeenCalledWith("newpassword123");
|
||||
expect(PasswordUtils.hashPassword).toHaveBeenCalledWith("newpassword123");
|
||||
expect(testEnv.mockUserService.updateUser).toHaveBeenCalledWith({
|
||||
expect(mockedUserService.updateUser).toHaveBeenCalledWith({
|
||||
...mockUser,
|
||||
password: "new-hashed-password"
|
||||
});
|
||||
});
|
||||
|
||||
it("should return bad request when user not found", async () => {
|
||||
testEnv.mockUserService.getUserByUUID.mockResolvedValue(null);
|
||||
mockedUserService.getUserByUUID.mockResolvedValue(null);
|
||||
|
||||
const response = await request(testEnv.app)
|
||||
.put("/user/me/password")
|
||||
@@ -252,7 +253,7 @@ describe("RestUser", () => {
|
||||
uuid: "test-user-uuid"
|
||||
};
|
||||
|
||||
testEnv.mockUserService.getUserByUUID.mockResolvedValue(mockUser);
|
||||
mockedUserService.getUserByUUID.mockResolvedValue(mockUser);
|
||||
|
||||
const invalidData = {
|
||||
password: "newpassword123",
|
||||
@@ -276,7 +277,7 @@ describe("RestUser", () => {
|
||||
uuid: "test-user-uuid"
|
||||
};
|
||||
|
||||
testEnv.mockUserService.getUserByUUID.mockResolvedValue(mockUser);
|
||||
mockedUserService.getUserByUUID.mockResolvedValue(mockUser);
|
||||
vi.mocked(PasswordUtils.validatePassword).mockReturnValue({
|
||||
valid: false,
|
||||
message: "Password too weak"
|
||||
@@ -331,7 +332,7 @@ describe("RestUser", () => {
|
||||
|
||||
describe("when user is an admin", () => {
|
||||
beforeEach(() => {
|
||||
testEnv.mockUserService.getUserByUUID.mockResolvedValue(adminUser);
|
||||
mockedUserService.getUserByUUID.mockResolvedValue(adminUser);
|
||||
});
|
||||
|
||||
it("should return all users", async () => {
|
||||
@@ -339,19 +340,19 @@ describe("RestUser", () => {
|
||||
{id: "1", name: "user1", uuid: "uuid1"},
|
||||
{id: "2", name: "user2", uuid: "uuid2"}
|
||||
];
|
||||
testEnv.mockUserService.getAllUsers.mockResolvedValue(mockUsers);
|
||||
mockedUserService.getAllUsers.mockResolvedValue(mockUsers);
|
||||
|
||||
const response = await request(testEnv.app)
|
||||
.get("/user/")
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.data.users).toEqual(mockUsers);
|
||||
expect(testEnv.mockUserService.getUserByUUID).toHaveBeenCalledWith(requestingUserUUID);
|
||||
expect(testEnv.mockUserService.getAllUsers).toHaveBeenCalled();
|
||||
expect(mockedUserService.getUserByUUID).toHaveBeenCalledWith(requestingUserUUID);
|
||||
expect(mockedUserService.getAllUsers).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should handle empty user list", async () => {
|
||||
testEnv.mockUserService.getAllUsers.mockResolvedValue([]);
|
||||
mockedUserService.getAllUsers.mockResolvedValue([]);
|
||||
|
||||
const response = await request(testEnv.app)
|
||||
.get("/user/")
|
||||
@@ -363,7 +364,7 @@ describe("RestUser", () => {
|
||||
|
||||
describe("when user is not an admin", () => {
|
||||
it("should return 404 Not Found if user is not an admin", async () => {
|
||||
testEnv.mockUserService.getUserByUUID.mockResolvedValue(nonAdminUser);
|
||||
mockedUserService.getUserByUUID.mockResolvedValue(nonAdminUser);
|
||||
|
||||
await request(testEnv.app)
|
||||
.get("/user/")
|
||||
@@ -371,7 +372,7 @@ describe("RestUser", () => {
|
||||
});
|
||||
|
||||
it("should return 404 Not Found if user does not exist", async () => {
|
||||
testEnv.mockUserService.getUserByUUID.mockResolvedValue(null);
|
||||
mockedUserService.getUserByUUID.mockResolvedValue(null);
|
||||
|
||||
await request(testEnv.app)
|
||||
.get("/user/")
|
||||
@@ -390,23 +391,23 @@ describe("RestUser", () => {
|
||||
|
||||
describe("when user is an admin", () => {
|
||||
beforeEach(() => {
|
||||
testEnv.mockUserService.getUserByUUID.mockResolvedValue(adminUser);
|
||||
mockedUserService.getUserByUUID.mockResolvedValue(adminUser);
|
||||
});
|
||||
|
||||
it("should return user by id", async () => {
|
||||
testEnv.mockUserService.getUserById.mockResolvedValue(mockUser);
|
||||
mockedUserService.getUserById.mockResolvedValue(mockUser);
|
||||
|
||||
const response = await request(testEnv.app)
|
||||
.get(`/user/${specificUserId}`)
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.data).toEqual(mockUser);
|
||||
expect(testEnv.mockUserService.getUserByUUID).toHaveBeenCalledWith(requestingUserUUID);
|
||||
expect(testEnv.mockUserService.getUserById).toHaveBeenCalledWith(specificUserId);
|
||||
expect(mockedUserService.getUserByUUID).toHaveBeenCalledWith(requestingUserUUID);
|
||||
expect(mockedUserService.getUserById).toHaveBeenCalledWith(specificUserId);
|
||||
});
|
||||
|
||||
it("should return bad request when target user is not found", async () => {
|
||||
testEnv.mockUserService.getUserById.mockResolvedValue(null);
|
||||
mockedUserService.getUserById.mockResolvedValue(null);
|
||||
|
||||
const response = await request(testEnv.app)
|
||||
.get(`/user/nonexistent-id`)
|
||||
@@ -418,7 +419,7 @@ describe("RestUser", () => {
|
||||
|
||||
describe("when user is not an admin", () => {
|
||||
it("should return 404 Not Found if user is not an admin", async () => {
|
||||
testEnv.mockUserService.getUserByUUID.mockResolvedValue(nonAdminUser);
|
||||
mockedUserService.getUserByUUID.mockResolvedValue(nonAdminUser);
|
||||
|
||||
await request(testEnv.app)
|
||||
.get(`/user/${specificUserId}`)
|
||||
@@ -426,7 +427,7 @@ describe("RestUser", () => {
|
||||
});
|
||||
|
||||
it("should return 404 Not Found if user does not exist", async () => {
|
||||
testEnv.mockUserService.getUserByUUID.mockResolvedValue(null);
|
||||
mockedUserService.getUserByUUID.mockResolvedValue(null);
|
||||
|
||||
await request(testEnv.app)
|
||||
.get(`/user/${specificUserId}`)
|
||||
|
||||
@@ -2,6 +2,10 @@ import { describe, it, expect, vi, beforeEach, type Mocked } from "vitest";
|
||||
import { getEventListeners } from "../../../../src/utils/websocket/websocketCustomEvents/websocketEventUtils";
|
||||
import { WebsocketEventType } from "../../../../src/utils/websocket/websocketCustomEvents/websocketEventType";
|
||||
import { CustomWebsocketEvent } from "../../../../src/utils/websocket/websocketCustomEvents/customWebsocketEvent";
|
||||
import type { UserService } from "../../../../src/db/services/db/UserService";
|
||||
import {
|
||||
CustomWebsocketEventUserService
|
||||
} from "../../../../src/utils/websocket/websocketCustomEvents/customWebsocketEventUserService";
|
||||
|
||||
type MockWs = {
|
||||
user: {
|
||||
@@ -9,10 +13,16 @@ type MockWs = {
|
||||
lastState: { global: { mode: string; brightness: number } };
|
||||
};
|
||||
send: Mocked<(data: any, options: { binary: boolean }) => void>;
|
||||
on: Mocked<(event: string, listener: (...args: any[]) => void) => void>;
|
||||
emit: Mocked<(event: string, ...args: any[]) => void>;
|
||||
};
|
||||
|
||||
type MockUserService = Mocked<UserService>;
|
||||
|
||||
|
||||
describe("websocketEventUtils.getEventListeners", () => {
|
||||
let mockWs: MockWs;
|
||||
let mockUserService: MockUserService;
|
||||
let listeners: CustomWebsocketEvent[];
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -22,8 +32,16 @@ describe("websocketEventUtils.getEventListeners", () => {
|
||||
lastState: { global: { mode: "idle", brightness: 42 } },
|
||||
},
|
||||
send: vi.fn(),
|
||||
on: vi.fn(),
|
||||
emit: vi.fn(),
|
||||
};
|
||||
listeners = getEventListeners(mockWs as any);
|
||||
|
||||
mockUserService = {
|
||||
getUserByUUID: vi.fn(),
|
||||
updateUser: vi.fn(),
|
||||
} as any;
|
||||
|
||||
listeners = getEventListeners(mockWs as any, mockUserService);
|
||||
});
|
||||
|
||||
it("should return an array of event listener objects", () => {
|
||||
@@ -34,6 +52,9 @@ describe("websocketEventUtils.getEventListeners", () => {
|
||||
expect(listener).toHaveProperty("event");
|
||||
expect(listener).toHaveProperty("handler");
|
||||
expect(typeof listener.handler).toBe("function");
|
||||
if (typeof listener === typeof CustomWebsocketEventUserService){
|
||||
expect(listener).toHaveProperty("userService", mockUserService);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -2,10 +2,12 @@ import { describe, it, expect, vi, beforeEach, afterEach, type Mocked } from "vi
|
||||
import { WebsocketEventHandler } from "../../../src/utils/websocket/websocketEventHandler";
|
||||
import { ExtendedWebSocket } from "../../../src/interfaces/extendedWebsocket";
|
||||
import { CustomWebsocketEvent } from "../../../src/utils/websocket/websocketCustomEvents/customWebsocketEvent";
|
||||
import {UserService} from "../../../src/db/services/db/UserService";
|
||||
|
||||
describe("WebsocketEventHandler", () => {
|
||||
let mockWebSocket: Mocked<ExtendedWebSocket>;
|
||||
let websocketEventHandler: WebsocketEventHandler;
|
||||
let mockUserService: Mocked<UserService>;
|
||||
let registeredHandlers: Map<string, (...args: any[]) => void>;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -27,7 +29,10 @@ describe("WebsocketEventHandler", () => {
|
||||
asyncUpdates: new Map([["update1", 123], ["update2", 456]]),
|
||||
} as unknown as Mocked<ExtendedWebSocket>;
|
||||
|
||||
websocketEventHandler = new WebsocketEventHandler(mockWebSocket);
|
||||
// not used in this test
|
||||
mockUserService = {} as Mocked<UserService>;
|
||||
|
||||
websocketEventHandler = new WebsocketEventHandler(mockWebSocket, mockUserService);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -108,6 +113,7 @@ describe("WebsocketEventHandler", () => {
|
||||
// @ts-ignore
|
||||
const bindSpy = vi.spyOn(customEvent.handler, "bind");
|
||||
|
||||
// @ts-ignore - Access to private method for testing purposes
|
||||
websocketEventHandler.registerCustomEvent(customEvent);
|
||||
|
||||
expect(mockWebSocket.on).toHaveBeenCalledWith("custom_event", expect.any(Function));
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import { describe, it, expect, vi, beforeEach, afterEach, type Mocked } from "vitest";
|
||||
import { WebsocketServerEventHandler } from "../../../src/utils/websocket/websocketServerEventHandler";
|
||||
import type { UserService } from "../../../src/db/services/db/UserService";
|
||||
|
||||
const { heartbeatSpy, getUserByUUID } = vi.hoisted(() => ({
|
||||
heartbeatSpy: vi.fn(),
|
||||
getUserByUUID: vi.fn(),
|
||||
const heartbeatSpy = vi.fn();
|
||||
vi.mock("../../../src/utils/websocket/websocketServerHeartbeatInterval", () => ({
|
||||
heartbeat: () => heartbeatSpy,
|
||||
}));
|
||||
|
||||
vi.mock("../../../src/utils/websocket/websocketServerHeartbeatInterval", () => {
|
||||
return {
|
||||
heartbeat: () => heartbeatSpy,
|
||||
};
|
||||
});
|
||||
|
||||
const userObj = {
|
||||
name: "tester",
|
||||
uuid: "uuid-1",
|
||||
@@ -19,38 +15,27 @@ const userObj = {
|
||||
lastState: { global: { mode: "idle", brightness: 50 } },
|
||||
};
|
||||
|
||||
vi.mock("../../../src/db/services/db/UserService", () => {
|
||||
return {
|
||||
UserService: {
|
||||
create: vi.fn().mockResolvedValue({
|
||||
getUserByUUID,
|
||||
}),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
class FakeWSS {
|
||||
clients = new Set<any>();
|
||||
handlers = new Map<string, Function>();
|
||||
on(event: string, handler: Function) {
|
||||
this.handlers.set(event, handler);
|
||||
}
|
||||
emit(event: string, ...args: any[]) {
|
||||
const h = this.handlers.get(event);
|
||||
if (h) h(...args);
|
||||
this.handlers.get(event)?.(...args);
|
||||
}
|
||||
}
|
||||
|
||||
import { WebsocketServerEventHandler } from "../../../src/utils/websocket/websocketServerEventHandler";
|
||||
|
||||
describe("WebsocketServerEventHandler", () => {
|
||||
let wss: FakeWSS;
|
||||
let mockUserService: Mocked<UserService>; // Variable für unseren Mock-Service
|
||||
|
||||
beforeEach(() => {
|
||||
wss = new FakeWSS();
|
||||
heartbeatSpy.mockReset();
|
||||
getUserByUUID.mockReset();
|
||||
getUserByUUID.mockResolvedValue(userObj);
|
||||
heartbeatSpy.mockClear();
|
||||
|
||||
mockUserService = {
|
||||
getUserByUUID: vi.fn().mockResolvedValue(userObj),
|
||||
} as any;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -58,12 +43,10 @@ describe("WebsocketServerEventHandler", () => {
|
||||
});
|
||||
|
||||
it("enableConnectionEvent sets user/payload/isAlive/asyncUpdates and calls callback", async () => {
|
||||
const handler = new WebsocketServerEventHandler(wss as any);
|
||||
const handler = new WebsocketServerEventHandler(wss as any, mockUserService);
|
||||
const cb = vi.fn();
|
||||
|
||||
const done = new Promise<void>((resolve) => {
|
||||
cb.mockImplementation(() => resolve());
|
||||
});
|
||||
const done = new Promise<void>((resolve) => cb.mockImplementation(() => resolve()));
|
||||
|
||||
handler.enableConnectionEvent(cb);
|
||||
|
||||
@@ -74,7 +57,7 @@ describe("WebsocketServerEventHandler", () => {
|
||||
|
||||
await done;
|
||||
|
||||
expect(getUserByUUID).toHaveBeenCalledWith("uuid-1");
|
||||
expect(mockUserService.getUserByUUID).toHaveBeenCalledWith("uuid-1");
|
||||
expect(ws.user).toEqual(userObj);
|
||||
expect(ws.payload).toEqual(req.payload);
|
||||
expect(ws.isAlive).toBe(true);
|
||||
@@ -84,7 +67,7 @@ describe("WebsocketServerEventHandler", () => {
|
||||
|
||||
it("enableHeartbeat starts interval and calls heartbeat()", () => {
|
||||
vi.useFakeTimers();
|
||||
const handler = new WebsocketServerEventHandler(wss as any);
|
||||
const handler = new WebsocketServerEventHandler(wss as any, mockUserService);
|
||||
|
||||
const id = handler.enableHeartbeat(1000);
|
||||
expect(["number", "object"]).toContain(typeof id);
|
||||
@@ -97,16 +80,16 @@ describe("WebsocketServerEventHandler", () => {
|
||||
});
|
||||
|
||||
it("enableCloseEvent registers Listener and calls callback on close", () => {
|
||||
const handler = new WebsocketServerEventHandler(wss as any);
|
||||
const handler = new WebsocketServerEventHandler(wss as any, mockUserService);
|
||||
const cb = vi.fn();
|
||||
|
||||
const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
||||
handler.enableCloseEvent(cb);
|
||||
|
||||
handler.enableCloseEvent(cb);
|
||||
wss.emit("close");
|
||||
|
||||
expect(cb).toHaveBeenCalledTimes(1);
|
||||
expect(logSpy).toHaveBeenCalledWith("WebSocket server closed");
|
||||
|
||||
logSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
+15
-8
@@ -5,6 +5,8 @@ import { ExtendedWebSocketServer } from "../src/websocket";
|
||||
import { WebsocketServerEventHandler } from "../src/utils/websocket/websocketServerEventHandler";
|
||||
import { WebsocketEventHandler } from "../src/utils/websocket/websocketEventHandler";
|
||||
import { getEventListeners } from "../src/utils/websocket/websocketCustomEvents/websocketEventUtils";
|
||||
import {UserService} from "../src/db/services/db/UserService";
|
||||
import {createMockUserService} from "./helpers/testSetup";
|
||||
|
||||
let mockWssInstance: Mocked<WebSocketServer>;
|
||||
let mockServerEventHandler: Mocked<WebsocketServerEventHandler>;
|
||||
@@ -24,6 +26,7 @@ vi.mock("../src/utils/websocket/websocketCustomEvents/websocketEventUtils");
|
||||
describe("ExtendedWebSocketServer", () => {
|
||||
let mockHttpServer: Mocked<Server>;
|
||||
let extendedWss: ExtendedWebSocketServer;
|
||||
let mockUserService: Mocked<UserService>;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
@@ -42,7 +45,9 @@ describe("ExtendedWebSocketServer", () => {
|
||||
close: vi.fn(),
|
||||
} as unknown as Mocked<WebSocketServer>;
|
||||
|
||||
extendedWss = new ExtendedWebSocketServer(mockHttpServer);
|
||||
mockUserService = createMockUserService();
|
||||
|
||||
extendedWss = new ExtendedWebSocketServer(mockHttpServer, mockUserService);
|
||||
});
|
||||
|
||||
describe("Constructor and Setup", () => {
|
||||
@@ -53,8 +58,8 @@ describe("ExtendedWebSocketServer", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should create and use a WebsocketServerEventHandler", () => {
|
||||
expect(WebsocketServerEventHandler).toHaveBeenCalledWith(mockWssInstance);
|
||||
it("should create and use a WebsocketServerEventHandler with the correct service", () => {
|
||||
expect(WebsocketServerEventHandler).toHaveBeenCalledWith(mockWssInstance, mockUserService);
|
||||
});
|
||||
|
||||
it("should enable the heartbeat", () => {
|
||||
@@ -99,9 +104,11 @@ describe("ExtendedWebSocketServer", () => {
|
||||
emit: vi.fn(), on: vi.fn(), user: { lastState: { global: { mode: "idle" } } },
|
||||
};
|
||||
mockClientEventHandler = {
|
||||
enableErrorEvent: vi.fn(), enablePongEvent: vi.fn(),
|
||||
enableMessageEvent: vi.fn(), enableDisconnectEvent: vi.fn(),
|
||||
registerCustomEvent: vi.fn(),
|
||||
enableErrorEvent: vi.fn(),
|
||||
enablePongEvent: vi.fn(),
|
||||
enableMessageEvent: vi.fn(),
|
||||
enableDisconnectEvent: vi.fn(),
|
||||
registerCustomEvents: vi.fn(),
|
||||
} as unknown as Mocked<WebsocketEventHandler>;
|
||||
|
||||
vi.mocked(WebsocketEventHandler).mockImplementation(() => mockClientEventHandler);
|
||||
@@ -110,12 +117,12 @@ describe("ExtendedWebSocketServer", () => {
|
||||
|
||||
it("should create and configure a WebsocketEventHandler for new clients", () => {
|
||||
connectionHandler(mockWsClient, {});
|
||||
expect(vi.mocked(WebsocketEventHandler)).toHaveBeenCalledWith(mockWsClient);
|
||||
expect(vi.mocked(WebsocketEventHandler)).toHaveBeenCalledWith(mockWsClient, mockUserService);
|
||||
expect(mockClientEventHandler.enableErrorEvent).toHaveBeenCalled();
|
||||
expect(mockClientEventHandler.enablePongEvent).toHaveBeenCalled();
|
||||
expect(mockClientEventHandler.enableMessageEvent).toHaveBeenCalled();
|
||||
expect(mockClientEventHandler.enableDisconnectEvent).toHaveBeenCalled();
|
||||
expect(mockClientEventHandler.registerCustomEvent).toHaveBeenCalled();
|
||||
expect(mockClientEventHandler.registerCustomEvents).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should emit initial events to the new client", () => {
|
||||
|
||||
Reference in New Issue
Block a user