@api-client/core 0.19.19 → 0.19.20
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/authorization/Utils.js +3 -3
- package/build/src/authorization/Utils.js.map +1 -1
- package/build/src/modeling/ApiModel.d.ts +16 -5
- package/build/src/modeling/ApiModel.d.ts.map +1 -1
- package/build/src/modeling/ApiModel.js +17 -2
- package/build/src/modeling/ApiModel.js.map +1 -1
- package/build/src/modeling/ApiValidation.d.ts.map +1 -1
- package/build/src/modeling/ApiValidation.js +2 -1
- package/build/src/modeling/ApiValidation.js.map +1 -1
- package/build/src/modeling/DomainProperty.d.ts +12 -0
- package/build/src/modeling/DomainProperty.d.ts.map +1 -1
- package/build/src/modeling/DomainProperty.js +23 -28
- package/build/src/modeling/DomainProperty.js.map +1 -1
- package/build/src/modeling/DomainSerialization.js +1 -1
- package/build/src/modeling/DomainSerialization.js.map +1 -1
- package/build/src/modeling/ExposedEntity.d.ts +15 -1
- package/build/src/modeling/ExposedEntity.d.ts.map +1 -1
- package/build/src/modeling/ExposedEntity.js +42 -4
- package/build/src/modeling/ExposedEntity.js.map +1 -1
- package/build/src/modeling/actions/Action.d.ts.map +1 -1
- package/build/src/modeling/actions/Action.js +1 -0
- package/build/src/modeling/actions/Action.js.map +1 -1
- package/build/src/modeling/actions/ListAction.d.ts +3 -17
- package/build/src/modeling/actions/ListAction.d.ts.map +1 -1
- package/build/src/modeling/actions/ListAction.js +18 -38
- package/build/src/modeling/actions/ListAction.js.map +1 -1
- package/build/src/modeling/actions/SearchAction.d.ts +4 -4
- package/build/src/modeling/actions/SearchAction.d.ts.map +1 -1
- package/build/src/modeling/actions/SearchAction.js +16 -13
- package/build/src/modeling/actions/SearchAction.js.map +1 -1
- package/build/src/modeling/generators/oas_312/OasGenerator.d.ts +32 -0
- package/build/src/modeling/generators/oas_312/OasGenerator.d.ts.map +1 -0
- package/build/src/modeling/generators/oas_312/OasGenerator.js +1452 -0
- package/build/src/modeling/generators/oas_312/OasGenerator.js.map +1 -0
- package/build/src/modeling/generators/oas_312/OasSchemaGenerator.d.ts +27 -0
- package/build/src/modeling/generators/oas_312/OasSchemaGenerator.d.ts.map +1 -0
- package/build/src/modeling/generators/oas_312/OasSchemaGenerator.js +295 -0
- package/build/src/modeling/generators/oas_312/OasSchemaGenerator.js.map +1 -0
- package/build/src/modeling/generators/oas_312/types.d.ts +1010 -0
- package/build/src/modeling/generators/oas_312/types.d.ts.map +1 -0
- package/build/src/modeling/generators/oas_312/types.js +2 -0
- package/build/src/modeling/generators/oas_312/types.js.map +1 -0
- package/build/src/modeling/generators/oas_320/OasGenerator.d.ts +16 -0
- package/build/src/modeling/generators/oas_320/OasGenerator.d.ts.map +1 -0
- package/build/src/modeling/generators/oas_320/OasGenerator.js +306 -0
- package/build/src/modeling/generators/oas_320/OasGenerator.js.map +1 -0
- package/build/src/modeling/generators/oas_320/OasSchemaGenerator.d.ts +25 -0
- package/build/src/modeling/generators/oas_320/OasSchemaGenerator.d.ts.map +1 -0
- package/build/src/modeling/generators/oas_320/OasSchemaGenerator.js +237 -0
- package/build/src/modeling/generators/oas_320/OasSchemaGenerator.js.map +1 -0
- package/build/src/modeling/generators/oas_320/types.d.ts +1219 -0
- package/build/src/modeling/generators/oas_320/types.d.ts.map +1 -0
- package/build/src/modeling/generators/oas_320/types.js +2 -0
- package/build/src/modeling/generators/oas_320/types.js.map +1 -0
- package/build/src/modeling/types.d.ts +50 -13
- package/build/src/modeling/types.d.ts.map +1 -1
- package/build/src/modeling/types.js.map +1 -1
- package/build/src/modeling/validation/api_model_rules.d.ts +1 -0
- package/build/src/modeling/validation/api_model_rules.d.ts.map +1 -1
- package/build/src/modeling/validation/api_model_rules.js +105 -29
- package/build/src/modeling/validation/api_model_rules.js.map +1 -1
- package/build/src/models/ProjectRequest.d.ts.map +1 -1
- package/build/src/models/ProjectRequest.js +0 -4
- package/build/src/models/ProjectRequest.js.map +1 -1
- package/build/src/models/transformers/ArcDexieTransformer.d.ts.map +1 -1
- package/build/src/models/transformers/ArcDexieTransformer.js +0 -4
- package/build/src/models/transformers/ArcDexieTransformer.js.map +1 -1
- package/build/src/models/transformers/ImportUtils.js +1 -1
- package/build/src/models/transformers/ImportUtils.js.map +1 -1
- package/build/src/models/transformers/PostmanBackupTransformer.d.ts.map +1 -1
- package/build/src/models/transformers/PostmanBackupTransformer.js +0 -4
- package/build/src/models/transformers/PostmanBackupTransformer.js.map +1 -1
- package/build/src/runtime/constants.d.ts +7 -0
- package/build/src/runtime/constants.d.ts.map +1 -0
- package/build/src/runtime/constants.js +8 -0
- package/build/src/runtime/constants.js.map +1 -0
- package/build/src/runtime/http-engine/ntlm/Des.d.ts.map +1 -1
- package/build/src/runtime/http-engine/ntlm/Des.js +1 -0
- package/build/src/runtime/http-engine/ntlm/Des.js.map +1 -1
- package/build/src/runtime/variables/EvalFunctions.d.ts.map +1 -1
- package/build/src/runtime/variables/EvalFunctions.js +0 -1
- package/build/src/runtime/variables/EvalFunctions.js.map +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/eslint.config.js +6 -0
- package/package.json +3 -1
- package/src/authorization/Utils.ts +3 -3
- package/src/modeling/ApiModel.ts +23 -8
- package/src/modeling/ApiValidation.ts +2 -0
- package/src/modeling/DomainProperty.ts +22 -18
- package/src/modeling/DomainSerialization.ts +1 -1
- package/src/modeling/ExposedEntity.ts +44 -4
- package/src/modeling/actions/Action.ts +1 -0
- package/src/modeling/actions/ListAction.ts +12 -30
- package/src/modeling/actions/SearchAction.ts +11 -8
- package/src/modeling/generators/oas_312/OasGenerator.ts +1685 -0
- package/src/modeling/generators/oas_312/OasSchemaGenerator.ts +322 -0
- package/src/modeling/generators/oas_312/types.ts +1052 -0
- package/src/modeling/generators/oas_320/OasGenerator.ts +359 -0
- package/src/modeling/generators/oas_320/OasSchemaGenerator.ts +255 -0
- package/src/modeling/generators/oas_320/types.ts +1259 -0
- package/src/modeling/types.ts +55 -22
- package/src/modeling/validation/api_model_rules.ts +103 -32
- package/src/models/ProjectRequest.ts +0 -4
- package/src/models/transformers/ArcDexieTransformer.ts +0 -4
- package/src/models/transformers/ImportUtils.ts +1 -1
- package/src/models/transformers/PostmanBackupTransformer.ts +0 -5
- package/src/runtime/constants.ts +9 -0
- package/src/runtime/http-engine/ntlm/Des.ts +1 -0
- package/src/runtime/variables/EvalFunctions.ts +0 -1
- package/tests/test-utils.ts +6 -2
- package/tests/unit/decorators/observed.spec.ts +8 -24
- package/tests/unit/decorators/observed_recursive.spec.ts +0 -1
- package/tests/unit/events/EventsTestHelpers.ts +0 -1
- package/tests/unit/events/events_polyfills.ts +0 -1
- package/tests/unit/legacy-transformers/DataTestHelper.ts +0 -2
- package/tests/unit/legacy-transformers/LegacyExportProcessor.spec.ts +0 -1
- package/tests/unit/modeling/actions/ListAction.spec.ts +9 -69
- package/tests/unit/modeling/actions/SearchAction.spec.ts +9 -35
- package/tests/unit/modeling/api_model.spec.ts +28 -0
- package/tests/unit/modeling/definitions/sku.spec.ts +0 -2
- package/tests/unit/modeling/domain_property.spec.ts +20 -1
- package/tests/unit/modeling/exposed_entity.spec.ts +71 -0
- package/tests/unit/modeling/generators/OasGenerator.spec.ts +302 -0
- package/tests/unit/modeling/validation/api_model_rules.spec.ts +113 -15
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { test } from '@japa/runner'
|
|
2
|
+
import { OasGenerator } from '../../../../src/modeling/generators/oas_312/OasGenerator.js'
|
|
3
|
+
import { ApiModel } from '../../../../src/modeling/ApiModel.js'
|
|
4
|
+
import { DataDomain } from '../../../../src/modeling/DataDomain.js'
|
|
5
|
+
import { SemanticType } from '../../../../src/modeling/Semantics.js'
|
|
6
|
+
import { AllowAuthenticatedAccessRule } from '../../../../src/modeling/rules/AllowAuthenticated.js'
|
|
7
|
+
|
|
8
|
+
test.group('OasGenerator', (group) => {
|
|
9
|
+
let domain: DataDomain
|
|
10
|
+
let api: ApiModel
|
|
11
|
+
|
|
12
|
+
group.each.setup(() => {
|
|
13
|
+
domain = new DataDomain({ info: { name: 'Test Domain', version: '1.0.0' } })
|
|
14
|
+
const model = domain.addModel({ info: { name: 'model1' } })
|
|
15
|
+
const user = model.addEntity({ info: { name: 'user' } })
|
|
16
|
+
user.addSemantic({ id: SemanticType.User })
|
|
17
|
+
user.addProperty({
|
|
18
|
+
type: 'string',
|
|
19
|
+
required: true,
|
|
20
|
+
info: { name: 'id' },
|
|
21
|
+
index: true,
|
|
22
|
+
primary: true,
|
|
23
|
+
readOnly: true,
|
|
24
|
+
})
|
|
25
|
+
const name = user.addProperty({ type: 'string', required: true, info: { name: 'name' }, search: true })
|
|
26
|
+
const email = user.addProperty({
|
|
27
|
+
type: 'string',
|
|
28
|
+
required: true,
|
|
29
|
+
index: true,
|
|
30
|
+
unique: true,
|
|
31
|
+
search: true,
|
|
32
|
+
info: { name: 'email' },
|
|
33
|
+
semantics: [{ id: SemanticType.Email }, { id: SemanticType.Username }],
|
|
34
|
+
})
|
|
35
|
+
const passwd = user.addProperty({
|
|
36
|
+
type: 'string',
|
|
37
|
+
required: true,
|
|
38
|
+
info: { name: 'password' },
|
|
39
|
+
writeOnly: true,
|
|
40
|
+
semantics: [{ id: SemanticType.Password }],
|
|
41
|
+
})
|
|
42
|
+
const role = user.addProperty({
|
|
43
|
+
type: 'string',
|
|
44
|
+
required: true,
|
|
45
|
+
info: { name: 'role' },
|
|
46
|
+
schema: { enum: ['admin', 'user'] },
|
|
47
|
+
index: true,
|
|
48
|
+
})
|
|
49
|
+
role.addSemantic({ id: SemanticType.UserRole })
|
|
50
|
+
user.addProperty({
|
|
51
|
+
type: 'string',
|
|
52
|
+
required: true,
|
|
53
|
+
info: { name: 'avatar' },
|
|
54
|
+
semantics: [{ id: SemanticType.ImageURL }],
|
|
55
|
+
})
|
|
56
|
+
user.addProperty({
|
|
57
|
+
type: 'string',
|
|
58
|
+
required: false,
|
|
59
|
+
info: { name: 'cv' },
|
|
60
|
+
semantics: [{ id: SemanticType.FileURL }],
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
const post = model.addEntity({
|
|
64
|
+
info: { name: 'post', description: 'Represents a post published by a user', displayName: 'Post' },
|
|
65
|
+
})
|
|
66
|
+
post.addProperty({
|
|
67
|
+
type: 'string',
|
|
68
|
+
required: true,
|
|
69
|
+
info: { name: 'id', description: 'Post ID', displayName: 'ID' },
|
|
70
|
+
primary: true,
|
|
71
|
+
readOnly: true,
|
|
72
|
+
})
|
|
73
|
+
const postTitle = post.addProperty({
|
|
74
|
+
type: 'string',
|
|
75
|
+
required: true,
|
|
76
|
+
info: { name: 'title', description: 'Post title', displayName: 'Title' },
|
|
77
|
+
semantics: [{ id: SemanticType.Title }],
|
|
78
|
+
schema: {
|
|
79
|
+
examples: ['My blog title', 'Another title'],
|
|
80
|
+
minimum: 5,
|
|
81
|
+
maximum: 255,
|
|
82
|
+
},
|
|
83
|
+
search: true,
|
|
84
|
+
})
|
|
85
|
+
const postSlug = post.addProperty({
|
|
86
|
+
type: 'string',
|
|
87
|
+
required: true,
|
|
88
|
+
info: {
|
|
89
|
+
name: 'slug',
|
|
90
|
+
description: 'Post slug. It is auto generated when a post is created and cannot be changed.',
|
|
91
|
+
displayName: 'Slug',
|
|
92
|
+
},
|
|
93
|
+
readOnly: true,
|
|
94
|
+
index: true,
|
|
95
|
+
semantics: [{ id: SemanticType.PublicUniqueName, config: {} }],
|
|
96
|
+
})
|
|
97
|
+
post.addProperty({ type: 'string', required: true, info: { name: 'content' }, search: true })
|
|
98
|
+
const postCreatedAt = post.addProperty({
|
|
99
|
+
type: 'string',
|
|
100
|
+
required: true,
|
|
101
|
+
info: { name: 'created_at' },
|
|
102
|
+
index: true,
|
|
103
|
+
semantics: [{ id: SemanticType.CreatedTimestamp }],
|
|
104
|
+
})
|
|
105
|
+
post.addProperty({
|
|
106
|
+
type: 'string',
|
|
107
|
+
required: true,
|
|
108
|
+
info: { name: 'updated_at' },
|
|
109
|
+
index: true,
|
|
110
|
+
semantics: [{ id: SemanticType.UpdatedTimestamp }],
|
|
111
|
+
})
|
|
112
|
+
post.addAssociation({
|
|
113
|
+
info: { name: 'author' },
|
|
114
|
+
targets: [{ key: user.key }],
|
|
115
|
+
multiple: false,
|
|
116
|
+
schema: { linked: true },
|
|
117
|
+
semantics: [{ id: SemanticType.ResourceOwnerIdentifier }],
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
api = new ApiModel({ key: 'api1', info: { name: 'Test API', version: '1.0.0' } }, domain)
|
|
121
|
+
api.license = {
|
|
122
|
+
name: 'BSD-3-Clause-No-Nuclear-License-2014',
|
|
123
|
+
url: 'https://opensource.org/licenses/BSD-3-Clause-No-Nuclear-License-2014',
|
|
124
|
+
}
|
|
125
|
+
api.contact = {
|
|
126
|
+
email: 'info@api.com',
|
|
127
|
+
name: 'API Admin',
|
|
128
|
+
url: 'https://api.com',
|
|
129
|
+
}
|
|
130
|
+
api.accessRule = [new AllowAuthenticatedAccessRule()]
|
|
131
|
+
api.session = {
|
|
132
|
+
properties: [email.key],
|
|
133
|
+
secret: 'test-secret',
|
|
134
|
+
cookie: {
|
|
135
|
+
enabled: true,
|
|
136
|
+
httpOnly: true,
|
|
137
|
+
secure: true,
|
|
138
|
+
kind: 'cookie',
|
|
139
|
+
lifetime: '30d',
|
|
140
|
+
name: 'sis',
|
|
141
|
+
sameSite: 'lax',
|
|
142
|
+
},
|
|
143
|
+
jwt: {
|
|
144
|
+
enabled: true,
|
|
145
|
+
kind: 'jwt',
|
|
146
|
+
lifetime: '30d',
|
|
147
|
+
},
|
|
148
|
+
}
|
|
149
|
+
api.authentication = {
|
|
150
|
+
strategy: 'UsernamePassword',
|
|
151
|
+
passwordKey: passwd.key,
|
|
152
|
+
}
|
|
153
|
+
api.authorization = {
|
|
154
|
+
strategy: 'RBAC',
|
|
155
|
+
roleKey: role.key,
|
|
156
|
+
}
|
|
157
|
+
api.pagination = { kind: 'cursor', defaultLimit: 50, maxLimit: 70 }
|
|
158
|
+
const userExposure = api.exposeEntity({ key: user.key })
|
|
159
|
+
userExposure.paginationContract = {
|
|
160
|
+
filterableFields: [role.key],
|
|
161
|
+
sortableFields: [name.key, email.key, role.key],
|
|
162
|
+
searchableFields: [name.key, email.key],
|
|
163
|
+
}
|
|
164
|
+
const postExposure = api.exposeEntity({ key: post.key })
|
|
165
|
+
postExposure.paginationContract = {
|
|
166
|
+
filterableFields: [postSlug.key, postCreatedAt.key],
|
|
167
|
+
sortableFields: [postSlug.key, postCreatedAt.key],
|
|
168
|
+
searchableFields: [postTitle.key],
|
|
169
|
+
}
|
|
170
|
+
userExposure.addAction({ kind: 'list', cacheTtl: 1800 })
|
|
171
|
+
userExposure.addAction({ kind: 'create', accessRule: [{ type: 'allowPublic' }] })
|
|
172
|
+
userExposure.addAction({ kind: 'read' })
|
|
173
|
+
userExposure.addAction({ kind: 'update', allowedMethods: ['PUT'] })
|
|
174
|
+
userExposure.addAction({ kind: 'delete', strategy: 'soft', retentionPeriod: 30 })
|
|
175
|
+
userExposure.addAction({ kind: 'search', maxAstDepth: 1 })
|
|
176
|
+
postExposure.addAction({ kind: 'list', cacheTtl: 3600 })
|
|
177
|
+
postExposure.addAction({ kind: 'update', allowedMethods: ['PUT', 'PATCH'] })
|
|
178
|
+
postExposure.addAction({ kind: 'delete', strategy: 'hard' })
|
|
179
|
+
postExposure.addAction({ kind: 'search', maxAstDepth: 2 })
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
test('generates basic OAS structure', async ({ assert }) => {
|
|
183
|
+
const generator = new OasGenerator(api)
|
|
184
|
+
const oas = generator.generate()
|
|
185
|
+
|
|
186
|
+
assert.equal(oas.openapi, '3.1.0')
|
|
187
|
+
assert.typeOf(oas.info, 'object')
|
|
188
|
+
assert.isObject(oas.paths)
|
|
189
|
+
assert.isObject(oas.components)
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
test('generates cookie auth endpoints', ({ assert }) => {
|
|
193
|
+
const generator = new OasGenerator(api)
|
|
194
|
+
const oas = generator.generate()
|
|
195
|
+
|
|
196
|
+
const components = oas.components as any
|
|
197
|
+
assert.deepEqual(components.securitySchemes.CookieAuth, {
|
|
198
|
+
type: 'apiKey',
|
|
199
|
+
in: 'cookie',
|
|
200
|
+
name: 'sis',
|
|
201
|
+
description: 'Session cookie obtained after authenticating',
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
const paths = oas.paths as any
|
|
205
|
+
assert.isObject(paths['/auth/cookie'].post)
|
|
206
|
+
assert.isObject(paths['/auth/cookie/logout'].post)
|
|
207
|
+
assert.include(paths['/auth/cookie'].post.tags, 'Authentication')
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
test('generates jwt auth endpoints', ({ assert }) => {
|
|
211
|
+
const generator = new OasGenerator(api)
|
|
212
|
+
const oas = generator.generate()
|
|
213
|
+
|
|
214
|
+
const components = oas.components as any
|
|
215
|
+
assert.deepEqual(components.securitySchemes.BearerAuth, {
|
|
216
|
+
type: 'http',
|
|
217
|
+
scheme: 'bearer',
|
|
218
|
+
bearerFormat: 'JWT',
|
|
219
|
+
description: 'JWT authorization obtained after trading Username/Password credentials',
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
const paths = oas.paths as any
|
|
223
|
+
assert.isObject(paths['/auth/token'].post)
|
|
224
|
+
assert.isObject(paths['/auth/token/{token}'].delete)
|
|
225
|
+
assert.include(paths['/auth/token'].post.tags, 'Authentication')
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
test('generates paths for exposed entity actions', ({ assert }) => {
|
|
229
|
+
const generator = new OasGenerator(api)
|
|
230
|
+
const oas = generator.generate()
|
|
231
|
+
|
|
232
|
+
const paths = oas.paths as any
|
|
233
|
+
|
|
234
|
+
// User endpoints (CRUD)
|
|
235
|
+
assert.isObject(paths['/users'].get, 'User list')
|
|
236
|
+
assert.isObject(paths['/users'].post, 'User create')
|
|
237
|
+
assert.isObject(paths['/users/{id}'].get, 'User read')
|
|
238
|
+
assert.isObject(paths['/users/{id}'].put, 'User put')
|
|
239
|
+
assert.isObject(paths['/users/{id}'].delete, 'User delete')
|
|
240
|
+
|
|
241
|
+
// Post endpoints (list, update, delete)
|
|
242
|
+
assert.isObject(paths['/posts'].get)
|
|
243
|
+
assert.isUndefined(paths['/posts'].post) // no create Action
|
|
244
|
+
assert.isUndefined(paths['/posts/{id}'].get) // no read action
|
|
245
|
+
assert.isObject(paths['/posts/{id}'].patch)
|
|
246
|
+
assert.isObject(paths['/posts/{id}'].put)
|
|
247
|
+
assert.isObject(paths['/posts/{id}'].delete)
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
test('generates standard error responses', ({ assert }) => {
|
|
251
|
+
const generator = new OasGenerator(api)
|
|
252
|
+
const oas = generator.generate()
|
|
253
|
+
|
|
254
|
+
const paths = oas.paths as any
|
|
255
|
+
const postUser = paths['/users'].post
|
|
256
|
+
const getUser = paths['/users'].get
|
|
257
|
+
|
|
258
|
+
assert.isObject(postUser.responses['400']) // Validation Error
|
|
259
|
+
assert.equal(
|
|
260
|
+
postUser.responses['400'].content['application/json'].schema.$ref,
|
|
261
|
+
'#/components/schemas/Error400BadRequestInvalidFormat'
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
// Should have 401 when using auth
|
|
265
|
+
assert.isObject(getUser.responses['401'])
|
|
266
|
+
assert.equal(
|
|
267
|
+
getUser.responses['401'].content['application/json'].schema.$ref,
|
|
268
|
+
'#/components/schemas/Error401Unauthorized'
|
|
269
|
+
)
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
test('generates search endpoints and AST queries', ({ assert }) => {
|
|
273
|
+
const generator = new OasGenerator(api)
|
|
274
|
+
const oas = generator.generate()
|
|
275
|
+
|
|
276
|
+
const paths = oas.paths as any
|
|
277
|
+
assert.isObject(paths['/users/search'].post)
|
|
278
|
+
assert.isObject(paths['/posts/search'].post)
|
|
279
|
+
|
|
280
|
+
assert.include(paths['/users/search'].post.tags, 'User')
|
|
281
|
+
assert.include(paths['/posts/search'].post.tags, 'Post')
|
|
282
|
+
|
|
283
|
+
const components = oas.components as any
|
|
284
|
+
assert.isObject(components.schemas.SearchWhereAST)
|
|
285
|
+
assert.isObject(components.schemas.SearchWhereNode)
|
|
286
|
+
assert.isObject(components.schemas.SearchWhereOperatorMap)
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
test('generates file upload endpoint', ({ assert }) => {
|
|
290
|
+
const generator = new OasGenerator(api)
|
|
291
|
+
const oas = generator.generate()
|
|
292
|
+
|
|
293
|
+
const paths = oas.paths as any
|
|
294
|
+
assert.isObject(paths['/upload'].post)
|
|
295
|
+
assert.include(paths['/upload'].post.tags, 'File Upload')
|
|
296
|
+
|
|
297
|
+
const requestBody = paths['/upload'].post.requestBody
|
|
298
|
+
assert.isObject(requestBody.content['*/*'])
|
|
299
|
+
assert.equal(requestBody.content['*/*'].schema.type, 'string')
|
|
300
|
+
assert.equal(requestBody.content['*/*'].schema.format, 'binary')
|
|
301
|
+
})
|
|
302
|
+
})
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
validateApiModelMetadata,
|
|
10
10
|
validateExposedEntity,
|
|
11
11
|
validateAction,
|
|
12
|
+
validateApiPagination,
|
|
12
13
|
} from '../../../../src/modeling/validation/api_model_rules.js'
|
|
13
14
|
import { ApiValidation } from '../../../../src/modeling/ApiValidation.js'
|
|
14
15
|
import { ExposedEntity } from '../../../../src/modeling/ExposedEntity.js'
|
|
@@ -248,25 +249,60 @@ test.group('ApiModel Validation', () => {
|
|
|
248
249
|
assert.isTrue(issues.some((i) => i.code === 'EXPOSURE_INVALID_RESOURCE_PATH_FORMAT'))
|
|
249
250
|
})
|
|
250
251
|
|
|
251
|
-
test('
|
|
252
|
-
const
|
|
253
|
-
const
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
252
|
+
test('validateExposedEntity - List needs pagination contract', ({ assert }) => {
|
|
253
|
+
const domain = new DataDomain({ info: { version: '1.0.0' } })
|
|
254
|
+
const model = new ApiModel({}, domain)
|
|
255
|
+
const exposure = new ExposedEntity(model, {
|
|
256
|
+
entity: { key: 'fake' },
|
|
257
|
+
hasCollection: true,
|
|
258
|
+
collectionPath: '/items',
|
|
259
|
+
resourcePath: '/items/{id}',
|
|
260
|
+
})
|
|
261
|
+
exposure.actions = [new ListAction(exposure)]
|
|
257
262
|
|
|
258
|
-
const issues =
|
|
259
|
-
assert.isTrue(issues.some((i) => i.code === '
|
|
263
|
+
const issues = validateExposedEntity(exposure, model)
|
|
264
|
+
assert.isTrue(issues.some((i) => i.code === 'EXPOSURE_MISSING_PAGINATION_CONTRACT'))
|
|
260
265
|
})
|
|
261
266
|
|
|
262
|
-
test('
|
|
263
|
-
const
|
|
264
|
-
const
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
+
test('validateExposedEntity - Search needs fields', ({ assert }) => {
|
|
268
|
+
const domain = new DataDomain({ info: { version: '1.0.0' } })
|
|
269
|
+
const model = new ApiModel({}, domain)
|
|
270
|
+
const exposure = new ExposedEntity(model, {
|
|
271
|
+
entity: { key: 'fake' },
|
|
272
|
+
hasCollection: true,
|
|
273
|
+
collectionPath: '/items',
|
|
274
|
+
resourcePath: '/items/{id}',
|
|
275
|
+
})
|
|
276
|
+
exposure.actions = [new SearchAction(exposure)]
|
|
277
|
+
exposure.paginationContract = {
|
|
278
|
+
searchableFields: [],
|
|
279
|
+
filterableFields: [],
|
|
280
|
+
sortableFields: [],
|
|
281
|
+
}
|
|
267
282
|
|
|
268
|
-
const issues =
|
|
269
|
-
assert.isTrue(issues.some((i) => i.code === '
|
|
283
|
+
const issues = validateExposedEntity(exposure, model)
|
|
284
|
+
assert.isTrue(issues.some((i) => i.code === 'EXPOSURE_SEARCH_MISSING_FIELDS'))
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
test('validateExposedEntity - List needs filters and sorting', ({ assert }) => {
|
|
288
|
+
const domain = new DataDomain({ info: { version: '1.0.0' } })
|
|
289
|
+
const model = new ApiModel({}, domain)
|
|
290
|
+
const exposure = new ExposedEntity(model, {
|
|
291
|
+
entity: { key: 'fake' },
|
|
292
|
+
hasCollection: true,
|
|
293
|
+
collectionPath: '/items',
|
|
294
|
+
resourcePath: '/items/{id}',
|
|
295
|
+
})
|
|
296
|
+
exposure.actions = [new ListAction(exposure)]
|
|
297
|
+
exposure.paginationContract = {
|
|
298
|
+
searchableFields: [],
|
|
299
|
+
filterableFields: [],
|
|
300
|
+
sortableFields: [],
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const issues = validateExposedEntity(exposure, model)
|
|
304
|
+
assert.isTrue(issues.some((i) => i.code === 'EXPOSURE_LIST_MISSING_FILTERS'))
|
|
305
|
+
assert.isTrue(issues.some((i) => i.code === 'EXPOSURE_LIST_MISSING_SORTING'))
|
|
270
306
|
})
|
|
271
307
|
|
|
272
308
|
test('validateAction - Delete needs strategy', ({ assert }) => {
|
|
@@ -289,6 +325,68 @@ test.group('ApiModel Validation', () => {
|
|
|
289
325
|
const issues = validateAction(action, exposure, model.key)
|
|
290
326
|
assert.isTrue(issues.some((i) => i.code === 'ACTION_UPDATE_MISSING_METHODS'))
|
|
291
327
|
})
|
|
328
|
+
|
|
329
|
+
test('validateApiPagination - missing pagination', ({ assert }) => {
|
|
330
|
+
const domain = new DataDomain({ info: { version: '1.0.0' } })
|
|
331
|
+
const model = new ApiModel({}, domain)
|
|
332
|
+
// @ts-expect-error testing missing pagination property
|
|
333
|
+
model.pagination = undefined
|
|
334
|
+
|
|
335
|
+
const exposure = new ExposedEntity(model, {
|
|
336
|
+
entity: { key: 'fake' },
|
|
337
|
+
hasCollection: true,
|
|
338
|
+
collectionPath: '/items',
|
|
339
|
+
resourcePath: '/items/{id}',
|
|
340
|
+
})
|
|
341
|
+
exposure.actions = [new ListAction(exposure)]
|
|
342
|
+
model.exposes.set(exposure.key, exposure)
|
|
343
|
+
|
|
344
|
+
const issues = validateApiPagination(model)
|
|
345
|
+
assert.isTrue(issues.some((i) => i.code === 'API_MISSING_PAGINATION'))
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
test('validateApiPagination - missing default limit', ({ assert }) => {
|
|
349
|
+
const domain = new DataDomain({ info: { version: '1.0.0' } })
|
|
350
|
+
const model = new ApiModel(
|
|
351
|
+
{
|
|
352
|
+
pagination: { kind: 'offset', maxLimit: 100 },
|
|
353
|
+
},
|
|
354
|
+
domain
|
|
355
|
+
)
|
|
356
|
+
const exposure = new ExposedEntity(model, {
|
|
357
|
+
entity: { key: 'fake' },
|
|
358
|
+
hasCollection: true,
|
|
359
|
+
collectionPath: '/items',
|
|
360
|
+
resourcePath: '/items/{id}',
|
|
361
|
+
})
|
|
362
|
+
exposure.actions = [new ListAction(exposure)]
|
|
363
|
+
model.exposes.set(exposure.key, exposure)
|
|
364
|
+
|
|
365
|
+
const issues = validateApiPagination(model)
|
|
366
|
+
assert.isTrue(issues.some((i) => i.code === 'API_MISSING_PAGINATION_DEFAULT_LIMIT'))
|
|
367
|
+
})
|
|
368
|
+
|
|
369
|
+
test('validateApiPagination - missing max limit', ({ assert }) => {
|
|
370
|
+
const domain = new DataDomain({ info: { version: '1.0.0' } })
|
|
371
|
+
const model = new ApiModel(
|
|
372
|
+
{
|
|
373
|
+
pagination: { kind: 'offset', defaultLimit: 20 },
|
|
374
|
+
},
|
|
375
|
+
domain
|
|
376
|
+
)
|
|
377
|
+
const exposure = new ExposedEntity(model, {
|
|
378
|
+
entity: { key: 'fake' },
|
|
379
|
+
hasCollection: true,
|
|
380
|
+
collectionPath: '/items',
|
|
381
|
+
resourcePath: '/items/{id}',
|
|
382
|
+
})
|
|
383
|
+
exposure.actions = [new ListAction(exposure)]
|
|
384
|
+
model.exposes.set(exposure.key, exposure)
|
|
385
|
+
|
|
386
|
+
const issues = validateApiPagination(model)
|
|
387
|
+
assert.isTrue(issues.some((i) => i.code === 'API_MISSING_PAGINATION_MAX_LIMIT'))
|
|
388
|
+
})
|
|
389
|
+
|
|
292
390
|
test('validateApiModel - success aggregates without issues', ({ assert }) => {
|
|
293
391
|
const domain = new DataDomain({ info: { version: '1.0.0' } })
|
|
294
392
|
const modelNode = domain.addModel({ key: 'users' })
|