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);
+ }
+ }
+
+}