@api-client/core 0.18.16 → 0.18.17
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/modeling/DataDomain.d.ts +7 -2
- package/build/src/modeling/DataDomain.d.ts.map +1 -1
- package/build/src/modeling/DataDomain.js +15 -2
- package/build/src/modeling/DataDomain.js.map +1 -1
- package/build/src/modeling/DomainSerialization.d.ts +6 -3
- package/build/src/modeling/DomainSerialization.d.ts.map +1 -1
- package/build/src/modeling/DomainSerialization.js +374 -52
- package/build/src/modeling/DomainSerialization.js.map +1 -1
- package/build/src/modeling/types.d.ts +69 -2
- package/build/src/modeling/types.d.ts.map +1 -1
- package/build/src/modeling/types.js.map +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/data/models/example-generator-api.json +12 -12
- package/package.json +1 -1
- package/src/modeling/DataDomain.ts +24 -3
- package/src/modeling/DomainSerialization.ts +442 -56
- package/src/modeling/types.ts +73 -2
- package/tests/unit/modeling/data_domain_serialization.spec.ts +504 -0
|
@@ -392,3 +392,507 @@ test.group('DataDomain Serialization and Deserialization', () => {
|
|
|
392
392
|
)
|
|
393
393
|
}).tags(['@modeling', '@serialization'])
|
|
394
394
|
})
|
|
395
|
+
|
|
396
|
+
test.group('Validation Tests', () => {
|
|
397
|
+
test('should throw validation error when property has no parent entity edge', ({ assert }) => {
|
|
398
|
+
const domain = new DataDomain()
|
|
399
|
+
const m1 = domain.addModel()
|
|
400
|
+
const e1 = m1.addEntity()
|
|
401
|
+
const p1 = e1.addProperty({ type: 'string' })
|
|
402
|
+
|
|
403
|
+
// Manually break the graph by removing the property edge
|
|
404
|
+
domain.graph.removeEdge(e1.key, p1.key)
|
|
405
|
+
|
|
406
|
+
assert.throws(() => domain.toJSON())
|
|
407
|
+
}).tags(['@modeling', '@serialization', '@validation'])
|
|
408
|
+
|
|
409
|
+
test('should throw validation error when association has no parent entity edge', ({ assert }) => {
|
|
410
|
+
const domain = new DataDomain()
|
|
411
|
+
const m1 = domain.addModel()
|
|
412
|
+
const e1 = m1.addEntity()
|
|
413
|
+
const e2 = m1.addEntity()
|
|
414
|
+
const a1 = e1.addAssociation({ key: e2.key })
|
|
415
|
+
|
|
416
|
+
// Manually break the graph by removing the association edge to parent
|
|
417
|
+
domain.graph.removeEdge(e1.key, a1.key)
|
|
418
|
+
|
|
419
|
+
assert.throws(() => domain.toJSON())
|
|
420
|
+
}).tags(['@modeling', '@serialization', '@validation'])
|
|
421
|
+
|
|
422
|
+
test('should throw validation error when property has multiple parent edges', ({ assert }) => {
|
|
423
|
+
const domain = new DataDomain()
|
|
424
|
+
const m1 = domain.addModel()
|
|
425
|
+
const e1 = m1.addEntity()
|
|
426
|
+
const e2 = m1.addEntity()
|
|
427
|
+
const p1 = e1.addProperty({ type: 'string' })
|
|
428
|
+
|
|
429
|
+
// Manually add an extra edge to create multiple parents
|
|
430
|
+
domain.graph.setEdge(e2.key, p1.key, { type: 'property' })
|
|
431
|
+
|
|
432
|
+
assert.throws(() => domain.toJSON())
|
|
433
|
+
}).tags(['@modeling', '@serialization', '@validation'])
|
|
434
|
+
|
|
435
|
+
test('should throw validation error when property has non-entity parent', ({ assert }) => {
|
|
436
|
+
const domain = new DataDomain()
|
|
437
|
+
const m1 = domain.addModel()
|
|
438
|
+
const e1 = m1.addEntity()
|
|
439
|
+
const p1 = e1.addProperty({ type: 'string' })
|
|
440
|
+
|
|
441
|
+
// Remove the proper edge and add an edge from model to property (invalid)
|
|
442
|
+
domain.graph.removeEdge(e1.key, p1.key)
|
|
443
|
+
domain.graph.setEdge(m1.key, p1.key, { type: 'property' })
|
|
444
|
+
|
|
445
|
+
assert.throws(() => domain.toJSON())
|
|
446
|
+
}).tags(['@modeling', '@serialization', '@validation'])
|
|
447
|
+
|
|
448
|
+
test('should throw validation error when entity has no model parent', ({ assert }) => {
|
|
449
|
+
const domain = new DataDomain()
|
|
450
|
+
const m1 = domain.addModel()
|
|
451
|
+
const e1 = m1.addEntity()
|
|
452
|
+
|
|
453
|
+
// Remove the entity from its model parent
|
|
454
|
+
domain.graph.setParent(e1.key, undefined)
|
|
455
|
+
|
|
456
|
+
assert.throws(() => domain.toJSON())
|
|
457
|
+
}).tags(['@modeling', '@serialization', '@validation'])
|
|
458
|
+
|
|
459
|
+
test('should throw validation error when association has no target entities', ({ assert }) => {
|
|
460
|
+
const domain = new DataDomain()
|
|
461
|
+
const m1 = domain.addModel()
|
|
462
|
+
const e1 = m1.addEntity()
|
|
463
|
+
const e2 = m1.addEntity()
|
|
464
|
+
const a1 = e1.addAssociation({ key: e2.key })
|
|
465
|
+
|
|
466
|
+
// Remove the association target edge
|
|
467
|
+
domain.graph.removeEdge(a1.key, e2.key)
|
|
468
|
+
|
|
469
|
+
assert.throws(() => domain.toJSON())
|
|
470
|
+
}).tags(['@modeling', '@serialization', '@validation'])
|
|
471
|
+
|
|
472
|
+
test('should throw validation error when association references non-existent target', ({ assert }) => {
|
|
473
|
+
const domain = new DataDomain()
|
|
474
|
+
const m1 = domain.addModel()
|
|
475
|
+
const e1 = m1.addEntity()
|
|
476
|
+
const e2 = m1.addEntity()
|
|
477
|
+
const a1 = e1.addAssociation({ key: e2.key })
|
|
478
|
+
|
|
479
|
+
// Remove the target entity but keep the edge pointing to it
|
|
480
|
+
domain.graph.removeNode(e2.key)
|
|
481
|
+
// Re-add the association with a broken target
|
|
482
|
+
domain.graph.setEdge(a1.key, 'non-existent-entity', { type: 'association' })
|
|
483
|
+
|
|
484
|
+
assert.throws(() => domain.toJSON())
|
|
485
|
+
}).tags(['@modeling', '@serialization', '@validation'])
|
|
486
|
+
|
|
487
|
+
test('should throw validation error when association references non-entity target', ({ assert }) => {
|
|
488
|
+
const domain = new DataDomain()
|
|
489
|
+
const m1 = domain.addModel()
|
|
490
|
+
const e1 = m1.addEntity()
|
|
491
|
+
const e2 = m1.addEntity()
|
|
492
|
+
const a1 = e1.addAssociation({ key: e2.key })
|
|
493
|
+
|
|
494
|
+
// Remove the proper target and make it point to the model instead
|
|
495
|
+
domain.graph.removeEdge(a1.key, e2.key)
|
|
496
|
+
domain.graph.setEdge(a1.key, m1.key, { type: 'association' })
|
|
497
|
+
|
|
498
|
+
assert.throws(() => domain.toJSON())
|
|
499
|
+
}).tags(['@modeling', '@serialization', '@validation'])
|
|
500
|
+
|
|
501
|
+
test('should pass validation for valid domain structure', ({ assert }) => {
|
|
502
|
+
const domain = new DataDomain()
|
|
503
|
+
|
|
504
|
+
// Create a valid structure with all elements
|
|
505
|
+
const ns1 = domain.addNamespace()
|
|
506
|
+
const m1 = ns1.addModel()
|
|
507
|
+
const m2 = domain.addModel() // Root level model (valid)
|
|
508
|
+
const e1 = m1.addEntity()
|
|
509
|
+
const e2 = m2.addEntity()
|
|
510
|
+
e1.addProperty({ type: 'string' })
|
|
511
|
+
e1.addAssociation({ key: e2.key })
|
|
512
|
+
|
|
513
|
+
// This should not throw any validation errors
|
|
514
|
+
assert.doesNotThrow(() => {
|
|
515
|
+
domain.toJSON()
|
|
516
|
+
})
|
|
517
|
+
}).tags(['@modeling', '@serialization', '@validation'])
|
|
518
|
+
|
|
519
|
+
test('should pass validation for models at root level without namespaces', ({ assert }) => {
|
|
520
|
+
const domain = new DataDomain()
|
|
521
|
+
|
|
522
|
+
// Create models directly at domain level (should be valid)
|
|
523
|
+
const m1 = domain.addModel()
|
|
524
|
+
const m2 = domain.addModel()
|
|
525
|
+
const e1 = m1.addEntity()
|
|
526
|
+
const e2 = m2.addEntity()
|
|
527
|
+
e1.addProperty({ type: 'string' })
|
|
528
|
+
e1.addAssociation({ key: e2.key })
|
|
529
|
+
|
|
530
|
+
// This should not throw any validation errors
|
|
531
|
+
assert.doesNotThrow(() => {
|
|
532
|
+
domain.toJSON()
|
|
533
|
+
})
|
|
534
|
+
}).tags(['@modeling', '@serialization', '@validation'])
|
|
535
|
+
|
|
536
|
+
test('should pass validation for foreign associations', ({ assert }) => {
|
|
537
|
+
const fd = new DataDomain()
|
|
538
|
+
const fm1 = fd.addModel()
|
|
539
|
+
const fe1 = fm1.addEntity()
|
|
540
|
+
fd.info.version = '1.0.0'
|
|
541
|
+
|
|
542
|
+
const domain = new DataDomain()
|
|
543
|
+
domain.registerForeignDomain(fd)
|
|
544
|
+
const m1 = domain.addModel()
|
|
545
|
+
const e1 = m1.addEntity()
|
|
546
|
+
e1.addAssociation({ key: fe1.key, domain: fd.key })
|
|
547
|
+
|
|
548
|
+
// Foreign associations should pass validation
|
|
549
|
+
assert.doesNotThrow(() => {
|
|
550
|
+
domain.toJSON()
|
|
551
|
+
})
|
|
552
|
+
}).tags(['@modeling', '@serialization', '@validation'])
|
|
553
|
+
})
|
|
554
|
+
|
|
555
|
+
test.group('DataDomain Lenient Mode Deserialization', () => {
|
|
556
|
+
test('should handle malformed node entries in lenient mode', ({ assert }) => {
|
|
557
|
+
const domain = new DataDomain()
|
|
558
|
+
const m1 = domain.addModel()
|
|
559
|
+
const e1 = m1.addEntity()
|
|
560
|
+
e1.addProperty({ type: 'string' })
|
|
561
|
+
|
|
562
|
+
const serialized = domain.toJSON()
|
|
563
|
+
|
|
564
|
+
// Corrupt a node entry by removing its kind
|
|
565
|
+
const corruptedSerialized = structuredClone(serialized)
|
|
566
|
+
if (corruptedSerialized.graph?.nodes?.[2]) {
|
|
567
|
+
// Find the property node and corrupt it
|
|
568
|
+
for (const node of corruptedSerialized.graph.nodes) {
|
|
569
|
+
if (
|
|
570
|
+
node.value &&
|
|
571
|
+
typeof node.value === 'object' &&
|
|
572
|
+
'kind' in node.value &&
|
|
573
|
+
node.value.kind === DomainPropertyKind
|
|
574
|
+
) {
|
|
575
|
+
delete node.value.kind
|
|
576
|
+
break
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// Should not throw in lenient mode
|
|
582
|
+
const restored = new DataDomain(corruptedSerialized, [], 'lenient')
|
|
583
|
+
|
|
584
|
+
// Should have issues recorded
|
|
585
|
+
assert.isAbove(restored.issues.length, 0)
|
|
586
|
+
const malformedIssues = restored.issues.filter((issue) => issue.type === 'malformed_entry')
|
|
587
|
+
assert.isAbove(malformedIssues.length, 0)
|
|
588
|
+
|
|
589
|
+
// Should have fewer nodes than original (corrupted node was skipped)
|
|
590
|
+
assert.isBelow([...restored.graph.nodes()].length, [...domain.graph.nodes()].length)
|
|
591
|
+
}).tags(['@modeling', '@serialization', '@lenient'])
|
|
592
|
+
|
|
593
|
+
test('should handle missing parent edges in lenient mode', ({ assert }) => {
|
|
594
|
+
const domain = new DataDomain()
|
|
595
|
+
const m1 = domain.addModel()
|
|
596
|
+
const e1 = m1.addEntity()
|
|
597
|
+
const p1 = e1.addProperty({ type: 'string' })
|
|
598
|
+
|
|
599
|
+
const serialized = domain.toJSON()
|
|
600
|
+
|
|
601
|
+
// Remove the edge from entity to property
|
|
602
|
+
const corruptedSerialized = structuredClone(serialized)
|
|
603
|
+
if (corruptedSerialized.graph?.edges) {
|
|
604
|
+
corruptedSerialized.graph.edges = corruptedSerialized.graph.edges.filter(
|
|
605
|
+
(edge) => !(edge.v === e1.key && edge.w === p1.key)
|
|
606
|
+
)
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// Should not throw in lenient mode
|
|
610
|
+
const restored = new DataDomain(corruptedSerialized, [], 'lenient')
|
|
611
|
+
|
|
612
|
+
// Should have issues recorded
|
|
613
|
+
assert.isAbove(restored.issues.length, 0)
|
|
614
|
+
const missingEdgeIssues = restored.issues.filter((issue) => issue.type === 'missing_edge')
|
|
615
|
+
assert.isAbove(missingEdgeIssues.length, 0)
|
|
616
|
+
|
|
617
|
+
// Property should be missing (was skipped due to missing parent edge)
|
|
618
|
+
assert.isUndefined(restored.findProperty(p1.key))
|
|
619
|
+
|
|
620
|
+
// But entity should still exist
|
|
621
|
+
assert.isDefined(restored.findEntity(e1.key))
|
|
622
|
+
}).tags(['@modeling', '@serialization', '@lenient'])
|
|
623
|
+
|
|
624
|
+
test('should handle missing target nodes in lenient mode', ({ assert }) => {
|
|
625
|
+
const domain = new DataDomain()
|
|
626
|
+
const m1 = domain.addModel()
|
|
627
|
+
const e1 = m1.addEntity()
|
|
628
|
+
const e2 = m1.addEntity()
|
|
629
|
+
const a1 = e1.addAssociation({ key: e2.key })
|
|
630
|
+
|
|
631
|
+
const serialized = domain.toJSON()
|
|
632
|
+
|
|
633
|
+
// Remove the target entity node but keep the edge
|
|
634
|
+
const corruptedSerialized = structuredClone(serialized)
|
|
635
|
+
if (corruptedSerialized.graph?.nodes) {
|
|
636
|
+
corruptedSerialized.graph.nodes = corruptedSerialized.graph.nodes.filter((node) => node.v !== e2.key)
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// Should not throw in lenient mode
|
|
640
|
+
const restored = new DataDomain(corruptedSerialized, [], 'lenient')
|
|
641
|
+
|
|
642
|
+
// Should have issues recorded
|
|
643
|
+
assert.isAbove(restored.issues.length, 0)
|
|
644
|
+
const missingNodeIssues = restored.issues.filter((issue) => issue.type === 'missing_node')
|
|
645
|
+
assert.isAbove(missingNodeIssues.length, 0)
|
|
646
|
+
|
|
647
|
+
// Source entity and association should exist
|
|
648
|
+
assert.isDefined(restored.findEntity(e1.key))
|
|
649
|
+
assert.isDefined(restored.findAssociation(a1.key))
|
|
650
|
+
|
|
651
|
+
// Target entity should be missing
|
|
652
|
+
assert.isUndefined(restored.findEntity(e2.key))
|
|
653
|
+
|
|
654
|
+
// Association should have no targets
|
|
655
|
+
const restoredAssociation = restored.findAssociation(a1.key)
|
|
656
|
+
assert.equal([...restoredAssociation!.listTargets()].length, 0)
|
|
657
|
+
}).tags(['@modeling', '@serialization', '@lenient'])
|
|
658
|
+
|
|
659
|
+
test('should handle invalid parent relationships in lenient mode', ({ assert }) => {
|
|
660
|
+
const domain = new DataDomain()
|
|
661
|
+
const ns1 = domain.addNamespace()
|
|
662
|
+
const m1 = ns1.addModel()
|
|
663
|
+
const e1 = m1.addEntity()
|
|
664
|
+
|
|
665
|
+
const serialized = domain.toJSON()
|
|
666
|
+
|
|
667
|
+
// Corrupt parent relationship - make entity point to non-existent parent
|
|
668
|
+
const corruptedSerialized = structuredClone(serialized)
|
|
669
|
+
if (corruptedSerialized.graph?.nodes) {
|
|
670
|
+
for (const node of corruptedSerialized.graph.nodes) {
|
|
671
|
+
if (node.v === e1.key && node.parents) {
|
|
672
|
+
node.parents = ['non-existent-parent'] // Invalid - non-existent parent
|
|
673
|
+
break
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// Should not throw in lenient mode
|
|
679
|
+
const restored = new DataDomain(corruptedSerialized, [], 'lenient')
|
|
680
|
+
|
|
681
|
+
// Should have issues recorded about missing parent node
|
|
682
|
+
assert.isAbove(restored.issues.length, 0)
|
|
683
|
+
const missingNodeIssues = restored.issues.filter((issue) => issue.type === 'missing_node')
|
|
684
|
+
assert.isAbove(missingNodeIssues.length, 0)
|
|
685
|
+
|
|
686
|
+
// Entity should still exist but may not have proper parent relationship
|
|
687
|
+
assert.isDefined(restored.findEntity(e1.key))
|
|
688
|
+
}).tags(['@modeling', '@serialization', '@lenient'])
|
|
689
|
+
|
|
690
|
+
test('should handle missing foreign dependencies gracefully in lenient mode', ({ assert }) => {
|
|
691
|
+
const fd = new DataDomain()
|
|
692
|
+
const fm1 = fd.addModel()
|
|
693
|
+
const fe1 = fm1.addEntity()
|
|
694
|
+
fd.info.version = '1.0.0'
|
|
695
|
+
|
|
696
|
+
const domain = new DataDomain()
|
|
697
|
+
domain.registerForeignDomain(fd)
|
|
698
|
+
const m1 = domain.addModel()
|
|
699
|
+
const e1 = m1.addEntity()
|
|
700
|
+
e1.addAssociation({ key: fe1.key, domain: fd.key })
|
|
701
|
+
|
|
702
|
+
const serialized = domain.toJSON()
|
|
703
|
+
|
|
704
|
+
// Deserialize without providing the foreign dependency
|
|
705
|
+
const restored = new DataDomain(serialized, [], 'lenient')
|
|
706
|
+
|
|
707
|
+
// Should have issues recorded about missing dependency
|
|
708
|
+
assert.isAbove(restored.issues.length, 0)
|
|
709
|
+
const missingNodeIssues = restored.issues.filter(
|
|
710
|
+
(issue) => issue.type === 'missing_node' && issue.message.includes('foreign')
|
|
711
|
+
)
|
|
712
|
+
assert.isAbove(missingNodeIssues.length, 0)
|
|
713
|
+
|
|
714
|
+
// Local entities should still exist
|
|
715
|
+
assert.isDefined(restored.findEntity(e1.key))
|
|
716
|
+
|
|
717
|
+
// Foreign entity should not exist
|
|
718
|
+
assert.isUndefined(restored.findForeignEntity(fe1.key, fd.key))
|
|
719
|
+
}).tags(['@modeling', '@serialization', '@lenient'])
|
|
720
|
+
|
|
721
|
+
test('should handle unknown node kinds in lenient mode', ({ assert }) => {
|
|
722
|
+
const domain = new DataDomain()
|
|
723
|
+
const m1 = domain.addModel()
|
|
724
|
+
|
|
725
|
+
const serialized = domain.toJSON()
|
|
726
|
+
|
|
727
|
+
// Add a node with unknown kind
|
|
728
|
+
const corruptedSerialized = structuredClone(serialized)
|
|
729
|
+
if (corruptedSerialized.graph?.nodes) {
|
|
730
|
+
corruptedSerialized.graph.nodes.push({
|
|
731
|
+
v: 'unknown-node',
|
|
732
|
+
value: {
|
|
733
|
+
kind: 'UnknownKind',
|
|
734
|
+
key: 'unknown-node',
|
|
735
|
+
info: { name: 'Unknown Node' },
|
|
736
|
+
},
|
|
737
|
+
})
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// Should not throw in lenient mode
|
|
741
|
+
const restored = new DataDomain(corruptedSerialized, [], 'lenient')
|
|
742
|
+
|
|
743
|
+
// Should have issues recorded
|
|
744
|
+
assert.isAbove(restored.issues.length, 0)
|
|
745
|
+
const unknownKindIssues = restored.issues.filter((issue) => issue.type === 'unknown_kind')
|
|
746
|
+
assert.isAbove(unknownKindIssues.length, 0)
|
|
747
|
+
|
|
748
|
+
// Original model should still exist
|
|
749
|
+
assert.isDefined(restored.findModel(m1.key))
|
|
750
|
+
|
|
751
|
+
// Unknown node should not exist in graph
|
|
752
|
+
assert.isFalse(restored.graph.hasNode('unknown-node'))
|
|
753
|
+
}).tags(['@modeling', '@serialization', '@lenient'])
|
|
754
|
+
|
|
755
|
+
test('should collect multiple issues and continue processing in lenient mode', ({ assert }) => {
|
|
756
|
+
const domain = new DataDomain()
|
|
757
|
+
const m1 = domain.addModel()
|
|
758
|
+
const e1 = m1.addEntity()
|
|
759
|
+
const e2 = m1.addEntity()
|
|
760
|
+
const p1 = e1.addProperty({ type: 'string' })
|
|
761
|
+
e1.addAssociation({ key: e2.key })
|
|
762
|
+
|
|
763
|
+
const serialized = domain.toJSON()
|
|
764
|
+
|
|
765
|
+
// Introduce multiple types of corruption
|
|
766
|
+
const corruptedSerialized = structuredClone(serialized)
|
|
767
|
+
if (corruptedSerialized.graph?.nodes && corruptedSerialized.graph?.edges) {
|
|
768
|
+
// 1. Remove kind from property node
|
|
769
|
+
for (const node of corruptedSerialized.graph.nodes) {
|
|
770
|
+
if (
|
|
771
|
+
node.value &&
|
|
772
|
+
typeof node.value === 'object' &&
|
|
773
|
+
'kind' in node.value &&
|
|
774
|
+
node.value.kind === DomainPropertyKind
|
|
775
|
+
) {
|
|
776
|
+
delete node.value.kind
|
|
777
|
+
break
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// 2. Remove target entity
|
|
782
|
+
corruptedSerialized.graph.nodes = corruptedSerialized.graph.nodes.filter((node) => node.v !== e2.key)
|
|
783
|
+
|
|
784
|
+
// 3. Add unknown kind node
|
|
785
|
+
corruptedSerialized.graph.nodes.push({
|
|
786
|
+
v: 'unknown-node',
|
|
787
|
+
value: {
|
|
788
|
+
kind: 'UnknownKind',
|
|
789
|
+
key: 'unknown-node',
|
|
790
|
+
info: { name: 'Unknown Node' },
|
|
791
|
+
},
|
|
792
|
+
})
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// Should not throw in lenient mode
|
|
796
|
+
const restored = new DataDomain(corruptedSerialized, [], 'lenient')
|
|
797
|
+
|
|
798
|
+
// Should have multiple issues recorded
|
|
799
|
+
assert.isAtLeast(restored.issues.length, 3)
|
|
800
|
+
|
|
801
|
+
// Should have different types of issues
|
|
802
|
+
const issueTypes = new Set(restored.issues.map((issue) => issue.type))
|
|
803
|
+
assert.isTrue(issueTypes.has('malformed_entry') || issueTypes.has('unknown_kind'))
|
|
804
|
+
assert.isTrue(issueTypes.has('missing_node'))
|
|
805
|
+
|
|
806
|
+
// Should still have functional entities where possible
|
|
807
|
+
assert.isDefined(restored.findEntity(e1.key))
|
|
808
|
+
assert.isDefined(restored.findModel(m1.key))
|
|
809
|
+
|
|
810
|
+
// Corrupted elements should be missing
|
|
811
|
+
assert.isUndefined(restored.findProperty(p1.key)) // Property was corrupted
|
|
812
|
+
assert.isUndefined(restored.findEntity(e2.key)) // Entity was removed
|
|
813
|
+
}).tags(['@modeling', '@serialization', '@lenient'])
|
|
814
|
+
|
|
815
|
+
test('should have empty issues array in strict mode on successful deserialization', ({ assert }) => {
|
|
816
|
+
const domain = new DataDomain()
|
|
817
|
+
const m1 = domain.addModel()
|
|
818
|
+
const e1 = m1.addEntity()
|
|
819
|
+
e1.addProperty({ type: 'string' })
|
|
820
|
+
|
|
821
|
+
const serialized = domain.toJSON()
|
|
822
|
+
const restored = new DataDomain(serialized, [], 'strict')
|
|
823
|
+
|
|
824
|
+
// Should have no issues in strict mode for valid data
|
|
825
|
+
assert.equal(restored.issues.length, 0)
|
|
826
|
+
}).tags(['@modeling', '@serialization', '@strict'])
|
|
827
|
+
|
|
828
|
+
test('should throw in strict mode but collect issues in lenient mode for same corrupted data', ({ assert }) => {
|
|
829
|
+
const domain = new DataDomain()
|
|
830
|
+
const m1 = domain.addModel()
|
|
831
|
+
const e1 = m1.addEntity()
|
|
832
|
+
e1.addProperty({ type: 'string' })
|
|
833
|
+
|
|
834
|
+
const serialized = domain.toJSON()
|
|
835
|
+
|
|
836
|
+
// Corrupt the data
|
|
837
|
+
const corruptedSerialized = structuredClone(serialized)
|
|
838
|
+
if (corruptedSerialized.graph?.nodes?.[2]) {
|
|
839
|
+
for (const node of corruptedSerialized.graph.nodes) {
|
|
840
|
+
if (
|
|
841
|
+
node.value &&
|
|
842
|
+
typeof node.value === 'object' &&
|
|
843
|
+
'kind' in node.value &&
|
|
844
|
+
node.value.kind === DomainPropertyKind
|
|
845
|
+
) {
|
|
846
|
+
delete node.value.kind
|
|
847
|
+
break
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// Should throw in strict mode
|
|
853
|
+
assert.throws(() => {
|
|
854
|
+
new DataDomain(corruptedSerialized, [], 'strict')
|
|
855
|
+
})
|
|
856
|
+
|
|
857
|
+
// Should not throw in lenient mode
|
|
858
|
+
const restored = new DataDomain(corruptedSerialized, [], 'lenient')
|
|
859
|
+
assert.isAbove(restored.issues.length, 0)
|
|
860
|
+
}).tags(['@modeling', '@serialization', '@lenient', '@strict'])
|
|
861
|
+
|
|
862
|
+
test('should provide helpful issue messages and context in lenient mode', ({ assert }) => {
|
|
863
|
+
const domain = new DataDomain()
|
|
864
|
+
const m1 = domain.addModel()
|
|
865
|
+
const e1 = m1.addEntity()
|
|
866
|
+
const p1 = e1.addProperty({ type: 'string', info: { name: 'testProperty' } })
|
|
867
|
+
|
|
868
|
+
const serialized = domain.toJSON()
|
|
869
|
+
|
|
870
|
+
// Remove the edge from entity to property
|
|
871
|
+
const corruptedSerialized = structuredClone(serialized)
|
|
872
|
+
if (corruptedSerialized.graph?.edges) {
|
|
873
|
+
corruptedSerialized.graph.edges = corruptedSerialized.graph.edges.filter(
|
|
874
|
+
(edge) => !(edge.v === e1.key && edge.w === p1.key)
|
|
875
|
+
)
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
const restored = new DataDomain(corruptedSerialized, [], 'lenient')
|
|
879
|
+
|
|
880
|
+
// Should have detailed issue information
|
|
881
|
+
assert.isAbove(restored.issues.length, 0)
|
|
882
|
+
const missingEdgeIssue = restored.issues.find((issue) => issue.type === 'missing_edge')
|
|
883
|
+
assert.isDefined(missingEdgeIssue)
|
|
884
|
+
|
|
885
|
+
// Should have helpful message
|
|
886
|
+
assert.include(missingEdgeIssue!.message, 'testProperty')
|
|
887
|
+
assert.include(missingEdgeIssue!.message.toLowerCase(), 'parent entity edge')
|
|
888
|
+
|
|
889
|
+
// Should have affected key
|
|
890
|
+
assert.equal(missingEdgeIssue!.affectedKey, p1.key)
|
|
891
|
+
|
|
892
|
+
// Should have resolution info
|
|
893
|
+
assert.include(missingEdgeIssue!.resolution!, 'skipped')
|
|
894
|
+
|
|
895
|
+
// Should have appropriate severity
|
|
896
|
+
assert.equal(missingEdgeIssue!.severity, 'error')
|
|
897
|
+
}).tags(['@modeling', '@serialization', '@lenient'])
|
|
898
|
+
})
|