meadow-integration 1.0.1 → 1.0.4
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/CONTRIBUTING.md +50 -0
- package/README.md +223 -7
- package/docs/README.md +107 -7
- package/docs/_sidebar.md +38 -0
- package/docs/_topbar.md +7 -0
- package/docs/cli-reference.md +242 -0
- package/docs/comprehensions.md +98 -0
- package/docs/cover.md +11 -0
- package/docs/css/docuserve.css +73 -0
- package/docs/examples-walkthrough.md +138 -0
- package/docs/index.html +37 -20
- package/docs/integration-adapter.md +109 -0
- package/docs/mapping-files.md +140 -0
- package/docs/programmatic-api.md +173 -0
- package/docs/rest-api-reference.md +731 -0
- package/docs/retold-catalog.json +153 -0
- package/docs/retold-keyword-index.json +4828 -0
- package/examples/Example-001-CSV-Check.sh +29 -0
- package/examples/Example-002-CSV-Transform-Implicit.sh +31 -0
- package/examples/Example-003-CSV-Transform-CLI-Options.sh +39 -0
- package/examples/Example-004-CSV-Transform-Mapping-File.sh +41 -0
- package/examples/Example-005-Multi-Entity-Bookstore.sh +60 -0
- package/examples/Example-006-Multi-CSV-Intersect.sh +74 -0
- package/examples/Example-007-Comprehension-To-Array.sh +41 -0
- package/examples/Example-008-Comprehension-To-CSV.sh +51 -0
- package/examples/Example-009-JSON-Array-Transform.sh +46 -0
- package/examples/Example-010-Programmatic-API.js +138 -0
- package/examples/README.md +44 -0
- package/examples/output/.gitignore +2 -0
- package/package.json +7 -4
- package/source/Meadow-Integration.js +3 -1
- package/source/cli/Meadow-Integration-CLI-Program.js +4 -1
- package/source/cli/commands/Meadow-Integration-Command-ObjectArrayToCSV.js +49 -32
- package/source/cli/commands/Meadow-Integration-Command-Serve.js +51 -0
- package/source/restserver/Meadow-Integration-Server-Endpoints.js +83 -0
- package/source/restserver/Meadow-Integration-Server.js +86 -0
- package/source/restserver/endpoints/Endpoint-CSVCheck.js +91 -0
- package/source/restserver/endpoints/Endpoint-CSVTransform.js +189 -0
- package/source/restserver/endpoints/Endpoint-ComprehensionArray.js +121 -0
- package/source/restserver/endpoints/Endpoint-ComprehensionIntersect.js +166 -0
- package/source/restserver/endpoints/Endpoint-ComprehensionPush.js +209 -0
- package/source/restserver/endpoints/Endpoint-EntityFromTabularFolder.js +252 -0
- package/source/restserver/endpoints/Endpoint-JSONArrayTransform.js +238 -0
- package/source/restserver/endpoints/Endpoint-ObjectArrayToCSV.js +231 -0
- package/source/restserver/endpoints/Endpoint-TSVCheck.js +93 -0
- package/source/restserver/endpoints/Endpoint-TSVTransform.js +191 -0
- package/test/Meadow-Integration-Server_test.js +1170 -0
- package/test/data/test-comprehension-secondary.json +8 -0
- package/test/data/test-comprehension.json +8 -0
- package/test/data/test-small.csv +6 -0
- package/test/data/test-small.json +7 -0
- package/test/data/test-small.tsv +6 -0
|
@@ -0,0 +1,1170 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Unit tests for Meadow Integration REST Server
|
|
3
|
+
|
|
4
|
+
Exercises all REST API endpoints provided by the integration server.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const Chai = require('chai');
|
|
8
|
+
const Expect = Chai.expect;
|
|
9
|
+
|
|
10
|
+
const libHTTP = require('http');
|
|
11
|
+
const libPath = require('path');
|
|
12
|
+
|
|
13
|
+
const MeadowIntegrationServer = require('../source/restserver/Meadow-Integration-Server.js');
|
|
14
|
+
|
|
15
|
+
const TEST_PORT = 18086;
|
|
16
|
+
const TEST_BASE_URL = `http://localhost:${TEST_PORT}`;
|
|
17
|
+
|
|
18
|
+
const TEST_DATA_DIR = libPath.join(__dirname, 'data');
|
|
19
|
+
const EXAMPLE_DATA_DIR = libPath.join(__dirname, '..', 'docs', 'examples', 'data');
|
|
20
|
+
|
|
21
|
+
let _Server = null;
|
|
22
|
+
|
|
23
|
+
// Helper to make HTTP requests
|
|
24
|
+
function makeRequest(pMethod, pPath, pBody, fCallback)
|
|
25
|
+
{
|
|
26
|
+
let tmpBodyString = (pBody !== null && pBody !== undefined) ? JSON.stringify(pBody) : null;
|
|
27
|
+
let tmpOptions =
|
|
28
|
+
{
|
|
29
|
+
hostname: 'localhost',
|
|
30
|
+
port: TEST_PORT,
|
|
31
|
+
path: pPath,
|
|
32
|
+
method: pMethod,
|
|
33
|
+
headers: {}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
if (tmpBodyString)
|
|
37
|
+
{
|
|
38
|
+
tmpOptions.headers['Content-Type'] = 'application/json';
|
|
39
|
+
tmpOptions.headers['Content-Length'] = Buffer.byteLength(tmpBodyString);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const tmpRequest = libHTTP.request(tmpOptions,
|
|
43
|
+
(pResponse) =>
|
|
44
|
+
{
|
|
45
|
+
let tmpData = '';
|
|
46
|
+
pResponse.on('data', (pChunk) => { tmpData += pChunk; });
|
|
47
|
+
pResponse.on('end',
|
|
48
|
+
() =>
|
|
49
|
+
{
|
|
50
|
+
let tmpParsed = null;
|
|
51
|
+
try
|
|
52
|
+
{
|
|
53
|
+
tmpParsed = JSON.parse(tmpData);
|
|
54
|
+
}
|
|
55
|
+
catch (pError)
|
|
56
|
+
{
|
|
57
|
+
// Not JSON — return raw string (e.g. CSV)
|
|
58
|
+
tmpParsed = tmpData;
|
|
59
|
+
}
|
|
60
|
+
return fCallback(null, pResponse.statusCode, tmpParsed, pResponse.headers);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
tmpRequest.on('error', (pError) => { return fCallback(pError); });
|
|
65
|
+
|
|
66
|
+
if (tmpBodyString)
|
|
67
|
+
{
|
|
68
|
+
tmpRequest.write(tmpBodyString);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
tmpRequest.end();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
suite
|
|
75
|
+
(
|
|
76
|
+
'Meadow Integration REST Server',
|
|
77
|
+
() =>
|
|
78
|
+
{
|
|
79
|
+
suiteSetup
|
|
80
|
+
(
|
|
81
|
+
(fDone) =>
|
|
82
|
+
{
|
|
83
|
+
_Server = new MeadowIntegrationServer(
|
|
84
|
+
{
|
|
85
|
+
APIServerPort: TEST_PORT,
|
|
86
|
+
LogLevel: 1
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
_Server.start(
|
|
90
|
+
(pError) =>
|
|
91
|
+
{
|
|
92
|
+
if (pError)
|
|
93
|
+
{
|
|
94
|
+
console.log('Error starting test server: ', pError);
|
|
95
|
+
}
|
|
96
|
+
Expect(pError).to.not.exist;
|
|
97
|
+
return fDone();
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
suiteTeardown
|
|
103
|
+
(
|
|
104
|
+
function(fDone)
|
|
105
|
+
{
|
|
106
|
+
this.timeout(10000);
|
|
107
|
+
if (_Server)
|
|
108
|
+
{
|
|
109
|
+
_Server.stop(
|
|
110
|
+
(pError) =>
|
|
111
|
+
{
|
|
112
|
+
return fDone();
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
else
|
|
116
|
+
{
|
|
117
|
+
return fDone();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
// ===== Status Endpoint =====
|
|
123
|
+
suite
|
|
124
|
+
(
|
|
125
|
+
'GET /1.0/Status',
|
|
126
|
+
() =>
|
|
127
|
+
{
|
|
128
|
+
test('Should return server status and endpoint list',
|
|
129
|
+
(fDone) =>
|
|
130
|
+
{
|
|
131
|
+
makeRequest('GET', '/1.0/Status', null,
|
|
132
|
+
(pError, pStatusCode, pBody) =>
|
|
133
|
+
{
|
|
134
|
+
Expect(pError).to.not.exist;
|
|
135
|
+
Expect(pStatusCode).to.equal(200);
|
|
136
|
+
Expect(pBody).to.be.an('object');
|
|
137
|
+
Expect(pBody.Status).to.equal('Running');
|
|
138
|
+
Expect(pBody.Product).to.equal('Meadow-Integration-Server');
|
|
139
|
+
Expect(pBody.Endpoints).to.be.an('array');
|
|
140
|
+
Expect(pBody.Endpoints.length).to.equal(15);
|
|
141
|
+
return fDone();
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
// ===== CSV Check Endpoint =====
|
|
148
|
+
suite
|
|
149
|
+
(
|
|
150
|
+
'POST /1.0/CSV/Check',
|
|
151
|
+
() =>
|
|
152
|
+
{
|
|
153
|
+
test('Should analyze a CSV file and return statistics',
|
|
154
|
+
(fDone) =>
|
|
155
|
+
{
|
|
156
|
+
makeRequest('POST', '/1.0/CSV/Check',
|
|
157
|
+
{ File: libPath.join(TEST_DATA_DIR, 'test-small.csv') },
|
|
158
|
+
(pError, pStatusCode, pBody) =>
|
|
159
|
+
{
|
|
160
|
+
Expect(pError).to.not.exist;
|
|
161
|
+
Expect(pStatusCode).to.equal(200);
|
|
162
|
+
Expect(pBody).to.be.an('object');
|
|
163
|
+
Expect(pBody.RowCount).to.equal(5);
|
|
164
|
+
Expect(pBody.ColumnCount).to.equal(5);
|
|
165
|
+
Expect(pBody.Headers).to.be.an('array');
|
|
166
|
+
Expect(pBody.Headers).to.include('id');
|
|
167
|
+
Expect(pBody.Headers).to.include('name');
|
|
168
|
+
return fDone();
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test('Should return 400 when no File is provided',
|
|
173
|
+
(fDone) =>
|
|
174
|
+
{
|
|
175
|
+
makeRequest('POST', '/1.0/CSV/Check', {},
|
|
176
|
+
(pError, pStatusCode, pBody) =>
|
|
177
|
+
{
|
|
178
|
+
Expect(pError).to.not.exist;
|
|
179
|
+
Expect(pStatusCode).to.equal(400);
|
|
180
|
+
Expect(pBody.Error).to.be.a('string');
|
|
181
|
+
return fDone();
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test('Should return 404 when file does not exist',
|
|
186
|
+
(fDone) =>
|
|
187
|
+
{
|
|
188
|
+
makeRequest('POST', '/1.0/CSV/Check',
|
|
189
|
+
{ File: '/tmp/nonexistent-test-file-99999.csv' },
|
|
190
|
+
(pError, pStatusCode, pBody) =>
|
|
191
|
+
{
|
|
192
|
+
Expect(pError).to.not.exist;
|
|
193
|
+
Expect(pStatusCode).to.equal(404);
|
|
194
|
+
Expect(pBody.Error).to.be.a('string');
|
|
195
|
+
return fDone();
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test('Should include records when Records flag is true',
|
|
200
|
+
(fDone) =>
|
|
201
|
+
{
|
|
202
|
+
makeRequest('POST', '/1.0/CSV/Check',
|
|
203
|
+
{ File: libPath.join(TEST_DATA_DIR, 'test-small.csv'), Records: true },
|
|
204
|
+
(pError, pStatusCode, pBody) =>
|
|
205
|
+
{
|
|
206
|
+
Expect(pError).to.not.exist;
|
|
207
|
+
Expect(pStatusCode).to.equal(200);
|
|
208
|
+
Expect(pBody.Records).to.be.an('array');
|
|
209
|
+
// Records are pushed per-column in collectStatistics, so count = rows * columns
|
|
210
|
+
Expect(pBody.Records.length).to.be.greaterThan(0);
|
|
211
|
+
Expect(pBody.RowCount).to.equal(5);
|
|
212
|
+
return fDone();
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test('Should analyze the airports CSV',
|
|
217
|
+
(fDone) =>
|
|
218
|
+
{
|
|
219
|
+
makeRequest('POST', '/1.0/CSV/Check',
|
|
220
|
+
{ File: libPath.join(TEST_DATA_DIR, 'vega', 'airports.csv') },
|
|
221
|
+
(pError, pStatusCode, pBody) =>
|
|
222
|
+
{
|
|
223
|
+
Expect(pError).to.not.exist;
|
|
224
|
+
Expect(pStatusCode).to.equal(200);
|
|
225
|
+
Expect(pBody.RowCount).to.equal(3376);
|
|
226
|
+
Expect(pBody.ColumnCount).to.equal(7);
|
|
227
|
+
return fDone();
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
// ===== CSV Transform Endpoint =====
|
|
234
|
+
suite
|
|
235
|
+
(
|
|
236
|
+
'POST /1.0/CSV/Transform',
|
|
237
|
+
() =>
|
|
238
|
+
{
|
|
239
|
+
test('Should transform a CSV file into a comprehension with implicit config',
|
|
240
|
+
(fDone) =>
|
|
241
|
+
{
|
|
242
|
+
makeRequest('POST', '/1.0/CSV/Transform',
|
|
243
|
+
{ File: libPath.join(TEST_DATA_DIR, 'test-small.csv') },
|
|
244
|
+
(pError, pStatusCode, pBody) =>
|
|
245
|
+
{
|
|
246
|
+
Expect(pError).to.not.exist;
|
|
247
|
+
Expect(pStatusCode).to.equal(200);
|
|
248
|
+
Expect(pBody).to.be.an('object');
|
|
249
|
+
// Implicit config should auto-detect entity from filename
|
|
250
|
+
let tmpEntityKeys = Object.keys(pBody);
|
|
251
|
+
Expect(tmpEntityKeys.length).to.be.greaterThan(0);
|
|
252
|
+
// Check that records exist in the entity
|
|
253
|
+
let tmpFirstEntity = pBody[tmpEntityKeys[0]];
|
|
254
|
+
Expect(Object.keys(tmpFirstEntity).length).to.equal(5);
|
|
255
|
+
return fDone();
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
test('Should transform with explicit entity and GUID template',
|
|
260
|
+
(fDone) =>
|
|
261
|
+
{
|
|
262
|
+
makeRequest('POST', '/1.0/CSV/Transform',
|
|
263
|
+
{
|
|
264
|
+
File: libPath.join(TEST_DATA_DIR, 'test-small.csv'),
|
|
265
|
+
Entity: 'Person',
|
|
266
|
+
GUIDName: 'GUIDPerson',
|
|
267
|
+
GUIDTemplate: 'Person_{~D:Record.id~}'
|
|
268
|
+
},
|
|
269
|
+
(pError, pStatusCode, pBody) =>
|
|
270
|
+
{
|
|
271
|
+
Expect(pError).to.not.exist;
|
|
272
|
+
Expect(pStatusCode).to.equal(200);
|
|
273
|
+
Expect(pBody).to.have.property('Person');
|
|
274
|
+
Expect(pBody.Person).to.have.property('Person_1');
|
|
275
|
+
Expect(pBody.Person).to.have.property('Person_5');
|
|
276
|
+
Expect(pBody.Person['Person_1'].name).to.equal('Alice');
|
|
277
|
+
return fDone();
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
test('Should transform with custom column mappings',
|
|
282
|
+
(fDone) =>
|
|
283
|
+
{
|
|
284
|
+
makeRequest('POST', '/1.0/CSV/Transform',
|
|
285
|
+
{
|
|
286
|
+
File: libPath.join(TEST_DATA_DIR, 'test-small.csv'),
|
|
287
|
+
Entity: 'Person',
|
|
288
|
+
GUIDName: 'GUIDPerson',
|
|
289
|
+
GUIDTemplate: 'Person_{~D:Record.id~}',
|
|
290
|
+
Mappings:
|
|
291
|
+
{
|
|
292
|
+
FullName: '{~D:Record.name~}',
|
|
293
|
+
Location: '{~D:Record.city~}'
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
(pError, pStatusCode, pBody) =>
|
|
297
|
+
{
|
|
298
|
+
Expect(pError).to.not.exist;
|
|
299
|
+
Expect(pStatusCode).to.equal(200);
|
|
300
|
+
Expect(pBody.Person['Person_1'].FullName).to.equal('Alice');
|
|
301
|
+
Expect(pBody.Person['Person_1'].Location).to.equal('Seattle');
|
|
302
|
+
return fDone();
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
test('Should return extended state when Extended flag is set',
|
|
307
|
+
(fDone) =>
|
|
308
|
+
{
|
|
309
|
+
makeRequest('POST', '/1.0/CSV/Transform',
|
|
310
|
+
{
|
|
311
|
+
File: libPath.join(TEST_DATA_DIR, 'test-small.csv'),
|
|
312
|
+
Entity: 'Person',
|
|
313
|
+
Extended: true
|
|
314
|
+
},
|
|
315
|
+
(pError, pStatusCode, pBody) =>
|
|
316
|
+
{
|
|
317
|
+
Expect(pError).to.not.exist;
|
|
318
|
+
Expect(pStatusCode).to.equal(200);
|
|
319
|
+
Expect(pBody).to.have.property('Comprehension');
|
|
320
|
+
Expect(pBody).to.have.property('ParsedRowCount');
|
|
321
|
+
Expect(pBody).to.have.property('Configuration');
|
|
322
|
+
Expect(pBody.ParsedRowCount).to.be.greaterThan(0);
|
|
323
|
+
return fDone();
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
test('Should return 400 when no File is provided',
|
|
328
|
+
(fDone) =>
|
|
329
|
+
{
|
|
330
|
+
makeRequest('POST', '/1.0/CSV/Transform', {},
|
|
331
|
+
(pError, pStatusCode, pBody) =>
|
|
332
|
+
{
|
|
333
|
+
Expect(pError).to.not.exist;
|
|
334
|
+
Expect(pStatusCode).to.equal(400);
|
|
335
|
+
return fDone();
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
test('Should return 404 when file does not exist',
|
|
340
|
+
(fDone) =>
|
|
341
|
+
{
|
|
342
|
+
makeRequest('POST', '/1.0/CSV/Transform',
|
|
343
|
+
{ File: '/tmp/nonexistent-test-file-99999.csv' },
|
|
344
|
+
(pError, pStatusCode, pBody) =>
|
|
345
|
+
{
|
|
346
|
+
Expect(pError).to.not.exist;
|
|
347
|
+
Expect(pStatusCode).to.equal(404);
|
|
348
|
+
return fDone();
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
// ===== TSV Check Endpoint =====
|
|
355
|
+
suite
|
|
356
|
+
(
|
|
357
|
+
'POST /1.0/TSV/Check',
|
|
358
|
+
() =>
|
|
359
|
+
{
|
|
360
|
+
test('Should analyze a TSV file and return statistics',
|
|
361
|
+
(fDone) =>
|
|
362
|
+
{
|
|
363
|
+
makeRequest('POST', '/1.0/TSV/Check',
|
|
364
|
+
{ File: libPath.join(TEST_DATA_DIR, 'test-small.tsv') },
|
|
365
|
+
(pError, pStatusCode, pBody) =>
|
|
366
|
+
{
|
|
367
|
+
Expect(pError).to.not.exist;
|
|
368
|
+
Expect(pStatusCode).to.equal(200);
|
|
369
|
+
Expect(pBody.RowCount).to.equal(5);
|
|
370
|
+
Expect(pBody.ColumnCount).to.equal(5);
|
|
371
|
+
Expect(pBody.Headers).to.include('id');
|
|
372
|
+
Expect(pBody.Headers).to.include('name');
|
|
373
|
+
return fDone();
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
test('Should return 400 when no File is provided',
|
|
378
|
+
(fDone) =>
|
|
379
|
+
{
|
|
380
|
+
makeRequest('POST', '/1.0/TSV/Check', {},
|
|
381
|
+
(pError, pStatusCode, pBody) =>
|
|
382
|
+
{
|
|
383
|
+
Expect(pError).to.not.exist;
|
|
384
|
+
Expect(pStatusCode).to.equal(400);
|
|
385
|
+
return fDone();
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
test('Should return 404 for nonexistent file',
|
|
390
|
+
(fDone) =>
|
|
391
|
+
{
|
|
392
|
+
makeRequest('POST', '/1.0/TSV/Check',
|
|
393
|
+
{ File: '/tmp/nonexistent-test-file-99999.tsv' },
|
|
394
|
+
(pError, pStatusCode, pBody) =>
|
|
395
|
+
{
|
|
396
|
+
Expect(pError).to.not.exist;
|
|
397
|
+
Expect(pStatusCode).to.equal(404);
|
|
398
|
+
return fDone();
|
|
399
|
+
});
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
);
|
|
403
|
+
|
|
404
|
+
// ===== TSV Transform Endpoint =====
|
|
405
|
+
suite
|
|
406
|
+
(
|
|
407
|
+
'POST /1.0/TSV/Transform',
|
|
408
|
+
() =>
|
|
409
|
+
{
|
|
410
|
+
test('Should transform a TSV file into a comprehension',
|
|
411
|
+
(fDone) =>
|
|
412
|
+
{
|
|
413
|
+
makeRequest('POST', '/1.0/TSV/Transform',
|
|
414
|
+
{
|
|
415
|
+
File: libPath.join(TEST_DATA_DIR, 'test-small.tsv'),
|
|
416
|
+
Entity: 'Person',
|
|
417
|
+
GUIDName: 'GUIDPerson',
|
|
418
|
+
GUIDTemplate: 'Person_{~D:Record.id~}'
|
|
419
|
+
},
|
|
420
|
+
(pError, pStatusCode, pBody) =>
|
|
421
|
+
{
|
|
422
|
+
Expect(pError).to.not.exist;
|
|
423
|
+
Expect(pStatusCode).to.equal(200);
|
|
424
|
+
Expect(pBody).to.have.property('Person');
|
|
425
|
+
Expect(pBody.Person).to.have.property('Person_1');
|
|
426
|
+
Expect(pBody.Person['Person_1'].name).to.equal('Alice');
|
|
427
|
+
Expect(Object.keys(pBody.Person).length).to.equal(5);
|
|
428
|
+
return fDone();
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
test('Should return 400 when no File is provided',
|
|
433
|
+
(fDone) =>
|
|
434
|
+
{
|
|
435
|
+
makeRequest('POST', '/1.0/TSV/Transform', {},
|
|
436
|
+
(pError, pStatusCode, pBody) =>
|
|
437
|
+
{
|
|
438
|
+
Expect(pError).to.not.exist;
|
|
439
|
+
Expect(pStatusCode).to.equal(400);
|
|
440
|
+
return fDone();
|
|
441
|
+
});
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
);
|
|
445
|
+
|
|
446
|
+
// ===== JSON Array Transform Endpoint =====
|
|
447
|
+
suite
|
|
448
|
+
(
|
|
449
|
+
'POST /1.0/JSONArray/Transform',
|
|
450
|
+
() =>
|
|
451
|
+
{
|
|
452
|
+
test('Should transform a JSON array file into a comprehension',
|
|
453
|
+
(fDone) =>
|
|
454
|
+
{
|
|
455
|
+
makeRequest('POST', '/1.0/JSONArray/Transform',
|
|
456
|
+
{
|
|
457
|
+
File: libPath.join(TEST_DATA_DIR, 'test-small.json'),
|
|
458
|
+
Entity: 'Person',
|
|
459
|
+
GUIDName: 'GUIDPerson',
|
|
460
|
+
GUIDTemplate: 'Person_{~D:Record.id~}'
|
|
461
|
+
},
|
|
462
|
+
(pError, pStatusCode, pBody) =>
|
|
463
|
+
{
|
|
464
|
+
Expect(pError).to.not.exist;
|
|
465
|
+
Expect(pStatusCode).to.equal(200);
|
|
466
|
+
Expect(pBody).to.have.property('Person');
|
|
467
|
+
Expect(pBody.Person).to.have.property('Person_1');
|
|
468
|
+
Expect(pBody.Person['Person_1'].name).to.equal('Alice');
|
|
469
|
+
Expect(Object.keys(pBody.Person).length).to.equal(5);
|
|
470
|
+
return fDone();
|
|
471
|
+
});
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
test('Should return 400 when no File is provided',
|
|
475
|
+
(fDone) =>
|
|
476
|
+
{
|
|
477
|
+
makeRequest('POST', '/1.0/JSONArray/Transform', {},
|
|
478
|
+
(pError, pStatusCode, pBody) =>
|
|
479
|
+
{
|
|
480
|
+
Expect(pError).to.not.exist;
|
|
481
|
+
Expect(pStatusCode).to.equal(400);
|
|
482
|
+
return fDone();
|
|
483
|
+
});
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
test('Should return 404 for nonexistent file',
|
|
487
|
+
(fDone) =>
|
|
488
|
+
{
|
|
489
|
+
makeRequest('POST', '/1.0/JSONArray/Transform',
|
|
490
|
+
{ File: '/tmp/nonexistent-test-file-99999.json' },
|
|
491
|
+
(pError, pStatusCode, pBody) =>
|
|
492
|
+
{
|
|
493
|
+
Expect(pError).to.not.exist;
|
|
494
|
+
Expect(pStatusCode).to.equal(404);
|
|
495
|
+
return fDone();
|
|
496
|
+
});
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
);
|
|
500
|
+
|
|
501
|
+
// ===== JSON Array Transform Records Endpoint =====
|
|
502
|
+
suite
|
|
503
|
+
(
|
|
504
|
+
'POST /1.0/JSONArray/TransformRecords',
|
|
505
|
+
() =>
|
|
506
|
+
{
|
|
507
|
+
test('Should transform in-memory records into a comprehension',
|
|
508
|
+
(fDone) =>
|
|
509
|
+
{
|
|
510
|
+
makeRequest('POST', '/1.0/JSONArray/TransformRecords',
|
|
511
|
+
{
|
|
512
|
+
Records:
|
|
513
|
+
[
|
|
514
|
+
{ id: '1', name: 'Alice', city: 'Seattle' },
|
|
515
|
+
{ id: '2', name: 'Bob', city: 'Portland' },
|
|
516
|
+
{ id: '3', name: 'Carol', city: 'Vancouver' }
|
|
517
|
+
],
|
|
518
|
+
Entity: 'Person',
|
|
519
|
+
GUIDName: 'GUIDPerson',
|
|
520
|
+
GUIDTemplate: 'Person_{~D:Record.id~}',
|
|
521
|
+
Mappings:
|
|
522
|
+
{
|
|
523
|
+
FullName: '{~D:Record.name~}',
|
|
524
|
+
Location: '{~D:Record.city~}'
|
|
525
|
+
}
|
|
526
|
+
},
|
|
527
|
+
(pError, pStatusCode, pBody) =>
|
|
528
|
+
{
|
|
529
|
+
Expect(pError).to.not.exist;
|
|
530
|
+
Expect(pStatusCode).to.equal(200);
|
|
531
|
+
Expect(pBody).to.have.property('Person');
|
|
532
|
+
Expect(pBody.Person).to.have.property('Person_1');
|
|
533
|
+
Expect(pBody.Person['Person_1'].FullName).to.equal('Alice');
|
|
534
|
+
Expect(pBody.Person['Person_1'].Location).to.equal('Seattle');
|
|
535
|
+
Expect(Object.keys(pBody.Person).length).to.equal(3);
|
|
536
|
+
return fDone();
|
|
537
|
+
});
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
test('Should return 400 when no Records array is provided',
|
|
541
|
+
(fDone) =>
|
|
542
|
+
{
|
|
543
|
+
makeRequest('POST', '/1.0/JSONArray/TransformRecords', {},
|
|
544
|
+
(pError, pStatusCode, pBody) =>
|
|
545
|
+
{
|
|
546
|
+
Expect(pError).to.not.exist;
|
|
547
|
+
Expect(pStatusCode).to.equal(400);
|
|
548
|
+
Expect(pBody.Error).to.contain('Records');
|
|
549
|
+
return fDone();
|
|
550
|
+
});
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
test('Should return 400 when Records array is empty',
|
|
554
|
+
(fDone) =>
|
|
555
|
+
{
|
|
556
|
+
makeRequest('POST', '/1.0/JSONArray/TransformRecords',
|
|
557
|
+
{ Records: [] },
|
|
558
|
+
(pError, pStatusCode, pBody) =>
|
|
559
|
+
{
|
|
560
|
+
Expect(pError).to.not.exist;
|
|
561
|
+
Expect(pStatusCode).to.equal(400);
|
|
562
|
+
Expect(pBody.Error).to.contain('empty');
|
|
563
|
+
return fDone();
|
|
564
|
+
});
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
test('Should return extended state when requested',
|
|
568
|
+
(fDone) =>
|
|
569
|
+
{
|
|
570
|
+
makeRequest('POST', '/1.0/JSONArray/TransformRecords',
|
|
571
|
+
{
|
|
572
|
+
Records:
|
|
573
|
+
[
|
|
574
|
+
{ id: '1', name: 'Alice' }
|
|
575
|
+
],
|
|
576
|
+
Entity: 'Person',
|
|
577
|
+
Extended: true
|
|
578
|
+
},
|
|
579
|
+
(pError, pStatusCode, pBody) =>
|
|
580
|
+
{
|
|
581
|
+
Expect(pError).to.not.exist;
|
|
582
|
+
Expect(pStatusCode).to.equal(200);
|
|
583
|
+
Expect(pBody).to.have.property('Comprehension');
|
|
584
|
+
Expect(pBody).to.have.property('ParsedRowCount');
|
|
585
|
+
Expect(pBody.ParsedRowCount).to.equal(1);
|
|
586
|
+
return fDone();
|
|
587
|
+
});
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
);
|
|
591
|
+
|
|
592
|
+
// ===== Comprehension Intersect Endpoint =====
|
|
593
|
+
suite
|
|
594
|
+
(
|
|
595
|
+
'POST /1.0/Comprehension/Intersect',
|
|
596
|
+
() =>
|
|
597
|
+
{
|
|
598
|
+
test('Should merge two in-memory comprehensions',
|
|
599
|
+
(fDone) =>
|
|
600
|
+
{
|
|
601
|
+
makeRequest('POST', '/1.0/Comprehension/Intersect',
|
|
602
|
+
{
|
|
603
|
+
PrimaryComprehension:
|
|
604
|
+
{
|
|
605
|
+
Person:
|
|
606
|
+
{
|
|
607
|
+
Person_1: { GUIDPerson: 'Person_1', Name: 'Alice', City: 'Seattle' },
|
|
608
|
+
Person_2: { GUIDPerson: 'Person_2', Name: 'Bob', City: 'Portland' }
|
|
609
|
+
}
|
|
610
|
+
},
|
|
611
|
+
SecondaryComprehension:
|
|
612
|
+
{
|
|
613
|
+
Person:
|
|
614
|
+
{
|
|
615
|
+
Person_1: { GUIDPerson: 'Person_1', Score: '95' },
|
|
616
|
+
Person_3: { GUIDPerson: 'Person_3', Name: 'Carol', City: 'Vancouver' }
|
|
617
|
+
}
|
|
618
|
+
},
|
|
619
|
+
Entity: 'Person'
|
|
620
|
+
},
|
|
621
|
+
(pError, pStatusCode, pBody) =>
|
|
622
|
+
{
|
|
623
|
+
Expect(pError).to.not.exist;
|
|
624
|
+
Expect(pStatusCode).to.equal(200);
|
|
625
|
+
Expect(pBody.Person).to.have.property('Person_1');
|
|
626
|
+
Expect(pBody.Person).to.have.property('Person_2');
|
|
627
|
+
Expect(pBody.Person).to.have.property('Person_3');
|
|
628
|
+
// Merged record should have both original and secondary properties
|
|
629
|
+
Expect(pBody.Person['Person_1'].Name).to.equal('Alice');
|
|
630
|
+
Expect(pBody.Person['Person_1'].Score).to.equal('95');
|
|
631
|
+
Expect(Object.keys(pBody.Person).length).to.equal(3);
|
|
632
|
+
return fDone();
|
|
633
|
+
});
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
test('Should auto-detect entity when not specified',
|
|
637
|
+
(fDone) =>
|
|
638
|
+
{
|
|
639
|
+
makeRequest('POST', '/1.0/Comprehension/Intersect',
|
|
640
|
+
{
|
|
641
|
+
PrimaryComprehension:
|
|
642
|
+
{
|
|
643
|
+
Book:
|
|
644
|
+
{
|
|
645
|
+
Book_1: { GUIDBook: 'Book_1', Title: 'Test Book' }
|
|
646
|
+
}
|
|
647
|
+
},
|
|
648
|
+
SecondaryComprehension:
|
|
649
|
+
{
|
|
650
|
+
Book:
|
|
651
|
+
{
|
|
652
|
+
Book_1: { GUIDBook: 'Book_1', Rating: '4.5' }
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
},
|
|
656
|
+
(pError, pStatusCode, pBody) =>
|
|
657
|
+
{
|
|
658
|
+
Expect(pError).to.not.exist;
|
|
659
|
+
Expect(pStatusCode).to.equal(200);
|
|
660
|
+
Expect(pBody.Book['Book_1'].Title).to.equal('Test Book');
|
|
661
|
+
Expect(pBody.Book['Book_1'].Rating).to.equal('4.5');
|
|
662
|
+
return fDone();
|
|
663
|
+
});
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
test('Should return 400 when PrimaryComprehension is missing',
|
|
667
|
+
(fDone) =>
|
|
668
|
+
{
|
|
669
|
+
makeRequest('POST', '/1.0/Comprehension/Intersect',
|
|
670
|
+
{ SecondaryComprehension: { Book: {} } },
|
|
671
|
+
(pError, pStatusCode, pBody) =>
|
|
672
|
+
{
|
|
673
|
+
Expect(pError).to.not.exist;
|
|
674
|
+
Expect(pStatusCode).to.equal(400);
|
|
675
|
+
Expect(pBody.Error).to.contain('PrimaryComprehension');
|
|
676
|
+
return fDone();
|
|
677
|
+
});
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
test('Should return 400 when SecondaryComprehension is missing',
|
|
681
|
+
(fDone) =>
|
|
682
|
+
{
|
|
683
|
+
makeRequest('POST', '/1.0/Comprehension/Intersect',
|
|
684
|
+
{ PrimaryComprehension: { Book: {} } },
|
|
685
|
+
(pError, pStatusCode, pBody) =>
|
|
686
|
+
{
|
|
687
|
+
Expect(pError).to.not.exist;
|
|
688
|
+
Expect(pStatusCode).to.equal(400);
|
|
689
|
+
Expect(pBody.Error).to.contain('SecondaryComprehension');
|
|
690
|
+
return fDone();
|
|
691
|
+
});
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
);
|
|
695
|
+
|
|
696
|
+
// ===== Comprehension Intersect Files Endpoint =====
|
|
697
|
+
suite
|
|
698
|
+
(
|
|
699
|
+
'POST /1.0/Comprehension/IntersectFiles',
|
|
700
|
+
() =>
|
|
701
|
+
{
|
|
702
|
+
test('Should merge two comprehension files',
|
|
703
|
+
(fDone) =>
|
|
704
|
+
{
|
|
705
|
+
makeRequest('POST', '/1.0/Comprehension/IntersectFiles',
|
|
706
|
+
{
|
|
707
|
+
File: libPath.join(TEST_DATA_DIR, 'test-comprehension.json'),
|
|
708
|
+
IntersectFile: libPath.join(TEST_DATA_DIR, 'test-comprehension-secondary.json'),
|
|
709
|
+
Entity: 'Person'
|
|
710
|
+
},
|
|
711
|
+
(pError, pStatusCode, pBody) =>
|
|
712
|
+
{
|
|
713
|
+
Expect(pError).to.not.exist;
|
|
714
|
+
Expect(pStatusCode).to.equal(200);
|
|
715
|
+
Expect(pBody.Person).to.have.property('Person_1');
|
|
716
|
+
Expect(pBody.Person).to.have.property('Person_2');
|
|
717
|
+
Expect(pBody.Person).to.have.property('Person_3');
|
|
718
|
+
Expect(pBody.Person).to.have.property('Person_4');
|
|
719
|
+
// Merged fields
|
|
720
|
+
Expect(pBody.Person['Person_1'].Name).to.equal('Alice');
|
|
721
|
+
Expect(pBody.Person['Person_1'].Score).to.equal('95');
|
|
722
|
+
return fDone();
|
|
723
|
+
});
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
test('Should return 400 when File is missing',
|
|
727
|
+
(fDone) =>
|
|
728
|
+
{
|
|
729
|
+
makeRequest('POST', '/1.0/Comprehension/IntersectFiles',
|
|
730
|
+
{ IntersectFile: libPath.join(TEST_DATA_DIR, 'test-comprehension-secondary.json') },
|
|
731
|
+
(pError, pStatusCode, pBody) =>
|
|
732
|
+
{
|
|
733
|
+
Expect(pError).to.not.exist;
|
|
734
|
+
Expect(pStatusCode).to.equal(400);
|
|
735
|
+
return fDone();
|
|
736
|
+
});
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
test('Should return 400 when IntersectFile is missing',
|
|
740
|
+
(fDone) =>
|
|
741
|
+
{
|
|
742
|
+
makeRequest('POST', '/1.0/Comprehension/IntersectFiles',
|
|
743
|
+
{ File: libPath.join(TEST_DATA_DIR, 'test-comprehension.json') },
|
|
744
|
+
(pError, pStatusCode, pBody) =>
|
|
745
|
+
{
|
|
746
|
+
Expect(pError).to.not.exist;
|
|
747
|
+
Expect(pStatusCode).to.equal(400);
|
|
748
|
+
return fDone();
|
|
749
|
+
});
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
test('Should return 404 for nonexistent primary file',
|
|
753
|
+
(fDone) =>
|
|
754
|
+
{
|
|
755
|
+
makeRequest('POST', '/1.0/Comprehension/IntersectFiles',
|
|
756
|
+
{
|
|
757
|
+
File: '/tmp/nonexistent-primary-99999.json',
|
|
758
|
+
IntersectFile: libPath.join(TEST_DATA_DIR, 'test-comprehension-secondary.json')
|
|
759
|
+
},
|
|
760
|
+
(pError, pStatusCode, pBody) =>
|
|
761
|
+
{
|
|
762
|
+
Expect(pError).to.not.exist;
|
|
763
|
+
Expect(pStatusCode).to.equal(404);
|
|
764
|
+
return fDone();
|
|
765
|
+
});
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
);
|
|
769
|
+
|
|
770
|
+
// ===== Comprehension ToArray Endpoint =====
|
|
771
|
+
suite
|
|
772
|
+
(
|
|
773
|
+
'POST /1.0/Comprehension/ToArray',
|
|
774
|
+
() =>
|
|
775
|
+
{
|
|
776
|
+
test('Should convert comprehension to an array',
|
|
777
|
+
(fDone) =>
|
|
778
|
+
{
|
|
779
|
+
makeRequest('POST', '/1.0/Comprehension/ToArray',
|
|
780
|
+
{
|
|
781
|
+
Comprehension:
|
|
782
|
+
{
|
|
783
|
+
Person:
|
|
784
|
+
{
|
|
785
|
+
Person_1: { GUIDPerson: 'Person_1', Name: 'Alice' },
|
|
786
|
+
Person_2: { GUIDPerson: 'Person_2', Name: 'Bob' },
|
|
787
|
+
Person_3: { GUIDPerson: 'Person_3', Name: 'Carol' }
|
|
788
|
+
}
|
|
789
|
+
},
|
|
790
|
+
Entity: 'Person'
|
|
791
|
+
},
|
|
792
|
+
(pError, pStatusCode, pBody) =>
|
|
793
|
+
{
|
|
794
|
+
Expect(pError).to.not.exist;
|
|
795
|
+
Expect(pStatusCode).to.equal(200);
|
|
796
|
+
Expect(pBody).to.be.an('array');
|
|
797
|
+
Expect(pBody.length).to.equal(3);
|
|
798
|
+
let tmpNames = pBody.map((r) => r.Name);
|
|
799
|
+
Expect(tmpNames).to.include('Alice');
|
|
800
|
+
Expect(tmpNames).to.include('Bob');
|
|
801
|
+
Expect(tmpNames).to.include('Carol');
|
|
802
|
+
return fDone();
|
|
803
|
+
});
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
test('Should auto-detect entity when not specified',
|
|
807
|
+
(fDone) =>
|
|
808
|
+
{
|
|
809
|
+
makeRequest('POST', '/1.0/Comprehension/ToArray',
|
|
810
|
+
{
|
|
811
|
+
Comprehension:
|
|
812
|
+
{
|
|
813
|
+
Book:
|
|
814
|
+
{
|
|
815
|
+
Book_1: { Title: 'Test Book' }
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
},
|
|
819
|
+
(pError, pStatusCode, pBody) =>
|
|
820
|
+
{
|
|
821
|
+
Expect(pError).to.not.exist;
|
|
822
|
+
Expect(pStatusCode).to.equal(200);
|
|
823
|
+
Expect(pBody).to.be.an('array');
|
|
824
|
+
Expect(pBody.length).to.equal(1);
|
|
825
|
+
Expect(pBody[0].Title).to.equal('Test Book');
|
|
826
|
+
return fDone();
|
|
827
|
+
});
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
test('Should return 400 when no Comprehension is provided',
|
|
831
|
+
(fDone) =>
|
|
832
|
+
{
|
|
833
|
+
makeRequest('POST', '/1.0/Comprehension/ToArray', {},
|
|
834
|
+
(pError, pStatusCode, pBody) =>
|
|
835
|
+
{
|
|
836
|
+
Expect(pError).to.not.exist;
|
|
837
|
+
Expect(pStatusCode).to.equal(400);
|
|
838
|
+
return fDone();
|
|
839
|
+
});
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
);
|
|
843
|
+
|
|
844
|
+
// ===== Comprehension ToArrayFromFile Endpoint =====
|
|
845
|
+
suite
|
|
846
|
+
(
|
|
847
|
+
'POST /1.0/Comprehension/ToArrayFromFile',
|
|
848
|
+
() =>
|
|
849
|
+
{
|
|
850
|
+
test('Should convert comprehension file to an array',
|
|
851
|
+
(fDone) =>
|
|
852
|
+
{
|
|
853
|
+
makeRequest('POST', '/1.0/Comprehension/ToArrayFromFile',
|
|
854
|
+
{
|
|
855
|
+
File: libPath.join(TEST_DATA_DIR, 'test-comprehension.json'),
|
|
856
|
+
Entity: 'Person'
|
|
857
|
+
},
|
|
858
|
+
(pError, pStatusCode, pBody) =>
|
|
859
|
+
{
|
|
860
|
+
Expect(pError).to.not.exist;
|
|
861
|
+
Expect(pStatusCode).to.equal(200);
|
|
862
|
+
Expect(pBody).to.be.an('array');
|
|
863
|
+
Expect(pBody.length).to.equal(3);
|
|
864
|
+
return fDone();
|
|
865
|
+
});
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
test('Should return 400 when no File is provided',
|
|
869
|
+
(fDone) =>
|
|
870
|
+
{
|
|
871
|
+
makeRequest('POST', '/1.0/Comprehension/ToArrayFromFile', {},
|
|
872
|
+
(pError, pStatusCode, pBody) =>
|
|
873
|
+
{
|
|
874
|
+
Expect(pError).to.not.exist;
|
|
875
|
+
Expect(pStatusCode).to.equal(400);
|
|
876
|
+
return fDone();
|
|
877
|
+
});
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
test('Should return 404 for nonexistent file',
|
|
881
|
+
(fDone) =>
|
|
882
|
+
{
|
|
883
|
+
makeRequest('POST', '/1.0/Comprehension/ToArrayFromFile',
|
|
884
|
+
{ File: '/tmp/nonexistent-test-99999.json' },
|
|
885
|
+
(pError, pStatusCode, pBody) =>
|
|
886
|
+
{
|
|
887
|
+
Expect(pError).to.not.exist;
|
|
888
|
+
Expect(pStatusCode).to.equal(404);
|
|
889
|
+
return fDone();
|
|
890
|
+
});
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
);
|
|
894
|
+
|
|
895
|
+
// ===== Comprehension ToCSV Endpoint =====
|
|
896
|
+
suite
|
|
897
|
+
(
|
|
898
|
+
'POST /1.0/Comprehension/ToCSV',
|
|
899
|
+
() =>
|
|
900
|
+
{
|
|
901
|
+
test('Should convert a Records array to CSV',
|
|
902
|
+
(fDone) =>
|
|
903
|
+
{
|
|
904
|
+
makeRequest('POST', '/1.0/Comprehension/ToCSV',
|
|
905
|
+
{
|
|
906
|
+
Records:
|
|
907
|
+
[
|
|
908
|
+
{ Name: 'Alice', City: 'Seattle', Score: 95 },
|
|
909
|
+
{ Name: 'Bob', City: 'Portland', Score: 87 }
|
|
910
|
+
]
|
|
911
|
+
},
|
|
912
|
+
(pError, pStatusCode, pBody, pHeaders) =>
|
|
913
|
+
{
|
|
914
|
+
Expect(pError).to.not.exist;
|
|
915
|
+
Expect(pStatusCode).to.equal(200);
|
|
916
|
+
// Body should be raw CSV text
|
|
917
|
+
Expect(pBody).to.be.a('string');
|
|
918
|
+
Expect(pBody).to.contain('City');
|
|
919
|
+
Expect(pBody).to.contain('Name');
|
|
920
|
+
Expect(pBody).to.contain('Alice');
|
|
921
|
+
Expect(pBody).to.contain('Bob');
|
|
922
|
+
return fDone();
|
|
923
|
+
});
|
|
924
|
+
});
|
|
925
|
+
|
|
926
|
+
test('Should convert a Comprehension object to CSV',
|
|
927
|
+
(fDone) =>
|
|
928
|
+
{
|
|
929
|
+
makeRequest('POST', '/1.0/Comprehension/ToCSV',
|
|
930
|
+
{
|
|
931
|
+
Comprehension:
|
|
932
|
+
{
|
|
933
|
+
Person:
|
|
934
|
+
{
|
|
935
|
+
Person_1: { Name: 'Alice', City: 'Seattle' },
|
|
936
|
+
Person_2: { Name: 'Bob', City: 'Portland' }
|
|
937
|
+
}
|
|
938
|
+
},
|
|
939
|
+
Entity: 'Person'
|
|
940
|
+
},
|
|
941
|
+
(pError, pStatusCode, pBody) =>
|
|
942
|
+
{
|
|
943
|
+
Expect(pError).to.not.exist;
|
|
944
|
+
Expect(pStatusCode).to.equal(200);
|
|
945
|
+
Expect(pBody).to.be.a('string');
|
|
946
|
+
Expect(pBody).to.contain('Alice');
|
|
947
|
+
Expect(pBody).to.contain('Seattle');
|
|
948
|
+
return fDone();
|
|
949
|
+
});
|
|
950
|
+
});
|
|
951
|
+
|
|
952
|
+
test('Should return 400 when no data is provided',
|
|
953
|
+
(fDone) =>
|
|
954
|
+
{
|
|
955
|
+
makeRequest('POST', '/1.0/Comprehension/ToCSV', {},
|
|
956
|
+
(pError, pStatusCode, pBody) =>
|
|
957
|
+
{
|
|
958
|
+
Expect(pError).to.not.exist;
|
|
959
|
+
Expect(pStatusCode).to.equal(400);
|
|
960
|
+
return fDone();
|
|
961
|
+
});
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
);
|
|
965
|
+
|
|
966
|
+
// ===== Comprehension ToCSVFromFile Endpoint =====
|
|
967
|
+
suite
|
|
968
|
+
(
|
|
969
|
+
'POST /1.0/Comprehension/ToCSVFromFile',
|
|
970
|
+
() =>
|
|
971
|
+
{
|
|
972
|
+
test('Should convert a comprehension file to CSV',
|
|
973
|
+
(fDone) =>
|
|
974
|
+
{
|
|
975
|
+
makeRequest('POST', '/1.0/Comprehension/ToCSVFromFile',
|
|
976
|
+
{
|
|
977
|
+
File: libPath.join(TEST_DATA_DIR, 'test-comprehension.json'),
|
|
978
|
+
Entity: 'Person'
|
|
979
|
+
},
|
|
980
|
+
(pError, pStatusCode, pBody) =>
|
|
981
|
+
{
|
|
982
|
+
Expect(pError).to.not.exist;
|
|
983
|
+
Expect(pStatusCode).to.equal(200);
|
|
984
|
+
Expect(pBody).to.be.a('string');
|
|
985
|
+
Expect(pBody).to.contain('Alice');
|
|
986
|
+
Expect(pBody).to.contain('GUIDPerson');
|
|
987
|
+
return fDone();
|
|
988
|
+
});
|
|
989
|
+
});
|
|
990
|
+
|
|
991
|
+
test('Should convert a JSON array file to CSV',
|
|
992
|
+
(fDone) =>
|
|
993
|
+
{
|
|
994
|
+
makeRequest('POST', '/1.0/Comprehension/ToCSVFromFile',
|
|
995
|
+
{
|
|
996
|
+
File: libPath.join(TEST_DATA_DIR, 'test-small.json')
|
|
997
|
+
},
|
|
998
|
+
(pError, pStatusCode, pBody) =>
|
|
999
|
+
{
|
|
1000
|
+
Expect(pError).to.not.exist;
|
|
1001
|
+
Expect(pStatusCode).to.equal(200);
|
|
1002
|
+
Expect(pBody).to.be.a('string');
|
|
1003
|
+
Expect(pBody).to.contain('Alice');
|
|
1004
|
+
Expect(pBody).to.contain('Seattle');
|
|
1005
|
+
return fDone();
|
|
1006
|
+
});
|
|
1007
|
+
});
|
|
1008
|
+
|
|
1009
|
+
test('Should return 400 when no File is provided',
|
|
1010
|
+
(fDone) =>
|
|
1011
|
+
{
|
|
1012
|
+
makeRequest('POST', '/1.0/Comprehension/ToCSVFromFile', {},
|
|
1013
|
+
(pError, pStatusCode, pBody) =>
|
|
1014
|
+
{
|
|
1015
|
+
Expect(pError).to.not.exist;
|
|
1016
|
+
Expect(pStatusCode).to.equal(400);
|
|
1017
|
+
return fDone();
|
|
1018
|
+
});
|
|
1019
|
+
});
|
|
1020
|
+
|
|
1021
|
+
test('Should return 404 for nonexistent file',
|
|
1022
|
+
(fDone) =>
|
|
1023
|
+
{
|
|
1024
|
+
makeRequest('POST', '/1.0/Comprehension/ToCSVFromFile',
|
|
1025
|
+
{ File: '/tmp/nonexistent-test-99999.json' },
|
|
1026
|
+
(pError, pStatusCode, pBody) =>
|
|
1027
|
+
{
|
|
1028
|
+
Expect(pError).to.not.exist;
|
|
1029
|
+
Expect(pStatusCode).to.equal(404);
|
|
1030
|
+
return fDone();
|
|
1031
|
+
});
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
);
|
|
1035
|
+
|
|
1036
|
+
// ===== Comprehension Push Endpoint =====
|
|
1037
|
+
suite
|
|
1038
|
+
(
|
|
1039
|
+
'POST /1.0/Comprehension/Push',
|
|
1040
|
+
() =>
|
|
1041
|
+
{
|
|
1042
|
+
test('Should return 400 when no Comprehension is provided',
|
|
1043
|
+
(fDone) =>
|
|
1044
|
+
{
|
|
1045
|
+
makeRequest('POST', '/1.0/Comprehension/Push', {},
|
|
1046
|
+
(pError, pStatusCode, pBody) =>
|
|
1047
|
+
{
|
|
1048
|
+
Expect(pError).to.not.exist;
|
|
1049
|
+
Expect(pStatusCode).to.equal(400);
|
|
1050
|
+
Expect(pBody.Error).to.contain('Comprehension');
|
|
1051
|
+
return fDone();
|
|
1052
|
+
});
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
);
|
|
1056
|
+
|
|
1057
|
+
// ===== Comprehension PushFile Endpoint =====
|
|
1058
|
+
suite
|
|
1059
|
+
(
|
|
1060
|
+
'POST /1.0/Comprehension/PushFile',
|
|
1061
|
+
() =>
|
|
1062
|
+
{
|
|
1063
|
+
test('Should return 400 when no File is provided',
|
|
1064
|
+
(fDone) =>
|
|
1065
|
+
{
|
|
1066
|
+
makeRequest('POST', '/1.0/Comprehension/PushFile', {},
|
|
1067
|
+
(pError, pStatusCode, pBody) =>
|
|
1068
|
+
{
|
|
1069
|
+
Expect(pError).to.not.exist;
|
|
1070
|
+
Expect(pStatusCode).to.equal(400);
|
|
1071
|
+
return fDone();
|
|
1072
|
+
});
|
|
1073
|
+
});
|
|
1074
|
+
|
|
1075
|
+
test('Should return 404 for nonexistent file',
|
|
1076
|
+
(fDone) =>
|
|
1077
|
+
{
|
|
1078
|
+
makeRequest('POST', '/1.0/Comprehension/PushFile',
|
|
1079
|
+
{ File: '/tmp/nonexistent-test-99999.json' },
|
|
1080
|
+
(pError, pStatusCode, pBody) =>
|
|
1081
|
+
{
|
|
1082
|
+
Expect(pError).to.not.exist;
|
|
1083
|
+
Expect(pStatusCode).to.equal(404);
|
|
1084
|
+
return fDone();
|
|
1085
|
+
});
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
);
|
|
1089
|
+
|
|
1090
|
+
// ===== Entity FromTabularFolder Endpoint =====
|
|
1091
|
+
suite
|
|
1092
|
+
(
|
|
1093
|
+
'POST /1.0/Entity/FromTabularFolder',
|
|
1094
|
+
() =>
|
|
1095
|
+
{
|
|
1096
|
+
test('Should generate comprehensions from a folder of CSV files',
|
|
1097
|
+
(fDone) =>
|
|
1098
|
+
{
|
|
1099
|
+
makeRequest('POST', '/1.0/Entity/FromTabularFolder',
|
|
1100
|
+
{
|
|
1101
|
+
Folder: libPath.join(EXAMPLE_DATA_DIR, 'seattle_neighborhoods')
|
|
1102
|
+
},
|
|
1103
|
+
(pError, pStatusCode, pBody) =>
|
|
1104
|
+
{
|
|
1105
|
+
Expect(pError).to.not.exist;
|
|
1106
|
+
Expect(pStatusCode).to.equal(200);
|
|
1107
|
+
Expect(pBody).to.be.an('object');
|
|
1108
|
+
// Should have entities derived from the 3 CSV files
|
|
1109
|
+
let tmpEntityKeys = Object.keys(pBody);
|
|
1110
|
+
Expect(tmpEntityKeys.length).to.be.greaterThan(0);
|
|
1111
|
+
return fDone();
|
|
1112
|
+
});
|
|
1113
|
+
});
|
|
1114
|
+
|
|
1115
|
+
test('Should return 400 when no Folder is provided',
|
|
1116
|
+
(fDone) =>
|
|
1117
|
+
{
|
|
1118
|
+
makeRequest('POST', '/1.0/Entity/FromTabularFolder', {},
|
|
1119
|
+
(pError, pStatusCode, pBody) =>
|
|
1120
|
+
{
|
|
1121
|
+
Expect(pError).to.not.exist;
|
|
1122
|
+
Expect(pStatusCode).to.equal(400);
|
|
1123
|
+
Expect(pBody.Error).to.contain('Folder');
|
|
1124
|
+
return fDone();
|
|
1125
|
+
});
|
|
1126
|
+
});
|
|
1127
|
+
|
|
1128
|
+
test('Should return 404 for nonexistent folder',
|
|
1129
|
+
(fDone) =>
|
|
1130
|
+
{
|
|
1131
|
+
makeRequest('POST', '/1.0/Entity/FromTabularFolder',
|
|
1132
|
+
{ Folder: '/tmp/nonexistent-folder-99999/' },
|
|
1133
|
+
(pError, pStatusCode, pBody) =>
|
|
1134
|
+
{
|
|
1135
|
+
Expect(pError).to.not.exist;
|
|
1136
|
+
Expect(pStatusCode).to.equal(404);
|
|
1137
|
+
return fDone();
|
|
1138
|
+
});
|
|
1139
|
+
});
|
|
1140
|
+
}
|
|
1141
|
+
);
|
|
1142
|
+
|
|
1143
|
+
// ===== Server Instantiation Tests =====
|
|
1144
|
+
suite
|
|
1145
|
+
(
|
|
1146
|
+
'Server Instantiation',
|
|
1147
|
+
() =>
|
|
1148
|
+
{
|
|
1149
|
+
test('Should be able to instantiate a MeadowIntegrationServer',
|
|
1150
|
+
(fDone) =>
|
|
1151
|
+
{
|
|
1152
|
+
let tmpServer = new MeadowIntegrationServer({ APIServerPort: 19999 });
|
|
1153
|
+
Expect(tmpServer).to.be.an('object');
|
|
1154
|
+
Expect(tmpServer._Fable).to.be.an('object');
|
|
1155
|
+
Expect(tmpServer._Orator).to.be.an('object');
|
|
1156
|
+
return fDone();
|
|
1157
|
+
});
|
|
1158
|
+
|
|
1159
|
+
test('Should export MeadowIntegrationServer from main module',
|
|
1160
|
+
(fDone) =>
|
|
1161
|
+
{
|
|
1162
|
+
let tmpModule = require('../source/Meadow-Integration.js');
|
|
1163
|
+
Expect(tmpModule).to.have.property('IntegrationServer');
|
|
1164
|
+
Expect(tmpModule.IntegrationServer).to.be.a('function');
|
|
1165
|
+
return fDone();
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
);
|
|
1169
|
+
}
|
|
1170
|
+
);
|