@api-client/core 0.14.2 → 0.14.4
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/browser.d.ts +1 -1
- package/build/src/browser.d.ts.map +1 -1
- package/build/src/browser.js +1 -0
- package/build/src/browser.js.map +1 -1
- package/build/src/events/BaseEvents.d.ts +4 -0
- package/build/src/events/BaseEvents.d.ts.map +1 -1
- package/build/src/events/BaseEvents.js.map +1 -1
- package/build/src/index.d.ts +2 -1
- package/build/src/index.d.ts.map +1 -1
- package/build/src/index.js +2 -0
- package/build/src/index.js.map +1 -1
- package/build/src/modeling/ApiFile.d.ts +23 -0
- package/build/src/modeling/ApiFile.d.ts.map +1 -0
- package/build/src/modeling/ApiFile.js +44 -0
- package/build/src/modeling/ApiFile.js.map +1 -0
- package/build/src/modeling/ApiModel.d.ts +159 -0
- package/build/src/modeling/ApiModel.d.ts.map +1 -0
- package/build/src/modeling/ApiModel.js +237 -0
- package/build/src/modeling/ApiModel.js.map +1 -0
- package/build/src/modeling/DataDomain.d.ts +1 -1
- package/build/src/modeling/DataDomain.d.ts.map +1 -1
- package/build/src/modeling/DataDomain.js +1 -3
- package/build/src/modeling/DataDomain.js.map +1 -1
- package/build/src/modeling/DomainEntity.js +1 -1
- package/build/src/modeling/DomainEntity.js.map +1 -1
- package/build/src/modeling/DomainFile.d.ts +1 -2
- package/build/src/modeling/DomainFile.d.ts.map +1 -1
- package/build/src/modeling/DomainFile.js +3 -41
- package/build/src/modeling/DomainFile.js.map +1 -1
- package/build/src/modeling/Semantics.d.ts +55 -8
- package/build/src/modeling/Semantics.d.ts.map +1 -1
- package/build/src/modeling/Semantics.js +62 -8
- package/build/src/modeling/Semantics.js.map +1 -1
- package/build/src/modeling/amf/ShapeGenerator.d.ts.map +1 -1
- package/build/src/modeling/amf/ShapeGenerator.js.map +1 -1
- package/build/src/modeling/types.d.ts +491 -0
- package/build/src/modeling/types.d.ts.map +1 -1
- package/build/src/modeling/types.js.map +1 -1
- package/build/src/models/kinds.d.ts +3 -0
- package/build/src/models/kinds.d.ts.map +1 -1
- package/build/src/models/kinds.js +3 -0
- package/build/src/models/kinds.js.map +1 -1
- package/build/src/models/store/File.d.ts +19 -2
- package/build/src/models/store/File.d.ts.map +1 -1
- package/build/src/models/store/File.js +100 -13
- package/build/src/models/store/File.js.map +1 -1
- package/build/src/models/store/Group.d.ts +76 -2
- package/build/src/models/store/Group.d.ts.map +1 -1
- package/build/src/models/store/Group.js +84 -1
- package/build/src/models/store/Group.js.map +1 -1
- package/build/src/sdk/GroupsSdk.d.ts +41 -0
- package/build/src/sdk/GroupsSdk.d.ts.map +1 -0
- package/build/src/sdk/GroupsSdk.js +135 -0
- package/build/src/sdk/GroupsSdk.js.map +1 -0
- package/build/src/sdk/RouteBuilder.d.ts +2 -0
- package/build/src/sdk/RouteBuilder.d.ts.map +1 -1
- package/build/src/sdk/RouteBuilder.js +6 -0
- package/build/src/sdk/RouteBuilder.js.map +1 -1
- package/build/src/sdk/Sdk.d.ts +2 -0
- package/build/src/sdk/Sdk.d.ts.map +1 -1
- package/build/src/sdk/Sdk.js +5 -0
- package/build/src/sdk/Sdk.js.map +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -3
- package/src/events/BaseEvents.ts +4 -0
- package/src/modeling/ApiFile.ts +53 -0
- package/src/modeling/ApiModel.ts +327 -0
- package/src/modeling/DataDomain.ts +1 -1
- package/src/modeling/DomainEntity.ts +1 -1
- package/src/modeling/DomainFile.ts +3 -40
- package/src/modeling/Semantics.ts +63 -8
- package/src/modeling/amf/ShapeGenerator.ts +1 -1
- package/src/modeling/types.ts +545 -0
- package/src/models/kinds.ts +3 -0
- package/src/models/store/File.ts +100 -13
- package/src/models/store/Group.ts +148 -2
- package/src/sdk/GroupsSdk.ts +150 -0
- package/src/sdk/RouteBuilder.ts +8 -0
- package/src/sdk/Sdk.ts +6 -0
- package/tests/unit/modeling/api_model.spec.ts +291 -0
- package/tests/unit/modeling/domain_entity.spec.ts +15 -15
- package/tests/unit/modeling/domain_file.spec.ts +1 -11
- package/tests/unit/modeling/domain_model_entities.spec.ts +2 -2
- package/tests/unit/modeling/semantics.spec.ts +8 -11
- package/tests/unit/models/File/constructor.spec.ts +3 -2
- package/tests/unit/models/File/shortcutTo.spec.ts +1 -1
|
@@ -1,21 +1,167 @@
|
|
|
1
|
+
import { GroupKind } from '../kinds.js'
|
|
2
|
+
import type { IDeletion } from './Deletion.js'
|
|
3
|
+
import { nanoid } from '../../nanoid.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @deprecated Use `GroupSchema` instead.
|
|
7
|
+
* This interface is kept for backward compatibility.
|
|
8
|
+
*/
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
10
|
+
export interface IGroup extends GroupSchema {}
|
|
11
|
+
|
|
1
12
|
/**
|
|
2
13
|
* An object representing a user group.
|
|
3
14
|
*/
|
|
4
|
-
export interface
|
|
15
|
+
export interface GroupSchema {
|
|
16
|
+
kind: typeof GroupKind
|
|
5
17
|
/**
|
|
6
18
|
* The key of the group.
|
|
19
|
+
* If not provided, it will be generated automatically by the API
|
|
7
20
|
*/
|
|
8
21
|
key: string
|
|
9
22
|
/**
|
|
10
|
-
* The name of the group
|
|
23
|
+
* The name of the group.
|
|
24
|
+
* This is a required field and must be unique within the organization.
|
|
25
|
+
* @required
|
|
11
26
|
*/
|
|
12
27
|
name: string
|
|
28
|
+
/**
|
|
29
|
+
* The description of the group.
|
|
30
|
+
*/
|
|
31
|
+
description?: string
|
|
13
32
|
/**
|
|
14
33
|
* The id of the user that created this group.
|
|
34
|
+
* This field is ignored by the API and is set automatically.
|
|
35
|
+
* @readonly
|
|
15
36
|
*/
|
|
16
37
|
owner: string
|
|
38
|
+
/**
|
|
39
|
+
* The icon of the group, if any.
|
|
40
|
+
* An optional icon for the group, stored as a URL or path.
|
|
41
|
+
* Max 255 characters.
|
|
42
|
+
*/
|
|
43
|
+
icon?: string
|
|
44
|
+
/**
|
|
45
|
+
* The color of the group, if any.
|
|
46
|
+
* An optional color for the group, stored as a hex code.
|
|
47
|
+
* Max 50 characters (including the #).
|
|
48
|
+
*/
|
|
49
|
+
color?: string
|
|
50
|
+
/**
|
|
51
|
+
* The organization ID this group belongs to.
|
|
52
|
+
* This field is ignored by the API and is set automatically.
|
|
53
|
+
* @readonly
|
|
54
|
+
*/
|
|
55
|
+
oid: string
|
|
17
56
|
/**
|
|
18
57
|
* The list of users in this group.
|
|
58
|
+
* This field is ignored by the API and is set automatically.
|
|
59
|
+
* @readonly
|
|
19
60
|
*/
|
|
20
61
|
users: string[]
|
|
62
|
+
/**
|
|
63
|
+
* Describes when this group was created
|
|
64
|
+
* This field is ignored by the API and is set automatically.
|
|
65
|
+
* @readonly
|
|
66
|
+
*/
|
|
67
|
+
createdAt: number
|
|
68
|
+
/**
|
|
69
|
+
* Describes when this group was updated
|
|
70
|
+
* This field is ignored by the API and is set automatically.
|
|
71
|
+
* @readonly
|
|
72
|
+
*/
|
|
73
|
+
updatedAt: number
|
|
74
|
+
/**
|
|
75
|
+
* Optional deletion information if the group has been deleted.
|
|
76
|
+
* This field is ignored by the API and is set automatically.
|
|
77
|
+
* @readonly
|
|
78
|
+
*/
|
|
79
|
+
deleteInfo?: IDeletion
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export class Group implements GroupSchema {
|
|
83
|
+
kind: typeof GroupKind = GroupKind
|
|
84
|
+
key = ''
|
|
85
|
+
name = ''
|
|
86
|
+
description?: string
|
|
87
|
+
owner = ''
|
|
88
|
+
icon?: string
|
|
89
|
+
color?: string
|
|
90
|
+
oid = ''
|
|
91
|
+
users: string[] = []
|
|
92
|
+
createdAt = 0
|
|
93
|
+
updatedAt = 0
|
|
94
|
+
deleteInfo?: IDeletion
|
|
95
|
+
|
|
96
|
+
static createSchema(input: Partial<GroupSchema> = {}): GroupSchema {
|
|
97
|
+
const { key = nanoid() } = input
|
|
98
|
+
const result: GroupSchema = {
|
|
99
|
+
kind: GroupKind,
|
|
100
|
+
key,
|
|
101
|
+
name: input.name || 'Unnamed group',
|
|
102
|
+
oid: input.oid || '',
|
|
103
|
+
owner: input.owner || '',
|
|
104
|
+
users: input.users || [],
|
|
105
|
+
createdAt: input.createdAt || Date.now(),
|
|
106
|
+
updatedAt: input.updatedAt || Date.now(),
|
|
107
|
+
}
|
|
108
|
+
if (input.description) {
|
|
109
|
+
result.description = input.description
|
|
110
|
+
}
|
|
111
|
+
if (input.icon) {
|
|
112
|
+
result.icon = input.icon
|
|
113
|
+
}
|
|
114
|
+
if (input.color) {
|
|
115
|
+
result.color = input.color
|
|
116
|
+
}
|
|
117
|
+
if (input.deleteInfo) {
|
|
118
|
+
result.deleteInfo = input.deleteInfo
|
|
119
|
+
}
|
|
120
|
+
return result
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
static fromName(name: string, oid: string): Group {
|
|
124
|
+
return new Group({ name, oid })
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
constructor(state?: Partial<GroupSchema>) {
|
|
128
|
+
const init = Group.createSchema(state)
|
|
129
|
+
this.key = init.key
|
|
130
|
+
this.name = init.name
|
|
131
|
+
this.description = init.description
|
|
132
|
+
this.owner = init.owner
|
|
133
|
+
this.icon = init.icon
|
|
134
|
+
this.color = init.color
|
|
135
|
+
this.oid = init.oid
|
|
136
|
+
this.users = init.users
|
|
137
|
+
this.createdAt = init.createdAt
|
|
138
|
+
this.updatedAt = init.updatedAt
|
|
139
|
+
this.deleteInfo = init.deleteInfo
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
toJSON(): GroupSchema {
|
|
143
|
+
const result: GroupSchema = {
|
|
144
|
+
kind: this.kind,
|
|
145
|
+
key: GroupKind,
|
|
146
|
+
name: this.name,
|
|
147
|
+
oid: this.oid,
|
|
148
|
+
owner: this.owner,
|
|
149
|
+
users: [...this.users],
|
|
150
|
+
createdAt: this.createdAt,
|
|
151
|
+
updatedAt: this.updatedAt,
|
|
152
|
+
}
|
|
153
|
+
if (this.description) {
|
|
154
|
+
result.description = this.description
|
|
155
|
+
}
|
|
156
|
+
if (this.icon) {
|
|
157
|
+
result.icon = this.icon
|
|
158
|
+
}
|
|
159
|
+
if (this.color) {
|
|
160
|
+
result.color = this.color
|
|
161
|
+
}
|
|
162
|
+
if (this.deleteInfo) {
|
|
163
|
+
result.deleteInfo = { ...this.deleteInfo }
|
|
164
|
+
}
|
|
165
|
+
return result
|
|
166
|
+
}
|
|
21
167
|
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { ContextListOptions, ContextListResult } from '../browser.js'
|
|
2
|
+
import { Exception } from '../exceptions/exception.js'
|
|
3
|
+
import type { GroupSchema } from '../models/store/Group.js'
|
|
4
|
+
import { RouteBuilder } from './RouteBuilder.js'
|
|
5
|
+
import {
|
|
6
|
+
E_INVALID_JSON,
|
|
7
|
+
E_RESPONSE_NO_VALUE,
|
|
8
|
+
E_RESPONSE_STATUS,
|
|
9
|
+
E_RESPONSE_UNKNOWN,
|
|
10
|
+
SdkBase,
|
|
11
|
+
type SdkOptions,
|
|
12
|
+
} from './SdkBase.js'
|
|
13
|
+
|
|
14
|
+
export class GroupsSdk extends SdkBase {
|
|
15
|
+
/**
|
|
16
|
+
* Creates a new group in the specified organization.
|
|
17
|
+
* @param oid The organization ID where the group will be created.
|
|
18
|
+
* @param info The information about the group to create. See the `GroupSchema` interface for required fields.
|
|
19
|
+
* @param request Optional SDK options, including authentication token.
|
|
20
|
+
* @returns A promise that resolves to the created group.
|
|
21
|
+
*/
|
|
22
|
+
async create(oid: string, info: Partial<GroupSchema>, request: SdkOptions = {}): Promise<GroupSchema> {
|
|
23
|
+
const { token } = request
|
|
24
|
+
const url = this.sdk.getUrl(RouteBuilder.groups(oid))
|
|
25
|
+
const body = JSON.stringify(info)
|
|
26
|
+
const result = await this.sdk.http.post(url.toString(), {
|
|
27
|
+
token,
|
|
28
|
+
body,
|
|
29
|
+
headers: {
|
|
30
|
+
'content-type': 'application/json',
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
this.inspectCommonStatusCodes(result)
|
|
34
|
+
const E_PREFIX = 'Unable to create a group. '
|
|
35
|
+
if (result.status !== 201) {
|
|
36
|
+
this.logInvalidResponse(result)
|
|
37
|
+
throw this.createApiError(`${E_PREFIX}${E_RESPONSE_STATUS}${result.status}`, result.body)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!result.body) {
|
|
41
|
+
throw new Exception(`${E_PREFIX}${E_RESPONSE_NO_VALUE}`, { code: 'E_RESPONSE_NO_VALUE', status: result.status })
|
|
42
|
+
}
|
|
43
|
+
let data: GroupSchema
|
|
44
|
+
try {
|
|
45
|
+
data = JSON.parse(result.body)
|
|
46
|
+
} catch {
|
|
47
|
+
throw new Exception(`${E_PREFIX}${E_INVALID_JSON}`, { code: 'E_INVALID_JSON', status: result.status })
|
|
48
|
+
}
|
|
49
|
+
if (!data.key) {
|
|
50
|
+
throw new Exception(`${E_PREFIX}${E_RESPONSE_UNKNOWN}`, { code: 'E_RESPONSE_UNKNOWN', status: result.status })
|
|
51
|
+
}
|
|
52
|
+
return data
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Lists user groups in the specified organization.
|
|
57
|
+
* If you want to list specific user groups, you can use the `uid` option to filter by user ID.
|
|
58
|
+
* @param oid The organization ID to list groups from.
|
|
59
|
+
* @param options Optional parameters for filtering, sorting, and pagination.
|
|
60
|
+
* @param request Optional SDK options, including authentication token.
|
|
61
|
+
* @returns A promise that resolves to a list of groups in the organization.
|
|
62
|
+
*/
|
|
63
|
+
async list(
|
|
64
|
+
oid: string,
|
|
65
|
+
options: ContextListOptions = {},
|
|
66
|
+
request: SdkOptions = {}
|
|
67
|
+
): Promise<ContextListResult<GroupSchema>> {
|
|
68
|
+
const { token } = request
|
|
69
|
+
const url = this.sdk.getUrl(RouteBuilder.groups(oid))
|
|
70
|
+
this.sdk.appendListOptions(url, options)
|
|
71
|
+
const result = await this.sdk.http.get(url.toString(), { token })
|
|
72
|
+
this.inspectCommonStatusCodes(result)
|
|
73
|
+
const E_PREFIX = 'Unable to create a group. '
|
|
74
|
+
if (result.status !== 200) {
|
|
75
|
+
this.logInvalidResponse(result)
|
|
76
|
+
throw this.createApiError(E_PREFIX + E_RESPONSE_STATUS + result.status, result.body)
|
|
77
|
+
}
|
|
78
|
+
if (!result.body) {
|
|
79
|
+
throw new Exception(E_PREFIX + E_RESPONSE_NO_VALUE, { code: 'E_RESPONSE_NO_VALUE', status: result.status })
|
|
80
|
+
}
|
|
81
|
+
let data: ContextListResult<GroupSchema>
|
|
82
|
+
try {
|
|
83
|
+
data = JSON.parse(result.body)
|
|
84
|
+
} catch {
|
|
85
|
+
throw new Exception(E_PREFIX + E_INVALID_JSON, { code: 'E_INVALID_JSON', status: result.status })
|
|
86
|
+
}
|
|
87
|
+
return data
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Updates an existing group in the specified organization.
|
|
92
|
+
* Note that this method support PUT requests only. Some fields are ignored by the API and are set automatically.
|
|
93
|
+
* @param oid The organization ID where the group exists.
|
|
94
|
+
* @param updated The updated information for the group. Must include the `key` of the group to update.
|
|
95
|
+
* @param request Optional SDK options, including authentication token.
|
|
96
|
+
* @returns A promise that resolves to the updated group.
|
|
97
|
+
*/
|
|
98
|
+
async update(oid: string, updated: GroupSchema, request: SdkOptions = {}): Promise<GroupSchema> {
|
|
99
|
+
const { token } = request
|
|
100
|
+
const url = this.sdk.getUrl(RouteBuilder.group(oid, updated.key))
|
|
101
|
+
const body = JSON.stringify(updated)
|
|
102
|
+
const result = await this.sdk.http.put(url.toString(), {
|
|
103
|
+
token,
|
|
104
|
+
body,
|
|
105
|
+
headers: {
|
|
106
|
+
'content-type': 'application/json',
|
|
107
|
+
},
|
|
108
|
+
})
|
|
109
|
+
this.inspectCommonStatusCodes(result)
|
|
110
|
+
const E_PREFIX = 'Unable to update a group. '
|
|
111
|
+
if (result.status !== 200) {
|
|
112
|
+
this.logInvalidResponse(result)
|
|
113
|
+
throw this.createApiError(`${E_PREFIX}${E_RESPONSE_STATUS}${result.status}`, result.body)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (!result.body) {
|
|
117
|
+
throw new Exception(`${E_PREFIX}${E_RESPONSE_NO_VALUE}`, { code: 'E_RESPONSE_NO_VALUE', status: result.status })
|
|
118
|
+
}
|
|
119
|
+
let data: GroupSchema
|
|
120
|
+
try {
|
|
121
|
+
data = JSON.parse(result.body)
|
|
122
|
+
} catch {
|
|
123
|
+
throw new Exception(`${E_PREFIX}${E_INVALID_JSON}`, { code: 'E_INVALID_JSON', status: result.status })
|
|
124
|
+
}
|
|
125
|
+
if (!data.key) {
|
|
126
|
+
throw new Exception(`${E_PREFIX}${E_RESPONSE_UNKNOWN}`, { code: 'E_RESPONSE_UNKNOWN', status: result.status })
|
|
127
|
+
}
|
|
128
|
+
return data
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Deletes a group in the specified organization.
|
|
133
|
+
* The groups support soft deletion, meaning that the group is not immediately removed from the database.
|
|
134
|
+
* @param oid The organization ID where the group exists.
|
|
135
|
+
* @param gid The group ID to delete.
|
|
136
|
+
* @param request Optional SDK options, including authentication token.
|
|
137
|
+
* @returns A promise that resolves when the group is deleted.
|
|
138
|
+
*/
|
|
139
|
+
async delete(oid: string, gid: string, request: SdkOptions = {}): Promise<void> {
|
|
140
|
+
const { token } = request
|
|
141
|
+
const url = this.sdk.getUrl(RouteBuilder.group(oid, gid))
|
|
142
|
+
const result = await this.sdk.http.delete(url.toString(), { token })
|
|
143
|
+
this.inspectCommonStatusCodes(result)
|
|
144
|
+
const E_PREFIX = 'Unable to delete a group. '
|
|
145
|
+
if (result.status !== 204) {
|
|
146
|
+
this.logInvalidResponse(result)
|
|
147
|
+
throw this.createApiError(`${E_PREFIX}${E_RESPONSE_STATUS}${result.status}`, result.body)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
package/src/sdk/RouteBuilder.ts
CHANGED
|
@@ -287,4 +287,12 @@ export class RouteBuilder {
|
|
|
287
287
|
static dataCatalogVersionUnpublish(entryId: string, version: string): string {
|
|
288
288
|
return `${RouteBuilder.dataCatalogVersion(entryId, version)}/unpublish`
|
|
289
289
|
}
|
|
290
|
+
|
|
291
|
+
static groups(oid: string): string {
|
|
292
|
+
return `/v1/orgs/${oid}/groups`
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
static group(oid: string, gid: string): string {
|
|
296
|
+
return `/v1/orgs/${oid}/groups/${gid}`
|
|
297
|
+
}
|
|
290
298
|
}
|
package/src/sdk/Sdk.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { TrashSdk } from './TrashSdk.js'
|
|
|
11
11
|
import { ProjectExecutionSdk } from './ProjectExecutionsSdk.js'
|
|
12
12
|
import { OrganizationsSdk } from './OrganizationsSdk.js'
|
|
13
13
|
import { DataCatalogSdk } from './DataCatalogSdk.js'
|
|
14
|
+
import { GroupsSdk } from './GroupsSdk.js'
|
|
14
15
|
|
|
15
16
|
const baseUriSymbol = Symbol('baseUri')
|
|
16
17
|
|
|
@@ -70,6 +71,8 @@ export abstract class Sdk {
|
|
|
70
71
|
organizations = new OrganizationsSdk(this)
|
|
71
72
|
|
|
72
73
|
dataCatalog = new DataCatalogSdk(this)
|
|
74
|
+
|
|
75
|
+
groups = new GroupsSdk(this)
|
|
73
76
|
/**
|
|
74
77
|
* When set it limits log output to minimum.
|
|
75
78
|
*/
|
|
@@ -162,6 +165,9 @@ export abstract class Sdk {
|
|
|
162
165
|
if (options.oid) {
|
|
163
166
|
searchParams.set('oid', String(options.oid))
|
|
164
167
|
}
|
|
168
|
+
if (options.uid) {
|
|
169
|
+
searchParams.set('uid', String(options.uid))
|
|
170
|
+
}
|
|
165
171
|
if (options.filter) {
|
|
166
172
|
const bytes = new TextEncoder().encode(JSON.stringify(options.filter))
|
|
167
173
|
// To be used when Uint8Array.prototype.toBase64() is available.
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import { test } from '@japa/runner'
|
|
2
|
+
import {
|
|
3
|
+
ApiModel,
|
|
4
|
+
ApiModelKind,
|
|
5
|
+
DataDomain,
|
|
6
|
+
type RolesBasedAccessControl,
|
|
7
|
+
type ApiModelSchema,
|
|
8
|
+
type ExposedEntity,
|
|
9
|
+
} from '../../../src/index.js'
|
|
10
|
+
|
|
11
|
+
test.group('ApiModel.createSchema()', () => {
|
|
12
|
+
test('creates a schema with default values', ({ assert }) => {
|
|
13
|
+
const schema = ApiModel.createSchema()
|
|
14
|
+
assert.equal(schema.kind, ApiModelKind)
|
|
15
|
+
assert.typeOf(schema.key, 'string')
|
|
16
|
+
assert.isNotEmpty(schema.key)
|
|
17
|
+
assert.deepInclude(schema.info, { name: 'Unnamed API' })
|
|
18
|
+
assert.deepEqual(schema.exposes, [])
|
|
19
|
+
assert.isUndefined(schema.userKey)
|
|
20
|
+
assert.isUndefined(schema.domain)
|
|
21
|
+
assert.isUndefined(schema.authentication)
|
|
22
|
+
assert.isUndefined(schema.authorization)
|
|
23
|
+
assert.isUndefined(schema.session)
|
|
24
|
+
assert.isUndefined(schema.accessRule)
|
|
25
|
+
assert.isUndefined(schema.rateLimiting)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('creates a schema with provided values', ({ assert }) => {
|
|
29
|
+
const input: Partial<ApiModelSchema> = {
|
|
30
|
+
key: 'test-api',
|
|
31
|
+
info: { name: 'Test API', description: 'A test API' },
|
|
32
|
+
exposes: [{ key: 'entity1', actions: [] }],
|
|
33
|
+
userKey: 'user-entity',
|
|
34
|
+
domain: { key: 'domain1', version: '1.0.0' },
|
|
35
|
+
authentication: { strategy: 'UsernamePassword' },
|
|
36
|
+
authorization: { strategy: 'RBAC', roleKey: 'role' } as RolesBasedAccessControl,
|
|
37
|
+
session: { secret: 'secret', properties: ['email'] },
|
|
38
|
+
accessRule: [{ type: 'public' }],
|
|
39
|
+
rateLimiting: { rules: [] },
|
|
40
|
+
}
|
|
41
|
+
const schema = ApiModel.createSchema(input)
|
|
42
|
+
|
|
43
|
+
assert.equal(schema.kind, ApiModelKind)
|
|
44
|
+
assert.equal(schema.key, 'test-api')
|
|
45
|
+
assert.deepInclude(schema.info, { name: 'Test API', description: 'A test API' })
|
|
46
|
+
assert.deepEqual(schema.exposes, [{ key: 'entity1', actions: [] }])
|
|
47
|
+
assert.equal(schema.userKey, 'user-entity')
|
|
48
|
+
assert.deepEqual(schema.domain, { key: 'domain1', version: '1.0.0' })
|
|
49
|
+
assert.deepEqual(schema.authentication, { strategy: 'UsernamePassword' })
|
|
50
|
+
assert.deepEqual(schema.authorization, { strategy: 'RBAC', roleKey: 'role' })
|
|
51
|
+
assert.deepEqual(schema.session, { secret: 'secret', properties: ['email'] })
|
|
52
|
+
assert.deepEqual(schema.accessRule, [{ type: 'public' }])
|
|
53
|
+
assert.deepEqual(schema.rateLimiting, { rules: [] })
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test('creates a schema with partial info', ({ assert }) => {
|
|
57
|
+
const schema = ApiModel.createSchema({ info: { name: 'Partial API' } })
|
|
58
|
+
assert.deepInclude(schema.info, { name: 'Partial API' })
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
test('creates a schema with empty info', ({ assert }) => {
|
|
62
|
+
const schema = ApiModel.createSchema({ info: {} })
|
|
63
|
+
assert.deepInclude(schema.info, { name: 'Unnamed API' })
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test.group('ApiModel.constructor()', () => {
|
|
68
|
+
test('creates an instance with default values', ({ assert }) => {
|
|
69
|
+
const model = new ApiModel()
|
|
70
|
+
assert.equal(model.kind, ApiModelKind)
|
|
71
|
+
assert.typeOf(model.key, 'string')
|
|
72
|
+
assert.isNotEmpty(model.key)
|
|
73
|
+
assert.equal(model.info.name, 'Unnamed API')
|
|
74
|
+
assert.deepEqual(model.exposes, [])
|
|
75
|
+
assert.isUndefined(model.userKey)
|
|
76
|
+
assert.isUndefined(model.domain)
|
|
77
|
+
assert.isUndefined(model.authentication)
|
|
78
|
+
assert.isUndefined(model.authorization)
|
|
79
|
+
assert.isUndefined(model.session)
|
|
80
|
+
assert.isUndefined(model.accessRule)
|
|
81
|
+
assert.isUndefined(model.rateLimiting)
|
|
82
|
+
assert.isUndefined(model.dataDomain)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
test('creates an instance with provided schema values', ({ assert }) => {
|
|
86
|
+
const schema: ApiModelSchema = {
|
|
87
|
+
kind: ApiModelKind,
|
|
88
|
+
key: 'test-api',
|
|
89
|
+
info: { name: 'Test API', description: 'A test API' },
|
|
90
|
+
exposes: [{ key: 'entity1', actions: [] }],
|
|
91
|
+
userKey: 'user-entity',
|
|
92
|
+
domain: { key: 'domain1', version: '1.0.0' },
|
|
93
|
+
authentication: { strategy: 'UsernamePassword' },
|
|
94
|
+
authorization: { strategy: 'RBAC', roleKey: 'role' } as RolesBasedAccessControl,
|
|
95
|
+
session: { secret: 'secret', properties: ['email'] },
|
|
96
|
+
accessRule: [{ type: 'public' }],
|
|
97
|
+
rateLimiting: { rules: [] },
|
|
98
|
+
}
|
|
99
|
+
const model = new ApiModel(schema)
|
|
100
|
+
|
|
101
|
+
assert.equal(model.key, 'test-api')
|
|
102
|
+
assert.equal(model.info.name, 'Test API')
|
|
103
|
+
assert.deepEqual(model.exposes, [{ key: 'entity1', actions: [] }])
|
|
104
|
+
assert.equal(model.userKey, 'user-entity')
|
|
105
|
+
assert.deepEqual(model.domain, { key: 'domain1', version: '1.0.0' })
|
|
106
|
+
assert.deepEqual(model.authentication, { strategy: 'UsernamePassword' })
|
|
107
|
+
assert.deepEqual(model.authorization, { strategy: 'RBAC', roleKey: 'role' })
|
|
108
|
+
assert.deepEqual(model.session, { secret: 'secret', properties: ['email'] })
|
|
109
|
+
assert.deepEqual(model.accessRule, [{ type: 'public' }])
|
|
110
|
+
assert.deepEqual(model.rateLimiting, { rules: [] })
|
|
111
|
+
assert.isUndefined(model.dataDomain)
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
test('creates an instance with a DataDomain', ({ assert }) => {
|
|
115
|
+
const domainSchema = DataDomain.createSchema({ key: 'my-domain' })
|
|
116
|
+
const model = new ApiModel({}, domainSchema)
|
|
117
|
+
assert.isDefined(model.dataDomain)
|
|
118
|
+
assert.instanceOf(model.dataDomain, DataDomain)
|
|
119
|
+
assert.equal(model.dataDomain!.key, 'my-domain')
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
test('notifies change when info is modified', async ({ assert }) => {
|
|
123
|
+
const model = new ApiModel()
|
|
124
|
+
let notified = false
|
|
125
|
+
model.addEventListener('change', () => {
|
|
126
|
+
notified = true
|
|
127
|
+
})
|
|
128
|
+
model.info.name = 'New Name'
|
|
129
|
+
await Promise.resolve() // Allow microtask to run
|
|
130
|
+
assert.isTrue(notified)
|
|
131
|
+
})
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
test.group('ApiModel.toJSON()', () => {
|
|
135
|
+
test('serializes default values', ({ assert }) => {
|
|
136
|
+
const model = new ApiModel()
|
|
137
|
+
const json = model.toJSON()
|
|
138
|
+
|
|
139
|
+
assert.equal(json.kind, ApiModelKind)
|
|
140
|
+
assert.equal(json.key, model.key)
|
|
141
|
+
assert.deepInclude(json.info, { name: 'Unnamed API' })
|
|
142
|
+
assert.deepEqual(json.exposes, [])
|
|
143
|
+
assert.isUndefined(json.userKey)
|
|
144
|
+
assert.isUndefined(json.domain)
|
|
145
|
+
assert.isUndefined(json.authentication)
|
|
146
|
+
assert.isUndefined(json.authorization)
|
|
147
|
+
assert.isUndefined(json.session)
|
|
148
|
+
assert.isUndefined(json.accessRule)
|
|
149
|
+
assert.isUndefined(json.rateLimiting)
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
test('serializes all provided values', ({ assert }) => {
|
|
153
|
+
const schema: ApiModelSchema = {
|
|
154
|
+
kind: ApiModelKind,
|
|
155
|
+
key: 'test-api',
|
|
156
|
+
info: { name: 'Test API', description: 'A test API' },
|
|
157
|
+
exposes: [{ key: 'entity1', actions: [] }],
|
|
158
|
+
userKey: 'user-entity',
|
|
159
|
+
domain: { key: 'domain1', version: '1.0.0' },
|
|
160
|
+
authentication: { strategy: 'UsernamePassword' },
|
|
161
|
+
authorization: { strategy: 'RBAC', roleKey: 'role' } as RolesBasedAccessControl,
|
|
162
|
+
session: { secret: 'secret', properties: ['email'] },
|
|
163
|
+
accessRule: [{ type: 'public' }],
|
|
164
|
+
rateLimiting: { rules: [] },
|
|
165
|
+
}
|
|
166
|
+
const model = new ApiModel(schema)
|
|
167
|
+
const json = model.toJSON()
|
|
168
|
+
|
|
169
|
+
assert.equal(json.key, 'test-api')
|
|
170
|
+
assert.deepInclude(json.info, { name: 'Test API', description: 'A test API' })
|
|
171
|
+
assert.deepEqual(json.exposes, [{ key: 'entity1', actions: [] }])
|
|
172
|
+
assert.equal(json.userKey, 'user-entity')
|
|
173
|
+
assert.deepEqual(json.domain, { key: 'domain1', version: '1.0.0' })
|
|
174
|
+
assert.deepEqual(json.authentication, { strategy: 'UsernamePassword' })
|
|
175
|
+
assert.deepEqual(json.authorization, { strategy: 'RBAC', roleKey: 'role' })
|
|
176
|
+
assert.deepEqual(json.session, { secret: 'secret', properties: ['email'] })
|
|
177
|
+
assert.deepEqual(json.accessRule, [{ type: 'public' }])
|
|
178
|
+
assert.deepEqual(json.rateLimiting, { rules: [] })
|
|
179
|
+
})
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
test.group('ApiModel.exposeEntity()', () => {
|
|
183
|
+
test('exposes a new entity', ({ assert }) => {
|
|
184
|
+
const model = new ApiModel()
|
|
185
|
+
const entityKey = 'new-entity'
|
|
186
|
+
const exposedEntity = model.exposeEntity(entityKey)
|
|
187
|
+
|
|
188
|
+
assert.isDefined(exposedEntity)
|
|
189
|
+
assert.equal(exposedEntity.key, entityKey)
|
|
190
|
+
assert.deepEqual(exposedEntity.actions, [])
|
|
191
|
+
assert.includeDeepMembers(model.exposes, [exposedEntity])
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
test('returns an existing entity if already exposed', ({ assert }) => {
|
|
195
|
+
const model = new ApiModel()
|
|
196
|
+
const entityKey = 'existing-entity'
|
|
197
|
+
const initialExposedEntity = model.exposeEntity(entityKey)
|
|
198
|
+
const retrievedExposedEntity = model.exposeEntity(entityKey)
|
|
199
|
+
|
|
200
|
+
assert.strictEqual(retrievedExposedEntity, initialExposedEntity)
|
|
201
|
+
assert.lengthOf(model.exposes, 1)
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
test('notifies change when a new entity is exposed', async ({ assert }) => {
|
|
205
|
+
const model = new ApiModel()
|
|
206
|
+
let notified = false
|
|
207
|
+
model.addEventListener('change', () => {
|
|
208
|
+
notified = true
|
|
209
|
+
})
|
|
210
|
+
model.exposeEntity('notify-entity')
|
|
211
|
+
await Promise.resolve() // Allow microtask to run
|
|
212
|
+
assert.isTrue(notified)
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
test('does not notify change if entity already exposed', async ({ assert }) => {
|
|
216
|
+
const model = new ApiModel()
|
|
217
|
+
model.exposeEntity('no-notify-entity') // First exposure
|
|
218
|
+
await Promise.resolve() // Allow microtask to run
|
|
219
|
+
let notified = false
|
|
220
|
+
model.addEventListener('change', () => {
|
|
221
|
+
notified = true
|
|
222
|
+
})
|
|
223
|
+
model.exposeEntity('no-notify-entity') // Second exposure
|
|
224
|
+
await Promise.resolve() // Allow microtask to run
|
|
225
|
+
assert.isFalse(notified)
|
|
226
|
+
})
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
test.group('ApiModel.removeEntity()', () => {
|
|
230
|
+
test('removes an existing entity', ({ assert }) => {
|
|
231
|
+
const model = new ApiModel()
|
|
232
|
+
const entityKey = 'entity-to-remove'
|
|
233
|
+
model.exposeEntity(entityKey)
|
|
234
|
+
assert.lengthOf(model.exposes, 1)
|
|
235
|
+
|
|
236
|
+
model.removeEntity(entityKey)
|
|
237
|
+
assert.lengthOf(model.exposes, 0)
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
test('does nothing if entity does not exist', ({ assert }) => {
|
|
241
|
+
const model = new ApiModel()
|
|
242
|
+
model.exposeEntity('existing-entity')
|
|
243
|
+
const initialExposes = [...model.exposes]
|
|
244
|
+
|
|
245
|
+
model.removeEntity('non-existing-entity')
|
|
246
|
+
assert.deepEqual(model.exposes, initialExposes)
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
test('notifies change when an entity is removed', async ({ assert }) => {
|
|
250
|
+
const model = new ApiModel()
|
|
251
|
+
const entityKey = 'notify-remove-entity'
|
|
252
|
+
model.exposeEntity(entityKey)
|
|
253
|
+
|
|
254
|
+
let notified = false
|
|
255
|
+
model.addEventListener('change', () => {
|
|
256
|
+
notified = true
|
|
257
|
+
})
|
|
258
|
+
model.removeEntity(entityKey)
|
|
259
|
+
await Promise.resolve() // Allow microtask to run
|
|
260
|
+
assert.isTrue(notified)
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
test('does not notify change if entity to remove does not exist', async ({ assert }) => {
|
|
264
|
+
const model = new ApiModel()
|
|
265
|
+
let notified = false
|
|
266
|
+
model.addEventListener('change', () => {
|
|
267
|
+
notified = true
|
|
268
|
+
})
|
|
269
|
+
model.removeEntity('no-notify-remove-entity')
|
|
270
|
+
await Promise.resolve() // Allow microtask to run
|
|
271
|
+
assert.isFalse(notified)
|
|
272
|
+
})
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
test.group('ApiModel.getExposedEntity()', () => {
|
|
276
|
+
test('returns an existing exposed entity', ({ assert }) => {
|
|
277
|
+
const model = new ApiModel()
|
|
278
|
+
const entityKey = 'get-entity'
|
|
279
|
+
const exposed: ExposedEntity = { key: entityKey, actions: [] }
|
|
280
|
+
model.exposes.push(exposed)
|
|
281
|
+
|
|
282
|
+
const retrievedEntity = model.getExposedEntity(entityKey)
|
|
283
|
+
assert.deepEqual(retrievedEntity, exposed)
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
test('returns undefined if entity is not exposed', ({ assert }) => {
|
|
287
|
+
const model = new ApiModel()
|
|
288
|
+
const retrievedEntity = model.getExposedEntity('non-exposed-entity')
|
|
289
|
+
assert.isUndefined(retrievedEntity)
|
|
290
|
+
})
|
|
291
|
+
})
|