b/163138925 Integrate command line module with Extended Validator

Change-Id: Icc7fc46ed2f870886b420f1af1f623394368c5fd
diff --git a/oas-cli/build.gradle b/oas-cli/build.gradle
index f00b209..4624448 100644
--- a/oas-cli/build.gradle
+++ b/oas-cli/build.gradle
@@ -4,6 +4,7 @@
 
 dependencies {
     implementation 'com.beust:jcommander:1.78'
+    implementation project(':oas-core')
 
     testImplementation project(':oas-test')
 }
@@ -13,4 +14,9 @@
         attributes('Main-Class': 'com.apigee.security.oas.CommandLineClient')
     }
 }
+
+javadoc {
+  title = "OpenApi3 Specification Security Client"
+}
+
 mainClassName = "com.apigee.security.oas.CommandLineClient"
\ No newline at end of file
diff --git a/oas-cli/src/main/java/com/apigee/security/oas/CommandLineBaseRunner.java b/oas-cli/src/main/java/com/apigee/security/oas/CommandLineBaseRunner.java
index 168084e..b35dfe7 100644
--- a/oas-cli/src/main/java/com/apigee/security/oas/CommandLineBaseRunner.java
+++ b/oas-cli/src/main/java/com/apigee/security/oas/CommandLineBaseRunner.java
@@ -1,44 +1,69 @@
 package com.apigee.security.oas;
 
+import com.apigee.security.oas.extendedvalidator.ExtendedValidator;
+import com.apigee.security.oas.extendedvalidator.ExtensionValidationMessage;
+import com.apigee.security.oas.parser.BaseParser;
+import com.apigee.security.oas.parser.ParserException;
 import com.beust.jcommander.ParameterException;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.flogger.FluentLogger;
 import com.google.inject.Provider;
 import java.io.PrintWriter;
+import java.util.Optional;
 import javax.inject.Inject;
+import org.openapi4j.parser.model.v3.OpenApi3;
 
-/** Class that handles the execution. */
 final class CommandLineBaseRunner implements CommandLineRunner {
 
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
   private final Provider<PrintWriter> printWriterProvider;
   private final CommandLineParser commandLineParser;
+  private final BaseParser baseParser;
+  private final ExtendedValidator extendedValidator;
 
   @Inject
   CommandLineBaseRunner(
-      Provider<PrintWriter> printWriterProvider, CommandLineParser commandLineParser) {
+      Provider<PrintWriter> printWriterProvider,
+      CommandLineParser commandLineParser,
+      BaseParser baseParser,
+      ExtendedValidator extendedValidator) {
     this.printWriterProvider = printWriterProvider;
     this.commandLineParser = commandLineParser;
+    this.baseParser = baseParser;
+    this.extendedValidator = extendedValidator;
   }
 
-  /**
-   * Calls different methods for parsing & validity of arguments, OpenAPI Specification Document
-   * (v3), and its security features.
-   *
-   * <p>Takes a {@code File} to a OpenAPI Specification (v3) document and outputs its Security
-   * Validity.
-   *
-   * @param args Arguments that are passed through command line interface.
-   */
   @Override
   public void run(String[] args) {
     PrintWriter printWriter = printWriterProvider.get();
+
     try {
       commandLineParser.parseArguments(args);
-    } catch (ParameterException e) {
+      OpenApi3 oas = baseParser.parse(commandLineParser.getOasFile());
+      ImmutableSet<ExtensionValidationMessage> errors = extendedValidator.validate(oas);
+      printWriter.printf("Found %d extension validation errors.\n\n", errors.size());
+      printValidationMessages(errors, printWriter);
+    } catch (ParserException | ParameterException e) {
       logger.atSevere().withCause(e).log("Unable to parse arguments");
-      printWriter.println(e.getLocalizedMessage());
+      printWriter.printf("Error : %s\n", e.getMessage());
+
+      Optional.ofNullable(e.getCause())
+          .ifPresent(cause -> printWriter.printf("Cause : %s\n", cause.getMessage()));
     } finally {
       printWriter.close();
     }
   }
+
+  private static void printValidationMessages(
+      ImmutableSet<ExtensionValidationMessage> errors, PrintWriter printWriter) {
+    Optional.ofNullable(errors)
+        .orElse(ImmutableSet.of())
+        .forEach(
+            validationMessage ->
+                printWriter.printf(
+                    "Type: %s\nMessage: %s\nPath: %s\n\n",
+                    validationMessage.type(),
+                    validationMessage.message(),
+                    validationMessage.path()));
+  }
 }
