Skip to content

Commit 065093a

Browse files
authored
feat(javascript): add accountCopyIndex helper (#4798)
1 parent 3a62efb commit 065093a

File tree

16 files changed

+579
-100
lines changed

16 files changed

+579
-100
lines changed

clients/algoliasearch-client-javascript/packages/client-common/src/transporter/errors.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,24 @@ export class AlgoliaError extends Error {
1212
}
1313
}
1414

15+
export class IndexNotFoundError extends AlgoliaError {
16+
constructor(indexName: string) {
17+
super(`${indexName} does not exist`, 'IndexNotFoundError');
18+
}
19+
}
20+
21+
export class IndicesInSameAppError extends AlgoliaError {
22+
constructor() {
23+
super('Indices are in the same application. Use operationIndex instead.', 'IndicesInSameAppError');
24+
}
25+
}
26+
27+
export class IndexAlreadyExistsError extends AlgoliaError {
28+
constructor(indexName: string) {
29+
super(`${indexName} index already exists.`, 'IndexAlreadyExistsError');
30+
}
31+
}
32+
1533
export class ErrorWithStackTrace extends AlgoliaError {
1634
stackTrace: StackFrame[];
1735

generators/src/main/java/com/algolia/codegen/cts/tests/SnippetsGenerator.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,13 @@ private Map<String, Snippet[]> loadSnippets(Map<String, CodegenOperation> operat
6767
if (ope == null || !(boolean) ope.vendorExtensions.getOrDefault("x-helper", false)) {
6868
continue;
6969
}
70+
71+
List<String> availableLanguages = (List<String>) ope.vendorExtensions.getOrDefault("x-available-languages", new ArrayList<>());
72+
73+
if (availableLanguages.size() > 0 && !availableLanguages.contains(language)) {
74+
continue;
75+
}
76+
7077
Snippet newSnippet = new Snippet(step.method, test.testName, step.parameters, step.requestOptions);
7178
Snippet[] existing = snippets.get(step.method);
7279
if (existing == null) {

generators/src/main/java/com/algolia/codegen/cts/tests/TestsClient.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,20 @@ public void run(Map<String, CodegenModel> models, Map<String, CodegenOperation>
114114

115115
throw new CTSException("Cannot find operation for method: " + step.method, test.testName);
116116
}
117+
118+
boolean isHelper = (boolean) ope.vendorExtensions.getOrDefault("x-helper", false);
119+
120+
if (isHelper) {
121+
List<String> availableLanguages = (List<String>) ope.vendorExtensions.getOrDefault(
122+
"x-available-languages",
123+
new ArrayList<>()
124+
);
125+
126+
if (availableLanguages.size() > 0 && !availableLanguages.contains(language)) {
127+
continue skipTest;
128+
}
129+
}
130+
117131
stepOut.put("stepTemplate", "tests/client/method.mustache");
118132
stepOut.put("isMethod", true); // TODO: remove once kotlin is converted
119133
stepOut.put("hasParams", ope.hasParams);
@@ -123,7 +137,6 @@ public void run(Map<String, CodegenModel> models, Map<String, CodegenOperation>
123137
stepOut.put("returnsBoolean", ope.returnType.equals("Boolean")); // ruby requires a ? for boolean functions.
124138
}
125139

126-
boolean isHelper = (boolean) ope.vendorExtensions.getOrDefault("x-helper", false);
127140
stepOut.put("isHelper", isHelper);
128141
// default to true because most api calls are asynchronous
129142
stepOut.put("isAsyncMethod", (boolean) ope.vendorExtensions.getOrDefault("x-asynchronous-helper", true));

playground/javascript/node/realtimePersonalization.ts

Lines changed: 0 additions & 27 deletions
This file was deleted.

scripts/cts/runCts.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { getTestOutputFolder } from '../config.ts';
55
import { createSpinner } from '../spinners.ts';
66
import type { Language } from '../types.ts';
77

8+
import { assertValidAccountCopyIndex } from './testServer/accountCopyIndex.ts';
89
import { printBenchmarkReport } from './testServer/benchmark.ts';
910
import { assertChunkWrapperValid } from './testServer/chunkWrapper.ts';
1011
import { startTestServer } from './testServer/index.ts';
@@ -147,10 +148,12 @@ export async function runCts(
147148
if (withClientServer && (clients.includes('search') || clients.includes('all') || process.platform === 'darwin')) {
148149
// the macos swift CI also runs the clients tests
149150
const skip = (lang: Language): number => (languages.includes(lang) ? 1 : 0);
151+
const only = skip;
150152

151153
assertValidTimeouts(languages.length);
152154
assertChunkWrapperValid(languages.length - skip('dart'));
153155
assertValidReplaceAllObjects(languages.length - skip('dart'));
156+
assertValidAccountCopyIndex(only('javascript'));
154157
assertValidReplaceAllObjectsFailed(languages.length - skip('dart'));
155158
assertValidReplaceAllObjectsScopes(languages.length - skip('dart'));
156159
assertValidWaitForApiKey(languages.length - skip('dart'));
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
import type { Server } from 'http';
2+
3+
import { expect } from 'chai';
4+
import type { Express } from 'express';
5+
import express from 'express';
6+
7+
import { setupServer } from './index.ts';
8+
9+
const aciState: Record<
10+
string,
11+
{
12+
setSettingsCount: number;
13+
getSettingsCount: number;
14+
saveRulesCount: number;
15+
browseRulesCount: number;
16+
saveSynonymsCount: number;
17+
browseSynonymsCount: number;
18+
saveObjectsCount: number;
19+
browseObjectsCount: number;
20+
waitTaskCount: number;
21+
successful: boolean;
22+
}
23+
> = {};
24+
25+
export function assertValidAccountCopyIndex(expectedCount: number): void {
26+
expect(Object.keys(aciState)).to.have.length(expectedCount);
27+
for (const lang in aciState) {
28+
expect(aciState[lang].waitTaskCount).to.equal(4);
29+
}
30+
}
31+
32+
function addRoutes(app: Express): void {
33+
app.use(express.urlencoded({ extended: true }));
34+
app.use(
35+
express.json({
36+
type: ['application/json', 'text/plain'], // the js client sends the body as text/plain
37+
}),
38+
);
39+
40+
app.get('/1/indexes/:indexName/settings', (req, res) => {
41+
const lang = req.params.indexName.match(/^cts_e2e_account_copy_index_(source|destination)_(.*)$/)?.[2] as string;
42+
43+
if (!aciState[lang] || aciState[lang].successful) {
44+
aciState[lang] = {
45+
setSettingsCount: 0,
46+
getSettingsCount: 0,
47+
saveRulesCount: 0,
48+
browseRulesCount: 0,
49+
saveSynonymsCount: 0,
50+
browseSynonymsCount: 0,
51+
saveObjectsCount: 0,
52+
browseObjectsCount: 0,
53+
waitTaskCount: 0,
54+
successful: false,
55+
};
56+
} else {
57+
expect(aciState).to.include.keys(lang);
58+
}
59+
60+
aciState[lang].getSettingsCount++;
61+
62+
if (req.params.indexName.includes('destination')) {
63+
res.status(404).json({ message: 'Index not found' });
64+
} else {
65+
res.status(200).json({
66+
minWordSizefor1Typo: 4,
67+
minWordSizefor2Typos: 8,
68+
hitsPerPage: 100,
69+
maxValuesPerFacet: 100,
70+
paginationLimitedTo: 10,
71+
exactOnSingleWordQuery: 'attribute',
72+
ranking: ['typo', 'geo', 'words', 'filters', 'proximity', 'attribute', 'exact', 'custom'],
73+
separatorsToIndex: '',
74+
removeWordsIfNoResults: 'none',
75+
queryType: 'prefixLast',
76+
highlightPreTag: '<em>',
77+
highlightPostTag: '</em>',
78+
alternativesAsExact: ['ignorePlurals', 'singleWordSynonym'],
79+
});
80+
}
81+
});
82+
83+
app.put('/1/indexes/:indexName/settings', (req, res) => {
84+
const lang = req.params.indexName.match(/^cts_e2e_account_copy_index_destination_(.*)$/)?.[1] as string;
85+
expect(aciState).to.include.keys(lang);
86+
87+
aciState[lang].setSettingsCount++;
88+
89+
res.json({ taskID: 123 + aciState[lang].setSettingsCount, updatedAt: '2021-01-01T00:00:00.000Z' });
90+
});
91+
92+
app.post('/1/indexes/:indexName/rules/search', (req, res) => {
93+
const lang = req.params.indexName.match(/^cts_e2e_account_copy_index_source_(.*)$/)?.[1] as string;
94+
expect(aciState).to.include.keys(lang);
95+
96+
aciState[lang].browseRulesCount++;
97+
98+
res.json({
99+
hits: [
100+
{
101+
conditions: [
102+
{
103+
alternatives: true,
104+
anchoring: 'contains',
105+
pattern: 'zorro',
106+
},
107+
],
108+
consequence: {
109+
params: {
110+
ignorePlurals: 'true',
111+
},
112+
filterPromotes: true,
113+
promote: [
114+
{
115+
objectIDs: ['\u00C6on Flux'],
116+
position: 0,
117+
},
118+
],
119+
},
120+
description: 'test_rule',
121+
enabled: true,
122+
objectID: 'qr-1725004648916',
123+
},
124+
],
125+
nbHits: 1,
126+
nbPages: 1,
127+
page: 0,
128+
});
129+
});
130+
131+
app.post('/1/indexes/:indexName/rules/batch', (req, res) => {
132+
const lang = req.params.indexName.match(/^cts_e2e_account_copy_index_destination_(.*)$/)?.[1] as string;
133+
expect(aciState).to.include.keys(lang);
134+
135+
aciState[lang].saveRulesCount++;
136+
137+
res.json({ taskID: 456 + aciState[lang].saveRulesCount, updatedAt: '2021-01-01T00:00:00.000Z' });
138+
});
139+
140+
app.post('/1/indexes/:indexName/synonyms/search', (req, res) => {
141+
const lang = req.params.indexName.match(/^cts_e2e_account_copy_index_source_(.*)$/)?.[1] as string;
142+
expect(aciState).to.include.keys(lang);
143+
144+
aciState[lang].browseSynonymsCount++;
145+
146+
res.json({
147+
hits: [
148+
{
149+
objectID: 'foo',
150+
},
151+
],
152+
nbHits: 1,
153+
nbPages: 1,
154+
page: 0,
155+
});
156+
});
157+
158+
app.post('/1/indexes/:indexName/synonyms/batch', (req, res) => {
159+
const lang = req.params.indexName.match(/^cts_e2e_account_copy_index_destination_(.*)$/)?.[1] as string;
160+
expect(aciState).to.include.keys(lang);
161+
162+
aciState[lang].saveSynonymsCount++;
163+
164+
res.json({ taskID: 789 + aciState[lang].saveSynonymsCount, updatedAt: '2021-01-01T00:00:00.000Z' });
165+
});
166+
167+
app.post('/1/indexes/:indexName/browse', (req, res) => {
168+
const lang = req.params.indexName.match(/^cts_e2e_account_copy_index_source_(.*)$/)?.[1] as string;
169+
expect(aciState).to.include.keys(lang);
170+
171+
aciState[lang].browseObjectsCount++;
172+
173+
res.json({
174+
page: 0,
175+
nbHits: 1,
176+
nbPages: 1,
177+
hitsPerPage: 1000,
178+
query: '',
179+
params: '',
180+
hits: [{ objectID: 'bar' }],
181+
});
182+
});
183+
184+
app.post('/1/indexes/:indexName/batch', (req, res) => {
185+
const lang = req.params.indexName.match(/^cts_e2e_account_copy_index_destination_(.*)$/)?.[1] as string;
186+
expect(aciState).to.include.keys(lang);
187+
188+
aciState[lang].saveObjectsCount++;
189+
190+
res.json({ taskID: 10 + aciState[lang].saveObjectsCount, updatedAt: '2021-01-01T00:00:00.000Z' });
191+
});
192+
193+
app.get('/1/indexes/:indexName/task/:taskID', (req, res) => {
194+
const lang = req.params.indexName.match(/^cts_e2e_account_copy_index_destination_(.*)$/)?.[1] as string;
195+
expect(aciState).to.include.keys(lang);
196+
197+
aciState[lang].waitTaskCount++;
198+
199+
res.json({ status: 'published', updatedAt: '2021-01-01T00:00:00.000Z' });
200+
});
201+
}
202+
203+
export function accountCopyIndexServer(): Promise<Server> {
204+
// this server is used to simulate the responses for the accountCopyIndex method,
205+
// and uses a state machine to determine if the logic is correct.
206+
return setupServer('accountCopyIndex', 6687, addRoutes);
207+
}

scripts/cts/testServer/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { createSpinner } from '../../spinners.ts';
77
import type { CTSType } from '../runCts.ts';
88

99
import { expect } from 'chai';
10+
import { accountCopyIndexServer } from './accountCopyIndex.ts';
1011
import { algoliaMockServer } from './algoliaMock.ts';
1112
import { apiKeyServer } from './apiKey.ts';
1213
import { benchmarkServer } from './benchmark.ts';
@@ -26,6 +27,7 @@ export async function startTestServer(suites: Record<CTSType, boolean>): Promise
2627
timeoutServer(),
2728
gzipServer(),
2829
timeoutServerBis(),
30+
accountCopyIndexServer(),
2931
replaceAllObjectsServer(),
3032
replaceAllObjectsServerFailed(),
3133
replaceAllObjectsScopesServer(),
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
description: Destination index already exists.
2+
content:
3+
application/json:
4+
schema:
5+
$ref: '../schemas/ErrorBase.yml'
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
description: Indices are in the same application. Use operationIndex instead.
2+
content:
3+
application/json:
4+
schema:
5+
$ref: '../schemas/ErrorBase.yml'

0 commit comments

Comments
 (0)