b/161470092 Implement traversal for OpenApi4j Tag object

Change-Id: Ib038193c03e25bf7365aae90a34292647eecd8ea
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExternalDocsTraversal.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExternalDocsTraversal.java
new file mode 100644
index 0000000..59a9440
--- /dev/null
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExternalDocsTraversal.java
@@ -0,0 +1,28 @@
+package com.apigee.security.oas.extendedvalidator;
+
+import com.google.inject.assistedinject.Assisted;
+import javax.inject.Inject;
+import org.openapi4j.parser.model.v3.ExternalDocs;
+
+/** A {@link Traversal} that traverses a {@link ExternalDocs} object. */
+final class ExternalDocsTraversal extends Traversal<ExternalDocs>
+    implements ExternalDocsTraversalCommand {
+
+  @Inject
+  ExternalDocsTraversal(
+      TraversalCoordinator traversalCoordinator,
+      ExtensionValidationIntegrator extensionValidationIntegrator,
+      TraversalCommandFactory traversalCommandFactory,
+      @Assisted ExternalDocs openApiSchemaObj) {
+    super(
+        traversalCoordinator,
+        extensionValidationIntegrator,
+        traversalCommandFactory,
+        openApiSchemaObj);
+  }
+
+  @Override
+  public void traverse() {
+    // TODO(b/161441872): Add call to process extensions.
+  }
+}
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExternalDocsTraversalCommand.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExternalDocsTraversalCommand.java
new file mode 100644
index 0000000..b2c79b9
--- /dev/null
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExternalDocsTraversalCommand.java
@@ -0,0 +1,4 @@
+package com.apigee.security.oas.extendedvalidator;
+
+/** A {@link TraversalCommand} interface for {@link ExternalDocsTraversal}. */
+public interface ExternalDocsTraversalCommand extends TraversalCommand {}
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/InfoTraversal.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/InfoTraversal.java
index 2f1781c..046b274 100644
--- a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/InfoTraversal.java
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/InfoTraversal.java
@@ -24,11 +24,9 @@
   public void traverse() {
     // TODO(b/161441872): Process extensions.
 
-    // TODO(b/161811574): Add check for Contacts' existence.
+    // TODO(b/161811574): Add checks for field unpacking.
     traversalCoordinator.handleTraversalCommand(
         traversalCommandFactory.create(openApiSchemaObj.getContact()));
-
-    // TODO(b/161811574): Add check for License's existence.
     traversalCoordinator.handleTraversalCommand(
         traversalCommandFactory.create(openApiSchemaObj.getLicense()));
   }
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 0138fee..109132d 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
@@ -28,10 +28,11 @@
 
     traverseInfo();
     traverseServers();
+    traverseTags();
   }
 
   private void traverseInfo() {
-    // TODO(b/161811574): Add check for Info's existence.
+    // TODO(b/161811574): Safely unpack Info.
 
     traversalCoordinator.handleTraversalCommand(
         traversalCommandFactory.create(openApiSchemaObj.getInfo()));
@@ -46,4 +47,14 @@
                         traversalCoordinator.handleTraversalCommand(
                             traversalCommandFactory.create(server))));
   }
+
+  private void traverseTags() {
+    Optional.ofNullable(openApiSchemaObj.getTags())
+        .ifPresent(
+            tags ->
+                tags.forEach(
+                    tag ->
+                        traversalCoordinator.handleTraversalCommand(
+                            traversalCommandFactory.create(tag))));
+  }
 }
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/OpenApiSpecificationTraversalCommand.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/OpenApiSpecificationTraversalCommand.java
index 2f65ded..c7e4cdc 100644
--- a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/OpenApiSpecificationTraversalCommand.java
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/OpenApiSpecificationTraversalCommand.java
@@ -1,4 +1,4 @@
 package com.apigee.security.oas.extendedvalidator;
 
-/** A {@link TraversalCommand} marker interface for {@link OpenApiSpecificationTraversal}. */
+/** A {@link TraversalCommand} interface for {@link OpenApiSpecificationTraversal}. */
 public interface OpenApiSpecificationTraversalCommand 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