diff --git a/oas-cli/src/main/java/com/apigee/security/oas/CommandLineModule.java b/oas-cli/src/main/java/com/apigee/security/oas/CommandLineModule.java
index 8ec27a0..0b2ca39 100644
--- a/oas-cli/src/main/java/com/apigee/security/oas/CommandLineModule.java
+++ b/oas-cli/src/main/java/com/apigee/security/oas/CommandLineModule.java
@@ -1,5 +1,7 @@
 package com.apigee.security.oas;
 
+import com.apigee.security.oas.extendedvalidator.ExtendedValidatorMainModule;
+import com.apigee.security.oas.parser.BaseParserModule;
 import com.google.inject.AbstractModule;
 
 /** Top level module that imports other Guice modules relied upon. */
@@ -8,6 +10,8 @@
   @Override
   protected void configure() {
     install(new CommandLineInnerModule());
+    install(new BaseParserModule());
+    install(new ExtendedValidatorMainModule());
     binder().requireExplicitBindings();
   }
 }
diff --git a/oas-cli/src/main/java/com/apigee/security/oas/CommandLineRunner.java b/oas-cli/src/main/java/com/apigee/security/oas/CommandLineRunner.java
index c1452cf..d184f61 100644
--- a/oas-cli/src/main/java/com/apigee/security/oas/CommandLineRunner.java
+++ b/oas-cli/src/main/java/com/apigee/security/oas/CommandLineRunner.java
@@ -3,6 +3,14 @@
 /** Performs end to end application logic. */
 interface CommandLineRunner {
 
-  /** Executes end to end application logic. */
+  /**
+   * Calls different methods for parsing & validity of arguments, OpenAPI Specification Document
+   * (v3), and its security features.
+   *
+   * <p>Takes a {@code File} to a OpenAPI Specification (v3) document and outputs its Security
+   * Validity.
+   *
+   * @param args Arguments that are passed through command line interface.
+   */
   void run(String[] args);
 }
diff --git a/oas-cli/src/main/java/com/apigee/security/oas/validators/FileValidator.java b/oas-cli/src/main/java/com/apigee/security/oas/validators/FileValidator.java
index 07edfde..8b9f1de 100644
--- a/oas-cli/src/main/java/com/apigee/security/oas/validators/FileValidator.java
+++ b/oas-cli/src/main/java/com/apigee/security/oas/validators/FileValidator.java
@@ -14,6 +14,8 @@
     if (!file.exists()) {
       throw new ParameterException(
           "The file is either not valid or not accessible", new FileNotFoundException());
+    } else if (file.length() == 0) {
+      throw new ParameterException("The file contents are empty");
     }
   }
 }
