Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 16 additions & 11 deletions etl-service/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,6 @@
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.15</version>
<configuration>
<includes>
<include>com.xtrmetl.etl.job.*</include>
<include>com.xtrmetl.etl.controller.EtlJobController*</include>
</includes>
</configuration>
<executions>
<execution>
<id>prepare-durable-job-coverage</id>
Expand All @@ -118,6 +112,12 @@
<goals>
<goal>report</goal>
</goals>
<configuration>
<includes>
<include>com/xtrmetl/etl/job/*.class</include>
<include>com/xtrmetl/etl/controller/EtlJobController*.class</include>
</includes>
</configuration>
</execution>
<execution>
<id>check-durable-job-coverage</id>
Expand All @@ -126,14 +126,19 @@
<goal>check</goal>
</goals>
<configuration>
<includes>
<include>com/xtrmetl/etl/job/*.class</include>
<include>com/xtrmetl/etl/controller/EtlJobController*.class</include>
</includes>
<rules>
<rule>
<element>CLASS</element>
<includes>
<include>com.xtrmetl.etl.job.*</include>
<include>com.xtrmetl.etl.controller.EtlJobController*</include>
</includes>
<element>BUNDLE</element>
<limits>
<limit>
<counter>CLASS</counter>
<value>TOTALCOUNT</value>
<minimum>1</minimum>
</limit>
<limit>
<counter>INSTRUCTION</counter>
<value>MISSEDCOUNT</value>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package com.xtrmetl.etl.documentation;

import org.junit.jupiter.api.Test;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.parsers.DocumentBuilderFactory;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.LinkedHashSet;
import java.util.Set;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
* Guards the JaCoCo durable-job coverage gate against an empty production-class selection that can
* otherwise satisfy zero-missed counters vacuously.
*/
class JaCoCoCoverageConfigurationTest {

private static final Path PROJECT_ROOT = projectRoot();
private static final Set<String> EXPECTED_CLASS_FILE_PATTERNS = Set.of(
"com/xtrmetl/etl/job/*.class",
"com/xtrmetl/etl/controller/EtlJobController*.class"
);

@Test
void reportAndCheckSelectCompiledClassFilesInsteadOfReusingAgentClassNames() throws Exception {
Element plugin = jacocoPlugin(parsePom());
Element pluginConfiguration = directChild(plugin, "configuration");
assertTrue(
pluginConfiguration == null || directChild(pluginConfiguration, "includes") == null,
"Plugin-level includes leak one filter syntax into prepare-agent, report, and check"
);

assertEquals(
EXPECTED_CLASS_FILE_PATTERNS,
executionIncludes(plugin, "report-durable-job-coverage")
);
assertEquals(
EXPECTED_CLASS_FILE_PATTERNS,
executionIncludes(plugin, "check-durable-job-coverage")
);
}

@Test
void coverageCheckRequiresAtLeastOneAnalyzedProductionClass() throws Exception {
Element plugin = jacocoPlugin(parsePom());
Element checkExecution = execution(plugin, "check-durable-job-coverage");
Element configuration = requireDirectChild(checkExecution, "configuration");
Element rules = requireDirectChild(configuration, "rules");
Element rule = requireDirectChild(rules, "rule");

assertEquals("BUNDLE", directChildText(rule, "element"));

Element limits = requireDirectChild(rule, "limits");
boolean foundNonEmptyGuard = false;
for (Element limit : directChildren(limits, "limit")) {
if ("CLASS".equals(directChildText(limit, "counter"))
&& "TOTALCOUNT".equals(directChildText(limit, "value"))
&& "1".equals(directChildText(limit, "minimum"))) {
foundNonEmptyGuard = true;
}
}
assertTrue(
foundNonEmptyGuard,
"Coverage must fail closed when the selected production bundle contains zero classes"
);
}

@Test
void intendedCoverageTargetContainsCompiledProductionClasses() {
Path classes = PROJECT_ROOT.resolve("etl-service/target/classes");
assertTrue(
Files.isRegularFile(classes.resolve("com/xtrmetl/etl/job/EtlJobService.class")),
"EtlJobService must be a real compiled production coverage target"
);
assertTrue(
Files.isRegularFile(classes.resolve("com/xtrmetl/etl/controller/EtlJobController.class")),
"EtlJobController must be a real compiled production coverage target"
);
}

private static Document parsePom() throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
factory.setXIncludeAware(false);
factory.setExpandEntityReferences(false);
return factory.newDocumentBuilder().parse(PROJECT_ROOT.resolve("etl-service/pom.xml").toFile());
}

