@protontech/drive-sdk 0.12.0 → 0.13.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.
Files changed (124) hide show
  1. package/dist/crypto/driveCrypto.d.ts +1 -0
  2. package/dist/crypto/driveCrypto.js +1 -0
  3. package/dist/crypto/driveCrypto.js.map +1 -1
  4. package/dist/interface/featureFlags.d.ts +2 -1
  5. package/dist/interface/featureFlags.js +1 -0
  6. package/dist/interface/featureFlags.js.map +1 -1
  7. package/dist/interface/httpClient.d.ts +1 -0
  8. package/dist/interface/nodes.d.ts +7 -0
  9. package/dist/interface/nodes.js.map +1 -1
  10. package/dist/internal/apiService/apiService.d.ts +1 -0
  11. package/dist/internal/apiService/apiService.js +16 -7
  12. package/dist/internal/apiService/apiService.js.map +1 -1
  13. package/dist/internal/apiService/apiService.test.js +24 -0
  14. package/dist/internal/apiService/apiService.test.js.map +1 -1
  15. package/dist/internal/apiService/driveTypes.d.ts +110 -101
  16. package/dist/internal/nodes/apiService.d.ts +4 -0
  17. package/dist/internal/nodes/apiService.js +4 -0
  18. package/dist/internal/nodes/apiService.js.map +1 -1
  19. package/dist/internal/nodes/apiService.test.js +8 -0
  20. package/dist/internal/nodes/apiService.test.js.map +1 -1
  21. package/dist/internal/nodes/cryptoService.js +3 -0
  22. package/dist/internal/nodes/cryptoService.js.map +1 -1
  23. package/dist/internal/nodes/extendedAttributes.d.ts +5 -5
  24. package/dist/internal/nodes/extendedAttributes.js +5 -14
  25. package/dist/internal/nodes/extendedAttributes.js.map +1 -1
  26. package/dist/internal/nodes/extendedAttributes.test.js +16 -22
  27. package/dist/internal/nodes/extendedAttributes.test.js.map +1 -1
  28. package/dist/internal/nodes/interface.d.ts +5 -0
  29. package/dist/internal/nodes/nodesManagement.d.ts +3 -3
  30. package/dist/internal/nodes/nodesManagement.js +7 -5
  31. package/dist/internal/nodes/nodesManagement.js.map +1 -1
  32. package/dist/internal/photos/albumsManager.js +1 -0
  33. package/dist/internal/photos/albumsManager.js.map +1 -1
  34. package/dist/internal/photos/nodes.d.ts +1 -1
  35. package/dist/internal/photos/nodes.js +2 -2
  36. package/dist/internal/photos/nodes.js.map +1 -1
  37. package/dist/internal/photos/upload.d.ts +5 -5
  38. package/dist/internal/photos/upload.js +8 -2
  39. package/dist/internal/photos/upload.js.map +1 -1
  40. package/dist/internal/sharingPublic/nodes.d.ts +1 -0
  41. package/dist/internal/upload/apiService.d.ts +45 -1
  42. package/dist/internal/upload/apiService.js +69 -1
  43. package/dist/internal/upload/apiService.js.map +1 -1
  44. package/dist/internal/upload/blockVerifier.d.ts +4 -1
  45. package/dist/internal/upload/blockVerifier.js +5 -0
  46. package/dist/internal/upload/blockVerifier.js.map +1 -1
  47. package/dist/internal/upload/cryptoService.d.ts +2 -2
  48. package/dist/internal/upload/cryptoService.js +1 -3
  49. package/dist/internal/upload/cryptoService.js.map +1 -1
  50. package/dist/internal/upload/fileUploader.d.ts +4 -3
  51. package/dist/internal/upload/fileUploader.js +17 -7
  52. package/dist/internal/upload/fileUploader.js.map +1 -1
  53. package/dist/internal/upload/index.d.ts +3 -3
  54. package/dist/internal/upload/index.js +17 -1
  55. package/dist/internal/upload/index.js.map +1 -1
  56. package/dist/internal/upload/index.test.d.ts +1 -0
  57. package/dist/internal/upload/index.test.js +71 -0
  58. package/dist/internal/upload/index.test.js.map +1 -0
  59. package/dist/internal/upload/interface.d.ts +2 -0
  60. package/dist/internal/upload/manager.d.ts +41 -2
  61. package/dist/internal/upload/manager.js +123 -42
  62. package/dist/internal/upload/manager.js.map +1 -1
  63. package/dist/internal/upload/manager.test.js +267 -0
  64. package/dist/internal/upload/manager.test.js.map +1 -1
  65. package/dist/internal/upload/smallFileUploader.d.ts +83 -0
  66. package/dist/internal/upload/smallFileUploader.js +197 -0
  67. package/dist/internal/upload/smallFileUploader.js.map +1 -0
  68. package/dist/internal/upload/smallFileUploader.test.d.ts +1 -0
  69. package/dist/internal/upload/smallFileUploader.test.js +358 -0
  70. package/dist/internal/upload/smallFileUploader.test.js.map +1 -0
  71. package/dist/internal/upload/streamReader.d.ts +4 -0
  72. package/dist/internal/upload/streamReader.js +37 -0
  73. package/dist/internal/upload/streamReader.js.map +1 -0
  74. package/dist/internal/upload/streamReader.test.d.ts +1 -0
  75. package/dist/internal/upload/streamReader.test.js +90 -0
  76. package/dist/internal/upload/streamReader.test.js.map +1 -0
  77. package/dist/internal/upload/streamUploader.d.ts +6 -0
  78. package/dist/internal/upload/streamUploader.js +3 -3
  79. package/dist/internal/upload/streamUploader.js.map +1 -1
  80. package/dist/internal/upload/telemetry.d.ts +3 -2
  81. package/dist/internal/upload/telemetry.js +3 -0
  82. package/dist/internal/upload/telemetry.js.map +1 -1
  83. package/dist/protonDrivePhotosClient.d.ts +1 -1
  84. package/dist/protonDrivePublicLinkClient.js +3 -1
  85. package/dist/protonDrivePublicLinkClient.js.map +1 -1
  86. package/dist/transformers.d.ts +1 -1
  87. package/dist/transformers.js +1 -0
  88. package/dist/transformers.js.map +1 -1
  89. package/package.json +1 -1
  90. package/src/crypto/driveCrypto.ts +2 -0
  91. package/src/interface/featureFlags.ts +1 -0
  92. package/src/interface/httpClient.ts +1 -0
  93. package/src/interface/nodes.ts +7 -0
  94. package/src/internal/apiService/apiService.test.ts +30 -0
  95. package/src/internal/apiService/apiService.ts +23 -7
  96. package/src/internal/apiService/driveTypes.ts +110 -101
  97. package/src/internal/nodes/apiService.test.ts +9 -0
  98. package/src/internal/nodes/apiService.ts +4 -0
  99. package/src/internal/nodes/cryptoService.ts +11 -1
  100. package/src/internal/nodes/extendedAttributes.test.ts +25 -25
  101. package/src/internal/nodes/extendedAttributes.ts +10 -19
  102. package/src/internal/nodes/interface.ts +5 -0
  103. package/src/internal/nodes/nodesManagement.ts +8 -6
  104. package/src/internal/photos/albumsManager.ts +1 -0
  105. package/src/internal/photos/nodes.ts +2 -2
  106. package/src/internal/photos/upload.ts +23 -10
  107. package/src/internal/upload/apiService.ts +167 -2
  108. package/src/internal/upload/blockVerifier.ts +12 -0
  109. package/src/internal/upload/cryptoService.ts +10 -10
  110. package/src/internal/upload/fileUploader.ts +20 -7
  111. package/src/internal/upload/index.test.ts +99 -0
  112. package/src/internal/upload/index.ts +45 -4
  113. package/src/internal/upload/interface.ts +2 -0
  114. package/src/internal/upload/manager.test.ts +367 -1
  115. package/src/internal/upload/manager.ts +226 -76
  116. package/src/internal/upload/smallFileUploader.test.ts +491 -0
  117. package/src/internal/upload/smallFileUploader.ts +353 -0
  118. package/src/internal/upload/streamReader.test.ts +109 -0
  119. package/src/internal/upload/streamReader.ts +38 -0
  120. package/src/internal/upload/streamUploader.ts +1 -1
  121. package/src/internal/upload/telemetry.ts +5 -1
  122. package/src/protonDrivePhotosClient.ts +1 -1
  123. package/src/protonDrivePublicLinkClient.ts +2 -0
  124. package/src/transformers.ts +2 -0