diff --git a/oas-cli/src/test/java/com/apigee/security/oas/CommandLineBaseRunnerIntegrationTest.java b/oas-cli/src/test/java/com/apigee/security/oas/CommandLineBaseRunnerIntegrationTest.java
new file mode 100644
index 0000000..7185266
--- /dev/null
+++ b/oas-cli/src/test/java/com/apigee/security/oas/CommandLineBaseRunnerIntegrationTest.java
@@ -0,0 +1,123 @@
+package com.apigee.security.oas;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.apigee.security.oas.providers.PrintWriterProvider;
+import com.google.common.io.Resources;
+import com.google.inject.Binder;
+import com.google.inject.Guice;
+import com.google.inject.Module;
+import com.google.inject.util.Modules;
+import java.io.PrintWriter;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.mockito.quality.Strictness;
+
+@Category(SlowTests.class)
+public class CommandLineBaseRunnerIntegrationTest {
+
+  public final class TestModule implements Module {
+    @Override
+    public void configure(Binder binder) {
+      binder.bind(PrintWriterProvider.class).toInstance(printWriterProvider);
+    }
+  }
+
+  @Rule public MockitoRule rule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
+  @Captor private ArgumentCaptor<String> stringCaptor;
+  @Captor private ArgumentCaptor<Integer> intCaptor;
+  @Mock private PrintWriterProvider printWriterProvider;
+  @Mock private PrintWriter printWriter;
+  private CommandLineRunner commandLineRunner;
+
+  private static String getResourcePath(String name) {
+    return Resources.getResource(name).getPath();
+  }
+
+  /** Overriding guice dependencies and setting up command line runner. */
+  @Before
+  public void setup() {
+    when(printWriterProvider.get()).thenReturn(printWriter);
+
+    commandLineRunner =
+        Guice.createInjector(Modules.override(new CommandLineModule()).with(new TestModule()))
+            .getInstance(CommandLineRunner.class);
+  }
+
+  @Test
+  public void run_wrongFileType_printsParserException() {
+    commandLineRunner.run(new String[] {"--file", getResourcePath("Test_Text_File.txt")});
+
+    verify(printWriter, times(2)).printf(any(String.class), stringCaptor.capture());
+
+    assertThat(stringCaptor.getAllValues().toString().toLowerCase()).contains("failed", "parse");
+  }
+
+  @Test
+  public void run_emptyFile_printsParameterException() {
+    commandLineRunner.run(new String[] {"--file", getResourcePath("Empty_Json_File.json")});
+
+    verify(printWriter).printf(any(String.class), stringCaptor.capture());
+
+    assertThat(stringCaptor.getValue().toLowerCase()).contains("file", "empty");
+  }
+
+  @Test
+  public void run_validOasFile_printsZeroErrors() {
+    commandLineRunner.run(new String[] {"--file", getResourcePath("Valid_OAS.json")});
+
+    verify(printWriter).printf(stringCaptor.capture(), intCaptor.capture());
+
+    assertThat(intCaptor.getValue()).isEqualTo(0);
+    assertThat(stringCaptor.getValue().toLowerCase()).contains("found", "errors");
+  }
+
+  @Test
+  public void run_validOasFile_validSecurityExtensionUsage_printsZeroErrors() {
+    commandLineRunner.run(
+        new String[] {"--file", getResourcePath("Valid_OAS_Valid_Extensions.json")});
+
+    verify(printWriter).printf(stringCaptor.capture(), intCaptor.capture());
+
+    assertThat(intCaptor.getValue()).isEqualTo(0);
+    assertThat(stringCaptor.getValue().toLowerCase()).contains("found", "errors");
+  }
+
+  @Test
+  public void run_invalidOasFile() {
+    commandLineRunner.run(new String[] {"--file", getResourcePath("Invalid_OAS.json")});
+
+    verify(printWriter, times(2)).printf(any(String.class), stringCaptor.capture());
+
+    assertThat(stringCaptor.getAllValues().toString().toLowerCase())
+        .contains("failed", "parse", "openapi3", "failure");
+  }
+
+  @Test
+  public void run_validOasFile_invalidSecurityExtensionSchema_printsErrors() {
+    commandLineRunner.run(
+        new String[] {"--file", getResourcePath("Valid_OAS_Invalid_Schema.json")});
+
+    verify(printWriter, times(4)).printf(any(String.class), stringCaptor.capture());
+    assertThat(stringCaptor.getAllValues()).contains("INVALID_SCHEMA");
+  }
+
+  @Test
+  public void run_validOasFile_invalidSecurityExtensionScope_printsErrors() {
+    commandLineRunner.run(new String[] {"--file", getResourcePath("Valid_OAS_Invalid_Scope.json")});
+
+    verify(printWriter, times(5)).printf(any(String.class), stringCaptor.capture());
+    assertThat(stringCaptor.getAllValues()).contains("INVALID_SCOPE");
+  }
+}
diff --git a/oas-cli/src/test/java/com/apigee/security/oas/CommandLineBaseRunnerTest.java b/oas-cli/src/test/java/com/apigee/security/oas/CommandLineBaseRunnerTest.java
index 2fc0284..5e686ea 100644
--- a/oas-cli/src/test/java/com/apigee/security/oas/CommandLineBaseRunnerTest.java
+++ b/oas-cli/src/test/java/com/apigee/security/oas/CommandLineBaseRunnerTest.java
@@ -7,8 +7,12 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import com.apigee.security.oas.extendedvalidator.ExtendedValidator;
+import com.apigee.security.oas.parser.BaseParser;
 import com.beust.jcommander.ParameterException;
