@api-client/core 0.18.16 → 0.18.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/build/src/{modeling → decorators}/observed.d.ts +3 -3
  2. package/build/src/decorators/observed.d.ts.map +1 -0
  3. package/build/src/{modeling → decorators}/observed.js +4 -4
  4. package/build/src/decorators/observed.js.map +1 -0
  5. package/build/src/modeling/ApiModel.js +1 -1
  6. package/build/src/modeling/ApiModel.js.map +1 -1
  7. package/build/src/modeling/DataDomain.d.ts +7 -2
  8. package/build/src/modeling/DataDomain.d.ts.map +1 -1
  9. package/build/src/modeling/DataDomain.js +15 -2
  10. package/build/src/modeling/DataDomain.js.map +1 -1
  11. package/build/src/modeling/DomainAssociation.d.ts +7 -0
  12. package/build/src/modeling/DomainAssociation.d.ts.map +1 -1
  13. package/build/src/modeling/DomainAssociation.js +44 -1
  14. package/build/src/modeling/DomainAssociation.js.map +1 -1
  15. package/build/src/modeling/DomainEntity.d.ts +6 -0
  16. package/build/src/modeling/DomainEntity.d.ts.map +1 -1
  17. package/build/src/modeling/DomainEntity.js +21 -1
  18. package/build/src/modeling/DomainEntity.js.map +1 -1
  19. package/build/src/modeling/DomainModel.js +1 -1
  20. package/build/src/modeling/DomainModel.js.map +1 -1
  21. package/build/src/modeling/DomainNamespace.js +1 -1
  22. package/build/src/modeling/DomainNamespace.js.map +1 -1
  23. package/build/src/modeling/DomainProperty.d.ts +5 -0
  24. package/build/src/modeling/DomainProperty.d.ts.map +1 -1
  25. package/build/src/modeling/DomainProperty.js +38 -1
  26. package/build/src/modeling/DomainProperty.js.map +1 -1
  27. package/build/src/modeling/DomainSerialization.d.ts +6 -3
  28. package/build/src/modeling/DomainSerialization.d.ts.map +1 -1
  29. package/build/src/modeling/DomainSerialization.js +374 -52
  30. package/build/src/modeling/DomainSerialization.js.map +1 -1
  31. package/build/src/modeling/types.d.ts +69 -2
  32. package/build/src/modeling/types.d.ts.map +1 -1
  33. package/build/src/modeling/types.js.map +1 -1
  34. package/build/src/models/Thing.js +1 -1
  35. package/build/src/models/Thing.js.map +1 -1
  36. package/build/tsconfig.tsbuildinfo +1 -1
  37. package/data/models/example-generator-api.json +10 -10
  38. package/package.json +2 -1
  39. package/src/{modeling → decorators}/observed.ts +5 -5
  40. package/src/modeling/ApiModel.ts +1 -1
  41. package/src/modeling/DataDomain.ts +24 -3
  42. package/src/modeling/DomainAssociation.ts +51 -1
  43. package/src/modeling/DomainEntity.ts +24 -1
  44. package/src/modeling/DomainModel.ts +1 -1
  45. package/src/modeling/DomainNamespace.ts +1 -1
  46. package/src/modeling/DomainProperty.ts +43 -1
  47. package/src/modeling/DomainSerialization.ts +440 -56
  48. package/src/modeling/types.ts +73 -2
  49. package/src/models/Thing.ts +1 -1
  50. package/tests/unit/decorators/observed.spec.ts +527 -0
  51. package/tests/unit/modeling/data_domain_serialization.spec.ts +508 -0
  52. package/tests/unit/modeling/domain_asociation.spec.ts +376 -0
  53. package/tests/unit/modeling/domain_entity.spec.ts +147 -0
  54. package/tests/unit/modeling/domain_property.spec.ts +273 -0
  55. package/build/src/modeling/observed.d.ts.map +0 -1
  56. package/build/src/modeling/observed.js.map +0 -1
@@ -392,3 +392,511 @@ 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 not 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
+ // Note, we specifically removed this from the validation as it would throw errors
470
+ // when associations were created without targets. This is not a structural issue and because
471
+ // of that, it shouldn't be considered a validation error in this context.
472
+ // It is reported through the validator, though.
473
+ assert.doesNotThrow(() => domain.toJSON())
474
+ }).tags(['@modeling', '@serialization', '@validation'])
475
+
476
+ test('should throw validation error when association references non-existent target', ({ assert }) => {
477
+ const domain = new DataDomain()
478
+ const m1 = domain.addModel()
479
+ const e1 = m1.addEntity()
480
+ const e2 = m1.addEntity()
481
+ const a1 = e1.addAssociation({ key: e2.key })
482
+
483
+ // Remove the target entity but keep the edge pointing to it
484
+ domain.graph.removeNode(e2.key)
485
+ // Re-add the association with a broken target
486
+ domain.graph.setEdge(a1.key, 'non-existent-entity', { type: 'association' })
487
+
488
+ assert.throws(() => domain.toJSON())
489
+ }).tags(['@modeling', '@serialization', '@validation'])
490
+
491
+ test('should throw validation error when association references non-entity target', ({ assert }) => {
492
+ const domain = new DataDomain()
493
+ const m1 = domain.addModel()
494
+ const e1 = m1.addEntity()
495
+ const e2 = m1.addEntity()
496
+ const a1 = e1.addAssociation({ key: e2.key })
497
+
498
+ // Remove the proper target and make it point to the model instead
499
+ domain.graph.removeEdge(a1.key, e2.key)
500
+ domain.graph.setEdge(a1.key, m1.key, { type: 'association' })
501
+
502
+ assert.throws(() => domain.toJSON())
503
+ }).tags(['@modeling', '@serialization', '@validation'])
504
+
505
+ test('should pass validation for valid domain structure', ({ assert }) => {
506
+ const domain = new DataDomain()
507
+
508
+ // Create a valid structure with all elements
509
+ const ns1 = domain.addNamespace()
510
+ const m1 = ns1.addModel()
511
+ const m2 = domain.addModel() // Root level model (valid)
512
+ const e1 = m1.addEntity()
513
+ const e2 = m2.addEntity()
514
+ e1.addProperty({ type: 'string' })
515
+ e1.addAssociation({ key: e2.key })
516
+
517
+ // This should not throw any validation errors
518
+ assert.doesNotThrow(() => {
519
+ domain.toJSON()
520
+ })
521
+ }).tags(['@modeling', '@serialization', '@validation'])
522
+
523
+ test('should pass validation for models at root level without namespaces', ({ assert }) => {
524
+ const domain = new DataDomain()
525
+
526
+ // Create models directly at domain level (should be valid)
527
+ const m1 = domain.addModel()
528
+ const m2 = domain.addModel()
529
+ const e1 = m1.addEntity()
530
+ const e2 = m2.addEntity()
531
+ e1.addProperty({ type: 'string' })
532
+ e1.addAssociation({ key: e2.key })
533
+
534
+ // This should not throw any validation errors
535
+ assert.doesNotThrow(() => {
536
+ domain.toJSON()
537
+ })
538
+ }).tags(['@modeling', '@serialization', '@validation'])
539
+
540
+ test('should pass validation for foreign associations', ({ assert }) => {
541
+ const fd = new DataDomain()
542
+ const fm1 = fd.addModel()
543
+ const fe1 = fm1.addEntity()
544
+ fd.info.version = '1.0.0'
545
+
546
+ const domain = new DataDomain()
547
+ domain.registerForeignDomain(fd)
548
+ const m1 = domain.addModel()
549
+ const e1 = m1.addEntity()
550
+ e1.addAssociation({ key: fe1.key, domain: fd.key })
551
+
552
+ // Foreign associations should pass validation
553
+ assert.doesNotThrow(() => {
554
+ domain.toJSON()
555
+ })
556
+ }).tags(['@modeling', '@serialization', '@validation'])
557
+ })
558
+
559
+ test.group('DataDomain Lenient Mode Deserialization', () => {
560
+ test('should handle malformed node entries in lenient mode', ({ assert }) => {
561
+ const domain = new DataDomain()
562
+ const m1 = domain.addModel()
563
+ const e1 = m1.addEntity()
564
+ e1.addProperty({ type: 'string' })
565
+
566
+ const serialized = domain.toJSON()
567
+
568
+ // Corrupt a node entry by removing its kind
569
+ const corruptedSerialized = structuredClone(serialized)
570
+ if (corruptedSerialized.graph?.nodes?.[2]) {
571
+ // Find the property node and corrupt it
572
+ for (const node of corruptedSerialized.graph.nodes) {
573
+ if (
574
+ node.value &&
575
+ typeof node.value === 'object' &&
576
+ 'kind' in node.value &&
577
+ node.value.kind === DomainPropertyKind
578
+ ) {
579
+ delete node.value.kind
580
+ break
581
+ }
582
+ }
583
+ }
584
+
585
+ // Should not throw in lenient mode
586
+ const restored = new DataDomain(corruptedSerialized, [], 'lenient')
587
+
588
+ // Should have issues recorded
589
+ assert.isAbove(restored.issues.length, 0)
590
+ const malformedIssues = restored.issues.filter((issue) => issue.type === 'malformed_entry')
591
+ assert.isAbove(malformedIssues.length, 0)
592
+
593
+ // Should have fewer nodes than original (corrupted node was skipped)
594
+ assert.isBelow([...restored.graph.nodes()].length, [...domain.graph.nodes()].length)
595
+ }).tags(['@modeling', '@serialization', '@lenient'])
596
+
597
+ test('should handle missing parent edges in lenient mode', ({ assert }) => {
598
+ const domain = new DataDomain()
599
+ const m1 = domain.addModel()
600
+ const e1 = m1.addEntity()
601
+ const p1 = e1.addProperty({ type: 'string' })
602
+
603
+ const serialized = domain.toJSON()
604
+
605
+ // Remove the edge from entity to property
606
+ const corruptedSerialized = structuredClone(serialized)
607
+ if (corruptedSerialized.graph?.edges) {
608
+ corruptedSerialized.graph.edges = corruptedSerialized.graph.edges.filter(
609
+ (edge) => !(edge.v === e1.key && edge.w === p1.key)
610
+ )
611
+ }
612
+
613
+ // Should not throw in lenient mode
614
+ const restored = new DataDomain(corruptedSerialized, [], 'lenient')
615
+
616
+ // Should have issues recorded
617
+ assert.isAbove(restored.issues.length, 0)
618
+ const missingEdgeIssues = restored.issues.filter((issue) => issue.type === 'missing_edge')
619
+ assert.isAbove(missingEdgeIssues.length, 0)
620
+
621
+ // Property should be missing (was skipped due to missing parent edge)
622
+ assert.isUndefined(restored.findProperty(p1.key))
623
+
624
+ // But entity should still exist
625
+ assert.isDefined(restored.findEntity(e1.key))
626
+ }).tags(['@modeling', '@serialization', '@lenient'])
627
+
628
+ test('should handle missing target nodes in lenient mode', ({ assert }) => {
629
+ const domain = new DataDomain()
630
+ const m1 = domain.addModel()
631
+ const e1 = m1.addEntity()
632
+ const e2 = m1.addEntity()
633
+ const a1 = e1.addAssociation({ key: e2.key })
634
+
635
+ const serialized = domain.toJSON()
636
+
637
+ // Remove the target entity node but keep the edge
638
+ const corruptedSerialized = structuredClone(serialized)
639
+ if (corruptedSerialized.graph?.nodes) {
640
+ corruptedSerialized.graph.nodes = corruptedSerialized.graph.nodes.filter((node) => node.v !== e2.key)
641
+ }
642
+
643
+ // Should not throw in lenient mode
644
+ const restored = new DataDomain(corruptedSerialized, [], 'lenient')
645
+
646
+ // Should have issues recorded
647
+ assert.isAbove(restored.issues.length, 0)
648
+ const missingNodeIssues = restored.issues.filter((issue) => issue.type === 'missing_node')
649
+ assert.isAbove(missingNodeIssues.length, 0)
650
+
651
+ // Source entity and association should exist
652
+ assert.isDefined(restored.findEntity(e1.key))
653
+ assert.isDefined(restored.findAssociation(a1.key))
654
+
655
+ // Target entity should be missing
656
+ assert.isUndefined(restored.findEntity(e2.key))
657
+
658
+ // Association should have no targets
659
+ const restoredAssociation = restored.findAssociation(a1.key)
660
+ assert.equal([...restoredAssociation!.listTargets()].length, 0)
661
+ }).tags(['@modeling', '@serialization', '@lenient'])
662
+
663
+ test('should handle invalid parent relationships in lenient mode', ({ assert }) => {
664
+ const domain = new DataDomain()
665
+ const ns1 = domain.addNamespace()
666
+ const m1 = ns1.addModel()
667
+ const e1 = m1.addEntity()
668
+
669
+ const serialized = domain.toJSON()
670
+
671
+ // Corrupt parent relationship - make entity point to non-existent parent
672
+ const corruptedSerialized = structuredClone(serialized)
673
+ if (corruptedSerialized.graph?.nodes) {
674
+ for (const node of corruptedSerialized.graph.nodes) {
675
+ if (node.v === e1.key && node.parents) {
676
+ node.parents = ['non-existent-parent'] // Invalid - non-existent parent
677
+ break
678
+ }
679
+ }
680
+ }
681
+
682
+ // Should not throw in lenient mode
683
+ const restored = new DataDomain(corruptedSerialized, [], 'lenient')
684
+
685
+ // Should have issues recorded about missing parent node
686
+ assert.isAbove(restored.issues.length, 0)
687
+ const missingNodeIssues = restored.issues.filter((issue) => issue.type === 'missing_node')
688
+ assert.isAbove(missingNodeIssues.length, 0)
689
+
690
+ // Entity should still exist but may not have proper parent relationship
691
+ assert.isDefined(restored.findEntity(e1.key))
692
+ }).tags(['@modeling', '@serialization', '@lenient'])
693
+
694
+ test('should handle missing foreign dependencies gracefully in lenient mode', ({ assert }) => {
695
+ const fd = new DataDomain()
696
+ const fm1 = fd.addModel()
697
+ const fe1 = fm1.addEntity()
698
+ fd.info.version = '1.0.0'
699
+
700
+ const domain = new DataDomain()
701
+ domain.registerForeignDomain(fd)
702
+ const m1 = domain.addModel()
703
+ const e1 = m1.addEntity()
704
+ e1.addAssociation({ key: fe1.key, domain: fd.key })
705
+
706
+ const serialized = domain.toJSON()
707
+
708
+ // Deserialize without providing the foreign dependency
709
+ const restored = new DataDomain(serialized, [], 'lenient')
710
+
711
+ // Should have issues recorded about missing dependency
712
+ assert.isAbove(restored.issues.length, 0)
713
+ const missingNodeIssues = restored.issues.filter(
714
+ (issue) => issue.type === 'missing_node' && issue.message.includes('foreign')
715
+ )
716
+ assert.isAbove(missingNodeIssues.length, 0)
717
+
718
+ // Local entities should still exist
719
+ assert.isDefined(restored.findEntity(e1.key))
720
+
721
+ // Foreign entity should not exist
722
+ assert.isUndefined(restored.findForeignEntity(fe1.key, fd.key))
723
+ }).tags(['@modeling', '@serialization', '@lenient'])
724
+
725
+ test('should handle unknown node kinds in lenient mode', ({ assert }) => {
726
+ const domain = new DataDomain()
727
+ const m1 = domain.addModel()
728
+
729
+ const serialized = domain.toJSON()
730
+
731
+ // Add a node with unknown kind
732
+ const corruptedSerialized = structuredClone(serialized)
733
+ if (corruptedSerialized.graph?.nodes) {
734
+ corruptedSerialized.graph.nodes.push({
735
+ v: 'unknown-node',
736
+ value: {
737
+ kind: 'UnknownKind',
738
+ key: 'unknown-node',
739
+ info: { name: 'Unknown Node' },
740
+ },
741
+ })
742
+ }
743
+
744
+ // Should not throw in lenient mode
745
+ const restored = new DataDomain(corruptedSerialized, [], 'lenient')
746
+
747
+ // Should have issues recorded
748
+ assert.isAbove(restored.issues.length, 0)
749
+ const unknownKindIssues = restored.issues.filter((issue) => issue.type === 'unknown_kind')
750
+ assert.isAbove(unknownKindIssues.length, 0)
751
+
752
+ // Original model should still exist
753
+ assert.isDefined(restored.findModel(m1.key))
754
+
755
+ // Unknown node should not exist in graph
756
+ assert.isFalse(restored.graph.hasNode('unknown-node'))
757
+ }).tags(['@modeling', '@serialization', '@lenient'])
758
+
759
+ test('should collect multiple issues and continue processing in lenient mode', ({ assert }) => {
760
+ const domain = new DataDomain()
761
+ const m1 = domain.addModel()
762
+ const e1 = m1.addEntity()
763
+ const e2 = m1.addEntity()
764
+ const p1 = e1.addProperty({ type: 'string' })
765
+ e1.addAssociation({ key: e2.key })
766
+
767
+ const serialized = domain.toJSON()
768
+
769
+ // Introduce multiple types of corruption
770
+ const corruptedSerialized = structuredClone(serialized)
771
+ if (corruptedSerialized.graph?.nodes && corruptedSerialized.graph?.edges) {
772
+ // 1. Remove kind from property node
773
+ for (const node of corruptedSerialized.graph.nodes) {
774
+ if (
775
+ node.value &&
776
+ typeof node.value === 'object' &&
777
+ 'kind' in node.value &&
778
+ node.value.kind === DomainPropertyKind
779
+ ) {
780
+ delete node.value.kind
781
+ break
782
+ }
783
+ }
784
+
785
+ // 2. Remove target entity
786
+ corruptedSerialized.graph.nodes = corruptedSerialized.graph.nodes.filter((node) => node.v !== e2.key)
787
+
788
+ // 3. Add unknown kind node
789
+ corruptedSerialized.graph.nodes.push({
790
+ v: 'unknown-node',
791
+ value: {
792
+ kind: 'UnknownKind',
793
+ key: 'unknown-node',
794
+ info: { name: 'Unknown Node' },
795
+ },
796
+ })
797
+ }
798
+
799
+ // Should not throw in lenient mode
800
+ const restored = new DataDomain(corruptedSerialized, [], 'lenient')
801
+
802
+ // Should have multiple issues recorded
803
+ assert.isAtLeast(restored.issues.length, 3)
804
+
805
+ // Should have different types of issues
806
+ const issueTypes = new Set(restored.issues.map((issue) => issue.type))
807
+ assert.isTrue(issueTypes.has('malformed_entry') || issueTypes.has('unknown_kind'))
808
+ assert.isTrue(issueTypes.has('missing_node'))
809
+
810
+ // Should still have functional entities where possible
811
+ assert.isDefined(restored.findEntity(e1.key))
812
+ assert.isDefined(restored.findModel(m1.key))
813
+
814
+ // Corrupted elements should be missing
815
+ assert.isUndefined(restored.findProperty(p1.key)) // Property was corrupted
816
+ assert.isUndefined(restored.findEntity(e2.key)) // Entity was removed
817
+ }).tags(['@modeling', '@serialization', '@lenient'])
818
+
819
+ test('should have empty issues array in strict mode on successful deserialization', ({ assert }) => {
820
+ const domain = new DataDomain()
821
+ const m1 = domain.addModel()
822
+ const e1 = m1.addEntity()
823
+ e1.addProperty({ type: 'string' })
824
+
825
+ const serialized = domain.toJSON()
826
+ const restored = new DataDomain(serialized, [], 'strict')
827
+
828
+ // Should have no issues in strict mode for valid data
829
+ assert.equal(restored.issues.length, 0)
830
+ }).tags(['@modeling', '@serialization', '@strict'])
831
+
832
+ test('should throw in strict mode but collect issues in lenient mode for same corrupted data', ({ assert }) => {
833
+ const domain = new DataDomain()
834
+ const m1 = domain.addModel()
835
+ const e1 = m1.addEntity()
836
+ e1.addProperty({ type: 'string' })
837
+
838
+ const serialized = domain.toJSON()
839
+
840
+ // Corrupt the data
841
+ const corruptedSerialized = structuredClone(serialized)
842
+ if (corruptedSerialized.graph?.nodes?.[2]) {
843
+ for (const node of corruptedSerialized.graph.nodes) {
844
+ if (
845
+ node.value &&
846
+ typeof node.value === 'object' &&
847
+ 'kind' in node.value &&
848
+ node.value.kind === DomainPropertyKind
849
+ ) {
850
+ delete node.value.kind
851
+ break
852
+ }
853
+ }
854
+ }
855
+
856
+ // Should throw in strict mode
857
+ assert.throws(() => {
858
+ new DataDomain(corruptedSerialized, [], 'strict')
859
+ })
860
+
861
+ // Should not throw in lenient mode
862
+ const restored = new DataDomain(corruptedSerialized, [], 'lenient')
863
+ assert.isAbove(restored.issues.length, 0)
864
+ }).tags(['@modeling', '@serialization', '@lenient', '@strict'])
865
+
866
+ test('should provide helpful issue messages and context in lenient mode', ({ assert }) => {
867
+ const domain = new DataDomain()
868
+ const m1 = domain.addModel()
869
+ const e1 = m1.addEntity()
870
+ const p1 = e1.addProperty({ type: 'string', info: { name: 'testProperty' } })
871
+
872
+ const serialized = domain.toJSON()
873
+
874
+ // Remove the edge from entity to property
875
+ const corruptedSerialized = structuredClone(serialized)
876
+ if (corruptedSerialized.graph?.edges) {
877
+ corruptedSerialized.graph.edges = corruptedSerialized.graph.edges.filter(
878
+ (edge) => !(edge.v === e1.key && edge.w === p1.key)
879
+ )
880
+ }
881
+
882
+ const restored = new DataDomain(corruptedSerialized, [], 'lenient')
883
+
884
+ // Should have detailed issue information
885
+ assert.isAbove(restored.issues.length, 0)
886
+ const missingEdgeIssue = restored.issues.find((issue) => issue.type === 'missing_edge')
887
+ assert.isDefined(missingEdgeIssue)
888
+
889
+ // Should have helpful message
890
+ assert.include(missingEdgeIssue!.message, 'testProperty')
891
+ assert.include(missingEdgeIssue!.message.toLowerCase(), 'parent entity edge')
892
+
893
+ // Should have affected key
894
+ assert.equal(missingEdgeIssue!.affectedKey, p1.key)
895
+
896
+ // Should have resolution info
897
+ assert.include(missingEdgeIssue!.resolution!, 'skipped')
898
+
899
+ // Should have appropriate severity
900
+ assert.equal(missingEdgeIssue!.severity, 'error')
901
+ }).tags(['@modeling', '@serialization', '@lenient'])
902
+ })