diff --git a/.code-samples.meilisearch.yaml b/.code-samples.meilisearch.yaml index 0478f5358..5d32b69a6 100644 --- a/.code-samples.meilisearch.yaml +++ b/.code-samples.meilisearch.yaml @@ -40,7 +40,7 @@ delete_documents_1: |- search_post_1: |- client.index('movies').search('American ninja') search_get_1: |- - client.index('movies').search('American ninja', {}, 'GET') + client.index('movies').search('American ninja') get_update_1: |- client.index('movies').getUpdateStatus(1) get_all_updates_1: |- diff --git a/README.md b/README.md index 9b2fbb50d..471651a83 100644 --- a/README.md +++ b/README.md @@ -188,7 +188,6 @@ All the supported options are described in the [search parameters](https://docs. ```javascript await index.search( 'wonder', - { attributesToHighlight: ['*'], filter: 'id >= 1' } @@ -267,7 +266,7 @@ You can abort a pending search request by providing an [AbortSignal](https://dev const controller = new AbortController() index - .search('wonder', {}, 'POST', { + .search('wonder', {}, { signal: controller.signal, }) .then((response) => { @@ -307,7 +306,11 @@ If you want to know more about the development workflow or want to contribute, p - Make a search request: -`client.index('xxx').search(query: string, options: SearchParams = {}, method: 'POST' | 'GET' = 'POST', config?: Partial): Promise>` +`client.index('xxx').search(query: string, options: SearchParams = {}, config?: Partial): Promise>` + +- Make a search request using GET method (slower than the search method): + +`client.index('xxx').searchGet(query: string, options: SearchParams = {}, config?: Partial): Promise>` ### Indexes diff --git a/src/index.ts b/src/index.ts index 914a598b6..e6b050d4d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -58,62 +58,62 @@ class Index implements Types.IndexInterface { async search

>( query?: string | null, options?: P, - method: Types.Methods = 'POST', config?: Partial ): Promise> { const url = `indexes/${this.uid}/search` - const params: Types.SearchRequest = { - q: query, - offset: options?.offset, - limit: options?.limit, - cropLength: options?.cropLength, - filter: options?.filter, - matches: options?.matches, - facetsDistribution: options?.facetsDistribution, - attributesToRetrieve: options?.attributesToRetrieve, - attributesToCrop: options?.attributesToCrop, - attributesToHighlight: options?.attributesToHighlight, + + return await this.httpRequest.post( + url, + removeUndefinedFromObject({ ...options, q: query }), + undefined, + config + ) + } + + /** + * Search for documents into an index using the GET method + * @memberof Index + * @method search + */ + async searchGet

>( + query?: string | null, + options?: P, + config?: Partial + ): Promise> { + const url = `indexes/${this.uid}/search` + + const parseFilter = (filter?: Types.Filter): string | undefined => { + if (typeof filter === 'string') return filter + else if (Array.isArray(filter)) + throw new MeiliSearchError( + 'The filter query parameter should be in string format when using searchGet' + ) + else return undefined } - if (method.toUpperCase() === 'POST') { - return await this.httpRequest.post( - url, - removeUndefinedFromObject(params), - undefined, - config - ) - } else if (method.toUpperCase() === 'GET') { - const parseFilter = (filter?: any) => { - if (typeof filter === 'string') return filter - else if (Array.isArray(filter)) return JSON.stringify(filter) - else return undefined - } - const getParams: Types.GetSearchRequest = { - ...params, - filter: parseFilter(options?.filter), - facetsDistribution: options?.facetsDistribution - ? JSON.stringify(options.facetsDistribution) - : undefined, - attributesToRetrieve: options?.attributesToRetrieve - ? options.attributesToRetrieve.join(',') - : undefined, - attributesToCrop: options?.attributesToCrop - ? options.attributesToCrop.join(',') - : undefined, - attributesToHighlight: options?.attributesToHighlight - ? options.attributesToHighlight.join(',') - : undefined, - } - return await this.httpRequest.get>( - url, - removeUndefinedFromObject(getParams), - config - ) - } else { - throw new MeiliSearchError( - 'method parameter should be either POST or GET' - ) + const getParams: Types.SearchRequestGET = { + q: query, + ...options, + filter: parseFilter(options?.filter), + facetsDistribution: options?.facetsDistribution + ? options.facetsDistribution.join(',') + : undefined, + attributesToRetrieve: options?.attributesToRetrieve + ? options.attributesToRetrieve.join(',') + : undefined, + attributesToCrop: options?.attributesToCrop + ? options.attributesToCrop.join(',') + : undefined, + attributesToHighlight: options?.attributesToHighlight + ? options.attributesToHighlight.join(',') + : undefined, } + + return await this.httpRequest.get>( + url, + removeUndefinedFromObject(getParams), + config + ) } /// diff --git a/src/types.ts b/src/types.ts index 20dd5775b..7b7059805 100644 --- a/src/types.ts +++ b/src/types.ts @@ -60,29 +60,29 @@ export interface SearchParams { matches?: boolean } -export interface SearchRequest { +export interface SearchRequestGET { q?: string | null offset?: number limit?: number + attributesToRetrieve?: string + attributesToCrop?: string cropLength?: number - attributesToRetrieve?: string[] - attributesToCrop?: string[] - attributesToHighlight?: string[] - facetsDistribution?: string[] - filter?: Filter | Filter[] | string + attributesToHighlight?: string + facetsDistribution?: string + filter?: string matches?: boolean } -export interface GetSearchRequest { +export interface SearchRequest { q?: string | null offset?: number limit?: number - attributesToRetrieve?: string - attributesToCrop?: string cropLength?: number - attributesToHighlight?: string - facetsDistribution?: string - filter?: string + attributesToRetrieve?: string[] + attributesToCrop?: string[] + attributesToHighlight?: string[] + facetsDistribution?: string[] + filter?: Filter matches?: boolean } @@ -280,8 +280,6 @@ export interface MeiliSearchInterface { getDumpStatus: (dumpUid: string) => Promise } -export type Methods = 'POST' | 'GET' - export interface IndexInterface { uid: string getUpdateStatus: (updateId: number) => Promise @@ -289,7 +287,11 @@ export interface IndexInterface { search:

>( query?: string | null, options?: P, - method?: Methods, + config?: Partial + ) => Promise> + searchGet:

>( + query?: string | null, + options?: P, config?: Partial ) => Promise> getRawInfo: () => Promise diff --git a/tests/get_search_tests.ts b/tests/get_search_tests.ts new file mode 100644 index 000000000..84342d0f1 --- /dev/null +++ b/tests/get_search_tests.ts @@ -0,0 +1,487 @@ +import * as Types from '../src/types' +import { + clearAllIndexes, + config, + masterClient, + privateClient, + publicClient, + BAD_HOST, + MeiliSearch, +} from './meilisearch-test-utils' + +const index = { + uid: 'movies_test', +} +const emptyIndex = { + uid: 'empty_test', +} + +const dataset = [ + { + 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: 'sci fi', + }, + { + 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", genre: 'fantasy' }, +] + +jest.setTimeout(100 * 1000) + +afterAll(() => { + return clearAllIndexes(config) +}) + +describe.each([ + { client: masterClient, permission: 'Master' }, + { client: privateClient, permission: 'Private' }, + { client: publicClient, permission: 'Public' }, +])('Test on GET search', ({ client, permission }) => { + beforeAll(async () => { + await clearAllIndexes(config) + await masterClient.createIndex(index.uid) + await masterClient.createIndex(emptyIndex.uid) + + const newFilterableAttributes = ['genre', 'title', 'id'] + const { updateId: settingUpdateId } = await masterClient + .index(index.uid) + .updateFilterableAttributes(newFilterableAttributes) + .then((response: Types.EnqueuedUpdate) => { + expect(response).toHaveProperty('updateId', expect.any(Number)) + return response + }) + + await masterClient.index(index.uid).waitForPendingUpdate(settingUpdateId) + const { updateId } = await masterClient + .index(index.uid) + .addDocuments(dataset) + + await masterClient.index(index.uid).waitForPendingUpdate(updateId) + }) + + test(`${permission} key: Basic search`, async () => { + await client + .index(index.uid) + .searchGet('prince', {}) + .then((response) => { + expect(response).toHaveProperty('hits', expect.any(Array)) + expect(response).toHaveProperty('offset', 0) + expect(response).toHaveProperty('limit', 20) + expect(response).toHaveProperty('processingTimeMs', expect.any(Number)) + expect(response).toHaveProperty('query', 'prince') + expect(response.hits.length).toEqual(2) + }) + }) + + test(`${permission} key: search with options`, async () => { + await client + .index(index.uid) + .searchGet('prince', { limit: 1 }) + .then((response) => { + expect(response).toHaveProperty('hits', expect.any(Array)) + expect(response).toHaveProperty('offset', 0) + expect(response).toHaveProperty('limit', 1) + expect(response).toHaveProperty('processingTimeMs', expect.any(Number)) + expect(response).toHaveProperty('query', 'prince') + expect(response.hits.length).toEqual(1) + }) + }) + + test(`${permission} key: search with array options`, async () => { + await client + .index(index.uid) + .searchGet('prince', { + attributesToRetrieve: ['*'], + }) + .then((response) => { + expect(response).toHaveProperty('hits', expect.any(Array)) + expect(response).toHaveProperty('offset', 0) + expect(response).toHaveProperty('limit', 20) + expect(response).toHaveProperty('processingTimeMs', expect.any(Number)) + expect(response).toHaveProperty('query', 'prince') + expect(response.hits.length).toEqual(2) + }) + }) + + test(`${permission} key: search with array options`, async () => { + await client + .index(index.uid) + .searchGet('prince', { + attributesToRetrieve: ['*'], + }) + .then((response) => { + expect(response).toHaveProperty('hits', expect.any(Array)) + expect(response).toHaveProperty('offset', 0) + expect(response).toHaveProperty('limit', 20) + expect(response).toHaveProperty('processingTimeMs', expect.any(Number)) + expect(response).toHaveProperty('query', 'prince') + expect(response.hits.length).toEqual(2) + }) + }) + + test(`${permission} key: search with options`, async () => { + await client + .index(index.uid) + .searchGet('prince', { limit: 1 }) + .then((response) => { + expect(response).toHaveProperty('hits', expect.any(Array)) + expect(response).toHaveProperty('offset', 0) + expect(response).toHaveProperty('limit', 1) + expect(response).toHaveProperty('processingTimeMs', expect.any(Number)) + expect(response).toHaveProperty('query', 'prince') + expect(response.hits.length).toEqual(1) + }) + }) + + test(`${permission} key: search with limit and offset`, async () => { + await client + .index(index.uid) + .searchGet('prince', { + limit: 1, + offset: 1, + }) + .then((response) => { + expect(response).toHaveProperty('hits', [ + { + id: 4, + title: 'Harry Potter and the Half-Blood Prince', + comment: 'The best book', + genre: 'fantasy', + }, + ]) + expect(response).toHaveProperty('offset', 1) + expect(response).toHaveProperty('limit', 1) + expect(response).toHaveProperty('processingTimeMs', expect.any(Number)) + expect(response).toHaveProperty('query', 'prince') + expect(response.hits.length).toEqual(1) + }) + }) + + test(`${permission} key: search with matches parameter and small croplength`, async () => { + await client + .index(index.uid) + .searchGet('prince', { + filter: 'title = "Le Petit Prince"', + attributesToCrop: ['*'], + cropLength: 5, + matches: true, + }) + .then((response) => { + expect(response).toHaveProperty('hits', expect.any(Array)) + expect(response).toHaveProperty('offset', 0) + expect(response).toHaveProperty('limit', 20) + expect(response).toHaveProperty('processingTimeMs', expect.any(Number)) + expect(response).toHaveProperty('query', 'prince') + expect(response.hits.length).toEqual(1) + expect(response.hits[0]).toHaveProperty('_matchesInfo', { + comment: [{ start: 22, length: 6 }], + title: [{ start: 9, length: 6 }], + }) + }) + }) + + test(`${permission} key: search with all options but not all fields`, async () => { + await client + .index(index.uid) + .searchGet('prince', { + limit: 5, + offset: 0, + attributesToRetrieve: ['id', 'title'], + attributesToCrop: ['*'], + cropLength: 6, + attributesToHighlight: ['*'], + filter: 'title = "Le Petit Prince"', + matches: true, + }) + .then((response) => { + expect(response).toHaveProperty('hits', expect.any(Array)) + expect(response).toHaveProperty('offset', 0) + expect(response).toHaveProperty('limit', 5) + expect(response).toHaveProperty('processingTimeMs', expect.any(Number)) + expect(response).toHaveProperty('query', 'prince') + expect(response.hits[0]._formatted).toHaveProperty('title') + expect(response.hits[0]._formatted).toHaveProperty('id') + expect(response.hits[0]).not.toHaveProperty('comment') + expect(response.hits[0]).not.toHaveProperty('description') + expect(response.hits.length).toEqual(1) + expect(response.hits[0]).toHaveProperty( + '_formatted', + expect.any(Object) + ) + expect(response.hits[0]._formatted).toHaveProperty( + 'title', + 'Petit Prince' + ) + expect(response.hits[0]).toHaveProperty( + '_matchesInfo', + expect.any(Object) + ) + }) + }) + + test(`${permission} key: search with all options and all fields`, async () => { + await client + .index(index.uid) + .searchGet('prince', { + limit: 5, + offset: 0, + attributesToRetrieve: ['*'], + attributesToCrop: ['*'], + cropLength: 6, + attributesToHighlight: ['*'], + filter: 'title = "Le Petit Prince"', + matches: true, + }) + .then((response) => { + expect(response).toHaveProperty('hits', expect.any(Array)) + expect(response).toHaveProperty('offset', 0) + expect(response).toHaveProperty('limit', 5) + expect(response).toHaveProperty('processingTimeMs', expect.any(Number)) + expect(response).toHaveProperty('query', 'prince') + expect(response.hits.length).toEqual(1) + expect(response.hits[0]).toHaveProperty( + '_formatted', + expect.any(Object) + ) + expect(response.hits[0]._formatted).toHaveProperty( + 'title', + 'Petit Prince' + ) + expect(response.hits[0]).toHaveProperty( + '_matchesInfo', + expect.any(Object) + ) + }) + }) + + test(`${permission} key: search with all options but specific fields`, async () => { + await client + .index(index.uid) + .searchGet('prince', { + limit: 5, + offset: 0, + attributesToRetrieve: ['id', 'title'], + attributesToCrop: ['id', 'title'], + cropLength: 6, + attributesToHighlight: ['id', 'title'], + filter: 'title = "Le Petit Prince"', + matches: true, + }) + .then((response) => { + expect(response).toHaveProperty('hits', expect.any(Array)) + expect(response).toHaveProperty('offset', 0) + expect(response).toHaveProperty('limit', 5) + expect(response).toHaveProperty('processingTimeMs', expect.any(Number)) + expect(response).toHaveProperty('query', 'prince') + expect(response.hits.length).toEqual(1) + expect(response.hits[0]).toHaveProperty('id', 456) + expect(response.hits[0]).toHaveProperty('title', 'Le Petit Prince') + expect(response.hits[0]).not.toHaveProperty('comment') + expect(response.hits[0]).toHaveProperty( + '_formatted', + expect.any(Object) + ) + expect(response.hits[0]).not.toHaveProperty( + 'description', + expect.any(Object) + ) + expect(response.hits[0]._formatted).toHaveProperty( + 'title', + 'Petit Prince' + ) + expect(response.hits[0]._formatted).not.toHaveProperty('comment') + expect(response.hits[0]).toHaveProperty( + '_matchesInfo', + expect.any(Object) + ) + }) + }) + + test(`${permission} key: search with filter and facetsDistribution`, async () => { + await client + .index(index.uid) + .searchGet('a', { + filter: 'genre = romance', + facetsDistribution: ['genre'], + }) + .then((response) => { + expect(response).toHaveProperty('facetsDistribution', { + genre: { romance: 2 }, + }) + expect(response).toHaveProperty('exhaustiveFacetsCount', false) + expect(response).toHaveProperty('exhaustiveNbHits', false) + expect(response).toHaveProperty('hits', expect.any(Array)) + expect(response.hits.length).toEqual(2) + }) + }) + + test(`${permission} key: search with filter on number`, async () => { + await client + .index(index.uid) + .searchGet('a', { + filter: 'id < 0', + facetsDistribution: ['genre'], + }) + .then((response) => { + expect(response).toHaveProperty('exhaustiveNbHits', false) + expect(response).toHaveProperty('hits', expect.any(Array)) + expect(response.hits.length).toEqual(0) + }) + }) + + test(`${permission} key: search with filter with spaces`, async () => { + await client + .index(index.uid) + .searchGet('h', { + filter: 'genre = "sci fi"', + }) + .then((response) => { + expect(response).toHaveProperty('hits', expect.any(Array)) + expect(response.hits.length).toEqual(1) + }) + }) + + test(`${permission} key: search with multiple filter`, async () => { + await client + .index(index.uid) + .searchGet('a', { + filter: 'genre = romance AND (genre = romance OR genre = romance)', + facetsDistribution: ['genre'], + }) + .then((response) => { + expect(response).toHaveProperty('facetsDistribution', { + genre: { romance: 2 }, + }) + expect(response).toHaveProperty('exhaustiveFacetsCount', false) + expect(response).toHaveProperty('exhaustiveNbHits', false) + expect(response).toHaveProperty('hits', expect.any(Array)) + expect(response.hits.length).toEqual(2) + }) + }) + + test(`${permission} key: search with multiple filter and undefined query (placeholder)`, async () => { + await client + .index(index.uid) + .searchGet(undefined, { + filter: 'genre = fantasy', + facetsDistribution: ['genre'], + }) + .then((response) => { + expect(response).toHaveProperty('facetsDistribution', { + genre: { fantasy: 2 }, + }) + expect(response.hits.length).toEqual(2) + }) + }) + + test(`${permission} key: search with multiple filter and null query (placeholder)`, async () => { + await client + .index(index.uid) + .searchGet(null, { + filter: 'genre = fantasy', + facetsDistribution: ['genre'], + }) + .then((response) => { + expect(response).toHaveProperty('facetsDistribution', { + genre: { fantasy: 2 }, + }) + expect(response.hits.length).toEqual(2) + expect(response.nbHits).toEqual(2) + }) + }) + + test(`${permission} key: search with multiple filter and empty string query (placeholder)`, async () => { + await client + .index(index.uid) + .searchGet('', { + filter: 'genre = fantasy', + facetsDistribution: ['genre'], + }) + .then((response) => { + expect(response).toHaveProperty('facetsDistribution', { + genre: { fantasy: 2 }, + }) + expect(response.hits.length).toEqual(2) + }) + }) + + test(`${permission} key: Try to search with wrong format filter`, async () => { + await expect( + client.index(index.uid).searchGet('prince', { + filter: ['hello'], + }) + ).rejects.toHaveProperty( + 'message', + 'The filter query parameter should be in string format when using searchGet' + ) + }) + + test(`${permission} key: Try to search on deleted index and fail`, async () => { + await masterClient.index(index.uid).delete() + await expect( + client.index(index.uid).searchGet('prince') + ).rejects.toHaveProperty('errorCode', Types.ErrorStatusCode.INDEX_NOT_FOUND) + }) +}) + +describe.each([ + { host: BAD_HOST, trailing: false }, + { host: `${BAD_HOST}/api`, trailing: false }, + { host: `${BAD_HOST}/trailing/`, trailing: true }, +])('Tests on url construction', ({ host, trailing }) => { + test(`Test get search route`, async () => { + const route = `indexes/${index.uid}/search` + const client = new MeiliSearch({ host }) + const strippedHost = trailing ? host.slice(0, -1) : host + await expect(client.index(index.uid).searchGet()).rejects.toHaveProperty( + 'message', + `request to ${strippedHost}/${route} failed, reason: connect ECONNREFUSED ${BAD_HOST.replace( + 'http://', + '' + )}` + ) + }) + + test(`Test post search route`, async () => { + const route = `indexes/${index.uid}/search` + const client = new MeiliSearch({ host }) + const strippedHost = trailing ? host.slice(0, -1) : host + await expect(client.index(index.uid).searchGet()).rejects.toHaveProperty( + 'message', + `request to ${strippedHost}/${route} failed, reason: connect ECONNREFUSED ${BAD_HOST.replace( + 'http://', + '' + )}` + ) + }) +}) diff --git a/tests/search_tests.ts b/tests/search_tests.ts index 1812410d0..6b402bd03 100644 --- a/tests/search_tests.ts +++ b/tests/search_tests.ts @@ -69,459 +69,405 @@ describe.each([ { client: masterClient, permission: 'Master' }, { client: privateClient, permission: 'Private' }, { client: publicClient, permission: 'Public' }, -])('Test on search', ({ client, permission }) => { - describe.each([ - { method: 'POST' as Types.Methods, permission, client }, - // { method: 'GET' as Types.Methods, permission, client }, // does not work with facetsDistribution - ])('Test on search', ({ client, permission, method }) => { - beforeAll(async () => { - await clearAllIndexes(config) - await masterClient.createIndex(index.uid) - await masterClient.createIndex(emptyIndex.uid) - - const newFilterableAttributes = ['genre', 'title', 'id'] - const { updateId: settingUpdateId } = await masterClient - .index(index.uid) - .updateFilterableAttributes(newFilterableAttributes) - .then((response: Types.EnqueuedUpdate) => { - expect(response).toHaveProperty('updateId', expect.any(Number)) - return response - }) - await masterClient.index(index.uid).waitForPendingUpdate(settingUpdateId) - const { updateId } = await masterClient - .index(index.uid) - .addDocuments(dataset) - await masterClient.index(index.uid).waitForPendingUpdate(updateId) - }) +])('Test on POST search', ({ client, permission }) => { + beforeAll(async () => { + await clearAllIndexes(config) + await masterClient.createIndex(index.uid) + await masterClient.createIndex(emptyIndex.uid) + + const newFilterableAttributes = ['genre', 'title', 'id'] + const { updateId: settingUpdateId } = await masterClient + .index(index.uid) + .updateFilterableAttributes(newFilterableAttributes) + .then((response: Types.EnqueuedUpdate) => { + expect(response).toHaveProperty('updateId', expect.any(Number)) + return response + }) + await masterClient.index(index.uid).waitForPendingUpdate(settingUpdateId) + const { updateId } = await masterClient + .index(index.uid) + .addDocuments(dataset) + await masterClient.index(index.uid).waitForPendingUpdate(updateId) + }) - test(`${permission} key: Basic ${method} search`, async () => { - await client - .index(index.uid) - .search('prince', {}, method) - .then((response) => { - expect(response).toHaveProperty('hits', expect.any(Array)) - expect(response).toHaveProperty('offset', 0) - expect(response).toHaveProperty('limit', 20) - expect(response).toHaveProperty( - 'processingTimeMs', - expect.any(Number) - ) - expect(response).toHaveProperty('query', 'prince') - expect(response.hits.length).toEqual(2) - }) - }) + test(`${permission} key: Basic search`, async () => { + await client + .index(index.uid) + .search('prince', {}) + .then((response) => { + expect(response).toHaveProperty('hits', expect.any(Array)) + expect(response).toHaveProperty('offset', 0) + expect(response).toHaveProperty('limit', 20) + expect(response).toHaveProperty('processingTimeMs', expect.any(Number)) + expect(response).toHaveProperty('query', 'prince') + expect(response.hits.length).toEqual(2) + }) + }) - test(`${permission} key: Basic ${method} phrase search`, async () => { - await client - .index(index.uid) - .search('"french book" about', {}, method) - .then((response) => { - expect(response).toHaveProperty('hits', expect.any(Array)) - expect(response).toHaveProperty('offset', 0) - expect(response).toHaveProperty('limit', 20) - expect(response).toHaveProperty( - 'processingTimeMs', - expect.any(Number) - ) - expect(response).toHaveProperty('query', '"french book" about') - expect(response.hits.length).toEqual(2) - }) - }) + test(`${permission} key: Basic phrase search`, async () => { + await client + .index(index.uid) + .search('"french book" about', {}) + .then((response) => { + expect(response).toHaveProperty('hits', expect.any(Array)) + expect(response).toHaveProperty('offset', 0) + expect(response).toHaveProperty('limit', 20) + expect(response).toHaveProperty('processingTimeMs', expect.any(Number)) + expect(response).toHaveProperty('query', '"french book" about') + expect(response.hits.length).toEqual(2) + }) + }) - test(`${permission} key: ${method} search with options`, async () => { - await client - .index(index.uid) - .search('prince', { limit: 1 }, method) - .then((response) => { - expect(response).toHaveProperty('hits', expect.any(Array)) - expect(response).toHaveProperty('offset', 0) - expect(response).toHaveProperty('limit', 1) - expect(response).toHaveProperty( - 'processingTimeMs', - expect.any(Number) - ) - expect(response).toHaveProperty('query', 'prince') - expect(response.hits.length).toEqual(1) - }) - }) + test(`${permission} key: search with options`, async () => { + await client + .index(index.uid) + .search('prince', { limit: 1 }) + .then((response) => { + expect(response).toHaveProperty('hits', expect.any(Array)) + expect(response).toHaveProperty('offset', 0) + expect(response).toHaveProperty('limit', 1) + expect(response).toHaveProperty('processingTimeMs', expect.any(Number)) + expect(response).toHaveProperty('query', 'prince') + expect(response.hits.length).toEqual(1) + }) + }) - test(`${permission} key: ${method} search with options`, async () => { - await client - .index(index.uid) - .search('prince', { limit: 1 }, method) - .then((response) => { - expect(response).toHaveProperty('hits', expect.any(Array)) - expect(response).toHaveProperty('offset', 0) - expect(response).toHaveProperty('limit', 1) - expect(response).toHaveProperty( - 'processingTimeMs', - expect.any(Number) - ) - expect(response).toHaveProperty('query', 'prince') - expect(response.hits.length).toEqual(1) - }) - }) + test(`${permission} key: search with array options`, async () => { + await client + .index(index.uid) + .search('prince', { + attributesToRetrieve: ['*'], + }) + .then((response) => { + expect(response).toHaveProperty('hits', expect.any(Array)) + expect(response).toHaveProperty('offset', 0) + expect(response).toHaveProperty('limit', 20) + expect(response).toHaveProperty('processingTimeMs', expect.any(Number)) + expect(response).toHaveProperty('query', 'prince') + expect(response.hits.length).toEqual(2) + }) + }) - test(`${permission} key: ${method} search with limit and offset`, async () => { - await client - .index(index.uid) - .search( - 'prince', - { - limit: 1, - offset: 1, - }, - method - ) - .then((response) => { - expect(response).toHaveProperty('hits', [ - { - id: 4, - title: 'Harry Potter and the Half-Blood Prince', - comment: 'The best book', - genre: 'fantasy', - }, - ]) - expect(response).toHaveProperty('offset', 1) - expect(response).toHaveProperty('limit', 1) - expect(response).toHaveProperty( - 'processingTimeMs', - expect.any(Number) - ) - expect(response).toHaveProperty('query', 'prince') - expect(response.hits.length).toEqual(1) - }) - }) + test(`${permission} key: search with array options`, async () => { + await client + .index(index.uid) + .search('prince', { + attributesToRetrieve: ['*'], + }) + .then((response) => { + expect(response).toHaveProperty('hits', expect.any(Array)) + expect(response).toHaveProperty('offset', 0) + expect(response).toHaveProperty('limit', 20) + expect(response).toHaveProperty('processingTimeMs', expect.any(Number)) + expect(response).toHaveProperty('query', 'prince') + expect(response.hits.length).toEqual(2) + }) + }) - test(`${permission} key: ${method} search with matches parameter and small croplength`, async () => { - await client - .index(index.uid) - .search( - 'prince', - { - filter: 'title = "Le Petit Prince"', - attributesToCrop: ['*'], - cropLength: 5, - matches: true, - }, - method - ) - .then((response) => { - expect(response).toHaveProperty('hits', expect.any(Array)) - expect(response).toHaveProperty('offset', 0) - expect(response).toHaveProperty('limit', 20) - expect(response).toHaveProperty( - 'processingTimeMs', - expect.any(Number) - ) - expect(response).toHaveProperty('query', 'prince') - expect(response.hits.length).toEqual(1) - expect(response.hits[0]).toHaveProperty('_matchesInfo', { - comment: [{ start: 22, length: 6 }], - title: [{ start: 9, length: 6 }], - }) - }) - }) + test(`${permission} key: search with options`, async () => { + await client + .index(index.uid) + .search('prince', { limit: 1 }) + .then((response) => { + expect(response).toHaveProperty('hits', expect.any(Array)) + expect(response).toHaveProperty('offset', 0) + expect(response).toHaveProperty('limit', 1) + expect(response).toHaveProperty('processingTimeMs', expect.any(Number)) + expect(response).toHaveProperty('query', 'prince') + expect(response.hits.length).toEqual(1) + }) + }) - test(`${permission} key: ${method} search with all options but not all fields`, async () => { - await client - .index(index.uid) - .search( - 'prince', + test(`${permission} key: search with limit and offset`, async () => { + await client + .index(index.uid) + .search('prince', { + limit: 1, + offset: 1, + }) + .then((response) => { + expect(response).toHaveProperty('hits', [ { - limit: 5, - offset: 0, - attributesToRetrieve: ['id', 'title'], - attributesToCrop: ['*'], - cropLength: 6, - attributesToHighlight: ['*'], - filter: 'title = "Le Petit Prince"', - matches: true, + id: 4, + title: 'Harry Potter and the Half-Blood Prince', + comment: 'The best book', + genre: 'fantasy', }, - method - ) - .then((response) => { - expect(response).toHaveProperty('hits', expect.any(Array)) - expect(response).toHaveProperty('offset', 0) - expect(response).toHaveProperty('limit', 5) - expect(response).toHaveProperty( - 'processingTimeMs', - expect.any(Number) - ) - expect(response).toHaveProperty('query', 'prince') - expect(response.hits[0]._formatted).toHaveProperty('title') - expect(response.hits[0]._formatted).toHaveProperty('id') - expect(response.hits[0]).not.toHaveProperty('comment') - expect(response.hits[0]).not.toHaveProperty('description') - expect(response.hits.length).toEqual(1) - expect(response.hits[0]).toHaveProperty( - '_formatted', - expect.any(Object) - ) - expect(response.hits[0]._formatted).toHaveProperty( - 'title', - 'Petit Prince' - ) - expect(response.hits[0]).toHaveProperty( - '_matchesInfo', - expect.any(Object) - ) - }) - }) + ]) + expect(response).toHaveProperty('offset', 1) + expect(response).toHaveProperty('limit', 1) + expect(response).toHaveProperty('processingTimeMs', expect.any(Number)) + expect(response).toHaveProperty('query', 'prince') + expect(response.hits.length).toEqual(1) + }) + }) - test(`${permission} key: ${method} search with all options and all fields`, async () => { - await client - .index(index.uid) - .search( - 'prince', - { - limit: 5, - offset: 0, - attributesToRetrieve: ['*'], - attributesToCrop: ['*'], - cropLength: 6, - attributesToHighlight: ['*'], - filter: 'title = "Le Petit Prince"', - matches: true, - }, - method - ) - .then((response) => { - expect(response).toHaveProperty('hits', expect.any(Array)) - expect(response).toHaveProperty('offset', 0) - expect(response).toHaveProperty('limit', 5) - expect(response).toHaveProperty( - 'processingTimeMs', - expect.any(Number) - ) - expect(response).toHaveProperty('query', 'prince') - expect(response.hits.length).toEqual(1) - expect(response.hits[0]).toHaveProperty( - '_formatted', - expect.any(Object) - ) - expect(response.hits[0]._formatted).toHaveProperty( - 'title', - 'Petit Prince' - ) - expect(response.hits[0]).toHaveProperty( - '_matchesInfo', - expect.any(Object) - ) + test(`${permission} key: search with matches parameter and small croplength`, async () => { + await client + .index(index.uid) + .search('prince', { + filter: 'title = "Le Petit Prince"', + attributesToCrop: ['*'], + cropLength: 5, + matches: true, + }) + .then((response) => { + expect(response).toHaveProperty('hits', expect.any(Array)) + expect(response).toHaveProperty('offset', 0) + expect(response).toHaveProperty('limit', 20) + expect(response).toHaveProperty('processingTimeMs', expect.any(Number)) + expect(response).toHaveProperty('query', 'prince') + expect(response.hits.length).toEqual(1) + expect(response.hits[0]).toHaveProperty('_matchesInfo', { + comment: [{ start: 22, length: 6 }], + title: [{ start: 9, length: 6 }], }) - }) + }) + }) - test(`${permission} key: ${method} search with all options but specific fields`, async () => { - await client - .index(index.uid) - .search( - 'prince', - { - limit: 5, - offset: 0, - attributesToRetrieve: ['id', 'title'], - attributesToCrop: ['id', 'title'], - cropLength: 6, - attributesToHighlight: ['id', 'title'], - filter: 'title = "Le Petit Prince"', - matches: true, - }, - method + test(`${permission} key: search with all options but not all fields`, async () => { + await client + .index(index.uid) + .search('prince', { + limit: 5, + offset: 0, + attributesToRetrieve: ['id', 'title'], + attributesToCrop: ['*'], + cropLength: 6, + attributesToHighlight: ['*'], + filter: 'title = "Le Petit Prince"', + matches: true, + }) + .then((response) => { + expect(response).toHaveProperty('hits', expect.any(Array)) + expect(response).toHaveProperty('offset', 0) + expect(response).toHaveProperty('limit', 5) + expect(response).toHaveProperty('processingTimeMs', expect.any(Number)) + expect(response).toHaveProperty('query', 'prince') + expect(response.hits[0]._formatted).toHaveProperty('title') + expect(response.hits[0]._formatted).toHaveProperty('id') + expect(response.hits[0]).not.toHaveProperty('comment') + expect(response.hits[0]).not.toHaveProperty('description') + expect(response.hits.length).toEqual(1) + expect(response.hits[0]).toHaveProperty( + '_formatted', + expect.any(Object) ) - .then((response) => { - expect(response).toHaveProperty('hits', expect.any(Array)) - expect(response).toHaveProperty('offset', 0) - expect(response).toHaveProperty('limit', 5) - expect(response).toHaveProperty( - 'processingTimeMs', - expect.any(Number) - ) - expect(response).toHaveProperty('query', 'prince') - expect(response.hits.length).toEqual(1) - expect(response.hits[0]).toHaveProperty('id', 456) - expect(response.hits[0]).toHaveProperty('title', 'Le Petit Prince') - expect(response.hits[0]).not.toHaveProperty('comment') - expect(response.hits[0]).toHaveProperty( - '_formatted', - expect.any(Object) - ) - expect(response.hits[0]).not.toHaveProperty( - 'description', - expect.any(Object) - ) - expect(response.hits[0]._formatted).toHaveProperty( - 'title', - 'Petit Prince' - ) - expect(response.hits[0]._formatted).not.toHaveProperty('comment') - expect(response.hits[0]).toHaveProperty( - '_matchesInfo', - expect.any(Object) - ) - }) - }) - - test(`${permission} key: ${method} search with filter and facetsDistribution`, async () => { - await client - .index(index.uid) - .search( - 'a', - { - filter: ['genre = romance'], - facetsDistribution: ['genre'], - }, - method + expect(response.hits[0]._formatted).toHaveProperty( + 'title', + 'Petit Prince' ) - .then((response) => { - expect(response).toHaveProperty('facetsDistribution', { - genre: { romance: 2 }, - }) - expect(response).toHaveProperty('exhaustiveFacetsCount', false) - expect(response).toHaveProperty('exhaustiveNbHits', false) - expect(response).toHaveProperty('hits', expect.any(Array)) - expect(response.hits.length).toEqual(2) - }) - }) - - test(`${permission} key: ${method} search with filter on number`, async () => { - await client - .index(index.uid) - .search( - 'a', - { - filter: 'id < 0', - }, - method + expect(response.hits[0]).toHaveProperty( + '_matchesInfo', + expect.any(Object) ) - .then((response) => { - expect(response).toHaveProperty('exhaustiveNbHits', false) - expect(response).toHaveProperty('hits', expect.any(Array)) - expect(response.hits.length).toEqual(0) - }) - }) + }) + }) - test(`${permission} key: ${method} search with filter with spaces`, async () => { - await client - .index(index.uid) - .search( - 'h', - { - filter: ['genre = "sci fi"'], - }, - method + test(`${permission} key: search with all options and all fields`, async () => { + await client + .index(index.uid) + .search('prince', { + limit: 5, + offset: 0, + attributesToRetrieve: ['*'], + attributesToCrop: ['*'], + cropLength: 6, + attributesToHighlight: ['*'], + filter: 'title = "Le Petit Prince"', + matches: true, + }) + .then((response) => { + expect(response).toHaveProperty('hits', expect.any(Array)) + expect(response).toHaveProperty('offset', 0) + expect(response).toHaveProperty('limit', 5) + expect(response).toHaveProperty('processingTimeMs', expect.any(Number)) + expect(response).toHaveProperty('query', 'prince') + expect(response.hits.length).toEqual(1) + expect(response.hits[0]).toHaveProperty( + '_formatted', + expect.any(Object) ) - .then((response) => { - expect(response).toHaveProperty('hits', expect.any(Array)) - expect(response.hits.length).toEqual(1) - }) - }) + expect(response.hits[0]._formatted).toHaveProperty( + 'title', + 'Petit Prince' + ) + expect(response.hits[0]).toHaveProperty( + '_matchesInfo', + expect.any(Object) + ) + }) + }) - test(`${permission} key: ${method} search with multiple filter`, async () => { - await client - .index(index.uid) - .search( - 'a', - { - filter: ['genre = romance', ['genre = romance', 'genre = romance']], - facetsDistribution: ['genre'], - }, - method + test(`${permission} key: search with all options but specific fields`, async () => { + await client + .index(index.uid) + .search('prince', { + limit: 5, + offset: 0, + attributesToRetrieve: ['id', 'title'], + attributesToCrop: ['id', 'title'], + cropLength: 6, + attributesToHighlight: ['id', 'title'], + filter: 'title = "Le Petit Prince"', + matches: true, + }) + .then((response) => { + expect(response).toHaveProperty('hits', expect.any(Array)) + expect(response).toHaveProperty('offset', 0) + expect(response).toHaveProperty('limit', 5) + expect(response).toHaveProperty('processingTimeMs', expect.any(Number)) + expect(response).toHaveProperty('query', 'prince') + expect(response.hits.length).toEqual(1) + expect(response.hits[0]).toHaveProperty('id', 456) + expect(response.hits[0]).toHaveProperty('title', 'Le Petit Prince') + expect(response.hits[0]).not.toHaveProperty('comment') + expect(response.hits[0]).toHaveProperty( + '_formatted', + expect.any(Object) + ) + expect(response.hits[0]).not.toHaveProperty( + 'description', + expect.any(Object) ) - .then((response) => { - expect(response).toHaveProperty('facetsDistribution', { - genre: { romance: 2 }, - }) - expect(response).toHaveProperty('exhaustiveFacetsCount', false) - expect(response).toHaveProperty('exhaustiveNbHits', false) - expect(response).toHaveProperty('hits', expect.any(Array)) - expect(response.hits.length).toEqual(2) + expect(response.hits[0]._formatted).toHaveProperty( + 'title', + 'Petit Prince' + ) + expect(response.hits[0]._formatted).not.toHaveProperty('comment') + expect(response.hits[0]).toHaveProperty( + '_matchesInfo', + expect.any(Object) + ) + }) + }) + + test(`${permission} key: search with filter and facetsDistribution`, async () => { + await client + .index(index.uid) + .search('a', { + filter: ['genre = romance'], + facetsDistribution: ['genre'], + }) + .then((response) => { + expect(response).toHaveProperty('facetsDistribution', { + genre: { romance: 2 }, }) - }) + expect(response).toHaveProperty('exhaustiveFacetsCount', false) + expect(response).toHaveProperty('exhaustiveNbHits', false) + expect(response).toHaveProperty('hits', expect.any(Array)) + expect(response.hits.length).toEqual(2) + }) + }) - test(`${permission} key: ${method} search with multiple filter and undefined query (placeholder)`, async () => { - await client - .index(index.uid) - .search( - undefined, - { - filter: ['genre = fantasy'], - facetsDistribution: ['genre'], - }, - method - ) - .then((response) => { - expect(response).toHaveProperty('facetsDistribution', { - genre: { fantasy: 2 }, - }) - expect(response.hits.length).toEqual(2) + test(`${permission} key: search with filter on number`, async () => { + await client + .index(index.uid) + .search('a', { + filter: 'id < 0', + }) + .then((response) => { + expect(response).toHaveProperty('exhaustiveNbHits', false) + expect(response).toHaveProperty('hits', expect.any(Array)) + expect(response.hits.length).toEqual(0) + }) + }) + + test(`${permission} key: search with filter with spaces`, async () => { + await client + .index(index.uid) + .search('h', { + filter: ['genre = "sci fi"'], + }) + .then((response) => { + expect(response).toHaveProperty('hits', expect.any(Array)) + expect(response.hits.length).toEqual(1) + }) + }) + + test(`${permission} key: search with multiple filter`, async () => { + await client + .index(index.uid) + .search('a', { + filter: ['genre = romance', ['genre = romance', 'genre = romance']], + facetsDistribution: ['genre'], + }) + .then((response) => { + expect(response).toHaveProperty('facetsDistribution', { + genre: { romance: 2 }, }) - }) + expect(response).toHaveProperty('exhaustiveFacetsCount', false) + expect(response).toHaveProperty('exhaustiveNbHits', false) + expect(response).toHaveProperty('hits', expect.any(Array)) + expect(response.hits.length).toEqual(2) + }) + }) - test(`${permission} key: ${method} search with multiple filter and null query (placeholder)`, async () => { - await client - .index(index.uid) - .search( - null, - { - filter: ['genre = fantasy'], - facetsDistribution: ['genre'], - }, - method - ) - .then((response) => { - expect(response).toHaveProperty('facetsDistribution', { - genre: { fantasy: 2 }, - }) - expect(response.hits.length).toEqual(2) - expect(response.nbHits).toEqual(2) + test(`${permission} key: search with multiple filter and undefined query (placeholder)`, async () => { + await client + .index(index.uid) + .search(undefined, { + filter: ['genre = fantasy'], + facetsDistribution: ['genre'], + }) + .then((response) => { + expect(response).toHaveProperty('facetsDistribution', { + genre: { fantasy: 2 }, }) - }) + expect(response.hits.length).toEqual(2) + }) + }) - test(`${permission} key: ${method} search with multiple filter and empty string query (placeholder)`, async () => { - await client - .index(index.uid) - .search( - '', - { - filter: ['genre = fantasy'], - facetsDistribution: ['genre'], - }, - method - ) - .then((response) => { - expect(response).toHaveProperty('facetsDistribution', { - genre: { fantasy: 2 }, - }) - expect(response.hits.length).toEqual(2) + test(`${permission} key: search with multiple filter and null query (placeholder)`, async () => { + await client + .index(index.uid) + .search(null, { + filter: ['genre = fantasy'], + facetsDistribution: ['genre'], + }) + .then((response) => { + expect(response).toHaveProperty('facetsDistribution', { + genre: { fantasy: 2 }, }) - }) + expect(response.hits.length).toEqual(2) + expect(response.nbHits).toEqual(2) + }) + }) - test(`${permission} key: ${method} search on index with no documents and no primary key`, async () => { - await client - .index(emptyIndex.uid) - .search('prince', {}, method) - .then((response) => { - expect(response).toHaveProperty('hits', []) - expect(response).toHaveProperty('offset', 0) - expect(response).toHaveProperty('limit', 20) - expect(response).toHaveProperty( - 'processingTimeMs', - expect.any(Number) - ) - expect(response).toHaveProperty('query', 'prince') - expect(response.hits.length).toEqual(0) + test(`${permission} key: search with multiple filter and empty string query (placeholder)`, async () => { + await client + .index(index.uid) + .search('', { + filter: ['genre = fantasy'], + facetsDistribution: ['genre'], + }) + .then((response) => { + expect(response).toHaveProperty('facetsDistribution', { + genre: { fantasy: 2 }, }) - }) + expect(response.hits.length).toEqual(2) + }) + }) - test(`${permission} key: Try to ${method} search on deleted index and fail`, async () => { - await masterClient.index(index.uid).delete() - await expect( - client.index(index.uid).search('prince', {}, method) - ).rejects.toHaveProperty( - 'errorCode', - Types.ErrorStatusCode.INDEX_NOT_FOUND - ) - }) + test(`${permission} key: search on index with no documents and no primary key`, async () => { + await client + .index(emptyIndex.uid) + .search('prince', {}) + .then((response) => { + expect(response).toHaveProperty('hits', []) + expect(response).toHaveProperty('offset', 0) + expect(response).toHaveProperty('limit', 20) + expect(response).toHaveProperty('processingTimeMs', expect.any(Number)) + expect(response).toHaveProperty('query', 'prince') + expect(response.hits.length).toEqual(0) + }) + }) + + test(`${permission} key: Try to search on deleted index and fail`, async () => { + await masterClient.index(index.uid).delete() + await expect( + client.index(index.uid).search('prince', {}) + ).rejects.toHaveProperty('errorCode', Types.ErrorStatusCode.INDEX_NOT_FOUND) }) }) @@ -549,77 +495,78 @@ describe.each([ // { client: privateClient, permission: 'Private' }, // { client: publicClient, permission: 'Public' }, ])('Test on abortable search', ({ client, permission }) => { - describe.each([ - { method: 'POST' as Types.Methods, permission, client }, - { method: 'GET' as Types.Methods, permission, client }, - ])('Test on abortable search', ({ client, permission, method }) => { - beforeAll(async () => { - await clearAllIndexes(config) - await masterClient.createIndex(index.uid) - }) + beforeAll(async () => { + await clearAllIndexes(config) + await masterClient.createIndex(index.uid) + }) - test(`${permission} key: ${method} search on index and abort`, () => { - const controller = new AbortController() + test(`${permission} key: search on index and abort`, () => { + const controller = new AbortController() - const searchPromise = client - .index(index.uid) - .search('unreachable', {}, method, { - signal: controller.signal, - }) + const searchPromise = client.index(index.uid).search( + 'unreachable', + {}, + { + signal: controller.signal, + } + ) - controller.abort() + controller.abort() - searchPromise.catch((error) => { - expect(error).toHaveProperty('message', 'The user aborted a request.') - }) + searchPromise.catch((error) => { + expect(error).toHaveProperty('message', 'The user aborted a request.') }) + }) - test(`${permission} key: ${method} search on index multiple times, and abort only one request`, () => { - const controllerA = new AbortController() - const controllerB = new AbortController() - const controllerC = new AbortController() + test(`${permission} key: search on index multiple times, and abort only one request`, () => { + const controllerA = new AbortController() + const controllerB = new AbortController() + const controllerC = new AbortController() - const searchQuery = 'prince' + const searchQuery = 'prince' - const searchAPromise = client - .index(index.uid) - .search(searchQuery, {}, method, { - signal: controllerA.signal, - }) + const searchAPromise = client.index(index.uid).search( + searchQuery, + {}, + { + signal: controllerA.signal, + } + ) - const searchBPromise = client - .index(index.uid) - .search(searchQuery, {}, method, { - signal: controllerB.signal, - }) + const searchBPromise = client.index(index.uid).search( + searchQuery, + {}, + { + signal: controllerB.signal, + } + ) - const searchCPromise = client - .index(index.uid) - .search(searchQuery, {}, method, { - signal: controllerC.signal, - }) + const searchCPromise = client.index(index.uid).search( + searchQuery, + {}, + { + signal: controllerC.signal, + } + ) - const searchDPromise = client - .index(index.uid) - .search(searchQuery, {}, method) + const searchDPromise = client.index(index.uid).search(searchQuery, {}) - controllerB.abort() + controllerB.abort() - searchDPromise.then((response) => { - expect(response).toHaveProperty('query', searchQuery) - }) + searchDPromise.then((response) => { + expect(response).toHaveProperty('query', searchQuery) + }) - searchCPromise.then((response) => { - expect(response).toHaveProperty('query', searchQuery) - }) + searchCPromise.then((response) => { + expect(response).toHaveProperty('query', searchQuery) + }) - searchAPromise.then((response) => { - expect(response).toHaveProperty('query', searchQuery) - }) + searchAPromise.then((response) => { + expect(response).toHaveProperty('query', searchQuery) + }) - searchBPromise.catch((error) => { - expect(error).toHaveProperty('message', 'The user aborted a request.') - }) + searchBPromise.catch((error) => { + expect(error).toHaveProperty('message', 'The user aborted a request.') }) }) }) diff --git a/tests/typed_search_tests.ts b/tests/typed_search_tests.ts index 3f3e2b417..2b0ba4351 100644 --- a/tests/typed_search_tests.ts +++ b/tests/typed_search_tests.ts @@ -1,4 +1,4 @@ -import { Methods, EnqueuedUpdate, ErrorStatusCode } from '../src/types' +import { EnqueuedUpdate, ErrorStatusCode } from '../src/types' import { clearAllIndexes, config, @@ -75,358 +75,292 @@ describe.each([ { client: privateClient, permission: 'Private' }, { client: publicClient, permission: 'Public' }, ])('Test on search', ({ client, permission }) => { - describe.each([ - { method: 'POST' as Methods, permission, client }, - // { method: 'GET' as Methods, permission, client }, skipped until fixed - ])('Test on search', ({ client, permission, method }) => { - beforeAll(async () => { - await clearAllIndexes(config) - await masterClient.createIndex(index.uid) - await masterClient.createIndex(emptyIndex.uid) - const newFilterableAttributes = ['genre', 'title'] - const { updateId: settingUpdateId } = await masterClient - .index(index.uid) - .updateFilterableAttributes(newFilterableAttributes) - .then((response: EnqueuedUpdate) => { - expect(response).toHaveProperty('updateId', expect.any(Number)) - return response - }) - await masterClient - .index(index.uid) - .waitForPendingUpdate(settingUpdateId) - const { updateId } = await masterClient - .index(index.uid) - .addDocuments(dataset) - await masterClient.index(index.uid).waitForPendingUpdate(updateId) - }) - - test(`${permission} key: ${method} Basic search`, async () => { - await client - .index(index.uid) - .search('prince', {}, method) - .then((response) => { - expect(response.hits.length === 2).toBeTruthy() - expect(response.offset === 0).toBeTruthy() - expect(response.limit === 20).toBeTruthy() - expect(response).toHaveProperty( - 'processingTimeMs', - expect.any(Number) - ) - expect(response.query === 'prince').toBeTruthy() - }) - }) + beforeAll(async () => { + await clearAllIndexes(config) + await masterClient.createIndex(index.uid) + await masterClient.createIndex(emptyIndex.uid) + const newFilterableAttributes = ['genre', 'title'] + const { updateId: settingUpdateId } = await masterClient + .index(index.uid) + .updateFilterableAttributes(newFilterableAttributes) + .then((response: EnqueuedUpdate) => { + expect(response).toHaveProperty('updateId', expect.any(Number)) + return response + }) + await masterClient + .index(index.uid) + .waitForPendingUpdate(settingUpdateId) + const { updateId } = await masterClient + .index(index.uid) + .addDocuments(dataset) + await masterClient.index(index.uid).waitForPendingUpdate(updateId) + }) - test(`${permission} key: ${method} Search with options`, async () => { - await client - .index(index.uid) - .search('prince', { limit: 1 }, method) - .then((response) => { - expect(response.hits.length === 1).toBeTruthy() - expect(response.offset === 0).toBeTruthy() - expect(response.limit === 1).toBeTruthy() - expect(response).toHaveProperty( - 'processingTimeMs', - expect.any(Number) - ) - expect(response.query === 'prince').toBeTruthy() - }) - }) + test(`${permission} key: Basic search`, async () => { + await client + .index(index.uid) + .search('prince', {}) + .then((response) => { + expect(response.hits.length === 2).toBeTruthy() + expect(response.offset === 0).toBeTruthy() + expect(response.limit === 20).toBeTruthy() + expect(response).toHaveProperty('processingTimeMs', expect.any(Number)) + expect(response.query === 'prince').toBeTruthy() + }) + }) - test(`${permission} key: ${method} Search with limit and offset`, async () => { - await client - .index(index.uid) - .search( - 'prince', - { - limit: 1, - offset: 1, - }, - method - ) - .then((response) => { - expect(response.hits.length === 1).toBeTruthy() - expect(response.offset === 1).toBeTruthy() - // expect(response.bloub).toEqual(0) -> ERROR, bloub does not exist on type Response - expect(response.limit === 1).toBeTruthy() - expect(response).toHaveProperty( - 'processingTimeMs', - expect.any(Number) - ) - expect(response.query === 'prince').toBeTruthy() - expect(response.hits[0].id).toEqual(4) - expect(response.hits[0].title).toEqual( - 'Harry Potter and the Half-Blood Prince' - ) - expect(response.hits[0].comment).toEqual('The best book') - expect(response.hits[0].genre).toEqual('fantasy') - expect(response.query === 'prince').toBeTruthy() - }) - }) + test(`${permission} key: Search with options`, async () => { + await client + .index(index.uid) + .search('prince', { limit: 1 }) + .then((response) => { + expect(response.hits.length === 1).toBeTruthy() + expect(response.offset === 0).toBeTruthy() + expect(response.limit === 1).toBeTruthy() + expect(response).toHaveProperty('processingTimeMs', expect.any(Number)) + expect(response.query === 'prince').toBeTruthy() + }) + }) - test(`${permission} key: ${method} Search with matches parameter and small croplength`, async () => { - await client - .index(index.uid) - .search( - 'prince', - { - filter: 'title = "Le Petit Prince"', - attributesToCrop: ['*'], - cropLength: 5, - matches: true, - }, - method + test(`${permission} key: Search with limit and offset`, async () => { + await client + .index(index.uid) + .search('prince', { + limit: 1, + offset: 1, + }) + .then((response) => { + expect(response.hits.length === 1).toBeTruthy() + expect(response.offset === 1).toBeTruthy() + // expect(response.bloub).toEqual(0) -> ERROR, bloub does not exist on type Response + expect(response.limit === 1).toBeTruthy() + expect(response).toHaveProperty('processingTimeMs', expect.any(Number)) + expect(response.query === 'prince').toBeTruthy() + expect(response.hits[0].id).toEqual(4) + expect(response.hits[0].title).toEqual( + 'Harry Potter and the Half-Blood Prince' ) - .then((response) => { - expect(response.hits.length === 1).toBeTruthy() - expect(response.offset === 0).toBeTruthy() - expect(response.limit === 20).toBeTruthy() - expect(response).toHaveProperty( - 'processingTimeMs', - expect.any(Number) - ) - expect(response.query === 'prince').toBeTruthy() - expect(response.hits[0]?._matchesInfo?.comment).toEqual([ - { start: 22, length: 6 }, - ]) - expect(response.hits[0]?._matchesInfo?.title).toEqual([ - { start: 9, length: 6 }, - ]) - }) - }) + expect(response.hits[0].comment).toEqual('The best book') + expect(response.hits[0].genre).toEqual('fantasy') + expect(response.query === 'prince').toBeTruthy() + }) + }) - test(`${permission} key: ${method} Search with all options but not all fields`, async () => { - await client - .index(index.uid) - .search( - 'prince', - { - limit: 5, - offset: 0, - attributesToRetrieve: ['id', 'title'], - attributesToCrop: ['*'], - cropLength: 6, - attributesToHighlight: ['*'], - filter: 'title = "Le Petit Prince"', - matches: true, - }, - method - ) - .then((response) => { - expect(response.hits.length === 1).toBeTruthy() - expect(response.offset === 0).toBeTruthy() - expect(response.limit === 5).toBeTruthy() - expect(response).toHaveProperty( - 'processingTimeMs', - expect.any(Number) - ) - expect(response.query === 'prince').toBeTruthy() - expect( - response.hits[0]?._formatted?.title === 'Petit Prince' - ).toBeTruthy() - expect(response.hits[0]._formatted?.id).toEqual(456) - expect(response.hits[0]).not.toHaveProperty('comment') - expect(response.hits[0]).not.toHaveProperty('description') - expect(response.hits[0]._formatted).toHaveProperty('comment') - expect(response.hits.length).toBe(1) - expect(response.hits[0]).toHaveProperty( - '_matchesInfo', - expect.any(Object) - ) - }) - }) + test(`${permission} key: Search with matches parameter and small croplength`, async () => { + await client + .index(index.uid) + .search('prince', { + filter: 'title = "Le Petit Prince"', + attributesToCrop: ['*'], + cropLength: 5, + matches: true, + }) + .then((response) => { + expect(response.hits.length === 1).toBeTruthy() + expect(response.offset === 0).toBeTruthy() + expect(response.limit === 20).toBeTruthy() + expect(response).toHaveProperty('processingTimeMs', expect.any(Number)) + expect(response.query === 'prince').toBeTruthy() + expect(response.hits[0]?._matchesInfo?.comment).toEqual([ + { start: 22, length: 6 }, + ]) + expect(response.hits[0]?._matchesInfo?.title).toEqual([ + { start: 9, length: 6 }, + ]) + }) + }) - test(`${permission} key: ${method} Search with all options and all fields`, async () => { - await client - .index(index.uid) - .search( - 'prince', - { - limit: 5, - offset: 0, - attributesToRetrieve: ['*'], - attributesToCrop: ['*'], - cropLength: 6, - attributesToHighlight: ['*'], - filter: 'title = "Le Petit Prince"', - matches: true, - }, - method + test(`${permission} key: Search with all options but not all fields`, async () => { + await client + .index(index.uid) + .search('prince', { + limit: 5, + offset: 0, + attributesToRetrieve: ['id', 'title'], + attributesToCrop: ['*'], + cropLength: 6, + attributesToHighlight: ['*'], + filter: 'title = "Le Petit Prince"', + matches: true, + }) + .then((response) => { + expect(response.hits.length === 1).toBeTruthy() + expect(response.offset === 0).toBeTruthy() + expect(response.limit === 5).toBeTruthy() + expect(response).toHaveProperty('processingTimeMs', expect.any(Number)) + expect(response.query === 'prince').toBeTruthy() + expect( + response.hits[0]?._formatted?.title === 'Petit Prince' + ).toBeTruthy() + expect(response.hits[0]._formatted?.id).toEqual(456) + expect(response.hits[0]).not.toHaveProperty('comment') + expect(response.hits[0]).not.toHaveProperty('description') + expect(response.hits[0]._formatted).toHaveProperty('comment') + expect(response.hits[0]._formatted).not.toHaveProperty('description') + expect(response.hits.length === 1).toBeTruthy() + expect(response.hits[0]).toHaveProperty( + '_matchesInfo', + expect.any(Object) ) - .then((response) => { - expect(response.hits.length === 1).toBeTruthy() - expect(response.offset === 0).toBeTruthy() - expect(response.limit === 5).toBeTruthy() - expect(response).toHaveProperty( - 'processingTimeMs', - expect.any(Number) - ) - expect(response.query === 'prince').toBeTruthy() - expect(response.hits[0]?.title === 'Le Petit Prince').toBeTruthy() - expect(response.hits[0]?._matchesInfo?.title?.[0]?.start).toBe(9) - expect(response.hits[0]?._matchesInfo?.title?.[0]?.length).toBe(6) - expect( - response.hits[0]?._formatted?.title === 'Petit Prince' - ).toBeTruthy() - }) - }) + }) + }) - test(`${permission} key: ${method} Search with all options but specific fields`, async () => { - await client - .index(index.uid) - .search( - 'prince', - { - limit: 5, - offset: 0, - attributesToRetrieve: ['id', 'title'], - attributesToCrop: ['id', 'title'], - cropLength: 6, - attributesToHighlight: ['id', 'title'], - filter: 'title = "Le Petit Prince"', - matches: true, - }, - method - ) - .then((response) => { - expect(response.hits.length === 1).toBeTruthy() - expect(response.offset === 0).toBeTruthy() - expect(response.limit === 5).toBeTruthy() - expect(response).toHaveProperty( - 'processingTimeMs', - expect.any(Number) - ) - expect(response.query === 'prince').toBeTruthy() + test(`${permission} key: Search with all options and all fields`, async () => { + await client + .index(index.uid) + .search('prince', { + limit: 5, + offset: 0, + attributesToRetrieve: ['*'], + attributesToCrop: ['*'], + cropLength: 6, + attributesToHighlight: ['*'], + filter: 'title = "Le Petit Prince"', + matches: true, + }) + .then((response) => { + expect(response.hits.length === 1).toBeTruthy() + expect(response.offset === 0).toBeTruthy() + expect(response.limit === 5).toBeTruthy() + expect(response).toHaveProperty('processingTimeMs', expect.any(Number)) + expect(response.query === 'prince').toBeTruthy() + expect(response.hits[0]?.title === 'Le Petit Prince').toBeTruthy() + expect( + response.hits[0]?._matchesInfo?.title?.[0]?.start === 9 + ).toBeTruthy() + expect( + response.hits[0]?._matchesInfo?.title?.[0]?.length === 6 + ).toBeTruthy() + expect( + response.hits[0]?._formatted?.title === 'Petit Prince' + ).toBeTruthy() + }) + }) - expect(response.hits[0].id).toEqual(456) - expect(response.hits[0].title).toEqual('Le Petit Prince') - // ERROR Property 'comment' does not exist on type 'Hit>'. - // expect(response.hits[0].comment).toEqual('comment') + test(`${permission} key: Search with all options but specific fields`, async () => { + await client + .index(index.uid) + .search('prince', { + limit: 5, + offset: 0, + attributesToRetrieve: ['id', 'title'], + attributesToCrop: ['id', 'title'], + cropLength: 6, + attributesToHighlight: ['id', 'title'], + filter: 'title = "Le Petit Prince"', + matches: true, + }) + .then((response) => { + expect(response.hits.length === 1).toBeTruthy() + expect(response.offset === 0).toBeTruthy() + expect(response.limit === 5).toBeTruthy() + expect(response).toHaveProperty('processingTimeMs', expect.any(Number)) + expect(response.query === 'prince').toBeTruthy() - expect(response.hits[0]?.title === 'Le Petit Prince').toBeTruthy() - expect(response.hits[0]?._matchesInfo?.title?.[0]?.start).toBe(9) - expect(response.hits[0]?._matchesInfo?.title?.[0]?.length).toBe(6) - expect( - response.hits[0]?._formatted?.title === 'Petit Prince' - ).toBeTruthy() - expect(response.hits[0]).not.toHaveProperty( - 'description', - expect.any(Object) - ) - expect(response.hits[0]._formatted).not.toHaveProperty('comment') - }) - }) + expect(response.hits[0].id).toEqual(456) + expect(response.hits[0].title).toEqual('Le Petit Prince') + // ERROR Property 'comment' does not exist on type 'Hit>'. + // expect(response.hits[0].comment).toEqual('comment') - test(`${permission} key: ${method} Search with filter and facetsDistribution`, async () => { - await client - .index(index.uid) - .search( - 'a', - { - filter: ['genre=romance'], - facetsDistribution: ['genre'], - }, - method + expect(response.hits[0]?.title === 'Le Petit Prince').toBeTruthy() + expect(response.hits[0]?._matchesInfo?.title).toEqual([ + { start: 9, length: 6 }, + ]) + expect( + response.hits[0]?._formatted?.title === 'Petit Prince' + ).toBeTruthy() + expect(response.hits[0]).not.toHaveProperty( + 'description', + expect.any(Object) ) - .then((response) => { - expect(response.facetsDistribution?.genre?.adventure).toBeUndefined() - expect(response.facetsDistribution?.genre?.fantasy).toBeUndefined() - expect(response.facetsDistribution?.genre?.romance).toBe(2) - expect(response.facetsDistribution?.genre['sci fi']).toBeUndefined() - expect(response.exhaustiveFacetsCount === false).toBeTruthy() - expect(response.hits.length === 2).toBeTruthy() - }) - }) + expect(response.hits[0]._formatted).not.toHaveProperty('comment') + }) + }) - test(`${permission} key: ${method} Search with filter with spaces`, async () => { - await client - .index(index.uid) - .search( - 'h', - { - filter: ['genre="sci fi"'], - }, - method - ) - .then((response) => { - expect(response).toHaveProperty('hits', expect.any(Array)) - expect(response.hits.length === 1).toBeTruthy() - }) - }) + test(`${permission} key: Search with filter and facetsDistribution`, async () => { + await client + .index(index.uid) + .search('a', { + filter: ['genre=romance'], + facetsDistribution: ['genre'], + }) + .then((response) => { + expect(response.facetsDistribution?.genre?.romance === 2).toBeTruthy() + expect(response.exhaustiveFacetsCount === false).toBeTruthy() + expect(response.hits.length === 2).toBeTruthy() + }) + }) - test(`${permission} key: ${method} Search with multiple filter`, async () => { - await client - .index(index.uid) - .search( - 'a', - { - filter: ['genre=romance', ['genre=romance', 'genre=romance']], - facetsDistribution: ['genre'], - }, - method - ) - .then((response) => { - expect(response.facetsDistribution?.genre?.romance === 2).toBeTruthy() - expect(response.exhaustiveFacetsCount === false).toBeTruthy() - expect(response.hits.length === 2).toBeTruthy() - }) - }) + test(`${permission} key: Search with filter with spaces`, async () => { + await client + .index(index.uid) + .search('h', { + filter: ['genre="sci fi"'], + }) + .then((response) => { + expect(response).toHaveProperty('hits', expect.any(Array)) + expect(response.hits.length === 1).toBeTruthy() + }) + }) - test(`${permission} key: ${method} search with multiple filter and placeholder search using undefined`, async () => { - await client - .index(index.uid) - .search( - undefined, - { - filter: ['genre = fantasy'], - facetsDistribution: ['genre'], - }, - method - ) - .then((response) => { - expect(response.facetsDistribution?.genre?.fantasy === 2).toBeTruthy() - expect(response.exhaustiveFacetsCount === false).toBeTruthy() - expect(response.hits.length === 2).toBeTruthy() - }) - }) + test(`${permission} key: Search with multiple filter`, async () => { + await client + .index(index.uid) + .search('a', { + filter: ['genre=romance', ['genre=romance', 'genre=romance']], + facetsDistribution: ['genre'], + }) + .then((response) => { + expect(response.facetsDistribution?.genre?.romance === 2).toBeTruthy() + expect(response.exhaustiveFacetsCount === false).toBeTruthy() + expect(response.hits.length === 2).toBeTruthy() + }) + }) - test(`${permission} key: ${method} search with multiple filter and placeholder search using NULL`, async () => { - await client - .index(index.uid) - .search( - null, - { - filter: ['genre = fantasy'], - facetsDistribution: ['genre'], - }, - method - ) - .then((response) => { - expect(response.facetsDistribution?.genre?.fantasy === 2).toBeTruthy() - expect(response.exhaustiveFacetsCount === false).toBeTruthy() - expect(response.hits.length === 2).toBeTruthy() - }) - }) + test(`${permission} key: Search with multiple filter and placeholder search using undefined`, async () => { + await client + .index(index.uid) + .search(undefined, { + filter: ['genre = fantasy'], + facetsDistribution: ['genre'], + }) + .then((response) => { + expect(response.facetsDistribution?.genre?.fantasy === 2).toBeTruthy() + expect(response.exhaustiveFacetsCount === false).toBeTruthy() + expect(response.hits.length === 2).toBeTruthy() + }) + }) - test(`${permission} key: Search on index with no documents and no primary key`, async () => { - await client - .index(emptyIndex.uid) - .search('prince', {}, method) - .then((response) => { - expect(response.limit === 20).toBeTruthy() - expect(response).toHaveProperty( - 'processingTimeMs', - expect.any(Number) - ) - expect(response.query === 'prince').toBeTruthy() - }) - }) + test(`${permission} key: Search with multiple filter and placeholder search using NULL`, async () => { + await client + .index(index.uid) + .search(null, { + filter: ['genre = fantasy'], + facetsDistribution: ['genre'], + }) + .then((response) => { + expect(response.facetsDistribution?.genre?.fantasy === 2).toBeTruthy() + expect(response.exhaustiveFacetsCount === false).toBeTruthy() + expect(response.hits.length === 2).toBeTruthy() + }) + }) - test(`${permission} key: Try to Search on deleted index and fail`, async () => { - await masterClient.index(index.uid).delete() - await expect( - client.index(index.uid).search('prince') - ).rejects.toHaveProperty('errorCode', ErrorStatusCode.INDEX_NOT_FOUND) - }) + test(`${permission} key: Search on index with no documents and no primary key`, async () => { + await client + .index(emptyIndex.uid) + .search('prince', {}) + .then((response) => { + expect(response.limit === 20).toBeTruthy() + expect(response).toHaveProperty('processingTimeMs', expect.any(Number)) + expect(response.query === 'prince').toBeTruthy() + }) + }) + + test(`${permission} key: Try to Search on deleted index and fail`, async () => { + await masterClient.index(index.uid).delete() + await expect( + client.index(index.uid).search('prince') + ).rejects.toHaveProperty('errorCode', ErrorStatusCode.INDEX_NOT_FOUND) }) })