cozy-client 60.44.0 → 61.0.0
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/CozyClient.cacheScope.spec.js +311 -0
- package/dist/CozyClient.createDirectory.spec.js +257 -0
- package/dist/CozyClient.js +73 -51
- package/dist/CozyClient.types.spec.js +84 -0
- package/dist/helpers/realtime.js +7 -5
- package/dist/links/StackLink.js +78 -40
- package/dist/queries/dsl.js +3 -2
- package/dist/store/queries.js +31 -9
- package/dist/store/queries.spec.js +51 -0
- package/dist/store/realtime.js +12 -6
- package/dist/types.js +5 -1
- package/package.json +3 -3
- package/types/CozyClient.d.ts +5 -12
- package/types/store/realtime.d.ts +4 -0
- package/types/types.d.ts +20 -2
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import CozyStackClient from '../../cozy-stack-client/src/CozyStackClient'
|
|
2
|
+
import CozyClient from './CozyClient'
|
|
3
|
+
import { Q } from './queries/dsl'
|
|
4
|
+
import { ensureFilePath } from './helpers/realtime'
|
|
5
|
+
import {
|
|
6
|
+
dispatchCreate,
|
|
7
|
+
dispatchUpdate,
|
|
8
|
+
dispatchDelete
|
|
9
|
+
} from './store/realtime'
|
|
10
|
+
|
|
11
|
+
const doctype = 'io.cozy.files'
|
|
12
|
+
const scopes = [null, 'drive-a', 'drive-b']
|
|
13
|
+
const folder = {
|
|
14
|
+
id: 'new-folder',
|
|
15
|
+
type: doctype,
|
|
16
|
+
attributes: { type: 'directory', name: 'New folder', dir_id: 'parent-id' },
|
|
17
|
+
meta: { rev: '1-created' }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function setup() {
|
|
21
|
+
const stackClient = new CozyStackClient({ uri: 'https://recipient.example' })
|
|
22
|
+
const client = new CozyClient({ stackClient })
|
|
23
|
+
const fetchJSON = jest.spyOn(stackClient, 'fetchJSON')
|
|
24
|
+
const definitions = scopes.map(driveId => {
|
|
25
|
+
const definition = Q(doctype).where({ type: 'directory' })
|
|
26
|
+
return driveId ? definition.sharingById(driveId) : definition
|
|
27
|
+
})
|
|
28
|
+
definitions.forEach((definition, index) => {
|
|
29
|
+
client.makeObservableQuery(definition, { as: scopes[index] || 'personal' })
|
|
30
|
+
})
|
|
31
|
+
return { client, fetchJSON, definitions }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function getCachedIds(client) {
|
|
35
|
+
return scopes.map(driveId =>
|
|
36
|
+
client.getQueryFromState(driveId || 'personal').data.map(doc => doc._id)
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function makeSyncedFolders() {
|
|
41
|
+
return scopes.map((driveId, index) => ({
|
|
42
|
+
_id: `folder-${index}`,
|
|
43
|
+
_type: doctype,
|
|
44
|
+
...folder.attributes,
|
|
45
|
+
...(driveId ? { driveId } : {})
|
|
46
|
+
}))
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
describe('file query cache scope', () => {
|
|
50
|
+
it('keeps shared query results out of personal and other shared lists', async () => {
|
|
51
|
+
const { client, fetchJSON, definitions } = setup()
|
|
52
|
+
fetchJSON.mockResolvedValue({ data: [folder], meta: { count: 1 } })
|
|
53
|
+
|
|
54
|
+
const response = await client.query(definitions[1], { as: 'refetched-a' })
|
|
55
|
+
|
|
56
|
+
expect(response.data[0]).toMatchObject({
|
|
57
|
+
_id: folder.id,
|
|
58
|
+
driveId: 'drive-a'
|
|
59
|
+
})
|
|
60
|
+
expect(getCachedIds(client)).toEqual([[], [folder.id], []])
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('keeps shared getById included children in the same drive scope', async () => {
|
|
64
|
+
const { client, fetchJSON } = setup()
|
|
65
|
+
fetchJSON.mockResolvedValue({
|
|
66
|
+
data: { ...folder, id: 'parent-id' },
|
|
67
|
+
included: [folder]
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
const response = await client.query(
|
|
71
|
+
Q(doctype)
|
|
72
|
+
.getById('parent-id')
|
|
73
|
+
.sharingById('drive-a'),
|
|
74
|
+
{ as: 'shared-parent' }
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
expect(response.included[0]).toMatchObject({
|
|
78
|
+
_id: folder.id,
|
|
79
|
+
driveId: 'drive-a'
|
|
80
|
+
})
|
|
81
|
+
expect(client.getDocumentFromState(doctype, folder.id).driveId).toBe(
|
|
82
|
+
'drive-a'
|
|
83
|
+
)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('does not treat a business driveId field on other doctypes as file scope', () => {
|
|
87
|
+
const { client } = setup()
|
|
88
|
+
client.makeObservableQuery(Q('io.cozy.todos'), { as: 'todos' })
|
|
89
|
+
const task = {
|
|
90
|
+
_id: 'task-id',
|
|
91
|
+
_type: 'io.cozy.todos',
|
|
92
|
+
driveId: 'business-data'
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
client.setData({ 'io.cozy.todos': [task] })
|
|
96
|
+
|
|
97
|
+
expect(client.getQueryFromState('todos').data).toEqual([task])
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('keeps the driveId query option in the query and cache scope', async () => {
|
|
101
|
+
const { client, fetchJSON, definitions } = setup()
|
|
102
|
+
fetchJSON.mockResolvedValue({ data: [folder], meta: { count: 1 } })
|
|
103
|
+
|
|
104
|
+
await client.query(definitions[0], {
|
|
105
|
+
as: 'drive-option',
|
|
106
|
+
driveId: 'drive-a'
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
expect(fetchJSON.mock.calls[0][1]).toBe('/sharings/drives/drive-a/_find')
|
|
110
|
+
expect(getCachedIds(client)).toEqual([[], [folder.id], []])
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it.each(scopes)(
|
|
114
|
+
'isolates realtime create, update and delete for scope %s',
|
|
115
|
+
async driveId => {
|
|
116
|
+
const { client } = setup()
|
|
117
|
+
const document = { _id: folder.id, ...folder.attributes }
|
|
118
|
+
const options = { driveId }
|
|
119
|
+
const expected = scopes.map(scope =>
|
|
120
|
+
scope === driveId ? [folder.id] : []
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
await dispatchCreate(client, doctype, document, options)
|
|
124
|
+
await dispatchCreate(client, doctype, document, options)
|
|
125
|
+
expect(getCachedIds(client)).toEqual(expected)
|
|
126
|
+
|
|
127
|
+
await dispatchUpdate(
|
|
128
|
+
client,
|
|
129
|
+
doctype,
|
|
130
|
+
{ ...document, name: 'Renamed' },
|
|
131
|
+
options
|
|
132
|
+
)
|
|
133
|
+
expect(getCachedIds(client)).toEqual(expected)
|
|
134
|
+
expect(client.getQueryFromState(driveId || 'personal').data[0].name).toBe(
|
|
135
|
+
'Renamed'
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
await dispatchDelete(client, doctype, document, options)
|
|
139
|
+
expect(getCachedIds(client)).toEqual([[], [], []])
|
|
140
|
+
}
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
it('keeps the shared parent lookup scoped when enhancing a realtime path', async () => {
|
|
144
|
+
const { client, fetchJSON } = setup()
|
|
145
|
+
fetchJSON.mockResolvedValue({
|
|
146
|
+
data: {
|
|
147
|
+
id: 'parent-id',
|
|
148
|
+
type: doctype,
|
|
149
|
+
attributes: { type: 'directory', name: 'Shared', path: '/Shared' },
|
|
150
|
+
meta: { rev: '1-parent' }
|
|
151
|
+
}
|
|
152
|
+
})
|
|
153
|
+
await client.query(
|
|
154
|
+
Q(doctype)
|
|
155
|
+
.getById('parent-id')
|
|
156
|
+
.sharingById('drive-a')
|
|
157
|
+
)
|
|
158
|
+
fetchJSON.mockClear()
|
|
159
|
+
|
|
160
|
+
await dispatchCreate(
|
|
161
|
+
client,
|
|
162
|
+
doctype,
|
|
163
|
+
{ _id: folder.id, ...folder.attributes },
|
|
164
|
+
{ driveId: 'drive-a', enhanceDocFn: ensureFilePath }
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
expect(client.getDocumentFromState(doctype, folder.id)).toMatchObject({
|
|
168
|
+
driveId: 'drive-a',
|
|
169
|
+
path: '/Shared/New folder'
|
|
170
|
+
})
|
|
171
|
+
expect(getCachedIds(client)).toEqual([[], ['parent-id', folder.id], []])
|
|
172
|
+
expect(fetchJSON).not.toHaveBeenCalled()
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it('keeps the drive scope already carried by realtime documents', async () => {
|
|
176
|
+
const { client } = setup()
|
|
177
|
+
|
|
178
|
+
await dispatchCreate(client, doctype, {
|
|
179
|
+
_id: folder.id,
|
|
180
|
+
...folder.attributes,
|
|
181
|
+
driveId: 'drive-b'
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
expect(getCachedIds(client)).toEqual([[], [], [folder.id]])
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
it('uses the realtime document driveId when the option targets another drive', async () => {
|
|
188
|
+
const { client } = setup()
|
|
189
|
+
const document = {
|
|
190
|
+
_id: folder.id,
|
|
191
|
+
...folder.attributes,
|
|
192
|
+
driveId: 'drive-a'
|
|
193
|
+
}
|
|
194
|
+
const options = { driveId: 'drive-b' }
|
|
195
|
+
|
|
196
|
+
await dispatchCreate(client, doctype, document, options)
|
|
197
|
+
expect(getCachedIds(client)).toEqual([[], [folder.id], []])
|
|
198
|
+
|
|
199
|
+
await dispatchUpdate(
|
|
200
|
+
client,
|
|
201
|
+
doctype,
|
|
202
|
+
{ ...document, name: 'Renamed' },
|
|
203
|
+
options
|
|
204
|
+
)
|
|
205
|
+
expect(getCachedIds(client)).toEqual([[], [folder.id], []])
|
|
206
|
+
expect(client.getQueryFromState('drive-a').data[0].name).toBe('Renamed')
|
|
207
|
+
|
|
208
|
+
await dispatchDelete(client, doctype, document, options)
|
|
209
|
+
expect(getCachedIds(client)).toEqual([[], [], []])
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
it('isolates a mixed offline sync batch and reads from the store', async () => {
|
|
213
|
+
const { client, definitions } = setup()
|
|
214
|
+
const documents = makeSyncedFolders()
|
|
215
|
+
|
|
216
|
+
client.setData({ [doctype]: documents })
|
|
217
|
+
expect(getCachedIds(client)).toEqual([
|
|
218
|
+
['folder-0'],
|
|
219
|
+
['folder-1'],
|
|
220
|
+
['folder-2']
|
|
221
|
+
])
|
|
222
|
+
|
|
223
|
+
for (let index = 0; index < scopes.length; index++) {
|
|
224
|
+
const response = await client.query(definitions[index], {
|
|
225
|
+
as: `from-store-${index}`,
|
|
226
|
+
executeFromStore: true
|
|
227
|
+
})
|
|
228
|
+
expect(response.data).toEqual([documents[index]])
|
|
229
|
+
|
|
230
|
+
const idsQuery = Q(doctype).getByIds(documents.map(doc => doc._id))
|
|
231
|
+
const driveId = scopes[index]
|
|
232
|
+
const idsResponse = await client.query(
|
|
233
|
+
driveId ? idsQuery.sharingById(driveId) : idsQuery,
|
|
234
|
+
{ as: `ids-from-store-${index}`, executeFromStore: true }
|
|
235
|
+
)
|
|
236
|
+
expect(idsResponse.data).toEqual([documents[index]])
|
|
237
|
+
}
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
it('preserves explicit driveId selectors used by offline shared-folder queries', () => {
|
|
241
|
+
const { client } = setup()
|
|
242
|
+
client.makeObservableQuery(
|
|
243
|
+
Q(doctype).where({ type: 'directory', driveId: 'drive-a' }),
|
|
244
|
+
{ as: 'offline-drive-a' }
|
|
245
|
+
)
|
|
246
|
+
const documents = makeSyncedFolders()
|
|
247
|
+
|
|
248
|
+
client.setData({ [doctype]: documents })
|
|
249
|
+
|
|
250
|
+
expect(client.getQueryFromState('offline-drive-a').data).toEqual([
|
|
251
|
+
documents[1]
|
|
252
|
+
])
|
|
253
|
+
expect(getCachedIds(client)).toEqual([
|
|
254
|
+
['folder-0'],
|
|
255
|
+
['folder-1'],
|
|
256
|
+
['folder-2']
|
|
257
|
+
])
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
it('keeps an observable query scoped when driveId is passed as an option', async () => {
|
|
261
|
+
const { client, fetchJSON } = setup()
|
|
262
|
+
const definition = Q(doctype).getById(folder.id)
|
|
263
|
+
const personal = client.makeObservableQuery(definition)
|
|
264
|
+
const shared = client.makeObservableQuery(definition, {
|
|
265
|
+
driveId: 'drive-a'
|
|
266
|
+
})
|
|
267
|
+
expect(shared.queryId).not.toBe(personal.queryId)
|
|
268
|
+
fetchJSON.mockResolvedValue({ data: folder })
|
|
269
|
+
|
|
270
|
+
await shared.fetch()
|
|
271
|
+
|
|
272
|
+
expect(shared.currentResult().data).toMatchObject({
|
|
273
|
+
_id: folder.id,
|
|
274
|
+
driveId: 'drive-a'
|
|
275
|
+
})
|
|
276
|
+
expect(client.getQueryFromState(personal.queryId).data).toEqual([])
|
|
277
|
+
expect(getCachedIds(client)).toEqual([[], [folder.id], []])
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
it('gives getById queries different cache keys and only updates the matching scope', async () => {
|
|
281
|
+
const { client, fetchJSON } = setup()
|
|
282
|
+
const queries = scopes.map(driveId => {
|
|
283
|
+
const definition = Q(doctype).getById(folder.id)
|
|
284
|
+
return client.makeObservableQuery(
|
|
285
|
+
driveId ? definition.sharingById(driveId) : definition
|
|
286
|
+
)
|
|
287
|
+
})
|
|
288
|
+
expect(queries.map(query => query.queryId)).toEqual([
|
|
289
|
+
`${doctype}/${folder.id}`,
|
|
290
|
+
`${doctype}/drive/drive-a/${folder.id}`,
|
|
291
|
+
`${doctype}/drive/drive-b/${folder.id}`
|
|
292
|
+
])
|
|
293
|
+
fetchJSON.mockResolvedValue({ data: folder })
|
|
294
|
+
|
|
295
|
+
await client.create(
|
|
296
|
+
doctype,
|
|
297
|
+
{ type: 'directory', name: 'New folder', dirId: 'parent-id' },
|
|
298
|
+
undefined,
|
|
299
|
+
{ driveId: 'drive-a', forceLink: 'stack' }
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
expect(
|
|
303
|
+
queries.map(query => query.currentResult().data?._id || null)
|
|
304
|
+
).toEqual([null, folder.id, null])
|
|
305
|
+
const wrongScope = await client.query(Q(doctype).getById(folder.id), {
|
|
306
|
+
as: 'personal-from-store',
|
|
307
|
+
executeFromStore: true
|
|
308
|
+
})
|
|
309
|
+
expect(wrongScope.data).toBe(null)
|
|
310
|
+
})
|
|
311
|
+
})
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import CozyStackClient from '../../cozy-stack-client/src/CozyStackClient'
|
|
2
|
+
import CozyClient from './CozyClient'
|
|
3
|
+
import { HasManyFiles } from './associations'
|
|
4
|
+
import { Q } from './queries/dsl'
|
|
5
|
+
import { dispatchCreate } from './store/realtime'
|
|
6
|
+
|
|
7
|
+
const doctype = 'io.cozy.files'
|
|
8
|
+
const folder = {
|
|
9
|
+
id: 'folder-id',
|
|
10
|
+
type: doctype,
|
|
11
|
+
attributes: {
|
|
12
|
+
name: 'New folder',
|
|
13
|
+
type: 'directory',
|
|
14
|
+
dir_id: 'parent-id'
|
|
15
|
+
},
|
|
16
|
+
meta: { rev: '1-created' }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const setup = async (schema = {}) => {
|
|
20
|
+
const stackClient = new CozyStackClient({ uri: 'https://recipient.example' })
|
|
21
|
+
const client = new CozyClient({ stackClient, schema })
|
|
22
|
+
const fetchJSON = jest.spyOn(stackClient, 'fetchJSON')
|
|
23
|
+
fetchJSON.mockResolvedValue({ data: [], meta: { count: 0 } })
|
|
24
|
+
for (const driveId of [null, 'shared-drive', 'other-drive']) {
|
|
25
|
+
const definition = Q(doctype).where({ dir_id: 'parent-id' })
|
|
26
|
+
await client.query(driveId ? definition.sharingById(driveId) : definition, {
|
|
27
|
+
as: driveId ? `children/${driveId}` : 'children'
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
fetchJSON.mockClear()
|
|
31
|
+
fetchJSON.mockResolvedValue({ data: folder })
|
|
32
|
+
return { client, fetchJSON }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const createDirectory = (client, driveId) =>
|
|
36
|
+
client.create(
|
|
37
|
+
doctype,
|
|
38
|
+
{ type: 'directory', name: 'New folder', dirId: 'parent-id' },
|
|
39
|
+
null,
|
|
40
|
+
{ driveId, forceLink: 'stack', as: 'create-directory' }
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
describe('CozyClient directory creation through StackLink', () => {
|
|
44
|
+
it.each([
|
|
45
|
+
[null, '/files'],
|
|
46
|
+
['shared-drive', '/sharings/drives/shared-drive']
|
|
47
|
+
])(
|
|
48
|
+
'creates a directory in scope %s and updates the cache',
|
|
49
|
+
async (driveId, prefix) => {
|
|
50
|
+
const { client, fetchJSON } = await setup()
|
|
51
|
+
const response = await createDirectory(client, driveId)
|
|
52
|
+
|
|
53
|
+
expect(fetchJSON).toHaveBeenCalledWith(
|
|
54
|
+
'POST',
|
|
55
|
+
`${prefix}/parent-id?Name=New%20folder&Type=directory&MetadataID=`,
|
|
56
|
+
undefined,
|
|
57
|
+
{ headers: { Date: '' } }
|
|
58
|
+
)
|
|
59
|
+
expect(response.data).toMatchObject({
|
|
60
|
+
_id: 'folder-id',
|
|
61
|
+
_type: doctype,
|
|
62
|
+
name: 'New folder',
|
|
63
|
+
dir_id: 'parent-id'
|
|
64
|
+
})
|
|
65
|
+
const queryId = driveId ? `children/${driveId}` : 'children'
|
|
66
|
+
expect(client.getQueryFromState(queryId).data).toEqual([response.data])
|
|
67
|
+
|
|
68
|
+
// Realtime echoes the sender's creation back into the same cache.
|
|
69
|
+
await dispatchCreate(client, doctype, response.data, { driveId })
|
|
70
|
+
expect(client.getQueryFromState(queryId).data).toEqual([response.data])
|
|
71
|
+
const otherQueryId = driveId ? 'children' : 'children/shared-drive'
|
|
72
|
+
expect(client.getQueryFromState(otherQueryId).data).toEqual([])
|
|
73
|
+
expect(client.getQueryFromState('children/other-drive').data).toEqual([])
|
|
74
|
+
expect(fetchJSON).toHaveBeenCalledTimes(1)
|
|
75
|
+
}
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
it('creates a shared-drive directory with save when the document has no revision', async () => {
|
|
79
|
+
const { client, fetchJSON } = await setup()
|
|
80
|
+
|
|
81
|
+
const response = await client.save(
|
|
82
|
+
{
|
|
83
|
+
_type: doctype,
|
|
84
|
+
type: 'directory',
|
|
85
|
+
name: 'New folder',
|
|
86
|
+
dirId: 'parent-id'
|
|
87
|
+
},
|
|
88
|
+
{ driveId: 'shared-drive', forceLink: 'stack' }
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
expect(fetchJSON).toHaveBeenCalledWith(
|
|
92
|
+
'POST',
|
|
93
|
+
'/sharings/drives/shared-drive/parent-id?Name=New%20folder&Type=directory&MetadataID=',
|
|
94
|
+
undefined,
|
|
95
|
+
{ headers: { Date: '' } }
|
|
96
|
+
)
|
|
97
|
+
expect(response.data).toMatchObject({
|
|
98
|
+
_id: 'folder-id',
|
|
99
|
+
_type: doctype,
|
|
100
|
+
type: 'directory',
|
|
101
|
+
dir_id: 'parent-id',
|
|
102
|
+
driveId: 'shared-drive'
|
|
103
|
+
})
|
|
104
|
+
expect(client.getQueryFromState('children/shared-drive').data).toEqual([
|
|
105
|
+
response.data
|
|
106
|
+
])
|
|
107
|
+
expect(client.getQueryFromState('children').data).toEqual([])
|
|
108
|
+
expect(client.getQueryFromState('children/other-drive').data).toEqual([])
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it('keeps shared-drive scope out of a subsequent local creation on the same client', async () => {
|
|
112
|
+
const { client, fetchJSON } = await setup()
|
|
113
|
+
const sharedResponse = await createDirectory(client, 'shared-drive')
|
|
114
|
+
fetchJSON.mockResolvedValue({ data: { ...folder, id: 'local-folder-id' } })
|
|
115
|
+
|
|
116
|
+
const localResponse = await createDirectory(client)
|
|
117
|
+
|
|
118
|
+
expect(fetchJSON.mock.calls.map(call => call[1])).toEqual([
|
|
119
|
+
'/sharings/drives/shared-drive/parent-id?Name=New%20folder&Type=directory&MetadataID=',
|
|
120
|
+
'/files/parent-id?Name=New%20folder&Type=directory&MetadataID='
|
|
121
|
+
])
|
|
122
|
+
expect(sharedResponse.data._id).toBe('folder-id')
|
|
123
|
+
expect(localResponse.data).toMatchObject({
|
|
124
|
+
_id: 'local-folder-id',
|
|
125
|
+
_type: doctype,
|
|
126
|
+
type: 'directory',
|
|
127
|
+
dir_id: 'parent-id'
|
|
128
|
+
})
|
|
129
|
+
expect(client.getQueryFromState('children').data).toEqual([
|
|
130
|
+
localResponse.data
|
|
131
|
+
])
|
|
132
|
+
expect(client.getQueryFromState('children/shared-drive').data).toEqual([
|
|
133
|
+
sharedResponse.data
|
|
134
|
+
])
|
|
135
|
+
expect(client.getQueryFromState('children/other-drive').data).toEqual([])
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('keeps the ordinary data endpoint for non-files created with a drive scope', async () => {
|
|
139
|
+
const { client, fetchJSON } = await setup()
|
|
140
|
+
const todoDoctype = 'io.cozy.todos'
|
|
141
|
+
fetchJSON.mockResolvedValue({ rows: [], total_rows: 0 })
|
|
142
|
+
await client.query(Q(todoDoctype), { as: 'todos' })
|
|
143
|
+
fetchJSON.mockClear()
|
|
144
|
+
fetchJSON.mockResolvedValue({
|
|
145
|
+
data: { _id: 'todo-id', _rev: '1-created', title: 'A task' }
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
const response = await client.create(
|
|
149
|
+
todoDoctype,
|
|
150
|
+
{ title: 'A task' },
|
|
151
|
+
null,
|
|
152
|
+
{
|
|
153
|
+
driveId: 'shared-drive',
|
|
154
|
+
forceLink: 'stack'
|
|
155
|
+
}
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
expect(fetchJSON).toHaveBeenCalledWith('POST', '/data/io.cozy.todos/', {
|
|
159
|
+
title: 'A task',
|
|
160
|
+
cozyMetadata: expect.objectContaining({
|
|
161
|
+
metadataVersion: 1,
|
|
162
|
+
createdAt: expect.any(String)
|
|
163
|
+
})
|
|
164
|
+
})
|
|
165
|
+
expect(response.data).toMatchObject({
|
|
166
|
+
_id: 'todo-id',
|
|
167
|
+
_type: todoDoctype,
|
|
168
|
+
title: 'A task'
|
|
169
|
+
})
|
|
170
|
+
expect(client.getQueryFromState('todos').data).toEqual([response.data])
|
|
171
|
+
expect(client.getQueryFromState('children').data).toEqual([])
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
it.each(['directory', 'file'])(
|
|
175
|
+
'rejects shared-drive %s creation with references before writing',
|
|
176
|
+
async type => {
|
|
177
|
+
const { client, fetchJSON } = await setup({
|
|
178
|
+
files: {
|
|
179
|
+
doctype,
|
|
180
|
+
relationships: {
|
|
181
|
+
icons: { type: HasManyFiles, doctype }
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
})
|
|
185
|
+
fetchJSON
|
|
186
|
+
.mockResolvedValueOnce({ data: folder })
|
|
187
|
+
.mockResolvedValueOnce({ data: [{ id: 'icon-id', type: doctype }] })
|
|
188
|
+
|
|
189
|
+
await expect(
|
|
190
|
+
client.create(
|
|
191
|
+
doctype,
|
|
192
|
+
{ type, name: 'New folder', dirId: 'parent-id', data: 'content' },
|
|
193
|
+
{ icons: [{ _id: 'icon-id', _type: doctype }] },
|
|
194
|
+
{ driveId: 'shared-drive', forceLink: 'stack' }
|
|
195
|
+
)
|
|
196
|
+
).rejects.toThrow(
|
|
197
|
+
'References are not supported when creating files in a shared drive'
|
|
198
|
+
)
|
|
199
|
+
expect(fetchJSON).not.toHaveBeenCalled()
|
|
200
|
+
expect(client.getQueryFromState('children').data).toEqual([])
|
|
201
|
+
}
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
it.each([{}, { icons: [] }])(
|
|
205
|
+
'allows shared-drive creation with empty references %j',
|
|
206
|
+
async references => {
|
|
207
|
+
const { client, fetchJSON } = await setup()
|
|
208
|
+
const response = await client.create(
|
|
209
|
+
doctype,
|
|
210
|
+
{ type: 'directory', name: 'New folder', dirId: 'parent-id' },
|
|
211
|
+
references,
|
|
212
|
+
{ driveId: 'shared-drive', forceLink: 'stack' }
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
expect(fetchJSON.mock.calls[0][1]).toBe(
|
|
216
|
+
'/sharings/drives/shared-drive/parent-id?Name=New%20folder&Type=directory&MetadataID='
|
|
217
|
+
)
|
|
218
|
+
expect(client.getQueryFromState('children/shared-drive').data).toEqual([
|
|
219
|
+
response.data
|
|
220
|
+
])
|
|
221
|
+
expect(client.getQueryFromState('children').data).toEqual([])
|
|
222
|
+
}
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
it('deduplicates a realtime echo arriving before the mutation response', async () => {
|
|
226
|
+
const { client, fetchJSON } = await setup()
|
|
227
|
+
fetchJSON.mockImplementation(async () => {
|
|
228
|
+
await dispatchCreate(
|
|
229
|
+
client,
|
|
230
|
+
doctype,
|
|
231
|
+
{
|
|
232
|
+
_id: folder.id,
|
|
233
|
+
_rev: folder.meta.rev,
|
|
234
|
+
...folder.attributes
|
|
235
|
+
},
|
|
236
|
+
{ driveId: 'shared-drive' }
|
|
237
|
+
)
|
|
238
|
+
return { data: folder }
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
const response = await createDirectory(client, 'shared-drive')
|
|
242
|
+
expect(client.getQueryFromState('children/shared-drive').data).toEqual([
|
|
243
|
+
response.data
|
|
244
|
+
])
|
|
245
|
+
expect(client.getQueryFromState('children').data).toEqual([])
|
|
246
|
+
expect(client.getQueryFromState('children/other-drive').data).toEqual([])
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
it('rejects a failed shared-drive creation without inserting a directory', async () => {
|
|
250
|
+
const { client, fetchJSON } = await setup()
|
|
251
|
+
const error = new Error('Forbidden')
|
|
252
|
+
fetchJSON.mockRejectedValue(error)
|
|
253
|
+
|
|
254
|
+
await expect(createDirectory(client, 'shared-drive')).rejects.toBe(error)
|
|
255
|
+
expect(client.getQueryFromState('children').data).toEqual([])
|
|
256
|
+
})
|
|
257
|
+
})
|