cozy-sharing 33.3.0 → 33.3.2
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/CHANGELOG.md +12 -0
- package/dist/SharingProvider.js +29 -42
- package/dist/SharingProvider.spec.js +71 -43
- package/dist/components/ShareRestrictionModal/helpers.js +21 -3
- package/dist/components/ShareRestrictionModal/helpers.spec.js +87 -13
- package/package.json +4 -4
- package/src/SharingProvider.jsx +10 -27
- package/src/SharingProvider.spec.jsx +45 -33
- package/src/components/ShareRestrictionModal/helpers.js +23 -3
- package/src/components/ShareRestrictionModal/helpers.spec.js +69 -3
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,18 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [33.3.2](https://github.com/cozy/cozy-libs/compare/cozy-sharing@33.3.1...cozy-sharing@33.3.2) (2026-06-11)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
- **cozy-sharing:** Use readonly endpoint in updateSharingMemberType ([f4914b6](https://github.com/cozy/cozy-libs/commit/f4914b61daaf5c20b17c4b9f1ddda511860a56ac))
|
|
11
|
+
|
|
12
|
+
## [33.3.1](https://github.com/cozy/cozy-libs/compare/cozy-sharing@33.3.0...cozy-sharing@33.3.1) (2026-06-10)
|
|
13
|
+
|
|
14
|
+
### Bug Fixes
|
|
15
|
+
|
|
16
|
+
- **cozy-sharing:** Expire share links at the end of the selected day ([db81ed9](https://github.com/cozy/cozy-libs/commit/db81ed936a15850f278404511c1b6faec0fc7a54))
|
|
17
|
+
|
|
6
18
|
# [33.3.0](https://github.com/cozy/cozy-libs/compare/cozy-sharing@33.2.2...cozy-sharing@33.3.0) (2026-06-10)
|
|
7
19
|
|
|
8
20
|
### Features
|
package/dist/SharingProvider.js
CHANGED
|
@@ -471,7 +471,7 @@ export var SharingProvider = /*#__PURE__*/function (_Component) {
|
|
|
471
471
|
*/
|
|
472
472
|
_defineProperty(_this2, "updateSharingMemberType", /*#__PURE__*/function () {
|
|
473
473
|
var _ref12 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee10(sharingId, memberIndex, newType) {
|
|
474
|
-
var sharing, member,
|
|
474
|
+
var sharing, member, makeReadOnly;
|
|
475
475
|
return _regeneratorRuntime.wrap(function _callee10$(_context10) {
|
|
476
476
|
while (1) switch (_context10.prev = _context10.next) {
|
|
477
477
|
case 0:
|
|
@@ -495,62 +495,49 @@ export var SharingProvider = /*#__PURE__*/function (_Component) {
|
|
|
495
495
|
throw new Error('Member not found');
|
|
496
496
|
|
|
497
497
|
case 6:
|
|
498
|
-
|
|
499
|
-
return _this2.props.client.collection('io.cozy.contacts').find({
|
|
500
|
-
email: {
|
|
501
|
-
$elemMatch: {
|
|
502
|
-
address: member.email
|
|
503
|
-
}
|
|
504
|
-
},
|
|
505
|
-
_id: {
|
|
506
|
-
$gt: null
|
|
507
|
-
}
|
|
508
|
-
}, {
|
|
509
|
-
indexedFields: ['_id']
|
|
510
|
-
});
|
|
498
|
+
makeReadOnly = newType === 'one-way';
|
|
511
499
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
if (matchedContacts.data.length) {
|
|
516
|
-
_context10.next = 11;
|
|
500
|
+
if (!(newType !== 'one-way' && newType !== 'two-way')) {
|
|
501
|
+
_context10.next = 9;
|
|
517
502
|
break;
|
|
518
503
|
}
|
|
519
504
|
|
|
520
|
-
throw new Error(
|
|
505
|
+
throw new Error('Unsupported sharing type: ' + newType);
|
|
521
506
|
|
|
522
|
-
case
|
|
523
|
-
|
|
507
|
+
case 9:
|
|
508
|
+
_context10.prev = 9;
|
|
524
509
|
|
|
525
|
-
|
|
526
|
-
|
|
510
|
+
if (!makeReadOnly) {
|
|
511
|
+
_context10.next = 15;
|
|
512
|
+
break;
|
|
513
|
+
}
|
|
527
514
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
readOnly = newType === 'one-way';
|
|
531
|
-
_context10.prev = 15;
|
|
532
|
-
_context10.next = 18;
|
|
533
|
-
return _this2.addRecipients({
|
|
534
|
-
document: sharing,
|
|
535
|
-
recipients: readOnly ? [] : [contact],
|
|
536
|
-
readOnlyRecipients: readOnly ? [contact] : []
|
|
537
|
-
});
|
|
515
|
+
_context10.next = 13;
|
|
516
|
+
return _this2.sharingCol.setReadOnly(sharing, memberIndex);
|
|
538
517
|
|
|
539
|
-
case
|
|
540
|
-
_context10.next =
|
|
518
|
+
case 13:
|
|
519
|
+
_context10.next = 17;
|
|
541
520
|
break;
|
|
542
521
|
|
|
543
|
-
case
|
|
544
|
-
_context10.
|
|
545
|
-
|
|
546
|
-
|
|
522
|
+
case 15:
|
|
523
|
+
_context10.next = 17;
|
|
524
|
+
return _this2.sharingCol.setReadWrite(sharing, memberIndex);
|
|
525
|
+
|
|
526
|
+
case 17:
|
|
527
|
+
_context10.next = 23;
|
|
528
|
+
break;
|
|
529
|
+
|
|
530
|
+
case 19:
|
|
531
|
+
_context10.prev = 19;
|
|
532
|
+
_context10.t0 = _context10["catch"](9);
|
|
533
|
+
log.error("Failed to change member ".concat(member.email, " permission type to ").concat(newType), _context10.t0);
|
|
547
534
|
throw _context10.t0;
|
|
548
535
|
|
|
549
|
-
case
|
|
536
|
+
case 23:
|
|
550
537
|
case "end":
|
|
551
538
|
return _context10.stop();
|
|
552
539
|
}
|
|
553
|
-
}, _callee10, null, [[
|
|
540
|
+
}, _callee10, null, [[9, 19]]);
|
|
554
541
|
}));
|
|
555
542
|
|
|
556
543
|
return function (_x16, _x17, _x18) {
|
|
@@ -412,12 +412,6 @@ describe('updateDocumentPermissions', function () {
|
|
|
412
412
|
})));
|
|
413
413
|
});
|
|
414
414
|
describe('updateSharingMemberType', function () {
|
|
415
|
-
var mockContact = {
|
|
416
|
-
_id: 'contact-1',
|
|
417
|
-
email: [{
|
|
418
|
-
address: 'bob@bob.cozy'
|
|
419
|
-
}]
|
|
420
|
-
};
|
|
421
415
|
var mockSharing = {
|
|
422
416
|
id: 'sharing-123',
|
|
423
417
|
type: 'io.cozy.sharings',
|
|
@@ -432,20 +426,14 @@ describe('updateSharingMemberType', function () {
|
|
|
432
426
|
}
|
|
433
427
|
};
|
|
434
428
|
var instance;
|
|
435
|
-
var mockContactsCollection;
|
|
436
429
|
beforeEach(function () {
|
|
437
|
-
mockContactsCollection = {
|
|
438
|
-
find: jest.fn().mockResolvedValue({
|
|
439
|
-
data: [mockContact]
|
|
440
|
-
})
|
|
441
|
-
};
|
|
442
430
|
var mockClient = {
|
|
443
431
|
getStackClient: function getStackClient() {
|
|
444
432
|
return {
|
|
445
433
|
uri: 'http://cozy.local'
|
|
446
434
|
};
|
|
447
435
|
},
|
|
448
|
-
collection: jest.fn().mockReturnValue(
|
|
436
|
+
collection: jest.fn().mockReturnValue({})
|
|
449
437
|
};
|
|
450
438
|
instance = new SharingProvider({
|
|
451
439
|
client: mockClient
|
|
@@ -454,9 +442,10 @@ describe('updateSharingMemberType', function () {
|
|
|
454
442
|
sharings: [mockSharing]
|
|
455
443
|
});
|
|
456
444
|
instance.sharingCol = {
|
|
457
|
-
|
|
445
|
+
setReadOnly: jest.fn().mockResolvedValue({}),
|
|
446
|
+
setReadWrite: jest.fn().mockResolvedValue({})
|
|
458
447
|
};
|
|
459
|
-
jest.spyOn(instance, '
|
|
448
|
+
jest.spyOn(instance, 'setState');
|
|
460
449
|
});
|
|
461
450
|
it('should throw when sharing is not found', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee8() {
|
|
462
451
|
return _regeneratorRuntime.wrap(function _callee8$(_context8) {
|
|
@@ -484,36 +473,33 @@ describe('updateSharingMemberType', function () {
|
|
|
484
473
|
}
|
|
485
474
|
}, _callee9);
|
|
486
475
|
})));
|
|
487
|
-
it('should
|
|
476
|
+
it('should call setReadOnly when switching to one-way', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee10() {
|
|
488
477
|
return _regeneratorRuntime.wrap(function _callee10$(_context10) {
|
|
489
478
|
while (1) switch (_context10.prev = _context10.next) {
|
|
490
479
|
case 0:
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
});
|
|
494
|
-
_context10.next = 3;
|
|
495
|
-
return expect(instance.updateSharingMemberType('sharing-123', 1, 'one-way')).rejects.toThrow('Contact not found');
|
|
480
|
+
_context10.next = 2;
|
|
481
|
+
return instance.updateSharingMemberType('sharing-123', 1, 'one-way');
|
|
496
482
|
|
|
497
|
-
case
|
|
483
|
+
case 2:
|
|
484
|
+
expect(instance.sharingCol.setReadOnly).toHaveBeenCalledWith(mockSharing, 1);
|
|
485
|
+
expect(instance.sharingCol.setReadWrite).not.toHaveBeenCalled();
|
|
486
|
+
|
|
487
|
+
case 4:
|
|
498
488
|
case "end":
|
|
499
489
|
return _context10.stop();
|
|
500
490
|
}
|
|
501
491
|
}, _callee10);
|
|
502
492
|
})));
|
|
503
|
-
it('should
|
|
493
|
+
it('should call setReadWrite when switching to two-way', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee11() {
|
|
504
494
|
return _regeneratorRuntime.wrap(function _callee11$(_context11) {
|
|
505
495
|
while (1) switch (_context11.prev = _context11.next) {
|
|
506
496
|
case 0:
|
|
507
497
|
_context11.next = 2;
|
|
508
|
-
return instance.updateSharingMemberType('sharing-123', 1, '
|
|
498
|
+
return instance.updateSharingMemberType('sharing-123', 1, 'two-way');
|
|
509
499
|
|
|
510
500
|
case 2:
|
|
511
|
-
expect(instance.sharingCol.
|
|
512
|
-
expect(instance.
|
|
513
|
-
document: mockSharing,
|
|
514
|
-
recipients: [],
|
|
515
|
-
readOnlyRecipients: [mockContact]
|
|
516
|
-
});
|
|
501
|
+
expect(instance.sharingCol.setReadWrite).toHaveBeenCalledWith(mockSharing, 1);
|
|
502
|
+
expect(instance.sharingCol.setReadOnly).not.toHaveBeenCalled();
|
|
517
503
|
|
|
518
504
|
case 4:
|
|
519
505
|
case "end":
|
|
@@ -521,34 +507,31 @@ describe('updateSharingMemberType', function () {
|
|
|
521
507
|
}
|
|
522
508
|
}, _callee11);
|
|
523
509
|
})));
|
|
524
|
-
it('should
|
|
510
|
+
it('should not dispatch a state update — realtime refreshes the store', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee12() {
|
|
525
511
|
return _regeneratorRuntime.wrap(function _callee12$(_context12) {
|
|
526
512
|
while (1) switch (_context12.prev = _context12.next) {
|
|
527
513
|
case 0:
|
|
528
514
|
_context12.next = 2;
|
|
529
|
-
return instance.updateSharingMemberType('sharing-123', 1, '
|
|
515
|
+
return instance.updateSharingMemberType('sharing-123', 1, 'one-way');
|
|
530
516
|
|
|
531
517
|
case 2:
|
|
532
|
-
expect(instance.
|
|
533
|
-
expect(instance.addRecipients).toHaveBeenCalledWith({
|
|
534
|
-
document: mockSharing,
|
|
535
|
-
recipients: [mockContact],
|
|
536
|
-
readOnlyRecipients: []
|
|
537
|
-
});
|
|
518
|
+
expect(instance.setState).not.toHaveBeenCalled();
|
|
538
519
|
|
|
539
|
-
case
|
|
520
|
+
case 3:
|
|
540
521
|
case "end":
|
|
541
522
|
return _context12.stop();
|
|
542
523
|
}
|
|
543
524
|
}, _callee12);
|
|
544
525
|
})));
|
|
545
|
-
it('should
|
|
526
|
+
it('should not dispatch a state update for two-way — realtime refreshes the store', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee13() {
|
|
546
527
|
return _regeneratorRuntime.wrap(function _callee13$(_context13) {
|
|
547
528
|
while (1) switch (_context13.prev = _context13.next) {
|
|
548
529
|
case 0:
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
530
|
+
_context13.next = 2;
|
|
531
|
+
return instance.updateSharingMemberType('sharing-123', 1, 'two-way');
|
|
532
|
+
|
|
533
|
+
case 2:
|
|
534
|
+
expect(instance.setState).not.toHaveBeenCalled();
|
|
552
535
|
|
|
553
536
|
case 3:
|
|
554
537
|
case "end":
|
|
@@ -556,6 +539,51 @@ describe('updateSharingMemberType', function () {
|
|
|
556
539
|
}
|
|
557
540
|
}, _callee13);
|
|
558
541
|
})));
|
|
542
|
+
it('should rethrow error if setReadOnly fails', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee14() {
|
|
543
|
+
return _regeneratorRuntime.wrap(function _callee14$(_context14) {
|
|
544
|
+
while (1) switch (_context14.prev = _context14.next) {
|
|
545
|
+
case 0:
|
|
546
|
+
instance.sharingCol.setReadOnly.mockRejectedValue(new Error('Network error'));
|
|
547
|
+
_context14.next = 3;
|
|
548
|
+
return expect(instance.updateSharingMemberType('sharing-123', 1, 'one-way')).rejects.toThrow('Network error');
|
|
549
|
+
|
|
550
|
+
case 3:
|
|
551
|
+
case "end":
|
|
552
|
+
return _context14.stop();
|
|
553
|
+
}
|
|
554
|
+
}, _callee14);
|
|
555
|
+
})));
|
|
556
|
+
it('should rethrow error if setReadWrite fails', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee15() {
|
|
557
|
+
return _regeneratorRuntime.wrap(function _callee15$(_context15) {
|
|
558
|
+
while (1) switch (_context15.prev = _context15.next) {
|
|
559
|
+
case 0:
|
|
560
|
+
instance.sharingCol.setReadWrite.mockRejectedValue(new Error('Network error'));
|
|
561
|
+
_context15.next = 3;
|
|
562
|
+
return expect(instance.updateSharingMemberType('sharing-123', 1, 'two-way')).rejects.toThrow('Network error');
|
|
563
|
+
|
|
564
|
+
case 3:
|
|
565
|
+
case "end":
|
|
566
|
+
return _context15.stop();
|
|
567
|
+
}
|
|
568
|
+
}, _callee15);
|
|
569
|
+
})));
|
|
570
|
+
it('should not dispatch state update if API call fails', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee16() {
|
|
571
|
+
return _regeneratorRuntime.wrap(function _callee16$(_context16) {
|
|
572
|
+
while (1) switch (_context16.prev = _context16.next) {
|
|
573
|
+
case 0:
|
|
574
|
+
instance.sharingCol.setReadOnly.mockRejectedValue(new Error('Network error'));
|
|
575
|
+
_context16.next = 3;
|
|
576
|
+
return expect(instance.updateSharingMemberType('sharing-123', 1, 'one-way')).rejects.toThrow('Network error');
|
|
577
|
+
|
|
578
|
+
case 3:
|
|
579
|
+
expect(instance.setState).not.toHaveBeenCalled();
|
|
580
|
+
|
|
581
|
+
case 4:
|
|
582
|
+
case "end":
|
|
583
|
+
return _context16.stop();
|
|
584
|
+
}
|
|
585
|
+
}, _callee16);
|
|
586
|
+
})));
|
|
559
587
|
}); // TODO Convert with react-testing-library
|
|
560
588
|
// describe('hasWriteAccess', () => {
|
|
561
589
|
// it('tells if a doc is writtable', () => {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
|
|
2
2
|
import _regeneratorRuntime from "@babel/runtime/regenerator";
|
|
3
|
+
import { endOfDay } from 'date-fns';
|
|
3
4
|
import { generateWebLink } from 'cozy-client';
|
|
4
5
|
import minilog from 'cozy-minilog';
|
|
5
6
|
import { getAppSlugFromDocumentType } from '../../helpers/link';
|
|
@@ -7,6 +8,21 @@ import { getShortcode } from '../../helpers/shortcodes';
|
|
|
7
8
|
var log = minilog('ShareRestrictionModal/helpers');
|
|
8
9
|
export var WRITE_PERMS = ['GET', 'POST', 'PUT', 'PATCH'];
|
|
9
10
|
export var READ_ONLY_PERMS = ['GET'];
|
|
11
|
+
/**
|
|
12
|
+
* A share link expires at the END of the selected day, so picking today keeps
|
|
13
|
+
* the link reachable for the rest of today and expires it the next day.
|
|
14
|
+
* Without this, the picked day defaults to its midnight (start of day), which
|
|
15
|
+
* is already in the past and makes the link unreachable as soon as it is set.
|
|
16
|
+
* @param {Date|string|null|undefined} date - The picked expiration day
|
|
17
|
+
* @returns {Date|null|undefined} - The same value normalized to end of day
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export var toExpirationDate = function toExpirationDate(date) {
|
|
21
|
+
if (!date) return date;
|
|
22
|
+
var parsed = date instanceof Date ? date : new Date(date);
|
|
23
|
+
if (parsed.toString() === 'Invalid Date') return date;
|
|
24
|
+
return endOfDay(parsed);
|
|
25
|
+
};
|
|
10
26
|
/**
|
|
11
27
|
* Create a sharing link for a file with specified options
|
|
12
28
|
* @param {object} options
|
|
@@ -250,7 +266,8 @@ export var makeTTL = function makeTTL(selectedDate) {
|
|
|
250
266
|
|
|
251
267
|
if (selectedDateConverted instanceof Date) {
|
|
252
268
|
var now = new Date();
|
|
253
|
-
var
|
|
269
|
+
var expiration = toExpirationDate(selectedDateConverted);
|
|
270
|
+
var ttl = expiration > now ? "".concat(Math.round((expiration - now) / 1000), "s") : undefined;
|
|
254
271
|
return ttl;
|
|
255
272
|
}
|
|
256
273
|
|
|
@@ -326,13 +343,14 @@ export var createPermissions = /*#__PURE__*/function () {
|
|
|
326
343
|
|
|
327
344
|
export var updatePermissions = /*#__PURE__*/function () {
|
|
328
345
|
var _ref9 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee4(_ref8) {
|
|
329
|
-
var file, t, dateToggle, selectedDate, passwordToggle, password, editingRights, documentType, updateDocumentPermissions, showAlert, expiresAt, ensurePassword, verbs;
|
|
346
|
+
var file, t, dateToggle, selectedDate, passwordToggle, password, editingRights, documentType, updateDocumentPermissions, showAlert, _toExpirationDate, expiresAt, ensurePassword, verbs;
|
|
347
|
+
|
|
330
348
|
return _regeneratorRuntime.wrap(function _callee4$(_context4) {
|
|
331
349
|
while (1) switch (_context4.prev = _context4.next) {
|
|
332
350
|
case 0:
|
|
333
351
|
file = _ref8.file, t = _ref8.t, dateToggle = _ref8.dateToggle, selectedDate = _ref8.selectedDate, passwordToggle = _ref8.passwordToggle, password = _ref8.password, editingRights = _ref8.editingRights, documentType = _ref8.documentType, updateDocumentPermissions = _ref8.updateDocumentPermissions, showAlert = _ref8.showAlert;
|
|
334
352
|
_context4.prev = 1;
|
|
335
|
-
expiresAt = dateToggle ? (selectedDate === null ||
|
|
353
|
+
expiresAt = dateToggle ? ((_toExpirationDate = toExpirationDate(selectedDate)) === null || _toExpirationDate === void 0 ? void 0 : _toExpirationDate.toISOString()) || undefined : '';
|
|
336
354
|
ensurePassword = passwordToggle ? password || undefined : '';
|
|
337
355
|
verbs = editingRights === 'readOnly' ? READ_ONLY_PERMS : WRITE_PERMS;
|
|
338
356
|
return _context4.abrupt("return", updateDocumentPermissions(file, {
|
|
@@ -1,7 +1,23 @@
|
|
|
1
1
|
import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
|
|
2
2
|
import _regeneratorRuntime from "@babel/runtime/regenerator";
|
|
3
|
-
import {
|
|
3
|
+
import { endOfDay } from 'date-fns';
|
|
4
|
+
import { makeTTL, revokePermissions, toExpirationDate, updatePermissions } from './helpers';
|
|
4
5
|
describe('ShareRestrictionModal/helpers', function () {
|
|
6
|
+
describe('toExpirationDate', function () {
|
|
7
|
+
it('returns falsy values unchanged', function () {
|
|
8
|
+
expect(toExpirationDate(null)).toBeNull();
|
|
9
|
+
expect(toExpirationDate(undefined)).toBeUndefined();
|
|
10
|
+
expect(toExpirationDate('')).toBe('');
|
|
11
|
+
});
|
|
12
|
+
it('normalizes a Date to the end of its day', function () {
|
|
13
|
+
var date = new Date(2100, 0, 1, 9, 30);
|
|
14
|
+
expect(toExpirationDate(date)).toEqual(endOfDay(date));
|
|
15
|
+
});
|
|
16
|
+
it('accepts an ISO string', function () {
|
|
17
|
+
var iso = '2100-01-01T09:30:00.000Z';
|
|
18
|
+
expect(toExpirationDate(iso)).toEqual(endOfDay(new Date(iso)));
|
|
19
|
+
});
|
|
20
|
+
});
|
|
5
21
|
describe('makeTTL', function () {
|
|
6
22
|
it('sould return undefined', function () {
|
|
7
23
|
expect(makeTTL()).toBeUndefined();
|
|
@@ -15,6 +31,18 @@ describe('ShareRestrictionModal/helpers', function () {
|
|
|
15
31
|
expect(makeTTL(new Date('2100-01-01T00:00:00.000Z'))).toBeDefined();
|
|
16
32
|
expect(makeTTL('2100-01-01T00:00:00.000Z')).toBeDefined();
|
|
17
33
|
});
|
|
34
|
+
it('keeps a link picked for today valid until the end of the day', function () {
|
|
35
|
+
// Regression: picking "today" used to resolve to midnight (already past),
|
|
36
|
+
// which dropped the TTL and the link never expired or was unreachable.
|
|
37
|
+
jest.useFakeTimers();
|
|
38
|
+
jest.setSystemTime(new Date('2100-01-01T12:00:00.000Z'));
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
expect(makeTTL(new Date())).toBeDefined();
|
|
42
|
+
} finally {
|
|
43
|
+
jest.useRealTimers();
|
|
44
|
+
}
|
|
45
|
+
});
|
|
18
46
|
});
|
|
19
47
|
describe('updatePermissions', function () {
|
|
20
48
|
var documentType = 'Files';
|
|
@@ -163,7 +191,7 @@ describe('ShareRestrictionModal/helpers', function () {
|
|
|
163
191
|
}, _callee4);
|
|
164
192
|
})));
|
|
165
193
|
it('should call updateDocumentPermissions with expected date & password if their switches are true', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee5() {
|
|
166
|
-
var updateDocumentPermissions, file;
|
|
194
|
+
var updateDocumentPermissions, file, selectedDate;
|
|
167
195
|
return _regeneratorRuntime.wrap(function _callee5$(_context5) {
|
|
168
196
|
while (1) switch (_context5.prev = _context5.next) {
|
|
169
197
|
case 0:
|
|
@@ -171,12 +199,13 @@ describe('ShareRestrictionModal/helpers', function () {
|
|
|
171
199
|
file = {
|
|
172
200
|
_id: '123'
|
|
173
201
|
};
|
|
174
|
-
|
|
202
|
+
selectedDate = new Date('2100-01-01T00:00:00.000Z');
|
|
203
|
+
_context5.next = 5;
|
|
175
204
|
return updatePermissions({
|
|
176
205
|
file: file,
|
|
177
206
|
t: jest.fn(),
|
|
178
207
|
dateToggle: true,
|
|
179
|
-
selectedDate:
|
|
208
|
+
selectedDate: selectedDate,
|
|
180
209
|
passwordToggle: true,
|
|
181
210
|
password: '1234',
|
|
182
211
|
editingRights: 'readOnly',
|
|
@@ -185,32 +214,77 @@ describe('ShareRestrictionModal/helpers', function () {
|
|
|
185
214
|
showAlert: jest.fn()
|
|
186
215
|
});
|
|
187
216
|
|
|
188
|
-
case
|
|
217
|
+
case 5:
|
|
189
218
|
expect(updateDocumentPermissions).toHaveBeenCalledWith(file, {
|
|
190
|
-
expiresAt:
|
|
219
|
+
expiresAt: endOfDay(selectedDate).toISOString(),
|
|
191
220
|
password: '1234',
|
|
192
221
|
verbs: ['GET']
|
|
193
222
|
});
|
|
194
223
|
|
|
195
|
-
case
|
|
224
|
+
case 6:
|
|
196
225
|
case "end":
|
|
197
226
|
return _context5.stop();
|
|
198
227
|
}
|
|
199
228
|
}, _callee5);
|
|
200
229
|
})));
|
|
230
|
+
it('sets expiration to the end of the selected day so picking today stays valid', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee6() {
|
|
231
|
+
var updateDocumentPermissions, file, today, expiresAt;
|
|
232
|
+
return _regeneratorRuntime.wrap(function _callee6$(_context6) {
|
|
233
|
+
while (1) switch (_context6.prev = _context6.next) {
|
|
234
|
+
case 0:
|
|
235
|
+
// Freeze time so the "expires in the future" assertion can't flake near
|
|
236
|
+
// midnight or under slow CI.
|
|
237
|
+
jest.useFakeTimers();
|
|
238
|
+
jest.setSystemTime(new Date('2100-01-01T12:00:00.000Z'));
|
|
239
|
+
_context6.prev = 2;
|
|
240
|
+
updateDocumentPermissions = jest.fn();
|
|
241
|
+
file = {
|
|
242
|
+
_id: '123'
|
|
243
|
+
};
|
|
244
|
+
today = new Date();
|
|
245
|
+
_context6.next = 8;
|
|
246
|
+
return updatePermissions({
|
|
247
|
+
file: file,
|
|
248
|
+
t: jest.fn(),
|
|
249
|
+
dateToggle: true,
|
|
250
|
+
selectedDate: today,
|
|
251
|
+
passwordToggle: false,
|
|
252
|
+
password: '',
|
|
253
|
+
editingRights: 'readOnly',
|
|
254
|
+
documentType: documentType,
|
|
255
|
+
updateDocumentPermissions: updateDocumentPermissions,
|
|
256
|
+
showAlert: jest.fn()
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
case 8:
|
|
260
|
+
expiresAt = updateDocumentPermissions.mock.calls[0][1].expiresAt;
|
|
261
|
+
expect(expiresAt).toBe(endOfDay(today).toISOString());
|
|
262
|
+
expect(new Date(expiresAt).getTime()).toBeGreaterThan(Date.now());
|
|
263
|
+
|
|
264
|
+
case 11:
|
|
265
|
+
_context6.prev = 11;
|
|
266
|
+
jest.useRealTimers();
|
|
267
|
+
return _context6.finish(11);
|
|
268
|
+
|
|
269
|
+
case 14:
|
|
270
|
+
case "end":
|
|
271
|
+
return _context6.stop();
|
|
272
|
+
}
|
|
273
|
+
}, _callee6, null, [[2,, 11, 14]]);
|
|
274
|
+
})));
|
|
201
275
|
});
|
|
202
276
|
describe('revokePermissions', function () {
|
|
203
277
|
var documentType = 'Files';
|
|
204
|
-
it('should call revokeSharingLink', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function
|
|
278
|
+
it('should call revokeSharingLink', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee7() {
|
|
205
279
|
var revokeSharingLink, file;
|
|
206
|
-
return _regeneratorRuntime.wrap(function
|
|
207
|
-
while (1) switch (
|
|
280
|
+
return _regeneratorRuntime.wrap(function _callee7$(_context7) {
|
|
281
|
+
while (1) switch (_context7.prev = _context7.next) {
|
|
208
282
|
case 0:
|
|
209
283
|
revokeSharingLink = jest.fn();
|
|
210
284
|
file = {
|
|
211
285
|
_id: '123'
|
|
212
286
|
};
|
|
213
|
-
|
|
287
|
+
_context7.next = 4;
|
|
214
288
|
return revokePermissions({
|
|
215
289
|
file: file,
|
|
216
290
|
t: jest.fn(),
|
|
@@ -224,9 +298,9 @@ describe('ShareRestrictionModal/helpers', function () {
|
|
|
224
298
|
|
|
225
299
|
case 5:
|
|
226
300
|
case "end":
|
|
227
|
-
return
|
|
301
|
+
return _context7.stop();
|
|
228
302
|
}
|
|
229
|
-
},
|
|
303
|
+
}, _callee7);
|
|
230
304
|
})));
|
|
231
305
|
});
|
|
232
306
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cozy-sharing",
|
|
3
|
-
"version": "33.3.
|
|
3
|
+
"version": "33.3.2",
|
|
4
4
|
"description": "Provides sharing login for React applications.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"author": "Cozy",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"babel-jest": "30.3.0",
|
|
54
54
|
"babel-plugin-css-modules-transform": "1.6.2",
|
|
55
55
|
"babel-plugin-inline-react-svg": "1.1.2",
|
|
56
|
-
"cozy-client": "^60.
|
|
56
|
+
"cozy-client": "^60.27.0",
|
|
57
57
|
"cozy-device-helper": "^4.0.0",
|
|
58
58
|
"cozy-intent": "^2.31.1",
|
|
59
59
|
"cozy-minilog": "^3.10.1",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"twake-i18n": "^0.4.1"
|
|
69
69
|
},
|
|
70
70
|
"peerDependencies": {
|
|
71
|
-
"cozy-client": ">=60.
|
|
71
|
+
"cozy-client": ">=60.27.0",
|
|
72
72
|
"cozy-device-helper": ">=4.0.0",
|
|
73
73
|
"cozy-flags": ">=4.8.1",
|
|
74
74
|
"cozy-intent": ">=2.29.1",
|
|
@@ -83,5 +83,5 @@
|
|
|
83
83
|
"sideEffects": [
|
|
84
84
|
"*.css"
|
|
85
85
|
],
|
|
86
|
-
"gitHead": "
|
|
86
|
+
"gitHead": "08272a4e1b47e169b6f4af662e30a40e4e9a4124"
|
|
87
87
|
}
|
package/src/SharingProvider.jsx
CHANGED
|
@@ -143,7 +143,7 @@ export class SharingProvider extends Component {
|
|
|
143
143
|
this.fetchAllSharings()
|
|
144
144
|
if (!client.plugins.realtime) {
|
|
145
145
|
// eslint-disable-next-line
|
|
146
|
-
|
|
146
|
+
console.warn(
|
|
147
147
|
`You should register the realtime plugin to your CozyClient instance see https://docs.cozy.io/en/cozy-realtime/#example`
|
|
148
148
|
)
|
|
149
149
|
} else {
|
|
@@ -353,36 +353,19 @@ export class SharingProvider extends Component {
|
|
|
353
353
|
const member = sharing.attributes.members[memberIndex]
|
|
354
354
|
if (!member) throw new Error('Member not found')
|
|
355
355
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
.find(
|
|
360
|
-
{
|
|
361
|
-
email: { $elemMatch: { address: member.email } },
|
|
362
|
-
_id: { $gt: null }
|
|
363
|
-
},
|
|
364
|
-
{ indexedFields: ['_id'] }
|
|
365
|
-
)
|
|
366
|
-
|
|
367
|
-
if (!matchedContacts.data.length) {
|
|
368
|
-
throw new Error(`Contact not found for member ${member.email}`)
|
|
356
|
+
const makeReadOnly = newType === 'one-way'
|
|
357
|
+
if (newType !== 'one-way' && newType !== 'two-way') {
|
|
358
|
+
throw new Error('Unsupported sharing type: ' + newType)
|
|
369
359
|
}
|
|
370
|
-
const contact = matchedContacts.data[0]
|
|
371
|
-
|
|
372
|
-
// Revoke (API call only, no state dispatch to avoid UI flickering)
|
|
373
|
-
await this.sharingCol.revokeRecipient(sharing, memberIndex)
|
|
374
|
-
|
|
375
|
-
// Re-add with the new permission type
|
|
376
|
-
const readOnly = newType === 'one-way'
|
|
377
360
|
try {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
}
|
|
361
|
+
if (makeReadOnly) {
|
|
362
|
+
await this.sharingCol.setReadOnly(sharing, memberIndex)
|
|
363
|
+
} else {
|
|
364
|
+
await this.sharingCol.setReadWrite(sharing, memberIndex)
|
|
365
|
+
}
|
|
383
366
|
} catch (error) {
|
|
384
367
|
log.error(
|
|
385
|
-
`Failed to
|
|
368
|
+
`Failed to change member ${member.email} permission type to ${newType}`,
|
|
386
369
|
error
|
|
387
370
|
)
|
|
388
371
|
throw error
|
|
@@ -292,7 +292,6 @@ describe('updateDocumentPermissions', () => {
|
|
|
292
292
|
})
|
|
293
293
|
|
|
294
294
|
describe('updateSharingMemberType', () => {
|
|
295
|
-
const mockContact = { _id: 'contact-1', email: [{ address: 'bob@bob.cozy' }] }
|
|
296
295
|
const mockSharing = {
|
|
297
296
|
id: 'sharing-123',
|
|
298
297
|
type: 'io.cozy.sharings',
|
|
@@ -305,16 +304,11 @@ describe('updateSharingMemberType', () => {
|
|
|
305
304
|
}
|
|
306
305
|
|
|
307
306
|
let instance
|
|
308
|
-
let mockContactsCollection
|
|
309
307
|
|
|
310
308
|
beforeEach(() => {
|
|
311
|
-
mockContactsCollection = {
|
|
312
|
-
find: jest.fn().mockResolvedValue({ data: [mockContact] })
|
|
313
|
-
}
|
|
314
|
-
|
|
315
309
|
const mockClient = {
|
|
316
310
|
getStackClient: () => ({ uri: 'http://cozy.local' }),
|
|
317
|
-
collection: jest.fn().mockReturnValue(
|
|
311
|
+
collection: jest.fn().mockReturnValue({})
|
|
318
312
|
}
|
|
319
313
|
|
|
320
314
|
instance = new SharingProvider({ client: mockClient })
|
|
@@ -323,9 +317,10 @@ describe('updateSharingMemberType', () => {
|
|
|
323
317
|
sharings: [mockSharing]
|
|
324
318
|
}
|
|
325
319
|
instance.sharingCol = {
|
|
326
|
-
|
|
320
|
+
setReadOnly: jest.fn().mockResolvedValue({}),
|
|
321
|
+
setReadWrite: jest.fn().mockResolvedValue({})
|
|
327
322
|
}
|
|
328
|
-
jest.spyOn(instance, '
|
|
323
|
+
jest.spyOn(instance, 'setState')
|
|
329
324
|
})
|
|
330
325
|
|
|
331
326
|
it('should throw when sharing is not found', async () => {
|
|
@@ -340,48 +335,65 @@ describe('updateSharingMemberType', () => {
|
|
|
340
335
|
).rejects.toThrow('Member not found')
|
|
341
336
|
})
|
|
342
337
|
|
|
343
|
-
it('should
|
|
344
|
-
|
|
338
|
+
it('should call setReadOnly when switching to one-way', async () => {
|
|
339
|
+
await instance.updateSharingMemberType('sharing-123', 1, 'one-way')
|
|
345
340
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
).rejects.toThrow('Contact not found')
|
|
341
|
+
expect(instance.sharingCol.setReadOnly).toHaveBeenCalledWith(mockSharing, 1)
|
|
342
|
+
expect(instance.sharingCol.setReadWrite).not.toHaveBeenCalled()
|
|
349
343
|
})
|
|
350
344
|
|
|
351
|
-
it('should
|
|
352
|
-
await instance.updateSharingMemberType('sharing-123', 1, '
|
|
345
|
+
it('should call setReadWrite when switching to two-way', async () => {
|
|
346
|
+
await instance.updateSharingMemberType('sharing-123', 1, 'two-way')
|
|
353
347
|
|
|
354
|
-
expect(instance.sharingCol.
|
|
348
|
+
expect(instance.sharingCol.setReadWrite).toHaveBeenCalledWith(
|
|
355
349
|
mockSharing,
|
|
356
350
|
1
|
|
357
351
|
)
|
|
358
|
-
expect(instance.
|
|
359
|
-
document: mockSharing,
|
|
360
|
-
recipients: [],
|
|
361
|
-
readOnlyRecipients: [mockContact]
|
|
362
|
-
})
|
|
352
|
+
expect(instance.sharingCol.setReadOnly).not.toHaveBeenCalled()
|
|
363
353
|
})
|
|
364
354
|
|
|
365
|
-
it('should
|
|
355
|
+
it('should not dispatch a state update — realtime refreshes the store', async () => {
|
|
356
|
+
await instance.updateSharingMemberType('sharing-123', 1, 'one-way')
|
|
357
|
+
|
|
358
|
+
expect(instance.setState).not.toHaveBeenCalled()
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
it('should not dispatch a state update for two-way — realtime refreshes the store', async () => {
|
|
366
362
|
await instance.updateSharingMemberType('sharing-123', 1, 'two-way')
|
|
367
363
|
|
|
368
|
-
expect(instance.
|
|
369
|
-
|
|
370
|
-
|
|
364
|
+
expect(instance.setState).not.toHaveBeenCalled()
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
it('should rethrow error if setReadOnly fails', async () => {
|
|
368
|
+
instance.sharingCol.setReadOnly.mockRejectedValue(
|
|
369
|
+
new Error('Network error')
|
|
371
370
|
)
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
371
|
+
|
|
372
|
+
await expect(
|
|
373
|
+
instance.updateSharingMemberType('sharing-123', 1, 'one-way')
|
|
374
|
+
).rejects.toThrow('Network error')
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
it('should rethrow error if setReadWrite fails', async () => {
|
|
378
|
+
instance.sharingCol.setReadWrite.mockRejectedValue(
|
|
379
|
+
new Error('Network error')
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
await expect(
|
|
383
|
+
instance.updateSharingMemberType('sharing-123', 1, 'two-way')
|
|
384
|
+
).rejects.toThrow('Network error')
|
|
377
385
|
})
|
|
378
386
|
|
|
379
|
-
it('should
|
|
380
|
-
instance.
|
|
387
|
+
it('should not dispatch state update if API call fails', async () => {
|
|
388
|
+
instance.sharingCol.setReadOnly.mockRejectedValue(
|
|
389
|
+
new Error('Network error')
|
|
390
|
+
)
|
|
381
391
|
|
|
382
392
|
await expect(
|
|
383
393
|
instance.updateSharingMemberType('sharing-123', 1, 'one-way')
|
|
384
394
|
).rejects.toThrow('Network error')
|
|
395
|
+
|
|
396
|
+
expect(instance.setState).not.toHaveBeenCalled()
|
|
385
397
|
})
|
|
386
398
|
})
|
|
387
399
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { endOfDay } from 'date-fns'
|
|
2
|
+
|
|
1
3
|
import { generateWebLink } from 'cozy-client'
|
|
2
4
|
import minilog from 'cozy-minilog'
|
|
3
5
|
|
|
@@ -9,6 +11,21 @@ const log = minilog('ShareRestrictionModal/helpers')
|
|
|
9
11
|
export const WRITE_PERMS = ['GET', 'POST', 'PUT', 'PATCH']
|
|
10
12
|
export const READ_ONLY_PERMS = ['GET']
|
|
11
13
|
|
|
14
|
+
/**
|
|
15
|
+
* A share link expires at the END of the selected day, so picking today keeps
|
|
16
|
+
* the link reachable for the rest of today and expires it the next day.
|
|
17
|
+
* Without this, the picked day defaults to its midnight (start of day), which
|
|
18
|
+
* is already in the past and makes the link unreachable as soon as it is set.
|
|
19
|
+
* @param {Date|string|null|undefined} date - The picked expiration day
|
|
20
|
+
* @returns {Date|null|undefined} - The same value normalized to end of day
|
|
21
|
+
*/
|
|
22
|
+
export const toExpirationDate = date => {
|
|
23
|
+
if (!date) return date
|
|
24
|
+
const parsed = date instanceof Date ? date : new Date(date)
|
|
25
|
+
if (parsed.toString() === 'Invalid Date') return date
|
|
26
|
+
return endOfDay(parsed)
|
|
27
|
+
}
|
|
28
|
+
|
|
12
29
|
/**
|
|
13
30
|
* Create a sharing link for a file with specified options
|
|
14
31
|
* @param {object} options
|
|
@@ -161,9 +178,10 @@ export const makeTTL = selectedDate => {
|
|
|
161
178
|
}
|
|
162
179
|
if (selectedDateConverted instanceof Date) {
|
|
163
180
|
const now = new Date()
|
|
181
|
+
const expiration = toExpirationDate(selectedDateConverted)
|
|
164
182
|
const ttl =
|
|
165
|
-
|
|
166
|
-
? `${Math.round((
|
|
183
|
+
expiration > now
|
|
184
|
+
? `${Math.round((expiration - now) / 1000)}s`
|
|
167
185
|
: undefined
|
|
168
186
|
return ttl
|
|
169
187
|
}
|
|
@@ -239,7 +257,9 @@ export const updatePermissions = async ({
|
|
|
239
257
|
showAlert
|
|
240
258
|
}) => {
|
|
241
259
|
try {
|
|
242
|
-
const expiresAt = dateToggle
|
|
260
|
+
const expiresAt = dateToggle
|
|
261
|
+
? toExpirationDate(selectedDate)?.toISOString() || undefined
|
|
262
|
+
: ''
|
|
243
263
|
const ensurePassword = passwordToggle ? password || undefined : ''
|
|
244
264
|
const verbs = editingRights === 'readOnly' ? READ_ONLY_PERMS : WRITE_PERMS
|
|
245
265
|
return updateDocumentPermissions(file, {
|
|
@@ -1,6 +1,29 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { endOfDay } from 'date-fns'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
makeTTL,
|
|
5
|
+
revokePermissions,
|
|
6
|
+
toExpirationDate,
|
|
7
|
+
updatePermissions
|
|
8
|
+
} from './helpers'
|
|
2
9
|
|
|
3
10
|
describe('ShareRestrictionModal/helpers', () => {
|
|
11
|
+
describe('toExpirationDate', () => {
|
|
12
|
+
it('returns falsy values unchanged', () => {
|
|
13
|
+
expect(toExpirationDate(null)).toBeNull()
|
|
14
|
+
expect(toExpirationDate(undefined)).toBeUndefined()
|
|
15
|
+
expect(toExpirationDate('')).toBe('')
|
|
16
|
+
})
|
|
17
|
+
it('normalizes a Date to the end of its day', () => {
|
|
18
|
+
const date = new Date(2100, 0, 1, 9, 30)
|
|
19
|
+
expect(toExpirationDate(date)).toEqual(endOfDay(date))
|
|
20
|
+
})
|
|
21
|
+
it('accepts an ISO string', () => {
|
|
22
|
+
const iso = '2100-01-01T09:30:00.000Z'
|
|
23
|
+
expect(toExpirationDate(iso)).toEqual(endOfDay(new Date(iso)))
|
|
24
|
+
})
|
|
25
|
+
})
|
|
26
|
+
|
|
4
27
|
describe('makeTTL', () => {
|
|
5
28
|
it('sould return undefined', () => {
|
|
6
29
|
expect(makeTTL()).toBeUndefined()
|
|
@@ -14,6 +37,17 @@ describe('ShareRestrictionModal/helpers', () => {
|
|
|
14
37
|
expect(makeTTL(new Date('2100-01-01T00:00:00.000Z'))).toBeDefined()
|
|
15
38
|
expect(makeTTL('2100-01-01T00:00:00.000Z')).toBeDefined()
|
|
16
39
|
})
|
|
40
|
+
it('keeps a link picked for today valid until the end of the day', () => {
|
|
41
|
+
// Regression: picking "today" used to resolve to midnight (already past),
|
|
42
|
+
// which dropped the TTL and the link never expired or was unreachable.
|
|
43
|
+
jest.useFakeTimers()
|
|
44
|
+
jest.setSystemTime(new Date('2100-01-01T12:00:00.000Z'))
|
|
45
|
+
try {
|
|
46
|
+
expect(makeTTL(new Date())).toBeDefined()
|
|
47
|
+
} finally {
|
|
48
|
+
jest.useRealTimers()
|
|
49
|
+
}
|
|
50
|
+
})
|
|
17
51
|
})
|
|
18
52
|
|
|
19
53
|
describe('updatePermissions', () => {
|
|
@@ -118,11 +152,12 @@ describe('ShareRestrictionModal/helpers', () => {
|
|
|
118
152
|
const updateDocumentPermissions = jest.fn()
|
|
119
153
|
const file = { _id: '123' }
|
|
120
154
|
|
|
155
|
+
const selectedDate = new Date('2100-01-01T00:00:00.000Z')
|
|
121
156
|
await updatePermissions({
|
|
122
157
|
file,
|
|
123
158
|
t: jest.fn(),
|
|
124
159
|
dateToggle: true,
|
|
125
|
-
selectedDate
|
|
160
|
+
selectedDate,
|
|
126
161
|
passwordToggle: true,
|
|
127
162
|
password: '1234',
|
|
128
163
|
editingRights: 'readOnly',
|
|
@@ -132,11 +167,42 @@ describe('ShareRestrictionModal/helpers', () => {
|
|
|
132
167
|
})
|
|
133
168
|
|
|
134
169
|
expect(updateDocumentPermissions).toHaveBeenCalledWith(file, {
|
|
135
|
-
expiresAt:
|
|
170
|
+
expiresAt: endOfDay(selectedDate).toISOString(),
|
|
136
171
|
password: '1234',
|
|
137
172
|
verbs: ['GET']
|
|
138
173
|
})
|
|
139
174
|
})
|
|
175
|
+
|
|
176
|
+
it('sets expiration to the end of the selected day so picking today stays valid', async () => {
|
|
177
|
+
// Freeze time so the "expires in the future" assertion can't flake near
|
|
178
|
+
// midnight or under slow CI.
|
|
179
|
+
jest.useFakeTimers()
|
|
180
|
+
jest.setSystemTime(new Date('2100-01-01T12:00:00.000Z'))
|
|
181
|
+
try {
|
|
182
|
+
const updateDocumentPermissions = jest.fn()
|
|
183
|
+
const file = { _id: '123' }
|
|
184
|
+
const today = new Date()
|
|
185
|
+
|
|
186
|
+
await updatePermissions({
|
|
187
|
+
file,
|
|
188
|
+
t: jest.fn(),
|
|
189
|
+
dateToggle: true,
|
|
190
|
+
selectedDate: today,
|
|
191
|
+
passwordToggle: false,
|
|
192
|
+
password: '',
|
|
193
|
+
editingRights: 'readOnly',
|
|
194
|
+
documentType,
|
|
195
|
+
updateDocumentPermissions,
|
|
196
|
+
showAlert: jest.fn()
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
const { expiresAt } = updateDocumentPermissions.mock.calls[0][1]
|
|
200
|
+
expect(expiresAt).toBe(endOfDay(today).toISOString())
|
|
201
|
+
expect(new Date(expiresAt).getTime()).toBeGreaterThan(Date.now())
|
|
202
|
+
} finally {
|
|
203
|
+
jest.useRealTimers()
|
|
204
|
+
}
|
|
205
|
+
})
|
|
140
206
|
})
|
|
141
207
|
|
|
142
208
|
describe('revokePermissions', () => {
|