const security = require("../../src/utils/security"); describe("Security Middleware", () => { let mockReq; let mockRes; let mockNext; beforeEach(() => { mockReq = { method: "GET", path: "/test", headers: {}, params: {}, body: {}, ip: "127.0.0.1", connection: { remoteAddress: "127.0.0.1" }, socket: { remoteAddress: "127.0.0.1" }, get: jest.fn(), }; mockRes = { status: jest.fn().mockReturnThis(), json: jest.fn(), setHeader: jest.fn(), }; mockNext = jest.fn(); }); describe("Rate Limiters", () => { test("should create general rate limiter", () => { expect(security.generalLimiter).toBeDefined(); expect(typeof security.generalLimiter).toBe("function"); }); test("should create purchase rate limiter", () => { expect(security.purchaseLimiter).toBeDefined(); expect(typeof security.purchaseLimiter).toBe("function"); }); test("should create admin rate limiter", () => { expect(security.adminLimiter).toBeDefined(); expect(typeof security.adminLimiter).toBe("function"); }); }); describe("Input Validation", () => { describe("validateEventId", () => { test("should pass valid event ID", () => { mockReq.params.eventId = "123"; security.validateEventId[0](mockReq, mockRes, mockNext); security.validateEventId[1](mockReq, mockRes, mockNext); expect(mockNext).toHaveBeenCalled(); expect(mockReq.params.eventId).toBe(123); // Should be converted to number }); test("should reject invalid event ID", () => { mockReq.params.eventId = "invalid"; security.validateEventId[0](mockReq, mockRes, mockNext); security.validateEventId[1](mockReq, mockRes, mockNext); expect(mockRes.status).toHaveBeenCalledWith(400); expect(mockRes.json).toHaveBeenCalledWith({ success: false, message: "Invalid event ID", errors: expect.any(Array), }); }); test("should reject negative event ID", () => { mockReq.params.eventId = "-1"; security.validateEventId[0](mockReq, mockRes, mockNext); security.validateEventId[1](mockReq, mockRes, mockNext); expect(mockRes.status).toHaveBeenCalledWith(400); }); test("should reject zero event ID", () => { mockReq.params.eventId = "0"; security.validateEventId[0](mockReq, mockRes, mockNext); security.validateEventId[1](mockReq, mockRes, mockNext); expect(mockRes.status).toHaveBeenCalledWith(400); }); }); describe("validatePurchaseId", () => { test("should pass valid UUID", () => { mockReq.params.purchaseId = "123e4567-e89b-12d3-a456-426614174000"; security.validatePurchaseId[0](mockReq, mockRes, mockNext); security.validatePurchaseId[1](mockReq, mockRes, mockNext); expect(mockNext).toHaveBeenCalled(); }); test("should reject invalid UUID", () => { mockReq.params.purchaseId = "invalid-uuid"; security.validatePurchaseId[0](mockReq, mockRes, mockNext); security.validatePurchaseId[1](mockReq, mockRes, mockNext); expect(mockRes.status).toHaveBeenCalledWith(400); expect(mockRes.json).toHaveBeenCalledWith({ success: false, message: "Invalid purchase ID", errors: expect.any(Array), }); }); }); describe("validateCleanupRequest", () => { test("should pass valid maxAgeHours", () => { mockReq.body.maxAgeHours = "48"; security.validateCleanupRequest[0](mockReq, mockRes, mockNext); security.validateCleanupRequest[1](mockReq, mockRes, mockNext); expect(mockNext).toHaveBeenCalled(); expect(mockReq.body.maxAgeHours).toBe(48); // Should be converted to number }); test("should pass without maxAgeHours", () => { delete mockReq.body.maxAgeHours; security.validateCleanupRequest[0](mockReq, mockRes, mockNext); security.validateCleanupRequest[1](mockReq, mockRes, mockNext); expect(mockNext).toHaveBeenCalled(); }); test("should reject invalid maxAgeHours", () => { mockReq.body.maxAgeHours = "99999"; // Too high security.validateCleanupRequest[0](mockReq, mockRes, mockNext); security.validateCleanupRequest[1](mockReq, mockRes, mockNext); expect(mockRes.status).toHaveBeenCalledWith(400); }); test("should reject negative maxAgeHours", () => { mockReq.body.maxAgeHours = "-1"; security.validateCleanupRequest[0](mockReq, mockRes, mockNext); security.validateCleanupRequest[1](mockReq, mockRes, mockNext); expect(mockRes.status).toHaveBeenCalledWith(400); }); }); }); describe("Security Headers", () => { test("should be defined", () => { expect(security.securityHeaders).toBeDefined(); expect(typeof security.securityHeaders).toBe("function"); }); }); describe("Request Size Limit", () => { test("should pass requests within size limit", () => { mockReq.headers["content-length"] = "1024"; // 1KB security.requestSizeLimit(mockReq, mockRes, mockNext); expect(mockNext).toHaveBeenCalled(); }); test("should reject requests exceeding size limit", () => { mockReq.headers["content-length"] = "2097152"; // 2MB security.requestSizeLimit(mockReq, mockRes, mockNext); expect(mockRes.status).toHaveBeenCalledWith(413); expect(mockRes.json).toHaveBeenCalledWith({ success: false, message: "Request entity too large. Maximum size is 1MB.", }); }); test("should handle missing content-length", () => { delete mockReq.headers["content-length"]; security.requestSizeLimit(mockReq, mockRes, mockNext); expect(mockNext).toHaveBeenCalled(); }); }); describe("CORS Options", () => { test("should have correct structure", () => { expect(security.corsOptions).toBeDefined(); expect(security.corsOptions.origin).toBeDefined(); expect(security.corsOptions.methods).toBeDefined(); expect(security.corsOptions.allowedHeaders).toBeDefined(); expect(security.corsOptions.credentials).toBeDefined(); expect(security.corsOptions.maxAge).toBeDefined(); }); test("should have default origins", () => { expect(security.corsOptions.origin).toContain("http://localhost:3000"); expect(security.corsOptions.origin).toContain("http://localhost:3049"); }); }); describe("IP Address Extraction", () => { test("should extract IP from req.ip", () => { mockReq.ip = "192.168.1.1"; const ip = security.getClientIP(mockReq); expect(ip).toBe("192.168.1.1"); }); test("should fallback to connection.remoteAddress", () => { delete mockReq.ip; mockReq.connection.remoteAddress = "192.168.1.2"; const ip = security.getClientIP(mockReq); expect(ip).toBe("192.168.1.2"); }); test("should fallback to socket.remoteAddress", () => { delete mockReq.ip; delete mockReq.connection.remoteAddress; mockReq.socket.remoteAddress = "192.168.1.3"; const ip = security.getClientIP(mockReq); expect(ip).toBe("192.168.1.3"); }); test("should return unknown if no IP found", () => { delete mockReq.ip; delete mockReq.connection.remoteAddress; delete mockReq.socket.remoteAddress; const ip = security.getClientIP(mockReq); expect(ip).toBe("unknown"); }); }); describe("Security Logging", () => { test("should log suspicious admin requests", () => { const consoleSpy = jest.spyOn(console, "warn"); mockReq.path = "/admin/test"; security.securityLogging(mockReq, mockRes, mockNext); expect(consoleSpy).toHaveBeenCalledWith( expect.stringContaining("Security Warning"), expect.objectContaining({ ip: "127.0.0.1", path: "/admin/test", method: "GET", }) ); expect(mockNext).toHaveBeenCalled(); }); test("should log path traversal attempts", () => { const consoleSpy = jest.spyOn(console, "warn"); mockReq.path = "/test/../admin"; security.securityLogging(mockReq, mockRes, mockNext); expect(consoleSpy).toHaveBeenCalledWith( expect.stringContaining("Security Warning"), expect.objectContaining({ path: "/test/../admin", }) ); }); test("should not log normal requests", () => { const consoleSpy = jest.spyOn(console, "warn"); mockReq.path = "/events/1"; security.securityLogging(mockReq, mockRes, mockNext); expect(consoleSpy).not.toHaveBeenCalled(); expect(mockNext).toHaveBeenCalled(); }); }); });