Skip to content

Commit

Permalink
ID-377 Add pact contract provider tests. (#60)
Browse files Browse the repository at this point in the history
* ID-377 Add pact provider verification tests.

* Add url resolution test.

* ID-377 Add pact contract provider verification.

* Turn on pact broker

* Add pact broker url

* Add pact broker user/pass

* Tag unit tests vs pact tests

* Fix pact broker url
  • Loading branch information
Ghost-in-a-Jar authored May 25, 2023
1 parent ac78352 commit b218ae7
Show file tree
Hide file tree
Showing 22 changed files with 236 additions and 15 deletions.
17 changes: 17 additions & 0 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,23 @@ jobs:
- name: Test with coverage
run: ./gradlew --build-cache jacocoTestReport --scan

- name: Configure pact
env:
PACTBROKER_APPLICATION_YAML_B64: ${{ secrets.PACTBROKER_APPLICATION_YAML_B64 }}
run: |
mkdir -p ./service/src/test/resources
echo $PACTBROKER_APPLICATION_YAML_B64 | base64 --decode >> ./service/src/test/resources/application.yml
- name: Verify pacts
env:
PACT_BROKER_URL: pact-broker.dsp-eng-tools.broadinstitute.org
PACT_PROVIDER_COMMIT: (git rev-parse HEAD)
PACT_PROVIDER_BRANCH: $(git rev-parse --abbrev-ref HEAD)
PACT_BROKER_USERNAME: ${{ secrets.PACT_BROKER_USERNAME }}
PACT_BROKER_PASSWORD: ${{ secrets.PACT_BROKER_PASSWORD }}
run: |
./gradlew --build-cache verifyPacts
- name: Codecov
uses: codecov/codecov-action@v3
with:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ DrsHub uses Gradle as a build tool. Some common Gradle commands you may want to
```shell
./gradlew generateSwaggerCode # Generate Swagger code for models and Swagger UI
./gradlew bootRun # Run DrsHub locally (Swagger UI at localhost:8080)
./gradlew test # Run the unit tests
./gradlew unitTest # Run the unit tests
./gradlew jib # Build the DrsHub Docker image
```

Expand Down
19 changes: 18 additions & 1 deletion service/analysis.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,22 @@ task runMinnieKenny(type: Exec) {
}

test {
useJUnitPlatform {
includeTags 'Unit'
}
dependsOn runMinnieKenny
}
}

task verifyPacts(type: Test) {
useJUnitPlatform {
includeTags 'Pact'
}
systemProperty 'pact.provider.version', System.getenv('PACT_PROVIDER_COMMIT')
systemProperty 'pact.provider.branch', System.getenv('PACT_PROVIDER_BRANCH')
systemProperty 'pact.verifier.publishResults', true
systemProperty 'pactbroker.host', System.getenv('PACT_BROKER_URL')
systemProperty 'pactbroker.auth.username', System.getenv('PACT_BROKER_USERNAME')
systemProperty 'pactbroker.auth.password', System.getenv('PACT_BROKER_PASSWORD')
systemProperty 'pactbroker.scheme', 'https'
}

9 changes: 9 additions & 0 deletions service/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ dependencies {
strictly('1.33')
}
}

testImplementation('org.springframework.boot:spring-boot-starter-test') {
// Fixes warning about multiple occurrences of JSONObject on the classpath
exclude group: 'com.vaadin.external.google', module: 'android-json'
}

testImplementation("au.com.dius.pact.provider:junit5:4.1.7")
testImplementation('au.com.dius.pact.provider:junit5spring:4.1.7')
}

sonarqube {
Expand All @@ -80,3 +88,4 @@ sonarqube {
property "sonar.sources", "src/main/java,src/main/resources/templates"
}
}

1 change: 0 additions & 1 deletion service/spring.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ dependencies {
testImplementation 'org.mockito:mockito-inline:5.2.0'
}

test { useJUnitPlatform() }

