@e22m4u/js-http-static-router 0.1.2 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/.mocharc.json +2 -1
  2. package/README.md +265 -17
  3. package/build-cjs.js +1 -1
  4. package/dist/cjs/index.cjs +114 -76
  5. package/example/server.js +23 -13
  6. package/mocha.setup.js +4 -0
  7. package/package.json +10 -8
  8. package/src/http-static-router.d.ts +7 -2
  9. package/src/http-static-router.js +73 -61
  10. package/src/http-static-router.spec.js +899 -0
  11. package/src/static-route.js +18 -8
  12. package/src/types.d.ts +7 -0
  13. package/src/utils/create-cookie-string.d.ts +6 -0
  14. package/src/utils/create-cookie-string.js +28 -0
  15. package/src/utils/create-cookie-string.spec.js +32 -0
  16. package/src/utils/create-error.d.ts +14 -0
  17. package/src/utils/create-error.js +29 -0
  18. package/src/utils/create-error.spec.js +42 -0
  19. package/src/utils/create-request-mock.d.ts +35 -0
  20. package/src/utils/create-request-mock.js +483 -0
  21. package/src/utils/create-request-mock.spec.js +646 -0
  22. package/src/utils/create-response-mock.d.ts +18 -0
  23. package/src/utils/create-response-mock.js +131 -0
  24. package/src/utils/create-response-mock.spec.js +150 -0
  25. package/src/utils/fetch-request-body.d.ts +26 -0
  26. package/src/utils/fetch-request-body.js +148 -0
  27. package/src/utils/fetch-request-body.spec.js +209 -0
  28. package/src/utils/get-pathname-from-url.d.ts +1 -1
  29. package/src/utils/{get-request-pathname.spec.js → get-pathname-from-url.spec.js} +1 -1
  30. package/src/utils/index.d.ts +8 -0
  31. package/src/utils/index.js +8 -0
  32. package/src/utils/is-readable-stream.d.ts +9 -0
  33. package/src/utils/is-readable-stream.js +12 -0
  34. package/src/utils/is-readable-stream.spec.js +23 -0
  35. package/src/utils/parse-content-type.d.ts +15 -0
  36. package/src/utils/parse-content-type.js +34 -0
  37. package/src/utils/parse-content-type.spec.js +79 -0
  38. package/src/utils/parse-cookie-string.d.ts +19 -0
  39. package/src/utils/parse-cookie-string.js +36 -0
  40. package/src/utils/parse-cookie-string.spec.js +45 -0
  41. package/{example/static → static}/index.html +2 -2
  42. /package/{example/static/nested/file.txt → static/assets/nested/heart.txt} +0 -0
  43. /package/{example/static/file.txt → static/assets/rabbit.txt} +0 -0
