@neuralinnovations/dataisland-sdk 0.0.1-dev3 → 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.
- package/README.md +91 -2
- package/package.json +1 -1
- package/src/disposable.ts +2 -3
- package/src/dto/workspacesResponse.ts +2 -2
- package/src/events.ts +12 -12
- package/src/index.ts +14 -0
- package/src/internal/app.impl.ts +6 -9
- package/src/services/middlewareService.ts +1 -1
- package/src/services/requestBuilder.ts +35 -10
- package/src/services/rpcService.ts +23 -6
- package/src/services/service.ts +7 -5
- package/src/storages/chat.ts +0 -16
- package/src/storages/chats.ts +17 -0
- package/src/storages/file.impl.ts +5 -4
- package/src/storages/file.ts +28 -0
- package/src/storages/files.impl.ts +40 -19
- package/src/storages/files.ts +12 -41
- package/src/storages/filesPage.ts +27 -0
- package/src/storages/groups.impl.ts +10 -10
- package/src/storages/organizations.impl.ts +1 -1
- package/src/storages/workspace.impl.ts +1 -1
- package/src/storages/workspace.ts +6 -0
- package/src/storages/workspaces.impl.ts +1 -1
- package/test/files.test.ts +52 -0
- package/test/index.test.ts +10 -113
- package/test/organization.test.ts +57 -0
- package/test/setup.ts +52 -0
- package/test/workspace.test.ts +71 -0
- /package/{test_file.pdf → test/data/test_file.pdf} +0 -0
package/README.md
CHANGED
@@ -1,7 +1,96 @@
|
|
1
1
|
# DataIsland Client SDK
|
2
2
|
|
3
|
-
|
3
|
+
The DataIsland Client SDK is a TypeScript library designed to seamlessly integrate DataIsland web services into websites.
|
4
4
|
|
5
|
+
## Table of contents
|
5
6
|
|
7
|
+
1. [Connect](#connect)
|
8
|
+
2. [Create app](#create-app)
|
9
|
+
3. [Use organizations](#use-organizations)
|
10
|
+
4. [Use chat](#use-chat)
|
11
|
+
5. [Use workspaces](#use-workspaces)
|
12
|
+
6. [Use files](#use-files)
|
13
|
+
7. [Use access groups](#use-access-groups)
|
14
|
+
8. [Use invites](#use-invites)
|
6
15
|
|
7
|
-
|
16
|
+
### Connect
|
17
|
+
|
18
|
+
For connecting this library to your website project simply install it using npm package manager.
|
19
|
+
|
20
|
+
`npm i @neuralinnovations/dataisland-sdk`
|
21
|
+
|
22
|
+
### Create app
|
23
|
+
|
24
|
+
You can initialize default app sdk instance using this code example.
|
25
|
+
|
26
|
+
```
|
27
|
+
const app = await appSdk("your-app-name", async (builder: AppBuilder) => {
|
28
|
+
builder.useHost(HOST)
|
29
|
+
builder.useCredential(new BearerCredential(TOKEN))
|
30
|
+
})
|
31
|
+
```
|
32
|
+
|
33
|
+
It is immpossible to create more than one app sdk intance with same name.
|
34
|
+
|
35
|
+
**HOST** is a DataIsland API url which can be passed using environment file.
|
36
|
+
|
37
|
+
Second required parameter for builder is Credentials. It is recomended to use Bearer credentials instance and pass your user Auth0 **TOKEN** in order to get access to API.
|
38
|
+
|
39
|
+
You can also add requests middlewares with builder options.
|
40
|
+
|
41
|
+
```
|
42
|
+
const app = await appSdk("your-app-name", async (builder: AppBuilder) => {
|
43
|
+
builder.useHost(YOUR_HOST)
|
44
|
+
builder.useAutomaticDataCollectionEnabled(false)
|
45
|
+
builder.useCredential(new BasicCredential("email", "password"))
|
46
|
+
builder.registerMiddleware(async (req, next) => {
|
47
|
+
req.headers.set("Your-header-name", "value")
|
48
|
+
return await next(req)
|
49
|
+
})
|
50
|
+
})
|
51
|
+
```
|
52
|
+
|
53
|
+
### Use organizations
|
54
|
+
|
55
|
+
Organization is a top data structure object, which represents a company or some group of people using our services.
|
56
|
+
It contains of users ( admin or regular ), workspaces with files ( folders with access control features ) and chats.
|
57
|
+
|
58
|
+
By default all user organizations are fetched with user profile during app sdk start. But if there are no organizations linked to the user, you should run Create organization flow. This flow requires organization name and description data, and also first workspace name and description data provided by user.
|
59
|
+
|
60
|
+
**NOTE** There are two types of DataIsland web api servers, public and private. On public servers users can create their own organization after first registration, on private servers you must register using invite links.
|
61
|
+
|
62
|
+
Default organization creation code example:
|
63
|
+
|
64
|
+
```
|
65
|
+
// create organization
|
66
|
+
const org = await app.organizations.create(
|
67
|
+
"your-organization-name",
|
68
|
+
"your-organization-description"
|
69
|
+
)
|
70
|
+
```
|
71
|
+
|
72
|
+
### Use workspaces
|
73
|
+
|
74
|
+
Workspaces are folder-like objects used to store files and controll access to it using acces groups. During creation you must pass organization Id, name and description of new workspace and regulation options. You can pass existing group ID or ask to create new group for this workspace in regulation section.
|
75
|
+
|
76
|
+
Default workspace creation example:
|
77
|
+
|
78
|
+
```
|
79
|
+
const wsPromise = org.workspaces.create(
|
80
|
+
"your-workspace-name",
|
81
|
+
"your-workspace-description",
|
82
|
+
regulation: {
|
83
|
+
isCreateNewGroup: boolean - "Bool option for new group creation"
|
84
|
+
newGroupName: string - "New group name"
|
85
|
+
groupIds: string[] - "Array of selected accessed groups IDs"
|
86
|
+
}
|
87
|
+
)
|
88
|
+
```
|
89
|
+
|
90
|
+
### Use files
|
91
|
+
|
92
|
+
### Use chat
|
93
|
+
|
94
|
+
### Use access groups
|
95
|
+
|
96
|
+
### Use Invites
|
package/package.json
CHANGED
package/src/disposable.ts
CHANGED
@@ -9,7 +9,8 @@ export interface Disposable {
|
|
9
9
|
* Represents a lifetime.
|
10
10
|
*/
|
11
11
|
export class Lifetime {
|
12
|
-
constructor(private readonly container: DisposableContainer) {
|
12
|
+
constructor(private readonly container: DisposableContainer) {
|
13
|
+
}
|
13
14
|
|
14
15
|
/**
|
15
16
|
* Define a new nested disposable to this lifetime.
|
@@ -147,5 +148,3 @@ export function disposable(action: () => void, target: unknown): Disposable {
|
|
147
148
|
action.call(target)
|
148
149
|
})
|
149
150
|
}
|
150
|
-
|
151
|
-
export const eternalLifetime = new DisposableContainer().lifetime
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { WorkspaceId } from "../storages/workspaces"
|
2
|
-
import { FileId } from "../storages/
|
2
|
+
import { FileId } from "../storages/file"
|
3
3
|
|
4
4
|
export interface WorkspaceProfileDto {
|
5
5
|
name: string
|
@@ -26,7 +26,7 @@ export interface FileProgressDto {
|
|
26
26
|
file_parts_count: number
|
27
27
|
completed_parts_count: number
|
28
28
|
success: boolean
|
29
|
-
error
|
29
|
+
error?: string
|
30
30
|
}
|
31
31
|
|
32
32
|
export interface FileDto {
|
package/src/events.ts
CHANGED
@@ -1,25 +1,25 @@
|
|
1
1
|
import { type Disposable, DisposableContainer } from "./disposable"
|
2
2
|
|
3
|
-
export interface Input<
|
4
|
-
type?:
|
5
|
-
data:
|
3
|
+
export interface Input<EventType, DataType> {
|
4
|
+
type?: EventType
|
5
|
+
data: DataType
|
6
6
|
}
|
7
7
|
|
8
|
-
export interface Event<
|
8
|
+
export interface Event<EventType, DataType> extends Input<EventType, DataType> {
|
9
9
|
unsubscribe: () => void
|
10
10
|
}
|
11
11
|
|
12
|
-
export interface EventSubscriber<
|
13
|
-
subscribe: (callback: (event: Event<
|
12
|
+
export interface EventSubscriber<EventType, DataType> {
|
13
|
+
subscribe: (callback: (event: Event<EventType, DataType>) => void, type?: EventType) => Disposable
|
14
14
|
}
|
15
15
|
|
16
|
-
export class EventDispatcher<
|
16
|
+
export class EventDispatcher<EventType, DataType> implements EventSubscriber<EventType, DataType> {
|
17
17
|
private _listeners: Array<{
|
18
|
-
callback: (value: Event<
|
18
|
+
callback: (value: Event<EventType, DataType>) => void
|
19
19
|
disposable: Disposable
|
20
20
|
}> = []
|
21
21
|
|
22
|
-
dispatch(input: Input<
|
22
|
+
dispatch(input: Input<EventType, DataType>): void {
|
23
23
|
this._listeners.slice().forEach(it => {
|
24
24
|
const value = {
|
25
25
|
type: input.type,
|
@@ -27,16 +27,16 @@ export class EventDispatcher<ET, DT> implements EventSubscriber<ET, DT> {
|
|
27
27
|
unsubscribe: () => {
|
28
28
|
it.disposable.dispose()
|
29
29
|
}
|
30
|
-
} satisfies Event<
|
30
|
+
} satisfies Event<EventType, DataType>
|
31
31
|
it.callback(value)
|
32
32
|
})
|
33
33
|
}
|
34
34
|
|
35
|
-
subscribe(callback: (event: Event<
|
35
|
+
subscribe(callback: (event: Event<EventType, DataType>) => void, type?: EventType): Disposable {
|
36
36
|
const container = new DisposableContainer()
|
37
37
|
if (type !== undefined) {
|
38
38
|
const cb = callback
|
39
|
-
const listener = (evt: Event<
|
39
|
+
const listener = (evt: Event<EventType, DataType>): void => {
|
40
40
|
if (evt.type === type) {
|
41
41
|
cb(evt)
|
42
42
|
}
|
package/src/index.ts
CHANGED
@@ -7,6 +7,17 @@ export * from "./events"
|
|
7
7
|
export * from "./disposable"
|
8
8
|
export * from "./credentials"
|
9
9
|
export * from "./appSdk"
|
10
|
+
export * from "./storages/organizations"
|
11
|
+
export * from "./storages/organization"
|
12
|
+
export * from "./storages/workspaces"
|
13
|
+
export * from "./storages/workspace"
|
14
|
+
export * from "./storages/groups"
|
15
|
+
export * from "./storages/userProfile"
|
16
|
+
export * from "./storages/files"
|
17
|
+
export * from "./storages/file"
|
18
|
+
export * from "./storages/filesPage"
|
19
|
+
export * from "./storages/chats"
|
20
|
+
export * from "./storages/chat"
|
10
21
|
|
11
22
|
const _appsNotReady = new Map<string, Promise<AppSdk>>()
|
12
23
|
const _appsReady = new Map<string, AppSdk>()
|
@@ -73,3 +84,6 @@ export async function appSdk(
|
|
73
84
|
}
|
74
85
|
return await appPromise
|
75
86
|
}
|
87
|
+
|
88
|
+
export { File } from "./storages/file"
|
89
|
+
export { FilesPage } from "./storages/filesPage"
|
package/src/internal/app.impl.ts
CHANGED
@@ -104,13 +104,6 @@ export class AppImplementation extends AppSdk {
|
|
104
104
|
return new OrganizationService(context)
|
105
105
|
})
|
106
106
|
|
107
|
-
// register middlewares
|
108
|
-
builder.registerMiddleware(async (req, next) => {
|
109
|
-
req.headers.set("accept", "text/plain")
|
110
|
-
req.headers.set("content-type", "application/json")
|
111
|
-
return await next(req)
|
112
|
-
})
|
113
|
-
|
114
107
|
// call customer setup
|
115
108
|
if (setup !== undefined) {
|
116
109
|
await setup(builder)
|
@@ -154,7 +147,9 @@ export class AppImplementation extends AppSdk {
|
|
154
147
|
const waitList: Array<Promise<void>> = []
|
155
148
|
// call onRegister service's callback
|
156
149
|
services.forEach(([serviceContext]) => {
|
157
|
-
|
150
|
+
if (typeof serviceContext.onRegister === "function") {
|
151
|
+
waitList.push(serviceContext.onRegister())
|
152
|
+
}
|
158
153
|
})
|
159
154
|
|
160
155
|
// wait for all services to register
|
@@ -167,7 +162,9 @@ export class AppImplementation extends AppSdk {
|
|
167
162
|
waitList.length = 0
|
168
163
|
// call onStart service's callback
|
169
164
|
services.forEach(([serviceContext]) => {
|
170
|
-
|
165
|
+
if (typeof serviceContext.onStart === "function") {
|
166
|
+
waitList.push(serviceContext.onStart())
|
167
|
+
}
|
171
168
|
})
|
172
169
|
|
173
170
|
// wait for all services to start
|
@@ -3,7 +3,7 @@ import { type Middleware } from "../middleware"
|
|
3
3
|
import { type Disposable } from "../disposable"
|
4
4
|
|
5
5
|
export class MiddlewareService extends Service {
|
6
|
-
_middlewares: Middleware[] = []
|
6
|
+
private _middlewares: Middleware[] = []
|
7
7
|
|
8
8
|
public useMiddleware(middleware: Middleware): Disposable {
|
9
9
|
this._middlewares.push(middleware)
|
@@ -48,18 +48,42 @@ export class RequestBuilder {
|
|
48
48
|
return this
|
49
49
|
}
|
50
50
|
|
51
|
-
public async
|
51
|
+
public async sendPostFormData(body: FormData): Promise<Response> {
|
52
52
|
const url = this._url
|
53
|
+
|
54
|
+
// set search params
|
53
55
|
url.search = this._searchParams.toString()
|
56
|
+
|
57
|
+
// create request
|
58
|
+
const req = new Request(url, {
|
59
|
+
method: "POST",
|
60
|
+
headers: this._headers,
|
61
|
+
body
|
62
|
+
})
|
63
|
+
|
64
|
+
// discard content type
|
65
|
+
const reqAny = req as any
|
66
|
+
reqAny.discardContentType = true
|
67
|
+
|
68
|
+
return await this._request(
|
69
|
+
req
|
70
|
+
)
|
71
|
+
}
|
72
|
+
|
73
|
+
public async sendPostJson(body: object | null | undefined): Promise<Response> {
|
74
|
+
const url = this._url
|
75
|
+
url.search = this._searchParams.toString()
|
76
|
+
let json: string | null | undefined = null
|
54
77
|
if (body !== undefined && body !== null && typeof body === "object") {
|
55
|
-
|
78
|
+
json = JSON.stringify(body)
|
56
79
|
}
|
80
|
+
const request = new Request(url, {
|
81
|
+
method: "POST",
|
82
|
+
headers: this._headers,
|
83
|
+
body: json
|
84
|
+
})
|
57
85
|
return await this._request(
|
58
|
-
|
59
|
-
method: "POST",
|
60
|
-
headers: this._headers,
|
61
|
-
body
|
62
|
-
})
|
86
|
+
request
|
63
87
|
)
|
64
88
|
}
|
65
89
|
|
@@ -85,17 +109,18 @@ export class RequestBuilder {
|
|
85
109
|
)
|
86
110
|
}
|
87
111
|
|
88
|
-
public async
|
112
|
+
public async sendPutJson(body: object | null | undefined): Promise<Response> {
|
89
113
|
const url = this._url
|
90
114
|
url.search = this._searchParams.toString()
|
115
|
+
let json: string | null | undefined = null
|
91
116
|
if (body !== undefined && body !== null && typeof body === "object") {
|
92
|
-
|
117
|
+
json = JSON.stringify(body)
|
93
118
|
}
|
94
119
|
return await this._request(
|
95
120
|
new Request(url, {
|
96
121
|
method: "PUT",
|
97
122
|
headers: this._headers,
|
98
|
-
body
|
123
|
+
body: json
|
99
124
|
})
|
100
125
|
)
|
101
126
|
}
|
@@ -14,6 +14,7 @@ export interface RequestOptions {
|
|
14
14
|
* RPC service.
|
15
15
|
*/
|
16
16
|
export class RpcService extends Service {
|
17
|
+
|
17
18
|
constructor(
|
18
19
|
serviceContext: ServiceContext,
|
19
20
|
/**
|
@@ -32,6 +33,22 @@ export class RpcService extends Service {
|
|
32
33
|
}
|
33
34
|
) {
|
34
35
|
super(serviceContext)
|
36
|
+
|
37
|
+
serviceContext.onRegister = async () => {
|
38
|
+
serviceContext.resolve(MiddlewareService)?.useMiddleware((req, next) => {
|
39
|
+
if (!req.headers.has("accept")) {
|
40
|
+
req.headers.set("accept", "text/plain")
|
41
|
+
}
|
42
|
+
|
43
|
+
if ((req as any).discardContentType) {
|
44
|
+
delete (req as any).discardContentType
|
45
|
+
} else {
|
46
|
+
req.headers.set("content-type", "application/json")
|
47
|
+
}
|
48
|
+
|
49
|
+
return next(req)
|
50
|
+
})
|
51
|
+
}
|
35
52
|
}
|
36
53
|
|
37
54
|
/**
|
@@ -87,35 +104,35 @@ export class RpcService extends Service {
|
|
87
104
|
/**
|
88
105
|
* Send a POST request.
|
89
106
|
* @param path
|
90
|
-
* @param body
|
107
|
+
* @param body JSON object
|
91
108
|
* @param options
|
92
109
|
*/
|
93
110
|
async post(
|
94
111
|
path: string,
|
95
|
-
body
|
112
|
+
body: object | null | undefined,
|
96
113
|
options?: RequestOptions
|
97
114
|
): Promise<Response> {
|
98
115
|
return this.requestBuilder(path)
|
99
116
|
.searchParams(options?.searchParams)
|
100
117
|
.headers(options?.headers)
|
101
|
-
.
|
118
|
+
.sendPostJson(body)
|
102
119
|
}
|
103
120
|
|
104
121
|
/**
|
105
122
|
* Send a PUT request.
|
106
123
|
* @param path
|
107
|
-
* @param body
|
124
|
+
* @param body JSON object
|
108
125
|
* @param options
|
109
126
|
*/
|
110
127
|
async put(
|
111
128
|
path: string,
|
112
|
-
body
|
129
|
+
body: object | null | undefined,
|
113
130
|
options?: RequestOptions
|
114
131
|
): Promise<Response> {
|
115
132
|
return this.requestBuilder(path)
|
116
133
|
.searchParams(options?.searchParams)
|
117
134
|
.headers(options?.headers)
|
118
|
-
.
|
135
|
+
.sendPutJson(body)
|
119
136
|
}
|
120
137
|
|
121
138
|
/**
|
package/src/services/service.ts
CHANGED
@@ -6,7 +6,8 @@ export class ServiceContext {
|
|
6
6
|
constructor(
|
7
7
|
public readonly context: Context,
|
8
8
|
private readonly disposableContainer: DisposableContainer
|
9
|
-
) {
|
9
|
+
) {
|
10
|
+
}
|
10
11
|
|
11
12
|
public get lifetime(): Lifetime {
|
12
13
|
return this.disposableContainer.lifetime
|
@@ -16,15 +17,15 @@ export class ServiceContext {
|
|
16
17
|
return this.context.resolve(type)
|
17
18
|
}
|
18
19
|
|
19
|
-
public async
|
20
|
+
public onRegister: () => Promise<void> = async (): Promise<void> => {
|
20
21
|
await Promise.resolve()
|
21
22
|
}
|
22
23
|
|
23
|
-
public async
|
24
|
+
public onStart: () => Promise<void> = async (): Promise<void> => {
|
24
25
|
await Promise.resolve()
|
25
26
|
}
|
26
27
|
|
27
|
-
public onUnregister(): void {
|
28
|
+
public onUnregister: () => void = (): void => {
|
28
29
|
// do nothing
|
29
30
|
}
|
30
31
|
}
|
@@ -42,5 +43,6 @@ export abstract class Service {
|
|
42
43
|
return this.serviceContext.context
|
43
44
|
}
|
44
45
|
|
45
|
-
public constructor(private readonly serviceContext: ServiceContext) {
|
46
|
+
public constructor(private readonly serviceContext: ServiceContext) {
|
47
|
+
}
|
46
48
|
}
|
package/src/storages/chat.ts
CHANGED
@@ -1,12 +1,5 @@
|
|
1
|
-
import { EventDispatcher } from "../events"
|
2
|
-
|
3
1
|
export type ChatId = string
|
4
2
|
|
5
|
-
export enum ChatsEvent {
|
6
|
-
ADDED = "added",
|
7
|
-
REMOVED = "removed"
|
8
|
-
}
|
9
|
-
|
10
3
|
export enum ChatAnswer {
|
11
4
|
SHORT = "short",
|
12
5
|
LONG = "long"
|
@@ -26,12 +19,3 @@ export abstract class Chat {
|
|
26
19
|
abstract question(message: string, answer?: ChatAnswer): Promise<void>
|
27
20
|
}
|
28
21
|
|
29
|
-
/**
|
30
|
-
* Chats storage.
|
31
|
-
*/
|
32
|
-
export abstract class Chats extends EventDispatcher<ChatsEvent, Chat> {
|
33
|
-
/**
|
34
|
-
* Create new chat.
|
35
|
-
*/
|
36
|
-
abstract create(): Promise<Chat>
|
37
|
-
}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
import { EventDispatcher } from "../events"
|
2
|
+
import { Chat } from "./chat"
|
3
|
+
|
4
|
+
export enum ChatsEvent {
|
5
|
+
ADDED = "added",
|
6
|
+
REMOVED = "removed"
|
7
|
+
}
|
8
|
+
|
9
|
+
/**
|
10
|
+
* Chats storage.
|
11
|
+
*/
|
12
|
+
export abstract class Chats extends EventDispatcher<ChatsEvent, Chat> {
|
13
|
+
/**
|
14
|
+
* Create new chat.
|
15
|
+
*/
|
16
|
+
abstract create(): Promise<Chat>
|
17
|
+
}
|
@@ -2,8 +2,8 @@ import { Context } from "../context"
|
|
2
2
|
import { Disposable } from "../disposable"
|
3
3
|
import { FileDto, FileProgressDto } from "../dto/workspacesResponse"
|
4
4
|
import { RpcService } from "../services/rpcService"
|
5
|
-
import { File } from "./files"
|
6
5
|
import { ResponseUtils } from "../services/responseUtils"
|
6
|
+
import { File } from "./file"
|
7
7
|
|
8
8
|
export class FileImpl extends File implements Disposable {
|
9
9
|
private _isDisposed: boolean = false
|
@@ -32,7 +32,7 @@ export class FileImpl extends File implements Disposable {
|
|
32
32
|
}
|
33
33
|
|
34
34
|
get name(): string {
|
35
|
-
return <string>this._content?.
|
35
|
+
return <string>this._content?.name
|
36
36
|
}
|
37
37
|
|
38
38
|
async url(): Promise<string> {
|
@@ -55,7 +55,7 @@ export class FileImpl extends File implements Disposable {
|
|
55
55
|
async status(): Promise<FileProgressDto> {
|
56
56
|
const response = await this.context
|
57
57
|
.resolve(RpcService)
|
58
|
-
?.requestBuilder("api/v1/Files/
|
58
|
+
?.requestBuilder("api/v1/Files/fetch")
|
59
59
|
.searchParam("id", this.id)
|
60
60
|
.sendGet()
|
61
61
|
|
@@ -63,6 +63,7 @@ export class FileImpl extends File implements Disposable {
|
|
63
63
|
await ResponseUtils.throwError(`Failed to get file ${this.id}`, response)
|
64
64
|
}
|
65
65
|
|
66
|
-
|
66
|
+
const content = await response!.json()
|
67
|
+
return content.progress as FileProgressDto
|
67
68
|
}
|
68
69
|
}
|
@@ -0,0 +1,28 @@
|
|
1
|
+
import { FileProgressDto } from "../dto/workspacesResponse"
|
2
|
+
|
3
|
+
export type FileId = string
|
4
|
+
|
5
|
+
/**
|
6
|
+
* File.
|
7
|
+
*/
|
8
|
+
export abstract class File {
|
9
|
+
/**
|
10
|
+
* File id.
|
11
|
+
*/
|
12
|
+
abstract get id(): FileId
|
13
|
+
|
14
|
+
/**
|
15
|
+
* File name.
|
16
|
+
*/
|
17
|
+
abstract get name(): string
|
18
|
+
|
19
|
+
/**
|
20
|
+
* Get temporary url.
|
21
|
+
*/
|
22
|
+
abstract url(): Promise<string>
|
23
|
+
|
24
|
+
/**
|
25
|
+
* Get file status.
|
26
|
+
*/
|
27
|
+
abstract status(): Promise<FileProgressDto>
|
28
|
+
}
|
@@ -1,12 +1,13 @@
|
|
1
1
|
import { Context } from "../context"
|
2
2
|
import { Disposable } from "../disposable"
|
3
3
|
import { FileDto, FileListResponse } from "../dto/workspacesResponse"
|
4
|
-
import { OrganizationService } from "../services/organizationService"
|
5
4
|
import { RpcService } from "../services/rpcService"
|
6
5
|
import { FileImpl } from "./file.impl"
|
7
|
-
import {
|
6
|
+
import { Files, FilesEvent, UploadFile } from "./files"
|
8
7
|
import { WorkspaceImpl } from "./workspace.impl"
|
9
8
|
import { ResponseUtils } from "../services/responseUtils"
|
9
|
+
import { File } from "./file"
|
10
|
+
import { FilesPage } from "./filesPage"
|
10
11
|
|
11
12
|
export class FilesPageImpl extends FilesPage implements Disposable {
|
12
13
|
private _isDisposed: boolean = false
|
@@ -100,9 +101,16 @@ export class FilesImpl extends Files {
|
|
100
101
|
page: number,
|
101
102
|
limit: number
|
102
103
|
): Promise<FilesPage> {
|
104
|
+
|
105
|
+
// check page
|
103
106
|
if (page === undefined || page === null) {
|
104
107
|
throw new Error("File fetch, page is undefined or null")
|
105
108
|
}
|
109
|
+
if (page < 0) {
|
110
|
+
throw new Error("File fetch, page is negative")
|
111
|
+
}
|
112
|
+
|
113
|
+
// check limit
|
106
114
|
if (limit === undefined || limit === null) {
|
107
115
|
throw new Error("File fetch, limit is undefined or null")
|
108
116
|
}
|
@@ -110,23 +118,19 @@ export class FilesImpl extends Files {
|
|
110
118
|
throw new Error("File fetch, limit is 0")
|
111
119
|
}
|
112
120
|
|
113
|
-
|
114
|
-
|
115
|
-
if (orgService === undefined) {
|
116
|
-
throw new Error("File fetch, organization service undefined")
|
117
|
-
}
|
118
|
-
|
121
|
+
// send request to the server
|
119
122
|
const response = await this.context
|
120
123
|
.resolve(RpcService)
|
121
124
|
?.requestBuilder("api/v1/Files/list")
|
122
125
|
|
123
126
|
.searchParam("workspaceId", this.workspace.id)
|
124
|
-
.searchParam("organizationId",
|
127
|
+
.searchParam("organizationId", this.workspace.organization.id)
|
125
128
|
.searchParam("query", query)
|
126
129
|
.searchParam("page", page.toString())
|
127
130
|
.searchParam("limit", limit.toString())
|
128
131
|
.sendGet()
|
129
132
|
|
133
|
+
// check response status
|
130
134
|
if (ResponseUtils.isFail(response)) {
|
131
135
|
await ResponseUtils.throwError(
|
132
136
|
`Files fetch query:${query}, page:${page}, limit:${limit}, failed`,
|
@@ -134,57 +138,74 @@ export class FilesImpl extends Files {
|
|
134
138
|
)
|
135
139
|
}
|
136
140
|
|
141
|
+
// parse files from the server's response
|
137
142
|
const files = (await response!.json()) as FileListResponse
|
138
143
|
|
144
|
+
// create files list
|
139
145
|
const filesList = new FilesPageImpl()
|
140
146
|
filesList.total = files.totalFilesCount
|
141
147
|
filesList.filesPerPage = files.filesPerPage
|
142
148
|
filesList.page = page
|
149
|
+
|
150
|
+
// init files from the server's response
|
143
151
|
for (const fl of files.files) {
|
152
|
+
|
153
|
+
// create file implementation
|
144
154
|
const file = new FileImpl(this.context).initFrom(fl)
|
145
155
|
|
156
|
+
// add file to the collection
|
146
157
|
filesList.files.push(file)
|
147
158
|
|
159
|
+
// dispatch event, file added
|
148
160
|
this.dispatch({
|
149
161
|
type: FilesEvent.ADDED,
|
150
162
|
data: file
|
151
163
|
})
|
152
164
|
}
|
153
165
|
|
166
|
+
// set files list
|
154
167
|
this.filesList = filesList
|
155
168
|
|
156
169
|
return filesList
|
157
170
|
}
|
158
171
|
|
159
|
-
async internalUpload(file:
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
throw new Error("File load, organization service undefined")
|
172
|
+
async internalUpload(file: UploadFile): Promise<File> {
|
173
|
+
// check file
|
174
|
+
if (file === undefined || file === null) {
|
175
|
+
throw new Error("File upload, file is undefined or null")
|
164
176
|
}
|
165
177
|
|
178
|
+
// form data to send
|
166
179
|
const form = new FormData()
|
167
|
-
form.append("organizationId",
|
180
|
+
form.append("organizationId", this.workspace.organization.id)
|
168
181
|
form.append("workspaceId", this.workspace.id)
|
169
182
|
form.append("name", file.name)
|
170
183
|
form.append("file", file, file.name)
|
171
184
|
|
185
|
+
// send request to the server
|
172
186
|
const response = await this.context
|
173
187
|
.resolve(RpcService)
|
174
188
|
?.requestBuilder("api/v1/Files")
|
175
|
-
.
|
189
|
+
.sendPostFormData(form)
|
190
|
+
|
191
|
+
// check response status
|
176
192
|
if (ResponseUtils.isFail(response)) {
|
177
|
-
await ResponseUtils.throwError(`File upload ${file}`, response)
|
193
|
+
await ResponseUtils.throwError(`File upload ${file.name}`, response)
|
178
194
|
}
|
195
|
+
|
196
|
+
// parse file from the server's response
|
179
197
|
const result = (await response!.json()).file as FileDto
|
180
198
|
|
199
|
+
// create file implementation
|
181
200
|
const fileImpl = new FileImpl(this.context).initFrom(result)
|
182
201
|
|
183
|
-
this
|
202
|
+
// TODO: why is this here?
|
203
|
+
this.filesList?.files.push(fileImpl)
|
184
204
|
|
205
|
+
// dispatch event, file added
|
185
206
|
this.dispatch({
|
186
207
|
type: FilesEvent.ADDED,
|
187
|
-
data:
|
208
|
+
data: fileImpl
|
188
209
|
})
|
189
210
|
|
190
211
|
return fileImpl
|
package/src/storages/files.ts
CHANGED
@@ -1,39 +1,19 @@
|
|
1
|
-
import {
|
2
|
-
import {
|
3
|
-
|
4
|
-
export type FileId = string
|
1
|
+
import { EventDispatcher } from "../events"
|
2
|
+
import { File, FileId } from "./file"
|
3
|
+
import { FilesPage } from "./filesPage"
|
5
4
|
|
5
|
+
/**
|
6
|
+
* Files event.
|
7
|
+
*/
|
6
8
|
export enum FilesEvent {
|
7
|
-
ADDED =
|
8
|
-
REMOVED =
|
9
|
+
ADDED = "added",
|
10
|
+
REMOVED = "removed"
|
9
11
|
}
|
10
12
|
|
11
|
-
export type UploadFile = File | Blob | string
|
12
|
-
|
13
13
|
/**
|
14
|
-
*
|
14
|
+
* Upload file.
|
15
15
|
*/
|
16
|
-
export
|
17
|
-
/**
|
18
|
-
* File id.
|
19
|
-
*/
|
20
|
-
abstract get id(): FileId
|
21
|
-
|
22
|
-
/**
|
23
|
-
* File name.
|
24
|
-
*/
|
25
|
-
abstract get name(): string
|
26
|
-
|
27
|
-
/**
|
28
|
-
* Get temporary url.
|
29
|
-
*/
|
30
|
-
abstract url(): Promise<string>
|
31
|
-
|
32
|
-
/**
|
33
|
-
* Get file status.
|
34
|
-
*/
|
35
|
-
abstract status(): Promise<FileProgressDto>
|
36
|
-
}
|
16
|
+
export type UploadFile = globalThis.File
|
37
17
|
|
38
18
|
/**
|
39
19
|
* Files storage.
|
@@ -42,7 +22,7 @@ export abstract class Files extends EventDispatcher<FilesEvent, File> {
|
|
42
22
|
/**
|
43
23
|
* Get file by id.
|
44
24
|
*/
|
45
|
-
abstract upload(file:
|
25
|
+
abstract upload(file: UploadFile): Promise<File>
|
46
26
|
|
47
27
|
/**
|
48
28
|
* Delete file.
|
@@ -53,15 +33,6 @@ export abstract class Files extends EventDispatcher<FilesEvent, File> {
|
|
53
33
|
/**
|
54
34
|
* Query files.
|
55
35
|
*/
|
56
|
-
abstract query(query: string, page: number, limit: number): Promise<
|
36
|
+
abstract query(query: string, page: number, limit: number): Promise<FilesPage>
|
57
37
|
}
|
58
38
|
|
59
|
-
export abstract class FilesList {
|
60
|
-
abstract get files(): File[]
|
61
|
-
|
62
|
-
abstract get pages(): number
|
63
|
-
|
64
|
-
abstract get total(): number
|
65
|
-
|
66
|
-
abstract get page(): number
|
67
|
-
}
|
@@ -0,0 +1,27 @@
|
|
1
|
+
import { File } from "./file"
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Files page.
|
5
|
+
*/
|
6
|
+
export abstract class FilesPage {
|
7
|
+
|
8
|
+
/**
|
9
|
+
* Get files.
|
10
|
+
*/
|
11
|
+
abstract get files(): File[]
|
12
|
+
|
13
|
+
/**
|
14
|
+
* Get pages count.
|
15
|
+
*/
|
16
|
+
abstract get pages(): number
|
17
|
+
|
18
|
+
/**
|
19
|
+
* Get total count.
|
20
|
+
*/
|
21
|
+
abstract get total(): number
|
22
|
+
|
23
|
+
/**
|
24
|
+
* Get current page.
|
25
|
+
*/
|
26
|
+
abstract get page(): number
|
27
|
+
}
|
@@ -73,12 +73,12 @@ export class GroupImpl extends Group implements Disposable {
|
|
73
73
|
} catch (e) {
|
74
74
|
console.error(e)
|
75
75
|
}
|
76
|
-
|
76
|
+
|
77
77
|
throw new Error(
|
78
78
|
`Groups get workspaces, response is not ok, status: ${response?.status},${response?.statusText} ${text}`
|
79
79
|
)
|
80
80
|
}
|
81
|
-
|
81
|
+
|
82
82
|
const workspaces = (await response.json()) as WorkspacesResponse
|
83
83
|
|
84
84
|
return workspaces.workspaces
|
@@ -101,7 +101,7 @@ export class GroupImpl extends Group implements Disposable {
|
|
101
101
|
const response = await this.context
|
102
102
|
.resolve(RpcService)
|
103
103
|
?.requestBuilder("api/v1/AccessGroups/name")
|
104
|
-
.
|
104
|
+
.sendPutJson({
|
105
105
|
groupId: this.id,
|
106
106
|
name: name
|
107
107
|
})
|
@@ -117,7 +117,7 @@ export class GroupImpl extends Group implements Disposable {
|
|
117
117
|
const response = await this.context
|
118
118
|
.resolve(RpcService)
|
119
119
|
?.requestBuilder("api/v1/AccessGroups/permits")
|
120
|
-
.
|
120
|
+
.sendPutJson({
|
121
121
|
groupId: this.id,
|
122
122
|
permits: permits
|
123
123
|
})
|
@@ -138,7 +138,7 @@ export class GroupImpl extends Group implements Disposable {
|
|
138
138
|
const response = await this.context
|
139
139
|
.resolve(RpcService)
|
140
140
|
?.requestBuilder("api/v1/AccessGroups/workspaces")
|
141
|
-
.
|
141
|
+
.sendPutJson({
|
142
142
|
groupId: this.id,
|
143
143
|
actualWorkspaceIds: workspaces
|
144
144
|
})
|
@@ -159,7 +159,7 @@ export class GroupImpl extends Group implements Disposable {
|
|
159
159
|
const response = await this.context
|
160
160
|
.resolve(RpcService)
|
161
161
|
?.requestBuilder("api/v1/AccessGroups/members")
|
162
|
-
.
|
162
|
+
.sendPutJson({
|
163
163
|
groupId: this.id,
|
164
164
|
memberIds: members
|
165
165
|
})
|
@@ -227,12 +227,12 @@ export class GroupsImpl extends Groups {
|
|
227
227
|
} catch (e) {
|
228
228
|
console.error(e)
|
229
229
|
}
|
230
|
-
|
230
|
+
|
231
231
|
throw new Error(
|
232
232
|
`Groups init, response is not ok, status: ${response?.status},${response?.statusText} ${text}`
|
233
233
|
)
|
234
234
|
}
|
235
|
-
|
235
|
+
|
236
236
|
const groups = (await response.json()) as AccessGroupsResponse
|
237
237
|
|
238
238
|
for (const gr of groups.groups){
|
@@ -258,7 +258,7 @@ export class GroupsImpl extends Groups {
|
|
258
258
|
const response = await this.context
|
259
259
|
.resolve(RpcService)
|
260
260
|
?.requestBuilder("api/v1/AccessGroups")
|
261
|
-
.
|
261
|
+
.sendPostJson({
|
262
262
|
name: name,
|
263
263
|
organizationId: organizationId,
|
264
264
|
permits: permits,
|
@@ -333,5 +333,5 @@ export class GroupsImpl extends Groups {
|
|
333
333
|
group.dispose()
|
334
334
|
}
|
335
335
|
|
336
|
-
|
336
|
+
|
337
337
|
}
|
@@ -130,7 +130,7 @@ export class OrganizationsImpl extends Organizations {
|
|
130
130
|
const response = await this.context
|
131
131
|
.resolve(RpcService)
|
132
132
|
?.requestBuilder("api/v1/Organizations")
|
133
|
-
.
|
133
|
+
.sendPostJson({
|
134
134
|
profile: {
|
135
135
|
name: name,
|
136
136
|
description: description
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import { EventDispatcher } from "../events"
|
2
2
|
import { Files } from "./files"
|
3
3
|
import { WorkspaceId } from "./workspaces"
|
4
|
+
import { Organization } from "./organization"
|
4
5
|
|
5
6
|
/**
|
6
7
|
* Workspace event.
|
@@ -16,6 +17,11 @@ export abstract class Workspace extends EventDispatcher<
|
|
16
17
|
WorkspaceEvent,
|
17
18
|
Workspace
|
18
19
|
> {
|
20
|
+
/**
|
21
|
+
* Organization.
|
22
|
+
*/
|
23
|
+
abstract get organization(): Organization
|
24
|
+
|
19
25
|
/**
|
20
26
|
* Workspace id.
|
21
27
|
*/
|
@@ -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
|
+
})
|
package/test/index.test.ts
CHANGED
@@ -12,9 +12,7 @@ import { CredentialService } from "../src/services/credentialService"
|
|
12
12
|
import { RpcService } from "../src/services/rpcService"
|
13
13
|
import { AppBuilder } from "../src/appBuilder"
|
14
14
|
import { UnitTest, AppSdkUnitTest } from "../src/unitTest"
|
15
|
-
import { HOST, TOKEN } from "./setup"
|
16
|
-
import { OrganizationImpl } from "../src/storages/organization.impl"
|
17
|
-
import * as fs from "fs"
|
15
|
+
import { HOST, randomHash, TOKEN } from "./setup"
|
18
16
|
|
19
17
|
test("SDK_VERSION", () => {
|
20
18
|
expect(SDK_VERSION).toBe(version)
|
@@ -29,110 +27,6 @@ test("Default SDK", async () => {
|
|
29
27
|
expect(app).not.toBeUndefined()
|
30
28
|
})
|
31
29
|
|
32
|
-
test("Create and delete organization, create and delete workspace", async () => {
|
33
|
-
const randomName = `org-test-${Math.random().toString(16)}`
|
34
|
-
const app = await appSdk(randomName, async builder => {
|
35
|
-
builder.useHost(HOST)
|
36
|
-
builder.useCredential(new DebugCredential(TOKEN))
|
37
|
-
})
|
38
|
-
|
39
|
-
const initLength = app.organizations.collection.length
|
40
|
-
|
41
|
-
const org = await app.organizations.create(
|
42
|
-
randomName,
|
43
|
-
"this is a unitTest description"
|
44
|
-
)
|
45
|
-
|
46
|
-
// check organization
|
47
|
-
expect(org).not.toBeUndefined()
|
48
|
-
expect(org).not.toBeNull()
|
49
|
-
expect(org).toBeInstanceOf(OrganizationImpl)
|
50
|
-
|
51
|
-
expect(org.id).not.toBeUndefined()
|
52
|
-
expect(org.id).not.toBeNull()
|
53
|
-
expect(org.id.trim()).not.toBe("")
|
54
|
-
|
55
|
-
// check name
|
56
|
-
expect(org.name).not.toBeUndefined()
|
57
|
-
expect(org.name).not.toBeNull()
|
58
|
-
expect(org.name.trim()).not.toBe("")
|
59
|
-
|
60
|
-
// check description
|
61
|
-
expect(org.description).not.toBeUndefined()
|
62
|
-
expect(org.description).not.toBeNull()
|
63
|
-
expect(org.description.trim()).not.toBe("")
|
64
|
-
|
65
|
-
// check organizations
|
66
|
-
expect(app.organizations.get(org.id)).toBe(org)
|
67
|
-
expect(app.organizations.tryGet(org.id)).toBe(org)
|
68
|
-
expect(app.organizations.collection.length).toBe(initLength + 1)
|
69
|
-
|
70
|
-
const initWorkspacesLength = org.workspaces.collection.length
|
71
|
-
|
72
|
-
const wsPromise = org.workspaces.create(
|
73
|
-
"test-workspace",
|
74
|
-
"test-workspace-description"
|
75
|
-
)
|
76
|
-
await expect(wsPromise).resolves.not.toThrow()
|
77
|
-
const ws = await wsPromise
|
78
|
-
expect(ws).not.toBeUndefined()
|
79
|
-
expect(ws).not.toBeNull()
|
80
|
-
expect(ws.name).toBe("test-workspace")
|
81
|
-
expect(ws.description).toBe("test-workspace-description")
|
82
|
-
expect(app.organizations.get(org.id).workspaces.collection.length).toBe(
|
83
|
-
initWorkspacesLength + 1
|
84
|
-
)
|
85
|
-
expect(org.workspaces.collection.length).toBe(initWorkspacesLength + 1)
|
86
|
-
expect(org.workspaces.get(ws.id)).toBe(ws)
|
87
|
-
expect(org.workspaces.tryGet(ws.id)).toBe(ws)
|
88
|
-
expect(org.workspaces.contains(ws.id)).toBe(true)
|
89
|
-
|
90
|
-
const buffer = fs.readFileSync("test_file.pdf")
|
91
|
-
const file_obj = new File([new Uint8Array(buffer)], "test_file.pdf", {
|
92
|
-
type: "text/plain"
|
93
|
-
})
|
94
|
-
|
95
|
-
const filePromise = ws.files.upload(file_obj)
|
96
|
-
await expect(filePromise).resolves.not.toThrow()
|
97
|
-
const file = await filePromise
|
98
|
-
|
99
|
-
expect(file).not.toBeUndefined()
|
100
|
-
expect(file).not.toBeNull()
|
101
|
-
expect(file.name).toBe("test_file.pdf")
|
102
|
-
|
103
|
-
let status = await file.status()
|
104
|
-
|
105
|
-
expect(status).not.toBeUndefined()
|
106
|
-
expect(status).not.toBeNull()
|
107
|
-
expect(status.file_id).toBe(file.id)
|
108
|
-
expect(status.file_parts_count).toBeGreaterThan(status.completed_parts_count)
|
109
|
-
|
110
|
-
while (
|
111
|
-
status.success == true &&
|
112
|
-
status.completed_parts_count !== status.file_parts_count
|
113
|
-
) {
|
114
|
-
await new Promise(r => setTimeout(r, 1000))
|
115
|
-
status = await file.status()
|
116
|
-
}
|
117
|
-
|
118
|
-
const queryPromise = ws.files.query("", 0, 20)
|
119
|
-
await expect(queryPromise).resolves.not.toThrow()
|
120
|
-
const filePage = await queryPromise
|
121
|
-
expect(filePage).not.toBeUndefined()
|
122
|
-
expect(filePage).not.toBeNull()
|
123
|
-
expect(filePage.files.length).toBe(1)
|
124
|
-
expect(filePage.pages).toBe(1)
|
125
|
-
|
126
|
-
await expect(ws.files.delete(file.id)).resolves.not.toThrow()
|
127
|
-
|
128
|
-
await expect(org.workspaces.delete(ws.id)).resolves.not.toThrow()
|
129
|
-
|
130
|
-
await expect(app.organizations.delete(org.id)).resolves.not.toThrow()
|
131
|
-
expect((<OrganizationImpl>org).isDisposed).toBe(true)
|
132
|
-
expect(app.organizations.collection.length).toBe(initLength)
|
133
|
-
expect(app.organizations.tryGet(org.id)).toBeUndefined()
|
134
|
-
})
|
135
|
-
|
136
30
|
test("SDK, middleware", async () => {
|
137
31
|
await AppSdkUnitTest.test(UnitTest.DEFAULT, async () => {
|
138
32
|
const app = await appSdk("test-settings", async (builder: AppBuilder) => {
|
@@ -203,10 +97,12 @@ test("SDK, it is impossible to setup the same application", async () => {
|
|
203
97
|
// this test is not stable if you run all tests at once
|
204
98
|
// because the app is cached all app instances
|
205
99
|
// we use a random identifier every time
|
206
|
-
const testId =
|
207
|
-
const promise = appSdk(
|
100
|
+
const testId = `test-setup-${randomHash()}`
|
101
|
+
const promise = appSdk(testId).then(() => {
|
102
|
+
})
|
208
103
|
await expect(
|
209
|
-
appSdk(
|
104
|
+
appSdk(testId, async () => {
|
105
|
+
})
|
210
106
|
).rejects.toThrow()
|
211
107
|
await promise
|
212
108
|
})
|
@@ -217,9 +113,10 @@ test("SDK, setup and get this app", async () => {
|
|
217
113
|
// this test is not stable if you run all tests at once
|
218
114
|
// because the app is cached all app instances
|
219
115
|
// we use a random identifier every time
|
220
|
-
const testId =
|
221
|
-
const promise = appSdk(
|
222
|
-
|
116
|
+
const testId = `test-get-${randomHash()}`
|
117
|
+
const promise = appSdk(testId).then(() => {
|
118
|
+
})
|
119
|
+
await expect(appSdk(testId)).resolves.toBeInstanceOf(AppSdk)
|
223
120
|
await promise
|
224
121
|
})
|
225
122
|
})
|
@@ -0,0 +1,57 @@
|
|
1
|
+
import { appSdk, DebugCredential } from "../src"
|
2
|
+
import { HOST, randomHash, TOKEN } from "./setup"
|
3
|
+
import { OrganizationImpl } from "../src/storages/organization.impl"
|
4
|
+
|
5
|
+
test("Organization", async () => {
|
6
|
+
// make random name
|
7
|
+
const randomName = `org-test-${randomHash()}`
|
8
|
+
|
9
|
+
// create app
|
10
|
+
const app = await appSdk(randomName, async builder => {
|
11
|
+
builder.useHost(HOST)
|
12
|
+
builder.useCredential(new DebugCredential(TOKEN))
|
13
|
+
})
|
14
|
+
|
15
|
+
// save init length
|
16
|
+
const initLength = app.organizations.collection.length
|
17
|
+
|
18
|
+
// create organization
|
19
|
+
const org = await app.organizations.create(
|
20
|
+
randomName,
|
21
|
+
"this is a unitTest description"
|
22
|
+
)
|
23
|
+
|
24
|
+
// check organization
|
25
|
+
expect(org).not.toBeUndefined()
|
26
|
+
expect(org).not.toBeNull()
|
27
|
+
expect(org).toBeInstanceOf(OrganizationImpl)
|
28
|
+
|
29
|
+
expect(org.id).not.toBeUndefined()
|
30
|
+
expect(org.id).not.toBeNull()
|
31
|
+
expect(org.id.trim()).not.toBe("")
|
32
|
+
|
33
|
+
// check name
|
34
|
+
expect(org.name).not.toBeUndefined()
|
35
|
+
expect(org.name).not.toBeNull()
|
36
|
+
expect(org.name.trim()).not.toBe("")
|
37
|
+
|
38
|
+
// check description
|
39
|
+
expect(org.description).not.toBeUndefined()
|
40
|
+
expect(org.description).not.toBeNull()
|
41
|
+
expect(org.description.trim()).not.toBe("")
|
42
|
+
|
43
|
+
// check organizations
|
44
|
+
expect(app.organizations.get(org.id)).toBe(org)
|
45
|
+
expect(app.organizations.tryGet(org.id)).toBe(org)
|
46
|
+
expect(app.organizations.collection.length).toBe(initLength + 1)
|
47
|
+
|
48
|
+
// delete organization
|
49
|
+
await expect(app.organizations.delete(org.id)).resolves.not.toThrow()
|
50
|
+
expect((<OrganizationImpl>org).isDisposed).toBe(true)
|
51
|
+
|
52
|
+
// check init length
|
53
|
+
expect(app.organizations.collection.length).toBe(initLength)
|
54
|
+
|
55
|
+
// check organization must be undefined because it was deleted
|
56
|
+
expect(app.organizations.tryGet(org.id)).toBeUndefined()
|
57
|
+
})
|
package/test/setup.ts
CHANGED
@@ -1,2 +1,54 @@
|
|
1
|
+
import { appSdk, AppSdk, DebugCredential } from "../src"
|
2
|
+
import { Organization } from "../src/storages/organization"
|
3
|
+
import { Workspace } from "../src/storages/workspace"
|
4
|
+
|
1
5
|
export const HOST = <string>process.env.HOST
|
2
6
|
export const TOKEN = <string>process.env.TOKEN
|
7
|
+
|
8
|
+
export const randomHash = (length: number = 5) => {
|
9
|
+
if (length <= 0) length = 1
|
10
|
+
return `name-${((Math.random() * Math.pow(10, length)) | 0).toString(16)}`
|
11
|
+
}
|
12
|
+
|
13
|
+
export const testInOrganization = async (func: (app: AppSdk, org: Organization) => Promise<void>, config ?: {
|
14
|
+
host: string,
|
15
|
+
token: string
|
16
|
+
}
|
17
|
+
): Promise<void> => {
|
18
|
+
const randomName = `org-name-${randomHash()}`
|
19
|
+
const app = await appSdk(randomName, async builder => {
|
20
|
+
builder.useHost(config?.host ?? HOST)
|
21
|
+
builder.useCredential(new DebugCredential(config?.token ?? TOKEN))
|
22
|
+
})
|
23
|
+
const org = await app.organizations.create(
|
24
|
+
randomName,
|
25
|
+
"this is a unitTest description"
|
26
|
+
)
|
27
|
+
try {
|
28
|
+
await func(app, org)
|
29
|
+
} finally {
|
30
|
+
if (app
|
31
|
+
.organizations.tryGet(org.id)
|
32
|
+
) {
|
33
|
+
await app.organizations.delete(org.id)
|
34
|
+
}
|
35
|
+
}
|
36
|
+
}
|
37
|
+
|
38
|
+
export const testInWorkspace = async (func: (app: AppSdk, org: Organization, workspace: Workspace)
|
39
|
+
=> Promise<void>, config?: {
|
40
|
+
host: string,
|
41
|
+
token: string,
|
42
|
+
}): Promise<void> => {
|
43
|
+
await testInOrganization(async (app, org) => {
|
44
|
+
const randomName = `workspace-${randomHash()}`
|
45
|
+
const workspace = await org.workspaces.create(randomName, `description of ${randomName}`)
|
46
|
+
try {
|
47
|
+
await func(app, org, workspace)
|
48
|
+
} finally {
|
49
|
+
if (org.workspaces.tryGet(workspace.id)) {
|
50
|
+
await org.workspaces.delete(workspace.id)
|
51
|
+
}
|
52
|
+
}
|
53
|
+
}, config)
|
54
|
+
}
|
@@ -0,0 +1,71 @@
|
|
1
|
+
import { testInOrganization, testInWorkspace } from "./setup"
|
2
|
+
|
3
|
+
test("Workspace create / delete", async () => {
|
4
|
+
await testInOrganization(async (app, org) => {
|
5
|
+
// save init length
|
6
|
+
const initWorkspaceCount = org.workspaces.collection.length
|
7
|
+
|
8
|
+
// create workspace
|
9
|
+
const wsPromise = org.workspaces.create(
|
10
|
+
"test-workspace",
|
11
|
+
"test-workspace-description"
|
12
|
+
)
|
13
|
+
|
14
|
+
// check not throw
|
15
|
+
await expect(wsPromise).resolves.not.toThrow()
|
16
|
+
|
17
|
+
// get workspace
|
18
|
+
const ws = await wsPromise
|
19
|
+
|
20
|
+
// check exists
|
21
|
+
expect(ws).not.toBeUndefined()
|
22
|
+
|
23
|
+
// check exists
|
24
|
+
expect(ws).not.toBeNull()
|
25
|
+
|
26
|
+
// check name
|
27
|
+
expect(ws.name).toBe("test-workspace")
|
28
|
+
|
29
|
+
// check description
|
30
|
+
expect(ws.description).toBe("test-workspace-description")
|
31
|
+
|
32
|
+
// check collection length
|
33
|
+
expect(app.organizations.get(org.id).workspaces.collection.length).toBe(
|
34
|
+
initWorkspaceCount + 1
|
35
|
+
)
|
36
|
+
|
37
|
+
// check collection length
|
38
|
+
expect(org.workspaces.collection.length).toBe(initWorkspaceCount + 1)
|
39
|
+
|
40
|
+
// check get
|
41
|
+
expect(org.workspaces.get(ws.id)).toBe(ws)
|
42
|
+
|
43
|
+
// check tryGet
|
44
|
+
expect(org.workspaces.tryGet(ws.id)).toBe(ws)
|
45
|
+
|
46
|
+
// check contains
|
47
|
+
expect(org.workspaces.contains(ws.id)).toBe(true)
|
48
|
+
|
49
|
+
// check delete
|
50
|
+
await expect(org.workspaces.delete(ws.id)).resolves.not.toThrow()
|
51
|
+
})
|
52
|
+
})
|
53
|
+
|
54
|
+
test("Workspace, change", async () => {
|
55
|
+
await testInWorkspace(async (app, org, ws) => {
|
56
|
+
|
57
|
+
expect(ws.name).not.toBe("new-name")
|
58
|
+
|
59
|
+
// rename
|
60
|
+
await ws.change("new-name", "new-description")
|
61
|
+
|
62
|
+
// check name
|
63
|
+
expect(ws.name).toBe("new-name")
|
64
|
+
|
65
|
+
// check description
|
66
|
+
expect(ws.description).toBe("new-description")
|
67
|
+
|
68
|
+
// check name
|
69
|
+
expect(ws.name).toBe("new-name")
|
70
|
+
})
|
71
|
+
})
|
File without changes
|