task bootRunDev {
bootRun.configure { systemProperty 'spring.profiles.active', 'human-readable-logging' }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,16 @@ default boolean shouldFetchAccessUrl(
AccessMethod.TypeEnum accessMethodType,
List<String> requestedFields,
boolean forceAccessUrl) {
return Fields.overlap(requestedFields, Fields.ACCESS_ID_FIELDS)
&& (forceAccessUrl
|| getAccessMethodConfigs().stream()
.anyMatch(
m ->
m.getType().getReturnedEquivalent() == accessMethodType
&& m.isFetchAccessUrl()));
var fieldsOverlap = Fields.overlap(requestedFields, Fields.ACCESS_URL_FIELDS);
var accessMethodConfigs = getAccessMethodConfigs();
var accessMethodTypeMatches =
accessMethodConfigs.stream()
.anyMatch(
m ->
m.getType().getReturnedEquivalent() == accessMethodType
&& m.isFetchAccessUrl());

return fieldsOverlap && (accessMethodTypeMatches || forceAccessUrl);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

import bio.terra.drshub.config.DrsProvider;
import bio.terra.drshub.generated.model.ResourceMetadata;
import java.util.List;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.util.List;
import lombok.Builder;
import lombok.Getter;

Expand Down
2 changes: 1 addition & 1 deletion service/src/main/java/bio/terra/drshub/models/Fields.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public class Fields {

public static final List<String> BOND_SA_FIELDS = List.of(GOOGLE_SERVICE_ACCOUNT);

public static final List<String> ACCESS_ID_FIELDS = List.of(ACCESS_URL);
public static final List<String> ACCESS_URL_FIELDS = List.of(ACCESS_URL);

public static boolean overlap(List<String> requestedFields, List<String> serviceFields) {
return serviceFields.stream().anyMatch(requestedFields::contains);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@
import bio.terra.drshub.models.Fields;
import bio.terra.drshub.services.DrsProviderService;
import io.github.ga4gh.drs.model.AccessMethod;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;

@Tag("Unit")
class DrsProviderInterfaceTest extends BaseTest {

@Autowired private DrsProviderService drsProviderService;
Expand Down Expand Up @@ -68,10 +70,10 @@ void testShouldFetchAccessUrl() {

assertFalse(
passportDrsProvider.shouldFetchAccessUrl(
AccessMethod.TypeEnum.GS, Fields.ACCESS_ID_FIELDS, false));
AccessMethod.TypeEnum.GS, Fields.ACCESS_URL_FIELDS, false));
assertFalse(
passportDrsProvider.shouldFetchAccessUrl(
AccessMethod.TypeEnum.S3, Fields.ACCESS_ID_FIELDS, false));
AccessMethod.TypeEnum.S3, Fields.ACCESS_URL_FIELDS, false));

var fenceProviderHost = getProviderHosts("fenceTokenOnly");
var fenceTestUri = String.format("drs://%s:12345", fenceProviderHost.compactUriPrefix());
Expand All @@ -80,7 +82,7 @@ void testShouldFetchAccessUrl() {

assertTrue(
fenceDrsProvider.shouldFetchAccessUrl(
AccessMethod.TypeEnum.GS, Fields.ACCESS_ID_FIELDS, false));
AccessMethod.TypeEnum.GS, Fields.ACCESS_URL_FIELDS, false));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;

@Tag("Unit")
public class DrsProviderTest extends BaseTest {

private final ProviderAccessMethodConfig testAccessMethodConfig =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import java.util.Random;
import java.util.UUID;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
Expand All @@ -54,6 +55,7 @@
import org.springframework.web.client.RestClientException;
import org.springframework.web.util.UriComponentsBuilder;

@Tag("Unit")
@AutoConfigureMockMvc
public class DrsHubApiControllerTest extends BaseTest {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
Expand All @@ -24,6 +25,7 @@
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;

@Tag("Unit")
@AutoConfigureMockMvc
public class GcsApiControllerTest extends BaseTest {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@

import bio.terra.drshub.BaseTest;
import bio.terra.drshub.DrsHubException;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;

@Tag("Unit")
@AutoConfigureMockMvc
public class GlobalExceptionHandlerTest extends BaseTest {
@MockBean PublicApiController publicApiControllerMock;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@
import bio.terra.drshub.BaseTest;
import bio.terra.drshub.config.DrsHubConfig;
import bio.terra.drshub.config.VersionProperties;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;

@Tag("Unit")
@AutoConfigureMockMvc
public class PublicApiControllerTest extends BaseTest {
@Autowired private MockMvc mvc;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package bio.terra.drshub.controllers;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;

import au.com.dius.pact.provider.junit5.PactVerificationContext;
import au.com.dius.pact.provider.junitsupport.Provider;
import au.com.dius.pact.provider.junitsupport.State;
import au.com.dius.pact.provider.junitsupport.loader.PactBroker;
import au.com.dius.pact.provider.spring.junit5.MockMvcTestTarget;
import au.com.dius.pact.provider.spring.junit5.PactVerificationSpringProvider;
import bio.terra.common.iam.BearerTokenFactory;
import bio.terra.drshub.config.DrsHubConfig;
import bio.terra.drshub.config.DrsProvider;
import bio.terra.drshub.config.ProviderAccessMethodConfig;
import bio.terra.drshub.logging.AuditLogger;
import bio.terra.drshub.models.AccessMethodConfigTypeEnum;
import bio.terra.drshub.models.AccessUrlAuthEnum;
import bio.terra.drshub.models.BondProviderEnum;
import bio.terra.drshub.models.DrsApi;
import bio.terra.drshub.models.DrsHubAuthorization;
import bio.terra.drshub.services.AuthService;
import bio.terra.drshub.services.DrsApiFactory;
import bio.terra.drshub.services.DrsProviderService;
import bio.terra.drshub.services.DrsResolutionService;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.github.ga4gh.drs.model.AccessMethod;
import io.github.ga4gh.drs.model.AccessURL;
import io.github.ga4gh.drs.model.Authorizations.SupportedTypesEnum;
import java.sql.Date;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.web.servlet.MockMvc;

@Tag("Pact")
@WebMvcTest
@ContextConfiguration(classes = {DrsHubApiController.class, PublicApiController.class})
@Provider("drshub-provider")
@PactBroker()
class VerifyPactsDrsHubApiController {

@MockBean private DrsHubConfig drsHubConfig;
@MockBean private BearerTokenFactory tokenFactory;
@MockBean private AuthService authService;
@MockBean private DrsApi drsApi;
@MockBean private DrsApiFactory drsApiFactory;
@MockBean private AuditLogger auditLogger;
@SpyBean private DrsResolutionService drsResolutionService;
@SpyBean private DrsProviderService drsProviderService;

@Autowired private ObjectMapper objectMapper;

// This mockMVC is what we use to test API requests and responses:
@Autowired private MockMvc mockMvc;

SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");

@TestTemplate
@ExtendWith(PactVerificationSpringProvider.class)
void pactVerificationTestTemplate(PactVerificationContext context) {
context.verifyInteraction();
}

@BeforeEach
void before(PactVerificationContext context) {
context.setTarget(new MockMvcTestTarget(mockMvc));
}

Optional<List<String>> getAuthForAccessMethodType(AccessMethod.TypeEnum accessMethodType) {
return Optional.of(List.of("Bearer: test 123"));
}

@State({"Drshub is ok"})
public void checkStatusEndpoint() throws Exception {}

@State({"resolve Drs url"})
public void resolveDrsUrl(Map<String, String> providerStateParams) throws Exception {
when(authService.buildAuthorizations(any(), any(), any()))
.thenReturn(
List.of(
new DrsHubAuthorization(
SupportedTypesEnum.PASSPORTAUTH, this::getAuthForAccessMethodType)));

when(authService.fetchUserServiceAccount(any(), any())).thenReturn(null);

when(drsApi.getObject(any(), any())).thenReturn(null);

var drsProvider = DrsProvider.create();
drsProvider.setHostRegex(".*\\.theanvil\\.io");
drsProvider.setMetadataAuth(false);
drsProvider.setBondProvider(BondProviderEnum.anvil);
drsProvider.setUseAliasesForLocalizationPath(true);

var accessMethodConfig = ProviderAccessMethodConfig.create();
accessMethodConfig.setAuth(AccessUrlAuthEnum.passport);
accessMethodConfig.setType(AccessMethodConfigTypeEnum.gs);
accessMethodConfig.setFetchAccessUrl(true);

var accessMethodConfigs = new ArrayList<ProviderAccessMethodConfig>();
accessMethodConfigs.add(accessMethodConfig);
drsProvider.setAccessMethodConfigs(accessMethodConfigs);
drsProvider.setName(providerStateParams.get("bondProvider"));

when(drsHubConfig.getDrsProviders()).thenReturn(Map.of("anvil", drsProvider));

when(drsApiFactory.getApiFromUriComponents(any(), any())).thenReturn(drsApi);
var drsObject =
new io.github.ga4gh.drs.model.DrsObject()
.id("1234567890")
.checksums(
List.of(
new io.github.ga4gh.drs.model.Checksum()
.checksum(providerStateParams.get("fileHash"))
.type("md5")))
.createdTime(Date.from(Instant.now()))
.description("test")
.mimeType("application/json")
.size(Long.parseLong(providerStateParams.get("fileSize")))
.updatedTime(dateFormat.parse(providerStateParams.get("timeCreated")))
.createdTime(dateFormat.parse(providerStateParams.get("timeCreated")))
.accessMethods(
List.of(
new AccessMethod()
.accessId(providerStateParams.get("fileId"))
.type(AccessMethod.TypeEnum.GS)))
.version("1.0");

when(drsApi.getObject(any(), any())).thenReturn(drsObject);

var accessUrl =
new AccessURL()
.url(providerStateParams.get("accessUrl"))
.headers(List.of("Header", "Example"));

when(drsApi.postAccessURL(any(), any(), any())).thenReturn(accessUrl);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
import static org.junit.jupiter.api.Assertions.*;

import io.github.ga4gh.drs.model.AccessMethod.TypeEnum;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;

@Tag("Unit")
class AccessMethodConfigTypeEnumTest {

@Test
Expand Down
Loading

0 comments on commit b218ae7

Please sign in to comment.