diff --git a/workshops/serverless-testing-workshop/java/unicorn-reservation-system/pom.xml b/workshops/serverless-testing-workshop/java/unicorn-reservation-system/pom.xml new file mode 100644 index 00000000..0a2f8544 --- /dev/null +++ b/workshops/serverless-testing-workshop/java/unicorn-reservation-system/pom.xml @@ -0,0 +1,64 @@ + + 4.0.0 + + com.myorg + unicorn-reservation-system + jar + 1.0-SNAPSHOT + + unicorn-reservation-system + + + 2.26.7 + 5.10.2 + 17 + 17 + + + + + + software.amazon.awssdk + bom + ${aws.java.sdk.version} + pom + import + + + + + + + software.amazon.awssdk + cloudformation + test + + + software.amazon.awssdk + dynamodb + test + + + software.amazon.awssdk + s3 + + + software.amazon.awssdk + apache-client + + + org.junit.jupiter + junit-jupiter + ${junit5.version} + test + + + org.apache.httpcomponents + httpclient + 4.5.14 + test + + + \ No newline at end of file diff --git a/workshops/serverless-testing-workshop/java/unicorn-reservation-system/src/main/java/com/myorg/urs/App.java b/workshops/serverless-testing-workshop/java/unicorn-reservation-system/src/main/java/com/myorg/urs/App.java new file mode 100644 index 00000000..5adb1e68 --- /dev/null +++ b/workshops/serverless-testing-workshop/java/unicorn-reservation-system/src/main/java/com/myorg/urs/App.java @@ -0,0 +1,9 @@ +package com.myorg.urs; + +public class App { + + public static void main(String... arguments) { + System.out.println("Hello World!"); + } + +} diff --git a/workshops/serverless-testing-workshop/java/unicorn-reservation-system/src/test/java/com/myorg/urs/integration/TestFileProcessor.java b/workshops/serverless-testing-workshop/java/unicorn-reservation-system/src/test/java/com/myorg/urs/integration/TestFileProcessor.java new file mode 100644 index 00000000..8f243170 --- /dev/null +++ b/workshops/serverless-testing-workshop/java/unicorn-reservation-system/src/test/java/com/myorg/urs/integration/TestFileProcessor.java @@ -0,0 +1,183 @@ +package com.myorg.urs.integration; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.http.apache.ApacheHttpClient; +import software.amazon.awssdk.services.cloudformation.CloudFormationClient; +import software.amazon.awssdk.services.cloudformation.model.DescribeStacksRequest; +import software.amazon.awssdk.services.cloudformation.model.DescribeStacksResponse; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.DeleteItemRequest; +import software.amazon.awssdk.services.dynamodb.model.DeleteItemResponse; +import software.amazon.awssdk.services.dynamodb.model.GetItemRequest; +import software.amazon.awssdk.services.dynamodb.model.GetItemResponse; +import software.amazon.awssdk.services.dynamodb.model.PutItemRequest; +import software.amazon.awssdk.services.dynamodb.model.PutItemResponse; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; +import software.amazon.awssdk.services.s3.model.PutObjectResponse; + +public class TestFileProcessor { + + private static S3Client s3Client; + + private static String dynamoDbTableName; + + private static String unicornInventoryBucket; + + private static String idPostfix; + + private static String testUnicorn; + + private static String testLocation; + + @BeforeAll + public static void runBeforeAll() { + s3Client = S3Client.builder() + .httpClient(ApacheHttpClient.create()) + .build(); + + String awsSamStackName = System.getenv("AWS_SAM_STACK_NAME"); + + Assertions.assertNotNull(awsSamStackName); + + DescribeStacksRequest describeStacksRequest = DescribeStacksRequest.builder().stackName(awsSamStackName) + .build(); + + CloudFormationClient cloudFormationClient = CloudFormationClient.builder().build(); + + DescribeStacksResponse describeStacksResponse = cloudFormationClient.describeStacks(describeStacksRequest); + + Assertions.assertNotNull(describeStacksResponse); + + describeStacksResponse.stacks().forEach(stack -> stack.outputs().forEach(output -> { + if (output.outputKey().equals("DynamoDBTableName")) { + dynamoDbTableName = output.outputValue(); + } else if (output.outputKey().equals("UnicornInventoryBucket")) { + unicornInventoryBucket = output.outputValue(); + } + })); + + Assertions.assertNotNull(dynamoDbTableName); + Assertions.assertNotNull(unicornInventoryBucket); + + idPostfix = "_%s".formatted(UUID.randomUUID().toString()); + testUnicorn = "TEST_UNI%s".formatted(idPostfix); + testLocation = "TEST_LOC%s".formatted(idPostfix); + } + + @AfterAll + public static void runAfterAll() { + // Delete testUnicorn + Map key = Map.of("PK", AttributeValue.builder().s(testUnicorn).build()); + + GetItemRequest getItemRequest = GetItemRequest.builder().tableName(dynamoDbTableName).key(key).build(); + + DynamoDbClient dynamoDbClient = DynamoDbClient.builder().build(); + + GetItemResponse getItemResponse = dynamoDbClient.getItem(getItemRequest); + + if (getItemResponse.hasItem()) { + DeleteItemRequest deleteItemRequest = DeleteItemRequest.builder().tableName(dynamoDbTableName).key(key) + .build(); + + DeleteItemResponse deleteItemResponse = dynamoDbClient.deleteItem(deleteItemRequest); + + Assertions.assertNotNull(deleteItemResponse); + } + + // Update LOCATION#LIST without testLocation + key = Map.of("PK", AttributeValue.builder().s("LOCATION#LIST").build()); + + getItemRequest = GetItemRequest.builder().tableName(dynamoDbTableName).key(key).build(); + + dynamoDbClient = DynamoDbClient.builder().build(); + + getItemResponse = dynamoDbClient.getItem(getItemRequest); + + Assertions.assertNotNull(getItemResponse); + + List locations = getItemResponse.item().get("LOCATIONS").l(); + + List updatedLocations = new ArrayList<>(); + + for (AttributeValue location : locations) { + if (!location.s().equals(testLocation)) { + updatedLocations.add(location); + } + } + + Map item = Map.of("PK", AttributeValue.builder().s("LOCATION#LIST").build(), + "LOCATIONS", AttributeValue.builder().l(updatedLocations).build()); + + PutItemRequest putItemRequest = PutItemRequest.builder().tableName(dynamoDbTableName).item(item).build(); + + PutItemResponse putItemResponse = dynamoDbClient.putItem(putItemRequest); + + Assertions.assertNotNull(putItemResponse); + } + + @Test + public void testFileProcessorHappyPath() { + Map key = Map.of("PK", AttributeValue.builder().s(testUnicorn).build()); + + GetItemRequest getItemRequest = GetItemRequest.builder().tableName(dynamoDbTableName).key(key).build(); + + DynamoDbClient dynamoDbClient = DynamoDbClient.builder().build(); + + GetItemResponse getItemResponse = dynamoDbClient.getItem(getItemRequest); + + Assertions.assertFalse(getItemResponse.hasItem()); + + String testDataCsv = "\"Unicorn Name\",\"Unicorn Location\"\n"; + testDataCsv += "\"%s\",\"%s\"\n".formatted(testUnicorn, testLocation); + + String testDataKey = "INTEGRATION_TEST/TEST%s.test_file_processor_happy_path.csv".formatted(idPostfix); + + PutObjectRequest putObjectRequest = PutObjectRequest.builder().bucket(unicornInventoryBucket) + .key(testDataKey).build(); + + PutObjectResponse putObjectResponse = s3Client.putObject(putObjectRequest, RequestBody.fromString(testDataCsv)); + Assertions.assertNotNull(putObjectResponse); + + int pollMaxSeconds = 30; + int pollCompleteSeconds = 0; + + while (pollCompleteSeconds < pollMaxSeconds) { + getItemResponse = dynamoDbClient.getItem(getItemRequest); + + if (getItemResponse.hasItem()) { + break; + } + + try { + Thread.sleep(1000); + } catch (InterruptedException exception) { + Assertions.fail(exception); + } + + pollCompleteSeconds++; + } + + Assertions.assertTrue(getItemResponse.hasItem(), "Item not found after time allowed for processing."); + Assertions.assertTrue((pollCompleteSeconds <= 2), + "Unicorns should be fast! Took too long: %d sec".formatted(pollCompleteSeconds)); + Assertions.assertEquals(getItemResponse.item().get("PK").s(), testUnicorn, + "Table PK is not set to the Unicorn Name: %s expected %s" + .formatted(getItemResponse.item().get("PK").s(), testUnicorn)); + Assertions.assertEquals(getItemResponse.item().get("LOCATION").s(), testLocation, + "Table LOCATION is not set to the Unicorn Location: %s expected %s" + .formatted(getItemResponse.item().get("LOCATION").s(), testLocation)); + } + +} \ No newline at end of file diff --git a/workshops/serverless-testing-workshop/java/unicorn-reservation-system/src/test/java/com/myorg/urs/integration/TestLocations.java b/workshops/serverless-testing-workshop/java/unicorn-reservation-system/src/test/java/com/myorg/urs/integration/TestLocations.java new file mode 100644 index 00000000..66a0e6c2 --- /dev/null +++ b/workshops/serverless-testing-workshop/java/unicorn-reservation-system/src/test/java/com/myorg/urs/integration/TestLocations.java @@ -0,0 +1,202 @@ +package com.myorg.urs.integration; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import software.amazon.awssdk.services.cloudformation.CloudFormationClient; +import software.amazon.awssdk.services.cloudformation.model.DescribeStacksRequest; +import software.amazon.awssdk.services.cloudformation.model.DescribeStacksResponse; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.GetItemRequest; +import software.amazon.awssdk.services.dynamodb.model.GetItemResponse; +import software.amazon.awssdk.services.dynamodb.model.PutItemRequest; +import software.amazon.awssdk.services.dynamodb.model.PutItemResponse; + +public class TestLocations { + + private static String apiEndpoint; + + private static String dynamoDbTableName; + + private static String idPostfix; + + private static String testLocation; + + @BeforeAll + public static void runBeforeAll() { + String awsSamStackName = System.getenv("AWS_SAM_STACK_NAME"); + + Assertions.assertNotNull(awsSamStackName); + + DescribeStacksRequest describeStacksRequest = DescribeStacksRequest.builder().stackName(awsSamStackName) + .build(); + + CloudFormationClient cloudFormationClient = CloudFormationClient.builder().build(); + + DescribeStacksResponse describeStacksResponse = cloudFormationClient.describeStacks(describeStacksRequest); + + Assertions.assertNotNull(describeStacksResponse); + + describeStacksResponse.stacks().forEach(stack -> stack.outputs().forEach(output -> { + if (output.outputKey().equals("ApiEndpoint")) { + apiEndpoint = output.outputValue(); + } else if (output.outputKey().equals("DynamoDBTableName")) { + dynamoDbTableName = output.outputValue(); + } + })); + + Assertions.assertNotNull(dynamoDbTableName); + + idPostfix = "_%s".formatted(UUID.randomUUID().toString()); + testLocation = "TEST_LOC%s".formatted(idPostfix); + + Map key = Map.of("PK", AttributeValue.builder().s("LOCATION#LIST").build()); + + GetItemRequest getItemRequest = GetItemRequest.builder().tableName(dynamoDbTableName).key(key).build(); + + DynamoDbClient dynamoDbClient = DynamoDbClient.builder().build(); + + GetItemResponse getItemResponse = dynamoDbClient.getItem(getItemRequest); + + Assertions.assertNotNull(getItemResponse); + + List locations = new ArrayList<>(); + + if (getItemResponse.hasItem()) { + List existingLocations = getItemResponse.item().get("LOCATIONS").l(); + + for (AttributeValue existingLocation : existingLocations) { + locations.add(existingLocation); + } + } + + locations.add(AttributeValue.builder().s(testLocation).build()); + + Map item = Map.of("PK", AttributeValue.builder().s("LOCATION#LIST").build(), + "LOCATIONS", AttributeValue.builder().l(locations).build()); + + PutItemRequest putItemRequest = PutItemRequest.builder().tableName(dynamoDbTableName).item(item).build(); + + PutItemResponse putItemResponse = dynamoDbClient.putItem(putItemRequest); + + Assertions.assertNotNull(putItemResponse); + } + + @AfterAll + public static void runAfterAll() { + Map key = Map.of("PK", AttributeValue.builder().s("LOCATION#LIST").build()); + + GetItemRequest getItemRequest = GetItemRequest.builder().tableName(dynamoDbTableName).key(key).build(); + + DynamoDbClient dynamoDbClient = DynamoDbClient.builder().build(); + + GetItemResponse getItemResponse = dynamoDbClient.getItem(getItemRequest); + + Assertions.assertNotNull(getItemResponse); + + List locations = getItemResponse.item().get("LOCATIONS").l(); + + List updatedLocations = new ArrayList<>(); + + for (AttributeValue location : locations) { + if (!location.s().equals(testLocation)) { + updatedLocations.add(location); + } + } + + Map item = Map.of("PK", AttributeValue.builder().s("LOCATION#LIST").build(), + "LOCATIONS", AttributeValue.builder().l(updatedLocations).build()); + + PutItemRequest putItemRequest = PutItemRequest.builder().tableName(dynamoDbTableName).item(item).build(); + + PutItemResponse putItemResponse = dynamoDbClient.putItem(putItemRequest); + + Assertions.assertNotNull(putItemResponse); + } + + @Test + public void testApiGateway200() { + Assertions.assertNotNull(apiEndpoint); + + CloseableHttpClient closeableHttpClient = HttpClients.createDefault(); + + HttpGet httpGet = new HttpGet("%s/locations".formatted(apiEndpoint)); + + try (CloseableHttpResponse closeableHttpResponse = closeableHttpClient.execute(httpGet)) { + Assertions.assertEquals(closeableHttpResponse.getStatusLine().getStatusCode(), 200); + + closeableHttpResponse.close(); + } catch (IOException exception) { + Assertions.fail(exception); + } + + try { + closeableHttpClient.close(); + } catch (IOException exception) { + Assertions.fail(exception); + } + } + + @Test + public void testLocationsResponse() { + Assertions.assertNotNull(apiEndpoint); + + CloseableHttpClient closeableHttpClient = HttpClients.createDefault(); + + HttpGet httpGet = new HttpGet("%s/locations".formatted(apiEndpoint)); + + try (CloseableHttpResponse closeableHttpResponse = closeableHttpClient.execute(httpGet)) { + String content = EntityUtils.toString(closeableHttpResponse.getEntity()); + Assertions.assertTrue(content.contains(testLocation)); + + closeableHttpResponse.close(); + } catch (IOException exception) { + Assertions.fail(exception); + } + + try { + closeableHttpClient.close(); + } catch (IOException exception) { + Assertions.fail(exception); + } + } + + @Test + public void testApiGateway404() { + Assertions.assertNotNull(apiEndpoint); + + CloseableHttpClient closeableHttpClient = HttpClients.createDefault(); + + HttpGet httpGet = new HttpGet("%s/incorrectxxlocationsxxincorrect".formatted(apiEndpoint)); + + try (CloseableHttpResponse closeableHttpResponse = closeableHttpClient.execute(httpGet)) { + String content = EntityUtils.toString(closeableHttpResponse.getEntity()); + Assertions.assertTrue(content.contains("Missing Authentication Token")); + + closeableHttpResponse.close(); + } catch (IOException exception) { + Assertions.fail(exception); + } + + try { + closeableHttpClient.close(); + } catch (IOException exception) { + Assertions.fail(exception); + } + } + +}