@neuralinnovations/dataisland-sdk 0.0.1-dev2 → 0.0.1-dev4

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 (64) hide show
  1. package/.editorconfig +4 -1
  2. package/.eslintrc.json +1 -1
  3. package/README.md +91 -2
  4. package/jest.config.ts +3 -3
  5. package/jest.setup.ts +2 -2
  6. package/package.json +3 -2
  7. package/src/appBuilder.ts +6 -6
  8. package/src/appSdk.ts +6 -6
  9. package/src/commands/startCommandHandler.ts +2 -2
  10. package/src/context.ts +3 -3
  11. package/src/credentials.ts +29 -7
  12. package/src/disposable.ts +3 -4
  13. package/src/dto/accessGroupResponse.ts +35 -0
  14. package/src/dto/chatResponse.ts +104 -0
  15. package/src/dto/userInfoResponse.ts +11 -1
  16. package/src/dto/workspacesResponse.ts +49 -0
  17. package/src/events.ts +13 -13
  18. package/src/index.ts +24 -12
  19. package/src/internal/app.impl.ts +25 -28
  20. package/src/internal/appBuilder.impl.ts +16 -16
  21. package/src/internal/createApp.impl.ts +3 -3
  22. package/src/services/commandService.ts +3 -3
  23. package/src/services/credentialService.ts +3 -3
  24. package/src/services/middlewareService.ts +4 -4
  25. package/src/services/organizationService.ts +18 -116
  26. package/src/services/requestBuilder.ts +40 -15
  27. package/src/services/responseUtils.ts +32 -0
  28. package/src/services/rpcService.ts +28 -11
  29. package/src/services/service.ts +10 -8
  30. package/src/services/userProfileService.ts +18 -66
  31. package/src/storages/chat.ts +21 -0
  32. package/src/storages/chats.ts +17 -0
  33. package/src/storages/file.impl.ts +69 -0
  34. package/src/storages/file.ts +28 -0
  35. package/src/storages/files.impl.ts +213 -0
  36. package/src/storages/files.ts +38 -0
  37. package/src/storages/filesPage.ts +27 -0
  38. package/src/storages/groups.impl.ts +337 -0
  39. package/src/storages/groups.ts +43 -0
  40. package/src/storages/organization.impl.ts +68 -0
  41. package/src/storages/organization.ts +33 -0
  42. package/src/storages/organizations.impl.ts +191 -0
  43. package/src/storages/organizations.ts +8 -28
  44. package/src/storages/userProfile.impl.ts +56 -0
  45. package/src/storages/userProfile.ts +2 -2
  46. package/src/storages/workspace.impl.ts +109 -0
  47. package/src/storages/workspace.ts +49 -0
  48. package/src/storages/workspaces.impl.ts +212 -0
  49. package/src/storages/workspaces.ts +53 -0
  50. package/test/commands.test.ts +8 -8
  51. package/test/data/test_file.pdf +0 -0
  52. package/test/disposable.test.ts +3 -3
  53. package/test/events.test.ts +4 -4
  54. package/test/files.test.ts +52 -0
  55. package/test/index.test.ts +42 -83
  56. package/test/organization.test.ts +57 -0
  57. package/test/registry.test.ts +8 -8
  58. package/test/services.test.ts +15 -15
  59. package/test/setup.ts +52 -0
  60. package/test/unitTest.test.ts +2 -2
  61. package/test/workspace.test.ts +71 -0
  62. package/src/services/organizationImpl.ts +0 -51
  63. package/src/services/organizationsImpl.ts +0 -55
  64. package/src/types.ts +0 -86
@@ -1,46 +1,26 @@
1
- import { EventDispatcher } from '../events'
1
+ import { EventDispatcher } from "../events"
2
+ import { Organization } from "./organization"
2
3
 
3
4
  /**
4
5
  * Organization id.
5
6
  */
6
7
  export type OrganizationId = string
7
8
 
8
- /**
9
- * Organization.
10
- */
11
- export abstract class Organization {
12
- /**
13
- * Organization id.
14
- */
15
- abstract get id(): OrganizationId
16
-
17
- /**
18
- * Organization name.
19
- */
20
- abstract get name(): string
21
-
22
- /**
23
- * Organization description.
24
- */
25
- abstract get description(): string
26
- }
27
-
28
9
  /**
29
10
  * Organization event.
30
11
  */
