cozy-harvest-lib 7.1.1 → 7.2.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/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
+ # [7.2.0](https://github.com/cozy/cozy-libs/compare/cozy-harvest-lib@7.1.1...cozy-harvest-lib@7.2.0) (2022-01-31)
7
+
8
+
9
+ ### Features
10
+
11
+ * Add the defaultFolderPath attribute to the account ([1e80f81](https://github.com/cozy/cozy-libs/commit/1e80f81))
12
+ * Some naming improvements + unit tests ([bc7e10d](https://github.com/cozy/cozy-libs/commit/bc7e10d))
13
+
14
+
15
+
16
+
17
+
6
18
  ## [7.1.1](https://github.com/cozy/cozy-libs/compare/cozy-harvest-lib@7.1.0...cozy-harvest-lib@7.1.1) (2022-01-28)
7
19
 
8
20
  **Note:** Version bump only for package cozy-harvest-lib
@@ -20,6 +20,7 @@ import MicroEE from 'microee';
20
20
  import get from 'lodash/get';
21
21
  import Realtime from 'cozy-realtime';
22
22
  import flag from 'cozy-flags';
23
+ import { Q } from 'cozy-client';
23
24
  import { fetchReusableAccount, saveAccount as _saveAccount, ACCOUNTS_DOCTYPE } from '../connections/accounts';
24
25
  import clone from 'lodash/clone';
25
26
  import { launchTrigger, prepareTriggerAccount, fetchTrigger, ensureTrigger } from '../connections/triggers';
@@ -730,13 +731,21 @@ export var ConnectionFlow = /*#__PURE__*/function () {
730
731
 
731
732
  case 5:
732
733
  ensuredTrigger = _context9.sent;
734
+ _context9.next = 8;
735
+ return this.ensureDefaultFolderPathInAccount(client, {
736
+ trigger: ensuredTrigger,
737
+ account: account,
738
+ konnector: konnector
739
+ });
740
+
741
+ case 8:
733
742
  logger.info("Trigger is ".concat(ensuredTrigger._id));
734
743
  this.trigger = ensuredTrigger;
735
744
  this.emit(UPDATE_EVENT);
736
- _context9.next = 11;
745
+ _context9.next = 13;
737
746
  return this.launch();
738
747
 
739
- case 11:
748
+ case 13:
740
749
  case "end":
741
750
  return _context9.stop();
742
751
  }
@@ -750,6 +759,87 @@ export var ConnectionFlow = /*#__PURE__*/function () {
750
759
 
751
760
  return ensureTriggerAndLaunch;
752
761
  }()
762
+ /**
763
+ * Ensures there is a defaultFolderPath attribute in the account
764
+ * if any folder is needed by the connector.
765
+ * The account is saved if any change is done to it.
766
+ * This defaultFolderPath is needed by the stack to recreate the
767
+ * destination folder if removed by any mean : drive application,
768
+ * desktop application etc
769
+ *
770
+ * @param {CozyClient} client - A cozy client
771
+ * @param {io.cozy.triggers} options.trigger
772
+ * @param {io.cozy.accounts} options.account
773
+ * @param {io.cozy.konnectors} options.konnector
774
+ */
775
+
776
+ }, {
777
+ key: "ensureDefaultFolderPathInAccount",
778
+ value: function () {
779
+ var _ensureDefaultFolderPathInAccount = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee10(client, _ref4) {
780
+ var trigger, account, konnector, folderId, folder, result, savedAccount;
781
+ return _regeneratorRuntime.wrap(function _callee10$(_context10) {
782
+ while (1) {
783
+ switch (_context10.prev = _context10.next) {
784
+ case 0:
785
+ trigger = _ref4.trigger, account = _ref4.account, konnector = _ref4.konnector;
786
+ folderId = get(trigger, 'message.folder_to_save');
787
+
788
+ if (folderId) {
789
+ _context10.next = 4;
790
+ break;
791
+ }
792
+
793
+ return _context10.abrupt("return", account);
794
+
795
+ case 4:
796
+ _context10.prev = 4;
797
+ _context10.next = 7;
798
+ return client.query(Q('io.cozy.files').getById(folderId));
799
+
800
+ case 7:
801
+ result = _context10.sent;
802
+ folder = result.data;
803
+ _context10.next = 15;
804
+ break;
805
+
806
+ case 11:
807
+ _context10.prev = 11;
808
+ _context10.t0 = _context10["catch"](4);
809
+ logger.warn("ConnectionFlow.ensureDefaultFolderPath: folder ".concat(folderId, " does not exist. Could not ensure defaultFolderPath. ").concat(_context10.t0.message));
810
+ return _context10.abrupt("return", account);
811
+
812
+ case 15:
813
+ if (!(folder.path !== account.defaultFolderPath)) {
814
+ _context10.next = 21;
815
+ break;
816
+ }
817
+
818
+ account.defaultFolderPath = folder.path;
819
+ _context10.next = 19;
820
+ return _saveAccount(client, konnector, account);
821
+
822
+ case 19:
823
+ savedAccount = _context10.sent;
824
+ return _context10.abrupt("return", savedAccount);
825
+
826
+ case 21:
827
+ return _context10.abrupt("return", account);
828
+
829
+ case 22:
830
+ case "end":
831
+ return _context10.stop();
832
+ }
833
+ }
834
+ }, _callee10, null, [[4, 11]]);
835
+ }));
836
+
837
+ function ensureDefaultFolderPathInAccount(_x9, _x10) {
838
+ return _ensureDefaultFolderPathInAccount.apply(this, arguments);
839
+ }
840
+
841
+ return ensureDefaultFolderPathInAccount;
842
+ }()
753
843
  /**
754
844
  * Launches the job and sets everything up to follow execution.
755
845
  */
@@ -757,41 +847,41 @@ export var ConnectionFlow = /*#__PURE__*/function () {
757
847
  }, {
758
848
  key: "launch",
759
849
  value: function () {
760
- var _launch = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee10() {
850
+ var _launch = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee11() {
761
851
  var _this2 = this;
762
852
 
763
- var _ref4,
764
- _ref4$autoSuccessTime,
853
+ var _ref5,
854
+ _ref5$autoSuccessTime,
765
855
  autoSuccessTimer,
766
856
  computedAutoSuccessTimer,
767
857
  launcher,
768
858
  _iterator2,
769
859
  _step2,
770
860
  _loop,
771
- _args10 = arguments;
861
+ _args11 = arguments;
772
862
 
773
- return _regeneratorRuntime.wrap(function _callee10$(_context10) {
863
+ return _regeneratorRuntime.wrap(function _callee11$(_context11) {
774
864
  while (1) {
775
- switch (_context10.prev = _context10.next) {
865
+ switch (_context11.prev = _context11.next) {
776
866
  case 0:
777
- _ref4 = _args10.length > 0 && _args10[0] !== undefined ? _args10[0] : {}, _ref4$autoSuccessTime = _ref4.autoSuccessTimer, autoSuccessTimer = _ref4$autoSuccessTime === void 0 ? true : _ref4$autoSuccessTime;
867
+ _ref5 = _args11.length > 0 && _args11[0] !== undefined ? _args11[0] : {}, _ref5$autoSuccessTime = _ref5.autoSuccessTimer, autoSuccessTimer = _ref5$autoSuccessTime === void 0 ? true : _ref5$autoSuccessTime;
778
868
  computedAutoSuccessTimer = autoSuccessTimer && !get(this, 'konnector.clientSide');
779
869
  logger.info('ConnectionFlow: Launching job...');
780
870
  this.setState({
781
871
  status: PENDING
782
872
  });
783
- _context10.next = 6;
873
+ _context11.next = 6;
784
874
  return prepareTriggerAccount(this.client, this.trigger);
785
875
 
786
876
  case 6:
787
- this.account = _context10.sent;
877
+ this.account = _context11.sent;
788
878
  this.realtime.subscribe('updated', ACCOUNTS_DOCTYPE, this.account._id, this.handleAccountUpdated);
789
879
  logger.info("ConnectionFlow: Subscribed to ".concat(ACCOUNTS_DOCTYPE, ":").concat(this.account._id));
790
- _context10.next = 11;
880
+ _context11.next = 11;
791
881
  return launchTrigger(this.client, this.trigger);
792
882
 
793
883
  case 11:
794
- this.job = _context10.sent;
884
+ this.job = _context11.sent;
795
885
 
796
886
  if (get(this, 'konnector.clientSide')) {
797
887
  logger.info('This connector can be run by the launcher', this.konnector.slug);
@@ -855,10 +945,10 @@ export var ConnectionFlow = /*#__PURE__*/function () {
855
945
 
856
946
  case 19:
857
947
  case "end":
858
- return _context10.stop();
948
+ return _context11.stop();
859
949
  }
860
950
  }
861
- }, _callee10, this);
951
+ }, _callee11, this);
862
952
  }));
863
953
 
864
954
  function launch() {
@@ -15,6 +15,7 @@ import KonnectorJobWatcher from './konnector/KonnectorJobWatcher';
15
15
  import { konnectorPolicy as biKonnectorPolicy } from '../services/budget-insight';
16
16
  import fixtures from '../../test/fixtures';
17
17
  import sentryHub from '../sentry';
18
+ import { Q } from 'cozy-client';
18
19
  jest.mock('../sentry', function () {
19
20
  var mockScope = {
20
21
  setTag: jest.fn()
@@ -161,6 +162,7 @@ var setup = function setup() {
161
162
  trigger = _ref6.trigger;
162
163
 
163
164
  var client = {
165
+ query: jest.fn(),
164
166
  collection: jest.fn().mockReturnValue({
165
167
  all: jest.fn().mockReturnValue({
166
168
  data: []
@@ -630,14 +632,106 @@ describe('ConnectionFlow', function () {
630
632
  }
631
633
  }, _callee20);
632
634
  })));
633
- it('should call the launcher when needed', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee22() {
634
- var _setup16, client, flow;
635
+ it('should add the defaultFolderPath to the account when needed', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee23() {
636
+ var _setup16, flow, client;
635
637
 
636
- return _regeneratorRuntime.wrap(function _callee22$(_context22) {
638
+ return _regeneratorRuntime.wrap(function _callee23$(_context23) {
637
639
  while (1) {
638
- switch (_context22.prev = _context22.next) {
640
+ switch (_context23.prev = _context23.next) {
639
641
  case 0:
640
- _setup16 = setup(), client = _setup16.client;
642
+ _setup16 = setup(), flow = _setup16.flow, client = _setup16.client;
643
+ prepareTriggerAccount.mockResolvedValue(fixtures.updatedAccount);
644
+ ensureTrigger.mockImplementation( /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee21() {
645
+ return _regeneratorRuntime.wrap(function _callee21$(_context21) {
646
+ while (1) {
647
+ switch (_context21.prev = _context21.next) {
648
+ case 0:
649
+ return _context21.abrupt("return", fixtures.createdTriggerWithFolder.attributes);
650
+
651
+ case 1:
652
+ case "end":
653
+ return _context21.stop();
654
+ }
655
+ }
656
+ }, _callee21);
657
+ })));
658
+ client.query.mockImplementation( /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee22() {
659
+ return _regeneratorRuntime.wrap(function _callee22$(_context22) {
660
+ while (1) {
661
+ switch (_context22.prev = _context22.next) {
662
+ case 0:
663
+ return _context22.abrupt("return", {
664
+ data: {
665
+ path: '/default/folder/path'
666
+ }
667
+ });
668
+
669
+ case 1:
670
+ case "end":
671
+ return _context22.stop();
672
+ }
673
+ }
674
+ }, _callee22);
675
+ })));
676
+ _context23.next = 6;
677
+ return flow.ensureTriggerAndLaunch(client, {
678
+ account: fixtures.existingAccount,
679
+ trigger: fixtures.existingTrigger,
680
+ konnector: fixtures.konnectorWithFolder
681
+ });
682
+
683
+ case 6:
684
+ expect(flow.account).toEqual(fixtures.updatedAccount);
685
+ expect(saveAccount).toHaveBeenCalledWith(client, fixtures.konnectorWithFolder, _objectSpread(_objectSpread({}, fixtures.existingAccount), {}, {
686
+ defaultFolderPath: '/default/folder/path'
687
+ }));
688
+ expect(client.query).toHaveBeenCalledWith(Q('io.cozy.files').getById(fixtures.createdTriggerWithFolder.attributes.message.folder_to_save));
689
+
690
+ case 9:
691
+ case "end":
692
+ return _context23.stop();
693
+ }
694
+ }
695
+ }, _callee23);
696
+ })));
697
+ it('should return unmodified account trigger folder does not exist', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee24() {
698
+ var _setup17, flow, client;
699
+
700
+ return _regeneratorRuntime.wrap(function _callee24$(_context24) {
701
+ while (1) {
702
+ switch (_context24.prev = _context24.next) {
703
+ case 0:
704
+ _setup17 = setup(), flow = _setup17.flow, client = _setup17.client;
705
+ prepareTriggerAccount.mockResolvedValue(fixtures.updatedAccount);
706
+ ensureTrigger.mockResolvedValue(fixtures.createdTriggerWithFolder.attributes);
707
+ client.query.mockRejectedValue(new Error('404'));
708
+ _context24.next = 6;
709
+ return flow.ensureTriggerAndLaunch(client, {
710
+ account: fixtures.existingAccount,
711
+ trigger: fixtures.existingTrigger,
712
+ konnector: fixtures.konnectorWithFolder
713
+ });
714
+
715
+ case 6:
716
+ expect(flow.account).toEqual(fixtures.updatedAccount);
717
+ expect(saveAccount).not.toHaveBeenCalled();
718
+ expect(client.query).toHaveBeenCalledWith(Q('io.cozy.files').getById(fixtures.createdTriggerWithFolder.attributes.message.folder_to_save));
719
+
720
+ case 9:
721
+ case "end":
722
+ return _context24.stop();
723
+ }
724
+ }
725
+ }, _callee24);
726
+ })));
727
+ it('should call the launcher when needed', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee26() {
728
+ var _setup18, client, flow;
729
+
730
+ return _regeneratorRuntime.wrap(function _callee26$(_context26) {
731
+ while (1) {
732
+ switch (_context26.prev = _context26.next) {
733
+ case 0:
734
+ _setup18 = setup(), client = _setup18.client;
641
735
  flow = new ConnectionFlow(client, fixtures.existingTrigger, fixtures.clientKonnector);
642
736
  window.cozy = {
643
737
  ClientConnectorLauncher: 'react-native'
@@ -645,22 +739,22 @@ describe('ConnectionFlow', function () {
645
739
  window.ReactNativeWebView = {
646
740
  postMessage: jest.fn()
647
741
  };
648
- ensureTrigger.mockImplementation( /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee21() {
649
- return _regeneratorRuntime.wrap(function _callee21$(_context21) {
742
+ ensureTrigger.mockImplementation( /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee25() {
743
+ return _regeneratorRuntime.wrap(function _callee25$(_context25) {
650
744
  while (1) {
651
- switch (_context21.prev = _context21.next) {
745
+ switch (_context25.prev = _context25.next) {
652
746
  case 0:
653
- return _context21.abrupt("return", fixtures.existingTrigger);
747
+ return _context25.abrupt("return", fixtures.existingTrigger);
654
748
 
655
749
  case 1:
656
750
  case "end":
657
- return _context21.stop();
751
+ return _context25.stop();
658
752
  }
659
753
  }
660
- }, _callee21);
754
+ }, _callee25);
661
755
  })));
662
756
  prepareTriggerAccount.mockResolvedValue(fixtures.updatedAccount);
663
- _context22.next = 8;
757
+ _context26.next = 8;
664
758
  return flow.ensureTriggerAndLaunch(client, {
665
759
  account: fixtures.existingAccount,
666
760
  trigger: fixtures.existingTrigger
@@ -680,10 +774,10 @@ describe('ConnectionFlow', function () {
680
774
 
681
775
  case 10:
682
776
  case "end":
683
- return _context22.stop();
777
+ return _context26.stop();
684
778
  }
685
779
  }
686
- }, _callee22);
780
+ }, _callee26);
687
781
  })));
688
782
  delete window.cozy;
689
783
  delete window.ReactNativeWebView;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cozy-harvest-lib",
3
- "version": "7.1.1",
3
+ "version": "7.2.0",
4
4
  "description": "Provides logic, modules and components for Cozy's harvest applications.",
5
5
  "main": "dist/index.js",
6
6
  "author": "Cozy",
@@ -85,5 +85,5 @@
85
85
  "react-router-dom": "^5.0.1"
86
86
  },
87
87
  "sideEffects": false,
88
- "gitHead": "bee23139882151ec2e55d817755fbfbb5798d4b2"
88
+ "gitHead": "beff9977aff2347bbef342015b062f5c4b71c6cd"
89
89
  }
@@ -3,6 +3,7 @@ import get from 'lodash/get'
3
3
 
4
4
  import Realtime from 'cozy-realtime'
5
5
  import flag from 'cozy-flags'
6
+ import { Q } from 'cozy-client'
6
7
 
7
8
  import {
8
9
  fetchReusableAccount,
@@ -474,12 +475,59 @@ export class ConnectionFlow {
474
475
  konnector,
475
476
  t
476
477
  })
478
+
479
+ await this.ensureDefaultFolderPathInAccount(client, {
480
+ trigger: ensuredTrigger,
481
+ account,
482
+ konnector
483
+ })
484
+
477
485
  logger.info(`Trigger is ${ensuredTrigger._id}`)
478
486
  this.trigger = ensuredTrigger
479
487
  this.emit(UPDATE_EVENT)
480
488
  await this.launch()
481
489
  }
482
490
 
491
+ /**
492
+ * Ensures there is a defaultFolderPath attribute in the account
493
+ * if any folder is needed by the connector.
494
+ * The account is saved if any change is done to it.
495
+ * This defaultFolderPath is needed by the stack to recreate the
496
+ * destination folder if removed by any mean : drive application,
497
+ * desktop application etc
498
+ *
499
+ * @param {CozyClient} client - A cozy client
500
+ * @param {io.cozy.triggers} options.trigger
501
+ * @param {io.cozy.accounts} options.account
502
+ * @param {io.cozy.konnectors} options.konnector
503
+ */
504
+ async ensureDefaultFolderPathInAccount(
505
+ client,
506
+ { trigger, account, konnector }
507
+ ) {
508
+ const folderId = get(trigger, 'message.folder_to_save')
509
+ if (!folderId) {
510
+ return account
511
+ }
512
+
513
+ let folder
514
+ try {
515
+ const result = await client.query(Q('io.cozy.files').getById(folderId))
516
+ folder = result.data
517
+ } catch (err) {
518
+ logger.warn(
519
+ `ConnectionFlow.ensureDefaultFolderPath: folder ${folderId} does not exist. Could not ensure defaultFolderPath. ${err.message}`
520
+ )
521
+ return account
522
+ }
523
+ if (folder.path !== account.defaultFolderPath) {
524
+ account.defaultFolderPath = folder.path
525
+ const savedAccount = await saveAccount(client, konnector, account)
526
+ return savedAccount
527
+ }
528
+ return account
529
+ }
530
+
483
531
  /**
484
532
  * Launches the job and sets everything up to follow execution.
485
533
  */
@@ -12,6 +12,7 @@ import KonnectorJobWatcher from './konnector/KonnectorJobWatcher'
12
12
  import { konnectorPolicy as biKonnectorPolicy } from '../services/budget-insight'
13
13
  import fixtures from '../../test/fixtures'
14
14
  import sentryHub from '../sentry'
15
+ import { Q } from 'cozy-client'
15
16
 
16
17
  jest.mock('../sentry', () => {
17
18
  const mockScope = {
@@ -90,6 +91,7 @@ const mockVaultClient = {
90
91
 
91
92
  const setup = ({ trigger } = {}) => {
92
93
  const client = {
94
+ query: jest.fn(),
93
95
  collection: jest.fn().mockReturnValue({
94
96
  all: jest.fn().mockReturnValue({
95
97
  data: []
@@ -388,6 +390,61 @@ describe('ConnectionFlow', () => {
388
390
  expect(flow.account).toEqual(fixtures.updatedAccount)
389
391
  })
390
392
 
393
+ it('should add the defaultFolderPath to the account when needed', async () => {
394
+ const { flow, client } = setup()
395
+ prepareTriggerAccount.mockResolvedValue(fixtures.updatedAccount)
396
+
397
+ ensureTrigger.mockImplementation(
398
+ async () => fixtures.createdTriggerWithFolder.attributes
399
+ )
400
+ client.query.mockImplementation(async () => ({
401
+ data: {
402
+ path: '/default/folder/path'
403
+ }
404
+ }))
405
+ await flow.ensureTriggerAndLaunch(client, {
406
+ account: fixtures.existingAccount,
407
+ trigger: fixtures.existingTrigger,
408
+ konnector: fixtures.konnectorWithFolder
409
+ })
410
+ expect(flow.account).toEqual(fixtures.updatedAccount)
411
+ expect(saveAccount).toHaveBeenCalledWith(
412
+ client,
413
+ fixtures.konnectorWithFolder,
414
+ {
415
+ ...fixtures.existingAccount,
416
+ defaultFolderPath: '/default/folder/path'
417
+ }
418
+ )
419
+ expect(client.query).toHaveBeenCalledWith(
420
+ Q('io.cozy.files').getById(
421
+ fixtures.createdTriggerWithFolder.attributes.message.folder_to_save
422
+ )
423
+ )
424
+ })
425
+
426
+ it('should return unmodified account trigger folder does not exist', async () => {
427
+ const { flow, client } = setup()
428
+ prepareTriggerAccount.mockResolvedValue(fixtures.updatedAccount)
429
+
430
+ ensureTrigger.mockResolvedValue(
431
+ fixtures.createdTriggerWithFolder.attributes
432
+ )
433
+ client.query.mockRejectedValue(new Error('404'))
434
+ await flow.ensureTriggerAndLaunch(client, {
435
+ account: fixtures.existingAccount,
436
+ trigger: fixtures.existingTrigger,
437
+ konnector: fixtures.konnectorWithFolder
438
+ })
439
+ expect(flow.account).toEqual(fixtures.updatedAccount)
440
+ expect(saveAccount).not.toHaveBeenCalled()
441
+ expect(client.query).toHaveBeenCalledWith(
442
+ Q('io.cozy.files').getById(
443
+ fixtures.createdTriggerWithFolder.attributes.message.folder_to_save
444
+ )
445
+ )
446
+ })
447
+
391
448
  it('should call the launcher when needed', async () => {
392
449
  const { client } = setup()
393
450
  const flow = new ConnectionFlow(
package/test/fixtures.js CHANGED
@@ -125,6 +125,20 @@ const fixtures = {
125
125
  }
126
126
  }
127
127
  },
128
+ createdTriggerWithFolder: {
129
+ id: 'created-trigger-id',
130
+ _type: 'io.cozy.triggers',
131
+ attributes: {
132
+ arguments: '0 0 0 * * 0',
133
+ type: '@cron',
134
+ worker: 'konnector',
135
+ message: {
136
+ account: 'updated-account-id',
137
+ konnector: 'konnectest',
138
+ folder_to_save: 'folder-id'
139
+ }
140
+ }
141
+ },
128
142
  launchedJob: {
129
143
  type: 'io.cozy.jobs',
130
144
  _id: 'lauched-job-id',