@api-client/core 0.18.37 → 0.18.39

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 (54) hide show
  1. package/build/src/browser.d.ts +1 -1
  2. package/build/src/browser.d.ts.map +1 -1
  3. package/build/src/browser.js.map +1 -1
  4. package/build/src/mocking/ModelingMock.d.ts +19 -0
  5. package/build/src/mocking/ModelingMock.d.ts.map +1 -0
  6. package/build/src/mocking/ModelingMock.js +19 -0
  7. package/build/src/mocking/ModelingMock.js.map +1 -0
  8. package/build/src/mocking/ProjectMock.js +1 -1
  9. package/build/src/mocking/ProjectMock.js.map +1 -1
  10. package/build/src/mocking/lib/File.d.ts +34 -0
  11. package/build/src/mocking/lib/File.d.ts.map +1 -0
  12. package/build/src/mocking/lib/File.js +64 -0
  13. package/build/src/mocking/lib/File.js.map +1 -0
  14. package/build/src/mocking/lib/Group.d.ts +16 -0
  15. package/build/src/mocking/lib/Group.d.ts.map +1 -0
  16. package/build/src/mocking/lib/Group.js +39 -0
  17. package/build/src/mocking/lib/Group.js.map +1 -0
  18. package/build/src/mocking/lib/Invitation.d.ts +16 -0
  19. package/build/src/mocking/lib/Invitation.d.ts.map +1 -0
  20. package/build/src/mocking/lib/Invitation.js +42 -0
  21. package/build/src/mocking/lib/Invitation.js.map +1 -0
  22. package/build/src/mocking/lib/Organization.d.ts +16 -0
  23. package/build/src/mocking/lib/Organization.d.ts.map +1 -0
  24. package/build/src/mocking/lib/Organization.js +34 -0
  25. package/build/src/mocking/lib/Organization.js.map +1 -0
  26. package/build/src/mocking/lib/Patch.d.ts +29 -0
  27. package/build/src/mocking/lib/Patch.d.ts.map +1 -0
  28. package/build/src/mocking/lib/Patch.js +102 -0
  29. package/build/src/mocking/lib/Patch.js.map +1 -0
  30. package/build/src/mocking/lib/Trash.d.ts +16 -0
  31. package/build/src/mocking/lib/Trash.d.ts.map +1 -0
  32. package/build/src/mocking/lib/Trash.js +39 -0
  33. package/build/src/mocking/lib/Trash.js.map +1 -0
  34. package/build/src/mocking/lib/User.d.ts +12 -12
  35. package/build/src/mocking/lib/User.d.ts.map +1 -1
  36. package/build/src/mocking/lib/User.js +29 -26
  37. package/build/src/mocking/lib/User.js.map +1 -1
  38. package/build/src/sdk/SdkMock.d.ts +125 -175
  39. package/build/src/sdk/SdkMock.d.ts.map +1 -1
  40. package/build/src/sdk/SdkMock.js +774 -627
  41. package/build/src/sdk/SdkMock.js.map +1 -1
  42. package/build/tsconfig.tsbuildinfo +1 -1
  43. package/data/models/example-generator-api.json +9 -9
  44. package/package.json +5 -3
  45. package/src/mocking/ModelingMock.ts +19 -0
  46. package/src/mocking/ProjectMock.ts +1 -1
  47. package/src/mocking/lib/File.ts +72 -0
  48. package/src/mocking/lib/Group.ts +52 -0
  49. package/src/mocking/lib/Invitation.ts +58 -0
  50. package/src/mocking/lib/Organization.ts +42 -0
  51. package/src/mocking/lib/Patch.ts +128 -0
  52. package/src/mocking/lib/Trash.ts +47 -0
  53. package/src/mocking/lib/User.ts +30 -29
  54. package/src/sdk/SdkMock.ts +966 -689
@@ -1,51 +1,47 @@
1
- import { nanoid } from '../nanoid.js'
1
+ import { type ResponseGenerator, setupWorker, type MockHandler, type SetupWorkerOptions } from '@jarrodek/amw'
2
2
  import type { IOrganization } from '../models/store/Organization.js'
3
3
  import type { GroupSchema } from '../models/store/Group.js'
4
4
  import type { IUser } from '../models/store/User.js'
5
5
  import type { InvitationSchema } from '../models/store/Invitation.js'
6
- import { OrganizationKind, GroupKind, InvitationKind } from '../models/kinds.js'
7
- import { Kind as UserKind } from '../models/store/User.js'
8
- import { File, type IFile, type FileBreadcrumb } from '../models/store/File.js'
9
- import { CertificateFileKind, DomainFileKind, FolderKind, ProjectKind } from '../models/kinds.js'
6
+ import { type IFile, type FileBreadcrumb } from '../models/store/File.js'
10
7
  import type { ContextListResult, IBulkOperationResult } from '../events/BaseEvents.js'
11
- import type { MediaPatchRevision } from '../patch/types.js'
12
- import type { IFolder } from '../models/Folder.js'
13
8
  import type { TrashEntry } from '../models/TrashEntry.js'
14
- import type { StoreSdk } from './StoreSdkWeb.js'
15
- import * as sinon from 'sinon'
16
- import { Exception } from '../exceptions/exception.js'
9
+ import { RouteBuilder } from './RouteBuilder.js'
10
+ import { ModelingMock } from '../mocking/ModelingMock.js'
17
11
 
