diff --git a/src/main/java/org/codelibs/fesen/client/action/HttpCloseIndexAction.java b/src/main/java/org/codelibs/fesen/client/action/HttpCloseIndexAction.java
index 6a2a21b..21eaa2e 100644
--- a/src/main/java/org/codelibs/fesen/client/action/HttpCloseIndexAction.java
+++ b/src/main/java/org/codelibs/fesen/client/action/HttpCloseIndexAction.java
@@ -29,6 +29,7 @@
import org.opensearch.action.admin.indices.close.CloseIndexResponse.IndexResult;
import org.opensearch.action.admin.indices.close.CloseIndexResponse.ShardResult;
import org.opensearch.action.admin.indices.close.CloseIndexResponse.ShardResult.Failure;
+import org.opensearch.action.support.ActiveShardCount;
import org.opensearch.core.action.ActionListener;
import org.opensearch.core.index.Index;
import org.opensearch.core.xcontent.XContentParser;
@@ -85,6 +86,10 @@ protected CurlRequest getCurlRequest(final CloseIndexRequest request) {
if (request.masterNodeTimeout() != null) {
curlRequest.param("master_timeout", request.masterNodeTimeout().toString());
}
+ if (!ActiveShardCount.DEFAULT.equals(request.waitForActiveShards())) {
+ curlRequest.param("wait_for_active_shards", getActiveShardsCountString(request.waitForActiveShards()));
+ }
+ appendIndicesOptions(curlRequest, request.indicesOptions());
return curlRequest;
}
diff --git a/src/main/java/org/codelibs/fesen/client/action/HttpCreatePitAction.java b/src/main/java/org/codelibs/fesen/client/action/HttpCreatePitAction.java
index f3b8bfb..8a1add9 100644
--- a/src/main/java/org/codelibs/fesen/client/action/HttpCreatePitAction.java
+++ b/src/main/java/org/codelibs/fesen/client/action/HttpCreatePitAction.java
@@ -16,12 +16,15 @@
package org.codelibs.fesen.client.action;
import java.io.IOException;
+import java.util.Map;
import org.codelibs.curl.CurlRequest;
+import org.codelibs.fesen.client.EngineInfo.EngineType;
import org.codelibs.fesen.client.HttpClient;
import org.opensearch.action.search.CreatePitAction;
import org.opensearch.action.search.CreatePitRequest;
import org.opensearch.action.search.CreatePitResponse;
+import org.opensearch.action.search.ShardSearchFailure;
import org.opensearch.action.support.IndicesOptions;
import org.opensearch.core.action.ActionListener;
import org.opensearch.core.xcontent.XContentParser;
@@ -30,10 +33,18 @@
* Handles the Create Point-in-Time (PIT) API over HTTP, opening a PIT reader
* context over one or more indices that can be reused across search requests.
*
- *
This action targets the OpenSearch REST API only. The
- * {@code /{index}/_search/point_in_time} endpoint is OpenSearch-specific;
- * Elasticsearch uses a different {@code _pit} endpoint and Elasticsearch 7 has
- * no PIT support at all. No Elasticsearch adaptation is provided.
+ *
PIT is supported on OpenSearch 2.x and later via the
+ * {@code POST /{index}/_search/point_in_time} endpoint, and on Elasticsearch
+ * 7.10+ and 8.x via the {@code POST /{index}/_pit} endpoint. The two backends
+ * differ in both endpoint and query parameters (OpenSearch accepts
+ * {@code allow_partial_pit_creation}, which the Elasticsearch {@code _pit}
+ * endpoint rejects), and their response bodies differ, so the engine reported by
+ * {@link HttpClient#getEngineInfo()} selects the request and response handling.
+ *
+ *
PIT is not available on OpenSearch 1.x, and the engine cannot be
+ * determined for {@link EngineType#UNKNOWN}; in both cases
+ * {@link #execute(CreatePitRequest, ActionListener)} fails the listener with an
+ * {@link UnsupportedOperationException}.
*/
public class HttpCreatePitAction extends HttpAction {
@@ -54,13 +65,23 @@ public HttpCreatePitAction(final HttpClient client, final CreatePitAction action
/**
* Executes the create PIT request and notifies the listener with the response.
*
+ *
Fails the listener with an {@link UnsupportedOperationException} when the
+ * backend is OpenSearch 1.x or an unknown engine.
+ *
* @param request the create PIT request containing the target indices and keep-alive
* @param listener the listener notified with the response or a failure
*/
public void execute(final CreatePitRequest request, final ActionListener listener) {
+ final EngineType type = client.getEngineInfo().getType();
+ if (type == EngineType.OPENSEARCH1 || type == EngineType.UNKNOWN) {
+ listener.onFailure(new UnsupportedOperationException(
+ "Point-In-Time is not supported on " + type + " over HTTP (requires OpenSearch 2.x+ or Elasticsearch 7.10+)"));
+ return;
+ }
+ final boolean elasticsearch = type == EngineType.ELASTICSEARCH7 || type == EngineType.ELASTICSEARCH8;
getCurlRequest(request).execute(response -> {
try (final XContentParser parser = createParser(response)) {
- final CreatePitResponse pitResponse = fromXContent(parser);
+ final CreatePitResponse pitResponse = elasticsearch ? parseEsCreate(parser) : fromXContent(parser);
listener.onResponse(pitResponse);
} catch (final Exception e) {
listener.onFailure(toOpenSearchException(response, e));
@@ -69,7 +90,7 @@ public void execute(final CreatePitRequest request, final ActionListener map = parser.map();
+ final String id = (String) map.get("id");
+ if (id == null) {
+ // Not a successful open-PIT body (e.g. an error response routed through the success
+ // path); surface it as a failure instead of returning a response with a null id.
+ throw new IOException("Unexpected create PIT response (missing \"id\"): " + map);
+ }
+ int total = 0;
+ int successful = 0;
+ int skipped = 0;
+ int failed = 0;
+ final Object shardsObj = map.get("_shards");
+ if (shardsObj instanceof Map) {
+ @SuppressWarnings("unchecked")
+ final Map shards = (Map) shardsObj;
+ total = toInt(shards.get("total"));
+ successful = toInt(shards.get("successful"));
+ skipped = toInt(shards.get("skipped"));
+ failed = toInt(shards.get("failed"));
+ }
+ return new CreatePitResponse(id, System.currentTimeMillis(), total, successful, skipped, failed, ShardSearchFailure.EMPTY_ARRAY);
+ }
+
+ private static int toInt(final Object value) {
+ return value instanceof Number ? ((Number) value).intValue() : 0;
+ }
+
+ /**
+ * Builds the HTTP request for the create PIT API endpoint. The endpoint and query
+ * parameters differ between OpenSearch and Elasticsearch, selected by the engine
+ * reported by {@link HttpClient#getEngineInfo()}.
*
* @param request the create PIT request
* @return the configured curl request
*/
protected CurlRequest getCurlRequest(final CreatePitRequest request) {
- // RestCreatePitAction: POST /{index}/_search/point_in_time
+ final EngineType type = client.getEngineInfo().getType();
+ if (type == EngineType.ELASTICSEARCH7 || type == EngineType.ELASTICSEARCH8) {
+ // Elasticsearch Open PIT API: POST /{index}/_pit
+ // Note: the Elasticsearch _pit endpoint does not accept allow_partial_pit_creation /
+ // allow_partial_search_results (unrecognized parameter -> HTTP 400), so it is not sent.
+ final CurlRequest curlRequest = client.getCurlRequest(POST, "/_pit", request.indices());
+ if (request.getKeepAlive() != null) {
+ curlRequest.param("keep_alive", request.getKeepAlive().getStringRep());
+ }
+ if (request.getPreference() != null) {
+ curlRequest.param("preference", request.getPreference());
+ }
+ if (request.getRouting() != null) {
+ curlRequest.param("routing", request.getRouting());
+ }
+ final IndicesOptions esIndicesOptions = request.indicesOptions();
+ if (esIndicesOptions != null) {
+ appendIndicesOptions(curlRequest, esIndicesOptions);
+ }
+ return curlRequest;
+ }
+ // OpenSearch RestCreatePitAction: POST /{index}/_search/point_in_time
final CurlRequest curlRequest = client.getCurlRequest(POST, "/_search/point_in_time", request.indices());
if (request.getKeepAlive() != null) {
curlRequest.param("keep_alive", request.getKeepAlive().getStringRep());
diff --git a/src/main/java/org/codelibs/fesen/client/action/HttpDeleteIndexAction.java b/src/main/java/org/codelibs/fesen/client/action/HttpDeleteIndexAction.java
index 58e8931..63e4d51 100644
--- a/src/main/java/org/codelibs/fesen/client/action/HttpDeleteIndexAction.java
+++ b/src/main/java/org/codelibs/fesen/client/action/HttpDeleteIndexAction.java
@@ -74,6 +74,7 @@ protected CurlRequest getCurlRequest(final DeleteIndexRequest request) {
if (request.masterNodeTimeout() != null) {
curlRequest.param("master_timeout", request.masterNodeTimeout().toString());
}
+ appendIndicesOptions(curlRequest, request.indicesOptions());
return curlRequest;
}
}
diff --git a/src/main/java/org/codelibs/fesen/client/action/HttpDeletePitAction.java b/src/main/java/org/codelibs/fesen/client/action/HttpDeletePitAction.java
index 59e210f..8eeb886 100644
--- a/src/main/java/org/codelibs/fesen/client/action/HttpDeletePitAction.java
+++ b/src/main/java/org/codelibs/fesen/client/action/HttpDeletePitAction.java
@@ -16,12 +16,16 @@
package org.codelibs.fesen.client.action;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import org.codelibs.curl.CurlRequest;
+import org.codelibs.fesen.client.EngineInfo.EngineType;
import org.codelibs.fesen.client.HttpClient;
import org.opensearch.OpenSearchException;
import org.opensearch.action.search.DeletePitAction;
+import org.opensearch.action.search.DeletePitInfo;
import org.opensearch.action.search.DeletePitRequest;
import org.opensearch.action.search.DeletePitResponse;
import org.opensearch.common.xcontent.json.JsonXContent;
@@ -35,15 +39,20 @@
* Handles the Delete Point-in-Time (PIT) API over HTTP, releasing one or more
* PIT reader contexts.
*
- * When the request targets the special {@code _all} identifier the delete-all
- * endpoint {@code DELETE /_search/point_in_time/_all} is used with no body;
- * otherwise the PIT identifiers are sent as a {@code {"pit_id":[...]}} body to
- * {@code DELETE /_search/point_in_time}.
+ *
On OpenSearch 2.x+ a delete-by-id request sends a {@code {"pit_id":[...]}}
+ * body to {@code DELETE /_search/point_in_time}, and the special {@code _all}
+ * identifier targets {@code DELETE /_search/point_in_time/_all} with no body. On
+ * Elasticsearch 7.10+ and 8.x a delete-by-id request sends a {@code {"id":"..."}}
+ * body to {@code DELETE /_pit} (the endpoint accepts a single identifier only, so
+ * a request carrying multiple identifiers fails the listener with an
+ * {@link UnsupportedOperationException}); Elasticsearch has no delete-all
+ * endpoint, so a delete-all request likewise fails the listener with an
+ * {@link UnsupportedOperationException}.
*
- *
This action targets the OpenSearch REST API only. The
- * {@code _search/point_in_time} endpoint is OpenSearch-specific; Elasticsearch
- * uses a different {@code _pit} endpoint and Elasticsearch 7 has no PIT support
- * at all. No Elasticsearch adaptation is provided.
+ *
PIT is not available on OpenSearch 1.x, and the engine cannot be determined
+ * for {@link EngineType#UNKNOWN}; in both cases
+ * {@link #execute(DeletePitRequest, ActionListener)} fails the listener with an
+ * {@link UnsupportedOperationException}.
*/
public class HttpDeletePitAction extends HttpAction {
@@ -67,17 +76,39 @@ public HttpDeletePitAction(final HttpClient client, final DeletePitAction action
/**
* Executes the delete PIT request and notifies the listener with the response.
*
+ *
Fails the listener with an {@link UnsupportedOperationException} when the
+ * backend is OpenSearch 1.x or an unknown engine, or when a delete-all request
+ * targets Elasticsearch (which has no delete-all endpoint).
+ *
* @param request the delete PIT request containing the PIT identifiers to release
* @param listener the listener notified with the response or a failure
*/
public void execute(final DeletePitRequest request, final ActionListener listener) {
+ final EngineType type = client.getEngineInfo().getType();
+ if (type == EngineType.OPENSEARCH1 || type == EngineType.UNKNOWN) {
+ listener.onFailure(new UnsupportedOperationException(
+ "Point-In-Time is not supported on " + type + " over HTTP (requires OpenSearch 2.x+ or Elasticsearch 7.10+)"));
+ return;
+ }
+ final boolean elasticsearch = type == EngineType.ELASTICSEARCH7 || type == EngineType.ELASTICSEARCH8;
+ if (elasticsearch && isDeleteAll(request)) {
+ listener.onFailure(new UnsupportedOperationException("Delete-all PITs is not supported on Elasticsearch over HTTP"));
+ return;
+ }
+ if (elasticsearch && request.getPitIds() != null && request.getPitIds().size() > 1) {
+ // The Elasticsearch _pit endpoint accepts a single id per request (an array body is
+ // rejected with HTTP 400); callers must delete multiple PITs individually.
+ listener.onFailure(new UnsupportedOperationException(
+ "Deleting multiple PITs in a single request is not supported on Elasticsearch over HTTP; delete them individually"));
+ return;
+ }
final CurlRequest curlRequest = getCurlRequest(request);
if (!isDeleteAll(request)) {
- curlRequest.body(buildBody(request));
+ curlRequest.body(elasticsearch ? buildEsBody(request) : buildBody(request));
}
curlRequest.execute(response -> {
try (final XContentParser parser = createParser(response)) {
- final DeletePitResponse pitResponse = fromXContent(parser);
+ final DeletePitResponse pitResponse = elasticsearch ? parseEsDelete(parser, request) : fromXContent(parser);
listener.onResponse(pitResponse);
} catch (final Exception e) {
listener.onFailure(toOpenSearchException(response, e));
@@ -86,7 +117,7 @@ public void execute(final DeletePitRequest request, final ActionListener map = parser.map();
+ if (!map.containsKey("succeeded")) {
+ // Not a successful close-PIT body (e.g. an error response routed through the success
+ // path); surface it as a failure instead of reporting every id as failed.
+ throw new IOException("Unexpected delete PIT response (missing \"succeeded\"): " + map);
+ }
+ final boolean succeeded = Boolean.TRUE.equals(map.get("succeeded"));
+ final List results = new ArrayList<>();
+ for (final String pitId : request.getPitIds()) {
+ results.add(new DeletePitInfo(succeeded, pitId));
+ }
+ return new DeletePitResponse(results);
+ }
+
+ /**
+ * Builds the HTTP request for the delete PIT API endpoint. The endpoint is selected by the
+ * engine reported by {@link HttpClient#getEngineInfo()}: Elasticsearch uses {@code /_pit},
+ * OpenSearch uses {@code /_search/point_in_time} (or its {@code /_all} variant for a
+ * delete-all request).
*
* @param request the delete PIT request
* @return the configured curl request
*/
protected CurlRequest getCurlRequest(final DeletePitRequest request) {
- // RestDeletePitAction
+ final EngineType type = client.getEngineInfo().getType();
+ if (type == EngineType.ELASTICSEARCH7 || type == EngineType.ELASTICSEARCH8) {
+ // Elasticsearch Close PIT API: DELETE /_pit
+ return client.getCurlRequest(DELETE, "/_pit");
+ }
+ // OpenSearch RestDeletePitAction
if (isDeleteAll(request)) {
return client.getCurlRequest(DELETE, "/_search/point_in_time/_all");
}
@@ -124,7 +190,8 @@ protected static boolean isDeleteAll(final DeletePitRequest request) {
}
/**
- * Serializes the delete PIT request body as a {@code {"pit_id":[...]}} JSON object.
+ * Serializes the delete PIT request body as a {@code {"pit_id":[...]}} JSON object
+ * for OpenSearch.
*
* @param request the delete PIT request
* @return the JSON request body
@@ -138,4 +205,24 @@ protected String buildBody(final DeletePitRequest request) {
throw new OpenSearchException("Failed to build a request.", e);
}
}
+
+ /**
+ * Serializes the delete PIT request body as a {@code {"id":"..."}} JSON object for
+ * Elasticsearch, which accepts a single identifier only. Callers with multiple identifiers
+ * are rejected earlier in {@link #execute(DeletePitRequest, ActionListener)}.
+ *
+ * @param request the delete PIT request
+ * @return the JSON request body
+ */
+ protected String buildEsBody(final DeletePitRequest request) {
+ try (final XContentBuilder builder = JsonXContent.contentBuilder()) {
+ builder.startObject();
+ builder.field("id", request.getPitIds().get(0));
+ builder.endObject();
+ builder.flush();
+ return BytesReference.bytes(builder).utf8ToString();
+ } catch (final IOException e) {
+ throw new OpenSearchException("Failed to build a request.", e);
+ }
+ }
}
diff --git a/src/main/java/org/codelibs/fesen/client/action/HttpFlushAction.java b/src/main/java/org/codelibs/fesen/client/action/HttpFlushAction.java
index 40388dd..6dc94dd 100644
--- a/src/main/java/org/codelibs/fesen/client/action/HttpFlushAction.java
+++ b/src/main/java/org/codelibs/fesen/client/action/HttpFlushAction.java
@@ -70,6 +70,7 @@ protected CurlRequest getCurlRequest(final FlushRequest request) {
final CurlRequest curlRequest = client.getCurlRequest(POST, "/_flush", request.indices());
curlRequest.param("wait_if_ongoing", Boolean.toString(request.waitIfOngoing()));
curlRequest.param("force", Boolean.toString(request.force()));
+ appendIndicesOptions(curlRequest, request.indicesOptions());
return curlRequest;
}
}
diff --git a/src/main/java/org/codelibs/fesen/client/action/HttpForceMergeAction.java b/src/main/java/org/codelibs/fesen/client/action/HttpForceMergeAction.java
index 49dea06..036f498 100644
--- a/src/main/java/org/codelibs/fesen/client/action/HttpForceMergeAction.java
+++ b/src/main/java/org/codelibs/fesen/client/action/HttpForceMergeAction.java
@@ -16,6 +16,7 @@
package org.codelibs.fesen.client.action;
import org.codelibs.curl.CurlRequest;
+import org.codelibs.fesen.client.EngineInfo.EngineType;
import org.codelibs.fesen.client.HttpClient;
import org.opensearch.action.admin.indices.forcemerge.ForceMergeAction;
import org.opensearch.action.admin.indices.forcemerge.ForceMergeRequest;
@@ -68,8 +69,16 @@ public void execute(final ForceMergeRequest request, final ActionListenerThis action targets the OpenSearch REST API only. The
- * {@code /_search/point_in_time/_all} endpoint is OpenSearch-specific;
- * Elasticsearch uses a different {@code _pit} endpoint and Elasticsearch 7 has
- * no PIT support at all. No Elasticsearch adaptation is provided.
+ * This API is OpenSearch-specific and supported only on OpenSearch 2.x and
+ * later via the {@code GET /_search/point_in_time/_all} endpoint. Elasticsearch
+ * (7.x and 8.x) has no equivalent list-all endpoint, OpenSearch 1.x predates
+ * PIT, and the engine cannot be determined for {@link EngineType#UNKNOWN};
+ * in all of these cases {@link #execute(GetAllPitNodesRequest, ActionListener)}
+ * fails the listener with an {@link UnsupportedOperationException}.
*/
public class HttpGetAllPitsAction extends HttpAction {
@@ -53,10 +56,19 @@ public HttpGetAllPitsAction(final HttpClient client, final GetAllPitsAction acti
/**
* Executes the get all PITs request and notifies the listener with the response.
*
+ *
Fails the listener with an {@link UnsupportedOperationException} on any backend
+ * other than OpenSearch 2.x+, since listing all PITs is OpenSearch-specific.
+ *
* @param request the get all PITs request
* @param listener the listener notified with the response or a failure
*/
public void execute(final GetAllPitNodesRequest request, final ActionListener listener) {
+ final EngineType type = client.getEngineInfo().getType();
+ if (type != EngineType.OPENSEARCH2 && type != EngineType.OPENSEARCH3) {
+ listener.onFailure(new UnsupportedOperationException(
+ "Listing all PITs is not supported on " + type + " over HTTP (OpenSearch 2.x+ only)"));
+ return;
+ }
getCurlRequest(request).execute(response -> {
try (final XContentParser parser = createParser(response)) {
final GetAllPitNodesResponse pitResponse = fromXContent(parser);
diff --git a/src/main/java/org/codelibs/fesen/client/action/HttpGetMappingsAction.java b/src/main/java/org/codelibs/fesen/client/action/HttpGetMappingsAction.java
index 819ab3d..0f42eda 100644
--- a/src/main/java/org/codelibs/fesen/client/action/HttpGetMappingsAction.java
+++ b/src/main/java/org/codelibs/fesen/client/action/HttpGetMappingsAction.java
@@ -84,6 +84,7 @@ protected CurlRequest getCurlRequest(final GetMappingsRequest request) {
if (client.getEngineInfo().getType() != EngineType.ELASTICSEARCH8) {
curlRequest.param("local", Boolean.toString(request.local()));
}
+ appendIndicesOptions(curlRequest, request.indicesOptions());
return curlRequest;
}
diff --git a/src/main/java/org/codelibs/fesen/client/action/HttpIndicesAliasesAction.java b/src/main/java/org/codelibs/fesen/client/action/HttpIndicesAliasesAction.java
index d419f21..555c914 100644
--- a/src/main/java/org/codelibs/fesen/client/action/HttpIndicesAliasesAction.java
+++ b/src/main/java/org/codelibs/fesen/client/action/HttpIndicesAliasesAction.java
@@ -22,11 +22,11 @@
import org.opensearch.OpenSearchException;
import org.opensearch.action.admin.indices.alias.IndicesAliasesAction;
import org.opensearch.action.admin.indices.alias.IndicesAliasesRequest;
-import org.opensearch.action.admin.indices.alias.IndicesAliasesRequest.AliasActions;
import org.opensearch.action.support.clustermanager.AcknowledgedResponse;
import org.opensearch.common.xcontent.XContentFactory;
import org.opensearch.core.action.ActionListener;
import org.opensearch.core.common.bytes.BytesReference;
+import org.opensearch.core.xcontent.ToXContent;
import org.opensearch.core.xcontent.XContentBuilder;
import org.opensearch.core.xcontent.XContentParser;
@@ -57,30 +57,7 @@ public HttpIndicesAliasesAction(final HttpClient client, final IndicesAliasesAct
* @param listener the listener to notify with the response or a failure
*/
public void execute(final IndicesAliasesRequest request, final ActionListener listener) {
- String source = null;
- try (final XContentBuilder builder = XContentFactory.jsonBuilder().startObject().startArray("actions")) {
- for (final AliasActions aliasAction : request.getAliasActions()) {
- builder.startObject().startObject(aliasAction.actionType().toString().toLowerCase());
- builder.array("indices", aliasAction.indices());
- builder.array("aliases", aliasAction.aliases());
- if (aliasAction.filter() != null) {
- builder.field("filter", aliasAction.filter());
- }
- if (aliasAction.indexRouting() != null) {
- builder.field("index_routing", aliasAction.indexRouting());
- }
- if (aliasAction.searchRouting() != null) {
- builder.field("search_routing", aliasAction.searchRouting());
- }
- builder.endObject().endObject();
- }
- builder.endArray().endObject();
- builder.flush();
- source = BytesReference.bytes(builder).utf8ToString();
- } catch (final IOException e) {
- throw new OpenSearchException("Failed to parse a request.", e);
- }
- getCurlRequest(request).body(source).execute(response -> {
+ getCurlRequest(request).execute(response -> {
try (final XContentParser parser = createParser(response)) {
final AcknowledgedResponse indicesAliasesResponse = AcknowledgedResponse.fromXContent(parser);
listener.onResponse(indicesAliasesResponse);
@@ -105,6 +82,14 @@ protected CurlRequest getCurlRequest(final IndicesAliasesRequest request) {
if (request.masterNodeTimeout() != null) {
curlRequest.param("master_timeout", request.masterNodeTimeout().toString());
}
- return curlRequest;
+ String source = null;
+ try (final XContentBuilder builder = XContentFactory.jsonBuilder()) {
+ request.toXContent(builder, ToXContent.EMPTY_PARAMS);
+ builder.flush();
+ source = BytesReference.bytes(builder).utf8ToString();
+ } catch (final IOException e) {
+ throw new OpenSearchException("Failed to parse a request.", e);
+ }
+ return curlRequest.body(source);
}
}
diff --git a/src/main/java/org/codelibs/fesen/client/action/HttpOpenIndexAction.java b/src/main/java/org/codelibs/fesen/client/action/HttpOpenIndexAction.java
index 172072a..59a83f4 100644
--- a/src/main/java/org/codelibs/fesen/client/action/HttpOpenIndexAction.java
+++ b/src/main/java/org/codelibs/fesen/client/action/HttpOpenIndexAction.java
@@ -76,8 +76,9 @@ protected CurlRequest getCurlRequest(final OpenIndexRequest request) {
curlRequest.param("master_timeout", request.masterNodeTimeout().toString());
}
if (!ActiveShardCount.DEFAULT.equals(request.waitForActiveShards())) {
- curlRequest.param("wait_for_active_shards", request.waitForActiveShards().toString());
+ curlRequest.param("wait_for_active_shards", getActiveShardsCountString(request.waitForActiveShards()));
}
+ appendIndicesOptions(curlRequest, request.indicesOptions());
return curlRequest;
}
}
diff --git a/src/main/java/org/codelibs/fesen/client/action/HttpRefreshAction.java b/src/main/java/org/codelibs/fesen/client/action/HttpRefreshAction.java
index 21d2b46..4c96d0a 100644
--- a/src/main/java/org/codelibs/fesen/client/action/HttpRefreshAction.java
+++ b/src/main/java/org/codelibs/fesen/client/action/HttpRefreshAction.java
@@ -66,6 +66,8 @@ public void execute(final RefreshRequest request, final ActionListener= 0) {
- curlRequest.param("version", Long.toString(request.version()));
- }
- if (!VersionType.INTERNAL.equals(request.versionType())) {
- curlRequest.param("version_type", request.versionType().name().toLowerCase(Locale.ROOT));
- }
return curlRequest;
}
}
diff --git a/src/test/java/org/codelibs/fesen/client/Elasticsearch7ClientTest.java b/src/test/java/org/codelibs/fesen/client/Elasticsearch7ClientTest.java
index 45fd0ee..a396568 100644
--- a/src/test/java/org/codelibs/fesen/client/Elasticsearch7ClientTest.java
+++ b/src/test/java/org/codelibs/fesen/client/Elasticsearch7ClientTest.java
@@ -18,6 +18,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.opensearch.core.action.ActionListener.wrap;
@@ -1298,4 +1299,136 @@ void test_info() throws Exception {
assertEquals("fesen", mainResponse.getClusterName().value());
}
}
+
+ @Test
+ void test_reindex() throws Exception {
+ final String srcIndex = "test_reindex_src";
+ final String destIndex = "test_reindex_dest";
+ final BulkRequestBuilder bulkRequestBuilder = client.prepareBulk();
+ for (int i = 1; i <= 3; i++) {
+ bulkRequestBuilder.add(client.prepareIndex().setIndex(srcIndex).setId(String.valueOf(i)).setSource("{\"value\":" + i + "}",
+ XContentType.JSON));
+ }
+ bulkRequestBuilder.execute().actionGet();
+ client.admin().indices().prepareRefresh(srcIndex).execute().actionGet();
+
+ final org.opensearch.index.reindex.ReindexRequest reindexRequest = new org.opensearch.index.reindex.ReindexRequest();
+ reindexRequest.setSourceIndices(srcIndex);
+ reindexRequest.setDestIndex(destIndex);
+ reindexRequest.setRefresh(true);
+ final org.opensearch.index.reindex.BulkByScrollResponse response =
+ client.execute(org.opensearch.index.reindex.ReindexAction.INSTANCE, reindexRequest).actionGet();
+ assertEquals(3L, response.getTotal());
+ assertEquals(3L, response.getCreated());
+ assertEquals(0, response.getBulkFailures().size());
+
+ client.admin().indices().prepareRefresh(destIndex).execute().actionGet();
+ final SearchResponse searchResponse = client.prepareSearch(destIndex).setQuery(QueryBuilders.matchAllQuery()).execute().actionGet();
+ assertEquals(3L, searchResponse.getHits().getTotalHits().value());
+ }
+
+ @Test
+ void test_update_by_query() throws Exception {
+ final String index = "test_update_by_query";
+ final BulkRequestBuilder bulkRequestBuilder = client.prepareBulk();
+ for (int i = 1; i <= 3; i++) {
+ bulkRequestBuilder.add(client.prepareIndex().setIndex(index).setId(String.valueOf(i))
+ .setSource("{\"value\":" + i + ",\"status\":\"new\"}", XContentType.JSON));
+ }
+ bulkRequestBuilder.execute().actionGet();
+ client.admin().indices().prepareRefresh(index).execute().actionGet();
+
+ final org.opensearch.index.reindex.UpdateByQueryRequest request = new org.opensearch.index.reindex.UpdateByQueryRequest(index);
+ request.setQuery(QueryBuilders.matchAllQuery());
+ request.setScript(new org.opensearch.script.Script("ctx._source.status = 'updated'"));
+ request.setConflicts("proceed");
+ request.setRefresh(true);
+ final org.opensearch.index.reindex.BulkByScrollResponse response =
+ client.execute(org.opensearch.index.reindex.UpdateByQueryAction.INSTANCE, request).actionGet();
+ assertEquals(3L, response.getTotal());
+ assertEquals(3L, response.getUpdated());
+ assertEquals(0, response.getBulkFailures().size());
+
+ final SearchResponse searchResponse =
+ client.prepareSearch(index).setQuery(QueryBuilders.matchQuery("status", "updated")).execute().actionGet();
+ assertEquals(3L, searchResponse.getHits().getTotalHits().value());
+ }
+
+ @Test
+ void test_delete_by_query() throws Exception {
+ final String index = "test_delete_by_query";
+ final BulkRequestBuilder bulkRequestBuilder = client.prepareBulk();
+ for (int i = 1; i <= 5; i++) {
+ final String group = i <= 2 ? "a" : "b";
+ bulkRequestBuilder.add(client.prepareIndex().setIndex(index).setId(String.valueOf(i))
+ .setSource("{\"value\":" + i + ",\"group\":\"" + group + "\"}", XContentType.JSON));
+ }
+ bulkRequestBuilder.execute().actionGet();
+ client.admin().indices().prepareRefresh(index).execute().actionGet();
+
+ final org.opensearch.index.reindex.DeleteByQueryRequest request = new org.opensearch.index.reindex.DeleteByQueryRequest(index);
+ request.setQuery(QueryBuilders.matchQuery("group", "a"));
+ request.setConflicts("proceed");
+ request.setRefresh(true);
+ final org.opensearch.index.reindex.BulkByScrollResponse response =
+ client.execute(org.opensearch.index.reindex.DeleteByQueryAction.INSTANCE, request).actionGet();
+ assertEquals(2L, response.getDeleted());
+ assertEquals(0, response.getBulkFailures().size());
+
+ final SearchResponse searchResponse = client.prepareSearch(index).setQuery(QueryBuilders.matchAllQuery()).execute().actionGet();
+ assertEquals(3L, searchResponse.getHits().getTotalHits().value());
+ }
+
+ @Test
+ void test_resolve_index() throws Exception {
+ final String index = "test_resolve_index";
+ final String alias = "test_resolve_index_alias";
+ client.admin().indices().prepareCreate(index).addAlias(new Alias(alias)).execute().actionGet();
+
+ final org.opensearch.action.admin.indices.resolve.ResolveIndexAction.Request request =
+ new org.opensearch.action.admin.indices.resolve.ResolveIndexAction.Request(new String[] { "test_resolve_index*" });
+ final org.opensearch.action.admin.indices.resolve.ResolveIndexAction.Response response =
+ client.execute(org.opensearch.action.admin.indices.resolve.ResolveIndexAction.INSTANCE, request).actionGet();
+ assertTrue(response.getIndices().stream().anyMatch(i -> index.equals(i.getName())));
+ assertTrue(response.getAliases().stream().anyMatch(a -> alias.equals(a.getName())));
+ }
+
+ @Test
+ void test_pit_create_delete() throws Exception {
+ final String index = "test_pit_create_delete";
+ final BulkRequestBuilder bulkRequestBuilder = client.prepareBulk();
+ for (int i = 1; i <= 3; i++) {
+ bulkRequestBuilder.add(
+ client.prepareIndex().setIndex(index).setId(String.valueOf(i)).setSource("{\"value\":" + i + "}", XContentType.JSON));
+ }
+ bulkRequestBuilder.execute().actionGet();
+ client.admin().indices().prepareRefresh(index).execute().actionGet();
+
+ final org.opensearch.action.search.CreatePitRequest createPitRequest =
+ new org.opensearch.action.search.CreatePitRequest(TimeValue.timeValueMinutes(1), true, index);
+ final org.opensearch.action.search.CreatePitResponse createPitResponse =
+ client.execute(org.opensearch.action.search.CreatePitAction.INSTANCE, createPitRequest).actionGet();
+ final String pitId = createPitResponse.getId();
+ assertNotNull(pitId);
+ assertFalse(pitId.isEmpty());
+
+ final org.opensearch.action.search.DeletePitResponse deletePitResponse = client
+ .execute(org.opensearch.action.search.DeletePitAction.INSTANCE, new org.opensearch.action.search.DeletePitRequest(pitId))
+ .actionGet();
+ assertTrue(deletePitResponse.getDeletePitResults().stream().allMatch(org.opensearch.action.search.DeletePitInfo::isSuccessful));
+ }
+
+ @Test
+ void test_pit_get_all_unsupported() throws Exception {
+ assertThrows(UnsupportedOperationException.class, () -> client
+ .execute(org.opensearch.action.search.GetAllPitsAction.INSTANCE, new org.opensearch.action.search.GetAllPitNodesRequest())
+ .actionGet());
+ }
+
+ @Test
+ void test_pit_delete_all_unsupported() throws Exception {
+ assertThrows(UnsupportedOperationException.class, () -> client
+ .execute(org.opensearch.action.search.DeletePitAction.INSTANCE, new org.opensearch.action.search.DeletePitRequest("_all"))
+ .actionGet());
+ }
}
diff --git a/src/test/java/org/codelibs/fesen/client/Elasticsearch8ClientTest.java b/src/test/java/org/codelibs/fesen/client/Elasticsearch8ClientTest.java
index 3426179..0d83239 100644
--- a/src/test/java/org/codelibs/fesen/client/Elasticsearch8ClientTest.java
+++ b/src/test/java/org/codelibs/fesen/client/Elasticsearch8ClientTest.java
@@ -18,6 +18,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.opensearch.core.action.ActionListener.wrap;
@@ -1303,4 +1304,136 @@ void test_info() throws Exception {
assertEquals("fesen", mainResponse.getClusterName().value());
}
}
+
+ @Test
+ void test_reindex() throws Exception {
+ final String srcIndex = "test_reindex_src";
+ final String destIndex = "test_reindex_dest";
+ final BulkRequestBuilder bulkRequestBuilder = client.prepareBulk();
+ for (int i = 1; i <= 3; i++) {
+ bulkRequestBuilder.add(client.prepareIndex().setIndex(srcIndex).setId(String.valueOf(i)).setSource("{\"value\":" + i + "}",
+ XContentType.JSON));
+ }
+ bulkRequestBuilder.execute().actionGet();
+ client.admin().indices().prepareRefresh(srcIndex).execute().actionGet();
+
+ final org.opensearch.index.reindex.ReindexRequest reindexRequest = new org.opensearch.index.reindex.ReindexRequest();
+ reindexRequest.setSourceIndices(srcIndex);
+ reindexRequest.setDestIndex(destIndex);
+ reindexRequest.setRefresh(true);
+ final org.opensearch.index.reindex.BulkByScrollResponse response =
+ client.execute(org.opensearch.index.reindex.ReindexAction.INSTANCE, reindexRequest).actionGet();
+ assertEquals(3L, response.getTotal());
+ assertEquals(3L, response.getCreated());
+ assertEquals(0, response.getBulkFailures().size());
+
+ client.admin().indices().prepareRefresh(destIndex).execute().actionGet();
+ final SearchResponse searchResponse = client.prepareSearch(destIndex).setQuery(QueryBuilders.matchAllQuery()).execute().actionGet();
+ assertEquals(3L, searchResponse.getHits().getTotalHits().value());
+ }
+
+ @Test
+ void test_update_by_query() throws Exception {
+ final String index = "test_update_by_query";
+ final BulkRequestBuilder bulkRequestBuilder = client.prepareBulk();
+ for (int i = 1; i <= 3; i++) {
+ bulkRequestBuilder.add(client.prepareIndex().setIndex(index).setId(String.valueOf(i))
+ .setSource("{\"value\":" + i + ",\"status\":\"new\"}", XContentType.JSON));
+ }
+ bulkRequestBuilder.execute().actionGet();
+ client.admin().indices().prepareRefresh(index).execute().actionGet();
+
+ final org.opensearch.index.reindex.UpdateByQueryRequest request = new org.opensearch.index.reindex.UpdateByQueryRequest(index);
+ request.setQuery(QueryBuilders.matchAllQuery());
+ request.setScript(new org.opensearch.script.Script("ctx._source.status = 'updated'"));
+ request.setConflicts("proceed");
+ request.setRefresh(true);
+ final org.opensearch.index.reindex.BulkByScrollResponse response =
+ client.execute(org.opensearch.index.reindex.UpdateByQueryAction.INSTANCE, request).actionGet();
+ assertEquals(3L, response.getTotal());
+ assertEquals(3L, response.getUpdated());
+ assertEquals(0, response.getBulkFailures().size());
+
+ final SearchResponse searchResponse =
+ client.prepareSearch(index).setQuery(QueryBuilders.matchQuery("status", "updated")).execute().actionGet();
+ assertEquals(3L, searchResponse.getHits().getTotalHits().value());
+ }
+
+ @Test
+ void test_delete_by_query() throws Exception {
+ final String index = "test_delete_by_query";
+ final BulkRequestBuilder bulkRequestBuilder = client.prepareBulk();
+ for (int i = 1; i <= 5; i++) {
+ final String group = i <= 2 ? "a" : "b";
+ bulkRequestBuilder.add(client.prepareIndex().setIndex(index).setId(String.valueOf(i))
+ .setSource("{\"value\":" + i + ",\"group\":\"" + group + "\"}", XContentType.JSON));
+ }
+ bulkRequestBuilder.execute().actionGet();
+ client.admin().indices().prepareRefresh(index).execute().actionGet();
+
+ final org.opensearch.index.reindex.DeleteByQueryRequest request = new org.opensearch.index.reindex.DeleteByQueryRequest(index);
+ request.setQuery(QueryBuilders.matchQuery("group", "a"));
+ request.setConflicts("proceed");
+ request.setRefresh(true);
+ final org.opensearch.index.reindex.BulkByScrollResponse response =
+ client.execute(org.opensearch.index.reindex.DeleteByQueryAction.INSTANCE, request).actionGet();
+ assertEquals(2L, response.getDeleted());
+ assertEquals(0, response.getBulkFailures().size());
+
+ final SearchResponse searchResponse = client.prepareSearch(index).setQuery(QueryBuilders.matchAllQuery()).execute().actionGet();
+ assertEquals(3L, searchResponse.getHits().getTotalHits().value());
+ }
+
+ @Test
+ void test_resolve_index() throws Exception {
+ final String index = "test_resolve_index";
+ final String alias = "test_resolve_index_alias";
+ client.admin().indices().prepareCreate(index).addAlias(new Alias(alias)).execute().actionGet();
+
+ final org.opensearch.action.admin.indices.resolve.ResolveIndexAction.Request request =
+ new org.opensearch.action.admin.indices.resolve.ResolveIndexAction.Request(new String[] { "test_resolve_index*" });
+ final org.opensearch.action.admin.indices.resolve.ResolveIndexAction.Response response =
+ client.execute(org.opensearch.action.admin.indices.resolve.ResolveIndexAction.INSTANCE, request).actionGet();
+ assertTrue(response.getIndices().stream().anyMatch(i -> index.equals(i.getName())));
+ assertTrue(response.getAliases().stream().anyMatch(a -> alias.equals(a.getName())));
+ }
+
+ @Test
+ void test_pit_create_delete() throws Exception {
+ final String index = "test_pit_create_delete";
+ final BulkRequestBuilder bulkRequestBuilder = client.prepareBulk();
+ for (int i = 1; i <= 3; i++) {
+ bulkRequestBuilder.add(
+ client.prepareIndex().setIndex(index).setId(String.valueOf(i)).setSource("{\"value\":" + i + "}", XContentType.JSON));
+ }
+ bulkRequestBuilder.execute().actionGet();
+ client.admin().indices().prepareRefresh(index).execute().actionGet();
+
+ final org.opensearch.action.search.CreatePitRequest createPitRequest =
+ new org.opensearch.action.search.CreatePitRequest(TimeValue.timeValueMinutes(1), true, index);
+ final org.opensearch.action.search.CreatePitResponse createPitResponse =
+ client.execute(org.opensearch.action.search.CreatePitAction.INSTANCE, createPitRequest).actionGet();
+ final String pitId = createPitResponse.getId();
+ assertNotNull(pitId);
+ assertFalse(pitId.isEmpty());
+
+ final org.opensearch.action.search.DeletePitResponse deletePitResponse = client
+ .execute(org.opensearch.action.search.DeletePitAction.INSTANCE, new org.opensearch.action.search.DeletePitRequest(pitId))
+ .actionGet();
+ assertTrue(deletePitResponse.getDeletePitResults().stream().allMatch(org.opensearch.action.search.DeletePitInfo::isSuccessful));
+ }
+
+ @Test
+ void test_pit_get_all_unsupported() throws Exception {
+ assertThrows(UnsupportedOperationException.class, () -> client
+ .execute(org.opensearch.action.search.GetAllPitsAction.INSTANCE, new org.opensearch.action.search.GetAllPitNodesRequest())
+ .actionGet());
+ }
+
+ @Test
+ void test_pit_delete_all_unsupported() throws Exception {
+ assertThrows(UnsupportedOperationException.class, () -> client
+ .execute(org.opensearch.action.search.DeletePitAction.INSTANCE, new org.opensearch.action.search.DeletePitRequest("_all"))
+ .actionGet());
+ }
}
diff --git a/src/test/java/org/codelibs/fesen/client/OpenSearch1ClientTest.java b/src/test/java/org/codelibs/fesen/client/OpenSearch1ClientTest.java
index f83d976..040aed1 100644
--- a/src/test/java/org/codelibs/fesen/client/OpenSearch1ClientTest.java
+++ b/src/test/java/org/codelibs/fesen/client/OpenSearch1ClientTest.java
@@ -18,6 +18,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.opensearch.core.action.ActionListener.wrap;
@@ -1299,4 +1300,105 @@ void test_info() throws Exception {
assertEquals("fesen", mainResponse.getClusterName().value());
}
}
+
+ @Test
+ void test_reindex() throws Exception {
+ final String srcIndex = "test_reindex_src";
+ final String destIndex = "test_reindex_dest";
+ final BulkRequestBuilder bulkRequestBuilder = client.prepareBulk();
+ for (int i = 1; i <= 3; i++) {
+ bulkRequestBuilder.add(client.prepareIndex().setIndex(srcIndex).setId(String.valueOf(i)).setSource("{\"value\":" + i + "}",
+ XContentType.JSON));
+ }
+ bulkRequestBuilder.execute().actionGet();
+ client.admin().indices().prepareRefresh(srcIndex).execute().actionGet();
+
+ final org.opensearch.index.reindex.ReindexRequest reindexRequest = new org.opensearch.index.reindex.ReindexRequest();
+ reindexRequest.setSourceIndices(srcIndex);
+ reindexRequest.setDestIndex(destIndex);
+ reindexRequest.setRefresh(true);
+ final org.opensearch.index.reindex.BulkByScrollResponse response =
+ client.execute(org.opensearch.index.reindex.ReindexAction.INSTANCE, reindexRequest).actionGet();
+ assertEquals(3L, response.getTotal());
+ assertEquals(3L, response.getCreated());
+ assertEquals(0, response.getBulkFailures().size());
+
+ client.admin().indices().prepareRefresh(destIndex).execute().actionGet();
+ final SearchResponse searchResponse = client.prepareSearch(destIndex).setQuery(QueryBuilders.matchAllQuery()).execute().actionGet();
+ assertEquals(3L, searchResponse.getHits().getTotalHits().value());
+ }
+
+ @Test
+ void test_update_by_query() throws Exception {
+ final String index = "test_update_by_query";
+ final BulkRequestBuilder bulkRequestBuilder = client.prepareBulk();
+ for (int i = 1; i <= 3; i++) {
+ bulkRequestBuilder.add(client.prepareIndex().setIndex(index).setId(String.valueOf(i))
+ .setSource("{\"value\":" + i + ",\"status\":\"new\"}", XContentType.JSON));
+ }
+ bulkRequestBuilder.execute().actionGet();
+ client.admin().indices().prepareRefresh(index).execute().actionGet();
+
+ final org.opensearch.index.reindex.UpdateByQueryRequest request = new org.opensearch.index.reindex.UpdateByQueryRequest(index);
+ request.setQuery(QueryBuilders.matchAllQuery());
+ request.setScript(new org.opensearch.script.Script("ctx._source.status = 'updated'"));
+ request.setConflicts("proceed");
+ request.setRefresh(true);
+ final org.opensearch.index.reindex.BulkByScrollResponse response =
+ client.execute(org.opensearch.index.reindex.UpdateByQueryAction.INSTANCE, request).actionGet();
+ assertEquals(3L, response.getTotal());
+ assertEquals(3L, response.getUpdated());
+ assertEquals(0, response.getBulkFailures().size());
+
+ final SearchResponse searchResponse =
+ client.prepareSearch(index).setQuery(QueryBuilders.matchQuery("status", "updated")).execute().actionGet();
+ assertEquals(3L, searchResponse.getHits().getTotalHits().value());
+ }
+
+ @Test
+ void test_delete_by_query() throws Exception {
+ final String index = "test_delete_by_query";
+ final BulkRequestBuilder bulkRequestBuilder = client.prepareBulk();
+ for (int i = 1; i <= 5; i++) {
+ final String group = i <= 2 ? "a" : "b";
+ bulkRequestBuilder.add(client.prepareIndex().setIndex(index).setId(String.valueOf(i))
+ .setSource("{\"value\":" + i + ",\"group\":\"" + group + "\"}", XContentType.JSON));
+ }
+ bulkRequestBuilder.execute().actionGet();
+ client.admin().indices().prepareRefresh(index).execute().actionGet();
+
+ final org.opensearch.index.reindex.DeleteByQueryRequest request = new org.opensearch.index.reindex.DeleteByQueryRequest(index);
+ request.setQuery(QueryBuilders.matchQuery("group", "a"));
+ request.setConflicts("proceed");
+ request.setRefresh(true);
+ final org.opensearch.index.reindex.BulkByScrollResponse response =
+ client.execute(org.opensearch.index.reindex.DeleteByQueryAction.INSTANCE, request).actionGet();
+ assertEquals(2L, response.getDeleted());
+ assertEquals(0, response.getBulkFailures().size());
+
+ final SearchResponse searchResponse = client.prepareSearch(index).setQuery(QueryBuilders.matchAllQuery()).execute().actionGet();
+ assertEquals(3L, searchResponse.getHits().getTotalHits().value());
+ }
+
+ @Test
+ void test_resolve_index() throws Exception {
+ final String index = "test_resolve_index";
+ final String alias = "test_resolve_index_alias";
+ client.admin().indices().prepareCreate(index).addAlias(new Alias(alias)).execute().actionGet();
+
+ final org.opensearch.action.admin.indices.resolve.ResolveIndexAction.Request request =
+ new org.opensearch.action.admin.indices.resolve.ResolveIndexAction.Request(new String[] { "test_resolve_index*" });
+ final org.opensearch.action.admin.indices.resolve.ResolveIndexAction.Response response =
+ client.execute(org.opensearch.action.admin.indices.resolve.ResolveIndexAction.INSTANCE, request).actionGet();
+ assertTrue(response.getIndices().stream().anyMatch(i -> index.equals(i.getName())));
+ assertTrue(response.getAliases().stream().anyMatch(a -> alias.equals(a.getName())));
+ }
+
+ @Test
+ void test_pit_unsupported() throws Exception {
+ final org.opensearch.action.search.CreatePitRequest createPitRequest =
+ new org.opensearch.action.search.CreatePitRequest(TimeValue.timeValueMinutes(1), true, "test_pit_unsupported");
+ assertThrows(UnsupportedOperationException.class,
+ () -> client.execute(org.opensearch.action.search.CreatePitAction.INSTANCE, createPitRequest).actionGet());
+ }
}
diff --git a/src/test/java/org/codelibs/fesen/client/OpenSearch2ClientTest.java b/src/test/java/org/codelibs/fesen/client/OpenSearch2ClientTest.java
index 5ac1b16..4ab720a 100644
--- a/src/test/java/org/codelibs/fesen/client/OpenSearch2ClientTest.java
+++ b/src/test/java/org/codelibs/fesen/client/OpenSearch2ClientTest.java
@@ -1371,4 +1371,155 @@ void test_info() throws Exception {
assertEquals("fesen", mainResponse.getClusterName().value());
}
}
+
+ @Test
+ void test_reindex() throws Exception {
+ final String srcIndex = "test_reindex_src";
+ final String destIndex = "test_reindex_dest";
+ final BulkRequestBuilder bulkRequestBuilder = client.prepareBulk();
+ for (int i = 1; i <= 3; i++) {
+ bulkRequestBuilder.add(client.prepareIndex().setIndex(srcIndex).setId(String.valueOf(i)).setSource("{\"value\":" + i + "}",
+ XContentType.JSON));
+ }
+ bulkRequestBuilder.execute().actionGet();
+ client.admin().indices().prepareRefresh(srcIndex).execute().actionGet();
+
+ final org.opensearch.index.reindex.ReindexRequest reindexRequest = new org.opensearch.index.reindex.ReindexRequest();
+ reindexRequest.setSourceIndices(srcIndex);
+ reindexRequest.setDestIndex(destIndex);
+ reindexRequest.setRefresh(true);
+ final org.opensearch.index.reindex.BulkByScrollResponse response =
+ client.execute(org.opensearch.index.reindex.ReindexAction.INSTANCE, reindexRequest).actionGet();
+ assertEquals(3L, response.getTotal());
+ assertEquals(3L, response.getCreated());
+ assertEquals(0, response.getBulkFailures().size());
+
+ client.admin().indices().prepareRefresh(destIndex).execute().actionGet();
+ final SearchResponse searchResponse = client.prepareSearch(destIndex).setQuery(QueryBuilders.matchAllQuery()).execute().actionGet();
+ assertEquals(3L, searchResponse.getHits().getTotalHits().value());
+ }
+
+ @Test
+ void test_update_by_query() throws Exception {
+ final String index = "test_update_by_query";
+ final BulkRequestBuilder bulkRequestBuilder = client.prepareBulk();
+ for (int i = 1; i <= 3; i++) {
+ bulkRequestBuilder.add(client.prepareIndex().setIndex(index).setId(String.valueOf(i))
+ .setSource("{\"value\":" + i + ",\"status\":\"new\"}", XContentType.JSON));
+ }
+ bulkRequestBuilder.execute().actionGet();
+ client.admin().indices().prepareRefresh(index).execute().actionGet();
+
+ final org.opensearch.index.reindex.UpdateByQueryRequest request = new org.opensearch.index.reindex.UpdateByQueryRequest(index);
+ request.setQuery(QueryBuilders.matchAllQuery());
+ request.setScript(new org.opensearch.script.Script("ctx._source.status = 'updated'"));
+ request.setConflicts("proceed");
+ request.setRefresh(true);
+ final org.opensearch.index.reindex.BulkByScrollResponse response =
+ client.execute(org.opensearch.index.reindex.UpdateByQueryAction.INSTANCE, request).actionGet();
+ assertEquals(3L, response.getTotal());
+ assertEquals(3L, response.getUpdated());
+ assertEquals(0, response.getBulkFailures().size());
+
+ final SearchResponse searchResponse =
+ client.prepareSearch(index).setQuery(QueryBuilders.matchQuery("status", "updated")).execute().actionGet();
+ assertEquals(3L, searchResponse.getHits().getTotalHits().value());
+ }
+
+ @Test
+ void test_delete_by_query() throws Exception {
+ final String index = "test_delete_by_query";
+ final BulkRequestBuilder bulkRequestBuilder = client.prepareBulk();
+ for (int i = 1; i <= 5; i++) {
+ final String group = i <= 2 ? "a" : "b";
+ bulkRequestBuilder.add(client.prepareIndex().setIndex(index).setId(String.valueOf(i))
+ .setSource("{\"value\":" + i + ",\"group\":\"" + group + "\"}", XContentType.JSON));
+ }
+ bulkRequestBuilder.execute().actionGet();
+ client.admin().indices().prepareRefresh(index).execute().actionGet();
+
+ final org.opensearch.index.reindex.DeleteByQueryRequest request = new org.opensearch.index.reindex.DeleteByQueryRequest(index);
+ request.setQuery(QueryBuilders.matchQuery("group", "a"));
+ request.setConflicts("proceed");
+ request.setRefresh(true);
+ final org.opensearch.index.reindex.BulkByScrollResponse response =
+ client.execute(org.opensearch.index.reindex.DeleteByQueryAction.INSTANCE, request).actionGet();
+ assertEquals(2L, response.getDeleted());
+ assertEquals(0, response.getBulkFailures().size());
+
+ final SearchResponse searchResponse = client.prepareSearch(index).setQuery(QueryBuilders.matchAllQuery()).execute().actionGet();
+ assertEquals(3L, searchResponse.getHits().getTotalHits().value());
+ }
+
+ @Test
+ void test_resolve_index() throws Exception {
+ final String index = "test_resolve_index";
+ final String alias = "test_resolve_index_alias";
+ client.admin().indices().prepareCreate(index).addAlias(new Alias(alias)).execute().actionGet();
+
+ final org.opensearch.action.admin.indices.resolve.ResolveIndexAction.Request request =
+ new org.opensearch.action.admin.indices.resolve.ResolveIndexAction.Request(new String[] { "test_resolve_index*" });
+ final org.opensearch.action.admin.indices.resolve.ResolveIndexAction.Response response =
+ client.execute(org.opensearch.action.admin.indices.resolve.ResolveIndexAction.INSTANCE, request).actionGet();
+ assertTrue(response.getIndices().stream().anyMatch(i -> index.equals(i.getName())));
+ assertTrue(response.getAliases().stream().anyMatch(a -> alias.equals(a.getName())));
+ }
+
+ @Test
+ void test_pit_lifecycle() throws Exception {
+ final String index = "test_pit_lifecycle";
+ final BulkRequestBuilder bulkRequestBuilder = client.prepareBulk();
+ for (int i = 1; i <= 3; i++) {
+ bulkRequestBuilder.add(
+ client.prepareIndex().setIndex(index).setId(String.valueOf(i)).setSource("{\"value\":" + i + "}", XContentType.JSON));
+ }
+ bulkRequestBuilder.execute().actionGet();
+ client.admin().indices().prepareRefresh(index).execute().actionGet();
+
+ final org.opensearch.action.search.CreatePitRequest createPitRequest =
+ new org.opensearch.action.search.CreatePitRequest(TimeValue.timeValueMinutes(1), true, index);
+ final org.opensearch.action.search.CreatePitResponse createPitResponse =
+ client.execute(org.opensearch.action.search.CreatePitAction.INSTANCE, createPitRequest).actionGet();
+ final String pitId = createPitResponse.getId();
+ assertNotNull(pitId);
+ assertFalse(pitId.isEmpty());
+
+ final org.opensearch.action.search.GetAllPitNodesResponse getAllPitsResponse = client
+ .execute(org.opensearch.action.search.GetAllPitsAction.INSTANCE, new org.opensearch.action.search.GetAllPitNodesRequest())
+ .actionGet();
+ assertTrue(getAllPitsResponse.getPitInfos().stream().anyMatch(p -> pitId.equals(p.getPitId())));
+
+ final org.opensearch.action.search.DeletePitResponse deletePitResponse = client
+ .execute(org.opensearch.action.search.DeletePitAction.INSTANCE, new org.opensearch.action.search.DeletePitRequest(pitId))
+ .actionGet();
+ assertTrue(deletePitResponse.getDeletePitResults().stream().allMatch(org.opensearch.action.search.DeletePitInfo::isSuccessful));
+ }
+
+ @Test
+ void test_pit_delete_all() throws Exception {
+ final String index = "test_pit_delete_all";
+ final BulkRequestBuilder bulkRequestBuilder = client.prepareBulk();
+ for (int i = 1; i <= 2; i++) {
+ bulkRequestBuilder.add(
+ client.prepareIndex().setIndex(index).setId(String.valueOf(i)).setSource("{\"value\":" + i + "}", XContentType.JSON));
+ }
+ bulkRequestBuilder.execute().actionGet();
+ client.admin().indices().prepareRefresh(index).execute().actionGet();
+
+ final org.opensearch.action.search.CreatePitRequest createPitRequest =
+ new org.opensearch.action.search.CreatePitRequest(TimeValue.timeValueMinutes(1), true, index);
+ final org.opensearch.action.search.CreatePitResponse createPitResponse =
+ client.execute(org.opensearch.action.search.CreatePitAction.INSTANCE, createPitRequest).actionGet();
+ assertNotNull(createPitResponse.getId());
+
+ final org.opensearch.action.search.DeletePitResponse deletePitResponse = client
+ .execute(org.opensearch.action.search.DeletePitAction.INSTANCE, new org.opensearch.action.search.DeletePitRequest("_all"))
+ .actionGet();
+ assertTrue(deletePitResponse.getDeletePitResults().stream().allMatch(org.opensearch.action.search.DeletePitInfo::isSuccessful));
+
+ final org.opensearch.action.search.GetAllPitNodesResponse getAllPitsResponse = client
+ .execute(org.opensearch.action.search.GetAllPitsAction.INSTANCE, new org.opensearch.action.search.GetAllPitNodesRequest())
+ .actionGet();
+ assertTrue(getAllPitsResponse.getPitInfos().isEmpty());
+ }
}
diff --git a/src/test/java/org/codelibs/fesen/client/OpenSearch3ClientTest.java b/src/test/java/org/codelibs/fesen/client/OpenSearch3ClientTest.java
index 82159f8..7b74516 100644
--- a/src/test/java/org/codelibs/fesen/client/OpenSearch3ClientTest.java
+++ b/src/test/java/org/codelibs/fesen/client/OpenSearch3ClientTest.java
@@ -2309,4 +2309,155 @@ void test_wlmStats_withNodeFilter() throws Exception {
}));
latch.await();
}
+
+ @Test
+ void test_reindex() throws Exception {
+ final String srcIndex = "test_reindex_src";
+ final String destIndex = "test_reindex_dest";
+ final BulkRequestBuilder bulkRequestBuilder = client.prepareBulk();
+ for (int i = 1; i <= 3; i++) {
+ bulkRequestBuilder.add(client.prepareIndex().setIndex(srcIndex).setId(String.valueOf(i)).setSource("{\"value\":" + i + "}",
+ XContentType.JSON));
+ }
+ bulkRequestBuilder.execute().actionGet();
+ client.admin().indices().prepareRefresh(srcIndex).execute().actionGet();
+
+ final org.opensearch.index.reindex.ReindexRequest reindexRequest = new org.opensearch.index.reindex.ReindexRequest();
+ reindexRequest.setSourceIndices(srcIndex);
+ reindexRequest.setDestIndex(destIndex);
+ reindexRequest.setRefresh(true);
+ final org.opensearch.index.reindex.BulkByScrollResponse response =
+ client.execute(org.opensearch.index.reindex.ReindexAction.INSTANCE, reindexRequest).actionGet();
+ assertEquals(3L, response.getTotal());
+ assertEquals(3L, response.getCreated());
+ assertEquals(0, response.getBulkFailures().size());
+
+ client.admin().indices().prepareRefresh(destIndex).execute().actionGet();
+ final SearchResponse searchResponse = client.prepareSearch(destIndex).setQuery(QueryBuilders.matchAllQuery()).execute().actionGet();
+ assertEquals(3L, searchResponse.getHits().getTotalHits().value());
+ }
+
+ @Test
+ void test_update_by_query() throws Exception {
+ final String index = "test_update_by_query";
+ final BulkRequestBuilder bulkRequestBuilder = client.prepareBulk();
+ for (int i = 1; i <= 3; i++) {
+ bulkRequestBuilder.add(client.prepareIndex().setIndex(index).setId(String.valueOf(i))
+ .setSource("{\"value\":" + i + ",\"status\":\"new\"}", XContentType.JSON));
+ }
+ bulkRequestBuilder.execute().actionGet();
+ client.admin().indices().prepareRefresh(index).execute().actionGet();
+
+ final org.opensearch.index.reindex.UpdateByQueryRequest request = new org.opensearch.index.reindex.UpdateByQueryRequest(index);
+ request.setQuery(QueryBuilders.matchAllQuery());
+ request.setScript(new org.opensearch.script.Script("ctx._source.status = 'updated'"));
+ request.setConflicts("proceed");
+ request.setRefresh(true);
+ final org.opensearch.index.reindex.BulkByScrollResponse response =
+ client.execute(org.opensearch.index.reindex.UpdateByQueryAction.INSTANCE, request).actionGet();
+ assertEquals(3L, response.getTotal());
+ assertEquals(3L, response.getUpdated());
+ assertEquals(0, response.getBulkFailures().size());
+
+ final SearchResponse searchResponse =
+ client.prepareSearch(index).setQuery(QueryBuilders.matchQuery("status", "updated")).execute().actionGet();
+ assertEquals(3L, searchResponse.getHits().getTotalHits().value());
+ }
+
+ @Test
+ void test_delete_by_query() throws Exception {
+ final String index = "test_delete_by_query";
+ final BulkRequestBuilder bulkRequestBuilder = client.prepareBulk();
+ for (int i = 1; i <= 5; i++) {
+ final String group = i <= 2 ? "a" : "b";
+ bulkRequestBuilder.add(client.prepareIndex().setIndex(index).setId(String.valueOf(i))
+ .setSource("{\"value\":" + i + ",\"group\":\"" + group + "\"}", XContentType.JSON));
+ }
+ bulkRequestBuilder.execute().actionGet();
+ client.admin().indices().prepareRefresh(index).execute().actionGet();
+
+ final org.opensearch.index.reindex.DeleteByQueryRequest request = new org.opensearch.index.reindex.DeleteByQueryRequest(index);
+ request.setQuery(QueryBuilders.matchQuery("group", "a"));
+ request.setConflicts("proceed");
+ request.setRefresh(true);
+ final org.opensearch.index.reindex.BulkByScrollResponse response =
+ client.execute(org.opensearch.index.reindex.DeleteByQueryAction.INSTANCE, request).actionGet();
+ assertEquals(2L, response.getDeleted());
+ assertEquals(0, response.getBulkFailures().size());
+
+ final SearchResponse searchResponse = client.prepareSearch(index).setQuery(QueryBuilders.matchAllQuery()).execute().actionGet();
+ assertEquals(3L, searchResponse.getHits().getTotalHits().value());
+ }
+
+ @Test
+ void test_resolve_index() throws Exception {
+ final String index = "test_resolve_index";
+ final String alias = "test_resolve_index_alias";
+ client.admin().indices().prepareCreate(index).addAlias(new Alias(alias)).execute().actionGet();
+
+ final org.opensearch.action.admin.indices.resolve.ResolveIndexAction.Request request =
+ new org.opensearch.action.admin.indices.resolve.ResolveIndexAction.Request(new String[] { "test_resolve_index*" });
+ final org.opensearch.action.admin.indices.resolve.ResolveIndexAction.Response response =
+ client.execute(org.opensearch.action.admin.indices.resolve.ResolveIndexAction.INSTANCE, request).actionGet();
+ assertTrue(response.getIndices().stream().anyMatch(i -> index.equals(i.getName())));
+ assertTrue(response.getAliases().stream().anyMatch(a -> alias.equals(a.getName())));
+ }
+
+ @Test
+ void test_pit_lifecycle() throws Exception {
+ final String index = "test_pit_lifecycle";
+ final BulkRequestBuilder bulkRequestBuilder = client.prepareBulk();
+ for (int i = 1; i <= 3; i++) {
+ bulkRequestBuilder.add(
+ client.prepareIndex().setIndex(index).setId(String.valueOf(i)).setSource("{\"value\":" + i + "}", XContentType.JSON));
+ }
+ bulkRequestBuilder.execute().actionGet();
+ client.admin().indices().prepareRefresh(index).execute().actionGet();
+
+ final org.opensearch.action.search.CreatePitRequest createPitRequest =
+ new org.opensearch.action.search.CreatePitRequest(TimeValue.timeValueMinutes(1), true, index);
+ final org.opensearch.action.search.CreatePitResponse createPitResponse =
+ client.execute(org.opensearch.action.search.CreatePitAction.INSTANCE, createPitRequest).actionGet();
+ final String pitId = createPitResponse.getId();
+ assertNotNull(pitId);
+ assertFalse(pitId.isEmpty());
+
+ final org.opensearch.action.search.GetAllPitNodesResponse getAllPitsResponse = client
+ .execute(org.opensearch.action.search.GetAllPitsAction.INSTANCE, new org.opensearch.action.search.GetAllPitNodesRequest())
+ .actionGet();
+ assertTrue(getAllPitsResponse.getPitInfos().stream().anyMatch(p -> pitId.equals(p.getPitId())));
+
+ final org.opensearch.action.search.DeletePitResponse deletePitResponse = client
+ .execute(org.opensearch.action.search.DeletePitAction.INSTANCE, new org.opensearch.action.search.DeletePitRequest(pitId))
+ .actionGet();
+ assertTrue(deletePitResponse.getDeletePitResults().stream().allMatch(org.opensearch.action.search.DeletePitInfo::isSuccessful));
+ }
+
+ @Test
+ void test_pit_delete_all() throws Exception {
+ final String index = "test_pit_delete_all";
+ final BulkRequestBuilder bulkRequestBuilder = client.prepareBulk();
+ for (int i = 1; i <= 2; i++) {
+ bulkRequestBuilder.add(
+ client.prepareIndex().setIndex(index).setId(String.valueOf(i)).setSource("{\"value\":" + i + "}", XContentType.JSON));
+ }
+ bulkRequestBuilder.execute().actionGet();
+ client.admin().indices().prepareRefresh(index).execute().actionGet();
+
+ final org.opensearch.action.search.CreatePitRequest createPitRequest =
+ new org.opensearch.action.search.CreatePitRequest(TimeValue.timeValueMinutes(1), true, index);
+ final org.opensearch.action.search.CreatePitResponse createPitResponse =
+ client.execute(org.opensearch.action.search.CreatePitAction.INSTANCE, createPitRequest).actionGet();
+ assertNotNull(createPitResponse.getId());
+
+ final org.opensearch.action.search.DeletePitResponse deletePitResponse = client
+ .execute(org.opensearch.action.search.DeletePitAction.INSTANCE, new org.opensearch.action.search.DeletePitRequest("_all"))
+ .actionGet();
+ assertTrue(deletePitResponse.getDeletePitResults().stream().allMatch(org.opensearch.action.search.DeletePitInfo::isSuccessful));
+
+ final org.opensearch.action.search.GetAllPitNodesResponse getAllPitsResponse = client
+ .execute(org.opensearch.action.search.GetAllPitsAction.INSTANCE, new org.opensearch.action.search.GetAllPitNodesRequest())
+ .actionGet();
+ assertTrue(getAllPitsResponse.getPitInfos().isEmpty());
+ }
}
diff --git a/src/test/java/org/codelibs/fesen/client/action/ActionTestUtils.java b/src/test/java/org/codelibs/fesen/client/action/ActionTestUtils.java
index 0303b26..a619992 100644
--- a/src/test/java/org/codelibs/fesen/client/action/ActionTestUtils.java
+++ b/src/test/java/org/codelibs/fesen/client/action/ActionTestUtils.java
@@ -22,9 +22,12 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import org.codelibs.curl.CurlRequest;
+import org.codelibs.fesen.client.EngineInfo;
+import org.codelibs.fesen.client.EngineInfo.EngineType;
import org.codelibs.fesen.client.HttpClient;
import org.codelibs.fesen.client.HttpClient.ContentType;
import org.opensearch.common.settings.Settings;
@@ -39,28 +42,35 @@
*/
final class ActionTestUtils {
- private static volatile HttpClient client;
+ private static final Map CLIENTS = new ConcurrentHashMap<>();
private ActionTestUtils() {
}
/**
* Returns a shared {@link HttpClient} that builds plain, inspectable curl requests.
+ * The backend engine is reported as OpenSearch 3.x.
*
* @return the test HTTP client
*/
static HttpClient testClient() {
- if (client == null) {
- synchronized (ActionTestUtils.class) {
- if (client == null) {
- client = createClient();
- }
- }
- }
- return client;
+ return testClient(EngineType.OPENSEARCH3);
}
- private static HttpClient createClient() {
+ /**
+ * Returns a shared {@link HttpClient} that builds plain, inspectable curl requests and
+ * reports the given backend engine type from {@link HttpClient#getEngineInfo()}, so that
+ * version-conditional parameter serialization can be exercised offline.
+ *
+ * @param engineType the backend engine type to report
+ * @return the test HTTP client
+ */
+ static HttpClient testClient(final EngineType engineType) {
+ return CLIENTS.computeIfAbsent(engineType, ActionTestUtils::createClient);
+ }
+
+ private static HttpClient createClient(final EngineType engineType) {
+ final EngineInfo engineInfo = stubEngineInfo(engineType);
final Settings settings = Settings.builder().putList("http.hosts", "localhost:9200").build();
return new HttpClient(settings, null) {
@Override
@@ -75,9 +85,43 @@ public CurlRequest getCurlRequest(final Function method, fi
}
return method.apply(buf.toString());
}
+
+ @Override
+ public EngineInfo getEngineInfo() {
+ return engineInfo;
+ }
};
}
+ private static EngineInfo stubEngineInfo(final EngineType engineType) {
+ final String distribution;
+ final String number;
+ switch (engineType) {
+ case ELASTICSEARCH7:
+ distribution = "elasticsearch";
+ number = "7.17.0";
+ break;
+ case ELASTICSEARCH8:
+ distribution = "elasticsearch";
+ number = "8.11.0";
+ break;
+ case OPENSEARCH1:
+ distribution = "opensearch";
+ number = "1.3.0";
+ break;
+ case OPENSEARCH2:
+ distribution = "opensearch";
+ number = "2.11.0";
+ break;
+ case OPENSEARCH3:
+ default:
+ distribution = "opensearch";
+ number = "3.0.0";
+ break;
+ }
+ return new EngineInfo(Map.of("version", Map.of("number", number, "distribution", distribution)));
+ }
+
/**
* Returns the raw {@code key=value} query parameter list of the given curl request,
* read reflectively from the protected {@code paramList} field.
@@ -114,6 +158,23 @@ static Map params(final CurlRequest request) {
return map;
}
+ /**
+ * Returns the request URL of the given curl request, read reflectively from the
+ * protected {@code url} field.
+ *
+ * @param request the curl request to inspect
+ * @return the request URL
+ */
+ static String url(final CurlRequest request) {
+ try {
+ final Field field = CurlRequest.class.getDeclaredField("url");
+ field.setAccessible(true);
+ return (String) field.get(request);
+ } catch (final ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
private static String decode(final String value) {
return URLDecoder.decode(value, StandardCharsets.UTF_8);
}
diff --git a/src/test/java/org/codelibs/fesen/client/action/HttpCloseIndexActionTest.java b/src/test/java/org/codelibs/fesen/client/action/HttpCloseIndexActionTest.java
new file mode 100644
index 0000000..b7a7f55
--- /dev/null
+++ b/src/test/java/org/codelibs/fesen/client/action/HttpCloseIndexActionTest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012-2025 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fesen.client.action;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+import java.util.Map;
+
+import org.junit.jupiter.api.Test;
+import org.opensearch.action.admin.indices.close.CloseIndexAction;
+import org.opensearch.action.admin.indices.close.CloseIndexRequest;
+import org.opensearch.action.support.ActiveShardCount;
+
+class HttpCloseIndexActionTest {
+
+ private final HttpCloseIndexAction clientAction = new HttpCloseIndexAction(ActionTestUtils.testClient(), CloseIndexAction.INSTANCE);
+
+ @Test
+ void test_getCurlRequest_waitForActiveShardsAll() {
+ final CloseIndexRequest request = new CloseIndexRequest("test-index").waitForActiveShards(ActiveShardCount.ALL);
+ final Map params = ActionTestUtils.params(clientAction.getCurlRequest(request));
+ assertEquals("all", params.get("wait_for_active_shards"));
+ }
+
+ @Test
+ void test_getCurlRequest_waitForActiveShardsDefaultNotSent() {
+ final CloseIndexRequest request = new CloseIndexRequest("test-index").waitForActiveShards(ActiveShardCount.DEFAULT);
+ final Map params = ActionTestUtils.params(clientAction.getCurlRequest(request));
+ assertFalse(params.containsKey("wait_for_active_shards"));
+ }
+}
diff --git a/src/test/java/org/codelibs/fesen/client/action/HttpCreatePitActionTest.java b/src/test/java/org/codelibs/fesen/client/action/HttpCreatePitActionTest.java
index d7d123e..9df51d3 100644
--- a/src/test/java/org/codelibs/fesen/client/action/HttpCreatePitActionTest.java
+++ b/src/test/java/org/codelibs/fesen/client/action/HttpCreatePitActionTest.java
@@ -16,14 +16,24 @@
package org.codelibs.fesen.client.action;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+import org.codelibs.fesen.client.EngineInfo.EngineType;
import org.junit.jupiter.api.Test;
import org.opensearch.action.search.CreatePitAction;
+import org.opensearch.action.search.CreatePitRequest;
import org.opensearch.action.search.CreatePitResponse;
+import org.opensearch.common.unit.TimeValue;
import org.opensearch.common.xcontent.json.JsonXContent;
+import org.opensearch.core.action.ActionListener;
import org.opensearch.core.xcontent.DeprecationHandler;
import org.opensearch.core.xcontent.NamedXContentRegistry;
import org.opensearch.core.xcontent.XContentParser;
@@ -32,6 +42,16 @@ class HttpCreatePitActionTest {
private final HttpCreatePitAction action = new HttpCreatePitAction(null, CreatePitAction.INSTANCE);
+ private static HttpCreatePitAction action(final EngineType engineType) {
+ return new HttpCreatePitAction(ActionTestUtils.testClient(engineType), CreatePitAction.INSTANCE);
+ }
+
+ private static CreatePitRequest request() {
+ return new CreatePitRequest(TimeValue.timeValueMinutes(5), true, "idx");
+ }
+
+ // --- OpenSearch fromXContent (unchanged behavior) ---
+
@Test
void test_fromXContent() throws IOException {
final String json = "{\"pit_id\":\"abc\",\"_shards\":{\"total\":2,\"successful\":2,\"skipped\":0,\"failed\":0},"
@@ -48,4 +68,90 @@ void test_fromXContent() throws IOException {
assertEquals(0, response.getFailedShards());
}
}
+
+ // --- Per-engine getCurlRequest ---
+
+ @Test
+ void test_getCurlRequest_openSearch() {
+ for (final EngineType engineType : new EngineType[] { EngineType.OPENSEARCH2, EngineType.OPENSEARCH3 }) {
+ final HttpCreatePitAction osAction = action(engineType);
+ final CreatePitRequest request = request();
+ final var curlRequest = osAction.getCurlRequest(request);
+ assertTrue(ActionTestUtils.url(curlRequest).endsWith("/idx/_search/point_in_time"), engineType.toString());
+ final Map params = ActionTestUtils.params(curlRequest);
+ assertEquals("true", params.get("allow_partial_pit_creation"), engineType.toString());
+ assertEquals("5m", params.get("keep_alive"), engineType.toString());
+ assertFalse(params.containsKey("allow_partial_search_results"), engineType.toString());
+ }
+ }
+
+ @Test
+ void test_getCurlRequest_elasticsearch() {
+ for (final EngineType engineType : new EngineType[] { EngineType.ELASTICSEARCH7, EngineType.ELASTICSEARCH8 }) {
+ final HttpCreatePitAction esAction = action(engineType);
+ final CreatePitRequest request = request();
+ final var curlRequest = esAction.getCurlRequest(request);
+ assertTrue(ActionTestUtils.url(curlRequest).endsWith("/idx/_pit"), engineType.toString());
+ final Map params = ActionTestUtils.params(curlRequest);
+ assertEquals("5m", params.get("keep_alive"), engineType.toString());
+ // The Elasticsearch _pit endpoint rejects both partial-results parameters (HTTP 400),
+ // so neither is sent.
+ assertFalse(params.containsKey("allow_partial_search_results"), engineType.toString());
+ assertFalse(params.containsKey("allow_partial_pit_creation"), engineType.toString());
+ }
+ }
+
+ // --- Gating ---
+
+ @Test
+ void test_execute_unsupportedEngine() {
+ // OpenSearch 1.x predates PIT; testClient cannot stub UNKNOWN (its stub falls back to OpenSearch 3.x).
+ final HttpCreatePitAction gatedAction = action(EngineType.OPENSEARCH1);
+ final AtomicReference failure = new AtomicReference<>();
+ gatedAction.execute(request(), ActionListener.wrap(r -> {}, failure::set));
+ assertInstanceOf(UnsupportedOperationException.class, failure.get());
+ }
+
+ // --- Elasticsearch response parsing ---
+
+ @Test
+ void test_parseEsCreate_idOnly() throws IOException {
+ final String json = "{\"id\":\"abc\"}";
+ try (final XContentParser parser =
+ JsonXContent.jsonXContent.createParser(NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, json)) {
+ final CreatePitResponse response = action.parseEsCreate(parser);
+ assertNotNull(response);
+ assertEquals("abc", response.getId());
+ assertEquals(0, response.getTotalShards());
+ assertEquals(0, response.getSuccessfulShards());
+ assertEquals(0, response.getSkippedShards());
+ assertEquals(0, response.getFailedShards());
+ }
+ }
+
+ @Test
+ void test_parseEsCreate_withShards() throws IOException {
+ final String json = "{\"id\":\"abc\",\"_shards\":{\"total\":5,\"successful\":5,\"skipped\":0,\"failed\":0}}";
+ try (final XContentParser parser =
+ JsonXContent.jsonXContent.createParser(NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, json)) {
+ final CreatePitResponse response = action.parseEsCreate(parser);
+ assertNotNull(response);
+ assertEquals("abc", response.getId());
+ assertEquals(5, response.getTotalShards());
+ assertEquals(5, response.getSuccessfulShards());
+ assertEquals(0, response.getSkippedShards());
+ assertEquals(0, response.getFailedShards());
+ }
+ }
+
+ @Test
+ void test_parseEsCreate_errorBody_throws() throws IOException {
+ // An error body (routed through the success path) must surface as a failure, not become a
+ // response with a null id.
+ final String json = "{\"error\":{\"type\":\"illegal_argument_exception\"},\"status\":400}";
+ try (final XContentParser parser =
+ JsonXContent.jsonXContent.createParser(NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, json)) {
+ assertThrows(IOException.class, () -> action.parseEsCreate(parser));
+ }
+ }
}
diff --git a/src/test/java/org/codelibs/fesen/client/action/HttpDeleteByQueryActionTest.java b/src/test/java/org/codelibs/fesen/client/action/HttpDeleteByQueryActionTest.java
index 0e82753..60420b9 100644
--- a/src/test/java/org/codelibs/fesen/client/action/HttpDeleteByQueryActionTest.java
+++ b/src/test/java/org/codelibs/fesen/client/action/HttpDeleteByQueryActionTest.java
@@ -18,6 +18,9 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Map;
import org.junit.jupiter.api.Test;
import org.opensearch.common.xcontent.json.JsonXContent;
@@ -26,11 +29,47 @@
import org.opensearch.core.xcontent.XContentParser;
import org.opensearch.index.reindex.BulkByScrollResponse;
import org.opensearch.index.reindex.DeleteByQueryAction;
+import org.opensearch.index.reindex.DeleteByQueryRequest;
class HttpDeleteByQueryActionTest {
private final HttpDeleteByQueryAction action = new HttpDeleteByQueryAction(null, DeleteByQueryAction.INSTANCE);
+ private final HttpDeleteByQueryAction clientAction =
+ new HttpDeleteByQueryAction(ActionTestUtils.testClient(), DeleteByQueryAction.INSTANCE);
+
+ @Test
+ void test_getCurlRequest_endpointAndParams() {
+ final DeleteByQueryRequest request = new DeleteByQueryRequest("idx");
+ request.setAbortOnVersionConflict(false);
+ request.setMaxDocs(30);
+ request.setSlices(2);
+ request.setRequestsPerSecond(100f);
+ request.setRouting("r2");
+ final Map params = ActionTestUtils.params(clientAction.getCurlRequest(request));
+ // Endpoint is POST /idx/_delete_by_query.
+ assertTrue(ActionTestUtils.url(clientAction.getCurlRequest(request)).contains("/idx/_delete_by_query"));
+ assertEquals("true", params.get("wait_for_completion"));
+ assertEquals("proceed", params.get("conflicts"));
+ assertEquals("30", params.get("max_docs"));
+ assertEquals("2", params.get("slices"));
+ assertEquals(Float.toString(request.getRequestsPerSecond()), params.get("requests_per_second"));
+ assertEquals("r2", params.get("routing"));
+ // delete-by-query has no pipeline parameter (unlike update-by-query).
+ assertFalse(params.containsKey("pipeline"));
+ }
+
+ @Test
+ void test_getCurlRequest_defaultsOmitOptionalParams() {
+ final DeleteByQueryRequest request = new DeleteByQueryRequest("idx");
+ final Map params = ActionTestUtils.params(clientAction.getCurlRequest(request));
+ assertEquals("true", params.get("wait_for_completion"));
+ assertFalse(params.containsKey("conflicts"));
+ assertFalse(params.containsKey("max_docs"));
+ assertFalse(params.containsKey("slices"));
+ assertFalse(params.containsKey("routing"));
+ }
+
@Test
void test_fromXContent() throws Exception {
final String json = "{\"took\":210,\"timed_out\":false,\"total\":75,\"updated\":0,\"created\":0,\"deleted\":75,"
diff --git a/src/test/java/org/codelibs/fesen/client/action/HttpDeleteIndexActionTest.java b/src/test/java/org/codelibs/fesen/client/action/HttpDeleteIndexActionTest.java
new file mode 100644
index 0000000..d71b121
--- /dev/null
+++ b/src/test/java/org/codelibs/fesen/client/action/HttpDeleteIndexActionTest.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2012-2025 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fesen.client.action;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.Map;
+
+import org.junit.jupiter.api.Test;
+import org.opensearch.action.admin.indices.delete.DeleteIndexAction;
+import org.opensearch.action.admin.indices.delete.DeleteIndexRequest;
+import org.opensearch.action.support.IndicesOptions;
+
+class HttpDeleteIndexActionTest {
+
+ private final HttpDeleteIndexAction action = new HttpDeleteIndexAction(ActionTestUtils.testClient(), DeleteIndexAction.INSTANCE);
+
+ @Test
+ void test_getCurlRequest_indicesOptions() {
+ final DeleteIndexRequest request = new DeleteIndexRequest("test-index");
+ request.indicesOptions(IndicesOptions.fromOptions(true, false, false, true));
+ final Map params = ActionTestUtils.params(action.getCurlRequest(request));
+ assertEquals("true", params.get("ignore_unavailable"));
+ assertEquals("false", params.get("allow_no_indices"));
+ assertEquals("closed", params.get("expand_wildcards"));
+ }
+}
diff --git a/src/test/java/org/codelibs/fesen/client/action/HttpDeletePitActionTest.java b/src/test/java/org/codelibs/fesen/client/action/HttpDeletePitActionTest.java
index 4d1e373..b0e5c91 100644
--- a/src/test/java/org/codelibs/fesen/client/action/HttpDeletePitActionTest.java
+++ b/src/test/java/org/codelibs/fesen/client/action/HttpDeletePitActionTest.java
@@ -16,15 +16,22 @@
package org.codelibs.fesen.client.action;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException;
+import java.util.concurrent.atomic.AtomicReference;
+import org.codelibs.fesen.client.EngineInfo.EngineType;
import org.junit.jupiter.api.Test;
import org.opensearch.action.search.DeletePitAction;
+import org.opensearch.action.search.DeletePitRequest;
import org.opensearch.action.search.DeletePitResponse;
import org.opensearch.common.xcontent.json.JsonXContent;
+import org.opensearch.core.action.ActionListener;
import org.opensearch.core.xcontent.DeprecationHandler;
import org.opensearch.core.xcontent.NamedXContentRegistry;
import org.opensearch.core.xcontent.XContentParser;
@@ -33,6 +40,12 @@ class HttpDeletePitActionTest {
private final HttpDeletePitAction action = new HttpDeletePitAction(null, DeletePitAction.INSTANCE);
+ private static HttpDeletePitAction action(final EngineType engineType) {
+ return new HttpDeletePitAction(ActionTestUtils.testClient(engineType), DeletePitAction.INSTANCE);
+ }
+
+ // --- OpenSearch fromXContent (unchanged behavior) ---
+
@Test
void test_fromXContent() throws IOException {
final String json = "{\"pits\":[{\"successful\":true,\"pit_id\":\"abc\"},{\"successful\":false,\"pit_id\":\"def\"}]}";
@@ -47,4 +60,104 @@ void test_fromXContent() throws IOException {
assertEquals("def", response.getDeletePitResults().get(1).getPitId());
}
}
+
+ // --- Per-engine request bodies ---
+
+ @Test
+ void test_buildBody_openSearch() {
+ final DeletePitRequest request = new DeletePitRequest("a", "b");
+ final String body = action(EngineType.OPENSEARCH3).buildBody(request);
+ assertTrue(body.contains("\"pit_id\""), body);
+ assertTrue(body.contains("\"a\""), body);
+ assertTrue(body.contains("\"b\""), body);
+ }
+
+ @Test
+ void test_buildEsBody_elasticsearch() {
+ // The Elasticsearch _pit endpoint accepts a single id (a JSON string, not an array).
+ final DeletePitRequest request = new DeletePitRequest("a");
+ final String body = action(EngineType.ELASTICSEARCH8).buildEsBody(request);
+ assertTrue(body.contains("\"id\""), body);
+ assertFalse(body.contains("\"pit_id\""), body);
+ assertTrue(body.contains("\"a\""), body);
+ assertFalse(body.contains("["), body);
+ }
+
+ // --- Per-engine getCurlRequest endpoints ---
+
+ @Test
+ void test_getCurlRequest_openSearch() {
+ final DeletePitRequest request = new DeletePitRequest("a", "b");
+ final String url = ActionTestUtils.url(action(EngineType.OPENSEARCH3).getCurlRequest(request));
+ assertTrue(url.endsWith("/_search/point_in_time"), url);
+ }
+
+ @Test
+ void test_getCurlRequest_elasticsearch() {
+ final DeletePitRequest request = new DeletePitRequest("a", "b");
+ final String url = ActionTestUtils.url(action(EngineType.ELASTICSEARCH8).getCurlRequest(request));
+ assertTrue(url.endsWith("/_pit"), url);
+ }
+
+ @Test
+ void test_getCurlRequest_deleteAll_openSearch() {
+ final DeletePitRequest request = new DeletePitRequest("_all");
+ final String url = ActionTestUtils.url(action(EngineType.OPENSEARCH3).getCurlRequest(request));
+ assertTrue(url.endsWith("/_search/point_in_time/_all"), url);
+ }
+
+ // --- Gating ---
+
+ @Test
+ void test_execute_unsupportedEngine() {
+ // OpenSearch 1.x predates PIT; testClient cannot stub UNKNOWN (its stub falls back to OpenSearch 3.x).
+ final AtomicReference failure = new AtomicReference<>();
+ action(EngineType.OPENSEARCH1).execute(new DeletePitRequest("a"), ActionListener.wrap(r -> {}, failure::set));
+ assertInstanceOf(UnsupportedOperationException.class, failure.get());
+ }
+
+ @Test
+ void test_execute_deleteAll_elasticsearchUnsupported() {
+ final AtomicReference failure = new AtomicReference<>();
+ action(EngineType.ELASTICSEARCH8).execute(new DeletePitRequest("_all"), ActionListener.wrap(r -> {}, failure::set));
+ assertInstanceOf(UnsupportedOperationException.class, failure.get());
+ }
+
+ @Test
+ void test_execute_multipleIds_elasticsearchUnsupported() {
+ // Elasticsearch _pit deletes a single id per request; multiple ids are rejected up front.
+ final AtomicReference failure = new AtomicReference<>();
+ action(EngineType.ELASTICSEARCH8).execute(new DeletePitRequest("a", "b"), ActionListener.wrap(r -> {}, failure::set));
+ assertInstanceOf(UnsupportedOperationException.class, failure.get());
+ }
+
+ // --- Elasticsearch response parsing ---
+
+ @Test
+ void test_parseEsDelete() throws IOException {
+ final String json = "{\"succeeded\":true,\"num_freed\":2}";
+ final DeletePitRequest request = new DeletePitRequest("a", "b");
+ try (final XContentParser parser =
+ JsonXContent.jsonXContent.createParser(NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, json)) {
+ final DeletePitResponse response = action.parseEsDelete(parser, request);
+ assertNotNull(response);
+ assertEquals(2, response.getDeletePitResults().size());
+ assertTrue(response.getDeletePitResults().get(0).isSuccessful());
+ assertTrue(response.getDeletePitResults().get(1).isSuccessful());
+ assertEquals("a", response.getDeletePitResults().get(0).getPitId());
+ assertEquals("b", response.getDeletePitResults().get(1).getPitId());
+ }
+ }
+
+ @Test
+ void test_parseEsDelete_errorBody_throws() throws IOException {
+ // An error body (routed through the success path) must surface as a failure, not be reported
+ // as an unsuccessful deletion of the requested ids.
+ final String json = "{\"error\":{\"type\":\"illegal_argument_exception\"},\"status\":400}";
+ final DeletePitRequest request = new DeletePitRequest("a");
+ try (final XContentParser parser =
+ JsonXContent.jsonXContent.createParser(NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, json)) {
+ assertThrows(IOException.class, () -> action.parseEsDelete(parser, request));
+ }
+ }
}
diff --git a/src/test/java/org/codelibs/fesen/client/action/HttpFlushActionTest.java b/src/test/java/org/codelibs/fesen/client/action/HttpFlushActionTest.java
new file mode 100644
index 0000000..851e2fd
--- /dev/null
+++ b/src/test/java/org/codelibs/fesen/client/action/HttpFlushActionTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2012-2025 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fesen.client.action;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.Map;
+
+import org.junit.jupiter.api.Test;
+import org.opensearch.action.admin.indices.flush.FlushAction;
+import org.opensearch.action.admin.indices.flush.FlushRequest;
+import org.opensearch.action.support.IndicesOptions;
+
+class HttpFlushActionTest {
+
+ private final HttpFlushAction action = new HttpFlushAction(ActionTestUtils.testClient(), FlushAction.INSTANCE);
+
+ @Test
+ void test_getCurlRequest_indicesOptions() {
+ final FlushRequest request = new FlushRequest("test-index");
+ request.indicesOptions(IndicesOptions.fromOptions(true, false, false, true));
+ final Map params = ActionTestUtils.params(action.getCurlRequest(request));
+ assertEquals("true", params.get("ignore_unavailable"));
+ assertEquals("false", params.get("allow_no_indices"));
+ assertEquals("closed", params.get("expand_wildcards"));
+ // existing flush params still present
+ assertEquals(String.valueOf(request.waitIfOngoing()), params.get("wait_if_ongoing"));
+ assertEquals(String.valueOf(request.force()), params.get("force"));
+ }
+}
diff --git a/src/test/java/org/codelibs/fesen/client/action/HttpForceMergeActionTest.java b/src/test/java/org/codelibs/fesen/client/action/HttpForceMergeActionTest.java
new file mode 100644
index 0000000..ddcff5b
--- /dev/null
+++ b/src/test/java/org/codelibs/fesen/client/action/HttpForceMergeActionTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2012-2025 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fesen.client.action;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+import java.util.Map;
+
+import org.codelibs.fesen.client.EngineInfo.EngineType;
+import org.junit.jupiter.api.Test;
+import org.opensearch.action.admin.indices.forcemerge.ForceMergeAction;
+import org.opensearch.action.admin.indices.forcemerge.ForceMergeRequest;
+import org.opensearch.action.support.IndicesOptions;
+
+class HttpForceMergeActionTest {
+
+ private final HttpForceMergeAction action = new HttpForceMergeAction(ActionTestUtils.testClient(), ForceMergeAction.INSTANCE);
+
+ @Test
+ void test_getCurlRequest_primaryOnly() {
+ final ForceMergeRequest request = new ForceMergeRequest("test-index").primaryOnly(true);
+ final Map params = ActionTestUtils.params(action.getCurlRequest(request));
+ assertEquals("true", params.get("primary_only"));
+ }
+
+ @Test
+ void test_getCurlRequest_primaryOnly_openSearch2() {
+ final HttpForceMergeAction os2Action =
+ new HttpForceMergeAction(ActionTestUtils.testClient(EngineType.OPENSEARCH2), ForceMergeAction.INSTANCE);
+ final ForceMergeRequest request = new ForceMergeRequest("test-index").primaryOnly(true);
+ final Map params = ActionTestUtils.params(os2Action.getCurlRequest(request));
+ assertEquals("true", params.get("primary_only"));
+ }
+
+ @Test
+ void test_getCurlRequest_primaryOnly_notSentOnLegacyEngines() {
+ // primary_only is unknown to OpenSearch 1.x and Elasticsearch 7/8, which reject it with HTTP 400.
+ for (final EngineType engineType : new EngineType[] { EngineType.OPENSEARCH1, EngineType.ELASTICSEARCH7,
+ EngineType.ELASTICSEARCH8 }) {
+ final HttpForceMergeAction legacyAction =
+ new HttpForceMergeAction(ActionTestUtils.testClient(engineType), ForceMergeAction.INSTANCE);
+ final ForceMergeRequest request = new ForceMergeRequest("test-index").primaryOnly(true);
+ final Map params = ActionTestUtils.params(legacyAction.getCurlRequest(request));
+ assertFalse(params.containsKey("primary_only"), engineType.toString());
+ // non-version-specific params are still forwarded
+ assertEquals(String.valueOf(request.maxNumSegments()), params.get("max_num_segments"), engineType.toString());
+ }
+ }
+
+ @Test
+ void test_getCurlRequest_defaultParams() {
+ final ForceMergeRequest request = new ForceMergeRequest("test-index");
+ final Map params = ActionTestUtils.params(action.getCurlRequest(request));
+ assertEquals(String.valueOf(request.primaryOnly()), params.get("primary_only"));
+ assertEquals(String.valueOf(request.maxNumSegments()), params.get("max_num_segments"));
+ assertEquals(String.valueOf(request.onlyExpungeDeletes()), params.get("only_expunge_deletes"));
+ assertEquals(String.valueOf(request.flush()), params.get("flush"));
+ }
+
+ @Test
+ void test_getCurlRequest_indicesOptions() {
+ final ForceMergeRequest request = new ForceMergeRequest("test-index");
+ request.indicesOptions(IndicesOptions.fromOptions(true, false, false, true));
+ final Map params = ActionTestUtils.params(action.getCurlRequest(request));
+ assertEquals("true", params.get("ignore_unavailable"));
+ assertEquals("false", params.get("allow_no_indices"));
+ assertEquals("closed", params.get("expand_wildcards"));
+ }
+}
diff --git a/src/test/java/org/codelibs/fesen/client/action/HttpGetAllPitsActionTest.java b/src/test/java/org/codelibs/fesen/client/action/HttpGetAllPitsActionTest.java
index 98c4fa4..6bcdfb8 100644
--- a/src/test/java/org/codelibs/fesen/client/action/HttpGetAllPitsActionTest.java
+++ b/src/test/java/org/codelibs/fesen/client/action/HttpGetAllPitsActionTest.java
@@ -16,14 +16,20 @@
package org.codelibs.fesen.client.action;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException;
+import java.util.concurrent.atomic.AtomicReference;
+import org.codelibs.fesen.client.EngineInfo.EngineType;
import org.junit.jupiter.api.Test;
+import org.opensearch.action.search.GetAllPitNodesRequest;
import org.opensearch.action.search.GetAllPitNodesResponse;
import org.opensearch.action.search.GetAllPitsAction;
import org.opensearch.common.xcontent.json.JsonXContent;
+import org.opensearch.core.action.ActionListener;
import org.opensearch.core.xcontent.DeprecationHandler;
import org.opensearch.core.xcontent.NamedXContentRegistry;
import org.opensearch.core.xcontent.XContentParser;
@@ -32,6 +38,12 @@ class HttpGetAllPitsActionTest {
private final HttpGetAllPitsAction action = new HttpGetAllPitsAction(null, GetAllPitsAction.INSTANCE);
+ private static HttpGetAllPitsAction action(final EngineType engineType) {
+ return new HttpGetAllPitsAction(ActionTestUtils.testClient(engineType), GetAllPitsAction.INSTANCE);
+ }
+
+ // --- OpenSearch fromXContent (unchanged behavior) ---
+
@Test
void test_fromXContent() throws IOException {
final String json = "{\"pits\":[{\"pit_id\":\"abc\",\"creation_time\":123,\"keep_alive\":300000}]}";
@@ -45,4 +57,27 @@ void test_fromXContent() throws IOException {
assertEquals(300000L, response.getPitInfos().get(0).getKeepAlive());
}
}
+
+ // --- Per-engine getCurlRequest ---
+
+ @Test
+ void test_getCurlRequest_openSearch() {
+ for (final EngineType engineType : new EngineType[] { EngineType.OPENSEARCH2, EngineType.OPENSEARCH3 }) {
+ final String url = ActionTestUtils.url(action(engineType).getCurlRequest(new GetAllPitNodesRequest()));
+ assertTrue(url.endsWith("/_search/point_in_time/_all"), engineType + ": " + url);
+ }
+ }
+
+ // --- Gating ---
+
+ @Test
+ void test_execute_unsupportedEngine() {
+ // Listing all PITs is OpenSearch 2.x+ only; testClient cannot stub UNKNOWN (its stub falls back to OpenSearch 3.x).
+ for (final EngineType engineType : new EngineType[] { EngineType.OPENSEARCH1, EngineType.ELASTICSEARCH7,
+ EngineType.ELASTICSEARCH8 }) {
+ final AtomicReference failure = new AtomicReference<>();
+ action(engineType).execute(new GetAllPitNodesRequest(), ActionListener.wrap(r -> {}, failure::set));
+ assertInstanceOf(UnsupportedOperationException.class, failure.get(), engineType.toString());
+ }
+ }
}
diff --git a/src/test/java/org/codelibs/fesen/client/action/HttpGetMappingsActionTest.java b/src/test/java/org/codelibs/fesen/client/action/HttpGetMappingsActionTest.java
new file mode 100644
index 0000000..f7f5f4c
--- /dev/null
+++ b/src/test/java/org/codelibs/fesen/client/action/HttpGetMappingsActionTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2012-2025 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fesen.client.action;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.codelibs.fesen.client.EngineInfo.EngineType;
+import org.junit.jupiter.api.Test;
+import org.opensearch.action.admin.indices.mapping.get.GetMappingsAction;
+import org.opensearch.action.admin.indices.mapping.get.GetMappingsRequest;
+import org.opensearch.action.admin.indices.mapping.get.GetMappingsResponse;
+import org.opensearch.action.support.IndicesOptions;
+import org.opensearch.cluster.metadata.MappingMetadata;
+import org.opensearch.common.xcontent.json.JsonXContent;
+import org.opensearch.core.xcontent.DeprecationHandler;
+import org.opensearch.core.xcontent.NamedXContentRegistry;
+import org.opensearch.core.xcontent.XContentParser;
+
+class HttpGetMappingsActionTest {
+
+ private final HttpGetMappingsAction os3Action =
+ new HttpGetMappingsAction(ActionTestUtils.testClient(EngineType.OPENSEARCH3), GetMappingsAction.INSTANCE);
+
+ private final HttpGetMappingsAction es8Action =
+ new HttpGetMappingsAction(ActionTestUtils.testClient(EngineType.ELASTICSEARCH8), GetMappingsAction.INSTANCE);
+
+ @Test
+ void test_getCurlRequest_localPresentOnOpenSearch3() {
+ final GetMappingsRequest request = new GetMappingsRequest().indices("test-index");
+ final Map params = ActionTestUtils.params(os3Action.getCurlRequest(request));
+ // The index is carried in the path, and the mapping endpoint is used.
+ assertTrue(ActionTestUtils.url(os3Action.getCurlRequest(request)).contains("/test-index/_mapping"));
+ // local is emitted for every engine except Elasticsearch 8.x.
+ assertTrue(params.containsKey("local"));
+ assertEquals(Boolean.toString(request.local()), params.get("local"));
+ }
+
+ @Test
+ void test_getCurlRequest_localAbsentOnElasticsearch8() {
+ final GetMappingsRequest request = new GetMappingsRequest().indices("test-index");
+ final Map params = ActionTestUtils.params(es8Action.getCurlRequest(request));
+ assertTrue(ActionTestUtils.url(es8Action.getCurlRequest(request)).contains("/test-index/_mapping"));
+ // Elasticsearch 8.x rejects the local parameter on the mapping endpoint, so the action suppresses it.
+ assertFalse(params.containsKey("local"));
+ // Indices options are still forwarded on Elasticsearch 8.x.
+ assertTrue(params.containsKey("ignore_unavailable"));
+ assertTrue(params.containsKey("allow_no_indices"));
+ assertTrue(params.containsKey("expand_wildcards"));
+ }
+
+ @Test
+ void test_getCurlRequest_indicesOptionsForwarded() {
+ final GetMappingsRequest request = new GetMappingsRequest().indices("test-index");
+ request.indicesOptions(IndicesOptions.fromOptions(true, false, false, true));
+ final Map params = ActionTestUtils.params(os3Action.getCurlRequest(request));
+ assertEquals("true", params.get("ignore_unavailable"));
+ assertEquals("false", params.get("allow_no_indices"));
+ assertEquals("closed", params.get("expand_wildcards"));
+ }
+
+ @Test
+ void test_fromXContent_skipsDynamicTemplates() throws IOException {
+ // OpenSearch mapping response: properties plus a dynamic_templates block that must be skipped.
+ final String json = "{\"test-index\":{\"mappings\":{" //
+ + "\"properties\":{\"field1\":{\"type\":\"text\"}}," //
+ + "\"dynamic_templates\":[{\"strings\":{\"match_mapping_type\":\"string\"," //
+ + "\"mapping\":{\"type\":\"keyword\"}}}]}}}";
+ try (final XContentParser parser =
+ JsonXContent.jsonXContent.createParser(NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, json)) {
+ final GetMappingsResponse response = HttpGetMappingsAction.fromXContent(parser);
+ assertNotNull(response);
+ final Map mappings = response.mappings();
+ // dynamic_templates is skipped for compatibility with older versions.
+ assertFalse(mappings.containsKey("dynamic_templates"));
+ assertEquals(1, mappings.size());
+ }
+ }
+}
diff --git a/src/test/java/org/codelibs/fesen/client/action/HttpIndicesAliasesActionTest.java b/src/test/java/org/codelibs/fesen/client/action/HttpIndicesAliasesActionTest.java
new file mode 100644
index 0000000..0da4e43
--- /dev/null
+++ b/src/test/java/org/codelibs/fesen/client/action/HttpIndicesAliasesActionTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2012-2025 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fesen.client.action;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+import org.opensearch.action.admin.indices.alias.IndicesAliasesAction;
+import org.opensearch.action.admin.indices.alias.IndicesAliasesRequest;
+import org.opensearch.action.admin.indices.alias.IndicesAliasesRequest.AliasActions;
+
+class HttpIndicesAliasesActionTest {
+
+ /**
+ * Builds the request body exactly as the production action does, by driving the
+ * real {@link HttpIndicesAliasesAction#getCurlRequest} and reading the emitted body.
+ */
+ private static String toBody(final IndicesAliasesRequest request) {
+ final HttpIndicesAliasesAction action = new HttpIndicesAliasesAction(ActionTestUtils.testClient(), IndicesAliasesAction.INSTANCE);
+ return action.getCurlRequest(request).body();
+ }
+
+ @Test
+ void test_body_addAction_writeIndexAndHidden() {
+ final IndicesAliasesRequest request = new IndicesAliasesRequest();
+ request.addAliasAction(AliasActions.add().index("test-index").alias("test-alias").writeIndex(true).isHidden(true));
+ final String body = toBody(request);
+ assertTrue(body.contains("\"is_write_index\":true"), body);
+ assertTrue(body.contains("\"is_hidden\":true"), body);
+ }
+
+ @Test
+ void test_body_addAction_routing() {
+ final IndicesAliasesRequest request = new IndicesAliasesRequest();
+ request.addAliasAction(AliasActions.add().index("test-index").alias("test-alias").routing("r1"));
+ final String body = toBody(request);
+ assertTrue(body.contains("\"routing\":\"r1\""), body);
+ }
+
+ @Test
+ void test_body_removeIndex_noBogusAliases() {
+ final IndicesAliasesRequest request = new IndicesAliasesRequest();
+ request.addAliasAction(AliasActions.removeIndex().index("old-index"));
+ final String body = toBody(request);
+ assertTrue(body.contains("remove_index"), body);
+ assertFalse(body.contains("\"aliases\""), body);
+ }
+}
diff --git a/src/test/java/org/codelibs/fesen/client/action/HttpOpenIndexActionTest.java b/src/test/java/org/codelibs/fesen/client/action/HttpOpenIndexActionTest.java
new file mode 100644
index 0000000..ec02508
--- /dev/null
+++ b/src/test/java/org/codelibs/fesen/client/action/HttpOpenIndexActionTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2012-2025 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fesen.client.action;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+import java.util.Map;
+
+import org.junit.jupiter.api.Test;
+import org.opensearch.action.admin.indices.open.OpenIndexAction;
+import org.opensearch.action.admin.indices.open.OpenIndexRequest;
+import org.opensearch.action.support.ActiveShardCount;
+
+class HttpOpenIndexActionTest {
+
+ private final HttpOpenIndexAction clientAction = new HttpOpenIndexAction(ActionTestUtils.testClient(), OpenIndexAction.INSTANCE);
+
+ @Test
+ void test_getCurlRequest_waitForActiveShardsAll() {
+ final OpenIndexRequest request = new OpenIndexRequest("test-index").waitForActiveShards(ActiveShardCount.ALL);
+ final Map params = ActionTestUtils.params(clientAction.getCurlRequest(request));
+ assertEquals("all", params.get("wait_for_active_shards"));
+ }
+
+ @Test
+ void test_getCurlRequest_waitForActiveShardsDefaultNotSent() {
+ final OpenIndexRequest request = new OpenIndexRequest("test-index");
+ final Map params = ActionTestUtils.params(clientAction.getCurlRequest(request));
+ assertFalse(params.containsKey("wait_for_active_shards"));
+ }
+
+ @Test
+ void test_getCurlRequest_waitForActiveShardsCount() {
+ final OpenIndexRequest request = new OpenIndexRequest("test-index").waitForActiveShards(ActiveShardCount.from(2));
+ final Map params = ActionTestUtils.params(clientAction.getCurlRequest(request));
+ assertEquals("2", params.get("wait_for_active_shards"));
+ }
+}
diff --git a/src/test/java/org/codelibs/fesen/client/action/HttpRefreshActionTest.java b/src/test/java/org/codelibs/fesen/client/action/HttpRefreshActionTest.java
new file mode 100644
index 0000000..aab5b72
--- /dev/null
+++ b/src/test/java/org/codelibs/fesen/client/action/HttpRefreshActionTest.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2012-2025 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fesen.client.action;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.Map;
+
+import org.junit.jupiter.api.Test;
+import org.opensearch.action.admin.indices.refresh.RefreshAction;
+import org.opensearch.action.admin.indices.refresh.RefreshRequest;
+import org.opensearch.action.support.IndicesOptions;
+
+class HttpRefreshActionTest {
+
+ private final HttpRefreshAction action = new HttpRefreshAction(ActionTestUtils.testClient(), RefreshAction.INSTANCE);
+
+ @Test
+ void test_getCurlRequest_indicesOptions() {
+ final RefreshRequest request = new RefreshRequest("test-index");
+ request.indicesOptions(IndicesOptions.fromOptions(true, false, false, true));
+ final Map params = ActionTestUtils.params(action.getCurlRequest(request));
+ assertEquals("true", params.get("ignore_unavailable"));
+ assertEquals("false", params.get("allow_no_indices"));
+ assertEquals("closed", params.get("expand_wildcards"));
+ }
+}
diff --git a/src/test/java/org/codelibs/fesen/client/action/HttpReindexActionTest.java b/src/test/java/org/codelibs/fesen/client/action/HttpReindexActionTest.java
index 9b7aad4..781f4c3 100644
--- a/src/test/java/org/codelibs/fesen/client/action/HttpReindexActionTest.java
+++ b/src/test/java/org/codelibs/fesen/client/action/HttpReindexActionTest.java
@@ -18,6 +18,9 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Map;
import org.junit.jupiter.api.Test;
import org.opensearch.common.xcontent.json.JsonXContent;
@@ -26,11 +29,72 @@
import org.opensearch.core.xcontent.XContentParser;
import org.opensearch.index.reindex.BulkByScrollResponse;
import org.opensearch.index.reindex.ReindexAction;
+import org.opensearch.index.reindex.ReindexRequest;
class HttpReindexActionTest {
private final HttpReindexAction action = new HttpReindexAction(null, ReindexAction.INSTANCE);
+ private final HttpReindexAction clientAction = new HttpReindexAction(ActionTestUtils.testClient(), ReindexAction.INSTANCE);
+
+ private static ReindexRequest reindexRequest() {
+ final ReindexRequest request = new ReindexRequest();
+ request.setSourceIndices("src");
+ request.setDestIndex("dst");
+ return request;
+ }
+
+ @Test
+ void test_getCurlRequest_endpointAndCommonParams() {
+ final ReindexRequest request = reindexRequest();
+ request.setRefresh(true);
+ request.setSlices(3);
+ request.setRequestsPerSecond(500f);
+ request.setWaitForActiveShards(2);
+ request.setMaxDocs(25);
+ request.setAbortOnVersionConflict(false);
+ final Map params = ActionTestUtils.params(clientAction.getCurlRequest(request));
+ // Endpoint is POST /_reindex with no index in the path.
+ assertTrue(ActionTestUtils.url(clientAction.getCurlRequest(request)).endsWith("/_reindex"));
+ // Critical: wait_for_completion is always forced so the body can be parsed synchronously.
+ assertEquals("true", params.get("wait_for_completion"));
+ assertEquals("true", params.get("refresh"));
+ assertEquals("3", params.get("slices"));
+ assertEquals(Float.toString(request.getRequestsPerSecond()), params.get("requests_per_second"));
+ assertEquals("2", params.get("wait_for_active_shards"));
+ // For reindex, max_docs and conflicts ride in the BODY, never as query parameters.
+ assertFalse(params.containsKey("max_docs"));
+ assertFalse(params.containsKey("conflicts"));
+ }
+
+ @Test
+ void test_getCurlRequest_body_carriesSourceDestMaxDocsConflicts() {
+ final ReindexRequest request = reindexRequest();
+ request.setMaxDocs(25);
+ request.setAbortOnVersionConflict(false);
+ final String body = clientAction.toSource(request);
+ assertTrue(body.contains("\"source\""));
+ assertTrue(body.contains("src"));
+ assertTrue(body.contains("\"dest\""));
+ assertTrue(body.contains("dst"));
+ assertTrue(body.contains("\"max_docs\""));
+ assertTrue(body.contains("\"conflicts\""));
+ }
+
+ @Test
+ void test_getCurlRequest_waitForCompletionAlwaysTrueAndNoLeakedParams() {
+ final ReindexRequest request = reindexRequest();
+ final Map params = ActionTestUtils.params(clientAction.getCurlRequest(request));
+ assertEquals("true", params.get("wait_for_completion"));
+ // Optional parameters are omitted when unset.
+ assertFalse(params.containsKey("refresh"));
+ assertFalse(params.containsKey("slices"));
+ assertFalse(params.containsKey("requests_per_second"));
+ assertFalse(params.containsKey("wait_for_active_shards"));
+ assertFalse(params.containsKey("conflicts"));
+ assertFalse(params.containsKey("max_docs"));
+ }
+
@Test
void test_fromXContent() throws Exception {
final String json = "{\"took\":147,\"timed_out\":false,\"total\":120,\"updated\":0,\"created\":120,\"deleted\":0,"
diff --git a/src/test/java/org/codelibs/fesen/client/action/HttpResolveIndexActionTest.java b/src/test/java/org/codelibs/fesen/client/action/HttpResolveIndexActionTest.java
index 04abed4..db10981 100644
--- a/src/test/java/org/codelibs/fesen/client/action/HttpResolveIndexActionTest.java
+++ b/src/test/java/org/codelibs/fesen/client/action/HttpResolveIndexActionTest.java
@@ -18,14 +18,17 @@
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException;
+import java.util.Map;
import org.junit.jupiter.api.Test;
import org.opensearch.action.admin.indices.resolve.ResolveIndexAction;
import org.opensearch.action.admin.indices.resolve.ResolveIndexAction.ResolvedAlias;
import org.opensearch.action.admin.indices.resolve.ResolveIndexAction.ResolvedDataStream;
import org.opensearch.action.admin.indices.resolve.ResolveIndexAction.ResolvedIndex;
+import org.opensearch.action.support.IndicesOptions;
import org.opensearch.common.xcontent.json.JsonXContent;
import org.opensearch.core.xcontent.DeprecationHandler;
import org.opensearch.core.xcontent.NamedXContentRegistry;
@@ -35,6 +38,21 @@ class HttpResolveIndexActionTest {
private final HttpResolveIndexAction action = new HttpResolveIndexAction(null, ResolveIndexAction.INSTANCE);
+ private final HttpResolveIndexAction clientAction =
+ new HttpResolveIndexAction(ActionTestUtils.testClient(), ResolveIndexAction.INSTANCE);
+
+ @Test
+ void test_getCurlRequest_urlAndIndicesOptions() {
+ final ResolveIndexAction.Request request =
+ new ResolveIndexAction.Request(new String[] { "idx1", "idx2" }, IndicesOptions.fromOptions(true, false, false, true));
+ // The index pattern is carried in the path under /_resolve/index/.
+ assertTrue(ActionTestUtils.url(clientAction.getCurlRequest(request)).contains("/_resolve/index/idx1,idx2"));
+ final Map params = ActionTestUtils.params(clientAction.getCurlRequest(request));
+ assertEquals("true", params.get("ignore_unavailable"));
+ assertEquals("false", params.get("allow_no_indices"));
+ assertEquals("closed", params.get("expand_wildcards"));
+ }
+
@Test
void test_fromXContent() throws IOException {
final String json = "{" //
diff --git a/src/test/java/org/codelibs/fesen/client/action/HttpUpdateActionTest.java b/src/test/java/org/codelibs/fesen/client/action/HttpUpdateActionTest.java
new file mode 100644
index 0000000..13bbd24
--- /dev/null
+++ b/src/test/java/org/codelibs/fesen/client/action/HttpUpdateActionTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2012-2025 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fesen.client.action;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+import java.util.Map;
+
+import org.junit.jupiter.api.Test;
+import org.opensearch.action.update.UpdateAction;
+import org.opensearch.action.update.UpdateRequest;
+
+class HttpUpdateActionTest {
+
+ private final HttpUpdateAction clientAction = new HttpUpdateAction(ActionTestUtils.testClient(), UpdateAction.INSTANCE);
+
+ /**
+ * The {@code _update} REST endpoint (RestUpdateAction) rejects {@code version}/{@code version_type}
+ * with HTTP 400 because internal versioning is illegal for updates (UpdateRequest itself throws
+ * UnsupportedOperationException on its version/versionType setters and always reports MATCH_ANY /
+ * INTERNAL). Concurrency control must go through if_seq_no/if_primary_term instead. This action must
+ * therefore never emit version params.
+ */
+ @Test
+ void test_getCurlRequest_versionParamsNotSent() {
+ final UpdateRequest request = new UpdateRequest("test-index", "1");
+ final Map params = ActionTestUtils.params(clientAction.getCurlRequest(request));
+ assertFalse(params.containsKey("version"));
+ assertFalse(params.containsKey("version_type"));
+ }
+
+ @Test
+ void test_getCurlRequest_ifSeqNoAndPrimaryTerm() {
+ final UpdateRequest request = new UpdateRequest("test-index", "1").setIfSeqNo(5L).setIfPrimaryTerm(2L);
+ final Map params = ActionTestUtils.params(clientAction.getCurlRequest(request));
+ assertEquals("5", params.get("if_seq_no"));
+ assertEquals("2", params.get("if_primary_term"));
+ }
+}
diff --git a/src/test/java/org/codelibs/fesen/client/action/HttpUpdateByQueryActionTest.java b/src/test/java/org/codelibs/fesen/client/action/HttpUpdateByQueryActionTest.java
index 7418f1d..23eda32 100644
--- a/src/test/java/org/codelibs/fesen/client/action/HttpUpdateByQueryActionTest.java
+++ b/src/test/java/org/codelibs/fesen/client/action/HttpUpdateByQueryActionTest.java
@@ -18,6 +18,9 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Map;
import org.junit.jupiter.api.Test;
import org.opensearch.common.xcontent.json.JsonXContent;
@@ -26,11 +29,50 @@
import org.opensearch.core.xcontent.XContentParser;
import org.opensearch.index.reindex.BulkByScrollResponse;
import org.opensearch.index.reindex.UpdateByQueryAction;
+import org.opensearch.index.reindex.UpdateByQueryRequest;
class HttpUpdateByQueryActionTest {
private final HttpUpdateByQueryAction action = new HttpUpdateByQueryAction(null, UpdateByQueryAction.INSTANCE);
+ private final HttpUpdateByQueryAction clientAction =
+ new HttpUpdateByQueryAction(ActionTestUtils.testClient(), UpdateByQueryAction.INSTANCE);
+
+ @Test
+ void test_getCurlRequest_endpointAndParams() {
+ final UpdateByQueryRequest request = new UpdateByQueryRequest("idx");
+ request.setAbortOnVersionConflict(false);
+ request.setMaxDocs(50);
+ request.setSlices(4);
+ request.setRequestsPerSecond(250f);
+ request.setRouting("r1");
+ request.setPipeline("p1");
+ final Map params = ActionTestUtils.params(clientAction.getCurlRequest(request));
+ // Endpoint is POST /idx/_update_by_query.
+ assertTrue(ActionTestUtils.url(clientAction.getCurlRequest(request)).contains("/idx/_update_by_query"));
+ assertEquals("true", params.get("wait_for_completion"));
+ // For by-query, conflicts and max_docs are query PARAMETERS (unlike reindex, where they ride in the body).
+ assertEquals("proceed", params.get("conflicts"));
+ assertEquals("50", params.get("max_docs"));
+ assertEquals("4", params.get("slices"));
+ assertEquals(Float.toString(request.getRequestsPerSecond()), params.get("requests_per_second"));
+ assertEquals("r1", params.get("routing"));
+ assertEquals("p1", params.get("pipeline"));
+ }
+
+ @Test
+ void test_getCurlRequest_defaultsOmitOptionalParams() {
+ final UpdateByQueryRequest request = new UpdateByQueryRequest("idx");
+ final Map params = ActionTestUtils.params(clientAction.getCurlRequest(request));
+ assertEquals("true", params.get("wait_for_completion"));
+ // conflicts defaults to abort, so it is not emitted; the remaining optionals stay unset.
+ assertFalse(params.containsKey("conflicts"));
+ assertFalse(params.containsKey("max_docs"));
+ assertFalse(params.containsKey("slices"));
+ assertFalse(params.containsKey("routing"));
+ assertFalse(params.containsKey("pipeline"));
+ }
+
@Test
void test_fromXContent() throws Exception {
final String json = "{\"took\":320,\"timed_out\":false,\"total\":100,\"updated\":80,\"created\":0,\"deleted\":0,"