@api-client/core 0.18.17 → 0.18.19
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 → decorators}/observed.d.ts +3 -3
- package/build/src/decorators/observed.d.ts.map +1 -0
- package/build/src/{modeling → decorators}/observed.js +4 -4
- package/build/src/decorators/observed.js.map +1 -0
- package/build/src/modeling/ApiModel.js +1 -1
- package/build/src/modeling/ApiModel.js.map +1 -1
- package/build/src/modeling/DataDomain.d.ts +35 -1
- package/build/src/modeling/DataDomain.d.ts.map +1 -1
- package/build/src/modeling/DataDomain.js +120 -0
- package/build/src/modeling/DataDomain.js.map +1 -1
- package/build/src/modeling/DomainAssociation.d.ts +7 -0
- package/build/src/modeling/DomainAssociation.d.ts.map +1 -1
- package/build/src/modeling/DomainAssociation.js +44 -1
- package/build/src/modeling/DomainAssociation.js.map +1 -1
- package/build/src/modeling/DomainEntity.d.ts +6 -0
- package/build/src/modeling/DomainEntity.d.ts.map +1 -1
- package/build/src/modeling/DomainEntity.js +21 -1
- package/build/src/modeling/DomainEntity.js.map +1 -1
- package/build/src/modeling/DomainModel.js +1 -1
- package/build/src/modeling/DomainModel.js.map +1 -1
- package/build/src/modeling/DomainNamespace.js +1 -1
- package/build/src/modeling/DomainNamespace.js.map +1 -1
- package/build/src/modeling/DomainProperty.d.ts +15 -0
- package/build/src/modeling/DomainProperty.d.ts.map +1 -1
- package/build/src/modeling/DomainProperty.js +64 -3
- package/build/src/modeling/DomainProperty.js.map +1 -1
- package/build/src/modeling/DomainSerialization.d.ts.map +1 -1
- package/build/src/modeling/DomainSerialization.js +2 -2
- package/build/src/modeling/DomainSerialization.js.map +1 -1
- package/build/src/modeling/definitions/SKU.d.ts.map +1 -1
- package/build/src/modeling/definitions/SKU.js +2 -0
- package/build/src/modeling/definitions/SKU.js.map +1 -1
- package/build/src/modeling/helpers/Intelisense.d.ts +472 -0
- package/build/src/modeling/helpers/Intelisense.d.ts.map +1 -0
- package/build/src/modeling/helpers/Intelisense.js +1200 -0
- package/build/src/modeling/helpers/Intelisense.js.map +1 -0
- package/build/src/modeling/templates/blog-domain.d.ts +40 -0
- package/build/src/modeling/templates/blog-domain.d.ts.map +1 -0
- package/build/src/modeling/templates/blog-domain.js +621 -0
- package/build/src/modeling/templates/blog-domain.js.map +1 -0
- package/build/src/modeling/templates/ecommerce-domain.d.ts +39 -0
- package/build/src/modeling/templates/ecommerce-domain.d.ts.map +1 -0
- package/build/src/modeling/templates/ecommerce-domain.js +663 -0
- package/build/src/modeling/templates/ecommerce-domain.js.map +1 -0
- package/build/src/modeling/types.d.ts +49 -0
- package/build/src/modeling/types.d.ts.map +1 -1
- package/build/src/modeling/types.js.map +1 -1
- package/build/src/models/Thing.js +1 -1
- package/build/src/models/Thing.js.map +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/data/models/example-generator-api.json +6 -6
- package/package.json +2 -1
- package/src/{modeling → decorators}/observed.ts +5 -5
- package/src/modeling/ApiModel.ts +1 -1
- package/src/modeling/DataDomain.ts +144 -0
- package/src/modeling/DomainAssociation.ts +51 -1
- package/src/modeling/DomainEntity.ts +24 -1
- package/src/modeling/DomainModel.ts +1 -1
- package/src/modeling/DomainNamespace.ts +1 -1
- package/src/modeling/DomainProperty.ts +66 -1
- package/src/modeling/DomainSerialization.ts +2 -4
- package/src/modeling/definitions/SKU.ts +2 -0
- package/src/modeling/helpers/Intelisense.ts +1345 -0
- package/src/modeling/templates/blog-domain.ts +787 -0
- package/src/modeling/templates/ecommerce-domain.ts +834 -0
- package/src/modeling/types.ts +63 -0
- package/src/models/Thing.ts +1 -1
- package/tests/unit/decorators/observed.spec.ts +527 -0
- package/tests/unit/modeling/DataDomain.search.spec.ts +188 -0
- package/tests/unit/modeling/data_domain_serialization.spec.ts +6 -2
- package/tests/unit/modeling/domain_asociation.spec.ts +376 -0
- package/tests/unit/modeling/domain_entity.spec.ts +147 -0
- package/tests/unit/modeling/domain_property.spec.ts +273 -0
- package/build/src/modeling/observed.d.ts.map +0 -1
- package/build/src/modeling/observed.js.map +0 -1
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { test } from '@japa/runner'
|
|
2
|
+
import { DataDomain } from '../../../src/modeling/DataDomain.js'
|
|
3
|
+
import { DomainEntityKind, DomainNamespaceKind, DomainPropertyKind } from '../../../src/models/kinds.js'
|
|
4
|
+
|
|
5
|
+
test.group('DataDomain Search', () => {
|
|
6
|
+
test('should return all nodes when no search criteria provided', ({ assert }) => {
|
|
7
|
+
const domain = new DataDomain({
|
|
8
|
+
key: 'test-domain',
|
|
9
|
+
info: { name: 'Test Domain' },
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
const namespace = domain.addNamespace({ info: { name: 'TestNamespace' } })
|
|
13
|
+
const model = namespace.addModel({ info: { name: 'TestModel' } })
|
|
14
|
+
const entity = model.addEntity({ info: { name: 'TestEntity' } })
|
|
15
|
+
entity.addProperty({ info: { name: 'testProperty' }, type: 'string' })
|
|
16
|
+
|
|
17
|
+
const results = domain.search()
|
|
18
|
+
|
|
19
|
+
assert.isAtLeast(results.length, 4) // namespace, model, entity, property
|
|
20
|
+
assert.isTrue(results.every((result) => result.matchedFields.length === 0))
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
test('should search by text query in name field', ({ assert }) => {
|
|
24
|
+
const domain = new DataDomain({
|
|
25
|
+
key: 'test-domain',
|
|
26
|
+
info: { name: 'Test Domain' },
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const namespace = domain.addNamespace({ info: { name: 'UserNamespace' } })
|
|
30
|
+
const model = namespace.addModel({ info: { name: 'UserModel' } })
|
|
31
|
+
const entity = model.addEntity({ info: { name: 'User' } })
|
|
32
|
+
entity.addProperty({ info: { name: 'email' }, type: 'string' })
|
|
33
|
+
|
|
34
|
+
const results = domain.search({ query: 'User' })
|
|
35
|
+
|
|
36
|
+
assert.equal(results.length, 3) // namespace, model, entity
|
|
37
|
+
assert.isTrue(results.every((result) => result.matchedFields.includes('name')))
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test('should search by text query in displayName field', ({ assert }) => {
|
|
41
|
+
const domain = new DataDomain({
|
|
42
|
+
key: 'test-domain',
|
|
43
|
+
info: { name: 'Test Domain' },
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
domain.addNamespace({ info: { name: 'TestNamespace', displayName: 'User Management' } })
|
|
47
|
+
|
|
48
|
+
const results = domain.search({ query: 'Management' })
|
|
49
|
+
|
|
50
|
+
assert.equal(results.length, 1)
|
|
51
|
+
assert.equal(results[0].node.kind, DomainNamespaceKind)
|
|
52
|
+
assert.isTrue(results[0].matchedFields.includes('displayName'))
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test('should search by text query in description field', ({ assert }) => {
|
|
56
|
+
const domain = new DataDomain({
|
|
57
|
+
key: 'test-domain',
|
|
58
|
+
info: { name: 'Test Domain' },
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
domain.addNamespace({
|
|
62
|
+
info: { name: 'TestNamespace', description: 'Handles user authentication' },
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
const results = domain.search({ query: 'authentication' })
|
|
66
|
+
|
|
67
|
+
assert.equal(results.length, 1)
|
|
68
|
+
assert.equal(results[0].node.kind, DomainNamespaceKind)
|
|
69
|
+
assert.isTrue(results[0].matchedFields.includes('description'))
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test('should filter by node type', ({ assert }) => {
|
|
73
|
+
const domain = new DataDomain({
|
|
74
|
+
key: 'test-domain',
|
|
75
|
+
info: { name: 'Test Domain' },
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
const namespace = domain.addNamespace({ info: { name: 'UserNamespace' } })
|
|
79
|
+
const model = namespace.addModel({ info: { name: 'UserModel' } })
|
|
80
|
+
const entity = model.addEntity({ info: { name: 'User' } })
|
|
81
|
+
entity.addProperty({ info: { name: 'userId' }, type: 'string' })
|
|
82
|
+
|
|
83
|
+
const entityResults = domain.search({ nodeTypes: [DomainEntityKind] })
|
|
84
|
+
const propertyResults = domain.search({ nodeTypes: [DomainPropertyKind] })
|
|
85
|
+
|
|
86
|
+
assert.equal(entityResults.length, 1)
|
|
87
|
+
assert.equal(entityResults[0].node.kind, DomainEntityKind)
|
|
88
|
+
|
|
89
|
+
assert.equal(propertyResults.length, 1)
|
|
90
|
+
assert.equal(propertyResults[0].node.kind, DomainPropertyKind)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
test('should combine text query and node type filters', ({ assert }) => {
|
|
94
|
+
const domain = new DataDomain({
|
|
95
|
+
key: 'test-domain',
|
|
96
|
+
info: { name: 'Test Domain' },
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
const namespace = domain.addNamespace({ info: { name: 'UserNamespace' } })
|
|
100
|
+
const model = namespace.addModel({ info: { name: 'UserModel' } })
|
|
101
|
+
const entity = model.addEntity({ info: { name: 'User' } })
|
|
102
|
+
entity.addProperty({ info: { name: 'userName' }, type: 'string' })
|
|
103
|
+
|
|
104
|
+
const results = domain.search({
|
|
105
|
+
query: 'User',
|
|
106
|
+
nodeTypes: [DomainEntityKind, DomainPropertyKind],
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
assert.equal(results.length, 2) // entity and property
|
|
110
|
+
assert.isTrue(
|
|
111
|
+
results.every((result) => result.node.kind === DomainEntityKind || result.node.kind === DomainPropertyKind)
|
|
112
|
+
)
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
test('should support case-sensitive search', ({ assert }) => {
|
|
116
|
+
const domain = new DataDomain({
|
|
117
|
+
key: 'test-domain',
|
|
118
|
+
info: { name: 'Test Domain' },
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
domain.addNamespace({ info: { name: 'user' } })
|
|
122
|
+
domain.addNamespace({ info: { name: 'User' } })
|
|
123
|
+
|
|
124
|
+
const caseSensitiveResults = domain.search({ query: 'User', caseSensitive: true })
|
|
125
|
+
const caseInsensitiveResults = domain.search({ query: 'User', caseSensitive: false })
|
|
126
|
+
|
|
127
|
+
assert.equal(caseSensitiveResults.length, 1)
|
|
128
|
+
assert.equal(caseInsensitiveResults.length, 2)
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
test('should support regex queries', ({ assert }) => {
|
|
132
|
+
const domain = new DataDomain({
|
|
133
|
+
key: 'test-domain',
|
|
134
|
+
info: { name: 'Test Domain' },
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
domain.addNamespace({ info: { name: 'user1' } })
|
|
138
|
+
domain.addNamespace({ info: { name: 'user2' } })
|
|
139
|
+
domain.addNamespace({ info: { name: 'admin' } })
|
|
140
|
+
|
|
141
|
+
const results = domain.search({ query: /^user\d+$/ })
|
|
142
|
+
|
|
143
|
+
assert.equal(results.length, 2)
|
|
144
|
+
assert.isTrue(results.every((result) => /^user\d+$/.test(result.node.info.name || '')))
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
test('should identify foreign nodes correctly', ({ assert }) => {
|
|
148
|
+
const foreignDomain = new DataDomain({
|
|
149
|
+
key: 'foreign-domain',
|
|
150
|
+
info: { name: 'Foreign Domain', version: '1.0.0' },
|
|
151
|
+
})
|
|
152
|
+
foreignDomain.addNamespace({ info: { name: 'ForeignNamespace' } })
|
|
153
|
+
|
|
154
|
+
const domain = new DataDomain({
|
|
155
|
+
key: 'main-domain',
|
|
156
|
+
info: { name: 'Main Domain' },
|
|
157
|
+
})
|
|
158
|
+
domain.registerForeignDomain(foreignDomain)
|
|
159
|
+
|
|
160
|
+
const resultsWithoutForeign = domain.search({ query: 'Foreign' })
|
|
161
|
+
const resultsWithForeign = domain.search({ query: 'Foreign', includeForeignDomains: true })
|
|
162
|
+
|
|
163
|
+
assert.equal(resultsWithoutForeign.length, 0)
|
|
164
|
+
assert.equal(resultsWithForeign.length, 1)
|
|
165
|
+
assert.isTrue(resultsWithForeign[0].isForeign)
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
test('should handle multiple matched fields', ({ assert }) => {
|
|
169
|
+
const domain = new DataDomain({
|
|
170
|
+
key: 'test-domain',
|
|
171
|
+
info: { name: 'Test Domain' },
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
domain.addNamespace({
|
|
175
|
+
info: {
|
|
176
|
+
name: 'TestNamespace',
|
|
177
|
+
displayName: 'Test Display',
|
|
178
|
+
description: 'Test description',
|
|
179
|
+
},
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
const results = domain.search({ query: 'Test' })
|
|
183
|
+
|
|
184
|
+
assert.equal(results.length, 1)
|
|
185
|
+
assert.equal(results[0].matchedFields.length, 3)
|
|
186
|
+
assert.includeMembers(results[0].matchedFields, ['name', 'displayName', 'description'])
|
|
187
|
+
})
|
|
188
|
+
})
|
|
@@ -456,7 +456,7 @@ test.group('Validation Tests', () => {
|
|
|
456
456
|
assert.throws(() => domain.toJSON())
|
|
457
457
|
}).tags(['@modeling', '@serialization', '@validation'])
|
|
458
458
|
|
|
459
|
-
test('should throw validation error when association has no target entities', ({ assert }) => {
|
|
459
|
+
test('should not throw validation error when association has no target entities', ({ assert }) => {
|
|
460
460
|
const domain = new DataDomain()
|
|
461
461
|
const m1 = domain.addModel()
|
|
462
462
|
const e1 = m1.addEntity()
|
|
@@ -466,7 +466,11 @@ test.group('Validation Tests', () => {
|
|
|
466
466
|
// Remove the association target edge
|
|
467
467
|
domain.graph.removeEdge(a1.key, e2.key)
|
|
468
468
|
|
|
469
|
-
|
|
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())
|
|
470
474
|
}).tags(['@modeling', '@serialization', '@validation'])
|
|
471
475
|
|
|
472
476
|
test('should throw validation error when association references non-existent target', ({ assert }) => {
|
|
@@ -908,3 +908,379 @@ test.group('DomainAssociation.hasSemantic()', () => {
|
|
|
908
908
|
assert.isFalse(association.hasSemantic(SemanticType.ResourceOwnerIdentifier))
|
|
909
909
|
})
|
|
910
910
|
})
|
|
911
|
+
|
|
912
|
+
test.group('DomainAssociation.duplicate()', () => {
|
|
913
|
+
test('duplicates an association successfully', ({ assert }) => {
|
|
914
|
+
const dataDomain = new DataDomain()
|
|
915
|
+
const model = dataDomain.addModel()
|
|
916
|
+
const entity = model.addEntity()
|
|
917
|
+
const targetEntity = model.addEntity()
|
|
918
|
+
|
|
919
|
+
const association = entity.addAssociation({ key: targetEntity.key })
|
|
920
|
+
association.info.name = 'originalAssociation'
|
|
921
|
+
association.required = true
|
|
922
|
+
association.multiple = false
|
|
923
|
+
|
|
924
|
+
const duplicate = association.duplicate()
|
|
925
|
+
|
|
926
|
+
assert.isDefined(duplicate)
|
|
927
|
+
assert.notEqual(duplicate.key, association.key)
|
|
928
|
+
assert.equal(duplicate.info.name, 'originalAssociation_copy')
|
|
929
|
+
assert.equal(duplicate.required, true)
|
|
930
|
+
assert.equal(duplicate.multiple, false)
|
|
931
|
+
})
|
|
932
|
+
|
|
933
|
+
test('places duplicate right after original association in fields list', ({ assert }) => {
|
|
934
|
+
const dataDomain = new DataDomain()
|
|
935
|
+
const model = dataDomain.addModel()
|
|
936
|
+
const entity = model.addEntity()
|
|
937
|
+
const targetEntity = model.addEntity()
|
|
938
|
+
|
|
939
|
+
// Add multiple fields to test ordering
|
|
940
|
+
const property1 = entity.addProperty({ info: { name: 'first' } })
|
|
941
|
+
const association1 = entity.addAssociation({ key: targetEntity.key })
|
|
942
|
+
association1.info.name = 'originalAssoc'
|
|
943
|
+
const property2 = entity.addProperty({ info: { name: 'second' } })
|
|
944
|
+
|
|
945
|
+
const duplicate = association1.duplicate()
|
|
946
|
+
|
|
947
|
+
// Check that duplicate is placed right after association1
|
|
948
|
+
const fieldKeys = entity.fields.map((f) => f.key)
|
|
949
|
+
const association1Index = fieldKeys.indexOf(association1.key)
|
|
950
|
+
const duplicateIndex = fieldKeys.indexOf(duplicate.key)
|
|
951
|
+
|
|
952
|
+
assert.equal(duplicateIndex, association1Index + 1)
|
|
953
|
+
assert.deepEqual(fieldKeys, [property1.key, association1.key, duplicate.key, property2.key])
|
|
954
|
+
})
|
|
955
|
+
|
|
956
|
+
test('generates unique name when base name conflicts', ({ assert }) => {
|
|
957
|
+
const dataDomain = new DataDomain()
|
|
958
|
+
const model = dataDomain.addModel()
|
|
959
|
+
const entity = model.addEntity()
|
|
960
|
+
const targetEntity = model.addEntity()
|
|
961
|
+
|
|
962
|
+
const association = entity.addAssociation({ key: targetEntity.key })
|
|
963
|
+
association.info.name = 'test'
|
|
964
|
+
// Add another association that conflicts with the first generated name
|
|
965
|
+
const conflictAssoc = entity.addAssociation({ key: targetEntity.key })
|
|
966
|
+
conflictAssoc.info.name = 'test_copy'
|
|
967
|
+
|
|
968
|
+
const duplicate = association.duplicate()
|
|
969
|
+
|
|
970
|
+
assert.equal(duplicate.info.name, 'test_copy_2')
|
|
971
|
+
})
|
|
972
|
+
|
|
973
|
+
test('uses fallback name when original association has no name', ({ assert }) => {
|
|
974
|
+
const dataDomain = new DataDomain()
|
|
975
|
+
const model = dataDomain.addModel()
|
|
976
|
+
const entity = model.addEntity()
|
|
977
|
+
const targetEntity = model.addEntity()
|
|
978
|
+
|
|
979
|
+
const association = entity.addAssociation({ key: targetEntity.key })
|
|
980
|
+
// The association gets the default name "new_association" from schema creation
|
|
981
|
+
assert.equal(association.info.name, 'new_association')
|
|
982
|
+
|
|
983
|
+
const duplicate = association.duplicate()
|
|
984
|
+
|
|
985
|
+
assert.equal(duplicate.info.name, 'new_association_copy')
|
|
986
|
+
})
|
|
987
|
+
|
|
988
|
+
test('uses field fallback when association name is explicitly undefined', ({ assert }) => {
|
|
989
|
+
const dataDomain = new DataDomain()
|
|
990
|
+
const model = dataDomain.addModel()
|
|
991
|
+
const entity = model.addEntity()
|
|
992
|
+
const targetEntity = model.addEntity()
|
|
993
|
+
|
|
994
|
+
const association = entity.addAssociation({ key: targetEntity.key })
|
|
995
|
+
association.info.name = 'test'
|
|
996
|
+
// Manually set name to undefined to test the fallback logic
|
|
997
|
+
association.info.name = undefined
|
|
998
|
+
|
|
999
|
+
const duplicate = association.duplicate()
|
|
1000
|
+
|
|
1001
|
+
assert.equal(duplicate.info.name, 'field_copy')
|
|
1002
|
+
})
|
|
1003
|
+
|
|
1004
|
+
test('does not copy key property', ({ assert }) => {
|
|
1005
|
+
const dataDomain = new DataDomain()
|
|
1006
|
+
const model = dataDomain.addModel()
|
|
1007
|
+
const entity = model.addEntity()
|
|
1008
|
+
const targetEntity = model.addEntity()
|
|
1009
|
+
|
|
1010
|
+
const association = entity.addAssociation({ key: targetEntity.key })
|
|
1011
|
+
const originalKey = association.key
|
|
1012
|
+
|
|
1013
|
+
const duplicate = association.duplicate()
|
|
1014
|
+
|
|
1015
|
+
assert.notEqual(duplicate.key, originalKey)
|
|
1016
|
+
})
|
|
1017
|
+
|
|
1018
|
+
test('does not copy targets initially but recreates them', ({ assert }) => {
|
|
1019
|
+
const dataDomain = new DataDomain()
|
|
1020
|
+
const model = dataDomain.addModel()
|
|
1021
|
+
const entity = model.addEntity()
|
|
1022
|
+
const targetEntity1 = model.addEntity()
|
|
1023
|
+
const targetEntity2 = model.addEntity()
|
|
1024
|
+
|
|
1025
|
+
const association = entity.addAssociation({ key: targetEntity1.key })
|
|
1026
|
+
association.addTarget(targetEntity2.key)
|
|
1027
|
+
association.info.name = 'multiTarget'
|
|
1028
|
+
|
|
1029
|
+
const duplicate = association.duplicate()
|
|
1030
|
+
|
|
1031
|
+
// Should have the same targets but different association instance
|
|
1032
|
+
const originalTargets = [...association.listTargets()]
|
|
1033
|
+
const duplicateTargets = [...duplicate.listTargets()]
|
|
1034
|
+
|
|
1035
|
+
assert.equal(originalTargets.length, duplicateTargets.length)
|
|
1036
|
+
assert.equal(originalTargets.length, 2)
|
|
1037
|
+
|
|
1038
|
+
// Check that target keys match
|
|
1039
|
+
const originalKeys = originalTargets.map((t) => t.key).sort()
|
|
1040
|
+
const duplicateKeys = duplicateTargets.map((t) => t.key).sort()
|
|
1041
|
+
assert.deepEqual(originalKeys, duplicateKeys)
|
|
1042
|
+
})
|
|
1043
|
+
|
|
1044
|
+
test('copies all other association attributes', ({ assert }) => {
|
|
1045
|
+
const dataDomain = new DataDomain()
|
|
1046
|
+
const model = dataDomain.addModel()
|
|
1047
|
+
const entity = model.addEntity()
|
|
1048
|
+
const targetEntity = model.addEntity()
|
|
1049
|
+
|
|
1050
|
+
const association = entity.addAssociation({ key: targetEntity.key })
|
|
1051
|
+
association.info.name = 'complexAssociation'
|
|
1052
|
+
association.info.description = 'A complex association'
|
|
1053
|
+
association.required = true
|
|
1054
|
+
association.multiple = true
|
|
1055
|
+
association.readOnly = true
|
|
1056
|
+
association.onDelete = 'cascade'
|
|
1057
|
+
association.semantics = [{ id: SemanticType.ResourceOwnerIdentifier }]
|
|
1058
|
+
association.schema = { linked: true, unionType: 'allOf' }
|
|
1059
|
+
association.bindings = [{ type: 'web', schema: { hidden: true } }]
|
|
1060
|
+
|
|
1061
|
+
const duplicate = association.duplicate()
|
|
1062
|
+
|
|
1063
|
+
assert.equal(duplicate.info.name, 'complexAssociation_copy')
|
|
1064
|
+
assert.equal(duplicate.info.description, 'A complex association')
|
|
1065
|
+
assert.equal(duplicate.required, true)
|
|
1066
|
+
assert.equal(duplicate.multiple, true)
|
|
1067
|
+
assert.equal(duplicate.readOnly, true)
|
|
1068
|
+
assert.equal(duplicate.onDelete, 'cascade')
|
|
1069
|
+
assert.deepEqual(duplicate.semantics, [{ id: SemanticType.ResourceOwnerIdentifier }])
|
|
1070
|
+
assert.deepEqual(duplicate.schema, { linked: true, unionType: 'allOf' })
|
|
1071
|
+
assert.deepEqual(duplicate.bindings, [{ type: 'web', schema: { hidden: true } }])
|
|
1072
|
+
})
|
|
1073
|
+
|
|
1074
|
+
test('creates independent copy of schema and bindings', ({ assert }) => {
|
|
1075
|
+
const dataDomain = new DataDomain()
|
|
1076
|
+
const model = dataDomain.addModel()
|
|
1077
|
+
const entity = model.addEntity()
|
|
1078
|
+
const targetEntity = model.addEntity()
|
|
1079
|
+
|
|
1080
|
+
const association = entity.addAssociation({ key: targetEntity.key })
|
|
1081
|
+
association.info.name = 'original'
|
|
1082
|
+
association.schema = { linked: false, unionType: 'anyOf' }
|
|
1083
|
+
association.bindings = [{ type: 'web', schema: { hidden: false } }]
|
|
1084
|
+
|
|
1085
|
+
const duplicate = association.duplicate()
|
|
1086
|
+
|
|
1087
|
+
// Modify original schema and bindings
|
|
1088
|
+
association.schema!.linked = true
|
|
1089
|
+
association.bindings[0].schema = { hidden: true }
|
|
1090
|
+
|
|
1091
|
+
// Duplicate should be unaffected
|
|
1092
|
+
assert.equal(duplicate.schema!.linked, false)
|
|
1093
|
+
assert.deepEqual(duplicate.bindings[0].schema, { hidden: false })
|
|
1094
|
+
})
|
|
1095
|
+
|
|
1096
|
+
test('copies targets correctly', ({ assert }) => {
|
|
1097
|
+
const dataDomain = new DataDomain()
|
|
1098
|
+
const model = dataDomain.addModel()
|
|
1099
|
+
const entity = model.addEntity()
|
|
1100
|
+
|
|
1101
|
+
const targetEntity1 = model.addEntity()
|
|
1102
|
+
const targetEntity2 = model.addEntity()
|
|
1103
|
+
|
|
1104
|
+
const association = entity.addAssociation()
|
|
1105
|
+
association.addTarget(targetEntity1.key)
|
|
1106
|
+
association.addTarget(targetEntity2.key)
|
|
1107
|
+
association.info.name = 'multiTargetAssoc'
|
|
1108
|
+
|
|
1109
|
+
// Check that original association has targets
|
|
1110
|
+
const originalTargets = [...association.listTargets()]
|
|
1111
|
+
assert.equal(originalTargets.length, 2)
|
|
1112
|
+
|
|
1113
|
+
const duplicate = association.duplicate()
|
|
1114
|
+
|
|
1115
|
+
// Check targets on duplicate
|
|
1116
|
+
const duplicateTargets = [...duplicate.listTargets()]
|
|
1117
|
+
const duplicateTargetObjects = duplicate.targets
|
|
1118
|
+
|
|
1119
|
+
// Check targets array structure
|
|
1120
|
+
assert.equal(duplicateTargetObjects.length, 2)
|
|
1121
|
+
assert.equal(duplicateTargetObjects[0].key, targetEntity1.key)
|
|
1122
|
+
assert.equal(duplicateTargetObjects[1].key, targetEntity2.key)
|
|
1123
|
+
|
|
1124
|
+
// Check resolved targets
|
|
1125
|
+
assert.equal(duplicateTargets.length, 2)
|
|
1126
|
+
const duplicateKeys = duplicateTargets.map((t) => t.key).sort()
|
|
1127
|
+
const expectedKeys = [targetEntity1.key, targetEntity2.key].sort()
|
|
1128
|
+
assert.deepEqual(duplicateKeys, expectedKeys)
|
|
1129
|
+
})
|
|
1130
|
+
|
|
1131
|
+
test('notifies domain of changes', ({ assert }) => {
|
|
1132
|
+
const dataDomain = new DataDomain()
|
|
1133
|
+
const model = dataDomain.addModel()
|
|
1134
|
+
const entity = model.addEntity()
|
|
1135
|
+
const targetEntity = model.addEntity()
|
|
1136
|
+
|
|
1137
|
+
const association = entity.addAssociation({ key: targetEntity.key })
|
|
1138
|
+
association.info.name = 'test'
|
|
1139
|
+
|
|
1140
|
+
let notificationCalled = false
|
|
1141
|
+
const originalNotify = dataDomain.notifyChange
|
|
1142
|
+
dataDomain.notifyChange = () => {
|
|
1143
|
+
notificationCalled = true
|
|
1144
|
+
originalNotify.call(dataDomain)
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
association.duplicate()
|
|
1148
|
+
|
|
1149
|
+
assert.isTrue(notificationCalled)
|
|
1150
|
+
|
|
1151
|
+
// Restore original method
|
|
1152
|
+
dataDomain.notifyChange = originalNotify
|
|
1153
|
+
})
|
|
1154
|
+
|
|
1155
|
+
test('throws error when association has no parent entity', ({ assert }) => {
|
|
1156
|
+
const dataDomain = new DataDomain()
|
|
1157
|
+
const association = new DomainAssociation(dataDomain, 'non-existent-parent', {
|
|
1158
|
+
info: { name: 'orphan' },
|
|
1159
|
+
})
|
|
1160
|
+
|
|
1161
|
+
assert.throws(() => {
|
|
1162
|
+
association.duplicate()
|
|
1163
|
+
}, 'Cannot duplicate association')
|
|
1164
|
+
})
|
|
1165
|
+
|
|
1166
|
+
test('throws error when association is not in parent entity fields list', ({ assert }) => {
|
|
1167
|
+
const dataDomain = new DataDomain()
|
|
1168
|
+
const model = dataDomain.addModel()
|
|
1169
|
+
const entity = model.addEntity()
|
|
1170
|
+
const association = new DomainAssociation(dataDomain, entity.key, {
|
|
1171
|
+
info: { name: 'detached' },
|
|
1172
|
+
})
|
|
1173
|
+
|
|
1174
|
+
// Add association to graph but not to entity fields
|
|
1175
|
+
dataDomain.graph.setNode(association.key, association)
|
|
1176
|
+
|
|
1177
|
+
assert.throws(() => {
|
|
1178
|
+
association.duplicate()
|
|
1179
|
+
}, 'does not exist on the parent entity fields list')
|
|
1180
|
+
})
|
|
1181
|
+
|
|
1182
|
+
test('works with empty entity fields list', ({ assert }) => {
|
|
1183
|
+
const dataDomain = new DataDomain()
|
|
1184
|
+
const model = dataDomain.addModel()
|
|
1185
|
+
const entity = model.addEntity()
|
|
1186
|
+
const targetEntity = model.addEntity()
|
|
1187
|
+
const association = entity.addAssociation({ key: targetEntity.key })
|
|
1188
|
+
association.info.name = 'only'
|
|
1189
|
+
|
|
1190
|
+
const duplicate = association.duplicate()
|
|
1191
|
+
|
|
1192
|
+
assert.equal(entity.fields.length, 2)
|
|
1193
|
+
assert.equal(entity.fields[0].key, association.key)
|
|
1194
|
+
assert.equal(entity.fields[1].key, duplicate.key)
|
|
1195
|
+
})
|
|
1196
|
+
|
|
1197
|
+
test('handles duplication with mixed field types', ({ assert }) => {
|
|
1198
|
+
const dataDomain = new DataDomain()
|
|
1199
|
+
const model = dataDomain.addModel()
|
|
1200
|
+
const entity = model.addEntity()
|
|
1201
|
+
const targetEntity = model.addEntity()
|
|
1202
|
+
|
|
1203
|
+
const property1 = entity.addProperty({ info: { name: 'prop1' } })
|
|
1204
|
+
const association1 = entity.addAssociation({ key: targetEntity.key })
|
|
1205
|
+
association1.info.name = 'assoc1'
|
|
1206
|
+
const property2 = entity.addProperty({ info: { name: 'prop2' } })
|
|
1207
|
+
|
|
1208
|
+
const duplicate = association1.duplicate()
|
|
1209
|
+
|
|
1210
|
+
// Should maintain correct order
|
|
1211
|
+
const fieldKeys = entity.fields.map((f) => f.key)
|
|
1212
|
+
assert.deepEqual(fieldKeys, [property1.key, association1.key, duplicate.key, property2.key])
|
|
1213
|
+
})
|
|
1214
|
+
|
|
1215
|
+
test('preserves field types correctly', ({ assert }) => {
|
|
1216
|
+
const dataDomain = new DataDomain()
|
|
1217
|
+
const model = dataDomain.addModel()
|
|
1218
|
+
const entity = model.addEntity()
|
|
1219
|
+
const targetEntity = model.addEntity()
|
|
1220
|
+
const association = entity.addAssociation({ key: targetEntity.key })
|
|
1221
|
+
|
|
1222
|
+
const duplicate = association.duplicate()
|
|
1223
|
+
|
|
1224
|
+
// Check that both original and duplicate have correct field type
|
|
1225
|
+
const originalField = entity.fields.find((f) => f.key === association.key)
|
|
1226
|
+
const duplicateField = entity.fields.find((f) => f.key === duplicate.key)
|
|
1227
|
+
|
|
1228
|
+
assert.equal(originalField?.type, 'association')
|
|
1229
|
+
assert.equal(duplicateField?.type, 'association')
|
|
1230
|
+
})
|
|
1231
|
+
|
|
1232
|
+
test('handles multiple target entities correctly', ({ assert }) => {
|
|
1233
|
+
const dataDomain = new DataDomain()
|
|
1234
|
+
const model = dataDomain.addModel()
|
|
1235
|
+
const entity = model.addEntity()
|
|
1236
|
+
const targetEntity1 = model.addEntity()
|
|
1237
|
+
const targetEntity2 = model.addEntity()
|
|
1238
|
+
const targetEntity3 = model.addEntity()
|
|
1239
|
+
|
|
1240
|
+
const association = entity.addAssociation()
|
|
1241
|
+
association.addTarget(targetEntity1.key)
|
|
1242
|
+
association.addTarget(targetEntity2.key)
|
|
1243
|
+
association.addTarget(targetEntity3.key)
|
|
1244
|
+
association.info.name = 'multiTarget'
|
|
1245
|
+
|
|
1246
|
+
const duplicate = association.duplicate()
|
|
1247
|
+
|
|
1248
|
+
const originalTargets = [...association.listTargets()]
|
|
1249
|
+
const duplicateTargets = [...duplicate.listTargets()]
|
|
1250
|
+
|
|
1251
|
+
assert.equal(originalTargets.length, 3)
|
|
1252
|
+
assert.equal(duplicateTargets.length, 3)
|
|
1253
|
+
|
|
1254
|
+
// Verify all targets are copied correctly
|
|
1255
|
+
const originalKeys = originalTargets.map((t) => t.key).sort()
|
|
1256
|
+
const duplicateKeys = duplicateTargets.map((t) => t.key).sort()
|
|
1257
|
+
assert.deepEqual(originalKeys, duplicateKeys)
|
|
1258
|
+
})
|
|
1259
|
+
|
|
1260
|
+
test('handles foreign domains correctly', ({ assert }) => {
|
|
1261
|
+
const fd = new DataDomain()
|
|
1262
|
+
const fd1 = fd.addModel()
|
|
1263
|
+
const fe1 = fd1.addEntity()
|
|
1264
|
+
fd.info.version = '1.0.0'
|
|
1265
|
+
|
|
1266
|
+
const dataDomain = new DataDomain()
|
|
1267
|
+
const model = dataDomain.addModel()
|
|
1268
|
+
const entity = model.addEntity()
|
|
1269
|
+
dataDomain.registerForeignDomain(fd)
|
|
1270
|
+
|
|
1271
|
+
const association = entity.addAssociation({ key: fe1.key, domain: fd.key })
|
|
1272
|
+
|
|
1273
|
+
const duplicate = association.duplicate()
|
|
1274
|
+
|
|
1275
|
+
const originalTargets = [...association.listTargets()]
|
|
1276
|
+
const duplicateTargets = [...duplicate.listTargets()]
|
|
1277
|
+
|
|
1278
|
+
assert.equal(originalTargets.length, 1)
|
|
1279
|
+
assert.equal(duplicateTargets.length, 1)
|
|
1280
|
+
|
|
1281
|
+
// Verify all targets are copied correctly
|
|
1282
|
+
const originalKeys = originalTargets.map((t) => t.key).sort()
|
|
1283
|
+
const duplicateKeys = duplicateTargets.map((t) => t.key).sort()
|
|
1284
|
+
assert.deepEqual(originalKeys, duplicateKeys)
|
|
1285
|
+
})
|
|
1286
|
+
})
|