+import com.google.common.collect.ImmutableSet;
 import com.google.inject.Provider;
+import java.io.File;
 import java.io.PrintWriter;
 import org.junit.Before;
 import org.junit.Rule;
@@ -19,14 +23,20 @@
 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.OpenApi3;
 
 @RunWith(JUnit4.class)
 public class CommandLineBaseRunnerTest {
-  @Rule public MockitoRule rule = MockitoJUnit.rule();
+  @Rule public MockitoRule rule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
 
   @Mock private Provider<PrintWriter> printWriterProvider;
   @Mock private CommandLineParser commandLineParser;
+  @Mock private BaseParser baseParser;
+  @Mock private ExtendedValidator extendedValidator;
   @Mock private PrintWriter printWriter;
+  @Mock private OpenApi3 openApi3;
+  @Mock private File file;
   @InjectMocks private CommandLineBaseRunner commandLineRunner;
 
   @Before
@@ -36,12 +46,38 @@
 
   @Test
   public void run_callsParseArguments() {
+    when(commandLineParser.getOasFile()).thenReturn(file);
+    when(baseParser.parse(any(File.class))).thenReturn(openApi3);
+    when(extendedValidator.validate(openApi3)).thenReturn(ImmutableSet.of());
+
     commandLineRunner.run(new String[] {""});
 
     verify(commandLineParser, atLeastOnce()).parseArguments(any(String[].class));
   }
 
   @Test
+  public void run_callsBaseParsersParse() {
+    when(commandLineParser.getOasFile()).thenReturn(file);
+    when(baseParser.parse(file)).thenReturn(openApi3);
+    when(extendedValidator.validate(openApi3)).thenReturn(ImmutableSet.of());
+
+    commandLineRunner.run(new String[] {""});
+
+    verify(baseParser, atLeastOnce()).parse(file);
+  }
+
+  @Test
+  public void run_callsExtendedValidatorsValidate() {
+    when(commandLineParser.getOasFile()).thenReturn(file);
+    when(baseParser.parse(file)).thenReturn(openApi3);
+    when(extendedValidator.validate(openApi3)).thenReturn(ImmutableSet.of());
+
+    commandLineRunner.run(new String[] {""});
+
+    verify(extendedValidator, atLeastOnce()).validate(openApi3);
+  }
+
+  @Test
   public void run_onException_printsExceptionMessageAndClosesPrintWriter() {
     String exceptionMessage = "No Parameters received";
     doThrow(new ParameterException(exceptionMessage))
@@ -50,7 +86,7 @@
 
     commandLineRunner.run(new String[] {""});
 
-    verify(printWriter, atLeastOnce()).println(contains(exceptionMessage));
+    verify(printWriter, atLeastOnce()).printf(any(String.class), contains(exceptionMessage));
     verify(printWriter, atLeastOnce()).close();
   }
 }
diff --git a/oas-cli/src/test/java/com/apigee/security/oas/CommandLineModuleTest.java b/oas-cli/src/test/java/com/apigee/security/oas/CommandLineModuleTest.java
index d5cf534..33b74de 100644
--- a/oas-cli/src/test/java/com/apigee/security/oas/CommandLineModuleTest.java
+++ b/oas-cli/src/test/java/com/apigee/security/oas/CommandLineModuleTest.java
@@ -3,6 +3,8 @@
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.catchThrowable;
 
+import com.apigee.security.oas.extendedvalidator.ExtendedValidator;
+import com.apigee.security.oas.parser.BaseParser;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import java.io.PrintWriter;
@@ -38,4 +40,16 @@
     assertThat(catchThrowable(() -> injector.getInstance(PrintWriter.class).close()))
         .doesNotThrowAnyException();
   }