private static Element jacocoPlugin(Document document) {
NodeList plugins = document.getElementsByTagName("plugin");
for (int index = 0; index < plugins.getLength(); index++) {
Element plugin = (Element) plugins.item(index);
if ("org.jacoco".equals(directChildText(plugin, "groupId"))
&& "jacoco-maven-plugin".equals(directChildText(plugin, "artifactId"))) {
return plugin;
}
}
throw new AssertionError("etl-service POM is missing jacoco-maven-plugin");
}

private static Set<String> executionIncludes(Element plugin, String executionId) {
Element configuration = requireDirectChild(execution(plugin, executionId), "configuration");
Element includes = requireDirectChild(configuration, "includes");
Set<String> values = new LinkedHashSet<>();
for (Element include : directChildren(includes, "include")) {
values.add(include.getTextContent().trim());
}
assertFalse(values.isEmpty(), "JaCoCo class-file selection must not be empty");
return values;
}

private static Element execution(Element plugin, String executionId) {
Element executions = requireDirectChild(plugin, "executions");
for (Element execution : directChildren(executions, "execution")) {
if (executionId.equals(directChildText(execution, "id"))) {
return execution;
}
}
throw new AssertionError("Missing JaCoCo execution: " + executionId);
}

private static Element requireDirectChild(Element parent, String name) {
Element child = directChild(parent, name);
assertNotNull(child, () -> "Missing <" + name + "> under <" + parent.getTagName() + ">");
return child;
}

private static Element directChild(Element parent, String name) {
NodeList children = parent.getChildNodes();
for (int index = 0; index < children.getLength(); index++) {
Node child = children.item(index);
if (child instanceof Element element && name.equals(element.getTagName())) {
return element;
}
}
return null;
}

private static Set<Element> directChildren(Element parent, String name) {
Set<Element> matches = new LinkedHashSet<>();
NodeList children = parent.getChildNodes();
for (int index = 0; index < children.getLength(); index++) {
Node child = children.item(index);
if (child instanceof Element element && name.equals(element.getTagName())) {
matches.add(element);
}
}
return matches;
}

private static String directChildText(Element parent, String name) {
Element child = directChild(parent, name);
return child == null ? null : child.getTextContent().trim();
}

