fable 3.1.65 → 3.1.67
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fable",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.67",
|
|
4
4
|
"description": "A service dependency injection, configuration and logging library.",
|
|
5
5
|
"main": "source/Fable.js",
|
|
6
6
|
"scripts": {
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
},
|
|
52
52
|
"homepage": "https://github.com/stevenvelozo/fable",
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"quackage": "^1.0.
|
|
54
|
+
"quackage": "^1.0.65"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
57
|
"async.eachlimit": "^0.5.2",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"fable-serviceproviderbase": "^3.0.19",
|
|
66
66
|
"fable-settings": "^3.0.16",
|
|
67
67
|
"fable-uuid": "^3.0.13",
|
|
68
|
-
"manyfest": "^1.0.
|
|
68
|
+
"manyfest": "^1.0.49",
|
|
69
69
|
"simple-get": "^4.0.1"
|
|
70
70
|
}
|
|
71
71
|
}
|
|
@@ -42,7 +42,8 @@ class FableServiceExpressionParser extends libFableServiceBase
|
|
|
42
42
|
this.log;
|
|
43
43
|
|
|
44
44
|
// The configuration for tokens that the solver recognizes, with precedence and friendly names.
|
|
45
|
-
|
|
45
|
+
// Clone the JSON so each instance gets its own tokenMap (require() caches and returns the same object reference).
|
|
46
|
+
this.tokenMap = JSON.parse(JSON.stringify(require('./Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-TokenMap.json')));
|
|
46
47
|
|
|
47
48
|
// Keep track of maximum token precedence
|
|
48
49
|
this.tokenMaxPrecedence = 4;
|
|
@@ -86,7 +87,8 @@ class FableServiceExpressionParser extends libFableServiceBase
|
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
// The configuration for which functions are available to the solver.
|
|
89
|
-
|
|
90
|
+
// Clone the JSON so each instance gets its own functionMap (require() caches and returns the same object reference).
|
|
91
|
+
this.functionMap = JSON.parse(JSON.stringify(require('./Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-FunctionMap.json')));
|
|
90
92
|
|
|
91
93
|
this.serviceType = 'ExpressionParser';
|
|
92
94
|
|
|
@@ -305,6 +305,111 @@ class FableServiceRestClient extends libFableServiceBase
|
|
|
305
305
|
return this.executeJSONRequest(pOptions, fCallback);
|
|
306
306
|
}
|
|
307
307
|
|
|
308
|
+
/**
|
|
309
|
+
* Upload binary data via POST.
|
|
310
|
+
*
|
|
311
|
+
* Accepts Buffer, Blob, or File as the body. In the browser, Blob/File
|
|
312
|
+
* bodies are converted to Buffer (via ArrayBuffer) before being passed
|
|
313
|
+
* to simple-get so the stream-http shim can send them correctly.
|
|
314
|
+
*
|
|
315
|
+
* The response body is read as a string (servers typically return JSON
|
|
316
|
+
* status for upload endpoints).
|
|
317
|
+
*
|
|
318
|
+
* @param {Record<string, any>} pOptions - Request options (url, body, headers, method)
|
|
319
|
+
* @param {(pError?: Error, pResponse: any, pBody?: any) => void} fCallback - Callback (pError, pResponse, pBody)
|
|
320
|
+
* @param {(pProgress: number) => void} [fOnProgress] - Optional progress callback (0.0 to 1.0); called with 1.0 on completion
|
|
321
|
+
*/
|
|
322
|
+
executeBinaryUpload(pOptions, fCallback, fOnProgress)
|
|
323
|
+
{
|
|
324
|
+
// Blob/File → Buffer conversion for simple-get compatibility
|
|
325
|
+
let tmpBody = pOptions.body;
|
|
326
|
+
|
|
327
|
+
if (typeof Blob !== 'undefined' && tmpBody instanceof Blob)
|
|
328
|
+
{
|
|
329
|
+
let tmpSelf = this;
|
|
330
|
+
tmpBody.arrayBuffer()
|
|
331
|
+
.then(
|
|
332
|
+
(pArrayBuffer) =>
|
|
333
|
+
{
|
|
334
|
+
pOptions.body = Buffer.from(pArrayBuffer);
|
|
335
|
+
tmpSelf._executeBinaryUploadInternal(pOptions, fCallback, fOnProgress);
|
|
336
|
+
})
|
|
337
|
+
.catch(
|
|
338
|
+
(pError) =>
|
|
339
|
+
{
|
|
340
|
+
return fCallback(pError);
|
|
341
|
+
});
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Already a Buffer, string, or stream — proceed directly
|
|
346
|
+
return this._executeBinaryUploadInternal(pOptions, fCallback, fOnProgress);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Internal binary upload implementation using simple-get.
|
|
351
|
+
*
|
|
352
|
+
* @param {Record<string, any>} pOptions - Request options with body already as Buffer
|
|
353
|
+
* @param {(pError?: Error, pResponse: any, pBody?: any) => void} fCallback - Callback (pError, pResponse, pBody)
|
|
354
|
+
* @param {(pProgress: number) => void} [fOnProgress] - Optional progress callback (0.0 to 1.0); called with 1.0 on completion
|
|
355
|
+
* @private
|
|
356
|
+
*/
|
|
357
|
+
_executeBinaryUploadInternal(pOptions, fCallback, fOnProgress)
|
|
358
|
+
{
|
|
359
|
+
let tmpOptions = this.preRequest(pOptions);
|
|
360
|
+
|
|
361
|
+
tmpOptions.RequestStartTime = this.fable.log.getTimeStamp();
|
|
362
|
+
|
|
363
|
+
if (this.TraceLog)
|
|
364
|
+
{
|
|
365
|
+
this.fable.log.debug(`Beginning ${tmpOptions.method} binary upload to ${tmpOptions.url} at ${tmpOptions.RequestStartTime}`);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
tmpOptions.json = false;
|
|
369
|
+
|
|
370
|
+
return libSimpleGet(tmpOptions,
|
|
371
|
+
(pError, pResponse) =>
|
|
372
|
+
{
|
|
373
|
+
if (pError)
|
|
374
|
+
{
|
|
375
|
+
return fCallback(pError, pResponse);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (this.TraceLog)
|
|
379
|
+
{
|
|
380
|
+
let tmpConnectTime = this.fable.log.getTimeStamp();
|
|
381
|
+
this.fable.log.debug(`--> Binary upload ${tmpOptions.method} connected in ${this.dataFormat.formatTimeDelta(tmpOptions.RequestStartTime, tmpConnectTime)}ms code ${pResponse.statusCode}`);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
let tmpData = '';
|
|
385
|
+
|
|
386
|
+
pResponse.on('data', (pChunk) =>
|
|
387
|
+
{
|
|
388
|
+
if (this.TraceLog)
|
|
389
|
+
{
|
|
390
|
+
let tmpChunkTime = this.fable.log.getTimeStamp();
|
|
391
|
+
this.fable.log.debug(`--> Binary upload ${tmpOptions.method} response chunk size ${pChunk.length}b received in ${this.dataFormat.formatTimeDelta(tmpOptions.RequestStartTime, tmpChunkTime)}ms`);
|
|
392
|
+
}
|
|
393
|
+
tmpData += pChunk;
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
pResponse.on('end', () =>
|
|
397
|
+
{
|
|
398
|
+
if (this.TraceLog)
|
|
399
|
+
{
|
|
400
|
+
let tmpCompletionTime = this.fable.log.getTimeStamp();
|
|
401
|
+
this.fable.log.debug(`==> Binary upload ${tmpOptions.method} completed in ${this.dataFormat.formatTimeDelta(tmpOptions.RequestStartTime, tmpCompletionTime)}ms`);
|
|
402
|
+
}
|
|
403
|
+
// Signal completion via progress callback
|
|
404
|
+
if (typeof fOnProgress === 'function')
|
|
405
|
+
{
|
|
406
|
+
fOnProgress(1.0);
|
|
407
|
+
}
|
|
408
|
+
return fCallback(pError, pResponse, tmpData);
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
|
|
308
413
|
getRawText(pOptionsOrURL, fCallback)
|
|
309
414
|
{
|
|
310
415
|
let tmpRequestOptions = (typeof(pOptionsOrURL) == 'object') ? pOptionsOrURL : {};
|
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration tests for the Fable RestClient executeBinaryUpload method.
|
|
3
|
+
*
|
|
4
|
+
* Spins up a real Orator/Restify server, uploads binary payloads via
|
|
5
|
+
* executeBinaryUpload, and verifies the server received them correctly.
|
|
6
|
+
*
|
|
7
|
+
* @license MIT
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
var libFable = require('../source/Fable.js');
|
|
11
|
+
var libOrator = require('orator');
|
|
12
|
+
var libOratorServiceServerRestify = require('orator-serviceserver-restify');
|
|
13
|
+
|
|
14
|
+
var Chai = require('chai');
|
|
15
|
+
var Expect = Chai.expect;
|
|
16
|
+
|
|
17
|
+
// Port for the test server — pick something unlikely to collide
|
|
18
|
+
var TEST_PORT = 18199;
|
|
19
|
+
|
|
20
|
+
suite
|
|
21
|
+
(
|
|
22
|
+
'RestClient Binary Upload',
|
|
23
|
+
function ()
|
|
24
|
+
{
|
|
25
|
+
// ----------------------------------------------------------------
|
|
26
|
+
// Test server lifecycle
|
|
27
|
+
// ----------------------------------------------------------------
|
|
28
|
+
var _Fable = null;
|
|
29
|
+
var _Orator = null;
|
|
30
|
+
// Track what the server received on each request
|
|
31
|
+
var _LastReceivedBody = null;
|
|
32
|
+
var _LastReceivedContentType = null;
|
|
33
|
+
|
|
34
|
+
suiteSetup
|
|
35
|
+
(
|
|
36
|
+
function (fDone)
|
|
37
|
+
{
|
|
38
|
+
_Fable = new libFable(
|
|
39
|
+
{
|
|
40
|
+
Product: 'BinaryUploadTestServer',
|
|
41
|
+
APIServerPort: TEST_PORT
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
_Fable.serviceManager.addServiceType('OratorServiceServer', libOratorServiceServerRestify);
|
|
45
|
+
|
|
46
|
+
_Orator = new libOrator(_Fable, {});
|
|
47
|
+
|
|
48
|
+
_Orator.initialize(
|
|
49
|
+
(pError) =>
|
|
50
|
+
{
|
|
51
|
+
if (pError)
|
|
52
|
+
{
|
|
53
|
+
return fDone(pError);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ----------------------------------------------------------
|
|
57
|
+
// POST /1.0/Artifact/Media/:id/:version
|
|
58
|
+
// Receives raw binary body, echoes back metadata as JSON.
|
|
59
|
+
// ----------------------------------------------------------
|
|
60
|
+
_Orator.serviceServer.doPost('/1.0/Artifact/Media/:IDArtifact/:Version',
|
|
61
|
+
(pRequest, pResponse, fNext) =>
|
|
62
|
+
{
|
|
63
|
+
let tmpChunks = [];
|
|
64
|
+
|
|
65
|
+
pRequest.on('data', (pChunk) =>
|
|
66
|
+
{
|
|
67
|
+
tmpChunks.push(pChunk);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
pRequest.on('end', () =>
|
|
71
|
+
{
|
|
72
|
+
_LastReceivedBody = Buffer.concat(tmpChunks);
|
|
73
|
+
_LastReceivedContentType = pRequest.headers['content-type'] || '';
|
|
74
|
+
|
|
75
|
+
pResponse.setHeader('content-type', 'application/json');
|
|
76
|
+
pResponse.send(200,
|
|
77
|
+
{
|
|
78
|
+
Success: true,
|
|
79
|
+
ReceivedBytes: _LastReceivedBody.length,
|
|
80
|
+
ContentType: _LastReceivedContentType,
|
|
81
|
+
IDArtifact: pRequest.params.IDArtifact,
|
|
82
|
+
Version: pRequest.params.Version
|
|
83
|
+
});
|
|
84
|
+
return fNext();
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// ----------------------------------------------------------
|
|
89
|
+
// GET /1.0/Artifact/Media/:id/:version
|
|
90
|
+
// Returns a small binary payload for download tests.
|
|
91
|
+
// ----------------------------------------------------------
|
|
92
|
+
_Orator.serviceServer.doGet('/1.0/Artifact/Media/:IDArtifact/:Version',
|
|
93
|
+
(pRequest, pResponse, fNext) =>
|
|
94
|
+
{
|
|
95
|
+
let tmpPayload = Buffer.from([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x01, 0x02, 0x03]);
|
|
96
|
+
pResponse.setHeader('content-type', 'image/png');
|
|
97
|
+
pResponse.sendRaw(200, tmpPayload);
|
|
98
|
+
return fNext();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// ----------------------------------------------------------
|
|
102
|
+
// POST /1.0/Echo
|
|
103
|
+
// JSON body parser echo endpoint for comparison testing.
|
|
104
|
+
// ----------------------------------------------------------
|
|
105
|
+
_Orator.serviceServer.doPost('/1.0/Echo',
|
|
106
|
+
libOratorServiceServerRestify.prototype.bodyParser.call(_Orator.serviceServer),
|
|
107
|
+
(pRequest, pResponse, fNext) =>
|
|
108
|
+
{
|
|
109
|
+
pResponse.send(200, { Echo: pRequest.body });
|
|
110
|
+
return fNext();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
_Orator.startService(
|
|
114
|
+
(pStartError) =>
|
|
115
|
+
{
|
|
116
|
+
return fDone(pStartError);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
suiteTeardown
|
|
123
|
+
(
|
|
124
|
+
function (fDone)
|
|
125
|
+
{
|
|
126
|
+
if (_Orator)
|
|
127
|
+
{
|
|
128
|
+
_Orator.stopService(fDone);
|
|
129
|
+
}
|
|
130
|
+
else
|
|
131
|
+
{
|
|
132
|
+
fDone();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
setup
|
|
138
|
+
(
|
|
139
|
+
function ()
|
|
140
|
+
{
|
|
141
|
+
_LastReceivedBody = null;
|
|
142
|
+
_LastReceivedContentType = null;
|
|
143
|
+
}
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
// ----------------------------------------------------------------
|
|
147
|
+
// Tests
|
|
148
|
+
// ----------------------------------------------------------------
|
|
149
|
+
|
|
150
|
+
suite
|
|
151
|
+
(
|
|
152
|
+
'executeBinaryUpload',
|
|
153
|
+
function ()
|
|
154
|
+
{
|
|
155
|
+
test
|
|
156
|
+
(
|
|
157
|
+
'Upload a Buffer payload and verify server received it.',
|
|
158
|
+
function (fTestComplete)
|
|
159
|
+
{
|
|
160
|
+
let tmpFable = new libFable({ TraceLog: true });
|
|
161
|
+
let tmpRestClient = tmpFable.instantiateServiceProvider('RestClient', { TraceLog: true });
|
|
162
|
+
|
|
163
|
+
let tmpPayload = Buffer.from('Hello binary world! This is a test payload with some bytes.');
|
|
164
|
+
let tmpOptions =
|
|
165
|
+
{
|
|
166
|
+
url: `http://localhost:${TEST_PORT}/1.0/Artifact/Media/42/1`,
|
|
167
|
+
method: 'POST',
|
|
168
|
+
body: tmpPayload,
|
|
169
|
+
headers: { 'Content-Type': 'image/jpeg' }
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
tmpRestClient.executeBinaryUpload(tmpOptions,
|
|
173
|
+
(pError, pResponse, pBody) =>
|
|
174
|
+
{
|
|
175
|
+
Expect(pError).to.not.be.an('error');
|
|
176
|
+
Expect(pResponse).to.be.an('object');
|
|
177
|
+
Expect(pResponse.statusCode).to.equal(200);
|
|
178
|
+
|
|
179
|
+
// Parse the JSON response from the server
|
|
180
|
+
let tmpParsedBody = JSON.parse(pBody);
|
|
181
|
+
Expect(tmpParsedBody.Success).to.equal(true);
|
|
182
|
+
Expect(tmpParsedBody.ReceivedBytes).to.equal(tmpPayload.length);
|
|
183
|
+
Expect(tmpParsedBody.ContentType).to.equal('image/jpeg');
|
|
184
|
+
Expect(tmpParsedBody.IDArtifact).to.equal('42');
|
|
185
|
+
Expect(tmpParsedBody.Version).to.equal('1');
|
|
186
|
+
|
|
187
|
+
// Verify the actual bytes the server received
|
|
188
|
+
Expect(_LastReceivedBody).to.be.instanceof(Buffer);
|
|
189
|
+
Expect(_LastReceivedBody.length).to.equal(tmpPayload.length);
|
|
190
|
+
Expect(_LastReceivedBody.toString()).to.equal(tmpPayload.toString());
|
|
191
|
+
|
|
192
|
+
fTestComplete();
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
test
|
|
198
|
+
(
|
|
199
|
+
'Upload binary data with different MIME type.',
|
|
200
|
+
function (fTestComplete)
|
|
201
|
+
{
|
|
202
|
+
let tmpFable = new libFable();
|
|
203
|
+
let tmpRestClient = tmpFable.instantiateServiceProvider('RestClient');
|
|
204
|
+
|
|
205
|
+
// Create a binary payload with non-text bytes
|
|
206
|
+
let tmpPayload = Buffer.alloc(256);
|
|
207
|
+
for (let i = 0; i < 256; i++)
|
|
208
|
+
{
|
|
209
|
+
tmpPayload[i] = i;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
let tmpOptions =
|
|
213
|
+
{
|
|
214
|
+
url: `http://localhost:${TEST_PORT}/1.0/Artifact/Media/99/3`,
|
|
215
|
+
method: 'POST',
|
|
216
|
+
body: tmpPayload,
|
|
217
|
+
headers: { 'Content-Type': 'application/pdf' }
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
tmpRestClient.executeBinaryUpload(tmpOptions,
|
|
221
|
+
(pError, pResponse, pBody) =>
|
|
222
|
+
{
|
|
223
|
+
Expect(pError).to.not.be.an('error');
|
|
224
|
+
|
|
225
|
+
let tmpParsedBody = JSON.parse(pBody);
|
|
226
|
+
Expect(tmpParsedBody.Success).to.equal(true);
|
|
227
|
+
Expect(tmpParsedBody.ReceivedBytes).to.equal(256);
|
|
228
|
+
Expect(tmpParsedBody.ContentType).to.equal('application/pdf');
|
|
229
|
+
Expect(tmpParsedBody.IDArtifact).to.equal('99');
|
|
230
|
+
Expect(tmpParsedBody.Version).to.equal('3');
|
|
231
|
+
|
|
232
|
+
// Verify all 256 byte values survived the round-trip
|
|
233
|
+
Expect(_LastReceivedBody.length).to.equal(256);
|
|
234
|
+
for (let i = 0; i < 256; i++)
|
|
235
|
+
{
|
|
236
|
+
Expect(_LastReceivedBody[i]).to.equal(i);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
fTestComplete();
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
test
|
|
245
|
+
(
|
|
246
|
+
'Progress callback is called with 1.0 on completion.',
|
|
247
|
+
function (fTestComplete)
|
|
248
|
+
{
|
|
249
|
+
let tmpFable = new libFable();
|
|
250
|
+
let tmpRestClient = tmpFable.instantiateServiceProvider('RestClient');
|
|
251
|
+
|
|
252
|
+
let tmpPayload = Buffer.from('progress test');
|
|
253
|
+
let tmpProgressCalled = false;
|
|
254
|
+
let tmpProgressValue = -1;
|
|
255
|
+
|
|
256
|
+
let tmpOptions =
|
|
257
|
+
{
|
|
258
|
+
url: `http://localhost:${TEST_PORT}/1.0/Artifact/Media/1/1`,
|
|
259
|
+
method: 'POST',
|
|
260
|
+
body: tmpPayload,
|
|
261
|
+
headers: { 'Content-Type': 'application/octet-stream' }
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
tmpRestClient.executeBinaryUpload(tmpOptions,
|
|
265
|
+
(pError, pResponse, pBody) =>
|
|
266
|
+
{
|
|
267
|
+
Expect(pError).to.not.be.an('error');
|
|
268
|
+
Expect(tmpProgressCalled).to.equal(true);
|
|
269
|
+
Expect(tmpProgressValue).to.equal(1.0);
|
|
270
|
+
fTestComplete();
|
|
271
|
+
},
|
|
272
|
+
(pProgress) =>
|
|
273
|
+
{
|
|
274
|
+
tmpProgressCalled = true;
|
|
275
|
+
tmpProgressValue = pProgress;
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
test
|
|
281
|
+
(
|
|
282
|
+
'Upload empty body returns success.',
|
|
283
|
+
function (fTestComplete)
|
|
284
|
+
{
|
|
285
|
+
let tmpFable = new libFable();
|
|
286
|
+
let tmpRestClient = tmpFable.instantiateServiceProvider('RestClient');
|
|
287
|
+
|
|
288
|
+
let tmpPayload = Buffer.alloc(0);
|
|
289
|
+
let tmpOptions =
|
|
290
|
+
{
|
|
291
|
+
url: `http://localhost:${TEST_PORT}/1.0/Artifact/Media/5/1`,
|
|
292
|
+
method: 'POST',
|
|
293
|
+
body: tmpPayload,
|
|
294
|
+
headers: { 'Content-Type': 'image/png' }
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
tmpRestClient.executeBinaryUpload(tmpOptions,
|
|
298
|
+
(pError, pResponse, pBody) =>
|
|
299
|
+
{
|
|
300
|
+
Expect(pError).to.not.be.an('error');
|
|
301
|
+
let tmpParsedBody = JSON.parse(pBody);
|
|
302
|
+
Expect(tmpParsedBody.Success).to.equal(true);
|
|
303
|
+
Expect(tmpParsedBody.ReceivedBytes).to.equal(0);
|
|
304
|
+
fTestComplete();
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
test
|
|
310
|
+
(
|
|
311
|
+
'Upload a large binary payload (1MB).',
|
|
312
|
+
function (fTestComplete)
|
|
313
|
+
{
|
|
314
|
+
this.timeout(10000);
|
|
315
|
+
|
|
316
|
+
let tmpFable = new libFable();
|
|
317
|
+
let tmpRestClient = tmpFable.instantiateServiceProvider('RestClient');
|
|
318
|
+
|
|
319
|
+
// Create a 1MB payload with a known pattern
|
|
320
|
+
let tmpSize = 1024 * 1024;
|
|
321
|
+
let tmpPayload = Buffer.alloc(tmpSize);
|
|
322
|
+
for (let i = 0; i < tmpSize; i++)
|
|
323
|
+
{
|
|
324
|
+
tmpPayload[i] = i % 256;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
let tmpOptions =
|
|
328
|
+
{
|
|
329
|
+
url: `http://localhost:${TEST_PORT}/1.0/Artifact/Media/100/1`,
|
|
330
|
+
method: 'POST',
|
|
331
|
+
body: tmpPayload,
|
|
332
|
+
headers: { 'Content-Type': 'application/octet-stream' }
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
tmpRestClient.executeBinaryUpload(tmpOptions,
|
|
336
|
+
(pError, pResponse, pBody) =>
|
|
337
|
+
{
|
|
338
|
+
Expect(pError).to.not.be.an('error');
|
|
339
|
+
|
|
340
|
+
let tmpParsedBody = JSON.parse(pBody);
|
|
341
|
+
Expect(tmpParsedBody.Success).to.equal(true);
|
|
342
|
+
Expect(tmpParsedBody.ReceivedBytes).to.equal(tmpSize);
|
|
343
|
+
|
|
344
|
+
// Verify first and last bytes
|
|
345
|
+
Expect(_LastReceivedBody[0]).to.equal(0);
|
|
346
|
+
Expect(_LastReceivedBody[255]).to.equal(255);
|
|
347
|
+
Expect(_LastReceivedBody[256]).to.equal(0);
|
|
348
|
+
Expect(_LastReceivedBody[tmpSize - 1]).to.equal((tmpSize - 1) % 256);
|
|
349
|
+
|
|
350
|
+
fTestComplete();
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
test
|
|
356
|
+
(
|
|
357
|
+
'Works with RestClientURLPrefix setting.',
|
|
358
|
+
function (fTestComplete)
|
|
359
|
+
{
|
|
360
|
+
let tmpFable = new libFable(
|
|
361
|
+
{
|
|
362
|
+
RestClientURLPrefix: `http://localhost:${TEST_PORT}`
|
|
363
|
+
});
|
|
364
|
+
let tmpRestClient = tmpFable.instantiateServiceProvider('RestClient');
|
|
365
|
+
|
|
366
|
+
let tmpPayload = Buffer.from('prefix test');
|
|
367
|
+
let tmpOptions =
|
|
368
|
+
{
|
|
369
|
+
url: '/1.0/Artifact/Media/7/2',
|
|
370
|
+
method: 'POST',
|
|
371
|
+
body: tmpPayload,
|
|
372
|
+
headers: { 'Content-Type': 'text/plain' }
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
tmpRestClient.executeBinaryUpload(tmpOptions,
|
|
376
|
+
(pError, pResponse, pBody) =>
|
|
377
|
+
{
|
|
378
|
+
Expect(pError).to.not.be.an('error');
|
|
379
|
+
|
|
380
|
+
let tmpParsedBody = JSON.parse(pBody);
|
|
381
|
+
Expect(tmpParsedBody.Success).to.equal(true);
|
|
382
|
+
Expect(tmpParsedBody.IDArtifact).to.equal('7');
|
|
383
|
+
Expect(tmpParsedBody.Version).to.equal('2');
|
|
384
|
+
Expect(tmpParsedBody.ContentType).to.equal('text/plain');
|
|
385
|
+
|
|
386
|
+
fTestComplete();
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
test
|
|
392
|
+
(
|
|
393
|
+
'Cookies are applied via prepareCookies.',
|
|
394
|
+
function (fTestComplete)
|
|
395
|
+
{
|
|
396
|
+
let tmpFable = new libFable();
|
|
397
|
+
let tmpRestClient = tmpFable.instantiateServiceProvider('RestClient');
|
|
398
|
+
|
|
399
|
+
tmpRestClient.cookie = { SessionID: 'abc123' };
|
|
400
|
+
|
|
401
|
+
let tmpPayload = Buffer.from('cookie test');
|
|
402
|
+
let tmpOptions =
|
|
403
|
+
{
|
|
404
|
+
url: `http://localhost:${TEST_PORT}/1.0/Artifact/Media/8/1`,
|
|
405
|
+
method: 'POST',
|
|
406
|
+
body: tmpPayload,
|
|
407
|
+
headers: { 'Content-Type': 'image/jpeg' }
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
tmpRestClient.executeBinaryUpload(tmpOptions,
|
|
411
|
+
(pError, pResponse, pBody) =>
|
|
412
|
+
{
|
|
413
|
+
Expect(pError).to.not.be.an('error');
|
|
414
|
+
|
|
415
|
+
let tmpParsedBody = JSON.parse(pBody);
|
|
416
|
+
Expect(tmpParsedBody.Success).to.equal(true);
|
|
417
|
+
|
|
418
|
+
fTestComplete();
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
);
|
|
424
|
+
|
|
425
|
+
suite
|
|
426
|
+
(
|
|
427
|
+
'executeChunkedRequest binary download',
|
|
428
|
+
function ()
|
|
429
|
+
{
|
|
430
|
+
test
|
|
431
|
+
(
|
|
432
|
+
'Download binary data via GET.',
|
|
433
|
+
function (fTestComplete)
|
|
434
|
+
{
|
|
435
|
+
let tmpFable = new libFable({ TraceLog: true });
|
|
436
|
+
let tmpRestClient = tmpFable.instantiateServiceProvider('RestClient', { TraceLog: true });
|
|
437
|
+
|
|
438
|
+
tmpRestClient.executeChunkedRequest(
|
|
439
|
+
{
|
|
440
|
+
url: `http://localhost:${TEST_PORT}/1.0/Artifact/Media/42/1`,
|
|
441
|
+
method: 'GET'
|
|
442
|
+
},
|
|
443
|
+
(pError, pResponse, pBody) =>
|
|
444
|
+
{
|
|
445
|
+
Expect(pError).to.not.be.an('error');
|
|
446
|
+
Expect(pResponse.statusCode).to.equal(200);
|
|
447
|
+
// Body is a string from executeChunkedRequest
|
|
448
|
+
Expect(pBody.length).to.be.greaterThan(0);
|
|
449
|
+
fTestComplete();
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
);
|
|
453
|
+
|
|
454
|
+
test
|
|
455
|
+
(
|
|
456
|
+
'Download binary data via executeChunkedRequestBinary returns Buffer.',
|
|
457
|
+
function (fTestComplete)
|
|
458
|
+
{
|
|
459
|
+
let tmpFable = new libFable();
|
|
460
|
+
let tmpRestClient = tmpFable.instantiateServiceProvider('RestClient');
|
|
461
|
+
|
|
462
|
+
tmpRestClient.executeChunkedRequestBinary(
|
|
463
|
+
{
|
|
464
|
+
url: `http://localhost:${TEST_PORT}/1.0/Artifact/Media/42/1`,
|
|
465
|
+
method: 'GET'
|
|
466
|
+
},
|
|
467
|
+
(pError, pResponse, pBuffer) =>
|
|
468
|
+
{
|
|
469
|
+
Expect(pError).to.not.be.an('error');
|
|
470
|
+
Expect(pResponse.statusCode).to.equal(200);
|
|
471
|
+
Expect(pBuffer).to.be.instanceof(Buffer);
|
|
472
|
+
// Check PNG magic bytes
|
|
473
|
+
Expect(pBuffer[0]).to.equal(0x89);
|
|
474
|
+
Expect(pBuffer[1]).to.equal(0x50);
|
|
475
|
+
Expect(pBuffer[2]).to.equal(0x4E);
|
|
476
|
+
Expect(pBuffer[3]).to.equal(0x47);
|
|
477
|
+
fTestComplete();
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
);
|
|
483
|
+
|
|
484
|
+
suite
|
|
485
|
+
(
|
|
486
|
+
'Round-trip: upload then download',
|
|
487
|
+
function ()
|
|
488
|
+
{
|
|
489
|
+
test
|
|
490
|
+
(
|
|
491
|
+
'Upload binary, then download and verify content matches.',
|
|
492
|
+
function (fTestComplete)
|
|
493
|
+
{
|
|
494
|
+
let tmpFable = new libFable();
|
|
495
|
+
let tmpRestClient = tmpFable.instantiateServiceProvider('RestClient');
|
|
496
|
+
|
|
497
|
+
// Upload
|
|
498
|
+
let tmpPayload = Buffer.from([0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE]);
|
|
499
|
+
let tmpOptions =
|
|
500
|
+
{
|
|
501
|
+
url: `http://localhost:${TEST_PORT}/1.0/Artifact/Media/200/1`,
|
|
502
|
+
method: 'POST',
|
|
503
|
+
body: tmpPayload,
|
|
504
|
+
headers: { 'Content-Type': 'application/octet-stream' }
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
tmpRestClient.executeBinaryUpload(tmpOptions,
|
|
508
|
+
(pUploadError, pUploadResponse, pUploadBody) =>
|
|
509
|
+
{
|
|
510
|
+
Expect(pUploadError).to.not.be.an('error');
|
|
511
|
+
|
|
512
|
+
let tmpParsedBody = JSON.parse(pUploadBody);
|
|
513
|
+
Expect(tmpParsedBody.Success).to.equal(true);
|
|
514
|
+
Expect(tmpParsedBody.ReceivedBytes).to.equal(6);
|
|
515
|
+
|
|
516
|
+
// Now download from the same endpoint
|
|
517
|
+
tmpRestClient.executeChunkedRequestBinary(
|
|
518
|
+
{
|
|
519
|
+
url: `http://localhost:${TEST_PORT}/1.0/Artifact/Media/200/1`,
|
|
520
|
+
method: 'GET'
|
|
521
|
+
},
|
|
522
|
+
(pDownloadError, pDownloadResponse, pBuffer) =>
|
|
523
|
+
{
|
|
524
|
+
Expect(pDownloadError).to.not.be.an('error');
|
|
525
|
+
Expect(pBuffer).to.be.instanceof(Buffer);
|
|
526
|
+
// The download endpoint returns a fixed PNG stub,
|
|
527
|
+
// not the uploaded data — but both should succeed
|
|
528
|
+
Expect(pBuffer.length).to.be.greaterThan(0);
|
|
529
|
+
fTestComplete();
|
|
530
|
+
});
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
);
|