Expose raw CPE version ranges for advisory tooling - #408
Conversation
AI-Assisted-by: OpenAI Codex <noreply@openai.com> Signed-off-by: Lucas Holt <luke@foolishgames.com>
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
Reviewer's GuideAdds a new GET /api/cpe/ranges endpoint and corresponding service-layer DTO pipeline to expose raw NVD CPE configuration ranges (including hierarchy and version boundaries) for a given CPE, plus tests to validate parsing, filtering, and serialization behavior. Sequence diagram for GET /api/cpe/ranges CPE range retrievalsequenceDiagram
title GET /api/cpe/ranges flow from controller to service
actor Tool
participant CpeController
participant AdvisoryService
Tool->>CpeController: ranges(cpe, startDate)
CpeController->>CpeController: parse(cpe)
CpeController->>AdvisoryService: cpeRangeDtos(vendor, product, startDate)
AdvisoryService->>AdvisoryService: cpeRangeDto(advisory, vendor, product)
AdvisoryService->>AdvisoryService: matchesProduct(criteria, vendor, product)
AdvisoryService-->>CpeController: List<CpeRangeAdvisoryDto>
CpeController-->>Tool: ResponseEntity<List<CpeRangeAdvisoryDto>>
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 2 issues
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="src/test/java/org/midnightbsd/advisory/services/AdvisoryServiceTest.java" line_range="245-254" />
<code_context>
.andExpect(content().contentTypeCompatibleWith("application/json;charset=UTF-8"));
}
+ @Test
+ void mvcTestGetCpeRanges() throws Exception {
+ when(advisoryService.cpeRangeDtos(anyString(), anyString(), ArgumentMatchers.isNull()))
</code_context>
<issue_to_address>
**issue (testing):** Add tests for edge cases in cpeRangeDtos (no config nodes, non-matching product, unparsable CPE URIs, and empty configurations).
Please add tests to cover the unhandled edge cases in `cpeRangeDtos`/`matchesProduct`:
- Advisory with `getConfigNodes() == null` returns an empty list and is skipped.
- Config nodes whose CPEs don’t match the requested vendor/product are filtered out (advisory effectively skipped).
- `ConfigNodeCpe` with malformed `cpe23Uri` (throwing in `CpeParser.parse`) is ignored without failing the response, and other nodes are still returned.
- Advisory whose `configurations` list ends up empty causes `cpeRangeDto` to return `null` and be omitted.
Targeted tests that set up these conditions (e.g., manipulating `adv.getConfigNodes()` and CPE URIs) will help ensure these behaviors remain stable across refactors.
</issue_to_address>
### Comment 2
<location path="src/test/java/org/midnightbsd/advisory/ctl/CpeControllerTest.java" line_range="98-95" />
<code_context>
.andExpect(content().contentTypeCompatibleWith("application/json;charset=UTF-8"));
}
+ @Test
+ void mvcTestGetCpeRanges() throws Exception {
+ when(advisoryService.cpeRangeDtos(anyString(), anyString(), ArgumentMatchers.isNull()))
+ .thenReturn(List.of(new CpeRangeAdvisoryDto(
+ 1, TEST_CVE_ID, "TEST ARCH", null, null, "HIGH", null, List.of())));
+
+ mockMvc
+ .perform(get("/api/cpe/ranges?cpe=cpe:2.3:a:eric_allman:sendmail:5.58:*:*:*:*:*:*:*"))
+ .andExpect(status().isOk())
+ .andExpect(content().contentTypeCompatibleWith("application/json;charset=UTF-8"));
+ }
+
</code_context>
<issue_to_address>
**suggestion (testing):** Strengthen cpe ranges controller tests by asserting payload and service interaction.
Right now `mvcTestGetCpeRanges` only checks status and content type. Since this endpoint returns structured NVD range data, the test should also:
- Assert the JSON body shape and key fields (e.g. exactly one advisory; expected `cveId`, `severity`, `configurations` via `jsonPath`).
- Verify `advisoryService.cpeRangeDtos` is invoked with the vendor and product parsed from the CPE string (using `ArgumentCaptor` or stricter `when`/`verify` matchers), rather than generic `anyString()` arguments.
This will ensure the controller correctly maps DTOs and passes parsed CPE values into the service.
Suggested implementation:
```java
import org.midnightbsd.advisory.services.AdvisoryService;
import org.mockito.ArgumentMatchers;
import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
```
```java
@Test
void mvcTestGetCpeRanges() throws Exception {
when(advisoryService.cpeRangeDtos(
ArgumentMatchers.eq("eric_allman"),
ArgumentMatchers.eq("sendmail"),
ArgumentMatchers.isNull()))
.thenReturn(List.of(new CpeRangeAdvisoryDto(
1, TEST_CVE_ID, "TEST ARCH", null, null, "HIGH", null, List.of())));
mockMvc
.perform(get("/api/cpe/ranges?cpe=cpe:2.3:a:eric_allman:sendmail:5.58:*:*:*:*:*:*:*"))
.andExpect(status().isOk())
.andExpect(content().contentTypeCompatibleWith("application/json;charset=UTF-8"))
.andExpect(jsonPath("$.length()").value(1))
.andExpect(jsonPath("$[0].cveId").value(TEST_CVE_ID))
.andExpect(jsonPath("$[0].severity").value("HIGH"))
.andExpect(jsonPath("$[0].configurations").isArray());
verify(advisoryService)
.cpeRangeDtos(
ArgumentMatchers.eq("eric_allman"),
ArgumentMatchers.eq("sendmail"),
ArgumentMatchers.isNull());
```
If the JSON field names in the CPE ranges response differ from `cveId`, `severity`, or `configurations`, adjust the `jsonPath` expressions to match the actual serialized property names used in `CpeRangeAdvisoryDto`.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| @Test | ||
| void cpeRangeDtosPreservesNvdRangeConstraints() { | ||
| var vendor = new Vendor(); | ||
| vendor.setName("vendor"); | ||
| var product = new Product(); | ||
| product.setName("product"); | ||
| product.setVendor(vendor); | ||
| ConfigNodeCpe cpe = adv.getConfigNodes().iterator().next().getConfigNodeCpes().iterator().next(); | ||
| cpe.setVersionStartIncluding("1.0"); | ||
| cpe.setVersionEndExcluding("2.0"); |
There was a problem hiding this comment.
issue (testing): Add tests for edge cases in cpeRangeDtos (no config nodes, non-matching product, unparsable CPE URIs, and empty configurations).
Please add tests to cover the unhandled edge cases in cpeRangeDtos/matchesProduct:
- Advisory with
getConfigNodes() == nullreturns an empty list and is skipped. - Config nodes whose CPEs don’t match the requested vendor/product are filtered out (advisory effectively skipped).
ConfigNodeCpewith malformedcpe23Uri(throwing inCpeParser.parse) is ignored without failing the response, and other nodes are still returned.- Advisory whose
configurationslist ends up empty causescpeRangeDtoto returnnulland be omitted.
Targeted tests that set up these conditions (e.g., manipulating adv.getConfigNodes() and CPE URIs) will help ensure these behaviors remain stable across refactors.
| @@ -94,6 +95,18 @@ void mvcTestGetCpeWithIncludeVersion() throws Exception { | |||
| .andExpect(content().contentTypeCompatibleWith("application/json;charset=UTF-8")); | |||
There was a problem hiding this comment.
suggestion (testing): Strengthen cpe ranges controller tests by asserting payload and service interaction.
Right now mvcTestGetCpeRanges only checks status and content type. Since this endpoint returns structured NVD range data, the test should also:
- Assert the JSON body shape and key fields (e.g. exactly one advisory; expected
cveId,severity,configurationsviajsonPath). - Verify
advisoryService.cpeRangeDtosis invoked with the vendor and product parsed from the CPE string (usingArgumentCaptoror stricterwhen/verifymatchers), rather than genericanyString()arguments.
This will ensure the controller correctly maps DTOs and passes parsed CPE values into the service.
Suggested implementation:
import org.midnightbsd.advisory.services.AdvisoryService;
import org.mockito.ArgumentMatchers;
import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @Test
void mvcTestGetCpeRanges() throws Exception {
when(advisoryService.cpeRangeDtos(
ArgumentMatchers.eq("eric_allman"),
ArgumentMatchers.eq("sendmail"),
ArgumentMatchers.isNull()))
.thenReturn(List.of(new CpeRangeAdvisoryDto(
1, TEST_CVE_ID, "TEST ARCH", null, null, "HIGH", null, List.of())));
mockMvc
.perform(get("/api/cpe/ranges?cpe=cpe:2.3:a:eric_allman:sendmail:5.58:*:*:*:*:*:*:*"))
.andExpect(status().isOk())
.andExpect(content().contentTypeCompatibleWith("application/json;charset=UTF-8"))
.andExpect(jsonPath("$.length()").value(1))
.andExpect(jsonPath("$[0].cveId").value(TEST_CVE_ID))
.andExpect(jsonPath("$[0].severity").value("HIGH"))
.andExpect(jsonPath("$[0].configurations").isArray());
verify(advisoryService)
.cpeRangeDtos(
ArgumentMatchers.eq("eric_allman"),
ArgumentMatchers.eq("sendmail"),
ArgumentMatchers.isNull());If the JSON field names in the CPE ranges response differ from cveId, severity, or configurations, adjust the jsonPath expressions to match the actual serialized property names used in CpeRangeAdvisoryDto.
There was a problem hiding this comment.
Pull request overview
This PR adds a new API surface to return advisories along with the raw NVD CPE configuration/range constraints (including configuration node hierarchy and inclusive/exclusive version bounds) so downstream tooling can perform its own version-range translation.
Changes:
- Add
GET /api/cpe/rangesto return advisory metadata plus raw configuration/match constraints. - Extend
AdvisoryServiceto build range-focused DTOs that preserve config node structure and version boundaries. - Add service/controller tests covering the new ranges behavior.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/main/java/org/midnightbsd/advisory/ctl/api/CpeController.java | Adds /api/cpe/ranges endpoint with CPE parsing/validation and optional startDate. |
| src/main/java/org/midnightbsd/advisory/services/AdvisoryService.java | Adds cpeRangeDtos() and DTO construction preserving config node structure and match boundaries. |
| src/main/java/org/midnightbsd/advisory/dto/CpeRangeDto.java | New DTO representing raw NVD CPE match constraints including inclusive/exclusive bounds. |
| src/main/java/org/midnightbsd/advisory/dto/CpeConfigurationDto.java | New DTO representing a configuration node and its match list. |
| src/main/java/org/midnightbsd/advisory/dto/CpeRangeAdvisoryDto.java | New top-level DTO bundling advisory metadata with configuration nodes. |
| src/test/java/org/midnightbsd/advisory/ctl/CpeControllerTest.java | Adds MVC coverage for the new /api/cpe/ranges endpoint. |
| src/test/java/org/midnightbsd/advisory/services/AdvisoryServiceTest.java | Adds unit coverage asserting range bounds and config-node hierarchy are preserved. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
|



What changed
GET /api/cpe/rangesWhy
The existing version-matching endpoint reduces advisory data to a yes/no match. MidnightBSD's VuXML generator needs the original NVD boundaries so it can translate them into ranges using MidnightBSD package versions, revisions, and epochs instead of assuming FreeBSD package versions match.
Validation
git diff --checkTests were run with Java 21 in interpreted mode on MidnightBSD. JaCoCo was skipped because its agent triggers a host JVM JIT crash. Spotless was not run because the repository's existing google-java-format version is incompatible with the available JDK compiler APIs.
Summary by Sourcery
Add an API endpoint to expose raw NVD CPE configuration ranges for advisories and wire it through the advisory service and controller.
New Features:
Enhancements:
Tests: