cozy-pouch-link 48.24.1 → 49.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/CozyPouchLink.js +593 -237
- package/dist/CozyPouchLink.spec.js +67 -42
- package/dist/PouchManager.js +317 -254
- package/dist/PouchManager.spec.js +91 -58
- package/dist/helpers.js +79 -0
- package/dist/helpers.spec.js +85 -1
- package/dist/jsonapi.js +54 -7
- package/dist/jsonapi.spec.js +57 -14
- package/dist/localStorage.js +646 -207
- package/dist/localStorage.spec.js +48 -0
- package/dist/mango.js +72 -20
- package/dist/mango.spec.js +1 -1
- package/dist/migrations/adapter.js +1 -1
- package/dist/platformWeb.js +120 -0
- package/dist/remote.js +39 -5
- package/dist/remote.spec.js +214 -0
- package/dist/replicateOnce.js +337 -0
- package/dist/startReplication.js +70 -45
- package/dist/startReplication.spec.js +374 -39
- package/dist/types.js +80 -0
- package/dist/utils.js +11 -2
- package/package.json +9 -5
- package/types/AccessToken.d.ts +16 -0
- package/types/CozyPouchLink.d.ts +228 -0
- package/types/PouchManager.d.ts +86 -0
- package/types/__tests__/fixtures.d.ts +48 -0
- package/types/__tests__/mocks.d.ts +4 -0
- package/types/helpers.d.ts +17 -0
- package/types/index.d.ts +1 -0
- package/types/jsonapi.d.ts +19 -0
- package/types/localStorage.d.ts +124 -0
- package/types/logger.d.ts +2 -0
- package/types/loop.d.ts +60 -0
- package/types/mango.d.ts +3 -0
- package/types/migrations/adapter.d.ts +18 -0
- package/types/platformWeb.d.ts +17 -0
- package/types/remote.d.ts +6 -0
- package/types/replicateOnce.d.ts +29 -0
- package/types/startReplication.d.ts +12 -0
- package/types/types.d.ts +104 -0
- package/types/utils.d.ts +3 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { PouchLocalStorage } from './localStorage'
|
|
2
|
+
|
|
3
|
+
describe('LocalStorage', () => {
|
|
4
|
+
describe('Type assertion', () => {
|
|
5
|
+
it('should throw if setItem method is missing', () => {
|
|
6
|
+
expect(() => {
|
|
7
|
+
new PouchLocalStorage({
|
|
8
|
+
getItem: jest.fn(),
|
|
9
|
+
removeItem: jest.fn()
|
|
10
|
+
})
|
|
11
|
+
}).toThrow(
|
|
12
|
+
'Provided storageEngine is missing the following methods: setItem'
|
|
13
|
+
)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('should throw if getItem method is missing', () => {
|
|
17
|
+
expect(() => {
|
|
18
|
+
new PouchLocalStorage({
|
|
19
|
+
setItem: jest.fn(),
|
|
20
|
+
removeItem: jest.fn()
|
|
21
|
+
})
|
|
22
|
+
}).toThrow(
|
|
23
|
+
'Provided storageEngine is missing the following methods: getItem'
|
|
24
|
+
)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('should throw if removeItem method is missing', () => {
|
|
28
|
+
expect(() => {
|
|
29
|
+
new PouchLocalStorage({
|
|
30
|
+
getItem: jest.fn(),
|
|
31
|
+
setItem: jest.fn()
|
|
32
|
+
})
|
|
33
|
+
}).toThrow(
|
|
34
|
+
'Provided storageEngine is missing the following methods: removeItem'
|
|
35
|
+
)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('should throw if multiple methods are missing', () => {
|
|
39
|
+
expect(() => {
|
|
40
|
+
new PouchLocalStorage({
|
|
41
|
+
getItem: jest.fn()
|
|
42
|
+
})
|
|
43
|
+
}).toThrow(
|
|
44
|
+
'Provided storageEngine is missing the following methods: setItem, removeItem'
|
|
45
|
+
)
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
})
|
package/dist/mango.js
CHANGED
|
@@ -5,28 +5,74 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", {
|
|
6
6
|
value: true
|
|
7
7
|
});
|
|
8
|
-
exports.getIndexFields = exports.getIndexNameFromFields = void 0;
|
|
8
|
+
exports.getIndexFields = exports.getIndexNameFromFields = exports.makeKeyFromPartialFilter = void 0;
|
|
9
9
|
|
|
10
10
|
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
|
|
11
11
|
|
|
12
|
-
var
|
|
12
|
+
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
|
|
13
13
|
|
|
14
|
-
var
|
|
14
|
+
var _head = _interopRequireDefault(require("lodash/head"));
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Process a partial filter to generate a string key
|
|
18
|
+
*
|
|
19
|
+
* /!\ Warning: this method is similar to cozy-stack-client mangoIndex.makeKeyFromPartialFilter()
|
|
20
|
+
* If you edit this method, please check if the change is also needed in mangoIndex file
|
|
21
|
+
*
|
|
22
|
+
* @param {object} condition - An object representing the partial filter or a sub-condition of the partial filter
|
|
23
|
+
* @returns {string} - The string key of the processed partial filter
|
|
24
|
+
*/
|
|
25
|
+
var makeKeyFromPartialFilter = function makeKeyFromPartialFilter(condition) {
|
|
26
|
+
if (typeof condition !== 'object' || condition === null) {
|
|
27
|
+
return String(condition);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
var conditions = Object.entries(condition).map(function (_ref) {
|
|
31
|
+
var _ref2 = (0, _slicedToArray2.default)(_ref, 2),
|
|
32
|
+
key = _ref2[0],
|
|
33
|
+
value = _ref2[1];
|
|
34
|
+
|
|
35
|
+
if (Array.isArray(value) && value.every(function (subObj) {
|
|
36
|
+
return typeof subObj === 'string';
|
|
37
|
+
})) {
|
|
38
|
+
return "".concat(key, "_(").concat(value.join('_'), ")");
|
|
39
|
+
} else if (Array.isArray(value)) {
|
|
40
|
+
return "(".concat(value.map(function (subCondition) {
|
|
41
|
+
return "".concat(makeKeyFromPartialFilter(subCondition));
|
|
42
|
+
}).join(")_".concat(key, "_(")), ")");
|
|
43
|
+
} else if (typeof value === 'object') {
|
|
44
|
+
return "".concat(key, "_").concat(makeKeyFromPartialFilter(value));
|
|
45
|
+
} else {
|
|
46
|
+
return "".concat(key, "_").concat(value);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
return conditions.join(')_and_(');
|
|
18
50
|
};
|
|
51
|
+
/**
|
|
52
|
+
* Name an index, based on its indexed fields and partial filter.
|
|
53
|
+
*
|
|
54
|
+
* It follows this naming convention:
|
|
55
|
+
* `by_{indexed_field1}_and_{indexed_field2}_filter_({partial_filter.key1}_{partial_filter.value1})_and_({partial_filter.key2}_{partial_filter.value2})`
|
|
56
|
+
*
|
|
57
|
+
* /!\ Warning: this method is similar to cozy-stack-client mangoIndex.getIndexNameFromFields()
|
|
58
|
+
* If you edit this method, please check if the change is also needed in mangoIndex file
|
|
59
|
+
*
|
|
60
|
+
* @param {Array<string>} fields - The indexed fields
|
|
61
|
+
* @param {object} [partialFilter] - The partial filter
|
|
62
|
+
* @returns {string} The index name, built from the fields
|
|
63
|
+
*/
|
|
19
64
|
|
|
20
|
-
exports.getIndexNameFromFields = getIndexNameFromFields;
|
|
21
65
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
return
|
|
66
|
+
exports.makeKeyFromPartialFilter = makeKeyFromPartialFilter;
|
|
67
|
+
|
|
68
|
+
var getIndexNameFromFields = function getIndexNameFromFields(fields, partialFilter) {
|
|
69
|
+
var indexName = "by_".concat(fields.join('_and_'));
|
|
70
|
+
|
|
71
|
+
if (partialFilter) {
|
|
72
|
+
return "".concat(indexName, "_filter_(").concat(makeKeyFromPartialFilter(partialFilter), ")");
|
|
29
73
|
}
|
|
74
|
+
|
|
75
|
+
return indexName;
|
|
30
76
|
};
|
|
31
77
|
/**
|
|
32
78
|
* @function
|
|
@@ -34,23 +80,29 @@ var getSortKeys = function getSortKeys(sort) {
|
|
|
34
80
|
* query to work
|
|
35
81
|
*
|
|
36
82
|
* @private
|
|
37
|
-
* @param {
|
|
83
|
+
* @param {import('./types').MangoQueryOptions} options - Mango query options
|
|
38
84
|
* @returns {Array} - Fields to index
|
|
39
85
|
*/
|
|
40
86
|
|
|
41
87
|
|
|
88
|
+
exports.getIndexNameFromFields = getIndexNameFromFields;
|
|
42
89
|
var defaultSelector = {
|
|
43
90
|
_id: {
|
|
44
91
|
$gt: null
|
|
45
92
|
}
|
|
46
93
|
};
|
|
47
94
|
|
|
48
|
-
var getIndexFields = function getIndexFields(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
95
|
+
var getIndexFields = function getIndexFields(
|
|
96
|
+
/** @type {import('./types').MangoQueryOptions} */
|
|
97
|
+
_ref3) {
|
|
98
|
+
var _ref3$selector = _ref3.selector,
|
|
99
|
+
selector = _ref3$selector === void 0 ? defaultSelector : _ref3$selector,
|
|
100
|
+
_ref3$sort = _ref3.sort,
|
|
101
|
+
sort = _ref3$sort === void 0 ? [] : _ref3$sort,
|
|
102
|
+
partialFilter = _ref3.partialFilter;
|
|
103
|
+
return Array.from(new Set([].concat((0, _toConsumableArray2.default)(sort.map(function (sortOption) {
|
|
104
|
+
return (0, _head.default)(Object.keys(sortOption));
|
|
105
|
+
})), (0, _toConsumableArray2.default)(selector ? Object.keys(selector) : []), (0, _toConsumableArray2.default)(partialFilter ? Object.keys(partialFilter) : []))));
|
|
54
106
|
};
|
|
55
107
|
|
|
56
108
|
exports.getIndexFields = getIndexFields;
|
package/dist/mango.spec.js
CHANGED
|
@@ -6,6 +6,6 @@ describe('mango utils', () => {
|
|
|
6
6
|
it('should be able to get the fields from the selector', () => {
|
|
7
7
|
const query = Q('io.cozy.rockets').sortBy([{ label: true }, { _id: true }])
|
|
8
8
|
const fields = getIndexFields(query)
|
|
9
|
-
expect(fields).toEqual(['
|
|
9
|
+
expect(fields).toEqual(['label', '_id'])
|
|
10
10
|
})
|
|
11
11
|
})
|
|
@@ -25,7 +25,7 @@ var getNewIndexedDBDatabaseName = function getNewIndexedDBDatabaseName(dbName) {
|
|
|
25
25
|
* @property {string} [toAdapter] - The new adapter type, e.g. 'indexeddb'
|
|
26
26
|
*
|
|
27
27
|
* @param {MigrationParams} params - The migration params
|
|
28
|
-
* @returns {object} - The migrated pouch
|
|
28
|
+
* @returns {Promise<object>} - The migrated pouch
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
31
|
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
|
|
5
|
+
Object.defineProperty(exports, "__esModule", {
|
|
6
|
+
value: true
|
|
7
|
+
});
|
|
8
|
+
exports.platformWeb = void 0;
|
|
9
|
+
|
|
10
|
+
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
|
|
11
|
+
|
|
12
|
+
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
|
|
13
|
+
|
|
14
|
+
var _pouchdbBrowser = _interopRequireDefault(require("pouchdb-browser"));
|
|
15
|
+
|
|
16
|
+
var events = {
|
|
17
|
+
addEventListener: function addEventListener(eventName, handler) {
|
|
18
|
+
document.addEventListener(eventName, handler);
|
|
19
|
+
},
|
|
20
|
+
removeEventListener: function removeEventListener(eventName, handler) {
|
|
21
|
+
document.removeEventListener(eventName, handler);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
var storage = {
|
|
25
|
+
getItem: function () {
|
|
26
|
+
var _getItem = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(key) {
|
|
27
|
+
return _regenerator.default.wrap(function _callee$(_context) {
|
|
28
|
+
while (1) {
|
|
29
|
+
switch (_context.prev = _context.next) {
|
|
30
|
+
case 0:
|
|
31
|
+
return _context.abrupt("return", window.localStorage.getItem(key));
|
|
32
|
+
|
|
33
|
+
case 1:
|
|
34
|
+
case "end":
|
|
35
|
+
return _context.stop();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}, _callee);
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
function getItem(_x) {
|
|
42
|
+
return _getItem.apply(this, arguments);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return getItem;
|
|
46
|
+
}(),
|
|
47
|
+
setItem: function () {
|
|
48
|
+
var _setItem = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee2(key, value) {
|
|
49
|
+
return _regenerator.default.wrap(function _callee2$(_context2) {
|
|
50
|
+
while (1) {
|
|
51
|
+
switch (_context2.prev = _context2.next) {
|
|
52
|
+
case 0:
|
|
53
|
+
return _context2.abrupt("return", window.localStorage.setItem(key, value));
|
|
54
|
+
|
|
55
|
+
case 1:
|
|
56
|
+
case "end":
|
|
57
|
+
return _context2.stop();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}, _callee2);
|
|
61
|
+
}));
|
|
62
|
+
|
|
63
|
+
function setItem(_x2, _x3) {
|
|
64
|
+
return _setItem.apply(this, arguments);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return setItem;
|
|
68
|
+
}(),
|
|
69
|
+
removeItem: function () {
|
|
70
|
+
var _removeItem = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee3(key) {
|
|
71
|
+
return _regenerator.default.wrap(function _callee3$(_context3) {
|
|
72
|
+
while (1) {
|
|
73
|
+
switch (_context3.prev = _context3.next) {
|
|
74
|
+
case 0:
|
|
75
|
+
return _context3.abrupt("return", window.localStorage.removeItem(key));
|
|
76
|
+
|
|
77
|
+
case 1:
|
|
78
|
+
case "end":
|
|
79
|
+
return _context3.stop();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}, _callee3);
|
|
83
|
+
}));
|
|
84
|
+
|
|
85
|
+
function removeItem(_x4) {
|
|
86
|
+
return _removeItem.apply(this, arguments);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return removeItem;
|
|
90
|
+
}()
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
var isOnline = /*#__PURE__*/function () {
|
|
94
|
+
var _ref = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee4() {
|
|
95
|
+
return _regenerator.default.wrap(function _callee4$(_context4) {
|
|
96
|
+
while (1) {
|
|
97
|
+
switch (_context4.prev = _context4.next) {
|
|
98
|
+
case 0:
|
|
99
|
+
return _context4.abrupt("return", window.navigator.onLine);
|
|
100
|
+
|
|
101
|
+
case 1:
|
|
102
|
+
case "end":
|
|
103
|
+
return _context4.stop();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}, _callee4);
|
|
107
|
+
}));
|
|
108
|
+
|
|
109
|
+
return function isOnline() {
|
|
110
|
+
return _ref.apply(this, arguments);
|
|
111
|
+
};
|
|
112
|
+
}();
|
|
113
|
+
|
|
114
|
+
var platformWeb = {
|
|
115
|
+
storage: storage,
|
|
116
|
+
events: events,
|
|
117
|
+
pouchAdapter: _pouchdbBrowser.default,
|
|
118
|
+
isOnline: isOnline
|
|
119
|
+
};
|
|
120
|
+
exports.platformWeb = platformWeb;
|
package/dist/remote.js
CHANGED
|
@@ -5,7 +5,7 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", {
|
|
6
6
|
value: true
|
|
7
7
|
});
|
|
8
|
-
exports.fetchRemoteLastSequence = exports.fetchRemoteInstance = void 0;
|
|
8
|
+
exports.fetchRemoteLastSequence = exports.fetchRemoteInstance = exports.isDatabaseUnradableError = exports.isDatabaseNotFoundError = exports.DATABASE_RESERVED_DOCTYPE_ERROR = exports.DATABASE_NOT_FOUND_ERROR = void 0;
|
|
9
9
|
|
|
10
10
|
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
|
|
11
11
|
|
|
@@ -13,13 +13,31 @@ var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/
|
|
|
13
13
|
|
|
14
14
|
var _AccessToken = _interopRequireDefault(require("./AccessToken"));
|
|
15
15
|
|
|
16
|
+
var DATABASE_NOT_FOUND_ERROR = 'Database does not exist';
|
|
17
|
+
exports.DATABASE_NOT_FOUND_ERROR = DATABASE_NOT_FOUND_ERROR;
|
|
18
|
+
var DATABASE_RESERVED_DOCTYPE_ERROR = 'Reserved doctype';
|
|
19
|
+
exports.DATABASE_RESERVED_DOCTYPE_ERROR = DATABASE_RESERVED_DOCTYPE_ERROR;
|
|
20
|
+
|
|
21
|
+
var isDatabaseNotFoundError = function isDatabaseNotFoundError(error) {
|
|
22
|
+
return error.message === DATABASE_NOT_FOUND_ERROR;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
exports.isDatabaseNotFoundError = isDatabaseNotFoundError;
|
|
26
|
+
|
|
27
|
+
var isDatabaseUnradableError = function isDatabaseUnradableError(error) {
|
|
28
|
+
return error.message === DATABASE_RESERVED_DOCTYPE_ERROR;
|
|
29
|
+
};
|
|
16
30
|
/**
|
|
17
31
|
* Fetch remote instance
|
|
18
32
|
*
|
|
19
33
|
* @param {URL} url - The remote instance URL, including the credentials
|
|
20
34
|
* @param {object} params - The params to query the remote instance
|
|
21
|
-
* @returns {object} The instance response
|
|
35
|
+
* @returns {Promise<object>} The instance response
|
|
22
36
|
*/
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
exports.isDatabaseUnradableError = isDatabaseUnradableError;
|
|
40
|
+
|
|
23
41
|
var fetchRemoteInstance = /*#__PURE__*/function () {
|
|
24
42
|
var _ref = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(url) {
|
|
25
43
|
var params,
|
|
@@ -66,9 +84,25 @@ var fetchRemoteInstance = /*#__PURE__*/function () {
|
|
|
66
84
|
return _context.abrupt("return", data);
|
|
67
85
|
|
|
68
86
|
case 16:
|
|
69
|
-
|
|
87
|
+
if (!(resp.status === 404)) {
|
|
88
|
+
_context.next = 18;
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
throw new Error(DATABASE_NOT_FOUND_ERROR);
|
|
93
|
+
|
|
94
|
+
case 18:
|
|
95
|
+
if (!(resp.status === 403 && data.error.includes('message=reserved doctype'))) {
|
|
96
|
+
_context.next = 20;
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
throw new Error(DATABASE_RESERVED_DOCTYPE_ERROR);
|
|
101
|
+
|
|
102
|
+
case 20:
|
|
103
|
+
throw new Error("Error (".concat(resp.status, ") while fetching remote instance: ").concat(JSON.stringify(data)));
|
|
70
104
|
|
|
71
|
-
case
|
|
105
|
+
case 21:
|
|
72
106
|
case "end":
|
|
73
107
|
return _context.stop();
|
|
74
108
|
}
|
|
@@ -84,7 +118,7 @@ var fetchRemoteInstance = /*#__PURE__*/function () {
|
|
|
84
118
|
* Fetch last sequence from remote instance
|
|
85
119
|
*
|
|
86
120
|
* @param {string} baseUrl - The base URL of the remote instance
|
|
87
|
-
* @returns {string} The last sequence
|
|
121
|
+
* @returns {Promise<string>} The last sequence
|
|
88
122
|
*/
|
|
89
123
|
|
|
90
124
|
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { enableFetchMocks, disableFetchMocks } from 'jest-fetch-mock'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
DATABASE_NOT_FOUND_ERROR,
|
|
5
|
+
DATABASE_RESERVED_DOCTYPE_ERROR,
|
|
6
|
+
fetchRemoteInstance,
|
|
7
|
+
fetchRemoteLastSequence
|
|
8
|
+
} from './remote'
|
|
9
|
+
|
|
10
|
+
describe('remote', () => {
|
|
11
|
+
beforeAll(() => {
|
|
12
|
+
enableFetchMocks()
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
fetch.resetMocks()
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
afterAll(() => {
|
|
20
|
+
disableFetchMocks()
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
describe('fetchRemoteInstance', () => {
|
|
24
|
+
it(`Should add Authorization header based on URL's password`, async () => {
|
|
25
|
+
const remoteUrl =
|
|
26
|
+
'https://user:SOME_TOKEN@claude.mycozy.cloud/data/io.cozy.accounts/_changes'
|
|
27
|
+
|
|
28
|
+
mockDatabaseOn(
|
|
29
|
+
'https://claude.mycozy.cloud/data/io.cozy.accounts/_changes'
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
await fetchRemoteInstance(new URL(remoteUrl))
|
|
33
|
+
|
|
34
|
+
const expectedHeaders = new Headers()
|
|
35
|
+
expectedHeaders.append('Accept', 'application/json')
|
|
36
|
+
expectedHeaders.append('Content-Type', 'application/json')
|
|
37
|
+
expectedHeaders.append('Authorization', 'Bearer SOME_TOKEN')
|
|
38
|
+
|
|
39
|
+
expect(fetch).toHaveBeenCalledWith(
|
|
40
|
+
'https://claude.mycozy.cloud/data/io.cozy.accounts/_changes',
|
|
41
|
+
{
|
|
42
|
+
headers: expectedHeaders
|
|
43
|
+
}
|
|
44
|
+
)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('Should return data when found', async () => {
|
|
48
|
+
const remoteUrl =
|
|
49
|
+
'https://user:SOME_TOKEN@claude.mycozy.cloud/data/io.cozy.accounts/_changes?limit=1&descending=true'
|
|
50
|
+
mockDatabaseOn(
|
|
51
|
+
'https://claude.mycozy.cloud/data/io.cozy.accounts/_changes?limit=1&descending=true'
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
const result = await fetchRemoteInstance(new URL(remoteUrl))
|
|
55
|
+
|
|
56
|
+
expect(result).toStrictEqual({
|
|
57
|
+
last_seq: '97-SOME_SEQ_VALUE',
|
|
58
|
+
pending: -1,
|
|
59
|
+
results: [
|
|
60
|
+
{
|
|
61
|
+
id: 'SOME_ID',
|
|
62
|
+
seq: '97-SOME_SEQ_VALUE',
|
|
63
|
+
doc: null,
|
|
64
|
+
changes: [{ rev: '3-SOME_REV' }]
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
})
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('Should add parameters when given', async () => {
|
|
71
|
+
const remoteUrl =
|
|
72
|
+
'https://user:SOME_TOKEN@claude.mycozy.cloud/data/io.cozy.accounts/_changes'
|
|
73
|
+
|
|
74
|
+
mockDatabaseOn(
|
|
75
|
+
'https://claude.mycozy.cloud/data/io.cozy.accounts/_changes?limit=1&descending=true'
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
await fetchRemoteInstance(new URL(remoteUrl), {
|
|
79
|
+
limit: 1,
|
|
80
|
+
descending: true
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
const expectedHeaders = new Headers()
|
|
84
|
+
expectedHeaders.append('Accept', 'application/json')
|
|
85
|
+
expectedHeaders.append('Content-Type', 'application/json')
|
|
86
|
+
expectedHeaders.append('Authorization', 'Bearer SOME_TOKEN')
|
|
87
|
+
|
|
88
|
+
expect(fetch).toHaveBeenCalledWith(
|
|
89
|
+
'https://claude.mycozy.cloud/data/io.cozy.accounts/_changes?limit=1&descending=true',
|
|
90
|
+
expect.anything()
|
|
91
|
+
)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('Should throw when 404 error', async () => {
|
|
95
|
+
const remoteUrl =
|
|
96
|
+
'https://user:SOME_TOKEN@claude.mycozy.cloud/data/io.cozy.accounts/_changes?limit=1&descending=true'
|
|
97
|
+
|
|
98
|
+
mockDatabaseNotFoundOn(
|
|
99
|
+
'https://claude.mycozy.cloud/data/io.cozy.accounts/_changes?limit=1&descending=true'
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
await expect(fetchRemoteInstance(new URL(remoteUrl))).rejects.toThrow(
|
|
103
|
+
DATABASE_NOT_FOUND_ERROR
|
|
104
|
+
)
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
describe('fetchRemoteLastSequence', () => {
|
|
109
|
+
it('Should return data when found', async () => {
|
|
110
|
+
const remoteUrl =
|
|
111
|
+
'https://user:SOME_TOKEN@claude.mycozy.cloud/data/io.cozy.accounts'
|
|
112
|
+
mockDatabaseOn(
|
|
113
|
+
'https://claude.mycozy.cloud/data/io.cozy.accounts/_changes?limit=1&descending=true'
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
const result = await fetchRemoteLastSequence(remoteUrl)
|
|
117
|
+
|
|
118
|
+
expect(result).toBe('97-SOME_SEQ_VALUE')
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('Shoud throw when HTTP error', async () => {
|
|
122
|
+
const remoteUrl =
|
|
123
|
+
'https://user:SOME_TOKEN@claude.mycozy.cloud/data/io.cozy.accounts'
|
|
124
|
+
mockUnknownErrorOn(
|
|
125
|
+
'https://claude.mycozy.cloud/data/io.cozy.accounts/_changes?limit=1&descending=true'
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
await expect(fetchRemoteLastSequence(remoteUrl)).rejects.toThrow(
|
|
129
|
+
'Error (503) while fetching remote instance: {"error":"code=503, message=SOME UNKNOWN ERROR"}'
|
|
130
|
+
)
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it('Shoud throw dedicated error when 404 error', async () => {
|
|
134
|
+
const remoteUrl =
|
|
135
|
+
'https://user:SOME_TOKEN@claude.mycozy.cloud/data/io.cozy.accounts'
|
|
136
|
+
mockDatabaseNotFoundOn(
|
|
137
|
+
'https://claude.mycozy.cloud/data/io.cozy.accounts/_changes?limit=1&descending=true'
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
await expect(fetchRemoteLastSequence(remoteUrl)).rejects.toThrow(
|
|
141
|
+
DATABASE_NOT_FOUND_ERROR
|
|
142
|
+
)
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it('Shoud throw dedicated error when Reserved Doctype error', async () => {
|
|
146
|
+
const remoteUrl =
|
|
147
|
+
'https://user:SOME_TOKEN@claude.mycozy.cloud/data/io.cozy.accounts'
|
|
148
|
+
mockDatabaseReservedDoctypeOn(
|
|
149
|
+
'https://claude.mycozy.cloud/data/io.cozy.accounts/_changes?limit=1&descending=true'
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
await expect(fetchRemoteLastSequence(remoteUrl)).rejects.toThrow(
|
|
153
|
+
DATABASE_RESERVED_DOCTYPE_ERROR
|
|
154
|
+
)
|
|
155
|
+
})
|
|
156
|
+
})
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
const mockDatabaseNotFoundOn = url => {
|
|
160
|
+
fetch.mockOnceIf(url, JSON.stringify({}), {
|
|
161
|
+
error: 'not_found',
|
|
162
|
+
ok: false,
|
|
163
|
+
reason: 'Database does not exist.',
|
|
164
|
+
status: 404
|
|
165
|
+
})
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const mockDatabaseReservedDoctypeOn = url => {
|
|
169
|
+
fetch.mockOnceIf(
|
|
170
|
+
url,
|
|
171
|
+
JSON.stringify({
|
|
172
|
+
error: 'code=403, message=reserved doctype io.cozy.sharings unreadable'
|
|
173
|
+
}),
|
|
174
|
+
{
|
|
175
|
+
ok: false,
|
|
176
|
+
status: 403
|
|
177
|
+
}
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const mockUnknownErrorOn = url => {
|
|
182
|
+
fetch.mockOnceIf(
|
|
183
|
+
url,
|
|
184
|
+
JSON.stringify({
|
|
185
|
+
error: 'code=503, message=SOME UNKNOWN ERROR'
|
|
186
|
+
}),
|
|
187
|
+
{
|
|
188
|
+
ok: false,
|
|
189
|
+
status: 503
|
|
190
|
+
}
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const mockDatabaseOn = url => {
|
|
195
|
+
fetch.mockOnceIf(
|
|
196
|
+
url,
|
|
197
|
+
JSON.stringify({
|
|
198
|
+
last_seq: '97-SOME_SEQ_VALUE',
|
|
199
|
+
pending: -1,
|
|
200
|
+
results: [
|
|
201
|
+
{
|
|
202
|
+
id: 'SOME_ID',
|
|
203
|
+
seq: '97-SOME_SEQ_VALUE',
|
|
204
|
+
doc: null,
|
|
205
|
+
changes: [{ rev: '3-SOME_REV' }]
|
|
206
|
+
}
|
|
207
|
+
]
|
|
208
|
+
}),
|
|
209
|
+
{
|
|
210
|
+
ok: true,
|
|
211
|
+
status: 200
|
|
212
|
+
}
|
|
213
|
+
)
|
|
214
|
+
}
|