Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PUB-2565 - Move functional to integration tests #446

Merged
merged 6 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion Jenkinsfile_CNP
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@

@Library("Infrastructure")

import uk.gov.hmcts.contino.GradleBuilder

def type = "java"
def product = "pip"
def component = "account-management"
def kv = product + '-ss-kv'

GradleBuilder builder = new GradleBuilder(this, product)

def setupTestSecrets() {
def bootstap_env = env.ENV == "prod" || env.ENV == "demo" || env.ENV == "sbox" ? env.ENV : "stg"
azureKeyVault(
Expand All @@ -18,14 +22,17 @@ def setupTestSecrets() {
secret('app-pip-publication-services-scope', 'PUBLICATION_SERVICES_AZ_API'),
secret('app-pip-subscription-management-scope', 'SUBSCRIPTION_MANAGEMENT_AZ_API'),
secret('app-tenant', 'TENANT_ID'),

secret('app-pip-subscription-management-id', 'CLIENT_ID_FT'),
secret('app-pip-subscription-management-pwd', 'CLIENT_SECRET_FT'),
]) {
env.APP_URI = "${APP_URI}"
env.CLIENT_ID = "${CLIENT_ID}"
env.CLIENT_SECRET = "${CLIENT_SECRET}"
env.PUBLICATION_SERVICES_AZ_API = "${PUBLICATION_SERVICES_AZ_API}"
env.SUBSCRIPTION_MANAGEMENT_AZ_API = "${SUBSCRIPTION_MANAGEMENT_AZ_API}"
env.TENANT_ID = "${TENANT_ID}"
env.CLIENT_ID_FT = "${CLIENT_ID_FT}"
env.CLIENT_SECRET_FT = "${CLIENT_SECRET_FT}"
}
}

Expand Down Expand Up @@ -62,4 +69,7 @@ withPipeline(type, product, component) {
disableLegacyDeployment()
enableApiGatewayTest()

afterAlways('test') {
builder.gradle('integration')
}
}
33 changes: 22 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,20 +196,23 @@ Below is a table of currently used environment variables for starting the servic

Secrets required for getting tests to run correctly can be found in the below table:

| Variable | Description |
|:-------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| APP_URI | Uniform Resource Identifier - the location where the application expects to receive bearer tokens after a successful authentication process. The application then validates received bearer tokens using the AUD parameter in the token. |
| CLIENT_ID | Unique ID for the application within Azure AD. Used to identify the application during authentication. |
| CLIENT_SECRET | Secret key for authentication requests to the service. |
| PUBLICATION_SERVICES_AZ_API | Used as part of the `scope` parameter when requesting a token from Azure. Used for service-to-service communication with the pip-publication-services service. |
| SUBSCRIPTION_MANAGEMENT_AZ_API | Used as part of the `scope` parameter when requesting a token from Azure. Used for service-to-service communication with the pip-subscription-management service. |
| TENANT_ID | Directory unique ID assigned to our Azure AD tenant. Represents the organisation that owns and manages the Azure AD instance. |
| Variable | Description |
|:-------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| APP_URI | Uniform Resource Identifier - the location where the application expects to receive bearer tokens after a successful authentication process. The application then validates received bearer tokens using the AUD parameter in the token. |
| CLIENT_ID | Unique ID for the application within Azure AD. Used to identify the application during authentication. |
| CLIENT_SECRET | Secret key for authentication requests to the service. |
| PUBLICATION_SERVICES_AZ_API | Used as part of the `scope` parameter when requesting a token from Azure. Used for service-to-service communication with the pip-publication-services service. |
| SUBSCRIPTION_MANAGEMENT_AZ_API | Used as part of the `scope` parameter when requesting a token from Azure. Used for service-to-service communication with the pip-subscription-management service. |
| TENANT_ID | Directory unique ID assigned to our Azure AD tenant. Represents the organisation that owns and manages the Azure AD instance. |
| CLIENT_ID_FT | Client ID of external service used for authentication with account-management application in the functional tests. |
| CLIENT_SECRET_FT | Client secret of external service.used for authentication with account-management application in the functional tests. |

#### Application.yaml files
The service can also be adapted using the yaml files found in the following locations:
- [src/main/resources/application.yaml](./src/main/resources/application.yaml) for changes to the behaviour of the service itself.
- [src/main/resources/application-dev.yaml](./src/main/resources/application-dev.yaml) for changes to the behaviour of the service when running locally.
- [src/test/resources/application-test.yaml](./src/test/resources/application-test.yaml) for changes to other test types (e.g. unit tests).
- [src/integrationTest/resources/application-integration.yaml](./src/integrationTest/resources/application-integration.yaml) for changes to the application when it's running integration tests.
- [src/integrationTest/resources/application-functional.yaml](./src/functionalTest/resources/application-functional.yaml) for changes to the application when its running functional tests.

### Fortify
Expand Down Expand Up @@ -315,19 +318,27 @@ We use a few automated tools to ensure quality and security within the service.

## Test Suite

