From 009fad77cfd7333637504cdd8525c2ad8bdf44e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9mentine=20Urquizar?= Date: Tue, 16 Jun 2020 15:19:21 +0200 Subject: [PATCH] Upd package for MeiliSeach v0.11 (#413) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Upd version * Upd version * Updated code samples * Update .code-samples.meilisearch.yaml Co-authored-by: Clémentine Urquizar * added code samples * Faceting * Test compatibility with v0.11 * Changes on client tests * Updated error message * Improved types for facetFilters * Faceting typo * Add attributesForFaceting in settings tests * Since this library is not node-only main should be UMD * build(deps-dev): bump eslint-plugin-jsdoc from 27.0.2 to 27.0.4 Bumps [eslint-plugin-jsdoc](https://github.com/gajus/eslint-plugin-jsdoc) from 27.0.2 to 27.0.4. - [Release notes](https://github.com/gajus/eslint-plugin-jsdoc/releases) - [Commits](https://github.com/gajus/eslint-plugin-jsdoc/compare/v27.0.2...v27.0.4) Signed-off-by: dependabot-preview[bot] * changes in error handler * getOrCreateIndex method and Error handler update * Linting and styling * Documentation * Change error names * Changed errorHandler name to httpErrorHandler * changed errorhandler name * Typo in class name * Remove comments * Change usage of createIndex * Update README.md Co-authored-by: Clémentine Urquizar * Make IndexOptions more type safe and create indexRequest type * Remove unecessary comment * Updated changelog * More specific changelog * Update CHANGELOG.md Co-authored-by: Clémentine Urquizar * Change version in CHANGELOG * Improve changelog * Wording in changelog * Update README.md * Improve changelog Co-authored-by: Charlotte Vermandel Co-authored-by: cvermand <33010418+bidoubiwa@users.noreply.github.com> Co-authored-by: Charlotte Vermandel Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- .code-samples.meilisearch.yaml | 50 ++++- CHANGELOG.md | 16 +- README.md | 18 +- examples/search_example.js | 2 +- examples/small_index.js | 4 +- package.json | 4 +- src/errors/http-error-handler.ts | 15 ++ .../meilisearch-api-error.ts} | 20 +- src/errors/meilisearch-communication-error.ts | 11 + .../meilisearch-timeout-error.ts | 0 src/index.ts | 50 ++++- src/meili-axios-wrapper.ts | 12 +- src/meilisearch.ts | 29 ++- src/types.ts | 49 +++-- tests/accept_new_fields_tests.ts | 9 +- tests/attributes_for_faceting_tests.ts | 139 ++++++++++++ tests/client_tests.ts | 202 +++++++----------- tests/displayed_attributes_tests.ts | 16 +- tests/distinct_attribute_tests.ts | 16 +- tests/documents_tests.ts | 29 ++- tests/get_or_create_tests.ts | 69 ++++++ tests/ranking_rules_tests.ts | 16 +- tests/search_tests.ts | 96 +++++++-- tests/searchable_attributes_tests.ts | 16 +- tests/settings_tests.ts | 19 +- tests/stop_words_tests.ts | 16 +- tests/synonyms_tests.ts | 16 +- tests/update_tests.ts | 15 +- tests/wait_for_pending_update_tests.ts | 7 +- 29 files changed, 649 insertions(+), 312 deletions(-) create mode 100644 src/errors/http-error-handler.ts rename src/{custom-errors/meilisearch-error.ts => errors/meilisearch-api-error.ts} (80%) create mode 100644 src/errors/meilisearch-communication-error.ts rename src/{custom-errors => errors}/meilisearch-timeout-error.ts (100%) create mode 100644 tests/attributes_for_faceting_tests.ts create mode 100644 tests/get_or_create_tests.ts diff --git a/.code-samples.meilisearch.yaml b/.code-samples.meilisearch.yaml index 5e8398cf4..712e0e85b 100644 --- a/.code-samples.meilisearch.yaml +++ b/.code-samples.meilisearch.yaml @@ -3,9 +3,9 @@ get_one_index_1: |- list_all_indexes_1: |- client.listIndexes() create_an_index_1: |- - client.createIndex({ uid: 'movies' }) + client.createIndex('movies') update_an_index_1: |- - client.updateIndex({ uid: 'movies' }) + client.updateIndex('movies', { primaryKey: 'movie_review_id' }) delete_an_index_1: |- client.getIndex('movies').deleteIndex() get_one_document_1: |- @@ -322,7 +322,7 @@ getting_started_create_index_md: |- var client = new MeiliSearch({ host: 'http://127.0.0.1:7700' }) const index = client - .createIndex({ uid: 'movies' }) + .createIndex('movies') .then((res) => console.log(res)) ``` @@ -340,3 +340,47 @@ getting_started_search_md: |- ``` [About this package](https://github.com/meilisearch/meilisearch-js/) +get_attributes_for_faceting_1: |- + client.getIndex('movies').getAttributesForFaceting() +update_attributes_for_faceting_1: |- + client.getIndex('movies') + .updateAttributesForFaceting([ + 'genre', + 'director' + ]) +reset_attributes_for_faceting_1: |- + client.getIndex('movies').resetAttributesForFaceting() +faceted_search_update_settings_1: |- + client.getIndex('movies') + .updateAttributesForFaceting([ + 'genre', + 'director' + ]) +faceted_search_facet_filters_1: |- + client.getIndex('movies') + .search('thriller', { + facetFilters: [['genres:Horror', 'genres:Mystery'], 'director:Jordan Peele'] + }) +faceted_search_facets_distribution_1: |- + client.getIndex('movies') + .search('Batman', { + facetsDistribution: ['genres'] + }) +faceted_search_walkthrough_attributes_for_faceting_1: |- + client.getIndex('movies') + .updateAttributesForFaceting([ + 'director', + 'producer', + 'genres', + 'production_companies' + ]) +faceted_search_walkthrough_facet_filters_1: |- + client.getIndex('movies') + .search('thriller', { + facetFilters: [['genres:Horror', 'genres:Mystery'], 'director:Jordan Peele'] + }) +faceted_search_walkthrough_facets_distribution_1: |- + client.getIndex('movies') + .search('Batman', { + facetsDistribution: ['genres'] + }) diff --git a/CHANGELOG.md b/CHANGELOG.md index f383f71f1..4f8371d24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,18 @@ -## V0.10 (released) +## V0.11.1 (released) + +- BREAKING: Usage of createIndex changed `createIndex(uid: string, options: IndexOptions): Index` #436 +- BREAKING: Changes in types + - `MeiliSearchApiErrorInterface` changes + - Removed `UpdateIndexRequest` and replaced it with `IndexOptions` +- Error Handler improved by adding a new MeiliSearchCommunicationError #436 +- Refactor Error handler #436 +- Add getOrCreateIndex method to meilisearch client #436 +- Faceting (#421) +- Improve tests with v11 (#422) +- Improve code examples (#434) +- Update dependencies + +## V0.10.1 (released) - Fix bug where you could not import the CJS package from a ES6 node environment. #410 diff --git a/README.md b/README.md index 27cc81c64..e6ee0817c 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ import MeiliSearch from 'meilisearch' apiKey: 'masterKey', }) - const index = await client.createIndex({ uid: 'books' }) // If your index does not exists + const index = await client.createIndex('books') // If your index does not exist // OR const index = client.getIndex('books') // If your index exists @@ -123,7 +123,7 @@ Output: This package is compatible with the following MeiliSearch versions: -- `v0.10.X` +- `v0.11.X` ## 🎬 Examples @@ -140,9 +140,9 @@ In this section, the examples contain the [`await` keyword](https://developer.mo ```javascript // Create an index -const index = await client.createIndex({ uid: 'books' }) +const index = await client.createIndex('books') // Create an index and give the primary-key -const index = await client.createIndex({ uid: 'books', primaryKey: 'book_id' }) +const index = await client.createIndex('books', { primaryKey: 'book_id' }) ``` #### List all indexes @@ -262,7 +262,7 @@ await index.search('prince', { limit: 1, attributesToHighlight: '*' }) ## ⚙️ Development Workflow -If you want to contribute, this sections describes the steps to follow. +If you want to contribute, this section describes the steps to follow. Thank you for your interest in a MeiliSearch tool! ♥️ @@ -325,19 +325,23 @@ A GitHub Action will be triggered and push the package on [npm](https://www.npmj - Create new index: -`client.createIndex(data: IndexRequest): Promise` +`client.createIndex(uid: string, options?: IndexOptions): Promise` - Get index object: `client.getIndex(uid: string): Indexes` +- Get or create index if it does not exist + +`client.getOrCreateIndex(uid: string, options?: IndexOptions): Promise` + - Show Index information: `index.show(): Promise` - Update Index: -`index.updateIndex(data: UpdateIndexRequest): Promise` +`index.updateIndex(data: IndexOptions): Promise` - Delete Index: diff --git a/examples/search_example.js b/examples/search_example.js index b8feb5f24..7028405cb 100644 --- a/examples/search_example.js +++ b/examples/search_example.js @@ -22,7 +22,7 @@ const addDataset = async () => { console.log({ indexes, indexFound }) if (!indexFound) { - await meili.createIndex(index) + await meili.createIndex(index.uid) } const documents = await meili.getIndex(index.uid).getDocuments() if (documents.length === 0) { diff --git a/examples/small_index.js b/examples/small_index.js index c028d2b43..8982a6da4 100644 --- a/examples/small_index.js +++ b/examples/small_index.js @@ -7,7 +7,7 @@ const config = { var meili = new MeiliSearch(config) -const index = { +const newIndex = { uid: 'movies_test', } @@ -30,7 +30,7 @@ const dataset = [ ] ;(async () => { - // This examples creates an index with 7 documents + // This example creates an index with 7 documents await meili.createIndex(index) const { updateId } = await meili.getIndex(index.uid).addDocuments(dataset) const res = await meili.getIndex(index.uid).waitForPendingUpdate(updateId) diff --git a/package.json b/package.json index 0b9e53dae..c23fd2d99 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meilisearch", - "version": "0.10.1", + "version": "0.11.0", "description": "The MeiliSearch JS client for Node.js and the browser.", "keywords": [ "meilisearch", @@ -16,7 +16,7 @@ "qdequele " ], "license": "MIT", - "main": "./dist/bundles/meilisearch.cjs.js", + "main": "./dist/bundles/meilisearch.umd.js", "module": "./dist/bundles/meilisearch.esm.js", "browser": "./dist/bundles/meilisearch.umd.js", "typings": "./dist/types/types.d.ts", diff --git a/src/errors/http-error-handler.ts b/src/errors/http-error-handler.ts new file mode 100644 index 000000000..913f027ba --- /dev/null +++ b/src/errors/http-error-handler.ts @@ -0,0 +1,15 @@ +import { AxiosError } from 'axios' +import MeiliSearchApiError from './meilisearch-api-error' +import MeiliSearchCommunicationError from './meilisearch-communication-error' + +function httpErrorHandler(e: AxiosError, cachedStack?: string): void { + if (e.response !== undefined) { + throw new MeiliSearchApiError(e, cachedStack) + } else if (e.isAxiosError) { + throw new MeiliSearchCommunicationError(e.message) + } else { + throw e + } +} + +export { httpErrorHandler } diff --git a/src/custom-errors/meilisearch-error.ts b/src/errors/meilisearch-api-error.ts similarity index 80% rename from src/custom-errors/meilisearch-error.ts rename to src/errors/meilisearch-api-error.ts index 43ff4a3cf..a1f783c79 100644 --- a/src/custom-errors/meilisearch-error.ts +++ b/src/errors/meilisearch-api-error.ts @@ -5,12 +5,16 @@ const MeiliSearchApiError: Types.MeiliSearchApiErrorConstructor = class extends Error implements Types.MeiliSearchApiErrorInterface { response?: Types.MeiliSearchApiErrorResponse - request?: Types.MeiliSearchApiErrorRequest + errorCode?: string + errorType?: string + errorLink?: string + stack?: string type: string constructor(error: AxiosError, cachedStack?: string) { super(error.message) - this.type = this.constructor.name + + this.type = 'MeiliSearchApiError' this.name = 'MeiliSearchApiError' // Fetch the native error message but add our application name in front of it. @@ -21,20 +25,16 @@ const MeiliSearchApiError: Types.MeiliSearchApiErrorConstructor = class statusText: error.response.statusText, path: error.response.config.url, method: error.response.config.method, - body: error.response.data, } + // If a custom message was sent back by our API // We change the error message to be more explicit if (error.response.data?.message !== undefined) { + this.errorCode = error.response.data.errorCode + this.errorType = error.response.data.errorType + this.errorLink = error.response.data.errorLink this.message = error.response.data.message } - } else { - // If MeiliSearch did not answered - this.request = { - url: error.request._currentUrl, - path: error.config.url, - method: error.config.method, - } } // use cached Stack on error object to keep the call stack diff --git a/src/errors/meilisearch-communication-error.ts b/src/errors/meilisearch-communication-error.ts new file mode 100644 index 000000000..d336ef467 --- /dev/null +++ b/src/errors/meilisearch-communication-error.ts @@ -0,0 +1,11 @@ +class MeiliSearchCommunicationError extends Error { + type: string + constructor(message: string) { + super(message) + this.name = 'MeiliSearchCommunicationError' + this.type = 'MeiliSearchCommunicationError' + Error.captureStackTrace(this, MeiliSearchCommunicationError) + } +} + +export default MeiliSearchCommunicationError diff --git a/src/custom-errors/meilisearch-timeout-error.ts b/src/errors/meilisearch-timeout-error.ts similarity index 100% rename from src/custom-errors/meilisearch-timeout-error.ts rename to src/errors/meilisearch-timeout-error.ts diff --git a/src/index.ts b/src/index.ts index 0ded2f652..f2d76ad0d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,7 +7,7 @@ 'use strict' -import MeiliSearchTimeOutError from './custom-errors/meilisearch-timeout-error' +import MeiliSearchTimeOutError from './errors/meilisearch-timeout-error' import MeiliAxiosWrapper from './meili-axios-wrapper' import * as Types from './types' import { sleep } from './utils' @@ -116,10 +116,15 @@ class Index extends MeiliAxiosWrapper implements Types.IndexInterface { if (options.filters !== undefined) { params.filters = options.filters } - if (options.matches !== undefined) { params.matches = options.matches } + if (options.facetFilters !== undefined) { + params.facetFilters = JSON.stringify(options.facetFilters) + } + if (options.facetsDistribution !== undefined) { + params.facetsDistribution = JSON.stringify(options.facetsDistribution) + } } return await this.get(url, { @@ -148,7 +153,7 @@ class Index extends MeiliAxiosWrapper implements Types.IndexInterface { * @method updateIndex */ async updateIndex( - data: Types.UpdateIndexRequest + data: Types.IndexOptions ): Promise { const url = `/indexes/${this.uid}` @@ -479,6 +484,45 @@ class Index extends MeiliAxiosWrapper implements Types.IndexInterface { return await this.delete(url) } + /// + /// ATTRIBUTES FOR FACETING + /// + + /** + * Get the attributes-for-faceting + * @memberof Index + * @method getAttributesForFaceting + */ + async getAttributesForFaceting(): Promise { + const url = `/indexes/${this.uid}/settings/attributes-for-faceting` + + return await this.get(url) + } + + /** + * Update the attributes-for-faceting. + * @memberof Index + * @method updateAttributesForFaceting + */ + async updateAttributesForFaceting( + attributesForFaceting: string[] + ): Promise { + const url = `/indexes/${this.uid}/settings/attributes-for-faceting` + + return await this.post(url, attributesForFaceting) + } + + /** + * Reset the attributes-for-faceting. + * @memberof Index + * @method resetAttributesForFaceting + */ + async resetAttributesForFaceting(): Promise { + const url = `/indexes/${this.uid}/settings/attributes-for-faceting` + + return await this.delete(url) + } + /// /// SEARCHABLE ATTRIBUTE /// diff --git a/src/meili-axios-wrapper.ts b/src/meili-axios-wrapper.ts index 21fedb6de..a2d4a68a9 100644 --- a/src/meili-axios-wrapper.ts +++ b/src/meili-axios-wrapper.ts @@ -13,7 +13,7 @@ import instance, { AxiosResponse, CancelTokenSource, } from 'axios' -import MeiliSearchApiError from './custom-errors/meilisearch-error' +import { httpErrorHandler } from './errors/http-error-handler' import * as Types from './types' class MeiliAxiosWrapper implements Types.MeiliAxiosWrapperInterface { @@ -55,7 +55,7 @@ class MeiliAxiosWrapper implements Types.MeiliAxiosWrapperInterface { try { return await this.instance.get(url, config) } catch (e) { - throw new MeiliSearchApiError(e, cachedStack) + throw httpErrorHandler(e, cachedStack) } } @@ -81,7 +81,7 @@ class MeiliAxiosWrapper implements Types.MeiliAxiosWrapperInterface { try { return await this.instance.post(url, data, config) } catch (e) { - throw new MeiliSearchApiError(e, cachedStack) + throw httpErrorHandler(e, cachedStack) } } @@ -94,7 +94,7 @@ class MeiliAxiosWrapper implements Types.MeiliAxiosWrapperInterface { try { return await this.instance.put(url, data, config) } catch (e) { - throw new MeiliSearchApiError(e, cachedStack) + throw httpErrorHandler(e, cachedStack) } } @@ -107,7 +107,7 @@ class MeiliAxiosWrapper implements Types.MeiliAxiosWrapperInterface { try { return await this.instance.patch(url, data, config) } catch (e) { - throw new MeiliSearchApiError(e, cachedStack) + throw httpErrorHandler(e, cachedStack) } } @@ -119,7 +119,7 @@ class MeiliAxiosWrapper implements Types.MeiliAxiosWrapperInterface { try { return await this.instance.delete(url, config) } catch (e) { - throw new MeiliSearchApiError(e, cachedStack) + throw httpErrorHandler(e, cachedStack) } } } diff --git a/src/meilisearch.ts b/src/meilisearch.ts index d639d2494..da2a1b946 100644 --- a/src/meilisearch.ts +++ b/src/meilisearch.ts @@ -22,12 +22,32 @@ class Meilisearch extends MeiliAxiosWrapper /** * Return an Index instance * @memberof Meilisearch - * @method Index + * @method getIndex */ getIndex(indexUid: string): Index { return new Index(this.config, indexUid) } + /** + * Get an index or create it if it does not exist + * @memberof Meilisearch + * @method getOrCreateIndex + */ + async getOrCreateIndex( + uid: string, + options: Types.IndexOptions = {} + ): Promise { + try { + const index = await this.createIndex(uid, options) + return index + } catch (e) { + if (e.errorCode === 'index_already_exists') { + return this.getIndex(uid) + } + throw e + } + } + /** * List all indexes in the database * @memberof Meilisearch @@ -44,10 +64,13 @@ class Meilisearch extends MeiliAxiosWrapper * @memberof Meilisearch * @method createIndex */ - async createIndex(data: Types.IndexRequest): Promise { + async createIndex( + uid: string, + options: Types.IndexOptions = {} + ): Promise { const url = '/indexes' - const index = await this.post(url, data) + const index = await this.post(url, { ...options, uid }) return new Index(this.config, index.uid) } diff --git a/src/types.ts b/src/types.ts index 30f22d521..c3fad942d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -15,8 +15,8 @@ import { import { Index } from './index' import MeiliAxiosWrapper from './meili-axios-wrapper' import MeiliSearch from './meilisearch' -import MeiliSearchApiError from './custom-errors/meilisearch-error' -import MeiliSearchTimeOutError from './custom-errors/meilisearch-timeout-error' +import MeiliSearchApiError from './errors/meilisearch-api-error' +import MeiliSearchTimeOutError from './errors/meilisearch-timeout-error' export { Index } export { MeiliSearchApiError } export { MeiliSearchTimeOutError } @@ -35,6 +35,10 @@ export interface IndexRequest { primaryKey?: string } +export interface IndexOptions { + primaryKey?: string +} + export interface IndexResponse { uid: string name?: string @@ -43,14 +47,12 @@ export interface IndexResponse { updatedAt: Date } -export interface UpdateIndexRequest { - primaryKey?: string -} - export interface AddDocumentParams { primaryKey?: string } +export type FacetFilter = string | FacetFilter[]; + export interface SearchParams { offset?: number limit?: number @@ -59,6 +61,8 @@ export interface SearchParams { cropLength?: number attributesToHighlight?: string[] | string filters?: string + facetFilters?: FacetFilter[] + facetsDistribution?: string[] matches?: boolean } @@ -70,6 +74,8 @@ export interface SearchRequest { attributesToCrop?: string cropLength?: number attributesToHighlight?: string + facetFilters?: string + facetsDistribution?: string filters?: string matches?: boolean } @@ -81,6 +87,8 @@ export interface SearchResponse { offset: number limit: number processingTimeMs: number + facetsDistribution?: object + exhaustiveFacetsCount?: boolean query: string } @@ -104,7 +112,9 @@ export interface Document { /* ** Settings */ + export interface Settings { + attributesForFaceting?: string[] distinctAttribute?: string searchableAttributes?: string[] displayedAttributes?: string[] @@ -216,8 +226,9 @@ export interface SysInfoPretty { export interface MeiliSearchInterface extends MeiliAxiosWrapper { config: Config getIndex: (indexUid: string) => Index + getOrCreateIndex: (uid: string, options?: IndexOptions) => Promise listIndexes: () => Promise - createIndex: (data: IndexRequest) => Promise + createIndex: (uid: string, options?: IndexOptions) => Promise getKeys: () => Promise isHealthy: () => Promise setHealthy: () => Promise @@ -238,7 +249,7 @@ export interface IndexInterface extends MeiliAxiosWrapperInterface { options?: SearchParams ) => Promise> show: () => Promise - updateIndex: (data: UpdateIndexRequest) => Promise + updateIndex: (indexData: IndexOptions) => Promise deleteIndex: () => Promise getStats: () => Promise getDocuments: (options?: GetDocumentsParams) => Promise @@ -273,6 +284,11 @@ export interface IndexInterface extends MeiliAxiosWrapperInterface { distinctAttribute: string ) => Promise resetDistinctAttribute: () => Promise + getAttributesForFaceting: () => Promise + updateAttributesForFaceting: ( + attributesForFaceting: string[] + ) => Promise + resetAttributesForFaceting: () => Promise getSearchableAttributes: () => Promise updateSearchableAttributes: ( searchableAttributes: string[] @@ -298,12 +314,12 @@ export interface MeiliAxiosWrapperInterface { url: string, data: IndexRequest, config?: AxiosRequestConfig - ) => Promise) & - (>( - url: string, - data?: T, - config?: AxiosRequestConfig - ) => Promise) + ) => Promise) & + (>( + url: string, + data?: T, + config?: AxiosRequestConfig + ) => Promise) put: >( url: string, data?: any, @@ -328,6 +344,11 @@ export interface MeiliSearchApiErrorInterface extends Error { name: string message: string stack?: string + errorCode?: string + errorType?: string + errorLink?: string + response?: MeiliSearchApiErrorResponse + request?: MeiliSearchApiErrorRequest } export interface MeiliSearchApiErrorResponse { status?: number diff --git a/tests/accept_new_fields_tests.ts b/tests/accept_new_fields_tests.ts index c05ddcda8..2446b650a 100644 --- a/tests/accept_new_fields_tests.ts +++ b/tests/accept_new_fields_tests.ts @@ -15,11 +15,6 @@ const index = { jest.setTimeout(100 * 1000) -beforeAll(async () => { - await clearAllIndexes(config) - await masterClient.createIndex(index) -}) - afterAll(() => { return clearAllIndexes(config) }) @@ -30,7 +25,7 @@ describe.each([ ])('Test on accept-new-fields', ({ client, permission }) => { beforeAll(async () => { await clearAllIndexes(config) - await masterClient.createIndex(index) + await masterClient.createIndex(index.uid) }) test(`${permission} key: Get accept new fields to be true`, async () => { await client @@ -75,7 +70,7 @@ describe.each([{ client: anonymousClient, permission: 'No' }])( test(`${permission} key: try to get accept-new-fields and be denied`, async () => { await expect( client.getIndex(index.uid).getAcceptNewFields() - ).rejects.toThrowError(`Invalid API key: Need a token`) + ).rejects.toThrowError(`You must have an authorization token`) }) } ) diff --git a/tests/attributes_for_faceting_tests.ts b/tests/attributes_for_faceting_tests.ts new file mode 100644 index 000000000..3c8ef6292 --- /dev/null +++ b/tests/attributes_for_faceting_tests.ts @@ -0,0 +1,139 @@ +import * as Types from '../src/types' +import { + clearAllIndexes, + config, + masterClient, + privateClient, + publicClient, + anonymousClient, + PUBLIC_KEY, +} from './meilisearch-test-utils' + +const index = { + uid: 'movies_test', +} + +const dataset = [ + { id: 123, title: 'Pride and Prejudice', genre: 'romance' }, + { id: 456, title: 'Le Petit Prince', genre: 'adventure' }, + { id: 2, title: 'Le Rouge et le Noir', genre: 'romance' }, + { id: 1, title: 'Alice In Wonderland', genre: 'adventure' }, + { id: 1344, title: 'The Hobbit', genre: 'adventure' }, + { + id: 4, + title: 'Harry Potter and the Half-Blood Prince', + genre: 'fantasy', + }, + { id: 42, title: "The Hitchhiker's Guide to the Galaxy" }, +] + +jest.setTimeout(100 * 1000) + +afterAll(() => { + return clearAllIndexes(config) +}) + +describe.each([ + { client: masterClient, permission: 'Master' }, + { client: privateClient, permission: 'Private' }, +])('Test on searchable attributes', ({ client, permission }) => { + beforeAll(async () => { + await clearAllIndexes(config) + await masterClient.createIndex(index.uid) + const { updateId } = await masterClient + .getIndex(index.uid) + .addDocuments(dataset) + await masterClient.getIndex(index.uid).waitForPendingUpdate(updateId) + }) + test(`${permission} key: Get default attributes for filtering`, async () => { + await client + .getIndex(index.uid) + .getAttributesForFaceting() + .then((response: String[]) => { + expect(response.sort()).toEqual([]) + }) + }) + test(`${permission} key: Update attributes for filtering`, async () => { + const new_attributes_for_faceting = ['genre'] + const { updateId } = await client + .getIndex(index.uid) + .updateAttributesForFaceting(new_attributes_for_faceting) + .then((response: Types.EnqueuedUpdate) => { + expect(response).toHaveProperty('updateId', expect.any(Number)) + return response + }) + await client.getIndex(index.uid).waitForPendingUpdate(updateId) + await client + .getIndex(index.uid) + .getAttributesForFaceting() + .then((response: String[]) => { + expect(response).toEqual(new_attributes_for_faceting) + }) + }) + test(`${permission} key: Reset attributes for filtering`, async () => { + const { updateId } = await client + .getIndex(index.uid) + .resetAttributesForFaceting() + .then((response: Types.EnqueuedUpdate) => { + expect(response).toHaveProperty('updateId', expect.any(Number)) + return response + }) + await client.getIndex(index.uid).waitForPendingUpdate(updateId) + await client + .getIndex(index.uid) + .getAttributesForFaceting() + .then((response: String[]) => { + expect(response.sort()).toEqual([]) + }) + }) +}) + +describe.each([{ client: publicClient, permission: 'Public' }])( + 'Test on attributes for filtering', + ({ client, permission }) => { + beforeAll(async () => { + await clearAllIndexes(config) + await masterClient.createIndex(index.uid) + }) + test(`${permission} key: try to get attributes for filtering and be denied`, async () => { + await expect( + client.getIndex(index.uid).getAttributesForFaceting() + ).rejects.toThrowError(`Invalid API key: ${PUBLIC_KEY}`) + }) + test(`${permission} key: try to update attributes for filtering and be denied`, async () => { + await expect( + client.getIndex(index.uid).updateAttributesForFaceting([]) + ).rejects.toThrowError(`Invalid API key: ${PUBLIC_KEY}`) + }) + test(`${permission} key: try to reset attributes for filtering and be denied`, async () => { + await expect( + client.getIndex(index.uid).resetAttributesForFaceting() + ).rejects.toThrowError(`Invalid API key: ${PUBLIC_KEY}`) + }) + } +) + +describe.each([{ client: anonymousClient, permission: 'No' }])( + 'Test on attributes for filtering', + ({ client, permission }) => { + beforeAll(async () => { + await clearAllIndexes(config) + await masterClient.createIndex(index.uid) + }) + test(`${permission} key: try to get attributes for filtering and be denied`, async () => { + await expect( + client.getIndex(index.uid).getAttributesForFaceting() + ).rejects.toThrowError(`You must have an authorization token`) + }) + test(`${permission} key: try to update attributes for filtering and be denied`, async () => { + await expect( + client.getIndex(index.uid).updateAttributesForFaceting([]) + ).rejects.toThrowError(`You must have an authorization token`) + }) + test(`${permission} key: try to reset attributes for filtering and be denied`, async () => { + await expect( + client.getIndex(index.uid).resetAttributesForFaceting() + ).rejects.toThrowError(`You must have an authorization token`) + }) + } +) diff --git a/tests/client_tests.ts b/tests/client_tests.ts index 87543648c..89fc5a1c0 100644 --- a/tests/client_tests.ts +++ b/tests/client_tests.ts @@ -1,4 +1,4 @@ -import * as Types from '../src/types' +import MeiliSearch, * as Types from '../src/types' import { clearAllIndexes, config, @@ -7,7 +7,6 @@ import { publicClient, anonymousClient, PUBLIC_KEY, - PRIVATE_KEY, } from './meilisearch-test-utils' const uidNoPrimaryKey = { @@ -18,10 +17,6 @@ const uidAndPrimaryKey = { primaryKey: 'id', } -beforeAll(() => { - return clearAllIndexes(config) -}) - afterAll(() => { return clearAllIndexes(config) }) @@ -43,7 +38,7 @@ describe.each([ }) test(`${permission} key: create with no primary key`, async () => { await client - .createIndex(uidNoPrimaryKey) + .createIndex(uidNoPrimaryKey.uid) .then((response: Types.Index) => { expect(response).toHaveProperty('uid', uidNoPrimaryKey.uid) }) @@ -60,7 +55,7 @@ describe.each([ }) test(`${permission} key: create with primary key`, async () => { await client - .createIndex(uidAndPrimaryKey) + .createIndex(uidAndPrimaryKey.uid, { primaryKey: uidAndPrimaryKey.primaryKey }) .then((response: Types.Index) => { expect(response).toHaveProperty('uid', uidAndPrimaryKey.uid) }) @@ -127,7 +122,14 @@ describe.each([ }) await expect(client.listIndexes()).resolves.toHaveLength(1) }) - + test(`${permission} key: bad host should raise CommunicationError`, async () => { + const client = new MeiliSearch({ host: 'badHost' }) + try { + await client.version() + } catch (e) { + expect(e.type).toEqual('MeiliSearchCommunicationError') + } + }) test(`${permission} key: show deleted index should fail`, async () => { const index = client.getIndex(uidNoPrimaryKey.uid) await expect(index.show()).rejects.toThrowError( @@ -136,14 +138,14 @@ describe.each([ }) test(`${permission} key: create index with already existing uid should fail`, async () => { - await expect(client.createIndex(uidAndPrimaryKey)).rejects.toThrowError( - `Impossible to create index; index already exists` + await expect(client.createIndex(uidAndPrimaryKey.uid, { primaryKey: uidAndPrimaryKey.primaryKey })).rejects.toThrowError( + `index already exists` ) }) test(`${permission} key: create index with missing uid should fail`, async () => { // @ts-ignore - await expect(client.createIndex({ uid: null })).rejects.toThrowError( + await expect(client.createIndex(null)).rejects.toThrowError( `Index creation must have an uid` ) }) @@ -161,116 +163,56 @@ describe.each([ expect(response).toBe(true) }) }) - }) -}) - -describe.each([{ client: masterClient, permission: 'Master' }])( - 'Test on routes where only master key has access', - ({ client, permission }) => { - describe('Test on base routes', () => { - test(`${permission} key: get version`, async () => { - await client.version().then((response: Types.Version) => { - expect(response).toHaveProperty('commitSha', expect.any(String)) - expect(response).toHaveProperty('buildDate', expect.any(String)) - expect(response).toHaveProperty('pkgVersion', expect.any(String)) - }) - }) - test(`${permission} key: get system info`, async () => { - await client.sysInfo().then((response: Types.SysInfo) => { - expect(response).toHaveProperty('memoryUsage', null) - expect(response).toHaveProperty('processorUsage', expect.any(Array)) - expect(response.global).toHaveProperty( - 'totalMemory', - expect.any(Number) - ) - expect(response.global).toHaveProperty( - 'usedMemory', - expect.any(Number) - ) - expect(response.global).toHaveProperty( - 'totalSwap', - expect.any(Number) - ) - expect(response.global).toHaveProperty('usedSwap', expect.any(Number)) - expect(response.global).toHaveProperty( - 'inputData', - expect.any(Number) - ) - expect(response.global).toHaveProperty( - 'outputData', - expect.any(Number) - ) - expect(response.process).toHaveProperty('memory', expect.any(Number)) - expect(response.process).toHaveProperty('cpu', expect.any(Number)) - }) - }) - test(`${permission} key: get pretty system info`, async () => { - await client.prettySysInfo().then((response: Types.SysInfoPretty) => { - expect(response).toHaveProperty('memoryUsage', expect.any(String)) - expect(response).toHaveProperty('processorUsage', expect.any(Array)) - expect(response.global).toHaveProperty( - 'totalMemory', - expect.any(String) - ) - expect(response.global).toHaveProperty( - 'usedMemory', - expect.any(String) - ) - expect(response.global).toHaveProperty( - 'totalSwap', - expect.any(String) - ) - expect(response.global).toHaveProperty('usedSwap', expect.any(String)) - expect(response.global).toHaveProperty( - 'inputData', - expect.any(String) - ) - expect(response.global).toHaveProperty( - 'outputData', - expect.any(String) - ) - expect(response.process).toHaveProperty('memory', expect.any(String)) - expect(response.process).toHaveProperty('cpu', expect.any(String)) - }) - }) - test(`${permission} key: get /stats information`, async () => { - await client.stats().then((response: Types.Stats) => { - expect(response).toHaveProperty('databaseSize', expect.any(Number)) - expect(response).toHaveProperty('lastUpdate') // TODO: Could be null, find out why - expect(response).toHaveProperty('indexes', expect.any(Object)) - }) + test(`${permission} key: get version`, async () => { + await client.version().then((response: Types.Version) => { + expect(response).toHaveProperty('commitSha', expect.any(String)) + expect(response).toHaveProperty('buildDate', expect.any(String)) + expect(response).toHaveProperty('pkgVersion', expect.any(String)) }) }) - } -) - -describe.each([{ client: privateClient, permission: 'Private' }])( - 'Test on routes where private key should not have access', - ({ client, permission }) => { - describe('Test on base routes', () => { - test(`${permission} key: try to get version and be denied`, async () => { - await expect(client.version()).rejects.toThrowError( - `Invalid API key: ${PRIVATE_KEY}` - ) - }) - test(`${permission} key: try to get system info and be denied`, async () => { - await expect(client.sysInfo()).rejects.toThrowError( - `Invalid API key: ${PRIVATE_KEY}` + test(`${permission} key: get system info`, async () => { + await client.sysInfo().then((response: Types.SysInfo) => { + expect(response).toHaveProperty('memoryUsage', null) + expect(response).toHaveProperty('processorUsage', expect.any(Array)) + expect(response.global).toHaveProperty( + 'totalMemory', + expect.any(Number) ) + expect(response.global).toHaveProperty('usedMemory', expect.any(Number)) + expect(response.global).toHaveProperty('totalSwap', expect.any(Number)) + expect(response.global).toHaveProperty('usedSwap', expect.any(Number)) + expect(response.global).toHaveProperty('inputData', expect.any(Number)) + expect(response.global).toHaveProperty('outputData', expect.any(Number)) + expect(response.process).toHaveProperty('memory', expect.any(Number)) + expect(response.process).toHaveProperty('cpu', expect.any(Number)) }) - test(`${permission} key: try to get pretty system info and be denied`, async () => { - await expect(client.prettySysInfo()).rejects.toThrowError( - `Invalid API key: ${PRIVATE_KEY}` + }) + test(`${permission} key: get pretty system info`, async () => { + await client.prettySysInfo().then((response: Types.SysInfoPretty) => { + expect(response).toHaveProperty('memoryUsage', expect.any(String)) + expect(response).toHaveProperty('processorUsage', expect.any(Array)) + expect(response.global).toHaveProperty( + 'totalMemory', + expect.any(String) ) + expect(response.global).toHaveProperty('usedMemory', expect.any(String)) + expect(response.global).toHaveProperty('totalSwap', expect.any(String)) + expect(response.global).toHaveProperty('usedSwap', expect.any(String)) + expect(response.global).toHaveProperty('inputData', expect.any(String)) + expect(response.global).toHaveProperty('outputData', expect.any(String)) + expect(response.process).toHaveProperty('memory', expect.any(String)) + expect(response.process).toHaveProperty('cpu', expect.any(String)) }) - test(`${permission} key: try to get /stats information and be denied`, async () => { - await expect(client.stats()).rejects.toThrowError( - `Invalid API key: ${PRIVATE_KEY}` - ) + }) + test(`${permission} key: get /stats information`, async () => { + await client.stats().then((response: Types.Stats) => { + expect(response).toHaveProperty('databaseSize', expect.any(Number)) + expect(response).toHaveProperty('lastUpdate') // TODO: Could be null, find out why + expect(response).toHaveProperty('indexes', expect.any(Object)) }) }) - } -) + }) +}) describe.each([{ client: publicClient, permission: 'Public' }])( 'Test on routes where public key should not have access', @@ -282,12 +224,12 @@ describe.each([{ client: publicClient, permission: 'Public' }])( ) }) test(`${permission} key: try to create Index with primary key and be denied`, async () => { - await expect(client.createIndex(uidAndPrimaryKey)).rejects.toThrowError( + await expect(client.createIndex(uidAndPrimaryKey.uid, { primaryKey: uidAndPrimaryKey.primaryKey })).rejects.toThrowError( `Invalid API key: ${PUBLIC_KEY}` ) }) test(`${permission} key: try to create Index with NO primary key and be denied`, async () => { - await expect(client.createIndex(uidNoPrimaryKey)).rejects.toThrowError( + await expect(client.createIndex(uidNoPrimaryKey.uid)).rejects.toThrowError( `Invalid API key: ${PUBLIC_KEY}` ) }) @@ -303,7 +245,7 @@ describe.each([{ client: publicClient, permission: 'Public' }])( }) test(`${permission} key: try to update index and be denied`, async () => { await expect( - client.getIndex(uidAndPrimaryKey.uid).updateIndex(uidAndPrimaryKey) + client.getIndex(uidAndPrimaryKey.uid).updateIndex({ primaryKey: uidAndPrimaryKey.primaryKey }) ).rejects.toThrowError(`Invalid API key: ${PUBLIC_KEY}`) }) }) @@ -338,54 +280,54 @@ describe.each([{ client: anonymousClient, permission: 'No' }])( describe('Test on indexes', () => { test(`${permission} key: try to get all indexes and be denied`, async () => { await expect(client.listIndexes()).rejects.toThrowError( - `Invalid API key: Need a token` + `You must have an authorization token` ) }) test(`${permission} key: try to create an index with primary key and be denied`, async () => { - await expect(client.createIndex(uidAndPrimaryKey)).rejects.toThrowError( - `Invalid API key: Need a token` + await expect(client.createIndex(uidAndPrimaryKey.uid, { primaryKey: uidAndPrimaryKey.primaryKey })).rejects.toThrowError( + `You must have an authorization token` ) }) test(`${permission} key: try to create an index with NO primary key and be denied`, async () => { - await expect(client.createIndex(uidNoPrimaryKey)).rejects.toThrowError( - `Invalid API key: Need a token` + await expect(client.createIndex(uidNoPrimaryKey.uid)).rejects.toThrowError( + `You must have an authorization token` ) }) test(`${permission} key: try to get index info and be denied`, async () => { await expect( client.getIndex(uidNoPrimaryKey.uid).show() - ).rejects.toThrowError(`Invalid API key: Need a token`) + ).rejects.toThrowError(`You must have an authorization token`) }) test(`${permission} key: try to delete index and be denied`, async () => { await expect( client.getIndex(uidAndPrimaryKey.uid).deleteIndex() - ).rejects.toThrowError(`Invalid API key: Need a token`) + ).rejects.toThrowError(`You must have an authorization token`) }) test(`${permission} key: try to update index and be denied`, async () => { await expect( - client.getIndex(uidAndPrimaryKey.uid).updateIndex(uidAndPrimaryKey) - ).rejects.toThrowError(`Invalid API key: Need a token`) + client.getIndex(uidAndPrimaryKey.uid).updateIndex({ primaryKey: uidAndPrimaryKey.primaryKey }) + ).rejects.toThrowError(`You must have an authorization token`) }) }) describe('Test on base routes', () => { test(`${permission} key: try to get version and be denied`, async () => { await expect(client.version()).rejects.toThrowError( - `Invalid API key: Need a token` + `You must have an authorization token` ) }) test(`${permission} key: try to get system info and be denied`, async () => { await expect(client.sysInfo()).rejects.toThrowError( - `Invalid API key: Need a token` + `You must have an authorization token` ) }) test(`${permission} key: try to get pretty system info and be denied`, async () => { await expect(client.prettySysInfo()).rejects.toThrowError( - `Invalid API key: Need a token` + `You must have an authorization token` ) }) test(`${permission} key: try to get /stats information and be denied`, async () => { await expect(client.stats()).rejects.toThrowError( - `Invalid API key: Need a token` + `You must have an authorization token` ) }) }) diff --git a/tests/displayed_attributes_tests.ts b/tests/displayed_attributes_tests.ts index f5b6c9bc0..2c15b9249 100644 --- a/tests/displayed_attributes_tests.ts +++ b/tests/displayed_attributes_tests.ts @@ -29,10 +29,6 @@ const dataset = [ jest.setTimeout(100 * 1000) -beforeAll(async () => { - await clearAllIndexes(config) -}) - afterAll(() => { return clearAllIndexes(config) }) @@ -43,7 +39,7 @@ describe.each([ ])('Test on displayed attributes', ({ client, permission }) => { beforeAll(async () => { await clearAllIndexes(config) - await masterClient.createIndex(index) + await masterClient.createIndex(index.uid) const { updateId } = await masterClient .getIndex(index.uid) .addDocuments(dataset) @@ -97,7 +93,7 @@ describe.each([{ client: publicClient, permission: 'Public' }])( ({ client, permission }) => { beforeAll(async () => { await clearAllIndexes(config) - await masterClient.createIndex(index) + await masterClient.createIndex(index.uid) }) test(`${permission} key: try to get displayed attributes and be denied`, async () => { await expect( @@ -122,22 +118,22 @@ describe.each([{ client: anonymousClient, permission: 'No' }])( ({ client, permission }) => { beforeAll(async () => { await clearAllIndexes(config) - await masterClient.createIndex(index) + await masterClient.createIndex(index.uid) }) test(`${permission} key: try to get displayed attributes and be denied`, async () => { await expect( client.getIndex(index.uid).getDisplayedAttributes() - ).rejects.toThrowError(`Invalid API key: Need a token`) + ).rejects.toThrowError(`You must have an authorization token`) }) test(`${permission} key: try to update displayed attributes and be denied`, async () => { await expect( client.getIndex(index.uid).updateDisplayedAttributes([]) - ).rejects.toThrowError(`Invalid API key: Need a token`) + ).rejects.toThrowError(`You must have an authorization token`) }) test(`${permission} key: try to reset displayed attributes and be denied`, async () => { await expect( client.getIndex(index.uid).resetDisplayedAttributes() - ).rejects.toThrowError(`Invalid API key: Need a token`) + ).rejects.toThrowError(`You must have an authorization token`) }) } ) diff --git a/tests/distinct_attribute_tests.ts b/tests/distinct_attribute_tests.ts index 512e00117..5cde33530 100644 --- a/tests/distinct_attribute_tests.ts +++ b/tests/distinct_attribute_tests.ts @@ -29,10 +29,6 @@ const dataset = [ jest.setTimeout(100 * 1000) -beforeAll(async () => { - await clearAllIndexes(config) -}) - afterAll(() => { return clearAllIndexes(config) }) @@ -43,7 +39,7 @@ describe.each([ ])('Test on distinct attribute', ({ client, permission }) => { beforeAll(async () => { await clearAllIndexes(config) - await masterClient.createIndex(index) + await masterClient.createIndex(index.uid) const { updateId } = await masterClient .getIndex(index.uid) .addDocuments(dataset) @@ -97,7 +93,7 @@ describe.each([{ client: publicClient, permission: 'Public' }])( ({ client, permission }) => { beforeAll(async () => { await clearAllIndexes(config) - await masterClient.createIndex(index) + await masterClient.createIndex(index.uid) }) test(`${permission} key: try to get distinct attribute and be denied`, async () => { await expect( @@ -122,22 +118,22 @@ describe.each([{ client: anonymousClient, permission: 'No' }])( ({ client, permission }) => { beforeAll(async () => { await clearAllIndexes(config) - await masterClient.createIndex(index) + await masterClient.createIndex(index.uid) }) test(`${permission} key: try to get distinct attribute and be denied`, async () => { await expect( client.getIndex(index.uid).getDistinctAttribute() - ).rejects.toThrowError(`Invalid API key: Need a token`) + ).rejects.toThrowError(`You must have an authorization token`) }) test(`${permission} key: try to update distinct attribute and be denied`, async () => { await expect( client.getIndex(index.uid).updateDistinctAttribute('title') - ).rejects.toThrowError(`Invalid API key: Need a token`) + ).rejects.toThrowError(`You must have an authorization token`) }) test(`${permission} key: try to reset distinct attribute and be denied`, async () => { await expect( client.getIndex(index.uid).resetDistinctAttribute() - ).rejects.toThrowError(`Invalid API key: Need a token`) + ).rejects.toThrowError(`You must have an authorization token`) }) } ) diff --git a/tests/documents_tests.ts b/tests/documents_tests.ts index b41ad8557..4390f471d 100644 --- a/tests/documents_tests.ts +++ b/tests/documents_tests.ts @@ -32,12 +32,6 @@ const dataset = [ ] jest.setTimeout(100 * 1000) -beforeAll(async () => { - await clearAllIndexes(config) - await masterClient.createIndex(uidNoPrimaryKey) - await masterClient.createIndex(uidAndPrimaryKey) -}) - afterAll(() => { return clearAllIndexes(config) }) @@ -48,8 +42,8 @@ describe.each([ ])('Test on documents', ({ client, permission }) => { beforeAll(async () => { await clearAllIndexes(config) - await masterClient.createIndex(uidNoPrimaryKey) - await masterClient.createIndex(uidAndPrimaryKey) + await masterClient.createIndex(uidNoPrimaryKey.uid) + await masterClient.createIndex(uidAndPrimaryKey.uid, { primaryKey: uidAndPrimaryKey.primaryKey }) }) test(`${permission} key: Add documents to uid with NO primary key`, async () => { const { updateId } = await client @@ -342,7 +336,7 @@ describe.each([ ] await client - .createIndex({ uid: 'updateUid' }) + .createIndex('updateUid') .then((response: Types.Index) => { expect(response).toHaveProperty('uid', 'updateUid') }) @@ -382,7 +376,10 @@ describe.each([ .getAllUpdateStatus() .then((response: Types.Update[]) => { const lastUpdate = response[response.length - 1] - expect(lastUpdate).toHaveProperty('error', 'document id is missing') + expect(lastUpdate).toHaveProperty( + 'error', + 'serializer error; Primary key is missing.' + ) expect(lastUpdate).toHaveProperty('status', 'failed') }) }) @@ -429,32 +426,32 @@ describe.each([{ client: anonymousClient, permission: 'No' }])( ({ client, permission }) => { test(`${permission} key: Try to add documents and be denied`, async () => { await expect(client.listIndexes()).rejects.toThrowError( - `Invalid API key: Need a token` + `You must have an authorization token` ) }) test(`${permission} key: Try to update documents and be denied`, async () => { await expect(client.listIndexes()).rejects.toThrowError( - `Invalid API key: Need a token` + `You must have an authorization token` ) }) test(`${permission} key: Try to get documents and be denied`, async () => { await expect(client.listIndexes()).rejects.toThrowError( - `Invalid API key: Need a token` + `You must have an authorization token` ) }) test(`${permission} key: Try to delete one document and be denied`, async () => { await expect(client.listIndexes()).rejects.toThrowError( - `Invalid API key: Need a token` + `You must have an authorization token` ) }) test(`${permission} key: Try to delete some documents and be denied`, async () => { await expect(client.listIndexes()).rejects.toThrowError( - `Invalid API key: Need a token` + `You must have an authorization token` ) }) test(`${permission} key: Try to delete all documents and be denied`, async () => { await expect(client.listIndexes()).rejects.toThrowError( - `Invalid API key: Need a token` + `You must have an authorization token` ) }) } diff --git a/tests/get_or_create_tests.ts b/tests/get_or_create_tests.ts new file mode 100644 index 000000000..d7f5c0392 --- /dev/null +++ b/tests/get_or_create_tests.ts @@ -0,0 +1,69 @@ +import * as Types from '../src/types' +import { + clearAllIndexes, + config, + masterClient, + privateClient, + publicClient, + anonymousClient, + PUBLIC_KEY, +} from './meilisearch-test-utils' + +const index = { + uid: 'movies_test', +} + +jest.setTimeout(100 * 1000) + +afterAll(() => { + return clearAllIndexes(config) +}) + +describe.each([ + { client: masterClient, permission: 'Master' }, + { client: privateClient, permission: 'Private' }, +])('Test on getOrCreateIndex', ({ client, permission }) => { + beforeEach(async () => { + await clearAllIndexes(config) + }) + test(`${permission} key: getOrCreateIndex without index created before`, async () => { + const newIndex = await client.getOrCreateIndex(index.uid) + expect(newIndex.uid).toEqual(index.uid) + const newIndexInfo = await client.getIndex(newIndex.uid).show() + expect(newIndexInfo.primaryKey).toEqual(null) + }) + test(`${permission} key: getOrCreateIndex on already existing index`, async () => { + await masterClient.createIndex(index.uid) + const newIndex = await client.getOrCreateIndex(index.uid) + expect(newIndex.uid).toEqual(index.uid) + }) + + test(`${permission} key: getOrCreateIndex with primary key`, async () => { + const newIndex = await client.getOrCreateIndex(index.uid, { primaryKey: 'primaryKey' }) + expect(newIndex.uid).toEqual(index.uid) + const newIndexInfo = await client.getIndex(newIndex.uid).show() + expect(newIndexInfo.primaryKey).toEqual('primaryKey') + }) +}) + +describe.each([{ client: publicClient, permission: 'Public' }])( + 'Test on getOrCreateIndex', + ({ client, permission }) => { + test(`${permission} key: try to getOrCreateIndex and be denied`, async () => { + await expect( + client.getIndex(index.uid).getAcceptNewFields() + ).rejects.toThrowError(`Invalid API key: ${PUBLIC_KEY}`) + }) + } +) + +describe.each([{ client: anonymousClient, permission: 'No' }])( + 'Test on getOrCreateIndex', + ({ client, permission }) => { + test(`${permission} key: try to getOrCreateIndex and be denied`, async () => { + await expect( + client.getIndex(index.uid).getAcceptNewFields() + ).rejects.toThrowError(`You must have an authorization token`) + }) + } +) diff --git a/tests/ranking_rules_tests.ts b/tests/ranking_rules_tests.ts index a45b03a6e..009d50842 100644 --- a/tests/ranking_rules_tests.ts +++ b/tests/ranking_rules_tests.ts @@ -38,10 +38,6 @@ const defaultRankingRules = [ jest.setTimeout(100 * 1000) -beforeAll(async () => { - await clearAllIndexes(config) -}) - afterAll(() => { return clearAllIndexes(config) }) @@ -52,7 +48,7 @@ describe.each([ ])('Test on ranking rules', ({ client, permission }) => { beforeAll(async () => { await clearAllIndexes(config) - await masterClient.createIndex(index) + await masterClient.createIndex(index.uid) const { updateId } = await masterClient .getIndex(index.uid) .addDocuments(dataset) @@ -106,7 +102,7 @@ describe.each([{ client: publicClient, permission: 'Public' }])( ({ client, permission }) => { beforeAll(async () => { await clearAllIndexes(config) - await masterClient.createIndex(index) + await masterClient.createIndex(index.uid) }) test(`${permission} key: try to get ranking rules and be denied`, async () => { await expect( @@ -131,22 +127,22 @@ describe.each([{ client: anonymousClient, permission: 'No' }])( ({ client, permission }) => { beforeAll(async () => { await clearAllIndexes(config) - await masterClient.createIndex(index) + await masterClient.createIndex(index.uid) }) test(`${permission} key: try to get ranking rules and be denied`, async () => { await expect( client.getIndex(index.uid).getRankingRules() - ).rejects.toThrowError(`Invalid API key: Need a token`) + ).rejects.toThrowError(`You must have an authorization token`) }) test(`${permission} key: try to update ranking rules and be denied`, async () => { await expect( client.getIndex(index.uid).updateRankingRules([]) - ).rejects.toThrowError(`Invalid API key: Need a token`) + ).rejects.toThrowError(`You must have an authorization token`) }) test(`${permission} key: try to reset ranking rules and be denied`, async () => { await expect( client.getIndex(index.uid).resetRankingRules() - ).rejects.toThrowError(`Invalid API key: Need a token`) + ).rejects.toThrowError(`You must have an authorization token`) }) } ) diff --git a/tests/search_tests.ts b/tests/search_tests.ts index 802c153db..d95256eae 100644 --- a/tests/search_tests.ts +++ b/tests/search_tests.ts @@ -16,34 +16,47 @@ const emptyIndex = { } const dataset = [ - { id: 123, title: 'Pride and Prejudice', comment: 'A great book' }, + { + id: 123, + title: 'Pride and Prejudice', + comment: 'A great book', + genre: 'romance', + }, { id: 456, title: 'Le Petit Prince', comment: 'A french book about a prince that walks on little cute planets', + genre: 'adventure', + }, + { + id: 2, + title: 'Le Rouge et le Noir', + comment: 'Another french book', + genre: 'romance', + }, + { + id: 1, + title: 'Alice In Wonderland', + comment: 'A weird book', + genre: 'adventure', + }, + { + id: 1344, + title: 'The Hobbit', + comment: 'An awesome book', + genre: 'adventure', }, - { id: 2, title: 'Le Rouge et le Noir', comment: 'Another french book' }, - { id: 1, title: 'Alice In Wonderland', comment: 'A weird book' }, - { id: 1344, title: 'The Hobbit', comment: 'An awesome book' }, { id: 4, title: 'Harry Potter and the Half-Blood Prince', comment: 'The best book', + genre: 'fantasy', }, - { id: 42, title: "The Hitchhiker's Guide to the Galaxy" }, + { id: 42, title: "The Hitchhiker's Guide to the Galaxy", genre: 'fantasy' }, ] jest.setTimeout(100 * 1000) -beforeAll(async () => { - await clearAllIndexes(config) - await masterClient.createIndex(index) - const { updateId } = await masterClient - .getIndex(index.uid) - .addDocuments(dataset) - await masterClient.getIndex(index.uid).waitForPendingUpdate(updateId) -}) - afterAll(() => { return clearAllIndexes(config) }) @@ -55,8 +68,17 @@ describe.each([ ])('Test on search', ({ client, permission }) => { beforeAll(async () => { await clearAllIndexes(config) - await masterClient.createIndex(index) - await masterClient.createIndex(emptyIndex) + await masterClient.createIndex(index.uid) + await masterClient.createIndex(emptyIndex.uid) + const new_attributes_for_faceting = ['genre'] + const { updateId: settingUpdateId } = await masterClient + .getIndex(index.uid) + .updateAttributesForFaceting(new_attributes_for_faceting) + .then((response: Types.EnqueuedUpdate) => { + expect(response).toHaveProperty('updateId', expect.any(Number)) + return response + }) + await masterClient.getIndex(index.uid).waitForPendingUpdate(settingUpdateId) const { updateId } = await masterClient .getIndex(index.uid) .addDocuments(dataset) @@ -143,8 +165,8 @@ describe.each([ expect(response).toHaveProperty('query', 'prince') expect(response.hits.length).toEqual(1) expect(response.hits[0]).toHaveProperty('_matchesInfo', { - comment: [{ start: 3, length: 6 }], - title: [{ start: 1, length: 6 }], + comment: [{ start: 2, length: 6 }], + title: [{ start: 0, length: 6 }], }) }) }) @@ -268,6 +290,40 @@ describe.each([ }) }) + test(`${permission} key: Search with facetFilters and facetDistribution`, async () => { + await client + .getIndex(index.uid) + .search('a', { + facetFilters: ['genre:romance'], + facetsDistribution: ['genre'], + }) + .then((response: Types.SearchResponse) => { + expect(response).toHaveProperty('facetsDistribution', { + genre: { adventure: 0, fantasy: 0, romance: 2 }, + }) + expect(response).toHaveProperty('exhaustiveFacetsCount', true) + expect(response).toHaveProperty('hits', expect.any(Array)) + expect(response.hits.length).toEqual(2) + }) + }) + + test(`${permission} key: Search with multiple facetFilters`, async () => { + await client + .getIndex(index.uid) + .search('a', { + facetFilters: ['genre:romance', ['genre:romance', 'genre:romance']], + facetsDistribution: ['genre'], + }) + .then((response: Types.SearchResponse) => { + expect(response).toHaveProperty('facetsDistribution', { + genre: { adventure: 0, fantasy: 0, romance: 2 }, + }) + expect(response).toHaveProperty('exhaustiveFacetsCount', true) + expect(response).toHaveProperty('hits', expect.any(Array)) + expect(response.hits.length).toEqual(2) + }) + }) + test(`${permission} key: Search on index with no documents and no primary key`, async () => { await client .getIndex(emptyIndex.uid) @@ -301,12 +357,12 @@ describe.each([{ client: anonymousClient, permission: 'Client' }])( ({ client, permission }) => { beforeAll(async () => { await clearAllIndexes(config) - await masterClient.createIndex(index) + await masterClient.createIndex(index.uid) }) test(`${permission} key: Try Basic search and be denied`, async () => { await expect( client.getIndex(index.uid).search('prince') - ).rejects.toThrowError(`Invalid API key: Need a token`) + ).rejects.toThrowError(`You must have an authorization token`) }) } ) diff --git a/tests/searchable_attributes_tests.ts b/tests/searchable_attributes_tests.ts index 66f3c7260..c2f10d712 100644 --- a/tests/searchable_attributes_tests.ts +++ b/tests/searchable_attributes_tests.ts @@ -29,10 +29,6 @@ const dataset = [ jest.setTimeout(100 * 1000) -beforeAll(async () => { - await clearAllIndexes(config) -}) - afterAll(() => { return clearAllIndexes(config) }) @@ -43,7 +39,7 @@ describe.each([ ])('Test on searchable attributes', ({ client, permission }) => { beforeAll(async () => { await clearAllIndexes(config) - await masterClient.createIndex(index) + await masterClient.createIndex(index.uid) const { updateId } = await masterClient .getIndex(index.uid) .addDocuments(dataset) @@ -97,7 +93,7 @@ describe.each([{ client: publicClient, permission: 'Public' }])( ({ client, permission }) => { beforeAll(async () => { await clearAllIndexes(config) - await masterClient.createIndex(index) + await masterClient.createIndex(index.uid) }) test(`${permission} key: try to get searchable attributes and be denied`, async () => { await expect( @@ -122,22 +118,22 @@ describe.each([{ client: anonymousClient, permission: 'No' }])( ({ client, permission }) => { beforeAll(async () => { await clearAllIndexes(config) - await masterClient.createIndex(index) + await masterClient.createIndex(index.uid) }) test(`${permission} key: try to get searchable attributes and be denied`, async () => { await expect( client.getIndex(index.uid).getSearchableAttributes() - ).rejects.toThrowError(`Invalid API key: Need a token`) + ).rejects.toThrowError(`You must have an authorization token`) }) test(`${permission} key: try to update searchable attributes and be denied`, async () => { await expect( client.getIndex(index.uid).updateSearchableAttributes([]) - ).rejects.toThrowError(`Invalid API key: Need a token`) + ).rejects.toThrowError(`You must have an authorization token`) }) test(`${permission} key: try to reset searchable attributes and be denied`, async () => { await expect( client.getIndex(index.uid).resetSearchableAttributes() - ).rejects.toThrowError(`Invalid API key: Need a token`) + ).rejects.toThrowError(`You must have an authorization token`) }) } ) diff --git a/tests/settings_tests.ts b/tests/settings_tests.ts index f38631294..ccbde4fc3 100644 --- a/tests/settings_tests.ts +++ b/tests/settings_tests.ts @@ -62,10 +62,6 @@ const defaultSettings = { jest.setTimeout(100 * 1000) -beforeAll(async () => { - await clearAllIndexes(config) -}) - afterAll(() => { return clearAllIndexes(config) }) @@ -76,8 +72,8 @@ describe.each([ ])('Test on settings', ({ client, permission }) => { beforeAll(async () => { await clearAllIndexes(config) - await masterClient.createIndex(index) - await masterClient.createIndex(indexAndPK) + await masterClient.createIndex(index.uid) + await masterClient.createIndex(indexAndPK.uid, { primaryKey: indexAndPK.primaryKey }) const { updateId } = await masterClient .getIndex(index.uid) .addDocuments(dataset) @@ -129,6 +125,7 @@ describe.each([ distinctAttribute: 'title', rankingRules: ['asc(title)', 'typo'], stopWords: ['the'], + attributesForFaceting: [] } const { updateId } = await client .getIndex(index.uid) @@ -351,7 +348,7 @@ describe.each([{ client: publicClient, permission: 'Public' }])( ({ client, permission }) => { beforeAll(async () => { await clearAllIndexes(config) - await masterClient.createIndex(index) + await masterClient.createIndex(index.uid) }) test(`${permission} key: try to get settings and be denied`, async () => { await expect( @@ -376,22 +373,22 @@ describe.each([{ client: anonymousClient, permission: 'No' }])( ({ client, permission }) => { beforeAll(async () => { await clearAllIndexes(config) - await masterClient.createIndex(index) + await masterClient.createIndex(index.uid) }) test(`${permission} key: try to get settings and be denied`, async () => { await expect( client.getIndex(index.uid).getSettings() - ).rejects.toThrowError(`Invalid API key: Need a token`) + ).rejects.toThrowError(`You must have an authorization token`) }) test(`${permission} key: try to update settings and be denied`, async () => { await expect( client.getIndex(index.uid).updateSettings({}) - ).rejects.toThrowError(`Invalid API key: Need a token`) + ).rejects.toThrowError(`You must have an authorization token`) }) test(`${permission} key: try to reset settings and be denied`, async () => { await expect( client.getIndex(index.uid).resetSettings() - ).rejects.toThrowError(`Invalid API key: Need a token`) + ).rejects.toThrowError(`You must have an authorization token`) }) } ) diff --git a/tests/stop_words_tests.ts b/tests/stop_words_tests.ts index 6716f264e..430b34b5f 100644 --- a/tests/stop_words_tests.ts +++ b/tests/stop_words_tests.ts @@ -29,10 +29,6 @@ const dataset = [ jest.setTimeout(100 * 1000) -beforeAll(async () => { - await clearAllIndexes(config) -}) - afterAll(() => { return clearAllIndexes(config) }) @@ -43,7 +39,7 @@ describe.each([ ])('Test on stop words', ({ client, permission }) => { beforeAll(async () => { await clearAllIndexes(config) - await masterClient.createIndex(index) + await masterClient.createIndex(index.uid) const { updateId } = await masterClient .getIndex(index.uid) .addDocuments(dataset) @@ -97,7 +93,7 @@ describe.each([{ client: publicClient, permission: 'Public' }])( ({ client, permission }) => { beforeAll(async () => { await clearAllIndexes(config) - await masterClient.createIndex(index) + await masterClient.createIndex(index.uid) }) test(`${permission} key: try to get stop words and be denied`, async () => { await expect( @@ -122,22 +118,22 @@ describe.each([{ client: anonymousClient, permission: 'No' }])( ({ client, permission }) => { beforeAll(async () => { await clearAllIndexes(config) - await masterClient.createIndex(index) + await masterClient.createIndex(index.uid) }) test(`${permission} key: try to get stop words and be denied`, async () => { await expect( client.getIndex(index.uid).getStopWords() - ).rejects.toThrowError(`Invalid API key: Need a token`) + ).rejects.toThrowError(`You must have an authorization token`) }) test(`${permission} key: try to update stop words and be denied`, async () => { await expect( client.getIndex(index.uid).updateStopWords([]) - ).rejects.toThrowError(`Invalid API key: Need a token`) + ).rejects.toThrowError(`You must have an authorization token`) }) test(`${permission} key: try to reset stop words and be denied`, async () => { await expect( client.getIndex(index.uid).resetStopWords() - ).rejects.toThrowError(`Invalid API key: Need a token`) + ).rejects.toThrowError(`You must have an authorization token`) }) } ) diff --git a/tests/synonyms_tests.ts b/tests/synonyms_tests.ts index 4fe892685..7d4da03df 100644 --- a/tests/synonyms_tests.ts +++ b/tests/synonyms_tests.ts @@ -29,10 +29,6 @@ const dataset = [ jest.setTimeout(100 * 1000) -beforeAll(async () => { - await clearAllIndexes(config) -}) - afterAll(() => { return clearAllIndexes(config) }) @@ -43,7 +39,7 @@ describe.each([ ])('Test on synonyms', ({ client, permission }) => { beforeAll(async () => { await clearAllIndexes(config) - await masterClient.createIndex(index) + await masterClient.createIndex(index.uid) const { updateId } = await masterClient .getIndex(index.uid) .addDocuments(dataset) @@ -99,7 +95,7 @@ describe.each([{ client: publicClient, permission: 'Public' }])( ({ client, permission }) => { beforeAll(async () => { await clearAllIndexes(config) - await masterClient.createIndex(index) + await masterClient.createIndex(index.uid) }) test(`${permission} key: try to get synonyms and be denied`, async () => { await expect( @@ -124,22 +120,22 @@ describe.each([{ client: anonymousClient, permission: 'No' }])( ({ client, permission }) => { beforeAll(async () => { await clearAllIndexes(config) - await masterClient.createIndex(index) + await masterClient.createIndex(index.uid) }) test(`${permission} key: try to get synonyms and be denied`, async () => { await expect( client.getIndex(index.uid).getSynonyms() - ).rejects.toThrowError(`Invalid API key: Need a token`) + ).rejects.toThrowError(`You must have an authorization token`) }) test(`${permission} key: try to update synonyms and be denied`, async () => { await expect( client.getIndex(index.uid).updateSynonyms({}) - ).rejects.toThrowError(`Invalid API key: Need a token`) + ).rejects.toThrowError(`You must have an authorization token`) }) test(`${permission} key: try to reset synonyms and be denied`, async () => { await expect( client.getIndex(index.uid).resetSynonyms() - ).rejects.toThrowError(`Invalid API key: Need a token`) + ).rejects.toThrowError(`You must have an authorization token`) }) } ) diff --git a/tests/update_tests.ts b/tests/update_tests.ts index 1b79e954f..f659a8748 100644 --- a/tests/update_tests.ts +++ b/tests/update_tests.ts @@ -29,11 +29,6 @@ const dataset = [ jest.setTimeout(100 * 1000) -beforeAll(async () => { - await clearAllIndexes(config) - await masterClient.createIndex(index) -}) - afterAll(() => { return clearAllIndexes(config) }) @@ -44,7 +39,7 @@ describe.each([ ])('Test on updates', ({ client, permission }) => { beforeAll(async () => { await clearAllIndexes(config) - await masterClient.createIndex(index) + await masterClient.createIndex(index.uid) }) test(`${permission} key: Get one update`, async () => { const { updateId } = await client @@ -89,7 +84,7 @@ describe.each([ test(`${permission} key: Try to get update that does not exist`, async () => { await expect( client.getIndex(index.uid).getUpdateStatus(2545) - ).rejects.toThrowError(`unknown update id`) + ).rejects.toThrowError(`Update 2545 not found`) }) }) @@ -98,7 +93,7 @@ describe.each([{ client: publicClient, permission: 'Public' }])( ({ client, permission }) => { beforeAll(async () => { await clearAllIndexes(config) - await masterClient.createIndex(index) + await masterClient.createIndex(index.uid) }) test(`${permission} key: Try to get a update and be denied`, async () => { await expect( @@ -113,12 +108,12 @@ describe.each([{ client: anonymousClient, permission: 'No' }])( ({ client, permission }) => { beforeAll(async () => { await clearAllIndexes(config) - await masterClient.createIndex(index) + await masterClient.createIndex(index.uid) }) test(`${permission} key: Try to get an update and be denied`, async () => { await expect( client.getIndex(index.uid).getUpdateStatus(0) - ).rejects.toThrowError(`Invalid API key: Need a token`) + ).rejects.toThrowError(`You must have an authorization token`) }) } ) diff --git a/tests/wait_for_pending_update_tests.ts b/tests/wait_for_pending_update_tests.ts index d29979a6c..2e227c53d 100644 --- a/tests/wait_for_pending_update_tests.ts +++ b/tests/wait_for_pending_update_tests.ts @@ -25,11 +25,6 @@ const dataset = [ jest.setTimeout(100 * 1000) -beforeAll(async () => { - await clearAllIndexes(config) - await masterClient.createIndex(index) -}) - afterAll(() => { return clearAllIndexes(config) }) @@ -40,7 +35,7 @@ describe.each([ ])('Test on wait-for-pending-update', ({ client, permission }) => { beforeEach(async () => { await clearAllIndexes(config) - await masterClient.createIndex(index) + await masterClient.createIndex(index.uid) }) test(`${permission} key: Get WaitForPendingStatus until done and resolved`, async () => { const { updateId } = await client.getIndex(index.uid).addDocuments(dataset)