@@ -1,5 +1,5 @@
1
1
  import { ValidationError } from '../../errors';
2
- import { ProtonDriveTelemetry, UploadMetadata } from '../../interface';
2
+ import { ProtonDriveTelemetry, ThumbnailType, UploadMetadata } from '../../interface';
3
3
  import { getMockTelemetry } from '../../tests/telemetry';
4
4
  import { ErrorCode } from '../apiService';
5
5
  import { UploadAPIService } from './apiService';
@@ -27,6 +27,14 @@ describe('UploadManager', () => {
27
27
  }),
28
28
  deleteDraft: jest.fn(),
29
29
  commitDraftRevision: jest.fn(),
30
+ uploadSmallFile: jest.fn().mockResolvedValue({
31
+ nodeUid: 'uploaded:nodeUid',
32
+ nodeRevisionUid: 'uploaded:nodeRevisionUid',
33
+ }),
34
+ uploadSmallRevision: jest.fn().mockResolvedValue({
35
+ nodeUid: 'revised:nodeUid',
36
+ nodeRevisionUid: 'revised:nodeRevisionUid',
37
+ }),
30
38
  };
31
39
  // @ts-expect-error No need to implement all methods for mocking
32
40
  cryptoService = {
@@ -59,6 +67,12 @@ describe('UploadManager', () => {
59
67
  signatureEmail: 'signatureEmail',
60
68
  armoredExtendedAttributes: 'newNode:armoredExtendedAttributes',
61
69
  }),
70
+ getSigningKeysForExistingNode: jest.fn().mockResolvedValue({
71
+ email: 'signatureEmail',
72
+ addressId: 'addressId',
73
+ nameAndPassphraseSigningKey: {} as any,
74
+ contentSigningKey: {} as any,
75
+ }),
62
76
  };
