From 2f9995f9997c39057067529f930dc165e5c79c6e Mon Sep 17 00:00:00 2001 From: Pierre Millot Date: Mon, 23 Sep 2024 17:00:18 +0200 Subject: [PATCH 01/15] feat(clients): cleanup after replaceAllObjects failure --- scripts/cts/runCts.ts | 2 + scripts/cts/testServer/index.ts | 2 + .../cts/testServer/replaceAllObjectsFailed.ts | 58 +++++++++++++++++++ templates/Bug_report.yml | 2 +- templates/go/search_helpers.mustache | 14 ++++- .../CTS/client/search/replaceAllObjects.json | 49 ++++++++++++++++ 6 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 scripts/cts/testServer/replaceAllObjectsFailed.ts diff --git a/scripts/cts/runCts.ts b/scripts/cts/runCts.ts index 9caa155ae4..101a9ebf44 100644 --- a/scripts/cts/runCts.ts +++ b/scripts/cts/runCts.ts @@ -9,6 +9,7 @@ import { printBenchmarkReport } from './testServer/benchmark.js'; import { assertChunkWrapperValid } from './testServer/chunkWrapper.js'; import { startTestServer } from './testServer/index.js'; import { assertValidReplaceAllObjects } from './testServer/replaceAllObjects.js'; +import { assertValidReplaceAllObjectsFailed } from './testServer/replaceAllObjectsFailed.js'; import { assertValidTimeouts } from './testServer/timeout.js'; import { assertValidWaitForApiKey } from './testServer/waitFor.js'; @@ -152,6 +153,7 @@ export async function runCts( assertValidTimeouts(languages.length); assertChunkWrapperValid(languages.length - skip('dart') - skip('scala')); assertValidReplaceAllObjects(languages.length - skip('dart') - skip('scala')); + assertValidReplaceAllObjectsFailed(languages.length - skip('dart') - skip('scala')); assertValidWaitForApiKey(languages.length - skip('dart') - skip('scala')); } if (withBenchmarkServer) { diff --git a/scripts/cts/testServer/index.ts b/scripts/cts/testServer/index.ts index 5bc595f9f2..5007a0ab03 100644 --- a/scripts/cts/testServer/index.ts +++ b/scripts/cts/testServer/index.ts @@ -11,6 +11,7 @@ import { benchmarkServer } from './benchmark.js'; import { chunkWrapperServer } from './chunkWrapper.js'; import { gzipServer } from './gzip.js'; import { replaceAllObjectsServer } from './replaceAllObjects.js'; +import { replaceAllObjectsServerFailed } from './replaceAllObjectsFailed.js'; import { timeoutServer } from './timeout.js'; import { timeoutServerBis } from './timeoutBis.js'; import { waitForApiKeyServer } from './waitFor.js'; @@ -23,6 +24,7 @@ export async function startTestServer(suites: Record): Promise gzipServer(), timeoutServerBis(), replaceAllObjectsServer(), + replaceAllObjectsServerFailed(), chunkWrapperServer(), waitForApiKeyServer(), apiKeyServer(), diff --git a/scripts/cts/testServer/replaceAllObjectsFailed.ts b/scripts/cts/testServer/replaceAllObjectsFailed.ts new file mode 100644 index 0000000000..0c4f95cf85 --- /dev/null +++ b/scripts/cts/testServer/replaceAllObjectsFailed.ts @@ -0,0 +1,58 @@ +import type { Server } from 'http'; + +import { expect } from 'chai'; +import type { Express } from 'express'; +import express from 'express'; + +import { setupServer } from '.'; + +const raoState: Record< + string, + { + tmpIndexName: string; + successful: boolean; + } +> = {}; + +export function assertValidReplaceAllObjectsFailed(expectedCount: number): void { + const count = Object.values(raoState).filter((s) => s.successful).length; + if (count !== expectedCount) { + throw new Error(`Expected ${expectedCount} call to replaceAllObjectsFailed, got ${count} instead.`); + } +} + +function addRoutes(app: Express): void { + app.use(express.urlencoded({ extended: true })); + app.use( + express.json({ + type: ['application/json', 'text/plain'], // the js client sends the body as text/plain + }), + ); + + app.post('/1/indexes/:indexName/operation', (req, res) => { + const lang = req.params.indexName.match(/^cts_e2e_replace_all_objects_too_big_(.*)$/)?.[1] as string; + raoState[lang] = { + tmpIndexName: req.body.destination, + successful: false, + }; + + res.json({ taskID: 123, updatedAt: '2021-01-01T00:00:00.000Z' }); + }); + + app.post('/1/indexes/:indexName/batch', (_req, res) => { + res.status(400).json({ message: 'Record is too big', status: 400 }); + }); + + app.delete('/1/indexes/:indexName', (req, res) => { + const lang = req.params.indexName.match(/^cts_e2e_replace_all_objects_too_big_(.*)_tmp/)?.[1] as string; + expect(raoState[lang].tmpIndexName).to.equal(req.params.indexName); + raoState[lang].successful = true; + + res.json({ taskID: 456, deletedAt: '2021-01-01T00:00:00.000Z' }); + }); +} + +export function replaceAllObjectsServerFailed(): Promise { + // this server is used to simulate the responses for the replaceAllObjects method, with cleanup + return setupServer('replaceAllObjectsFailed', 6684, addRoutes); +} diff --git a/templates/Bug_report.yml b/templates/Bug_report.yml index ecf2dfdbd5..d01e1b3905 100644 --- a/templates/Bug_report.yml +++ b/templates/Bug_report.yml @@ -27,7 +27,7 @@ body: id: client attributes: label: Client - description: Which API are you targetting? + description: Which API are you targeting? options: - All - AB testing diff --git a/templates/go/search_helpers.mustache b/templates/go/search_helpers.mustache index 0845019265..4b2fac9fae 100644 --- a/templates/go/search_helpers.mustache +++ b/templates/go/search_helpers.mustache @@ -653,35 +653,47 @@ func (c *APIClient) ReplaceAllObjects(indexName string, objects []map[string]any return nil, err } - opts = append(opts, WithWaitForTasks(true)) + opts = append(opts, WithWaitForTasks(true)) batchResp, err := c.ChunkedBatch(tmpIndexName, objects, ACTION_ADD_OBJECT, opts...) if err != nil { + _, _ = c.DeleteIndex(c.NewApiDeleteIndexRequest(tmpIndexName)) + return nil, err } _, err = c.WaitForTask(tmpIndexName, copyResp.TaskID, toIterableOptions(opts)...) if err != nil { + _, _ = c.DeleteIndex(c.NewApiDeleteIndexRequest(tmpIndexName)) + return nil, err } copyResp, err = c.OperationIndex(c.NewApiOperationIndexRequest(indexName, NewOperationIndexParams(OPERATION_TYPE_COPY, tmpIndexName, WithOperationIndexParamsScope([]ScopeType{SCOPE_TYPE_SETTINGS, SCOPE_TYPE_RULES, SCOPE_TYPE_SYNONYMS}))), toRequestOptions(opts)...) if err != nil { + _, _ = c.DeleteIndex(c.NewApiDeleteIndexRequest(tmpIndexName)) + return nil, err } _, err = c.WaitForTask(tmpIndexName, copyResp.TaskID, toIterableOptions(opts)...) if err != nil { + _, _ = c.DeleteIndex(c.NewApiDeleteIndexRequest(tmpIndexName)) + return nil, err } moveResp, err := c.OperationIndex(c.NewApiOperationIndexRequest(tmpIndexName, NewOperationIndexParams(OPERATION_TYPE_MOVE, indexName)), toRequestOptions(opts)...) if err != nil { + _, _ = c.DeleteIndex(c.NewApiDeleteIndexRequest(tmpIndexName)) + return nil, err } _, err = c.WaitForTask(tmpIndexName, moveResp.TaskID, toIterableOptions(opts)...) if err != nil { + _, _ = c.DeleteIndex(c.NewApiDeleteIndexRequest(tmpIndexName)) + return nil, err } diff --git a/tests/CTS/client/search/replaceAllObjects.json b/tests/CTS/client/search/replaceAllObjects.json index 372d75953f..d25a4393d9 100644 --- a/tests/CTS/client/search/replaceAllObjects.json +++ b/tests/CTS/client/search/replaceAllObjects.json @@ -111,5 +111,54 @@ } } ] + }, + { + "testName": "replaceAllObjects should cleanup on failure", + "autoCreateClient": false, + "steps": [ + { + "type": "createClient", + "parameters": { + "appId": "test-app-id", + "apiKey": "test-api-key", + "customHosts": [ + { + "host": "${{localhost}}", + "port": 6684 + } + ] + } + }, + { + "type": "method", + "method": "replaceAllObjects", + "parameters": { + "indexName": "cts_e2e_replace_all_objects_too_big_${{language}}", + "objects": [ + { + "objectID": "fine", + "body": "small obj" + }, + { + "objectID": "toolarge", + "body": "something bigger than 10KB" + } + ] + }, + "expected": { + "error": { + "csharp": "{\\\"message\\\":\\\"Record is too big\\\",\\\"status\\\":400}", + "go": "API error [400] {\\\"message\\\":\\\"Record is too big\\\",\\\"status\\\":400}", + "java": "Status Code: 400 - {\\\"message\\\":\\\"Record is too big\\\",\\\"status\\\":400}", + "javascript": "Record is too big", + "kotlin": "Client request(POST http://${{localhost}}:6684/1/indexes/cts_e2e_replace_all_objects_too_big_${{language}}/batch) invalid: 400 Bad Request. Text: \\\"{\\\"message\\\":\\\"Record is too big\\\",\\\"status\\\":400}\\\"", + "php": "Record is too big", + "python": "Record is too big", + "ruby": "Record is too big", + "swift": "HTTP error: Status code: 400 Message: Record is too big" + } + } + } + ] } ] From b9dbffd7df8b655c85d4d51480665877406af6e4 Mon Sep 17 00:00:00 2001 From: Thomas Raffray Date: Thu, 24 Oct 2024 17:36:16 +0200 Subject: [PATCH 02/15] chore(swift): add cleanup method to replace all objects helper --- .../Search/Extra/SearchClientExtension.swift | 90 ++++++++++--------- .../cts/testServer/replaceAllObjectsFailed.ts | 2 +- 2 files changed, 49 insertions(+), 43 deletions(-) diff --git a/clients/algoliasearch-client-swift/Sources/Search/Extra/SearchClientExtension.swift b/clients/algoliasearch-client-swift/Sources/Search/Extra/SearchClientExtension.swift index fefa966a70..6a6282247c 100644 --- a/clients/algoliasearch-client-swift/Sources/Search/Extra/SearchClientExtension.swift +++ b/clients/algoliasearch-client-swift/Sources/Search/Extra/SearchClientExtension.swift @@ -549,51 +549,57 @@ public extension SearchClient { ) async throws -> ReplaceAllObjectsResponse { let tmpIndexName = "\(indexName)_tmp_\(Int.random(in: 1_000_000 ..< 10_000_000))" - var copyOperationResponse = try await operationIndex( - indexName: indexName, - operationIndexParams: OperationIndexParams( - operation: .copy, - destination: tmpIndexName, - scope: [.settings, .rules, .synonyms] - ), - requestOptions: requestOptions - ) + do { + var copyOperationResponse = try await operationIndex( + indexName: indexName, + operationIndexParams: OperationIndexParams( + operation: .copy, + destination: tmpIndexName, + scope: [.settings, .rules, .synonyms] + ), + requestOptions: requestOptions + ) - let batchResponses = try await self.chunkedBatch( - indexName: tmpIndexName, - objects: objects, - waitForTasks: true, - batchSize: batchSize, - requestOptions: requestOptions - ) - try await self.waitForTask(indexName: tmpIndexName, taskID: copyOperationResponse.taskID) + let batchResponses = try await self.chunkedBatch( + indexName: tmpIndexName, + objects: objects, + waitForTasks: true, + batchSize: batchSize, + requestOptions: requestOptions + ) + try await self.waitForTask(indexName: tmpIndexName, taskID: copyOperationResponse.taskID) - copyOperationResponse = try await operationIndex( - indexName: indexName, - operationIndexParams: OperationIndexParams( - operation: .copy, - destination: tmpIndexName, - scope: [.settings, .rules, .synonyms] - ), - requestOptions: requestOptions - ) - try await self.waitForTask(indexName: tmpIndexName, taskID: copyOperationResponse.taskID) - - let moveOperationResponse = try await self.operationIndex( - indexName: tmpIndexName, - operationIndexParams: OperationIndexParams( - operation: .move, - destination: indexName - ), - requestOptions: requestOptions - ) - try await self.waitForTask(indexName: tmpIndexName, taskID: moveOperationResponse.taskID) + copyOperationResponse = try await operationIndex( + indexName: indexName, + operationIndexParams: OperationIndexParams( + operation: .copy, + destination: tmpIndexName, + scope: [.settings, .rules, .synonyms] + ), + requestOptions: requestOptions + ) + try await self.waitForTask(indexName: tmpIndexName, taskID: copyOperationResponse.taskID) - return ReplaceAllObjectsResponse( - copyOperationResponse: copyOperationResponse, - batchResponses: batchResponses, - moveOperationResponse: moveOperationResponse - ) + let moveOperationResponse = try await self.operationIndex( + indexName: tmpIndexName, + operationIndexParams: OperationIndexParams( + operation: .move, + destination: indexName + ), + requestOptions: requestOptions + ) + try await self.waitForTask(indexName: tmpIndexName, taskID: moveOperationResponse.taskID) + + return ReplaceAllObjectsResponse( + copyOperationResponse: copyOperationResponse, + batchResponses: batchResponses, + moveOperationResponse: moveOperationResponse + ) + } catch { + _ = try? await deleteIndex(indexName: tmpIndexName) + + throw error + } } /// Generate a secured API key diff --git a/scripts/cts/testServer/replaceAllObjectsFailed.ts b/scripts/cts/testServer/replaceAllObjectsFailed.ts index 0c4f95cf85..589ecc3da7 100644 --- a/scripts/cts/testServer/replaceAllObjectsFailed.ts +++ b/scripts/cts/testServer/replaceAllObjectsFailed.ts @@ -4,7 +4,7 @@ import { expect } from 'chai'; import type { Express } from 'express'; import express from 'express'; -import { setupServer } from '.'; +import { setupServer } from './index.js'; const raoState: Record< string, From 3f911489bd9b31170d0414a36494c9eb8e8d4979 Mon Sep 17 00:00:00 2001 From: Thomas Raffray Date: Thu, 31 Oct 2024 07:45:20 +0100 Subject: [PATCH 03/15] chore(java): cleanup --- templates/java/api_helpers.mustache | 78 ++++++++++++++++------------- 1 file changed, 42 insertions(+), 36 deletions(-) diff --git a/templates/java/api_helpers.mustache b/templates/java/api_helpers.mustache index 80213a82fb..d5d56836bc 100644 --- a/templates/java/api_helpers.mustache +++ b/templates/java/api_helpers.mustache @@ -848,47 +848,53 @@ public ReplaceAllObjectsResponse replaceAllObjects( Random rnd = new Random(); String tmpIndexName = indexName + "_tmp_" + rnd.nextInt(100); - // Copy settings, synonyms and rules - UpdatedAtResponse copyOperationResponse = operationIndex( - indexName, - new OperationIndexParams() - .setOperation(OperationType.COPY) - .setDestination(tmpIndexName) - .addScope(ScopeType.SETTINGS) - .addScope(ScopeType.RULES) - .addScope(ScopeType.SYNONYMS), - requestOptions - ); + try { + // Copy settings, synonyms and rules + UpdatedAtResponse copyOperationResponse = operationIndex( + indexName, + new OperationIndexParams() + .setOperation(OperationType.COPY) + .setDestination(tmpIndexName) + .addScope(ScopeType.SETTINGS) + .addScope(ScopeType.RULES) + .addScope(ScopeType.SYNONYMS), + requestOptions + ); - // Save new objects - List batchResponses = chunkedBatch(tmpIndexName, objects, Action.ADD_OBJECT, true, batchSize, requestOptions); + // Save new objects + List batchResponses = chunkedBatch(tmpIndexName, objects, Action.ADD_OBJECT, true, batchSize, requestOptions); - waitForTask(tmpIndexName, copyOperationResponse.getTaskID(), requestOptions); + waitForTask(tmpIndexName, copyOperationResponse.getTaskID(), requestOptions); - copyOperationResponse = operationIndex( - indexName, - new OperationIndexParams() - .setOperation(OperationType.COPY) - .setDestination(tmpIndexName) - .addScope(ScopeType.SETTINGS) - .addScope(ScopeType.RULES) - .addScope(ScopeType.SYNONYMS), - requestOptions - ); - waitForTask(tmpIndexName, copyOperationResponse.getTaskID(), requestOptions); + copyOperationResponse = operationIndex( + indexName, + new OperationIndexParams() + .setOperation(OperationType.COPY) + .setDestination(tmpIndexName) + .addScope(ScopeType.SETTINGS) + .addScope(ScopeType.RULES) + .addScope(ScopeType.SYNONYMS), + requestOptions + ); + waitForTask(tmpIndexName, copyOperationResponse.getTaskID(), requestOptions); - // Move temporary index to source index - UpdatedAtResponse moveOperationResponse = operationIndex( - tmpIndexName, - new OperationIndexParams().setOperation(OperationType.MOVE).setDestination(indexName), - requestOptions - ); - waitForTask(tmpIndexName, moveOperationResponse.getTaskID(), requestOptions); + // Move temporary index to source index + UpdatedAtResponse moveOperationResponse = operationIndex( + tmpIndexName, + new OperationIndexParams().setOperation(OperationType.MOVE).setDestination(indexName), + requestOptions + ); + waitForTask(tmpIndexName, moveOperationResponse.getTaskID(), requestOptions); - return new ReplaceAllObjectsResponse() - .setCopyOperationResponse(copyOperationResponse) - .setBatchResponses(batchResponses) - .setMoveOperationResponse(moveOperationResponse); + return new ReplaceAllObjectsResponse() + .setCopyOperationResponse(copyOperationResponse) + .setBatchResponses(batchResponses) + .setMoveOperationResponse(moveOperationResponse); + } catch (Exception e) { + deleteIndex(tmpIndexName); + + throw e; + } } /** From 7c25424a667eb8dc2652930f7f216af8fd910dfe Mon Sep 17 00:00:00 2001 From: Thomas Raffray Date: Thu, 31 Oct 2024 10:14:08 +0100 Subject: [PATCH 04/15] chore(scala): cleanup --- .../algoliasearch/extension/package.scala | 82 ++++++++++--------- 1 file changed, 43 insertions(+), 39 deletions(-) diff --git a/clients/algoliasearch-client-scala/src/main/scala/algoliasearch/extension/package.scala b/clients/algoliasearch-client-scala/src/main/scala/algoliasearch/extension/package.scala index b916606295..cbf6f23b13 100644 --- a/clients/algoliasearch-client-scala/src/main/scala/algoliasearch/extension/package.scala +++ b/clients/algoliasearch-client-scala/src/main/scala/algoliasearch/extension/package.scala @@ -359,50 +359,54 @@ package object extension { } val tmpIndexName = s"${indexName}_tmp_${scala.util.Random.nextInt(100)}" - for { - copy <- client.operationIndex( - indexName = indexName, - operationIndexParams = OperationIndexParams( - operation = OperationType.Copy, - destination = tmpIndexName, - scope = Some(Seq(ScopeType.Settings, ScopeType.Rules, ScopeType.Synonyms)) - ), - requestOptions = requestOptions - ) + try { + for { + copy <- client.operationIndex( + indexName = indexName, + operationIndexParams = OperationIndexParams( + operation = OperationType.Copy, + destination = tmpIndexName, + scope = Some(Seq(ScopeType.Settings, ScopeType.Rules, ScopeType.Synonyms)) + ), + requestOptions = requestOptions + ) - batchResponses <- chunkedBatch( - indexName = tmpIndexName, - objects = objects, - action = Action.AddObject, - waitForTasks = true, - batchSize = batchSize, - requestOptions = requestOptions - ) + batchResponses <- chunkedBatch( + indexName = tmpIndexName, + objects = objects, + action = Action.AddObject, + waitForTasks = true, + batchSize = batchSize, + requestOptions = requestOptions + ) - _ <- client.waitTask(indexName = tmpIndexName, taskID = copy.taskID, requestOptions = requestOptions) + _ <- client.waitTask(indexName = tmpIndexName, taskID = copy.taskID, requestOptions = requestOptions) - copy <- client.operationIndex( - indexName = indexName, - operationIndexParams = OperationIndexParams( - operation = OperationType.Copy, - destination = tmpIndexName, - scope = Some(Seq(ScopeType.Settings, ScopeType.Rules, ScopeType.Synonyms)) - ), - requestOptions = requestOptions - ) - _ <- client.waitTask(indexName = tmpIndexName, taskID = copy.taskID, requestOptions = requestOptions) + copy <- client.operationIndex( + indexName = indexName, + operationIndexParams = OperationIndexParams( + operation = OperationType.Copy, + destination = tmpIndexName, + scope = Some(Seq(ScopeType.Settings, ScopeType.Rules, ScopeType.Synonyms)) + ), + requestOptions = requestOptions + ) + _ <- client.waitTask(indexName = tmpIndexName, taskID = copy.taskID, requestOptions = requestOptions) - move <- client.operationIndex( - indexName = tmpIndexName, - operationIndexParams = OperationIndexParams(operation = OperationType.Move, destination = indexName), - requestOptions = requestOptions + move <- client.operationIndex( + indexName = tmpIndexName, + operationIndexParams = OperationIndexParams(operation = OperationType.Move, destination = indexName), + requestOptions = requestOptions + ) + _ <- client.waitTask(indexName = tmpIndexName, taskID = move.taskID, requestOptions = requestOptions) + } yield ReplaceAllObjectsResponse( + copyOperationResponse = copy, + batchResponses = batchResponses, + moveOperationResponse = move ) - _ <- client.waitTask(indexName = tmpIndexName, taskID = move.taskID, requestOptions = requestOptions) - } yield ReplaceAllObjectsResponse( - copyOperationResponse = copy, - batchResponses = batchResponses, - moveOperationResponse = move - ) + } finally { + client.deleteIndex(tmpIndexName) + } } def indexExists(indexName: String)(implicit ec: ExecutionContext): Future[Boolean] = { From 8682e0d2acad3a91e88fc423d6de0ebe518f9975 Mon Sep 17 00:00:00 2001 From: Thomas Raffray Date: Thu, 31 Oct 2024 11:46:12 +0100 Subject: [PATCH 05/15] chore(kotlin): cleanup --- .../algolia/client/extensions/SearchClient.kt | 81 +++++++++++-------- 1 file changed, 46 insertions(+), 35 deletions(-) diff --git a/clients/algoliasearch-client-kotlin/client/src/commonMain/kotlin/com/algolia/client/extensions/SearchClient.kt b/clients/algoliasearch-client-kotlin/client/src/commonMain/kotlin/com/algolia/client/extensions/SearchClient.kt index da143eb1dd..e8b7405dac 100644 --- a/clients/algoliasearch-client-kotlin/client/src/commonMain/kotlin/com/algolia/client/extensions/SearchClient.kt +++ b/clients/algoliasearch-client-kotlin/client/src/commonMain/kotlin/com/algolia/client/extensions/SearchClient.kt @@ -465,46 +465,50 @@ public suspend fun SearchClient.replaceAllObjects( ): ReplaceAllObjectsResponse { val tmpIndexName = "${indexName}_tmp_${Random.nextInt(from = 0, until = 100)}" - var copy = operationIndex( - indexName = indexName, - operationIndexParams = OperationIndexParams( - operation = OperationType.Copy, - destination = tmpIndexName, - scope = listOf(ScopeType.Settings, ScopeType.Rules, ScopeType.Synonyms), - ), - requestOptions = requestOptions, - ) + try { + var copy = operationIndex( + indexName = indexName, + operationIndexParams = OperationIndexParams( + operation = OperationType.Copy, + destination = tmpIndexName, + scope = listOf(ScopeType.Settings, ScopeType.Rules, ScopeType.Synonyms), + ), + requestOptions = requestOptions, + ) - val batchResponses = this.chunkedBatch( - indexName = tmpIndexName, - objects = objects, - action = Action.AddObject, - waitForTask = true, - batchSize = batchSize, - requestOptions = requestOptions, - ) + val batchResponses = this.chunkedBatch( + indexName = tmpIndexName, + objects = objects, + action = Action.AddObject, + waitForTask = true, + batchSize = batchSize, + requestOptions = requestOptions, + ) - waitForTask(indexName = tmpIndexName, taskID = copy.taskID) + waitForTask(indexName = tmpIndexName, taskID = copy.taskID) - copy = operationIndex( - indexName = indexName, - operationIndexParams = OperationIndexParams( - operation = OperationType.Copy, - destination = tmpIndexName, - scope = listOf(ScopeType.Settings, ScopeType.Rules, ScopeType.Synonyms), - ), - requestOptions = requestOptions, - ) - waitForTask(indexName = tmpIndexName, taskID = copy.taskID) + copy = operationIndex( + indexName = indexName, + operationIndexParams = OperationIndexParams( + operation = OperationType.Copy, + destination = tmpIndexName, + scope = listOf(ScopeType.Settings, ScopeType.Rules, ScopeType.Synonyms), + ), + requestOptions = requestOptions, + ) + waitForTask(indexName = tmpIndexName, taskID = copy.taskID) - val move = operationIndex( - indexName = tmpIndexName, - operationIndexParams = OperationIndexParams(operation = OperationType.Move, destination = indexName), - requestOptions = requestOptions, - ) - waitForTask(indexName = tmpIndexName, taskID = move.taskID) + val move = operationIndex( + indexName = tmpIndexName, + operationIndexParams = OperationIndexParams(operation = OperationType.Move, destination = indexName), + requestOptions = requestOptions, + ) + waitForTask(indexName = tmpIndexName, taskID = move.taskID) - return ReplaceAllObjectsResponse(copy, batchResponses, move) + return ReplaceAllObjectsResponse(copy, batchResponses, move) + } finally { + deleteIndex(tmpIndexName) + } } /** @@ -535,6 +539,13 @@ public fun securedApiKeyRemainingValidity(apiKey: String): Duration { return validUntil - Clock.System.now() } +/** + * Checks that an index exists. + * + * @param indexName The name of the index to check. + * @return true if the index exists, false otherwise. + * @throws AlgoliaApiException if an error occurs during the request. + */ public suspend fun SearchClient.indexExists(indexName: String): Boolean { try { getSettings(indexName) From 13d15759d578b525519d2ab19d2375a61e5c8096 Mon Sep 17 00:00:00 2001 From: Thomas Raffray Date: Thu, 31 Oct 2024 12:27:07 +0100 Subject: [PATCH 06/15] chore(python): cleanup --- templates/python/search_helpers.mustache | 105 ++++++++++++----------- 1 file changed, 54 insertions(+), 51 deletions(-) diff --git a/templates/python/search_helpers.mustache b/templates/python/search_helpers.mustache index 80a7343761..dcd07d1a83 100644 --- a/templates/python/search_helpers.mustache +++ b/templates/python/search_helpers.mustache @@ -335,57 +335,60 @@ """ tmp_index_name = self.create_temporary_name(index_name) - {{^isSyncClient}}async {{/isSyncClient}}def _copy() -> UpdatedAtResponse: - return {{^isSyncClient}}await {{/isSyncClient}}self.operation_index( - index_name=index_name, - operation_index_params=OperationIndexParams( - operation=OperationType.COPY, - destination=tmp_index_name, - scope=[ - ScopeType("settings"), - ScopeType("rules"), - ScopeType("synonyms"), - ], - ), - request_options=request_options, - ) - - copy_operation_response = {{^isSyncClient}}await {{/isSyncClient}}_copy() - - batch_responses = {{^isSyncClient}}await {{/isSyncClient}}self.chunked_batch( - index_name=tmp_index_name, - objects=objects, - wait_for_tasks=True, - batch_size=batch_size, - request_options=request_options, - ) - - {{^isSyncClient}}await {{/isSyncClient}}self.wait_for_task( - index_name=tmp_index_name, task_id=copy_operation_response.task_id - ) - - copy_operation_response = {{^isSyncClient}}await {{/isSyncClient}}_copy() - {{^isSyncClient}}await {{/isSyncClient}}self.wait_for_task( - index_name=tmp_index_name, task_id=copy_operation_response.task_id - ) - - move_operation_response = {{^isSyncClient}}await {{/isSyncClient}}self.operation_index( - index_name=tmp_index_name, - operation_index_params=OperationIndexParams( - operation=OperationType.MOVE, - destination=index_name, - ), - request_options=request_options, - ) - {{^isSyncClient}}await {{/isSyncClient}}self.wait_for_task( - index_name=tmp_index_name, task_id=move_operation_response.task_id - ) - - return ReplaceAllObjectsResponse( - copy_operation_response=copy_operation_response, - batch_responses=batch_responses, - move_operation_response=move_operation_response, - ) + try: + {{^isSyncClient}}async {{/isSyncClient}}def _copy() -> UpdatedAtResponse: + return {{^isSyncClient}}await {{/isSyncClient}}self.operation_index( + index_name=index_name, + operation_index_params=OperationIndexParams( + operation=OperationType.COPY, + destination=tmp_index_name, + scope=[ + ScopeType("settings"), + ScopeType("rules"), + ScopeType("synonyms"), + ], + ), + request_options=request_options, + ) + + copy_operation_response = {{^isSyncClient}}await {{/isSyncClient}}_copy() + + batch_responses = {{^isSyncClient}}await {{/isSyncClient}}self.chunked_batch( + index_name=tmp_index_name, + objects=objects, + wait_for_tasks=True, + batch_size=batch_size, + request_options=request_options, + ) + + {{^isSyncClient}}await {{/isSyncClient}}self.wait_for_task( + index_name=tmp_index_name, task_id=copy_operation_response.task_id + ) + + copy_operation_response = {{^isSyncClient}}await {{/isSyncClient}}_copy() + {{^isSyncClient}}await {{/isSyncClient}}self.wait_for_task( + index_name=tmp_index_name, task_id=copy_operation_response.task_id + ) + + move_operation_response = {{^isSyncClient}}await {{/isSyncClient}}self.operation_index( + index_name=tmp_index_name, + operation_index_params=OperationIndexParams( + operation=OperationType.MOVE, + destination=index_name, + ), + request_options=request_options, + ) + {{^isSyncClient}}await {{/isSyncClient}}self.wait_for_task( + index_name=tmp_index_name, task_id=move_operation_response.task_id + ) + + return ReplaceAllObjectsResponse( + copy_operation_response=copy_operation_response, + batch_responses=batch_responses, + move_operation_response=move_operation_response, + ) + finally: + {{^isSyncClient}}await {{/isSyncClient}}self.delete_index(tmp_index_name) {{^isSyncClient}}async {{/isSyncClient}}def index_exists(self, index_name: str) -> bool: """ From 070304fd84f79a743b3e703edd1219a172a1bc65 Mon Sep 17 00:00:00 2001 From: Thomas Raffray Date: Thu, 31 Oct 2024 12:28:59 +0100 Subject: [PATCH 07/15] chore(javascript): cleanup --- .../clients/client/api/helpers.mustache | 90 ++++++++++--------- 1 file changed, 47 insertions(+), 43 deletions(-) diff --git a/templates/javascript/clients/client/api/helpers.mustache b/templates/javascript/clients/client/api/helpers.mustache index 7610e01fef..57e73e744d 100644 --- a/templates/javascript/clients/client/api/helpers.mustache +++ b/templates/javascript/clients/client/api/helpers.mustache @@ -401,57 +401,61 @@ async replaceAllObjects( const randomSuffix = Math.floor(Math.random() * 1000000) + 100000; const tmpIndexName = `${indexName}_tmp_${randomSuffix}`; - let copyOperationResponse = await this.operationIndex( - { - indexName, - operationIndexParams: { - operation: 'copy', - destination: tmpIndexName, - scope: ['settings', 'rules', 'synonyms'], + try { + let copyOperationResponse = await this.operationIndex( + { + indexName, + operationIndexParams: { + operation: 'copy', + destination: tmpIndexName, + scope: ['settings', 'rules', 'synonyms'], + }, }, - }, - requestOptions - ); + requestOptions + ); - const batchResponses = await this.chunkedBatch( - { indexName: tmpIndexName, objects, waitForTasks: true, batchSize }, - requestOptions - ); + const batchResponses = await this.chunkedBatch( + { indexName: tmpIndexName, objects, waitForTasks: true, batchSize }, + requestOptions + ); - await this.waitForTask({ - indexName: tmpIndexName, - taskID: copyOperationResponse.taskID, - }); + await this.waitForTask({ + indexName: tmpIndexName, + taskID: copyOperationResponse.taskID, + }); - copyOperationResponse = await this.operationIndex( - { - indexName, - operationIndexParams: { - operation: 'copy', - destination: tmpIndexName, - scope: ['settings', 'rules', 'synonyms'], + copyOperationResponse = await this.operationIndex( + { + indexName, + operationIndexParams: { + operation: 'copy', + destination: tmpIndexName, + scope: ['settings', 'rules', 'synonyms'], + }, }, - }, - requestOptions - ); - await this.waitForTask({ - indexName: tmpIndexName, - taskID: copyOperationResponse.taskID, - }); + requestOptions + ); + await this.waitForTask({ + indexName: tmpIndexName, + taskID: copyOperationResponse.taskID, + }); - const moveOperationResponse = await this.operationIndex( - { + const moveOperationResponse = await this.operationIndex( + { + indexName: tmpIndexName, + operationIndexParams: { operation: 'move', destination: indexName }, + }, + requestOptions + ); + await this.waitForTask({ indexName: tmpIndexName, - operationIndexParams: { operation: 'move', destination: indexName }, - }, - requestOptions - ); - await this.waitForTask({ - indexName: tmpIndexName, - taskID: moveOperationResponse.taskID, - }); + taskID: moveOperationResponse.taskID, + }); - return { copyOperationResponse, batchResponses, moveOperationResponse }; + return { copyOperationResponse, batchResponses, moveOperationResponse }; + } finally { + await this.deleteIndex({ indexName: tmpIndexName }); + } }, async indexExists({ indexName }: GetSettingsProps): Promise { From 0a8dee86b25cc14fb6b4b16371128c9ebb603af8 Mon Sep 17 00:00:00 2001 From: Thomas Raffray Date: Thu, 31 Oct 2024 12:32:52 +0100 Subject: [PATCH 08/15] chore(csharp): cleanup --- .../Utils/SearchClientExtensions.cs | 59 +++++++++++-------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/clients/algoliasearch-client-csharp/algoliasearch/Utils/SearchClientExtensions.cs b/clients/algoliasearch-client-csharp/algoliasearch/Utils/SearchClientExtensions.cs index d621aa0972..073f5e19a2 100644 --- a/clients/algoliasearch-client-csharp/algoliasearch/Utils/SearchClientExtensions.cs +++ b/clients/algoliasearch-client-csharp/algoliasearch/Utils/SearchClientExtensions.cs @@ -344,7 +344,7 @@ public async Task> BrowseRulesAsync(string indexName, SearchRu var page = prevResp?.Item2 ?? 0; var searchSynonymsResponse = await SearchRulesAsync(indexName, searchRulesParams, requestOptions); return new Tuple(searchSynonymsResponse, page + 1); - }, resp => resp?.Item1 is { NbHits: < hitsPerPage }).ConfigureAwait(false); + }, resp => resp?.Item1 is { Hits.Count: < hitsPerPage }).ConfigureAwait(false); return all.SelectMany(u => u.Item1.Hits); } @@ -367,7 +367,7 @@ public async Task> BrowseSynonymsAsync(string indexName, var searchSynonymsResponse = await SearchSynonymsAsync(indexName, synonymsParams, requestOptions); page = page + 1; return new Tuple(searchSynonymsResponse, page); - }, resp => resp?.Item1 is { NbHits: < hitsPerPage }).ConfigureAwait(false); + }, resp => resp?.Item1 is { Hits.Count: < hitsPerPage }).ConfigureAwait(false); return all.SelectMany(u => u.Item1.Hits); } @@ -491,37 +491,44 @@ public async Task ReplaceAllObjectsAsync(string in var rnd = new Random(); var tmpIndexName = $"{indexName}_tmp_{rnd.Next(100)}"; - var copyResponse = await OperationIndexAsync(indexName, - new OperationIndexParams(OperationType.Copy, tmpIndexName) - { Scope = [ScopeType.Settings, ScopeType.Rules, ScopeType.Synonyms] }, options, cancellationToken) - .ConfigureAwait(false); + try + { + var copyResponse = await OperationIndexAsync(indexName, + new OperationIndexParams(OperationType.Copy, tmpIndexName) + { Scope = [ScopeType.Settings, ScopeType.Rules, ScopeType.Synonyms] }, options, cancellationToken) + .ConfigureAwait(false); - var batchResponse = await ChunkedBatchAsync(tmpIndexName, objects, Action.AddObject, true, batchSize, - options, cancellationToken).ConfigureAwait(false); + var batchResponse = await ChunkedBatchAsync(tmpIndexName, objects, Action.AddObject, true, batchSize, + options, cancellationToken).ConfigureAwait(false); - await WaitForTaskAsync(tmpIndexName, copyResponse.TaskID, requestOptions: options, ct: cancellationToken) - .ConfigureAwait(false); + await WaitForTaskAsync(tmpIndexName, copyResponse.TaskID, requestOptions: options, ct: cancellationToken) + .ConfigureAwait(false); - copyResponse = await OperationIndexAsync(indexName, - new OperationIndexParams(OperationType.Copy, tmpIndexName) - { Scope = [ScopeType.Settings, ScopeType.Rules, ScopeType.Synonyms] }, options, cancellationToken) - .ConfigureAwait(false); - await WaitForTaskAsync(tmpIndexName, copyResponse.TaskID, requestOptions: options, ct: cancellationToken) - .ConfigureAwait(false); + copyResponse = await OperationIndexAsync(indexName, + new OperationIndexParams(OperationType.Copy, tmpIndexName) + { Scope = [ScopeType.Settings, ScopeType.Rules, ScopeType.Synonyms] }, options, cancellationToken) + .ConfigureAwait(false); + await WaitForTaskAsync(tmpIndexName, copyResponse.TaskID, requestOptions: options, ct: cancellationToken) + .ConfigureAwait(false); - var moveResponse = await OperationIndexAsync(tmpIndexName, - new OperationIndexParams(OperationType.Move, indexName), options, cancellationToken) - .ConfigureAwait(false); + var moveResponse = await OperationIndexAsync(tmpIndexName, + new OperationIndexParams(OperationType.Move, indexName), options, cancellationToken) + .ConfigureAwait(false); - await WaitForTaskAsync(tmpIndexName, moveResponse.TaskID, requestOptions: options, ct: cancellationToken) - .ConfigureAwait(false); + await WaitForTaskAsync(tmpIndexName, moveResponse.TaskID, requestOptions: options, ct: cancellationToken) + .ConfigureAwait(false); - return new ReplaceAllObjectsResponse + return new ReplaceAllObjectsResponse + { + CopyOperationResponse = copyResponse, + MoveOperationResponse = moveResponse, + BatchResponses = batchResponse + }; + } + finally { - CopyOperationResponse = copyResponse, - MoveOperationResponse = moveResponse, - BatchResponses = batchResponse - }; + await DeleteIndexAsync(tmpIndexName, cancellationToken: cancellationToken).ConfigureAwait(false); + } } /// From e77d49f506b06049de1a327d227fbbee2384fc9e Mon Sep 17 00:00:00 2001 From: Thomas Raffray Date: Thu, 31 Oct 2024 12:34:36 +0100 Subject: [PATCH 09/15] chore(php): cleanup --- templates/php/api.mustache | 50 ++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/templates/php/api.mustache b/templates/php/api.mustache index 12274200d4..a66f58de19 100644 --- a/templates/php/api.mustache +++ b/templates/php/api.mustache @@ -438,48 +438,52 @@ use Algolia\AlgoliaSearch\Exceptions\NotFoundException; { $tmpIndexName = $indexName.'_tmp_'.rand(10000000, 99999999); - $copyOperationResponse = $this->operationIndex( + try { + $copyOperationResponse = $this->operationIndex( $indexName, [ - 'operation' => 'copy', - 'destination' => $tmpIndexName, - 'scope' => ['settings', 'rules', 'synonyms'], + 'operation' => 'copy', + 'destination' => $tmpIndexName, + 'scope' => ['settings', 'rules', 'synonyms'], ], $requestOptions - ); + ); - $batchResponses = $this->chunkedBatch($tmpIndexName, $objects, 'addObject', true, $batchSize, $requestOptions); + $batchResponses = $this->chunkedBatch($tmpIndexName, $objects, 'addObject', true, $batchSize, $requestOptions); - $this->waitForTask($tmpIndexName, $copyOperationResponse['taskID']); + $this->waitForTask($tmpIndexName, $copyOperationResponse['taskID']); - $copyOperationResponse = $this->operationIndex( + $copyOperationResponse = $this->operationIndex( $indexName, [ - 'operation' => 'copy', - 'destination' => $tmpIndexName, - 'scope' => ['settings', 'rules', 'synonyms'], + 'operation' => 'copy', + 'destination' => $tmpIndexName, + 'scope' => ['settings', 'rules', 'synonyms'], ], $requestOptions - ); + ); - $this->waitForTask($tmpIndexName, $copyOperationResponse['taskID']); + $this->waitForTask($tmpIndexName, $copyOperationResponse['taskID']); - $moveOperationResponse = $this->operationIndex( + $moveOperationResponse = $this->operationIndex( $tmpIndexName, [ - 'operation' => 'move', - 'destination' => $indexName, + 'operation' => 'move', + 'destination' => $indexName, ], $requestOptions - ); + ); - $this->waitForTask($tmpIndexName, $moveOperationResponse['taskID']); + $this->waitForTask($tmpIndexName, $moveOperationResponse['taskID']); - return [ - "copyOperationResponse" => $copyOperationResponse, - "batchResponses" => $batchResponses, - "moveOperationResponse" => $moveOperationResponse - ]; + return [ + 'copyOperationResponse' => $copyOperationResponse, + 'batchResponses' => $batchResponses, + 'moveOperationResponse' => $moveOperationResponse, + ]; + } finally { + $this->deleteIndex($tmpIndexName); + } } /** From 5e813566b7a41e17503d8c9442d47134ad424ae7 Mon Sep 17 00:00:00 2001 From: Thomas Raffray Date: Thu, 31 Oct 2024 12:36:48 +0100 Subject: [PATCH 10/15] chore(ruby): cleanup --- templates/ruby/search_helpers.mustache | 118 +++++++++++++------------ 1 file changed, 61 insertions(+), 57 deletions(-) diff --git a/templates/ruby/search_helpers.mustache b/templates/ruby/search_helpers.mustache index 2c54d095a3..7bc7852511 100644 --- a/templates/ruby/search_helpers.mustache +++ b/templates/ruby/search_helpers.mustache @@ -307,63 +307,67 @@ end def replace_all_objects(index_name, objects, batch_size = 1000, request_options = {}) tmp_index_name = index_name + '_tmp_' + rand(10_000_000).to_s - copy_operation_response = operation_index( - index_name, - Search::OperationIndexParams.new( - operation: Search::OperationType::COPY, - destination: tmp_index_name, - scope: [ - Search::ScopeType::SETTINGS, - Search::ScopeType::RULES, - Search::ScopeType::SYNONYMS - ] - ), - request_options - ) - - batch_responses = chunked_batch( - tmp_index_name, - objects, - Search::Action::ADD_OBJECT, - true, - batch_size, - request_options - ) - - wait_for_task(tmp_index_name, copy_operation_response.task_id) - - copy_operation_response = operation_index( - index_name, - Search::OperationIndexParams.new( - operation: Search::OperationType::COPY, - destination: tmp_index_name, - scope: [ - Search::ScopeType::SETTINGS, - Search::ScopeType::RULES, - Search::ScopeType::SYNONYMS - ] - ), - request_options - ) - - wait_for_task(tmp_index_name, copy_operation_response.task_id) - - move_operation_response = operation_index( - tmp_index_name, - Search::OperationIndexParams.new( - operation: Search::OperationType::MOVE, - destination: index_name - ), - request_options - ) - - wait_for_task(tmp_index_name, move_operation_response.task_id) - - Search::ReplaceAllObjectsResponse.new( - copy_operation_response: copy_operation_response, - batch_responses: batch_responses, - move_operation_response: move_operation_response - ) + begin + copy_operation_response = operation_index( + index_name, + Search::OperationIndexParams.new( + operation: Search::OperationType::COPY, + destination: tmp_index_name, + scope: [ + Search::ScopeType::SETTINGS, + Search::ScopeType::RULES, + Search::ScopeType::SYNONYMS + ] + ), + request_options + ) + + batch_responses = chunked_batch( + tmp_index_name, + objects, + Search::Action::ADD_OBJECT, + true, + batch_size, + request_options + ) + + wait_for_task(tmp_index_name, copy_operation_response.task_id) + + copy_operation_response = operation_index( + index_name, + Search::OperationIndexParams.new( + operation: Search::OperationType::COPY, + destination: tmp_index_name, + scope: [ + Search::ScopeType::SETTINGS, + Search::ScopeType::RULES, + Search::ScopeType::SYNONYMS + ] + ), + request_options + ) + + wait_for_task(tmp_index_name, copy_operation_response.task_id) + + move_operation_response = operation_index( + tmp_index_name, + Search::OperationIndexParams.new( + operation: Search::OperationType::MOVE, + destination: index_name + ), + request_options + ) + + wait_for_task(tmp_index_name, move_operation_response.task_id) + + Search::ReplaceAllObjectsResponse.new( + copy_operation_response: copy_operation_response, + batch_responses: batch_responses, + move_operation_response: move_operation_response + ) + ensure + delete_index(tmp_index_name) + end end def index_exists?(index_name) From b6128709b04b0cc90f82ae7a8f3ae0512542b8ed Mon Sep 17 00:00:00 2001 From: Pierre Millot Date: Tue, 31 Dec 2024 13:11:48 +0100 Subject: [PATCH 11/15] okay ? --- .../Utils/SearchClientExtensions.cs | 4 +- .../Search/Extra/SearchClientExtension.swift | 2 + scripts/cts/testServer/index.ts | 2 +- scripts/cts/testServer/replaceAllObjects.ts | 15 ++++- templates/java/api_helpers.mustache | 60 ++++++++++++------- tests/CTS/client/ingestion/api.json | 2 +- tests/CTS/client/search/api.json | 2 +- tests/CTS/client/search/indexExists.json | 2 +- .../CTS/client/search/replaceAllObjects.json | 7 +-- tests/CTS/client/search/saveObjects.json | 2 +- tests/output/javascript/yarn.lock | 10 ++-- .../kotlin/com/algolia/utils/Assert.kt | 2 +- 12 files changed, 71 insertions(+), 39 deletions(-) diff --git a/clients/algoliasearch-client-csharp/algoliasearch/Utils/SearchClientExtensions.cs b/clients/algoliasearch-client-csharp/algoliasearch/Utils/SearchClientExtensions.cs index d569c0eb01..83ebbf82e3 100644 --- a/clients/algoliasearch-client-csharp/algoliasearch/Utils/SearchClientExtensions.cs +++ b/clients/algoliasearch-client-csharp/algoliasearch/Utils/SearchClientExtensions.cs @@ -498,7 +498,7 @@ public async Task ReplaceAllObjectsAsync(string in { var copyResponse = await OperationIndexAsync(indexName, new OperationIndexParams(OperationType.Copy, tmpIndexName) - { Scope = [ScopeType.Settings, ScopeType.Rules, ScopeType.Synonyms] }, options, cancellationToken) + { Scope = [ScopeType.Settings, ScopeType.Rules, ScopeType.Synonyms] }, options, cancellationToken) .ConfigureAwait(false); var batchResponse = await ChunkedBatchAsync(tmpIndexName, objects, Action.AddObject, true, batchSize, @@ -509,7 +509,7 @@ await WaitForTaskAsync(tmpIndexName, copyResponse.TaskID, requestOptions: option copyResponse = await OperationIndexAsync(indexName, new OperationIndexParams(OperationType.Copy, tmpIndexName) - { Scope = [ScopeType.Settings, ScopeType.Rules, ScopeType.Synonyms] }, options, cancellationToken) + { Scope = [ScopeType.Settings, ScopeType.Rules, ScopeType.Synonyms] }, options, cancellationToken) .ConfigureAwait(false); await WaitForTaskAsync(tmpIndexName, copyResponse.TaskID, requestOptions: options, ct: cancellationToken) .ConfigureAwait(false); diff --git a/clients/algoliasearch-client-swift/Sources/Search/Extra/SearchClientExtension.swift b/clients/algoliasearch-client-swift/Sources/Search/Extra/SearchClientExtension.swift index 1ea9736b1b..bbb9b3c72e 100644 --- a/clients/algoliasearch-client-swift/Sources/Search/Extra/SearchClientExtension.swift +++ b/clients/algoliasearch-client-swift/Sources/Search/Extra/SearchClientExtension.swift @@ -610,6 +610,8 @@ public extension SearchClient { throw error } + + _ = try? await deleteIndex(indexName: tmpIndexName) } /// Generate a secured API key diff --git a/scripts/cts/testServer/index.ts b/scripts/cts/testServer/index.ts index 5007a0ab03..b057ba4661 100644 --- a/scripts/cts/testServer/index.ts +++ b/scripts/cts/testServer/index.ts @@ -59,7 +59,7 @@ export async function setupServer(name: string, port: number, addRoutes: (app: E // 404 handler app.use((req, res) => { - console.error('endpoint not implemented for', req.method, req.url); + console.error(`[PORT ${port}] endpoint not implemented for`, req.method, req.url); res.status(404).json({ message: 'not found' }); }); diff --git a/scripts/cts/testServer/replaceAllObjects.ts b/scripts/cts/testServer/replaceAllObjects.ts index 33278723e1..a860413320 100644 --- a/scripts/cts/testServer/replaceAllObjects.ts +++ b/scripts/cts/testServer/replaceAllObjects.ts @@ -14,12 +14,13 @@ const raoState: Record< waitTaskCount: number; tmpIndexName: string; waitingForFinalWaitTask: boolean; + tmpIndexDeleted: boolean; successful: boolean; } > = {}; export function assertValidReplaceAllObjects(expectedCount: number): void { - const count = Object.values(raoState).filter((s) => s.successful).length; + const count = Object.values(raoState).filter((s) => s.successful && s.tmpIndexDeleted).length; if (count !== expectedCount) { throw new Error(`Expected ${expectedCount} call to replaceAllObjects, got ${count} instead.`); } @@ -50,6 +51,7 @@ function addRoutes(app: Express): void { waitTaskCount: 0, tmpIndexName: req.body.destination, waitingForFinalWaitTask: false, + tmpIndexDeleted: false, successful: false, }; } else { @@ -68,6 +70,7 @@ function addRoutes(app: Express): void { waitTaskCount: 6, tmpIndexName: req.params.indexName, waitingForFinalWaitTask: false, + tmpIndexDeleted: false, successful: false, }); @@ -113,6 +116,16 @@ function addRoutes(app: Express): void { res.json({ status: 'published', updatedAt: '2021-01-01T00:00:00.000Z' }); }); + + app.delete('/1/indexes/:indexName', (req, res) => { + const lang = req.params.indexName.match(/^cts_e2e_replace_all_objects_(.*)_tmp_\d+$/)?.[1] as string; + expect(raoState).to.include.keys(lang); + expect(raoState[lang].tmpIndexName).to.equal(req.params.indexName); + + raoState[lang].tmpIndexDeleted = true; + + res.json({ taskID: 456, deletedAt: '2021-01-01T00:00:00.000Z' }); + }); } export function replaceAllObjectsServer(): Promise { diff --git a/templates/java/api_helpers.mustache b/templates/java/api_helpers.mustache index ef65bc04c3..eb5fc96e2b 100644 --- a/templates/java/api_helpers.mustache +++ b/templates/java/api_helpers.mustache @@ -653,24 +653,6 @@ public List chunkedBatch( return chunkedBatch(indexName, objects, action, waitForTasks, 1000, requestOptions); } -/** - * Push a new set of objects and remove all previous ones. Settings, synonyms and query rules are - * untouched. Replace all records in an index without any downtime. See - * https://api-clients-automation.netlify.app/docs/add-new-api-client#5-helpers for implementation - * details. - * - * @param indexName The `indexName` to replace `objects` in. - * @param objects The array of `objects` to store in the given Algolia `indexName`. - * @param batchSize The size of the chunk of `objects`. The number of `batch` calls will be equal - * to `length(objects) / batchSize`. - * @throws AlgoliaRetryException When the retry has failed on all hosts - * @throws AlgoliaApiException When the API sends an http error code - * @throws AlgoliaRuntimeException When an error occurred during the serialization - */ -public ReplaceAllObjectsResponse replaceAllObjects(String indexName, Iterable objects, int batchSize) { - return replaceAllObjects(indexName, objects, batchSize, null); -} - /** * Helper: Saves the given array of objects in the given index. The `chunkedBatch` helper is used * under the hood, which creates a `batch` requests with at most 1000 objects in it. @@ -891,6 +873,40 @@ public List partialUpdateObjects( ); } +/** + * Push a new set of objects and remove all previous ones. Settings, synonyms and query rules are + * untouched. Replace all records in an index without any downtime. See + * https://api-clients-automation.netlify.app/docs/add-new-api-client#5-helpers for implementation + * details. + * + * @param indexName The `indexName` to replace `objects` in. + * @param objects The array of `objects` to store in the given Algolia `indexName`. + * @throws AlgoliaRetryException When the retry has failed on all hosts + * @throws AlgoliaApiException When the API sends an http error code + * @throws AlgoliaRuntimeException When an error occurred during the serialization + */ +public ReplaceAllObjectsResponse replaceAllObjects(String indexName, Iterable objects) { + return replaceAllObjects(indexName, objects, -1); +} + +/** + * Push a new set of objects and remove all previous ones. Settings, synonyms and query rules are + * untouched. Replace all records in an index without any downtime. See + * https://api-clients-automation.netlify.app/docs/add-new-api-client#5-helpers for implementation + * details. + * + * @param indexName The `indexName` to replace `objects` in. + * @param objects The array of `objects` to store in the given Algolia `indexName`. + * @param batchSize The size of the chunk of `objects`. The number of `batch` calls will be equal + * to `length(objects) / batchSize`. + * @throws AlgoliaRetryException When the retry has failed on all hosts + * @throws AlgoliaApiException When the API sends an http error code + * @throws AlgoliaRuntimeException When an error occurred during the serialization + */ +public ReplaceAllObjectsResponse replaceAllObjects(String indexName, Iterable objects, int batchSize) { + return replaceAllObjects(indexName, objects, batchSize, null); +} + /** * Push a new set of objects and remove all previous ones. Settings, synonyms and query rules are * untouched. Replace all records in an index without any downtime. See @@ -916,6 +932,10 @@ public ReplaceAllObjectsResponse replaceAllObjects( Random rnd = new Random(); String tmpIndexName = indexName + "_tmp_" + rnd.nextInt(100); + if (batchSize == -1) { + batchSize = 1000; + } + try { // Copy settings, synonyms and rules UpdatedAtResponse copyOperationResponse = operationIndex( @@ -958,10 +978,8 @@ public ReplaceAllObjectsResponse replaceAllObjects( .setCopyOperationResponse(copyOperationResponse) .setBatchResponses(batchResponses) .setMoveOperationResponse(moveOperationResponse); - } catch (Exception e) { + } finally { deleteIndex(tmpIndexName); - - throw e; } } diff --git a/tests/CTS/client/ingestion/api.json b/tests/CTS/client/ingestion/api.json index 2f9d3ebef8..474b906fca 100644 --- a/tests/CTS/client/ingestion/api.json +++ b/tests/CTS/client/ingestion/api.json @@ -28,7 +28,7 @@ "go": "API error [429] Too Many Requests", "java": "Status Code: 429 - Too Many Requests", "javascript": "Too Many Requests", - "kotlin": "Client request(GET http://%localhost%:6676/1/html-error) invalid: 429 Too Many Requests. Text: \\\"429 Too Many Requests\\\"", + "kotlin": "Client request\\\\(GET http://%localhost%:6676/1/html-error\\\\) invalid: 429 Too Many Requests. Text: \\\"429 Too Many Requests\\\"", "php": "429: Too Many Requests", "python": "Too Many Requests", "ruby": "429: Too Many Requests", diff --git a/tests/CTS/client/search/api.json b/tests/CTS/client/search/api.json index 315daca938..a2def1c53b 100644 --- a/tests/CTS/client/search/api.json +++ b/tests/CTS/client/search/api.json @@ -137,7 +137,7 @@ "go": "failed to do request: all hosts have been contacted unsuccessfully, it can either be a server or a network error or wrong appID/key credentials were used. You can use 'ExposeIntermediateNetworkErrors: true' in the config to investigate.", "java": "Error(s) while processing the retry strategy\\nCaused by: java.net.SocketTimeoutException: timeout", "javascript": "Unreachable hosts - your application id may be incorrect. If the error persists, please reach out to the Algolia Support team: https://alg.li/support.", - "kotlin": "Error(s) while processing the retry strategy", + "kotlin": "Error\\\\(s\\\\) while processing the retry strategy", "php": "Impossible to connect, please check your Algolia Application Id.", "python": "Unreachable hosts", "ruby": "Unreachable hosts. Last error for %localhost%: Net::ReadTimeout with #", diff --git a/tests/CTS/client/search/indexExists.json b/tests/CTS/client/search/indexExists.json index f2582333ee..84117b466b 100644 --- a/tests/CTS/client/search/indexExists.json +++ b/tests/CTS/client/search/indexExists.json @@ -85,7 +85,7 @@ "go": "API error [403] Invalid API key", "java": "Status Code: 403 - {\\\"message\\\":\\\"Invalid API key\\\"}", "javascript": "Invalid API key", - "kotlin": "Client request(GET http://%localhost%:6681/1/indexes/indexExistsERROR/settings) invalid: 403 Forbidden. Text: \\\"{\\\"message\\\":\\\"Invalid API key\\\"}\\\"", + "kotlin": "Client request\\\\(GET http://%localhost%:6681/1/indexes/indexExistsERROR/settings\\\\) invalid: 403 Forbidden. Text: \\\"\\\\{\\\"message\\\":\\\"Invalid API key\\\"\\\\}\\\"", "php": "Invalid API key", "python": "Invalid API key", "ruby": "403: Invalid API key", diff --git a/tests/CTS/client/search/replaceAllObjects.json b/tests/CTS/client/search/replaceAllObjects.json index d25a4393d9..c2a525294e 100644 --- a/tests/CTS/client/search/replaceAllObjects.json +++ b/tests/CTS/client/search/replaceAllObjects.json @@ -123,7 +123,6 @@ "apiKey": "test-api-key", "customHosts": [ { - "host": "${{localhost}}", "port": 6684 } ] @@ -148,13 +147,13 @@ "expected": { "error": { "csharp": "{\\\"message\\\":\\\"Record is too big\\\",\\\"status\\\":400}", - "go": "API error [400] {\\\"message\\\":\\\"Record is too big\\\",\\\"status\\\":400}", + "go": "API error [400] Record is too big", "java": "Status Code: 400 - {\\\"message\\\":\\\"Record is too big\\\",\\\"status\\\":400}", "javascript": "Record is too big", - "kotlin": "Client request(POST http://${{localhost}}:6684/1/indexes/cts_e2e_replace_all_objects_too_big_${{language}}/batch) invalid: 400 Bad Request. Text: \\\"{\\\"message\\\":\\\"Record is too big\\\",\\\"status\\\":400}\\\"", + "kotlin": "Client request\\\\(POST http://%localhost%:6684/1/indexes/cts_e2e_replace_all_objects_too_big_${{language}}_tmp_\\\\d+/batch\\\\) invalid: 400 Bad Request. Text: \\\"\\\\{\\\"message\\\":\\\"Record is too big\\\",\\\"status\\\":400\\\\}\\\"", "php": "Record is too big", "python": "Record is too big", - "ruby": "Record is too big", + "ruby": "400: Record is too big", "swift": "HTTP error: Status code: 400 Message: Record is too big" } } diff --git a/tests/CTS/client/search/saveObjects.json b/tests/CTS/client/search/saveObjects.json index 060d85bd9b..49445d7dbd 100644 --- a/tests/CTS/client/search/saveObjects.json +++ b/tests/CTS/client/search/saveObjects.json @@ -84,7 +84,7 @@ "go": "API error [403] Invalid Application-ID or API key", "java": "Status Code: 403 - {\\\"message\\\":\\\"Invalid Application-ID or API key\\\",\\\"status\\\":403}", "javascript": "Invalid Application-ID or API key", - "kotlin": "Client request(POST http://%localhost%:6680/1/indexes/cts_e2e_saveObjects_kotlin/batch) invalid: 403 Forbidden. Text: \\\"{\\\"message\\\":\\\"Invalid Application-ID or API key\\\",\\\"status\\\":403}\\\"", + "kotlin": "Client request\\\\(POST http://%localhost%:6680/1/indexes/cts_e2e_saveObjects_kotlin/batch\\\\) invalid: 403 Forbidden. Text: \\\"\\\\{\\\"message\\\":\\\"Invalid Application-ID or API key\\\",\\\"status\\\":403\\\\}\\\"", "php": "Invalid Application-ID or API key", "python": "Invalid Application-ID or API key", "ruby": "403: Invalid Application-ID or API key", diff --git a/tests/output/javascript/yarn.lock b/tests/output/javascript/yarn.lock index b9207a4425..dc805df3f6 100644 --- a/tests/output/javascript/yarn.lock +++ b/tests/output/javascript/yarn.lock @@ -361,12 +361,12 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:22.10.1": - version: 22.10.1 - resolution: "@types/node@npm:22.10.1" +"@types/node@npm:22.10.2": + version: 22.10.2 + resolution: "@types/node@npm:22.10.2" dependencies: undici-types: "npm:~6.20.0" - checksum: 10/c802a526da2f3fa3ccefd00a71244e7cb825329951719e79e8fec62b1dbc2855388c830489770611584665ce10be23c05ed585982038b24924e1ba2c2cce03fd + checksum: 10/451adfefed4add58b069407173e616220fd4aaa3307cdde1bb701aa053b65b54ced8483db2f870dcedec7a58cb3b06101fbc19d85852716672ec1fd3660947fa languageName: node linkType: hard @@ -981,7 +981,7 @@ __metadata: dependencies: "@algolia/client-composition": "link:../../../clients/algoliasearch-client-javascript/packages/client-composition" "@algolia/requester-testing": "link:../../../clients/algoliasearch-client-javascript/packages/requester-testing" - "@types/node": "npm:22.10.1" + "@types/node": "npm:22.10.2" algoliasearch: "link:../../../clients/algoliasearch-client-javascript/packages/algoliasearch" dotenv: "npm:16.4.7" typescript: "npm:5.7.2" diff --git a/tests/output/kotlin/src/commonTest/kotlin/com/algolia/utils/Assert.kt b/tests/output/kotlin/src/commonTest/kotlin/com/algolia/utils/Assert.kt index 3bed962d1d..81b7960ca7 100644 --- a/tests/output/kotlin/src/commonTest/kotlin/com/algolia/utils/Assert.kt +++ b/tests/output/kotlin/src/commonTest/kotlin/com/algolia/utils/Assert.kt @@ -67,6 +67,6 @@ fun assertEmptyBody(body: Any) { fun assertError(throwable: Throwable, message: String) { when (throwable) { is SkipException -> println("Test skipped because of non-nullable") - else -> assertEquals(message, throwable.message) + else -> assertTrue(throwable.message!!.matches(message.toRegex()), "Expected error: $message, but got: ${throwable.message}") } } From 852ff31bdfa942ddd51c7b472b065f2e9dc7205a Mon Sep 17 00:00:00 2001 From: Pierre Millot Date: Tue, 31 Dec 2024 13:13:31 +0100 Subject: [PATCH 12/15] trigger From a905623bf36ac4687a063d5a451ba558caa9416e Mon Sep 17 00:00:00 2001 From: Pierre Millot Date: Tue, 31 Dec 2024 13:37:49 +0100 Subject: [PATCH 13/15] why swift --- scripts/cts/testServer/replaceAllObjects.ts | 7 ++++--- templates/go/search_helpers.mustache | 16 ++++------------ 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/scripts/cts/testServer/replaceAllObjects.ts b/scripts/cts/testServer/replaceAllObjects.ts index a860413320..d966d68805 100644 --- a/scripts/cts/testServer/replaceAllObjects.ts +++ b/scripts/cts/testServer/replaceAllObjects.ts @@ -20,9 +20,10 @@ const raoState: Record< > = {}; export function assertValidReplaceAllObjects(expectedCount: number): void { - const count = Object.values(raoState).filter((s) => s.successful && s.tmpIndexDeleted).length; - if (count !== expectedCount) { - throw new Error(`Expected ${expectedCount} call to replaceAllObjects, got ${count} instead.`); + expect(Object.keys(raoState)).to.have.length(expectedCount); + for (const lang in raoState) { + expect(raoState[lang].successful).to.equal(true); + expect(raoState[lang].tmpIndexDeleted).to.equal(true); } } diff --git a/templates/go/search_helpers.mustache b/templates/go/search_helpers.mustache index 1bf131b234..bd5ec53ed8 100644 --- a/templates/go/search_helpers.mustache +++ b/templates/go/search_helpers.mustache @@ -657,47 +657,39 @@ func (c *APIClient) ReplaceAllObjects(indexName string, objects []map[string]any return nil, err } + defer func() { + _, _ = c.DeleteIndex(c.NewApiDeleteIndexRequest(tmpIndexName)) + }() + opts = append(opts, WithWaitForTasks(true)) batchResp, err := c.ChunkedBatch(tmpIndexName, objects, ACTION_ADD_OBJECT, opts...) if err != nil { - _, _ = c.DeleteIndex(c.NewApiDeleteIndexRequest(tmpIndexName)) - return nil, err } _, err = c.WaitForTask(tmpIndexName, copyResp.TaskID, toIterableOptions(opts)...) if err != nil { - _, _ = c.DeleteIndex(c.NewApiDeleteIndexRequest(tmpIndexName)) - return nil, err } copyResp, err = c.OperationIndex(c.NewApiOperationIndexRequest(indexName, NewOperationIndexParams(OPERATION_TYPE_COPY, tmpIndexName, WithOperationIndexParamsScope([]ScopeType{SCOPE_TYPE_SETTINGS, SCOPE_TYPE_RULES, SCOPE_TYPE_SYNONYMS}))), toRequestOptions(opts)...) if err != nil { - _, _ = c.DeleteIndex(c.NewApiDeleteIndexRequest(tmpIndexName)) - return nil, err } _, err = c.WaitForTask(tmpIndexName, copyResp.TaskID, toIterableOptions(opts)...) if err != nil { - _, _ = c.DeleteIndex(c.NewApiDeleteIndexRequest(tmpIndexName)) - return nil, err } moveResp, err := c.OperationIndex(c.NewApiOperationIndexRequest(tmpIndexName, NewOperationIndexParams(OPERATION_TYPE_MOVE, indexName)), toRequestOptions(opts)...) if err != nil { - _, _ = c.DeleteIndex(c.NewApiDeleteIndexRequest(tmpIndexName)) - return nil, err } _, err = c.WaitForTask(tmpIndexName, moveResp.TaskID, toIterableOptions(opts)...) if err != nil { - _, _ = c.DeleteIndex(c.NewApiDeleteIndexRequest(tmpIndexName)) - return nil, err } From fe93da5f3d645669b0b90d6a5e0ed03214bf82a8 Mon Sep 17 00:00:00 2001 From: Pierre Millot Date: Tue, 31 Dec 2024 13:47:05 +0100 Subject: [PATCH 14/15] self ? --- .../Sources/Search/Extra/SearchClientExtension.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clients/algoliasearch-client-swift/Sources/Search/Extra/SearchClientExtension.swift b/clients/algoliasearch-client-swift/Sources/Search/Extra/SearchClientExtension.swift index bbb9b3c72e..174858bceb 100644 --- a/clients/algoliasearch-client-swift/Sources/Search/Extra/SearchClientExtension.swift +++ b/clients/algoliasearch-client-swift/Sources/Search/Extra/SearchClientExtension.swift @@ -606,12 +606,12 @@ public extension SearchClient { moveOperationResponse: moveOperationResponse ) } catch { - _ = try? await deleteIndex(indexName: tmpIndexName) + _ = try? await self.deleteIndex(indexName: tmpIndexName) throw error } - _ = try? await deleteIndex(indexName: tmpIndexName) + _ = try? await self.deleteIndex(indexName: tmpIndexName) } /// Generate a secured API key From a05f24ac1cfee9c63b3c0b9cce374d8f1719da5a Mon Sep 17 00:00:00 2001 From: Pierre Millot Date: Tue, 31 Dec 2024 14:45:53 +0100 Subject: [PATCH 15/15] only delete in case of failure --- .../Utils/SearchClientExtensions.cs | 4 +++- .../algolia/client/extensions/SearchClient.kt | 4 +++- .../scala/algoliasearch/extension/package.scala | 8 ++++++-- .../Search/Extra/SearchClientExtension.swift | 2 -- scripts/cts/testServer/index.ts | 5 +++-- scripts/cts/testServer/replaceAllObjects.ts | 14 -------------- .../cts/testServer/replaceAllObjectsFailed.ts | 3 ++- templates/go/search_helpers.mustache | 16 ++++++++++++---- templates/java/api_helpers.mustache | 4 +++- .../clients/client/api/helpers.mustache | 4 +++- templates/php/api.mustache | 6 ++++-- templates/python/search_helpers.mustache | 4 +++- templates/ruby/search_helpers.mustache | 4 +++- 13 files changed, 45 insertions(+), 33 deletions(-) diff --git a/clients/algoliasearch-client-csharp/algoliasearch/Utils/SearchClientExtensions.cs b/clients/algoliasearch-client-csharp/algoliasearch/Utils/SearchClientExtensions.cs index 83ebbf82e3..bdd5f41b31 100644 --- a/clients/algoliasearch-client-csharp/algoliasearch/Utils/SearchClientExtensions.cs +++ b/clients/algoliasearch-client-csharp/algoliasearch/Utils/SearchClientExtensions.cs @@ -528,9 +528,11 @@ await WaitForTaskAsync(tmpIndexName, moveResponse.TaskID, requestOptions: option BatchResponses = batchResponse }; } - finally + catch (Exception ex) { await DeleteIndexAsync(tmpIndexName, cancellationToken: cancellationToken).ConfigureAwait(false); + + throw ex; } } diff --git a/clients/algoliasearch-client-kotlin/client/src/commonMain/kotlin/com/algolia/client/extensions/SearchClient.kt b/clients/algoliasearch-client-kotlin/client/src/commonMain/kotlin/com/algolia/client/extensions/SearchClient.kt index b9063fb9ce..809c6b9868 100644 --- a/clients/algoliasearch-client-kotlin/client/src/commonMain/kotlin/com/algolia/client/extensions/SearchClient.kt +++ b/clients/algoliasearch-client-kotlin/client/src/commonMain/kotlin/com/algolia/client/extensions/SearchClient.kt @@ -513,8 +513,10 @@ public suspend fun SearchClient.replaceAllObjects( waitForTask(indexName = tmpIndexName, taskID = move.taskID) return ReplaceAllObjectsResponse(copy, batchResponses, move) - } finally { + } catch (e: Exception) { deleteIndex(tmpIndexName) + + throw e } } diff --git a/clients/algoliasearch-client-scala/src/main/scala/algoliasearch/extension/package.scala b/clients/algoliasearch-client-scala/src/main/scala/algoliasearch/extension/package.scala index 14d80e6d75..4ecdf17507 100644 --- a/clients/algoliasearch-client-scala/src/main/scala/algoliasearch/extension/package.scala +++ b/clients/algoliasearch-client-scala/src/main/scala/algoliasearch/extension/package.scala @@ -411,8 +411,12 @@ package object extension { batchResponses = batchResponses, moveOperationResponse = move ) - } finally { - client.deleteIndex(tmpIndexName) + } catch { + case e : Throwable => { + client.deleteIndex(tmpIndexName) + + throw e + } } } diff --git a/clients/algoliasearch-client-swift/Sources/Search/Extra/SearchClientExtension.swift b/clients/algoliasearch-client-swift/Sources/Search/Extra/SearchClientExtension.swift index 174858bceb..d0dab61934 100644 --- a/clients/algoliasearch-client-swift/Sources/Search/Extra/SearchClientExtension.swift +++ b/clients/algoliasearch-client-swift/Sources/Search/Extra/SearchClientExtension.swift @@ -610,8 +610,6 @@ public extension SearchClient { throw error } - - _ = try? await self.deleteIndex(indexName: tmpIndexName) } /// Generate a secured API key diff --git a/scripts/cts/testServer/index.ts b/scripts/cts/testServer/index.ts index b057ba4661..62b3811574 100644 --- a/scripts/cts/testServer/index.ts +++ b/scripts/cts/testServer/index.ts @@ -6,6 +6,7 @@ import express from 'express'; import { createSpinner } from '../../spinners.js'; import type { CTSType } from '../runCts.js'; +import { expect } from 'chai'; import { apiKeyServer } from './apiKey.js'; import { benchmarkServer } from './benchmark.js'; import { chunkWrapperServer } from './chunkWrapper.js'; @@ -60,13 +61,13 @@ export async function setupServer(name: string, port: number, addRoutes: (app: E // 404 handler app.use((req, res) => { console.error(`[PORT ${port}] endpoint not implemented for`, req.method, req.url); - res.status(404).json({ message: 'not found' }); + expect.fail('endpoint not implemented'); }); // catch all error handler app.use((err, _req, res, _) => { console.error(err.message); - res.status(500).send({ message: err.message }); + expect.fail(err.message); }); const server = await new Promise((resolve) => { diff --git a/scripts/cts/testServer/replaceAllObjects.ts b/scripts/cts/testServer/replaceAllObjects.ts index d966d68805..7e900ccc88 100644 --- a/scripts/cts/testServer/replaceAllObjects.ts +++ b/scripts/cts/testServer/replaceAllObjects.ts @@ -14,7 +14,6 @@ const raoState: Record< waitTaskCount: number; tmpIndexName: string; waitingForFinalWaitTask: boolean; - tmpIndexDeleted: boolean; successful: boolean; } > = {}; @@ -23,7 +22,6 @@ export function assertValidReplaceAllObjects(expectedCount: number): void { expect(Object.keys(raoState)).to.have.length(expectedCount); for (const lang in raoState) { expect(raoState[lang].successful).to.equal(true); - expect(raoState[lang].tmpIndexDeleted).to.equal(true); } } @@ -52,7 +50,6 @@ function addRoutes(app: Express): void { waitTaskCount: 0, tmpIndexName: req.body.destination, waitingForFinalWaitTask: false, - tmpIndexDeleted: false, successful: false, }; } else { @@ -71,7 +68,6 @@ function addRoutes(app: Express): void { waitTaskCount: 6, tmpIndexName: req.params.indexName, waitingForFinalWaitTask: false, - tmpIndexDeleted: false, successful: false, }); @@ -117,16 +113,6 @@ function addRoutes(app: Express): void { res.json({ status: 'published', updatedAt: '2021-01-01T00:00:00.000Z' }); }); - - app.delete('/1/indexes/:indexName', (req, res) => { - const lang = req.params.indexName.match(/^cts_e2e_replace_all_objects_(.*)_tmp_\d+$/)?.[1] as string; - expect(raoState).to.include.keys(lang); - expect(raoState[lang].tmpIndexName).to.equal(req.params.indexName); - - raoState[lang].tmpIndexDeleted = true; - - res.json({ taskID: 456, deletedAt: '2021-01-01T00:00:00.000Z' }); - }); } export function replaceAllObjectsServer(): Promise { diff --git a/scripts/cts/testServer/replaceAllObjectsFailed.ts b/scripts/cts/testServer/replaceAllObjectsFailed.ts index 589ecc3da7..d2aa5094fa 100644 --- a/scripts/cts/testServer/replaceAllObjectsFailed.ts +++ b/scripts/cts/testServer/replaceAllObjectsFailed.ts @@ -17,6 +17,7 @@ const raoState: Record< export function assertValidReplaceAllObjectsFailed(expectedCount: number): void { const count = Object.values(raoState).filter((s) => s.successful).length; if (count !== expectedCount) { + console.log(JSON.stringify(raoState, null, 2)); throw new Error(`Expected ${expectedCount} call to replaceAllObjectsFailed, got ${count} instead.`); } } @@ -44,7 +45,7 @@ function addRoutes(app: Express): void { }); app.delete('/1/indexes/:indexName', (req, res) => { - const lang = req.params.indexName.match(/^cts_e2e_replace_all_objects_too_big_(.*)_tmp/)?.[1] as string; + const lang = req.params.indexName.match(/^cts_e2e_replace_all_objects_too_big_(.*)_tmp_\d+/)?.[1] as string; expect(raoState[lang].tmpIndexName).to.equal(req.params.indexName); raoState[lang].successful = true; diff --git a/templates/go/search_helpers.mustache b/templates/go/search_helpers.mustache index bd5ec53ed8..1bf131b234 100644 --- a/templates/go/search_helpers.mustache +++ b/templates/go/search_helpers.mustache @@ -657,39 +657,47 @@ func (c *APIClient) ReplaceAllObjects(indexName string, objects []map[string]any return nil, err } - defer func() { - _, _ = c.DeleteIndex(c.NewApiDeleteIndexRequest(tmpIndexName)) - }() - opts = append(opts, WithWaitForTasks(true)) batchResp, err := c.ChunkedBatch(tmpIndexName, objects, ACTION_ADD_OBJECT, opts...) if err != nil { + _, _ = c.DeleteIndex(c.NewApiDeleteIndexRequest(tmpIndexName)) + return nil, err } _, err = c.WaitForTask(tmpIndexName, copyResp.TaskID, toIterableOptions(opts)...) if err != nil { + _, _ = c.DeleteIndex(c.NewApiDeleteIndexRequest(tmpIndexName)) + return nil, err } copyResp, err = c.OperationIndex(c.NewApiOperationIndexRequest(indexName, NewOperationIndexParams(OPERATION_TYPE_COPY, tmpIndexName, WithOperationIndexParamsScope([]ScopeType{SCOPE_TYPE_SETTINGS, SCOPE_TYPE_RULES, SCOPE_TYPE_SYNONYMS}))), toRequestOptions(opts)...) if err != nil { + _, _ = c.DeleteIndex(c.NewApiDeleteIndexRequest(tmpIndexName)) + return nil, err } _, err = c.WaitForTask(tmpIndexName, copyResp.TaskID, toIterableOptions(opts)...) if err != nil { + _, _ = c.DeleteIndex(c.NewApiDeleteIndexRequest(tmpIndexName)) + return nil, err } moveResp, err := c.OperationIndex(c.NewApiOperationIndexRequest(tmpIndexName, NewOperationIndexParams(OPERATION_TYPE_MOVE, indexName)), toRequestOptions(opts)...) if err != nil { + _, _ = c.DeleteIndex(c.NewApiDeleteIndexRequest(tmpIndexName)) + return nil, err } _, err = c.WaitForTask(tmpIndexName, moveResp.TaskID, toIterableOptions(opts)...) if err != nil { + _, _ = c.DeleteIndex(c.NewApiDeleteIndexRequest(tmpIndexName)) + return nil, err } diff --git a/templates/java/api_helpers.mustache b/templates/java/api_helpers.mustache index eb5fc96e2b..52dadf5449 100644 --- a/templates/java/api_helpers.mustache +++ b/templates/java/api_helpers.mustache @@ -978,8 +978,10 @@ public ReplaceAllObjectsResponse replaceAllObjects( .setCopyOperationResponse(copyOperationResponse) .setBatchResponses(batchResponses) .setMoveOperationResponse(moveOperationResponse); - } finally { + } catch (Exception e) { deleteIndex(tmpIndexName); + + throw e; } } diff --git a/templates/javascript/clients/client/api/helpers.mustache b/templates/javascript/clients/client/api/helpers.mustache index 48a8119739..50ef00744d 100644 --- a/templates/javascript/clients/client/api/helpers.mustache +++ b/templates/javascript/clients/client/api/helpers.mustache @@ -459,8 +459,10 @@ async replaceAllObjects( }); return { copyOperationResponse, batchResponses, moveOperationResponse }; - } finally { + } catch (error) { await this.deleteIndex({ indexName: tmpIndexName }); + + throw error; } }, diff --git a/templates/php/api.mustache b/templates/php/api.mustache index dd2dcef79d..60a5ca43ed 100644 --- a/templates/php/api.mustache +++ b/templates/php/api.mustache @@ -481,8 +481,10 @@ use Algolia\AlgoliaSearch\Exceptions\NotFoundException; 'batchResponses' => $batchResponses, 'moveOperationResponse' => $moveOperationResponse, ]; - } finally { + } catch (\Throwable $e) { $this->deleteIndex($tmpIndexName); + + throw $e; } } @@ -636,7 +638,7 @@ use Algolia\AlgoliaSearch\Exceptions\NotFoundException; $this->getSettings($indexName); } catch (NotFoundException $e) { return false; - } catch (Exception $e) { + } catch (\Throwable $e) { throw $e; } diff --git a/templates/python/search_helpers.mustache b/templates/python/search_helpers.mustache index f2d0b6e6f7..f124c0f3dc 100644 --- a/templates/python/search_helpers.mustache +++ b/templates/python/search_helpers.mustache @@ -417,9 +417,11 @@ batch_responses=batch_responses, move_operation_response=move_operation_response, ) - finally: + except Exception as e: {{^isSyncClient}}await {{/isSyncClient}}self.delete_index(tmp_index_name) + raise e + {{^isSyncClient}}async {{/isSyncClient}}def index_exists(self, index_name: str) -> bool: """ Helper: Checks if the given `index_name` exists. diff --git a/templates/ruby/search_helpers.mustache b/templates/ruby/search_helpers.mustache index 33121ff576..e12eb3c95c 100644 --- a/templates/ruby/search_helpers.mustache +++ b/templates/ruby/search_helpers.mustache @@ -405,8 +405,10 @@ def replace_all_objects(index_name, objects, batch_size = 1000, request_options batch_responses: batch_responses, move_operation_response: move_operation_response ) - ensure + rescue Exception => e delete_index(tmp_index_name) + + raise e end end