new file mode 100644
index 0000000..52ecd34
--- /dev/null
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/TagTraversal.java
@@ -0,0 +1,34 @@
+package com.apigee.security.oas.extendedvalidator;
+
+import com.google.inject.assistedinject.Assisted;
+import java.util.Optional;
+import javax.inject.Inject;
+import org.openapi4j.parser.model.v3.Tag;
+
+/** A {@link Traversal} that traverses a {@link Tag} object. */
+final class TagTraversal extends Traversal<Tag> implements TagTraversalCommand {
+
+  @Inject
+  TagTraversal(
+      TraversalCoordinator traversalCoordinator,
+      ExtensionValidationIntegrator extensionValidationIntegrator,
+      TraversalCommandFactory traversalCommandFactory,
+      @Assisted Tag openApiSchemaObj) {
+    super(
+        traversalCoordinator,
+        extensionValidationIntegrator,
+        traversalCommandFactory,
+        openApiSchemaObj);
+  }
+
+  @Override
+  public void traverse() {
+    // TODO(b/161441872): Add call to process extensions.
+
+    Optional.ofNullable(openApiSchemaObj.getExternalDocs())
+        .ifPresent(
+            externalDocs ->
+                traversalCoordinator.handleTraversalCommand(
+                    traversalCommandFactory.create(externalDocs)));
+  }
+}
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/TagTraversalCommand.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/TagTraversalCommand.java
new file mode 100644
index 0000000..23bd91b
--- /dev/null
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/TagTraversalCommand.java
@@ -0,0 +1,4 @@
+package com.apigee.security.oas.extendedvalidator;
+
+/** A {@link TraversalCommand} interface for {@link TagTraversal}. */
+public interface TagTraversalCommand extends TraversalCommand {}
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/Traversal.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/Traversal.java
index cc1ad65..7211dfb 100644
--- a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/Traversal.java
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/Traversal.java
@@ -5,10 +5,10 @@
 /** Superclass field skeleton for traversing an {@link OpenApiSchema} object. */
 abstract class Traversal<T extends OpenApiSchema> {
 
-  protected T openApiSchemaObj;
-  protected TraversalCoordinator traversalCoordinator;
-  protected ExtensionValidationIntegrator extensionValidationIntegrator;
-  protected TraversalCommandFactory traversalCommandFactory;
+  protected final T openApiSchemaObj;
+  protected final TraversalCoordinator traversalCoordinator;
+  protected final ExtensionValidationIntegrator extensionValidationIntegrator;
+  protected final TraversalCommandFactory traversalCommandFactory;
 
   Traversal(
       TraversalCoordinator traversalCoordinator,
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 b742780..e7d0ad4 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
@@ -1,11 +1,13 @@
 package com.apigee.security.oas.extendedvalidator;
 
 import org.openapi4j.parser.model.v3.Contact;
+import org.openapi4j.parser.model.v3.ExternalDocs;
 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.Server;
 import org.openapi4j.parser.model.v3.ServerVariable;
+import org.openapi4j.parser.model.v3.Tag;
 
 /**
  * Generates {@link TraversalCommand} implementations via Guice's {@link
@@ -13,15 +15,19 @@
  */
 interface TraversalCommandFactory {
 
-  OpenApiSpecificationTraversalCommand create(OpenApi3 openApiSpec);
+  ContactTraversalCommand create(Contact contact);
+
+  ExternalDocsTraversalCommand create(ExternalDocs externalDocs);
 
   InfoTraversalCommand create(Info info);
 
-  ContactTraversalCommand create(Contact contact);
-
   LicenseTraversalCommand create(License license);
 
+  OpenApiSpecificationTraversalCommand create(OpenApi3 openApiSpec);
+
   ServerTraversalCommand create(Server server);
 
   ServerVariableTraversalCommand create(ServerVariable serverVariable, 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 8384642..bcaba34 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
@@ -9,13 +9,15 @@
   protected void configure() {
     install(
         new FactoryModuleBuilder()
+            .implement(ContactTraversalCommand.class, ContactTraversal.class)
+            .implement(ExternalDocsTraversalCommand.class, ExternalDocsTraversal.class)
+            .implement(InfoTraversalCommand.class, InfoTraversal.class)
+            .implement(LicenseTraversalCommand.class, LicenseTraversal.class)
             .implement(
                 OpenApiSpecificationTraversalCommand.class, OpenApiSpecificationTraversal.class)
-            .implement(InfoTraversalCommand.class, InfoTraversal.class)
-            .implement(ContactTraversalCommand.class, ContactTraversal.class)
-            .implement(LicenseTraversalCommand.class, LicenseTraversal.class)
             .implement(ServerTraversalCommand.class, ServerTraversal.class)
             .implement(ServerVariableTraversalCommand.class, ServerVariableTraversal.class)
+            .implement(TagTraversalCommand.class, TagTraversal.class)
             .build(TraversalCommandFactory.class));
   }
 }
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 348e2d4..0887e9d 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
@@ -19,6 +19,7 @@
 import org.openapi4j.parser.model.v3.Info;
 import org.openapi4j.parser.model.v3.OpenApi3;
 import org.openapi4j.parser.model.v3.Server;
+import org.openapi4j.parser.model.v3.Tag;
 
 @RunWith(JUnit4.class)
 public class OpenApiSpecificationTraversalTest {
@@ -31,6 +32,7 @@
 
   private OpenApiSpecificationTraversal openApiSpecificationTraversal;
   private ImmutableList<Server> servers;
+  private ImmutableList<Tag> tags;
 
   /** Sets up live and mocked instances for testing. */
   @Before
@@ -49,6 +51,9 @@
     servers = ImmutableList.of(new Server(), new Server(), new Server());
     when(openApiSpec.getServers()).thenReturn(servers);
 
+    tags = ImmutableList.of(new Tag(), new Tag(), new Tag());
+    when(openApiSpec.getTags()).thenReturn(tags);
+
     when(openApiSpec.getInfo()).thenReturn(new Info());
   }
 
@@ -59,7 +64,7 @@
     verify(traversalCoordinator).handleTraversalCommand(any(InfoTraversal.class));
   }
 
-  // TODO(b/161811574): Add tests for additional nullable children.
+  // TODO(b/161811574): Add tests to check other nullable children.
 
   @Test
   public void traverse_sendsServerChildrenTraversalsToTraversalCommander() {
@@ -75,4 +80,19 @@
 
     openApiSpecificationTraversal.traverse();
   }
+
+  @Test
+  public void traverse_sendsTagChildrenTraversalsToTraversalCommander() {
+    openApiSpecificationTraversal.traverse();
+
+    verify(traversalCoordinator, times(tags.size()))
+        .handleTraversalCommand(any(TagTraversalCommand.class));
+  }
+
+  @Test
+  public void traverse_nullTagChildren_doesNotThrowException() {
+    when(openApiSpec.getTags()).thenReturn(null);
+
+    openApiSpecificationTraversal.traverse();
+  }
 }
diff --git a/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/TagTraversalTest.java b/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/TagTraversalTest.java
new file mode 100644
index 0000000..bf3db6f
--- /dev/null
+++ b/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/TagTraversalTest.java
@@ -0,0 +1,58 @@
+package com.apigee.security.oas.extendedvalidator;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.google.inject.Guice;
+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.Tag;
+
+@RunWith(JUnit4.class)
+public class TagTraversalTest {
+
+  @Rule public final MockitoRule rule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
+
+  @Mock private TraversalCoordinator traversalCoordinator;
+  @Mock private ExtensionValidationIntegrator extensionValidationIntegrator;
+  @Mock private Tag tag;
+
+  private TagTraversal tagTraversal;
+
+  /** Sets up live and mocked instances for testing. */
+  @Before
+  public void setup() {
+    TraversalCommandFactory traversalCommandFactory =
+        Guice.createInjector(new ExtendedValidatorMainModule())
+            .getInstance(TraversalCommandFactory.class);
+
+    tagTraversal =
+        new TagTraversal(
+            traversalCoordinator, extensionValidationIntegrator, traversalCommandFactory, tag);
+  }
+
+  @Test
+  public void traverse_sendsTraversalCommanderExternalDocsTraversalCommand() {
+    when(tag.getExternalDocs()).thenReturn(new ExternalDocs());
+
+    tagTraversal.traverse();
+
+    verify(traversalCoordinator).handleTraversalCommand(any(ExternalDocsTraversal.class));
+  }
+
+  @Test
+  public void traverse_nullExternalDocChild_doesNotFail() {
+    when(tag.getExternalDocs()).thenReturn(null);
+
+    tagTraversal.traverse();
+  }
+}
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 9f580b4..25920ac 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
@@ -8,11 +8,13 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 import org.openapi4j.parser.model.v3.Contact;
+import org.openapi4j.parser.model.v3.ExternalDocs;
 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.Server;
 import org.openapi4j.parser.model.v3.ServerVariable;
+import org.openapi4j.parser.model.v3.Tag;
 
 @RunWith(JUnit4.class)
 public class TraversalCommandFactoryTest {
@@ -64,4 +66,15 @@
 
     assertThat(traversal).isInstanceOf(ServerVariableTraversalCommand.class);
   }
+
+  @Test
+  public void create_tagObjectPassed_returnsTagTraversalCommand() {
+    assertThat(traversalCommandFactory.create(new Tag())).isInstanceOf(TagTraversalCommand.class);
+  }
+
+  @Test
+  public void create_externalDocsObjectPassed_returnsExternalDocsTraversalCommand() {
+    assertThat(traversalCommandFactory.create(new ExternalDocs()))
+        .isInstanceOf(ExternalDocsTraversalCommand.class);
+  }
 }