From 5856e5163498cdc8e33f220de5af88c33acbe930 Mon Sep 17 00:00:00 2001 From: Anthony Galagade <72087089+agalagade@users.noreply.github.com> Date: Mon, 9 Jan 2023 16:30:14 +0100 Subject: [PATCH] :FC-1343: :zap: Integration Tests (#100) * :FC-1343: :zap: Integration Tests * :zap: removed workflow due to write permissions issues * :zap: Added the integration test workflow * :zap: added log * :zap: update workflow to check env * :zap: updated the workflow * :zap: testing Environment variables * :zap: testing env * :zap: adding integration test env * :zap: update * :zap: added env variables * :zap: removed the env variables and ignore the integration test * :zap: ignore integration tests in Java CI * :zap: update Java CI * :zap: custom test ignore module * :zap: applied ignore test on Java CI * :zap: removed the test log * :zap: moving test related files to test folder Co-authored-by: LivePerson FaaS Core Team Account <60709588+LivePersonFaas@users.noreply.github.com> Co-authored-by: chrbrt <20794486+chrbrt@users.noreply.github.com> --- .github/workflows/integration-test.maven.yml | 37 +++ .github/workflows/maven-publish.yml | 6 + .github/workflows/test.maven.yml | 2 +- pom.xml | 16 ++ .../client/FaaSIntegrationClientTest.java | 272 ++++++++++++++++++ .../faas/util/AuthBearerGenerator.java | 90 ++++++ .../faas/util/BearerConfigResponseObject.java | 49 ++++ .../liveperson/faas/util/BearerResponse.java | 62 ++++ 8 files changed, 533 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/integration-test.maven.yml create mode 100644 src/test/java/com/liveperson/faas/client/FaaSIntegrationClientTest.java create mode 100644 src/test/java/com/liveperson/faas/util/AuthBearerGenerator.java create mode 100644 src/test/java/com/liveperson/faas/util/BearerConfigResponseObject.java create mode 100644 src/test/java/com/liveperson/faas/util/BearerResponse.java diff --git a/.github/workflows/integration-test.maven.yml b/.github/workflows/integration-test.maven.yml new file mode 100644 index 0000000..426b17b --- /dev/null +++ b/.github/workflows/integration-test.maven.yml @@ -0,0 +1,37 @@ +# This workflow will run Integration Tests for the project with Maven, and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven + +name: Integration Tests + +on: + pull_request: + branches: [ develop, master ] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up JDK 1.8 + uses: actions/setup-java@v3 + with: + java-version: 11 + distribution: 'adopt' + server-id: ossrh + server-username: OSSRH_USERNAME + server-password: OSSRH_PASSWORD + gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY_1 }} + gpg-passphrase: MAVEN_GPG_PASSPHRASE + - name: Integration testing with Maven + run: | + mvn test -Dtest=FaaSIntegrationClientTest + env: + ACCOUNT_ID: ${{ secrets.ACCOUNT_ID }} + LAMBDAUUID: ${{ secrets.LAMBDAUUID }} + CLIENT_ID: ${{ secrets.CLIENT_ID }} + CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }} + USER: ${{ secrets.USER }} + PASS: ${{ secrets.PASS }} + OSSRH_USERNAME: ${{ secrets.NEXUS_USERNAME }} + OSSRH_PASSWORD: ${{ secrets.NEXUS_PASSWORD }} + MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PRIVATE_KEY_PASSPHRASE }} diff --git a/.github/workflows/maven-publish.yml b/.github/workflows/maven-publish.yml index 68d6374..43f3334 100644 --- a/.github/workflows/maven-publish.yml +++ b/.github/workflows/maven-publish.yml @@ -30,3 +30,9 @@ jobs: OSSRH_USERNAME: ${{ secrets.NEXUS_USERNAME }} OSSRH_PASSWORD: ${{ secrets.NEXUS_PASSWORD }} MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PRIVATE_KEY_PASSPHRASE }} + ACCOUNT_ID: ${{ secrets.ACCOUNT_ID }} + LAMBDAUUID: ${{ secrets.LAMBDAUUID }} + CLIENT_ID: ${{ secrets.CLIENT_ID }} + CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }} + USER: ${{ secrets.USER }} + PASS: ${{ secrets.PASS }} diff --git a/.github/workflows/test.maven.yml b/.github/workflows/test.maven.yml index 11591f1..f11dd35 100644 --- a/.github/workflows/test.maven.yml +++ b/.github/workflows/test.maven.yml @@ -21,4 +21,4 @@ jobs: distribution: 'temurin' cache: maven - name: Build with Maven - run: mvn --batch-mode --update-snapshots verify + run: mvn --batch-mode --update-snapshots verify -DIntegrationModule.test.excludes="**/*FaaSIntegrationClientTest.java" diff --git a/pom.xml b/pom.xml index d5cced1..3ffc1d5 100644 --- a/pom.xml +++ b/pom.xml @@ -146,8 +146,24 @@ + + org.apache.maven.plugins + maven-surefire-plugin + 2.9 + + + ${IntegrationModule.test.excludes} + + + + + false + ${IntegrationModule.skip.tests} + **/*Test.java + **/*FaaSIntegrationClientTest.java + ossrh diff --git a/src/test/java/com/liveperson/faas/client/FaaSIntegrationClientTest.java b/src/test/java/com/liveperson/faas/client/FaaSIntegrationClientTest.java new file mode 100644 index 0000000..62eea42 --- /dev/null +++ b/src/test/java/com/liveperson/faas/client/FaaSIntegrationClientTest.java @@ -0,0 +1,272 @@ +package com.liveperson.faas.client; + +import com.liveperson.faas.client.types.OptionalParams; +import com.liveperson.faas.csds.CsdsMapClient; +import com.liveperson.faas.dto.FaaSInvocation; +import com.liveperson.faas.exception.*; +import com.liveperson.faas.http.DefaultRestClient; +import com.liveperson.faas.metriccollector.MetricCollector; +import com.liveperson.faas.response.lambda.ErrorLogResponseObject; +import com.liveperson.faas.response.lambda.LambdaResponse; +import com.liveperson.faas.security.AuthSignatureBuilder; +import com.liveperson.faas.util.AuthBearerGenerator; +import com.liveperson.faas.util.BearerConfigResponseObject; +import com.liveperson.faas.util.EventResponse; +import com.liveperson.faas.util.UUIDResponse; + +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class FaaSIntegrationClientTest { + private static AuthSignature authSignatureBuilder; + private FaaSWebClient client; + private FaaSWebClient clientWithBearer; + private DefaultRestClient restClient = new DefaultRestClient(); + private MetricCollector metricCollector; + private DefaultIsImplementedCache defaultIsImplementedCache; + private String accountId = System.getenv("ACCOUNT_ID"); + private String clientId = System.getenv("CLIENT_ID"); + private String clientSecret = System.getenv("CLIENT_SECRET"); + private String userId; + private String externalSystem = "test_system"; + private FaaSEvent event = FaaSEvent.MessagingNewConversation; + private String lambdaUUID = System.getenv("LAMBDAUUID"); + private String requestId = "requestId"; + + private int defaultTimeOut = 15000; + + private OptionalParams optionalParams; + + @Before + public void before() throws Exception, TokenGenerationException { + client = getFaaSClient(); + authSignatureBuilder = new AuthSignature(); + clientWithBearer = getFaaSClientBearer(); + optionalParams = new OptionalParams(); + optionalParams.setTimeOutInMs(defaultTimeOut); + optionalParams.setRequestId(requestId); + userId = authSignatureBuilder.getUserId(); + } + + @Test + public void getLambdas() throws Exception { + List lambdaResponse = clientWithBearer.getLambdas(userId, new HashMap(), + optionalParams); + assertTrue(lambdaResponse.size() > 0); + assertTrue(lambdaResponse.toString().contains("name")); + assertTrue(lambdaResponse.toString().contains("uuid")); + assertTrue(lambdaResponse.toString().contains("createdAt")); + assertTrue(lambdaResponse.toString().contains("updatedAt")); + assertTrue(lambdaResponse.toString().contains("updatedBy")); + } + + @Test + public void getLambdasWithOptionalQueryParameters() throws Exception { + HashMap filterMap = new HashMap(); + filterMap.put("eventId", "not-existing"); + List lambdaResponse = clientWithBearer.getLambdas(userId, filterMap, optionalParams); + assertTrue(lambdaResponse.size() == 0); + } + + @Test(expected = FaaSException.class) + public void getLambdasWithInvalidStateValueQueryParameter() throws IOException, FaaSException { + try { + HashMap filterMap = new HashMap(); + filterMap.put("state", "active"); + clientWithBearer.getLambdas(userId, filterMap, optionalParams); + + } catch (FaaSException e) { + assertTrue(e.getCause().toString().contains("Received response code 400")); + throw e; + } + } + + @Test + public void isImplementedEventRetrievedFromCache() throws Exception { + boolean isImplemented = client.isImplemented(externalSystem, event, optionalParams); + assertTrue("Lambda should be implemented", isImplemented); + } + + @Test + public void isImplementedEventRetrievedFromCacheOnNotExistingEvent() throws Exception { + boolean isImplemented = client.isImplemented(externalSystem, FaaSEvent.ChatPostSurveyEmailTranscript, + optionalParams); + assertFalse("Lambda should not be implemented", isImplemented); + } + + @Test + public void invokeViaEventType() throws Exception { + UUIDResponse eventPayload = new UUIDResponse(); + long timestamp = System.currentTimeMillis(); + Map headers = getTestHeaders(); + FaaSInvocation invocationData = getUUIDResponseFaaSInvocation(eventPayload, timestamp, headers); + EventResponse[] response = client.invokeByEvent(externalSystem, event, invocationData, EventResponse[].class, + optionalParams); + assertEquals("Success", response[0].result.value); + assertNotNull(response[0].uuid, "The uuid should not be null"); + } + + @Test + public void invokeViaEventTypeWithValidPayload() throws Exception { + UUIDResponse eventPayload = new UUIDResponse(); + eventPayload.value = "validLogs"; + long timestamp = System.currentTimeMillis(); + Map headers = getTestHeaders(); + FaaSInvocation invocationData = getUUIDResponseFaaSInvocation(eventPayload, timestamp, headers); + EventResponse[] response = client.invokeByEvent(externalSystem, event, invocationData, EventResponse[].class, + optionalParams); + assertEquals("With Payload", response[0].result.value); + } + + @Test + public void invokeViaEventTypeWithNonExistingEvent() throws Exception { + UUIDResponse eventPayload = new UUIDResponse(); + long timestamp = System.currentTimeMillis(); + Map headers = getTestHeaders(); + FaaSInvocation invocationData = getUUIDResponseFaaSInvocation(eventPayload, timestamp, headers); + EventResponse[] response = client.invokeByEvent(externalSystem, FaaSEvent.ChatPostSurveyEmailTranscript, + invocationData, EventResponse[].class, optionalParams); + assertTrue(response.length == 0); + } + + @Test + public void invokeViaUUIDWithRequestId() throws Exception { + String payload = "request_data"; + long timestamp = System.currentTimeMillis(); + FaaSInvocation invocationData = new FaaSInvocation(null, payload); + invocationData.setTimestamp(timestamp); + optionalParams.setRequestId(requestId); + + String response = client.invokeByUUID(externalSystem, lambdaUUID, invocationData, String.class, optionalParams); + assertEquals("Success", response); + } + + @Test + public void invokeViaUUIDWithoutPayload() throws Exception { + long timestamp = System.currentTimeMillis(); + FaaSInvocation invocationData = new FaaSInvocation(null, null); + invocationData.setTimestamp(timestamp); + String response = client.invokeByUUID(externalSystem, lambdaUUID, invocationData, String.class, optionalParams); + assertEquals("Success", response); + } + + @Test + public void invokeViaUUIDWithValidPayload() throws Exception { + String payload = "validLogs"; + long timestamp = System.currentTimeMillis(); + FaaSInvocation invocationData = new FaaSInvocation(null, payload); + invocationData.setTimestamp(timestamp); + String response = client.invokeByUUID(externalSystem, lambdaUUID, invocationData, String.class, optionalParams); + assertEquals("validLogs", response); + } + + @Test(expected = FaaSException.class) + public void invokeViaUUIDWithTimeoutPayload() throws IOException, FaaSException { + + String payload = "timeout"; + long timestamp = System.currentTimeMillis(); + FaaSInvocation invocationData = new FaaSInvocation(null, payload); + invocationData.setTimestamp(timestamp); + optionalParams.setRequestId(requestId); + try { + client.invokeByUUID(externalSystem, lambdaUUID, invocationData, ErrorLogResponseObject.class, + optionalParams); + } catch (FaaSException e) { + assertEquals("Error occured during lambda invocation", e.getMessage()); + assertEquals("Read timed out", e.getCause().getMessage()); + throw e; + } + } + + @Test(expected = FaaSDetailedException.class) + public void invokeViaUUIDThrowsFaasDetailedException() throws FaaSException, FaaSDetailedException { + + String payload = "error"; + long timestamp = System.currentTimeMillis(); + FaaSInvocation invocationData = new FaaSInvocation(null, payload); + invocationData.setTimestamp(timestamp); + try { + client.invokeByUUID(externalSystem, lambdaUUID, invocationData, optionalParams); + } catch (FaaSDetailedException e) { + assertEquals(FaaSLambdaErrorCodes.RUNTIME_EXCEPTION.getCode(), e.getFaaSError().getErrorCode()); + assertEquals(901, e.getCause().getStatusCode()); + throw e; + } + } + + private FaaSWebClient getFaaSClient() { + return new FaaSWebClient.Builder(accountId).withClientId(clientId) + .withClientSecret(clientSecret) + .withRestClient(restClient) + .withMetricCollector(metricCollector) + .withIsImplementedCache(defaultIsImplementedCache) + .build(); + } + + private FaaSWebClient getFaaSClientBearer() { + return new FaaSWebClient.Builder(accountId).withAuthSignatureBuilder(authSignatureBuilder) + .withRestClient(restClient) + .withMetricCollector(metricCollector) + .withIsImplementedCache(defaultIsImplementedCache) + .build(); + } + + private Map getTestHeaders() { + Map headers = new HashMap(); + headers.put("testHeader", "testHeaderValue"); + return headers; + } + + private FaaSInvocation getUUIDResponseFaaSInvocation(UUIDResponse payload, long timestamp, + Map headers) { + FaaSInvocation invocationData = new FaaSInvocation(); + invocationData.setHeaders(headers); + invocationData.setPayload(payload); + invocationData.setTimestamp(timestamp); + return invocationData; + } + +} + +class AuthSignature implements AuthSignatureBuilder { + + private DefaultRestClient restClient = new DefaultRestClient(); + private CsdsMapClient csdsClient = new CsdsMapClient(getAlphaDomains()); + private String accountId = System.getenv("ACCOUNT_ID"); + private String username = System.getenv("USER"); + private String password = System.getenv("PASS"); + private AuthBearerGenerator bearerGenerator; + private BearerConfigResponseObject configData; + private String authHeader; + + public AuthSignature() throws TokenGenerationException { + this.bearerGenerator = new AuthBearerGenerator(restClient, csdsClient, accountId, username, password); + this.authHeader = bearerGenerator.retrieveBearerToken(); + this.configData = bearerGenerator.retrieveBearerConfig(); + } + + @Override + public String getAuthHeader() { + return authHeader; + } + + public String getUserId() { + return configData.getUserId(); + } + + private Map getAlphaDomains() { + Map domains = new HashMap(); + domains.put("agentVep", "va-a.agentvep.liveperson.net"); + return domains; + } +} \ No newline at end of file diff --git a/src/test/java/com/liveperson/faas/util/AuthBearerGenerator.java b/src/test/java/com/liveperson/faas/util/AuthBearerGenerator.java new file mode 100644 index 0000000..65b2ea7 --- /dev/null +++ b/src/test/java/com/liveperson/faas/util/AuthBearerGenerator.java @@ -0,0 +1,90 @@ +package com.liveperson.faas.util; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.liveperson.faas.csds.CsdsClient; +import com.liveperson.faas.exception.CsdsRetrievalException; +import com.liveperson.faas.exception.TokenGenerationException; +import com.liveperson.faas.http.RestClient; +import com.liveperson.faas.security.AuthExpiryTester; +import com.liveperson.faas.security.BearerGenerator; +import com.liveperson.faas.security.JwtExpiryTester; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class AuthBearerGenerator implements BearerGenerator { + + static Logger logger = LogManager.getLogger(); + String accountId; + String username; + String password; + private RestClient restClient; + private CsdsClient csdsClient; + private BearerResponse currentAuth; + private AuthExpiryTester authExpiryTester; + + public AuthBearerGenerator(RestClient restClient, CsdsClient csdsClient, String accountId, + String username, + String password) { + this.restClient = restClient; + this.csdsClient = csdsClient; + this.accountId = accountId; + this.username = username; + this.password = password; + this.authExpiryTester = new JwtExpiryTester(); + + } + + @Override + public String retrieveBearerToken() throws TokenGenerationException { + if (jwtNotInitializedOrAboutToExpire()) { + try { + currentAuth = generateAuthBearer(accountId, username, password); + } catch (CsdsRetrievalException | IOException e) { + logger.warn("There was an error retrieving Bearer from Server. " + e); + if (jwtFailedToInitializeOrExpired()) { + throw new TokenGenerationException("No valid Bearer could be retrieved and current one is expired", e); + } + } + } + + return "Bearer " + this.currentAuth.getBearer(); + } + public BearerConfigResponseObject retrieveBearerConfig() throws TokenGenerationException{ + if(this.currentAuth == null){ + throw new TokenGenerationException("No valid Bearer could be retrieved and current one is expired"); + } + + return this.currentAuth.getConfig(); + } + + private BearerResponse generateAuthBearer(String accountId, String username, String password) throws CsdsRetrievalException, + IOException { + String sentinelBaseUrl = csdsClient.getDomain("agentVep"); + String jwtUrl = String.format("https://%s/api/account/%s/login" + + "?v=1.3", + sentinelBaseUrl, accountId); + Map headers = new HashMap<>(); + headers.put("Content-type", "application/x-www-form-urlencoded"); + String body = String.format("username=%s&password=%s", username, password); + String response = restClient.post(jwtUrl, headers, body); + ObjectMapper mapper = new ObjectMapper(); + BearerResponse bearerData = mapper.readValue(response, new TypeReference() { + }); + return bearerData; + } + + private boolean jwtNotInitializedOrAboutToExpire() { + return currentAuth == null || authExpiryTester.isAboutToExpire(currentAuth.getBearer()); + } + + private boolean jwtFailedToInitializeOrExpired() { + return currentAuth == null || authExpiryTester.isExpired(currentAuth.getBearer()); + } + +} diff --git a/src/test/java/com/liveperson/faas/util/BearerConfigResponseObject.java b/src/test/java/com/liveperson/faas/util/BearerConfigResponseObject.java new file mode 100644 index 0000000..b827b12 --- /dev/null +++ b/src/test/java/com/liveperson/faas/util/BearerConfigResponseObject.java @@ -0,0 +1,49 @@ +package com.liveperson.faas.util; + +import java.util.Objects; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class BearerConfigResponseObject { + private String userId; + private String loginName; + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getLoginName() { + return userId; + } + + public void setLoginName(String loginName) { + this.loginName = loginName; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BearerConfigResponseObject that = (BearerConfigResponseObject) o; + return Objects.equals(loginName, that.loginName) && + Objects.equals(userId, that.userId); + } + + @Override + public int hashCode() { + return Objects.hash(loginName, userId); + } + + @Override + public String toString() { + return "BearerConfigResponseObject{" + + "loginName='" + loginName + '\'' + + ", userId='" + userId + '\'' + + '}'; + } +} \ No newline at end of file diff --git a/src/test/java/com/liveperson/faas/util/BearerResponse.java b/src/test/java/com/liveperson/faas/util/BearerResponse.java new file mode 100644 index 0000000..77ba35c --- /dev/null +++ b/src/test/java/com/liveperson/faas/util/BearerResponse.java @@ -0,0 +1,62 @@ +package com.liveperson.faas.util; + +import java.util.Objects; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class BearerResponse { + private String bearer; + private BearerConfigResponseObject config; + private String csrf; + + public String getBearer() { + return bearer; + } + + public void setBearer(String bearer) { + this.bearer = bearer; + } + + public String getCsrf() { + return csrf; + } + + public void setCsrf(String csrf) { + this.csrf = csrf; + } + + public BearerConfigResponseObject getConfig() { + return config; + } + + public void setConfig(BearerConfigResponseObject config) { + this.config = config; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BearerResponse that = (BearerResponse) o; + return Objects.equals(bearer, that.bearer) && + Objects.equals(config, that.config) && + Objects.equals(csrf, that.csrf); + } + + @Override + public int hashCode() { + return Objects.hash(bearer, config, csrf); + } + + @Override + public String toString() { + return "BearerResponse{" + + "bearer='" + bearer + '\'' + + ", config='" + config + '\'' + + ", csrf='" + csrf + '\'' + + '}'; + } + + +} \ No newline at end of file