greenrhombus/tests/utils/fastCgi.test.js

150 lines
4.4 KiB
JavaScript

const {
FastCGIHandler,
constants,
buildFastCGIPacket,
buildNameValuePair,
writeFastCGIPacket
} = require("../../src/utils/fastCgi.js");
const net = require("net");
const EventEmitter = require("events");
jest.mock("net");
describe("FastCGI module", () => {
let mockSocket;
beforeEach(() => {
mockSocket = {
write: jest.fn(),
on: jest.fn().mockImplementation((event, handler) => {
if (event === "data") {
mockSocket.dataHandler = handler;
}
return mockSocket; // Enable method chaining
}),
removeListener: jest.fn().mockImplementation((event, handler) => {
if (event === "data" && mockSocket.dataHandler == handler) {
mockSocket.dataHandler = () => {};
}
return mockSocket; // Enable method chaining
}),
dataHandler: () => {}
};
net.createConnection.mockReturnValue(mockSocket);
});
afterEach(() => {
jest.clearAllMocks();
});
test("should create a socket and connect", () => {
const handler = new FastCGIHandler({ host: "localhost", port: 9000 });
expect(net.createConnection).toHaveBeenCalledWith(
{ host: "localhost", port: 9000 },
expect.any(Function)
);
expect(mockSocket.on).toHaveBeenCalledWith("error", expect.any(Function));
expect(mockSocket.on).toHaveBeenCalledWith("data", expect.any(Function));
expect(handler).toBeInstanceOf(EventEmitter);
});
test("should build a FastCGI packet", () => {
const content = Buffer.from("test");
const packet = buildFastCGIPacket(1, 1, content);
expect(packet.length).toBe(16); // Header + padded content
expect(packet.readUInt8(0)).toBe(1); // version
expect(packet.readUInt8(1)).toBe(1); // type
expect(packet.readUInt16BE(2)).toBe(1); // requestID
expect(packet.readUInt16BE(4)).toBe(4); // content length
expect(packet.readUInt8(6)).toBe(4); // padding length
});
test("should write FastCGI packets to the socket", () => {
const handler = new FastCGIHandler({});
const content = Buffer.from("test");
writeFastCGIPacket(handler.socket, 1, handler.requestID, content);
expect(mockSocket.write).toHaveBeenCalledTimes(1);
expect(mockSocket.write).toHaveBeenCalledWith(expect.any(Buffer));
});
// eslint-disable-next-line jest/no-done-callback
test("should handle socket data and emit events", (done) => {
const handler = new FastCGIHandler({});
handler.stdout.on("data", (chunk) => {
expect(chunk).toStrictEqual(Buffer.from("Hello World"));
done();
});
// Simulate a FastCGI STDOUT packet
const packet = buildFastCGIPacket(
constants.STDOUT,
handler.requestID,
Buffer.from("Hello World")
);
mockSocket.dataHandler(packet);
});
// eslint-disable-next-line jest/no-done-callback
test("should handle END_REQUEST and emit 'exit' event", (done) => {
const handler = new FastCGIHandler({});
handler.on("exit", (code, signal) => {
expect(code).toBe(0);
expect(signal).toBeNull();
done();
});
// Simulate an END_REQUEST packet
const content = Buffer.alloc(8);
content.writeUInt32BE(0, 0); // appStatus
content.writeUInt8(0, 4); // protocolStatus
const packet = buildFastCGIPacket(
constants.END_REQUEST,
handler.requestID,
content
);
mockSocket.dataHandler(packet);
});
test("should handle UNKNOWN_TYPE and discard it", () => {
const handler = new FastCGIHandler({});
const dataListener = jest.fn();
handler.stdout.on("data", dataListener);
// Simulate an UNKNOWN_TYPE packet
const packet = buildFastCGIPacket(
constants.UNKNOWN_TYPE,
handler.requestID,
Buffer.alloc(0)
);
mockSocket.dataHandler(packet);
expect(dataListener).not.toHaveBeenCalledWith();
});
test("should build name-value pairs correctly", () => {
const pair = buildNameValuePair("key", "value");
expect(pair.length).toBe(10); // 1 byte for key length, 1 for value length, and key-value content
expect(pair.toString("utf8", 2)).toBe("keyvalue");
});
test("should send environment variables on init", () => {
const env = { FOO: "BAR", BAZ: "QUX" };
const handler = new FastCGIHandler({ env });
handler.init();
expect(mockSocket.write).toHaveBeenCalled();
const calls = mockSocket.write.mock.calls;
const envPacket = calls.find(([arg]) => arg.includes(Buffer.from("FOO")));
expect(envPacket).toBeTruthy();
});
});