Merge "b/162508047 Add Path Traversal"
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/BaseTraversalHelper.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/BaseTraversalHelper.java
index 701ba8a..58e4e14 100644
--- a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/BaseTraversalHelper.java
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/BaseTraversalHelper.java
@@ -15,6 +15,7 @@
 import org.openapi4j.parser.model.v3.OpenApi3;
 import org.openapi4j.parser.model.v3.Operation;
 import org.openapi4j.parser.model.v3.Parameter;
+import org.openapi4j.parser.model.v3.Path;
 import org.openapi4j.parser.model.v3.Response;
 import org.openapi4j.parser.model.v3.SecurityRequirement;
 import org.openapi4j.parser.model.v3.Server;
@@ -116,6 +117,17 @@
   }
 
   @Override
+  public void sendPathTraversals(ImmutableMap<String, Path> pathMap) {
+    Optional.ofNullable(pathMap)
+        .ifPresent(
+            paths ->
+                paths.forEach(
+                    (name, path) ->
+                        coordinator.handleTraversalCommand(
+                            factory.create(path, traversalPath, name))));
+  }
+
+  @Override
   public void sendResponseTraversals(ImmutableMap<String, Response> responses) {
     Optional.ofNullable(responses)
         .ifPresent(
@@ -139,8 +151,8 @@
   }
 
   @Override
-  public void sendServerTraversals(ImmutableList<Server> serversList) {
-    Optional.ofNullable(serversList)
+  public void sendServerTraversals(ImmutableList<Server> serverList) {
+    Optional.ofNullable(serverList)
         .ifPresent(
             servers ->
                 servers.forEach(
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/CallbackTraversal.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/CallbackTraversal.java
index ca89f3c..7ed41fc 100644
--- a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/CallbackTraversal.java
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/CallbackTraversal.java
@@ -29,6 +29,10 @@
   @Override
   public void traverse() {
     // TODO(b/161441872): Process extensions.
-    // TODO(b/162508047): Add path object traversal and callback object traversal logic.
+
+    TraversalHelper traversalHelper = traversalHelperFactory.create(getUpdatedTraversalPath());
+
+    traversalHelper.sendPathTraversals(
+        Immutables.toImmutableMap(openApiSchemaObj.getCallbackPaths()));
   }
 }
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/OpenApiSpecificationTraversal.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/OpenApiSpecificationTraversal.java
index 1709d93..07b547f 100644
--- a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/OpenApiSpecificationTraversal.java
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/OpenApiSpecificationTraversal.java
@@ -1,6 +1,7 @@
 package com.apigee.security.oas.extendedvalidator;
 
 import static com.apigee.security.oas.extendedvalidator.Immutables.toImmutableList;
+import static com.apigee.security.oas.extendedvalidator.Immutables.toImmutableMap;
 
 import com.google.common.collect.ImmutableList;
 import com.google.inject.assistedinject.Assisted;
@@ -29,6 +30,8 @@
     traversalHelper.sendInfoTraversal(openApiSchemaObj.getInfo());
     traversalHelper.sendServerTraversals(toImmutableList(openApiSchemaObj.getServers()));
     traversalHelper.sendTagTraversals(toImmutableList(openApiSchemaObj.getTags()));
-    // TODO(b/162508047): Add path object traversal and callback object traversal logic.
+    traversalHelper.sendPathTraversals(toImmutableMap(openApiSchemaObj.getPaths()));
+    // TODO(b/162682119): Send OpenApiSpecification SecurityRequirements to TraversalHelper.
+    // TODO(b/162677159): Send Components traversal.
   }
 }
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/PathTraversal.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/PathTraversal.java
new file mode 100644
index 0000000..5ba0797
--- /dev/null
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/PathTraversal.java
@@ -0,0 +1,42 @@
+package com.apigee.security.oas.extendedvalidator;
+
+import com.google.common.collect.ImmutableList;
+import com.google.inject.assistedinject.Assisted;
+import java.util.Map;
+import java.util.Optional;
+import javax.inject.Inject;
+import org.openapi4j.parser.model.OpenApiSchema;
+import org.openapi4j.parser.model.v3.Path;
+
+/** A {@link Traversal} that traverses a {@link Path} object. */
+final class PathTraversal extends Traversal<Path> implements PathTraversalCommand {
+
+  @Inject
+  PathTraversal(
+      TraversalHelperFactory traversalHelperFactory,
+      ExtensionValidationIntegrator extensionValidationIntegrator,
+      @Assisted
+          ImmutableList<Map.Entry<Class<? extends OpenApiSchema>, Optional<String>>> traversalPath,
+      @Assisted Path openApiSchemaObj,
+      @Assisted String name) {
+    super(
+        traversalHelperFactory,
+        extensionValidationIntegrator,
+        traversalPath,
+        openApiSchemaObj,
+        name);
+  }
+
+  @Override
+  public void traverse() {
+    // TODO(b/161441872): Process extensions.
+
+    TraversalHelper traversalHelper = traversalHelperFactory.create(getUpdatedTraversalPath());
+
+    traversalHelper.sendParameterTraversals(
+        Immutables.toImmutableList(openApiSchemaObj.getParameters()));
+    traversalHelper.sendServerTraversals(Immutables.toImmutableList(openApiSchemaObj.getServers()));
+    traversalHelper.sendOperationTraversals(
+        Immutables.toImmutableMap(openApiSchemaObj.getOperations()));
+  }
+}
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/PathTraversalCommand.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/PathTraversalCommand.java
new file mode 100644
index 0000000..0a3db17
--- /dev/null
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/PathTraversalCommand.java
@@ -0,0 +1,4 @@
+package com.apigee.security.oas.extendedvalidator;
+
+/** A {@link TraversalCommand} interface for {@link PathTraversal}. */
+public interface PathTraversalCommand extends TraversalCommand {}
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/TagTraversal.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/TagTraversal.java
index 1300951..c6e9a40 100644
--- a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/TagTraversal.java
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/TagTraversal.java
@@ -26,6 +26,7 @@
     // TODO(b/161441872): Process extensions.
 
     TraversalHelper traversalHelper = traversalHelperFactory.create(getUpdatedTraversalPath());
+
     traversalHelper.sendExternalDocsTraversal(openApiSchemaObj.getExternalDocs());
   }
 }
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/TraversalCommandFactory.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/TraversalCommandFactory.java
index 2167c17..654187b 100644
--- a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/TraversalCommandFactory.java
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/TraversalCommandFactory.java
@@ -12,6 +12,7 @@
 import org.openapi4j.parser.model.v3.OpenApi3;
 import org.openapi4j.parser.model.v3.Operation;
 import org.openapi4j.parser.model.v3.Parameter;