+
+  @Test
+  public void createBaseParser_shouldNotThrowException() {
+    assertThat(catchThrowable(() -> injector.getInstance(BaseParser.class)))
+        .doesNotThrowAnyException();
+  }
+
+  @Test
+  public void createExtendedValidator_shouldNotThrowException() {
+    assertThat(catchThrowable(() -> injector.getInstance(ExtendedValidator.class)))
+        .doesNotThrowAnyException();
+  }
 }
diff --git a/oas-cli/src/test/java/com/apigee/security/oas/FileValidatorTest.java b/oas-cli/src/test/java/com/apigee/security/oas/FileValidatorTest.java
index 2416039..674f296 100644
--- a/oas-cli/src/test/java/com/apigee/security/oas/FileValidatorTest.java
+++ b/oas-cli/src/test/java/com/apigee/security/oas/FileValidatorTest.java
@@ -7,7 +7,6 @@
 import com.apigee.security.oas.validators.FileValidator;
 import com.beust.jcommander.ParameterException;
 import com.google.common.io.Resources;
-import java.net.URISyntaxException;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -15,14 +14,15 @@
 
 @RunWith(JUnit4.class)
 public class FileValidatorTest {
-  private static final String VALID_FILE_NAME = "validOpenApi3DemoSpec.yaml";
 
-  private String validFileUriParam;
   private FileValidator fileValidator;
 
+  private static String getResourcePath(String name) {
+    return Resources.getResource(name).getPath();
+  }
+
   @Before
-  public void setup() throws URISyntaxException {
-    validFileUriParam = Resources.getResource(VALID_FILE_NAME).toURI().getPath();
+  public void setup() {
     fileValidator = new FileValidator();
   }
 
@@ -34,8 +34,19 @@
   }
 
   @Test
+  public void validate_emptyFile_throwsParameterExceptionWithMessage() {
+    assertThatThrownBy(
+            () -> fileValidator.validate("file", getResourcePath("Empty_Json_File.json")))
+        .isInstanceOf(ParameterException.class)
+        .hasMessageContainingAll("file", "empty");
+  }
+
+  @Test
   public void validate_existentFile_doesNotThrowException() {
-    assertThat(catchThrowable(() -> fileValidator.validate("file", validFileUriParam)))
+    assertThat(
+            catchThrowable(
+                () ->
+                    fileValidator.validate("file", getResourcePath("validOpenApi3DemoSpec.yaml"))))
         .doesNotThrowAnyException();
   }
 }
