@sanity/sdk 2.18.0 → 3.0.0-rc.1
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/dist/_chunks-dts/createGroqSearchFilter.d.ts +233 -12
- package/dist/_chunks-dts/createGroqSearchFilter.d.ts.map +1 -1
- package/dist/_chunks-es/_internal.js +1 -0
- package/dist/_chunks-es/_internal.js.map +1 -1
- package/dist/_chunks-es/createGroqSearchFilter.js +284 -6
- package/dist/_chunks-es/createGroqSearchFilter.js.map +1 -1
- package/dist/_chunks-es/telemetryManager.js +2 -2
- package/dist/_exports/_internal.d.ts +2 -2
- package/dist/_exports/_internal.d.ts.map +1 -1
- package/dist/_exports/_internal.js +3 -1
- package/dist/index.d.ts +599 -185
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +492 -337
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/_exports/_internal.ts +11 -0
- package/src/_exports/index.ts +57 -12
- package/src/access/checkPermissions.test.ts +55 -0
- package/src/access/checkPermissions.ts +54 -0
- package/src/applications/applications.test-d.ts +83 -0
- package/src/applications/applications.test.ts +130 -0
- package/src/applications/applications.ts +364 -0
- package/src/applications/userApplications.test.ts +126 -0
- package/src/applications/userApplications.ts +261 -0
- package/src/auth/getOrganizationVerificationState.test.ts +7 -7
- package/src/auth/getOrganizationVerificationState.ts +4 -4
- package/src/document/documentStore.concurrency.test.ts +313 -22
- package/src/installations/installations.test-d.ts +68 -0
- package/src/installations/installations.test.ts +62 -0
- package/src/installations/installations.ts +202 -0
- package/src/organization/organization.test-d.ts +18 -84
- package/src/organization/organization.test.ts +11 -11
- package/src/organization/organization.ts +26 -54
- package/src/organizations/organizations.test-d.ts +11 -32
- package/src/organizations/organizations.test.ts +8 -8
- package/src/organizations/organizations.ts +29 -54
- package/src/project/project.test-d.ts +19 -75
- package/src/project/project.test.ts +57 -10
- package/src/project/project.ts +33 -52
- package/src/projects/projects.test-d.ts +15 -21
- package/src/projects/projects.test.ts +8 -8
- package/src/projects/projects.ts +25 -49
- package/src/store/fetcherStore.test-d.ts +121 -0
- package/src/store/fetcherStore.test.ts +299 -0
- package/src/store/fetcherStore.ts +801 -0
- package/src/utils/buildQuery.test.ts +14 -0
- package/src/utils/buildQuery.ts +17 -0
- package/src/utils/includeShape.ts +36 -0
- package/src/utils/setCleanupTimeout.ts +21 -8
- package/dist/_chunks-es/version.js +0 -7
- package/dist/_chunks-es/version.js.map +0 -1
- package/src/utils/defineIntent.test.ts +0 -477
- package/src/utils/defineIntent.ts +0 -244
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import {switchMap} from 'rxjs'
|
|
2
|
+
|
|
3
|
+
import {getClientState} from '../client/clientStore'
|
|
4
|
+
import {defineFetcher, defineMutation} from '../store/fetcherStore'
|
|
5
|
+
import {buildQuery} from '../utils/buildQuery'
|
|
6
|
+
import {type Included, type IncludesAny} from '../utils/includeShape'
|
|
7
|
+
|
|
8
|
+
// The Applications API is currently only available on vX.
|
|
9
|
+
const API_VERSION = 'vX'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Related data the Applications API can embed in a response. `interfaces`,
|
|
13
|
+
* `workspaces` and `access` are children of the active deployment and are
|
|
14
|
+
* embedded under {@link Application.activeDeployment}; `config.studio` and
|
|
15
|
+
* `config.mfManifest` are embedded under {@link Application.config}.
|
|
16
|
+
*
|
|
17
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
18
|
+
* @public
|
|
19
|
+
*/
|
|
20
|
+
export type ApplicationInclude =
|
|
21
|
+
| 'interfaces'
|
|
22
|
+
| 'workspaces'
|
|
23
|
+
| 'access'
|
|
24
|
+
| 'config.studio'
|
|
25
|
+
| 'config.mfManifest'
|
|
26
|
+
| 'activeDeployment'
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* An interface exposed by a deployment, embedded when `interfaces` is included.
|
|
30
|
+
*
|
|
31
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
32
|
+
* @public
|
|
33
|
+
*/
|
|
34
|
+
export interface ApplicationInterface {
|
|
35
|
+
id: string
|
|
36
|
+
type: 'app' | 'worker' | 'asset_source' | 'panel'
|
|
37
|
+
name: string
|
|
38
|
+
title: string
|
|
39
|
+
version: string
|
|
40
|
+
/** Module federation module ID; resolved from the host mf-manifest at runtime */
|
|
41
|
+
moduleId: string
|
|
42
|
+
metadata: {group?: string; priority?: number} | null
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* A studio workspace of a deployment, embedded when `workspaces` is included.
|
|
47
|
+
* Studio applications only.
|
|
48
|
+
*
|
|
49
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
50
|
+
* @public
|
|
51
|
+
*/
|
|
52
|
+
export interface ApplicationWorkspace {
|
|
53
|
+
id: string
|
|
54
|
+
name: string
|
|
55
|
+
title: string | null
|
|
56
|
+
subtitle: string | null
|
|
57
|
+
projectId: string
|
|
58
|
+
dataset: string
|
|
59
|
+
schemaDescriptorId: string | null
|
|
60
|
+
basePath: string | null
|
|
61
|
+
/** Sanitized SVG icon markup */
|
|
62
|
+
icon: string | null
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* A resource an application accesses, embedded when `access` is included.
|
|
67
|
+
*
|
|
68
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
69
|
+
* @public
|
|
70
|
+
*/
|
|
71
|
+
export interface ApplicationAccess {
|
|
72
|
+
id: string
|
|
73
|
+
resourceType: 'datasets' | 'canvases' | 'dashboards' | 'media-libraries'
|
|
74
|
+
resourceId: string
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Studio configuration, embedded under `config` when `config.studio` is included.
|
|
79
|
+
*
|
|
80
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
81
|
+
* @public
|
|
82
|
+
*/
|
|
83
|
+
export interface ApplicationStudioConfig {
|
|
84
|
+
projectId: string
|
|
85
|
+
/** Release channel (`next`, `stable`, `latest`) or a pinned semver; null if unset */
|
|
86
|
+
autoUpdatingVersion: string | null
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The active deployment as embedded under {@link Application.activeDeployment}.
|
|
91
|
+
* Its `interfaces`, `workspaces` and `access` children are each present only
|
|
92
|
+
* when the matching {@link ApplicationInclude} token was requested. `workspaces`
|
|
93
|
+
* is omitted (not `null`) for non-studio applications even when requested.
|
|
94
|
+
*
|
|
95
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
96
|
+
* @public
|
|
97
|
+
*/
|
|
98
|
+
export type ApplicationDeployment<Include extends ApplicationInclude = never> = {
|
|
99
|
+
id: string
|
|
100
|
+
applicationId: string
|
|
101
|
+
size: number | null
|
|
102
|
+
version: string | null
|
|
103
|
+
isAutoUpdating: boolean | null
|
|
104
|
+
isActiveDeployment: boolean
|
|
105
|
+
deployedBy: string | null
|
|
106
|
+
createdAt: string
|
|
107
|
+
updatedAt: string
|
|
108
|
+
} & Included<ApplicationInclude, 'interfaces', Include, {interfaces: ApplicationInterface[]}> &
|
|
109
|
+
Included<ApplicationInclude, 'workspaces', Include, {workspaces?: ApplicationWorkspace[]}> &
|
|
110
|
+
Included<ApplicationInclude, 'access', Include, {access: ApplicationAccess[]}>
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The always-present fields of an org-owned application returned by the
|
|
114
|
+
* Applications API (`/applications`), before any {@link ApplicationInclude}.
|
|
115
|
+
*
|
|
116
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
117
|
+
* @public
|
|
118
|
+
*/
|
|
119
|
+
export interface ApplicationBase {
|
|
120
|
+
id: string
|
|
121
|
+
type: 'studio' | 'coreApp'
|
|
122
|
+
title: string
|
|
123
|
+
icon: string | null
|
|
124
|
+
isSingleton: boolean
|
|
125
|
+
visibility: 'default' | 'unlisted' | 'disabled'
|
|
126
|
+
slug: string | null
|
|
127
|
+
externalUrl: string | null
|
|
128
|
+
organizationId: string
|
|
129
|
+
createdAt: string
|
|
130
|
+
updatedAt: string
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Requesting any of these tokens forces the deployment summary into the response.
|
|
134
|
+
type DeploymentToken = 'activeDeployment' | 'interfaces' | 'workspaces' | 'access'
|
|
135
|
+
|
|
136
|
+
// `config` is present when either config child was requested; each child is
|
|
137
|
+
// gated on its own token.
|
|
138
|
+
type ApplicationConfigShape<Include extends ApplicationInclude> = Included<
|
|
139
|
+
ApplicationInclude,
|
|
140
|
+
'config.studio',
|
|
141
|
+
Include,
|
|
142
|
+
{config: {studio: ApplicationStudioConfig}}
|
|
143
|
+
> &
|
|
144
|
+
Included<ApplicationInclude, 'config.mfManifest', Include, {config: {mfManifest: unknown}}>
|
|
145
|
+
|
|
146
|
+
// `activeDeployment` is present when any deployment-related token was requested;
|
|
147
|
+
// a wide include array makes it optional.
|
|
148
|
+
type ApplicationDeploymentShape<Include extends ApplicationInclude> = [ApplicationInclude] extends [
|
|
149
|
+
Include,
|
|
150
|
+
]
|
|
151
|
+
? {activeDeployment?: ApplicationDeployment<Include> | null}
|
|
152
|
+
: IncludesAny<Include, DeploymentToken> extends true
|
|
153
|
+
? {activeDeployment: ApplicationDeployment<Include> | null}
|
|
154
|
+
: unknown
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* An org-owned application as returned by the Applications API
|
|
158
|
+
* (`/applications`). `config` and `activeDeployment` (with its
|
|
159
|
+
* `interfaces`/`workspaces`/`access` children) are present only when the
|
|
160
|
+
* corresponding {@link ApplicationInclude} tokens were requested.
|
|
161
|
+
*
|
|
162
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
163
|
+
* @public
|
|
164
|
+
*/
|
|
165
|
+
export type Application<Include extends ApplicationInclude = never> = ApplicationBase &
|
|
166
|
+
ApplicationConfigShape<Include> &
|
|
167
|
+
ApplicationDeploymentShape<Include>
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Options for listing an organization's applications.
|
|
171
|
+
*
|
|
172
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
173
|
+
* @public
|
|
174
|
+
*/
|
|
175
|
+
export interface ApplicationsOptions<Include extends ApplicationInclude = ApplicationInclude> {
|
|
176
|
+
organizationId: string
|
|
177
|
+
/** Filter by application type */
|
|
178
|
+
type?: string
|
|
179
|
+
include?: Include[]
|
|
180
|
+
/** Page size (1-100, default 50), or `'none'` to disable pagination */
|
|
181
|
+
limit?: number | 'none'
|
|
182
|
+
/** Cursor from a previous response's `nextCursor` */
|
|
183
|
+
cursor?: string
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* The cursor-paginated envelope returned by `GET /applications`.
|
|
188
|
+
*
|
|
189
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
190
|
+
* @public
|
|
191
|
+
*/
|
|
192
|
+
export interface ApplicationsResponse<Include extends ApplicationInclude = never> {
|
|
193
|
+
nextCursor: string | null
|
|
194
|
+
data: Application<Include>[]
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** The API takes `include` as a comma-separated list; sorted so key and query agree */
|
|
198
|
+
function serializeInclude(include: ApplicationInclude[] | undefined): string | undefined {
|
|
199
|
+
return include?.length ? [...include].sort().join(',') : undefined
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Fetcher for an organization's applications (`GET /applications`), on the
|
|
204
|
+
* shared fetcher cache.
|
|
205
|
+
*
|
|
206
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
207
|
+
* @internal
|
|
208
|
+
*/
|
|
209
|
+
export const applications = defineFetcher<
|
|
210
|
+
[options: ApplicationsOptions],
|
|
211
|
+
ApplicationsResponse<ApplicationInclude>
|
|
212
|
+
>({
|
|
213
|
+
name: 'applications',
|
|
214
|
+
getKey: (_instance, options) =>
|
|
215
|
+
[
|
|
216
|
+
options.organizationId,
|
|
217
|
+
options.type ?? '',
|
|
218
|
+
serializeInclude(options.include) ?? '',
|
|
219
|
+
options.limit ?? '',
|
|
220
|
+
options.cursor ?? '',
|
|
221
|
+
].join(':'),
|
|
222
|
+
fetch: (instance) => (options) =>
|
|
223
|
+
getClientState(instance, {apiVersion: API_VERSION, scope: 'global'}).observable.pipe(
|
|
224
|
+
switchMap((client) =>
|
|
225
|
+
client.observable.request<ApplicationsResponse<ApplicationInclude>>({
|
|
226
|
+
uri: '/applications',
|
|
227
|
+
query: buildQuery({
|
|
228
|
+
organizationId: options.organizationId,
|
|
229
|
+
type: options.type,
|
|
230
|
+
include: serializeInclude(options.include),
|
|
231
|
+
limit: options.limit,
|
|
232
|
+
cursor: options.cursor,
|
|
233
|
+
}),
|
|
234
|
+
tag: 'applications.list',
|
|
235
|
+
}),
|
|
236
|
+
),
|
|
237
|
+
),
|
|
238
|
+
tags: (data) => [
|
|
239
|
+
{type: 'application', id: 'LIST'},
|
|
240
|
+
...data.data.map((app) => ({type: 'application', id: app.id})),
|
|
241
|
+
],
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Fetcher for a single application (`GET /applications/:id`), on the shared
|
|
246
|
+
* fetcher cache. No pre-seeding from {@link applications}: its list keys
|
|
247
|
+
* include the organization, which this fetcher's params can't reconstruct.
|
|
248
|
+
*
|
|
249
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
250
|
+
* @internal
|
|
251
|
+
*/
|
|
252
|
+
export const application = defineFetcher<
|
|
253
|
+
[applicationId: string, options?: {include?: ApplicationInclude[]}],
|
|
254
|
+
Application<ApplicationInclude>
|
|
255
|
+
>({
|
|
256
|
+
name: 'application',
|
|
257
|
+
getKey: (_instance, applicationId, options) =>
|
|
258
|
+
`${applicationId}:${serializeInclude(options?.include) ?? ''}`,
|
|
259
|
+
fetch: (instance) => (applicationId, options) =>
|
|
260
|
+
getClientState(instance, {apiVersion: API_VERSION, scope: 'global'}).observable.pipe(
|
|
261
|
+
switchMap((client) =>
|
|
262
|
+
client.observable.request<Application<ApplicationInclude>>({
|
|
263
|
+
uri: `/applications/${applicationId}`,
|
|
264
|
+
query: buildQuery({include: serializeInclude(options?.include)}),
|
|
265
|
+
tag: 'applications.get',
|
|
266
|
+
}),
|
|
267
|
+
),
|
|
268
|
+
),
|
|
269
|
+
tags: (data) => [{type: 'application', id: data.id}],
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Confirmation returned by the API's soft-delete endpoints.
|
|
274
|
+
*
|
|
275
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
276
|
+
* @internal
|
|
277
|
+
*/
|
|
278
|
+
export interface DeletedResult {
|
|
279
|
+
deleted: boolean
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Dashboard visibility for an application. The same set of values backs a user
|
|
284
|
+
* application's `dashboardStatus`.
|
|
285
|
+
*
|
|
286
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
287
|
+
* @internal
|
|
288
|
+
*/
|
|
289
|
+
export type ApplicationVisibility = ApplicationBase['visibility']
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Mutable properties of an application (`PATCH /applications/:id`) — the
|
|
293
|
+
* writable subset of {@link ApplicationBase}, each optional, keyed by id.
|
|
294
|
+
*
|
|
295
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
296
|
+
* @internal
|
|
297
|
+
*/
|
|
298
|
+
export type UpdateApplicationInput = {applicationId: string} & Partial<
|
|
299
|
+
Pick<ApplicationBase, 'title' | 'icon' | 'visibility'>
|
|
300
|
+
>
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Mutation to update an application (`PATCH /applications/:id`). Invalidates the
|
|
304
|
+
* updated application and the list so active entries reconverge on server truth.
|
|
305
|
+
*
|
|
306
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
307
|
+
* @internal
|
|
308
|
+
*/
|
|
309
|
+
export const updateApplication = defineMutation<UpdateApplicationInput, Application>({
|
|
310
|
+
name: 'updateApplication',
|
|
311
|
+
mutationFn:
|
|
312
|
+
(instance) =>
|
|
313
|
+
({applicationId, ...body}) =>
|
|
314
|
+
getClientState(instance, {apiVersion: API_VERSION, scope: 'global'}).observable.pipe(
|
|
315
|
+
switchMap((client) =>
|
|
316
|
+
client.observable.request<Application>({
|
|
317
|
+
uri: `/applications/${applicationId}`,
|
|
318
|
+
method: 'PATCH',
|
|
319
|
+
body,
|
|
320
|
+
tag: 'applications.update',
|
|
321
|
+
}),
|
|
322
|
+
),
|
|
323
|
+
),
|
|
324
|
+
invalidates: (_result, {applicationId}) => [
|
|
325
|
+
{type: 'application', id: 'LIST'},
|
|
326
|
+
{type: 'application', id: applicationId},
|
|
327
|
+
],
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Input for {@link deleteApplication}.
|
|
332
|
+
*
|
|
333
|
+
* @internal
|
|
334
|
+
*/
|
|
335
|
+
export interface DeleteApplicationInput {
|
|
336
|
+
applicationId: string
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Mutation to soft-delete an application (`DELETE /applications/:id`).
|
|
341
|
+
* Invalidates the deleted application and the list.
|
|
342
|
+
*
|
|
343
|
+
* @see https://www.sanity.io/docs/http-reference/applications-api
|
|
344
|
+
* @internal
|
|
345
|
+
*/
|
|
346
|
+
export const deleteApplication = defineMutation<DeleteApplicationInput, DeletedResult>({
|
|
347
|
+
name: 'deleteApplication',
|
|
348
|
+
mutationFn:
|
|
349
|
+
(instance) =>
|
|
350
|
+
({applicationId}) =>
|
|
351
|
+
getClientState(instance, {apiVersion: API_VERSION, scope: 'global'}).observable.pipe(
|
|
352
|
+
switchMap((client) =>
|
|
353
|
+
client.observable.request<DeletedResult>({
|
|
354
|
+
uri: `/applications/${applicationId}`,
|
|
355
|
+
method: 'DELETE',
|
|
356
|
+
tag: 'applications.delete',
|
|
357
|
+
}),
|
|
358
|
+
),
|
|
359
|
+
),
|
|
360
|
+
invalidates: (_result, {applicationId}) => [
|
|
361
|
+
{type: 'application', id: 'LIST'},
|
|
362
|
+
{type: 'application', id: applicationId},
|
|
363
|
+
],
|
|
364
|
+
})
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import {type SanityClient} from '@sanity/client'
|
|
2
|
+
import {of} from 'rxjs'
|
|
3
|
+
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest'
|
|
4
|
+
|
|
5
|
+
import {getClientState} from '../client/clientStore'
|
|
6
|
+
import {createSanityInstance, type SanityInstance} from '../store/createSanityInstance'
|
|
7
|
+
import {type StateSource} from '../store/createStateSourceAction'
|
|
8
|
+
import {
|
|
9
|
+
createUserApplication,
|
|
10
|
+
deleteUserApplication,
|
|
11
|
+
updateUserApplication,
|
|
12
|
+
userApplication,
|
|
13
|
+
userApplications,
|
|
14
|
+
} from './userApplications'
|
|
15
|
+
|
|
16
|
+
vi.mock('../client/clientStore')
|
|
17
|
+
|
|
18
|
+
describe('userApplications', () => {
|
|
19
|
+
let instance: SanityInstance
|
|
20
|
+
const request = vi.fn()
|
|
21
|
+
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
instance = createSanityInstance({projectId: 'p', dataset: 'd'})
|
|
24
|
+
request.mockReset()
|
|
25
|
+
vi.mocked(getClientState).mockReturnValue({
|
|
26
|
+
observable: of({observable: {request}} as unknown as SanityClient),
|
|
27
|
+
} as StateSource<SanityClient>)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
instance.dispose()
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('lists user applications for an organization on the global client', async () => {
|
|
35
|
+
const apps = [{id: 'ua1'}, {id: 'ua2'}]
|
|
36
|
+
request.mockReturnValue(of(apps))
|
|
37
|
+
|
|
38
|
+
const result = await userApplications.resolveState(instance, {organizationId: 'org1'})
|
|
39
|
+
|
|
40
|
+
expect(result).toEqual(apps)
|
|
41
|
+
expect(getClientState).toHaveBeenCalledWith(instance, {
|
|
42
|
+
apiVersion: 'v2024-08-01',
|
|
43
|
+
scope: 'global',
|
|
44
|
+
})
|
|
45
|
+
expect(request).toHaveBeenCalledWith({
|
|
46
|
+
uri: '/user-applications',
|
|
47
|
+
query: {organizationId: 'org1'},
|
|
48
|
+
tag: 'user-applications.list',
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('fetches a single user application by id', async () => {
|
|
53
|
+
const app = {id: 'ua1', appHost: 'my-studio'}
|
|
54
|
+
request.mockReturnValue(of(app))
|
|
55
|
+
|
|
56
|
+
const result = await userApplication.resolveState(instance, 'ua1')
|
|
57
|
+
|
|
58
|
+
expect(result).toEqual(app)
|
|
59
|
+
expect(request).toHaveBeenCalledWith({
|
|
60
|
+
uri: '/user-applications/ua1',
|
|
61
|
+
tag: 'user-applications.get',
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('creates a user application, sending query params and body, and refetches the list', async () => {
|
|
66
|
+
request.mockImplementation((opts) => {
|
|
67
|
+
const {method} = opts as {method?: string}
|
|
68
|
+
return method === 'POST' ? of({id: 'ua-new', title: 'New'}) : of([{id: 'ua-new'}])
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
const source = userApplications.getState(instance, {organizationId: 'org1'})
|
|
72
|
+
const unsubscribe = source.subscribe()
|
|
73
|
+
await vi.waitFor(() => expect(source.getCurrent().status).toBe('success'))
|
|
74
|
+
|
|
75
|
+
const {data, invalidated} = await createUserApplication(instance, {
|
|
76
|
+
organizationId: 'org1',
|
|
77
|
+
appType: 'studio',
|
|
78
|
+
urlType: 'internal',
|
|
79
|
+
appHost: 'my-studio',
|
|
80
|
+
title: 'New',
|
|
81
|
+
})
|
|
82
|
+
await invalidated
|
|
83
|
+
|
|
84
|
+
expect(data).toEqual({id: 'ua-new', title: 'New'})
|
|
85
|
+
expect(request).toHaveBeenCalledWith({
|
|
86
|
+
uri: '/user-applications',
|
|
87
|
+
method: 'POST',
|
|
88
|
+
query: {organizationId: 'org1', appType: 'studio'},
|
|
89
|
+
body: {urlType: 'internal', appHost: 'my-studio', title: 'New'},
|
|
90
|
+
tag: 'user-applications.create',
|
|
91
|
+
})
|
|
92
|
+
// initial list GET + POST + list refetch after invalidation
|
|
93
|
+
expect(request).toHaveBeenCalledTimes(3)
|
|
94
|
+
unsubscribe()
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('updates a user application via PATCH', async () => {
|
|
98
|
+
request.mockReturnValue(of({id: 'ua1', title: 'Renamed'}))
|
|
99
|
+
|
|
100
|
+
const {data} = await updateUserApplication(instance, {
|
|
101
|
+
userApplicationId: 'ua1',
|
|
102
|
+
title: 'Renamed',
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
expect(data).toEqual({id: 'ua1', title: 'Renamed'})
|
|
106
|
+
expect(request).toHaveBeenCalledWith({
|
|
107
|
+
uri: '/user-applications/ua1',
|
|
108
|
+
method: 'PATCH',
|
|
109
|
+
body: {title: 'Renamed'},
|
|
110
|
+
tag: 'user-applications.update',
|
|
111
|
+
})
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('deletes a user application via DELETE with no body', async () => {
|
|
115
|
+
request.mockReturnValue(of({deleted: true}))
|
|
116
|
+
|
|
117
|
+
const {data} = await deleteUserApplication(instance, {userApplicationId: 'ua1'})
|
|
118
|
+
|
|
119
|
+
expect(data).toEqual({deleted: true})
|
|
120
|
+
expect(request).toHaveBeenCalledWith({
|
|
121
|
+
uri: '/user-applications/ua1',
|
|
122
|
+
method: 'DELETE',
|
|
123
|
+
tag: 'user-applications.delete',
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
})
|