@@ -0,0 +1,131 @@
1
+ import {PassThrough} from 'stream';
2
+
3
+ /**
4
+ * Create response mock.
5
+ *
6
+ * @returns {import('http').ServerResponse}
7
+ */
8
+ export function createResponseMock() {
9
+ const response = new PassThrough();
10
+ response.statusCode = 200;
11
+ patchEncoding(response);
12
+ patchHeaders(response);
13
+ patchBody(response);
14
+ return response;
15
+ }
16
+
17
+ /**
18
+ * Patch encoding.
19
+ *
20
+ * @param {object} response
21
+ */
22
+ function patchEncoding(response) {
23
+ Object.defineProperty(response, '_encoding', {
24
+ configurable: true,
25
+ writable: true,
26
+ value: undefined,
27
+ });
28
+ Object.defineProperty(response, 'setEncoding', {
29
+ configurable: true,
30
+ value: function (enc) {
31
+ this._encoding = enc;
32
+ return this;
33
+ },
34
+ });
35
+ Object.defineProperty(response, 'getEncoding', {
36
+ configurable: true,
37
+ value: function () {
38
+ return this._encoding;
39
+ },
40
+ });
41
+ }
42
+
43
+ /**
44
+ * Patch headers.
45
+ *
46
+ * @param {object} response
47
+ */
48
+ function patchHeaders(response) {
49
+ Object.defineProperty(response, '_headersSent', {
50
+ configurable: true,
51
+ writable: true,
52
+ value: false,
53
+ });
54
+ Object.defineProperty(response, 'headersSent', {
55
+ configurable: true,
56
+ get() {
57
+ return this._headersSent;
58
+ },
59
+ });
60
+ Object.defineProperty(response, '_headers', {
61
+ configurable: true,
62
+ writable: true,
63
+ value: {},
64
+ });
65
+ Object.defineProperty(response, 'setHeader', {
66
+ configurable: true,
67
+ value: function (name, value) {
68
+ if (this.headersSent) {
69
+ throw new Error(
70
+ 'Error [ERR_HTTP_HEADERS_SENT]: ' +
71
+ 'Cannot set headers after they are sent to the client',
72
+ );
73
+ }
74
+ const key = name.toLowerCase();
75
+ this._headers[key] = Array.isArray(value)
76
+ ? [...value.map(String)]
77
+ : String(value);
78
+ return this;
79
+ },
80
+ });
81
+ Object.defineProperty(response, 'getHeader', {
82
+ configurable: true,
83
+ value: function (name) {
84
+ return this._headers[name.toLowerCase()];
85
+ },
86
+ });
87
+ Object.defineProperty(response, 'getHeaders', {
88
+ configurable: true,
89
+ value: function () {
90
+ return JSON.parse(JSON.stringify(this._headers));
91
+ },
92
+ });
93
+ }
94
+
95
+ /**
96
+ * Patch body.
97
+ *
98
+ * @param {object} response
99
+ */
100
+ function patchBody(response) {
101
+ let resolve, reject;
102
+ const promise = new Promise((rsv, rej) => {
103
+ resolve = rsv;
104
+ reject = rej;
105
+ });
106
+ const data = [];
107
+ response.on('data', c => data.push(c));
108
+ response.on('error', e => reject(e));
109
+ response.on('end', () => {
110
+ resolve(Buffer.concat(data));
111
+ });
112
+ // флаг _headersSent должен быть установлен синхронно
113
+ // после вызова метода response.end, так как асинхронная
114
+ // установка (к примеру в response.on('end')) не позволит
115
+ // отследить отправку ответа при синхронном выполнении
116
+ const originalEnd = response.end.bind(response);
117
+ response.end = function (...args) {
118
+ this._headersSent = true;
119
+ return originalEnd(...args);
120
+ };
121
+ Object.defineProperty(response, 'getBody', {
122
+ configurable: true,
123
+ value: function () {
124
+ return promise.then(buffer => {
125
+ const enc = this.getEncoding();
126
+ const str = buffer.toString(enc);
127
+ return data.length ? str : undefined;
128
+ });
129
+ },
130
+ });
131
+ }
@@ -0,0 +1,150 @@
1
+ import {expect} from 'chai';
2
+ import {PassThrough} from 'stream';
3
+ import {createResponseMock} from './create-response-mock.js';
4
+
5
+ describe('createResponseMock', function () {
6
+ it('should return an instance of PassThrough', function () {
7
+ const res = createResponseMock();
8
+ expect(res).to.be.instanceof(PassThrough);
9
+ });
10
+
11
+ describe('statusCode', function () {
12
+ it('should be initialized with 200 by default', function () {
13
+ const res = createResponseMock();
14
+ expect(res.statusCode).to.be.eq(200);
15
+ });
16
+ });
17
+
18
+ describe('setEncoding', function () {
19
+ it('should set the given encoding and return the response', function () {
20
+ const res = createResponseMock();
21
+ expect(res._encoding).to.be.undefined;
22
+ const ret = res.setEncoding('utf-8');
23
+ expect(ret).to.be.eq(res);
24
+ expect(res._encoding).to.be.eq('utf-8');
25
+ });
26
+ });
27
+
28
+ describe('getEncoding', function () {
29
+ it('should return the current encoding', function () {
30
+ const res = createResponseMock();
31
+ expect(res._encoding).to.be.undefined;
32
+ const ret1 = res.getEncoding();
33
+ expect(ret1).to.be.undefined;
34
+ res._encoding = 'utf-8';
35
+ const ret2 = res.getEncoding();
36
+ expect(ret2).to.be.eq('utf-8');
37
+ });
38
+ });
39
+
40
+ describe('headersSent', function () {
41
+ it('should return false when the response is not sent', function () {
42
+ const res = createResponseMock();
43
+ expect(res._headersSent).to.be.false;
44
+ expect(res.headersSent).to.be.false;
45
+ });
46
+
47
+ it('should return a value from the "_headersSent" property', function () {
48
+ const res = createResponseMock();
49
+ expect(res._headersSent).to.be.false;
50
+ expect(res.headersSent).to.be.false;
51
+ res._headersSent = true;
52
+ expect(res.headersSent).to.be.true;
53
+ });
54
+ });
55
+
56
+ describe('setHeader', function () {
57
+ it('should set the given header and return the response', function () {
58
+ const res = createResponseMock();
59
+ expect(res._headers['foo']).to.be.eq(undefined);
60
+ const ret = res.setHeader('foo', 'bar');
61
+ expect(ret).to.be.eq(res);
62
+ expect(res._headers['foo']).to.be.eq('bar');
63
+ });
64
+
65
+ it('should throw an error when headers are sent', function () {
66
+ const res = createResponseMock();
67
+ res._headersSent = true;
68
+ const throwable = () => res.setHeader('foo');
69
+ expect(throwable).to.throw(
70
+ 'Error [ERR_HTTP_HEADERS_SENT]: ' +
71
+ 'Cannot set headers after they are sent to the client',
72
+ );
73
+ });
74
+
75
+ it('should set the header value as a String', function () {
76
+ const res = createResponseMock();
77
+ expect(res._headers['num']).to.be.eq(undefined);
78
+ const ret = res.setHeader('num', 10);
79
+ expect(ret).to.be.eq(res);
80
+ expect(res._headers['num']).to.be.eq('10');
81
+ });
82
+
83
+ it('should not stringify an array value', function () {
84
+ const res = createResponseMock();
85
+ expect(res._headers['key']).to.be.eq(undefined);
86
+ const ret = res.setHeader('key', ['foo', 'bar']);
87
+ expect(ret).to.be.eq(res);
88
+ expect(res._headers['key']).to.be.eql(['foo', 'bar']);
89
+ });
90
+
91
+ it('should stringify an array elements', function () {
92
+ const res = createResponseMock();
93
+ expect(res._headers['key']).to.be.eq(undefined);
94
+ const ret = res.setHeader('key', [1, 2]);
95
+ expect(ret).to.be.eq(res);
96
+ expect(res._headers['key']).to.be.eql(['1', '2']);
97
+ });
98
+ });
99
+
100
+ describe('getHeader', function () {
101
+ it('should return the header value if exists', function () {
102
+ const res = createResponseMock();
103
+ res._headers['foo'] = 'bar';
104
+ const ret = res.getHeader('foo');
105
+ expect(ret).to.be.eq('bar');
106
+ });
107
+
108
+ it('should ignore case-sensitivity for header names', function () {
109
+ const res = createResponseMock();
110
+ res._headers['foo'] = 'bar';
111
+ const ret = res.getHeader('FOO');
112
+ expect(ret).to.be.eq('bar');
113
+ });
114
+ });
115
+
116
+ describe('getHeaders', function () {
117
+ it('should return a copy of the headers object', function () {
118
+ const res = createResponseMock();
119
+ const ret1 = res.getHeaders();
120
+ res._headers['foo'] = 'bar';
121
+ res._headers['baz'] = 'qux';
122
+ const ret2 = res.getHeaders();
123
+ expect(ret1).to.be.eql({});
124
+ expect(ret2).to.be.eql({foo: 'bar', baz: 'qux'});
125
+ expect(ret1).not.to.be.eq(res._headers);
126
+ expect(ret2).not.to.be.eq(res._headers);
127
+ });
128
+ });
129
+
130
+ describe('getBody', function () {
131
+ it('should return a Promise of the stream content', async function () {
132
+ const body = 'Lorem Ipsum is simply dummy text.';
133
+ const res = createResponseMock();
134
+ res.end(body);
135
+ const promise = res.getBody();
136
+ expect(promise).to.be.instanceof(Promise);
137
+ const result = await promise;
138
+ expect(result).to.be.eq(body);
139
+ });
140
+ });
141
+
142
+ describe('Stream', function () {
143
+ it('should set the property "headersSent" to true when the stream ends', function () {
144
+ const res = createResponseMock();
145
+ expect(res.headersSent).to.be.false;
146
+ res.end('test');
147
+ expect(res.headersSent).to.be.true;
148
+ });
149
+ });
150
+ });
@@ -0,0 +1,26 @@
1
+ import {IncomingMessage} from 'http';
2
+
3
+ /**
4
+ * Character encoding list.
5
+ */
6
+ export const CHARACTER_ENCODING_LIST: (
7
+ | 'ascii'
8
+ | 'utf8'
9
+ | 'utf-8'
10
+ | 'utf16le'
11
+ | 'utf-16le'
12
+ | 'ucs2'
13
+ | 'ucs-2'
14
+ | 'latin1'
15
+ )[];
16
+
17
+ /**
18
+ * Fetch request body.
19
+ *
20
+ * @param request
21
+ * @param bodyBytesLimit
22
+ */
23
+ export declare function fetchRequestBody(
24
+ request: IncomingMessage,
25
+ bodyBytesLimit?: number,
26
+ ): Promise<string | undefined>;
@@ -0,0 +1,148 @@
1
+ import HttpErrors from 'http-errors';
2
+ import {IncomingMessage} from 'http';
3
+ import {createError} from './create-error.js';
4
+ import {InvalidArgumentError} from '@e22m4u/js-format';
5
+ import {parseContentType} from './parse-content-type.js';
6
+
7
+ /**
8
+ * Character encoding list.
9
+ */
10
+ export const CHARACTER_ENCODING_LIST = [
11
+ 'ascii',
12
+ 'utf8',
13
+ 'utf-8',
14
+ 'utf16le',
15
+ 'utf-16le',
16
+ 'ucs2',
17
+ 'ucs-2',
18
+ 'latin1',
19
+ ];
20
+
21
+ /**
22
+ * Fetch request body.
23
+ *
24
+ * @param {IncomingMessage} request
25
+ * @param {number} bodyBytesLimit
26
+ * @returns {Promise<string|undefined>}
27
+ */
28
+ export function fetchRequestBody(request, bodyBytesLimit = 0) {
29
+ if (!(request instanceof IncomingMessage)) {
30
+ throw new InvalidArgumentError(
31
+ 'Parameter "request" must be an instance of IncomingMessage, ' +
32
+ 'but %v was given.',
33
+ request,
34
+ );
35
+ }
36
+ if (typeof bodyBytesLimit !== 'number') {
37
+ throw new InvalidArgumentError(
38
+ 'Parameter "bodyBytesLimit" must be a Number, but %v was given.',
39
+ bodyBytesLimit,
40
+ );
41
+ }
42
+ return new Promise((resolve, reject) => {
43
+ // сравнение внутреннего ограничения
44
+ // размера тела запроса с заголовком
45
+ // "content-length"
46
+ const contentLength = parseInt(
47
+ request.headers['content-length'] || '0',
48
+ 10,
49
+ );
50
+ if (bodyBytesLimit && contentLength && contentLength > bodyBytesLimit) {
51
+ throw createError(
52
+ HttpErrors.PayloadTooLarge,
53
+ 'Request body limit is %s bytes, but %s bytes given.',
54
+ bodyBytesLimit,
55
+ contentLength,
56
+ );
57
+ }
58
+ // определение кодировки
59
+ // по заголовку "content-type"
60
+ let encoding = 'utf-8';
61
+ const contentType = request.headers['content-type'] || '';
62
+ if (contentType) {
63
+ const parsedContentType = parseContentType(contentType);
64
+ if (parsedContentType && parsedContentType.charset) {
65
+ encoding = parsedContentType.charset.toLowerCase();
66
+ if (!CHARACTER_ENCODING_LIST.includes(encoding)) {
67
+ throw createError(
68
+ HttpErrors.UnsupportedMediaType,
69
+ 'Request encoding %v is not supported.',
70
+ encoding,
71
+ );
72
+ }
73
+ }
74
+ }
75
+ // подготовка массива загружаемых байтов
76
+ // и счетчика для отслеживания их объема
77
+ const data = [];
78
+ let receivedLength = 0;
79
+ // обработчик проверяет объем загружаемых
80
+ // данных и складывает их в массив
81
+ const onData = chunk => {
82
+ receivedLength += chunk.length;
83
+ if (bodyBytesLimit && receivedLength > bodyBytesLimit) {
84
+ cleanupListeners();
85
+ const error = createError(
86
+ HttpErrors.PayloadTooLarge,
87
+ 'Request body limit is %v bytes, but %v bytes given.',
88
+ bodyBytesLimit,
89
+ receivedLength,
90
+ );
91
+ // после удаления слушателей поток продолжает быть
92
+ // в состоянии resume (flowing mode), данные будут
93
+ // считываться в никуда, и чтобы сэкономить трафик
94
+ // и ресурсы сервера при превышении лимита,
95
+ // выполняется уничтожение потока запроса
96
+ request.unpipe();
97
+ request.destroy();
98
+ reject(error);
99
+ return;
100
+ }
101
+ data.push(chunk);
102
+ };
103
+ // кода данные полностью загружены, нужно удалить
104
+ // обработчики событий, и сравнить полученный объем
105
+ // данных с заявленным в заголовке "content-length"
106
+ const onEnd = () => {
107
+ cleanupListeners();
108
+ if (contentLength && contentLength !== receivedLength) {
109
+ const error = createError(
110
+ HttpErrors.BadRequest,
111
+ 'Received bytes do not match the "content-length" header.',
112
+ );
113
+ reject(error);
114
+ return;
115
+ }
116
+ // объединение массива байтов в буфер, кодирование
117
+ // результата в строку, и передача полученных данных
118
+ // в ожидающий Promise
119
+ const buffer = Buffer.concat(data);
120
+ const body = buffer.toString(encoding);
121
+ resolve(body || undefined);
122
+ };
123
+ // при ошибке загрузки тела запроса,
124
+ // удаляются обработчики событий,
125
+ // и отклоняется ожидающий Promise
126
+ // ошибкой с кодом 400
127
+ const onError = error => {
128
+ cleanupListeners();
129
+ reject(HttpErrors(400, error));
130
+ };
131
+ // запрос может иметь слушателей, установленных самим Node.js
132
+ // сервером или другими инструментами, которые подписались
133
+ // на close, aborted или error, потому нельзя использовать
134
+ // метод removeAllListeners
135
+ const cleanupListeners = () => {
136
+ request.removeListener('data', onData);
137
+ request.removeListener('end', onEnd);
138
+ request.removeListener('error', onError);
139
+ };
140
+ // добавление обработчиков прослушивающих
141
+ // события входящего запроса и возобновление
142
+ // потока данных
143
+ request.on('data', onData);
144
+ request.on('end', onEnd);
145
+ request.on('error', onError);
146
+ request.resume();
147
+ });
148
+ }
@@ -0,0 +1,209 @@
1
+ import {expect} from 'chai';
2
+ import {format} from '@e22m4u/js-format';
3
+ import {fetchRequestBody} from './fetch-request-body.js';
4
+ import {createRequestMock} from './create-request-mock.js';
5
+
6
+ describe('fetchRequestBody', function () {
7
+ it('should require the parameter "request" to be an instance of IncomingMessage', function () {
8
+ const throwable = v => () => fetchRequestBody(v);
9
+ const error = v =>
10
+ format(
11
+ 'Parameter "request" must be an instance of IncomingMessage, ' +
12
+ 'but %s was given.',
13
+ v,
14
+ );
15
+ expect(throwable('str')).to.throw(error('"str"'));
16
+ expect(throwable('')).to.throw(error('""'));
17
+ expect(throwable(10)).to.throw(error('10'));
18
+ expect(throwable(0)).to.throw(error('0'));
19
+ expect(throwable(true)).to.throw(error('true'));
20
+ expect(throwable(false)).to.throw(error('false'));
21
+ expect(throwable(null)).to.throw(error('null'));
22
+ expect(throwable({})).to.throw(error('Object'));
23
+ expect(throwable([])).to.throw(error('Array'));
24
+ expect(throwable(undefined)).to.throw(error('undefined'));
25
+ throwable(createRequestMock())();
26
+ });
27
+
28
+ it('should require the parameter "bodyBytesLimit" to be a Number', function () {
29
+ const req = createRequestMock();
30
+ const throwable = v => () => fetchRequestBody(req, v);
31
+ const error = v =>
32
+ format(
33
+ 'Parameter "bodyBytesLimit" must be a Number, but %s was given.',
34
+ v,
35
+ );
36
+ expect(throwable('str')).to.throw(error('"str"'));
37
+ expect(throwable('')).to.throw(error('""'));
38
+ expect(throwable(true)).to.throw(error('true'));
39
+ expect(throwable(false)).to.throw(error('false'));
40
+ expect(throwable(null)).to.throw(error('null'));
41
+ expect(throwable({})).to.throw(error('Object'));
42
+ expect(throwable([])).to.throw(error('Array'));
43
+ throwable(10)();
44
+ throwable(0)();
45
+ throwable(undefined)();
46
+ });
47
+
48
+ it('should return a string from the string body', async function () {
49
+ const body = 'Lorem Ipsum is simply dummy text.';
50
+ const req = createRequestMock({body});
51
+ const result = await fetchRequestBody(req);
52
+ expect(result).to.be.eq(body);
53
+ });
54
+
55
+ it('should return a string from the buffer body', async function () {
56
+ const body = 'Lorem Ipsum is simply dummy text.';
57
+ const req = createRequestMock({body: Buffer.from(body)});
58
+ const result = await fetchRequestBody(req);
59
+ expect(result).to.be.eq(body);
60
+ });
61
+
62
+ describe('encoding usage from the "content-type" header', function () {
63
+ it('should throw an error for an unsupported encoding', async function () {
64
+ const body = 'Lorem Ipsum is simply dummy text.';
65
+ const req = createRequestMock({
66
+ body,
67
+ headers: {'content-type': 'text/plain; charset=unknown'},
68
+ });
69
+ const promise = fetchRequestBody(req);
70
+ await expect(promise).to.be.rejectedWith(
71
+ 'Request encoding "unknown" is not supported.',
72
+ );
73
+ });
74
+
75
+ it('should ignore the "content-type" header when no charset specified', async function () {
76
+ const body = 'Lorem Ipsum is simply dummy text.';
77
+ const req = createRequestMock({
78
+ body,
79
+ headers: {'content-type': 'text/plain'},
80
+ });
81
+ const result = await fetchRequestBody(req);
82
+ expect(result).to.be.eq(body);
83
+ });
84
+
85
+ it('should decode non-UTF-8 encoding to a plain text', async function () {
86
+ const originalBody = 'Hello, world!';
87
+ const req = createRequestMock({
88
+ body: originalBody,
89
+ encoding: 'latin1',
90
+ headers: {'content-type': `text/plain; charset=latin1`},
91
+ });
92
+ const result = await fetchRequestBody(req);
93
+ expect(result).to.be.eq(originalBody);
94
+ });
95
+ });
96
+
97
+ describe('the "content-length" header usage', function () {
98
+ it('should throw an error if the body length is greater than the header', async function () {
99
+ const body = 'Lorem Ipsum is simply dummy text.';
100
+ const bodyLength = Buffer.from(body).byteLength;
101
+ const contentLength = String(bodyLength + 10);
102
+ const req = createRequestMock({
103
+ body,
104
+ headers: {'content-length': contentLength},
105
+ });
106
+ const promise = fetchRequestBody(req);
107
+ await expect(promise).to.be.rejectedWith(
108
+ 'Received bytes do not match the "content-length" header.',
109
+ );
110
+ });
111
+
112
+ it('should throw an error if the body length is lower than the header', async function () {
113
+ const body = 'Lorem Ipsum is simply dummy text.';
114
+ const bodyLength = Buffer.from(body).byteLength;
115
+ const contentLength = String(bodyLength - 10);
116
+ const req = createRequestMock({
117
+ body,
118
+ headers: {'content-length': contentLength},
119
+ });
120
+ const promise = fetchRequestBody(req);
121
+ await expect(promise).to.be.rejectedWith(
122
+ 'Received bytes do not match the "content-length" header.',
123
+ );
124
+ });
125
+
126
+ it('should not throw an error if the body length matches the header', async function () {
127
+ const body = 'Lorem Ipsum is simply dummy text.';
128
+ const contentLength = String(Buffer.from(body).byteLength);
129
+ const req = createRequestMock({
130
+ body,
131
+ headers: {'content-length': contentLength},
132
+ });
133
+ const result = await fetchRequestBody(req);
134
+ expect(result).to.be.eq(body);
135
+ });
136
+ });
137
+
138
+ describe('the parameter "bodyBytesLimit" usage', function () {
139
+ it('should throw an error if the "content-length" header is greater than the limit', async function () {
140
+ const body = 'Lorem Ipsum is simply dummy text.';
141
+ const bodyLength = Buffer.from(body).byteLength;
142
+ const bodyLimit = bodyLength - 10;
143
+ const req = createRequestMock({
144
+ body,
145
+ headers: {'content-length': String(bodyLength)},
146
+ });
147
+ const error = format(
148
+ 'Request body limit is %s bytes, but %s bytes given.',
149
+ bodyLimit,
150
+ bodyLength,
151
+ );
152
+ const promise = fetchRequestBody(req, bodyLimit);
153
+ await expect(promise).to.be.rejectedWith(error);
154
+ });
155
+
156
+ it('should not throw an error if the "content-length" header does match with the limit', async function () {
157
+ const body = 'Lorem Ipsum is simply dummy text.';
158
+ const bodyLength = Buffer.from(body).byteLength;
159
+ const req = createRequestMock({
160
+ body,
161
+ headers: {'content-length': String(bodyLength)},
162
+ });
163
+ const result = await fetchRequestBody(req, bodyLength);
164
+ expect(result).to.be.eq(body);
165
+ });
166
+
167
+ it('should not throw an error if the "content-length" header is lower than the limit', async function () {
168
+ const body = 'Lorem Ipsum is simply dummy text.';
169
+ const bodyLength = Buffer.from(body).byteLength;
170
+ const req = createRequestMock({
171
+ body,
172
+ headers: {'content-length': String(bodyLength)},
173
+ });
174
+ const result = await fetchRequestBody(req, bodyLength + 10);
175
+ expect(result).to.be.eq(body);
176
+ });
177
+
178
+ it('should throw an error if the body length is greater than the limit', async function () {
179
+ const body = 'Lorem Ipsum is simply dummy text.';
180
+ const bodyLength = Buffer.from(body).byteLength;
181
+ const bodyLimit = bodyLength - 10;
182
+ const req = createRequestMock({body});
183
+ const error = format(
184
+ 'Request body limit is %s bytes, but %s bytes given.',
185
+ bodyLimit,
186
+ bodyLength,
187
+ );
188
+ const promise = fetchRequestBody(req, bodyLimit);
189
+ await expect(promise).to.be.rejectedWith(error);
190
+ });
191
+
192
+ it('should not throw an error if the body length does match with the limit', async function () {
193
+ const body = 'Lorem Ipsum is simply dummy text.';
194
+ const bodyLength = Buffer.from(body).byteLength;
195
+ const req = createRequestMock({body});
196
+ const result = await fetchRequestBody(req, bodyLength);
197
+ expect(result).to.be.eq(body);
198
+ });
199
+
200
+ it('should not throw an error if the body length is lower than the limit', async function () {
201
+ const body = 'Lorem Ipsum is simply dummy text.';
202
+ const bodyLength = Buffer.from(body).byteLength;
203
+ const bodyLimit = bodyLength + 10;
204
+ const req = createRequestMock({body});
205
+ const result = await fetchRequestBody(req, bodyLimit);
206
+ expect(result).to.be.eq(body);
207
+ });
208
+ });
209
+ });
@@ -2,6 +2,6 @@
2
2
  /**
3
3
  * Get pathname from url.
4
4
  *
5
- * @param request
5
+ * @param url
6
6
  */
7
7
  export declare function getPathnameFromUrl(url: string): string;
@@ -3,7 +3,7 @@ import {format} from '@e22m4u/js-format';
3
3
  import {getPathnameFromUrl} from './get-pathname-from-url.js';
4
4
 
5
5
  describe('getPathnameFromUrl', function () {
6
- it('should require the parameter "request" to be a String', function () {
6
+ it('should require the parameter "url" to be a String', function () {
7
7
  const throwable = v => () => getPathnameFromUrl(v);
8
8
  const error = v =>
9
9
  format('Parameter "url" must be a String, but %s was given.', v);
@@ -1,2 +1,10 @@
1
+ export * from './create-error.js';
1
2
  export * from './escape-regexp.js';
3
+ export * from './fetch-request-body.js';
4
+ export * from './is-readable-stream.js';
5
+ export * from './parse-content-type.js';
6
+ export * from './create-request-mock.js';
7
+ export * from './parse-cookie-string.js';
8
+ export * from './create-response-mock.js';
9
+ export * from './create-cookie-string.js';
2
10
  export * from './get-pathname-from-url.js';