Skip to content

Commit d987c63

Browse files
committed
Add settings/localized-attributes methods and tests, adjust tests, add locale types
1 parent c3b713e commit d987c63

File tree

5 files changed

+342
-21
lines changed

5 files changed

+342
-21
lines changed

src/indexes.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import {
5454
Embedders,
5555
SearchCutoffMs,
5656
SearchSimilarDocumentsParams,
57+
LocalizedAttributes,
5758
} from './types';
5859
import { removeUndefinedFromObject } from './utils';
5960
import { HttpRequests } from './http-requests';
@@ -1393,6 +1394,47 @@ class Index<T extends Record<string, any> = Record<string, any>> {
13931394

13941395
return new EnqueuedTask(task);
13951396
}
1397+
1398+
///
1399+
/// LOCALIZED ATTRIBUTES SETTINGS
1400+
///
1401+
1402+
/**
1403+
* Get the localized attributes settings.
1404+
*
1405+
* @returns Promise containing object of localized attributes settings
1406+
*/
1407+
async getLocalizedAttributes(): Promise<LocalizedAttributes> {
1408+
const url = `indexes/${this.uid}/settings/localized-attributes`;
1409+
return await this.httpRequest.get<LocalizedAttributes>(url);
1410+
}
1411+
1412+
/**
1413+
* Update the localized attributes settings.
1414+
*
1415+
* @param localizedAttributes - Localized attributes object
1416+
* @returns Promise containing an EnqueuedTask
1417+
*/
1418+
async updateLocalizedAttributes(
1419+
localizedAttributes: LocalizedAttributes,
1420+
): Promise<EnqueuedTask> {
1421+
const url = `indexes/${this.uid}/settings/localized-attributes`;
1422+
const task = await this.httpRequest.put(url, localizedAttributes);
1423+
1424+
return new EnqueuedTask(task);
1425+
}
1426+
1427+
/**
1428+
* Reset the localized attributes settings.
1429+
*
1430+
* @returns Promise containing an EnqueuedTask
1431+
*/
1432+
async resetLocalizedAttributes(): Promise<EnqueuedTask> {
1433+
const url = `indexes/${this.uid}/settings/localized-attributes`;
1434+
const task = await this.httpRequest.delete(url);
1435+
1436+
return new EnqueuedTask(task);
1437+
}
13961438
}
13971439

13981440
export { Index };

src/types/types.ts

Lines changed: 77 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,77 @@ export type HybridSearch = {
108108
semanticRatio?: number;
109109
};
110110

111+
export type Locale =
112+
| 'epo'
113+
| 'eng'
114+
| 'rus'
115+
| 'cmn'
116+
| 'spa'
117+
| 'por'
118+
| 'ita'
119+
| 'ben'
120+
| 'fra'
121+
| 'deu'
122+
| 'ukr'
123+
| 'kat'
124+
| 'ara'
125+
| 'hin'
126+
| 'jpn'
127+
| 'heb'
128+
| 'yid'
129+
| 'pol'
130+
| 'amh'
131+
| 'jav'
132+
| 'kor'
133+
| 'nob'
134+
| 'dan'
135+
| 'swe'
136+
| 'fin'
137+
| 'tur'
138+
| 'nld'
139+
| 'hun'
140+
| 'ces'
141+
| 'ell'
142+
| 'bul'
143+
| 'bel'
144+
| 'mar'
145+
| 'kan'
146+
| 'ron'
147+
| 'slv'
148+
| 'hrv'
149+
| 'srp'
150+
| 'mkd'
151+
| 'lit'
152+
| 'lav'
153+
| 'est'
154+
| 'tam'
155+
| 'vie'
156+
| 'urd'
157+
| 'tha'
158+
| 'guj'
159+
| 'uzb'
160+
| 'pan'
161+
| 'aze'
162+
| 'ind'
163+
| 'tel'
164+
| 'pes'
165+
| 'mal'
166+
| 'ori'
167+
| 'mya'
168+
| 'nep'
169+
| 'sin'
170+
| 'khm'
171+
| 'tuk'
172+
| 'aka'
173+
| 'zul'
174+
| 'sna'
175+
| 'afr'
176+
| 'lat'
177+
| 'slk'
178+
| 'cat'
179+
| 'tgl'
180+
| 'hye';
181+
111182
export type SearchParams = Query &
112183
Pagination &
113184
Highlight &
@@ -130,9 +201,7 @@ export type SearchParams = Query &
130201
hybrid?: HybridSearch;
131202
distinct?: string;
132203
retrieveVectors?: boolean;
133-
// @TODO: Either explicitly type this (that implies keeping it up to date),
134-
// or link to the docs where the available locales are listed
135-
locales?: string[];
204+
locales?: Locale[];
136205
};
137206

138207
// Search parameters for searches made with the GET method
@@ -433,8 +502,7 @@ export type SearchCutoffMs = number | null;
433502

