@api-client/core 0.12.7 → 0.12.9

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.
Files changed (50) hide show
  1. package/build/src/browser.d.ts +2 -1
  2. package/build/src/browser.d.ts.map +1 -1
  3. package/build/src/browser.js +2 -1
  4. package/build/src/browser.js.map +1 -1
  5. package/build/src/index.d.ts +2 -1
  6. package/build/src/index.d.ts.map +1 -1
  7. package/build/src/index.js +2 -1
  8. package/build/src/index.js.map +1 -1
  9. package/build/src/models/kinds.d.ts +2 -0
  10. package/build/src/models/kinds.d.ts.map +1 -1
  11. package/build/src/models/kinds.js +2 -0
  12. package/build/src/models/kinds.js.map +1 -1
  13. package/build/src/models/store/Invitation.d.ts +146 -0
  14. package/build/src/models/store/Invitation.d.ts.map +1 -0
  15. package/build/src/models/store/Invitation.js +186 -0
  16. package/build/src/models/store/Invitation.js.map +1 -0
  17. package/build/src/models/store/Organization.d.ts +4 -4
  18. package/build/src/models/store/Organization.d.ts.map +1 -1
  19. package/build/src/models/store/Organization.js +9 -9
  20. package/build/src/models/store/Organization.js.map +1 -1
  21. package/build/src/runtime/store/HttpWeb.js +5 -5
  22. package/build/src/runtime/store/HttpWeb.js.map +1 -1
  23. package/build/src/runtime/store/OrganizationsSdk.d.ts +58 -2
  24. package/build/src/runtime/store/OrganizationsSdk.d.ts.map +1 -1
  25. package/build/src/runtime/store/OrganizationsSdk.js +215 -4
  26. package/build/src/runtime/store/OrganizationsSdk.js.map +1 -1
  27. package/build/src/runtime/store/RouteBuilder.d.ts +7 -2
  28. package/build/src/runtime/store/RouteBuilder.d.ts.map +1 -1
  29. package/build/src/runtime/store/RouteBuilder.js +21 -6
  30. package/build/src/runtime/store/RouteBuilder.js.map +1 -1
  31. package/build/src/runtime/store/Sdk.d.ts +1 -1
  32. package/build/src/runtime/store/Sdk.d.ts.map +1 -1
  33. package/build/src/runtime/store/Sdk.js +1 -1
  34. package/build/src/runtime/store/Sdk.js.map +1 -1
  35. package/build/src/runtime/store/SdkBase.d.ts +2 -2
  36. package/build/src/runtime/store/SdkBase.d.ts.map +1 -1
  37. package/build/src/runtime/store/SdkBase.js +2 -2
  38. package/build/src/runtime/store/SdkBase.js.map +1 -1
  39. package/data/models/example-generator-api.json +8 -8
  40. package/eslint.config.js +6 -5
  41. package/package.json +23 -5
  42. package/src/models/kinds.ts +2 -0
  43. package/src/models/store/Invitation.ts +287 -0
  44. package/src/models/store/Organization.ts +11 -12
  45. package/src/runtime/store/HttpWeb.ts +5 -5
  46. package/src/runtime/store/OrganizationsSdk.ts +225 -6
  47. package/src/runtime/store/RouteBuilder.ts +28 -8
  48. package/src/runtime/store/Sdk.ts +1 -1
  49. package/src/runtime/store/SdkBase.ts +2 -2
  50. package/tests/unit/models/store/Invitation.spec.ts +223 -0
@@ -7,9 +7,11 @@ import {
7
7
  SdkOptions,
8
8
  } from './SdkBase.js'
9
9
  import { RouteBuilder } from './RouteBuilder.js'
10
- import { ContextListResult } from '../../events/BaseEvents.js'
11
- import { IOrganization } from '../../models/store/Organization.js'
10
+ import type { ContextListResult } from '../../events/BaseEvents.js'
11
+ import type { IOrganization, UserOrganizationGrantType } from '../../models/store/Organization.js'
12
+ import type { InvitationSchema } from '../../models/store/Invitation.js'
12
13
  import { SdkError } from './Errors.js'
