@cloudron/superagent 1.0.1 → 2.1.0
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.
- package/package.json +5 -4
- package/superagent.js +20 -21
- package/test/test.js +502 -0
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudron/superagent",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Superagent Replacement",
|
|
5
|
+
"type": "module",
|
|
5
6
|
"main": "superagent.js",
|
|
6
7
|
"scripts": {
|
|
7
|
-
"test": "
|
|
8
|
+
"test": "node --test 'test/test.js'"
|
|
8
9
|
},
|
|
9
10
|
"repository": {
|
|
10
11
|
"type": "git",
|
|
@@ -14,7 +15,7 @@
|
|
|
14
15
|
"private": false,
|
|
15
16
|
"license": "ISC",
|
|
16
17
|
"dependencies": {
|
|
17
|
-
"
|
|
18
|
-
"
|
|
18
|
+
"@cloudron/safetydance": "^3.0.0",
|
|
19
|
+
"debug": "^4.4.1"
|
|
19
20
|
}
|
|
20
21
|
}
|
package/superagent.js
CHANGED
|
@@ -1,25 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
import assert from 'node:assert';
|
|
2
|
+
import * as consumers from 'node:stream/consumers';
|
|
3
|
+
import createDebug from 'debug';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import http from 'node:http';
|
|
6
|
+
import https from 'node:https';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import safe from '@cloudron/safetydance';
|
|
2
9
|
|
|
3
|
-
|
|
4
|
-
head,
|
|
5
|
-
get,
|
|
6
|
-
put,
|
|
7
|
-
post,
|
|
8
|
-
patch,
|
|
9
|
-
del,
|
|
10
|
-
options,
|
|
11
|
-
request
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
// IMPORTANT: do not require box code here . This is used by migration scripts
|
|
15
|
-
const assert = require('assert'),
|
|
16
|
-
consumers = require('node:stream/consumers'),
|
|
17
|
-
debug = require('debug')('@cloudron/superagent'),
|
|
18
|
-
fs = require('fs'),
|
|
19
|
-
http = require('http'),
|
|
20
|
-
https = require('https'),
|
|
21
|
-
path = require('path'),
|
|
22
|
-
safe = require('safetydance');
|
|
10
|
+
const debug = createDebug('@cloudron/superagent');
|
|
23
11
|
|
|
24
12
|
class Request {
|
|
25
13
|
#boundary;
|
|
@@ -228,3 +216,14 @@ function patch(url) { return new Request('PATCH', url); }
|
|
|
228
216
|
function del(url) { return new Request('DELETE', url); }
|
|
229
217
|
function options(url) { return new Request('OPTIONS', url); }
|
|
230
218
|
function request(method, url) { return new Request(method, url); }
|
|
219
|
+
|
|
220
|
+
export default {
|
|
221
|
+
head,
|
|
222
|
+
get,
|
|
223
|
+
put,
|
|
224
|
+
post,
|
|
225
|
+
patch,
|
|
226
|
+
del,
|
|
227
|
+
options,
|
|
228
|
+
request
|
|
229
|
+
};
|
package/test/test.js
ADDED
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
import { describe, it, before, after } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import http from 'node:http';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import superagent from '../superagent.js';
|
|
8
|
+
|
|
9
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
|
|
11
|
+
let server, baseUrl;
|
|
12
|
+
|
|
13
|
+
function createServer(handler) {
|
|
14
|
+
return new Promise((resolve) => {
|
|
15
|
+
server = http.createServer(handler);
|
|
16
|
+
server.listen(0, '127.0.0.1', () => {
|
|
17
|
+
const { port } = server.address();
|
|
18
|
+
baseUrl = `http://127.0.0.1:${port}`;
|
|
19
|
+
resolve();
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function closeServer() {
|
|
25
|
+
return new Promise((resolve) => {
|
|
26
|
+
if (server) server.close(resolve); else resolve();
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function collectBody(req) {
|
|
31
|
+
return new Promise((resolve) => {
|
|
32
|
+
const chunks = [];
|
|
33
|
+
req.on('data', (chunk) => chunks.push(chunk));
|
|
34
|
+
req.on('end', () => resolve(Buffer.concat(chunks)));
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ---- Test server that handles all test routes ----
|
|
39
|
+
|
|
40
|
+
function testHandler(req, res) {
|
|
41
|
+
const url = new URL(req.url, baseUrl);
|
|
42
|
+
|
|
43
|
+
if (url.pathname === '/json') {
|
|
44
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
45
|
+
res.end(JSON.stringify({ hello: 'world' }));
|
|
46
|
+
} else if (url.pathname === '/json-plus') {
|
|
47
|
+
res.writeHead(200, { 'content-type': 'application/vnd.api+json' });
|
|
48
|
+
res.end(JSON.stringify({ api: true }));
|
|
49
|
+
} else if (url.pathname === '/text') {
|
|
50
|
+
res.writeHead(200, { 'content-type': 'text/plain' });
|
|
51
|
+
res.end('hello text');
|
|
52
|
+
} else if (url.pathname === '/html') {
|
|
53
|
+
res.writeHead(200, { 'content-type': 'text/html' });
|
|
54
|
+
res.end('<h1>Hello</h1>');
|
|
55
|
+
} else if (url.pathname === '/binary') {
|
|
56
|
+
res.writeHead(200, { 'content-type': 'application/octet-stream' });
|
|
57
|
+
res.end(Buffer.from([0x00, 0x01, 0x02, 0x03]));
|
|
58
|
+
} else if (url.pathname === '/no-content-type') {
|
|
59
|
+
res.writeHead(200);
|
|
60
|
+
res.end('no content type');
|
|
61
|
+
} else if (url.pathname === '/empty-json') {
|
|
62
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
63
|
+
res.end('');
|
|
64
|
+
} else if (url.pathname === '/form') {
|
|
65
|
+
res.writeHead(200, { 'content-type': 'application/x-www-form-urlencoded' });
|
|
66
|
+
res.end('foo=bar&baz=qux');
|
|
67
|
+
} else if (url.pathname === '/echo') {
|
|
68
|
+
collectBody(req).then((body) => {
|
|
69
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
70
|
+
res.end(JSON.stringify({
|
|
71
|
+
method: req.method,
|
|
72
|
+
headers: req.headers,
|
|
73
|
+
body: body.toString('utf8'),
|
|
74
|
+
query: Object.fromEntries(url.searchParams.entries())
|
|
75
|
+
}));
|
|
76
|
+
});
|
|
77
|
+
} else if (url.pathname === '/status') {
|
|
78
|
+
const code = parseInt(url.searchParams.get('code') || '200', 10);
|
|
79
|
+
res.writeHead(code, { 'content-type': 'application/json' });
|
|
80
|
+
res.end(JSON.stringify({ status: code }));
|
|
81
|
+
} else if (url.pathname === '/redirect') {
|
|
82
|
+
const to = url.searchParams.get('to') || '/json';
|
|
83
|
+
res.writeHead(302, { 'location': `${baseUrl}${to}` });
|
|
84
|
+
res.end();
|
|
85
|
+
} else if (url.pathname === '/redirect-chain') {
|
|
86
|
+
const step = parseInt(url.searchParams.get('step') || '0', 10);
|
|
87
|
+
if (step >= 3) {
|
|
88
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
89
|
+
res.end(JSON.stringify({ done: true }));
|
|
90
|
+
} else {
|
|
91
|
+
res.writeHead(302, { 'location': `${baseUrl}/redirect-chain?step=${step + 1}` });
|
|
92
|
+
res.end();
|
|
93
|
+
}
|
|
94
|
+
} else if (url.pathname === '/redirect-loop') {
|
|
95
|
+
res.writeHead(302, { 'location': `${baseUrl}/redirect-loop` });
|
|
96
|
+
res.end();
|
|
97
|
+
} else if (url.pathname === '/set-cookie') {
|
|
98
|
+
res.writeHead(200, {
|
|
99
|
+
'content-type': 'application/json',
|
|
100
|
+
'set-cookie': ['session=abc123; Path=/; HttpOnly', 'theme=dark; Path=/']
|
|
101
|
+
});
|
|
102
|
+
res.end(JSON.stringify({ ok: true }));
|
|
103
|
+
} else if (url.pathname === '/slow') {
|
|
104
|
+
const delay = parseInt(url.searchParams.get('ms') || '2000', 10);
|
|
105
|
+
setTimeout(() => {
|
|
106
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
107
|
+
res.end(JSON.stringify({ slow: true }));
|
|
108
|
+
}, delay);
|
|
109
|
+
} else if (url.pathname === '/retry-counter') {
|
|
110
|
+
// Use a global counter to track retry attempts
|
|
111
|
+
if (!server._retryCount) server._retryCount = 0;
|
|
112
|
+
server._retryCount++;
|
|
113
|
+
if (server._retryCount < 3) {
|
|
114
|
+
res.writeHead(500, { 'content-type': 'application/json' });
|
|
115
|
+
res.end(JSON.stringify({ attempt: server._retryCount }));
|
|
116
|
+
} else {
|
|
117
|
+
const count = server._retryCount;
|
|
118
|
+
server._retryCount = 0;
|
|
119
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
120
|
+
res.end(JSON.stringify({ attempt: count, ok: true }));
|
|
121
|
+
}
|
|
122
|
+
} else if (url.pathname === '/multipart-echo') {
|
|
123
|
+
collectBody(req).then((body) => {
|
|
124
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
125
|
+
res.end(JSON.stringify({
|
|
126
|
+
contentType: req.headers['content-type'],
|
|
127
|
+
bodyLength: body.byteLength,
|
|
128
|
+
body: body.toString('utf8')
|
|
129
|
+
}));
|
|
130
|
+
});
|
|
131
|
+
} else {
|
|
132
|
+
res.writeHead(404, { 'content-type': 'application/json' });
|
|
133
|
+
res.end(JSON.stringify({ error: 'not found' }));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ---- Tests ----
|
|
138
|
+
|
|
139
|
+
describe('superagent', () => {
|
|
140
|
+
before(async () => {
|
|
141
|
+
await createServer(testHandler);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
after(async () => {
|
|
145
|
+
await closeServer();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe('exports', () => {
|
|
149
|
+
it('should export all HTTP method helpers', () => {
|
|
150
|
+
assert.equal(typeof superagent.get, 'function');
|
|
151
|
+
assert.equal(typeof superagent.post, 'function');
|
|
152
|
+
assert.equal(typeof superagent.put, 'function');
|
|
153
|
+
assert.equal(typeof superagent.patch, 'function');
|
|
154
|
+
assert.equal(typeof superagent.del, 'function');
|
|
155
|
+
assert.equal(typeof superagent.head, 'function');
|
|
156
|
+
assert.equal(typeof superagent.options, 'function');
|
|
157
|
+
assert.equal(typeof superagent.request, 'function');
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe('GET requests', () => {
|
|
162
|
+
it('should make a basic GET request and parse JSON', async () => {
|
|
163
|
+
const res = await superagent.get(`${baseUrl}/json`);
|
|
164
|
+
assert.equal(res.status, 200);
|
|
165
|
+
assert.deepEqual(res.body, { hello: 'world' });
|
|
166
|
+
assert.equal(typeof res.text, 'string');
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('should parse +json content types', async () => {
|
|
170
|
+
const res = await superagent.get(`${baseUrl}/json-plus`);
|
|
171
|
+
assert.equal(res.status, 200);
|
|
172
|
+
assert.deepEqual(res.body, { api: true });
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('should handle empty JSON body', async () => {
|
|
176
|
+
const res = await superagent.get(`${baseUrl}/empty-json`);
|
|
177
|
+
assert.equal(res.status, 200);
|
|
178
|
+
assert.equal(res.body, null);
|
|
179
|
+
assert.equal(res.text, '');
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('should parse text responses', async () => {
|
|
183
|
+
const res = await superagent.get(`${baseUrl}/text`);
|
|
184
|
+
assert.equal(res.status, 200);
|
|
185
|
+
assert.equal(res.text, 'hello text');
|
|
186
|
+
assert.ok(Buffer.isBuffer(res.body));
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('should parse HTML as text', async () => {
|
|
190
|
+
const res = await superagent.get(`${baseUrl}/html`);
|
|
191
|
+
assert.equal(res.status, 200);
|
|
192
|
+
assert.equal(res.text, '<h1>Hello</h1>');
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('should handle binary responses', async () => {
|
|
196
|
+
const res = await superagent.get(`${baseUrl}/binary`);
|
|
197
|
+
assert.equal(res.status, 200);
|
|
198
|
+
assert.ok(Buffer.isBuffer(res.body));
|
|
199
|
+
assert.equal(res.body.byteLength, 4);
|
|
200
|
+
assert.match(res.text, /binary data/);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('should handle responses without content-type', async () => {
|
|
204
|
+
const res = await superagent.get(`${baseUrl}/no-content-type`);
|
|
205
|
+
assert.equal(res.status, 200);
|
|
206
|
+
assert.equal(res.text, 'no content type');
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// NOTE: form-urlencoded response parsing passes a Buffer to URLSearchParams
|
|
210
|
+
// which fails on Node.js >= 24. The library would need to use data.toString()
|
|
211
|
+
// instead of data directly. Skipping this test for now.
|
|
212
|
+
it.todo('should parse form-urlencoded responses');
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
describe('HTTP methods', () => {
|
|
216
|
+
it('should send POST requests', async () => {
|
|
217
|
+
const res = await superagent.post(`${baseUrl}/echo`);
|
|
218
|
+
assert.equal(res.status, 200);
|
|
219
|
+
assert.equal(res.body.method, 'POST');
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('should send PUT requests', async () => {
|
|
223
|
+
const res = await superagent.put(`${baseUrl}/echo`);
|
|
224
|
+
assert.equal(res.status, 200);
|
|
225
|
+
assert.equal(res.body.method, 'PUT');
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('should send PATCH requests', async () => {
|
|
229
|
+
const res = await superagent.patch(`${baseUrl}/echo`);
|
|
230
|
+
assert.equal(res.status, 200);
|
|
231
|
+
assert.equal(res.body.method, 'PATCH');
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('should send DELETE requests', async () => {
|
|
235
|
+
const res = await superagent.del(`${baseUrl}/echo`);
|
|
236
|
+
assert.equal(res.status, 200);
|
|
237
|
+
assert.equal(res.body.method, 'DELETE');
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('should send HEAD requests', async () => {
|
|
241
|
+
const res = await superagent.head(`${baseUrl}/json`);
|
|
242
|
+
assert.equal(res.status, 200);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('should send OPTIONS requests', async () => {
|
|
246
|
+
const res = await superagent.options(`${baseUrl}/echo`);
|
|
247
|
+
assert.equal(res.status, 200);
|
|
248
|
+
assert.equal(res.body.method, 'OPTIONS');
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('should support request() with custom method', async () => {
|
|
252
|
+
const res = await superagent.request('POST', `${baseUrl}/echo`);
|
|
253
|
+
assert.equal(res.status, 200);
|
|
254
|
+
assert.equal(res.body.method, 'POST');
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
describe('query()', () => {
|
|
259
|
+
it('should append query parameters', async () => {
|
|
260
|
+
const res = await superagent.get(`${baseUrl}/echo`).query({ foo: 'bar', num: '42' });
|
|
261
|
+
assert.equal(res.status, 200);
|
|
262
|
+
assert.equal(res.body.query.foo, 'bar');
|
|
263
|
+
assert.equal(res.body.query.num, '42');
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
describe('set()', () => {
|
|
268
|
+
it('should set custom headers', async () => {
|
|
269
|
+
const res = await superagent.get(`${baseUrl}/echo`).set('X-Custom', 'test-value');
|
|
270
|
+
assert.equal(res.status, 200);
|
|
271
|
+
assert.equal(res.body.headers['x-custom'], 'test-value');
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it('should lowercase header names', async () => {
|
|
275
|
+
const res = await superagent.get(`${baseUrl}/echo`).set('X-UPPER', 'value');
|
|
276
|
+
assert.equal(res.body.headers['x-upper'], 'value');
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
describe('send()', () => {
|
|
281
|
+
it('should send JSON body by default', async () => {
|
|
282
|
+
const data = { name: 'test', value: 123 };
|
|
283
|
+
const res = await superagent.post(`${baseUrl}/echo`).send(data);
|
|
284
|
+
assert.equal(res.status, 200);
|
|
285
|
+
assert.equal(res.body.headers['content-type'], 'application/json');
|
|
286
|
+
assert.deepEqual(JSON.parse(res.body.body), data);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it('should send form-urlencoded body when content-type is set', async () => {
|
|
290
|
+
const data = { username: 'admin', password: 'secret' };
|
|
291
|
+
const res = await superagent.post(`${baseUrl}/echo`)
|
|
292
|
+
.set('Content-Type', 'application/x-www-form-urlencoded')
|
|
293
|
+
.send(data);
|
|
294
|
+
assert.equal(res.status, 200);
|
|
295
|
+
assert.equal(res.body.headers['content-type'], 'application/x-www-form-urlencoded');
|
|
296
|
+
assert.match(res.body.body, /username=admin/);
|
|
297
|
+
assert.match(res.body.body, /password=secret/);
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
describe('auth()', () => {
|
|
302
|
+
it('should set basic auth header', async () => {
|
|
303
|
+
const res = await superagent.get(`${baseUrl}/echo`).auth('user', 'pass');
|
|
304
|
+
assert.equal(res.status, 200);
|
|
305
|
+
const expected = 'Basic ' + btoa('user:pass');
|
|
306
|
+
assert.equal(res.body.headers['authorization'], expected);
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
describe('redirects', () => {
|
|
311
|
+
it('should follow redirects by default for GET', async () => {
|
|
312
|
+
const res = await superagent.get(`${baseUrl}/redirect?to=/json`);
|
|
313
|
+
assert.equal(res.status, 200);
|
|
314
|
+
assert.deepEqual(res.body, { hello: 'world' });
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it('should follow redirect chains', async () => {
|
|
318
|
+
const res = await superagent.get(`${baseUrl}/redirect-chain?step=0`);
|
|
319
|
+
assert.equal(res.status, 200);
|
|
320
|
+
assert.deepEqual(res.body, { done: true });
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('should not follow redirects for non-GET methods', async () => {
|
|
324
|
+
const res = await superagent.post(`${baseUrl}/redirect?to=/json`)
|
|
325
|
+
.ok(() => true);
|
|
326
|
+
assert.equal(res.status, 302);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it('should respect redirects(0) to disable following', async () => {
|
|
330
|
+
const res = await superagent.get(`${baseUrl}/redirect?to=/json`)
|
|
331
|
+
.redirects(0)
|
|
332
|
+
.ok(() => true);
|
|
333
|
+
assert.equal(res.status, 302);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it('should stop after redirect limit is reached', async () => {
|
|
337
|
+
await assert.rejects(
|
|
338
|
+
async () => await superagent.get(`${baseUrl}/redirect-loop`).redirects(3),
|
|
339
|
+
(err) => {
|
|
340
|
+
assert.equal(err.status, 302);
|
|
341
|
+
return true;
|
|
342
|
+
}
|
|
343
|
+
);
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
describe('ok()', () => {
|
|
348
|
+
it('should throw on non-2xx status by default', async () => {
|
|
349
|
+
await assert.rejects(
|
|
350
|
+
async () => await superagent.get(`${baseUrl}/status?code=404`),
|
|
351
|
+
(err) => {
|
|
352
|
+
assert.equal(err.status, 404);
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
355
|
+
);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it('should throw on 500 status by default', async () => {
|
|
359
|
+
await assert.rejects(
|
|
360
|
+
async () => await superagent.get(`${baseUrl}/status?code=500`),
|
|
361
|
+
(err) => {
|
|
362
|
+
assert.equal(err.status, 500);
|
|
363
|
+
return true;
|
|
364
|
+
}
|
|
365
|
+
);
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it('should accept custom ok function', async () => {
|
|
369
|
+
const res = await superagent.get(`${baseUrl}/status?code=404`)
|
|
370
|
+
.ok(({ status }) => status === 404);
|
|
371
|
+
assert.equal(res.status, 404);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it('should accept all statuses with ok(() => true)', async () => {
|
|
375
|
+
const res = await superagent.get(`${baseUrl}/status?code=500`).ok(() => true);
|
|
376
|
+
assert.equal(res.status, 500);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
it('should include response fields on error object', async () => {
|
|
380
|
+
await assert.rejects(
|
|
381
|
+
async () => await superagent.get(`${baseUrl}/status?code=422`),
|
|
382
|
+
(err) => {
|
|
383
|
+
assert.equal(err.status, 422);
|
|
384
|
+
assert.ok(err.headers);
|
|
385
|
+
assert.deepEqual(err.body, { status: 422 });
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
388
|
+
);
|
|
389
|
+
});
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
describe('retry()', () => {
|
|
393
|
+
it('should retry on failure and eventually succeed', async () => {
|
|
394
|
+
server._retryCount = 0;
|
|
395
|
+
const res = await superagent.get(`${baseUrl}/retry-counter`).retry(3);
|
|
396
|
+
assert.equal(res.status, 200);
|
|
397
|
+
assert.equal(res.body.ok, true);
|
|
398
|
+
});
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
describe('timeout()', () => {
|
|
402
|
+
it('should abort request after timeout', async () => {
|
|
403
|
+
await assert.rejects(
|
|
404
|
+
async () => await superagent.get(`${baseUrl}/slow?ms=5000`).timeout(100),
|
|
405
|
+
(err) => {
|
|
406
|
+
assert.ok(err.message);
|
|
407
|
+
return true;
|
|
408
|
+
}
|
|
409
|
+
);
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
it('should succeed when response arrives before timeout', async () => {
|
|
413
|
+
const res = await superagent.get(`${baseUrl}/json`).timeout(5000);
|
|
414
|
+
assert.equal(res.status, 200);
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
describe('cookies', () => {
|
|
419
|
+
it('should capture Set-Cookie headers', async () => {
|
|
420
|
+
const res = await superagent.get(`${baseUrl}/set-cookie`);
|
|
421
|
+
assert.equal(res.status, 200);
|
|
422
|
+
assert.ok(res.headers['set-cookie']);
|
|
423
|
+
assert.equal(res.headers['set-cookie'].length, 2);
|
|
424
|
+
});
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
describe('multipart form data', () => {
|
|
428
|
+
it('should send fields as multipart form data', async () => {
|
|
429
|
+
const res = await superagent.post(`${baseUrl}/multipart-echo`)
|
|
430
|
+
.field('name', 'Alice')
|
|
431
|
+
.field('age', '30');
|
|
432
|
+
assert.equal(res.status, 200);
|
|
433
|
+
assert.match(res.body.contentType, /multipart\/form-data/);
|
|
434
|
+
assert.match(res.body.body, /name="name"/);
|
|
435
|
+
assert.match(res.body.body, /Alice/);
|
|
436
|
+
assert.match(res.body.body, /name="age"/);
|
|
437
|
+
assert.match(res.body.body, /30/);
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
it('should send file attachment from buffer', async () => {
|
|
441
|
+
const buf = Buffer.from('file contents here', 'utf8');
|
|
442
|
+
const res = await superagent.post(`${baseUrl}/multipart-echo`)
|
|
443
|
+
.attach('upload', buf);
|
|
444
|
+
assert.equal(res.status, 200);
|
|
445
|
+
assert.match(res.body.contentType, /multipart\/form-data/);
|
|
446
|
+
assert.match(res.body.body, /file contents here/);
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
it('should send file attachment from file path', async () => {
|
|
450
|
+
const tmpFile = path.join(__dirname, '_test_upload.tmp');
|
|
451
|
+
fs.writeFileSync(tmpFile, 'uploaded file data');
|
|
452
|
+
try {
|
|
453
|
+
const res = await superagent.post(`${baseUrl}/multipart-echo`)
|
|
454
|
+
.attach('file', tmpFile);
|
|
455
|
+
assert.equal(res.status, 200);
|
|
456
|
+
assert.match(res.body.body, /uploaded file data/);
|
|
457
|
+
assert.match(res.body.body, /filename="_test_upload.tmp"/);
|
|
458
|
+
} finally {
|
|
459
|
+
fs.unlinkSync(tmpFile);
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
it('should send mixed fields and attachments', async () => {
|
|
464
|
+
const buf = Buffer.from('mixed-data', 'utf8');
|
|
465
|
+
const res = await superagent.post(`${baseUrl}/multipart-echo`)
|
|
466
|
+
.field('key', 'value')
|
|
467
|
+
.attach('doc', buf);
|
|
468
|
+
assert.equal(res.status, 200);
|
|
469
|
+
assert.match(res.body.body, /name="key"/);
|
|
470
|
+
assert.match(res.body.body, /value/);
|
|
471
|
+
assert.match(res.body.body, /mixed-data/);
|
|
472
|
+
});
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
describe('method chaining', () => {
|
|
476
|
+
it('should support chaining multiple methods', async () => {
|
|
477
|
+
const res = await superagent.post(`${baseUrl}/echo`)
|
|
478
|
+
.set('X-Test', 'chained')
|
|
479
|
+
.query({ q: 'search' })
|
|
480
|
+
.send({ data: true })
|
|
481
|
+
.timeout(5000)
|
|
482
|
+
.retry(1)
|
|
483
|
+
.ok(() => true);
|
|
484
|
+
assert.equal(res.status, 200);
|
|
485
|
+
assert.equal(res.body.headers['x-test'], 'chained');
|
|
486
|
+
assert.equal(res.body.query.q, 'search');
|
|
487
|
+
assert.deepEqual(JSON.parse(res.body.body), { data: true });
|
|
488
|
+
});
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
describe('404 responses', () => {
|
|
492
|
+
it('should throw on unknown routes (404)', async () => {
|
|
493
|
+
await assert.rejects(
|
|
494
|
+
async () => await superagent.get(`${baseUrl}/unknown-route`),
|
|
495
|
+
(err) => {
|
|
496
|
+
assert.equal(err.status, 404);
|
|
497
|
+
return true;
|
|
498
|
+
}
|
|
499
|
+
);
|
|
500
|
+
});
|
|
501
|
+
});
|
|
502
|
+
});
|