+import org.openapi4j.parser.model.v3.Path;
 import org.openapi4j.parser.model.v3.Response;
 import org.openapi4j.parser.model.v3.SecurityRequirement;
 import org.openapi4j.parser.model.v3.Server;
@@ -56,6 +57,11 @@
       Parameter parameter,
       ImmutableList<Map.Entry<Class<? extends OpenApiSchema>, Optional<String>>> traversalPath);
 
+  PathTraversalCommand create(
+      Path path,
+      ImmutableList<Map.Entry<Class<? extends OpenApiSchema>, Optional<String>>> traversalPath,
+      String name);
+
   ResponseTraversalCommand create(
       Response response,
       ImmutableList<Map.Entry<Class<? extends OpenApiSchema>, Optional<String>>> traversalPath,
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/TraversalCommandFactoryModule.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/TraversalCommandFactoryModule.java
index fb3d8a7..a226e1a 100644
--- a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/TraversalCommandFactoryModule.java
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/TraversalCommandFactoryModule.java
@@ -18,6 +18,7 @@
                 OpenApiSpecificationTraversalCommand.class, OpenApiSpecificationTraversal.class)
             .implement(OperationTraversalCommand.class, OperationTraversal.class)
             .implement(ParameterTraversalCommand.class, ParameterTraversal.class)
+            .implement(PathTraversalCommand.class, PathTraversal.class)
             .implement(ResponseTraversalCommand.class, ResponseTraversal.class)
             .implement(
                 SecurityRequirementTraversalCommand.class, SecurityRequirementTraversal.class)
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/TraversalHelper.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/TraversalHelper.java
index c2c6c94..1f6ea32 100644
--- a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/TraversalHelper.java
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/TraversalHelper.java
@@ -10,6 +10,7 @@
 import org.openapi4j.parser.model.v3.OpenApi3;
 import org.openapi4j.parser.model.v3.Operation;
 import org.openapi4j.parser.model.v3.Parameter;
+import org.openapi4j.parser.model.v3.Path;
 import org.openapi4j.parser.model.v3.Response;
 import org.openapi4j.parser.model.v3.SecurityRequirement;
 import org.openapi4j.parser.model.v3.Server;
@@ -35,6 +36,8 @@
 
   void sendParameterTraversals(ImmutableList<Parameter> parameters);
 
+  void sendPathTraversals(ImmutableMap<String, Path> paths);
+
   void sendResponseTraversals(ImmutableMap<String, Response> responses);
 
   void sendSecurityRequirementTraversals(ImmutableList<SecurityRequirement> securityRequirements);
