actionhero
Version:
actionhero.js is a multi-transport API Server with integrated cluster capabilities and delayed tasks
213 lines (185 loc) • 7.16 kB
text/typescript
import { PassThrough } from "stream";
import * as request from "request-promise-native";
import { api, Process, config } from "./../../../src/index";
const actionhero = new Process();
let url;
const toJson = async (string) => {
try {
return JSON.parse(string);
} catch (error) {
return error;
}
};
jest.mock("./../../../src/config/servers/web.ts", () => ({
__esModule: true,
test: {
servers: {
web: () => {
return {
saveRawBody: true,
enabled: true,
secure: false,
urlPathForActions: "api",
urlPathForFiles: "public",
rootEndpointType: "file",
port: 18080 + parseInt(process.env.JEST_WORKER_ID || "0"),
matchExtensionMime: true,
simpleRouting: true,
queryRouting: true,
metadataOptions: {
serverInformation: true,
requesterInformation: false,
},
fingerprintOptions: {
cookieKey: "sessionID",
},
};
},
},
},
}));
describe("Server: Web", () => {
beforeAll(async () => {
await actionhero.start();
url = "http://localhost:" + config.servers.web.port;
});
afterAll(async () => {
await actionhero.stop();
});
describe("connection.rawConnection.rawBody", () => {
beforeAll(() => {
api.actions.versions.paramTestAction = [1];
api.actions.actions.paramTestAction = {
// @ts-ignore
1: {
name: "paramTestAction",
description: "I return connection.rawConnection.params",
version: 1,
run: async (data) => {
data.response = data.connection.rawConnection.params;
if (data.connection.rawConnection.params.rawBody) {
data.response.rawBody = data.connection.rawConnection.params.rawBody.toString();
}
},
},
};
api.routes.loadRoutes();
});
afterAll(() => {
delete api.actions.actions.paramTestAction;
delete api.actions.versions.paramTestAction;
});
test(".rawBody will contain the raw POST body without parsing", async () => {
const requestBody = '{"key": "value"}';
const body = await request
.post(url + "/api/paramTestAction", {
body: requestBody,
headers: { "Content-type": "application/json" },
})
.then(toJson);
expect(body.body.key).toEqual("value");
expect(body.rawBody).toEqual('{"key": "value"}');
});
describe("invalid/improper mime types", () => {
test(".body will be empty if the content-type cannot be handled by formidable and not crash", async () => {
const requestBody = "<texty>this is like xml</texty>";
const body = await request
.post(url + "/api/paramTestAction", {
body: requestBody,
headers: { "Content-type": "text/xml" },
})
.then(toJson);
expect(body.body).toEqual({});
expect(body.rawBody).toEqual(requestBody);
});
test("will set the body properly if mime type is wrong (bad header)", async () => {
const requestBody = "<texty>this is like xml</texty>";
const body = await request
.post(url + "/api/paramTestAction", {
body: requestBody,
headers: { "Content-type": "application/json" },
})
.then(toJson);
expect(body.body).toEqual({});
expect(body.rawBody).toEqual(requestBody);
});
test("will set the body properly if mime type is wrong (text)", async () => {
const requestBody = "I am normal \r\n text with \r\n line breaks";
const body = await request
.post(url + "/api/paramTestAction", {
body: requestBody,
headers: { "Content-type": "text/plain" },
})
.then(toJson);
expect(body.body).toEqual({});
expect(body.rawBody).toEqual(requestBody);
});
test("rawBody will exist if the content-type cannot be handled by formidable", async () => {
const requestPart1 = "<texty><innerNode>more than";
const requestPart2 = " two words</innerNode></texty>";
const bufferStream = new PassThrough();
const req = request.post(url + "/api/paramTestAction", {
headers: { "Content-type": "text/xml" },
});
bufferStream.write(Buffer.from(requestPart1)); // write the first part
bufferStream.pipe(req);
setTimeout(() => {
bufferStream.end(Buffer.from(requestPart2)); // end signals no more is coming
}, 50);
await new Promise((resolve, reject) => {
bufferStream.on("finish", resolve);
});
const respString = await req;
const resp = JSON.parse(respString);
expect(resp.error).toBeUndefined();
expect(resp.body).toEqual({});
expect(resp.rawBody).toEqual(requestPart1 + requestPart2);
});
test("rawBody and form will process JSON with odd stream testing", async () => {
const requestJson = { a: 1, b: "two" };
const requestString = JSON.stringify(requestJson);
const middleIdx = Math.floor(requestString.length / 2);
const requestPart1 = requestString.substring(0, middleIdx);
const requestPart2 = requestString.substring(middleIdx);
const bufferStream = new PassThrough();
const req = request.post(url + "/api/paramTestAction", {
headers: { "Content-type": "application/json" },
});
bufferStream.write(Buffer.from(requestPart1)); // write the first part
bufferStream.pipe(req);
setTimeout(() => {
bufferStream.end(Buffer.from(requestPart2)); // end signals no more is coming
}, 50);
await new Promise((resolve, reject) => {
bufferStream.on("finish", resolve);
});
const respString = await req;
const resp = JSON.parse(respString);
expect(resp.error).toBeUndefined();
expect(resp.body).toEqual(requestJson);
expect(resp.rawBody).toEqual(requestString);
});
test("rawBody processing will not hang on writable error", async () => {
const requestPart1 = "<texty><innerNode>more than";
const bufferStream = new PassThrough();
const req = request.post(url + "/api/paramTestAction", {
headers: { "Content-type": "text/xml" },
});
bufferStream.write(Buffer.from(requestPart1)); // write the first part
bufferStream.pipe(req);
setTimeout(() => {
// bufferStream.destroy(new Error('This stream is broken.')) // sends an error and closes the stream
bufferStream.end();
}, 50);
await new Promise((resolve, reject) => {
bufferStream.on("finish", resolve);
});
const respString = await req;
const resp = JSON.parse(respString);
expect(resp.error).toBeUndefined();
expect(resp.body).toEqual({});
expect(resp.rawBody).toEqual(requestPart1); // stream ends with only one part processed
});
});
});
});