@api-client/core 0.18.5 → 0.18.7
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/build/src/lib/dom_purify.d.ts +4 -0
- package/build/src/lib/dom_purify.d.ts.map +1 -0
- package/build/src/lib/dom_purify.js +23 -0
- package/build/src/lib/dom_purify.js.map +1 -0
- package/build/src/modeling/helpers/database.js +2 -2
- package/build/src/modeling/helpers/database.js.map +1 -1
- package/build/src/modeling/importers/CsvImporter.d.ts +40 -6
- package/build/src/modeling/importers/CsvImporter.d.ts.map +1 -1
- package/build/src/modeling/importers/CsvImporter.js +75 -2
- package/build/src/modeling/importers/CsvImporter.js.map +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/data/models/example-generator-api.json +25 -25
- package/package.json +4 -2
- package/src/lib/dom_purify.ts +25 -0
- package/src/modeling/helpers/database.ts +2 -2
- package/src/modeling/importers/CsvImporter.ts +115 -5
- package/tests/unit/lib/dom_purify.spec.ts +12 -0
- package/tests/unit/modeling/importers/csv_importer.spec.ts +351 -0
|
@@ -50,6 +50,7 @@ test.group('CsvImporter: import', (group) => {
|
|
|
50
50
|
],
|
|
51
51
|
values: [[1, 'test-item', 99.9]],
|
|
52
52
|
header: ['id', 'name', 'value'],
|
|
53
|
+
file: 'products.csv',
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
await importer.import(parseResult, 'My Products')
|
|
@@ -87,6 +88,7 @@ test.group('CsvImporter: import', (group) => {
|
|
|
87
88
|
format: [{ index: 0, name: 'First Name', type: 'string' }],
|
|
88
89
|
values: [['John']],
|
|
89
90
|
header: ['First Name'],
|
|
91
|
+
file: 'users.csv',
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
await importer.import(parseResult, 'User List!')
|
|
@@ -113,6 +115,7 @@ test.group('CsvImporter: import', (group) => {
|
|
|
113
115
|
],
|
|
114
116
|
values: [], // No data rows
|
|
115
117
|
header: ['id', 'email'],
|
|
118
|
+
file: 'empty.csv',
|
|
116
119
|
}
|
|
117
120
|
|
|
118
121
|
await importer.import(parseResult, 'users')
|
|
@@ -140,6 +143,7 @@ test.group('CsvImporter: import', (group) => {
|
|
|
140
143
|
// @ts-expect-error TypeScript expects values to be defined
|
|
141
144
|
values: [[null, 'has-value', undefined]],
|
|
142
145
|
header: ['col_a', 'col_b', 'col_c'],
|
|
146
|
+
file: 'test.csv',
|
|
143
147
|
}
|
|
144
148
|
|
|
145
149
|
await importer.import(parseResult, 'test_data')
|
|
@@ -169,6 +173,7 @@ test.group('CsvImporter: import', (group) => {
|
|
|
169
173
|
],
|
|
170
174
|
values: [[123, 45.67]],
|
|
171
175
|
header: ['integer_val', 'float_val'],
|
|
176
|
+
file: 'numeric.csv',
|
|
172
177
|
}
|
|
173
178
|
|
|
174
179
|
await importer.import(parseResult, 'Numeric Data')
|
|
@@ -187,3 +192,349 @@ test.group('CsvImporter: import', (group) => {
|
|
|
187
192
|
assert.deepEqual(floatProp!.bindings, [{ type: 'web', schema: { format: 'double' } }])
|
|
188
193
|
})
|
|
189
194
|
})
|
|
195
|
+
|
|
196
|
+
test.group('CsvImporter: importMany', (group) => {
|
|
197
|
+
let domain: DataDomain
|
|
198
|
+
|
|
199
|
+
group.each.setup(() => {
|
|
200
|
+
domain = new DataDomain()
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
test('should import multiple CSV structures into a single model', async ({ assert }) => {
|
|
204
|
+
const importer = new CsvImporter(domain)
|
|
205
|
+
const parseResults: ParseResult[] = [
|
|
206
|
+
{
|
|
207
|
+
format: [
|
|
208
|
+
{ index: 0, name: 'id', type: 'number', format: 'integer' },
|
|
209
|
+
{ index: 1, name: 'name', type: 'string' },
|
|
210
|
+
],
|
|
211
|
+
values: [
|
|
212
|
+
[1, 'Product A'],
|
|
213
|
+
[2, 'Product B'],
|
|
214
|
+
],
|
|
215
|
+
header: ['id', 'name'],
|
|
216
|
+
file: 'products.csv',
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
format: [
|
|
220
|
+
{ index: 0, name: 'user_id', type: 'number', format: 'integer' },
|
|
221
|
+
{ index: 1, name: 'email', type: 'string' },
|
|
222
|
+
{ index: 2, name: 'active', type: 'boolean' },
|
|
223
|
+
],
|
|
224
|
+
values: [
|
|
225
|
+
[100, 'user@example.com', true],
|
|
226
|
+
[101, 'admin@example.com', false],
|
|
227
|
+
],
|
|
228
|
+
header: ['user_id', 'email', 'active'],
|
|
229
|
+
file: 'users.csv',
|
|
230
|
+
},
|
|
231
|
+
]
|
|
232
|
+
|
|
233
|
+
const result = await importer.importMany(parseResults, 'E-commerce Data')
|
|
234
|
+
|
|
235
|
+
// Check model
|
|
236
|
+
const model = findModelByName(domain, 'e_commerce_data')
|
|
237
|
+
assert.exists(model)
|
|
238
|
+
assert.equal(model!.info.displayName, 'E-commerce Data')
|
|
239
|
+
|
|
240
|
+
// Check entities
|
|
241
|
+
assert.lengthOf(result.entities, 2)
|
|
242
|
+
assert.equal(result.model, model)
|
|
243
|
+
|
|
244
|
+
// Check products entity
|
|
245
|
+
const productsEntity = findEntityByName(domain, 'products')
|
|
246
|
+
assert.exists(productsEntity)
|
|
247
|
+
assert.isUndefined(productsEntity!.info.displayName, 'the products entity should not have a displayName')
|
|
248
|
+
|
|
249
|
+
const productIdProp = findPropertyByName(productsEntity!, 'id')
|
|
250
|
+
assert.exists(productIdProp)
|
|
251
|
+
assert.equal(productIdProp!.type, 'number')
|
|
252
|
+
assert.deepEqual(productIdProp!.bindings, [{ type: 'web', schema: { format: 'int64' } }])
|
|
253
|
+
assert.deepEqual(productIdProp!.schema?.examples, ['1'])
|
|
254
|
+
|
|
255
|
+
const productNameProp = findPropertyByName(productsEntity!, 'name')
|
|
256
|
+
assert.exists(productNameProp)
|
|
257
|
+
assert.equal(productNameProp!.type, 'string')
|
|
258
|
+
assert.deepEqual(productNameProp!.schema?.examples, ['Product A'])
|
|
259
|
+
|
|
260
|
+
// Check users entity
|
|
261
|
+
const usersEntity = findEntityByName(domain, 'users')
|
|
262
|
+
assert.exists(usersEntity)
|
|
263
|
+
assert.isUndefined(usersEntity!.info.displayName, 'the users entity should not have a displayName')
|
|
264
|
+
|
|
265
|
+
const userIdProp = findPropertyByName(usersEntity!, 'user_id')
|
|
266
|
+
assert.exists(userIdProp)
|
|
267
|
+
assert.equal(userIdProp!.type, 'number')
|
|
268
|
+
assert.deepEqual(userIdProp!.bindings, [{ type: 'web', schema: { format: 'int64' } }])
|
|
269
|
+
assert.deepEqual(userIdProp!.schema?.examples, ['100'])
|
|
270
|
+
|
|
271
|
+
const emailProp = findPropertyByName(usersEntity!, 'email')
|
|
272
|
+
assert.exists(emailProp)
|
|
273
|
+
assert.equal(emailProp!.type, 'string')
|
|
274
|
+
assert.deepEqual(emailProp!.schema?.examples, ['user@example.com'])
|
|
275
|
+
|
|
276
|
+
const activeProp = findPropertyByName(usersEntity!, 'active')
|
|
277
|
+
assert.exists(activeProp)
|
|
278
|
+
assert.equal(activeProp!.type, 'boolean')
|
|
279
|
+
assert.deepEqual(activeProp!.schema?.examples, ['true'])
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
test('should handle name sanitization for model and entities', async ({ assert }) => {
|
|
283
|
+
const importer = new CsvImporter(domain)
|
|
284
|
+
const parseResults: ParseResult[] = [
|
|
285
|
+
{
|
|
286
|
+
format: [{ index: 0, name: 'First Name', type: 'string' }],
|
|
287
|
+
values: [['John']],
|
|
288
|
+
header: ['First Name'],
|
|
289
|
+
file: 'Employee List!',
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
format: [{ index: 0, name: 'Department Name', type: 'string' }],
|
|
293
|
+
values: [['Engineering']],
|
|
294
|
+
header: ['Department Name'],
|
|
295
|
+
file: 'Department Info@',
|
|
296
|
+
},
|
|
297
|
+
]
|
|
298
|
+
|
|
299
|
+
await importer.importMany(parseResults, 'HR Data!')
|
|
300
|
+
|
|
301
|
+
const model = findModelByName(domain, 'hr_data')
|
|
302
|
+
assert.exists(model)
|
|
303
|
+
assert.equal(model!.info.displayName, 'HR Data!')
|
|
304
|
+
|
|
305
|
+
const employeeEntity = findEntityByName(domain, 'employee_list')
|
|
306
|
+
assert.exists(employeeEntity)
|
|
307
|
+
assert.equal(employeeEntity!.info.displayName, 'Employee List!')
|
|
308
|
+
|
|
309
|
+
const departmentEntity = findEntityByName(domain, 'department_info')
|
|
310
|
+
assert.exists(departmentEntity)
|
|
311
|
+
assert.equal(departmentEntity!.info.displayName, 'Department Info@')
|
|
312
|
+
|
|
313
|
+
const firstNameProp = findPropertyByName(employeeEntity!, 'first_name')
|
|
314
|
+
assert.exists(firstNameProp)
|
|
315
|
+
assert.equal(firstNameProp!.info.displayName, 'First Name')
|
|
316
|
+
|
|
317
|
+
const deptNameProp = findPropertyByName(departmentEntity!, 'department_name')
|
|
318
|
+
assert.exists(deptNameProp)
|
|
319
|
+
assert.equal(deptNameProp!.info.displayName, 'Department Name')
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
test('should handle empty data arrays for some entities', async ({ assert }) => {
|
|
323
|
+
const importer = new CsvImporter(domain)
|
|
324
|
+
const parseResults: ParseResult[] = [
|
|
325
|
+
{
|
|
326
|
+
format: [
|
|
327
|
+
{ index: 0, name: 'id', type: 'number', format: 'integer' },
|
|
328
|
+
{ index: 1, name: 'title', type: 'string' },
|
|
329
|
+
],
|
|
330
|
+
values: [[1, 'Sample Title']],
|
|
331
|
+
header: ['id', 'title'],
|
|
332
|
+
file: 'articles.csv',
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
format: [
|
|
336
|
+
{ index: 0, name: 'tag_id', type: 'number' },
|
|
337
|
+
{ index: 1, name: 'tag_name', type: 'string' },
|
|
338
|
+
],
|
|
339
|
+
values: [], // No data rows
|
|
340
|
+
header: ['tag_id', 'tag_name'],
|
|
341
|
+
file: 'tags.csv',
|
|
342
|
+
},
|
|
343
|
+
]
|
|
344
|
+
|
|
345
|
+
const result = await importer.importMany(parseResults, 'Blog Data')
|
|
346
|
+
|
|
347
|
+
assert.lengthOf(result.entities, 2)
|
|
348
|
+
|
|
349
|
+
const articlesEntity = findEntityByName(domain, 'articles')
|
|
350
|
+
assert.exists(articlesEntity)
|
|
351
|
+
|
|
352
|
+
const idProp = findPropertyByName(articlesEntity!, 'id')
|
|
353
|
+
assert.exists(idProp)
|
|
354
|
+
assert.deepEqual(idProp!.schema?.examples, ['1'])
|
|
355
|
+
|
|
356
|
+
const tagsEntity = findEntityByName(domain, 'tags')
|
|
357
|
+
assert.exists(tagsEntity)
|
|
358
|
+
|
|
359
|
+
const tagIdProp = findPropertyByName(tagsEntity!, 'tag_id')
|
|
360
|
+
assert.exists(tagIdProp)
|
|
361
|
+
assert.isUndefined(tagIdProp!.schema?.examples, 'should have no examples for empty data')
|
|
362
|
+
|
|
363
|
+
const tagNameProp = findPropertyByName(tagsEntity!, 'tag_name')
|
|
364
|
+
assert.exists(tagNameProp)
|
|
365
|
+
assert.isUndefined(tagNameProp!.schema?.examples, 'should have no examples for empty data')
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
test('should handle null and undefined values in example rows', async ({ assert }) => {
|
|
369
|
+
const importer = new CsvImporter(domain)
|
|
370
|
+
const parseResults: ParseResult[] = [
|
|
371
|
+
{
|
|
372
|
+
format: [
|
|
373
|
+
{ index: 0, name: 'col_a', type: 'string' },
|
|
374
|
+
{ index: 1, name: 'col_b', type: 'string' },
|
|
375
|
+
],
|
|
376
|
+
// @ts-expect-error TypeScript expects values to be defined
|
|
377
|
+
values: [[null, 'valid-value']],
|
|
378
|
+
header: ['col_a', 'col_b'],
|
|
379
|
+
file: 'test_data.csv',
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
format: [
|
|
383
|
+
{ index: 0, name: 'col_x', type: 'string' },
|
|
384
|
+
{ index: 1, name: 'col_y', type: 'string' },
|
|
385
|
+
],
|
|
386
|
+
// @ts-expect-error TypeScript expects values to be defined
|
|
387
|
+
values: [['another-value', undefined]],
|
|
388
|
+
header: ['col_x', 'col_y'],
|
|
389
|
+
file: 'more_test_data.csv',
|
|
390
|
+
},
|
|
391
|
+
]
|
|
392
|
+
|
|
393
|
+
const result = await importer.importMany(parseResults, 'Test Data')
|
|
394
|
+
|
|
395
|
+
assert.lengthOf(result.entities, 2)
|
|
396
|
+
|
|
397
|
+
const testDataEntity = findEntityByName(domain, 'test_data')
|
|
398
|
+
assert.exists(testDataEntity)
|
|
399
|
+
|
|
400
|
+
const propA = findPropertyByName(testDataEntity!, 'col_a')
|
|
401
|
+
assert.exists(propA)
|
|
402
|
+
assert.isUndefined(propA!.schema?.examples, 'should not create example for null')
|
|
403
|
+
|
|
404
|
+
const propB = findPropertyByName(testDataEntity!, 'col_b')
|
|
405
|
+
assert.exists(propB)
|
|
406
|
+
assert.deepEqual(propB!.schema?.examples, ['valid-value'])
|
|
407
|
+
|
|
408
|
+
const moreTestDataEntity = findEntityByName(domain, 'more_test_data')
|
|
409
|
+
assert.exists(moreTestDataEntity)
|
|
410
|
+
|
|
411
|
+
const propX = findPropertyByName(moreTestDataEntity!, 'col_x')
|
|
412
|
+
assert.exists(propX)
|
|
413
|
+
assert.deepEqual(propX!.schema?.examples, ['another-value'])
|
|
414
|
+
|
|
415
|
+
const propY = findPropertyByName(moreTestDataEntity!, 'col_y')
|
|
416
|
+
assert.exists(propY)
|
|
417
|
+
assert.isUndefined(propY!.schema?.examples, 'should not create example for undefined')
|
|
418
|
+
})
|
|
419
|
+
|
|
420
|
+
test('should handle different number formats across entities', async ({ assert }) => {
|
|
421
|
+
const importer = new CsvImporter(domain)
|
|
422
|
+
const parseResults: ParseResult[] = [
|
|
423
|
+
{
|
|
424
|
+
format: [
|
|
425
|
+
{ index: 0, name: 'int_val', type: 'number', format: 'integer' },
|
|
426
|
+
{ index: 1, name: 'decimal_val', type: 'number', format: 'decimal' },
|
|
427
|
+
],
|
|
428
|
+
values: [[42, 3.14]],
|
|
429
|
+
header: ['int_val', 'decimal_val'],
|
|
430
|
+
file: 'numbers.csv',
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
format: [
|
|
434
|
+
{ index: 0, name: 'price', type: 'number', format: 'decimal' },
|
|
435
|
+
{ index: 1, name: 'quantity', type: 'number', format: 'integer' },
|
|
436
|
+
],
|
|
437
|
+
values: [[19.99, 5]],
|
|
438
|
+
header: ['price', 'quantity'],
|
|
439
|
+
file: 'inventory.csv',
|
|
440
|
+
},
|
|
441
|
+
]
|
|
442
|
+
|
|
443
|
+
const result = await importer.importMany(parseResults, 'Numeric Data')
|
|
444
|
+
|
|
445
|
+
assert.lengthOf(result.entities, 2)
|
|
446
|
+
|
|
447
|
+
const numbersEntity = findEntityByName(domain, 'numbers')
|
|
448
|
+
assert.exists(numbersEntity)
|
|
449
|
+
|
|
450
|
+
const intProp = findPropertyByName(numbersEntity!, 'int_val')
|
|
451
|
+
assert.exists(intProp)
|
|
452
|
+
assert.equal(intProp!.type, 'number')
|
|
453
|
+
assert.deepEqual(intProp!.bindings, [{ type: 'web', schema: { format: 'int64' } }])
|
|
454
|
+
|
|
455
|
+
const decimalProp = findPropertyByName(numbersEntity!, 'decimal_val')
|
|
456
|
+
assert.exists(decimalProp)
|
|
457
|
+
assert.equal(decimalProp!.type, 'number')
|
|
458
|
+
assert.deepEqual(decimalProp!.bindings, [{ type: 'web', schema: { format: 'double' } }])
|
|
459
|
+
|
|
460
|
+
const inventoryEntity = findEntityByName(domain, 'inventory')
|
|
461
|
+
assert.exists(inventoryEntity)
|
|
462
|
+
|
|
463
|
+
const priceProp = findPropertyByName(inventoryEntity!, 'price')
|
|
464
|
+
assert.exists(priceProp)
|
|
465
|
+
assert.equal(priceProp!.type, 'number')
|
|
466
|
+
assert.deepEqual(priceProp!.bindings, [{ type: 'web', schema: { format: 'double' } }])
|
|
467
|
+
|
|
468
|
+
const quantityProp = findPropertyByName(inventoryEntity!, 'quantity')
|
|
469
|
+
assert.exists(quantityProp)
|
|
470
|
+
assert.equal(quantityProp!.type, 'number')
|
|
471
|
+
assert.deepEqual(quantityProp!.bindings, [{ type: 'web', schema: { format: 'int64' } }])
|
|
472
|
+
})
|
|
473
|
+
|
|
474
|
+
test('should handle single CSV data in array', async ({ assert }) => {
|
|
475
|
+
const importer = new CsvImporter(domain)
|
|
476
|
+
const parseResults: ParseResult[] = [
|
|
477
|
+
{
|
|
478
|
+
format: [
|
|
479
|
+
{ index: 0, name: 'id', type: 'number', format: 'integer' },
|
|
480
|
+
{ index: 1, name: 'name', type: 'string' },
|
|
481
|
+
],
|
|
482
|
+
values: [[1, 'Single Item']],
|
|
483
|
+
header: ['id', 'name'],
|
|
484
|
+
file: 'single.csv',
|
|
485
|
+
},
|
|
486
|
+
]
|
|
487
|
+
|
|
488
|
+
const result = await importer.importMany(parseResults, 'Single Data')
|
|
489
|
+
|
|
490
|
+
const model = findModelByName(domain, 'single_data')
|
|
491
|
+
assert.exists(model)
|
|
492
|
+
assert.equal(model!.info.displayName, 'Single Data')
|
|
493
|
+
|
|
494
|
+
assert.lengthOf(result.entities, 1)
|
|
495
|
+
assert.equal(result.model, model)
|
|
496
|
+
|
|
497
|
+
const singleEntity = findEntityByName(domain, 'single')
|
|
498
|
+
assert.exists(singleEntity)
|
|
499
|
+
assert.isUndefined(singleEntity!.info.displayName, 'the single entity should not have a displayName')
|
|
500
|
+
|
|
501
|
+
const idProp = findPropertyByName(singleEntity!, 'id')
|
|
502
|
+
assert.exists(idProp)
|
|
503
|
+
assert.equal(idProp!.type, 'number')
|
|
504
|
+
assert.deepEqual(idProp!.schema?.examples, ['1'])
|
|
505
|
+
|
|
506
|
+
const nameProp = findPropertyByName(singleEntity!, 'name')
|
|
507
|
+
assert.exists(nameProp)
|
|
508
|
+
assert.equal(nameProp!.type, 'string')
|
|
509
|
+
assert.deepEqual(nameProp!.schema?.examples, ['Single Item'])
|
|
510
|
+
})
|
|
511
|
+
|
|
512
|
+
test('should handle entities with fallback names when file names are missing', async ({ assert }) => {
|
|
513
|
+
const importer = new CsvImporter(domain)
|
|
514
|
+
const parseResults: ParseResult[] = [
|
|
515
|
+
{
|
|
516
|
+
format: [{ index: 0, name: 'col1', type: 'string' }],
|
|
517
|
+
values: [['value1']],
|
|
518
|
+
header: ['col1'],
|
|
519
|
+
file: '',
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
format: [{ index: 0, name: 'col2', type: 'string' }],
|
|
523
|
+
values: [['value2']],
|
|
524
|
+
header: ['col2'],
|
|
525
|
+
file: '',
|
|
526
|
+
},
|
|
527
|
+
]
|
|
528
|
+
|
|
529
|
+
const result = await importer.importMany(parseResults, 'Fallback Data')
|
|
530
|
+
|
|
531
|
+
assert.lengthOf(result.entities, 2)
|
|
532
|
+
|
|
533
|
+
// Should use fallback names based on entity index
|
|
534
|
+
const entity0 = findEntityByName(domain, 'entity_0')
|
|
535
|
+
assert.exists(entity0)
|
|
536
|
+
|
|
537
|
+
const entity1 = findEntityByName(domain, 'entity_1')
|
|
538
|
+
assert.exists(entity1)
|
|
539
|
+
})
|
|
540
|
+
})
|