@api-client/core 0.18.27 → 0.18.28
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/ApiModel.d.ts +18 -9
- package/build/src/modeling/ApiModel.d.ts.map +1 -1
- package/build/src/modeling/ApiModel.js +141 -13
- package/build/src/modeling/ApiModel.js.map +1 -1
- package/build/src/modeling/helpers/endpointHelpers.d.ts +2 -0
- package/build/src/modeling/helpers/endpointHelpers.d.ts.map +1 -0
- package/build/src/modeling/helpers/endpointHelpers.js +6 -0
- package/build/src/modeling/helpers/endpointHelpers.js.map +1 -0
- package/build/src/modeling/helpers/keying.d.ts +9 -0
- package/build/src/modeling/helpers/keying.d.ts.map +1 -0
- package/build/src/modeling/helpers/keying.js +10 -0
- package/build/src/modeling/helpers/keying.js.map +1 -0
- package/build/src/modeling/types.d.ts +77 -8
- 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/package.json +2 -1
- package/src/modeling/ApiModel.ts +159 -15
- package/src/modeling/helpers/endpointHelpers.ts +5 -0
- package/src/modeling/helpers/keying.ts +11 -0
- package/src/modeling/readme.md +153 -7
- package/src/modeling/types.ts +84 -8
- package/tests/unit/modeling/api_model.spec.ts +25 -137
- package/tests/unit/modeling/api_model_expose_entity.spec.ts +190 -0
- package/tests/unit/modeling/api_model_remove_entity.spec.ts +82 -0
- package/tests/unit/modeling/helpers/endpointHelpers.spec.ts +10 -0
|
@@ -10,10 +10,7 @@ import {
|
|
|
10
10
|
type ApiLicense,
|
|
11
11
|
} from '../../../src/index.js'
|
|
12
12
|
|
|
13
|
-
test.group('ApiModel.createSchema()', (
|
|
14
|
-
g.tests.forEach((test) => {
|
|
15
|
-
test.tags(['@modeling', '@api', '@schema'])
|
|
16
|
-
})
|
|
13
|
+
test.group('ApiModel.createSchema()', () => {
|
|
17
14
|
test('creates a schema with default values', ({ assert }) => {
|
|
18
15
|
const schema = ApiModel.createSchema()
|
|
19
16
|
assert.equal(schema.kind, ApiModelKind)
|
|
@@ -31,13 +28,13 @@ test.group('ApiModel.createSchema()', (g) => {
|
|
|
31
28
|
assert.isUndefined(schema.termsOfService)
|
|
32
29
|
assert.isUndefined(schema.contact)
|
|
33
30
|
assert.isUndefined(schema.license)
|
|
34
|
-
})
|
|
31
|
+
}).tags(['@modeling', '@api', '@schema'])
|
|
35
32
|
|
|
36
33
|
test('creates a schema with provided values', ({ assert }) => {
|
|
37
34
|
const input: Partial<ApiModelSchema> = {
|
|
38
35
|
key: 'test-api',
|
|
39
36
|
info: { name: 'Test API', description: 'A test API' },
|
|
40
|
-
exposes: [{ key: 'entity1', actions: [] }],
|
|
37
|
+
exposes: [{ key: 'entity1', actions: [], path: '', entity: { key: 'entity1' } }],
|
|
41
38
|
user: { key: 'user-entity' },
|
|
42
39
|
dependencyList: [{ key: 'domain1', version: '1.0.0' }],
|
|
43
40
|
authentication: { strategy: 'UsernamePassword' },
|
|
@@ -54,7 +51,7 @@ test.group('ApiModel.createSchema()', (g) => {
|
|
|
54
51
|
assert.equal(schema.kind, ApiModelKind)
|
|
55
52
|
assert.equal(schema.key, 'test-api')
|
|
56
53
|
assert.deepInclude(schema.info, { name: 'Test API', description: 'A test API' })
|
|
57
|
-
assert.deepEqual(schema.exposes, [{ key: 'entity1', actions: [] }])
|
|
54
|
+
assert.deepEqual(schema.exposes, [{ key: 'entity1', actions: [], path: '', entity: { key: 'entity1' } }])
|
|
58
55
|
assert.deepEqual(schema.user, { key: 'user-entity' })
|
|
59
56
|
assert.deepEqual(schema.dependencyList, [{ key: 'domain1', version: '1.0.0' }])
|
|
60
57
|
assert.deepEqual(schema.authentication, { strategy: 'UsernamePassword' })
|
|
@@ -65,24 +62,20 @@ test.group('ApiModel.createSchema()', (g) => {
|
|
|
65
62
|
assert.equal(schema.termsOfService, 'https://example.com/terms')
|
|
66
63
|
assert.deepEqual(schema.contact, { name: 'John Doe', email: 'john.doe@example.com' })
|
|
67
64
|
assert.deepEqual(schema.license, { name: 'MIT', url: 'https://opensource.org/licenses/MIT' })
|
|
68
|
-
})
|
|
65
|
+
}).tags(['@modeling', '@api', '@schema'])
|
|
69
66
|
|
|
70
67
|
test('creates a schema with partial info', ({ assert }) => {
|
|
71
68
|
const schema = ApiModel.createSchema({ info: { name: 'Partial API' } })
|
|
72
69
|
assert.deepInclude(schema.info, { name: 'Partial API' })
|
|
73
|
-
})
|
|
70
|
+
}).tags(['@modeling', '@api', '@schema'])
|
|
74
71
|
|
|
75
72
|
test('creates a schema with empty info', ({ assert }) => {
|
|
76
73
|
const schema = ApiModel.createSchema({ info: {} })
|
|
77
74
|
assert.deepInclude(schema.info, { name: 'Unnamed API' })
|
|
78
|
-
})
|
|
75
|
+
}).tags(['@modeling', '@api', '@schema'])
|
|
79
76
|
})
|
|
80
77
|
|
|
81
|
-
test.group('ApiModel.constructor()', (
|
|
82
|
-
g.tests.forEach((test) => {
|
|
83
|
-
test.tags(['@modeling', '@api', '@creation'])
|
|
84
|
-
})
|
|
85
|
-
|
|
78
|
+
test.group('ApiModel.constructor()', () => {
|
|
86
79
|
test('creates an instance with default values', ({ assert }) => {
|
|
87
80
|
const model = new ApiModel()
|
|
88
81
|
assert.equal(model.kind, ApiModelKind)
|
|
@@ -100,14 +93,14 @@ test.group('ApiModel.constructor()', (g) => {
|
|
|
100
93
|
assert.isUndefined(model.contact)
|
|
101
94
|
assert.isUndefined(model.license)
|
|
102
95
|
assert.deepEqual(model.dependencyList, [])
|
|
103
|
-
})
|
|
96
|
+
}).tags(['@modeling', '@api', '@creation'])
|
|
104
97
|
|
|
105
98
|
test('creates an instance with provided schema values', ({ assert }) => {
|
|
106
99
|
const schema: ApiModelSchema = {
|
|
107
100
|
kind: ApiModelKind,
|
|
108
101
|
key: 'test-api',
|
|
109
102
|
info: { name: 'Test API', description: 'A test API' },
|
|
110
|
-
exposes: [{ key: 'entity1', actions: [] }],
|
|
103
|
+
exposes: [{ key: 'entity1', actions: [], path: '', entity: { key: 'entity1' } }],
|
|
111
104
|
user: { key: 'user-entity' },
|
|
112
105
|
dependencyList: [{ key: 'domain1', version: '1.0.0' }],
|
|
113
106
|
authentication: { strategy: 'UsernamePassword' },
|
|
@@ -123,7 +116,7 @@ test.group('ApiModel.constructor()', (g) => {
|
|
|
123
116
|
|
|
124
117
|
assert.equal(model.key, 'test-api')
|
|
125
118
|
assert.equal(model.info.name, 'Test API')
|
|
126
|
-
assert.deepEqual(model.exposes, [{ key: 'entity1', actions: [] }])
|
|
119
|
+
assert.deepEqual(model.exposes, [{ key: 'entity1', actions: [], path: '', entity: { key: 'entity1' } }])
|
|
127
120
|
assert.deepEqual(model.user, { key: 'user-entity' })
|
|
128
121
|
assert.deepEqual(model.dependencyList, [{ key: 'domain1', version: '1.0.0' }])
|
|
129
122
|
assert.deepEqual(model.authentication, { strategy: 'UsernamePassword' })
|
|
@@ -134,7 +127,7 @@ test.group('ApiModel.constructor()', (g) => {
|
|
|
134
127
|
assert.equal(model.termsOfService, 'https://example.com/terms')
|
|
135
128
|
assert.deepEqual(model.contact, { name: 'John Doe', email: 'john.doe@example.com' })
|
|
136
129
|
assert.deepEqual(model.license, { name: 'MIT', url: 'https://opensource.org/licenses/MIT' })
|
|
137
|
-
})
|
|
130
|
+
}).tags(['@modeling', '@api', '@creation'])
|
|
138
131
|
|
|
139
132
|
test('creates an instance with a DataDomain', ({ assert }) => {
|
|
140
133
|
const domainSchema = DataDomain.createSchema({ key: 'my-domain' })
|
|
@@ -146,7 +139,7 @@ test.group('ApiModel.constructor()', (g) => {
|
|
|
146
139
|
assert.isDefined(model.domain)
|
|
147
140
|
assert.instanceOf(model.domain, DataDomain)
|
|
148
141
|
assert.equal(model.domain!.key, 'my-domain')
|
|
149
|
-
})
|
|
142
|
+
}).tags(['@modeling', '@api', '@creation'])
|
|
150
143
|
|
|
151
144
|
test('notifies change when info is modified', async ({ assert }) => {
|
|
152
145
|
const model = new ApiModel()
|
|
@@ -157,13 +150,10 @@ test.group('ApiModel.constructor()', (g) => {
|
|
|
157
150
|
model.info.name = 'New Name'
|
|
158
151
|
await Promise.resolve() // Allow microtask to run
|
|
159
152
|
assert.isTrue(notified)
|
|
160
|
-
})
|
|
153
|
+
}).tags(['@modeling', '@api', '@creation'])
|
|
161
154
|
})
|
|
162
155
|
|
|
163
|
-
test.group('ApiModel.toJSON()', (
|
|
164
|
-
g.tests.forEach((test) => {
|
|
165
|
-
test.tags(['@modeling', '@api', '@serialization'])
|
|
166
|
-
})
|
|
156
|
+
test.group('ApiModel.toJSON()', () => {
|
|
167
157
|
test('serializes default values', ({ assert }) => {
|
|
168
158
|
const model = new ApiModel()
|
|
169
159
|
const json = model.toJSON()
|
|
@@ -182,14 +172,14 @@ test.group('ApiModel.toJSON()', (g) => {
|
|
|
182
172
|
assert.isUndefined(json.termsOfService)
|
|
183
173
|
assert.isUndefined(json.contact)
|
|
184
174
|
assert.isUndefined(json.license)
|
|
185
|
-
})
|
|
175
|
+
}).tags(['@modeling', '@api', '@serialization'])
|
|
186
176
|
|
|
187
177
|
test('serializes all provided values', ({ assert }) => {
|
|
188
178
|
const schema: ApiModelSchema = {
|
|
189
179
|
kind: ApiModelKind,
|
|
190
180
|
key: 'test-api',
|
|
191
181
|
info: { name: 'Test API', description: 'A test API' },
|
|
192
|
-
exposes: [{ key: 'entity1', actions: [] }],
|
|
182
|
+
exposes: [{ key: 'entity1', actions: [], path: '', entity: { key: 'entity1' } }],
|
|
193
183
|
user: { key: 'user-entity' },
|
|
194
184
|
dependencyList: [{ key: 'domain1', version: '1.0.0' }],
|
|
195
185
|
authentication: { strategy: 'UsernamePassword' },
|
|
@@ -206,7 +196,7 @@ test.group('ApiModel.toJSON()', (g) => {
|
|
|
206
196
|
|
|
207
197
|
assert.equal(json.key, 'test-api')
|
|
208
198
|
assert.deepInclude(json.info, { name: 'Test API', description: 'A test API' })
|
|
209
|
-
assert.deepEqual(json.exposes, [{ key: 'entity1', actions: [] }])
|
|
199
|
+
assert.deepEqual(json.exposes, [{ key: 'entity1', actions: [], path: '', entity: { key: 'entity1' } }])
|
|
210
200
|
assert.deepEqual(json.user, { key: 'user-entity' })
|
|
211
201
|
assert.deepEqual(json.dependencyList, [{ key: 'domain1', version: '1.0.0' }])
|
|
212
202
|
assert.deepEqual(json.authentication, { strategy: 'UsernamePassword' })
|
|
@@ -217,125 +207,23 @@ test.group('ApiModel.toJSON()', (g) => {
|
|
|
217
207
|
assert.equal(json.termsOfService, 'https://example.com/terms')
|
|
218
208
|
assert.deepEqual(json.contact, { name: 'John Doe', email: 'john.doe@example.com' })
|
|
219
209
|
assert.deepEqual(json.license, { name: 'MIT', url: 'https://opensource.org/licenses/MIT' })
|
|
220
|
-
})
|
|
221
|
-
})
|
|
222
|
-
|
|
223
|
-
test.group('ApiModel.exposeEntity()', (g) => {
|
|
224
|
-
g.tests.forEach((test) => {
|
|
225
|
-
test.tags(['@modeling', '@api'])
|
|
226
|
-
})
|
|
227
|
-
test('exposes a new entity', ({ assert }) => {
|
|
228
|
-
const model = new ApiModel()
|
|
229
|
-
const entityKey = 'new-entity'
|
|
230
|
-
const exposedEntity = model.exposeEntity(entityKey)
|
|
231
|
-
|
|
232
|
-
assert.isDefined(exposedEntity)
|
|
233
|
-
assert.equal(exposedEntity.key, entityKey)
|
|
234
|
-
assert.deepEqual(exposedEntity.actions, [])
|
|
235
|
-
assert.includeDeepMembers(model.exposes, [exposedEntity])
|
|
236
|
-
})
|
|
237
|
-
|
|
238
|
-
test('returns an existing entity if already exposed', ({ assert }) => {
|
|
239
|
-
const model = new ApiModel()
|
|
240
|
-
const entityKey = 'existing-entity'
|
|
241
|
-
const initialExposedEntity = model.exposeEntity(entityKey)
|
|
242
|
-
const retrievedExposedEntity = model.exposeEntity(entityKey)
|
|
243
|
-
|
|
244
|
-
assert.strictEqual(retrievedExposedEntity, initialExposedEntity)
|
|
245
|
-
assert.lengthOf(model.exposes, 1)
|
|
246
|
-
})
|
|
247
|
-
|
|
248
|
-
test('notifies change when a new entity is exposed', async ({ assert }) => {
|
|
249
|
-
const model = new ApiModel()
|
|
250
|
-
let notified = false
|
|
251
|
-
model.addEventListener('change', () => {
|
|
252
|
-
notified = true
|
|
253
|
-
})
|
|
254
|
-
model.exposeEntity('notify-entity')
|
|
255
|
-
await Promise.resolve() // Allow microtask to run
|
|
256
|
-
assert.isTrue(notified)
|
|
257
|
-
})
|
|
258
|
-
|
|
259
|
-
test('does not notify change if entity already exposed', async ({ assert }) => {
|
|
260
|
-
const model = new ApiModel()
|
|
261
|
-
model.exposeEntity('no-notify-entity') // First exposure
|
|
262
|
-
await Promise.resolve() // Allow microtask to run
|
|
263
|
-
let notified = false
|
|
264
|
-
model.addEventListener('change', () => {
|
|
265
|
-
notified = true
|
|
266
|
-
})
|
|
267
|
-
model.exposeEntity('no-notify-entity') // Second exposure
|
|
268
|
-
await Promise.resolve() // Allow microtask to run
|
|
269
|
-
assert.isFalse(notified)
|
|
270
|
-
})
|
|
271
|
-
})
|
|
272
|
-
|
|
273
|
-
test.group('ApiModel.removeEntity()', (g) => {
|
|
274
|
-
g.tests.forEach((test) => {
|
|
275
|
-
test.tags(['@modeling', '@api'])
|
|
276
|
-
})
|
|
277
|
-
test('removes an existing entity', ({ assert }) => {
|
|
278
|
-
const model = new ApiModel()
|
|
279
|
-
const entityKey = 'entity-to-remove'
|
|
280
|
-
model.exposeEntity(entityKey)
|
|
281
|
-
assert.lengthOf(model.exposes, 1)
|
|
282
|
-
|
|
283
|
-
model.removeEntity(entityKey)
|
|
284
|
-
assert.lengthOf(model.exposes, 0)
|
|
285
|
-
})
|
|
286
|
-
|
|
287
|
-
test('does nothing if entity does not exist', ({ assert }) => {
|
|
288
|
-
const model = new ApiModel()
|
|
289
|
-
model.exposeEntity('existing-entity')
|
|
290
|
-
const initialExposes = [...model.exposes]
|
|
291
|
-
|
|
292
|
-
model.removeEntity('non-existing-entity')
|
|
293
|
-
assert.deepEqual(model.exposes, initialExposes)
|
|
294
|
-
})
|
|
295
|
-
|
|
296
|
-
test('notifies change when an entity is removed', async ({ assert }) => {
|
|
297
|
-
const model = new ApiModel()
|
|
298
|
-
const entityKey = 'notify-remove-entity'
|
|
299
|
-
model.exposeEntity(entityKey)
|
|
300
|
-
|
|
301
|
-
let notified = false
|
|
302
|
-
model.addEventListener('change', () => {
|
|
303
|
-
notified = true
|
|
304
|
-
})
|
|
305
|
-
model.removeEntity(entityKey)
|
|
306
|
-
await Promise.resolve() // Allow microtask to run
|
|
307
|
-
assert.isTrue(notified)
|
|
308
|
-
})
|
|
309
|
-
|
|
310
|
-
test('does not notify change if entity to remove does not exist', async ({ assert }) => {
|
|
311
|
-
const model = new ApiModel()
|
|
312
|
-
let notified = false
|
|
313
|
-
model.addEventListener('change', () => {
|
|
314
|
-
notified = true
|
|
315
|
-
})
|
|
316
|
-
model.removeEntity('no-notify-remove-entity')
|
|
317
|
-
await Promise.resolve() // Allow microtask to run
|
|
318
|
-
assert.isFalse(notified)
|
|
319
|
-
})
|
|
210
|
+
}).tags(['@modeling', '@api', '@serialization'])
|
|
320
211
|
})
|
|
321
212
|
|
|
322
|
-
test.group('ApiModel.getExposedEntity()', (
|
|
323
|
-
g.tests.forEach((test) => {
|
|
324
|
-
test.tags(['@modeling', '@api'])
|
|
325
|
-
})
|
|
213
|
+
test.group('ApiModel.getExposedEntity()', () => {
|
|
326
214
|
test('returns an existing exposed entity', ({ assert }) => {
|
|
327
215
|
const model = new ApiModel()
|
|
328
216
|
const entityKey = 'get-entity'
|
|
329
|
-
const exposed: ExposedEntity = { key: entityKey, actions: [] }
|
|
217
|
+
const exposed: ExposedEntity = { key: entityKey, actions: [], path: '', entity: { key: entityKey } }
|
|
330
218
|
model.exposes.push(exposed)
|
|
331
219
|
|
|
332
|
-
const retrievedEntity = model.getExposedEntity(entityKey)
|
|
220
|
+
const retrievedEntity = model.getExposedEntity({ key: entityKey })
|
|
333
221
|
assert.deepEqual(retrievedEntity, exposed)
|
|
334
|
-
})
|
|
222
|
+
}).tags(['@modeling', '@api'])
|
|
335
223
|
|
|
336
224
|
test('returns undefined if entity is not exposed', ({ assert }) => {
|
|
337
225
|
const model = new ApiModel()
|
|
338
|
-
const retrievedEntity = model.getExposedEntity('non-exposed-entity')
|
|
226
|
+
const retrievedEntity = model.getExposedEntity({ key: 'non-exposed-entity' })
|
|
339
227
|
assert.isUndefined(retrievedEntity)
|
|
340
|
-
})
|
|
228
|
+
}).tags(['@modeling', '@api'])
|
|
341
229
|
})
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { test } from '@japa/runner'
|
|
2
|
+
import { ApiModel, DataDomain } from '../../../src/index.js'
|
|
3
|
+
|
|
4
|
+
test.group('ApiModel.exposeEntity()', () => {
|
|
5
|
+
test('exposes a new entity', ({ assert }) => {
|
|
6
|
+
const domain = new DataDomain()
|
|
7
|
+
domain.info.version = '1.0.0'
|
|
8
|
+
const dm = domain.addModel()
|
|
9
|
+
const e1 = dm.addEntity()
|
|
10
|
+
const model = new ApiModel()
|
|
11
|
+
model.attachDataDomain(domain)
|
|
12
|
+
const exposedEntity = model.exposeEntity({ key: e1.key })
|
|
13
|
+
|
|
14
|
+
assert.isDefined(exposedEntity)
|
|
15
|
+
assert.typeOf(exposedEntity.key, 'string')
|
|
16
|
+
assert.deepEqual(exposedEntity.entity, { key: e1.key })
|
|
17
|
+
assert.deepEqual(exposedEntity.actions, [])
|
|
18
|
+
assert.includeDeepMembers(model.exposes, [exposedEntity])
|
|
19
|
+
}).tags(['@modeling', '@api'])
|
|
20
|
+
|
|
21
|
+
test('returns an existing entity if already exposed', ({ assert }) => {
|
|
22
|
+
const domain = new DataDomain()
|
|
23
|
+
domain.info.version = '1.0.0'
|
|
24
|
+
const dm = domain.addModel()
|
|
25
|
+
const e1 = dm.addEntity()
|
|
26
|
+
const model = new ApiModel()
|
|
27
|
+
model.attachDataDomain(domain)
|
|
28
|
+
const initialExposedEntity = model.exposeEntity({ key: e1.key })
|
|
29
|
+
const retrievedExposedEntity = model.exposeEntity({ key: e1.key })
|
|
30
|
+
|
|
31
|
+
assert.strictEqual(retrievedExposedEntity, initialExposedEntity)
|
|
32
|
+
assert.lengthOf(model.exposes, 1)
|
|
33
|
+
}).tags(['@modeling', '@api'])
|
|
34
|
+
|
|
35
|
+
test('notifies change when a new entity is exposed', async ({ assert }) => {
|
|
36
|
+
const domain = new DataDomain()
|
|
37
|
+
domain.info.version = '1.0.0'
|
|
38
|
+
const dm = domain.addModel()
|
|
39
|
+
const e1 = dm.addEntity()
|
|
40
|
+
const model = new ApiModel()
|
|
41
|
+
model.attachDataDomain(domain)
|
|
42
|
+
let notified = false
|
|
43
|
+
model.addEventListener('change', () => {
|
|
44
|
+
notified = true
|
|
45
|
+
})
|
|
46
|
+
model.exposeEntity({ key: e1.key })
|
|
47
|
+
await Promise.resolve() // Allow microtask to run
|
|
48
|
+
assert.isTrue(notified)
|
|
49
|
+
}).tags(['@modeling', '@api'])
|
|
50
|
+
|
|
51
|
+
test('does not notify change if entity already exposed', async ({ assert }) => {
|
|
52
|
+
const domain = new DataDomain()
|
|
53
|
+
domain.info.version = '1.0.0'
|
|
54
|
+
const dm = domain.addModel()
|
|
55
|
+
const e1 = dm.addEntity()
|
|
56
|
+
const model = new ApiModel()
|
|
57
|
+
model.attachDataDomain(domain)
|
|
58
|
+
model.exposeEntity({ key: e1.key }) // First exposure
|
|
59
|
+
await Promise.resolve() // Allow microtask to run
|
|
60
|
+
let notified = false
|
|
61
|
+
model.addEventListener('change', () => {
|
|
62
|
+
notified = true
|
|
63
|
+
})
|
|
64
|
+
model.exposeEntity({ key: e1.key }) // Second exposure
|
|
65
|
+
await Promise.resolve() // Allow microtask to run
|
|
66
|
+
assert.isFalse(notified)
|
|
67
|
+
}).tags(['@modeling', '@api'])
|
|
68
|
+
|
|
69
|
+
test('exposes nested entities through associations', ({ assert }) => {
|
|
70
|
+
const domain = new DataDomain()
|
|
71
|
+
domain.info.version = '1.0.0'
|
|
72
|
+
const dm = domain.addModel()
|
|
73
|
+
const eA = dm.addEntity({ info: { name: 'A' } })
|
|
74
|
+
const eB = dm.addEntity({ info: { name: 'B' } })
|
|
75
|
+
// Add association from A to B
|
|
76
|
+
eA.addAssociation({ key: eB.key }, { info: { name: 'entityB' } })
|
|
77
|
+
const model = new ApiModel()
|
|
78
|
+
model.attachDataDomain(domain)
|
|
79
|
+
const exposedA = model.exposeEntity({ key: eA.key }, { followAssociations: true })
|
|
80
|
+
// Find nested exposure for B
|
|
81
|
+
const nestedB = model.exposes.find((e) => !e.isRoot && e.entity.key === eB.key)
|
|
82
|
+
assert.isDefined(nestedB)
|
|
83
|
+
assert.deepEqual(nestedB?.parent?.key, exposedA.key)
|
|
84
|
+
assert.strictEqual(nestedB?.path, 'entitybs')
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
test('does not infinitely expose circular associations', ({ assert }) => {
|
|
88
|
+
const domain = new DataDomain()
|
|
89
|
+
domain.info.version = '1.0.0'
|
|
90
|
+
const dm = domain.addModel()
|
|
91
|
+
const eA = dm.addEntity({ info: { name: 'A' } })
|
|
92
|
+
const eB = dm.addEntity({ info: { name: 'B' } })
|
|
93
|
+
// A -> B, B -> A
|
|
94
|
+
eA.addAssociation({ key: eB.key }, { info: { name: 'assocAB' } })
|
|
95
|
+
eB.addAssociation({ key: eA.key }, { info: { name: 'assocBA' } })
|
|
96
|
+
const model = new ApiModel()
|
|
97
|
+
model.attachDataDomain(domain)
|
|
98
|
+
model.exposeEntity({ key: eA.key }, { followAssociations: true, maxDepth: 4 })
|
|
99
|
+
|
|
100
|
+
assert.lengthOf(model.exposes, 2, 'has only 2 exposures')
|
|
101
|
+
// Should expose A (root), B (nested under A), but not infinitely nest
|
|
102
|
+
const exposedA = model.exposes.find((e) => e.isRoot && e.entity.key === eA.key)
|
|
103
|
+
const nestedB = model.exposes.find((e) => !e.isRoot && e.entity.key === eB.key)
|
|
104
|
+
// There should be only one nested exposure for B under A
|
|
105
|
+
assert.isDefined(exposedA)
|
|
106
|
+
assert.isDefined(nestedB)
|
|
107
|
+
// There should NOT be a nested exposure for A under B
|
|
108
|
+
const circularA = model.exposes.find((e) => !e.isRoot && e.entity.key === eA.key && e.parent?.key === nestedB?.key)
|
|
109
|
+
assert.isUndefined(circularA)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
test('does not expose self-association as nested entity', ({ assert }) => {
|
|
113
|
+
const domain = new DataDomain()
|
|
114
|
+
domain.info.version = '1.0.0'
|
|
115
|
+
const dm = domain.addModel()
|
|
116
|
+
const eA = dm.addEntity({ info: { name: 'A' } })
|
|
117
|
+
// A -> A (self-association)
|
|
118
|
+
eA.addAssociation({ key: eA.key }, { info: { name: 'assocAA' } })
|
|
119
|
+
const model = new ApiModel()
|
|
120
|
+
model.attachDataDomain(domain)
|
|
121
|
+
model.exposeEntity({ key: eA.key }, { followAssociations: true })
|
|
122
|
+
// Should only expose A as root, not as nested
|
|
123
|
+
const exposedA = model.exposes.find((e) => e.isRoot && e.entity.key === eA.key)
|
|
124
|
+
const nestedA = model.exposes.find((e) => !e.isRoot && e.entity.key === eA.key)
|
|
125
|
+
assert.isDefined(exposedA)
|
|
126
|
+
assert.isUndefined(nestedA)
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
test('exposes multi-level associations (A -> B -> C)', ({ assert }) => {
|
|
130
|
+
const domain = new DataDomain()
|
|
131
|
+
domain.info.version = '1.0.0'
|
|
132
|
+
const dm = domain.addModel()
|
|
133
|
+
const eA = dm.addEntity({ info: { name: 'A' } })
|
|
134
|
+
const eB = dm.addEntity({ info: { name: 'B' } })
|
|
135
|
+
const eC = dm.addEntity({ info: { name: 'C' } })
|
|
136
|
+
eA.addAssociation({ key: eB.key }, { info: { name: 'toB' } })
|
|
137
|
+
eB.addAssociation({ key: eC.key }, { info: { name: 'toC' } })
|
|
138
|
+
const model = new ApiModel()
|
|
139
|
+
model.attachDataDomain(domain)
|
|
140
|
+
model.exposeEntity({ key: eA.key }, { followAssociations: true })
|
|
141
|
+
const nestedB = model.exposes.find((e) => !e.isRoot && e.entity.key === eB.key)
|
|
142
|
+
const nestedC = model.exposes.find((e) => !e.isRoot && e.entity.key === eC.key)
|
|
143
|
+
assert.isDefined(nestedB)
|
|
144
|
+
assert.isDefined(nestedC)
|
|
145
|
+
assert.deepEqual(nestedB?.parent?.key, model.exposes.find((e) => e.isRoot && e.entity.key === eA.key)?.key)
|
|
146
|
+
assert.deepEqual(nestedC?.parent?.key, nestedB?.key)
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
test('exposes multiple associations from one entity (A -> B, A -> C)', ({ assert }) => {
|
|
150
|
+
const domain = new DataDomain()
|
|
151
|
+
domain.info.version = '1.0.0'
|
|
152
|
+
const dm = domain.addModel()
|
|
153
|
+
const eA = dm.addEntity({ info: { name: 'A' } })
|
|
154
|
+
const eB = dm.addEntity({ info: { name: 'B' } })
|
|
155
|
+
const eC = dm.addEntity({ info: { name: 'C' } })
|
|
156
|
+
eA.addAssociation({ key: eB.key }, { info: { name: 'toB' } })
|
|
157
|
+
eA.addAssociation({ key: eC.key }, { info: { name: 'toC' } })
|
|
158
|
+
const model = new ApiModel()
|
|
159
|
+
model.attachDataDomain(domain)
|
|
160
|
+
model.exposeEntity({ key: eA.key }, { followAssociations: true })
|
|
161
|
+
const nestedB = model.exposes.find((e) => !e.isRoot && e.entity.key === eB.key)
|
|
162
|
+
const nestedC = model.exposes.find((e) => !e.isRoot && e.entity.key === eC.key)
|
|
163
|
+
assert.isDefined(nestedB)
|
|
164
|
+
assert.isDefined(nestedC)
|
|
165
|
+
assert.deepEqual(nestedB?.parent?.key, model.exposes.find((e) => e.isRoot && e.entity.key === eA.key)?.key)
|
|
166
|
+
assert.deepEqual(nestedC?.parent?.key, model.exposes.find((e) => e.isRoot && e.entity.key === eA.key)?.key)
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
test('respects maxDepth option (A -> B -> C -> D, maxDepth=2)', ({ assert }) => {
|
|
170
|
+
const domain = new DataDomain()
|
|
171
|
+
domain.info.version = '1.0.0'
|
|
172
|
+
const dm = domain.addModel()
|
|
173
|
+
const eA = dm.addEntity({ info: { name: 'A' } })
|
|
174
|
+
const eB = dm.addEntity({ info: { name: 'B' } })
|
|
175
|
+
const eC = dm.addEntity({ info: { name: 'C' } })
|
|
176
|
+
const eD = dm.addEntity({ info: { name: 'D' } })
|
|
177
|
+
eA.addAssociation({ key: eB.key }, { info: { name: 'toB' } })
|
|
178
|
+
eB.addAssociation({ key: eC.key }, { info: { name: 'toC' } })
|
|
179
|
+
eC.addAssociation({ key: eD.key }, { info: { name: 'toD' } })
|
|
180
|
+
const model = new ApiModel()
|
|
181
|
+
model.attachDataDomain(domain)
|
|
182
|
+
model.exposeEntity({ key: eA.key }, { followAssociations: true, maxDepth: 2 })
|
|
183
|
+
const nestedB = model.exposes.find((e) => !e.isRoot && e.entity.key === eB.key)
|
|
184
|
+
const nestedC = model.exposes.find((e) => !e.isRoot && e.entity.key === eC.key)
|
|
185
|
+
const nestedD = model.exposes.find((e) => !e.isRoot && e.entity.key === eD.key)
|
|
186
|
+
assert.isDefined(nestedB)
|
|
187
|
+
assert.isDefined(nestedC)
|
|
188
|
+
assert.isUndefined(nestedD)
|
|
189
|
+
})
|
|
190
|
+
})
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { test } from '@japa/runner'
|
|
2
|
+
import { ApiModel, DataDomain } from '../../../src/index.js'
|
|
3
|
+
|
|
4
|
+
test.group('ApiModel.removeEntity()', () => {
|
|
5
|
+
test('removes an existing entity', ({ assert }) => {
|
|
6
|
+
const domain = new DataDomain()
|
|
7
|
+
domain.info.version = '1.0.0'
|
|
8
|
+
const dm = domain.addModel()
|
|
9
|
+
const e1 = dm.addEntity()
|
|
10
|
+
const model = new ApiModel()
|
|
11
|
+
model.attachDataDomain(domain)
|
|
12
|
+
model.exposeEntity({ key: e1.key })
|
|
13
|
+
assert.lengthOf(model.exposes, 1)
|
|
14
|
+
|
|
15
|
+
model.removeEntity({ key: e1.key })
|
|
16
|
+
assert.lengthOf(model.exposes, 0)
|
|
17
|
+
}).tags(['@modeling', '@api'])
|
|
18
|
+
|
|
19
|
+
test('removes an entity and its nested children', ({ assert }) => {
|
|
20
|
+
const domain = new DataDomain()
|
|
21
|
+
domain.info.version = '1.0.0'
|
|
22
|
+
const dm = domain.addModel()
|
|
23
|
+
const eA = dm.addEntity({ info: { name: 'A' } })
|
|
24
|
+
const eB = dm.addEntity({ info: { name: 'B' } })
|
|
25
|
+
// A -> B
|
|
26
|
+
eA.addAssociation({ key: eB.key }, { info: { name: 'toB' } })
|
|
27
|
+
const model = new ApiModel()
|
|
28
|
+
model.attachDataDomain(domain)
|
|
29
|
+
model.exposeEntity({ key: eA.key }, { followAssociations: true })
|
|
30
|
+
// Ensure nested exposure for B was created
|
|
31
|
+
const nestedB = model.exposes.find((e) => !e.isRoot && e.entity.key === eB.key)
|
|
32
|
+
assert.isDefined(nestedB)
|
|
33
|
+
assert.isAbove(model.exposes.length, 1)
|
|
34
|
+
|
|
35
|
+
// Remove root entity A and expect children to be removed as well
|
|
36
|
+
model.removeEntity({ key: eA.key })
|
|
37
|
+
assert.lengthOf(model.exposes, 0)
|
|
38
|
+
}).tags(['@modeling', '@api'])
|
|
39
|
+
|
|
40
|
+
test('does nothing if entity does not exist', ({ assert }) => {
|
|
41
|
+
const domain = new DataDomain()
|
|
42
|
+
domain.info.version = '1.0.0'
|
|
43
|
+
const dm = domain.addModel()
|
|
44
|
+
const e1 = dm.addEntity()
|
|
45
|
+
const model = new ApiModel()
|
|
46
|
+
model.attachDataDomain(domain)
|
|
47
|
+
model.exposeEntity({ key: e1.key })
|
|
48
|
+
const initialExposes = [...model.exposes]
|
|
49
|
+
|
|
50
|
+
model.removeEntity({ key: 'non-existing-entity' })
|
|
51
|
+
assert.deepEqual(model.exposes, initialExposes)
|
|
52
|
+
}).tags(['@modeling', '@api'])
|
|
53
|
+
|
|
54
|
+
test('notifies change when an entity is removed', async ({ assert }) => {
|
|
55
|
+
const domain = new DataDomain()
|
|
56
|
+
domain.info.version = '1.0.0'
|
|
57
|
+
const dm = domain.addModel()
|
|
58
|
+
const e1 = dm.addEntity()
|
|
59
|
+
const model = new ApiModel()
|
|
60
|
+
model.attachDataDomain(domain)
|
|
61
|
+
model.exposeEntity({ key: e1.key })
|
|
62
|
+
|
|
63
|
+
let notified = false
|
|
64
|
+
model.addEventListener('change', () => {
|
|
65
|
+
notified = true
|
|
66
|
+
})
|
|
67
|
+
model.removeEntity({ key: e1.key })
|
|
68
|
+
await Promise.resolve() // Allow microtask to run
|
|
69
|
+
assert.isTrue(notified)
|
|
70
|
+
}).tags(['@modeling', '@api'])
|
|
71
|
+
|
|
72
|
+
test('does not notify change if entity to remove does not exist', async ({ assert }) => {
|
|
73
|
+
const model = new ApiModel()
|
|
74
|
+
let notified = false
|
|
75
|
+
model.addEventListener('change', () => {
|
|
76
|
+
notified = true
|
|
77
|
+
})
|
|
78
|
+
model.removeEntity({ key: 'no-notify-remove-entity' })
|
|
79
|
+
await Promise.resolve() // Allow microtask to run
|
|
80
|
+
assert.isFalse(notified)
|
|
81
|
+
}).tags(['@modeling', '@api'])
|
|
82
|
+
})
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { test } from '@japa/runner'
|
|
2
|
+
import * as helpers from '../../../../src/modeling/helpers/endpointHelpers.js'
|
|
3
|
+
|
|
4
|
+
test.group('endpointHelpers', () => {
|
|
5
|
+
test('paramNameFor extracts last key', ({ assert }) => {
|
|
6
|
+
assert.equal(helpers.paramNameFor('product'), 'productId')
|
|
7
|
+
assert.equal(helpers.paramNameFor('domain:product'), 'productId')
|
|
8
|
+
assert.equal(helpers.paramNameFor('a:b:c'), 'cId')
|
|
9
|
+
})
|
|
10
|
+
})
|