fable 3.1.64 → 3.1.66

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 (24) hide show
  1. package/docs/services/expression-parser-functions/README.md +9 -0
  2. package/docs/services/expression-parser-functions/beziercurvefit.md +57 -0
  3. package/docs/services/expression-parser-functions/bezierpoint.md +55 -0
  4. package/docs/services/expression-parser-functions/intercept.md +59 -0
  5. package/docs/services/expression-parser-functions/setvalue.md +55 -0
  6. package/docs/services/expression-parser-functions/slope.md +51 -0
  7. package/docs/services/expression-parser-functions/ternary.md +180 -0
  8. package/docs/services/expression-parser.md +72 -0
  9. package/example_applications/mathematical_playground/Math-Solver-Harness.js +108 -0
  10. package/example_applications/mathematical_playground/TestSuite-AcidTest.json +178 -0
  11. package/example_applications/mathematical_playground/TestSuite-Identities.json +147 -0
  12. package/example_applications/mathematical_playground/TestSuite-Precision.json +127 -0
  13. package/example_applications/mathematical_playground/TestSuite-Spreadsheet.json +198 -0
  14. package/package.json +3 -3
  15. package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-ExpressionTokenizer-DirectiveMutation.js +143 -0
  16. package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-ExpressionTokenizer.js +1 -1
  17. package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-FunctionMap.json +5 -4
  18. package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-TokenMap.json +65 -1
  19. package/source/services/Fable-Service-ExpressionParser.js +1 -0
  20. package/source/services/Fable-Service-Logic.js +33 -0
  21. package/source/services/Fable-Service-Math.js +78 -0
  22. package/source/services/Fable-Service-RestClient.js +105 -0
  23. package/test/ExpressionParser_tests.js +290 -0
  24. package/test/RestClientBinaryUpload_test.js +537 -0
@@ -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
+ );