与 Jersey 错误处理协议

Pact with Jersey error handling

我创建了这个很棒的控制点:

@Named
@Path("/awsome")
@Api(value = "An Awsome api")
public class AwsomeEndpoint extends BaseEndpoint {

  @GET
  @Path("{isThisAwsome}")
  @Produces(MediaType.APPLICATION_JSON)
  @ApiOperation(value = "Get organisation by isthisAwsome",
                notes = "Get AwsomeAnswer matching the given isThisAwsome.",
                responseContainer = "single result",
                response = JsonOrganisation.class)
  @ApiResponses(value = {@ApiResponse(code = 200, message = "Awsome found"),
                         @ApiResponse(code = 404, message = "Awsome not found"),
                         @ApiResponse(code = 500, message = "Internal Error")})
  public Response getAwsomeResponse(@PathParam("isThisAwsome") String isThisAwsome) {
    Response response = handleErrors(() -> Response.ok(AwsomeResponseTransformer.transform(awsomeService.getAwsomeByisThisAwsome(isThisAwsome))).build());
    return response;
  }
}

此代码使用 Lamda 来处理错误流:

public class BaseEndpoint {

  protected Response handleErrors(Supplier<Response> responseSupplier) {
    Response response;
    try {
      response = responseSupplier.get();
    } catch (AwsomeRuntimeException e) {
      response = createExceptionResponse(e);
    }
    return response;
  }

  private Response createExceptionResponse(AwsomeRuntimeException e) {
    Response.ResponseBuilder response;

    if (e.getExceptionStatus() == ExceptionStatus.NOT_FOUND) {
      response = Response.status(Status.NOT_FOUND).entity(e.getMessage());
    } else if (e.getExceptionStatus() == ExceptionStatus.ILLEGAL_ARGUMENT) {
      response = Response.status(Status.BAD_REQUEST).entity(e.getMessage());
    } else {
      response = Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage());
    }

    return response.header("","").header("Pragma", "no-cache, no-store").header("Cache-Control", "no-cache, no-store").header("Expires", "0").build();
  }}

为此我创建了一些 Pact 测试,所有的 pact 都有效,除非我们模拟我们调用的服务抛出的错误。

契约测试的例子class:

RunWith(PactRunner.class)
@Provider("awsome")
@PactBroker(authentication=@PactBrokerAuth(username = "${pact.broker.username:x}", password = "${pact.broker.password:x}"),
            protocol="${pact.broker.protocol:https}", host="${pact.broker.host:hosted.pact.dius.com.au}", port="${pact.broker.port:443}",
            failIfNoPactsFound=false)
public class AccMgtPactIntegrationTest extends JerseyTest {

  @TestTarget public final HttpTarget target = new HttpTarget(getPort());
  private AwsomeService awsomeService;
  private UserService         userService;

  @Override
  protected ResourceConfig configure() {
    awsomeService = mock(AwsomeService.class);
    return new ResourceConfig().register(new OrganisationsEndpoint(awsomeService)).register(new AwsomeEndpoint())
        //                               .property(ServerProperties.RESPONSE_SET_STATUS_OVER_SEND_ERROR, "true")
        ;
  }

  @State("Notn found")
  public void whenOrganisationWithShortNameDoesNotExist() {
    when(awsomeService.getOrganisationByShortName("WME")).thenThrow(new AwsomeRuntimeException(ExceptionStatus.NOT_FOUND));
  } }

现在我从 Pact 中得到了一些陈旧的错误,我认为这不是 pact 的问题,而是 girlzy 在 pact 中的工作方式。因为我收到以下错误:

 Verifying a pact between consumer and aswsome   Given when
 organisation with shortName does not exist   Get awsome by isItAwsome
     returns a response which
       has status code 404 (OK)
       includes headers
         "Content-Type" with value "application/json" (FAILED)
       has a matching body (FAILED)

Failures:

0) 按短名称获取组织 returns 一个响应,其中包括 headers "Content-Type" 值 "application/json" 预期 header 'Content-Type' 具有值 'application/json' 但 'text/html;charset=ISO-8859-1'

1) 通过短名称获取组织 returns 匹配 body 比较 -> 预期响应类型为 'application/json' 但实际类型为 'text/html'

现在,如果我设置(注释掉的属性)RESPONSE_SET_STATUS_OVER_SEND_ERROR

然后协议就这样失败了:

Verifying a pact between consumer and aswsome
  Given when organisation with shortName does not exist
  Get awsome by isItAwsome:
