Repository: eclipse/microprofile-lra
Pull request #145 (2019-04-29 15:51:11)

issue117 Validate that the parent context is available for nested LRAs
This PR adds a test issue #117 


Patch:
diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/ContextTckResource.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/ContextTckResource.java
index 9cacbe3..781cfff 100644
--- a/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/ContextTckResource.java
+++ b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/ContextTckResource.java
@@ -50,6 +50,7 @@ import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 import static org.eclipse.microprofile.lra.annotation.ws.rs.LRA.LRA_HTTP_CONTEXT_HEADER;
+import static org.eclipse.microprofile.lra.annotation.ws.rs.LRA.LRA_HTTP_PARENT_CONTEXT_HEADER;
 import static org.eclipse.microprofile.lra.tck.participant.api.NonParticipatingTckResource.SUPPORTS_PATH;
 import static org.eclipse.microprofile.lra.tck.participant.api.NonParticipatingTckResource.TCK_NON_PARTICIPANT_RESOURCE_PATH;
 import static org.junit.Assert.assertEquals;
@@ -62,6 +63,7 @@ public class ContextTckResource {
 
     public static final String NEW_LRA_PATH = "/new-lra";
     public static final String REQUIRED_LRA_PATH = "/required-lra";
+    public static final String NESTED_LRA_PATH = "/nested-lra";
     // resource path for testing that context on outbound and inbound calls made from an
     // method annotated with @LRA are spec compliant
     public static final String CONTEXT_CHECK_LRA_PATH = "/context-check-lra";
@@ -93,6 +95,7 @@ public class ContextTckResource {
     private static class LRAMetric {
         String lraId;
         Map<String, AtomicInteger> metrics = Stream.of(
+                new AbstractMap.SimpleEntry<>(LRA.Type.NESTED.name(), new AtomicInteger(0)),
                 new AbstractMap.SimpleEntry<>(Complete.class.getName(), new AtomicInteger(0)),
                 new AbstractMap.SimpleEntry<>(Compensate.class.getName(), new AtomicInteger(0)),
                 new AbstractMap.SimpleEntry<>(Status.class.getName(), new AtomicInteger(0)),
@@ -203,6 +206,14 @@ public class ContextTckResource {
         return Response.ok().entity(lraId).build();
     }
 
+    @LRA(value = LRA.Type.NESTED, end = false)
+    @PUT
+    @Path(NESTED_LRA_PATH)
+    public Response nestedLRA(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) String nestedLRA,
+                              @HeaderParam(LRA_HTTP_PARENT_CONTEXT_HEADER) String parentLRA) {
+        return Response.ok().entity(nestedLRA + "," + parentLRA).build();
+    }
+
     // test that outgoing calls do not affect the calling context
     @LRA(value = LRA.Type.REQUIRED)
     @PUT
@@ -259,9 +270,13 @@ public class ContextTckResource {
     @PUT
     @Path("/compensate")
     @Compensate
-    public Response compensateWork(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) String lraId)
+    public Response compensateWork(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) String lraId,
+                                   @HeaderParam(LRA_HTTP_PARENT_CONTEXT_HEADER) String parent)
             throws NotFoundException {
         incrementMetric(Compensate.class.getName(), lraId);
+        if (parent != null) {
+            incrementMetric(LRA.Type.NESTED.name(), parent);
+        }
 
         return getEndPhaseResponse(false);
     }
@@ -269,9 +284,13 @@ public class ContextTckResource {
     @PUT
     @Path("/complete")
     @Complete
-    public Response completeWork(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) String lraId)
+    public Response completeWork(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) String lraId,
+                                 @HeaderParam(LRA_HTTP_PARENT_CONTEXT_HEADER) String parent)
             throws NotFoundException {
         incrementMetric(Complete.class.getName(), lraId);
+        if (parent != null) {
+            incrementMetric(LRA.Type.NESTED.name(), parent);
+        }
 
         return getEndPhaseResponse(true);
     }
@@ -279,8 +298,12 @@ public class ContextTckResource {
     @Status
     @GET
     @Path(STATUS_PATH)
-    public Response status(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) String lraId) {
+    public Response status(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) String lraId,
+                           @HeaderParam(LRA_HTTP_PARENT_CONTEXT_HEADER) String parent) {
         incrementMetric(Status.class.getName(), lraId);
+        if (parent != null) {
+            incrementMetric(LRA.Type.NESTED.name(), parent);
+        }
 
         return Response.status(endPhaseStatus).entity(status.name()).build();
     }
@@ -288,8 +311,12 @@ public class ContextTckResource {
     @Forget
     @DELETE
     @Path("/forget")
-    public Response forget(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) String lraId) {
+    public Response forget(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) String lraId,
+                           @HeaderParam(LRA_HTTP_PARENT_CONTEXT_HEADER) String parent) {
         incrementMetric(Forget.class.getName(), lraId);
+        if (parent != null) {
+            incrementMetric(LRA.Type.NESTED.name(), parent);
+        }
 
         return Response.status(endPhaseStatus).entity(status.name()).build();
     }


Test patch:
diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckContextTests.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckContextTests.java
index 31fac2f..5fc262d 100644
--- a/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckContextTests.java
+++ b/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckContextTests.java
@@ -23,6 +23,7 @@ import org.eclipse.microprofile.lra.annotation.Complete;
 import org.eclipse.microprofile.lra.annotation.Forget;
 import org.eclipse.microprofile.lra.annotation.ParticipantStatus;
 import org.eclipse.microprofile.lra.annotation.Status;
+import org.eclipse.microprofile.lra.annotation.ws.rs.LRA;
 import org.eclipse.microprofile.lra.tck.participant.api.ContextTckResource;
 import org.jboss.arquillian.container.test.api.Deployment;
 import org.jboss.arquillian.junit.Arquillian;
@@ -45,12 +46,14 @@ import static org.eclipse.microprofile.lra.tck.participant.api.ContextTckResourc
 import static org.eclipse.microprofile.lra.tck.participant.api.ContextTckResource.LRA_TCK_FAULT_TYPE_HEADER;
 import static org.eclipse.microprofile.lra.tck.participant.api.ContextTckResource.LRA_TCK_HTTP_CONTEXT_HEADER;
 import static org.eclipse.microprofile.lra.tck.participant.api.ContextTckResource.METRIC_PATH;
+import static org.eclipse.microprofile.lra.tck.participant.api.ContextTckResource.NESTED_LRA_PATH;
 import static org.eclipse.microprofile.lra.tck.participant.api.ContextTckResource.NEW_LRA_PATH;
 import static org.eclipse.microprofile.lra.tck.participant.api.ContextTckResource.REQUIRED_LRA_PATH;
 import static org.eclipse.microprofile.lra.tck.participant.api.ContextTckResource.RESET_PATH;
 import static org.eclipse.microprofile.lra.tck.participant.api.ContextTckResource.STATUS_PATH;
 import static org.eclipse.microprofile.lra.tck.participant.api.ContextTckResource.TCK_CONTEXT_RESOURCE_PATH;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 /**
  * test that Compensate, Complete, Status, Forget and Leave annotations work without an LRA annotation
@@ -122,7 +125,6 @@ public class TckContextTests extends TckTestBase {
         // verify that the resource was not asked to complete
         String completions = invoke(METRIC_PATH + "/" + Complete.class.getName(), GET, lra);
 
-
         assertEquals(testName.getMethodName() + ": Resource left but was still asked to complete",
                 "0", completions);
     }
@@ -158,6 +160,45 @@ public class TckContextTests extends TckTestBase {
         assertEquals(testName.getMethodName() + " resource forget should have been called", "1", count);
     }
 
+    /*
+     * test that the parent context is available when:
+     * - a method executes with a nested LRA
+     * - when a participant callback is invoked
+     */
+    @Test
+    public void testParentContextAvailable() {
+        // start an LRA
+        String topLevelLRA = invoke(NEW_LRA_PATH, PUT, null);
+        // start a nested LRA
+        String result = invoke(NESTED_LRA_PATH, PUT, topLevelLRA);
+        // the resource method should return the nested LRA and the top level LRA separated by a comma
+        assertTrue(result.contains(","));
+        assertEquals(testName.getMethodName() + ": wrong parent LRA", topLevelLRA, result.split(",")[1]);
+
+        String nestedLRA = result.split(",")[0];
+
+        // end the top level LRA
+        invoke(REQUIRED_LRA_PATH, PUT, topLevelLRA);
+
+        // check that the resource was asked to complete twice, one in the context of the nested LRA and a
+        // second time in the context of the top level LRA
+
+        String nestedCompletions = invoke(METRIC_PATH + "/" + Complete.class.getName(), GET, nestedLRA);
+        assertEquals(testName.getMethodName() + ": resource should have completed for the nested LRA",
+                "1", nestedCompletions);
+
+        String topLevelCompletions = invoke(METRIC_PATH + "/" + Complete.class.getName(), GET, topLevelLRA);
+        assertEquals(testName.getMethodName() + ": resource should have completed for the top level LRA",
+                "1", topLevelCompletions);
+
+        // and validate that the parent LRA header was present when the nested LRA was asked to complete
+        String endCallsWithParentContextHeaderPresent = invoke(METRIC_PATH + "/" + LRA.Type.NESTED.name(),
+                GET, topLevelLRA);
+        assertEquals(testName.getMethodName() +
+                        ": when the resource was asked to complete a nested LRA the parent context header was missing",
+                "1", endCallsWithParentContextHeaderPresent);
+    }
+
     // invoke a method in an LRA context which performs various outgoing calls checking that the notion of active context
     // conforms with what is written in the specification
     @Test