63
77
  nodesService = {
64
78
  getNode: jest.fn(async (nodeUid: string) => ({
@@ -263,6 +277,358 @@ describe('UploadManager', () => {
263
277
  });
264
278
  });
265
279
 
280
+ describe('generateNewFileCrypto', () => {
281
+ it('should throw when parent is not a folder (no hashKey)', async () => {
282
+ nodesService.getNodeKeys = jest.fn().mockResolvedValue({ hashKey: undefined });
283
+
284
+ const result = manager.generateNewFileCrypto('parentUid', 'fileName');
285
+
286
+ await expect(result).rejects.toThrow('Creating files in non-folders is not allowed');
287
+ expect(nodesService.getNodeKeys).toHaveBeenCalledWith('parentUid');
288
+ expect(cryptoService.generateFileCrypto).not.toHaveBeenCalled();
289
+ });
290
+
291
+ it('should return generated crypto with parentHashKey when parent is folder', async () => {
292
+ const result = await manager.generateNewFileCrypto('parentUid', 'fileName');
293
+
294
+ expect(nodesService.getNodeKeys).toHaveBeenCalledWith('parentUid');
295
+ expect(cryptoService.generateFileCrypto).toHaveBeenCalledWith(
296
+ 'parentUid',
297
+ { key: 'parentNode:nodekey', hashKey: 'parentNode:hashKey' },
298
+ 'fileName',
299
+ );
300
+ expect(result).toMatchObject({
301
+ parentHashKey: 'parentNode:hashKey',
302
+ encryptedNode: { encryptedName: 'newNode:encryptedName', hash: 'newNode:hash' },
303
+ nodeKeys: expect.anything(),
304
+ contentKey: expect.anything(),
305
+ signingKeys: { email: 'signatureEmail' },
306
+ });
307
+ });
308
+ });
309
+
310
+ describe('getExistingFileNodeCrypto', () => {
311
+ it('should throw when node has no active revision', async () => {
312
+ nodesService.getNode = jest.fn().mockResolvedValue({
313
+ uid: 'fileNodeUid',
314
+ parentUid: 'parentUid',
315
+ activeRevision: { ok: false, error: new Error('No revision') },
316
+ });
317
+
318
+ const result = manager.getExistingFileNodeCrypto('fileNodeUid');
319
+
320
+ await expect(result).rejects.toThrow('Creating revisions in non-files is not allowed');
321
+ });
322
+
323
+ it('should throw when nodeKeys has no contentKeyPacketSessionKey', async () => {
324
+ nodesService.getNode = jest.fn().mockResolvedValue({
325
+ uid: 'fileNodeUid',
326
+ parentUid: 'parentUid',
327
+ activeRevision: { ok: true, value: { uid: 'revisionUid' } },
328
+ });
329
+ nodesService.getNodeKeys = jest.fn().mockResolvedValue({
330
+ key: 'nodeKey',
331
+ contentKeyPacket: new Uint8Array([1, 2, 3]),
332
+ hashKey: 'hashKey',
333
+ });
334
+
335
+ const result = manager.getExistingFileNodeCrypto('fileNodeUid');
336
+
337
+ await expect(result).rejects.toThrow('Creating revisions in non-files is not allowed');
338
+ });
339
+
340
+ it('should throw when nodeKeys has no contentKeyPacket', async () => {
341
+ nodesService.getNode = jest.fn().mockResolvedValue({
342
+ uid: 'fileNodeUid',
343
+ parentUid: 'parentUid',
344
+ activeRevision: { ok: true, value: { uid: 'revisionUid' } },
345
+ });
346
+ nodesService.getNodeKeys = jest.fn().mockResolvedValue({
347
+ key: 'nodeKey',
348
+ contentKeyPacketSessionKey: 'sessionKey',
349
+ hashKey: 'hashKey',
350
+ });
351
+
352
+ const result = manager.getExistingFileNodeCrypto('fileNodeUid');
353
+
354
+ await expect(result).rejects.toThrow('Content key packet is required for small revision upload');
355
+ });
356
+
357
+ it('should return key, contentKeyPacket, contentKeyPacketSessionKey and signingKeys', async () => {
358
+ const contentKeyPacket = new Uint8Array([1, 2, 3]);
359
+ nodesService.getNode = jest.fn().mockResolvedValue({
360
+ uid: 'fileNodeUid',
361
+ parentUid: 'parentUid',
362
+ activeRevision: { ok: true, value: { uid: 'revisionUid' } },
363
+ });
364
+ nodesService.getNodeKeys = jest.fn().mockResolvedValue({
365
+ key: 'nodeKey',
366
+ contentKeyPacket,
367
+ contentKeyPacketSessionKey: 'sessionKey',
368
+ hashKey: 'hashKey',
369
+ });
370
+
371
+ const result = await manager.getExistingFileNodeCrypto('fileNodeUid');
372
+
373
+ expect(cryptoService.getSigningKeysForExistingNode).toHaveBeenCalledWith({
374
+ nodeUid: 'fileNodeUid',
375
+ parentNodeUid: 'parentUid',
376
+ });
377
+ expect(result).toEqual({
378
+ key: 'nodeKey',
379
+ contentKeyPacket,
380
+ contentKeyPacketSessionKey: 'sessionKey',
381
+ signingKeys: {
382
+ email: 'signatureEmail',
383
+ addressId: 'addressId',
384
+ nameAndPassphraseSigningKey: {},
385
+ contentSigningKey: {},
386
+ },
387
+ });
388
+ });
389
+ });
390
+
391
+ describe('uploadFile', () => {
392
+ const nodeCrypto = {
393
+ encryptedNode: { encryptedName: 'encName', hash: 'hash' },
394
+ nodeKeys: {
395
+ encrypted: {
396
+ armoredKey: 'armoredKey',
397
+ armoredPassphrase: 'armoredPassphrase',
398
+ armoredPassphraseSignature: 'armoredPassphraseSignature',
399
+ },
400
+ },
401
+ contentKey: {
402
+ encrypted: {
403
+ base64ContentKeyPacket: 'base64ContentKeyPacket',
404
+ armoredContentKeyPacketSignature: 'armoredContentKeyPacketSignature',
405
+ },
406
+ },
407
+ signingKeys: { email: 'signatureEmail' },
408
+ } as any;
409
+ const metadata = { mediaType: 'application/octet-stream', expectedSize: 100 } as UploadMetadata;
410
+ const commitPayload = {
411
+ armoredManifestSignature: 'manifestSignature',
412
+ armoredExtendedAttributes: 'extAttr',
413
+ };
414
+ const encryptedBlock = {
415
+ encryptedData: new Uint8Array([1, 2, 3]),
416
+ armoredSignature: 'blockSig',
417
+ verificationToken: new Uint8Array([4, 5, 6]),
418
+ };
419
+ const encryptedThumbnails = [{ type: ThumbnailType.Type1, encryptedData: new Uint8Array([7, 8, 9]) }];
420
+
421
+ it('should call uploadSmallFile and notifyChildCreated on success', async () => {
422
+ const result = await manager.uploadFile(
423
+ 'parentUid',
424
+ nodeCrypto,
425
+ metadata,
426
+ commitPayload,
427
+ encryptedBlock,
428
+ encryptedThumbnails,
429
+ );
430
+
431
+ expect(result).toEqual({
432
+ nodeUid: 'uploaded:nodeUid',
433
+ nodeRevisionUid: 'uploaded:nodeRevisionUid',
434
+ });
435
+ expect(apiService.uploadSmallFile).toHaveBeenCalledWith(
436
+ 'parentUid',
437
+ {
438
+ armoredEncryptedName: 'encName',
439
+ hash: 'hash',
440
+ mediaType: 'application/octet-stream',
441
+ armoredNodeKey: 'armoredKey',
442
+ armoredNodePassphrase: 'armoredPassphrase',
443
+ armoredNodePassphraseSignature: 'armoredPassphraseSignature',
444
+ base64ContentKeyPacket: 'base64ContentKeyPacket',
445
+ armoredContentKeyPacketSignature: 'armoredContentKeyPacketSignature',
446
+ armoredExtendedAttributes: 'extAttr',
447
+ signatureEmail: 'signatureEmail',
448
+ },
449
+ {
450
+ armoredManifestSignature: 'manifestSignature',
451
+ block: encryptedBlock,
452
+ thumbnails: encryptedThumbnails,
453
+ },
454
+ undefined,
455
+ );
456
+ expect(nodesService.notifyChildCreated).toHaveBeenCalledWith('parentUid');
457
+ });
458
+
459
+ it('should delete existing draft and retry on ALREADY_EXISTS when own draft', async () => {
460
+ let firstCall = true;
461
+ apiService.uploadSmallFile = jest.fn().mockImplementation(() => {
462
+ if (firstCall) {
463
+ firstCall = false;
464
+ throw new ValidationError('Already exists', ErrorCode.ALREADY_EXISTS, {
465
+ ConflictLinkID: 'existingLinkId',
466
+ ConflictDraftRevisionID: 'existingDraftRevisionId',
467
+ ConflictDraftClientUID: clientUid,
468
+ });
469
+ }
470
+ return {
471
+ nodeUid: 'uploaded:nodeUid',
472
+ nodeRevisionUid: 'uploaded:nodeRevisionUid',
473
+ };
474
+ });
475
+
476
+ const result = await manager.uploadFile(
477
+ 'volumeId~parentUid',
478
+ nodeCrypto,
479
+ { ...metadata, overrideExistingDraftByOtherClient: false },
480
+ commitPayload,
481
+ encryptedBlock,
482
+ encryptedThumbnails,
483
+ );
484
+
485
+ expect(result).toEqual({
486
+ nodeUid: 'uploaded:nodeUid',
487
+ nodeRevisionUid: 'uploaded:nodeRevisionUid',
488
+ });
489
+ expect(apiService.deleteDraft).toHaveBeenCalledWith('volumeId~existingLinkId');
490
+ expect(apiService.uploadSmallFile).toHaveBeenCalledTimes(2);
491
+ });
492
+
493
+ it('should call uploadSmallFile with block undefined for zero-byte file', async () => {
494
+ const result = await manager.uploadFile(
495
+ 'parentUid',
496
+ nodeCrypto,
497
+ { ...metadata, expectedSize: 0 },
498
+ commitPayload,
499
+ undefined,
500
+ [],
501
+ );
502
+
503
+ expect(result).toEqual({
504
+ nodeUid: 'uploaded:nodeUid',
505
+ nodeRevisionUid: 'uploaded:nodeRevisionUid',
506
+ });
507
+ expect(apiService.uploadSmallFile).toHaveBeenCalledWith(
508
+ 'parentUid',
509
+ expect.objectContaining({
510
+ armoredEncryptedName: 'encName',
511
+ hash: 'hash',
512
+ mediaType: 'application/octet-stream',
513
+ armoredExtendedAttributes: 'extAttr',
514
+ signatureEmail: 'signatureEmail',
515
+ }),
516
+ {
517
+ armoredManifestSignature: 'manifestSignature',
518
+ block: undefined,
519
+ thumbnails: [],
520
+ },
521
+ undefined,
522
+ );
523
+ expect(nodesService.notifyChildCreated).toHaveBeenCalledWith('parentUid');
524
+ });
525
+ });
526
+
527
+ describe('uploadSmallRevision', () => {
528
+ const nodeCrypto = { signingKeys: { email: 'signatureEmail' } } as any;
529
+ const commitPayload = {
530
+ armoredManifestSignature: 'manifestSig',
531
+ armoredExtendedAttributes: 'extAttr',
532
+ };
533
+ const encryptedBlock = {
534
+ encryptedData: new Uint8Array([1, 2, 3]),
535
+ armoredSignature: 'blockSig',
536
+ verificationToken: new Uint8Array([4, 5, 6]),
537
+ };
538
+ const encryptedThumbnails = [{ type: ThumbnailType.Type1, encryptedData: new Uint8Array([7, 8, 9]) }];
539
+
540
+ it('should throw when file has no revision', async () => {
541
+ nodesService.getNode = jest.fn().mockResolvedValue({
542
+ uid: 'fileNodeUid',
543
+ parentUid: 'parentUid',
544
+ activeRevision: { ok: false, error: new Error('No revision') },
545
+ });
546
+
547
+ const result = manager.uploadSmallRevision(
548
+ 'fileNodeUid',
549
+ nodeCrypto,
550
+ commitPayload,
551
+ encryptedBlock,
552
+ encryptedThumbnails,
553
+ );
554
+
555
+ await expect(result).rejects.toThrow('File has no revision');
556
+ expect(apiService.uploadSmallRevision).not.toHaveBeenCalled();
557
+ });
558
+
559
+ it('should call uploadSmallRevision and notifyNodeChanged on success', async () => {
560
+ nodesService.getNode = jest.fn().mockResolvedValue({
561
+ uid: 'fileNodeUid',
562
+ parentUid: 'parentUid',
563
+ activeRevision: { ok: true, value: { uid: 'currentRevisionUid' } },
564
+ });
565
+
566
+ const result = await manager.uploadSmallRevision(
567
+ 'fileNodeUid',
568
+ nodeCrypto,
569
+ commitPayload,
570
+ encryptedBlock,
571
+ encryptedThumbnails,
572
+ );
573
+
574
+ expect(result).toEqual({
575
+ nodeUid: 'revised:nodeUid',
576
+ nodeRevisionUid: 'revised:nodeRevisionUid',
577
+ });
578
+ expect(apiService.uploadSmallRevision).toHaveBeenCalledWith(
579
+ 'fileNodeUid',
580
+ 'currentRevisionUid',
581
+ {
582
+ signatureEmail: 'signatureEmail',
583
+ armoredExtendedAttributes: 'extAttr',
584
+ },
585
+ {
586
+ armoredManifestSignature: 'manifestSig',
587
+ block: encryptedBlock,
588
+ thumbnails: encryptedThumbnails,
589
+ },
590
+ undefined,
591
+ );
592
+ expect(nodesService.notifyNodeChanged).toHaveBeenCalledWith('fileNodeUid');
593
+ });
594
+
595
+ it('should call uploadSmallRevision with block undefined for zero-byte revision', async () => {
596
+ nodesService.getNode = jest.fn().mockResolvedValue({
597
+ uid: 'fileNodeUid',
598
+ parentUid: 'parentUid',
599
+ activeRevision: { ok: true, value: { uid: 'currentRevisionUid' } },
600
+ });
601
+
602
+ const result = await manager.uploadSmallRevision(
603
+ 'fileNodeUid',
604
+ nodeCrypto,
605
+ commitPayload,
606
+ undefined,
607
+ [],
608
+ );
609
+
610
+ expect(result).toEqual({
611
+ nodeUid: 'revised:nodeUid',
612
+ nodeRevisionUid: 'revised:nodeRevisionUid',
613
+ });
614
+ expect(apiService.uploadSmallRevision).toHaveBeenCalledWith(
615
+ 'fileNodeUid',
616
+ 'currentRevisionUid',
617
+ {
618
+ signatureEmail: 'signatureEmail',
619
+ armoredExtendedAttributes: 'extAttr',
620
+ },
621
+ {
622
+ armoredManifestSignature: 'manifestSig',
623
+ block: undefined,
624
+ thumbnails: [],
625
+ },
626
+ undefined,
627
+ );
628
+ expect(nodesService.notifyNodeChanged).toHaveBeenCalledWith('fileNodeUid');
629
+ });
630
+ });
631
+
266
632
  describe('commit draft', () => {
267
633
  const nodeRevisionDraft = {
268
634
  nodeUid: 'newNode:nodeUid',