31
- export enum OrganizationEvent {
32
- ADDED = 'added',
33
- REMOVED = 'removed',
34
- CHANGED = 'changed',
35
- CURRENT_CHANGED = 'currentChanged'
12
+ export enum OrganizationsEvent {
13
+ ADDED = "added",
14
+ REMOVED = "removed",
15
+ CURRENT_CHANGED = "currentChanged"
36
16
  }
37
17
 
38
18
  /**
39
19
  * Organizations storage.
40
20
  */
41
21
  export abstract class Organizations extends EventDispatcher<
42
- OrganizationEvent,
43
- Organization | Organizations
22
+ OrganizationsEvent,
23
+ Organization
44
24
  > {
45
25
  /**
46
26
  * User's organizations.
@@ -0,0 +1,56 @@
1
+ import { UserEvent, UserProfile } from "./userProfile"
2
+ import { UserInfoResponse } from "../dto/userInfoResponse"
3
+
4
+ export class UserProfileImpl extends UserProfile {
5
+ private content?: UserInfoResponse
6
+
7
+ get id(): string {
8
+ if (this.content) {
9
+ return this.content.user.id
10
+ }
11
+ throw new Error("The profile is not loaded.")
12
+ }
13
+
14
+ get name(): string {
15
+ if (this.content) {
16
+ return this.content.user.profile.name
17
+ }
18
+ throw new Error("The profile is not loaded.")
19
+ }
20
+
21
+ get email(): string {
22
+ if (this.content) {
23
+ return this.content.user.profile.email
24
+ }
25
+ throw new Error("The profile is not loaded.")
26
+ }
27
+
28
+ get isDeleted(): boolean {
29
+ if (this.content) {
30
+ return this.content.user.isDeleted
31
+ }
32
+ throw new Error("The profile is not loaded.")
33
+ }
34
+
35
+ get createdAt(): Date {
36
+ if (this.content) {
37
+ return new Date(this.content.user.created_at)
38
+ }
39
+ throw new Error("The profile is not loaded.")
40
+ }
41
+
42
+ get modifiedAt(): Date {
43
+ if (this.content) {
44
+ return new Date(this.content.user.modified_at)
45
+ }
46
+ throw new Error("The profile is not loaded.")
47
+ }
48
+
49
+ initFrom(content: UserInfoResponse) {
50
+ this.content = content
51
+ this.dispatch({
52
+ type: UserEvent.CHANGED,
53
+ data: this
54
+ })
55
+ }
56
+ }
@@ -1,9 +1,9 @@
1
- import { EventDispatcher } from '../events'
1
+ import { EventDispatcher } from "../events"
2
2
 
3
3
  export type UserId = string
4
4
 
5
5
  export enum UserEvent {
6
- CHANGED = 'changed'
6
+ CHANGED = "changed"
7
7
  }
8
8
 
9
9
  export abstract class UserProfile extends EventDispatcher<
@@ -0,0 +1,109 @@
1
+ import { Context } from "../context"
2
+ import { Files } from "./files"
3
+ import { Workspace, WorkspaceEvent } from "./workspace"
4
+ import { OrganizationImpl } from "./organization.impl"
5
+ import { WorkspaceDto } from "../dto/workspacesResponse"
6
+ import { RpcService } from "../services/rpcService"
7
+ import { FilesImpl } from "./files.impl"
8
+ import { ResponseUtils } from "../services/responseUtils"
9
+
10
+ export class WorkspaceImpl extends Workspace {
11
+ private _isMarkAsDeleted: boolean = false
12
+ private _workspace?: WorkspaceDto
13
+
14
+ private readonly _files: FilesImpl
15
+
16
+ constructor(
17
+ public readonly organization: OrganizationImpl,
18
+ public readonly context: Context
19
+ ) {
20
+ super()
21
+ this._files = new FilesImpl(this, context)
22
+ }
23
+
24
+ get id(): string {
25
+ if (this._workspace) {
26
+ return this._workspace.id
27
+ }
28
+ throw new Error("Workspace is not loaded.")
29
+ }
30
+
31
+ get name(): string {
32
+ if (this._workspace) {
33
+ return this._workspace.profile.name
34
+ }
35
+ throw new Error("Workspace is not loaded.")
36
+ }
37
+
38
+ get description(): string {
39
+ if (this._workspace) {
40
+ return this._workspace.profile.description
41
+ }
42
+ throw new Error("Workspace is not loaded.")
43
+ }
44
+
45
+ get files(): Files {
46
+ return this._files
47
+ }
48
+
49
+ async change(name: string, description: string): Promise<void> {
50
+ if (!this._workspace) {
51
+ throw new Error("Workspace is not loaded.")
52
+ }
53
+ if (this._isMarkAsDeleted) {
54
+ throw new Error("Workspace is marked as deleted.")
55
+ }
56
+ if (name === this.name && description === this.description) {
57
+ return Promise.resolve()
58
+ }
59
+ if (name === undefined || name === null || name.trim() === "") {
60
+ throw new Error("Name is required. Please provide a valid name.")
61
+ }
62
+ if (
63
+ description === undefined ||
64
+ description === null ||
65
+ description.trim() === ""
66
+ ) {
67
+ throw new Error(
68
+ "Description is required. Please provide a valid description."
69
+ )
70
+ }
71
+
72
+ const response = await this.context
73
+ .resolve(RpcService)
74
+ ?.requestBuilder("api/v1/Workspaces")
75
+ .sendPutJson({
76
+ workspaceId: this.id,
77
+ profile: {
78
+ name,
79
+ description
80
+ }
81
+ })
82
+
83
+ if (ResponseUtils.isFail(response)) {
84
+ await ResponseUtils.throwError("Failed to change workspace", response)
85
+ }
86
+
87
+ if (this._workspace) {
88
+ this._workspace.profile.name = name
89
+ this._workspace.profile.description = description
90
+ }
91
+
92
+ this.dispatch({
93
+ type: WorkspaceEvent.CHANGED,
94
+ data: this
95
+ })
96
+ }
97
+
98
+ async initFrom(workspace: WorkspaceDto) {
99
+ this._workspace = workspace
100
+ }
101
+
102
+ get isMarkAsDeleted(): boolean {
103
+ return this._isMarkAsDeleted
104
+ }
105
+
106
+ markToDelete(): void {
107
+ this._isMarkAsDeleted = true
108
+ }
109
+ }
@@ -0,0 +1,49 @@
1
+ import { EventDispatcher } from "../events"
2
+ import { Files } from "./files"
3
+ import { WorkspaceId } from "./workspaces"
4
+ import { Organization } from "./organization"
5
+
6
+ /**
7
+ * Workspace event.
8
+ */
9
+ export enum WorkspaceEvent {
10
+ CHANGED = "changed"
11
+ }
12
+
13
+ /**
14
+ * Workspace.
15
+ */
16
+ export abstract class Workspace extends EventDispatcher<
17
+ WorkspaceEvent,
18
+ Workspace
19
+ > {
20
+ /**
21
+ * Organization.
22
+ */
23
+ abstract get organization(): Organization
24
+
25
+ /**
26
+ * Workspace id.
27
+ */
28
+ abstract get id(): WorkspaceId
29
+
30
+ /**
31
+ * Workspace name.
32
+ */
33
+ abstract get name(): string
34
+
35
+ /**
36
+ * Workspace description.
37
+ */
38
+ abstract get description(): string
39
+
40
+ /**
41
+ * Workspace files.
42
+ */
43
+ abstract get files(): Files
44
+
45
+ /**
46
+ * Change workspace name and description.
47
+ */
48
+ abstract change(name: string, description: string): Promise<void>
49
+ }
@@ -0,0 +1,212 @@
1
+ import { WorkspaceId, Workspaces, WorkspacesEvent } from "./workspaces"
2
+ import { OrganizationImpl } from "./organization.impl"
3
+ import { Context } from "../context"
4
+ import { Workspace } from "./workspace"
5
+ import { WorkspaceImpl } from "./workspace.impl"
6
+ import { OrganizationId } from "./organizations"
7
+ import { RpcService } from "../services/rpcService"
8
+ import { OrganizationWorkspaces } from "../dto/userInfoResponse"
9
+ import { WorkspaceDto } from "../dto/workspacesResponse"
10
+ import { ResponseUtils } from "../services/responseUtils"
11
+
12
+ export class WorkspacesImpl extends Workspaces {
13
+ private readonly _workspaces: WorkspaceImpl[] = []
14
+
15
+ constructor(
16
+ private readonly organization: OrganizationImpl,
17
+ private readonly context: Context
18
+ ) {
19
+ super()
20
+ }
21
+
22
+ get collection(): readonly Workspace[] {
23
+ return this._workspaces
24
+ }
25
+
26
+ get(id: string): Workspace {
27
+ return <Workspace>this.tryGet(id)
28
+ }
29
+
30
+ tryGet(id: string): Workspace | undefined {
31
+ return this._workspaces.find(workspace => workspace.id === id)
32
+ }
33
+
34
+ contains(id: string): boolean {
35
+ return this._workspaces.find(workspace => workspace.id === id) !== undefined
36
+ }
37
+
38
+ /**
39
+ * Create workspace.
40
+ * @param name
41
+ * @param description
42
+ * @param regulation
43
+ */
44
+ async create(
45
+ name: string,
46
+ description: string,
47
+ regulation?: {
48
+ isCreateNewGroup: boolean
49
+ newGroupName: string
50
+ groupIds: string[]
51
+ }
52
+ ): Promise<Workspace> {
53
+ if (name === undefined || name === null || name.trim() === "") {
54
+ throw new Error("Name is required, must be not empty")
55
+ }
56
+ if (
57
+ description === undefined ||
58
+ description === null ||
59
+ description.trim() === ""
60
+ ) {
61
+ throw new Error("Description is required, must be not empty")
62
+ }
63
+ if (regulation) {
64
+ if (
65
+ regulation.isCreateNewGroup === undefined ||
66
+ regulation.isCreateNewGroup === null
67
+ ) {
68
+ throw new Error("isCreateNewGroup is required, must be not empty")
69
+ }
70
+ if (
71
+ regulation.newGroupName === undefined ||
72
+ regulation.newGroupName === null ||
73
+ regulation.newGroupName.trim() === ""
74
+ ) {
75
+ throw new Error("newGroupName is required, must be not empty")
76
+ }
77
+ if (
78
+ regulation.groupIds === undefined ||
79
+ regulation.groupIds === null ||
80
+ regulation.groupIds.length === 0
81
+ ) {
82
+ throw new Error("groupIds is required, must be not empty")
83
+ }
84
+ }
85
+
86
+ // send create request to the server
87
+ const response = await this.context
88
+ .resolve(RpcService)
89
+ ?.requestBuilder("api/v1/Workspaces")
90
+ .sendPostJson({
91
+ organizationId: this.organization.id,
92
+ profile: {
93
+ name: name,
94
+ description: description
95
+ },
96
+ regulation: {
97
+ isCreateNewGroup: regulation?.isCreateNewGroup ?? false,
98
+ newGroupName: regulation?.newGroupName ?? "",
99
+ groupIds: regulation?.groupIds ?? []
100
+ }
101
+ })
102
+
103
+ // check response status
104
+ if (ResponseUtils.isFail(response)) {
105
+ await ResponseUtils.throwError("Failed to create workspace", response)
106
+ }
107
+
108
+ // parse workspace from the server's response
109
+ const content = (await response!.json()).workspace as WorkspaceDto
110
+
111
+ // create workspace implementation
112
+ const workspace = new WorkspaceImpl(this.organization, this.context)
113
+ await workspace.initFrom(content)
114
+
115
+ // add workspace to the collection
116
+ this._workspaces.push(workspace)
117
+
118
+ // dispatch event
119
+ this.dispatch({
120
+ type: WorkspacesEvent.ADDED,
121
+ data: workspace
122
+ })
123
+
124
+ return workspace
125
+ }
126
+
127
+ /**
128
+ * Delete workspace.
129
+ * @param id
130
+ */
131
+ async delete(id: WorkspaceId): Promise<void> {
132
+ // get workspace by id
133
+ const workspace = <WorkspaceImpl>this.tryGet(id)
134
+
135
+ // check if workspace is found
136
+ if (!workspace) {
137
+ throw new Error(`Workspace ${id} is not found`)
138
+ }
139
+
140
+ // check if workspace is already marked as deleted
141
+ if (workspace.isMarkAsDeleted) {
142
+ throw new Error(`Workspace ${id} is already marked as deleted`)
143
+ }
144
+
145
+ // mark workspace as deleted
146
+ workspace.markToDelete()
147
+
148
+ // send delete request to the server
149
+ const response = await this.context
150
+ .resolve(RpcService)
151
+ ?.requestBuilder("api/v1/Workspaces")
152
+ .searchParam("id", id)
153
+ .sendDelete()
154
+
155
+ // check response status
156
+ if (ResponseUtils.isFail(response)) {
157
+ await ResponseUtils.throwError(
158
+ `Failed to delete workspace: ${workspace.organization.name}/${workspace.name}:${id}`,
159
+ response
160
+ )
161
+ }
162
+
163
+ // remove workspace from the collection
164
+ const index = this._workspaces.indexOf(<WorkspaceImpl>workspace)
165
+ if (index < 0) {
166
+ throw new Error(`Workspace ${id} is not found`)
167
+ }
168
+ this._workspaces.splice(index, 1)
169
+
170
+ // dispatch event
171
+ this.dispatch({
172
+ type: WorkspacesEvent.REMOVED,
173
+ data: workspace
174
+ })
175
+ }
176
+
177
+ async initFrom(organizationId: OrganizationId): Promise<void> {
178
+ // init workspaces from the server's response
179
+ const response = await this.context
180
+ .resolve(RpcService)
181
+ ?.requestBuilder("api/v1/Organizations")
182
+ .searchParam("id", organizationId)
183
+ .sendGet()
184
+
185
+ // check response status
186
+ if (ResponseUtils.isFail(response)) {
187
+ await ResponseUtils.throwError("Failed to fetch workspaces.", response)
188
+ }
189
+
190
+ // parse workspaces from the server's response
191
+ const workspaces = ((await response!.json()) as OrganizationWorkspaces)
192
+ .workspaces
193
+
194
+ // init workspaces from the server's response
195
+ for (const workspace of workspaces) {
196
+ // create workspace implementation
197
+ const workspaceImpl = new WorkspaceImpl(this.organization, this.context)
198
+
199
+ // init workspace from the server's response
200
+ await workspaceImpl.initFrom(workspace)
201
+
202
+ // add workspace to the collection
203
+ this._workspaces.push(workspaceImpl)
204
+
205
+ // dispatch event
206
+ this.dispatch({
207
+ type: WorkspacesEvent.ADDED,
208
+ data: workspaceImpl
209
+ })
210
+ }
211
+ }
212
+ }
@@ -0,0 +1,53 @@
1
+ import { EventDispatcher } from "../events"
2
+ import { Workspace } from "./workspace"
3
+
4
+ export type WorkspaceId = string
5
+
6
+ /**
7
+ * Workspaces event.
8
+ */
9
+ export enum WorkspacesEvent {
10
+ ADDED = "added",
11
+ REMOVED = "removed"
12
+ }
13
+
14
+ /**
15
+ * Organization's workspaces.
16
+ */
17
+ export abstract class Workspaces extends EventDispatcher<
18
+ WorkspacesEvent,
19
+ Workspace
20
+ > {
21
+ /**
22
+ * Workspaces.
23
+ */
24
+ abstract get collection(): ReadonlyArray<Workspace>
25
+
26
+ /**
27
+ * Get workspace by id.
28
+ * @param id
29
+ */
30
+ abstract get(id: WorkspaceId): Workspace
31
+
32
+ /**
33
+ * Try to get workspace by id.
34
+ * @param id
35
+ */
36
+ abstract tryGet(id: WorkspaceId): Workspace | undefined
37
+
38
+ /**
39
+ * Check if workspace exists.
40
+ * @param id
41
+ */
42
+ abstract contains(id: WorkspaceId): boolean
43
+
44
+ /**
45
+ * Create workspace.
46
+ */
47
+ abstract create(name: string, description: string): Promise<Workspace>
48
+
49
+ /**
50
+ * Delete workspace.
51
+ */
52
+ abstract delete(id: WorkspaceId): Promise<void>
53
+ }
@@ -1,24 +1,24 @@
1
- import { Command, CommandHandler } from '../src/services/commandService'
2
- import { appSdk } from '../src'
3
- import { UnitTest, AppSdkUnitTest } from '../src/unitTest'
1
+ import { Command, CommandHandler } from "../src/services/commandService"
2
+ import { appSdk } from "../src"
3
+ import { UnitTest, AppSdkUnitTest } from "../src/unitTest"
4
4
 
5
5
  class Cmd extends Command {
6
- constructor(public readonly name: string = 'test') {
6
+ constructor(public readonly name: string = "test") {
7
7
  super()
8
8
  }
9
9
  }
10
10
 
11
11
  class CmdHandler extends CommandHandler<Cmd> {
12
12
  async execute(message: Cmd): Promise<void> {
13
- expect(message.name).toBe('test-command')
13
+ expect(message.name).toBe("test-command")
14
14
  }
15
15
  }
16
16
 
17
- test('Commands test', async () => {
17
+ test("Commands test", async () => {
18
18
  await AppSdkUnitTest.test(UnitTest.DEFAULT, async () => {
19
- const app = await appSdk('test-commands', async builder => {
19
+ const app = await appSdk("test-commands", async builder => {
20
20
  builder.registerCommand(Cmd, context => new CmdHandler(context))
21
21
  })
22
- expect(app.context.execute(new Cmd('test-command'))).toBeDefined()
22
+ expect(app.context.execute(new Cmd("test-command"))).toBeDefined()
23
23
  })
24
24
  })
Binary file
@@ -1,6 +1,6 @@
1
- import { DisposableContainer } from '../src'
1
+ import { DisposableContainer } from "../src"
2
2
 
3
- test('DisposableContainer', () => {
3
+ test("DisposableContainer", () => {
4
4
  const disposable = new DisposableContainer()
5
5
  expect(disposable.isDisposed).toBe(false)
6
6
  expect(disposable.lifetime.isDisposed).toBe(false)
@@ -9,7 +9,7 @@ test('DisposableContainer', () => {
9
9
  expect(disposable.lifetime.isDisposed).toBe(true)
10
10
  })
11
11
 
12
- test('DisposableContainer, dispose order', () => {
12
+ test("DisposableContainer, dispose order", () => {
13
13
  const indexes: number[] = []
14
14
  const disposable = new DisposableContainer()
15
15
  disposable.addCallback(() => {
@@ -1,6 +1,6 @@
1
- import { EventDispatcher } from '../src'
1
+ import { EventDispatcher } from "../src"
2
2
 
3
- test('Events, test general', () => {
3
+ test("Events, test general", () => {
4
4
  enum ET {
5
5
  A,
6
6
  B
@@ -78,7 +78,7 @@ test('Events, test general', () => {
78
78
  expect(b2).toBe(3)
79
79
  })
80
80
 
81
- test('Events, test this', () => {
81
+ test("Events, test this", () => {
82
82
  enum ET {
83
83
  A,
84
84
  B
@@ -128,7 +128,7 @@ test('Events, test this', () => {
128
128
  expect(b.value).toBe(2)
129
129
  })
130
130
 
131
- test('Events, test unsubscribe', () => {
131
+ test("Events, test unsubscribe", () => {
132
132
  const dispatch = new EventDispatcher<unknown, number>()
133
133
 
134
134
  let index = 0
@@ -0,0 +1,52 @@
1
+ import fs from "fs"
2
+ import { testInWorkspace } from "./setup"
3
+
4
+ test("Files", async () => {
5
+ await testInWorkspace(async (app, org, ws) => {
6
+
7
+ expect(app).not.toBeUndefined()
8
+ expect(org).not.toBeUndefined()
9
+
10
+ const buffer = fs.readFileSync("test/data/test_file.pdf")
11
+ const file_obj = new File([new Uint8Array(buffer)], "test_file.pdf", {
12
+ type: "application/pdf"
13
+ })
14
+
15
+ const filePromise = ws.files.upload(file_obj)
16
+ await expect(filePromise).resolves.not.toThrow()
17
+ const file = await filePromise
18
+
19
+ expect(file).not.toBeUndefined()
20
+ expect(file).not.toBeNull()
21
+ expect(file.name).toBe("test_file.pdf")
22
+
23
+ let status = await file.status()
24
+
25
+ expect(status).not.toBeUndefined()
26
+ expect(status).not.toBeNull()
27
+ if (!status.success && status.error) {
28
+ console.error(status.error)
29
+ }
30
+ expect(status.success).toBe(true)
31
+ expect(status.file_id).toBe(file.id)
32
+ expect(status.file_parts_count).toBeGreaterThanOrEqual(status.completed_parts_count)
33
+
34
+ while (
35
+ status.success &&
36
+ status.completed_parts_count !== status.file_parts_count
37
+ ) {
38
+ await new Promise(r => setTimeout(r, 1000))
39
+ status = await file.status()
40
+ }
41
+
42
+ const queryPromise = ws.files.query("", 0, 20)
43
+ await expect(queryPromise).resolves.not.toThrow()
44
+ const filePage = await queryPromise
45
+ expect(filePage).not.toBeUndefined()
46
+ expect(filePage).not.toBeNull()
47
+ expect(filePage.files.length).toBe(1)
48
+ expect(filePage.pages).toBe(1)
49
+
50
+ await expect(ws.files.delete(file.id)).resolves.not.toThrow()
51
+ })
52
+ })