
com.vmware.connectors.test.ControllerTestsBase Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of connectors-test Show documentation
Show all versions of connectors-test Show documentation
Common testing code for developing connectors
The newest version!
/*
* Copyright © 2017 VMware, Inc. All Rights Reserved.
* SPDX-License-Identifier: BSD-2-Clause
*/
package com.vmware.connectors.test;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import com.vmware.connectors.mock.MockWebServerWrapper;
import com.vmware.connectors.mock.PhaserClientHttpConnector;
import io.restassured.module.jsv.JsonSchemaValidator;
import okhttp3.mockwebserver.MockWebServer;
import org.apache.commons.io.IOUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientCodecCustomizer;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.reactive.function.client.WebClient;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.springframework.http.HttpHeaders.AUTHORIZATION;
import static org.springframework.http.MediaType.APPLICATION_JSON;
/**
* Created by Rob Worsnop on 12/1/16.
*/
@ExtendWith(SpringExtension.class)
@TestPropertySource(locations = "classpath:application-test.properties")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Import(JwtUtils.class)
@SuppressWarnings("PMD.SignatureDeclareThrowsException")
public class ControllerTestsBase {
private static final Logger logger = LoggerFactory.getLogger(ControllerTestsBase.class);
protected final static String X_AUTH_HEADER = "X-Connector-Authorization";
protected final static String X_BASE_URL_HEADER = "X-Connector-Base-Url";
@Autowired
protected JwtUtils jwt;
@Autowired
protected ObjectMapper mapper;
@Autowired
protected WebTestClient webClient;
@LocalServerPort
private int connectorPort;
protected MockWebServerWrapper mockBackend;
@BeforeEach
void setup() {
mockBackend = new MockWebServerWrapper(new MockWebServer());
}
@AfterEach
void shutdown() throws IOException {
mockBackend.verify();
mockBackend.shutdown();
}
protected String accessToken(String uri) {
try {
String audience = "http://localhost:" + connectorPort + uri;
return jwt.createConnectorToken(audience);
} catch (IOException | GeneralSecurityException e) {
throw new AssertionError(e);
}
}
protected void testProtectedResource(HttpMethod method, String uri) throws Exception {
// Try without authorization; should never work
webClient.method(method)
.uri(uri)
.exchange()
.expectStatus().isUnauthorized();
// Try with expired token
String audience = "http://localhost:" + connectorPort + uri;
webClient.method(method)
.uri(uri)
.header(AUTHORIZATION, bearer(jwt.createConnectorToken(Instant.now(), audience)))
.exchange()
.expectStatus().isUnauthorized();
// Try with wrong audience
webClient.method(method)
.uri(uri)
.header(AUTHORIZATION, bearer(jwt.createConnectorToken("wrong audience")))
.exchange()
.expectStatus().isForbidden();
// Try with missing audience
webClient.method(method)
.uri(uri)
.header(AUTHORIZATION, bearer(jwt.createConnectorToken(null)))
.exchange()
.expectStatus().isForbidden();
}
private static String bearer(String token) {
return "Bearer " + token;
}
public static String fromFile(String fileName) throws IOException {
try (InputStream stream = new ClassPathResource(fileName).getInputStream()) {
return IOUtils.toString(stream, Charset.defaultCharset());
}
}
public static byte[] bytesFromFile(String fileName) {
try (InputStream stream = new ClassPathResource(fileName).getInputStream()) {
return IOUtils.toByteArray(stream);
} catch (IOException e) {
throw new RuntimeException(e); //NOPMD allows method to be called from lambda
}
}
protected void testConnectorDiscovery() throws IOException {
String xForwardedHost = "https://my-connector";
// Confirm connector has updated the host placeholder.
String expectedMetadata = fromFile("/static/discovery/metadata.json")
.replace("${CONNECTOR_HOST}", xForwardedHost);
testConnectorDiscovery(expectedMetadata);
}
protected void testConnectorDiscovery(String expectedMetadata) throws IOException {
webClient.get()
.uri("/")
.headers(ControllerTestsBase::headers)
.exchange()
.expectStatus().is2xxSuccessful()
.expectBody()
.json(expectedMetadata)
.jsonPath("$.object_types.card").exists();
String body = webClient.get()
.uri("/")
.headers(ControllerTestsBase::headers)
.exchange()
.expectStatus()
.is2xxSuccessful()
.returnResult(String.class)
.getResponseBody()
.collect(Collectors.joining())
.block();
assertThat("Discovery schema failed.", body, JsonSchemaValidator.matchesJsonSchema(fromFile("/connector-discovery-response-schema.json")));
}
protected void headers(HttpHeaders headers, String uri) {
try {
String token = jwt.createConnectorToken("https://my-connector" + uri);
addTokenToAuthHeader(headers, token);
} catch (IOException | GeneralSecurityException e) {
throw new AssertionError(e);
}
}
protected void preHireHeaders(HttpHeaders headers, String uri, boolean isPreHire) {
try {
String token = jwt.createConnectorTokenForPreHire("https://my-connector" + uri, isPreHire);
addTokenToAuthHeader(headers, token);
} catch (GeneralSecurityException | IOException ex) {
throw new AssertionError(ex);
}
}
private void addTokenToAuthHeader(HttpHeaders headers, String token) {
headers.add(AUTHORIZATION, bearer(token));
headers(headers);
}
protected static void headers(HttpHeaders headers) {
headers.add("x-forwarded-host", "my-connector");
headers.add("x-forwarded-proto", "https");
headers.add("x-forwarded-port", "443");
}
protected void testRegex(String tokenProperty, String emailInput, List expected) throws Exception {
String body = new String(getConnectorMetaData(), UTF_8);
DocumentContext ctx = JsonPath.parse(body);
Integer captureGroup = ctx.read("$.object_types.card.fields." + tokenProperty + ".capture_group");
String regex = ctx.read("$.object_types.card.fields." + tokenProperty + ".regex");
verifyRegex(regex, captureGroup, emailInput, expected);
}
private byte[] getConnectorMetaData() {
return webClient.get()
.uri("/")
.header(AUTHORIZATION, bearer(accessToken("/")))
.accept(APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.returnResult(byte[].class).getResponseBodyContent();
}
private void verifyRegex(String regex, Integer captureGroup, String emailInput, List expected) throws Exception {
List results = new RegexMatcher().getMatches(regex, emailInput, Optional.ofNullable(captureGroup).orElse(0));
assertThat("Card regex test failed.", results, equalTo(expected));
}
/*
* Code ported from the Android project's code to match more closely what the client is doing:
* - https://stash.air-watch.com/projects/UFO/repos/android-roswell-framework/browse/roswellframework/src/main/java/com/vmware/roswell/framework/etc/RegexMatcher.java?at=403dfa349a17901ba3a888eb2e98ab14ddae5825#39
* - https://stash.air-watch.com/projects/UFO/repos/android-roswell-framework/browse/roswellframework/src/main/java/com/vmware/roswell/framework/json/HCSConnectorDeserializer.java?at=403dfa349a17901ba3a888eb2e98ab14ddae5825#114
*/
private static class RegexMatcher {
List getMatches(String regex, String text, int captureGroupIndex) {
List allMatches = new ArrayList<>();
for (String line : text.split("\\n")) {
Matcher m = Pattern.compile(regex).matcher(line);
int totalGroupCount = m.groupCount() + 1; // +1 because group zero (the whole regex) isn't included in groupCount()
if (captureGroupIndex >= 0 && captureGroupIndex < totalGroupCount) {
while (m.find()) {
allMatches.add(m.group(captureGroupIndex));
}
} else {
logger.warn(
"Connector has a regex field with capture_group_index ({}) greater than the number of groups ({}) in the regex << {} >>",
captureGroupIndex, totalGroupCount, regex
);
break;
}
}
return allMatches;
}
}
@TestConfiguration
static class ControllerTestConfiguration {
@Bean
public WebClient.Builder webClientBuilder(WebClientCodecCustomizer codecCustomizer) {
WebClient.Builder builder = WebClient.builder();
codecCustomizer.customize(builder);
return builder.clientConnector(new PhaserClientHttpConnector());
}
}
}