b/162014527 Define Operation Traversal structure

Change-Id: I3f62adf49efaeb53af230f78c9f90b67505d7800
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 a10f139..25d8108 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
@@ -12,6 +12,7 @@
 import org.openapi4j.parser.model.v3.Info;
 import org.openapi4j.parser.model.v3.License;
 import org.openapi4j.parser.model.v3.OpenApi3;
+import org.openapi4j.parser.model.v3.Operation;
 import org.openapi4j.parser.model.v3.Server;
 import org.openapi4j.parser.model.v3.ServerVariable;
 import org.openapi4j.parser.model.v3.Tag;
@@ -78,6 +79,17 @@
   }
 
   @Override
+  public void sendOperationTraversals(ImmutableMap<String, Operation> operations) {
+    Optional.ofNullable(operations)
+        .ifPresent(
+            operationObj ->
+                operationObj.forEach(
+                    (name, operation) ->
+                        coordinator.handleTraversalCommand(
+                            factory.create(operation, traversalPath, name))));
+  }
+
+  @Override
   public void sendServerTraversals(ImmutableList<Server> serversList) {
     Optional.ofNullable(serversList)
         .ifPresent(
@@ -88,17 +100,14 @@
   }
 
   @Override
-  public void sendServerVariableTraversals(
-      ImmutableMap<String, ServerVariable> serverVariablesMap) {
-    Optional.ofNullable(serverVariablesMap)
+  public void sendServerVariableTraversals(ImmutableMap<String, ServerVariable> serverVariableMap) {
+    Optional.ofNullable(serverVariableMap)
         .ifPresent(
-            serverVariables -> {
-              serverVariables.forEach(
-                  (name, serverVariable) -> {
-                    coordinator.handleTraversalCommand(
-                        factory.create(serverVariable, name, traversalPath));
-                  });
-            });
+            serverVariables ->
+                serverVariables.forEach(
+                    (name, serverVariable) ->
+                        coordinator.handleTraversalCommand(
+                            factory.create(serverVariable, traversalPath, name))));
   }
 
   @Override
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/OperationTraversal.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/OperationTraversal.java
new file mode 100644
index 0000000..013073f
--- /dev/null
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/OperationTraversal.java
@@ -0,0 +1,43 @@
+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.Operation;
+
+/** A {@link Traversal} that traverses an {@link Operation} object. */
+final class OperationTraversal extends Traversal<Operation> implements OperationTraversalCommand {
+
+  @Inject
+  OperationTraversal(
+      TraversalHelperFactory traversalHelperFactory,
+      ExtensionValidationIntegrator extensionValidationIntegrator,
+      @Assisted
+          ImmutableList<Map.Entry<Class<? extends OpenApiSchema>, Optional<String>>> traversalPath,
+      @Assisted Operation 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.sendExternalDocsTraversal(openApiSchemaObj.getExternalDocs());
+    // TODO(b/162949100): Send Callback Traversals.
+    traversalHelper.sendServerTraversals(Immutables.toImmutableList(openApiSchemaObj.getServers()));
+    // TODO(b/162942095): Send Parameter Traversals.
+    // TODO(b/163024263): Send Response Traversals.
+    // TODO(b/163024265): Send Security Requirement Traversals.
+  }
+}
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/OperationTraversalCommand.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/OperationTraversalCommand.java
new file mode 100644
index 0000000..5518f63
--- /dev/null
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/OperationTraversalCommand.java
@@ -0,0 +1,4 @@
+package com.apigee.security.oas.extendedvalidator;
+
+/** A {@link TraversalCommand} interface for {@link OperationTraversal}. */
+public interface OperationTraversalCommand extends TraversalCommand {}
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 54ea11a..89e15d6 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
@@ -9,6 +9,7 @@
 import org.openapi4j.parser.model.v3.Info;
 import org.openapi4j.parser.model.v3.License;
 import org.openapi4j.parser.model.v3.OpenApi3;
+import org.openapi4j.parser.model.v3.Operation;
 import org.openapi4j.parser.model.v3.Server;
 import org.openapi4j.parser.model.v3.ServerVariable;
 import org.openapi4j.parser.model.v3.Tag;
@@ -37,14 +38,19 @@
 
   OpenApiSpecificationTraversalCommand create(OpenApi3 openApiSpec);
 
+  OperationTraversalCommand create(
+      Operation operation,
+      ImmutableList<Map.Entry<Class<? extends OpenApiSchema>, Optional<String>>> traversalPath,
+      String name);
+
   ServerTraversalCommand create(
       Server server,
       ImmutableList<Map.Entry<Class<? extends OpenApiSchema>, Optional<String>>> traversalPath);
 
   ServerVariableTraversalCommand create(
       ServerVariable serverVariable,
-      String name,
-      ImmutableList<Map.Entry<Class<? extends OpenApiSchema>, Optional<String>>> traversalPath);
+      ImmutableList<Map.Entry<Class<? extends OpenApiSchema>, Optional<String>>> traversalPath,
+      String name);
 
   TagTraversalCommand create(
       Tag tag,
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 bcaba34..a4a71ca 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
@@ -15,6 +15,7 @@
             .implement(LicenseTraversalCommand.class, LicenseTraversal.class)
             .implement(
                 OpenApiSpecificationTraversalCommand.class, OpenApiSpecificationTraversal.class)
+            .implement(OperationTraversalCommand.class, OperationTraversal.class)
             .implement(ServerTraversalCommand.class, ServerTraversal.class)
             .implement(ServerVariableTraversalCommand.class, ServerVariableTraversal.class)
             .implement(TagTraversalCommand.class, TagTraversal.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 1df3e3c..319252a 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
@@ -7,6 +7,7 @@
 import org.openapi4j.parser.model.v3.Info;
 import org.openapi4j.parser.model.v3.License;
 import org.openapi4j.parser.model.v3.OpenApi3;
+import org.openapi4j.parser.model.v3.Operation;
 import org.openapi4j.parser.model.v3.Server;
 import org.openapi4j.parser.model.v3.ServerVariable;
 import org.openapi4j.parser.model.v3.Tag;
@@ -24,6 +25,8 @@
 
   void sendOpenApiTraversal(OpenApi3 openApiSpec);
 
+  void sendOperationTraversals(ImmutableMap<String, Operation> operations);
+
   void sendServerTraversals(ImmutableList<Server> serversList);
 
   void sendServerVariableTraversals(ImmutableMap<String, ServerVariable> serverVariables);
diff --git a/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/BaseTraversalHelperTest.java b/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/BaseTraversalHelperTest.java
index 00e2e5d..9ce9a18 100644
--- a/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/BaseTraversalHelperTest.java
+++ b/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/BaseTraversalHelperTest.java
@@ -21,6 +21,7 @@
 import org.openapi4j.parser.model.v3.Info;
 import org.openapi4j.parser.model.v3.License;
 import org.openapi4j.parser.model.v3.OpenApi3;
+import org.openapi4j.parser.model.v3.Operation;
 import org.openapi4j.parser.model.v3.Server;
 import org.openapi4j.parser.model.v3.ServerVariable;
 import org.openapi4j.parser.model.v3.Tag;
@@ -42,6 +43,8 @@
   private final ImmutableMap<String, ServerVariable> serverVariables =
       ImmutableMap.of(
           "one", new ServerVariable(), "two", new ServerVariable(), "three", new ServerVariable());
+  private final ImmutableMap<String, Operation> operations =
+      ImmutableMap.of("one", new Operation(), "two", new Operation(), "three", new Operation());
 
   @Rule public MockitoRule rule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
   @Mock private TraversalCoordinator traversalCoordinator;
@@ -79,7 +82,7 @@
   }
 
   @Test
-  public void sendExternalDocsTraversals_nullParameter_doesNotFail() {
+  public void sendExternalDocsTraversal_nullParameter_doesNotFail() {
     traversalHelper.sendExternalDocsTraversal(null);
   }
 
@@ -91,7 +94,7 @@
   }
 
   @Test
-  public void sendInfoTraversals_nullParameter_doesNotFail() {
+  public void sendInfoTraversal_nullParameter_doesNotFail() {
     traversalHelper.sendInfoTraversal(null);
   }
 
@@ -103,12 +106,12 @@
   }
 
   @Test
-  public void sendLicenseTraversals_nullParameter_doesNotFail() {
+  public void sendLicenseTraversal_nullParameter_doesNotFail() {
     traversalHelper.sendLicenseTraversal(null);
   }
 
   @Test
-  public void sendOpenApi_sendsOpenApiSpecificationTraversalCommandsToCoordinator() {
+  public void sendOpenApiTraversal_sendsOpenApiSpecificationTraversalCommandToCoordinator() {
     traversalHelper.sendOpenApiTraversal(openApiSpec);
 
     verify(traversalCoordinator)
@@ -116,11 +119,24 @@
   }
 
   @Test
-  public void sendOpenApi_nullParameter_doesNotFail() {
+  public void sendOpenApiTraversal_nullParameter_doesNotFail() {
     traversalHelper.sendOpenApiTraversal(null);
   }
 
   @Test
+  public void sendOperationTraversals_sendsOperationTraversalCommandsToCoordinator() {
+    traversalHelper.sendOperationTraversals(operations);
+
+    verify(traversalCoordinator, times(operations.size()))
+        .handleTraversalCommand(any(OperationTraversalCommand.class));
+  }
+
+  @Test
+  public void sendOperationTraversals_nullParameter_doesNotFail() {
+    traversalHelper.sendOperationTraversals(null);
+  }
+
+  @Test
   public void sendServerTraversals_sendsServerTraversalCommandsToCoordinator() {
     traversalHelper.sendServerTraversals(servers);
 
@@ -134,7 +150,7 @@
   }
 
   @Test
-  public void sendServerVariables_sendsServerVariableTraversalCommandsToCoordinator() {
+  public void sendServerVariableTraversals_sendsServerVariableTraversalCommandsToCoordinator() {
     traversalHelper.sendServerVariableTraversals(serverVariables);
 
     verify(traversalCoordinator, times(serverVariables.size()))
@@ -142,7 +158,7 @@
   }
 
   @Test
-  public void sendServerVariables_nullParameter_doesNotFail() {
+  public void sendServerVariableTraversals_nullParameter_doesNotFail() {
     traversalHelper.sendServerVariableTraversals(null);
   }
 
diff --git a/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/OperationTraversalTest.java b/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/OperationTraversalTest.java
new file mode 100644
index 0000000..e20fc57
--- /dev/null
+++ b/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/OperationTraversalTest.java
@@ -0,0 +1,77 @@
+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 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.ExternalDocs;
+import org.openapi4j.parser.model.v3.Operation;
+import org.openapi4j.parser.model.v3.Server;
+
+@RunWith(JUnit4.class)
+public class OperationTraversalTest {
+
+  @Rule public final MockitoRule rule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
+
+  @Mock private TraversalHelperFactory traversalHelperFactory;
+  @Mock private TraversalHelper traversalHelper;
+  @Mock private ExtensionValidationIntegrator extensionValidationIntegrator;
+  @Mock private Operation operation;
+
+  private final ExternalDocs externalDocs = new ExternalDocs();
+  private final ImmutableList servers = ImmutableList.of(new Server(), new Server(), new Server());
+
+  private OperationTraversal operationTraversal;
+
+  /** Sets up live and mocked instances for testing. */
+  @Before
+  public void setup() {
+
+    when(operation.getExternalDocs()).thenReturn(externalDocs);
+    when(operation.getServers()).thenReturn(servers);
+
+    operationTraversal =
+        new OperationTraversal(
+            traversalHelperFactory,
+            extensionValidationIntegrator,
+            ImmutableList.of(),
+            operation,
+            "name");
+
+    when(traversalHelperFactory.create(any(ImmutableList.class))).thenReturn(traversalHelper);
+  }
+
+  @Test
+  public void traverse_sendsExternalDocsToTraversalHelper() {
+    operationTraversal.traverse();
+
+    verify(traversalHelper).sendExternalDocsTraversal(externalDocs);
+  }
+
+  @Test
+  public void traverse_sendsServersToTraversalHelper() {
+    operationTraversal.traverse();
+
+    verify(traversalHelper).sendServerTraversals(servers);
+  }
+
+  @Test
+  public void traverse_nullServersMember_doesNotFail() {
+    when(operation.getServers()).thenReturn(null);
+
+    operationTraversal.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 9eab5e8..7e1d85e 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
@@ -16,6 +16,7 @@
 import org.openapi4j.parser.model.v3.Info;
 import org.openapi4j.parser.model.v3.License;
 import org.openapi4j.parser.model.v3.OpenApi3;
+import org.openapi4j.parser.model.v3.Operation;
 import org.openapi4j.parser.model.v3.Server;
 import org.openapi4j.parser.model.v3.ServerVariable;
 import org.openapi4j.parser.model.v3.Tag;
@@ -30,53 +31,48 @@
       traversalPath = ImmutableList.copyOf(new ArrayDeque<>());
 
   @Test
-  public void
-      create_openApiSpecificationObjectPassed_returnsOpenApiSpecificationTraversalCommand() {
-    assertThat(traversalCommandFactory.create(new OpenApi3()))
-        .isInstanceOf(OpenApiSpecificationTraversalCommand.class);
+  public void create_contactPassed_returnsNonNullObject() {
+    assertThat(traversalCommandFactory.create(new Contact(), traversalPath)).isNotNull();
   }
 
   @Test
-  public void create_infoObjectPassed_returnsInfoTraversalCommand() {
-    assertThat(traversalCommandFactory.create(new Info(), traversalPath))
-        .isInstanceOf(InfoTraversalCommand.class);
+  public void create_externalDocsPassed_returnsNonNullObject() {
+    assertThat(traversalCommandFactory.create(new ExternalDocs(), traversalPath)).isNotNull();
   }
 
   @Test
-  public void create_contactObjectPassed_returnsContactTraversalCommand() {
-    assertThat(traversalCommandFactory.create(new Contact(), traversalPath))
-        .isInstanceOf(ContactTraversalCommand.class);
+  public void create_infoPassed_returnsNonNullObject() {
+    assertThat(traversalCommandFactory.create(new Info(), traversalPath)).isNotNull();
   }
 
   @Test
-  public void create_licenseObjectPassed_returnsLicenseTraversalCommand() {
-    assertThat(traversalCommandFactory.create(new License(), traversalPath))
-        .isInstanceOf(LicenseTraversalCommand.class);
+  public void create_licensePassed_returnsNonNullObject() {
+    assertThat(traversalCommandFactory.create(new License(), traversalPath)).isNotNull();
   }
 
   @Test
-  public void create_serverObjectPassed_returnsServerTraversalCommand() {
-    assertThat(traversalCommandFactory.create(new Server(), traversalPath))
-        .isInstanceOf(ServerTraversalCommand.class);
+  public void create_openApiSpecificationPassed_returnsNonNullObject() {
+    assertThat(traversalCommandFactory.create(new OpenApi3())).isNotNull();
   }
 
   @Test
-  public void create_nameAndServerVariableObjectPassed_returnsServerVariableTraversalCommand() {
-    ServerVariableTraversalCommand traversal =
-        traversalCommandFactory.create(new ServerVariable(), "serverVariableName", traversalPath);
-
-    assertThat(traversal).isInstanceOf(ServerVariableTraversalCommand.class);
+  public void create_operationAndNamePassed_returnsNonNullObject() {
+    assertThat(traversalCommandFactory.create(new Operation(), traversalPath, "name")).isNotNull();
   }
 
   @Test
-  public void create_tagObjectPassed_returnsTagTraversalCommand() {
-    assertThat(traversalCommandFactory.create(new Tag(), traversalPath))
-        .isInstanceOf(TagTraversalCommand.class);
+  public void create_serverPassed_returnsNonNullObject() {
+    assertThat(traversalCommandFactory.create(new Server(), traversalPath)).isNotNull();
   }
 
   @Test
-  public void create_externalDocsObjectPassed_returnsExternalDocsTraversalCommand() {
-    assertThat(traversalCommandFactory.create(new ExternalDocs(), traversalPath))
-        .isInstanceOf(ExternalDocsTraversalCommand.class);
+  public void create_serverVariableAndNamePassed_returnsNonNullObject() {
+    assertThat(traversalCommandFactory.create(new ServerVariable(), traversalPath, "name"))
+        .isNotNull();
+  }
+
+  @Test
+  public void create_tagPassed_returnsNonNullObject() {
+    assertThat(traversalCommandFactory.create(new Tag(), traversalPath)).isNotNull();
   }
 }