14
+ import type { PatchInfo } from '../../patch/types.js'
13
15
 
14
16
  export class OrganizationsSdk extends SdkBase {
15
17
  /**
@@ -37,10 +39,10 @@ export class OrganizationsSdk extends SdkBase {
37
39
  try {
38
40
  data = JSON.parse(result.body)
39
41
  } catch {
40
- throw new Error(`${E_PREFIX}${E_INVALID_JSON}.`)
42
+ throw new Error(`${E_PREFIX}${E_INVALID_JSON}`)
41
43
  }
42
44
  if (!Array.isArray(data.items)) {
43
- throw new Error(`${E_PREFIX}${E_RESPONSE_UNKNOWN}.`)
45
+ throw new Error(`${E_PREFIX}${E_RESPONSE_UNKNOWN}`)
44
46
  }
45
47
  return data
46
48
  }
@@ -70,10 +72,227 @@ export class OrganizationsSdk extends SdkBase {
70
72
  try {
71
73
  data = JSON.parse(result.body)
72
74
  } catch {
73
- throw new Error(`${E_PREFIX}${E_INVALID_JSON}.`)
75
+ throw new Error(`${E_PREFIX}${E_INVALID_JSON}`)
74
76
  }
75
77
  if (!data.kind) {
76
- throw new Error(`${E_PREFIX}${E_RESPONSE_UNKNOWN}.`)
78
+ throw new Error(`${E_PREFIX}${E_RESPONSE_UNKNOWN}`)
79
+ }
80
+ return data
81
+ }
82
+
83
+ /**
84
+ * Lists all invitations for a given organization.
85
+ * @param oid The organization ID.
86
+ * @param request The request options.
87
+ * @returns A promise that resolves to a list of invitations.
88
+ */
89
+ async listInvitations(oid: string, request: SdkOptions = {}): Promise<ContextListResult<InvitationSchema>> {
90
+ const { token } = request
91
+ const url = this.sdk.getUrl(RouteBuilder.invitations(oid))
92
+ const result = await this.sdk.http.get(url.toString(), { token })
93
+ this.inspectCommonStatusCodes(result)
94
+ const E_PREFIX = 'Unable to list organization invitations. '
95
+ if (result.status !== 200) {
96
+ this.logInvalidResponse(result)
97
+ let e = this.createGenericSdkError(result.body)
98
+ if (!e) {
99
+ e = new SdkError(`${E_PREFIX}${E_RESPONSE_STATUS}${result.status}`, result.status)
100
+ e.response = result.body
101
+ }
102
+ throw e
103
+ }
104
+ if (!result.body) {
105
+ throw new Error(`${E_PREFIX}${E_RESPONSE_NO_VALUE}`)
106
+ }
107
+ let data: ContextListResult<InvitationSchema>
108
+ try {
109
+ data = JSON.parse(result.body)
110
+ } catch {
111
+ throw new Error(`${E_PREFIX}${E_INVALID_JSON}`)
112
+ }
113
+ if (!Array.isArray(data.items)) {
114
+ throw new Error(`${E_PREFIX}${E_RESPONSE_UNKNOWN}`)
115
+ }
116
+ return data
117
+ }
118
+
119
+ /**
120
+ * Creates an invitation for a user to join an organization.
121
+ * @param oid The organization ID.
122
+ * @param email The email address of the user to invite.
123
+ * @param grant_type The type of grant for the user.
124
+ * @param name The name of the user (optional).
125
+ * @param request The request options.
126
+ * @returns A promise that resolves to the created invitation.
127
+ */
128
+ async createInvitation(
129
+ oid: string,
130
+ email: string,
131
+ grant_type: UserOrganizationGrantType,
132
+ name?: string,
133
+ request: SdkOptions = {}
134
+ ): Promise<InvitationSchema> {
135
+ const url = this.sdk.getUrl(RouteBuilder.invitations(oid))
136
+ const body = {
137
+ email,
138
+ name,
139
+ grant_type,
140
+ }
141
+ const result = await this.sdk.http.post(url.toString(), {
142
+ body: JSON.stringify(body),
143
+ token: request.token,
144
+ headers: {
145
+ 'Content-Type': 'application/json',
146
+ },
147
+ })
148
+ this.inspectCommonStatusCodes(result)
149
+ const E_PREFIX = 'Unable to create an invitation. '
150
+ if (result.status !== 200) {
151
+ throw new Error(`${E_PREFIX}Invalid response status: ${result.status}`)
152
+ }
153
+ if (!result.body) {
154
+ throw new Error(`${E_PREFIX}Response has no value.`)
155
+ }
156
+ let data: InvitationSchema
157
+ try {
158
+ data = JSON.parse(result.body)
159
+ } catch {
160
+ throw new Error(`${E_PREFIX}${E_INVALID_JSON}`)
161
+ }
162
+ if (!data.kind) {
163
+ throw new Error(`${E_PREFIX}${E_RESPONSE_UNKNOWN}`)
164
+ }
165
+ return data
166
+ }
167
+
168
+ /**
169
+ * Finds an invitation by its token.
170
+ * @param oid The organization ID.
171
+ * @param token The invitation token.
172
+ * @param request The request options.
173
+ * @returns A promise that resolves to the found invitation.
174
+ */
175
+ async findInvitationByToken(oid: string, token: string, request: SdkOptions = {}): Promise<InvitationSchema> {
176
+ const url = this.sdk.getUrl(RouteBuilder.findInvitation(oid))
177
+ url.searchParams.append('token', token)
178
+ const result = await this.sdk.http.get(url.toString(), request)
179
+ this.inspectCommonStatusCodes(result)
180
+ const E_PREFIX = 'Unable to find invitation by token. '
181
+ if (result.status !== 200) {
182
+ throw new Error(`${E_PREFIX}Invalid response status: ${result.status}`)
183
+ }
184
+ if (!result.body) {
185
+ throw new Error(`${E_PREFIX}Response has no value.`)
186
+ }
187
+ let data: InvitationSchema
188
+ try {
189
+ data = JSON.parse(result.body)
190
+ } catch {
191
+ throw new Error(`${E_PREFIX}${E_INVALID_JSON}`)
192
+ }
193
+ if (!data.kind) {
194
+ throw new Error(`${E_PREFIX}${E_RESPONSE_UNKNOWN}`)
195
+ }
196
+ return data
197
+ }
198
+
199
+ /**
200
+ * Declines an invitation.
201
+ * @param oid The organization ID.
202
+ * @param id The invitation ID.
203
+ * @param request The request options.
204
+ * @returns A promise that resolves when the invitation is declined.
205
+ */
206
+ async declineInvitation(oid: string, id: string, request: SdkOptions = {}): Promise<InvitationSchema> {
207
+ const url = this.sdk.getUrl(RouteBuilder.declineInvitation(oid, id))
208
+ const result = await this.sdk.http.post(url.toString(), request)
209
+ this.inspectCommonStatusCodes(result)
210
+ const E_PREFIX = 'Unable to decline invitation. '
211
+ if (result.status !== 200) {
212
+ throw new Error(`${E_PREFIX}Invalid response status: ${result.status}`)
213
+ }
214
+ if (!result.body) {
215
+ throw new Error(`${E_PREFIX}Response has no value.`)
216
+ }
217
+ let data: InvitationSchema
218
+ try {
219
+ data = JSON.parse(result.body)
220
+ } catch {
221
+ throw new Error(`${E_PREFIX}${E_INVALID_JSON}`)
222
+ }
223
+ if (!data.kind) {
224
+ throw new Error(`${E_PREFIX}${E_RESPONSE_UNKNOWN}`)
225
+ }
226
+ return data
227
+ }
228
+
229
+ /**
230
+ * Soft-deletes an invitation.
231
+ * @param oid The organization ID.
232
+ * @param id The invitation ID.
233
+ * @param request The request options.
234
+ * @returns A promise that resolves to the deleted invitation.
235
+ */
236
+ async deleteInvitation(oid: string, id: string, request: SdkOptions = {}): Promise<InvitationSchema> {
237
+ const url = this.sdk.getUrl(RouteBuilder.invitation(oid, id))
238
+ const result = await this.sdk.http.delete(url.toString(), request)
239
+ this.inspectCommonStatusCodes(result)
240
+ const E_PREFIX = 'Unable to delete invitation. '
241
+ if (result.status !== 200) {
242
+ throw new Error(`${E_PREFIX}Invalid response status: ${result.status}`)
243
+ }
244
+ if (!result.body) {
245
+ throw new Error(`${E_PREFIX}Response has no value.`)
246
+ }
247
+ let data: InvitationSchema
248
+ try {
249
+ data = JSON.parse(result.body)
250
+ } catch {
251
+ throw new Error(`${E_PREFIX}${E_INVALID_JSON}`)
252
+ }
253
+ if (!data.kind) {
254
+ throw new Error(`${E_PREFIX}${E_RESPONSE_UNKNOWN}`)
255
+ }
256
+ return data
257
+ }
258
+
259
+ /**
260
+ * Patches an invitation. The server performs the patch validation.
261
+ * The API only allows to patch some of the invitation properties:
262
+ * - name
263
+ * - expires_at
264
+ * - grant_type
265
+ * @param oid The organization ID.
266
+ * @param id The invitation ID.
267
+ * @param info The patch information.
268
+ * @param request The request options.
269
+ * @returns A promise that resolves to the patched invitation.
270
+ */
271
+ async patchInvitation(oid: string, id: string, info: PatchInfo, request: SdkOptions = {}): Promise<InvitationSchema> {
272
+ const url = this.sdk.getUrl(RouteBuilder.invitation(oid, id))
273
+ const result = await this.sdk.http.patch(url.toString(), {
274
+ body: JSON.stringify(info),
275
+ token: request.token,
276
+ headers: {
277
+ 'Content-Type': 'application/json',
278
+ },
279
+ })
280
+ this.inspectCommonStatusCodes(result)
281
+ const E_PREFIX = 'Unable to update invitation. '
282
+ if (result.status !== 200) {
283
+ throw new Error(`${E_PREFIX}Invalid response status: ${result.status}`)
284
+ }
285
+ if (!result.body) {
286
+ throw new Error(`${E_PREFIX}Response has no value.`)
287
+ }
288
+ let data: InvitationSchema
289
+ try {
290
+ data = JSON.parse(result.body)
291
+ } catch {
292
+ throw new Error(`${E_PREFIX}${E_INVALID_JSON}`)
293
+ }
294
+ if (!data.kind) {
295
+ throw new Error(`${E_PREFIX}${E_RESPONSE_UNKNOWN}`)
77
296
  }
78
297
  return data
79
298
  }
@@ -158,6 +158,34 @@ export class RouteBuilder {
158
158
  return '/v1/orgs'
159
159
  }
160
160
 
161
+ static organization(oid: string): string {
162
+ return `/v1/orgs/${oid}`
163
+ }
164
+
165
+ static users(oid: string): string {
166
+ return `${this.organization(oid)}/users`
167
+ }
168
+
169
+ static user(oid: string, key: string): string {
170
+ return `${this.organization(oid)}/users/${key}`
171
+ }
172
+
173
+ static invitations(oid: string): string {
174
+ return `${this.organization(oid)}/invitations`
175
+ }
176
+
177
+ static invitation(oid: string, id: string): string {
178
+ return `${this.invitations(oid)}/${id}`
179
+ }
180
+
181
+ static findInvitation(oid: string): string {
182
+ return `${this.invitations(oid)}/find`
183
+ }
184
+
185
+ static declineInvitation(oid: string, id: string): string {
186
+ return `${this.invitation(oid, id)}/decline`
187
+ }
188
+
161
189
  /**
162
190
  * @deprecated Not used anymore
163
191
  */
@@ -191,14 +219,6 @@ export class RouteBuilder {
191
219
  return '/v1/users/me'
192
220
  }
193
221
 
194
- static users(oid: string): string {
195
- return `/v1/orgs/${oid}/users`
196
- }
197
-
198
- static user(oid: string, key: string): string {
199
- return `/v1/orgs/${oid}/users/${key}`
200
- }
201
-
202
222
  static dataCatalog(): string {
203
223
  return '/v1/datacatalog'
204
224
  }
@@ -67,7 +67,7 @@ export abstract class Sdk {
67
67
 
68
68
  projectExecution = new ProjectExecutionSdk(this)
69
69
 
70
- organization = new OrganizationsSdk(this)
70
+ organizations = new OrganizationsSdk(this)
71
71
 
72
72
  dataCatalog = new DataCatalogSdk(this)
73
73
  /**
@@ -33,10 +33,10 @@ export interface IStoreResponse {
33
33
  body?: string
34
34
  }
35
35
 
36
- export const E_INVALID_JSON = 'The response is not a valid JSON.'
36
+ export const E_INVALID_JSON = 'Invalid JSON response.'
37
37
  export const E_RESPONSE_NO_VALUE = 'The response has no value.'
38
38
  export const E_RESPONSE_STATUS = 'Invalid response status: '
39
- export const E_RESPONSE_UNKNOWN = 'The response has unknown format.'
39
+ export const E_RESPONSE_UNKNOWN = 'Unknown response from the server.'
40
40
  export const E_RESPONSE_LOCATION = 'The response has no "location" header.'
41
41
 
42
42
  export class SdkBase {
@@ -0,0 +1,223 @@
1
+ import { test } from '@japa/runner'
2
+ import { Invitation, InvitationKind, type InvitationSchema, type IDeletion } from '../../../../src/index.js'
3
+ import { OperationType } from '@api-client/json/patch/types.js'
4
+
5
+ const baseInput: Partial<InvitationSchema> = {
6
+ email: 'test@example.com',
7
+ oid: 'org-123',
8
+ grantType: 'viewer',
9
+ }
10
+
11
+ test.group('Invitation', (group) => {
12
+ group.tap((t) => t.tags(['@store', '@invitation']))
13
+
14
+ test('constructor initializes with minimal valid input', ({ assert }) => {
15
+ const invitation = new Invitation(baseInput)
16
+ assert.equal(invitation.kind, InvitationKind)
17
+ assert.isString(invitation.key)
18
+ assert.isNotEmpty(invitation.key)
19
+ assert.equal(invitation.email, baseInput.email)
20
+ assert.equal(invitation.oid, baseInput.oid)
21
+ assert.equal(invitation.grantType, baseInput.grantType)
22
+ assert.equal(invitation.uid, '') // default
23
+ assert.equal(invitation.token, '') // default
24
+ assert.equal(invitation.expiresAt, 0) // default
25
+ assert.equal(invitation.status, 'pending') // default
26
+ assert.isUndefined(invitation.name)
27
+ assert.isUndefined(invitation.respondedAt)
28
+ assert.isFalse(invitation.deleted)
29
+ assert.isUndefined(invitation.deletedInfo)
30
+ assert.equal(invitation.createdAt, 0) // default
31
+ assert.equal(invitation.updatedAt, 0) // default
32
+ })
33
+
34
+ test('constructor initializes with full input', ({ assert }) => {
35
+ const fullInput: InvitationSchema = {
36
+ kind: InvitationKind,
37
+ key: 'inv-key-123',
38
+ uid: 'user-abc',
39
+ oid: 'org-xyz',
40
+ email: 'full@example.com',
41
+ name: 'Test User',
42
+ token: 'secret-token',
43
+ expiresAt: Date.now() + 3600000,
44
+ respondedAt: Date.now(),
45
+ status: 'accepted',
46
+ grantType: 'editor',
47
+ deleted: true,
48
+ deletedInfo: { time: Date.now(), user: 'admin-user', byMe: false, name: 'Admin' },
49
+ createdAt: Date.now() - 7200000,
50
+ updatedAt: Date.now() - 3600000,
51
+ }
52
+ const invitation = new Invitation(fullInput)
53
+ assert.deepEqual(invitation.toJSON(), fullInput)
54
+ })
55
+
56
+ test('toJSON() serializes all properties correctly', ({ assert }) => {
57
+ const deletionInfo: IDeletion = { time: Date.now(), user: 'deleter-id', byMe: false, name: 'Deleter' }
58
+ const input: Partial<InvitationSchema> = {
59
+ ...baseInput,
60
+ key: 'inv-key-json',
61
+ uid: 'user-json',
62
+ name: 'JSON User',
63
+ token: 'json-token',
64
+ expiresAt: 1234567890,
65
+ respondedAt: 1234500000,
66
+ status: 'declined',
67
+ grantType: 'manager',
68
+ deleted: true,
69
+ deletedInfo: deletionInfo,
70
+ createdAt: 1230000000,
71
+ updatedAt: 1234000000,
72
+ }
73
+ const invitation = new Invitation(input)
74
+ const json = invitation.toJSON()
75
+
76
+ assert.deepEqual(json, {
77
+ kind: InvitationKind,
78
+ key: 'inv-key-json',
79
+ uid: 'user-json',
80
+ oid: baseInput.oid,
81
+ email: baseInput.email,
82
+ name: 'JSON User',
83
+ token: 'json-token',
84
+ expiresAt: 1234567890,
85
+ respondedAt: 1234500000,
86
+ status: 'declined',
87
+ grantType: 'manager',
88
+ deleted: true,
89
+ deletedInfo: deletionInfo,
90
+ createdAt: 1230000000,
91
+ updatedAt: 1234000000,
92
+ })
93
+ })
94
+
95
+ test('toJSON() omits optional fields when not set', ({ assert }) => {
96
+ const invitation = new Invitation(baseInput)
97
+ const json = invitation.toJSON()
98
+
99
+ assert.notProperty(json, 'name')
100
+ assert.notProperty(json, 'respondedAt')
101
+ assert.notProperty(json, 'deletedInfo') // deleted is false, so deletedInfo should not be present
102
+ })
103
+
104
+ test('getPatch() returns empty array when no changes', ({ assert }) => {
105
+ const invitation = new Invitation(baseInput)
106
+ assert.deepEqual(invitation.getPatch(), [])
107
+ })
108
+
109
+ test('setName() adds name if not present', ({ assert }) => {
110
+ const invitation = new Invitation(baseInput)
111
+ invitation.setName('New Name')
112
+ assert.deepEqual(invitation.getPatch(), [{ op: OperationType.add, path: '/name', value: 'New Name' }])
113
+ })
114
+
115
+ test('setName() replaces name if different', ({ assert }) => {
116
+ const invitation = new Invitation({ ...baseInput, name: 'Old Name' })
117
+ invitation.setName('New Name')
118
+ assert.deepEqual(invitation.getPatch(), [{ op: OperationType.replace, path: '/name', value: 'New Name' }])
119
+ })
120
+
121
+ test('setName() removes name if set to undefined', ({ assert }) => {
122
+ const invitation = new Invitation({ ...baseInput, name: 'Old Name' })
123
+ invitation.setName(undefined)
124
+ assert.deepEqual(invitation.getPatch(), [{ op: OperationType.remove, path: '/name' }])
125
+ })
126
+
127
+ test('setName() does nothing if name is the same', ({ assert }) => {
128
+ const invitation = new Invitation({ ...baseInput, name: 'Same Name' })
129
+ invitation.setName('Same Name')
130
+ assert.deepEqual(invitation.getPatch(), [])
131
+ })
132
+
133
+ test('setExpiresAt() replaces expiresAt if different', ({ assert }) => {
134
+ const invitation = new Invitation({ ...baseInput, expiresAt: 1000 })
135
+ invitation.setExpiresAt(2000)
136
+ assert.deepEqual(invitation.getPatch(), [{ op: OperationType.replace, path: '/expiresAt', value: 2000 }])
137
+ })
138
+
139
+ test('setExpiresAt() does nothing if expiresAt is the same', ({ assert }) => {
140
+ const invitation = new Invitation({ ...baseInput, expiresAt: 1000 })
141
+ invitation.setExpiresAt(1000)
142
+ assert.deepEqual(invitation.getPatch(), [])
143
+ })
144
+
145
+ test('setGrantType() replaces grantType if different', ({ assert }) => {
146
+ const invitation = new Invitation({ ...baseInput, grantType: 'viewer' })
147
+ invitation.setGrantType('editor')
148
+ assert.deepEqual(invitation.getPatch(), [{ op: OperationType.replace, path: '/grantType', value: 'editor' }])
149
+ })
150
+
151
+ test('setGrantType() does nothing if grantType is the same', ({ assert }) => {
152
+ const invitation = new Invitation({ ...baseInput, grantType: 'viewer' })
153
+ invitation.setGrantType('viewer')
154
+ assert.deepEqual(invitation.getPatch(), [])
155
+ })
156
+
157
+ test('builder getter initializes and returns builder', ({ assert }) => {
158
+ const invitation = new Invitation(baseInput)
159
+ const builder1 = invitation.builder
160
+ assert.isObject(builder1)
161
+ const builder2 = invitation.builder
162
+ assert.strictEqual(builder1, builder2) // Should return the same instance
163
+ })
164
+ })
165
+
166
+ test.group('Invitation.createSchema()', (schemaGroup) => {
167
+ schemaGroup.tap((t) => t.tags(['@store', '@invitation', '@static']))
168
+
169
+ test('throws if email is missing', ({ assert }) => {
170
+ assert.throws(
171
+ () => Invitation.createSchema({ oid: 'org-1', grantType: 'viewer' }),
172
+ 'Email is required to create an invitation schema.'
173
+ )
174
+ })
175
+
176
+ test('throws if oid is missing', ({ assert }) => {
177
+ assert.throws(
178
+ () => Invitation.createSchema({ email: 'test@example.com', grantType: 'viewer' }),
179
+ 'Organization ID is required to create an invitation schema.'
180
+ )
181
+ })
182
+
183
+ test('throws if grantType is missing', ({ assert }) => {
184
+ assert.throws(
185
+ () => Invitation.createSchema({ email: 'test@example.com', oid: 'org-1' }),
186
+ 'Grant type is required to create an invitation schema.'
187
+ )
188
+ })
189
+
190
+ test('creates schema with all default values for optional fields', ({ assert }) => {
191
+ const schema = Invitation.createSchema(baseInput)
192
+ assert.equal(schema.kind, InvitationKind)
193
+ assert.isString(schema.key)
194
+ assert.equal(schema.uid, '')
195
+ assert.equal(schema.oid, baseInput.oid)
196
+ assert.equal(schema.email, baseInput.email)
197
+ assert.equal(schema.grantType, baseInput.grantType)
198
+ assert.equal(schema.token, '')
199
+ assert.equal(schema.expiresAt, 0)
200
+ assert.equal(schema.status, 'pending')
201
+ assert.equal(schema.createdAt, 0)
202
+ assert.equal(schema.updatedAt, 0)
203
+ assert.isUndefined(schema.name)
204
+ assert.isUndefined(schema.respondedAt)
205
+ })
206
+
207
+ test('creates schema with provided optional values', ({ assert }) => {
208
+ const partial: Partial<InvitationSchema> = {
209
+ ...baseInput,
210
+ name: 'Provided Name',
211
+ token: 'provided-token',
212
+ expiresAt: 999,
213
+ respondedAt: 888,
214
+ status: 'accepted',
215
+ }
216
+ const schema = Invitation.createSchema(partial)
217
+ assert.equal(schema.name, 'Provided Name')
218
+ assert.equal(schema.token, 'provided-token')
219
+ assert.equal(schema.expiresAt, 999)
220
+ assert.equal(schema.respondedAt, 888)
221
+ assert.equal(schema.status, 'accepted')
222
+ })
223
+ })