Refactor tests (#1300)

This commit is contained in:
Stian Thorgersen 2021-11-12 12:34:19 +01:00 committed by GitHub
parent d55ecae2b2
commit 408d2fad10
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 288 additions and 365 deletions

View file

@ -0,0 +1,39 @@
# This workflow will build a Java project with Maven
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
name: Test External Links
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
schedule:
- cron: '0 5 * * *'
jobs:
test:
name: Keycloak documentation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Build
run: mvn install -B -DskipTests
- name: Test
run: mvn test -B -pl tests -Dtest=ExternalLinksTest
test-product:
name: Product documentation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Build
run: mvn install -B -Dproduct
- name: Test
run: mvn test -B -Dproduct -pl tests -Dtest=ExternalLinksTest

View file

@ -1,7 +1,7 @@
# This workflow will build a Java project with Maven # This workflow will build a Java project with Maven
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
name: Test name: Test Guides
on: on:
push: push:
@ -11,7 +11,7 @@ on:
jobs: jobs:
build: build:
name: Build and test Keycloak documentation name: Keycloak documentation
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@ -19,10 +19,12 @@ jobs:
uses: actions/setup-java@v1 uses: actions/setup-java@v1
with: with:
java-version: 1.8 java-version: 1.8
- name: Build with Maven - name: Build
run: mvn package -B run: mvn install -B -DskipTests
- name: Test
run: mvn test -B -pl tests -Dtest=!ExternalLinksTest
build-product: build-product:
name: Build and test Product documentation name: Product documentation
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@ -30,5 +32,7 @@ jobs:
uses: actions/setup-java@v1 uses: actions/setup-java@v1
with: with:
java-version: 1.8 java-version: 1.8
- name: Build with Maven - name: Build
run: mvn package -B -Dproduct run: mvn install -B -Dproduct
- name: Test
run: mvn test -B -Dproduct -pl tests -Dtest=!ExternalLinksTest

View file

@ -21,6 +21,7 @@
<version.compiler.plugin>3.6.1</version.compiler.plugin> <version.compiler.plugin>3.6.1</version.compiler.plugin>
<version.jar.plugin>3.0.2</version.jar.plugin> <version.jar.plugin>3.0.2</version.jar.plugin>
<version.install.plugin>2.5.2</version.install.plugin> <version.install.plugin>2.5.2</version.install.plugin>
<version.surefire.plugin>2.22.2</version.surefire.plugin>
<maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.source>1.8</maven.compiler.source>
@ -129,6 +130,11 @@
<artifactId>maven-dependency-plugin</artifactId> <artifactId>maven-dependency-plugin</artifactId>
<version>${version.plugin.dependency}</version> <version>${version.plugin.dependency}</version>
</plugin> </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${version.surefire.plugin}</version>
</plugin>
<plugin> <plugin>
<groupId>org.keycloak.documentation</groupId> <groupId>org.keycloak.documentation</groupId>
<artifactId>header-maven-plugin</artifactId> <artifactId>header-maven-plugin</artifactId>

View file

@ -83,9 +83,9 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>org.junit.jupiter</groupId>
<artifactId>junit</artifactId> <artifactId>junit-jupiter</artifactId>
<version>4.13.1</version> <version>5.8.1</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>

View file

@ -1,124 +0,0 @@
package org.keycloak.documentation.test;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.keycloak.documentation.test.utils.DocUtils;
import org.keycloak.documentation.test.utils.LinkUtils;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public abstract class AbstractDocsTest {
private static final Logger log = LogManager.getLogger(AbstractDocsTest.class);
protected static final Config config = new Config();
protected static LinkUtils linkUtils;
protected static String body;
protected static Boolean verbose = System.getProperties().containsKey("verbose") ? true : false;
protected DocUtils utils = new DocUtils();
protected File guideDir;
protected String guideUrl;
@BeforeClass
public static void beforeClass() {
linkUtils = new LinkUtils(config, verbose);
}
@Before
public void before() throws IOException {
guideDir = config.getGuideDir(getGuideDirName());
guideUrl = config.getGuideBaseUrl() + "/" + config.getGuideUrlFragment(getGuideDirName()) + "/";
if (body == null) {
if (config.isLoadFromFiles()) {
File htmlFile = config.getGuideHtmlFile(getGuideDirName());
body = utils.readBody(htmlFile);
} else {
log.info("Loading guide from '" + guideUrl);
body = utils.readBody(new URL(guideUrl));
}
body = rewriteLinksToGuides(body);
}
}
@AfterClass
public static void afterClass() {
linkUtils.close();
body = null;
}
public abstract String getGuideDirName();
@Test
public void checkVariables() {
Set<String> missingVariables = utils.findMissingVariables(body, config.getIgnoredVariables());
checkFailures("Variables not found ", missingVariables);
}
@Test
public void checkIncludes() {
Set<String> missingIncludes = utils.findMissingIncludes(body);
checkFailures("Includes not found", missingIncludes);
}
@Test
public void checkImages() {
Set<String> failures = linkUtils.findInvalidImages(body, guideDir, guideUrl);
checkFailures("Images not found", failures);
}
@Test
public void checkInternalAnchors() {
Set<String> invalidInternalAnchors = linkUtils.findInvalidInternalAnchors(body);
checkFailures("Internal anchors not found", invalidInternalAnchors);
}
@Test
public void checkExternalLinks() throws IOException {
List<LinkUtils.InvalidLink> invalidLinks = linkUtils.findInvalidLinks(body, config.getIgnoredLinks(), config.getIgnoredLinkRedirects());
if (!invalidLinks.isEmpty()) {
Set<String> failures = new HashSet<>();
for (LinkUtils.InvalidLink l : invalidLinks) {
failures.add(l.getLink() + " (" + l.getError() + ")");
}
throw new Failures("Invalid links", failures);
}
}
private void checkFailures(String message, Set<String> failures) {
if (!failures.isEmpty()) {
throw new Failures(message, failures);
}
}
private String rewriteLinksToGuides(String body) throws MalformedURLException {
if (config.isLoadFromFiles()) {
for (Map.Entry<String, String> e : config.getGuideFragmentToDir().entrySet()) {
String originalUrl = config.getDocBaseUrl() + "/" + e.getKey() + "/";
String replacementUrl = config.getGuideHtmlFile(e.getValue()).toURI().toURL().toString();
body = body.replace("href=\"" + originalUrl, "href=\"" + replacementUrl);
}
} else {
body = body.replace("href=\"" + config.getDocBaseUrl(), "href=\"" + config.getGuideBaseUrl());
}
return body;
}
}

View file

@ -1,10 +0,0 @@
package org.keycloak.documentation.test;
public class AuthorizationServicesTest extends AbstractDocsTest {
@Override
public String getGuideDirName() {
return "authorization_services";
}
}

View file

@ -18,6 +18,8 @@ public class Config {
private static final Logger log = LogManager.getLogger(Config.class); private static final Logger log = LogManager.getLogger(Config.class);
protected static final Config instance = new Config();
private File docsRootDir; private File docsRootDir;
private List<String> ignoredLinkRedirects; private List<String> ignoredLinkRedirects;
@ -34,7 +36,11 @@ public class Config {
private Map<String, String> guideDirToFragment; private Map<String, String> guideDirToFragment;
private Map<String, String> guideFragmentToDir; private Map<String, String> guideFragmentToDir;
public Config() { public static Config getInstance() {
return instance;
}
private Config() {
docsRootDir = findDocsRoot(); docsRootDir = findDocsRoot();
ignoredLinkRedirects = loadConfig("/ignored-link-redirects"); ignoredLinkRedirects = loadConfig("/ignored-link-redirects");
ignoredVariables = loadConfig("/ignored-variables"); ignoredVariables = loadConfig("/ignored-variables");
@ -171,7 +177,7 @@ public class Config {
private List<String> loadConfig(String resource) { private List<String> loadConfig(String resource) {
try { try {
return IOUtils.readLines(AbstractDocsTest.class.getResourceAsStream(resource), "utf-8"); return IOUtils.readLines(Config.class.getResourceAsStream(resource), "utf-8");
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@ -179,7 +185,7 @@ public class Config {
private Map<String, String> loadConfigMap(String resource) { private Map<String, String> loadConfigMap(String resource) {
try { try {
List<String> lines = IOUtils.readLines(AbstractDocsTest.class.getResourceAsStream(resource), "utf-8"); List<String> lines = IOUtils.readLines(Config.class.getResourceAsStream(resource), "utf-8");
Map<String, String> m = new HashMap<>(); Map<String, String> m = new HashMap<>();
for (String l : lines) { for (String l : lines) {
String[] s = l.split("="); String[] s = l.split("=");

View file

@ -0,0 +1,30 @@
package org.keycloak.documentation.test;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.keycloak.documentation.test.utils.LinkUtils;
import java.io.IOException;
import java.util.List;
public class ExternalLinksTest {
@ParameterizedTest
@MethodSource("org.keycloak.documentation.test.Guides#guides()")
public void checkExternalLinks(String guideName) throws IOException {
Guide guide = new Guide(guideName);
List<LinkUtils.InvalidLink> invalidLinks = LinkUtils.getInstance().findInvalidLinks(guide);
if (!invalidLinks.isEmpty()) {
StringBuilder sb = new StringBuilder();
sb.append("Broken links (" + invalidLinks.size() + "):");
for (LinkUtils.InvalidLink l : invalidLinks) {
sb.append("\n\t\t- " + l.getLink() + " (" + l.getError() + ")");
}
Assertions.fail(sb.toString());
}
}
}

View file

@ -1,35 +0,0 @@
package org.keycloak.documentation.test;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.Set;
public class Failures extends AssertionError {
private Set<String> failures;
public Failures(String error, Set<String> failures) {
super(error);
this.failures = failures;
}
@Override
public void printStackTrace() {
printStackTrace(System.out);
}
@Override
public void printStackTrace(PrintStream s) {
for (String f : failures) {
s.println("* " + f);
}
}
@Override
public void printStackTrace(PrintWriter s) {
for (String f : failures) {
s.println("* " + f);
}
}
}

View file

@ -1,18 +0,0 @@
package org.keycloak.documentation.test;
import org.junit.Assume;
import org.junit.BeforeClass;
public class GettingStartedTest extends AbstractDocsTest {
@BeforeClass
public static void skipOnCommunity() {
Assume.assumeTrue("Skipping product OpenShift guide testing in community", System.getProperties().containsKey("product"));
}
@Override
public String getGuideDirName() {
return "getting_started";
}
}

View file

@ -0,0 +1,73 @@
package org.keycloak.documentation.test;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.keycloak.documentation.test.utils.DocUtils;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;
public class Guide {
private static final Logger log = LogManager.getLogger(Guide.class);
protected DocUtils utils = new DocUtils();
private String guide;
private String body;
private File guideDir;
private String guideUrl;
public Guide(String guide) throws IOException {
this.guide = guide;
Config config = Config.getInstance();
guideDir = config.getGuideDir(guide);
guideUrl = config.getGuideBaseUrl() + "/" + config.getGuideUrlFragment(guide) + "/";
if (body == null) {
if (config.isLoadFromFiles()) {
File htmlFile = config.getGuideHtmlFile(guide);
body = utils.readBody(htmlFile);
} else {
log.info("Loading guide from '" + guideUrl);
body = utils.readBody(new URL(guideUrl));
}
body = rewriteLinksToGuides(config, body);
}
}
public String getBody() {
return body;
}
public File getDir() {
return guideDir;
}
public String getUrl() {
return guideUrl;
}
private String rewriteLinksToGuides(Config config, String body) throws MalformedURLException {
if (config.isLoadFromFiles()) {
for (Map.Entry<String, String> e : config.getGuideFragmentToDir().entrySet()) {
String originalUrl = config.getDocBaseUrl() + "/" + e.getKey() + "/";
String replacementUrl = config.getGuideHtmlFile(e.getValue()).toURI().toURL().toString();
body = body.replace("href=\"" + originalUrl, "href=\"" + replacementUrl);
}
} else {
body = body.replace("href=\"" + config.getDocBaseUrl(), "href=\"" + config.getGuideBaseUrl());
}
return body;
}
}

View file

@ -0,0 +1,54 @@
package org.keycloak.documentation.test;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.keycloak.documentation.test.utils.DocUtils;
import org.keycloak.documentation.test.utils.LinkUtils;
import java.io.IOException;
import java.util.Set;
public class GuideTest {
@ParameterizedTest
@MethodSource("org.keycloak.documentation.test.Guides#guides()")
public void checkVariables(String guideName) throws IOException {
Set<String> missingVariables = DocUtils.findMissingVariables(new Guide(guideName));
checkFailures("Variables not found ", missingVariables);
}
@ParameterizedTest
@MethodSource("org.keycloak.documentation.test.Guides#guides()")
public void checkIncludes(String guideName) throws IOException {
Set<String> missingIncludes = DocUtils.findMissingIncludes(new Guide(guideName));
checkFailures("Includes not found", missingIncludes);
}
@ParameterizedTest
@MethodSource("org.keycloak.documentation.test.Guides#guides()")
public void checkImages(String guideName) throws IOException {
Set<String> failures = LinkUtils.getInstance().findInvalidImages(new Guide(guideName));
checkFailures("Images not found", failures);
}
@ParameterizedTest
@MethodSource("org.keycloak.documentation.test.Guides#guides()")
public void checkInternalAnchors(String guideName) throws IOException {
Set<String> invalidInternalAnchors = LinkUtils.getInstance().findInvalidInternalAnchors(new Guide(guideName));
checkFailures("Internal anchors not found", invalidInternalAnchors);
}
private void checkFailures(String message, Set<String> failures) {
if (!failures.isEmpty()) {
StringBuilder sb = new StringBuilder();
sb.append(message + " (" + failures.size() + "):");
for (String f : failures) {
sb.append("\n\t\t- " + f);
}
Assertions.fail(sb.toString());
}
}
}

View file

@ -0,0 +1,33 @@
package org.keycloak.documentation.test;
import java.util.LinkedList;
import java.util.List;
public class Guides {
private static String[] guides;
static {
boolean product = System.getProperties().containsKey("product");
List<String> g = new LinkedList<>();
g.add("authorization_services");
g.add("release_notes");
g.add("securing_apps");
g.add("server_admin");
g.add("server_development");
g.add("server_installation");
g.add("upgrading");
if (product) {
g.add("getting_started");
g.add("openshift");
}
guides = g.toArray(new String[g.size()]);
}
public static String[] guides() {
return guides;
}
}

View file

@ -1,18 +0,0 @@
package org.keycloak.documentation.test;
import org.junit.Assume;
import org.junit.BeforeClass;
public class OpenShiftTest extends AbstractDocsTest {
@BeforeClass
public static void skipOnCommunity() {
Assume.assumeTrue("Skipping product OpenShift guide testing in community", System.getProperties().containsKey("product"));
}
@Override
public String getGuideDirName() {
return "openshift";
}
}

View file

@ -1,10 +0,0 @@
package org.keycloak.documentation.test;
public class ReleaseNotesTest extends AbstractDocsTest {
@Override
public String getGuideDirName() {
return "release_notes";
}
}

View file

@ -1,10 +0,0 @@
package org.keycloak.documentation.test;
public class SecuringAppsTest extends AbstractDocsTest {
@Override
public String getGuideDirName() {
return "securing_apps";
}
}

View file

@ -1,10 +0,0 @@
package org.keycloak.documentation.test;
public class ServerAdminTest extends AbstractDocsTest {
@Override
public String getGuideDirName() {
return "server_admin";
}
}

View file

@ -1,10 +0,0 @@
package org.keycloak.documentation.test;
public class ServerDeveloperTest extends AbstractDocsTest {
@Override
public String getGuideDirName() {
return "server_development";
}
}

View file

@ -1,10 +0,0 @@
package org.keycloak.documentation.test;
public class ServerInstallationTest extends AbstractDocsTest {
@Override
public String getGuideDirName() {
return "server_installation";
}
}

View file

@ -1,10 +0,0 @@
package org.keycloak.documentation.test;
public class UpgradingTest extends AbstractDocsTest {
@Override
public String getGuideDirName() {
return "upgrading";
}
}

View file

@ -2,6 +2,8 @@ package org.keycloak.documentation.test.utils;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.keycloak.documentation.test.Config;
import org.keycloak.documentation.test.Guide;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@ -16,7 +18,7 @@ import java.util.regex.Pattern;
public class DocUtils { public class DocUtils {
public String readBody(File htmlFile) throws IOException { public static String readBody(File htmlFile) throws IOException {
String s = FileUtils.readFileToString(htmlFile, "utf-8"); String s = FileUtils.readFileToString(htmlFile, "utf-8");
Pattern p = Pattern.compile("<body.*?>(.*?)</body>.*?",Pattern.DOTALL); Pattern p = Pattern.compile("<body.*?>(.*?)</body>.*?",Pattern.DOTALL);
@ -26,7 +28,7 @@ public class DocUtils {
return m.group(1); return m.group(1);
} }
public String readBody(URL url) throws IOException { public static String readBody(URL url) throws IOException {
HttpURLConnection connection = null; HttpURLConnection connection = null;
try { try {
@ -60,10 +62,11 @@ public class DocUtils {
} }
} }
public Set<String> findMissingVariables(String body, List<String> ignoredVariables) { public static Set<String> findMissingVariables(Guide guide) {
List<String> ignoredVariables = Config.getInstance().getIgnoredVariables();
Set<String> missingVariables = new HashSet<>(); Set<String> missingVariables = new HashSet<>();
Pattern p = Pattern.compile("[^$/=\n]\\{([^ }\"]*)}"); Pattern p = Pattern.compile("[^$/=\n]\\{([^ }\"]*)}");
Matcher m = p.matcher(body); Matcher m = p.matcher(guide.getBody());
while (m.find()) { while (m.find()) {
String key = m.group(1); String key = m.group(1);
if (!key.isEmpty() && !ignoredVariables.contains(key)) { if (!key.isEmpty() && !ignoredVariables.contains(key)) {
@ -73,10 +76,10 @@ public class DocUtils {
return missingVariables; return missingVariables;
} }
public Set<String> findMissingIncludes(String body) { public static Set<String> findMissingIncludes(Guide guide) {
Set<String> missingIncludes = new HashSet<>(); Set<String> missingIncludes = new HashSet<>();
Pattern p = Pattern.compile("Unresolved directive.*"); Pattern p = Pattern.compile("Unresolved directive.*");
Matcher m = p.matcher(body); Matcher m = p.matcher(guide.getBody());
if (m.find()) { if (m.find()) {
missingIncludes.add(m.group()); missingIncludes.add(m.group());
} }

View file

@ -8,8 +8,6 @@ import org.apache.hc.client5.http.impl.DefaultHttpRequestRetryStrategy;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.HttpStatus;
@ -19,11 +17,6 @@ import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.util.TimeValue; import org.apache.hc.core5.util.TimeValue;
import java.io.IOException; import java.io.IOException;
import java.security.cert.X509Certificate;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.X509TrustManager;
public class HttpUtils { public class HttpUtils {
@ -86,7 +79,6 @@ public class HttpUtils {
try { try {
client.execute(method, responseHandler); client.execute(method, responseHandler);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace();
response.setError("exception " + e.getMessage()); response.setError("exception " + e.getMessage());
response.setSuccess(false); response.setSuccess(false);
} }
@ -104,50 +96,11 @@ public class HttpUtils {
.disableRedirectHandling() .disableRedirectHandling()
.setConnectionManager( .setConnectionManager(
PoolingHttpClientConnectionManagerBuilder.create() PoolingHttpClientConnectionManagerBuilder.create()
.setSSLSocketFactory(new NoopSSLConnectionSocketFactory())
.build() .build()
) )
.build(); .build();
} }
private static class NoopSSLConnectionSocketFactory extends SSLConnectionSocketFactory {
private static SSLContext sslContext;
static {
try {
sslContext = SSLContext.getInstance("TLS");
sslContext.init(
null,
new X509TrustManager[] {
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}
},
null
);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public NoopSSLConnectionSocketFactory() {
super(sslContext, new NoopHostnameVerifier());
}
@Override
protected void verifySession(String hostname, SSLSession sslSession) throws SSLException {
// no-op
}
}
public static class Response { public static class Response {
private boolean success; private boolean success;

View file

@ -4,6 +4,7 @@ import org.apache.commons.io.FileUtils;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.keycloak.documentation.test.Config; import org.keycloak.documentation.test.Config;
import org.keycloak.documentation.test.Guide;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
@ -18,16 +19,18 @@ public class LinkUtils {
private static final Logger logger = LogManager.getLogger(LinkUtils.class); private static final Logger logger = LogManager.getLogger(LinkUtils.class);
private static final LinkUtils instance = new LinkUtils();
private HttpUtils http = new HttpUtils(); private HttpUtils http = new HttpUtils();
private Config config;
private File verifiedLinksCacheFile; private File verifiedLinksCacheFile;
private boolean verbose;
private Map<String, Long> verifiedLinks; private Map<String, Long> verifiedLinks;
public LinkUtils(Config config, boolean verbose) { public static LinkUtils getInstance() {
this.config = config; return instance;
this.verifiedLinksCacheFile = config.getVerifiedLinksCache(); }
this.verbose = verbose;
private LinkUtils() {
this.verifiedLinksCacheFile = Config.getInstance().getVerifiedLinksCache();
this.verifiedLinks = loadCheckedLinksCache(); this.verifiedLinks = loadCheckedLinksCache();
} }
@ -35,15 +38,15 @@ public class LinkUtils {
saveCheckedLinksCache(); saveCheckedLinksCache();
} }
public Set<String> findInvalidInternalAnchors(String body) { public Set<String> findInvalidInternalAnchors(Guide guide) {
Set<String> invalidInternalAnchors = new HashSet<>(); Set<String> invalidInternalAnchors = new HashSet<>();
Pattern p = Pattern.compile("<a href=\"([^ \"]*)[^>]*\">"); Pattern p = Pattern.compile("<a href=\"([^ \"]*)[^>]*\">");
Matcher m = p.matcher(body); Matcher m = p.matcher(guide.getBody());
while (m.find()) { while (m.find()) {
String link = m.group(1); String link = m.group(1);
if (link.startsWith("#")) { if (link.startsWith("#")) {
if (!body.contains("id=\"" + link.substring(1) + "\"")) { if (!guide.getBody().contains("id=\"" + link.substring(1) + "\"")) {
invalidInternalAnchors.add(link.substring(1)); invalidInternalAnchors.add(link.substring(1));
} }
} }
@ -51,14 +54,14 @@ public class LinkUtils {
return invalidInternalAnchors; return invalidInternalAnchors;
} }
public List<InvalidLink> findInvalidLinks(String body, List<String> ignoredLinks, List<String> ignoredLinkRedirects) throws IOException { public List<InvalidLink> findInvalidLinks(Guide guide) throws IOException {
List<InvalidLink> invalidLinks = new LinkedList<>(); List<InvalidLink> invalidLinks = new LinkedList<>();
Pattern p = Pattern.compile("<a href=\"([^ \"]*)[^>]*\">"); Pattern p = Pattern.compile("<a href=\"([^ \"]*)[^>]*\">");
Matcher m = p.matcher(body); Matcher m = p.matcher(guide.getBody());
while (m.find()) { while (m.find()) {
String link = m.group(1); String link = m.group(1);
if (verifyLink(link, ignoredLinks, invalidLinks)) { if (verifyLink(link, Config.getInstance().getIgnoredLinks(), invalidLinks)) {
if (link.startsWith("http")) { if (link.startsWith("http")) {
String anchor = link.contains("#") ? link.split("#")[1] : null; String anchor = link.contains("#") ? link.split("#")[1] : null;
String error = null; String error = null;
@ -66,7 +69,7 @@ public class LinkUtils {
HttpUtils.Response response = anchor != null ? http.load(link) : http.isValid(link); HttpUtils.Response response = anchor != null ? http.load(link) : http.isValid(link);
if (response.getRedirectLocation() != null) { if (response.getRedirectLocation() != null) {
if (!validRedirect(response.getRedirectLocation(), ignoredLinkRedirects)) { if (!validRedirect(response.getRedirectLocation(), Config.getInstance().getIgnoredLinkRedirects())) {
error = "invalid redirect to " + response.getRedirectLocation(); error = "invalid redirect to " + response.getRedirectLocation();
} }
} else if (response.isSuccess() && anchor != null) { } else if (response.isSuccess() && anchor != null) {
@ -79,16 +82,8 @@ public class LinkUtils {
if (error == null) { if (error == null) {
verifiedLinks.put(link, System.currentTimeMillis()); verifiedLinks.put(link, System.currentTimeMillis());
if (verbose) {
System.out.println("[OK] " + link);
}
} else { } else {
invalidLinks.add(new InvalidLink(link, error)); invalidLinks.add(new InvalidLink(link, error));
if (verbose) {
System.out.println("[BAD] " + link);
}
} }
} else if (link.startsWith("file")) { } else if (link.startsWith("file")) {
File f = new File(new URL(link).getFile()); File f = new File(new URL(link).getFile());
@ -110,36 +105,28 @@ public class LinkUtils {
return invalidLinks; return invalidLinks;
} }
public Set<String> findInvalidImages(String body, File guideDir, String guideUrl) { public Set<String> findInvalidImages(Guide guide) {
Set<String> missingImages = new HashSet<>(); Set<String> missingImages = new HashSet<>();
Pattern p = Pattern.compile("<img src=\"([^ \"]*)[^>]*\""); Pattern p = Pattern.compile("<img src=\"([^ \"]*)[^>]*\"");
Matcher m = p.matcher(body); Matcher m = p.matcher(guide.getBody());
while (m.find()) { while (m.find()) {
String image = m.group(1); String image = m.group(1);
if (config.isLoadFromFiles()) { if (Config.getInstance().isLoadFromFiles()) {
File f = new File(guideDir, image); File f = new File(guide.getDir(), image);
if (!f.isFile()) { if (!f.isFile()) {
missingImages.add(image); missingImages.add(image);
} }
} else { } else {
if (image.startsWith("./")) { if (image.startsWith("./")) {
image = guideUrl + image; image = guide.getUrl() + image;
} }
if (!verifiedLinks.containsKey(image)) { if (!verifiedLinks.containsKey(image)) {
boolean valid = http.isValid(image).isSuccess(); boolean valid = http.isValid(image).isSuccess();
if (valid) { if (valid) {
verifiedLinks.put(image, System.currentTimeMillis()); verifiedLinks.put(image, System.currentTimeMillis());
if (verbose) {
System.out.println("[OK] " + image);
}
} else { } else {
missingImages.add(image); missingImages.add(image);
if (verbose) {
System.out.println("[BAD] " + image);
}
} }
} }
} }