This microservice is comprehensively tested using both unit and functional tests.
This microservice is comprehensively tested using unit, integration and functional tests.

### Unit tests

Unit tests can be run on demand using `./gradlew test`.

### Integration tests

Integration tests can be run on demand using `./gradlew integration`.

For our integration tests, we are using Square's [MockWebServer](https://github.com/square/okhttp/tree/master/mockwebserver) library. This allows us to test the full HTTP stack for our service-to-service interactions.

The mock server interacts with external CaTH services on staging.

### Functional tests

Functional tests can be run using `./gradlew functional`

For our functional tests, we are using Square's [MockWebServer](https://github.com/square/okhttp/tree/master/mockwebserver) library. This allows us to test the full HTTP stack for our service-to-service interactions.
Functional testing is performed on the stood-up account-management instance on the dev pod (during pull request) or on staging (when running on the master branch).

The functional tests also call out to Data Management in staging to retrieve publications.
This account-management instance interacts with external CaTH services on staging.

## Contributing
We are happy to accept third-party contributions. See [.github/CONTRIBUTING.md](./.github/CONTRIBUTING.md) for more details.
Expand Down
29 changes: 27 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ sourceSets {
resources.srcDir file('src/functionalTest/resources')
}

integrationTest {
java {
compileClasspath += main.output
runtimeClasspath += main.output
srcDir file('src/integrationTest/java')
}
resources.srcDir file('src/integrationTest/resources')
}

