@api-client/core 0.18.47 → 0.18.49
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/mocking/ModelingMock.d.ts +2 -0
- package/build/src/mocking/ModelingMock.d.ts.map +1 -1
- package/build/src/mocking/ModelingMock.js +2 -0
- package/build/src/mocking/ModelingMock.js.map +1 -1
- package/build/src/mocking/lib/File.d.ts +1 -0
- package/build/src/mocking/lib/File.d.ts.map +1 -1
- package/build/src/mocking/lib/File.js +1 -0
- package/build/src/mocking/lib/File.js.map +1 -1
- package/build/src/mocking/lib/Permission.d.ts +35 -0
- package/build/src/mocking/lib/Permission.d.ts.map +1 -0
- package/build/src/mocking/lib/Permission.js +89 -0
- package/build/src/mocking/lib/Permission.js.map +1 -0
- package/build/src/modeling/ApiModel.d.ts +12 -4
- package/build/src/modeling/ApiModel.d.ts.map +1 -1
- package/build/src/modeling/ApiModel.js +76 -31
- package/build/src/modeling/ApiModel.js.map +1 -1
- package/build/src/modeling/ExposedEntity.d.ts +9 -0
- package/build/src/modeling/ExposedEntity.d.ts.map +1 -1
- package/build/src/modeling/ExposedEntity.js +23 -0
- package/build/src/modeling/ExposedEntity.js.map +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/data/models/example-generator-api.json +9 -9
- package/package.json +3 -3
- package/src/mocking/ModelingMock.ts +2 -0
- package/src/mocking/lib/File.ts +1 -0
- package/src/mocking/lib/Permission.ts +100 -0
- package/src/modeling/ApiModel.ts +82 -37
- package/src/modeling/ExposedEntity.ts +28 -0
- package/tests/unit/mocking/current/Permission.spec.ts +285 -0
- package/tests/unit/modeling/api_model.spec.ts +20 -0
- package/tests/unit/modeling/api_model_expose_entity.spec.ts +25 -0
- package/tests/unit/modeling/api_model_remove_entity.spec.ts +17 -10
- package/tests/unit/modeling/exposed_entity_setter_validation.spec.ts +107 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { test } from '@japa/runner'
|
|
2
|
+
import { Permission } from '../../../../src/mocking/lib/Permission.js'
|
|
3
|
+
import { Kind as PermissionKind } from '../../../../src/models/store/Permission.js'
|
|
4
|
+
import type { IPermission } from '../../../../src/models/store/Permission.js'
|
|
5
|
+
|
|
6
|
+
test.group('permission()', (group) => {
|
|
7
|
+
let permission: Permission
|
|
8
|
+
|
|
9
|
+
group.each.setup(() => {
|
|
10
|
+
permission = new Permission()
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
test('returns an object', ({ assert }) => {
|
|
14
|
+
const result = permission.permission()
|
|
15
|
+
assert.typeOf(result, 'object')
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('has the {property} property of a type {type}')
|
|
19
|
+
.with([
|
|
20
|
+
{ property: 'kind', type: 'string' },
|
|
21
|
+
{ property: 'key', type: 'string' },
|
|
22
|
+
{ property: 'type', type: 'string' },
|
|
23
|
+
{ property: 'granteeId', type: 'string' },
|
|
24
|
+
{ property: 'itemId', type: 'string' },
|
|
25
|
+
{ property: 'role', type: 'string' },
|
|
26
|
+
{ property: 'addingUser', type: 'string' },
|
|
27
|
+
{ property: 'depth', type: 'number' },
|
|
28
|
+
{ property: 'sourceRule', type: 'string' },
|
|
29
|
+
])
|
|
30
|
+
.run(({ assert }, { property, type }) => {
|
|
31
|
+
const result = permission.permission()
|
|
32
|
+
assert.typeOf(result[property as keyof IPermission], type)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test('has the correct kind', ({ assert }) => {
|
|
36
|
+
const result = permission.permission()
|
|
37
|
+
assert.equal(result.kind, PermissionKind)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test('type is one of the valid values', ({ assert }) => {
|
|
41
|
+
const result = permission.permission()
|
|
42
|
+
assert.include(['user', 'group', 'organization'], result.type)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test('role is one of the valid values', ({ assert }) => {
|
|
46
|
+
const result = permission.permission()
|
|
47
|
+
assert.include(['reader', 'commenter', 'writer', 'owner'], result.role)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test('sourceRule is one of the valid values', ({ assert }) => {
|
|
51
|
+
const result = permission.permission()
|
|
52
|
+
assert.include(['direct_user_grant', 'creator_default_owner', 'parent_owner_editor_rule'], result.sourceRule)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test('uses passed key', ({ assert }) => {
|
|
56
|
+
const result = permission.permission({ key: 'custom-key' })
|
|
57
|
+
assert.equal(result.key, 'custom-key')
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('uses passed type', ({ assert }) => {
|
|
61
|
+
const result = permission.permission({ type: 'group' })
|
|
62
|
+
assert.equal(result.type, 'group')
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test('uses passed granteeId', ({ assert }) => {
|
|
66
|
+
const result = permission.permission({ granteeId: 'grantee-123' })
|
|
67
|
+
assert.equal(result.granteeId, 'grantee-123')
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test('uses passed itemId', ({ assert }) => {
|
|
71
|
+
const result = permission.permission({ itemId: 'item-456' })
|
|
72
|
+
assert.equal(result.itemId, 'item-456')
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test('uses passed role', ({ assert }) => {
|
|
76
|
+
const result = permission.permission({ role: 'writer' })
|
|
77
|
+
assert.equal(result.role, 'writer')
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test('uses passed addingUser', ({ assert }) => {
|
|
81
|
+
const result = permission.permission({ addingUser: 'user-789' })
|
|
82
|
+
assert.equal(result.addingUser, 'user-789')
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
test('uses passed depth', ({ assert }) => {
|
|
86
|
+
const result = permission.permission({ depth: 2 })
|
|
87
|
+
assert.equal(result.depth, 2)
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
test('uses passed sourceRule', ({ assert }) => {
|
|
91
|
+
const result = permission.permission({ sourceRule: 'creator_default_owner' })
|
|
92
|
+
assert.equal(result.sourceRule, 'creator_default_owner')
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
test('generates displayName by default', ({ assert }) => {
|
|
96
|
+
const result = permission.permission()
|
|
97
|
+
assert.typeOf(result.displayName, 'string')
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test('uses passed displayName', ({ assert }) => {
|
|
101
|
+
const result = permission.permission({ displayName: 'John Doe' })
|
|
102
|
+
assert.equal(result.displayName, 'John Doe')
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
test('can explicitly set displayName to undefined', ({ assert }) => {
|
|
106
|
+
const result = permission.permission({ displayName: undefined })
|
|
107
|
+
assert.isUndefined(result.displayName)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
test('generates expirationTime for user type by default', ({ assert }) => {
|
|
111
|
+
const result = permission.permission({ type: 'user' })
|
|
112
|
+
assert.typeOf(result.expirationTime, 'number')
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
test('generates expirationTime for group type by default', ({ assert }) => {
|
|
116
|
+
const result = permission.permission({ type: 'group' })
|
|
117
|
+
assert.typeOf(result.expirationTime, 'number')
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
test('does not generate expirationTime for organization type', ({ assert }) => {
|
|
121
|
+
const result = permission.permission({ type: 'organization' })
|
|
122
|
+
assert.isUndefined(result.expirationTime)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
test('uses passed expirationTime', ({ assert }) => {
|
|
126
|
+
const time = Date.now() + 86400000
|
|
127
|
+
const result = permission.permission({ expirationTime: time })
|
|
128
|
+
assert.equal(result.expirationTime, time)
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
test('can explicitly set expirationTime to undefined', ({ assert }) => {
|
|
132
|
+
const result = permission.permission({ type: 'user', expirationTime: undefined })
|
|
133
|
+
assert.isUndefined(result.expirationTime)
|
|
134
|
+
})
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
test.group('userPermission()', (group) => {
|
|
138
|
+
let permission: Permission
|
|
139
|
+
|
|
140
|
+
group.each.setup(() => {
|
|
141
|
+
permission = new Permission()
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
test('returns an object', ({ assert }) => {
|
|
145
|
+
const result = permission.userPermission()
|
|
146
|
+
assert.typeOf(result, 'object')
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
test('has type set to user', ({ assert }) => {
|
|
150
|
+
const result = permission.userPermission()
|
|
151
|
+
assert.equal(result.type, 'user')
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
test('has displayName', ({ assert }) => {
|
|
155
|
+
const result = permission.userPermission()
|
|
156
|
+
assert.typeOf(result.displayName, 'string')
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
test('has expirationTime', ({ assert }) => {
|
|
160
|
+
const result = permission.userPermission()
|
|
161
|
+
assert.typeOf(result.expirationTime, 'number')
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
test('uses passed init values', ({ assert }) => {
|
|
165
|
+
const result = permission.userPermission({ role: 'owner', itemId: 'item-123' })
|
|
166
|
+
assert.equal(result.type, 'user')
|
|
167
|
+
assert.equal(result.role, 'owner')
|
|
168
|
+
assert.equal(result.itemId, 'item-123')
|
|
169
|
+
})
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
test.group('groupPermission()', (group) => {
|
|
173
|
+
let permission: Permission
|
|
174
|
+
|
|
175
|
+
group.each.setup(() => {
|
|
176
|
+
permission = new Permission()
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
test('returns an object', ({ assert }) => {
|
|
180
|
+
const result = permission.groupPermission()
|
|
181
|
+
assert.typeOf(result, 'object')
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
test('has type set to group', ({ assert }) => {
|
|
185
|
+
const result = permission.groupPermission()
|
|
186
|
+
assert.equal(result.type, 'group')
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
test('has displayName', ({ assert }) => {
|
|
190
|
+
const result = permission.groupPermission()
|
|
191
|
+
assert.typeOf(result.displayName, 'string')
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
test('has expirationTime', ({ assert }) => {
|
|
195
|
+
const result = permission.groupPermission()
|
|
196
|
+
assert.typeOf(result.expirationTime, 'number')
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
test('uses passed init values', ({ assert }) => {
|
|
200
|
+
const result = permission.groupPermission({ role: 'commenter', granteeId: 'group-456' })
|
|
201
|
+
assert.equal(result.type, 'group')
|
|
202
|
+
assert.equal(result.role, 'commenter')
|
|
203
|
+
assert.equal(result.granteeId, 'group-456')
|
|
204
|
+
})
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
test.group('organizationPermission()', (group) => {
|
|
208
|
+
let permission: Permission
|
|
209
|
+
|
|
210
|
+
group.each.setup(() => {
|
|
211
|
+
permission = new Permission()
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
test('returns an object', ({ assert }) => {
|
|
215
|
+
const result = permission.organizationPermission()
|
|
216
|
+
assert.typeOf(result, 'object')
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
test('has type set to organization', ({ assert }) => {
|
|
220
|
+
const result = permission.organizationPermission()
|
|
221
|
+
assert.equal(result.type, 'organization')
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
test('has displayName', ({ assert }) => {
|
|
225
|
+
const result = permission.organizationPermission()
|
|
226
|
+
assert.typeOf(result.displayName, 'string')
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
test('does not have expirationTime', ({ assert }) => {
|
|
230
|
+
const result = permission.organizationPermission()
|
|
231
|
+
assert.isUndefined(result.expirationTime)
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
test('uses passed init values', ({ assert }) => {
|
|
235
|
+
const result = permission.organizationPermission({ role: 'reader', addingUser: 'admin-789' })
|
|
236
|
+
assert.equal(result.type, 'organization')
|
|
237
|
+
assert.equal(result.role, 'reader')
|
|
238
|
+
assert.equal(result.addingUser, 'admin-789')
|
|
239
|
+
})
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
test.group('permissions()', (group) => {
|
|
243
|
+
let permission: Permission
|
|
244
|
+
|
|
245
|
+
group.each.setup(() => {
|
|
246
|
+
permission = new Permission()
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
test('returns an array', ({ assert }) => {
|
|
250
|
+
const result = permission.permissions()
|
|
251
|
+
assert.isArray(result)
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
test('returns 25 items by default', ({ assert }) => {
|
|
255
|
+
const result = permission.permissions()
|
|
256
|
+
assert.lengthOf(result, 25)
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
test('returns specified number of items', ({ assert }) => {
|
|
260
|
+
const result = permission.permissions(10)
|
|
261
|
+
assert.lengthOf(result, 10)
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
test('all items have the correct kind', ({ assert }) => {
|
|
265
|
+
const result = permission.permissions(5)
|
|
266
|
+
result.forEach((item) => {
|
|
267
|
+
assert.equal(item.kind, PermissionKind)
|
|
268
|
+
})
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
test('uses passed init values for all items', ({ assert }) => {
|
|
272
|
+
const result = permission.permissions(5, { type: 'user', role: 'owner' })
|
|
273
|
+
result.forEach((item) => {
|
|
274
|
+
assert.equal(item.type, 'user')
|
|
275
|
+
assert.equal(item.role, 'owner')
|
|
276
|
+
})
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
test('generates unique keys', ({ assert }) => {
|
|
280
|
+
const result = permission.permissions(10)
|
|
281
|
+
const keys = result.map((item) => item.key)
|
|
282
|
+
const uniqueKeys = new Set(keys)
|
|
283
|
+
assert.equal(keys.length, uniqueKeys.size)
|
|
284
|
+
})
|
|
285
|
+
})
|
|
@@ -163,6 +163,26 @@ test.group('ApiModel.constructor()', () => {
|
|
|
163
163
|
assert.equal(model.domain!.key, 'my-domain')
|
|
164
164
|
}).tags(['@modeling', '@api', '@creation'])
|
|
165
165
|
|
|
166
|
+
test('initializes domain dependency correctly when passed to constructor', ({ assert }) => {
|
|
167
|
+
const domain = new DataDomain()
|
|
168
|
+
domain.info.version = '1.0.0'
|
|
169
|
+
|
|
170
|
+
const model = new ApiModel({}, domain)
|
|
171
|
+
|
|
172
|
+
assert.isDefined(model.domain)
|
|
173
|
+
assert.equal(model.domain?.key, domain.key)
|
|
174
|
+
assert.lengthOf(model.dependencyList, 1)
|
|
175
|
+
assert.equal(model.dependencyList[0].key, domain.key)
|
|
176
|
+
assert.equal(model.dependencyList[0].version, '1.0.0')
|
|
177
|
+
}).tags(['@modeling', '@api', '@creation'])
|
|
178
|
+
|
|
179
|
+
test('throws if passed domain has no version', ({ assert }) => {
|
|
180
|
+
const domain = new DataDomain()
|
|
181
|
+
// No version set
|
|
182
|
+
|
|
183
|
+
assert.throws(() => new ApiModel({}, domain), /must have a version/)
|
|
184
|
+
}).tags(['@modeling', '@api', '@creation'])
|
|
185
|
+
|
|
166
186
|
test('notifies change when info is modified', async ({ assert }) => {
|
|
167
187
|
const model = new ApiModel()
|
|
168
188
|
let notified = false
|
|
@@ -186,4 +186,29 @@ test.group('ApiModel.exposeEntity()', () => {
|
|
|
186
186
|
assert.isDefined(nestedC)
|
|
187
187
|
assert.isUndefined(nestedD)
|
|
188
188
|
})
|
|
189
|
+
test('resolves root path collision by appending a number', ({ assert }) => {
|
|
190
|
+
const domain = new DataDomain()
|
|
191
|
+
domain.info.version = '1.0.0'
|
|
192
|
+
const dm = domain.addModel()
|
|
193
|
+
// Two entities that will generate the same plural path "/items"
|
|
194
|
+
const e1 = dm.addEntity({ info: { name: 'Item' } })
|
|
195
|
+
const e2 = dm.addEntity({ info: { name: 'Item' } }) // Same name
|
|
196
|
+
|
|
197
|
+
const model = new ApiModel()
|
|
198
|
+
model.attachDataDomain(domain)
|
|
199
|
+
|
|
200
|
+
// Expose first entity -> /items
|
|
201
|
+
const exp1 = model.exposeEntity({ key: e1.key })
|
|
202
|
+
assert.equal(exp1.collectionPath, '/items')
|
|
203
|
+
|
|
204
|
+
// Expose second entity -> should resolve collision to /items-1
|
|
205
|
+
const exp2 = model.exposeEntity({ key: e2.key })
|
|
206
|
+
assert.equal(exp2.collectionPath, '/items-1')
|
|
207
|
+
assert.equal(exp2.resourcePath, '/items-1/{id}')
|
|
208
|
+
|
|
209
|
+
// Expose third entity -> /items-2
|
|
210
|
+
const e3 = dm.addEntity({ info: { name: 'Item' } })
|
|
211
|
+
const exp3 = model.exposeEntity({ key: e3.key })
|
|
212
|
+
assert.equal(exp3.collectionPath, '/items-2')
|
|
213
|
+
}).tags(['@modeling', '@api'])
|
|
189
214
|
})
|
|
@@ -9,10 +9,10 @@ test.group('ApiModel.removeEntity()', () => {
|
|
|
9
9
|
const e1 = dm.addEntity()
|
|
10
10
|
const model = new ApiModel()
|
|
11
11
|
model.attachDataDomain(domain)
|
|
12
|
-
model.exposeEntity({ key: e1.key })
|
|
12
|
+
const exposure = model.exposeEntity({ key: e1.key })
|
|
13
13
|
assert.lengthOf(model.exposes, 1)
|
|
14
14
|
|
|
15
|
-
model.
|
|
15
|
+
model.removeExposedEntity(exposure.key)
|
|
16
16
|
assert.lengthOf(model.exposes, 0)
|
|
17
17
|
}).tags(['@modeling', '@api'])
|
|
18
18
|
|
|
@@ -26,18 +26,18 @@ test.group('ApiModel.removeEntity()', () => {
|
|
|
26
26
|
eA.addAssociation({ key: eB.key }, { info: { name: 'toB' } })
|
|
27
27
|
const model = new ApiModel()
|
|
28
28
|
model.attachDataDomain(domain)
|
|
29
|
-
model.exposeEntity({ key: eA.key }, { followAssociations: true })
|
|
29
|
+
const rootExposure = model.exposeEntity({ key: eA.key }, { followAssociations: true })
|
|
30
30
|
// Ensure nested exposure for B was created
|
|
31
31
|
const nestedB = model.exposes.find((e) => !e.isRoot && e.entity.key === eB.key)
|
|
32
32
|
assert.isDefined(nestedB)
|
|
33
33
|
assert.isAbove(model.exposes.length, 1)
|
|
34
34
|
|
|
35
|
-
// Remove root
|
|
36
|
-
model.
|
|
35
|
+
// Remove root exposure for A and expect children to be removed as well
|
|
36
|
+
model.removeExposedEntity(rootExposure.key)
|
|
37
37
|
assert.lengthOf(model.exposes, 0)
|
|
38
38
|
}).tags(['@modeling', '@api'])
|
|
39
39
|
|
|
40
|
-
test('
|
|
40
|
+
test('throws error if entity does not exist', ({ assert }) => {
|
|
41
41
|
const domain = new DataDomain()
|
|
42
42
|
domain.info.version = '1.0.0'
|
|
43
43
|
const dm = domain.addModel()
|
|
@@ -46,7 +46,10 @@ test.group('ApiModel.removeEntity()', () => {
|
|
|
46
46
|
model.attachDataDomain(domain)
|
|
47
47
|
model.exposeEntity({ key: e1.key })
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
assert.throws(
|
|
50
|
+
() => model.removeExposedEntity('non-existing-key'),
|
|
51
|
+
'Exposed entity with key "non-existing-key" not found.'
|
|
52
|
+
)
|
|
50
53
|
assert.lengthOf(model.exposes, 1, 'exposes count should remain unchanged')
|
|
51
54
|
}).tags(['@modeling', '@api'])
|
|
52
55
|
|
|
@@ -57,13 +60,13 @@ test.group('ApiModel.removeEntity()', () => {
|
|
|
57
60
|
const e1 = dm.addEntity()
|
|
58
61
|
const model = new ApiModel()
|
|
59
62
|
model.attachDataDomain(domain)
|
|
60
|
-
model.exposeEntity({ key: e1.key })
|
|
63
|
+
const exposure = model.exposeEntity({ key: e1.key })
|
|
61
64
|
|
|
62
65
|
let notified = false
|
|
63
66
|
model.addEventListener('change', () => {
|
|
64
67
|
notified = true
|
|
65
68
|
})
|
|
66
|
-
model.
|
|
69
|
+
model.removeExposedEntity(exposure.key)
|
|
67
70
|
await Promise.resolve() // Allow microtask to run
|
|
68
71
|
assert.isTrue(notified)
|
|
69
72
|
}).tags(['@modeling', '@api'])
|
|
@@ -74,7 +77,11 @@ test.group('ApiModel.removeEntity()', () => {
|
|
|
74
77
|
model.addEventListener('change', () => {
|
|
75
78
|
notified = true
|
|
76
79
|
})
|
|
77
|
-
|
|
80
|
+
try {
|
|
81
|
+
model.removeExposedEntity('no-notify-remove-entity')
|
|
82
|
+
} catch {
|
|
83
|
+
// ignore error
|
|
84
|
+
}
|
|
78
85
|
await Promise.resolve() // Allow microtask to run
|
|
79
86
|
assert.isFalse(notified)
|
|
80
87
|
}).tags(['@modeling', '@api'])
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { test } from '@japa/runner'
|
|
2
|
+
import { ApiModel, DataDomain } from '../../../src/index.js'
|
|
3
|
+
|
|
4
|
+
test.group('ExposedEntity Path Setter Validation', () => {
|
|
5
|
+
test('throws when setting collection path that collides with another root entity', ({ assert }) => {
|
|
6
|
+
const domain = new DataDomain()
|
|
7
|
+
domain.info.version = '1.0.0'
|
|
8
|
+
const dm = domain.addModel()
|
|
9
|
+
const e1 = dm.addEntity({ info: { name: 'A' } })
|
|
10
|
+
const e2 = dm.addEntity({ info: { name: 'B' } })
|
|
11
|
+
|
|
12
|
+
const model = new ApiModel()
|
|
13
|
+
model.attachDataDomain(domain)
|
|
14
|
+
|
|
15
|
+
model.exposeEntity({ key: e1.key }) // /as
|
|
16
|
+
const exp2 = model.exposeEntity({ key: e2.key }) // /bs
|
|
17
|
+
|
|
18
|
+
// Try to rename exp2's collection path to /as (collision)
|
|
19
|
+
assert.throws(
|
|
20
|
+
() => exp2.setCollectionPath('/as'),
|
|
21
|
+
'Collection path "/as" is already in use by another root entity.'
|
|
22
|
+
)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
test('throws when setting resource path that collides with another root entity (singleton)', ({ assert }) => {
|
|
26
|
+
const domain = new DataDomain()
|
|
27
|
+
domain.info.version = '1.0.0'
|
|
28
|
+
const dm = domain.addModel()
|
|
29
|
+
const e1 = dm.addEntity({ info: { name: 'A' } })
|
|
30
|
+
const e2 = dm.addEntity({ info: { name: 'B' } }) // Collection-less
|
|
31
|
+
|
|
32
|
+
const model = new ApiModel()
|
|
33
|
+
model.attachDataDomain(domain)
|
|
34
|
+
|
|
35
|
+
const exp1 = model.exposeEntity({ key: e1.key }) // /as, /as/{id}
|
|
36
|
+
// Manually force a collision for testing resource path (singleton vs singleton or singleton vs resource)
|
|
37
|
+
// Let's make exp2 a singleton
|
|
38
|
+
const exp2 = model.exposeEntity({ key: e2.key })
|
|
39
|
+
// Remove collection from exp2 so we can set arbitrary resource path
|
|
40
|
+
// Note: implementation of setResourcePath for collection-less allows any 2 segments
|
|
41
|
+
// We need to simulate the state where hasCollection is false
|
|
42
|
+
exp2.hasCollection = false
|
|
43
|
+
|
|
44
|
+
// Set exp1 resource path to something specific
|
|
45
|
+
exp1.hasCollection = false
|
|
46
|
+
exp1.setResourcePath('/shared/path')
|
|
47
|
+
|
|
48
|
+
// Try to set exp2 resource path to same
|
|
49
|
+
assert.throws(
|
|
50
|
+
() => exp2.setResourcePath('/shared/path'),
|
|
51
|
+
'Resource path "/shared/path" is already in use by another root entity.'
|
|
52
|
+
)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test('allows setting non-colliding paths for root entity', ({ assert }) => {
|
|
56
|
+
const domain = new DataDomain()
|
|
57
|
+
domain.info.version = '1.0.0'
|
|
58
|
+
const dm = domain.addModel()
|
|
59
|
+
const e1 = dm.addEntity({ info: { name: 'A' } })
|
|
60
|
+
|
|
61
|
+
const model = new ApiModel()
|
|
62
|
+
model.attachDataDomain(domain)
|
|
63
|
+
|
|
64
|
+
const exp1 = model.exposeEntity({ key: e1.key }) // /as
|
|
65
|
+
|
|
66
|
+
exp1.setCollectionPath('/new-path')
|
|
67
|
+
assert.equal(exp1.collectionPath, '/new-path')
|
|
68
|
+
|
|
69
|
+
exp1.hasCollection = false // allow arbitrary resource path
|
|
70
|
+
exp1.setResourcePath('/custom/resource')
|
|
71
|
+
assert.equal(exp1.resourcePath, '/custom/resource')
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test('does not validate collision for non-root entities', ({ assert }) => {
|
|
75
|
+
const domain = new DataDomain()
|
|
76
|
+
domain.info.version = '1.0.0'
|
|
77
|
+
const dm = domain.addModel()
|
|
78
|
+
const eA = dm.addEntity({ info: { name: 'A' } })
|
|
79
|
+
const eB = dm.addEntity({ info: { name: 'B' } })
|
|
80
|
+
eA.addAssociation({ key: eB.key }, { info: { name: 'toB' } })
|
|
81
|
+
|
|
82
|
+
const model = new ApiModel()
|
|
83
|
+
model.attachDataDomain(domain)
|
|
84
|
+
|
|
85
|
+
// expose A -> B
|
|
86
|
+
const rootExp = model.exposeEntity({ key: eA.key }, { followAssociations: true })
|
|
87
|
+
const nestedExp = model.exposes.find((e) => !e.isRoot)
|
|
88
|
+
|
|
89
|
+
assert.isDefined(nestedExp)
|
|
90
|
+
// Assuming root entity has collection path /as
|
|
91
|
+
assert.equal(rootExp.collectionPath, '/as')
|
|
92
|
+
|
|
93
|
+
// Try to set nested entity's collection path to /as
|
|
94
|
+
// Since it's not root, it should NOT check for collision with rootExp
|
|
95
|
+
// (In reality this path is technically valid relative to parent, but here we just check that
|
|
96
|
+
// it doesn't throw the specific root collision error)
|
|
97
|
+
|
|
98
|
+
// setCollectionPath logic for non-root:
|
|
99
|
+
// It doesn't check checks.
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
nestedExp?.setCollectionPath('/as')
|
|
103
|
+
} catch (e) {
|
|
104
|
+
assert.notInclude((e as Error).message, 'already in use by another root entity')
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
})
|