Skip to content

Commit 4c6fd05

Browse files
committed
[backend] feat: review feedback
1 parent 3a04121 commit 4c6fd05

2 files changed

Lines changed: 89 additions & 57 deletions

File tree

openaev-api/src/test/java/io/openaev/rest/inject/InjectApiTest.java

Lines changed: 64 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1395,7 +1395,7 @@ private Object[] buildInjectWithOutputParser(OutputParser outputParser) throws E
13951395
/** Wraps stdout content in the expected JSON envelope used by the implant callback. */
13961396
private InjectExecutionInput buildStdoutInput(String stdoutContent) {
13971397
InjectExecutionInput input = new InjectExecutionInput();
1398-
input.setMessage("{\"stdout\":\"" + stdoutContent.replace("\\", "\\\\") + "\"}");
1398+
input.setMessage("{\"stdout\":\"" + stdoutContent + "\"}");
13991399
input.setAction(InjectExecutionAction.command_execution);
14001400
input.setStatus("SUCCESS");
14011401
return input;
@@ -1413,10 +1413,13 @@ void shouldCreateFindingsForEachCveExtractedFromRawOutput() throws Exception {
14131413
Inject cveInject = (Inject) setup[0];
14141414
String agentId = (String) setup[1];
14151415

1416-
String rawOutput =
1417-
"[CVE-2025-25241] [http] [critical] http://192.168.1.10/\\n"
1418-
+ "[CVE-2025-99999] [http] [high] http://192.168.1.20/\\n";
1419-
InjectExecutionInput input = buildStdoutInput(rawOutput);
1416+
// Build message directly, same format as given_targetedAsset_should_linkFindingToIt
1417+
InjectExecutionInput input = new InjectExecutionInput();
1418+
input.setMessage(
1419+
"{\"stdout\":\"[CVE-2025-25241] [http] [critical] http://192.168.1.10/\\n"
1420+
+ "[CVE-2025-99999] [http] [high] http://192.168.1.20/\\n\"}");
1421+
input.setAction(InjectExecutionAction.command_execution);
1422+
input.setStatus("SUCCESS");
14201423

14211424
// -- EXECUTE --
14221425
performCallbackRequest(agentId, cveInject.getId(), input);
@@ -1426,9 +1429,9 @@ void shouldCreateFindingsForEachCveExtractedFromRawOutput() throws Exception {
14261429
.atMost(15, TimeUnit.SECONDS)
14271430
.with()
14281431
.pollInterval(1, TimeUnit.SECONDS)
1429-
.until(() -> findingRepository.findAllByInjectId(cveInject.getId()).size() >= 2);
1432+
.until(() -> injectTestHelper.findFindingsByInjectId(cveInject.getId()).size() >= 2);
14301433

1431-
List<Finding> cveFindings = findingRepository.findAllByInjectId(cveInject.getId());
1434+
List<Finding> cveFindings = injectTestHelper.findFindingsByInjectId(cveInject.getId());
14321435
assertEquals(2, cveFindings.size());
14331436
assertTrue(
14341437
cveFindings.stream().anyMatch(f -> f.getValue().contains("CVE-2025-25241")),
@@ -1456,12 +1459,12 @@ void shouldNotCreateCveFindingsWhenRawOutputContainsNoCveMatches() throws Except
14561459

14571460
// -- ASSERT --
14581461
Awaitility.await()
1459-
.atMost(8, TimeUnit.SECONDS)
1462+
.atMost(15, TimeUnit.SECONDS)
14601463
.with()
14611464
.pollInterval(1, TimeUnit.SECONDS)
1462-
.until(() -> true);
1465+
.until(() -> injectTestHelper.hasInjectStatusTrace(cveInject.getId()));
14631466
assertTrue(
1464-
findingRepository.findAllByInjectId(cveInject.getId()).isEmpty(),
1467+
injectTestHelper.findFindingsByInjectId(cveInject.getId()).isEmpty(),
14651468
"No findings expected when output has no CVE match");
14661469
}
14671470

@@ -1498,9 +1501,9 @@ void shouldCreateFindingForEachCredentialPairExtractedFromRawOutput() throws Exc
14981501
.atMost(15, TimeUnit.SECONDS)
14991502
.with()
15001503
.pollInterval(1, TimeUnit.SECONDS)
1501-
.until(() -> findingRepository.findAllByInjectId(credInject.getId()).size() >= 2);
1504+
.until(() -> injectTestHelper.findFindingsByInjectId(credInject.getId()).size() >= 2);
15021505

1503-
List<Finding> credFindings = findingRepository.findAllByInjectId(credInject.getId());
1506+
List<Finding> credFindings = injectTestHelper.findFindingsByInjectId(credInject.getId());
15041507
assertEquals(2, credFindings.size());
15051508
assertTrue(
15061509
credFindings.stream().anyMatch(f -> f.getValue().contains("alice:secret123")),
@@ -1535,11 +1538,11 @@ void shouldNotCreateCredentialsFindingsWhenRawOutputContainsNoCredentials() thro
15351538

15361539
// -- ASSERT --
15371540
Awaitility.await()
1538-
.atMost(8, TimeUnit.SECONDS)
1541+
.atMost(15, TimeUnit.SECONDS)
15391542
.with()
15401543
.pollInterval(1, TimeUnit.SECONDS)
1541-
.until(() -> true);
1542-
assertTrue(findingRepository.findAllByInjectId(credInject.getId()).isEmpty());
1544+
.until(() -> injectTestHelper.hasInjectStatusTrace(credInject.getId()));
1545+
assertTrue(injectTestHelper.findFindingsByInjectId(credInject.getId()).isEmpty());
15431546
}
15441547

15451548
// PortScan
@@ -1548,24 +1551,26 @@ void shouldNotCreateCredentialsFindingsWhenRawOutputContainsNoCredentials() thro
15481551
@DisplayName("Should create a finding for each open port/service extracted from raw output")
15491552
void shouldCreateFindingForEachOpenPortServiceExtractedFromRawOutput() throws Exception {
15501553
// -- PREPARE --
1551-
RegexGroup hostGroup = OutputParserFixture.getRegexGroup("host", "$2");
1552-
RegexGroup portGroup = OutputParserFixture.getRegexGroup("port", "$3");
1553-
RegexGroup serviceGroup = OutputParserFixture.getRegexGroup("service", "$4");
1554+
RegexGroup hostGroup = OutputParserFixture.getRegexGroup("host", "$1");
1555+
RegexGroup portGroup = OutputParserFixture.getRegexGroup("port", "$2");
1556+
RegexGroup serviceGroup = OutputParserFixture.getRegexGroup("service", "$3");
15541557
ContractOutputElement portScanElement =
15551558
OutputParserFixture.getContractOutputElement(
15561559
ContractOutputType.PortsScan,
1557-
"^\\s*(TCP|UDP)\\s+([\\d\\.]+|\\*)?:?(\\d+)\\s+\\S+\\s+(\\S+)",
1560+
"(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}):(\\d+)\\s+\\S+\\s+(LISTENING)",
15581561
Set.of(hostGroup, portGroup, serviceGroup),
15591562
true);
15601563
OutputParser outputParser = OutputParserFixture.getOutputParser(Set.of(portScanElement));
15611564
Object[] setup = buildInjectWithOutputParser(outputParser);
15621565
Inject portScanInject = (Inject) setup[0];
15631566
String agentId = (String) setup[1];
15641567

1565-
String rawOutput =
1566-
" TCP 192.168.1.10:135 0.0.0.0:0 LISTENING\\n"
1567-
+ " TCP 10.0.0.5:443 0.0.0.0:0 LISTENING\\n";
1568-
InjectExecutionInput input = buildStdoutInput(rawOutput);
1568+
InjectExecutionInput input = new InjectExecutionInput();
1569+
input.setMessage(
1570+
"{\"stdout\":\"192.168.1.10:135 0.0.0.0:0 LISTENING\\n"
1571+
+ "10.0.0.5:443 0.0.0.0:0 LISTENING\\n\"}");
1572+
input.setAction(InjectExecutionAction.command_execution);
1573+
input.setStatus("SUCCESS");
15691574

15701575
// -- EXECUTE --
15711576
performCallbackRequest(agentId, portScanInject.getId(), input);
@@ -1575,10 +1580,11 @@ void shouldCreateFindingForEachOpenPortServiceExtractedFromRawOutput() throws Ex
15751580
.atMost(15, TimeUnit.SECONDS)
15761581
.with()
15771582
.pollInterval(1, TimeUnit.SECONDS)
1578-
.until(() -> findingRepository.findAllByInjectId(portScanInject.getId()).size() >= 2);
1583+
.until(
1584+
() -> injectTestHelper.findFindingsByInjectId(portScanInject.getId()).size() >= 2);
15791585

15801586
List<Finding> portScanFindings =
1581-
findingRepository.findAllByInjectId(portScanInject.getId());
1587+
injectTestHelper.findFindingsByInjectId(portScanInject.getId());
15821588
assertEquals(2, portScanFindings.size());
15831589
assertTrue(
15841590
portScanFindings.stream().anyMatch(f -> f.getValue().contains("192.168.1.10")),
@@ -1593,13 +1599,13 @@ void shouldCreateFindingForEachOpenPortServiceExtractedFromRawOutput() throws Ex
15931599
@DisplayName("Should not create PortScan findings when raw output has no port scan matches")
15941600
void shouldNotCreatePortScanFindingsWhenRawOutputHasNoPortScanMatches() throws Exception {
15951601
// -- PREPARE --
1596-
RegexGroup hostGroup = OutputParserFixture.getRegexGroup("host", "$2");
1597-
RegexGroup portGroup = OutputParserFixture.getRegexGroup("port", "$3");
1598-
RegexGroup serviceGroup = OutputParserFixture.getRegexGroup("service", "$4");
1602+
RegexGroup hostGroup = OutputParserFixture.getRegexGroup("host", "$1");
1603+
RegexGroup portGroup = OutputParserFixture.getRegexGroup("port", "$2");
1604+
RegexGroup serviceGroup = OutputParserFixture.getRegexGroup("service", "$3");
15991605
ContractOutputElement portScanElement =
16001606
OutputParserFixture.getContractOutputElement(
16011607
ContractOutputType.PortsScan,
1602-
"^\\s*(TCP|UDP)\\s+([\\d\\.]+|\\*)?:?(\\d+)\\s+\\S+\\s+(\\S+)",
1608+
"(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}):(\\d+)\\s+\\S+\\s+(LISTENING)",
16031609
Set.of(hostGroup, portGroup, serviceGroup),
16041610
true);
16051611
OutputParser outputParser = OutputParserFixture.getOutputParser(Set.of(portScanElement));
@@ -1614,11 +1620,11 @@ void shouldNotCreatePortScanFindingsWhenRawOutputHasNoPortScanMatches() throws E
16141620

16151621
// -- ASSERT --
16161622
Awaitility.await()
1617-
.atMost(8, TimeUnit.SECONDS)
1623+
.atMost(15, TimeUnit.SECONDS)
16181624
.with()
16191625
.pollInterval(1, TimeUnit.SECONDS)
1620-
.until(() -> true);
1621-
assertTrue(findingRepository.findAllByInjectId(portScanInject.getId()).isEmpty());
1626+
.until(() -> injectTestHelper.hasInjectStatusTrace(portScanInject.getId()));
1627+
assertTrue(injectTestHelper.findFindingsByInjectId(portScanInject.getId()).isEmpty());
16221628
}
16231629

16241630
// Port
@@ -1652,9 +1658,9 @@ void shouldCreateFindingForEachPortNumberExtractedFromRawOutput() throws Excepti
16521658
.atMost(15, TimeUnit.SECONDS)
16531659
.with()
16541660
.pollInterval(1, TimeUnit.SECONDS)
1655-
.until(() -> findingRepository.findAllByInjectId(portInject.getId()).size() >= 2);
1661+
.until(() -> injectTestHelper.findFindingsByInjectId(portInject.getId()).size() >= 2);
16561662

1657-
List<Finding> portFindings = findingRepository.findAllByInjectId(portInject.getId());
1663+
List<Finding> portFindings = injectTestHelper.findFindingsByInjectId(portInject.getId());
16581664
assertEquals(2, portFindings.size());
16591665
assertTrue(
16601666
portFindings.stream().anyMatch(f -> f.getValue().equals("8080")),
@@ -1688,11 +1694,11 @@ void shouldNotCreatePortFindingsWhenRawOutputContainsNoPortMatches() throws Exce
16881694

16891695
// -- ASSERT --
16901696
Awaitility.await()
1691-
.atMost(8, TimeUnit.SECONDS)
1697+
.atMost(15, TimeUnit.SECONDS)
16921698
.with()
16931699
.pollInterval(1, TimeUnit.SECONDS)
1694-
.until(() -> true);
1695-
assertTrue(findingRepository.findAllByInjectId(portInject.getId()).isEmpty());
1700+
.until(() -> injectTestHelper.hasInjectStatusTrace(portInject.getId()));
1701+
assertTrue(injectTestHelper.findFindingsByInjectId(portInject.getId()).isEmpty());
16961702
}
16971703

16981704
// Text
@@ -1721,9 +1727,9 @@ void shouldCreateFindingsForEachTextValueExtractedFromRawOutput() throws Excepti
17211727
.atMost(15, TimeUnit.SECONDS)
17221728
.with()
17231729
.pollInterval(1, TimeUnit.SECONDS)
1724-
.until(() -> !findingRepository.findAllByInjectId(textInject.getId()).isEmpty());
1730+
.until(() -> !injectTestHelper.findFindingsByInjectId(textInject.getId()).isEmpty());
17251731

1726-
List<Finding> textFindings = findingRepository.findAllByInjectId(textInject.getId());
1732+
List<Finding> textFindings = injectTestHelper.findFindingsByInjectId(textInject.getId());
17271733
assertFalse(textFindings.isEmpty(), "Expected at least one text finding");
17281734
textFindings.forEach(f -> assertEquals(ContractOutputType.Text, f.getType()));
17291735
}
@@ -1748,11 +1754,11 @@ void shouldNotCreateTextFindingsWhenRawOutputContainsNoMatches() throws Exceptio
17481754

17491755
// -- ASSERT --
17501756
Awaitility.await()
1751-
.atMost(8, TimeUnit.SECONDS)
1757+
.atMost(15, TimeUnit.SECONDS)
17521758
.with()
17531759
.pollInterval(1, TimeUnit.SECONDS)
1754-
.until(() -> true);
1755-
assertTrue(findingRepository.findAllByInjectId(textInject.getId()).isEmpty());
1760+
.until(() -> injectTestHelper.hasInjectStatusTrace(textInject.getId()));
1761+
assertTrue(injectTestHelper.findFindingsByInjectId(textInject.getId()).isEmpty());
17561762
}
17571763

17581764
// Number
@@ -1781,9 +1787,10 @@ void shouldCreateFindingsForEachNumberExtractedFromRawOutput() throws Exception
17811787
.atMost(15, TimeUnit.SECONDS)
17821788
.with()
17831789
.pollInterval(1, TimeUnit.SECONDS)
1784-
.until(() -> findingRepository.findAllByInjectId(numberInject.getId()).size() >= 2);
1790+
.until(() -> injectTestHelper.findFindingsByInjectId(numberInject.getId()).size() >= 2);
17851791

1786-
List<Finding> numberFindings = findingRepository.findAllByInjectId(numberInject.getId());
1792+
List<Finding> numberFindings =
1793+
injectTestHelper.findFindingsByInjectId(numberInject.getId());
17871794
assertEquals(2, numberFindings.size());
17881795
assertTrue(numberFindings.stream().anyMatch(f -> f.getValue().equals("1234")));
17891796
assertTrue(numberFindings.stream().anyMatch(f -> f.getValue().equals("5678")));
@@ -1810,11 +1817,11 @@ void shouldNotCreateNumberFindingsWhenRawOutputContainsNoNumericMatches() throws
18101817

18111818
// -- ASSERT --
18121819
Awaitility.await()
1813-
.atMost(8, TimeUnit.SECONDS)
1820+
.atMost(15, TimeUnit.SECONDS)
18141821
.with()
18151822
.pollInterval(1, TimeUnit.SECONDS)
1816-
.until(() -> true);
1817-
assertTrue(findingRepository.findAllByInjectId(numberInject.getId()).isEmpty());
1823+
.until(() -> injectTestHelper.hasInjectStatusTrace(numberInject.getId()));
1824+
assertTrue(injectTestHelper.findFindingsByInjectId(numberInject.getId()).isEmpty());
18181825
}
18191826

18201827
// IPv4
@@ -1848,9 +1855,9 @@ void shouldCreateFindingForEachValidIPv4AddressExtractedFromRawOutput() throws E
18481855
.atMost(15, TimeUnit.SECONDS)
18491856
.with()
18501857
.pollInterval(1, TimeUnit.SECONDS)
1851-
.until(() -> !findingRepository.findAllByInjectId(ipv4Inject.getId()).isEmpty());
1858+
.until(() -> !injectTestHelper.findFindingsByInjectId(ipv4Inject.getId()).isEmpty());
18521859

1853-
List<Finding> ipv4Findings = findingRepository.findAllByInjectId(ipv4Inject.getId());
1860+
List<Finding> ipv4Findings = injectTestHelper.findFindingsByInjectId(ipv4Inject.getId());
18541861
assertFalse(ipv4Findings.isEmpty(), "Expected at least one IPv4 finding");
18551862
assertTrue(
18561863
ipv4Findings.stream().anyMatch(f -> f.getValue().equals("192.168.1.10")),
@@ -1886,11 +1893,11 @@ void shouldNotCreateIPv4FindingsWhenRawOutputContainsNoValidIPv4Addresses() thro
18861893

18871894
// -- ASSERT --
18881895
Awaitility.await()
1889-
.atMost(8, TimeUnit.SECONDS)
1896+
.atMost(15, TimeUnit.SECONDS)
18901897
.with()
18911898
.pollInterval(1, TimeUnit.SECONDS)
1892-
.until(() -> true);
1893-
assertTrue(findingRepository.findAllByInjectId(ipv4Inject.getId()).isEmpty());
1899+
.until(() -> injectTestHelper.hasInjectStatusTrace(ipv4Inject.getId()));
1900+
assertTrue(injectTestHelper.findFindingsByInjectId(ipv4Inject.getId()).isEmpty());
18941901
}
18951902

18961903
// IPv6
@@ -1921,9 +1928,9 @@ void shouldCreateFindingForEachValidIPv6AddressExtractedFromRawOutput() throws E
19211928
.atMost(15, TimeUnit.SECONDS)
19221929
.with()
19231930
.pollInterval(1, TimeUnit.SECONDS)
1924-
.until(() -> findingRepository.findAllByInjectId(ipv6Inject.getId()).size() >= 2);
1931+
.until(() -> injectTestHelper.findFindingsByInjectId(ipv6Inject.getId()).size() >= 2);
19251932

1926-
List<Finding> ipv6Findings = findingRepository.findAllByInjectId(ipv6Inject.getId());
1933+
List<Finding> ipv6Findings = injectTestHelper.findFindingsByInjectId(ipv6Inject.getId());
19271934
assertEquals(2, ipv6Findings.size());
19281935
assertTrue(
19291936
ipv6Findings.stream().anyMatch(f -> f.getValue().contains("fe80::1b03:a1ff:ccdb:b464")),
@@ -1955,11 +1962,11 @@ void shouldNotCreateIPv6FindingsWhenRawOutputContainsNoIPv6Addresses() throws Ex
19551962

19561963
// -- ASSERT --
19571964
Awaitility.await()
1958-
.atMost(8, TimeUnit.SECONDS)
1965+
.atMost(15, TimeUnit.SECONDS)
19591966
.with()
19601967
.pollInterval(1, TimeUnit.SECONDS)
1961-
.until(() -> true);
1962-
assertTrue(findingRepository.findAllByInjectId(ipv6Inject.getId()).isEmpty());
1968+
.until(() -> injectTestHelper.hasInjectStatusTrace(ipv6Inject.getId()));
1969+
assertTrue(injectTestHelper.findFindingsByInjectId(ipv6Inject.getId()).isEmpty());
19631970
}
19641971
}
19651972
}

