Spring Cloud Contract:访问合同响应中的主机名和端口以生成URL

时间:2017-09-28 10:31:33

标签: groovy design-by-contract spring-cloud-contract

我们正在尝试提供具有以下特征的合同:

<?php if( !empty( $current_author_profile->user_url ) ) {?>
<li><i class="fa fa-link"></i><a href="<?php echo esc_url( $current_author_profile->user_url );?>" target="_blank" title="<?php echo esc_url( $current_author_profile->tagline );?>"><?php echo docdirect_parse_url( $current_author_profile->user_url);?></a></li>
<?php }?>

如果我们的客户端使用stubrunner并选择不同的端口,e.q。 9876,&#34; saveLink&#34;应该在响应URL中反映此端口。 我们无法找到一种简单的API方法来获取主机和端口信息。 fromRequest()或url()仅返回请求URL的相对部分。是否有API方法或简单的解决方案来满足此要求?还有其他建议吗?

1 个答案:

答案 0 :(得分:0)

首先,我认为你错过了合同测试的概念。我想知道为什么在那里拥有那些具体的价值观至关重要?在合同测试中,您只会对链接包含URL和端口的某些值感兴趣。你甚至不会打电话给那个URL。因此,您最有可能在继续前进之前改变您的方法。

如果您认为这是唯一的方法,我会就如何解决这个问题提出自己的意见(虽然我没有对它进行过测试,但它看起来应该可行;)。

目前使这一切工作并不容易。一旦https://github.com/spring-cloud/spring-cloud-contract/pull/429合并,即将发布的Edgware版本将更容易。

我会尝试考虑一种解决方法。我们要做的是添加一个转换机制,在从WireMock发回之前修改响应有效负载。我们需要转换类,我们也需要扩展现有的存根服务机制。

首先,让我们创建一个自定义WireMock扩展,它将分析WireMock使用的每个映射。我们只想修改其中包含saveLink的那个。

class CustomExtension extends ResponseTransformer {

    @Override
    String getName() {
        return "url-transformer";
    }

    /**
     * Transformer that converts the save the way we want it to look like
     */
    @Override
    Response transform(Request request, Response response, FileSource files, Parameters parameters) {
    if (requestRelatedToMyParticularCase(response)) {
      String body = "\"{\"saveLink\" : \"http://"+ url.host + ":" + url.port + "/v2/entity/save\"}\"";
      return new Response(response.getStatus(), response.getStatusMessage(),
                body, response.getHeaders(), response.wasConfigured(), response.getFault(), response.isFromProxy());
    }
    // if it's not related continue as usual
        return response;
    }

  private boolean requestRelatedToMyParticularCase(Response response) {
    // is it related to your particular scenario ?
    return response.bodyAsString.contains("saveLink");
  }

    /**
     * We want to apply this transformation for all mappings
     */
    @Override
    boolean applyGlobally() {
        return true
    }
}

现在,您可以创建一个实现HttpServerStub的类,并将其注册为此处显示的http://cloud.spring.io/spring-cloud-static/Dalston.SR3/#_custom_stub_runner。它基本上是WireMockHttpServerStub的副本,我们在手动添加变换器时进行了更改

public class MyCustomWireMockHttpServerStub implements HttpServerStub {

    private static final Logger log = LoggerFactory.getLogger(MyCustomWireMockHttpServerStub.class);
    private static final int INVALID_PORT = -1;

    private WireMockServer wireMockServer;

  @Override
    public HttpServerStub start(int port) {
        this.wireMockServer = new WireMockServer(myConfig().port(port)
                .notifier(new Slf4jNotifier(true)));
        this.wireMockServer.start();
        return this;
    }

    private WireMockConfiguration myConfig() {
        if (ClassUtils.isPresent("org.springframework.cloud.contract.wiremock.WireMockSpring", null)) {
            return WireMockSpring.options()
                    .extensions(responseTransformers());
        }
        return new WireMockConfiguration().extensions(responseTransformers());
    }

    private Extension[] responseTransformers() {
      List<Extension> extensions = new ArrayList<>();
      extensions.add(defaultResponseTemplateTransformer());
      extensions.add(new CustomExtension());
      return extensions.toArray(new Extension[extensions.size()]);
    }

    private ResponseTemplateTransformer defaultResponseTemplateTransformer() {
        return new ResponseTemplateTransformer(false, helpers());
    }

    @Override
    public int port() {
        return isRunning() ? this.wireMockServer.port() : INVALID_PORT;
    }

    @Override
    public boolean isRunning() {
        return this.wireMockServer != null && this.wireMockServer.isRunning();
    }

    @Override
    public HttpServerStub start() {
        if (isRunning()) {
            if (log.isDebugEnabled()) {
                log.debug("The server is already running at port [" + port() + "]");
            }
            return this;
        }
        return start(SocketUtils.findAvailableTcpPort());
    }

    @Override
    public HttpServerStub stop() {
        if (!isRunning()) {
            if (log.isDebugEnabled()) {
                log.debug("Trying to stop a non started server!");
            }
            return this;
        }
        this.wireMockServer.stop();
        return this;
    }

    @Override
    public HttpServerStub registerMappings(Collection<File> stubFiles) {
        if (!isRunning()) {
            throw new IllegalStateException("Server not started!");
        }
        registerStubMappings(stubFiles);
        return this;
    }

    @Override public String registeredMappings() {
        Collection<String> mappings = new ArrayList<>();
        for (StubMapping stubMapping : this.wireMockServer.getStubMappings()) {
            mappings.add(stubMapping.toString());
        }
        return jsonArrayOfMappings(mappings);
    }

    private String jsonArrayOfMappings(Collection<String> mappings) {
        return "[" + StringUtils.collectionToDelimitedString(mappings, ",\n") + "]";
    }

    @Override
    public boolean isAccepted(File file) {
        return file.getName().endsWith(".json");
    }

    StubMapping getMapping(File file) {
        try (InputStream stream = Files.newInputStream(file.toPath())) {
            return StubMapping.buildFrom(
                    StreamUtils.copyToString(stream, Charset.forName("UTF-8")));
        }
        catch (IOException e) {
            throw new IllegalStateException("Cannot read file", e);
        }
    }

    private void registerStubMappings(Collection<File> stubFiles) {
        WireMock wireMock = new WireMock("localhost", port(), "");
        registerDefaultHealthChecks(wireMock);
        registerStubs(stubFiles, wireMock);
    }

    private void registerDefaultHealthChecks(WireMock wireMock) {
        registerHealthCheck(wireMock, "/ping");
        registerHealthCheck(wireMock, "/health");
    }

    private void registerStubs(Collection<File> sortedMappings, WireMock wireMock) {
        for (File mappingDescriptor : sortedMappings) {
            try {
                wireMock.register(getMapping(mappingDescriptor));
                if (log.isDebugEnabled()) {
                    log.debug("Registered stub mappings from [" + mappingDescriptor + "]");
                }
            }
            catch (Exception e) {
                if (log.isDebugEnabled()) {
                    log.debug("Failed to register the stub mapping [" + mappingDescriptor + "]", e);
                }
            }
        }
    }

    private void registerHealthCheck(WireMock wireMock, String url) {
        registerHealthCheck(wireMock, url, "OK");
    }

    private void registerHealthCheck(WireMock wireMock, String url, String body) {
        wireMock.register(
                WireMock.get(WireMock.urlEqualTo(url)).willReturn(WireMock.aResponse().withBody(body).withStatus(200)));
    }
}