diff --git a/oas-cli/src/test/resources/Empty_Json_File.json b/oas-cli/src/test/resources/Empty_Json_File.json
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/oas-cli/src/test/resources/Empty_Json_File.json
diff --git a/oas-cli/src/test/resources/Invalid_OAS.json b/oas-cli/src/test/resources/Invalid_OAS.json
new file mode 100644
index 0000000..577cd49
--- /dev/null
+++ b/oas-cli/src/test/resources/Invalid_OAS.json
@@ -0,0 +1,54 @@
+{
+  "openapi": "3.0.0",
+  "info": {
+    "title": "Simple API overview",
+    "version": "2.0.0"
+  },
+  "/": {
+    "paths": {
+      "summary": "List API versions",
+      "get": {
+        "operationId": "listVersionsv2",
+        "responses": {
+          "200": {
+            "description": "200 response",
+            "application/json": {
+              "content": {
+                "examples": {
+                  "value": {
+                    "foo": {
+                      "versions": [
+                        {
+                          "status": "CURRENT",
+                          "updated": "2011-01-21T11:33:21Z",
+                          "id": "v2.0",
+                          "links": [
+                            {
+                              "href": "http://127.0.0.1:8774/v2/",
+                              "rel": "self"
+                            }
+                          ]
+                        },
+                        {
+                          "status": "EXPERIMENTAL",
+                          "updated": "2013-07-23T11:33:21Z",
+                          "id": "v3.0",
+                          "links": [
+                            {
+                              "href": "http://127.0.0.1:8774/v3/",
+                              "rel": "self"
+                            }
+                          ]
+                        }
+                      ]
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/oas-cli/src/test/resources/Test_Text_File.txt b/oas-cli/src/test/resources/Test_Text_File.txt
new file mode 100644
index 0000000..bdf08de
--- /dev/null
+++ b/oas-cli/src/test/resources/Test_Text_File.txt
@@ -0,0 +1 @@
+test file
\ No newline at end of file
diff --git a/oas-cli/src/test/resources/Valid_OAS.json b/oas-cli/src/test/resources/Valid_OAS.json
new file mode 100644
index 0000000..1d40e7e
--- /dev/null
+++ b/oas-cli/src/test/resources/Valid_OAS.json
@@ -0,0 +1,54 @@
+{
+  "openapi": "3.0.0",
+  "info": {
+    "title": "Simple API overview",
+    "version": "2.0.0"
+  },
+  "paths": {
+    "/": {
+      "get": {
+        "operationId": "listVersionsv2",
+        "summary": "List API versions",
+        "responses": {
+          "200": {
+            "description": "200 response",
+            "content": {
+              "application/json": {
+                "examples": {
+                  "foo": {
+                    "value": {
+                      "versions": [
+                        {
+                          "status": "CURRENT",
+                          "updated": "2011-01-21T11:33:21Z",
+                          "id": "v2.0",
+                          "links": [
+                            {
+                              "href": "http://127.0.0.1:8774/v2/",
+                              "rel": "self"
+                            }
+                          ]
+                        },
+                        {
+                          "status": "EXPERIMENTAL",
+                          "updated": "2013-07-23T11:33:21Z",
+                          "id": "v3.0",
+                          "links": [
+                            {
+                              "href": "http://127.0.0.1:8774/v3/",
+                              "rel": "self"
+                            }
+                          ]
+                        }
+                      ]
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/oas-cli/src/test/resources/Valid_OAS_Invalid_Schema.json b/oas-cli/src/test/resources/Valid_OAS_Invalid_Schema.json
new file mode 100644
index 0000000..1b6aab3
--- /dev/null
+++ b/oas-cli/src/test/resources/Valid_OAS_Invalid_Schema.json
@@ -0,0 +1,81 @@
+{
+  "openapi": "3.0.0",
+  "info": {
+    "title": "Simple API overview",
+    "version": "2.0.0"
+  },
+  "x-security-type-definitions": {
+  	"inherits-from": ["ns"]
+  },
+  "paths": {
+    "/user": {
+      "get": {
+        "x-security-allow": ["pii"],
+        "operationId": "listVersionsv2",
+        "summary": "List API versions",
+        "parameters": [
+          {
+            "x-security-type": ["ns", 1],
+            "name": "limit",
+            "in": "query",
+            "description": "How many items to return at one time (max 100)",
+            "required": false,
+            "schema": {
+              "type": "integer",
+              "format": "int32"
+            }
+          },
+          {
+            "x-security-type": [2,"pii"],
+            "name": "phone",
+            "in": "query",
+            "description": "Phone number",
+            "required": false,
+            "schema": {
+              "type": "string"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "200 response",
+            "content": {
+              "application/json": {
+                "examples": {
+                  "foo": {
+                    "value": {
+                      "versions": [
+                        {
+                          "status": "CURRENT",
+                          "updated": "2011-01-21T11:33:21Z",
+                          "id": "v2.0",
+                          "links": [
+                            {
+                              "href": "http://127.0.0.1:8774/v2/",
+                              "rel": "self"
+                            }
+                          ]
+                        },
+                        {
+                          "status": "EXPERIMENTAL",
+                          "updated": "2013-07-23T11:33:21Z",
+                          "id": "v3.0",
+                          "links": [
+                            {
+                              "href": "http://127.0.0.1:8774/v3/",
+                              "rel": "self"
+                            }
+                          ]
+                        }
+                      ]
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/oas-cli/src/test/resources/Valid_OAS_Invalid_Scope.json b/oas-cli/src/test/resources/Valid_OAS_Invalid_Scope.json
new file mode 100644
index 0000000..f237d22
--- /dev/null
+++ b/oas-cli/src/test/resources/Valid_OAS_Invalid_Scope.json
@@ -0,0 +1,82 @@
+{
+  "openapi": "3.0.0",
+  "info": {
+    "title": "Simple API overview",
+    "version": "2.0.0"
+  },
+  "x-security-type-definitions": {
+  	"inherits-from": ["ns"]
+  },
+  "paths": {
+    "/user": {
+      "x-security-allow": ["pii"],
+      "get": {
+        "operationId": "listVersionsv2",
+        "summary": "List API versions",
+        "x-custom-ext": ["data"],
+        "parameters": [
+          {
+            "x-security-type": ["ns", 1],
+            "name": "limit",
+            "in": "query",
+            "description": "How many items to return at one time (max 100)",
+            "required": false,
+            "schema": {
+              "type": "integer",
+              "format": "int32"
+            }
+          },
+          {
+            "x-security-type": [2, "pii"],
+            "name": "phone",
+            "in": "query",
+            "description": "Phone number",
+            "required": false,
+            "schema": {
+              "type": "string"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "200 response",
+            "content": {
+              "application/json": {
+                "examples": {
+                  "foo": {
+                    "value": {
+                      "versions": [
+                        {
+                          "status": "CURRENT",
+                          "updated": "2011-01-21T11:33:21Z",
+                          "id": "v2.0",
+                          "links": [
+                            {
+                              "href": "http://127.0.0.1:8774/v2/",
+                              "rel": "self"
+                            }
+                          ]
+                        },
+                        {
+                          "status": "EXPERIMENTAL",
+                          "updated": "2013-07-23T11:33:21Z",
+                          "id": "v3.0",
+                          "links": [
+                            {
+                              "href": "http://127.0.0.1:8774/v3/",
+                              "rel": "self"
+                            }
+                          ]
+                        }
+                      ]
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/oas-cli/src/test/resources/Valid_OAS_Valid_Extensions.json b/oas-cli/src/test/resources/Valid_OAS_Valid_Extensions.json
new file mode 100644
index 0000000..34c5b96
--- /dev/null
+++ b/oas-cli/src/test/resources/Valid_OAS_Valid_Extensions.json
@@ -0,0 +1,83 @@
+{
+  "openapi": "3.0.0",
+  "info": {
+    "title": "Simple API overview",
+    "version": "2.0.0"
+  },
+  "x-security-type-definitions": [
+    {
+  	"type": "pii",
+  	"inherits-from": ["ns"]
+  }],
+  "paths": {
+    "/user": {
+      "get": {
+      	"x-security-allow": ["pii"],
+        "operationId": "listVersionsv2",
+        "summary": "List API versions",
+        "parameters": [
+          {
+            "x-security-type": ["ns"],
+            "name": "limit",
+            "in": "query",
+            "description": "How many items to return at one time (max 100)",
+            "required": false,
+            "schema": {
+              "type": "integer",
+              "format": "int32"
+            }
+          },
+          {
+            "x-security-type": ["pii"],
+            "name": "phone",
+            "in": "query",
+            "description": "Phone number",
+            "required": false,
+            "schema": {
+              "type": "string"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "200 response",
+            "content": {
+              "application/json": {
+                "examples": {
+                  "foo": {
+                    "value": {
+                      "versions": [
+                        {
+                          "status": "CURRENT",
+                          "updated": "2011-01-21T11:33:21Z",
+                          "id": "v2.0",
+                          "links": [
+                            {
+                              "href": "http://127.0.0.1:8774/v2/",
+                              "rel": "self"
+                            }
+                          ]
+                        },
+                        {
+                          "status": "EXPERIMENTAL",
+                          "updated": "2013-07-23T11:33:21Z",
+                          "id": "v3.0",
+                          "links": [
+                            {
+                              "href": "http://127.0.0.1:8774/v3/",
+                              "rel": "self"
+                            }
+                          ]
+                        }
+                      ]
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/oas-core/build.gradle b/oas-core/build.gradle
index 908c2e3..eb00aba 100644
--- a/oas-core/build.gradle
+++ b/oas-core/build.gradle
@@ -1,6 +1,8 @@
+apply plugin: 'java-library'
+
 dependencies {
-    implementation "org.openapi4j:openapi-parser:${libVersions.openapiParser}"
-    implementation "org.openapi4j:openapi-core:${libVersions.openapiCore}"
+    api "org.openapi4j:openapi-parser:${libVersions.openapiParser}"
+    api "org.openapi4j:openapi-core:${libVersions.openapiCore}"
     implementation "com.google.auto.value:auto-value-annotations:${libVersions.autoValue}"
     implementation "com.google.inject.extensions:guice-assistedinject:${libVersions.guice}"
     implementation "com.networknt:json-schema-validator:${libVersions.jsonSchemaValidator}"
@@ -11,3 +13,7 @@
     annotationProcessor "com.google.auto.value:auto-value:${libVersions.autoValue}"
 }
 
+javadoc {
+  title = "OpenApi3 Specification Security Library"
+}
+
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/BaseExtensionValidationIntegrator.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/BaseExtensionValidationIntegrator.java
deleted file mode 100644
index 1fc861e..0000000
--- a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/BaseExtensionValidationIntegrator.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.apigee.security.oas.extendedvalidator;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import java.util.Map;
-import java.util.Optional;
-import org.openapi4j.parser.model.OpenApiSchema;
-
-final class BaseExtensionValidationIntegrator extends ExtensionValidationIntegrator {
-
-  // TODO(b/161441872): Inject ExtensionValidationIntegrator member dependencies.
-
-  @Override
-  public void validateExtensions(
-      ImmutableMap<String, Object> extensions,
-      ImmutableList<Map.Entry<Class<? extends OpenApiSchema>, Optional<String>>>
-          updatedTraversalPath) {
-    // TODO(b/161441872): Add validation logic here.
-  }
-}
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtensionValidationIntegrator.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtensionValidationIntegrator.java
deleted file mode 100644
index c38c062..0000000
--- a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtensionValidationIntegrator.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.apigee.security.oas.extendedvalidator;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import java.util.Map;
-import java.util.Optional;
-import org.openapi4j.parser.model.OpenApiSchema;
-
-/** Carries out the process of validating ApiSecurityTool's OpenApi extensions. */
-public abstract class ExtensionValidationIntegrator {
-
-  // TODO(b/161441872): Add needed member dependencies to be injected by child classes.
-
-  /**
-   * Validates extensions from a mapping of extension {@linkplain String name} to extension
-   * {@linkplain Object content}.
-   */
-  abstract void validateExtensions(
-      ImmutableMap<String, Object> extensions,
-      ImmutableList<Map.Entry<Class<? extends OpenApiSchema>, Optional<String>>>
-          updatedTraversalPath);
-}
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtensionValidationMessage.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtensionValidationMessage.java
index 387da6c..505b982 100644
--- a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtensionValidationMessage.java
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/ExtensionValidationMessage.java
@@ -4,14 +4,14 @@
 
 /** Holds information about an error occurred during validation. */
 @AutoValue
-abstract class ExtensionValidationMessage {
+public abstract class ExtensionValidationMessage {
 
   static Builder builder() {
     return new AutoValue_ExtensionValidationMessage.Builder();
   }
 
   @AutoValue.Builder
-  abstract static class Builder {
+  public abstract static class Builder {
     abstract Builder setType(String value);
 
     abstract Builder setMessage(String value);
@@ -21,9 +21,9 @@
     abstract ExtensionValidationMessage build();
   }
 
-  abstract String type();
+  public abstract String type();
 
-  abstract String message();
+  public abstract String message();
 
-  abstract String path();
+  public abstract String path();
 }
diff --git a/oas-test/src/main/java/com/apigee/security/oas/SlowTests.java b/oas-test/src/main/java/com/apigee/security/oas/SlowTests.java
new file mode 100644
index 0000000..5e6aa0f
--- /dev/null
+++ b/oas-test/src/main/java/com/apigee/security/oas/SlowTests.java
@@ -0,0 +1,4 @@
+package com.apigee.security.oas;
+
+/** {@link org.junit.experimental.categories.Category Category} marker interface for categorizing slow integration tests. */
+public interface SlowTests {}