openaev-api/src/test/java/io/openaev/utils/helpers/InjectTestHelper.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import io.openaev.database.repository.*;
55
import io.openaev.utils.fixtures.*;
66
import io.openaev.utils.fixtures.composers.*;
7+
import java.util.List;
78
import lombok.RequiredArgsConstructor;
89
import org.springframework.stereotype.Component;
910
import org.springframework.transaction.annotation.Propagation;
@@ -97,4 +98,28 @@ public Finding forceSaveFinding(Finding finding) {
9798
public Asset forceSaveAsset(Asset asset) {
9899
return assetRepository.save(asset);
99100
}
101+
102+
/**
103+
* Queries findings for a given inject ID in a new independent transaction, so that findings
104+
* committed by async processing threads are visible even when called from within an outer
105+
* {@code @Transactional} test method.
106+
*/
107+
@Transactional(propagation = Propagation.REQUIRES_NEW)
108+
public List<Finding> findFindingsByInjectId(String injectId) {
109+
return findingRepository.findAllByInjectId(injectId);
110+
}
111+
112+
/**
113+
* Returns true if the inject has at least one execution trace, confirming async processing
114+
* completed. Runs in a new independent transaction so results committed by async threads are
115+
* visible from within an outer {@code @Transactional} test.
116+
*/
117+
@Transactional(propagation = Propagation.REQUIRES_NEW)
118+
public boolean hasInjectStatusTrace(String injectId) {
119+
return injectRepository
120+
.findById(injectId)
121+
.flatMap(Inject::getStatus)
122+
.filter(s -> !s.getTraces().isEmpty())
123+
.isPresent();
124+
}
100125
}

0 commit comments

Comments
 (0)