ul 13, 2017 12:54:14 PM org.glassfish.grizzly.servlet.ServletHandler doServletService
SEVERE: service exception:
javax.servlet.ServletException: javax.servlet.ServletException: java.lang.StringIndexOutOfBoundsException: String index out of range: 0
    at org.glassfish.grizzly.servlet.FilterChainImpl.doFilter(FilterChainImpl.java:151)
    at org.glassfish.grizzly.servlet.FilterChainImpl.invokeFilterChain(FilterChainImpl.java:106)
    at org.glassfish.grizzly.servlet.ServletHandler.doServletService(ServletHandler.java:224)
    at org.glassfish.grizzly.servlet.ServletHandler.service(ServletHandler.java:173)
    at org.glassfish.grizzly.http.server.HttpHandler.run(HttpHandler.java:224)
    at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:593)
    at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.run(AbstractThreadPool.java:573)
    at java.lang.Thread.run(Thread.java:745)
Caused by: javax.servlet.ServletException: java.lang.StringIndexOutOfBoundsException: String index out of range: 0
    at org.glassfish.jersey.servlet.WebComponent.serviceImpl(WebComponent.java:489)
    at org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:427)
    at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:388)
    at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:341)
    at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:228)
    at org.glassfish.grizzly.servlet.FilterChainImpl.doFilter(FilterChainImpl.java:147)
    ... 7 more
Caused by: java.lang.StringIndexOutOfBoundsException: String index out of range: 0
    at java.lang.String.charAt(String.java:658)
    at org.glassfish.grizzly.http.HttpHeader.isSpecialHeader(HttpHeader.java:925)
    at org.glassfish.grizzly.http.HttpHeader.handleGetSpecialHeader(HttpHeader.java:901)
    at org.glassfish.grizzly.http.HttpHeader.containsHeader(HttpHeader.java:762)
    at org.glassfish.grizzly.http.server.Response.containsHeader(Response.java:1268)
    at org.glassfish.grizzly.servlet.HttpServletResponseImpl.containsHeader(HttpServletResponseImpl.java:472)
    at org.glassfish.jersey.servlet.internal.ResponseWriter.writeResponseStatusAndHeaders(ResponseWriter.java:159)
    at org.glassfish.jersey.server.ServerRuntime$Responder.writeResponse(ServerRuntime.java:683)
    at org.glassfish.jersey.server.ServerRuntime$Responder.processResponse(ServerRuntime.java:444)
    at org.glassfish.jersey.server.ServerRuntime$Responder.process(ServerRuntime.java:434)
    at org.glassfish.jersey.server.ServerRuntime.run(ServerRuntime.java:329)
    at org.glassfish.jersey.internal.Errors.call(Errors.java:271)
    at org.glassfish.jersey.internal.Errors.call(Errors.java:267)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:297)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:267)
    at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:317)
    at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:305)
    at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:1154)
    at org.glassfish.jersey.servlet.WebComponent.serviceImpl(WebComponent.java:473)
    ... 12 more

    returns a response which
      has status code 404 (FAILED)
      includes headers
        "Content-Type" with value "application/json" (FAILED)
      has a matching body (FAILED)

Failures:

0) Get organisation by short name returns a response which includes headers "Content-Type" with value "application/json"
      Expected header 'Content-Type' to have value 'application/json' but was 'text/html;charset=ISO-8859-1'

1) Get organisation by short name returns a response which has a matching body
      comparison -> Expected a response type of 'application/json' but the actual type was 'text/html'

2) Get organisation by short name returns a response which has status code 

    404
          assert expectedStatus == actualStatus
                 |              |  |
                 404            |  500
                                false

感觉grizly不喜欢我为错误处理做的Lamda。但是我不想仅仅为了让我的提供者因为他的服务器容器而工作而改变我的代码。我一直在尝试调试代码的任何想法,它会正确流动,直到将它移交给容器。然后它似乎完全忽略了 Jersy 注释

我认为您的模拟设置不正确,这就是您没有处理正确异常的原因。而不是

when(awsomeService.getOrganisationByShortName("WME")).thenThrow(new AwsomeRuntimeException(ExceptionStatus.NOT_FOUND));

你能试试吗

when(awsomeService.getOrganisationByShortName(anyString()).thenThrow(new AwsomeRuntimeException(ExceptionStatus.NOT_FOUND));

上面的行应该可以解决您的嘲笑行为。

所以最后,修复感觉很脏,但它起作用了。似乎 grizley 容器并没有真正加载整个球衣。所以最后我最终将 JSON 响应放在我的模拟中,如下所示:

when(awsomeService.getOrganisationByShortName("WME")).thenThrow(new AwsomeRuntimeException(ExceptionStatus.NOT_FOUND,"{\n"
        + "    \"timestamp\": 1500040460362,\n" + "    \"status\": 404,\n" + "    \"error\": \"Not Found\",\n" + "    \"message\": \"Not Found\",\n"
        + "    \"path\": \"/awsome/isThisAwsome/ABCDE\"\n" + "}"));

Pact 似乎很高兴它变得像 JSON 可以映射到的响应一样。而且我不必在测试容器中加载比我想要的更多的 Jersy。 ( 我猜不出来)。

因此,总的来说,它的工作方式并不完全令人满意,但它是一种非标准流程,所以我现在可以接受。