diff --git a/htmlSanityCheck-core/src/main/java/org/aim42/htmlsanitycheck/Configuration.java b/htmlSanityCheck-core/src/main/java/org/aim42/htmlsanitycheck/Configuration.java index f9d4a627..9de94a7a 100644 --- a/htmlSanityCheck-core/src/main/java/org/aim42/htmlsanitycheck/Configuration.java +++ b/htmlSanityCheck-core/src/main/java/org/aim42/htmlsanitycheck/Configuration.java @@ -51,7 +51,7 @@ public class Configuration { @Builder.Default Set excludes = new HashSet<>(); @Builder.Default - Set indexFilenames = defaultIndeFilenames(); + Set indexFilenames = defaultIndexFilenames(); /* * Explanation for configuring http status codes: @@ -77,13 +77,13 @@ public Configuration() { this.ignoreIPAddresses = false;// warning if numerical IP addresses this.ignoreLocalhost = false;// warning if localhost-URLs this.indexFilenames - = defaultIndeFilenames(); + = defaultIndexFilenames(); this.prefixOnlyHrefExtensions = Web.POSSIBLE_EXTENSIONS; this.checksToExecute = AllCheckers.CHECKER_CLASSES; } - private static Set defaultIndeFilenames() { + private static Set defaultIndexFilenames() { return Arrays.stream("index.html,index.htm".split(",")).collect(Collectors.toSet()); } diff --git a/htmlSanityCheck-gradle-plugin/README.adoc b/htmlSanityCheck-gradle-plugin/README.adoc index 66873cd8..3ac67528 100644 --- a/htmlSanityCheck-gradle-plugin/README.adoc +++ b/htmlSanityCheck-gradle-plugin/README.adoc @@ -92,7 +92,7 @@ Type: Directory. + Type: `org.gradle.api.file.FileCollection`. + -Default: All files in `+{sourceDir}+` whose names end with `.html`. +Default: All files in `+{sourceDir}+` whose names end with `.html` or `.htm`. `checkingResultsDir` (optional):: Directory where the checking results are written to. + diff --git a/htmlSanityCheck-gradle-plugin/src/main/groovy/org/aim42/htmlsanitycheck/gradle/HtmlSanityCheckTask.groovy b/htmlSanityCheck-gradle-plugin/src/main/groovy/org/aim42/htmlsanitycheck/gradle/HtmlSanityCheckTask.groovy index bdead7ac..5088bb31 100644 --- a/htmlSanityCheck-gradle-plugin/src/main/groovy/org/aim42/htmlsanitycheck/gradle/HtmlSanityCheckTask.groovy +++ b/htmlSanityCheck-gradle-plugin/src/main/groovy/org/aim42/htmlsanitycheck/gradle/HtmlSanityCheckTask.groovy @@ -117,6 +117,7 @@ class HtmlSanityCheckTask extends DefaultTask { if (sourceDocuments == null) { sourceDocuments = project.fileTree(sourceDir) sourceDocuments.include('**/*.html') + sourceDocuments.include('**/*.htm') } } diff --git a/htmlSanityCheck-gradle-plugin/src/test/groovy/org/aim42/htmlsanitycheck/gradle/HtmlSanityCheckTaskSpec.groovy b/htmlSanityCheck-gradle-plugin/src/test/groovy/org/aim42/htmlsanitycheck/gradle/HtmlSanityCheckTaskSpec.groovy index 82c569ec..84baa903 100644 --- a/htmlSanityCheck-gradle-plugin/src/test/groovy/org/aim42/htmlsanitycheck/gradle/HtmlSanityCheckTaskSpec.groovy +++ b/htmlSanityCheck-gradle-plugin/src/test/groovy/org/aim42/htmlsanitycheck/gradle/HtmlSanityCheckTaskSpec.groovy @@ -62,4 +62,32 @@ class HtmlSanityCheckTaskSpec extends HtmlSanityCheckBaseSpec { e.message.contains("Your build configuration included 'failOnErrors=true', and 1 error(s) were found on all checked pages.") } + def "should include .htm files when auto-populating sourceDocuments"() { + given: + // Create .htm file + def htmFile = new File(sourceDir, "document.htm") + htmFile << VALID_HTML + + // Create .html file + htmlFile << VALID_HTML + + // Create subdirectory with .htm file + def subDir = new File(sourceDir, "docs") + subDir.mkdirs() + def nestedHtmFile = new File(subDir, "nested.htm") + nestedHtmFile << VALID_HTML + + when: + task.setSourceDir(testProjectDir.root) + + then: + task.sourceDocuments != null + // Verify that sourceDocuments includes both .html and .htm files + def fileNames = task.sourceDocuments.files.collect { it.name } + fileNames.contains("test.html") + fileNames.contains("document.htm") + fileNames.contains("nested.htm") + task.sourceDocuments.files.size() == 3 + } + } \ No newline at end of file diff --git a/htmlSanityCheck-maven-plugin/README.adoc b/htmlSanityCheck-maven-plugin/README.adoc index 15ce403e..058d3336 100644 --- a/htmlSanityCheck-maven-plugin/README.adoc +++ b/htmlSanityCheck-maven-plugin/README.adoc @@ -41,7 +41,7 @@ Use the following snippet inside a Maven build file: - src/file-to-test.html <2> + src/file-to-test.html <2> src <3> @@ -79,7 +79,7 @@ Type: Directory. + Type: `org.maven.api.file.FileCollection`. + -Default: All files in `+{sourceDir}+` whose names end with `.html`. +Default: All files in `+{sourceDir}+` whose names end with `.html` or `.htm`. `checkingResultsDir` (optional):: Directory where the checking results are written to. + diff --git a/htmlSanityCheck-maven-plugin/src/main/java/org/aim42/htmlsanitycheck/maven/HtmlSanityCheckMojo.java b/htmlSanityCheck-maven-plugin/src/main/java/org/aim42/htmlsanitycheck/maven/HtmlSanityCheckMojo.java index 4461d283..d884b2f1 100644 --- a/htmlSanityCheck-maven-plugin/src/main/java/org/aim42/htmlsanitycheck/maven/HtmlSanityCheckMojo.java +++ b/htmlSanityCheck-maven-plugin/src/main/java/org/aim42/htmlsanitycheck/maven/HtmlSanityCheckMojo.java @@ -270,9 +270,16 @@ void logBuildParameter(Configuration myConfig) { } protected Configuration setupConfiguration() { + // Auto-populate sourceDocuments from sourceDir if not specified + // (same pattern as Gradle plugin and CLI) + Set documentsToCheck = sourceDocuments; + if (documentsToCheck == null && sourceDir != null) { + documentsToCheck = findHtmlFiles(sourceDir); + } + Configuration result = Configuration.builder() .excludes(excludes.stream().map(Pattern::compile).collect(Collectors.toSet())) - .sourceDocuments(sourceDocuments) + .sourceDocuments(documentsToCheck) .sourceDir(sourceDir) .checkingResultsDir(checkingResultsDir) .junitResultsDir(junitResultsDir) @@ -301,6 +308,33 @@ protected Configuration setupConfiguration() { return result; } + + /** + * Recursively finds all HTML files in the given directory. + * Searches for files with .html and .htm extensions. + * + * @param directory the directory to search + * @return set of HTML files found + */ + private Set findHtmlFiles(File directory) { + Set htmlFiles = new HashSet<>(); + if (directory == null || !directory.exists() || !directory.isDirectory()) { + return htmlFiles; + } + + File[] files = directory.listFiles(); + if (files != null) { + for (File file : files) { + if (file.isDirectory()) { + // Recursively search subdirectories + htmlFiles.addAll(findHtmlFiles(file)); + } else if (file.isFile() && (file.getName().endsWith(".html") || file.getName().endsWith(".htm"))) { + htmlFiles.add(file); + } + } + } + return htmlFiles; + } } /*======================================================================== diff --git a/htmlSanityCheck-maven-plugin/src/test/java/org/aim42/htmlsanitycheck/maven/HtmlSanityCheckMojoTest.java b/htmlSanityCheck-maven-plugin/src/test/java/org/aim42/htmlsanitycheck/maven/HtmlSanityCheckMojoTest.java index c708755a..e2af2944 100644 --- a/htmlSanityCheck-maven-plugin/src/test/java/org/aim42/htmlsanitycheck/maven/HtmlSanityCheckMojoTest.java +++ b/htmlSanityCheck-maven-plugin/src/test/java/org/aim42/htmlsanitycheck/maven/HtmlSanityCheckMojoTest.java @@ -36,6 +36,68 @@ void setupConfiguration() { Assertions.assertThat(config.getFailOnErrors()).isFalse(); } + @Test + void setupConfigurationWithHttpSuccessCodes() throws Exception { + // Create mojo with custom HTTP success codes + Set customSuccessCodes = new HashSet<>(); + customSuccessCodes.add(299); + + HtmlSanityCheckMojo mojo = new HtmlSanityCheckMojo(); + setField(mojo, "httpSuccessCodes", customSuccessCodes); + + Configuration config = mojo.setupConfiguration(); + + Assertions.assertThat(config).isNotNull(); + Assertions.assertThat(config.getHttpSuccessCodes()).contains(299); + } + + @Test + void setupConfigurationWithHttpErrorCodes() throws Exception { + // Create mojo with custom HTTP error codes + Set customErrorCodes = new HashSet<>(); + customErrorCodes.add(599); + + HtmlSanityCheckMojo mojo = new HtmlSanityCheckMojo(); + setField(mojo, "httpErrorCodes", customErrorCodes); + + Configuration config = mojo.setupConfiguration(); + + Assertions.assertThat(config).isNotNull(); + Assertions.assertThat(config.getHttpErrorCodes()).contains(599); + } + + @Test + void setupConfigurationWithHttpWarningCodes() throws Exception { + // Create mojo with custom HTTP warning codes + Set customWarningCodes = new HashSet<>(); + customWarningCodes.add(199); + + HtmlSanityCheckMojo mojo = new HtmlSanityCheckMojo(); + setField(mojo, "httpWarningCodes", customWarningCodes); + + Configuration config = mojo.setupConfiguration(); + + Assertions.assertThat(config).isNotNull(); + Assertions.assertThat(config.getHttpWarningCodes()).contains(199); + } + + @Test + void setupConfigurationWithEmptyHttpStatusCodesShouldNotOverride() throws Exception { + // Create mojo with empty HTTP status code sets (should not override defaults) + HtmlSanityCheckMojo mojo = new HtmlSanityCheckMojo(); + setField(mojo, "httpSuccessCodes", new HashSet()); + setField(mojo, "httpErrorCodes", new HashSet()); + setField(mojo, "httpWarningCodes", new HashSet()); + + Configuration config = mojo.setupConfiguration(); + + // Verify that default codes are still present (not overridden by empty sets) + Assertions.assertThat(config).isNotNull(); + Assertions.assertThat(config.getHttpSuccessCodes()).contains(200); // Default success code + Assertions.assertThat(config.getHttpErrorCodes()).contains(404); // Default error code + Assertions.assertThat(config.getHttpWarningCodes()).contains(301); // Default warning code (redirect) + } + @Test void logBuildParameter() { @@ -190,6 +252,188 @@ void execuuserte() throws IOException, MojoExecutionException { deleteDirectory(resultDir.toFile()); } + @Test + void executeWithOnlySourceDir_ShouldSucceed() throws IOException, MojoExecutionException { + // Setup: Create temp directories + Path junitDir = Files.createTempDirectory("MojoJunit"); + Path resultDir = Files.createTempDirectory("MojoResult"); + Path sourceDir = Files.createTempDirectory("MojoSource"); + + // Create HTML file in root of sourceDir + File rootHtmlFile = new File(sourceDir.toFile(), "root.html"); + Files.write(rootHtmlFile.toPath(), VALID_HTML.getBytes(StandardCharsets.UTF_8)); + + // Create subdirectory with another HTML file + File subDir = new File(sourceDir.toFile(), "subdir"); + boolean mkdirSuccess = subDir.mkdirs(); + Assertions.assertThat(mkdirSuccess).isTrue(); + File subHtmlFile = new File(subDir, "nested.html"); + Files.write(subHtmlFile.toPath(), VALID_HTML.getBytes(StandardCharsets.UTF_8)); + + // Create Mojo and set only sourceDir field (NOT sourceDocuments) + // This simulates a Maven pom.xml with only configured + HtmlSanityCheckMojo mojo = new TestableHtmlSanityCheckMojo( + sourceDir.toFile(), + null, // sourceDocuments explicitly NOT set + resultDir.toFile(), + junitDir.toFile() + ); + + // This should succeed - setupConfiguration() will auto-populate sourceDocuments + mojo.execute(); + + // Clean up + deleteDirectory(sourceDir.toFile()); + deleteDirectory(junitDir.toFile()); + deleteDirectory(resultDir.toFile()); + } + + @Test + void findHtmlFilesWithNullDirectory() throws Exception { + HtmlSanityCheckMojo mojo = new HtmlSanityCheckMojo(); + setField(mojo, "sourceDir", null); + setField(mojo, "sourceDocuments", null); + + Configuration config = mojo.setupConfiguration(); + + // When both sourceDir and sourceDocuments are null, the config should have null sourceDocuments + // (This will be caught by validation) + Assertions.assertThat(config.getSourceDocuments()).isNull(); + } + + @Test + void findHtmlFilesWithNonExistentDirectory() throws Exception { + Path nonExistentDir = java.nio.file.Paths.get("/tmp/this-directory-does-not-exist-" + System.currentTimeMillis()); + HtmlSanityCheckMojo mojo = new HtmlSanityCheckMojo(); + setField(mojo, "sourceDir", nonExistentDir.toFile()); + setField(mojo, "sourceDocuments", null); + + Configuration config = mojo.setupConfiguration(); + + // Should return empty set when directory doesn't exist + Assertions.assertThat(config.getSourceDocuments()).isEmpty(); + } + + @Test + void findHtmlFilesWithEmptyDirectory() throws Exception { + Path emptyDir = Files.createTempDirectory("MojoEmpty"); + + HtmlSanityCheckMojo mojo = new HtmlSanityCheckMojo(); + setField(mojo, "sourceDir", emptyDir.toFile()); + setField(mojo, "sourceDocuments", null); + + Configuration config = mojo.setupConfiguration(); + + // Should return empty set when directory is empty + Assertions.assertThat(config.getSourceDocuments()).isEmpty(); + + // Clean up + Files.deleteIfExists(emptyDir); + } + + @Test + void findHtmlFilesIgnoresNonHtmlFiles() throws Exception { + Path sourceDir = Files.createTempDirectory("MojoSource"); + + // Create various non-HTML files + File txtFile = new File(sourceDir.toFile(), "readme.txt"); + Files.write(txtFile.toPath(), "Text content".getBytes(StandardCharsets.UTF_8)); + + File pdfFile = new File(sourceDir.toFile(), "document.pdf"); + Files.write(pdfFile.toPath(), "PDF content".getBytes(StandardCharsets.UTF_8)); + + File xmlFile = new File(sourceDir.toFile(), "config.xml"); + Files.write(xmlFile.toPath(), "".getBytes(StandardCharsets.UTF_8)); + + // Create one HTML file + File htmlFile = new File(sourceDir.toFile(), "page.html"); + Files.write(htmlFile.toPath(), VALID_HTML.getBytes(StandardCharsets.UTF_8)); + + HtmlSanityCheckMojo mojo = new HtmlSanityCheckMojo(); + setField(mojo, "sourceDir", sourceDir.toFile()); + setField(mojo, "sourceDocuments", null); + + Configuration config = mojo.setupConfiguration(); + + // Should only find the HTML file + Assertions.assertThat(config.getSourceDocuments()).hasSize(1); + Assertions.assertThat(config.getSourceDocuments()) + .extracting(File::getName) + .containsExactly("page.html"); + + // Clean up + deleteDirectory(sourceDir.toFile()); + } + + @Test + void executeWithOnlySourceDir_ShouldIncludeHtmFiles() throws IOException, MojoExecutionException { + // Setup: Create temp directories + Path junitDir = Files.createTempDirectory("MojoJunit"); + Path resultDir = Files.createTempDirectory("MojoResult"); + Path sourceDir = Files.createTempDirectory("MojoSource"); + + // Create .htm file in root + File rootHtmFile = new File(sourceDir.toFile(), "document.htm"); + Files.write(rootHtmFile.toPath(), VALID_HTML.getBytes(StandardCharsets.UTF_8)); + + // Create .html file for comparison + File rootHtmlFile = new File(sourceDir.toFile(), "page.html"); + Files.write(rootHtmlFile.toPath(), VALID_HTML.getBytes(StandardCharsets.UTF_8)); + + // Create subdirectory with .htm file + File subDir = new File(sourceDir.toFile(), "docs"); + boolean mkdirSuccess = subDir.mkdirs(); + Assertions.assertThat(mkdirSuccess).isTrue(); + File nestedHtmFile = new File(subDir, "nested.htm"); + Files.write(nestedHtmFile.toPath(), VALID_HTML.getBytes(StandardCharsets.UTF_8)); + + // Create Mojo and set only sourceDir field + HtmlSanityCheckMojo mojo = new TestableHtmlSanityCheckMojo( + sourceDir.toFile(), + null, // sourceDocuments explicitly NOT set + resultDir.toFile(), + junitDir.toFile() + ); + + // Get the configuration to verify sourceDocuments includes .htm files + Configuration config = mojo.setupConfiguration(); + + // Verify that both .html and .htm files are discovered + Assertions.assertThat(config.getSourceDocuments()).isNotNull(); + Assertions.assertThat(config.getSourceDocuments()).hasSize(3); + Assertions.assertThat(config.getSourceDocuments()) + .extracting(File::getName) + .containsExactlyInAnyOrder("document.htm", "page.html", "nested.htm"); + + // Execute should succeed with both file types + mojo.execute(); + + // Clean up + deleteDirectory(sourceDir.toFile()); + deleteDirectory(junitDir.toFile()); + deleteDirectory(resultDir.toFile()); + } + + /** + * Helper class to allow setting private fields for testing + */ + static class TestableHtmlSanityCheckMojo extends HtmlSanityCheckMojo { + TestableHtmlSanityCheckMojo(File sourceDir, Set sourceDocuments, + File checkingResultsDir, File junitResultsDir) { + // Use reflection to set private fields + try { + HtmlSanityCheckMojoTest.setField(this, "sourceDir", sourceDir); + HtmlSanityCheckMojoTest.setField(this, "sourceDocuments", sourceDocuments); + HtmlSanityCheckMojoTest.setField(this, "checkingResultsDir", checkingResultsDir); + HtmlSanityCheckMojoTest.setField(this, "junitResultsDir", junitResultsDir); + HtmlSanityCheckMojoTest.setField(this, "checkerClasses", AllCheckers.CHECKER_CLASSES); + HtmlSanityCheckMojoTest.setField(this, "excludes", new HashSet()); + } catch (Exception e) { + throw new RuntimeException("Failed to set fields", e); + } + } + } + // Helper functions @@ -203,5 +447,15 @@ void deleteDirectory(File directoryToBeDeleted) throws IOException { Files.deleteIfExists(directoryToBeDeleted.toPath()); } + /** + * Helper method to set private fields on HtmlSanityCheckMojo for testing + */ + private static void setField(Object target, String fieldName, Object value) + throws NoSuchFieldException, IllegalAccessException { + java.lang.reflect.Field field = HtmlSanityCheckMojo.class.getDeclaredField(fieldName); + field.setAccessible(true); + field.set(target, value); + } + }