This is an automated email from the ASF dual-hosted git repository. spmallette pushed a commit to branch TINKERPOP-3238 in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit 88b222949ce62a028afcbfa7d4354e5483750168 Author: Stephen Mallette <[email protected]> AuthorDate: Mon Jun 15 16:25:27 2026 -0400 TINKERPOP-3238 Throw FailResponseException for fail() step over remote The Java driver now throws a FailResponseException, a subclass of ResponseException that implements Failure, when the server returns a SERVER_ERROR_FAIL_STEP (595) status code. This makes remote handling of the fail() step more consistent with embedded behavior. Failure.format() is guarded against null traversal/traverser context since that data is not transmitted from the server, preventing a NullPointerException when formatting a remotely-reconstructed Failure. Adds unit tests for both the defensive format() logic and the new exception, plus documentation in the reference and upgrade guides. Assisted-by: Claude Code:claude-opus-4-8 --- CHANGELOG.asciidoc | 1 + docs/src/reference/the-traversal.asciidoc | 14 +++ docs/src/upgrade/release-3.7.x.asciidoc | 17 ++++ .../gremlin/process/traversal/Failure.java | 107 ++++++++++++++------- .../gremlin/process/traversal/FailureTest.java | 101 +++++++++++++++++++ .../apache/tinkerpop/gremlin/driver/Handler.java | 13 ++- .../driver/exception/FailResponseException.java | 60 ++++++++++++ .../exception/FailResponseExceptionTest.java | 65 +++++++++++++ .../gremlin/server/GremlinServerIntegrateTest.java | 4 + 9 files changed, 346 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index e07f7b64a1..8f37ba96ad 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -26,6 +26,7 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima === TinkerPop 3.7.7 (Release Date: NOT OFFICIALLY RELEASED YET) * Added `NextN(n)` to `Traversal` in `gremlin-go` for batched result iteration, providing API parity with `next(n)` in the Java, Python, and .NET GLVs. +* Added `FailResponseException` to `gremlin-driver` which is thrown `fail()` step is triggered on the server making it more consistent with embedded behavior. * Fixed conjoin has incorrect null handling. * Expanded `gremlin-python` CI matrix to test against Python 3.9, 3.10, 3.11, 3.12, and 3.13. * Add Node 26 support for `gremlin-javascript` and `gremlint`. diff --git a/docs/src/reference/the-traversal.asciidoc b/docs/src/reference/the-traversal.asciidoc index 0715d31107..5876c36758 100644 --- a/docs/src/reference/the-traversal.asciidoc +++ b/docs/src/reference/the-traversal.asciidoc @@ -1667,6 +1667,20 @@ rollback. Moreover, the ability to rollback at all is graph provider dependent. configured without transaction support, will simply be left in a partially mutated state whether the action to rollback on `fail()` was implemented or not. +The type of exception that `fail()` produces depends on how the traversal is executed: + +* *Embedded* - When executed directly against a `Graph` instance in the same JVM, `fail()` throws a +`FailStep.FailException` which implements the `Failure` interface. The `Failure` provides programmatic access to the +failure `Message`, `Traverser`, `Traversal` and `Metadata` as shown in the formatted output above. +* *Remote (HTTP)* - When executed against Gremlin Server over HTTP, the server returns a `500` error with a message that +indicates that the `fail()` step was triggered. The richer `Failure` data described above is not retained. +* *Remote (WebSockets)* - When executed against Gremlin Server over WebSockets, the server returns the +`595`/`SERVER_ERROR_FAIL_STEP` status code. In the Java driver this surfaces as a `FailResponseException`, a subclass of +`ResponseException` that also implements the `Failure` interface so that the failure can be caught and handled in a +manner more consistent with the embedded case. Note that the `Failure` data (e.g. `Traverser`, `Traversal`, `Metadata`) +is not transmitted from the server, so those accessors return empty or `null` values. Other Gremlin Language Variants +continue to surface this condition as their standard remote exception type. + *Additional References* link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#fail()++[`fail()`], diff --git a/docs/src/upgrade/release-3.7.x.asciidoc b/docs/src/upgrade/release-3.7.x.asciidoc index 70fea1d83e..0d7e175746 100644 --- a/docs/src/upgrade/release-3.7.x.asciidoc +++ b/docs/src/upgrade/release-3.7.x.asciidoc @@ -57,7 +57,24 @@ an empty string instead. See: link:https://issues.apache.org/jira/browse/TINKERPOP-3225[TINKERPOP-3225] +==== FailResponseException for fail() Step +The purpose of `fail()` step is to allow the user to raise error conditions when a traversal takes a particular path. +It provides exception feedback to the user while immediately stopping the traversal in an error state. In embedded +cases, it raises a `FailException` which implements the `Failure` interface and provides more details about the failure +itself. Unfortunately, remote cases behaved differently and returned the common `ResponseException`. While it was +possible to check the response code on this exception, it was not possible to catch the exception in quite the same way +as embedded. + +To remedy this shortcoming, this version introduces a new `FailResponseException` which extends `ResponseException` and +implements the `Failure` interface. When the driver encounters a 595 status code, an indicator that the `fail()` step +was triggered on the server, it will throw a `FailResponseException` instead of the generic `ResponseException`. Note +that the server does not send back many of the internal details needed to fully form a `Failure`, so the +`FailResponseException` is really just a marker that makes it easier to trap these types of errors. + +Note that this change is for Java only and designed to better align embedded and remote use cases. + +See: link:https://issues.apache.org/jira/browse/TINKERPOP-3238[TINKERPOP-3238] == TinkerPop 3.7.6 diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Failure.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Failure.java index 775e685eea..3c5b2ed0e8 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Failure.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Failure.java @@ -18,7 +18,7 @@ */ package org.apache.tinkerpop.gremlin.process.traversal; -import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.FailStep; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; import org.apache.tinkerpop.gremlin.process.traversal.step.util.EmptyStep; import org.apache.tinkerpop.gremlin.process.traversal.translator.GroovyTranslator; import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement; @@ -27,20 +27,45 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; +/** + * Represents a failure raised by the {@link GraphTraversal#fail()} step which forces a traversal to immediately stop + * in an error state. Implementations carry the contextual information about where and why the failure occurred so that + * it can be inspected programmatically or rendered for display by way of {@link #format()}. + * <p/> + * When a {@code fail()} occurs in embedded mode the full context (the offending {@link Traverser} and {@link Traversal}) + * is available. When reconstructed from a remote server response, that context is not transmitted and the relevant + * accessors may return {@code null}. Implementations and callers should account for that possibility. + * + * @author Stephen Mallette (http://stephen.genoprime.com) + */ public interface Failure { static Translator.ScriptTranslator TRANSLATOR = GroovyTranslator.of(""); + /** + * Gets the message associated with the failure as provided to the {@code fail()} step. + */ String getMessage(); + /** + * Gets any additional metadata associated with the failure. Returns an empty {@code Map} when there is no metadata. + */ Map<String,Object> getMetadata(); + /** + * Gets the {@link Traverser} that was being processed when the failure was triggered. May return {@code null} when + * the {@code Failure} was reconstructed from a remote response where this context is not available. + */ Traverser.Admin getTraverser(); + /** + * Gets the {@link Traversal} that contained the {@code fail()} step that triggered the failure. May return + * {@code null} when the {@code Failure} was reconstructed from a remote response where this context is not + * available. + */ Traversal.Admin getTraversal(); /** @@ -48,44 +73,58 @@ public interface Failure { */ public default String format() { final List<String> lines = new ArrayList<>(); - final Step parentStep = (Step) getTraversal().getParent(); + + // some Failure implementations - notably those constructed from a remote response such as + // FailResponseException - do not carry the traversal/traverser context as that data is not transmitted + // from the server. guard against null returns so that format() degrades gracefully rather than throwing + // a NullPointerException. + final Traversal.Admin traversal = getTraversal(); + final Traverser.Admin traverser = getTraverser(); + final Step parentStep = traversal != null ? (Step) traversal.getParent() : null; lines.add(String.format("Message > %s", getMessage())); - lines.add(String.format("Traverser> %s", getTraverser().toString())); - final TraverserGenerator generator = getTraversal().getTraverserGenerator(); - final Traverser.Admin traverser = getTraverser(); - if (generator.getProvidedRequirements().contains(TraverserRequirement.BULK)) { - lines.add(String.format(" Bulk > %s", traverser.bulk())); - } - if (generator.getProvidedRequirements().contains(TraverserRequirement.SACK)) { - lines.add(String.format(" Sack > %s", traverser.sack())); - } - if (generator.getProvidedRequirements().contains(TraverserRequirement.PATH)) { - lines.add(String.format(" Path > %s", traverser.path())); - } - if (generator.getProvidedRequirements().contains(TraverserRequirement.SINGLE_LOOP) || - generator.getProvidedRequirements().contains(TraverserRequirement.NESTED_LOOP) ) { - final Set<String> loopNames = traverser.getLoopNames(); - final String loopsLine = loopNames.isEmpty() ? - String.valueOf(traverser.asAdmin().loops()) : - loopNames.stream().collect(Collectors.toMap(loopName -> loopName, traverser::loops)).toString(); - lines.add(String.format(" Loops > %s", loopsLine)); - } - if (generator.getProvidedRequirements().contains(TraverserRequirement.SIDE_EFFECTS)) { - final TraversalSideEffects tse = traverser.getSideEffects(); - final Set<String> keys = tse.keys(); - lines.add(String.format(" S/E > %s", keys.stream().collect(Collectors.toMap(k -> k, tse::get)))); + // not sure how you'd have one without the other really + if (traverser != null) { + lines.add(String.format("Traverser> %s", traverser.toString())); + + if (traversal != null) { + final TraverserGenerator generator = traversal.getTraverserGenerator(); + if (generator.getProvidedRequirements().contains(TraverserRequirement.BULK)) { + lines.add(String.format(" Bulk > %s", traverser.bulk())); + } + if (generator.getProvidedRequirements().contains(TraverserRequirement.SACK)) { + lines.add(String.format(" Sack > %s", traverser.sack())); + } + if (generator.getProvidedRequirements().contains(TraverserRequirement.PATH)) { + lines.add(String.format(" Path > %s", traverser.path())); + } + if (generator.getProvidedRequirements().contains(TraverserRequirement.SINGLE_LOOP) || + generator.getProvidedRequirements().contains(TraverserRequirement.NESTED_LOOP) ) { + final Set<String> loopNames = traverser.getLoopNames(); + final String loopsLine = loopNames.isEmpty() ? + String.valueOf(traverser.asAdmin().loops()) : + loopNames.stream().collect(Collectors.toMap(loopName -> loopName, traverser::loops)).toString(); + lines.add(String.format(" Loops > %s", loopsLine)); + } + if (generator.getProvidedRequirements().contains(TraverserRequirement.SIDE_EFFECTS)) { + final TraversalSideEffects tse = traverser.getSideEffects(); + final Set<String> keys = tse.keys(); + lines.add(String.format(" S/E > %s", keys.stream().collect(Collectors.toMap(k -> k, tse::get)))); + } + } } - // removes the starting period so that "__.out()" simply presents as "out()" - lines.add(String.format("Traversal> %s", TRANSLATOR.translate(getTraversal()).getScript().substring(1))); + if (traversal != null) { + // removes the starting period so that "__.out()" simply presents as "out()" + lines.add(String.format("Traversal> %s", TRANSLATOR.translate(traversal).getScript().substring(1))); - // not sure there is a situation where fail() would be used where it was not wrapped in a parent, - // but on the odd case that it is it can be handled - if (parentStep != EmptyStep.instance()) { - lines.add(String.format("Parent > %s [%s]", - parentStep.getClass().getSimpleName(), TRANSLATOR.translate(parentStep.getTraversal()).getScript().substring(1))); + // not sure there is a situation where fail() would be used where it was not wrapped in a parent, + // but on the odd case that it is it can be handled + if (parentStep != null && parentStep != EmptyStep.instance()) { + lines.add(String.format("Parent > %s [%s]", + parentStep.getClass().getSimpleName(), TRANSLATOR.translate(parentStep.getTraversal()).getScript().substring(1))); + } } lines.add(String.format("Metadata > %s", getMetadata())); diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/FailureTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/FailureTest.java new file mode 100644 index 0000000000..351a53fce9 --- /dev/null +++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/FailureTest.java @@ -0,0 +1,101 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.tinkerpop.gremlin.process.traversal; + +import org.junit.Test; + +import java.util.Collections; +import java.util.Map; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertNotNull; + +/** + * Tests for the default methods on the {@link Failure} interface, with particular focus on {@link Failure#format()} + * gracefully handling {@code Failure} implementations that do not carry traversal/traverser context (e.g. those + * constructed from a remote response such as {@code FailResponseException}). + */ +public class FailureTest { + + /** + * Minimal {@link Failure} that returns {@code null} for the traversal and traverser context the same way a + * {@code Failure} reconstructed from a remote server response would. + */ + private static class RemoteLikeFailure implements Failure { + private final String message; + private final Map<String, Object> metadata; + + private RemoteLikeFailure(final String message, final Map<String, Object> metadata) { + this.message = message; + this.metadata = metadata; + } + + @Override + public String getMessage() { + return message; + } + + @Override + public Map<String, Object> getMetadata() { + return metadata; + } + + @Override + public Traverser.Admin getTraverser() { + return null; + } + + @Override + public Traversal.Admin getTraversal() { + return null; + } + } + + @Test + public void shouldFormatWithoutTraversalAndTraverserContext() { + final Failure failure = new RemoteLikeFailure("make it stop", Collections.emptyMap()); + + // would throw a NullPointerException prior to the null guards being added to format() + final String formatted = failure.format(); + + assertNotNull(formatted); + assertThat(formatted, containsString("fail() Step Triggered")); + assertThat(formatted, containsString("Message > make it stop")); + assertThat(formatted, containsString("Metadata > {}")); + + // these sections depend on traversal/traverser context which is absent so they should be omitted entirely + assertThat(formatted, not(containsString("Traverser>"))); + assertThat(formatted, not(containsString("Traversal>"))); + assertThat(formatted, not(containsString("Parent >"))); + } + + @Test + public void shouldFormatWithNullMessageAndMetadata() { + final Failure failure = new RemoteLikeFailure(null, null); + + // null message and metadata should still format without error + final String formatted = failure.format(); + + assertNotNull(formatted); + assertThat(formatted, containsString("Message > null")); + assertThat(formatted, containsString("Metadata > null")); + } +} diff --git a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Handler.java b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Handler.java index 588c54f686..3b1adca514 100644 --- a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Handler.java +++ b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Handler.java @@ -20,6 +20,7 @@ package org.apache.tinkerpop.gremlin.driver; import io.netty.util.AttributeMap; import org.apache.commons.lang3.exception.ExceptionUtils; +import org.apache.tinkerpop.gremlin.driver.exception.FailResponseException; import org.apache.tinkerpop.gremlin.driver.exception.ResponseException; import org.apache.tinkerpop.gremlin.util.Tokens; import org.apache.tinkerpop.gremlin.util.message.RequestMessage; @@ -242,8 +243,16 @@ final class Handler { (String) attributes.get(Tokens.STATUS_ATTRIBUTE_STACK_TRACE) : null; final List<String> exceptions = attributes.containsKey(Tokens.STATUS_ATTRIBUTE_EXCEPTIONS) ? (List<String>) attributes.get(Tokens.STATUS_ATTRIBUTE_EXCEPTIONS) : null; - queue.markError(new ResponseException(response.getStatus().getCode(), response.getStatus().getMessage(), - exceptions, stackTrace, cleanStatusAttributes(attributes))); + + // a SERVER_ERROR_FAIL_STEP indicates that the traversal triggered a fail() step on the server. + // throw the more specific FailResponseException which implements Failure so that handling can + // be made more consistent with the local fail() behavior. + final ResponseException responseException = statusCode == ResponseStatusCode.SERVER_ERROR_FAIL_STEP ? + new FailResponseException(response.getStatus().getMessage(), exceptions, stackTrace, + cleanStatusAttributes(attributes)) : + new ResponseException(response.getStatus().getCode(), response.getStatus().getMessage(), + exceptions, stackTrace, cleanStatusAttributes(attributes)); + queue.markError(responseException); } } diff --git a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/exception/FailResponseException.java b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/exception/FailResponseException.java new file mode 100644 index 0000000000..2529349de1 --- /dev/null +++ b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/exception/FailResponseException.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.tinkerpop.gremlin.driver.exception; + +import org.apache.tinkerpop.gremlin.process.traversal.Failure; +import org.apache.tinkerpop.gremlin.process.traversal.Traversal; +import org.apache.tinkerpop.gremlin.process.traversal.Traverser; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; +import org.apache.tinkerpop.gremlin.util.message.ResponseStatusCode; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * Provides a {@link Failure} implementation for {@link ResponseException}. This exception is thrown instead of + * a {@code ResponseException} when the server returns a {@code status.code} of + * {@link ResponseStatusCode#SERVER_ERROR_FAIL_STEP} which indicates that a step in the traversal failed by way of + * {@link GraphTraversal#fail()}. This approach helps make remote exception handling for that step more consistent + * with the local {@link GraphTraversal#fail()} behavior. + */ +public class FailResponseException extends ResponseException implements Failure { + public FailResponseException(final String serverMessage, + final List<String> remoteExceptionHierarchy, final String remoteStackTrace, + final Map<String,Object> statusAttributes) { + super(ResponseStatusCode.SERVER_ERROR_FAIL_STEP, serverMessage, remoteExceptionHierarchy, + remoteStackTrace, statusAttributes); + } + + @Override + public Map<String, Object> getMetadata() { + return Collections.emptyMap(); + } + + @Override + public Traverser.Admin getTraverser() { + return null; + } + + @Override + public Traversal.Admin getTraversal() { + return null; + } +} diff --git a/gremlin-driver/src/test/java/org/apache/tinkerpop/gremlin/driver/exception/FailResponseExceptionTest.java b/gremlin-driver/src/test/java/org/apache/tinkerpop/gremlin/driver/exception/FailResponseExceptionTest.java new file mode 100644 index 0000000000..53dbea7247 --- /dev/null +++ b/gremlin-driver/src/test/java/org/apache/tinkerpop/gremlin/driver/exception/FailResponseExceptionTest.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.tinkerpop.gremlin.driver.exception; + +import org.apache.tinkerpop.gremlin.process.traversal.Failure; +import org.apache.tinkerpop.gremlin.util.message.ResponseStatusCode; +import org.junit.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +public class FailResponseExceptionTest { + + @Test + public void shouldBeAResponseExceptionAndFailure() { + final FailResponseException ex = new FailResponseException("make it stop", null, null, null); + assertThat(ex, instanceOf(ResponseException.class)); + assertThat(ex, instanceOf(Failure.class)); + assertEquals(ResponseStatusCode.SERVER_ERROR_FAIL_STEP, ex.getResponseStatusCode()); + assertEquals("make it stop", ex.getMessage()); + } + + @Test + public void shouldReturnEmptyFailureContextSinceItIsNotTransmittedFromServer() { + final FailResponseException ex = new FailResponseException("make it stop", null, null, null); + assertTrue(ex.getMetadata().isEmpty()); + assertNull(ex.getTraverser()); + assertNull(ex.getTraversal()); + } + + @Test + public void shouldFormatWithoutThrowingWhenFailureContextIsAbsent() { + final FailResponseException ex = new FailResponseException("make it stop", null, null, null); + + // format() must not NPE even though getTraversal()/getTraverser() return null for a remotely + // reconstructed Failure + final String formatted = ex.format(); + + assertNotNull(formatted); + assertThat(formatted, containsString("fail() Step Triggered")); + assertThat(formatted, containsString("Message > make it stop")); + assertThat(formatted, containsString("Metadata > {}")); + } +} diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerIntegrateTest.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerIntegrateTest.java index df9e680609..5a6d327914 100644 --- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerIntegrateTest.java +++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerIntegrateTest.java @@ -27,6 +27,8 @@ import org.apache.commons.configuration2.BaseConfiguration; import org.apache.commons.configuration2.Configuration; import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.tinkerpop.gremlin.driver.exception.FailResponseException; +import org.apache.tinkerpop.gremlin.process.traversal.Failure; import org.apache.tinkerpop.gremlin.server.channel.HttpTestChannelizer; import org.apache.tinkerpop.gremlin.server.channel.TestChannelizer; import org.apache.tinkerpop.gremlin.server.channel.UnifiedChannelizer; @@ -1376,6 +1378,8 @@ public class GremlinServerIntegrateTest extends AbstractGremlinServerIntegration } catch (Exception ex) { final Throwable t = ex.getCause(); assertThat(t, instanceOf(ResponseException.class)); + assertThat(t, instanceOf(FailResponseException.class)); + assertThat(t, instanceOf(Failure.class)); assertEquals("make it stop", t.getMessage()); assertEquals(ResponseStatusCode.SERVER_ERROR_FAIL_STEP, ((ResponseException) t).getResponseStatusCode()); }