434503
export type LocalizedAttribute = {
435504
attributePatterns: string[];
436-
// @TODO: Type or link to docs
437-
locales: string[];
505+
locales: Locale[];
438506
};
439507

440508
export type LocalizedAttributes = LocalizedAttribute[] | null;
@@ -1004,6 +1072,10 @@ export const ErrorStatusCode = {
10041072
/** @see https://www.meilisearch.com/docs/reference/errors/error_codes#invalid_settings_search_cutoff_ms */
10051073
INVALID_SETTINGS_SEARCH_CUTOFF_MS: 'invalid_settings_search_cutoff_ms',
10061074

1075+
/** @see https://www.meilisearch.com/docs/reference/errors/error_codes#invalid_settings_search_cutoff_ms */
1076+
INVALID_SETTINGS_LOCALIZED_ATTRIBUTES:
1077+
'invalid_settings_localized_attributes',
1078+
10071079
/** @see https://www.meilisearch.com/docs/reference/errors/error_codes#invalid_task_before_enqueued_at */
10081080
INVALID_TASK_BEFORE_ENQUEUED_AT: 'invalid_task_before_enqueued_at',
10091081

tests/localized_attributes.test.ts

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
import { ErrorStatusCode, type LocalizedAttributes } from '../src/types';
2+
import {
3+
clearAllIndexes,
4+
config,
5+
BAD_HOST,
6+
MeiliSearch,
7+
getClient,
8+
dataset,
9+
} from './utils/meilisearch-test-utils';
10+
11+
const index = {
12+
uid: 'movies_test',
13+
};
14+
15+
const DEFAULT_LOCALIZED_ATTRIBUTES = null;
16+
17+
jest.setTimeout(100 * 1000);
18+
19+
afterAll(() => {
20+
return clearAllIndexes(config);
21+
});
22+
23+
describe.each([{ permission: 'Master' }, { permission: 'Admin' }])(
24+
'Test on localizedAttributes',
25+
({ permission }) => {
26+
beforeEach(async () => {
27+
await clearAllIndexes(config);
28+
const client = await getClient('Master');
29+
const { taskUid } = await client.index(index.uid).addDocuments(dataset);
30+
await client.waitForTask(taskUid);
31+
});
32+
33+
test(`${permission} key: Get default localizedAttributes settings`, async () => {
34+
const client = await getClient(permission);
35+
const response = await client.index(index.uid).getLocalizedAttributes();
36+
37+
expect(response).toEqual(DEFAULT_LOCALIZED_ATTRIBUTES);
38+
});
39+
40+
test(`${permission} key: Update localizedAttributes to valid value`, async () => {
41+
const client = await getClient(permission);
42+
const newLocalizedAttributes: LocalizedAttributes = [
43+
{ attributePatterns: ['title'], locales: ['eng'] },
44+
];
45+
const task = await client
46+
.index(index.uid)
47+
.updateLocalizedAttributes(newLocalizedAttributes);
48+
await client.waitForTask(task.taskUid);
49+
50+
const response = await client.index(index.uid).getLocalizedAttributes();
51+
52+
expect(response).toEqual(newLocalizedAttributes);
53+
});
54+
55+
test(`${permission} key: Update localizedAttributes to null`, async () => {
56+
const client = await getClient(permission);
57+
const newLocalizedAttributes = null;
58+
const task = await client
59+
.index(index.uid)
60+
.updateLocalizedAttributes(newLocalizedAttributes);
61+
await client.index(index.uid).waitForTask(task.taskUid);
62+
63+
const response = await client.index(index.uid).getLocalizedAttributes();
64+
65+
expect(response).toEqual(DEFAULT_LOCALIZED_ATTRIBUTES);
66+
});
67+
68+
test(`${permission} key: Update localizedAttributes with invalid value`, async () => {
69+
const client = await getClient(permission);
70+
const newLocalizedAttributes = 'hello' as any; // bad localizedAttributes value
71+
72+
await expect(
73+
client
74+
.index(index.uid)
75+
.updateLocalizedAttributes(newLocalizedAttributes),
76+
).rejects.toHaveProperty(
77+
'cause.code',
78+
ErrorStatusCode.INVALID_SETTINGS_LOCALIZED_ATTRIBUTES,
79+
);
80+
});
81+
82+
test(`${permission} key: Reset localizedAttributes`, async () => {
83+
const client = await getClient(permission);
84+
const newLocalizedAttributes: LocalizedAttributes = [];
85+
const updateTask = await client
86+
.index(index.uid)
87+
.updateLocalizedAttributes(newLocalizedAttributes);
88+
await client.waitForTask(updateTask.taskUid);
89+
const task = await client.index(index.uid).resetLocalizedAttributes();
90+
await client.waitForTask(task.taskUid);
91+
92+
const response = await client.index(index.uid).getLocalizedAttributes();
93+
94+
expect(response).toEqual(DEFAULT_LOCALIZED_ATTRIBUTES);
95+
});
96+
},
97+
);
98+
99+
describe.each([{ permission: 'Search' }])(
100+
'Test on localizedAttributes',
101+
({ permission }) => {
102+
beforeEach(async () => {
103+
const client = await getClient('Master');
104+
const { taskUid } = await client.createIndex(index.uid);
105+
await client.waitForTask(taskUid);
106+
});
107+
108+
test(`${permission} key: try to get localizedAttributes and be denied`, async () => {
109+
const client = await getClient(permission);
110+
await expect(
111+
client.index(index.uid).getLocalizedAttributes(),
112+
).rejects.toHaveProperty('cause.code', ErrorStatusCode.INVALID_API_KEY);
113+
});
114+
115+
test(`${permission} key: try to update localizedAttributes and be denied`, async () => {
116+
const client = await getClient(permission);
117+
await expect(
118+
client.index(index.uid).updateLocalizedAttributes([]),
119+
).rejects.toHaveProperty('cause.code', ErrorStatusCode.INVALID_API_KEY);
120+
});
121+
122+
test(`${permission} key: try to reset localizedAttributes and be denied`, async () => {
123+
const client = await getClient(permission);
124+
await expect(
125+
client.index(index.uid).resetLocalizedAttributes(),
126+
).rejects.toHaveProperty('cause.code', ErrorStatusCode.INVALID_API_KEY);
127+
});
128+
},
129+
);
130+
131+
describe.each([{ permission: 'No' }])(
132+
'Test on localizedAttributes',
133+
({ permission }) => {
134+
beforeAll(async () => {
135+
const client = await getClient('Master');
136+
const { taskUid } = await client.createIndex(index.uid);
137+
await client.waitForTask(taskUid);
138+
});
139+
140+
test(`${permission} key: try to get localizedAttributes and be denied`, async () => {
141+
const client = await getClient(permission);
142+
await expect(
143+
client.index(index.uid).getLocalizedAttributes(),
144+
).rejects.toHaveProperty(
145+
'cause.code',
146+
ErrorStatusCode.MISSING_AUTHORIZATION_HEADER,
147+
);
148+
});
149+
150+
test(`${permission} key: try to update localizedAttributes and be denied`, async () => {
151+
const client = await getClient(permission);
152+
await expect(
153+
client.index(index.uid).updateLocalizedAttributes([]),
154+
).rejects.toHaveProperty(
155+
'cause.code',
156+
ErrorStatusCode.MISSING_AUTHORIZATION_HEADER,
157+
);
158+
});
159+
160+
test(`${permission} key: try to reset localizedAttributes and be denied`, async () => {
161+
const client = await getClient(permission);
162+
await expect(
163+
client.index(index.uid).resetLocalizedAttributes(),
164+
).rejects.toHaveProperty(
165+
'cause.code',
166+
ErrorStatusCode.MISSING_AUTHORIZATION_HEADER,
167+
);
168+
});
169+
},
170+
);
171+
172+
describe.each([
173+
{ host: BAD_HOST, trailing: false },
174+
{ host: `${BAD_HOST}/api`, trailing: false },
175+
{ host: `${BAD_HOST}/trailing/`, trailing: true },
176+
])('Tests on url construction', ({ host, trailing }) => {
177+
test(`Test getLocalizedAttributes route`, async () => {
178+
const route = `indexes/${index.uid}/settings/localized-attributes`;
179+
const client = new MeiliSearch({ host });
180+
const strippedHost = trailing ? host.slice(0, -1) : host;
181+
await expect(
182+
client.index(index.uid).getLocalizedAttributes(),
183+
).rejects.toHaveProperty(
184+
'message',
185+
`Request to ${strippedHost}/${route} has failed`,
186+
);
187+
});
188+
189+
test(`Test updateLocalizedAttributes route`, async () => {
190+
const route = `indexes/${index.uid}/settings/localized-attributes`;
191+
const client = new MeiliSearch({ host });
192+
const strippedHost = trailing ? host.slice(0, -1) : host;
193+
await expect(
194+
client.index(index.uid).updateLocalizedAttributes(null),
195+
).rejects.toHaveProperty(
196+
'message',
197+
`Request to ${strippedHost}/${route} has failed`,
198+
);
199+
});
200+
201+
test(`Test resetLocalizedAttributes route`, async () => {
202+
const route = `indexes/${index.uid}/settings/localized-attributes`;
203+
const client = new MeiliSearch({ host });
204+
const strippedHost = trailing ? host.slice(0, -1) : host;
205+
await expect(
206+
client.index(index.uid).resetLocalizedAttributes(),
207+
).rejects.toHaveProperty(
208+
'message',
209+
`Request to ${strippedHost}/${route} has failed`,
210+
);
211+
});
212+
});

0 commit comments

Comments
 (0)