UNPKG

actionhero

Version:

The reusable, scalable, and quick node.js API server for stateless and stateful applications

209 lines (182 loc) 7.14 kB
import { PassThrough } from "stream"; import axios, { AxiosError } from "axios"; import { api, Process, config } from "./../../../src/index"; const actionhero = new Process(); let url: string; jest.mock("./../../../src/config/web.ts", () => ({ __esModule: true, test: { web: () => { return { enabled: true, saveRawBody: true, automaticRoutes: ["post"], secure: false, urlPathForActions: "api", urlPathForFiles: "public", rootEndpointType: "file", port: 18080 + parseInt(process.env.JEST_WORKER_ID || "0"), matchExtensionMime: true, metadataOptions: { serverInformation: true, requesterInformation: false, }, fingerprintOptions: { cookieKey: "sessionID", }, }; }, }, })); describe("Server: Web", () => { beforeAll(async () => { await actionhero.start(); url = "http://localhost:" + config.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 response = await axios.post( url + "/api/paramTestAction", requestBody, { headers: { "Content-type": "application/json" }, }, ); expect(response.data.body.key).toEqual("value"); expect(response.data.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 response = await axios.post( url + "/api/paramTestAction", requestBody, { headers: { "Content-type": "text/xml" }, }, ); expect(response.data.body).toEqual({}); expect(response.data.rawBody).toEqual(requestBody); }); // TODO: mime types with axios are too strong test.skip("will set the body properly if mime type is wrong (bad header)", async () => { const requestBody = "<texty>this is like xml</texty>"; const response = await axios.post( url + "/api/paramTestAction", requestBody, { headers: { "Content-type": "application/json" }, }, ); expect(response.data.body).toEqual({}); expect(response.data.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 response = await axios.post( url + "/api/paramTestAction", requestBody, { headers: { "Content-type": "text/plain" }, }, ); expect(response.data.body).toEqual({}); expect(response.data.rawBody).toEqual(requestBody); }); // TODO Axios doesn't pipe test.skip("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 = axios.post(url + "/api/paramTestAction", { headers: { "Content-type": "text/xml" }, }); bufferStream.write(Buffer.from(requestPart1)); // write the first part // @ts-ignore (we are not await-ing) 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 { data } = await req; expect(data.error).toBeUndefined(); expect(data.body).toEqual({}); expect(data.rawBody).toEqual(requestPart1 + requestPart2); }); // TODO Axios doesn't pipe test.skip("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 = axios.post(url + "/api/paramTestAction", { headers: { "Content-type": "application/json" }, }); bufferStream.write(Buffer.from(requestPart1)); // write the first part // @ts-ignore (we are not await-ing) 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 { data } = await req; expect(data.error).toBeUndefined(); expect(data.body).toEqual(requestJson); expect(data.rawBody).toEqual(requestString); }); // TODO Axios doesn't pipe test.skip("rawBody processing will not hang on writable error", async () => { const requestPart1 = "<texty><innerNode>more than"; const bufferStream = new PassThrough(); const req = axios.post(url + "/api/paramTestAction", { headers: { "Content-type": "text/xml" }, }); bufferStream.write(Buffer.from(requestPart1)); // write the first part // @ts-ignore (we are not await-ing) 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 { data } = await req; expect(data.error).toBeUndefined(); expect(data.body).toEqual({}); expect(data.rawBody).toEqual(requestPart1); // stream ends with only one part processed }); }); }); });