diff --git a/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/CallbackTraversalTest.java b/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/CallbackTraversalTest.java
new file mode 100644
index 0000000..dc4944d
--- /dev/null
+++ b/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/CallbackTraversalTest.java
@@ -0,0 +1,67 @@
+package com.apigee.security.oas.extendedvalidator;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.mockito.quality.Strictness;
+import org.openapi4j.parser.model.v3.Callback;
+import org.openapi4j.parser.model.v3.Path;
+
+@RunWith(JUnit4.class)
+public class CallbackTraversalTest {
+
+  private final ImmutableMap<String, Path> callbackPaths =
+      ImmutableMap.of("one", new Path(), "two", new Path(), "three", new Path());
+
+  private CallbackTraversal callbackTraversal;
+
+  @Rule public MockitoRule rule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
+  @Mock private TraversalHelperFactory traversalHelperFactory;
+  @Mock private TraversalHelper traversalHelper;
+  @Mock private ExtensionValidationIntegrator extensionValidationIntegrator;
+  @Mock private Callback callback;
+
+  /** Sets up live and mocked instances for testing. */
+  @Before
+  public void setup() {
+    callbackTraversal =
+        new CallbackTraversal(
+            traversalHelperFactory,
+            extensionValidationIntegrator,
+            ImmutableList.of(),
+            callback,
+            "name");
+
+    when(callback.getCallbackPaths()).thenReturn(callbackPaths);
+
+    when(traversalHelperFactory.create(any(ImmutableList.class))).thenReturn(traversalHelper);
+  }
+
+  @Test
+  public void traverse_sendsCallbackPathsToTraversalHelper() {
+    callbackTraversal.traverse();
+
+    verify(traversalHelper).sendPathTraversals(callbackPaths);
+  }
+
+  @Test
+  public void traverse_nullCallbackPathsMember_doesNotFail() {
+    when(callback.getCallbackPaths()).thenReturn(null);
+
+    callbackTraversal.traverse();
+
+    verify(traversalHelper, atLeastOnce()).sendPathTraversals(any());
+  }
+}
diff --git a/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/OpenApiSpecificationTraversalTest.java b/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/OpenApiSpecificationTraversalTest.java
index 4df4f5d..efe1ae1 100644
--- a/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/OpenApiSpecificationTraversalTest.java
+++ b/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/OpenApiSpecificationTraversalTest.java
@@ -6,6 +6,7 @@
 import static org.mockito.Mockito.when;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -18,6 +19,7 @@
 import org.openapi4j.parser.model.v3.ExternalDocs;
 import org.openapi4j.parser.model.v3.Info;
 import org.openapi4j.parser.model.v3.OpenApi3;
+import org.openapi4j.parser.model.v3.Path;
 import org.openapi4j.parser.model.v3.Server;
 import org.openapi4j.parser.model.v3.Tag;
 
@@ -38,6 +40,9 @@
       ImmutableList.of(new Server(), new Server(), new Server());
   private final ImmutableList<Tag> tags = ImmutableList.of(new Tag(), new Tag(), new Tag());
 
+  private final ImmutableMap<String, Path> paths =
+      ImmutableMap.of("one", new Path(), "two", new Path(), "three", new Path());
+
   private OpenApiSpecificationTraversal openApiSpecificationTraversal;
 
   /** Sets up live and mocked instances for testing. */
@@ -48,33 +53,45 @@
             traversalHelperFactory, extensionValidationIntegrator, openApiSpec);
 
     when(openApiSpec.getServers()).thenReturn(servers);
-
     when(openApiSpec.getTags()).thenReturn(tags);
-
     when(openApiSpec.getInfo()).thenReturn(info);
-
     when(openApiSpec.getExternalDocs()).thenReturn(externalDocs);
+    when(openApiSpec.getPaths()).thenReturn(paths);
 
     when(traversalHelperFactory.create(any(ImmutableList.class))).thenReturn(traversalHelper);
   }
 
   @Test
-  public void traverse_sendsInfoToTraversalHelper() {
-    openApiSpecificationTraversal.traverse();
-
-    verify(traversalHelperFactory, atLeastOnce()).create(any(ImmutableList.class));
-    verify(traversalHelper).sendInfoTraversal(info);
-  }
-
-  @Test
   public void traverse_sendsExternalDocsToTraversalHelper() {
     openApiSpecificationTraversal.traverse();
 
-    verify(traversalHelperFactory, atLeastOnce()).create(any(ImmutableList.class));
     verify(traversalHelper).sendExternalDocsTraversal(externalDocs);
   }
 
   @Test
