const { EventEmitter } = require("events"); const { Transaction } = require("modsecurity"); jest.mock("modsecurity"); process.serverConfig = { modSecurityRulesPath: "/path/to/rules" }; const modSecurityIntegration = require("../src/index.js"); const modInfo = require("../modInfo.json"); describe("ModSecurity Integration", () => { let req, res, logFacilities, config, next; beforeEach(() => { req = new EventEmitter(); req.read = jest.fn(); req.unshift = jest.fn(); req.method = "GET"; req.url = "/test"; req.httpVersion = "1.1"; req.rawHeaders = ["Host", "example.com"]; req.socket = { realRemoteAddress: "127.0.0.1", realRemotePort: 12345, localAddress: "127.0.0.1", localPort: 8080 }; req.headers = {}; req._readableState = { length: 0, ended: true }; res = new EventEmitter(); res.setHeader = jest.fn(); res.removeHeader = jest.fn(); res.writeHead = jest.fn(); res.write = jest.fn(); res.end = jest.fn(); res.error = jest.fn(); res.getHeaders = jest.fn().mockReturnValue({}); logFacilities = { errmessage: jest.fn() }; config = {}; next = jest.fn(); jest.clearAllMocks(); Transaction.mockReset(); }); test("should call next if no rules error and no security response", () => { modSecurityIntegration(req, res, logFacilities, config, next); expect(next).toHaveBeenCalled(); }); test("should process connection and call next if no security response", () => { Transaction.prototype.processConnection.mockReturnValue(true); modSecurityIntegration(req, res, logFacilities, config, next); expect(Transaction.prototype.processConnection).toHaveBeenCalledWith( "127.0.0.1", 12345, "127.0.0.1", 8080 ); expect(next).toHaveBeenCalled(); }); test("should process URI and call next if no security response", () => { Transaction.prototype.processConnection.mockReturnValue(true); Transaction.prototype.processURI.mockReturnValue(true); modSecurityIntegration(req, res, logFacilities, config, next); expect(Transaction.prototype.processURI).toHaveBeenCalledWith( "/test", "GET", "1.1" ); expect(next).toHaveBeenCalled(); }); test("should process request headers and call next if no security response", () => { Transaction.prototype.processConnection.mockReturnValue(true); Transaction.prototype.processURI.mockReturnValue(true); Transaction.prototype.processRequestHeaders.mockReturnValue(true); modSecurityIntegration(req, res, logFacilities, config, next); expect(Transaction.prototype.processRequestHeaders).toHaveBeenCalled(); expect(next).toHaveBeenCalled(); }); test("should handle security response and call processIntervention", () => { const securityResponse = { status: 403, log: "Blocked by ModSecurity" }; Transaction.prototype.processConnection.mockReturnValue(securityResponse); modSecurityIntegration(req, res, logFacilities, config, next); expect(res.error).toHaveBeenCalledWith( 403, `modsecurity-integration/${modInfo.version}`, new Error("Blocked by ModSecurity") ); expect(logFacilities.errmessage).toHaveBeenCalledWith( "Request blocked by ModSecurity." ); expect(logFacilities.errmessage).toHaveBeenCalledWith( "Blocked by ModSecurity" ); }); test("should handle unknown ModSecurity error", () => { Transaction.prototype.processConnection.mockReturnValue(false); modSecurityIntegration(req, res, logFacilities, config, next); expect(res.error).toHaveBeenCalledWith( 500, `modsecurity-integration/${modInfo.version}`, new Error("Unknown ModSecurity error.") ); }); test("should handle request body and call next if no security response", () => { Transaction.prototype.processConnection.mockReturnValue(true); Transaction.prototype.processURI.mockReturnValue(true); Transaction.prototype.processRequestHeaders.mockReturnValue(true); Transaction.prototype.appendRequestBody.mockReturnValue(true); Transaction.prototype.processRequestBody.mockReturnValue(true); modSecurityIntegration(req, res, logFacilities, config, next); expect(Transaction.prototype.processRequestBody).toHaveBeenCalled(); expect(next).toHaveBeenCalled(); }); test("should block request if security response is detected during processResponseHeaders", () => { const securityResponse = { status: 403, log: "Blocked by ModSecurity" }; Transaction.prototype.processConnection.mockReturnValue(true); Transaction.prototype.processURI.mockReturnValue(true); Transaction.prototype.processRequestHeaders.mockReturnValue(true); Transaction.prototype.processResponseHeaders.mockReturnValue( securityResponse ); modSecurityIntegration(req, res, logFacilities, config, next); res.writeHead(200, "OK"); res.write("Something that will trigger security response."); res.end(); expect(res.error).toHaveBeenCalledWith( 403, `modsecurity-integration/${modInfo.version}`, new Error("Blocked by ModSecurity") ); expect(logFacilities.errmessage).toHaveBeenCalledWith( "Request blocked by ModSecurity." ); expect(logFacilities.errmessage).toHaveBeenCalledWith( "Blocked by ModSecurity" ); }); test("should block request if security response is detected during appendResponseBody", () => { const securityResponse = { status: 403, log: "Blocked by ModSecurity" }; Transaction.prototype.processConnection.mockReturnValue(true); Transaction.prototype.processURI.mockReturnValue(true); Transaction.prototype.processRequestHeaders.mockReturnValue(true); Transaction.prototype.appendResponseBody.mockReturnValue(securityResponse); modSecurityIntegration(req, res, logFacilities, config, next); res.writeHead(200, "OK"); res.write("Something that will trigger security response."); res.end(); expect(res.error).toHaveBeenCalledWith( 403, `modsecurity-integration/${modInfo.version}`, new Error("Blocked by ModSecurity") ); expect(logFacilities.errmessage).toHaveBeenCalledWith( "Request blocked by ModSecurity." ); expect(logFacilities.errmessage).toHaveBeenCalledWith( "Blocked by ModSecurity" ); }); test("should block request if security response is detected during processResponseBody", () => { const securityResponse = { status: 403, log: "Blocked by ModSecurity" }; Transaction.prototype.processConnection.mockReturnValue(true); Transaction.prototype.processURI.mockReturnValue(true); Transaction.prototype.processRequestHeaders.mockReturnValue(true); Transaction.prototype.processResponseBody.mockReturnValue(securityResponse); modSecurityIntegration(req, res, logFacilities, config, next); res.writeHead(200, "OK"); res.write("Something that will trigger security response."); res.end(); expect(res.error).toHaveBeenCalledWith( 403, `modsecurity-integration/${modInfo.version}`, new Error("Blocked by ModSecurity") ); expect(logFacilities.errmessage).toHaveBeenCalledWith( "Request blocked by ModSecurity." ); expect(logFacilities.errmessage).toHaveBeenCalledWith( "Blocked by ModSecurity" ); }); test("should handle maxRequestCheckedSize exceeded and call next", () => { config.maxRequestCheckedSize = 10; req.headers["content-length"] = 20; req._readableState.ended = false; req._readableState.length = 0; req.read.mockImplementation((size) => Buffer.alloc(Math.min(20, size))); modSecurityIntegration(req, res, logFacilities, config, next); req.emit("readable"); expect(next).toHaveBeenCalled(); }); test("should handle maxRequestCheckedSizeStrict", () => { config.maxRequestCheckedSizeStrict = true; config.maxRequestCheckedSize = 10; req.headers["content-length"] = 20; req._readableState.ended = false; req._readableState.length = 0; modSecurityIntegration(req, res, logFacilities, config, next); expect(res.error).toHaveBeenCalledWith( 413, `modsecurity-integration/${modInfo.version}` ); }); });