retold-facto 0.0.4 → 0.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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "retold-facto",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Data warehouse and knowledge graph storage for the Retold ecosystem.",
|
|
5
5
|
"main": "source/Retold-Facto.js",
|
|
6
6
|
"bin": {
|
|
@@ -54,9 +54,9 @@
|
|
|
54
54
|
},
|
|
55
55
|
"homepage": "https://github.com/stevenvelozo/retold-facto",
|
|
56
56
|
"devDependencies": {
|
|
57
|
-
"chai": "^
|
|
57
|
+
"chai": "^6.2.2",
|
|
58
58
|
"meadow-connection-sqlite": "^1.0.18",
|
|
59
|
-
"puppeteer": "^24.
|
|
59
|
+
"puppeteer": "^24.40.0",
|
|
60
60
|
"quackage": "^1.0.65",
|
|
61
61
|
"stricture": "^4.0.2",
|
|
62
62
|
"supertest": "^7.2.2"
|
|
@@ -64,21 +64,21 @@
|
|
|
64
64
|
"dependencies": {
|
|
65
65
|
"@codemirror/lang-markdown": "^6.5.0",
|
|
66
66
|
"@codemirror/state": "^6.6.0",
|
|
67
|
-
"@codemirror/view": "^6.
|
|
67
|
+
"@codemirror/view": "^6.41.0",
|
|
68
68
|
"bibliograph": "^0.1.4",
|
|
69
69
|
"codemirror": "^6.0.2",
|
|
70
70
|
"fable": "^3.1.67",
|
|
71
71
|
"fable-serviceproviderbase": "^3.0.19",
|
|
72
|
-
"fast-xml-parser": "^5.5.
|
|
72
|
+
"fast-xml-parser": "^5.5.10",
|
|
73
73
|
"meadow": "^2.0.33",
|
|
74
74
|
"meadow-connection-manager": "^1.0.0",
|
|
75
75
|
"meadow-connection-mysql": "^1.0.14",
|
|
76
76
|
"meadow-endpoints": "^4.0.14",
|
|
77
|
-
"meadow-integration": "^1.0.
|
|
77
|
+
"meadow-integration": "^1.0.24",
|
|
78
78
|
"orator": "^6.0.4",
|
|
79
|
-
"orator-serviceserver-restify": "^2.0.
|
|
79
|
+
"orator-serviceserver-restify": "^2.0.10",
|
|
80
80
|
"orator-static-server": "^2.0.4",
|
|
81
|
-
"pict": "^1.0.
|
|
81
|
+
"pict": "^1.0.361",
|
|
82
82
|
"pict-router": "^1.0.9",
|
|
83
83
|
"pict-section-flow": "^0.0.17",
|
|
84
84
|
"pict-section-modal": "^0.0.1",
|
|
@@ -1794,50 +1794,32 @@ class RetoldFactoProjectionEngine extends libFableServiceProviderBase
|
|
|
1794
1794
|
|
|
1795
1795
|
tmpLog.push(`[${new Date().toISOString()}] Pushing ${tmpRecordGUIDs.length} records via IntegrationAdapter to ${tmpServerURL}${tmpTargetEntityName}/Upsert`);
|
|
1796
1796
|
|
|
1797
|
-
//
|
|
1798
|
-
|
|
1799
|
-
|
|
1797
|
+
// Use integrateRecords which fetches the schema first,
|
|
1798
|
+
// then marshals and pushes records in sequence.
|
|
1799
|
+
tmpAdapter.integrateRecords(
|
|
1800
|
+
(pIntegrateError) =>
|
|
1800
1801
|
{
|
|
1801
|
-
|
|
1802
|
+
let tmpMarshaledCount = Object.keys(tmpAdapter._MarshaledRecords).length;
|
|
1803
|
+
|
|
1804
|
+
if (pIntegrateError)
|
|
1802
1805
|
{
|
|
1803
|
-
tmpLog.push(`[${new Date().toISOString()}]
|
|
1804
|
-
pResponse.send(
|
|
1805
|
-
{
|
|
1806
|
-
Error: `Marshal error: ${pMarshalError.message}`,
|
|
1807
|
-
RecordsProcessed: pRecords.length,
|
|
1808
|
-
RecordsTransformed: tmpRecordGUIDs.length,
|
|
1809
|
-
StagingFile: tmpStagingFile,
|
|
1810
|
-
Log: tmpLog.join('\n')
|
|
1811
|
-
});
|
|
1812
|
-
return fNext();
|
|
1806
|
+
tmpLog.push(`[${new Date().toISOString()}] Integration error: ${pIntegrateError.message}`);
|
|
1813
1807
|
}
|
|
1814
1808
|
|
|
1815
|
-
|
|
1816
|
-
tmpLog.push(`[${new Date().toISOString()}] Marshaled ${tmpMarshaledCount} records; pushing to server...`);
|
|
1809
|
+
tmpLog.push(`[${new Date().toISOString()}] Import complete: ${pRecords.length} source records, ${tmpRecordGUIDs.length} unique, ${tmpMarshaledCount} upserted`);
|
|
1817
1810
|
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
Success: !pPushError,
|
|
1831
|
-
RecordsProcessed: pRecords.length,
|
|
1832
|
-
RecordsTransformed: tmpRecordGUIDs.length,
|
|
1833
|
-
RecordsDeduplicated: tmpMappingOutcome.ParsedRowCount - tmpRecordGUIDs.length,
|
|
1834
|
-
BadRecords: tmpMappingOutcome.BadRecords.length,
|
|
1835
|
-
RecordsUpserted: tmpMarshaledCount,
|
|
1836
|
-
StagingFile: tmpStagingFile,
|
|
1837
|
-
Log: tmpLog.join('\n')
|
|
1838
|
-
});
|
|
1839
|
-
return fNext();
|
|
1840
|
-
});
|
|
1811
|
+
pResponse.send(
|
|
1812
|
+
{
|
|
1813
|
+
Success: !pIntegrateError,
|
|
1814
|
+
RecordsProcessed: pRecords.length,
|
|
1815
|
+
RecordsTransformed: tmpRecordGUIDs.length,
|
|
1816
|
+
RecordsDeduplicated: tmpMappingOutcome.ParsedRowCount - tmpRecordGUIDs.length,
|
|
1817
|
+
BadRecords: tmpMappingOutcome.BadRecords.length,
|
|
1818
|
+
RecordsUpserted: tmpMarshaledCount,
|
|
1819
|
+
StagingFile: tmpStagingFile,
|
|
1820
|
+
Log: tmpLog.join('\n')
|
|
1821
|
+
});
|
|
1822
|
+
return fNext();
|
|
1841
1823
|
});
|
|
1842
1824
|
};
|
|
1843
1825
|
|
|
@@ -4113,5 +4113,344 @@ suite
|
|
|
4113
4113
|
);
|
|
4114
4114
|
}
|
|
4115
4115
|
);
|
|
4116
|
+
|
|
4117
|
+
suite
|
|
4118
|
+
(
|
|
4119
|
+
'Projection Deploy and Import Pipeline',
|
|
4120
|
+
function()
|
|
4121
|
+
{
|
|
4122
|
+
this.timeout(10000);
|
|
4123
|
+
|
|
4124
|
+
let _PipelineSourceID = 0;
|
|
4125
|
+
let _PipelineRawDatasetID = 0;
|
|
4126
|
+
let _PipelineProjectionDatasetID = 0;
|
|
4127
|
+
let _PipelineStoreConnectionID = 0;
|
|
4128
|
+
let _PipelineProjectionStoreID = 0;
|
|
4129
|
+
let _PipelineMappingID = 0;
|
|
4130
|
+
|
|
4131
|
+
test
|
|
4132
|
+
(
|
|
4133
|
+
'Create a source for the pipeline test',
|
|
4134
|
+
function(fDone)
|
|
4135
|
+
{
|
|
4136
|
+
_SuperTest
|
|
4137
|
+
.post('/1.0/Source')
|
|
4138
|
+
.send({ Name: 'Pipeline Test Source', Type: 'API', URL: 'https://example.com', Protocol: 'HTTPS', Active: 1 })
|
|
4139
|
+
.expect(200)
|
|
4140
|
+
.end(
|
|
4141
|
+
(pError, pResponse) =>
|
|
4142
|
+
{
|
|
4143
|
+
if (pError) return fDone(pError);
|
|
4144
|
+
_PipelineSourceID = pResponse.body.IDSource;
|
|
4145
|
+
Expect(_PipelineSourceID).to.be.greaterThan(0);
|
|
4146
|
+
return fDone();
|
|
4147
|
+
});
|
|
4148
|
+
}
|
|
4149
|
+
);
|
|
4150
|
+
|
|
4151
|
+
test
|
|
4152
|
+
(
|
|
4153
|
+
'Create a raw dataset and ingest records with JSON content',
|
|
4154
|
+
function(fDone)
|
|
4155
|
+
{
|
|
4156
|
+
_SuperTest
|
|
4157
|
+
.post('/1.0/Dataset')
|
|
4158
|
+
.send({ Name: 'Pipeline Raw Dataset', Type: 'Raw', Description: 'Raw data for pipeline test' })
|
|
4159
|
+
.expect(200)
|
|
4160
|
+
.end(
|
|
4161
|
+
(pError, pResponse) =>
|
|
4162
|
+
{
|
|
4163
|
+
if (pError) return fDone(pError);
|
|
4164
|
+
_PipelineRawDatasetID = pResponse.body.IDDataset;
|
|
4165
|
+
Expect(_PipelineRawDatasetID).to.be.greaterThan(0);
|
|
4166
|
+
|
|
4167
|
+
let tmpJSONContent = JSON.stringify([
|
|
4168
|
+
{ Country: 'USA', Capital: 'Washington' },
|
|
4169
|
+
{ Country: 'Canada', Capital: 'Ottawa' },
|
|
4170
|
+
{ Country: 'Mexico', Capital: 'Mexico City' }
|
|
4171
|
+
]);
|
|
4172
|
+
|
|
4173
|
+
_SuperTest
|
|
4174
|
+
.post('/facto/ingest/file')
|
|
4175
|
+
.send(
|
|
4176
|
+
{
|
|
4177
|
+
IDDataset: _PipelineRawDatasetID,
|
|
4178
|
+
IDSource: _PipelineSourceID,
|
|
4179
|
+
Format: 'json',
|
|
4180
|
+
Type: 'country-data',
|
|
4181
|
+
Content: tmpJSONContent
|
|
4182
|
+
})
|
|
4183
|
+
.expect(200)
|
|
4184
|
+
.end(
|
|
4185
|
+
(pIngestError, pIngestResponse) =>
|
|
4186
|
+
{
|
|
4187
|
+
if (pIngestError) return fDone(pIngestError);
|
|
4188
|
+
Expect(pIngestResponse.body.Ingested).to.equal(3);
|
|
4189
|
+
return fDone();
|
|
4190
|
+
});
|
|
4191
|
+
});
|
|
4192
|
+
}
|
|
4193
|
+
);
|
|
4194
|
+
|
|
4195
|
+
test
|
|
4196
|
+
(
|
|
4197
|
+
'Create a Projection dataset with MicroDDL schema',
|
|
4198
|
+
function(fDone)
|
|
4199
|
+
{
|
|
4200
|
+
let tmpDDL = '! CountryFlat\n@ IDCountryFlat\n% GUIDCountryFlat\n$ CountryName 120\n$ CapitalCity 120';
|
|
4201
|
+
|
|
4202
|
+
_SuperTest
|
|
4203
|
+
.post('/1.0/Dataset')
|
|
4204
|
+
.send(
|
|
4205
|
+
{
|
|
4206
|
+
Name: 'CountryFlat Projection',
|
|
4207
|
+
Type: 'Projection',
|
|
4208
|
+
Description: 'Flat country projection',
|
|
4209
|
+
SchemaDefinition: tmpDDL
|
|
4210
|
+
})
|
|
4211
|
+
.expect(200)
|
|
4212
|
+
.end(
|
|
4213
|
+
(pError, pResponse) =>
|
|
4214
|
+
{
|
|
4215
|
+
if (pError) return fDone(pError);
|
|
4216
|
+
_PipelineProjectionDatasetID = pResponse.body.IDDataset;
|
|
4217
|
+
Expect(_PipelineProjectionDatasetID).to.be.greaterThan(0);
|
|
4218
|
+
return fDone();
|
|
4219
|
+
});
|
|
4220
|
+
}
|
|
4221
|
+
);
|
|
4222
|
+
|
|
4223
|
+
test
|
|
4224
|
+
(
|
|
4225
|
+
'Create a StoreConnection (SQLite :memory:)',
|
|
4226
|
+
function(fDone)
|
|
4227
|
+
{
|
|
4228
|
+
_SuperTest
|
|
4229
|
+
.post('/1.0/StoreConnection')
|
|
4230
|
+
.send({ Name: 'Pipeline SQLite Store', Type: 'SQLite', Config: JSON.stringify({ SQLiteFilePath: ':memory:' }), Status: 'OK' })
|
|
4231
|
+
.expect(200)
|
|
4232
|
+
.end(
|
|
4233
|
+
(pError, pResponse) =>
|
|
4234
|
+
{
|
|
4235
|
+
if (pError) return fDone(pError);
|
|
4236
|
+
_PipelineStoreConnectionID = pResponse.body.IDStoreConnection;
|
|
4237
|
+
Expect(_PipelineStoreConnectionID).to.be.greaterThan(0);
|
|
4238
|
+
return fDone();
|
|
4239
|
+
});
|
|
4240
|
+
}
|
|
4241
|
+
);
|
|
4242
|
+
|
|
4243
|
+
test
|
|
4244
|
+
(
|
|
4245
|
+
'Deploy the projection schema to the store',
|
|
4246
|
+
function(fDone)
|
|
4247
|
+
{
|
|
4248
|
+
_SuperTest
|
|
4249
|
+
.post(`/facto/projection/${_PipelineProjectionDatasetID}/deploy`)
|
|
4250
|
+
.send({ IDStoreConnection: _PipelineStoreConnectionID, TargetTableName: 'CountryFlat' })
|
|
4251
|
+
.expect(200)
|
|
4252
|
+
.end(
|
|
4253
|
+
(pError, pResponse) =>
|
|
4254
|
+
{
|
|
4255
|
+
if (pError) return fDone(pError);
|
|
4256
|
+
Expect(pResponse.body.Success).to.equal(true);
|
|
4257
|
+
// Extract the projection store ID
|
|
4258
|
+
let tmpPS = pResponse.body.ProjectionStore || {};
|
|
4259
|
+
_PipelineProjectionStoreID = tmpPS.IDProjectionStore || 0;
|
|
4260
|
+
|
|
4261
|
+
// If not directly available, query for it
|
|
4262
|
+
if (!_PipelineProjectionStoreID)
|
|
4263
|
+
{
|
|
4264
|
+
_SuperTest
|
|
4265
|
+
.get(`/facto/projection/${_PipelineProjectionDatasetID}/stores`)
|
|
4266
|
+
.expect(200)
|
|
4267
|
+
.end(
|
|
4268
|
+
(pStoreError, pStoreResponse) =>
|
|
4269
|
+
{
|
|
4270
|
+
if (pStoreError) return fDone(pStoreError);
|
|
4271
|
+
Expect(pStoreResponse.body.Stores.length).to.be.greaterThan(0);
|
|
4272
|
+
_PipelineProjectionStoreID = pStoreResponse.body.Stores[0].IDProjectionStore;
|
|
4273
|
+
Expect(_PipelineProjectionStoreID).to.be.greaterThan(0);
|
|
4274
|
+
return fDone();
|
|
4275
|
+
});
|
|
4276
|
+
}
|
|
4277
|
+
else
|
|
4278
|
+
{
|
|
4279
|
+
Expect(_PipelineProjectionStoreID).to.be.greaterThan(0);
|
|
4280
|
+
return fDone();
|
|
4281
|
+
}
|
|
4282
|
+
});
|
|
4283
|
+
}
|
|
4284
|
+
);
|
|
4285
|
+
|
|
4286
|
+
test
|
|
4287
|
+
(
|
|
4288
|
+
'Bug 1: Entity routes should be accessible after deploy (GET /1.0/CountryFlat/Schema)',
|
|
4289
|
+
function(fDone)
|
|
4290
|
+
{
|
|
4291
|
+
// After deploy, _saveProjectionStore should have called
|
|
4292
|
+
// _registerProjectionEntity which registers Meadow entity
|
|
4293
|
+
// routes on the restify server.
|
|
4294
|
+
// Bug: the doCreate/doUpdate callback returned 4 args but
|
|
4295
|
+
// the handler only accepted 3, so registration was skipped
|
|
4296
|
+
// and this endpoint would 404.
|
|
4297
|
+
_SuperTest
|
|
4298
|
+
.get('/1.0/CountryFlat/Schema')
|
|
4299
|
+
.expect(200)
|
|
4300
|
+
.end(
|
|
4301
|
+
(pError, pResponse) =>
|
|
4302
|
+
{
|
|
4303
|
+
if (pError) return fDone(pError);
|
|
4304
|
+
// The schema endpoint should return entity metadata, not a 404
|
|
4305
|
+
Expect(pResponse.body).to.be.an('object');
|
|
4306
|
+
Expect(pResponse.body.title).to.equal('CountryFlat');
|
|
4307
|
+
return fDone();
|
|
4308
|
+
});
|
|
4309
|
+
}
|
|
4310
|
+
);
|
|
4311
|
+
|
|
4312
|
+
test
|
|
4313
|
+
(
|
|
4314
|
+
'Create a mapping with template expressions',
|
|
4315
|
+
function(fDone)
|
|
4316
|
+
{
|
|
4317
|
+
let tmpMappingConfig = JSON.stringify(
|
|
4318
|
+
{
|
|
4319
|
+
Entity: 'CountryFlat',
|
|
4320
|
+
GUIDTemplate: '{~D:Record.Country~}',
|
|
4321
|
+
Mappings:
|
|
4322
|
+
{
|
|
4323
|
+
CountryName: '{~D:Record.Country~}',
|
|
4324
|
+
CapitalCity: '{~D:Record.Capital~}'
|
|
4325
|
+
}
|
|
4326
|
+
});
|
|
4327
|
+
|
|
4328
|
+
_SuperTest
|
|
4329
|
+
.post(`/facto/projection/${_PipelineProjectionDatasetID}/mapping`)
|
|
4330
|
+
.send(
|
|
4331
|
+
{
|
|
4332
|
+
IDSource: _PipelineSourceID,
|
|
4333
|
+
Name: 'Country Mapping',
|
|
4334
|
+
MappingConfiguration: tmpMappingConfig
|
|
4335
|
+
})
|
|
4336
|
+
.expect(200)
|
|
4337
|
+
.end(
|
|
4338
|
+
(pError, pResponse) =>
|
|
4339
|
+
{
|
|
4340
|
+
if (pError) return fDone(pError);
|
|
4341
|
+
Expect(pResponse.body.Success).to.equal(true);
|
|
4342
|
+
|
|
4343
|
+
// The mapping ID may be nested in a Meadow query result
|
|
4344
|
+
let tmpMapping = pResponse.body.Mapping || {};
|
|
4345
|
+
_PipelineMappingID = tmpMapping.IDProjectionMapping || 0;
|
|
4346
|
+
|
|
4347
|
+
// Fall back to querying for the mapping
|
|
4348
|
+
if (!_PipelineMappingID)
|
|
4349
|
+
{
|
|
4350
|
+
_SuperTest
|
|
4351
|
+
.get(`/facto/projection/${_PipelineProjectionDatasetID}/mappings`)
|
|
4352
|
+
.expect(200)
|
|
4353
|
+
.end(
|
|
4354
|
+
(pMapError, pMapResponse) =>
|
|
4355
|
+
{
|
|
4356
|
+
if (pMapError) return fDone(pMapError);
|
|
4357
|
+
Expect(pMapResponse.body.Mappings.length).to.be.greaterThan(0);
|
|
4358
|
+
_PipelineMappingID = pMapResponse.body.Mappings[0].IDProjectionMapping;
|
|
4359
|
+
Expect(_PipelineMappingID).to.be.greaterThan(0);
|
|
4360
|
+
return fDone();
|
|
4361
|
+
});
|
|
4362
|
+
}
|
|
4363
|
+
else
|
|
4364
|
+
{
|
|
4365
|
+
Expect(_PipelineMappingID).to.be.greaterThan(0);
|
|
4366
|
+
return fDone();
|
|
4367
|
+
}
|
|
4368
|
+
});
|
|
4369
|
+
}
|
|
4370
|
+
);
|
|
4371
|
+
|
|
4372
|
+
test
|
|
4373
|
+
(
|
|
4374
|
+
'Execute the import via integrateRecords',
|
|
4375
|
+
function(fDone)
|
|
4376
|
+
{
|
|
4377
|
+
_SuperTest
|
|
4378
|
+
.post(`/facto/projection/${_PipelineProjectionDatasetID}/import`)
|
|
4379
|
+
.send(
|
|
4380
|
+
{
|
|
4381
|
+
IDProjectionMapping: _PipelineMappingID,
|
|
4382
|
+
IDProjectionStore: _PipelineProjectionStoreID,
|
|
4383
|
+
IDSource: _PipelineSourceID
|
|
4384
|
+
})
|
|
4385
|
+
.expect(200)
|
|
4386
|
+
.end(
|
|
4387
|
+
(pError, pResponse) =>
|
|
4388
|
+
{
|
|
4389
|
+
if (pError) return fDone(pError);
|
|
4390
|
+
Expect(pResponse.body.Success, `Import should succeed. Response: ${JSON.stringify(pResponse.body).substring(0, 1000)}`).to.equal(true);
|
|
4391
|
+
Expect(pResponse.body.RecordsProcessed).to.equal(3);
|
|
4392
|
+
Expect(pResponse.body.RecordsTransformed).to.be.greaterThan(0);
|
|
4393
|
+
Expect(pResponse.body.RecordsUpserted).to.be.greaterThan(0);
|
|
4394
|
+
return fDone();
|
|
4395
|
+
});
|
|
4396
|
+
}
|
|
4397
|
+
);
|
|
4398
|
+
|
|
4399
|
+
test
|
|
4400
|
+
(
|
|
4401
|
+
'Bug 2: Projected records should have field values populated (not empty)',
|
|
4402
|
+
function(fDone)
|
|
4403
|
+
{
|
|
4404
|
+
// After import, the projected records in the CountryFlat
|
|
4405
|
+
// table should have CountryName and CapitalCity populated.
|
|
4406
|
+
// Bug: using marshalSourceRecords + pushRecordsToServer
|
|
4407
|
+
// separately bypassed the schema fetch step needed by the
|
|
4408
|
+
// IntegrationAdapter to marshal field values, leaving them
|
|
4409
|
+
// empty. Using integrateRecords fixes this.
|
|
4410
|
+
_SuperTest
|
|
4411
|
+
.get('/1.0/CountryFlats/0/10')
|
|
4412
|
+
.expect(200)
|
|
4413
|
+
.end(
|
|
4414
|
+
(pError, pResponse) =>
|
|
4415
|
+
{
|
|
4416
|
+
if (pError) return fDone(pError);
|
|
4417
|
+
Expect(pResponse.body).to.be.an('array');
|
|
4418
|
+
Expect(pResponse.body.length).to.equal(3);
|
|
4419
|
+
|
|
4420
|
+
// Find the USA record and verify field values
|
|
4421
|
+
let tmpUSA = pResponse.body.find((pRecord) => { return pRecord.CountryName === 'USA'; });
|
|
4422
|
+
Expect(tmpUSA).to.be.an('object');
|
|
4423
|
+
Expect(tmpUSA.CountryName).to.equal('USA');
|
|
4424
|
+
Expect(tmpUSA.CapitalCity).to.equal('Washington');
|
|
4425
|
+
|
|
4426
|
+
// Verify another record
|
|
4427
|
+
let tmpCanada = pResponse.body.find((pRecord) => { return pRecord.CountryName === 'Canada'; });
|
|
4428
|
+
Expect(tmpCanada).to.be.an('object');
|
|
4429
|
+
Expect(tmpCanada.CapitalCity).to.equal('Ottawa');
|
|
4430
|
+
|
|
4431
|
+
return fDone();
|
|
4432
|
+
});
|
|
4433
|
+
}
|
|
4434
|
+
);
|
|
4435
|
+
|
|
4436
|
+
test
|
|
4437
|
+
(
|
|
4438
|
+
'Verify projected record count matches source record count',
|
|
4439
|
+
function(fDone)
|
|
4440
|
+
{
|
|
4441
|
+
_SuperTest
|
|
4442
|
+
.get('/1.0/CountryFlats/Count')
|
|
4443
|
+
.expect(200)
|
|
4444
|
+
.end(
|
|
4445
|
+
(pError, pResponse) =>
|
|
4446
|
+
{
|
|
4447
|
+
if (pError) return fDone(pError);
|
|
4448
|
+
Expect(pResponse.body.Count).to.equal(3);
|
|
4449
|
+
return fDone();
|
|
4450
|
+
});
|
|
4451
|
+
}
|
|
4452
|
+
);
|
|
4453
|
+
}
|
|
4454
|
+
);
|
|
4116
4455
|
}
|
|
4117
4456
|
);
|