+  public void traverse_sendsInfoToTraversalHelper() {
+    openApiSpecificationTraversal.traverse();
+
+    verify(traversalHelper).sendInfoTraversal(info);
+  }
+
+  @Test
+  public void traverse_sendsPathsToTraversalHelper() {
+    openApiSpecificationTraversal.traverse();
+
+    verify(traversalHelper).sendPathTraversals(paths);
+  }
+
+  @Test
+  public void traverse_nullPathsMember_doesNotFail() {
+    when(openApiSpec.getPaths()).thenReturn(null);
+
+    openApiSpecificationTraversal.traverse();
+
+    verify(traversalHelper, atLeastOnce()).sendPathTraversals(any());
+  }
+
+  @Test
   public void traverse_sendsServersToTraversalHelper() {
     openApiSpecificationTraversal.traverse();
 
diff --git a/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/PathTraversalTest.java b/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/PathTraversalTest.java
new file mode 100644
index 0000000..042f0a4
--- /dev/null
+++ b/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/PathTraversalTest.java
@@ -0,0 +1,108 @@
+package com.apigee.security.oas.extendedvalidator;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.mockito.quality.Strictness;
+import org.openapi4j.parser.model.v3.Operation;
+import org.openapi4j.parser.model.v3.Parameter;
+import org.openapi4j.parser.model.v3.Path;
+import org.openapi4j.parser.model.v3.Server;
+
+@RunWith(JUnit4.class)
+public class PathTraversalTest {
+
+  private final ImmutableList<Parameter> parameters =
+      ImmutableList.of(new Parameter(), new Parameter(), new Parameter(), new Parameter());
+  private final ImmutableList<Server> servers =
+      ImmutableList.of(new Server(), new Server(), new Server());
+
+  private final ImmutableMap<String, Operation> operations =
+      ImmutableMap.of("one", new Operation(), "two", new Operation(), "three", new Operation());
+
+  private PathTraversal pathTraversal;
+
+  @Rule public MockitoRule rule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
+  @Mock private TraversalHelperFactory traversalHelperFactory;
+  @Mock private TraversalHelper traversalHelper;
+  @Mock private ExtensionValidationIntegrator extensionValidationIntegrator;
+  @Mock private Path path;
+
+  /** Sets up live and mocked instances for testing. */
+  @Before
+  public void setup() {
+    pathTraversal =
+        new PathTraversal(
+            traversalHelperFactory,
+            extensionValidationIntegrator,
+            ImmutableList.of(),
+            path,
+            "name");
+
+    when(path.getOperations()).thenReturn(operations);
+    when(path.getParameters()).thenReturn(parameters);
+    when(path.getServers()).thenReturn(servers);
+
+    when(traversalHelperFactory.create(any(ImmutableList.class))).thenReturn(traversalHelper);
+  }
+
+  @Test
+  public void traverse_sendsOperationsToTraversalHelper() {
+    pathTraversal.traverse();
+
+    verify(traversalHelper).sendOperationTraversals(operations);
+  }
+
+  @Test
+  public void traverse_nullOperationsMember_doesNotFail() {
+    when(path.getOperations()).thenReturn(null);
+
+    pathTraversal.traverse();
+
+    verify(traversalHelper, atLeastOnce()).sendOperationTraversals(any());
+  }
+
+  @Test
+  public void traverse_sendsParametersToTraversalHelper() {
+    pathTraversal.traverse();
+
+    verify(traversalHelper).sendParameterTraversals(parameters);
+  }
+
+  @Test
+  public void traverse_nullParametersMember_doesNotFail() {
+    when(path.getParameters()).thenReturn(null);
+
+    pathTraversal.traverse();
+
+    verify(traversalHelper, atLeastOnce()).sendParameterTraversals(any());
+  }
+
+  @Test
+  public void traverse_sendsServersToTraversalHelper() {
+    pathTraversal.traverse();
+
+    verify(traversalHelper).sendServerTraversals(servers);
+  }
+
+  @Test
+  public void traverse_nullServersMember_doesNotFail() {
+    when(path.getServers()).thenReturn(null);
+
+    pathTraversal.traverse();
+
+    verify(traversalHelper, atLeastOnce()).sendServerTraversals(any());
+  }
+}
diff --git a/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/TraversalCommandFactoryTest.java b/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/TraversalCommandFactoryTest.java
index 9f8e177..455543d 100644
--- a/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/TraversalCommandFactoryTest.java
+++ b/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/TraversalCommandFactoryTest.java
@@ -19,6 +19,7 @@
 import org.openapi4j.parser.model.v3.OpenApi3;
 import org.openapi4j.parser.model.v3.Operation;
 import org.openapi4j.parser.model.v3.Parameter;
+import org.openapi4j.parser.model.v3.Path;
 import org.openapi4j.parser.model.v3.Response;
 import org.openapi4j.parser.model.v3.SecurityRequirement;
 import org.openapi4j.parser.model.v3.Server;
@@ -75,6 +76,11 @@
   }
 
   @Test
+  public void create_pathAndNamePassed_returnsNonNullObject() {
+    assertThat(traversalCommandFactory.create(new Path(), traversalPath, "name")).isNotNull();
+  }
+
+  @Test
   public void create_responseAndNamePassed_returnsNonNullObject() {
     assertThat(traversalCommandFactory.create(new Response(), traversalPath, "name")).isNotNull();
   }