Skip to content

Commit

Permalink
Merge pull request #17 from sh3pik/chore/support-multipart-form-data
Browse files Browse the repository at this point in the history
Support requests with multipart/form-data content type
  • Loading branch information
sbycrosz authored Nov 1, 2021
2 parents ddad331 + 312ffbc commit 9011e46
Show file tree
Hide file tree
Showing 7 changed files with 843 additions and 768 deletions.
18 changes: 9 additions & 9 deletions lib/server/createRecordingServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,25 @@ const createRecordingServer = async (config) => {
const getRecordingFileName = utils.createGetRecordingFileName();

app.use(async (req, res) => {
const { method, url, body, headers } = req;
const { method, url } = req;

let response;
const { headers, data } = utils.isFormRequest(req)
? await utils.getMultipartPayload(req)
: { headers: req.headers, data: req.body };

try {
response = await axios.request({
const response = await axios
.request({
baseURL: _target,
data: body,
data,
url,
method,
headers: {
...headers,
host: null,
'Cache-Control': 'no-cache',
},
});
} catch (error) {
response = error.response; // eslint-disable-line
}
})
.catch((error) => error.response);

logger.debug(`Recorded ${method} ${url} [${response.status}]`);
res.status(response.status || 500);
Expand Down
42 changes: 42 additions & 0 deletions lib/server/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import path from 'path';
import fse from 'fs-extra';
import tar from 'tar';
import md5File from 'md5-file/promise';
import FormData from 'form-data';
import formidable from 'formidable';
// eslint-disable-next-line import/no-unresolved
import fs from 'fs/promises';

export const getRequestKey = (method, url) => {
return `${method}-${_.kebabCase(url)}`;
Expand Down Expand Up @@ -85,3 +89,41 @@ export const isDirectoryInSyncWithArchive = async (archive, directory) => {

return directoryHash === archiveHash;
};

export const isFormRequest = (request) => {
return /^multipart\/form-data/.test(request.headers['content-type']);
};

export const getMultipartPayload = async (request) => {
const { files, fields } = await parseForm(request);

const data = new FormData();
Object.entries(fields).forEach(([key, value]) => {
data.append(key, value);
});
await Promise.all(
Object.entries(files).map(async ([key, value]) => {
const buffer = await fs.readFile(value.path);
data.append(key, buffer, value.name);
}),
);

return {
headers: {
...request.headers,
...data.getHeaders(),
},
data: data.getBuffer(),
};
};

function parseForm(request) {
return new Promise((resolve, reject) => {
const form = formidable({ multiples: true });

form.parse(request, (error, fields, files) => {
if (error) reject(error);
resolve({ fields, files });
});
});
}
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@
"dependencies": {
"@babel/cli": "^7.4.4",
"arg": "^4.1.0",
"axios": "^0.18.1",
"axios": "^0.24.0",
"body-parser": "^1.19.0",
"chalk": "^2.4.2",
"express": "^4.17.0",
"express": "^4.17.1",
"form-data": "^4.0.0",
"formidable": "^1.2.2",
"fs-extra": "^8.0.1",
"lodash": "^4.17.13",
"md5-file": "^4.0.0",
Expand Down
29 changes: 12 additions & 17 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,29 @@ Node-playback-server allows you to run your e2e tests against a mock server. See

![Screen Shot 2021-09-03 at 10 17 20 AM](https://user-images.githubusercontent.com/4867178/131940571-38ba4f21-0f6d-4346-b124-58bbd00250e0.png)


## Usage (CLI)

```js
```bash
Commands

$ node-playback-server record Start a recording server
$ node-playback-server play Start a playback server
$ node-playback-server clean Clears temp directory

OPTIONS

--port (p) Server port (optional)
--archive (x) Recording archive (use either archive or directory)
--directory (d) Recording directory (use either archive or directory)
--target (t) Server to forward request onto (record mode)
--loglevel (l) Server log level (1 to 5)
```


## Usage (Node)

### Record
```

```js
import { createRecordingServer } from 'node-playback-server';

describe('login', () => {
Expand All @@ -49,15 +47,15 @@ describe('login', () => {

it('serves response from archive', async () => {
const baseURL = `http://localhost:${recordingServer.port}`;
// Now any request to baseURL will be proxied to target ('https://api.stashaway.sg')
// Now any request to baseURL will be proxied to target ('https://api.stashaway.sg')
// and recorded to archive './recording/login.tgz'
});
});
```

### Playback
```

```js
import { createPlaybackServer } from 'node-playback-server';

describe('login', () => {
Expand All @@ -75,7 +73,7 @@ describe('login', () => {

it('proxies GET request and record them', async () => {
const baseURL = `http://localhost:${recordingServer.port}`;
// Now any request to baseURL will be served from stored response
// Now any request to baseURL will be served from stored response
});
});
```
Expand All @@ -84,8 +82,5 @@ describe('login', () => {

1. Integrates nicely with node ecosystem so you can spawn a playback-server on your test runner.
2. Handles cases when the recording responses changes.
- e.g. `GET /user/profile` responses might changes depending whether user has finished onboarding, this library records them as 2 separate JSON files.

## Improvements

1. Allows forwarding of multipart-form data (Currently, it only works for JSON responses)
- e.g. `GET /user/profile` responses might changes depending whether user has finished onboarding, this library records them as 2 separate JSON files.
3. Supports requests with multipart/form-data content type.
17 changes: 17 additions & 0 deletions tests/createRecordingServer.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import fse from 'fs-extra';
import axios from 'axios';
import FormData from 'form-data';
import createTargetServer from './helpers/createTargetServer';
import { createRecordingServer } from '../lib';

Expand Down Expand Up @@ -45,6 +46,22 @@ describe('recordingServer', () => {
expect(fse.existsSync(`${RECORDING_DIR}/POST-static-1.json`)).toEqual(true);
});

it('proxies multipart/form-data request and record them', async () => {
const data = new FormData();
data.append('name', 'Fred');

const response = await axios({
url: `${RECORDING_SERVER_URL}/multipart`,
method: 'post',
data: data.getBuffer(),
headers: {
...data.getHeaders(),
},
});
expect(response.data).toEqual({ data: 'post-static--multipart' });
expect(fse.existsSync(`${RECORDING_DIR}/POST-multipart-1.json`)).toEqual(true);
});

it('proxies errors request and record them', async () => {
await expect(axios.post(`${RECORDING_SERVER_URL}/not-found`)).rejects.toThrow('Request failed with status code 404');
expect(fse.existsSync(`${RECORDING_DIR}/POST-not-found-1.json`)).toEqual(true);
Expand Down
4 changes: 4 additions & 0 deletions tests/helpers/createTargetServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ const createTargetServer = async (config) => {
res.json({ data: `post-static--${req.body.data}` });
});

app.post('/multipart', (req, res) => {
res.json({ data: 'post-static--multipart' });
});

let server;
await new Promise((resolve) => {
server = app.listen(_port, resolve);
Expand Down
Loading

0 comments on commit 9011e46

Please sign in to comment.