18
- /**
19
- * Options for customizing mock responses.
20
- */
21
- export interface MockResponseOptions {
12
+ export interface MockResult {
22
13
  /**
23
- * Custom response data to return instead of generated random data.
14
+ * Custom response generator for the mock intercept.
24
15
  */
25
- data?: unknown
16
+ response?: ResponseGenerator
26
17
  /**
27
- * HTTP status code to return. Defaults to 200.
18
+ * If true, the request body will be generated using the body generator.
19
+ * It is useful when only setting up response headers or status code, but
20
+ * the request body should still be generated, even when missing in the `response` option.
28
21
  */
29
- status?: number
30
- /**
31
- * Custom headers to include in the response.
32
- */
33
- headers?: Record<string, string>
22
+ forceBody?: boolean
34
23
  }
35
24
 
36
- /**
37
- * A stub reference that can be used to restore the original behavior.
38
- */
39
- export interface StubReference {
25
+ export interface MockListResult extends MockResult {
26
+ /**
27
+ * Number of items to generate in the list response.
28
+ */
29
+ size?: number
40
30
  /**
41
- * Restores the original behavior.
31
+ * If true, the response will include a cursor for pagination.
32
+ * If false, the response will not include a cursor.
33
+ * If not set, a random choice will be made.
42
34
  */
43
- restore: () => void
35
+ cursor?: boolean
44
36
  }
45
37
 
46
38
  /**
47
- * SDK mocking utility for testing. Provides simple API to mock SDK calls
48
- * with random or custom responses.
39
+ * SDK mocking utility for testing. Uses Service Workers to intercept HTTP requests
40
+ * and provide mock responses for API calls.
41
+ *
42
+ * This class uses the `@jarrodek/amw` library to set up a Service Worker that intercepts
43
+ * fetch requests matching the configured routes. Each method adds an intercept for a specific
44
+ * API endpoint, returning either random generated data or custom responses.
49
45
  *
50
46
  * @example
51
47
  * ```typescript
@@ -53,193 +49,477 @@ export interface StubReference {
53
49
  * import { StoreSdk } from '@api-client/core/sdk/StoreSdkWeb.js';
54
50
  *
55
51
  * const sdk = new StoreSdk('http://localhost:8080');
56
- * const mocker = new SdkMock(sdk);
52
+ * const mocker = new SdkMock({
53
+ * swPath: '/mockServiceWorker.js', // Path to the Service Worker script
54
+ * base: 'http://localhost:8080' // Base URL matching the SDK
55
+ * });
56
+ *
57
+ * // Initialize the Service Worker
58
+ * await mocker.setup();
57
59
  *
58
- * // Simple usage - returns random valid organization
59
- * const stub1 = mocker.organizations.list();
60
+ * // Add intercept - returns random organizations
61
+ * await mocker.organizations.list();
60
62
  *
61
- * // Custom response
62
- * const stub2 = mocker.organizations.create({
63
- * data: { key: 'org-1', name: 'Test Org', ... }
63
+ * // Custom response with specific data
64
+ * await mocker.organizations.create({
65
+ * response: {
66
+ * status: 201,
67
+ * headers: { 'Content-Type': 'application/json', 'X-Custom-Header': 'value' },
68
+ * body: JSON.stringify({ key: 'custom-id', name: 'Custom Org' })
69
+ * }
64
70
  * });
65
71
  *
66
- * // Custom status and headers
67
- * const stub3 = mocker.users.me({
68
- * status: 404,
69
- * headers: { 'X-Custom': 'value' }
72
+ * // Control pagination in list responses
73
+ * await mocker.groups.list({
74
+ * size: 10, // Number of items to generate
75
+ * cursor: true // Include pagination cursor
70
76
  * });
71
77
  *
72
- * // Restore all stubs
73
- * mocker.restore();
78
+ * // Error simulation
79
+ * await mocker.users.me({
80
+ * response: {
81
+ * status: 404,
82
+ * headers: { 'Content-Type': 'application/json' },
83
+ * body: JSON.stringify({ error: 'Not found' })
84
+ * }
85
+ * });
86
+ *
87
+ * // Remove all intercepts (keep Service Worker active)
88
+ * await mocker.reset();
89
+ *
90
+ * // Stop and remove the Service Worker
91
+ * await mocker.teardown();
74
92
  * ```
75
93
  */
76
94
  export class SdkMock {
77
- private stubs: StubReference[] = []
95
+ handler?: MockHandler
96
+ gen = new ModelingMock()
78
97
 
79
- constructor(private sdk: StoreSdk) {}
98
+ constructor(public options?: SetupWorkerOptions) {}
99
+
100
+ /**
101
+ * Initializes the mock handler. It uses options provided in the constructor.
102
+ * It has to be called before using any of the mock methods.
103
+ */
104
+ async setup(): Promise<void> {
105
+ this.handler = await setupWorker(this.options)
106
+ }
107
+
108
+ /**
109
+ * Removes the mock worker and all intercepts.
110
+ */
111
+ async teardown(): Promise<void> {
112
+ await this.mock.stop()
113
+ }
114
+
115
+ /**
116
+ * Removes all added intercepts.
117
+ */
118
+ async reset(): Promise<void> {
119
+ await this.mock.reset()
120
+ }
121
+
122
+ get mock(): MockHandler {
123
+ if (!this.handler) {
124
+ throw new Error('Mock handler not initialized. Call setup() first.')
125
+ }
126
+ return this.handler
127
+ }
128
+
129
+ protected createCursorOption(init: MockListResult = {}): string | undefined {
130
+ if (init.cursor === false) {
131
+ return undefined
132
+ }
133
+ const hasCursor = init.cursor === true ? true : this.gen.faker.datatype.boolean()
134
+ if (!hasCursor) {
135
+ return undefined
136
+ }
137
+ return this.gen.faker.internet.jwt()
138
+ }
139
+
140
+ protected createDefaultResponse(
141
+ status: number,
142
+ headers?: Record<string, string>,
143
+ body?: () => string,
144
+ userConfig?: MockResult
145
+ ): ResponseGenerator {
146
+ let respond: ResponseGenerator
147
+ if (userConfig?.response) {
148
+ // user config takes precedence
149
+ respond = userConfig.response
150
+ } else {
151
+ respond = {}
152
+ }
153
+ // Set defaults if not provided in user config
154
+ if (!respond.status) {
155
+ respond.status = status
156
+ }
157
+ // only set headers if the user didn't configure the response only.
158
+ // The user may want to remove default headers.
159
+ if (!userConfig || !userConfig.response) {
160
+ respond.headers = headers
161
+ }
162
+ if (!respond.body && userConfig?.forceBody && body) {
163
+ // when body is missing and forceBody is set, generate the body
164
+ respond.body = body()
165
+ } else if (body && (!userConfig || !userConfig.response)) {
166
+ // we set the body by default when the user config is missing
167
+ respond.body = body()
168
+ }
169
+ return respond
170
+ }
80
171
 
81
172
  /**
82
173
  * Organization API mocks.
83
174
  */
84
175
  organizations = {
85
176
  /**
86
- * Mocks the `organizations.list()` method.
87
- * @param options Optional response customization.
88
- * @returns A stub reference that can be used to restore the original behavior.
177
+ * Adds an intercept to mock the `organizations.list()` method.
178
+ * @param options Optional response configuration
89
179
  */
90
- list: (options?: MockResponseOptions): StubReference => {
91
- return this.createStub('organizations', 'list', () => {
92
- const defaultData = {
93
- items: [this.generateOrganization(), this.generateOrganization()],
94
- nextPageToken: undefined,
95
- }
96
- if (options?.status !== undefined && options.status !== 200) {
97
- throw new Exception('Mocked error', { status: options.status })
98
- }
99
- return (options?.data ?? defaultData) as unknown
180
+ list: async (init?: MockListResult): Promise<void> => {
181
+ const { mock } = this
182
+ // const respond = init?.response ?? {
183
+ // status: 200,
184
+ // headers: { 'content-type': 'application/json' },
185
+ // body: JSON.stringify({
186
+ // items: this.gen.organization.organizations(init?.size ?? 5),
187
+ // cursor: this.createCursorOption(init),
188
+ // } as ContextListResult<IOrganization>),
189
+ // }
190
+ const respond = this.createDefaultResponse(
191
+ 200,
192
+ { 'content-type': 'application/json' },
193
+ () =>
194
+ JSON.stringify({
195
+ items: this.gen.organization.organizations(init?.size ?? 5),
196
+ cursor: this.createCursorOption(init),
197
+ } as ContextListResult<IOrganization>),
198
+ init
199
+ )
200
+ await mock.add({
201
+ match: {
202
+ uri: RouteBuilder.organizations(),
203
+ methods: ['GET'],
204
+ },
205
+ respond,
100
206
  })
101
207
  },
102
208
 
103
209
  /**
104
- * Mocks the `organizations.create()` method.
105
- * @param options Optional response customization.
106
- * @returns A stub reference that can be used to restore the original behavior.
210
+ * Adds an intercept to mock the `organizations.create()` method.
211
+ * @param options Optional response configuration
107
212
  */
108
- create: (options?: MockResponseOptions): StubReference => {
109
- return this.createStub('organizations', 'create', () => {
110
- const defaultData = this.generateOrganization()
111
- if (options?.status !== undefined && options.status !== 200) {
112
- throw new Exception('Mocked error', { status: options.status })
113
- }
114
- return (options?.data ?? defaultData) as unknown
213
+ create: async (init?: MockResult): Promise<void> => {
214
+ const { mock } = this
215
+ // const respond = init?.response ?? {
216
+ // status: 200,
217
+ // headers: { 'content-type': 'application/json' },
218
+ // body: JSON.stringify(this.gen.organization.organization()),
219
+ // }
220
+ const respond = this.createDefaultResponse(
221
+ 200,
222
+ { 'content-type': 'application/json' },
223
+ () => JSON.stringify(this.gen.organization.organization()),
224
+ init
225
+ )
226
+ await mock.add({
227
+ match: {
228
+ uri: RouteBuilder.organizations(),
229
+ methods: ['POST'],
230
+ },
231
+ respond,
115
232
  })
116
233
  },
117
234
 
118
235
  invitations: {
119
- list: (options?: MockResponseOptions): StubReference => {
120
- return this.createStub('organizations.invitations', 'list', () => {
121
- const defaultData = {
122
- items: [this.generateInvitation(), this.generateInvitation()],
123
- }
124
- if (options?.status !== undefined && options.status !== 200) {
125
- throw new Exception('Mocked error', { status: options.status })
126
- }
127
- return (options?.data ?? defaultData) as unknown
236
+ list: async (init?: MockListResult): Promise<void> => {
237
+ const { mock } = this
238
+ // const respond = init?.response ?? {
239
+ // status: 200,
240
+ // headers: { 'content-type': 'application/json' },
241
+ // body: JSON.stringify({
242
+ // items: this.gen.invitation.invitations(init?.size ?? 5),
243
+ // cursor: this.createCursorOption(init),
244
+ // } as ContextListResult<InvitationSchema>),
245
+ // }
246
+ const respond = this.createDefaultResponse(
247
+ 200,
248
+ { 'content-type': 'application/json' },
249
+ () => {
250
+ const obj: ContextListResult<InvitationSchema> = {
251
+ items: this.gen.invitation.invitations(init?.size ?? 5),
252
+ cursor: this.createCursorOption(init),
253
+ }
254
+ return JSON.stringify(obj)
255
+ },
256
+ init
257
+ )
258
+ await mock.add({
259
+ match: {
260
+ uri: RouteBuilder.invitations(':oid'),
261
+ methods: ['GET'],
262
+ },
263
+ respond,
128
264
  })
129
265
  },
130
- create: (options?: MockResponseOptions): StubReference => {
131
- return this.createStub('organizations.invitations', 'create', () => {
132
- const defaultData = this.generateInvitation()
133
- if (options?.status !== undefined && options.status !== 200) {
134
- throw new Exception('Mocked error', { status: options.status })
135
- }
136
- return (options?.data ?? defaultData) as unknown
266
+ create: async (init?: MockResult): Promise<void> => {
267
+ const { mock } = this
268
+ // const respond = init?.response ?? {
269
+ // status: 200,
270
+ // headers: { 'content-type': 'application/json' },
271
+ // body: JSON.stringify(this.gen.invitation.invitation()),
272
+ // }
273
+ const respond = this.createDefaultResponse(
274
+ 200,
275
+ { 'content-type': 'application/json' },
276
+ () => JSON.stringify(this.gen.invitation.invitation()),
277
+ init
278
+ )
279
+ await mock.add({
280
+ match: {
281
+ uri: RouteBuilder.invitations(':oid'),
282
+ methods: ['POST'],
283
+ },
284
+ respond,
137
285
  })
138
286
  },
139
- findByToken: (options?: MockResponseOptions): StubReference => {
140
- return this.createStub('organizations.invitations', 'findByToken', () => {
141
- const defaultData = this.generateInvitation()
142
- if (options?.status !== undefined && options.status !== 200) {
143
- throw new Exception('Mocked error', { status: options.status })
144
- }
145
- return (options?.data ?? defaultData) as unknown
287
+ findByToken: async (init?: MockResult): Promise<void> => {
288
+ const { mock } = this
289
+ // const respond = init?.response ?? {
290
+ // status: 200,
291
+ // headers: { 'content-type': 'application/json' },
292
+ // body: JSON.stringify(this.gen.invitation.invitation()),
293
+ // }
294
+ const respond = this.createDefaultResponse(
295
+ 200,
296
+ { 'content-type': 'application/json' },
297
+ () => JSON.stringify(this.gen.invitation.invitation()),
298
+ init
299
+ )
300
+
301
+ await mock.add({
302
+ match: {
303
+ uri: RouteBuilder.findInvitation(),
304
+ methods: ['GET'],
305
+ },
306
+ respond,
146
307
  })
147
308
  },
148
- decline: (options?: MockResponseOptions): StubReference => {
149
- return this.createStub('organizations.invitations', 'decline', () => {
150
- const defaultData = this.generateInvitation()
151
- if (options?.status !== undefined && options.status !== 200) {
152
- throw new Exception('Mocked error', { status: options.status })
153
- }
154
- return (options?.data ?? defaultData) as unknown
309
+ decline: async (init?: MockResult): Promise<void> => {
310
+ const { mock } = this
311
+ // const respond = init?.response ?? {
312
+ // status: 200,
313
+ // headers: { 'content-type': 'application/json' },
314
+ // body: JSON.stringify(this.gen.invitation.invitation()),
315
+ // }
316
+ const respond = this.createDefaultResponse(
317
+ 200,
318
+ { 'content-type': 'application/json' },
319
+ () => JSON.stringify(this.gen.invitation.invitation()),
320
+ init
321
+ )
322
+
323
+ await mock.add({
324
+ match: {
325
+ uri: RouteBuilder.declineInvitation(':oid', ':id'),
326
+ methods: ['POST'],
327
+ },
328
+ respond,
155
329
  })
156
330
  },
157
- delete: (options?: MockResponseOptions): StubReference => {
158
- return this.createStub('organizations.invitations', 'delete', () => {
159
- const defaultData = this.generateInvitation()
160
- if (options?.status !== undefined && options.status !== 200) {
161
- throw new Exception('Mocked error', { status: options.status })
162
- }
163
- return (options?.data ?? defaultData) as unknown
331
+ delete: async (init?: MockResult): Promise<void> => {
332
+ const { mock } = this
333
+ // const respond = init?.response ?? {
334
+ // status: 200,
335
+ // headers: { 'content-type': 'application/json' },
336
+ // body: JSON.stringify(this.gen.invitation.invitation()),
337
+ // }
338
+ const respond = this.createDefaultResponse(
339
+ 200,
340
+ { 'content-type': 'application/json' },
341
+ () => JSON.stringify(this.gen.invitation.invitation()),
342
+ init
343
+ )
344
+
345
+ await mock.add({
346
+ match: {
347
+ uri: RouteBuilder.invitation(':oid', ':id'),
348
+ methods: ['DELETE'],
349
+ },
350
+ respond,
164
351
  })
165
352
  },
166
- patch: (options?: MockResponseOptions): StubReference => {
167
- return this.createStub('organizations.invitations', 'patch', () => {
168
- const defaultData = this.generateInvitation()
169
- if (options?.status !== undefined && options.status !== 200) {
170
- throw new Exception('Mocked error', { status: options.status })
171
- }
172
- return (options?.data ?? defaultData) as unknown
353
+ patch: async (init?: MockResult): Promise<void> => {
354
+ const { mock } = this
355
+ // const respond = init?.response ?? {
356
+ // status: 200,
357
+ // headers: { 'content-type': 'application/json' },
358
+ // body: JSON.stringify(this.gen.invitation.invitation()),
359
+ // }
360
+ const respond = this.createDefaultResponse(
361
+ 200,
362
+ { 'content-type': 'application/json' },
363
+ () => JSON.stringify(this.gen.invitation.invitation()),
364
+ init
365
+ )
366
+ await mock.add({
367
+ match: {
368
+ uri: RouteBuilder.invitation(':oid', ':id'),
369
+ methods: ['PATCH'],
370
+ },
371
+ respond,
173
372
  })
174
373
  },
175
- resend: (options?: MockResponseOptions): StubReference => {
176
- return this.createStub('organizations.invitations', 'resend', () => {
177
- const defaultData = this.generateInvitation()
178
- if (options?.status !== undefined && options.status !== 200) {
179
- throw new Exception('Mocked error', { status: options.status })
180
- }
181
- return (options?.data ?? defaultData) as unknown
374
+ resend: async (init?: MockResult): Promise<void> => {
375
+ const { mock } = this
376
+ // const respond = init?.response ?? {
377
+ // status: 200,
378
+ // headers: { 'content-type': 'application/json' },
379
+ // body: JSON.stringify(this.gen.invitation.invitation()),
380
+ // }
381
+ const respond = this.createDefaultResponse(
382
+ 200,
383
+ { 'content-type': 'application/json' },
384
+ () => JSON.stringify(this.gen.invitation.invitation()),
385
+ init
386
+ )
387
+
388
+ await mock.add({
389
+ match: {
390
+ uri: RouteBuilder.resendInvitation(':oid', ':id'),
391
+ methods: ['PUT'],
392
+ },
393
+ respond,
182
394
  })
183
395
  },
184
396
  },
185
397
 
186
398
  users: {
187
- list: (options?: MockResponseOptions): StubReference => {
188
- return this.createStub('organizations.users', 'list', () => {
189
- const defaultData = {
190
- items: [this.generateUser(), this.generateUser()],
191
- }
192
- if (options?.status !== undefined && options.status !== 200) {
193
- throw new Exception('Mocked error', { status: options.status })
194
- }
195
- return (options?.data ?? defaultData) as unknown
399
+ list: async (init?: MockListResult): Promise<void> => {
400
+ const { mock } = this
401
+ // const respond = init?.response ?? {
402
+ // status: 200,
403
+ // headers: { 'content-type': 'application/json' },
404
+ // body: JSON.stringify({
405
+ // items: this.gen.user.users(init?.size ?? 5),
406
+ // cursor: this.createCursorOption(init),
407
+ // } as ContextListResult<IUser>),
408
+ // }
409
+ const respond = this.createDefaultResponse(
410
+ 200,
411
+ { 'content-type': 'application/json' },
412
+ () => {
413
+ const obj: ContextListResult<IUser> = {
414
+ items: this.gen.user.users(init?.size ?? 5),
415
+ cursor: this.createCursorOption(init),
416
+ }
417
+ return JSON.stringify(obj)
418
+ },
419
+ init
420
+ )
421
+ await mock.add({
422
+ match: {
423
+ uri: RouteBuilder.organizationUsers(':oid'),
424
+ methods: ['GET'],
425
+ },
426
+ respond,
196
427
  })
197
428
  },
198
- read: (options?: MockResponseOptions): StubReference => {
199
- return this.createStub('organizations.users', 'read', () => {
200
- const defaultData = this.generateUser()
201
- if (options?.status !== undefined && options.status !== 200) {
202
- throw new Exception('Mocked error', { status: options.status })
203
- }
204
- return (options?.data ?? defaultData) as unknown
429
+ read: async (init?: MockResult): Promise<void> => {
430
+ const { mock } = this
431
+ // const respond = init?.response ?? {
432
+ // status: 200,
433
+ // headers: { 'content-type': 'application/json' },
434
+ // body: JSON.stringify(this.gen.user.user()),
435
+ // }
436
+ const respond = this.createDefaultResponse(
437
+ 200,
438
+ { 'content-type': 'application/json' },
439
+ () => JSON.stringify(this.gen.user.user()),
440
+ init
441
+ )
442
+ await mock.add({
443
+ match: {
444
+ uri: RouteBuilder.organizationUser(':oid', ':id'),
445
+ methods: ['GET'],
446
+ },
447
+ respond,
205
448
  })
206
449
  },
207
- readBatch: (options?: MockResponseOptions): StubReference => {
208
- return this.createStub('organizations.users', 'readBatch', () => {
209
- const defaultData = {
210
- items: [this.generateUser(), this.generateUser()],
211
- }
212
- if (options?.status !== undefined && options.status !== 200) {
213
- throw new Exception('Mocked error', { status: options.status })
214
- }
215
- return (options?.data ?? defaultData) as unknown
450
+ readBatch: async (init?: MockListResult): Promise<void> => {
451
+ const { mock } = this
452
+ const path = RouteBuilder.organizationUserBatch(':oid')
453
+ const respond = init?.response ?? {
454
+ status: 200,
455
+ headers: { 'content-type': 'application/json' },
456
+ body: JSON.stringify({
457
+ items: this.gen.user.users(init?.size ?? 5),
458
+ cursor: this.createCursorOption(init),
459
+ } as ContextListResult<IUser>),
460
+ }
461
+ await mock.add({
462
+ match: {
463
+ uri: path,
464
+ methods: ['POST'],
465
+ },
466
+ respond,
216
467
  })
217
468
  },
218
- activate: (options?: MockResponseOptions): StubReference => {
219
- return this.createStub('organizations.users', 'activate', () => {
220
- const defaultData = this.generateUser()
221
- if (options?.status !== undefined && options.status !== 200) {
222
- throw new Exception('Mocked error', { status: options.status })
223
- }
224
- return (options?.data ?? defaultData) as unknown
469
+ activate: async (init?: MockResult): Promise<void> => {
470
+ const { mock } = this
471
+ // const respond = init?.response ?? {
472
+ // status: 200,
473
+ // headers: { 'content-type': 'application/json' },
474
+ // body: JSON.stringify(this.gen.user.user()),
475
+ // }
476
+ const respond = this.createDefaultResponse(
477
+ 200,
478
+ { 'content-type': 'application/json' },
479
+ () => JSON.stringify(this.gen.user.user()),
480
+ init
481
+ )
482
+ await mock.add({
483
+ match: {
484
+ uri: RouteBuilder.organizationUserActivate(':oid', ':id'),
485
+ methods: ['POST'],
486
+ },
487
+ respond,
225
488
  })
226
489
  },
227
- deactivate: (options?: MockResponseOptions): StubReference => {
228
- return this.createStub('organizations.users', 'deactivate', () => {
229
- const defaultData = this.generateUser()
230
- if (options?.status !== undefined && options.status !== 200) {
231
- throw new Exception('Mocked error', { status: options.status })
232
- }
233
- return (options?.data ?? defaultData) as unknown
490
+ deactivate: async (init?: MockResult): Promise<void> => {
491
+ const { mock } = this
492
+ // const respond = init?.response ?? {
493
+ // status: 200,
494
+ // headers: { 'content-type': 'application/json' },
495
+ // body: JSON.stringify(this.gen.user.user()),
496
+ // }
497
+ const respond = this.createDefaultResponse(
498
+ 200,
499
+ { 'content-type': 'application/json' },
500
+ () => JSON.stringify(this.gen.user.user()),
501
+ init
502
+ )
503
+ await mock.add({
504
+ match: {
505
+ uri: RouteBuilder.organizationUserDeactivate(':oid', ':id'),
506
+ methods: ['POST'],
507
+ },
508
+ respond,
234
509
  })
235
510
  },
236
- delete: (options?: MockResponseOptions): StubReference => {
237
- return this.createStub('organizations.users', 'delete', () => {
238
- const status = options?.status ?? 204
239
- if (status !== 204) {
240
- throw new Exception('Mocked error', { status })
241
- }
242
- return undefined
511
+ delete: async (init?: MockResult): Promise<void> => {
512
+ const { mock } = this
513
+ // const respond = init?.response ?? {
514
+ // status: 204,
515
+ // }
516
+ const respond = this.createDefaultResponse(204, undefined, undefined, init)
517
+ await mock.add({
518
+ match: {
519
+ uri: RouteBuilder.organizationUser(':oid', ':id'),
520
+ methods: ['DELETE'],
521
+ },
522
+ respond,
243
523
  })
244
524
  },
245
525
  },
@@ -252,105 +532,156 @@ export class SdkMock {
252
532
  /**
253
533
  * Mocks the `groups.list()` method.
254
534
  * @param options Optional response customization.
255
- * @returns A stub reference that can be used to restore the original behavior.
256
535
  */
257
- list: (options?: MockResponseOptions): StubReference => {
258
- return this.createStub('groups', 'list', () => {
259
- const defaultData = {
260
- items: [this.generateGroup(), this.generateGroup()],
261
- nextPageToken: undefined,
262
- }
263
- if (options?.status !== undefined && options.status !== 200) {
264
- throw new Exception('Mocked error', { status: options.status })
265
- }
266
- return (options?.data ?? defaultData) as unknown
536
+ list: async (init?: MockListResult): Promise<void> => {
537
+ const { mock } = this
538
+ // const respond = init?.response ?? {
539
+ // status: 200,
540
+ // headers: { 'content-type': 'application/json' },
541
+ // body: JSON.stringify({
542
+ // items: this.gen.group.groups(init?.size ?? 5),
543
+ // cursor: this.createCursorOption(init),
544
+ // } as ContextListResult<GroupSchema>),
545
+ // }
546
+ const respond = this.createDefaultResponse(
547
+ 200,
548
+ { 'content-type': 'application/json' },
549
+ () => {
550
+ const obj: ContextListResult<GroupSchema> = {
551
+ items: this.gen.group.groups(init?.size ?? 5),
552
+ cursor: this.createCursorOption(init),
553
+ }
554
+ return JSON.stringify(obj)
555
+ },
556
+ init
557
+ )
558
+ await mock.add({
559
+ match: {
560
+ uri: RouteBuilder.groups(':oid'),
561
+ methods: ['GET'],
562
+ },
563
+ respond,
267
564
  })
268
565
  },
269
566
 
270
567
  /**
271
568
  * Mocks the `groups.create()` method.
272
569
  * @param options Optional response customization.
273
- * @returns A stub reference that can be used to restore the original behavior.
274
570
  */
275
- create: (options?: MockResponseOptions): StubReference => {
276
- return this.createStub('groups', 'create', () => {
277
- const defaultData = this.generateGroup()
278
- const status = options?.status ?? 201
279
- if (status !== 201) {
280
- throw new Exception('Mocked error', { status })
281
- }
282
- return (options?.data ?? defaultData) as unknown
283
- })
284
- },
285
-
286
- /**
287
- * Mocks the `groups.read()` method.
288
- * @param options Optional response customization.
289
- * @returns A stub reference that can be used to restore the original behavior.
290
- */
291
- read: (options?: MockResponseOptions): StubReference => {
292
- return this.createStub('groups', 'read', () => {
293
- const defaultData = this.generateGroup()
294
- if (options?.status !== undefined && options.status !== 200) {
295
- throw new Exception('Mocked error', { status: options.status })
296
- }
297
- return (options?.data ?? defaultData) as unknown
571
+ create: async (init?: MockResult): Promise<void> => {
572
+ const { mock } = this
573
+ // const respond = init?.response ?? {
574
+ // status: 201,
575
+ // headers: { 'content-type': 'application/json' },
576
+ // body: JSON.stringify(this.gen.group.group()),
577
+ // }
578
+ const respond = this.createDefaultResponse(
579
+ 201,
580
+ { 'content-type': 'application/json' },
581
+ () => JSON.stringify(this.gen.group.group()),
582
+ init
583
+ )
584
+ await mock.add({
585
+ match: {
586
+ uri: RouteBuilder.groups(':oid'),
587
+ methods: ['POST'],
588
+ },
589
+ respond,
298
590
  })
299
591
  },
300
592
 
301
593
  /**
302
594
  * Mocks the `groups.update()` method.
303
595
  * @param options Optional response customization.
304
- * @returns A stub reference that can be used to restore the original behavior.
305
596
  */
306
- update: (options?: MockResponseOptions): StubReference => {
307
- return this.createStub('groups', 'update', () => {
308
- const defaultData = this.generateGroup()
309
- if (options?.status !== undefined && options.status !== 200) {
310
- throw new Exception('Mocked error', { status: options.status })
311
- }
312
- return (options?.data ?? defaultData) as unknown
597
+ update: async (init?: MockResult): Promise<void> => {
598
+ const { mock } = this
599
+ // const respond = init?.response ?? {
600
+ // status: 200,
601
+ // headers: { 'content-type': 'application/json' },
602
+ // body: JSON.stringify(this.gen.group.group()),
603
+ // }
604
+ const respond = this.createDefaultResponse(
605
+ 200,
606
+ { 'content-type': 'application/json' },
607
+ () => JSON.stringify(this.gen.group.group()),
608
+ init
609
+ )
610
+ await mock.add({
611
+ match: {
612
+ uri: RouteBuilder.group(':oid', ':key'),
613
+ methods: ['PATCH'],
614
+ },
615
+ respond,
313
616
  })
314
617
  },
315
618
 
316
619
  /**
317
620
  * Mocks the `groups.delete()` method.
318
621
  * @param options Optional response customization.
319
- * @returns A stub reference that can be used to restore the original behavior.
320
622
  */
321
- delete: (options?: MockResponseOptions): StubReference => {
322
- return this.createStub('groups', 'delete', () => {
323
- const status = options?.status ?? 204
324
- if (status !== 204) {
325
- throw new Exception('Mocked error', { status })
326
- }
327
- return undefined
623
+ delete: async (init?: MockResult): Promise<void> => {
624
+ const { mock } = this
625
+ // const respond = init?.response ?? {
626
+ // status: 204,
627
+ // }
628
+ const respond = this.createDefaultResponse(204, undefined, undefined, init)
629
+ await mock.add({
630
+ match: {
631
+ uri: RouteBuilder.group(':oid', ':key'),
632
+ methods: ['DELETE'],
633
+ },
634
+ respond,
328
635
  })
329
636
  },
330
637
 
331
638
  /**
332
639
  * Mocks the `groups.addUsers()` method.
333
640
  */
334
- addUsers: (options?: MockResponseOptions): StubReference => {
335
- return this.createStub('groups', 'addUsers', () => {
336
- const defaultData = this.generateGroup()
337
- if (options?.status !== undefined && options.status !== 200) {
338
- throw new Exception('Mocked error', { status: options.status })
339
- }
340
- return (options?.data ?? defaultData) as unknown
641
+ addUsers: async (init?: MockResult): Promise<void> => {
642
+ const { mock } = this
643
+ // const respond = init?.response ?? {
644
+ // status: 200,
645
+ // headers: { 'content-type': 'application/json' },
646
+ // body: JSON.stringify(this.gen.group.group()),
647
+ // }
648
+ const respond = this.createDefaultResponse(
649
+ 200,
650
+ { 'content-type': 'application/json' },
651
+ () => JSON.stringify(this.gen.group.group()),
652
+ init
653
+ )
654
+ await mock.add({
655
+ match: {
656
+ uri: RouteBuilder.groupUsers(':oid', ':key'),
657
+ methods: ['POST'],
658
+ },
659
+ respond,
341
660
  })
342
661
  },
343
662
 
344
663
  /**
345
664
  * Mocks the `groups.removeUsers()` method.
346
665
  */
347
- removeUsers: (options?: MockResponseOptions): StubReference => {
348
- return this.createStub('groups', 'removeUsers', () => {
349
- const defaultData = this.generateGroup()
350
- if (options?.status !== undefined && options.status !== 200) {
351
- throw new Exception('Mocked error', { status: options.status })
352
- }
353
- return (options?.data ?? defaultData) as unknown
666
+ removeUsers: async (init?: MockResult): Promise<void> => {
667
+ const { mock } = this
668
+ // const respond = init?.response ?? {
669
+ // status: 200,
670
+ // headers: { 'content-type': 'application/json' },
671
+ // body: JSON.stringify(this.gen.group.group()),
672
+ // }
673
+ const respond = this.createDefaultResponse(
674
+ 200,
675
+ { 'content-type': 'application/json' },
676
+ () => JSON.stringify(this.gen.group.group()),
677
+ init
678
+ )
679
+ await mock.add({
680
+ match: {
681
+ uri: RouteBuilder.groupUsers(':oid', ':key'),
682
+ methods: ['DELETE'],
683
+ },
684
+ respond,
354
685
  })
355
686
  },
356
687
  }
@@ -362,39 +693,27 @@ export class SdkMock {
362
693
  /**
363
694
  * Mocks the `user.me()` method.
364
695
  * @param options Optional response customization.
365
- * @returns A stub reference that can be used to restore the original behavior.
366
- */
367
- me: (options?: MockResponseOptions): StubReference => {
368
- return this.createStub('user', 'me', () => {
369
- const defaultData = this.generateUser()
370
- if (options?.status !== undefined && options.status !== 200) {
371
- throw new Exception(`Mocked error. Status: ${options.status}`, { status: options.status })
372
- }
373
- return (options?.data ?? defaultData) as unknown
374
- })
375
- },
376
- }
377
-
378
- /**
379
- * Auth API mocks.
380
- */
381
- auth = {
382
- /**
383
- * Mocks the `auth.oauthRedirect()` method.
384
- * This method returns `null` by default as it performs window navigation.
385
- * @param options Optional response customization.
386
- * @returns A stub reference that can be used to restore the original behavior.
387
696
  */
388
- oauthRedirect: (options?: MockResponseOptions): StubReference => {
389
- return this.createStub(
390
- 'auth',
391
- 'oauthRedirect',
392
- () => {
393
- const defaultData = options?.data ?? null
394
- return defaultData
395
- },
396
- true
697
+ me: async (init?: MockResult): Promise<void> => {
698
+ const { mock } = this
699
+ // const respond = init?.response ?? {
700
+ // status: 200,
701
+ // headers: { 'content-type': 'application/json' },
702
+ // body: JSON.stringify(this.gen.user.user()),
703
+ // }
704
+ const respond = this.createDefaultResponse(
705
+ 200,
706
+ { 'content-type': 'application/json' },
707
+ () => JSON.stringify(this.gen.user.user()),
708
+ init
397
709
  )
710
+ await mock.add({
711
+ match: {
712
+ uri: RouteBuilder.usersMe(),
713
+ methods: ['GET'],
714
+ },
715
+ respond,
716
+ })
398
717
  },
399
718
  }
400
719
 
@@ -405,232 +724,394 @@ export class SdkMock {
405
724
  /**
406
725
  * Mocks the `file.list()` method.
407
726
  */
408
- list: (options?: MockResponseOptions): StubReference => {
409
- return this.createStub('file', 'list', () => {
410
- const defaultData: ContextListResult<IFile> = {
411
- items: [this.generateFile(), this.generateFile()],
412
- }
413
- if (options?.status !== undefined && options.status !== 200) {
414
- throw new Exception('Mocked error', { status: options.status })
415
- }
416
- return (options?.data ?? defaultData) as unknown
727
+ list: async (init?: MockListResult): Promise<void> => {
728
+ const { mock } = this
729
+ // const respond = init?.response ?? {
730
+ // status: 200,
731
+ // headers: { 'content-type': 'application/json' },
732
+ // body: JSON.stringify({
733
+ // items: this.gen.file.files(init?.size ?? 5),
734
+ // cursor: this.createCursorOption(init),
735
+ // } as ContextListResult<IFile>),
736
+ // }
737
+ const respond = this.createDefaultResponse(
738
+ 200,
739
+ { 'content-type': 'application/json' },
740
+ () => {
741
+ const obj: ContextListResult<IFile> = {
742
+ items: this.gen.file.files(init?.size ?? 5),
743
+ cursor: this.createCursorOption(init),
744
+ }
745
+ return JSON.stringify(obj)
746
+ },
747
+ init
748
+ )
749
+ await mock.add({
750
+ match: {
751
+ uri: RouteBuilder.files(':oid'),
752
+ methods: ['GET'],
753
+ },
754
+ respond,
417
755
  })
418
756
  },
419
757
 
420
758
  /**
421
759
  * Mocks the `file.createMeta()` method.
422
760
  */
423
- createMeta: (options?: MockResponseOptions): StubReference => {
424
- return this.createStub('file', 'createMeta', () => {
425
- const defaultData: IFile = this.generateFile()
426
- const status = options?.status ?? 201
427
- if (status !== 201) {
428
- throw new Exception('Mocked error', { status })
429
- }
430
- return (options?.data ?? defaultData) as unknown
761
+ createMeta: async (init?: MockResult): Promise<void> => {
762
+ const { mock } = this
763
+ // const respond = init?.response ?? {
764
+ // status: 201,
765
+ // headers: { 'content-type': 'application/json' },
766
+ // body: JSON.stringify(this.gen.file.file()),
767
+ // }
768
+ const respond = this.createDefaultResponse(
769
+ 201,
770
+ { 'content-type': 'application/json' },
771
+ () => JSON.stringify(this.gen.file.file()),
772
+ init
773
+ )
774
+ await mock.add({
775
+ match: {
776
+ uri: RouteBuilder.files(':oid'),
777
+ methods: ['POST'],
778
+ },
779
+ respond,
431
780
  })
432
781
  },
433
782
 
434
783
  /**
435
784
  * Mocks the `file.createMedia()` method.
436
785
  */
437
- createMedia: (options?: MockResponseOptions): StubReference => {
438
- return this.createStub('file', 'createMedia', () => {
439
- const status = options?.status ?? 200
440
- if (status !== 200) {
441
- throw new Exception('Mocked error', { status })
442
- }
443
- return undefined
786
+ createMedia: async (init?: MockResult): Promise<void> => {
787
+ const { mock } = this
788
+ // const respond = init?.response ?? {
789
+ // status: 200,
790
+ // }
791
+ const respond = this.createDefaultResponse(200, undefined, undefined, init)
792
+ await mock.add({
793
+ match: {
794
+ uri: RouteBuilder.fileMedia(':oid', ':id'),
795
+ methods: ['PUT'],
796
+ },
797
+ respond,
444
798
  })
445
799
  },
446
800
 
447
801
  /**
448
802
  * Mocks the `file.create()` method.
449
803
  */
450
- create: (options?: MockResponseOptions): StubReference => {
451
- return this.createStub('file', 'create', () => {
452
- const status = options?.status ?? 201
453
- if (status !== 201) {
454
- throw new Exception('Mocked error', { status })
455
- }
456
- const defaultData: IFile = this.generateFile()
457
- return (options?.data ?? defaultData) as unknown
458
- })
804
+ create: async (init?: MockResult): Promise<void> => {
805
+ await this.file.createMeta(init)
806
+ // When SDK's file.create() is called, it responds with
807
+ // what the result of file.createMeta() would be.
808
+ // Because of that, we don't need to configure the media request.
809
+ await this.file.createMedia()
459
810
  },
460
811
 
461
812
  /**
462
813
  * Mocks the `file.createFolder()` method.
463
814
  */
464
- createFolder: (options?: MockResponseOptions): StubReference => {
465
- return this.createStub('file', 'createFolder', () => {
466
- const status = options?.status ?? 201
467
- if (status !== 201) {
468
- throw new Exception('Mocked error', { status })
469
- }
470
- const defaultData: IFolder = this.generateFolder()
471
- return (options?.data ?? defaultData) as unknown
815
+ createFolder: async (init?: MockResult): Promise<void> => {
816
+ const { mock } = this
817
+ // const respond = init?.response ?? {
818
+ // status: 201,
819
+ // headers: { 'content-type': 'application/json' },
820
+ // body: JSON.stringify(this.gen.file.folder()),
821
+ // }
822
+ const respond = this.createDefaultResponse(
823
+ 201,
824
+ { 'content-type': 'application/json' },
825
+ () => JSON.stringify(this.gen.file.folder()),
826
+ init
827
+ )
828
+ await mock.add({
829
+ match: {
830
+ uri: RouteBuilder.files(':oid'),
831
+ methods: ['POST'],
832
+ },
833
+ respond,
472
834
  })
473
835
  },
474
836
 
475
837
  /**
476
838
  * Mocks the `file.read()` method.
477
839
  */
478
- read: (options?: MockResponseOptions): StubReference => {
479
- return this.createStub('file', 'read', () => {
480
- const defaultData: IFile = this.generateFile()
481
- if (options?.status !== undefined && options.status !== 200) {
482
- throw new Exception('Mocked error', { status: options.status })
483
- }
484
- return (options?.data ?? defaultData) as unknown
840
+ read: async (init?: MockResult): Promise<void> => {
841
+ const { mock } = this
842
+ // const respond = init?.response ?? {
843
+ // status: 200,
844
+ // headers: { 'content-type': 'application/json' },
845
+ // body: JSON.stringify(this.gen.file.file()),
846
+ // }
847
+ const respond = this.createDefaultResponse(
848
+ 200,
849
+ { 'content-type': 'application/json' },
850
+ () => JSON.stringify(this.gen.file.file()),
851
+ init
852
+ )
853
+ await mock.add({
854
+ match: {
855
+ uri: RouteBuilder.file(':oid', ':id'),
856
+ methods: ['GET'],
857
+ },
858
+ respond,
485
859
  })
486
860
  },
487
861
 
488
862
  /**
489
863
  * Mocks the `file.readMedia()` method.
490
864
  */
491
- readMedia: (options?: MockResponseOptions): StubReference => {
492
- return this.createStub('file', 'readMedia', () => {
493
- if (options?.status !== undefined && options.status !== 200) {
494
- throw new Exception('Mocked error', { status: options.status })
495
- }
496
- const defaultData = { media: { ok: true }, version: 1 }
497
- return (options?.data ?? defaultData) as unknown
865
+ readMedia: async (init?: MockResult): Promise<void> => {
866
+ const { mock } = this
867
+ // const respond = init?.response ?? {
868
+ // status: 200,
869
+ // headers: {
870
+ // 'content-type': 'application/json',
871
+ // 'x-version': `${this.gen.faker.number.int({ min: 1, max: 100 })}`,
872
+ // },
873
+ // body: JSON.stringify({ data: this.gen.faker.lorem.sentences() }),
874
+ // }
875
+ const respond = this.createDefaultResponse(
876
+ 200,
877
+ {
878
+ 'content-type': 'application/json',
879
+ 'x-version': `${this.gen.faker.number.int({ min: 1, max: 100 })}`,
880
+ },
881
+ () => JSON.stringify({ data: this.gen.faker.lorem.sentences() }),
882
+ init
883
+ )
884
+ await mock.add({
885
+ match: {
886
+ uri: RouteBuilder.fileMedia(':oid', ':id'),
887
+ methods: ['GET'],
888
+ },
889
+ respond,
498
890
  })
499
891
  },
500
892
 
501
893
  /**
502
894
  * Mocks the `file.readBulk()` method.
503
895
  */
504
- readBulk: (options?: MockResponseOptions): StubReference => {
505
- return this.createStub('file', 'readBulk', () => {
506
- const defaultData: IBulkOperationResult<IFile> = {
507
- items: [this.generateFile(), undefined],
508
- }
509
- if (options?.status !== undefined && options.status !== 200) {
510
- throw new Exception('Mocked error', { status: options.status })
511
- }
512
- return (options?.data ?? defaultData) as unknown
896
+ readBulk: async (init?: MockListResult): Promise<void> => {
897
+ const { mock } = this
898
+ // const respond = init?.response ?? {
899
+ // status: 200,
900
+ // headers: { 'content-type': 'application/json' },
901
+ // body: JSON.stringify({
902
+ // items: this.gen.file.files(init?.size ?? 5),
903
+ // } as IBulkOperationResult<IFile>),
904
+ // }
905
+ const respond = this.createDefaultResponse(
906
+ 200,
907
+ { 'content-type': 'application/json' },
908
+ () => {
909
+ const obj: IBulkOperationResult<IFile> = {
910
+ items: this.gen.file.files(init?.size ?? 5),
911
+ }
912
+ return JSON.stringify(obj)
913
+ },
914
+ init
915
+ )
916
+ await mock.add({
917
+ match: {
918
+ uri: RouteBuilder.filesBatch(':oid'),
919
+ methods: ['POST'],
920
+ },
921
+ respond,
513
922
  })
514
923
  },
515
924
 
516
925
  /**
517
926
  * Mocks the `file.patch()` method.
518
927
  */
519
- patch: (options?: MockResponseOptions): StubReference => {
520
- return this.createStub('file', 'patch', () => {
521
- const defaultData: IFile = this.generateFile()
522
- if (options?.status !== undefined && options.status !== 200) {
523
- throw new Exception('Mocked error', { status: options.status })
524
- }
525
- return (options?.data ?? defaultData) as unknown
928
+ patch: async (init?: MockResult): Promise<void> => {
929
+ const { mock } = this
930
+ // const respond = init?.response ?? {
931
+ // status: 200,
932
+ // headers: { 'content-type': 'application/json' },
933
+ // body: JSON.stringify(this.gen.file.file()),
934
+ // }
935
+ const respond = this.createDefaultResponse(
936
+ 200,
937
+ { 'content-type': 'application/json' },
938
+ () => JSON.stringify(this.gen.file.file()),
939
+ init
940
+ )
941
+ await mock.add({
942
+ match: {
943
+ uri: RouteBuilder.file(':oid', ':id'),
944
+ methods: ['PATCH'],
945
+ },
946
+ respond,
526
947
  })
527
948
  },
528
949
 
529
950
  /**
530
951
  * Mocks the `file.patchMedia()` method.
531
952
  */
532
- patchMedia: (options?: MockResponseOptions): StubReference => {
533
- return this.createStub('file', 'patchMedia', () => {
534
- const defaultData: MediaPatchRevision = this.generateMediaPatchRevision()
535
- if (options?.status !== undefined && options.status !== 200) {
536
- throw new Exception('Mocked error', { status: options.status })
537
- }
538
- return (options?.data ?? defaultData) as unknown
953
+ patchMedia: async (init?: MockResult): Promise<void> => {
954
+ const { mock } = this
955
+ // const respond = init?.response ?? {
956
+ // status: 200,
957
+ // headers: { 'content-type': 'application/json' },
958
+ // body: JSON.stringify(this.gen.patch.mediaPatchRevision()),
959
+ // }
960
+ const respond = this.createDefaultResponse(
961
+ 200,
962
+ { 'content-type': 'application/json' },
963
+ () => JSON.stringify(this.gen.patch.mediaPatchRevision()),
964
+ init
965
+ )
966
+ await mock.add({
967
+ match: {
968
+ uri: RouteBuilder.fileMedia(':oid', ':id'),
969
+ methods: ['PATCH'],
970
+ },
971
+ respond,
539
972
  })
540
973
  },
541
974
 
542
975
  /**
543
976
  * Mocks the `file.delete()` method.
544
977
  */
545
- delete: (options?: MockResponseOptions): StubReference => {
546
- return this.createStub('file', 'delete', () => {
547
- const status = options?.status ?? 204
548
- if (status !== 204) {
549
- throw new Exception('Mocked error', { status })
550
- }
551
- return undefined
978
+ delete: async (init?: MockResult): Promise<void> => {
979
+ const { mock } = this
980
+ // const respond = init?.response ?? {
981
+ // status: 204,
982
+ // }
983
+ const respond = this.createDefaultResponse(204, undefined, undefined, init)
984
+ await mock.add({
985
+ match: {
986
+ uri: RouteBuilder.file(':oid', ':id'),
987
+ methods: ['DELETE'],
988
+ },
989
+ respond,
552
990
  })
553
991
  },
554
992
 
555
993
  /**
556
994
  * Mocks the `file.deleteBulk()` method.
557
995
  */
558
- deleteBulk: (options?: MockResponseOptions): StubReference => {
559
- return this.createStub('file', 'deleteBulk', () => {
560
- const status = options?.status ?? 204
561
- if (status !== 204) {
562
- throw new Exception('Mocked error', { status })
563
- }
564
- return undefined
996
+ deleteBulk: async (init?: MockResult): Promise<void> => {
997
+ const { mock } = this
998
+ // const respond = init?.response ?? {
999
+ // status: 204,
1000
+ // }
1001
+ const respond = this.createDefaultResponse(204, undefined, undefined, init)
1002
+ await mock.add({
1003
+ match: {
1004
+ uri: RouteBuilder.files(':oid'),
1005
+ methods: ['DELETE'],
1006
+ },
1007
+ respond,
565
1008
  })
566
1009
  },
567
1010
 
568
1011
  /**
569
1012
  * Mocks the `file.patchUsers()` method.
570
1013
  */
571
- patchUsers: (options?: MockResponseOptions): StubReference => {
572
- return this.createStub('file', 'patchUsers', () => {
573
- const defaultData: IFile = this.generateFile()
574
- if (options?.status !== undefined && options.status !== 200) {
575
- throw new Exception('Mocked error', { status: options.status })
576
- }
577
- return (options?.data ?? defaultData) as unknown
1014
+ patchUsers: async (init?: MockResult): Promise<void> => {
1015
+ const { mock } = this
1016
+ // const respond = init?.response ?? {
1017
+ // status: 200,
1018
+ // headers: { 'content-type': 'application/json' },
1019
+ // body: JSON.stringify(this.gen.file.file()),
1020
+ // }
1021
+ const respond = this.createDefaultResponse(
1022
+ 200,
1023
+ { 'content-type': 'application/json' },
1024
+ () => JSON.stringify(this.gen.file.file()),
1025
+ init
1026
+ )
1027
+ await mock.add({
1028
+ match: {
1029
+ uri: RouteBuilder.filesAccess(':oid', ':id'),
1030
+ methods: ['PATCH'],
1031
+ },
1032
+ respond,
578
1033
  })
579
1034
  },
580
1035
 
581
1036
  /**
582
1037
  * Mocks the `file.addUser()` method.
583
1038
  */
584
- addUser: (options?: MockResponseOptions): StubReference => {
585
- return this.createStub('file', 'addUser', () => {
586
- const defaultData: IFile = this.generateFile()
587
- if (options?.status !== undefined && options.status !== 200) {
588
- throw new Exception('Mocked error', { status: options.status })
589
- }
590
- return (options?.data ?? defaultData) as unknown
591
- })
1039
+ addUser: async (init?: MockResult): Promise<void> => {
1040
+ await this.file.patchUsers(init)
592
1041
  },
593
1042
 
594
1043
  /**
595
1044
  * Mocks the `file.removeUser()` method.
596
1045
  */
597
- removeUser: (options?: MockResponseOptions): StubReference => {
598
- return this.createStub('file', 'removeUser', () => {
599
- const defaultData: IFile = this.generateFile()
600
- if (options?.status !== undefined && options.status !== 200) {
601
- throw new Exception('Mocked error', { status: options.status })
602
- }
603
- return (options?.data ?? defaultData) as unknown
604
- })
1046
+ removeUser: async (init?: MockResult): Promise<void> => {
1047
+ await this.file.patchUsers(init)
605
1048
  },
606
1049
 
607
1050
  /**
608
1051
  * Mocks the `file.listUsers()` method.
609
1052
  */
610
- listUsers: (options?: MockResponseOptions): StubReference => {
611
- return this.createStub('file', 'listUsers', () => {
612
- const defaultData: ContextListResult<IUser> = {
613
- items: [this.generateUser(), this.generateUser()],
614
- }
615
- if (options?.status !== undefined && options.status !== 200) {
616
- throw new Exception('Mocked error', { status: options.status })
617
- }
618
- return (options?.data ?? defaultData) as unknown
1053
+ listUsers: async (init?: MockListResult): Promise<void> => {
1054
+ const { mock } = this
1055
+ // const respond = init?.response ?? {
1056
+ // status: 200,
1057
+ // headers: { 'content-type': 'application/json' },
1058
+ // body: JSON.stringify({
1059
+ // items: this.gen.user.users(init?.size ?? 5),
1060
+ // cursor: this.createCursorOption(init),
1061
+ // } as ContextListResult<IUser>),
1062
+ // }
1063
+ const respond = this.createDefaultResponse(
1064
+ 200,
1065
+ { 'content-type': 'application/json' },
1066
+ () => {
1067
+ const obj: ContextListResult<IUser> = {
1068
+ items: this.gen.user.users(init?.size ?? 5),
1069
+ cursor: this.createCursorOption(init),
1070
+ }
1071
+ return JSON.stringify(obj)
1072
+ },
1073
+ init
1074
+ )
1075
+ await mock.add({
1076
+ match: {
1077
+ uri: RouteBuilder.fileUsers(':oid', ':id'),
1078
+ methods: ['GET'],
1079
+ },
1080
+ respond,
619
1081
  })
620
1082
  },
621
1083
 
622
1084
  /**
623
1085
  * Mocks the `file.breadcrumbs()` method.
624
1086
  */
625
- breadcrumbs: (options?: MockResponseOptions): StubReference => {
626
- return this.createStub('file', 'breadcrumbs', () => {
627
- const defaultData: ContextListResult<FileBreadcrumb> = {
628
- items: this.generateBreadcrumbs(),
629
- }
630
- if (options?.status !== undefined && options.status !== 200) {
631
- throw new Exception('Mocked error', { status: options.status })
632
- }
633
- return (options?.data ?? defaultData) as unknown
1087
+ breadcrumbs: async (init?: MockListResult): Promise<void> => {
1088
+ const { mock } = this
1089
+ // const respond = init?.response ?? {
1090
+ // status: 200,
1091
+ // headers: { 'content-type': 'application/json' },
1092
+ // body: JSON.stringify({
1093
+ // items: this.gen.file.fileBreadcrumbs(init?.size ?? 5),
1094
+ // cursor: this.createCursorOption(init),
1095
+ // } as ContextListResult<FileBreadcrumb>),
1096
+ // }
1097
+ const respond = this.createDefaultResponse(
1098
+ 200,
1099
+ { 'content-type': 'application/json' },
1100
+ () => {
1101
+ const obj: ContextListResult<FileBreadcrumb> = {
1102
+ items: this.gen.file.fileBreadcrumbs(init?.size ?? 5),
1103
+ cursor: this.createCursorOption(init),
1104
+ }
1105
+ return JSON.stringify(obj)
1106
+ },
1107
+ init
1108
+ )
1109
+ await mock.add({
1110
+ match: {
1111
+ uri: RouteBuilder.fileBreadcrumbs(':oid', ':id'),
1112
+ methods: ['GET'],
1113
+ },
1114
+ respond,
634
1115
  })
635
1116
  },
636
1117
  }
@@ -639,15 +1120,34 @@ export class SdkMock {
639
1120
  * Shared API mocks.
640
1121
  */
641
1122
  shared = {
642
- list: (options?: MockResponseOptions): StubReference => {
643
- return this.createStub('shared', 'list', () => {
644
- const defaultData: ContextListResult<IFile> = {
645
- items: [this.generateFile(), this.generateFile()],
646
- }
647
- if (options?.status !== undefined && options.status !== 200) {
648
- throw new Exception('Mocked error', { status: options.status })
649
- }
650
- return (options?.data ?? defaultData) as unknown
1123
+ list: async (init?: MockListResult): Promise<void> => {
1124
+ const { mock } = this
1125
+ // const respond = init?.response ?? {
1126
+ // status: 200,
1127
+ // headers: { 'content-type': 'application/json' },
1128
+ // body: JSON.stringify({
1129
+ // items: this.gen.file.files(init?.size ?? 5),
1130
+ // cursor: this.createCursorOption(init),
1131
+ // } as ContextListResult<IFile>),
1132
+ // }
1133
+ const respond = this.createDefaultResponse(
1134
+ 200,
1135
+ { 'content-type': 'application/json' },
1136
+ () => {
1137
+ const obj: ContextListResult<IFile> = {
1138
+ items: this.gen.file.files(init?.size ?? 5),
1139
+ cursor: this.createCursorOption(init),
1140
+ }
1141
+ return JSON.stringify(obj)
1142
+ },
1143
+ init
1144
+ )
1145
+ await mock.add({
1146
+ match: {
1147
+ uri: RouteBuilder.shared(':oid'),
1148
+ methods: ['GET'],
1149
+ },
1150
+ respond,
651
1151
  })
652
1152
  },
653
1153
  }
@@ -656,300 +1156,77 @@ export class SdkMock {
656
1156
  * Trash API mocks.
657
1157
  */
658
1158
  trash = {
659
- list: (options?: MockResponseOptions): StubReference => {
660
- return this.createStub('trash', 'list', () => {
661
- const defaultData: ContextListResult<TrashEntry> = {
662
- items: [this.generateTrashEntry(), this.generateTrashEntry()],
663
- }
664
- if (options?.status !== undefined && options.status !== 200) {
665
- throw new Exception('Mocked error', { status: options.status })
666
- }
667
- return (options?.data ?? defaultData) as unknown
1159
+ list: async (init?: MockListResult): Promise<void> => {
1160
+ const { mock } = this
1161
+ // const respond = init?.response ?? {
1162
+ // status: 200,
1163
+ // headers: { 'content-type': 'application/json' },
1164
+ // body: JSON.stringify({
1165
+ // items: this.gen.trash.trashEntries(init?.size ?? 5),
1166
+ // cursor: this.createCursorOption(init),
1167
+ // } as ContextListResult<TrashEntry>),
1168
+ // }
1169
+ const respond = this.createDefaultResponse(
1170
+ 200,
1171
+ { 'content-type': 'application/json' },
1172
+ () => {
1173
+ const obj: ContextListResult<TrashEntry> = {
1174
+ items: this.gen.trash.trashEntries(init?.size ?? 5),
1175
+ cursor: this.createCursorOption(init),
1176
+ }
1177
+ return JSON.stringify(obj)
1178
+ },
1179
+ init
1180
+ )
1181
+ await mock.add({
1182
+ match: {
1183
+ uri: RouteBuilder.trash(':oid'),
1184
+ methods: ['GET'],
1185
+ },
1186
+ respond,
668
1187
  })
669
1188
  },
670
- delete: (options?: MockResponseOptions): StubReference => {
671
- return this.createStub('trash', 'delete', () => {
672
- const status = options?.status ?? 204
673
- if (status !== 204) {
674
- throw new Exception('Mocked error', { status })
675
- }
676
- return undefined
1189
+ delete: async (init?: MockResult): Promise<void> => {
1190
+ const { mock } = this
1191
+ // const respond = init?.response ?? {
1192
+ // status: 204,
1193
+ // }
1194
+ const respond = this.createDefaultResponse(204, undefined, undefined, init)
1195
+ await mock.add({
1196
+ match: {
1197
+ uri: RouteBuilder.trashBatchDelete(':oid'),
1198
+ methods: ['DELETE'],
1199
+ },
1200
+ respond,
677
1201
  })
678
1202
  },
679
- restore: (options?: MockResponseOptions): StubReference => {
680
- return this.createStub('trash', 'restore', () => {
681
- const status = options?.status ?? 204
682
- if (status !== 204) {
683
- throw new Exception('Mocked error', { status })
684
- }
685
- return undefined
1203
+ restore: async (init?: MockResult): Promise<void> => {
1204
+ const { mock } = this
1205
+ // const respond = init?.response ?? {
1206
+ // status: 204,
1207
+ // }
1208
+ const respond = this.createDefaultResponse(204, undefined, undefined, init)
1209
+ await mock.add({
1210
+ match: {
1211
+ uri: RouteBuilder.trashBatchRestore(':oid'),
1212
+ methods: ['POST'],
1213
+ },
1214
+ respond,
686
1215
  })
687
1216
  },
688
- empty: (options?: MockResponseOptions): StubReference => {
689
- return this.createStub('trash', 'empty', () => {
690
- const status = options?.status ?? 204
691
- if (status !== 204) {
692
- throw new Exception('Mocked error', { status })
693
- }
694
- return undefined
1217
+ empty: async (init?: MockResult): Promise<void> => {
1218
+ const { mock } = this
1219
+ // const respond = init?.response ?? {
1220
+ // status: 204,
1221
+ // }
1222
+ const respond = this.createDefaultResponse(204, undefined, undefined, init)
1223
+ await mock.add({
1224
+ match: {
1225
+ uri: RouteBuilder.trashEmpty(':oid'),
1226
+ methods: ['DELETE'],
1227
+ },
1228
+ respond,
695
1229
  })
696
1230
  },
697
1231
  }
698
-
699
- /**
700
- * Restores all stubs created by this mocker.
701
- */
702
- restore(): void {
703
- this.stubs.forEach((stub) => stub.restore())
704
- this.stubs = []
705
- }
706
-
707
- /**
708
- * Creates a stub for a specific SDK method.
709
- * @param api The API name (e.g., 'organizations', 'groups', 'user', 'auth').
710
- * @param method The method name to stub.
711
- * @param implementation The stub implementation that returns the mocked data.
712
- * @returns A stub reference.
713
- */
714
- private createStub(
715
- api:
716
- | 'organizations'
717
- | 'groups'
718
- | 'user'
719
- | 'auth'
720
- | 'file'
721
- | 'shared'
722
- | 'trash'
723
- | 'organizations.invitations'
724
- | 'organizations.users',
725
- method: string,
726
- implementation: () => unknown,
727
- sync?: boolean
728
- ): StubReference {
729
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
730
- let target: any = this.sdk
731
- const parts = api.split('.')
732
- for (const part of parts) {
733
- target = target[part]
734
- if (!target) {
735
- throw new Error(`API '${api}' not found in SDK`)
736
- }
737
- }
738
-
739
- const original = target[method]
740
- if (typeof original !== 'function') {
741
- throw new Error(`Method '${method}' not found in ${api} API`)
742
- }
743
-
744
- // Store original and create stub using sinon
745
- const stub = sync
746
- ? sinon.stub(target, method).callsFake(() => implementation())
747
- : sinon.stub(target, method).callsFake(async () => implementation())
748
-
749
- const stubRef: StubReference = {
750
- restore: () => {
751
- if (stub) {
752
- stub.restore()
753
- }
754
- const index = this.stubs.indexOf(stubRef)
755
- if (index > -1) {
756
- this.stubs.splice(index, 1)
757
- }
758
- },
759
- }
760
-
761
- this.stubs.push(stubRef)
762
- return stubRef
763
- }
764
-
765
- /**
766
- * Creates a response object from data and options.
767
- * @param defaultData Default data to return if not overridden.
768
- * @param options Response options.
769
- * @returns The final data to return (for non-HTTP responses) or IStoreResponse (for HTTP responses).
770
- */
771
- private createResponse(defaultData: unknown, options?: MockResponseOptions): unknown {
772
- // For SDK-level stubs we return plain data. Status and headers are
773
- // accepted but not propagated, since high-level SDK methods return data.
774
- const data = options?.data !== undefined ? options.data : defaultData
775
- return data
776
- }
777
-
778
- /**
779
- * Generates a random organization object.
780
- */
781
- private generateOrganization(): IOrganization {
782
- return {
783
- kind: OrganizationKind,
784
- key: nanoid(),
785
- name: `Organization ${this.randomString()}`,
786
- createdBy: nanoid(),
787
- createdDate: Date.now() - Math.random() * 1000 * 60 * 60 * 24 * 30,
788
- grantType: this.randomChoice(['owner', 'manager', 'editor', 'viewer'] as const),
789
- }
790
- }
791
-
792
- /**
793
- * Generates a random group object.
794
- */
795
- private generateGroup(): GroupSchema {
796
- return {
797
- kind: GroupKind,
798
- key: nanoid(),
799
- name: `Group ${this.randomString()}`,
800
- description: `Description for ${this.randomString()}`,
801
- owner: nanoid(),
802
- oid: nanoid(),
803
- users: [],
804
- createdAt: Date.now() - Math.random() * 1000 * 60 * 60 * 24 * 30,
805
- updatedAt: Date.now() - Math.random() * 1000 * 60 * 60 * 24 * 7,
806
- icon: `https://example.com/icon-${nanoid()}.png`,
807
- color: this.randomColor(),
808
- }
809
- }
810
-
811
- /**
812
- * Generates a random user object.
813
- */
814
- private generateUser(): IUser {
815
- const firstName = this.randomString()
816
- const lastName = this.randomString()
817
- return {
818
- kind: UserKind,
819
- key: nanoid(),
820
- name: `${firstName} ${lastName}`,
821
- email: [
822
- {
823
- email: `${firstName.toLowerCase()}.${lastName.toLowerCase()}@example.com`,
824
- verified: true,
825
- },
826
- ],
827
- status: 'active',
828
- created: Date.now() - Math.random() * 1000 * 60 * 60 * 24 * 365,
829
- updated: Date.now() - Math.random() * 1000 * 60 * 60 * 24 * 7,
830
- }
831
- }
832
-
833
- /**
834
- * Generates a random file meta object.
835
- */
836
- private generateFile(): IFile {
837
- const kind = this.randomChoice([ProjectKind, DomainFileKind, CertificateFileKind, FolderKind] as const)
838
- const name = `File ${this.randomString()}`
839
- return File.createSchema({ kind, info: { name } })
840
- }
841
-
842
- /**
843
- * Generates a random folder meta object.
844
- */
845
- private generateFolder(): IFolder {
846
- const file = File.createSchema({ kind: FolderKind, info: { name: `Folder ${this.randomString()}` } }) as IFolder
847
- return file
848
- }
849
-
850
- /**
851
- * Generates a random media patch revision object.
852
- */
853
- private generateMediaPatchRevision(): MediaPatchRevision {
854
- const version = Math.floor(Math.random() * 10) + 1
855
- return {
856
- id: nanoid(),
857
- timestamp: Date.now(),
858
- patch: [],
859
- version,
860
- revert: [],
861
- newVersion: version + 1,
862
- }
863
- }
864
-
865
- /**
866
- * Generates a random breadcrumbs list.
867
- */
868
- private generateBreadcrumbs(): FileBreadcrumb[] {
869
- const depth = 2 + Math.floor(Math.random() * 2) // 2-3
870
- const items: FileBreadcrumb[] = []
871
- for (let i = 0; i < depth; i += 1) {
872
- items.push({ key: nanoid(), kind: i === depth - 1 ? ProjectKind : FolderKind, name: this.randomString() })
873
- }
874
- return items
875
- }
876
-
877
- /**
878
- * Generates a random invitation object.
879
- */
880
- private generateInvitation(): InvitationSchema {
881
- const firstName = this.randomString()
882
- const lastName = this.randomString()
883
- const now = Date.now()
884
- return {
885
- kind: InvitationKind,
886
- key: nanoid(),
887
- uid: nanoid(),
888
- oid: nanoid(),
889
- email: `${firstName.toLowerCase()}.${lastName.toLowerCase()}@example.com`,
890
- name: `${firstName} ${lastName}`,
891
- token: nanoid(),
892
- expiresAt: now + 7 * 24 * 60 * 60 * 1000,
893
- status: 'pending',
894
- grantType: this.randomChoice(['owner', 'manager', 'editor', 'viewer'] as const),
895
- createdAt: now - Math.random() * 1000 * 60 * 60 * 24,
896
- updatedAt: now - Math.random() * 1000 * 60 * 60,
897
- resent: 0,
898
- lastSentAt: now - Math.random() * 1000 * 60 * 60,
899
- }
900
- }
901
-
902
- /**
903
- * Generates a random trash entry.
904
- */
905
- private generateTrashEntry(): TrashEntry {
906
- return {
907
- key: nanoid(),
908
- refKey: nanoid(),
909
- kind: this.randomChoice([ProjectKind, DomainFileKind, CertificateFileKind, FolderKind] as const),
910
- name: `Deleted ${this.randomString()}`,
911
- info: { byMe: false, time: Date.now() - Math.floor(Math.random() * 1000000), user: nanoid(), name: 'User' },
912
- capabilities: { canDelete: true, canRestore: true },
913
- }
914
- }
915
-
916
- /**
917
- * Generates a random string.
918
- */
919
- private randomString(): string {
920
- const words = [
921
- 'Alpha',
922
- 'Beta',
923
- 'Gamma',
924
- 'Delta',
925
- 'Epsilon',
926
- 'Zeta',
927
- 'Theta',
928
- 'Lambda',
929
- 'Sigma',
930
- 'Omega',
931
- 'Phoenix',
932
- 'Dragon',
933
- 'Tiger',
934
- 'Eagle',
935
- 'Falcon',
936
- ]
937
- return words[Math.floor(Math.random() * words.length)]
938
- }
939
-
940
- /**
941
- * Returns a random choice from an array.
942
- */
943
- private randomChoice<T>(choices: readonly T[]): T {
944
- return choices[Math.floor(Math.random() * choices.length)]
945
- }
946
-
947
- /**
948
- * Generates a random hex color.
949
- */
950
- private randomColor(): string {
951
- return `#${Math.floor(Math.random() * 16777215)
952
- .toString(16)
953
- .padStart(6, '0')}`
954
- }
955
1232
  }