├── .eslintrc
├── .github
└── workflows
│ └── node.js.yml
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── main.js
├── package-lock.json
├── package.json
└── test
├── bufferAsync.js
├── fixture
└── fixture.jpg
└── streamAsync.js
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb-base",
3 | "env": {
4 | "node": true,
5 | "mocha": true
6 | },
7 | "parserOptions": {
8 | "sourceType": "script"
9 | },
10 | "rules": {
11 | "comma-dangle": 0,
12 | "consistent-return": 0,
13 | "func-names": 0,
14 | "new-cap": [2, { "newIsCap": true, "capIsNew": false }],
15 | "no-param-reassign": 0,
16 | "no-plusplus": 0,
17 | "no-restricted-syntax": 0,
18 | "no-shadow": [2, { "allow": ["err", "n"] }],
19 | "no-underscore-dangle": 0,
20 | "prefer-rest-params": 0,
21 | "strict": 0
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/.github/workflows/node.js.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: Node.js CI
5 |
6 | on:
7 | push:
8 | branches: [ "master" ]
9 | pull_request:
10 | branches: [ "master" ]
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | strategy:
18 | matrix:
19 | node-version: [12.x, 14.x, 16.x, 18.x]
20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
21 |
22 | steps:
23 | - uses: actions/checkout@v3
24 | - name: Use Node.js ${{ matrix.node-version }}
25 | uses: actions/setup-node@v3
26 | with:
27 | node-version: ${{ matrix.node-version }}
28 | cache: 'npm'
29 | - run: npm ci
30 | - run: npm run lint && npm run cover
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (https://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # TypeScript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # next.js build output
61 | .next
62 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (https://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # TypeScript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # next.js build output
61 | .next
62 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015-2019 Alexey Bystrov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | multi-part [](https://github.com/strikeentco/multi-part/blob/master/LICENSE) [](https://www.npmjs.com/package/multi-part)
2 | ==========
3 | [](https://travis-ci.org/strikeentco/multi-part) [](https://www.npmjs.com/package/multi-part) [](https://codeclimate.com/github/strikeentco/multi-part/test_coverage)
4 |
5 | A `multi-part` allows you to create multipart/form-data `Stream` and `Buffer`, which can be used to submit forms and file uploads to other web applications.
6 |
7 | It extends [`multi-part-lite`](https://github.com/strikeentco/multi-part-lite) and adds automatic data type detection.
8 |
9 | Supports: `Strings`, `Numbers`, `Arrays`, `ReadableStreams`, `Buffers` and `Vinyl`.
10 |
11 | ## Install
12 | ```sh
13 | $ npm install multi-part --save
14 | ```
15 |
16 | ## Usage
17 | Usage with `got` as `Stream`:
18 |
19 | ```js
20 | const got = require('got');
21 | const Multipart = require('multi-part');
22 | const form = new Multipart();
23 |
24 | form.append('photo', got.stream('https://avatars1.githubusercontent.com/u/2401029'));
25 | form.append('field', 'multi-part test');
26 |
27 | (async () => {
28 | const body = await form.stream();
29 | got.post('127.0.0.1:3000', { headers: form.getHeaders(), body });
30 | })()
31 | ```
32 | Usage with `got` as `Buffer`:
33 |
34 | ```js
35 | const got = require('got');
36 | const Multipart = require('multi-part');
37 | const form = new Multipart();
38 |
39 | form.append('photo', got.stream('https://avatars1.githubusercontent.com/u/2401029'));
40 | form.append('field', 'multi-part test');
41 |
42 | (async () => {
43 | const body = await form.buffer();
44 | got.post('127.0.0.1:3000', { headers: form.getHeaders(false), body });
45 | })()
46 | ```
47 | Usage with `http`/`https` as `Stream`:
48 |
49 | ```js
50 | const http = require('http');
51 | const https = require('https');
52 | const Multipart = require('multi-part');
53 | const form = new Multipart();
54 |
55 | form.append('photo', https.request('https://avatars1.githubusercontent.com/u/2401029'));
56 |
57 | (async () => {
58 | const stream = await form.stream();
59 | stream.pipe(http.request({ headers: form.getHeaders(), hostname: '127.0.0.1', port: 3000, method: 'POST' }));
60 | })()
61 | ```
62 | Usage with `http`/`https` as `Buffer`:
63 |
64 | ```js
65 | const http = require('http');
66 | const https = require('https');
67 | const Multipart = require('multi-part');
68 | const form = new Multipart();
69 |
70 | form.append('photo', https.request('https://avatars1.githubusercontent.com/u/2401029'));
71 |
72 | (async () => {
73 | const body = await form.buffer();
74 | const req = http.request({ headers: form.getHeaders(false), hostname: '127.0.0.1', port: 3000, method: 'POST' });
75 | req.end(body);
76 | })()
77 | ```
78 |
79 | # API
80 |
81 | ### new Multipart([options])
82 | ### new MultipartAsync([options])
83 |
84 | Constructor.
85 |
86 | ### Params:
87 | * **[options]** (*Object*) - `Object` with options:
88 | * **[boundary]** (*String|Number*) - Custom boundary for `multipart` data. Ex: if equal `CustomBoundary`, boundary will be equal exactly `CustomBoundary`.
89 | * **[boundaryPrefix]** (*String|Number*) - Custom boundary prefix for `multipart` data. Ex: if equal `CustomBoundary`, boundary will be equal something like `--CustomBoundary567689371204`.
90 | * **[defaults]** (*Object*) - `Object` with defaults values:
91 | * **[name]** (*String*) - File name which will be used, if `filename` is not specified in the options of `.append` method. By default `file`.
92 | * **[ext]** (*String*) - File extension which will be used, if `filename` is not specified in the options of `.append` method. By default `bin`.
93 | * **[type]** (*String*) - File content-type which will be used, if `contentType` is not specified in the options of `.append` method. By default `application/octet-stream`.
94 |
95 | ```js
96 | const Multipart = require('multi-part');
97 | const { MultipartAsync } = require('multi-part');
98 | ```
99 |
100 | ### .append(name, value, [options])
101 |
102 | Adds a new data to the `multipart/form-data` stream.
103 |
104 | ### Params:
105 | * **name** (*String|Number*) - Field name. Ex: `photo`.
106 | * **value** (*Mixed*) - Value can be `String`, `Number`, `Array`, `Buffer`, `ReadableStream` or even [Vynil](https://www.npmjs.com/package/vinyl).
107 | * **[options]** (*Object*) - Additional options:
108 | * **filename** (*String*) - File name. Ex: `anonim.jpg`.
109 | * **contentType** (*String*) - File content type. It's not necessary if you have already specified file name. If you are not sure about the content type - leave `filename` and `contentType` empty and it will be automatically determined, if possible. Ex: `image/jpeg`.
110 |
111 | If `value` is an array, `append` will be called for each value:
112 | ```js
113 | form.append('array', [0, [2, 3], 1]);
114 |
115 | // similar to
116 |
117 | form.append('array', 0);
118 | form.append('array', 2);
119 | form.append('array', 3);
120 | form.append('array', 1);
121 | ```
122 |
123 | `Null`, `false` and `true` will be converted to `'0'`, `'0'` and `'1'`. Numbers will be converted to strings also.
124 |
125 | For `Buffer` and `ReadableStream` content type will be automatically determined, if it's possible, and name will be specified according to content type. If content type is `image/jpeg`, file name will be set as `file.jpeg` (if `filename` option is not specified).
In case content type is undetermined, content type and file name will be set as `application/octet-stream` and `file.bin`.
126 |
127 | ### .stream()
128 |
129 | Returns a `Promise` with a `multipart/form-data` stream.
130 |
131 | ### .buffer()
132 |
133 | Returns a `Promise` with a buffer of the `multipart/form-data` stream data.
134 |
135 | ### .getBoundary()
136 |
137 | Returns the form boundary used in the `multipart/form-data` stream.
138 |
139 | ```js
140 | form.getBoundary(); // -> '--MultipartBoundary352840693617'
141 | ```
142 |
143 | ### .getLength()
144 |
145 | Returns the length of a buffer of the `multipart/form-data` stream data.
146 |
147 | Should be called after `.buffer()`;
148 |
149 | For `.stream()` it's always `0`.
150 |
151 | ```js
152 | await form.buffer();
153 | form.getLength(); // -> 12345
154 | ```
155 |
156 | ### .getHeaders(chunked = true)
157 |
158 | Returns the headers.
159 |
160 | If you want to get correct `content-length`, you should call it after `.buffer()`. There is no way to know `content-length` of the `.stream()`, so it will be always `0`.
161 |
162 | ### Params:
163 | * **chunked** (*Boolean*) - If `false` - headers will include `content-length` header, otherwise there will be `transfer-encoding: 'chunked'`.
164 |
165 | ```js
166 | form.getHeaders(); // ->
167 | //{
168 | // 'transfer-encoding': 'chunked',
169 | // 'content-type': 'multipart/form-data; boundary="--MultipartBoundary352840693617"'
170 | //}
171 | ```
172 | With `.buffer()`:
173 | ```js
174 | form.getHeaders(false); // ->
175 | //{
176 | // 'content-length': '0',
177 | // 'content-type': 'multipart/form-data; boundary="--MultipartBoundary352840693617"'
178 | //}
179 |
180 | await form.buffer();
181 | form.getHeaders(false); // ->
182 | //{
183 | // 'content-length': '12345',
184 | // 'content-type': 'multipart/form-data; boundary="--MultipartBoundary352840693617"'
185 | //}
186 | ```
187 |
188 | ## License
189 |
190 | The MIT License (MIT)
191 | Copyright (c) 2015-2022 Alexey Bystrov
192 |
--------------------------------------------------------------------------------
/main.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /* eslint-disable class-methods-use-this, no-await-in-loop, max-len, no-promise-executor-return */
4 |
5 | const { basename } = require('path');
6 | const MultipartLite = require('multi-part-lite');
7 | const {
8 | isBuffer, isStream, isHTTPStream, isVinyl
9 | } = require('multi-part-lite/lib/helpers');
10 | const mime = require('mime-kind');
11 |
12 | const {
13 | init, started, ended, stack, generate, next, length
14 | } = MultipartLite.symbols;
15 |
16 | const CRLF = '\r\n';
17 |
18 | class Multipart extends MultipartLite {
19 | /**
20 | * Returns file name of val
21 | * @param {Object} val
22 | * @param {String} [val.filename]
23 | * @param {String} [val.path]
24 | * @param {Object} defaults
25 | * @param {String} defaults.name
26 | * @param {String} defaults.ext
27 | * @returns {Promise}
28 | * @async
29 | */
30 | async getFileName(val, { name, ext, type }) {
31 | if (isBuffer(val)) {
32 | const m = await mime.async(val, type);
33 | return `${name}.${m.ext}`;
34 | }
35 |
36 | const filename = val.filename || val.path;
37 |
38 | if (filename) {
39 | return basename(filename);
40 | }
41 |
42 | return `${name}.${ext}`;
43 | }
44 |
45 | /**
46 | * Returns content-type of val
47 | * @param {Object} val
48 | * @param {String} [val.contentType]
49 | * @param {Object} defaults
50 | * @param {String} defaults.type
51 | * @returns {Promise}
52 | * @async
53 | */
54 | async getContentType(val, { type }) {
55 | if (val.contentType) {
56 | return val.contentType;
57 | }
58 | const m = await mime.async(val.filename, type);
59 | return m.mime;
60 | }
61 |
62 | async [init]() {
63 | if (this[ended] || this[started]) {
64 | return;
65 | }
66 | this[started] = true;
67 | let value = this[stack].shift();
68 | while (value) {
69 | await this[generate](...value);
70 | value = this[stack].shift();
71 | }
72 | this._append(`--${this.getBoundary()}--`, CRLF);
73 | this[ended] = true;
74 | this[next]();
75 | }
76 |
77 | async [generate](field, value, { filename, contentType }) {
78 | this._append(`--${this.getBoundary()}${CRLF}`);
79 | this._append(`Content-Disposition: form-data; name="${field}"`);
80 | if (isBuffer(value) || isStream(value) || isHTTPStream(value) || isVinyl(value)) {
81 | if (isVinyl(value)) {
82 | filename = filename || value.basename;
83 | value = value.contents;
84 | }
85 | const file = await this.getFileName(filename ? { filename } : value, this.opts.defaults);
86 | this._append(`; filename="${file}"${CRLF}`);
87 | const type = await this.getContentType({ filename: filename || file, contentType }, this.opts.defaults);
88 | this._append(`Content-Type: ${type}${CRLF}`);
89 | } else {
90 | this._append(CRLF);
91 | }
92 |
93 | return this._append(CRLF, value, CRLF);
94 | }
95 |
96 | /**
97 | * Returns stream
98 | * @returns {Promise}
99 | * @async
100 | */
101 | async stream() {
102 | await this[init]();
103 | return this;
104 | }
105 |
106 | /**
107 | * Returns buffer of the stream
108 | * @returns {Promise}
109 | * @async
110 | */
111 | async buffer() {
112 | return new Promise((resolve, reject) => {
113 | this.once('error', reject);
114 | const buffer = [];
115 | this.on('data', (data) => {
116 | buffer.push(data);
117 | });
118 | this.on('end', () => {
119 | const body = Buffer.concat(buffer);
120 | this[length] = Buffer.byteLength(body);
121 | return resolve(body);
122 | });
123 | return this[init]().catch(reject);
124 | });
125 | }
126 | }
127 |
128 | module.exports = Multipart;
129 | module.exports.MultipartAsync = Multipart;
130 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "multi-part",
3 | "author": "Alexey Bystrov ",
4 | "version": "4.0.0",
5 | "engines": {
6 | "node": ">=10"
7 | },
8 | "description": "Simple multipart/form-data implementation with automatic data type detection. Supports: Strings, Numbers, Arrays, Streams, Buffers and Vinyl.",
9 | "keywords": [
10 | "multi-part",
11 | "form",
12 | "data",
13 | "buffer",
14 | "stream",
15 | "vinyl",
16 | "form-data",
17 | "multipart"
18 | ],
19 | "main": "./main.js",
20 | "files": [
21 | "main.js"
22 | ],
23 | "scripts": {
24 | "test": "mocha test",
25 | "lint": "eslint main.js",
26 | "check": "npm run lint && npm run test",
27 | "cover": "nyc ./node_modules/mocha/bin/_mocha && nyc report --reporter=html"
28 | },
29 | "repository": {
30 | "type": "git",
31 | "url": "git+https://github.com/strikeentco/multi-part.git"
32 | },
33 | "bugs": {
34 | "url": "https://github.com/strikeentco/multi-part/issues"
35 | },
36 | "dependencies": {
37 | "mime-kind": "^4.0.0",
38 | "multi-part-lite": "^1.0.0"
39 | },
40 | "devDependencies": {
41 | "eslint": "^8.24.0",
42 | "eslint-config-airbnb-base": "^15.0.0",
43 | "eslint-plugin-import": "^2.26.0",
44 | "express": "^4.18.1",
45 | "got": "^9.6.0",
46 | "mocha": "^6.2.3",
47 | "multer": "^1.4.4",
48 | "nyc": "^14.1.1",
49 | "should": "^13.2.3",
50 | "vinyl": "^2.2.1"
51 | },
52 | "license": "MIT"
53 | }
54 |
--------------------------------------------------------------------------------
/test/bufferAsync.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const should = require('should/as-function');
4 | const http = require('http');
5 | const https = require('https');
6 | const fs = require('fs');
7 | const Stream = require('stream');
8 |
9 | const File = require('vinyl');
10 | const got = require('got');
11 | const app = require('express')();
12 | const multer = require('multer');
13 |
14 | const { MultipartAsync: Multipart } = require('../main');
15 |
16 | const upload = multer({ dest: `${__dirname}/uploads/` });
17 | const photoFile = `${__dirname}/fixture/fixture.jpg`;
18 |
19 | function chunkSync(data, length) {
20 | const buf = Buffer.alloc(length);
21 | const fd = fs.openSync(data.path, data.flags);
22 |
23 | fs.readSync(fd, buf, 0, length);
24 | fs.closeSync(fd);
25 |
26 | return buf;
27 | }
28 |
29 | const photoVinyl = new File({
30 | path: 'anon.jpg',
31 | contents: chunkSync({ path: photoFile, flags: 'r' }, 9379)
32 | });
33 |
34 | describe('multi-part.async().buffer()', function () {
35 | let server;
36 | this.timeout(10000);
37 | before((done) => {
38 | app.post('/', upload.single('photo'), (req, res) => {
39 | if (req.file) {
40 | return res.json({
41 | filename: req.file.originalname,
42 | mime: req.file.mimetype,
43 | fields: req.body
44 | });
45 | }
46 | return res.json('Nothing');
47 | });
48 |
49 | server = http.createServer(app);
50 | server.listen(4000, done);
51 | });
52 |
53 | after(() => server.close());
54 |
55 | describe('get custom boundary', () => {
56 | it('should be ok', () => {
57 | const form = new Multipart({ boundary: '--CustomBoundary12345' });
58 | should(form.getBoundary()).be.eql('--CustomBoundary12345');
59 | should(form.getHeaders()).be.eql({
60 | 'transfer-encoding': 'chunked',
61 | 'content-type': 'multipart/form-data; boundary="--CustomBoundary12345"'
62 | });
63 | });
64 | });
65 |
66 | describe('append nothing', () => {
67 | it('should be ok', async () => {
68 | const form = new Multipart();
69 | const body = await form.buffer();
70 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(), body }).then((res) => {
71 | should(res.body).be.eql('"Nothing"');
72 | });
73 | });
74 |
75 | it('should be ok', (done) => {
76 | const form = new Multipart();
77 | const req = http.request({
78 | headers: form.getHeaders(), hostname: '127.0.0.1', port: 4000, method: 'POST'
79 | }, (res) => {
80 | const chunks = [];
81 | res.on('data', (chunk) => {
82 | chunks.push(chunk);
83 | });
84 | res.on('end', () => {
85 | should(Buffer.concat(chunks).toString('utf8')).be.eql('"Nothing"');
86 | done();
87 | });
88 | });
89 | form.buffer().then((body) => {
90 | req.write(body);
91 | req.end();
92 | });
93 | });
94 |
95 | it('should throw', () => {
96 | const form = new Multipart();
97 | should(() => form.append({}, null)).throw('Field must be specified and must be a string or a number');
98 | should(() => form.append('test')).throw('Value can\'t be undefined');
99 | });
100 | });
101 |
102 | describe('append vinyl', () => {
103 | it('should be ok', async () => {
104 | const form = new Multipart();
105 | form.append('photo', photoVinyl);
106 | const body = await form.buffer();
107 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(), body }).then((res) => {
108 | should(JSON.parse(res.body)).be.eql({ filename: 'anon.jpg', mime: 'image/jpeg', fields: {} });
109 | });
110 | });
111 |
112 | it('should be ok', async () => {
113 | const form = new Multipart();
114 | form.append('photo', photoVinyl, { filename: 'anon.jpg' });
115 | const body = await form.buffer();
116 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(), body }).then((res) => {
117 | should(JSON.parse(res.body)).be.eql({ filename: 'anon.jpg', mime: 'image/jpeg', fields: {} });
118 | });
119 | });
120 |
121 | it('should be ok', (done) => {
122 | const form = new Multipart();
123 | form.append('photo', photoVinyl);
124 | const req = http.request({
125 | headers: form.getHeaders(), hostname: '127.0.0.1', port: 4000, method: 'POST'
126 | }, (res) => {
127 | const chunks = [];
128 | res.on('data', (chunk) => {
129 | chunks.push(chunk);
130 | });
131 | res.on('end', () => {
132 | should(JSON.parse(Buffer.concat(chunks).toString('utf8'))).be.eql({ filename: 'anon.jpg', mime: 'image/jpeg', fields: {} });
133 | done();
134 | });
135 | });
136 | form.buffer().then((body) => {
137 | req.write(body);
138 | req.end();
139 | });
140 | });
141 | });
142 |
143 | describe('append array', () => {
144 | it('should be ok', async () => {
145 | const form = new Multipart();
146 | form.append('array', ['arr', ['arr1', 'arr2'], 'arr3', null]);
147 | form.append('photo', photoVinyl);
148 | form.append('array', []);
149 | const body = await form.buffer();
150 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(), body }).then((res) => {
151 | should(JSON.parse(res.body)).be.eql({ filename: 'anon.jpg', mime: 'image/jpeg', fields: { array: ['arr', 'arr1', 'arr2', 'arr3', '0', ''] } });
152 | });
153 | });
154 |
155 | it('should be ok', (done) => {
156 | const form = new Multipart();
157 | form.append('array', ['arr', ['arr1', 'arr2'], 'arr3', null]);
158 | form.append('photo', photoVinyl);
159 | form.append('array', []);
160 | const req = http.request({
161 | headers: form.getHeaders(), hostname: '127.0.0.1', port: 4000, method: 'POST'
162 | }, (res) => {
163 | const chunks = [];
164 | res.on('data', (chunk) => {
165 | chunks.push(chunk);
166 | });
167 | res.on('end', () => {
168 | should(JSON.parse(Buffer.concat(chunks).toString('utf8'))).be.eql({ filename: 'anon.jpg', mime: 'image/jpeg', fields: { array: ['arr', 'arr1', 'arr2', 'arr3', '0', ''] } });
169 | done();
170 | });
171 | });
172 | form.buffer().then((body) => {
173 | req.write(body);
174 | req.end();
175 | });
176 | });
177 | });
178 |
179 | describe('append stream', () => {
180 | describe('chunked', () => {
181 | it('should be ok', async () => {
182 | const form = new Multipart();
183 | form.append('field', 12345);
184 | form.append('photo', fs.createReadStream(photoFile));
185 | form.append('field', null);
186 | const body = await form.buffer();
187 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(), body }).then((res) => {
188 | should(JSON.parse(res.body)).be.eql({ filename: 'fixture.jpg', mime: 'image/jpeg', fields: { field: ['12345', '0'] } });
189 | });
190 | });
191 |
192 | it('should be ok', async () => {
193 | const form = new Multipart();
194 | form.append('field', 12345);
195 | form.append('photo', fs.createReadStream(photoFile), { filename: 'a.jpg', contentType: 'image/jpeg' });
196 | form.append('field', null);
197 | const body = await form.buffer();
198 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(), body }).then((res) => {
199 | should(res.body).be.eql('{"filename":"a.jpg","mime":"image/jpeg","fields":{"field":["12345","0"]}}');
200 | });
201 | });
202 |
203 | it('should be ok', async () => {
204 | const form = new Multipart();
205 | form.append('field', 12345);
206 | form.append('photo', fs.createReadStream(photoFile), { filename: 'a.jpg' });
207 | form.append('field', null);
208 | const body = await form.buffer();
209 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(), body }).then((res) => {
210 | should(res.body).be.eql('{"filename":"a.jpg","mime":"image/jpeg","fields":{"field":["12345","0"]}}');
211 | });
212 | });
213 |
214 | it('should be ok', async () => {
215 | const form = new Multipart();
216 | form.append('field', 12345);
217 | form.append('photo', fs.createReadStream(photoFile), { contentType: 'image/jpeg' });
218 | form.append('field', null);
219 | const body = await form.buffer();
220 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(), body }).then((res) => {
221 | should(res.body).be.eql('{"filename":"fixture.jpg","mime":"image/jpeg","fields":{"field":["12345","0"]}}');
222 | });
223 | });
224 |
225 | it('should be ok', async () => {
226 | const form = new Multipart();
227 | form.append('field', 12345);
228 | form.append('photo', https.request('https://avatars1.githubusercontent.com/u/2401029'));
229 | form.append('field', null);
230 | const body = await form.buffer();
231 | await got.post('http://127.0.0.1:4000', { headers: form.getHeaders(), body }).then((res) => {
232 | should(res.body).be.eql('{"filename":"2401029","mime":"application/octet-stream","fields":{"field":["12345","0"]}}');
233 | });
234 | await got.post('http://127.0.0.1:4000', { headers: form.getHeaders(), body }).then((res) => {
235 | should(res.body).be.eql('{"filename":"2401029","mime":"application/octet-stream","fields":{"field":["12345","0"]}}');
236 | });
237 | });
238 |
239 | it('should be ok', (done) => {
240 | const form = new Multipart();
241 | form.append('field', 12345);
242 | form.append('photo', https.request('https://avatars1.githubusercontent.com/u/2401029'));
243 | form.append('field', null);
244 | const req = http.request({
245 | headers: form.getHeaders(), hostname: '127.0.0.1', port: 4000, method: 'POST'
246 | }, (res) => {
247 | const chunks = [];
248 | res.on('data', (chunk) => {
249 | chunks.push(chunk);
250 | });
251 | res.on('end', () => {
252 | should(Buffer.concat(chunks).toString('utf8')).be.eql('{"filename":"2401029","mime":"application/octet-stream","fields":{"field":["12345","0"]}}');
253 | done();
254 | });
255 | });
256 | form.buffer().then((body) => {
257 | req.write(body);
258 | req.end();
259 | });
260 | });
261 |
262 | it('should be ok', async () => {
263 | const stream = new Stream();
264 | stream.readable = true;
265 |
266 | setTimeout(() => {
267 | stream.emit('end');
268 | stream.emit('close');
269 | }, 50);
270 |
271 | const form = new Multipart();
272 | form.append('field', stream);
273 | const body = await form.buffer();
274 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(), body }).then((res) => {
275 | should(res.body).be.eql('"Nothing"');
276 | });
277 | });
278 |
279 | it('should be ok', async () => {
280 | const stream = new Stream();
281 | stream.readable = true;
282 |
283 | const form = new Multipart();
284 | form.append('field', stream);
285 | setTimeout(() => {
286 | stream.emit('end');
287 | stream.emit('close');
288 | }, 50);
289 | const body = await form.buffer();
290 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(), body }).then((res) => {
291 | should(res.body).be.eql('"Nothing"');
292 | });
293 | });
294 |
295 | it('should throw', async () => {
296 | const stream = new Stream();
297 | stream.readable = true;
298 | stream.destroy = () => {
299 | stream.emit('end');
300 | };
301 |
302 | const form = new Multipart();
303 | form.append('field', stream);
304 | setTimeout(() => {
305 | stream.emit('error');
306 | }, 50);
307 | const body = await form.buffer();
308 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(), body }).catch((e) => {
309 | should(e.message).be.eql('Response code 500 (Internal Server Error)');
310 | });
311 | });
312 |
313 | it('should be ok', async () => {
314 | const stream = new Stream();
315 | stream.readable = true;
316 | stream.destroy = () => {
317 | stream.emit('end');
318 | };
319 |
320 | stream.emit('data', 'Text');
321 | setTimeout(() => {
322 | stream.emit('end');
323 | }, 50);
324 |
325 | const form = new Multipart();
326 | form.append('field', 12345);
327 | form.append('photo', https.request('https://avatars1.githubusercontent.com/u/2401029'));
328 | form.append('field', stream);
329 | const body = await form.buffer();
330 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(), body }).then((res) => {
331 | should(res.body).be.eql('{"filename":"2401029","mime":"application/octet-stream","fields":{"field":["12345",""]}}');
332 | });
333 | });
334 |
335 | it('should be ok', (done) => {
336 | https.get('https://avatars1.githubusercontent.com/u/2401029')
337 | .on('response', async (photo) => {
338 | const form = new Multipart();
339 | form.append('field', 12345);
340 | form.append('photo', photo);
341 | form.append('field', null);
342 | const body = await form.buffer();
343 | got.post('http://127.0.0.1:4000', { headers: form.getHeaders(), body }).then((res) => {
344 | should(res.body).be.eql('{"filename":"file.bin","mime":"application/octet-stream","fields":{"field":["12345","0"]}}');
345 | done();
346 | });
347 | });
348 | });
349 |
350 | it('should throw', async () => {
351 | const form = new Multipart();
352 | form.append('photo', got.stream('http://127.0.0.1', { retries: 0 }));
353 | return form.buffer().catch(e => should(e.message).startWith('connect ECONNREFUSED'));
354 | });
355 |
356 | it('should throw', () => {
357 | const form = new Multipart();
358 | form.append('field', 12345);
359 | form.append('field', null);
360 | form._append = {};
361 | return form.buffer().catch(e => should(e.message).be.eql('this._append is not a function'));
362 | });
363 |
364 | it('should throw', () => {
365 | const form = new Multipart();
366 | form.append('field', 12345);
367 | form.append('photo', http.request({ hostname: '127.0.0.1' }));
368 | form.append('photo', fs.createReadStream(photoFile));
369 | form.append('field', null);
370 | return form.buffer().catch(e => should(e.message).startWith('connect ECONNREFUSED'));
371 | });
372 | });
373 |
374 | describe('length', () => {
375 | it('should be ok', async () => {
376 | const form = new Multipart();
377 | form.append('field', 12345);
378 | form.append('photo', fs.createReadStream(photoFile));
379 | form.append('field', null);
380 | const body = await form.buffer();
381 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(false), body }).then((res) => {
382 | should(JSON.parse(res.body)).be.eql({ filename: 'fixture.jpg', mime: 'image/jpeg', fields: { field: ['12345', '0'] } });
383 | });
384 | });
385 |
386 | it('should be ok', async () => {
387 | const form = new Multipart();
388 | form.append('field', 12345);
389 | form.append('photo', fs.createReadStream(photoFile), { filename: 'a.jpg', contentType: 'image/jpeg' });
390 | form.append('field', null);
391 | const body = await form.buffer();
392 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(false), body }).then((res) => {
393 | should(res.body).be.eql('{"filename":"a.jpg","mime":"image/jpeg","fields":{"field":["12345","0"]}}');
394 | });
395 | });
396 |
397 | it('should be ok', async () => {
398 | const form = new Multipart();
399 | form.append('field', 12345);
400 | form.append('photo', fs.createReadStream(photoFile), { filename: 'a.jpg' });
401 | form.append('field', null);
402 | const body = await form.buffer();
403 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(false), body }).then((res) => {
404 | should(res.body).be.eql('{"filename":"a.jpg","mime":"image/jpeg","fields":{"field":["12345","0"]}}');
405 | });
406 | });
407 |
408 | it('should be ok', async () => {
409 | const form = new Multipart();
410 | form.append('field', 12345);
411 | form.append('photo', fs.createReadStream(photoFile), { contentType: 'image/jpeg' });
412 | form.append('field', null);
413 | const body = await form.buffer();
414 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(false), body }).then((res) => {
415 | should(res.body).be.eql('{"filename":"fixture.jpg","mime":"image/jpeg","fields":{"field":["12345","0"]}}');
416 | });
417 | });
418 |
419 | it('should be ok', async () => {
420 | const form = new Multipart();
421 | form.append('field', 12345);
422 | form.append('photo', https.request('https://avatars1.githubusercontent.com/u/2401029'));
423 | form.append('field', null);
424 | const body = await form.buffer();
425 | await got.post('http://127.0.0.1:4000', { headers: form.getHeaders(false), body }).then((res) => {
426 | should(res.body).be.eql('{"filename":"2401029","mime":"application/octet-stream","fields":{"field":["12345","0"]}}');
427 | });
428 | await got.post('http://127.0.0.1:4000', { headers: form.getHeaders(false), body }).then((res) => {
429 | should(res.body).be.eql('{"filename":"2401029","mime":"application/octet-stream","fields":{"field":["12345","0"]}}');
430 | });
431 | });
432 |
433 | it('should be ok', (done) => {
434 | const form = new Multipart();
435 | form.append('field', 12345);
436 | form.append('photo', https.request('https://avatars1.githubusercontent.com/u/2401029'));
437 | form.append('field', null);
438 | form.buffer().then((body) => {
439 | const req = http.request({
440 | headers: form.getHeaders(false), hostname: '127.0.0.1', port: 4000, method: 'POST'
441 | }, (res) => {
442 | const chunks = [];
443 | res.on('data', (chunk) => {
444 | chunks.push(chunk);
445 | });
446 | res.on('end', () => {
447 | should(Buffer.concat(chunks).toString('utf8')).be.eql('{"filename":"2401029","mime":"application/octet-stream","fields":{"field":["12345","0"]}}');
448 | done();
449 | });
450 | });
451 | req.write(body);
452 | req.end();
453 | });
454 | });
455 |
456 | it('should be ok', async () => {
457 | const stream = new Stream();
458 | stream.readable = true;
459 |
460 | setTimeout(() => {
461 | stream.emit('end');
462 | stream.emit('close');
463 | }, 50);
464 |
465 | const form = new Multipart();
466 | form.append('field', stream);
467 | const body = await form.buffer();
468 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(false), body }).then((res) => {
469 | should(res.body).be.eql('"Nothing"');
470 | });
471 | });
472 |
473 | it('should be ok', async () => {
474 | const stream = new Stream();
475 | stream.readable = true;
476 |
477 | const form = new Multipart();
478 | form.append('field', stream);
479 | setTimeout(() => {
480 | stream.emit('end');
481 | stream.emit('close');
482 | }, 50);
483 | const body = await form.buffer();
484 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(false), body }).then((res) => {
485 | should(res.body).be.eql('"Nothing"');
486 | });
487 | });
488 |
489 | it('should throw', async () => {
490 | const stream = new Stream();
491 | stream.readable = true;
492 | stream.destroy = () => {
493 | stream.emit('end');
494 | };
495 |
496 | const form = new Multipart();
497 | form.append('field', stream);
498 | setTimeout(() => {
499 | stream.emit('error');
500 | }, 50);
501 | const body = await form.buffer();
502 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(false), body }).catch((e) => {
503 | should(e.message).be.eql('Response code 500 (Internal Server Error)');
504 | });
505 | });
506 |
507 | it('should be ok', async () => {
508 | const stream = new Stream();
509 | stream.readable = true;
510 | stream.destroy = () => {
511 | stream.emit('end');
512 | };
513 |
514 | stream.emit('data', 'Text');
515 | setTimeout(() => {
516 | stream.emit('end');
517 | }, 50);
518 |
519 | const form = new Multipart();
520 | form.append('field', 12345);
521 | form.append('photo', https.request('https://avatars1.githubusercontent.com/u/2401029'));
522 | form.append('field', stream);
523 | const body = await form.buffer();
524 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(false), body }).then((res) => {
525 | should(res.body).be.eql('{"filename":"2401029","mime":"application/octet-stream","fields":{"field":["12345",""]}}');
526 | });
527 | });
528 |
529 | it('should be ok', (done) => {
530 | https.get('https://avatars1.githubusercontent.com/u/2401029')
531 | .on('response', async (photo) => {
532 | const form = new Multipart();
533 | form.append('field', 12345);
534 | form.append('photo', photo);
535 | form.append('field', null);
536 | const body = await form.buffer();
537 | got.post('http://127.0.0.1:4000', { headers: form.getHeaders(false), body }).then((res) => {
538 | should(res.body).be.eql('{"filename":"file.bin","mime":"application/octet-stream","fields":{"field":["12345","0"]}}');
539 | done();
540 | });
541 | });
542 | });
543 | });
544 | });
545 |
546 | describe('append buffer', () => {
547 | const photoBuffer = chunkSync({ path: photoFile, flags: 'r' }, 9379);
548 | describe('chunked', () => {
549 | it('should be ok', async () => {
550 | const form = new Multipart();
551 | form.append('field', 12345);
552 | form.append('photo', photoBuffer);
553 | form.append('field', null);
554 | const body = await form.buffer();
555 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(), body }).then((res) => {
556 | should(res.body).be.eql('{"filename":"file.jpg","mime":"image/jpeg","fields":{"field":["12345","0"]}}');
557 | });
558 | });
559 |
560 | it('should be ok', async () => {
561 | const form = new Multipart();
562 | form.append('field', 12345);
563 | form.append('photo', photoBuffer, { filename: 'a.jpg', contentType: 'image/jpeg' });
564 | form.append('field', null);
565 | const body = await form.buffer();
566 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(), body }).then((res) => {
567 | should(res.body).be.eql('{"filename":"a.jpg","mime":"image/jpeg","fields":{"field":["12345","0"]}}');
568 | });
569 | });
570 |
571 | it('should be ok', async () => {
572 | const form = new Multipart();
573 | form.append('field', 12345);
574 | form.append('photo', photoBuffer, { filename: 'a.jpg' });
575 | form.append('field', null);
576 | const body = await form.buffer();
577 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(), body }).then((res) => {
578 | should(res.body).be.eql('{"filename":"a.jpg","mime":"image/jpeg","fields":{"field":["12345","0"]}}');
579 | });
580 | });
581 |
582 | it('should be ok', async () => {
583 | const form = new Multipart();
584 | form.append('field', 12345);
585 | form.append('photo', photoBuffer, { contentType: 'image/jpeg' });
586 | form.append('field', null);
587 | const body = await form.buffer();
588 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(), body }).then((res) => {
589 | should(res.body).be.eql('{"filename":"file.jpg","mime":"image/jpeg","fields":{"field":["12345","0"]}}');
590 | });
591 | });
592 |
593 | it('should be ok', (done) => {
594 | const form = new Multipart();
595 | form.append('field', 12345);
596 | form.append('photo', photoBuffer);
597 | form.append('field', null);
598 | const req = http.request({
599 | headers: form.getHeaders(), hostname: '127.0.0.1', port: 4000, method: 'POST'
600 | }, (res) => {
601 | const chunks = [];
602 | res.on('data', (chunk) => {
603 | chunks.push(chunk);
604 | });
605 | res.on('end', () => {
606 | should(Buffer.concat(chunks).toString('utf8')).be.eql('{"filename":"file.jpg","mime":"image/jpeg","fields":{"field":["12345","0"]}}');
607 | done();
608 | });
609 | });
610 | form.buffer().then((body) => {
611 | req.write(body);
612 | req.end();
613 | });
614 | });
615 | });
616 |
617 | describe('length', () => {
618 | it('should be ok', async () => {
619 | const form = new Multipart();
620 | form.append('field', 12345);
621 | form.append('photo', photoBuffer);
622 | form.append('field', null);
623 | const body = await form.buffer();
624 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(false), body }).then((res) => {
625 | should(res.body).be.eql('{"filename":"file.jpg","mime":"image/jpeg","fields":{"field":["12345","0"]}}');
626 | });
627 | });
628 |
629 | it('should be ok', async () => {
630 | const form = new Multipart();
631 | form.append('field', 12345);
632 | form.append('photo', photoBuffer, { filename: 'a.jpg', contentType: 'image/jpeg' });
633 | form.append('field', null);
634 | const body = await form.buffer();
635 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(false), body }).then((res) => {
636 | should(res.body).be.eql('{"filename":"a.jpg","mime":"image/jpeg","fields":{"field":["12345","0"]}}');
637 | });
638 | });
639 |
640 | it('should be ok', async () => {
641 | const form = new Multipart();
642 | form.append('field', 12345);
643 | form.append('photo', photoBuffer, { filename: 'a.jpg' });
644 | form.append('field', null);
645 | const body = await form.buffer();
646 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(false), body }).then((res) => {
647 | should(res.body).be.eql('{"filename":"a.jpg","mime":"image/jpeg","fields":{"field":["12345","0"]}}');
648 | });
649 | });
650 |
651 | it('should be ok', async () => {
652 | const form = new Multipart();
653 | form.append('field', 12345);
654 | form.append('photo', photoBuffer, { contentType: 'image/jpeg' });
655 | form.append('field', null);
656 | const body = await form.buffer();
657 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(false), body }).then((res) => {
658 | should(res.body).be.eql('{"filename":"file.jpg","mime":"image/jpeg","fields":{"field":["12345","0"]}}');
659 | });
660 | });
661 |
662 | it('should be ok', (done) => {
663 | const form = new Multipart();
664 | form.append('field', 12345);
665 | form.append('photo', photoBuffer);
666 | form.append('field', null);
667 | form.buffer().then((body) => {
668 | const req = http.request({
669 | headers: form.getHeaders(false), hostname: '127.0.0.1', port: 4000, method: 'POST'
670 | }, (res) => {
671 | const chunks = [];
672 | res.on('data', (chunk) => {
673 | chunks.push(chunk);
674 | });
675 | res.on('end', () => {
676 | should(Buffer.concat(chunks).toString('utf8')).be.eql('{"filename":"file.jpg","mime":"image/jpeg","fields":{"field":["12345","0"]}}');
677 | done();
678 | });
679 | });
680 | req.write(body);
681 | req.end();
682 | });
683 | });
684 | });
685 | });
686 | });
687 |
--------------------------------------------------------------------------------
/test/fixture/fixture.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strikeentco/multi-part/06968a326cd7921fb86acf209b591290e71418ff/test/fixture/fixture.jpg
--------------------------------------------------------------------------------
/test/streamAsync.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const should = require('should/as-function');
4 | const http = require('http');
5 | const https = require('https');
6 | const fs = require('fs');
7 | const Stream = require('stream');
8 |
9 | const File = require('vinyl');
10 | const got = require('got');
11 | const app = require('express')();
12 | const multer = require('multer');
13 |
14 | const { MultipartAsync: Multipart } = require('../main');
15 |
16 | const upload = multer({ dest: `${__dirname}/uploads/` });
17 | const photoFile = `${__dirname}/fixture/fixture.jpg`;
18 |
19 | function chunkSync(data, length) {
20 | const buf = Buffer.alloc(length);
21 | const fd = fs.openSync(data.path, data.flags);
22 |
23 | fs.readSync(fd, buf, 0, length);
24 | fs.closeSync(fd);
25 |
26 | return buf;
27 | }
28 |
29 | const photoVinyl = new File({
30 | path: 'anon.jpg',
31 | contents: chunkSync({ path: photoFile, flags: 'r' }, 9379)
32 | });
33 |
34 | describe('multi-part.async().stream()', function () {
35 | let server;
36 | this.timeout(10000);
37 | before((done) => {
38 | app.post('/', upload.single('photo'), (req, res) => {
39 | if (req.file) {
40 | return res.json({
41 | filename: req.file.originalname,
42 | mime: req.file.mimetype,
43 | fields: req.body
44 | });
45 | }
46 | return res.json('Nothing');
47 | });
48 |
49 | server = http.createServer(app);
50 | server.listen(4000, done);
51 | });
52 |
53 | after(() => server.close());
54 |
55 | describe('get custom boundary', () => {
56 | it('should be ok', () => {
57 | const form = new Multipart({ boundary: '--CustomBoundary12345' });
58 | should(form.getBoundary()).be.eql('--CustomBoundary12345');
59 | should(form.getHeaders()).be.eql({
60 | 'transfer-encoding': 'chunked',
61 | 'content-type': 'multipart/form-data; boundary="--CustomBoundary12345"'
62 | });
63 | });
64 | });
65 |
66 | describe('append nothing', () => {
67 | it('should be ok', async () => {
68 | const form = new Multipart();
69 | const body = await form.stream();
70 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(), body }).then((res) => {
71 | should(res.body).be.eql('"Nothing"');
72 | });
73 | });
74 |
75 | it('should be ok', (done) => {
76 | const form = new Multipart();
77 | const req = http.request({
78 | headers: form.getHeaders(), hostname: '127.0.0.1', port: 4000, method: 'POST'
79 | }, (res) => {
80 | const chunks = [];
81 | res.on('data', (chunk) => {
82 | chunks.push(chunk);
83 | });
84 | res.on('end', () => {
85 | should(Buffer.concat(chunks).toString('utf8')).be.eql('"Nothing"');
86 | done();
87 | });
88 | });
89 | form.stream().then((body) => body.pipe(req));
90 | });
91 |
92 | it('should throw', () => {
93 | const form = new Multipart();
94 | should(() => form.append({}, null)).throw('Field must be specified and must be a string or a number');
95 | should(() => form.append('test')).throw('Value can\'t be undefined');
96 | });
97 | });
98 |
99 | describe('append vinyl', () => {
100 | it('should be ok', async () => {
101 | const form = new Multipart();
102 | form.append('photo', photoVinyl);
103 | const body = await form.stream();
104 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(), body }).then((res) => {
105 | should(JSON.parse(res.body)).be.eql({ filename: 'anon.jpg', mime: 'image/jpeg', fields: {} });
106 | });
107 | });
108 |
109 | it('should be ok', async () => {
110 | const form = new Multipart();
111 | form.append('photo', photoVinyl, { filename: 'anon.jpg' });
112 | const body = await form.stream();
113 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(), body }).then((res) => {
114 | should(JSON.parse(res.body)).be.eql({ filename: 'anon.jpg', mime: 'image/jpeg', fields: {} });
115 | });
116 | });
117 |
118 | it('should be ok', (done) => {
119 | const form = new Multipart();
120 | form.append('photo', photoVinyl);
121 | const req = http.request({
122 | headers: form.getHeaders(), hostname: '127.0.0.1', port: 4000, method: 'POST'
123 | }, (res) => {
124 | const chunks = [];
125 | res.on('data', (chunk) => {
126 | chunks.push(chunk);
127 | });
128 | res.on('end', () => {
129 | should(JSON.parse(Buffer.concat(chunks).toString('utf8'))).be.eql({ filename: 'anon.jpg', mime: 'image/jpeg', fields: {} });
130 | done();
131 | });
132 | });
133 | form.stream().then((body) => body.pipe(req));
134 | });
135 | });
136 |
137 | describe('append array', () => {
138 | it('should be ok', async () => {
139 | const form = new Multipart();
140 | form.append('array', ['arr', ['arr1', 'arr2'], 'arr3', null]);
141 | form.append('photo', photoVinyl);
142 | form.append('array', []);
143 | const body = await form.stream();
144 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(), body }).then((res) => {
145 | should(JSON.parse(res.body)).be.eql({ filename: 'anon.jpg', mime: 'image/jpeg', fields: { array: ['arr', 'arr1', 'arr2', 'arr3', '0', ''] } });
146 | });
147 | });
148 |
149 | it('should be ok', (done) => {
150 | const form = new Multipart();
151 | form.append('array', ['arr', ['arr1', 'arr2'], 'arr3', null]);
152 | form.append('photo', photoVinyl);
153 | form.append('array', []);
154 | const req = http.request({
155 | headers: form.getHeaders(), hostname: '127.0.0.1', port: 4000, method: 'POST'
156 | }, (res) => {
157 | const chunks = [];
158 | res.on('data', (chunk) => {
159 | chunks.push(chunk);
160 | });
161 | res.on('end', () => {
162 | should(JSON.parse(Buffer.concat(chunks).toString('utf8'))).be.eql({ filename: 'anon.jpg', mime: 'image/jpeg', fields: { array: ['arr', 'arr1', 'arr2', 'arr3', '0', ''] } });
163 | done();
164 | });
165 | });
166 | form.stream().then((body) => body.pipe(req));
167 | });
168 | });
169 |
170 | describe('append stream', () => {
171 | it('should be ok', async () => {
172 | const form = new Multipart();
173 | form.append('field', 12345);
174 | form.append('photo', fs.createReadStream(photoFile));
175 | form.append('field', null);
176 | const body = await form.stream();
177 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(), body }).then((res) => {
178 | should(JSON.parse(res.body)).be.eql({ filename: 'fixture.jpg', mime: 'image/jpeg', fields: { field: ['12345', '0'] } });
179 | });
180 | });
181 |
182 | it('should be ok', async () => {
183 | const form = new Multipart();
184 | form.append('field', 12345);
185 | form.append('photo', fs.createReadStream(photoFile), { filename: 'a.jpg', contentType: 'image/jpeg' });
186 | form.append('field', null);
187 | const body = await form.stream();
188 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(), body }).then((res) => {
189 | should(res.body).be.eql('{"filename":"a.jpg","mime":"image/jpeg","fields":{"field":["12345","0"]}}');
190 | });
191 | });
192 |
193 | it('should be ok', async () => {
194 | const form = new Multipart();
195 | form.append('field', 12345);
196 | form.append('photo', fs.createReadStream(photoFile), { filename: 'a.jpg' });
197 | form.append('field', null);
198 | const body = await form.stream();
199 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(), body }).then((res) => {
200 | should(res.body).be.eql('{"filename":"a.jpg","mime":"image/jpeg","fields":{"field":["12345","0"]}}');
201 | });
202 | });
203 |
204 | it('should be ok', async () => {
205 | const form = new Multipart();
206 | form.append('field', 12345);
207 | form.append('photo', fs.createReadStream(photoFile), { contentType: 'image/jpeg' });
208 | form.append('field', null);
209 | const body = await form.stream();
210 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(), body }).then((res) => {
211 | should(res.body).be.eql('{"filename":"fixture.jpg","mime":"image/jpeg","fields":{"field":["12345","0"]}}');
212 | });
213 | });
214 |
215 | it('should be ok', async () => {
216 | const form = new Multipart();
217 | form.append('field', 12345);
218 | form.append('photo', https.request('https://avatars1.githubusercontent.com/u/2401029'));
219 | form.append('field', null);
220 | let body = await form.stream();
221 | await got.post('http://127.0.0.1:4000', { headers: form.getHeaders(), body }).then((res) => {
222 | should(res.body).be.eql('{"filename":"2401029","mime":"application/octet-stream","fields":{"field":["12345","0"]}}');
223 | });
224 | body = await form.stream();
225 | await got.post('http://127.0.0.1:4000', { headers: form.getHeaders(), body }).then((res) => {
226 | should(res.body).be.eql('"Nothing"');
227 | });
228 | });
229 |
230 | it('should be ok', (done) => {
231 | const form = new Multipart();
232 | form.append('field', 12345);
233 | form.append('photo', https.request('https://avatars1.githubusercontent.com/u/2401029'));
234 | form.append('field', null);
235 | const req = http.request({
236 | headers: form.getHeaders(), hostname: '127.0.0.1', port: 4000, method: 'POST'
237 | }, (res) => {
238 | const chunks = [];
239 | res.on('data', (chunk) => {
240 | chunks.push(chunk);
241 | });
242 | res.on('end', () => {
243 | should(Buffer.concat(chunks).toString('utf8')).be.eql('{"filename":"2401029","mime":"application/octet-stream","fields":{"field":["12345","0"]}}');
244 | done();
245 | });
246 | });
247 | form.stream().then((body) => body.pipe(req));
248 | });
249 |
250 | it('should be ok', async () => {
251 | const stream = new Stream();
252 | stream.readable = true;
253 |
254 | setTimeout(() => {
255 | stream.emit('end');
256 | stream.emit('close');
257 | }, 50);
258 |
259 | const form = new Multipart();
260 | form.append('field', stream);
261 | const body = await form.stream();
262 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(), body }).then((res) => {
263 | should(res.body).be.eql('"Nothing"');
264 | });
265 | });
266 |
267 | it('should be ok', async () => {
268 | const stream = new Stream();
269 | stream.readable = true;
270 |
271 | const form = new Multipart();
272 | form.append('field', stream);
273 | setTimeout(() => {
274 | // stream.emit('close');
275 | stream.emit('end');
276 | stream.emit('close');
277 | }, 50);
278 | const body = await form.stream();
279 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(), body }).then((res) => {
280 | should(res.body).be.eql('"Nothing"');
281 | });
282 | });
283 |
284 | it('should throw', async () => {
285 | const stream = new Stream();
286 | stream.readable = true;
287 | stream.destroy = () => {
288 | stream.emit('end');
289 | };
290 |
291 | const form = new Multipart();
292 | form.append('field', stream);
293 | setTimeout(() => {
294 | stream.emit('error');
295 | }, 50);
296 | const body = await form.stream();
297 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(), body }).catch((e) => {
298 | should(e.message).be.eql('Response code 500 (Internal Server Error)');
299 | });
300 | });
301 |
302 | it('should be ok', async () => {
303 | const stream = new Stream();
304 | stream.readable = true;
305 | stream.destroy = () => {
306 | stream.emit('end');
307 | };
308 |
309 | stream.emit('data', 'Text');
310 | setTimeout(() => {
311 | stream.emit('end');
312 | }, 50);
313 |
314 | const form = new Multipart();
315 | form.append('field', 12345);
316 | form.append('photo', https.request('https://avatars1.githubusercontent.com/u/2401029'));
317 | form.append('field', stream);
318 | const body = await form.stream();
319 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(), body }).then((res) => {
320 | should(res.body).be.eql('{"filename":"2401029","mime":"application/octet-stream","fields":{"field":["12345",""]}}');
321 | });
322 | });
323 |
324 | it('should be ok', (done) => {
325 | https.get('https://avatars1.githubusercontent.com/u/2401029')
326 | .on('response', async (photo) => {
327 | const form = new Multipart();
328 | form.append('field', 12345);
329 | form.append('photo', photo);
330 | form.append('field', null);
331 | const body = await form.stream();
332 | got.post('http://127.0.0.1:4000', { headers: form.getHeaders(), body }).then((res) => {
333 | should(res.body).be.eql('{"filename":"file.bin","mime":"application/octet-stream","fields":{"field":["12345","0"]}}');
334 | done();
335 | });
336 | });
337 | });
338 |
339 | it('should throw', () => {
340 | const form = new Multipart();
341 | form.append('photo', got.stream('http://127.0.0.1', { retries: 0 }));
342 | return form.stream().catch((e) => should(e.message).startWith('connect ECONNREFUSED'));
343 | });
344 |
345 | it('should throw', () => {
346 | const form = new Multipart();
347 | form.append('field', 12345);
348 | form.append('field', null);
349 | form._append = {};
350 | return form.stream().catch((e) => should(e.message).startWith('this._append is not a function'));
351 | });
352 |
353 | it('should throw', (done) => {
354 | const form = new Multipart();
355 | form.append('field', 12345);
356 | form.append('photo', http.request({ hostname: '127.0.0.1' }));
357 | form.append('photo', fs.createReadStream(photoFile));
358 | form.append('field', null);
359 | form.once('error', (e) => {
360 | should(e.message).startWith('connect ECONNREFUSED');
361 | done();
362 | });
363 | form.stream();
364 | });
365 | });
366 |
367 | describe('append buffer', () => {
368 | const photoBuffer = chunkSync({ path: photoFile, flags: 'r' }, 9379);
369 | it('should be ok', async () => {
370 | const form = new Multipart();
371 | form.append('field', 12345);
372 | form.append('photo', photoBuffer);
373 | form.append('field', null);
374 | const body = await form.stream();
375 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(), body }).then((res) => {
376 | should(res.body).be.eql('{"filename":"file.jpg","mime":"image/jpeg","fields":{"field":["12345","0"]}}');
377 | });
378 | });
379 |
380 | it('should be ok', async () => {
381 | const form = new Multipart();
382 | form.append('field', 12345);
383 | form.append('photo', photoBuffer, { filename: 'a.jpg', contentType: 'image/jpeg' });
384 | form.append('field', null);
385 | const body = await form.stream();
386 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(), body }).then((res) => {
387 | should(res.body).be.eql('{"filename":"a.jpg","mime":"image/jpeg","fields":{"field":["12345","0"]}}');
388 | });
389 | });
390 |
391 | it('should be ok', async () => {
392 | const form = new Multipart();
393 | form.append('field', 12345);
394 | form.append('photo', photoBuffer, { filename: 'a.jpg' });
395 | form.append('field', null);
396 | const body = await form.stream();
397 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(), body }).then((res) => {
398 | should(res.body).be.eql('{"filename":"a.jpg","mime":"image/jpeg","fields":{"field":["12345","0"]}}');
399 | });
400 | });
401 |
402 | it('should be ok', async () => {
403 | const form = new Multipart();
404 | form.append('field', 12345);
405 | form.append('photo', photoBuffer, { contentType: 'image/jpeg' });
406 | form.append('field', null);
407 | const body = await form.stream();
408 | return got.post('http://127.0.0.1:4000', { headers: form.getHeaders(), body }).then((res) => {
409 | should(res.body).be.eql('{"filename":"file.jpg","mime":"image/jpeg","fields":{"field":["12345","0"]}}');
410 | });
411 | });
412 |
413 | it('should be ok', (done) => {
414 | const form = new Multipart();
415 | form.append('field', 12345);
416 | form.append('photo', photoBuffer);
417 | form.append('field', null);
418 | const req = http.request({
419 | headers: form.getHeaders(), hostname: '127.0.0.1', port: 4000, method: 'POST'
420 | }, (res) => {
421 | const chunks = [];
422 | res.on('data', (chunk) => {
423 | chunks.push(chunk);
424 | });
425 | res.on('end', () => {
426 | should(Buffer.concat(chunks).toString('utf8')).be.eql('{"filename":"file.jpg","mime":"image/jpeg","fields":{"field":["12345","0"]}}');
427 | done();
428 | });
429 | });
430 | form.stream().then((body) => body.pipe(req));
431 | });
432 | });
433 | });
434 |
--------------------------------------------------------------------------------