From d72554925ea8e466b52d6f2b6ce73008beaf5fc2 Mon Sep 17 00:00:00 2001 From: amit-ksh Date: Tue, 16 Apr 2024 23:16:36 +0530 Subject: [PATCH] Add searchCutoffMs index setting --- src/indexes.ts | 42 ++++++++ src/types/types.ts | 8 ++ tests/searchCutoffMs.ts | 225 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 275 insertions(+) create mode 100644 tests/searchCutoffMs.ts diff --git a/src/indexes.ts b/src/indexes.ts index c26cf97dc..f8a84801f 100644 --- a/src/indexes.ts +++ b/src/indexes.ts @@ -52,6 +52,7 @@ import { Dictionary, ProximityPrecision, Embedders, + SearchCutoffMsSettings, } from './types' import { removeUndefinedFromObject } from './utils' import { HttpRequests } from './http-requests' @@ -1334,6 +1335,47 @@ class Index = Record> { return task } + + /// + /// SEARCHCUTOFFMS SETTINGS + /// + + /** + * Get the SearchCutoffMs settings. + * + * @returns Promise containing object of SearchCutoffMs settings + */ + async getSearchCutoffMs(): Promise { + const url = `indexes/${this.uid}/settings/search-cutoff-ms` + return await this.httpRequest.get(url) + } + + /** + * Update the SearchCutoffMs settings. + * + * @param searchCutoffMs - Object containing SearchCutoffMsSettings + * @returns Promise containing an EnqueuedTask + */ + async updateSearchCutoffMs( + searchCutoffMs: SearchCutoffMsSettings + ): Promise { + const url = `indexes/${this.uid}/settings/search-cutoff-ms` + const task = await this.httpRequest.patch(url, searchCutoffMs) + + return new EnqueuedTask(task) + } + + /** + * Reset the SearchCutoffMs settings. + * + * @returns Promise containing an EnqueuedTask + */ + async resetSearchCutoffMs(): Promise { + const url = `indexes/${this.uid}/settings/search-cutoff-ms` + const task = await this.httpRequest.delete(url) + + return new EnqueuedTask(task) + } } export { Index } diff --git a/src/types/types.ts b/src/types/types.ts index 1e0e5fc03..606d20ccb 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -373,6 +373,10 @@ export type PaginationSettings = { maxTotalHits?: number | null } +export type SearchCutoffMsSettings = { + searchCutoffMs?: number | null +} + export type Settings = { filterableAttributes?: FilterableAttributes distinctAttribute?: DistinctAttribute @@ -390,6 +394,7 @@ export type Settings = { dictionary?: Dictionary proximityPrecision?: ProximityPrecision embedders?: Embedders + searchCutoffMs?: SearchCutoffMsSettings } /* @@ -930,6 +935,9 @@ export const ErrorStatusCode = { /** @see https://www.meilisearch.com/docs/reference/errors/error_codes#invalid_settings_pagination */ INVALID_SETTINGS_PAGINATION: 'invalid_settings_pagination', + /** @see https://www.meilisearch.com/docs/reference/errors/error_codes#invalid_settings_search_cutoff_ms */ + INVALID_SETTINGS_SEARCH_CUTOFF_MS: 'invalid_settings_search_cutoff_ms', + /** @see https://www.meilisearch.com/docs/reference/errors/error_codes#invalid_task_before_enqueued_at */ INVALID_TASK_BEFORE_ENQUEUED_AT: 'invalid_task_before_enqueued_at', diff --git a/tests/searchCutoffMs.ts b/tests/searchCutoffMs.ts new file mode 100644 index 000000000..3b5c853d6 --- /dev/null +++ b/tests/searchCutoffMs.ts @@ -0,0 +1,225 @@ +import { ErrorStatusCode } from '../src/types' +import { + clearAllIndexes, + config, + BAD_HOST, + MeiliSearch, + getClient, + dataset, +} from './utils/meilisearch-test-utils' + +const index = { + uid: 'movies_test', +} + +const DEFAULT_SEARCHCUTOFFMS = 1500 + +jest.setTimeout(100 * 1000) + +afterAll(() => { + return clearAllIndexes(config) +}) + +describe.each([{ permission: 'Master' }, { permission: 'Admin' }])( + 'Test on searchCutoffMs', + ({ permission }) => { + beforeEach(async () => { + await clearAllIndexes(config) + const client = await getClient('Master') + const { taskUid } = await client.index(index.uid).addDocuments(dataset) + await client.waitForTask(taskUid) + }) + + test(`${permission} key: Get default searchCutoffMs settings`, async () => { + const client = await getClient(permission) + const response = await client.index(index.uid).getSearchCutoffMs() + + expect(response).toEqual({ searchCutoffMs: DEFAULT_SEARCHCUTOFFMS }) + }) + + test(`${permission} key: Update searchCutoffMs to valid value`, async () => { + const client = await getClient(permission) + const newSearchCutoffMs = { + searchCutoffMs: 100, + } + const task = await client + .index(index.uid) + .updateSearchCutoffMs(newSearchCutoffMs) + await client.waitForTask(task.taskUid) + + const response = await client.index(index.uid).getSearchCutoffMs() + + expect(response).toEqual(newSearchCutoffMs) + }) + + test(`${permission} key: Update searchCutoffMs to null`, async () => { + const client = await getClient(permission) + const newSearchCutoffMs = { + searchCutoffMs: null, + } + const task = await client + .index(index.uid) + .updateSearchCutoffMs(newSearchCutoffMs) + await client.index(index.uid).waitForTask(task.taskUid) + + const response = await client.index(index.uid).getSearchCutoffMs() + + expect(response).toEqual({ searchCutoffMs: DEFAULT_SEARCHCUTOFFMS }) + }) + + test(`${permission} key: Update searchCutoffMs with invalid value`, async () => { + const client = await getClient(permission) + const newSearchCutoffMs = { + searchCutoffMs: 'hello', // bad searchCutoffMs value + } as any + + await expect( + client.index(index.uid).updateSearchCutoffMs(newSearchCutoffMs) + ).rejects.toHaveProperty( + 'code', + ErrorStatusCode.INVALID_SETTINGS_SEARCH_CUTOFF_MS + ) + }) + + test(`${permission} key: Reset searchCutoffMs`, async () => { + const client = await getClient(permission) + const newSearchCutoffMs = { + searchCutoffMs: 100, + } + const updateTask = await client + .index(index.uid) + .updateSearchCutoffMs(newSearchCutoffMs) + await client.waitForTask(updateTask.taskUid) + const task = await client.index(index.uid).resetSearchCutoffMs() + await client.waitForTask(task.taskUid) + + const response = await client.index(index.uid).getSearchCutoffMs() + + expect(response).toEqual({ searchCutoffMs: DEFAULT_SEARCHCUTOFFMS }) + }) + } +) + +describe.each([{ permission: 'Search' }])( + 'Test on searchCutoffMs', + ({ permission }) => { + beforeEach(async () => { + const client = await getClient('Master') + const { taskUid } = await client.createIndex(index.uid) + await client.waitForTask(taskUid) + }) + + test(`${permission} key: try to get searchCutoffMs and be denied`, async () => { + const client = await getClient(permission) + await expect( + client.index(index.uid).getSearchCutoffMs() + ).rejects.toHaveProperty('code', ErrorStatusCode.INVALID_API_KEY) + }) + + test(`${permission} key: try to update searchCutoffMs and be denied`, async () => { + const client = await getClient(permission) + await expect( + client.index(index.uid).updateSearchCutoffMs({ searchCutoffMs: 100 }) + ).rejects.toHaveProperty('code', ErrorStatusCode.INVALID_API_KEY) + }) + + test(`${permission} key: try to reset searchCutoffMs and be denied`, async () => { + const client = await getClient(permission) + await expect( + client.index(index.uid).resetSearchCutoffMs() + ).rejects.toHaveProperty('code', ErrorStatusCode.INVALID_API_KEY) + }) + } +) + +describe.each([{ permission: 'No' }])( + 'Test on searchCutoffMs', + ({ permission }) => { + beforeAll(async () => { + const client = await getClient('Master') + const { taskUid } = await client.createIndex(index.uid) + await client.waitForTask(taskUid) + }) + + test(`${permission} key: try to get searchCutoffMs and be denied`, async () => { + const client = await getClient(permission) + await expect( + client.index(index.uid).getSearchCutoffMs() + ).rejects.toHaveProperty( + 'code', + ErrorStatusCode.MISSING_AUTHORIZATION_HEADER + ) + }) + + test(`${permission} key: try to update searchCutoffMs and be denied`, async () => { + const client = await getClient(permission) + await expect( + client.index(index.uid).updateSearchCutoffMs({ searchCutoffMs: 100 }) + ).rejects.toHaveProperty( + 'code', + ErrorStatusCode.MISSING_AUTHORIZATION_HEADER + ) + }) + + test(`${permission} key: try to reset searchCutoffMs and be denied`, async () => { + const client = await getClient(permission) + await expect( + client.index(index.uid).resetSearchCutoffMs() + ).rejects.toHaveProperty( + 'code', + ErrorStatusCode.MISSING_AUTHORIZATION_HEADER + ) + }) + } +) + +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 getSearchCutoffMs route`, async () => { + const route = `indexes/${index.uid}/settings/search-cutoff-ms` + const client = new MeiliSearch({ host }) + const strippedHost = trailing ? host.slice(0, -1) : host + await expect( + client.index(index.uid).getSearchCutoffMs() + ).rejects.toHaveProperty( + 'message', + `request to ${strippedHost}/${route} failed, reason: connect ECONNREFUSED ${BAD_HOST.replace( + 'http://', + '' + )}` + ) + }) + + test(`Test updateSearchCutoffMs route`, async () => { + const route = `indexes/${index.uid}/settings/search-cutoff-ms` + const client = new MeiliSearch({ host }) + const strippedHost = trailing ? host.slice(0, -1) : host + await expect( + client.index(index.uid).updateSearchCutoffMs({ searchCutoffMs: null }) + ).rejects.toHaveProperty( + 'message', + `request to ${strippedHost}/${route} failed, reason: connect ECONNREFUSED ${BAD_HOST.replace( + 'http://', + '' + )}` + ) + }) + + test(`Test resetSearchCutoffMs route`, async () => { + const route = `indexes/${index.uid}/settings/search-cutoff-ms` + const client = new MeiliSearch({ host }) + const strippedHost = trailing ? host.slice(0, -1) : host + await expect( + client.index(index.uid).resetSearchCutoffMs() + ).rejects.toHaveProperty( + 'message', + `request to ${strippedHost}/${route} failed, reason: connect ECONNREFUSED ${BAD_HOST.replace( + 'http://', + '' + )}` + ) + }) +})