diff --git a/.gitignore b/.gitignore index ad8bc6af3..3c3324c0e 100644 --- a/.gitignore +++ b/.gitignore @@ -18,7 +18,10 @@ doc/framework dead_links.json # Cypress debug -doc/6/getting-started/.react/cypress/screenshots -doc/6/getting-started/.react/cypress/videos -doc/6/getting-started/.vuejs/cypress/screenshots -doc/6/getting-started/.vuejs/cypress/videos +doc/7/getting-started/.react/cypress/screenshots +doc/7/getting-started/.react/cypress/videos +doc/7/getting-started/.vuejs/cypress/screenshots +doc/7/getting-started/.vuejs/cypress/videos + +# Debug snippets +test-*.js \ No newline at end of file diff --git a/doc/7/controllers/security/refresh/index.md b/doc/7/controllers/security/refresh/index.md new file mode 100644 index 000000000..6e3f6fa3c --- /dev/null +++ b/doc/7/controllers/security/refresh/index.md @@ -0,0 +1,35 @@ +--- +code: true +type: page +title: refresh +--- + +# refresh + +Forces an immediate [reindexation](https://www.elastic.co/guide/en/elasticsearch/reference/7.4/docs-refresh.html) of the provided security collection. + +The available security collections are: `users`, `profiles`, `roles`. + +When writing or deleting documents in Kuzzle, the changes need to be indexed before being reflected in the search results. +By default, this operation can take up to 1 second. + +::: warning +Forcing immediate refreshes comes with performance costs, and should only performed when absolutely necessary. +::: + + +```js +refresh(collection); +``` + +## Arguments + +- `collection`: collection name to refresh + +## Resolves + +Resolves when the refresh has been done. + +## Usage + +<<< ./snippets/refresh.js \ No newline at end of file diff --git a/doc/7/controllers/security/refresh/snippets/refresh.js b/doc/7/controllers/security/refresh/snippets/refresh.js new file mode 100644 index 000000000..32bd8498f --- /dev/null +++ b/doc/7/controllers/security/refresh/snippets/refresh.js @@ -0,0 +1,6 @@ +try { + await kuzzle.security.refresh('users'); + console.log('Success'); +} catch (e) { + console.error(e); +} \ No newline at end of file diff --git a/doc/7/controllers/security/refresh/snippets/refresh.test.yml b/doc/7/controllers/security/refresh/snippets/refresh.test.yml new file mode 100644 index 000000000..deb50cd40 --- /dev/null +++ b/doc/7/controllers/security/refresh/snippets/refresh.test.yml @@ -0,0 +1,5 @@ +name: security#refresh +description: Refreshes security collection +hooks: +template: default +expected: Success diff --git a/doc/7/core-classes/search-result/next/index.md b/doc/7/core-classes/search-result/next/index.md index 3233dccd7..13540aa3e 100644 --- a/doc/7/core-classes/search-result/next/index.md +++ b/doc/7/core-classes/search-result/next/index.md @@ -20,9 +20,9 @@ next(); Resolves to a `SearchResult` object, or to `null` if no more pages are available. -## Throw +## Rejects -This method throws an exception if: +This method returns a rejected promise with an error if: - No pagination strategy can be applied (see below) - If invoking it would lead to more than 10 000 items being retrieved with the `from/size` strategy @@ -56,12 +56,19 @@ You can restrict the scroll session maximum duration under the `services.storage If the initial search contains `sort` and `size` parameters, the `next` method retrieves the next page of results following the sort order, the last item of the current page acting as a live cursor. -To avoid too many duplicates, it is advised to provide a sort combination that will always identify one item only. The recommended way is to use the field `_uid` which is certain to contain one unique value for each document. +This strategy uses Elasticsearch [search_after](https://www.elastic.co/guide/en/elasticsearch/reference/7.4/search-request-body.html#request-body-search-search-after) parameter. + +::: warning +You have to provide a sort combination that will always identify one item only. The recommended way is to use the field `_id` which is certain to contain one unique value for each document. +To prevent partial retrieval of results, the SDK will reject with an error if the sort combination can identify multiple items. +::: Because this method does not freeze the search results between two calls, there can be missing or duplicated documents between two result pages. This method efficiently mitigates the costs of scroll searches, but returns less consistent results: it's a middle ground, ideal for real-time search requests. +<<< ./snippets/sortsize.js + ### Strategy: from / size If the initial search contains `from` and `size` parameters, the `next` method retrieves the next page of result by incrementing the `from` offset. @@ -69,6 +76,6 @@ If the initial search contains `from` and `size` parameters, the `next` method r Because this method does not freeze the search results between two calls, there can be missing or duplicated documents between two result pages. It's the fastest pagination method available, but also the less consistent, and it is not possible to retrieve more than 10000 items using it. -Above that limit, any call to `next` throws an Exception. +Above that limit, any call to `next` will return a rejected promise with an error. <<< ./snippets/fromsize.js diff --git a/doc/7/core-classes/search-result/next/snippets/sortsize.js b/doc/7/core-classes/search-result/next/snippets/sortsize.js new file mode 100644 index 000000000..7ef0791b3 --- /dev/null +++ b/doc/7/core-classes/search-result/next/snippets/sortsize.js @@ -0,0 +1,47 @@ +try { + const documents = []; + + for (let i = 0; i < 100; i++) { + documents.push({ _id: `suv_no${i}`, body: { category: 'suv' } }); + } + + await kuzzle.document.mCreate('nyc-open-data', 'yellow-taxi', documents, { + refresh: 'wait_for' + }); + + let results = await kuzzle.document.search( + 'nyc-open-data', + 'yellow-taxi', + { + query: { match: { category: 'suv' } }, + sort: [ + { '_kuzzle_info.createdAt': 'desc' }, + '_id' + ] + }, + { size: 5 }); + + // Fetch the matched items by advancing through the result pages + const matched = []; + + while (results) { + matched.push(...results.hits); + results = await results.next(); + } + + console.log(matched[0]); + /* + { _id: 'suv_no1', + _score: 0.03390155, + _source: + { _kuzzle_info: + { author: '-1', + updater: null, + updatedAt: null, + createdAt: 1570093133057 }, + category: 'suv' } } + */ + console.log(`Successfully retrieved ${matched.length} documents`); +} catch (error) { + console.error(error.message); +} diff --git a/doc/7/core-classes/search-result/next/snippets/sortsize.test.yml b/doc/7/core-classes/search-result/next/snippets/sortsize.test.yml new file mode 100644 index 000000000..d6c320f00 --- /dev/null +++ b/doc/7/core-classes/search-result/next/snippets/sortsize.test.yml @@ -0,0 +1,11 @@ +name: searchresult#sortsize +description: Next method with sort/size +hooks: + before: | + curl -XDELETE kuzzle:7512/nyc-open-data + curl -XPOST kuzzle:7512/nyc-open-data/_create + curl -XPUT kuzzle:7512/nyc-open-data/yellow-taxi + after: | + curl -XDELETE kuzzle:7512/nyc-open-data +template: default +expected: Successfully retrieved 100 documents \ No newline at end of file diff --git a/doc/7/getting-started/react-native/index.md b/doc/7/getting-started/react-native/index.md index 7c635c79f..4d8d9ea82 100644 --- a/doc/7/getting-started/react-native/index.md +++ b/doc/7/getting-started/react-native/index.md @@ -16,7 +16,7 @@ This section deals with **Kuzzle V2** (+ **Javascript SDK 7**) and **React Nativ - **Node.js** >= 12.0.0 ([install here](https://nodejs.org/en/download/)) - **Running Kuzzle V2 Stack** ([instructions here](/core/2/guides/getting-started/running-kuzzle)) -- **Expo CLI** ([install here](https://docs.expo.io/versions/v36.0.0/get-started/installation/)) +- **Expo CLI** ([install here](https://docs.expo.io/get-started/installation)) "[Expo](https://docs.expo.io/versions/latest/) is a framework and a platform for universal React applications. It is a set of tools and services built around React Native and native platforms that help you develop, build, deploy, and quickly iterate on iOS, Android, and web apps from the same JavaScript/TypeScript codebase." diff --git a/package-lock.json b/package-lock.json index 707a27796..ae4166032 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "kuzzle-sdk", - "version": "7.1.4", + "version": "7.2.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1815,12 +1815,6 @@ "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", "dev": true }, - "at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "dev": true - }, "atob": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", @@ -3510,28 +3504,48 @@ } }, "eslint-loader": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-loader/-/eslint-loader-4.0.0.tgz", - "integrity": "sha512-QoaFRdh3oXt5i2uonSjO8dDnncsG05w7qvA7yYMvGDne8zAEk9R+R1rsfunp3OKVdO5mAJelf1x2Z1kYp664kA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/eslint-loader/-/eslint-loader-4.0.2.tgz", + "integrity": "sha512-EDpXor6lsjtTzZpLUn7KmXs02+nIjGcgees9BYjNkWra3jVq5vVa8IoCKgzT2M7dNNeoMBtaSG83Bd40N3poLw==", "dev": true, "requires": { - "fs-extra": "^9.0.0", - "loader-fs-cache": "^1.0.3", + "find-cache-dir": "^3.3.1", + "fs-extra": "^8.1.0", "loader-utils": "^2.0.0", "object-hash": "^2.0.3", "schema-utils": "^2.6.5" }, "dependencies": { + "find-cache-dir": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", + "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, "fs-extra": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.0.tgz", - "integrity": "sha512-pmEYSk3vYsG/bF651KPUXZ+hvjpgWYw/Gc7W9NFUe3ZVLczKKWIij3IKpOrQcdw4TILtibFslZ0UmR8Vvzig4g==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, "requires": { - "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^1.0.0" + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" } }, "graceful-fs": { @@ -3540,16 +3554,6 @@ "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", "dev": true }, - "jsonfile": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz", - "integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^1.0.0" - } - }, "loader-utils": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", @@ -3561,10 +3565,52 @@ "json5": "^2.1.2" } }, - "universalify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", - "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==", + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true } } @@ -6105,57 +6151,6 @@ } } }, - "loader-fs-cache": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/loader-fs-cache/-/loader-fs-cache-1.0.3.tgz", - "integrity": "sha512-ldcgZpjNJj71n+2Mf6yetz+c9bM4xpKtNds4LbqXzU/PTdeAX0g3ytnU1AJMEcTk2Lex4Smpe3Q/eCTsvUBxbA==", - "dev": true, - "requires": { - "find-cache-dir": "^0.1.1", - "mkdirp": "^0.5.1" - }, - "dependencies": { - "find-cache-dir": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-0.1.1.tgz", - "integrity": "sha1-yN765XyKUqinhPnjHFfHQumToLk=", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "mkdirp": "^0.5.1", - "pkg-dir": "^1.0.0" - } - }, - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "dev": true, - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "dev": true, - "requires": { - "pinkie-promise": "^2.0.0" - } - }, - "pkg-dir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz", - "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", - "dev": true, - "requires": { - "find-up": "^1.0.0" - } - } - } - }, "loader-runner": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", @@ -6808,9 +6803,9 @@ } }, "nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", + "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==", "dev": true, "optional": true }, @@ -7597,21 +7592,6 @@ "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "dev": true }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true, - "requires": { - "pinkie": "^2.0.0" - } - }, "pkg-dir": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", @@ -8636,9 +8616,9 @@ } }, "source-map-support": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz", - "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==", + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", "dev": true, "requires": { "buffer-from": "^1.0.0", @@ -9045,9 +9025,9 @@ } }, "terser": { - "version": "4.6.11", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.6.11.tgz", - "integrity": "sha512-76Ynm7OXUG5xhOpblhytE7X58oeNSmC8xnNhjWVo8CksHit0U0kO4hfNbPrrYwowLWFgM2n9L176VNx2QaHmtA==", + "version": "4.6.12", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.6.12.tgz", + "integrity": "sha512-fnIwuaKjFPANG6MAixC/k1TDtnl1YlPLUlLVIxxGZUn1gfUx2+l3/zGNB72wya+lgsb50QBi2tUV75RiODwnww==", "dev": true, "requires": { "commander": "^2.20.0", @@ -9555,16 +9535,16 @@ } }, "webpack": { - "version": "4.42.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.42.1.tgz", - "integrity": "sha512-SGfYMigqEfdGchGhFFJ9KyRpQKnipvEvjc1TwrXEPCM6H5Wywu10ka8o3KGrMzSMxMQKt8aCHUFh5DaQ9UmyRg==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.43.0.tgz", + "integrity": "sha512-GW1LjnPipFW2Y78OOab8NJlCflB7EFskMih2AHdvjbpKMeDJqEgSx24cXXXiPS65+WSwVyxtDsJH6jGX2czy+g==", "dev": true, "requires": { "@webassemblyjs/ast": "1.9.0", "@webassemblyjs/helper-module-context": "1.9.0", "@webassemblyjs/wasm-edit": "1.9.0", "@webassemblyjs/wasm-parser": "1.9.0", - "acorn": "^6.2.1", + "acorn": "^6.4.1", "ajv": "^6.10.2", "ajv-keywords": "^3.4.1", "chrome-trace-event": "^1.0.2", @@ -9581,7 +9561,7 @@ "schema-utils": "^1.0.0", "tapable": "^1.1.3", "terser-webpack-plugin": "^1.4.3", - "watchpack": "^1.6.0", + "watchpack": "^1.6.1", "webpack-sources": "^1.4.1" }, "dependencies": { @@ -9746,9 +9726,9 @@ } }, "ws": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.3.tgz", - "integrity": "sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ==" + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.5.tgz", + "integrity": "sha512-C34cIU4+DB2vMyAbmEKossWq2ZQDr6QEyuuCzWrM9zfw1sGc0mYiJ0UnG9zzNykt49C2Fi34hvr2vssFQRS6EA==" }, "xregexp": { "version": "4.3.0", diff --git a/package.json b/package.json index cafb909b8..5212009e8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "kuzzle-sdk", - "version": "7.1.4", + "version": "7.2.0", "description": "Official Javascript SDK for Kuzzle", "author": "The Kuzzle Team ", "repository": { @@ -39,7 +39,7 @@ "license": "Apache-2.0", "dependencies": { "min-req-promise": "^1.0.1", - "ws": "^7.2.3" + "ws": "^7.2.5" }, "devDependencies": { "@babel/core": "^7.9.0", @@ -49,7 +49,7 @@ "cucumber": "^6.0.5", "eslint": "^6.8.0", "eslint-friendly-formatter": "^4.0.1", - "eslint-loader": "^4.0.0", + "eslint-loader": "^4.0.2", "kuzdoc": "^1.2.2", "lolex": "^6.0.0", "mocha": "7.1.1", @@ -61,7 +61,7 @@ "should": "13.2.3", "should-sinon": "0.0.6", "sinon": "^9.0.2", - "webpack": "^4.42.1" + "webpack": "^4.43.0" }, "engines": { "node": ">= 10.13.0" diff --git a/src/Kuzzle.js b/src/Kuzzle.js index b59124aa7..24e0b2b8b 100644 --- a/src/Kuzzle.js +++ b/src/Kuzzle.js @@ -501,9 +501,6 @@ Discarded request: ${JSON.stringify(request)}`)); this._lastTokenExpired = now; - this.auth.authenticationToken = null; - this.realtime.tokenExpired(); - this.emit('tokenExpired'); } diff --git a/src/controllers/Auth.js b/src/controllers/Auth.js index 49cdacbc7..0dcb9a0e5 100644 --- a/src/controllers/Auth.js +++ b/src/controllers/Auth.js @@ -18,6 +18,10 @@ class AuthController extends BaseController { super(kuzzle, 'auth'); this._authenticationToken = null; + + this.kuzzle.on('tokenExpired', () => { + this._authenticationToken = null; + }); } get authenticationToken () { @@ -27,9 +31,11 @@ class AuthController extends BaseController { set authenticationToken (encodedJwt) { if (encodedJwt === undefined || encodedJwt === null) { this._authenticationToken = null; - } else if (typeof encodedJwt === 'string') { + } + else if (typeof encodedJwt === 'string') { this._authenticationToken = new Jwt(encodedJwt); - } else { + } + else { throw new Error(`Invalid token argument: ${encodedJwt}`); } } diff --git a/src/controllers/Collection.js b/src/controllers/Collection.js index c7bc36456..42ec86320 100644 --- a/src/controllers/Collection.js +++ b/src/controllers/Collection.js @@ -50,11 +50,14 @@ class CollectionController extends BaseController { } getMapping (index, collection, options = {}) { - return this.query({ + const request = { index, collection, - action: 'getMapping' - }, options) + action: 'getMapping', + includeKuzzleMeta: options.includeKuzzleMeta || false + }; + + return this.query(request, options) .then(response => response.result); } @@ -74,8 +77,6 @@ class CollectionController extends BaseController { size: options.size || 0, from: options.from }; - delete options.from; - delete options.size; return this.query(request, options) .then(response => response.result); @@ -89,8 +90,6 @@ class CollectionController extends BaseController { for (const opt of ['from', 'size', 'scroll']) { request[opt] = options[opt]; - - delete options[opt]; } return this.query(request, options) diff --git a/src/controllers/Document.js b/src/controllers/Document.js index 9b1c7972f..bf62cacd0 100644 --- a/src/controllers/Document.js +++ b/src/controllers/Document.js @@ -182,8 +182,8 @@ class DocumentController extends BaseController { search (index, collection, body = {}, options = {}) { return this._search(index, collection, body, options) - .then(({ response, request }) => ( - new DocumentSearchResult(this.kuzzle, request, options, response.result) + .then(({ response, request, opts }) => ( + new DocumentSearchResult(this.kuzzle, request, opts, response.result) )); } @@ -197,15 +197,12 @@ class DocumentController extends BaseController { for (const opt of ['from', 'size', 'scroll']) { request[opt] = options[opt]; - delete options[opt]; } - if (!options.verb) { - options.verb = 'POST'; - } + const opts = { verb: 'POST', ...options }; - return this.query(request, options) - .then(response => ({ response, request })); + return this.query(request, opts) + .then(response => ({ response, request, opts })); } update (index, collection, _id, body, options = {}) { @@ -218,8 +215,6 @@ class DocumentController extends BaseController { retryOnConflict: options.retryOnConflict, source: options.source }; - delete options.source; - delete options.retryOnConflict; return this.query(request, options) .then(response => response.result); diff --git a/src/controllers/Realtime.js b/src/controllers/Realtime.js index ad1959a90..aca778320 100644 --- a/src/controllers/Realtime.js +++ b/src/controllers/Realtime.js @@ -11,6 +11,8 @@ class RealTimeController extends BaseController { this.subscriptions = {}; this.subscriptionsOff = {}; + + this.kuzzle.on('tokenExpired', () => this.tokenExpired()); } count (roomId, options = {}) { diff --git a/src/controllers/Security.js b/src/controllers/Security.js index 1c0e17be9..f4f959a7d 100644 --- a/src/controllers/Security.js +++ b/src/controllers/Security.js @@ -113,7 +113,10 @@ class SecurityController extends BaseController { }; return this.query(request, options) - .then(response => new Profile(this.kuzzle, response.result._id, response.result._source.policies)); + .then(response => new Profile( + this.kuzzle, + response.result._id, + response.result._source)); } createOrReplaceRole (_id, body, options = {}) { @@ -135,7 +138,10 @@ class SecurityController extends BaseController { }; return this.query(request, options) - .then(response => new Profile(this.kuzzle, response.result._id, response.result._source.policies)); + .then(response => new Profile( + this.kuzzle, + response.result._id, + response.result._source)); } createRestrictedUser (body, _id = null, options = {}) { @@ -247,11 +253,11 @@ class SecurityController extends BaseController { } getProfile (_id, options = {}) { - return this.query({ - _id, - action: 'getProfile' - }, options) - .then(response => new Profile(this.kuzzle, response.result._id, response.result._source.policies)); + return this.query({_id, action: 'getProfile'}, options) + .then(response => new Profile( + this.kuzzle, + response.result._id, + response.result._source)); } getProfileMapping (options = {}) { @@ -347,11 +353,9 @@ class SecurityController extends BaseController { } mGetProfiles (ids, options = {}) { - return this.query({ - action: 'mGetProfiles', - body: {ids} - }, options) - .then(response => response.result.hits.map(hit => new Profile(this.kuzzle, hit._id , hit._source.policies))); + return this.query({action: 'mGetProfiles', body: {ids}}, options) + .then(response => response.result.hits.map( + hit => new Profile(this.kuzzle, hit._id, hit._source))); } mGetUsers (ids, options = {}) { @@ -372,6 +376,13 @@ class SecurityController extends BaseController { .then(response => response.result.hits.map(hit => new Role(this.kuzzle, hit._id, hit._source.controllers))); } + refresh(collection) { + return this.query({ + collection, + action: 'refresh' + }); + } + replaceUser (_id, body, options = {}) { const request = { _id, @@ -389,7 +400,6 @@ class SecurityController extends BaseController { }; for (const opt of ['from', 'size', 'scroll']) { request[opt] = options[opt]; - delete options[opt]; } return this.query(request, options) @@ -403,7 +413,6 @@ class SecurityController extends BaseController { }; for (const opt of ['from', 'size']) { request[opt] = options[opt]; - delete options[opt]; } return this.query(request, options) @@ -417,7 +426,6 @@ class SecurityController extends BaseController { }; for (const opt of ['from', 'size', 'scroll']) { request[opt] = options[opt]; - delete options[opt]; } return this.query(request, options) @@ -440,8 +448,12 @@ class SecurityController extends BaseController { body, action: 'updateProfile' }; + return this.query(request, options) - .then(response => new Profile(this.kuzzle, response.result._id, response.result._source.policies)); + .then(response => new Profile( + this.kuzzle, + response.result._id, + response.result._source)); } updateProfileMapping (body, options = {}) { diff --git a/src/controllers/Server.js b/src/controllers/Server.js index 47d56369a..ece80e71e 100644 --- a/src/controllers/Server.js +++ b/src/controllers/Server.js @@ -23,15 +23,7 @@ class ServerController extends BaseControler { return this.query({ action: 'adminExists' }, options) - .then(response => { - if (typeof response.result !== 'object' || typeof response.result.exists !== 'boolean') { - const error = new Error('adminExists: bad response format'); - error.status = 400; - error.response = response; - return Promise.reject(error); - } - return response.result.exists; - }); + .then(response => response.result.exists); } @@ -114,15 +106,7 @@ class ServerController extends BaseControler { return this.query({ action: 'now' }, options) - .then(response => { - if (typeof response.result !== 'object' || typeof response.result.now !== 'number') { - const error = new Error('now: bad response format'); - error.status = 400; - error.response = response; - return Promise.reject(error); - } - return response.result.now; - }); + .then(response => response.result.now); } } diff --git a/src/core/searchResult/Profile.js b/src/core/searchResult/Profile.js index 08a18f7b2..be27cd96d 100644 --- a/src/core/searchResult/Profile.js +++ b/src/core/searchResult/Profile.js @@ -2,13 +2,13 @@ const Profile = require('../security/Profile'); const SearchResultBase = require('./SearchResultBase'); class ProfileSearchResult extends SearchResultBase { - constructor (kuzzle, request, options, response) { super(kuzzle, request, options, response); this._searchAction = 'searchProfiles'; this._scrollAction = 'scrollProfiles'; - this.hits = response.hits.map(hit => new Profile(this._kuzzle, hit._id, hit._source.policies)); + this.hits = response.hits.map( + hit => new Profile(this._kuzzle, hit._id, hit._source)); } next () { @@ -18,7 +18,9 @@ class ProfileSearchResult extends SearchResultBase { return null; } - nextSearchResult.hits = nextSearchResult._response.hits.map(hit => new Profile(nextSearchResult._kuzzle, hit._id, hit._source.policies)); + nextSearchResult.hits = nextSearchResult._response.hits.map( + hit => new Profile(nextSearchResult._kuzzle, hit._id, hit._source)); + return nextSearchResult; }); } diff --git a/src/core/searchResult/Role.js b/src/core/searchResult/Role.js index d2b473134..d03574051 100644 --- a/src/core/searchResult/Role.js +++ b/src/core/searchResult/Role.js @@ -16,7 +16,7 @@ class RoleSearchResult extends SearchResultBase { // in Kuzzle API, scrollRoles action is not available, and searchRoles allows only from and size parameters // => we deny "scroll" and "sort" parameters. if (this._request.scroll || this._request.sort) { - throw new Error('only from/size params are allowed for role search'); + return Promise.reject(new Error('only from/size params are allowed for role search')); } return super.next() diff --git a/src/core/searchResult/SearchResultBase.js b/src/core/searchResult/SearchResultBase.js index f2d588c09..0949a76ec 100644 --- a/src/core/searchResult/SearchResultBase.js +++ b/src/core/searchResult/SearchResultBase.js @@ -38,20 +38,42 @@ class SearchResultBase { .then(response => this._buildNextSearchResult(response)); } else if (this._request.size && this._request.body.sort) { - const - request = Object.assign({}, this._request, { - action: this._searchAction - }), - hit = this._response.hits[this._response.hits.length - 1]; + const request = { ...this._request, action: this._searchAction }; + const hit = this._response.hits[this._response.hits.length - 1]; + + // When sorting only on a non unique field, the search_after will not iterate + // over all documents having the same values but ES will returns the results + // directly after. + // It resulting in having less fetched documents than the total and thus the SDK + // try to fetch the next results page but it's empty + if (! hit) { + return Promise.reject(new Error('Unable to retrieve all results from search: the sort combination must identify one item only. Add document "_id" to the sort.')); + } request.body.search_after = []; - for (const sort of this._request.body.sort) { + let sorts; + if (typeof this._request.body.sort === 'string') { + sorts = [this._request.body.sort]; + } + else if (Array.isArray(this._request.body.sort)) { + sorts = this._request.body.sort; + } + else { + sorts = Object.keys(this._request.body.sort); + } + + if (sorts.length === 0) { + return Promise.reject(new Error('Unable to retrieve next results from search: sort param is empty')); + } + + for (const sort of sorts) { const key = typeof sort === 'string' ? sort : Object.keys(sort)[0]; - const value = key === '_uid' - ? this._request.collection + '#' + hit._id + + const value = key === '_id' + ? hit._id : this._get(hit._source, key.split('.')); request.body.search_after.push(value); @@ -65,14 +87,15 @@ class SearchResultBase { return Promise.resolve(null); } - return this._kuzzle.query(Object.assign({}, this._request, { + return this._kuzzle.query({ + ...this._request, action: this._searchAction, from: this.fetched - }), this._options) + }, this._options) .then(response => this._buildNextSearchResult(response)); } - throw new Error('Unable to retrieve next results from search: missing scrollId, from/sort, or from/size params'); + return Promise.reject(new Error('Unable to retrieve next results from search: missing scrollId, from/sort, or from/size params')); } _get (object, path) { diff --git a/src/core/security/Profile.js b/src/core/security/Profile.js index e4cacfcf4..ca68a6d38 100644 --- a/src/core/security/Profile.js +++ b/src/core/security/Profile.js @@ -4,10 +4,11 @@ class Profile { * @param {Kuzzle} kuzzle * @param {Object} data */ - constructor (kuzzle, _id = null, policies = []) { + constructor (kuzzle, _id = null, content = null) { this._kuzzle = kuzzle; this._id = _id; - this.policies = policies; + this.rateLimit = content && content.rateLimit ? content.rateLimit : 0; + this.policies = content && content.policies ? content.policies : []; } get kuzzle () { @@ -21,7 +22,10 @@ class Profile { if (!this.policies || this.policies.length === 0) { return Promise.resolve([]); } - return this.kuzzle.security.mGetRoles(this.policies.map(policy => policy.roleId), options); + + return this.kuzzle.security.mGetRoles( + this.policies.map(policy => policy.roleId), + options); } } diff --git a/src/protocols/abstract/Base.js b/src/protocols/abstract/Base.js index d8aa9319a..81d6a752d 100644 --- a/src/protocols/abstract/Base.js +++ b/src/protocols/abstract/Base.js @@ -98,7 +98,7 @@ Discarded request: ${JSON.stringify(request)}`)); this.emit('queryError', error, request); - if (request.action !== 'logout' && error.message === 'Token expired') { + if (request.action !== 'logout' && error.id === 'security.token.invalid') { this.emit('tokenExpired'); } diff --git a/test-js.js b/test-js.js deleted file mode 100644 index 900a76a13..000000000 --- a/test-js.js +++ /dev/null @@ -1,38 +0,0 @@ -class BaseHook { - constructor (event, filters) { - - } - - async action () { - - } -} - -backend.registerHook({ - event: 'generic:document:beforeWrite', - action: request => request, - filters: [ - { - exists: 'body.clientId' - }, - { - equals: { 'input.resource.index': 'omniscient' } - } - ] -}) - -this.hooks = { - 'generic:document:beforeWrite': { - action: () => {}, - filters: [ - { - // filter for event first arg - exists: 'body.clientId' - }, - { - // filter for event second arg - equals: { 'input.resource.index': 'omniscient' } - } - ] - } -}; diff --git a/test-sdk.js b/test-sdk.js deleted file mode 100644 index f785fe74a..000000000 --- a/test-sdk.js +++ /dev/null @@ -1,25 +0,0 @@ -const { Kuzzle, Http } = require('./index'); - -const kuzzle = new Kuzzle(new Http('localhost')) - -kuzzle.on('networkError', console.error); - -const run = async () => { - await kuzzle.connect() - - const result = await kuzzle.document.search('nyc-open-data', 'yellow-taxi', { - query: { - range: { - age: { - lte: 8, - gte: 5 - } - } - } - }); - - // Documents with age between 5 and 8 - console.log(result.hits); -}; - -run().catch(error => console.log(error)); diff --git a/test/controllers/auth.test.js b/test/controllers/auth.test.js index 93e2f3f2d..a3e774792 100644 --- a/test/controllers/auth.test.js +++ b/test/controllers/auth.test.js @@ -1,10 +1,11 @@ -const - Jwt = require('../../src/core/Jwt'), - AuthController = require('../../src/controllers/Auth'), - User = require('../../src/core/security/User'), - generateJwt = require('../mocks/generateJwt.mock'), - sinon = require('sinon'), - should = require('should'); +const sinon = require('sinon'); +const should = require('should'); + +const KuzzleEventEmitter = require('../../src/core/KuzzleEventEmitter'); +const Jwt = require('../../src/core/Jwt'); +const AuthController = require('../../src/controllers/Auth'); +const User = require('../../src/core/security/User'); +const generateJwt = require('../mocks/generateJwt.mock'); describe('Auth Controller', () => { const options = {opt: 'in'}; @@ -13,13 +14,24 @@ describe('Auth Controller', () => { kuzzle; beforeEach(() => { - kuzzle = { - emit: sinon.stub(), - query: sinon.stub() - }; + kuzzle = new KuzzleEventEmitter(); + kuzzle.query = sinon.stub(); + kuzzle.auth = new AuthController(kuzzle); }); + describe('on: tokenExpired', () => { + it('should set the authenticationToken to null', () => { + kuzzle.auth.authenticationToken = generateJwt(); + + kuzzle.emit('tokenExpired'); + + process.nextTick(() => { + should(kuzzle.auth.authenticationToken).be.null(); + }); + }); + }); + describe('createApiKey', () => { it('should send request to Kuzzle API', async () => { const apiResult = { @@ -309,6 +321,8 @@ describe('Auth Controller', () => { }); it('should trigger a "loginAttempt" event once the user is logged in', () => { + kuzzle.emit = sinon.stub(); + return kuzzle.auth.login('strategy', credentials, 'expiresIn') .then(() => { should(kuzzle.emit) diff --git a/test/controllers/collection.test.js b/test/controllers/collection.test.js index 7e9e70f1b..957de1e7e 100644 --- a/test/controllers/collection.test.js +++ b/test/controllers/collection.test.js @@ -131,6 +131,8 @@ describe('Collection Controller', () => { } } }); + options.includeKuzzleMeta = true; + return kuzzle.collection.getMapping('index', 'collection', options) .then(res => { @@ -140,7 +142,8 @@ describe('Collection Controller', () => { controller: 'collection', action: 'getMapping', index: 'index', - collection: 'collection' + collection: 'collection', + includeKuzzleMeta: true }, options); should(res).match({ @@ -260,7 +263,7 @@ describe('Collection Controller', () => { from: 3, size: 42, scroll: 'scroll' - }, {foo: 'bar'}); + }, { foo: 'bar', from: 3, scroll: 'scroll', size: 42 }); should(res).be.an.instanceOf(SpecificationsSearchResult); should(res._request).match({ diff --git a/test/controllers/document.test.js b/test/controllers/document.test.js index d95ebca9f..5ab034ef9 100644 --- a/test/controllers/document.test.js +++ b/test/controllers/document.test.js @@ -394,7 +394,8 @@ describe('Document Controller', () => { }, options); should(res).be.an.instanceOf(DocumentSearchResult); - should(res._options).be.equal(options); + should(res._options).match(options); + should(res._options.verb).be.eql('POST'); should(res._response).be.equal(result); should(res.fetched).be.equal(3); should(res.total).be.equal(3); @@ -527,7 +528,7 @@ describe('Document Controller', () => { body: {foo: 'bar'}, retryOnConflict: true, source: undefined - }, {}); + }, { retryOnConflict: true }); should(res).be.equal(result); }); @@ -555,7 +556,7 @@ describe('Document Controller', () => { body: { foo: 'bar' }, retryOnConflict: undefined, source: true - }, {}); + }, { source: true }); should(res).be.equal(result); }); diff --git a/test/controllers/realtime.test.js b/test/controllers/realtime.test.js index fd56f3bd9..5aa4ebcb8 100644 --- a/test/controllers/realtime.test.js +++ b/test/controllers/realtime.test.js @@ -1,22 +1,21 @@ -const - AuthController = require('../../src/controllers/Auth'), - RealtimeController = require('../../src/controllers/Realtime'), - generateJwt = require('../mocks/generateJwt.mock'), - mockrequire = require('mock-require'), - sinon = require('sinon'), - should = require('should'), - uuidv4 = require('../../src/utils/uuidv4'); +const mockrequire = require('mock-require'); +const sinon = require('sinon'); +const should = require('should'); + +const KuzzleEventEmitter = require('../../src/core/KuzzleEventEmitter'); +const AuthController = require('../../src/controllers/Auth'); +const RealtimeController = require('../../src/controllers/Realtime'); +const generateJwt = require('../mocks/generateJwt.mock'); +const uuidv4 = require('../../src/utils/uuidv4'); describe('Realtime Controller', () => { const options = {opt: 'in'}; let kuzzle; beforeEach(() => { - kuzzle = { - addListener: sinon.stub(), - query: sinon.stub().resolves(), - emit: sinon.stub() - }; + kuzzle = new KuzzleEventEmitter(); + kuzzle.query = sinon.stub(); + kuzzle.realtime = new RealtimeController(kuzzle); kuzzle.auth = new AuthController(kuzzle); kuzzle.auth.authenticationToken = generateJwt(); @@ -26,6 +25,19 @@ describe('Realtime Controller', () => { mockrequire.stopAll(); }); + describe('on: tokenExpired', () => { + it('should call tokenExpired() method', () => { + kuzzle.realtime.tokenExpired = sinon.stub(); + + kuzzle.emit('tokenExpired'); + + process.nextTick(() => { + should(kuzzle.realtime.tokenExpired).be.called(); + }); + }); + }); + + describe('#count', () => { it('should call realtime/count query with the roomId and return a Promise which resolves a number', () => { kuzzle.query.resolves({result: {roomId: 'roomId', count: 1234}}); diff --git a/test/controllers/security.test.js b/test/controllers/security.test.js index b3f02fc32..dd497d8d6 100644 --- a/test/controllers/security.test.js +++ b/test/controllers/security.test.js @@ -1,13 +1,12 @@ -const - SecurityController = require('../../src/controllers/Security'), - Profile = require('../../src/core/security/Profile'), - Role = require('../../src/core/security/Role'), - User = require('../../src/core/security/User'), - ProfileSearchResult = require('../../src/core/searchResult/Profile'), - RoleSearchResult = require('../../src/core/searchResult/Role'), - UserSearchResult = require('../../src/core/searchResult/User'), - sinon = require('sinon'), - should = require('should'); +const SecurityController = require('../../src/controllers/Security'); +const Profile = require('../../src/core/security/Profile'); +const Role = require('../../src/core/security/Role'); +const User = require('../../src/core/security/User'); +const ProfileSearchResult = require('../../src/core/searchResult/Profile'); +const RoleSearchResult = require('../../src/core/searchResult/Role'); +const UserSearchResult = require('../../src/core/searchResult/User'); +const sinon = require('sinon'); +const should = require('should'); describe('Security Controller', () => { const options = {opt: 'in'}; @@ -910,6 +909,25 @@ describe('Security Controller', () => { }); }); + describe('refresh', () => { + it('should call security/refresh query and return a Promise', () => { + kuzzle.query.resolves(null); + + return kuzzle.security.refresh('collection') + .then(res => { + should(kuzzle.query) + .be.calledOnce() + .be.calledWith({ + controller: 'security', + action: 'refresh', + collection: 'collection' + }); + + should(res).be.Null(); + }); + }); + }); + describe('replaceUser', () => { it('should call security/replaceUser query with the user content and return a Promise which resolves a User object', () => { kuzzle.query.resolves({ @@ -996,10 +1014,9 @@ describe('Security Controller', () => { from: 1, size: 2, scroll: '10s' - }, {}); + }, { from: 1, scroll: '10s', size: 2 }); should(res).be.an.instanceOf(ProfileSearchResult); - should(res._options).be.empty(); should(res._response).be.equal(result); should(res.fetched).be.equal(2); should(res.total).be.equal(3); @@ -1059,10 +1076,9 @@ describe('Security Controller', () => { body: {controllers: ['foo', 'bar']}, from: 1, size: 2 - }, {}); + }, { from: 1, size: 2 }); should(res).be.an.instanceOf(RoleSearchResult); - should(res._options).be.empty(); should(res._response).be.equal(result); should(res.fetched).be.equal(2); should(res.total).be.equal(3); @@ -1124,10 +1140,9 @@ describe('Security Controller', () => { from: 1, size: 2, scroll: '10s' - }, {}); + }, { from: 1, scroll: '10s', size: 2 }); should(res).be.an.instanceOf(UserSearchResult); - should(res._options).be.empty(); should(res._response).be.equal(result); should(res.fetched).be.equal(2); should(res.total).be.equal(3); diff --git a/test/controllers/server.test.js b/test/controllers/server.test.js index e1b1dd01a..243eb9467 100644 --- a/test/controllers/server.test.js +++ b/test/controllers/server.test.js @@ -47,21 +47,6 @@ describe('Server Controller', () => { }); }); - it('should reject the promise if receiving a response in bad format (missing result)', () => { - kuzzle.query.resolves({foo: 'bar'}); - return should(kuzzle.server.adminExists()).be.rejectedWith({status: 400, message: 'adminExists: bad response format'}); - }); - - it('should reject the promise if receiving a response in bad format (missing "exists" attribute)', () => { - kuzzle.query.resolves({result: {foo: 'bar'}}); - return should(kuzzle.server.adminExists()).be.rejectedWith({status: 400, message: 'adminExists: bad response format'}); - }); - - it('should reject the promise if receiving a response in bad format (bad type of "exists" attribute)', () => { - kuzzle.query.resolves({result: {exists: 'foobar'}}); - return should(kuzzle.server.adminExists()).be.rejectedWith({status: 400, message: 'adminExists: bad response format'}); - }); - it('should reject the promise if an error occurs', () => { const error = new Error('foobar error'); error.status = 412; @@ -427,21 +412,6 @@ describe('Server Controller', () => { }); }); - it('should reject the promise if receiving a response in bad format (missing result)', () => { - kuzzle.query.resolves({foo: 'bar'}); - return should(kuzzle.server.now()).be.rejectedWith({status: 400, message: 'now: bad response format'}); - }); - - it('should reject the promise if receiving a response in bad format (missing "now" attribute)', () => { - kuzzle.query.resolves({result: {foo: 'bar'}}); - return should(kuzzle.server.now()).be.rejectedWith({status: 400, message: 'now: bad response format'}); - }); - - it('should reject the promise if receiving a response in bad format (bad type for "now" attribute)', () => { - kuzzle.query.resolves({result: {now: 'bar'}}); - return should(kuzzle.server.now()).be.rejectedWith({status: 400, message: 'now: bad response format'}); - }); - it('should reject the promise if an error occurs', () => { const error = new Error('foobar error'); error.status = 412; diff --git a/test/core/searchResult/document.test.js b/test/core/searchResult/document.test.js index a51041d5f..eade0043f 100644 --- a/test/core/searchResult/document.test.js +++ b/test/core/searchResult/document.test.js @@ -89,9 +89,8 @@ describe('DocumentSearchResult', () => { searchResult = new DocumentSearchResult(kuzzle, request, options, response); - should(function () { - searchResult.next(); - }).throw('Unable to retrieve next results from search: missing scrollId, from/sort, or from/size params'); + return should(searchResult.next()) + .be.rejectedWith('Unable to retrieve next results from search: missing scrollId, from/sort, or from/size params'); }); describe('#with scroll option', () => { @@ -153,18 +152,20 @@ describe('DocumentSearchResult', () => { }); describe('#with size and sort option', () => { - const nextResponse = { - hits: [ - {_id: 'document3', _score: 0.6543, _source: {foo: 'barbaz', bar: 4567}}, - {_id: 'document4', _score: 0.6123, _source: {foo: 'bazbar', bar: 6789}} - ], - aggregations: 'nextAggregations', - total: 30 - }; + let nextResponse; beforeEach(() => { + nextResponse = { + hits: [ + {_id: 'document3', _score: 0.6543, _source: {foo: 'barbaz', bar: 4567}}, + {_id: 'document4', _score: 0.6123, _source: {foo: 'bazbar', bar: 6789}} + ], + aggregations: 'nextAggregations', + total: 30 + }; + request.size = 2; - request.body.sort = ['foo', {bar: 'asc'}, {_uid: 'desc'}]; + request.body.sort = ['foo', {bar: 'asc'}, {_id: 'desc'}]; response = { hits: [ @@ -191,8 +192,8 @@ describe('DocumentSearchResult', () => { query: { foo: 'bar' }, - sort: ['foo', {bar: 'asc'}, {_uid: 'desc'}], - search_after: ['barbar', 2345, 'collection#document2'] + sort: ['foo', {bar: 'asc'}, {_id: 'desc'}], + search_after: ['barbar', 2345, 'document2'] }, controller: 'document', action: 'search', @@ -215,6 +216,22 @@ describe('DocumentSearchResult', () => { should(nextSearchResult.aggregations).equal(nextResponse.aggregations); }); }); + + it('should reject with an error if the sort is invalid', () => { + request.body.sort = []; + searchResult = new DocumentSearchResult(kuzzle, request, options, response); + + return should(searchResult.next()) + .be.rejected(); + }); + + it('should reject if the sort combination does not allow to retrieve all the documents', () => { + response.hits = []; + searchResult = new DocumentSearchResult(kuzzle, request, options, response); + + return should(searchResult.next()) + .be.rejected(); + }); }); describe('#with from and size option', () => { diff --git a/test/core/searchResult/profile.test.js b/test/core/searchResult/profile.test.js index 4bf577208..8df5c0803 100644 --- a/test/core/searchResult/profile.test.js +++ b/test/core/searchResult/profile.test.js @@ -82,7 +82,7 @@ describe('ProfileSearchResult', () => { }); - it('should throw an error if neither scroll, nor size/sort, nor size/from parameters are set', () => { + it('should reject with an error if neither scroll, nor size/sort, nor size/from parameters are set', () => { response = { scrollId: 'scroll-id', hits: [ @@ -94,9 +94,8 @@ describe('ProfileSearchResult', () => { searchResult = new ProfileSearchResult(kuzzle, request, options, response); - should(function () { - searchResult.next(); - }).throw('Unable to retrieve next results from search: missing scrollId, from/sort, or from/size params'); + return should(searchResult.next()) + .be.rejectedWith('Unable to retrieve next results from search: missing scrollId, from/sort, or from/size params'); }); describe('#with scroll option', () => { diff --git a/test/core/searchResult/role.test.js b/test/core/searchResult/role.test.js index e806eb58b..554199b49 100644 --- a/test/core/searchResult/role.test.js +++ b/test/core/searchResult/role.test.js @@ -79,34 +79,30 @@ describe('RoleSearchResult', () => { should(kuzzle.query).not.be.called(); should(result).be.Null(); }); - }); - it('should throw an error if scrollId parameters is set', () => { + it('should reject with an error if scrollId parameters is set', () => { request.scroll = '10s'; searchResult = new RoleSearchResult(kuzzle, request, options, response); - should(function () { - searchResult.next(); - }).throw('only from/size params are allowed for role search'); + return should(searchResult.next()) + .be.rejectedWith('only from/size params are allowed for role search'); }); - it('should throw an error if sort parameters is set', () => { - request.sort = ['foo', {bar: 'asc'}]; + it('should reject with an error if sort parameters is set', () => { + request.sort = ['foo', { bar: 'asc' }]; searchResult = new RoleSearchResult(kuzzle, request, options, response); - should(function () { - searchResult.next(); - }).throw('only from/size params are allowed for role search'); + return should(searchResult.next()) + .be.rejectedWith('only from/size params are allowed for role search'); }); - it('should throw an error if size and from parameters are not set', () => { + it('should reject with an error if size and from parameters are not set', () => { searchResult = new RoleSearchResult(kuzzle, request, options, response); - should(function () { - searchResult.next(); - }).throw('Unable to retrieve next results from search: missing scrollId, from/sort, or from/size params'); + return should(searchResult.next()) + .be.rejectedWith('Unable to retrieve next results from search: missing scrollId, from/sort, or from/size params'); }); describe('#with from and size option', () => { diff --git a/test/core/searchResult/specifications.test.js b/test/core/searchResult/specifications.test.js index 2b1e06932..156a9e69f 100644 --- a/test/core/searchResult/specifications.test.js +++ b/test/core/searchResult/specifications.test.js @@ -71,7 +71,7 @@ describe('SpecificationsSearchResult', () => { }); - it('should throw an error if neither scroll, nor size/sort, nor size/from parameters are set', () => { + it('should reject with an error if neither scroll, nor size/sort, nor size/from parameters are set', () => { response = { scrollId: 'scroll-id', hits: [ @@ -83,9 +83,8 @@ describe('SpecificationsSearchResult', () => { searchResult = new SpecificationsSearchResult(kuzzle, request, options, response); - should(function () { - searchResult.next(); - }).throw('Unable to retrieve next results from search: missing scrollId, from/sort, or from/size params'); + return should(searchResult.next()) + .be.rejectedWith('Unable to retrieve next results from search: missing scrollId, from/sort, or from/size params'); }); describe('#with scroll option', () => { diff --git a/test/core/searchResult/user.test.js b/test/core/searchResult/user.test.js index effdb7c63..c696db429 100644 --- a/test/core/searchResult/user.test.js +++ b/test/core/searchResult/user.test.js @@ -84,7 +84,7 @@ describe('UserSearchResult', () => { }); - it('should throw an error if neither scroll, nor size/sort, nor size/from parameters are set', () => { + it('should reject with an error if neither scroll, nor size/sort, nor size/from parameters are set', () => { response = { scrollId: 'scroll-id', hits: [ @@ -96,9 +96,8 @@ describe('UserSearchResult', () => { searchResult = new UserSearchResult(kuzzle, request, options, response); - should(function () { - searchResult.next(); - }).throw('Unable to retrieve next results from search: missing scrollId, from/sort, or from/size params'); + return should(searchResult.next()) + .be.rejectedWith('Unable to retrieve next results from search: missing scrollId, from/sort, or from/size params'); }); describe('#with scroll option', () => { diff --git a/test/core/security/profile.test.js b/test/core/security/profile.test.js index 7a4bef851..90dea92ae 100644 --- a/test/core/security/profile.test.js +++ b/test/core/security/profile.test.js @@ -1,23 +1,61 @@ -const - Profile = require('../../../src/core/security/Profile'), - sinon = require('sinon'), - should = require('should'); +const Profile = require('../../../src/core/security/Profile'); +const sinon = require('sinon'); +const should = require('should'); describe('Profile', () => { - let profile; + let kuzzleMock; beforeEach(() => { - profile = new Profile({ + kuzzleMock = { security: { mGetRoles: sinon.stub().resolves([ {_id: 'role1', controllers: ['foo', 'bar']}, {_id: 'role2', controllers: ['foo', 'baz']} ]) } + }; + }); + + describe('profile class', () => { + it('should initialize a null profile with the correct default values', () => { + const profile = new Profile(kuzzleMock); + + should(profile._id).be.null(); + should(profile.policies).be.Array().and.be.empty(); + should(profile.rateLimit).eql(0); + }); + + it('should initialize an empty profile with the correct default values', () => { + const profile = new Profile(kuzzleMock, 'foo', {}); + + should(profile._id).eql('foo'); + should(profile.policies).be.Array().and.be.empty(); + should(profile.rateLimit).eql(0); + }); + + it('should initialize itself properly from a Kuzzle profile document', () => { + const policies = [ + { oh: 'noes' }, + { foo: 'bar' }, + ]; + const profile = new Profile(kuzzleMock, 'foo', { + policies, + rateLimit: 123, + }); + + should(profile._id).eql('foo'); + should(profile.policies).eql(policies); + should(profile.rateLimit).eql(123); }); }); describe('getRoles', () => { + let profile; + + beforeEach(() => { + profile = new Profile(kuzzleMock); + }); + it('should return a Promise which resolves to an empty array if no profile is attached', () => { return profile.getRoles() .then(roles => { @@ -44,4 +82,4 @@ describe('Profile', () => { }); }); }); -}); \ No newline at end of file +}); diff --git a/test/protocol/Base.test.js b/test/protocol/Base.test.js index ca4314651..524aa786b 100644 --- a/test/protocol/Base.test.js +++ b/test/protocol/Base.test.js @@ -119,7 +119,7 @@ describe('Common Protocol', () => { eventStub = sinon.stub(), response = { error: { - message: 'Token expired' + id: 'security.token.invalid' } };