/** Finds the repository root from root- or module-scoped Maven execution. */
private static Path projectRoot() {
Path current = Paths.get(System.getProperty("user.dir")).toAbsolutePath();
Path lastPomParent = null;
while (current != null) {
if (Files.exists(current.resolve(".git"))) {
return current;
}
if (Files.exists(current.resolve("pom.xml"))) {
lastPomParent = current;
}
current = current.getParent();
}
if (lastPomParent != null) {
return lastPomParent;
}
throw new IllegalStateException("Could not find project root");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@
* Keeps the durable-job production slice bound to an executable 100% coverage policy.
*
* <p>The policy is intentionally scoped to the production classes introduced by the durable-job
* intake slice. It requires current Java-compatible JaCoCo instrumentation and zero missed
* instructions, lines, methods, or branches while the ordinary {@code mvn test} lifecycle runs.</p>
* intake slice. It requires current Java-compatible JaCoCo instrumentation, a non-empty selected
* production bundle, and zero missed instructions, lines, methods, or branches while the ordinary
* {@code mvn test} lifecycle runs.</p>
*/
class EtlJobCoveragePolicyTest {

/**
* Requires the ETL module build to fail when any durable-job production path is untested.
* Requires the ETL module build to fail when the durable-job production slice is empty or any
* selected production path is untested.
*
* @throws IOException when the module build descriptor cannot be read
*/
Expand All @@ -36,10 +38,14 @@ void etlModuleEnforcesCompleteInstructionAndBranchCoverageForTheDurableJobSlice(
assertTrue(modulePom.contains("<phase>test</phase>"));
assertTrue(modulePom.contains("<goal>report</goal>"));
assertTrue(modulePom.contains("<goal>check</goal>"));
assertTrue(modulePom.contains("<include>com.xtrmetl.etl.job.*</include>"));
assertTrue(modulePom.contains("<include>com/xtrmetl/etl/job/*.class</include>"));
assertTrue(modulePom.contains(
"<include>com.xtrmetl.etl.controller.EtlJobController*</include>"
"<include>com/xtrmetl/etl/controller/EtlJobController*.class</include>"
));
assertTrue(modulePom.contains("<element>BUNDLE</element>"));
assertTrue(modulePom.contains("<counter>CLASS</counter>"));
assertTrue(modulePom.contains("<value>TOTALCOUNT</value>"));
assertTrue(modulePom.contains("<minimum>1</minimum>"));
assertTrue(modulePom.contains("<counter>INSTRUCTION</counter>"));
assertTrue(modulePom.contains("<counter>LINE</counter>"));
assertTrue(modulePom.contains("<counter>METHOD</counter>"));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.xtrmetl.etl.job;

import org.junit.jupiter.api.Test;

import java.time.Instant;
import java.util.UUID;

import static org.junit.jupiter.api.Assertions.assertThrows;

/**
* Closes branch-coverage gaps in immutable durable-job model validation that are part of the
* non-vacuous JaCoCo production slice.
*
* <p>These cases exercise each distinct short-circuit path in origin-relative status URL and
* lifecycle-dependent failure-code validation. They are intentionally model-boundary tests: no
* database, transaction, network, or controller fixture is needed to prove the record invariants.</p>
*/
class EtlJobModelCoverageBoundaryTest {

private static final UUID JOB_RECORD_ID = UUID.fromString(
"cf4f083f-8c90-4f34-a8b6-b53761de44ef"
);
private static final Instant CREATED_AT = Instant.parse("2026-08-04T10:00:00Z");
private static final Instant UPDATED_AT = Instant.parse("2026-08-04T10:00:01Z");

@Test
void rejectsEveryUnsafeAcceptedResponseStatusUrlForm() {
assertThrows(
IllegalArgumentException.class,
() -> new EtlJobAcceptedResponse(
JOB_RECORD_ID,
EtlJobStatus.PENDING,
" "
)
);
assertThrows(
IllegalArgumentException.class,
() -> new EtlJobAcceptedResponse(
JOB_RECORD_ID,
EtlJobStatus.PENDING,
"api/etl/jobs/" + JOB_RECORD_ID
)
);
assertThrows(
IllegalArgumentException.class,
() -> new EtlJobAcceptedResponse(
JOB_RECORD_ID,
EtlJobStatus.PENDING,
"//attacker.example/api/etl/jobs/" + JOB_RECORD_ID
)
);
}

@Test
void coversBothFailedStateFailureCodeShortCircuitForms() {
assertThrows(
IllegalArgumentException.class,
() -> new EtlJobSnapshot(
JOB_RECORD_ID,
EtlJobStatus.FAILED,
1,
" ",
CREATED_AT,
UPDATED_AT
)
);
assertThrows(
IllegalArgumentException.class,
() -> new EtlJobStatusResponse(
JOB_RECORD_ID,
EtlJobStatus.FAILED,
1,
null,
CREATED_AT,
UPDATED_AT
)
);
}
}
Loading
Loading