smokeTest {
java {
compileClasspath += main.output
Expand Down Expand Up @@ -77,6 +86,9 @@ configurations {
functionalTestImplementation.extendsFrom testImplementation
functionalTestRuntimeOnly.extendsFrom runtimeOnly

integrationTestImplementation.extendsFrom testImplementation
integrationTestRuntimeOnly.extendsFrom runtimeOnly

smokeTestImplementation.extendsFrom testImplementation
smokeTestRuntimeOnly.extendsFrom runtimeOnly

Expand Down Expand Up @@ -113,6 +125,13 @@ task functional(type: Test) {
classpath = sourceSets.functionalTest.runtimeClasspath
}

task integration(type: Test) {
description = "Runs integration tests"
group = "Verification"
testClassesDirs = sourceSets.integrationTest.output.classesDirs
classpath = sourceSets.integrationTest.runtimeClasspath
}

task smoke(type: Test) {
description = "Runs Smoke Tests"
testClassesDirs = sourceSets.smokeTest.output.classesDirs
Expand All @@ -127,7 +146,7 @@ checkstyle {

pmd {
toolVersion = "7.6.0"
sourceSets = [sourceSets.main, sourceSets.test, sourceSets.functionalTest, sourceSets.smokeTest]
sourceSets = [sourceSets.main, sourceSets.test, sourceSets.functionalTest, sourceSets.integrationTest, sourceSets.smokeTest]
reportsDir = file("$project.buildDir/reports/pmd")
ruleSetFiles = files("config/pmd/ruleset.xml")
}
Expand All @@ -137,7 +156,7 @@ jacoco {
}

jacocoTestReport {
executionData(test)
executionData(test, integration)
reports {
xml.required = true
csv.required = false
Expand Down Expand Up @@ -246,6 +265,8 @@ dependencies {
testImplementation group: 'org.apiguardian', name: 'apiguardian-api', version: '1.1.2'
testImplementation group: 'org.mockito', name: 'mockito-inline', version: '5.2.0'
testImplementation group: 'com.github.hmcts', name: 'fortify-client', version: '1.4.4', classifier: 'all'

functionalTestImplementation group: 'io.rest-assured', name: 'rest-assured'
}

task fortifyScan(type: JavaExec) {
Expand All @@ -272,6 +293,10 @@ rootProject.tasks.named("processFunctionalTestResources") {
duplicatesStrategy = 'include'
}

rootProject.tasks.named("processIntegrationTestResources") {
duplicatesStrategy = 'include'
}

wrapper {
distributionType = Wrapper.DistributionType.ALL
}
7 changes: 5 additions & 2 deletions charts/pip-account-management/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ name: pip-account-management
apiVersion: v2
appVersion: "1.0"
home: https://github.com/hmcts/pip-account-management
version: 0.0.45
version: 0.0.46
description: Publishing & Information Account Management
maintainers:
- name: HMCTS PIP Team
dependencies:
- name: java
version: 5.2.1
repository: 'https://hmctspublic.azurecr.io/helm/v1/repo/'

- name: blobstorage
version: 2.0.2
repository: https://hmctspublic.azurecr.io/helm/v1/repo/
condition: blobstorage.enabled
17 changes: 16 additions & 1 deletion charts/pip-account-management/values.dev.template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,21 @@ java:
DB_USER: "{{ .Values.postgresql.auth.username}}"
DB_PASS: "{{ .Values.postgresql.auth.password}}"
DB_PORT: "5432"
#disableKeyVaults: true
SPRING_PROFILES_ACTIVE: blobStorageDev
postgresql:
enabled: true
secrets:
STORAGE_ACCOUNT_NAME:
secretRef: storage-account-{{ .Release.Name }}-blobstorage
key: storage_account_name
STORAGE_ACCOUNT_URL:
secretRef: storage-secret-{{ .Release.Name }}-blobstorage
key: blobEndpoint
STORAGE_ACCOUNT_KEY:
secretRef: storage-secret-{{ .Release.Name }}-blobstorage
key: accessKey
blobstorage:
resourceGroup: pip-aso-dev-rg
setup:
containers:
- files
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package uk.gov.hmcts.reform.pip.account.management.controllers;

import io.restassured.response.Response;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.io.ClassPathResource;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import uk.gov.hmcts.reform.pip.account.management.utils.FunctionalTestBase;
import uk.gov.hmcts.reform.pip.account.management.utils.OAuthClient;

import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.springframework.http.HttpHeaders.AUTHORIZATION;
import static org.springframework.http.HttpStatus.OK;

@ExtendWith(SpringExtension.class)
@ActiveProfiles(profiles = "functional")
@SpringBootTest(classes = {OAuthClient.class})
class MediaApplicationCreationTest extends FunctionalTestBase {

private static final String TEST_NAME = "E2E Account Management Test Name";
private static final String TEST_EMPLOYER = "E2E Account Management Test Employer";
private static final String TEST_EMAIL_PREFIX = String.format(
"pip-am-test-email-%s", ThreadLocalRandom.current().nextInt(1000, 9999));

private static final String TEST_EMAIL = String.format(TEST_EMAIL_PREFIX + "@justice.gov.uk");
private static final String STATUS = "PENDING";

private static final String TESTING_SUPPORT_APPLICATION_URL = "/testing-support/application/";
private static final String MEDIA_APPLICATION_URL = "/application";
private static final String BEARER = "Bearer ";
private static final String MOCK_FILE = "files/test-image.png";

@AfterAll
public void teardown() {
doDeleteRequest(TESTING_SUPPORT_APPLICATION_URL + TEST_EMAIL, Map.of(AUTHORIZATION, BEARER + accessToken), "");
}

@Test
void shouldBeAbleToCreateAMediaApplication() throws Exception {
final Response response =
doPostMultipartForApplication(MEDIA_APPLICATION_URL, Map.of(AUTHORIZATION, BEARER + accessToken),
new ClassPathResource(MOCK_FILE).getFile(), TEST_NAME, TEST_EMAIL, TEST_EMPLOYER, STATUS);

assertThat(response.getStatusCode()).isEqualTo(OK.value());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package uk.gov.hmcts.reform.pip.account.management.utils;

public class AuthException extends RuntimeException {
private static final long serialVersionUID = -6991745899622330407L;

public AuthException(String error) {
super(error);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package uk.gov.hmcts.reform.pip.account.management.utils;

import io.restassured.RestAssured;
import io.restassured.response.Response;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.TestInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.util.CollectionUtils;
import uk.gov.hmcts.reform.pip.account.management.Application;

import java.io.File;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import static io.restassured.RestAssured.given;
import static org.springframework.http.HttpHeaders.CONTENT_TYPE;

@SpringBootTest(classes = {Application.class, OAuthClient.class},
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class FunctionalTestBase {

protected static final String CONTENT_TYPE_VALUE = "application/json";

@Autowired
private OAuthClient authClient;

protected String accessToken;

@Value("${test-url}")
private String testUrl;

@BeforeAll
void setUp() {
RestAssured.baseURI = testUrl;
accessToken = authClient.generateAccessToken();
}

protected Response doGetRequest(final String path, final Map<String, String> additionalHeaders) {
return given()
.relaxedHTTPSValidation()
.headers(getRequestHeaders(additionalHeaders))
.when()
.get(path)
.thenReturn();
}

protected Response doPostRequest(final String path, final Map<String, String> additionalHeaders,
final String body) {
return given()
.relaxedHTTPSValidation()
.headers(getRequestHeaders(additionalHeaders))
.body(body)
.when()
.post(path)
.thenReturn();
}

protected Response doPostMultipartForApplication(final String path, final Map<String, String> additionalHeaders,
final File file, String name, String email, String employer, String status) {
return given()
.relaxedHTTPSValidation()
.headers(additionalHeaders)
.multiPart("file", file)
.multiPart("fullName", name)
.multiPart("email", email)
.multiPart("employer", employer)
.multiPart("status", status)
.when()
.post(path)
.thenReturn();
}

protected Response doDeleteRequest(final String path, final Map<String, String> additionalHeaders,
final String body) {
return given()
.relaxedHTTPSValidation()
.headers(getRequestHeaders(additionalHeaders))
.body(body)
.when()
.delete(path)
.thenReturn();
}

private static Map<String, String> getRequestHeaders(final Map<String, String> additionalHeaders) {
final Map<String, String> headers = new ConcurrentHashMap<>(Map.of(CONTENT_TYPE, CONTENT_TYPE_VALUE));
if (!CollectionUtils.isEmpty(additionalHeaders)) {
headers.putAll(additionalHeaders);
}
return headers;
}
}
Loading