Conversation
WalkthroughReplaces Joda-Time with java.time across modules; updates DTOs, repositories, services, controllers, JSP editors/views, and tests. Refactors OCPP WebSocket pipeline and routing (InvocationContext), introduces configuration Paths, replaces singleton mappers with injected ObjectMapper, and adjusts codegen/bindings to Java time. Multiple POM updates remove Joda dependencies and add test libs. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Client as Client (ChargePoint)
participant EP as WebSocketEndpoint (v12/15/16)
participant CFG as OcppConfiguration
participant IC as InvocationContext (per version)
participant P as IncomingPipeline
participant S as Serializer
participant D as Deserializer
participant X as Sender
participant SRV as CentralSystemService*
note over CFG: Boot-time wiring<br/>EnumMap<OcppVersion, InvocationContext>
Client->>EP: WebSocket CALL [JSON]
EP->>IC: toCommunicationContext(chargeBoxId, task)
IC->>IC: Build OcppJsonCall + FutureResponseContext
IC-->>EP: CommunicationContext
EP->>P: init(pipeline) then accept(context)
rect rgba(230,240,255,0.5)
P->>S: serialize outgoing CALL
S-->>P: JSON array message
P->>X: send over WebSocket
end
EP->>SRV: Dispatch via handler (on inbound)
SRV-->>EP: Result/Error
rect rgba(230,255,230,0.5)
EP->>D: deserialize incoming RESULT/ERROR
D-->>P: Populate response payload
end
P-->>Client: Complete FutureResponseContext
note over EP,SRV: Timestamps now Instant/OffsetDateTime
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120+ minutes Possibly related PRs
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
e7afd0b to
227a321
Compare
There was a problem hiding this comment.
Actionable comments posted: 25
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
steve/src/test/java/de/rwth/idsg/steve/OperationalTestSoapOCPP16.java (2)
493-519: Avoid flaky equality against DB timestamps by truncating both sides.Comparing startTimeStamp to t.getStartTimestamp() with equals may fail due to fractional seconds truncation in the DB. Truncate both to a common unit before asserting.
- var startTimeStamp = OffsetDateTime.now(); + var startTimeStamp = nowTs(); ... - Assertions.assertEquals(startTimeStamp, t.getStartTimestamp()); + Assertions.assertEquals( + startTimeStamp, + t.getStartTimestamp().truncatedTo(startTimeStamp.getNano() == 0 ? java.time.temporal.ChronoUnit.SECONDS + : java.time.temporal.ChronoUnit.MILLIS) + );Alternatively, if you standardize nowTs() to seconds, simply truncate DB values to seconds in the assertion:
- Assertions.assertEquals(startTimeStamp, t.getStartTimestamp()); + Assertions.assertEquals(startTimeStamp, t.getStartTimestamp().withNano(0));
545-566: Stop timestamp equality has the same precision hazard as start timestamp.Mirror the start-time fix for stopTimeStamp and equality.
- var stopTimeStamp = OffsetDateTime.now(); + var stopTimeStamp = nowTs(); ... - Assertions.assertEquals(stopTimeStamp, t.getStopTimestamp()); + Assertions.assertEquals(stopTimeStamp, t.getStopTimestamp().withNano(0));steve-api/src/main/java/de/rwth/idsg/steve/web/api/ApiControllerAdvice.java (1)
49-53: Ensure JavaTimeModule Is Registered for Jackson SerializationI didn’t find any registration of
JavaTimeModule(or equivalent JSR-310 support) in your customObjectMapperconfigurations. Without it,LocalDateTimeand otherjava.timetypes will either serialize as arrays or may fail depending on default settings.• No occurrences of
JavaTimeModule,jackson-datatype-jsr310, or use ofJackson2ObjectMapperBuilderwere detected in the API module or shared mapper utilities.
• Your controllers and services instantiate plainnew ObjectMapper()(e.g. inAjaxCallController,JsonObjectMapper, etc.) without registering the JSR-310 module.
• If you rely on Spring Boot’s auto-configuration, ensurecom.fasterxml.jackson.datatype:jackson-datatype-jsr310is on the classpath; otherwise you must manually register the module.Please add one of the following:
- Register the module on your shared mapper bean:
@Configuration public class JacksonConfig { @Bean public Jackson2ObjectMapperBuilderCustomizer jacksonCustomizer() { return builder -> builder .modules(new JavaTimeModule()) .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); } }- Or, when creating
ObjectMapperinstances directly:ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new JavaTimeModule()); mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);This will ensure all your
LocalDateTime(and other JSR-310 types) serialize to ISO-8601 strings rather than numeric arrays.steve/src/main/java/de/rwth/idsg/steve/repository/impl/OcppServerRepositoryImpl.java (1)
151-163: Enforce explicit timezone normalization intoLocalDateTimeThe current implementation of
DateTimeUtils.toLocalDateTime(OffsetDateTime dt)simply callsdt.toLocalDateTime(), which discards any offset information and retains only the local date-time fields. This means an incoming timestamp like2025-08-25T14:00+02:00becomes2025-08-25T14:00in the database, without adjusting to the server’s time zone.• Location:
steve-core/src/main/java/de/rwth/idsg/steve/utils/DateTimeUtils.java:48–52public static LocalDateTime toLocalDateTime(OffsetDateTime dt) { if (dt == null) { return null; } else { return dt.toLocalDateTime(); // <-- drops the offset, does not normalize } }• Usages in the repository (e.g., in
OcppServerRepositoryImpl.java,TaskStoreImpl.java) inherit this behavior whenever persisting or querying timestamps.Recommendation: Refactor
toLocalDateTimeto convert theOffsetDateTimeto the server’s (or configured) time zone before dropping the offset, for example:public static LocalDateTime toLocalDateTime(OffsetDateTime dt) { if (dt == null) { return null; } else { - return dt.toLocalDateTime(); + // Normalize to system default (or specified) zone, then strip offset + return dt.atZoneSameInstant(ZoneId.systemDefault()) + .toLocalDateTime(); } }This ensures that all persisted
LocalDateTimevalues reflect the same universal instant, adjusted to your chosen zone.
steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/UpdateTransactionParams.java
Show resolved
Hide resolved
steve-core/src/main/java/de/rwth/idsg/steve/utils/DateTimeUtils.java
Outdated
Show resolved
Hide resolved
steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/config/OcppWebSocketConfiguration.java
Outdated
Show resolved
Hide resolved
steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/config/OcppWebSocketConfiguration.java
Outdated
Show resolved
Hide resolved
| @Component | ||
| public final class Ocpp12TypeStore extends AbstractTypeStore { | ||
|
|
||
| public static final Ocpp12TypeStore INSTANCE = new Ocpp12TypeStore(); | ||
|
|
||
| private Ocpp12TypeStore() { | ||
| public Ocpp12TypeStore() { |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Ensure no stale references to the removed singleton remain
Search for Ocpp12TypeStore.INSTANCE or direct construction to prevent accidental bypassing of the Spring bean.
🏁 Script executed:
#!/bin/bash
# Find stale singleton or manual instantiations
rg -nP '\bOcpp12TypeStore\.INSTANCE\b|\bnew\s+Ocpp12TypeStore\s*\(' -g '!**/target/**'Length of output: 208
Replace direct instantiation of Ocpp12TypeStore in tests with Spring‐managed bean
The search revealed one remaining manual instantiation in your test suite, which bypasses Spring’s bean lifecycle:
- File: steve/src/test/java/de/rwth/idsg/steve/TypeStoreTest.java
Line 37:Ocpp12TypeStore typeStore = new Ocpp12TypeStore();
Please update this to use Spring injection instead of new so you’re testing the same bean configuration that your application uses. For example:
– Ocpp12TypeStore typeStore = new Ocpp12TypeStore();
+ @Autowired
+ private Ocpp12TypeStore typeStore;Or, if you prefer constructor injection in JUnit 5:
@SpringBootTest
class TypeStoreTest {
private final Ocpp12TypeStore typeStore;
@Autowired
TypeStoreTest(Ocpp12TypeStore typeStore) {
this.typeStore = typeStore;
}
// … tests …
}This ensures your tests exercise the Spring‐managed singleton and avoid accidentally testing an uninitialized instance.
🤖 Prompt for AI Agents
In
steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/ocpp12/Ocpp12TypeStore.java
around lines 28-31 and in
steve/src/test/java/de/rwth/idsg/steve/TypeStoreTest.java (line ~37), replace
the manual instantiation "new Ocpp12TypeStore()" in the test with a
Spring-managed bean: annotate the test class with the appropriate Spring test
annotation (e.g., @SpringBootTest), inject Ocpp12TypeStore via @Autowired (field
or constructor injection) and remove the direct new call so the test uses the
Spring singleton and lifecycle.
| OCPP_TAG_ACTIVITY.EXPIRY_DATE.isNull() | ||
| .or(OCPP_TAG_ACTIVITY.EXPIRY_DATE.greaterThan(LocalDateTime.now())) | ||
| ); |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Same here: keep the types aligned and prefer UTC for comparisons.
Aligns with the rest of the migration and UI assumptions (UTC for LDT comparisons).
Apply:
- OCPP_TAG_ACTIVITY.EXPIRY_DATE.isNull()
- .or(OCPP_TAG_ACTIVITY.EXPIRY_DATE.greaterThan(LocalDateTime.now()))
+ OCPP_TAG_ACTIVITY.EXPIRY_DATE.isNull()
+ .or(OCPP_TAG_ACTIVITY.EXPIRY_DATE.greaterThan(OffsetDateTime.now(ZoneOffset.UTC)))📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| OCPP_TAG_ACTIVITY.EXPIRY_DATE.isNull() | |
| .or(OCPP_TAG_ACTIVITY.EXPIRY_DATE.greaterThan(LocalDateTime.now())) | |
| ); | |
| OCPP_TAG_ACTIVITY.EXPIRY_DATE.isNull() | |
| .or(OCPP_TAG_ACTIVITY.EXPIRY_DATE.greaterThan(OffsetDateTime.now(ZoneOffset.UTC))) | |
| ); |
🤖 Prompt for AI Agents
In
steve/src/main/java/de/rwth/idsg/steve/repository/impl/OcppTagRepositoryImpl.java
around lines 123-125, the expiry-date predicate uses LocalDateTime.now() (system
default zone) which misaligns types and zones; change the comparison to use UTC
(e.g. LocalDateTime.now(ZoneOffset.UTC)) or, preferably, use a timezone-aware
type (OffsetDateTime/Instant) for both the DB column and the comparison so the
types match, then replace the .greaterThan(...) call with a comparison against
the UTC instant/offset datetime while keeping the existing null check.
| .and(OCPP_TAG_ACTIVITY.EXPIRY_DATE.isNull() | ||
| .or(OCPP_TAG_ACTIVITY.EXPIRY_DATE.greaterThan(DateTime.now()))) | ||
| .or(OCPP_TAG_ACTIVITY.EXPIRY_DATE.greaterThan(LocalDateTime.now()))) | ||
| .fetch(OCPP_TAG_ACTIVITY.ID_TAG); |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Active tags query: align EXPIRY_DATE comparison with OffsetDateTime.
This keeps semantics consistent across methods.
Apply:
- .and(OCPP_TAG_ACTIVITY.EXPIRY_DATE.isNull()
- .or(OCPP_TAG_ACTIVITY.EXPIRY_DATE.greaterThan(LocalDateTime.now())))
+ .and(
+ OCPP_TAG_ACTIVITY.EXPIRY_DATE.isNull()
+ .or(OCPP_TAG_ACTIVITY.EXPIRY_DATE.greaterThan(OffsetDateTime.now(ZoneOffset.UTC)))
+ )📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| .and(OCPP_TAG_ACTIVITY.EXPIRY_DATE.isNull() | |
| .or(OCPP_TAG_ACTIVITY.EXPIRY_DATE.greaterThan(DateTime.now()))) | |
| .or(OCPP_TAG_ACTIVITY.EXPIRY_DATE.greaterThan(LocalDateTime.now()))) | |
| .fetch(OCPP_TAG_ACTIVITY.ID_TAG); | |
| .and( | |
| OCPP_TAG_ACTIVITY.EXPIRY_DATE.isNull() | |
| .or(OCPP_TAG_ACTIVITY.EXPIRY_DATE.greaterThan(OffsetDateTime.now(ZoneOffset.UTC))) | |
| ) | |
| .fetch(OCPP_TAG_ACTIVITY.ID_TAG); |
🤖 Prompt for AI Agents
In
steve/src/main/java/de/rwth/idsg/steve/repository/impl/OcppTagRepositoryImpl.java
around lines 190 to 192, the active tags query compares
OCPP_TAG_ACTIVITY.EXPIRY_DATE to LocalDateTime.now(), causing a type/semantics
mismatch; replace LocalDateTime.now() with OffsetDateTime.now() (or otherwise
convert to the same OffsetDateTime type used by EXPIRY_DATE), update imports to
java.time.OffsetDateTime if missing, and ensure the jOOQ comparison uses the
OffsetDateTime value so the .greaterThan(...) comparison is type-consistent with
EXPIRY_DATE.
| private static class TransactionMapper implements RecordMapper<Record12<Integer, String, Integer, String, | ||
| LocalDateTime, String, LocalDateTime, String, String, Integer, Integer, TransactionStopEventActor>, Transaction> { | ||
| @Override | ||
| public Transaction map(Record12<Integer, String, Integer, String, DateTime, String, DateTime, String, String, Integer, Integer, TransactionStopEventActor> r) { | ||
| public Transaction map(Record12<Integer, String, Integer, String, LocalDateTime, String, LocalDateTime, String, | ||
| String, Integer, Integer, TransactionStopEventActor> r) { |
There was a problem hiding this comment.
Break long generic signature to satisfy Checkstyle line-length (120) and improve readability
- private static class TransactionMapper implements RecordMapper<Record12<Integer, String, Integer, String,
- LocalDateTime, String, LocalDateTime, String, String, Integer, Integer, TransactionStopEventActor>, Transaction> {
+ private static class TransactionMapper implements
+ RecordMapper<Record12<Integer, String, Integer, String, LocalDateTime, String, LocalDateTime, String,
+ String, Integer, Integer, TransactionStopEventActor>, Transaction> {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private static class TransactionMapper implements RecordMapper<Record12<Integer, String, Integer, String, | |
| LocalDateTime, String, LocalDateTime, String, String, Integer, Integer, TransactionStopEventActor>, Transaction> { | |
| @Override | |
| public Transaction map(Record12<Integer, String, Integer, String, DateTime, String, DateTime, String, String, Integer, Integer, TransactionStopEventActor> r) { | |
| public Transaction map(Record12<Integer, String, Integer, String, LocalDateTime, String, LocalDateTime, String, | |
| String, Integer, Integer, TransactionStopEventActor> r) { | |
| private static class TransactionMapper implements | |
| RecordMapper<Record12<Integer, String, Integer, String, LocalDateTime, String, LocalDateTime, String, | |
| String, Integer, Integer, TransactionStopEventActor>, Transaction> { | |
| @Override | |
| public Transaction map(Record12<Integer, String, Integer, String, LocalDateTime, String, LocalDateTime, String, | |
| String, Integer, Integer, TransactionStopEventActor> r) { |
🧰 Tools
🪛 GitHub Check: checkstyle
[failure] 384-384: [checkstyle] steve/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java#L384 <com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck>
Line is longer than 120 characters (found 126).
There was a problem hiding this comment.
Actionable comments posted: 21
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java (2)
21-21: Remove incorrect Guava MediaType importAfter the fix above, this import is both incorrect (no
SOAP_XML_UTF_8in Guava) and unused.-import com.google.common.net.MediaType;
118-146: Build a fresh request inside toRepeat() to avoid cross-thread mutationConstruct the SOAP request locally per iteration, then execute. This also keeps lines within the 120-char limit.
@Override public void toRepeat() { var mvr = threadLocalClient.get().meterValues( new MeterValuesRequest() .withConnectorId(connectorId) .withTransactionId(transactionId) .withMeterValue( new MeterValue() .withTimestamp(stopDateTime) .withSampledValue( new SampledValue() .withMeasurand(Measurand.ENERGY_ACTIVE_IMPORT_REGISTER) .withValue("555") .withUnit(UnitOfMeasure.WH))), chargeBoxId ); assertThat(mvr).isNotNull(); try { - httpClient.execute(req, httpResponse -> { + var req = RequestBuilder.post(path) + .addHeader("SOAPAction", "urn://Ocpp/Cs/2015/10/StopTransaction") + .setEntity(new StringEntity(body, contentType)) + .build(); + httpClient.execute(req, httpResponse -> { if (httpResponse.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { throw new RuntimeException("Not OK"); } return null; }); } catch (Exception e) { e.printStackTrace(); } }
♻️ Duplicate comments (8)
steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/connectorStatus.jsp (2)
96-96: Use of f:offsetToMillis for OffsetDateTime — correct and consistent with the taglibThis addresses the previous feedback about using the correct function for OffsetDateTime.
96-96: Fix Checkstyle failure: remove tab character at L96CI flagged FileTabCharacterCheck on this exact line. Replace the leading tab with spaces to unblock the build.
Apply:
- <td data-sort-value="${f:offsetToMillis(cs.statusTimestamp)}">${cs.timeStamp}</td> + <td data-sort-value="${f:offsetToMillis(cs.statusTimestamp)}">${cs.timeStamp}</td>Optional verification to ensure no other tabs remain in this JSP:
#!/bin/bash # Show any remaining tab characters in this JSP rg -nP '\t' steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/connectorStatus.jspAlso confirm cs.statusTimestamp is guaranteed non-null; otherwise this function will throw. If null is possible, consider a safe fallback (e.g., leave data-sort-value empty).
steve-ocpp/src/test/java/de/rwth/idsg/ocpp/jaxb/JavaDateTimeConverterTest.java (3)
65-65: Fix Checkstyle ParenPad violations and variable name typos.
173-173: Correct DST expectations for America/New_York on June 30, 2022.The test expectations use UTC−05:00 (EST) but America/New_York observes Eastern Daylight Time (UTC−04:00) on June 30, 2022. These will cause test failures when run.
Also applies to: 177-177, 181-181, 185-185
196-196: Correct DST expectations for America/New_York on June 30, 2022.Similar to the previous occurrences, these test expectations use UTC−05:00 instead of the correct UTC−04:00 for EDT on June 30, 2022.
Also applies to: 200-200
steve-ocpp/src/main/java/de/rwth/idsg/ocpp/DateTimeUtils.java (2)
21-23: Replace Lombok with explicit private constructor for Checkstyle compliance.Also applies to: 32-33
94-95: Use instant-based offset calculation for DST safety.steve/src/test/java/de/rwth/idsg/steve/issues/Issue81.java (1)
75-79: Excellent improvements to generate valid OCPP values.The changes address the previous review feedback perfectly:
connectorIdnow uses positive values (1-7) suitable for OCPPmeterStartuses non-negative values (0-999,999)- Timestamp uses UTC to avoid timezone-dependent behavior
🧹 Nitpick comments (46)
steve/src/test/java/de/rwth/idsg/steve/ocpp/OcppVersionTest.java (2)
31-33: Minor naming/readability nit and toolchain check forvar
- Rename locals to be more intention‑revealing.
- Ensure CI/toolchain targets Java 10+ (commonly 17+) since
varis used in tests.Apply this small refactor:
- var toTest = input.getValue(); - var inputBack = OcppVersion.fromValue(toTest); - assertThat(inputBack).isEqualTo(input); + var value = input.getValue(); + var roundTrip = OcppVersion.fromValue(value); + assertThat(roundTrip).isEqualTo(input);If you still support compiling tests with Java 8 in some environments, replace
varwith the explicit types.
42-43: Collapse property checks for clearer failuresA single extracting-based assertion yields a tighter failure message and implicitly guards against null actuals.
- assertThat(protocol.getTransport()).isEqualTo(transport); - assertThat(protocol.getVersion()).isEqualTo(version); + assertThat(protocol) + .extracting(p -> p.getTransport(), p -> p.getVersion()) + .containsExactly(transport, version);steve/src/test/java/de/rwth/idsg/steve/ocpp/OcppProtocolTest.java (1)
31-33: Prefer identity check for enums; minor ergonomicsSince OcppProtocol is an enum, identity is stricter and communicates intent better.
Apply this focused diff:
- assertThat(inputBack).isEqualTo(input); + assertThat(inputBack).isSameAs(input);Optional polish (outside selected lines):
- Give the parameterized test a descriptive name to aid failures:
@ParameterizedTest(name = "[{index}] fromCompositeValue round-trips {0}")
- Add a negative-case to document behavior for invalid input (adjust expected exception to your contract):
import static org.assertj.core.api.Assertions.assertThatThrownBy; import org.junit.jupiter.params.provider.NullAndEmptySource; import org.junit.jupiter.params.provider.ValueSource; @ParameterizedTest @NullAndEmptySource @ValueSource(strings = {"invalid", " OCPP1.6 ", "Ocpp_2.0"}) void fromCompositeValue_rejects_invalid_values(String invalid) { assertThatThrownBy(() -> OcppProtocol.fromCompositeValue(invalid)) .isInstanceOf(IllegalArgumentException.class); }steve/src/test/java/de/rwth/idsg/steve/utils/StringUtilsTest.java (4)
59-67: Nit: inline simple assertions to reduce noiseFor null/empty inputs you can inline the call to improve readability.
Apply either or both of these diffs:
- var val = StringUtils.joinByComma(null); - assertThat(val).isNull(); + assertThat(StringUtils.joinByComma(null)).isNull();- var val = StringUtils.joinByComma(List.of()); - assertThat(val).isNull(); + assertThat(StringUtils.joinByComma(List.of())).isNull();
71-85: Join semantics are asserted well; consider adding coverage for trimming/case sensitivityCurrent tests cover 1, 2, and duplicate elements with order preservation. If
StringUtils.joinByCommais expected to trim items and deduplicate case-insensitively (or reject null elements), add targeted tests for:
- Elements with surrounding spaces: [" hey ", "ho "] -> "hey,ho"
- Case-variants treated as duplicates or not: ["Hey", "hey"] -> expected?
- Null/blank elements handling
I can draft these tests if you confirm the intended behavior.
101-104: Make the single-element assertion more idiomaticYou can express intent in one line with AssertJ.
- assertThat(val).hasSize(1); - assertThat(val.get(0)).isEqualTo("1one"); + assertThat(val).containsExactly("1one");
108-112: Avoid sorting/Stream.toList by asserting order-insensitivelyIf the order of
splitByCommais not guaranteed, prefer an order-agnostic assertion and drop the extra sort/toList call. This also avoids relying on Stream.toList (JDK 16+).- var sortedVal = val.stream().sorted().toList(); - assertThat(sortedVal).containsExactly("1one", "2two"); + assertThat(val).containsExactlyInAnyOrder("1one", "2two");If the implementation guarantees insertion order, assert directly without sorting:
- var sortedVal = val.stream().sorted().toList(); - assertThat(sortedVal).containsExactly("1one", "2two"); + assertThat(val).containsExactly("1one", "2two");To decide between the two assertions above, please confirm whether
StringUtils.splitByCommapreserves input order.steve-ocpp-websocket/src/test/java/de/rwth/idsg/steve/ocpp/ws/custom/CustomStringModuleTest.java (3)
38-41: Reduce duplication with a parameterized test.All three methods follow the same arrange/act/assert shape. Consider a single JUnit 5 parameterized test fed by a method source for input/expected pairs. This keeps intent tight and avoids future drift.
Example sketch:
@ParameterizedTest @MethodSource("stringCases") void shouldEscapeHtmlAsExpected(String raw, String expectedEscaped) throws Exception { var output = mapper.writeValueAsString(new SimpleJsonModel(raw)); assertThat(output).isEqualTo("{\"someText\":\"" + expectedEscaped + "\"}"); } private static Stream<Arguments> stringCases() { return Stream.of( Arguments.of("normal string", "normal string"), Arguments.of("<a href=\"link\">Some link</a>", "<a href="link">Some link</a>"), Arguments.of("<script src=\"http://someurl.com/script.js\"/>", "<script src="http://someurl.com/script.js"/>") ); }Also applies to: 45-48, 52-55
45-48: Make assertions resilient to JSON formatting drift.Asserting the whole JSON string can become brittle across Jackson upgrades or mapper tweaks. Prefer asserting the field value extracted from JSON.
Apply this diff to this block (same pattern can be used in the other tests):
- var output = mapper.writeValueAsString(input); - assertThat(output).isEqualTo("{\"someText\":\"<a href="link">Some link</a>\"}"); + var output = mapper.writeValueAsString(input); + var node = mapper.readTree(output); + assertThat(node.get("someText").asText()) + .isEqualTo("<a href="link">Some link</a>");
52-55: Consider a few extra edge cases for completeness.Optional, but adding cases like:
- Single quotes in attributes: ...
- Mixed content / nested tags
- Angle brackets without forming a tag (e.g., 2 < 3)
will harden the contract of your custom string module.steve/src/test/java/de/rwth/idsg/steve/utils/TransactionStopServiceHelperTest.java (4)
30-33: Test documents ceil behavior for floating-to-int conversionInput "11.01" expecting "12" implies ceiling semantics for positive values. Consider adding boundary cases to lock in intent (e.g., "11.00" -> "11", "11.99" -> "12") and a negative input if those are possible.
@Test public void testFloatingStringToIntString() { - var actual = TransactionStopServiceHelper.floatingStringToIntString("11.01"); - assertThat(actual).isEqualTo("12"); + assertThat(TransactionStopServiceHelper.floatingStringToIntString("11.01")).isEqualTo("12"); + assertThat(TransactionStopServiceHelper.floatingStringToIntString("11.00")).isEqualTo("11"); + assertThat(TransactionStopServiceHelper.floatingStringToIntString("11.99")).isEqualTo("12"); }
63-64: Confirm acceptance criteria for unit-less numeric valuesAccepting a plain numeric string without unit as energy may be intentional for lenient chargers. If this is required for interoperability, keep it; otherwise consider requiring a unit for stricter validation.
72-73: Decimal numeric without unit accepted — verify spec alignmentSimilar to the previous case but with decimals; ensure this matches OCPP data handling expectations in
TransactionStopServiceHelper.isEnergyValue.
54-164: Reduce duplication with parameterized tests for isEnergyValue matrixThese cases form a neat matrix over (format, unit, measurand, value). Converting to a JUnit 5
@ParameterizedTestwith@MethodSourcewill cut duplication and improve readability/coverage.+@ParameterizedTest +@MethodSource("energyCases") +void testIsEnergy_matrix(TransactionDetails.MeterValues mv, boolean expected) { + assertThat(TransactionStopServiceHelper.isEnergyValue(mv)).isEqualTo(expected); +} + +static Stream<Arguments> energyCases() { + return Stream.of( + Arguments.of(TransactionDetails.MeterValues.builder().build(), false), + Arguments.of(TransactionDetails.MeterValues.builder().value("22").build(), true), + Arguments.of(TransactionDetails.MeterValues.builder().value("22.5").build(), true), + Arguments.of(TransactionDetails.MeterValues.builder().value("gibberish").format("SignedData").build(), false), + Arguments.of(TransactionDetails.MeterValues.builder().value("22").format("Raw").unit("Celsius").build(), false), + Arguments.of(TransactionDetails.MeterValues.builder().value("22").format("Raw").unit("Wh").measurand("Current.Export").build(), false), + Arguments.of(TransactionDetails.MeterValues.builder().value("22").format(null).unit("Wh").measurand("Energy.Active.Import.Register").build(), true), + Arguments.of(TransactionDetails.MeterValues.builder().value("22").format("Raw").unit("Wh").measurand("Energy.Active.Import.Register").build(), true), + Arguments.of(TransactionDetails.MeterValues.builder().value("22").format("Raw").unit("kWh").measurand("Energy.Active.Import.Register").build(), true), + Arguments.of(TransactionDetails.MeterValues.builder().value("22a819()b").format("Raw").unit("Wh").measurand("Energy.Active.Import.Register").build(), false), + Arguments.of(TransactionDetails.MeterValues.builder().value("22").unit("Wh").build(), true) + ); +}steve/src/test/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandlerTest.java (6)
43-45: Use RFC-sanctioned example domain in tests; minor naming nitPrefer
example.comto avoid real domains in tests. Also, consider renaming the method to “successful” (one “l”) for consistency.Apply within this hunk:
- var in = "https://www.google.com/steve/websocket/CentralSystemService/BBEI12"; + var in = "https://example.com/steve/websocket/CentralSystemService/BBEI12";
50-52: Nit: “suffix” reads clearer than “postfix”Method name
testGetLastBitFromUrl_noPostfixcould be..._noSuffixfor more idiomatic terminology. No functional change implied.
64-66: Confirm intent: percent character is preserved (no URL-decoding)This expectation encodes a contract that IDs containing
%are not decoded. Please confirm the handshake logic downstream expects the raw segment and won’t later double-decode or reject it. If decoding is expected, adjust to test%25cases instead.I can add companion tests for
%25-encoded input vs literal%to lock down behavior.
78-80: Add a case for encoded spaces (%20)You cover literal spaces; URLs typically carry spaces as
%20. Add one more assertion to clarify whether decoding happens.Proposed addition:
@Test public void testGetLastBitFromUrl_successWithEncodedSpace() { var in = "/steve/websocket/CentralSystemService/BBE%20I12"; var out = OcppWebSocketHandshakeHandler.getLastBitFromUrl(in); // Decide and assert one of these based on intended contract: // assertThat(out).isEqualTo("BBE%20I12"); // keep as-is (no decode) // assertThat(out).isEqualTo("BBE I12"); // decode percent-encoding }
85-87: Definition of “last bit” includes slashes — verify API contractExpecting
"889/BBEI12"suggests “last bit” means “everything afterCentralSystemService/” rather than the final path segment. If that’s intentional, consider documenting it ingetLastBitFromUrlJavadoc; if not, adjust implementation/tests to return only the last segment ("BBEI12").I can draft the Javadoc and add a dedicated test for multiple nested segments to make this explicit.
92-94: Reduce duplication with a parameterized testMultiple methods follow the arrange-act-assert pattern with only input/expected differing. Consider consolidating via JUnit 5 parameterized tests for maintainability.
Example (drop-in replacement for several single-case tests):
@org.junit.jupiter.params.ParameterizedTest @org.junit.jupiter.params.provider.CsvSource({ "'', ''", "null, ''", "'https://example.com/steve/websocket/CentralSystemService/BBEI12', 'BBEI12'", "'/steve/websocket/CentralSystemService/', ''", "'/steve/websocket/CentralSystemService/BBEI12', 'BBEI12'", "'/steve/websocket/CentralSystemService/BBE%I12', 'BBE%I12'", "'/steve/websocket/CentralSystemService/BBE-I12', 'BBE-I12'", "'/steve/websocket/CentralSystemService/BBE I12', 'BBE I12'", "'/steve/websocket/CentralSystemService/889/BBEI12', '889/BBEI12'", "'/steve/websocket/CentralSystemService/%889 /BBEI12-', '%889 /BBEI12-'" }) void testGetLastBitFromUrl_cases(String in, String expected) { var normalizedIn = "null".equals(in) ? null : in; var out = OcppWebSocketHandshakeHandler.getLastBitFromUrl(normalizedIn); assertThat(out).isEqualTo(expected); }Note: keep any decoding/encoding decisions consistent with earlier comments.
steve/src/test/java/de/rwth/idsg/steve/TypeStoreTest.java (3)
41-41: Prefer isSameAs for Class comparisons (nit)For clarity/consistency, use identity checks when asserting on Class objects.
- assertThat(actionResponse.getResponseClass()).isEqualTo(ocpp.cp._2010._08.ResetResponse.class); + assertThat(actionResponse.getResponseClass()).isSameAs(ocpp.cp._2010._08.ResetResponse.class); - assertThat(actionResponse.getResponseClass()).isEqualTo(ocpp.cp._2012._06.UpdateFirmwareResponse.class); + assertThat(actionResponse.getResponseClass()).isSameAs(ocpp.cp._2012._06.UpdateFirmwareResponse.class); - assertThat(actionResponse.getResponseClass()).isEqualTo(ocpp.cp._2015._10.UpdateFirmwareResponse.class); + assertThat(actionResponse.getResponseClass()).isSameAs(ocpp.cp._2015._10.UpdateFirmwareResponse.class);Also applies to: 54-54, 67-67
34-45: Reduce repetition across versioned tests (optional)Three nearly identical tests differ only by versioned types and action names. Consider a small helper or a parameterized test to DRY this up. It will make it easier to add OCPP 2.0.x later without duplicating assertions.
Also applies to: 48-58, 61-71
38-45: Add a negative-path assertion for unknown actionsA quick check that findRequestClass("Unknown") behaves as expected (null or throws) would lock in error-handling behavior and prevent silent regressions.
I can add a small follow-up test that asserts the chosen behavior across all three versions—tell me which contract you expect (null vs exception).
Also applies to: 51-58, 64-71
steve/src/test/java/de/rwth/idsg/steve/web/validation/IdTagValidatorTest.java (4)
31-31: private final field: good; consider static final (minor).Making the validator
static finalwould avoid re-instantiation per test instance; harmless either way in JUnit 5.- private final IdTagValidator validator = new IdTagValidator(); + private static final IdTagValidator validator = new IdTagValidator();
50-51: Mixed case: covered; optionally reduce duplication.Two assertions are fine; consider parameterizing or looping for consistency with other tests that have multiple inputs.
56-59: Letters+numbers: covered; minor DRY improvement.You can collapse the three assertions to a tiny loop for brevity (apply similarly to other multi-assert methods if you like).
- assertThat(validator.isValid("test12", null)).isTrue(); - assertThat(validator.isValid("89test", null)).isTrue(); - assertThat(validator.isValid("te9s0t", null)).isTrue(); + for (String s : java.util.List.of("test12", "89test", "te9s0t")) { + assertThat(validator.isValid(s, null)).isTrue(); + }
40-46: Optional: collapse repetitive positives into a single parameterized test.Reduces boilerplate and gives clearer failure output for the specific input case. If you’re open to it, replace multiple methods with one parameterized test (JUnit 5):
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; @ParameterizedTest(name = "[{index}] valid: \"{0}\"") @ValueSource(strings = { "test","TEST","TesT","tEst", "test12","89test","te9s0t", ".test","test.","te..st", "-test","test-","te--st", "_test","test_","te__st", ":test","test:", "te::st", "VID:00XXXXXXXXXX", "#test","test#","te##st","#FreeCharging", "1t.E-S_:t20#" }) void validSamples_areAccepted(String input) { assertThat(validator.isValid(input, null)).isTrue(); }Also applies to: 50-51, 56-59, 63-66, 70-73, 77-80, 87-92, 96-99, 102-102, 107-107
steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java (2)
92-108: OCPP 1.5 flow is correct; minor consistency nit on local var typeBehavior looks right and mirrors the 1.2 test. Small consistency improvement: you switched to var across the file, but kept an explicit type for auth on Line 102. Consider using var for uniformity.
Apply this minimal diff:
- ocpp.cs._2012._06.AuthorizeResponse auth = client.authorize( + var auth = client.authorize( new ocpp.cs._2012._06.AuthorizeRequest() .withIdTag(REGISTERED_OCPP_TAG), REGISTERED_CHARGE_BOX_ID);
115-132: Unreachable assertions inside the thrown-by block; trim and tighten the 1.6 testThe WebServiceException is expected from authorize against an unknown station. Assertions on auth after authorize (Lines 130-131) are unreachable if the exception is thrown as intended. Recommend asserting boot outside, and limiting the thrown-by block to just authorize.
Apply this refactor to remove dead assertions and make the intent explicit:
- assertThatExceptionOfType(WebServiceException.class).isThrownBy(() -> { - var boot = client.bootNotification( - new ocpp.cs._2015._10.BootNotificationRequest() - .withChargePointVendor(getRandomString()) - .withChargePointModel(getRandomString()), - getRandomString()); - assertThat(boot).isNotNull(); - assertThat(boot.getStatus()).isEqualTo(ocpp.cs._2015._10.RegistrationStatus.REJECTED); - - var auth = client.authorize( - new ocpp.cs._2015._10.AuthorizeRequest() - .withIdTag(getRandomString()), - getRandomString()); - assertThat(auth).isNotNull(); - assertThat(auth.getIdTagInfo().getStatus()).isEqualTo(ocpp.cs._2015._10.AuthorizationStatus.INVALID); - }); + var boot = client.bootNotification( + new ocpp.cs._2015._10.BootNotificationRequest() + .withChargePointVendor(getRandomString()) + .withChargePointModel(getRandomString()), + getRandomString()); + assertThat(boot).isNotNull(); + assertThat(boot.getStatus()).isEqualTo(ocpp.cs._2015._10.RegistrationStatus.REJECTED); + + assertThatExceptionOfType(WebServiceException.class).isThrownBy(() -> + client.authorize( + new ocpp.cs._2015._10.AuthorizeRequest() + .withIdTag(getRandomString()), + getRandomString() + ) + );If you also want to validate the “INVALID” status path explicitly, consider a separate, non-exceptional path test against a station setup that returns a normal response.
steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java (3)
176-184: Nit: fix test method nameMethod name is “tesWithUnauthorizedStation”; consider renaming to “testWithUnauthorizedStation” for consistency.
- public void tesWithUnauthorizedStation() { + public void testWithUnauthorizedStation() {
136-136: Prefer fail() with a descriptive message in error callbacksProviding context on failure speeds up debugging when the error consumer is invoked unexpectedly.
Apply across the listed lines:
- error -> fail() + error -> fail("Unexpected error: " + error)If you prefer specific messages, use the diffs in my earlier comments tailored per test.
Also applies to: 143-143, 198-198, 112-112, 119-119, 88-88, 95-95
76-99: Optional: de-duplicate OCPP 1.2/1.5/1.6 tests via @ParameterizedTestThese three tests share the same flow (start, BootNotification, Authorize). A parameterized test with Arguments (version, request/response classes, expected statuses) would reduce duplication and ease future additions.
Offer to provide a parameterized template if you want it in this PR.
Also applies to: 100-123, 124-147
steve/src/test/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidatorTest.java (1)
31-31: Consider making the validator a static final field.JUnit 5 creates a new test instance per method by default; making this a static final field avoids per-instance allocation. Low impact either way.
- private final ChargeBoxIdValidator validator = new ChargeBoxIdValidator(); + private static final ChargeBoxIdValidator validator = new ChargeBoxIdValidator();steve/src/test/java/de/rwth/idsg/steve/StressTest.java (1)
90-93: Time source in tests: consider a controllable clock for determinism.Using OffsetDateTime.now() directly is fine here, but if flakiness shows up in time-based assertions elsewhere, consider injecting a Clock/Supplier to make timestamps deterministic under load.
steve/src/test/java/de/rwth/idsg/steve/StressTestJsonOCPP16.java (2)
95-96: Preserve error context in failure callbacks.
fail()without a message drops the exception details. Include the error to aid debugging of flaky stress tests.Example change (apply similarly to each callback):
- error -> fail() + error -> fail("BootNotification failed: " + error)Also applies to: 113-114, 125-126, 149-150, 163-164, 173-174, 184-185, 195-196
203-205: Avoid ThreadLocal retention after close.Call
remove()after closing to prevent lingering references when threads are reused.public void afterRepeat() { - threadLocalChargePoint.get().close(); + threadLocalChargePoint.get().close(); + threadLocalChargePoint.remove(); }steve/src/test/java/de/rwth/idsg/steve/issues/Issue73Fix.java (5)
33-33: Use UTC for OCPP timestamps and confirm java.time mappingOCPP 1.6 timestamps are expected to be UTC. Using OffsetDateTime.now() will use the local offset; prefer UTC explicitly. Also, please double-check that the generated OCPP classes now accept java.time types (as intended by this PR).
Apply this diff:
@@ -import java.time.OffsetDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; @@ - .withTimestamp(OffsetDateTime.now()) + .withTimestamp(OffsetDateTime.now(ZoneOffset.UTC))If you also want deterministic tests, consider injecting a Clock (Clock.fixed) and calling OffsetDateTime.now(clock).
Also applies to: 115-116
78-80: Ensure distinct charge box IDs (low probability collision guard)getRandomString() should already avoid collisions, but a quick assertion keeps intent explicit and protects against edge cases.
- var chargeBox1 = getRandomString(); - var chargeBox2 = getRandomString(); + var chargeBox1 = getRandomString(); + var chargeBox2 = getRandomString(); + assertThat(chargeBox1).isNotEqualTo(chargeBox2);
21-21: Prefer JDK collections over Guava where possibleSince you’re already on modern Java, List.of reduces dependencies and is perfectly suited here.
-import com.google.common.collect.Lists; @@ - sendBoot(client, Lists.newArrayList(chargeBox1, chargeBox2)); + sendBoot(client, List.of(chargeBox1, chargeBox2));Also applies to: 81-81
93-101: BootNotification: optionally assert more fields for stronger coverageIf available in your generated classes, also assert currentTime and interval to catch regressions in heartbeat scheduling.
assertThat(boot).isNotNull(); assertThat(boot.getStatus()).isEqualTo(RegistrationStatus.ACCEPTED); + // Optional stronger checks: + assertThat(boot.getCurrentTime()).isNotNull(); + assertThat(boot.getInterval()).isGreaterThan(0);
62-72: Consider enabling try-with-resources by making Application AutoCloseableThe nested
finallyblocks ensure that bothapp.stop()and__DatabasePreparer__.cleanUp()always run—well done. Currently,steve/src/main/java/de/rwth/idsg/steve/Application.javadefines:public class Application { // … private final JettyServer server; // … }with no
implements AutoCloseableclause. To simplify the shutdown logic, you can optionally refactorApplicationto implementAutoCloseable:• steve/src/main/java/de/rwth/idsg/steve/Application.java
-public class Application { +public class Application implements AutoCloseable { private final JettyServer server; // … + @Override + public void close() { + stop(); + } }• Call site in Issue73Fix.java
- var app = new Application(config); - try { - app.start(); - test(); - } finally { - try { - app.stop(); - } finally { - __DatabasePreparer__.cleanUp(); - } - } + try (var app = new Application(config)) { + app.start(); + test(); + } finally { + __DatabasePreparer__.cleanUp(); + }This optional refactor reduces nesting and leverages the built-in resource-management pattern.
steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java (5)
110-116: Simplify ThreadLocal initializationUse
ThreadLocal.withInitialto avoid manual seeding inbeforeRepeat(). IfbeforeRepeat()is required by the interface, keep it as a no-op.- private final ThreadLocal<CentralSystemService> threadLocalClient = new ThreadLocal<>(); + private final ThreadLocal<CentralSystemService> threadLocalClient = + ThreadLocal.withInitial(() -> getForOcpp16(path)); @Override public void beforeRepeat() { - threadLocalClient.set(getForOcpp16(path)); + // no-op: initialized via withInitial() }
94-97: Strengthen assertion on transaction idGuard against zero/negative transaction ids to catch subtle failures earlier.
- var transactionId = start.getTransactionId(); + var transactionId = start.getTransactionId(); + assertThat(transactionId).isPositive();
162-176: Prefer unique MessageID per SOAP requestWS-Addressing
MessageIDshould be unique. Using a static UUID in a stress test can degrade diagnostics or violate expectations on some stacks.- "<MessageID xmlns=\"http://www.w3.org/2005/08/addressing\">urn:uuid:47c9e1d9-a278-4e9c-8f08-565c29d86167</MessageID>" + + "<MessageID xmlns=\"http://www.w3.org/2005/08/addressing\">urn:uuid:" + java.util.UUID.randomUUID() + "</MessageID>" +If you prefer imports, add:
+import java.util.UUID;and change to
UUID.randomUUID().
171-175: Be explicit about timestamp formatting
OffsetDateTime#toString()is ISO-8601, but being explicit removes ambiguity (e.g., fractional seconds). This also documents the expected wire format.- "<timestamp>" + stop + "</timestamp>" + + "<timestamp>" + stop.format(java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME) + "</timestamp>" +If you prefer imports, add:
+import java.time.format.DateTimeFormatter;and use
DateTimeFormatter.ISO_OFFSET_DATE_TIME.
101-104: Optional: encode SOAPAction in Content-Type (SOAP 1.2)Some SOAP 1.2 servers prefer/require the
actionparameter onapplication/soap+xml. You can set it viawithParameters(...). Keep theSOAPActionheader for broad compatibility if needed.- var contentType = ContentType.create("application/soap+xml", StandardCharsets.UTF_8); + var contentType = ContentType.create("application/soap+xml", StandardCharsets.UTF_8) + .withParameters(new org.apache.http.message.BasicNameValuePair( + "action", "urn://Ocpp/Cs/2015/10/StopTransaction"));Please confirm how your server handles SOAP 1.2 actions; adjust accordingly.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (33)
pom.xml(2 hunks)steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/UpdateTransactionParams.java(2 hunks)steve-core/src/main/java/de/rwth/idsg/steve/utils/DateTimeUtils.java(2 hunks)steve-ocpp-websocket/pom.xml(1 hunks)steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/config/OcppWebSocketConfiguration.java(2 hunks)steve-ocpp-websocket/src/test/java/de/rwth/idsg/steve/ocpp/ws/custom/CustomStringModuleTest.java(2 hunks)steve-ocpp/pom.xml(1 hunks)steve-ocpp/src/main/java/de/rwth/idsg/ocpp/DateTimeUtils.java(1 hunks)steve-ocpp/src/test/java/de/rwth/idsg/ocpp/jaxb/JavaDateTimeConverterTest.java(1 hunks)steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/connectorStatus.jsp(2 hunks)steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/data-man/ocppTags.jsp(3 hunks)steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/data-man/transactionDetails.jsp(2 hunks)steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/data-man/transactions.jsp(2 hunks)steve/pom.xml(1 hunks)steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java(3 hunks)steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java(3 hunks)steve/src/test/java/de/rwth/idsg/steve/OperationalTestSoapOCPP16.java(12 hunks)steve/src/test/java/de/rwth/idsg/steve/StressTest.java(3 hunks)steve/src/test/java/de/rwth/idsg/steve/StressTestJsonOCPP16.java(5 hunks)steve/src/test/java/de/rwth/idsg/steve/StressTestSoapOCPP16.java(3 hunks)steve/src/test/java/de/rwth/idsg/steve/TypeStoreTest.java(2 hunks)steve/src/test/java/de/rwth/idsg/steve/issues/Issue1219.java(4 hunks)steve/src/test/java/de/rwth/idsg/steve/issues/Issue72.java(5 hunks)steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java(6 hunks)steve/src/test/java/de/rwth/idsg/steve/issues/Issue73Fix.java(4 hunks)steve/src/test/java/de/rwth/idsg/steve/issues/Issue81.java(5 hunks)steve/src/test/java/de/rwth/idsg/steve/ocpp/OcppProtocolTest.java(1 hunks)steve/src/test/java/de/rwth/idsg/steve/ocpp/OcppVersionTest.java(1 hunks)steve/src/test/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandlerTest.java(1 hunks)steve/src/test/java/de/rwth/idsg/steve/utils/StringUtilsTest.java(2 hunks)steve/src/test/java/de/rwth/idsg/steve/utils/TransactionStopServiceHelperTest.java(11 hunks)steve/src/test/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidatorTest.java(1 hunks)steve/src/test/java/de/rwth/idsg/steve/web/validation/IdTagValidatorTest.java(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (11)
- steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/data-man/transactions.jsp
- steve-ocpp/pom.xml
- steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/config/OcppWebSocketConfiguration.java
- pom.xml
- steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/data-man/transactionDetails.jsp
- steve/src/test/java/de/rwth/idsg/steve/issues/Issue1219.java
- steve/src/test/java/de/rwth/idsg/steve/StressTestSoapOCPP16.java
- steve-core/src/main/java/de/rwth/idsg/steve/utils/DateTimeUtils.java
- steve/src/test/java/de/rwth/idsg/steve/issues/Issue72.java
- steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/data-man/ocppTags.jsp
- steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/UpdateTransactionParams.java
🧰 Additional context used
🧬 Code graph analysis (3)
steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java (1)
steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java (1)
__DatabasePreparer__(68-202)
steve/src/test/java/de/rwth/idsg/steve/issues/Issue73Fix.java (1)
steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java (1)
__DatabasePreparer__(68-202)
steve-ocpp/src/main/java/de/rwth/idsg/ocpp/DateTimeUtils.java (1)
steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/JspDateTimeUtils.java (1)
NoArgsConstructor(28-38)
🪛 GitHub Check: checkstyle
steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java
[failure] 86-86: [checkstyle] steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java#L86 <com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck>
Line is longer than 120 characters (found 122).
[failure] 93-93: [checkstyle] steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java#L93 <com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck>
Line is longer than 120 characters (found 138).
[failure] 110-110: [checkstyle] steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java#L110 <com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck>
Line is longer than 120 characters (found 122).
[failure] 117-117: [checkstyle] steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java#L117 <com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck>
Line is longer than 120 characters (found 138).
[failure] 141-141: [checkstyle] steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java#L141 <com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck>
Line is longer than 120 characters (found 124).
[failure] 155-155: [checkstyle] steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java#L155 <com.puppycrawl.tools.checkstyle.checks.blocks.LeftCurlyCheck>
'{' at column 45 should have line break after.
[failure] 155-155: [checkstyle] steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java#L155 <com.puppycrawl.tools.checkstyle.checks.whitespace.WhitespaceAroundCheck>
'{' is not followed by whitespace.
[failure] 155-155: [checkstyle] steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java#L155 <com.puppycrawl.tools.checkstyle.checks.coding.EmptyStatementCheck>
Empty statement.
[failure] 168-168: [checkstyle] steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java#L168 <com.puppycrawl.tools.checkstyle.checks.blocks.LeftCurlyCheck>
'{' at column 45 should have line break after.
[failure] 168-168: [checkstyle] steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java#L168 <com.puppycrawl.tools.checkstyle.checks.whitespace.WhitespaceAroundCheck>
'{' is not followed by whitespace.
[failure] 168-168: [checkstyle] steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java#L168 <com.puppycrawl.tools.checkstyle.checks.coding.EmptyStatementCheck>
Empty statement.
steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java
[failure] 99-99: [checkstyle] steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java#L99 <com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck>
Line is longer than 120 characters (found 123).
steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/connectorStatus.jsp
[failure] 96-96: [checkstyle] steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/connectorStatus.jsp#L96 <com.puppycrawl.tools.checkstyle.checks.whitespace.FileTabCharacterCheck>
Line contains a tab character.
steve/src/test/java/de/rwth/idsg/steve/StressTestJsonOCPP16.java
[failure] 132-132: [checkstyle] steve/src/test/java/de/rwth/idsg/steve/StressTestJsonOCPP16.java#L132 <com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck>
Line is longer than 120 characters (found 127).
steve/src/test/java/de/rwth/idsg/steve/OperationalTestSoapOCPP16.java
[failure] 498-498: [checkstyle] steve/src/test/java/de/rwth/idsg/steve/OperationalTestSoapOCPP16.java#L498 <com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck>
Line is longer than 120 characters (found 125).
[failure] 549-549: [checkstyle] steve/src/test/java/de/rwth/idsg/steve/OperationalTestSoapOCPP16.java#L549 <com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck>
Line is longer than 120 characters (found 123).
steve-ocpp/src/main/java/de/rwth/idsg/ocpp/DateTimeUtils.java
[failure] 32-32: [checkstyle] steve-ocpp/src/main/java/de/rwth/idsg/ocpp/DateTimeUtils.java#L32 <com.puppycrawl.tools.checkstyle.checks.design.HideUtilityClassConstructorCheck>
Utility classes should not have a public or default constructor.
steve-ocpp/src/test/java/de/rwth/idsg/ocpp/jaxb/JavaDateTimeConverterTest.java
[failure] 65-65: [checkstyle] steve-ocpp/src/test/java/de/rwth/idsg/ocpp/jaxb/JavaDateTimeConverterTest.java#L65 <com.puppycrawl.tools.checkstyle.checks.whitespace.ParenPadCheck>
'(' is followed by whitespace.
[failure] 76-76: [checkstyle] steve-ocpp/src/test/java/de/rwth/idsg/ocpp/jaxb/JavaDateTimeConverterTest.java#L76 <com.puppycrawl.tools.checkstyle.checks.whitespace.ParenPadCheck>
'(' is followed by whitespace.
[failure] 82-82: [checkstyle] steve-ocpp/src/test/java/de/rwth/idsg/ocpp/jaxb/JavaDateTimeConverterTest.java#L82 <com.puppycrawl.tools.checkstyle.checks.whitespace.ParenPadCheck>
'(' is followed by whitespace.
[failure] 114-114: [checkstyle] steve-ocpp/src/test/java/de/rwth/idsg/ocpp/jaxb/JavaDateTimeConverterTest.java#L114 <com.puppycrawl.tools.checkstyle.checks.whitespace.ParenPadCheck>
'(' is followed by whitespace.
[failure] 156-156: [checkstyle] steve-ocpp/src/test/java/de/rwth/idsg/ocpp/jaxb/JavaDateTimeConverterTest.java#L156 <com.puppycrawl.tools.checkstyle.checks.whitespace.ParenPadCheck>
'(' is followed by whitespace.
[failure] 170-170: [checkstyle] steve-ocpp/src/test/java/de/rwth/idsg/ocpp/jaxb/JavaDateTimeConverterTest.java#L170 <com.puppycrawl.tools.checkstyle.checks.whitespace.ParenPadCheck>
'(' is followed by whitespace.
[failure] 193-193: [checkstyle] steve-ocpp/src/test/java/de/rwth/idsg/ocpp/jaxb/JavaDateTimeConverterTest.java#L193 <com.puppycrawl.tools.checkstyle.checks.whitespace.ParenPadCheck>
'(' is followed by whitespace.
[failure] 214-214: [checkstyle] steve-ocpp/src/test/java/de/rwth/idsg/ocpp/jaxb/JavaDateTimeConverterTest.java#L214 <com.puppycrawl.tools.checkstyle.checks.whitespace.ParenPadCheck>
'(' is followed by whitespace.
[failure] 221-221: [checkstyle] steve-ocpp/src/test/java/de/rwth/idsg/ocpp/jaxb/JavaDateTimeConverterTest.java#L221 <com.puppycrawl.tools.checkstyle.checks.whitespace.ParenPadCheck>
'(' is followed by whitespace.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
- GitHub Check: build (ubuntu-24.04, 21, mysql:8.0)
- GitHub Check: build (ubuntu-24.04, 17, mysql:8.0)
- GitHub Check: build (ubuntu-24.04, 21, mariadb:10.5.21)
- GitHub Check: build (ubuntu-24.04, 21, mariadb:10.3)
- GitHub Check: build (ubuntu-24.04, 17, mariadb:10.3)
- GitHub Check: build (ubuntu-24.04, 17, mariadb:10.5.21)
- GitHub Check: build (ubuntu-24.04, 17, mariadb:10.4.30)
- GitHub Check: build (ubuntu-22.04, 17, mariadb:10.6.14)
- GitHub Check: build (ubuntu-22.04, 17, mariadb:10.4.30)
- GitHub Check: build (ubuntu-22.04, 21, mysql:8.0)
- GitHub Check: build (ubuntu-22.04, 21, mariadb:10.3)
- GitHub Check: build (ubuntu-22.04, 21, mariadb:10.4.30)
- GitHub Check: build (ubuntu-22.04, 17, mariadb:10.5.21)
- GitHub Check: build (ubuntu-22.04, 17, mysql:8.0)
- GitHub Check: build (ubuntu-22.04, 21, mariadb:10.6.14)
- GitHub Check: Analyze (javascript-typescript)
- GitHub Check: Analyze (java-kotlin)
- GitHub Check: Analyze (actions)
- GitHub Check: build (ubuntu-22.04, 17, mariadb:10.3)
- GitHub Check: build (ubuntu-22.04, 17, mariadb:10.5.21)
🔇 Additional comments (63)
steve/src/test/java/de/rwth/idsg/steve/ocpp/OcppVersionTest.java (2)
24-24: AssertJ migration looks goodStatic import of assertThat aligns with AssertJ style and improves readability.
39-41: LGTM on full-coverage loopIterating all versions per transport is concise and ensures broad coverage;
varusage keeps noise low.steve-ocpp-websocket/pom.xml (1)
54-58: LGTM: AssertJ added in test scope aligns with repo-wide test modernizationRelying on parent dependencyManagement for the version keeps things consistent. No issues spotted here.
steve/src/test/java/de/rwth/idsg/steve/ocpp/OcppProtocolTest.java (1)
24-25: AssertJ migration in tests: LGTMStatic import of AssertJ aligns with the repo-wide move and keeps assertions concise. No issues.
steve/src/test/java/de/rwth/idsg/steve/utils/StringUtilsTest.java (6)
31-31: AssertJ migration import looks goodStatic import of assertThat is correct and consistent with the AssertJ style used below.
39-43: Operation name assertion (OCPP 1.2, multiple) is clear and preciseGood switch to fluent assertion; naming reads well.
45-49: Operation name assertion (OCPP 1.5, single) LGTM
51-55: Operation name assertion (OCPP 1.6) LGTM
89-97: Split-by-comma null/empty behavior is explicit and soundAsserting non-null empty collections is a good contract for callers; no changes suggested.
111-112: Baseline Java 17 and AssertJ Dependency Verified
- pom.xml defines
<java.version>17and setsmaven.compiler.source/maven.compiler.targetto${java.version}, ensuring support for bothvar(Java 10+) andStream.toList()(Java 16+)- AssertJ Core (3.27.3) is declared with
<scope>test</scope>in the project’s POMsNo changes required.
steve-ocpp-websocket/src/test/java/de/rwth/idsg/steve/ocpp/ws/custom/CustomStringModuleTest.java (2)
26-26: AssertJ migration: LGTM.Switching to AssertJ’s fluent assertions improves readability and failure diffs. No issues here.
38-41:varusage is safe—project is compiled for Java 17; please confirm CI JDK versionThe top-level
pom.xmldefines<java.version>17</java.version>and the Maven Compiler Plugin is picking up this property for bothsourceandtarget, so use ofvar(Java 10+) is fully supported. AssertJ is correctly declared with<scope>test</scope>. To avoid any surprises on CI:
- Top-level
/pom.xml
•<java.version>17</java.version>
•<maven.compiler.source>${java.version}</maven.compiler.source>
•<maven.compiler.target>${java.version}</maven.compiler.target>- steve-ocpp-websocket/pom.xml
•org.assertj:assertj-corehas<scope>test</scope>- CI pipelines (e.g., GitHub Actions, Jenkins)
• Verify build agents are running JDK 11 or 17 (at minimum Java 10) sovarcompilation and tests passsteve-ui-jsp/src/main/resources/webapp/WEB-INF/views/connectorStatus.jsp (1)
21-21: No duplicate datetime taglib foundI searched all shared header JSPs under
webapp/for a<%@ taglib prefix="f" uri="https://www.steve-project.org/tld/datetime" %>directive and found none. The only occurrences are in individual view files, so there’s no redundant declaration in the common header.steve/src/test/java/de/rwth/idsg/steve/utils/TransactionStopServiceHelperTest.java (11)
24-25: AssertJ migration looks goodStatic import of
assertThatis correct and consistent with repository-wide AssertJ adoption.
37-40: LGTM: rounding case"234.678" -> "235" aligns with the implied ceiling semantics. No issues.
54-55: Empty meter values correctly rejected
isEnergyValueshould be false with no data. Test captures that.
82-83: SignedData correctly excludedRejecting
format="SignedData"looks correct as such payloads aren’t raw numeric values.
93-94: Non-energy unit correctly rejected"Celsius" should not be accepted for energy values. Good guard.
105-106: Non-import measurand correctly rejected"Current.Export" is not an energy import register; rejecting it avoids mixing measurands.
117-118: Null format accepted with correct unit/measurandAllowing null format while still validating unit/measurand is reasonable.
129-130: Raw format accepted
format="Raw"with matching unit/measurand is a valid positive case.
141-142: kWh unit acceptedAccepting both "Wh" and "kWh" improves robustness. Looks good.
153-154: Non-numeric value correctly rejectedGuards against garbage input; good negative test.
163-164: Value with unit acceptedAnother valid positive case validating the unit path.
steve/src/test/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandlerTest.java (5)
23-23: AssertJ migration looks goodSwitching to AssertJ’s
assertThataligns with modern testing conventions and improves readability.
37-39: Null input -> empty result coveredGood guard-rail test for NPE avoidance.
57-59: Happy-path (relative path) assertion is clearCovers the common case succinctly.
71-73: Hyphenated IDs coveredGood coverage for common identifier characters.
29-31: Java Compiler Configuration Verified — Java 17 Enabled Across All ModulesThe root
pom.xmldefines
<java.version>17</java.version><maven.compiler.source>${java.version}</maven.compiler.source><maven.compiler.target>${java.version}</maven.compiler.target>This configuration is inherited by the
stevemodule (and all submodules), so Java 17’svarsupport is guaranteed. No further changes are required.steve/src/test/java/de/rwth/idsg/steve/TypeStoreTest.java (2)
26-26: AssertJ migration in tests — LGTMUsing AssertJ’s assertThat improves readability and failure messages. No concerns.
36-36: No remaining singletons and Java 17 confirmed
- A recursive grep for
Ocpp(12|15|16)TypeStore.INSTANCEreturned zero matches, so there are no lingering singleton references.- The root
pom.xmldefines<java.version>17</java.version>and configures the Maven Compiler Plugin with<source>${java.version}</source>and<target>${java.version}</target>, ensuringvaris supported.Tests are now fully isolated and the codebase targets Java 17+. All checks pass.
steve/src/test/java/de/rwth/idsg/steve/web/validation/IdTagValidatorTest.java (10)
23-23: AssertJ migration: idiomatic and consistent.Static import of assertThat and isTrue() usage looks good and improves readability.
35-35: Null-as-valid case covered.Matches Bean Validation convention where null is typically considered valid unless constrained separately.
40-40: Lowercase letters: covered.Assertion reads clearly with AssertJ.
45-45: Uppercase letters: covered.No issues spotted.
63-66: Dot cases (prefix/suffix/double): covered.Good coverage of tricky positions.
70-73: Dash cases: covered.Looks good.
77-80: Underscore cases: covered.No issues.
87-92: Colon variants incl. vendor pattern: covered.Nice to include the real-world “VID:00XXXXXXXXXX” example.
96-99: Pound sign variants and known tag: covered.Good inclusion of Webasto example from the linked PR.
Also applies to: 102-102
107-107: Combined characters: covered.Solid smoke test for mixed tokens.
steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java (2)
34-35: AssertJ migration looks goodSwitching to AssertJ’s fluent API improves readability and diagnostics in failures. No issues spotted with the static imports.
72-88: OCPP 1.2 flow assertions are clear and minimalUse of var and AssertJ reads well; the ACCEPTED statuses for BootNotification and Authorize are asserted succinctly. No functional concerns here.
steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java (2)
39-43: AssertJ migration and BDD style: LGTMAdopting AssertJ (assertThat, catchThrowable, then, fail) is consistent and improves readability across the tests.
61-61: Profile assertion switch to AssertJ: LGTMUsing assertThat(...).isEqualTo(...) here is clear and consistent with the new test style.
steve/pom.xml (1)
282-285: AssertJ test dependency addition is aligned with the migration.Switching to AssertJ in tests is consistent with the codebase changes and the root dependencyManagement entry. Good move.
steve/src/test/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidatorTest.java (1)
23-24: AssertJ migration for assertions looks good.Static import is correct; assertions are idiomatic AssertJ.
steve/src/test/java/de/rwth/idsg/steve/StressTest.java (1)
55-57: Config assertions updated to AssertJ: LGTM.Clear and more readable than JUnit assertions.
steve/src/test/java/de/rwth/idsg/steve/OperationalTestSoapOCPP16.java (2)
132-139: Interceptor behavior test reads well.Using
assertThatExceptionOfType(WebServiceException.class).isThrownBy(...)is clear and expressive.
569-591: Boot/status helpers: concise and consistent.Nice consolidation; using
varand AssertJ keeps the tests terse.steve/src/test/java/de/rwth/idsg/steve/StressTestJsonOCPP16.java (1)
116-127: AssertJ usage and OffsetDateTime migration look solid.The migration to java.time and AssertJ in the stress workflow is coherent and consistent with the rest of the PR.
Also applies to: 156-165, 166-175, 187-196
steve/src/test/java/de/rwth/idsg/steve/issues/Issue73Fix.java (4)
39-39: AssertJ migration looks goodStatic import of assertThat aligns with the rest of the changes and improves readability.
105-108: Authorize checks look goodClear and focused assertions on status per charge box.
111-122: StartTransaction assertions are on-pointNon-null response, positive transactionId, and DB inTransaction verification nicely cover the Issue 73 scenario. With the earlier UTC change, this should be robust across time zones.
62-80: Java sourceCompatibility is already set to Java 17 via MavenVerified that the root
pom.xmldefines
<java.version>17</java.version>(line 31)<maven.compiler.source>${java.version}</maven.compiler.source>(line 32)<maven.compiler.target>${java.version}</maven.compiler.target>(line 33)
and that the Maven Compiler Plugin (v 3.14.0) is configured to use these properties (lines 203–204). All modules inherit this setting, so usingvar(introduced in Java 10) is fully supported. No further changes are needed.steve-ocpp/src/test/java/de/rwth/idsg/ocpp/jaxb/JavaDateTimeConverterTest.java (3)
39-54: Well-designed flexible datetime parser for OCPP compliance.The parser correctly handles optional fractional seconds and optional offset components, which aligns well with OCPP protocol requirements. The use of
parseStrict()ensures proper validation.
58-64: Appropriate fixed-precision formatter for consistent output.The formatter with fixed 3-digit millisecond precision ensures consistent datetime representation across OCPP communications, which is important for interoperability.
127-133: Robust test approach with temporal tolerance.Using
isCloseTowith 1-second tolerance is appropriate for datetime conversion testing, accounting for potential minor precision differences during conversion operations.steve-ocpp/src/main/java/de/rwth/idsg/ocpp/DateTimeUtils.java (2)
66-81: Solid implementation of timezone-aware datetime parsing.The method correctly handles both offset and non-offset datetime strings, with appropriate fallback logic and error handling for missing components.
83-98: Good UTC preference alignment with OCPP recommendations.The implementation follows OCPP best practices by defaulting to UTC formatting when no specific zone is provided, which improves interoperability between systems.
steve/src/test/java/de/rwth/idsg/steve/issues/Issue81.java (3)
31-32: Good migration from Joda-Time to Java Time.The replacement of
org.joda.time.DateTimewithjava.time.OffsetDateTimeandZoneOffsetaligns with the PR's objective to remove Joda-Time dependencies. The addition of AssertJ assertions also improves test readability.Also applies to: 38-38
56-56: Appropriate use ofvarfor improved code readability.The adoption of local variable type inference reduces verbosity while maintaining type safety. The variable names clearly indicate their types and purpose.
Also applies to: 68-68, 75-75, 88-88, 98-98, 104-104
73-73: Good migration to AssertJ assertions.The replacement of JUnit assertions with AssertJ (
assertThat().isEqualTo()andassertThat().isNotNull()) provides more readable and fluent assertions.Also applies to: 89-89, 105-105
| <dependency> | ||
| <groupId>org.assertj</groupId> | ||
| <artifactId>assertj-core</artifactId> | ||
| <scope>test</scope> | ||
| </dependency> |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Verify version is inherited from parent and avoid module-local version drift
Sanity-check that:
- assertj-core’s version resolves from the parent’s dependencyManagement (expected: 3.27.3), and
- no other module pins a different version.
Run the script below from the repo root to confirm:
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "1) Confirm AssertJ is managed once in dependencyManagement with the expected version:"
rg -nC3 -P '(?s)<dependencyManagement>.*?<artifactId>\s*assertj-core\s*</artifactId>.*?<version>\s*3\.27\.3\s*</version>.*?</dependencyManagement>' pom.xml || {
echo "AssertJ not found in dependencyManagement with version 3.27.3 in root pom.xml"; exit 1; }
echo
echo "2) Ensure no module declares an explicit AssertJ version (should be empty):"
rg -n -g '!**/target/**' -P '(?s)<dependency>\s*<groupId>\s*org\.assertj\s*</groupId>\s*<artifactId>\s*assertj-core\s*</artifactId>\s*.*?<version>' || true
echo
echo "3) Show all places where assertj-core is declared (for a quick visual check of scopes):"
rg -nC2 -g '!**/target/**' -P '<artifactId>\s*assertj-core\s*</artifactId>'Length of output: 532
Add AssertJ-core to the parent POM’s dependencyManagement
The root pom.xml currently does not declare an entry for assertj-core in its <dependencyManagement> section (the version check failed), so all modules pull an implicit or default version. To enforce the expected 3.27.3 test-scope version and prevent drift, please add the following under the top-level <dependencyManagement><dependencies> block in your root pom.xml:
<dependencyManagement>
<dependencies>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <version>3.27.3</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
</dependencyManagement>• File to update: pom.xml at the repository root
• After committing, rerun your grep checks to confirm:
- That the parent now manages AssertJ-core at 3.27.3
- That no module POM still declares an explicit
<version>forassertj-core
🤖 Prompt for AI Agents
In pom.xml at repository root (around lines 54-58 of the child snippet), the
parent POM does not manage assertj-core so modules inherit implicit versions;
add an entry for org.assertj:assertj-core with version 3.27.3 and scope test
under the top-level <dependencyManagement><dependencies> block in the root
pom.xml, commit the change, then run the grep checks to ensure the parent now
manages assertj-core at 3.27.3 and that no module POMs still declare an explicit
<version> for assertj-core.
| import static org.assertj.core.api.Assertions.assertThat; | ||
| import static org.assertj.core.api.Assertions.catchThrowable; | ||
| import static org.assertj.core.api.Assertions.fail; | ||
| import static org.assertj.core.api.BDDAssertions.then; | ||
|
|
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Quick self-check script for style issues in this file
Run this to confirm the Checkstyle-related fixes (long lines and the stray semicolons inside lambdas) and the optional renames.
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java"
echo "1) Stray semicolon in lambda bodies (-> {;):"
rg -nP '->\s*\{;' "$FILE" || echo "OK: No stray semicolons in lambda bodies."
echo
echo "2) Lines over 120 chars:"
rg -nP '.{121,}$' "$FILE" || echo "OK: No lines exceed 120 characters."
echo
echo "3) Bare error -> fail() usages:"
rg -n 'error\s*->\s*fail\(\)' "$FILE" || echo "OK: All error callbacks provide messages."
echo
echo "4) Misspelled test name:"
rg -n 'tesWithUnauthorizedStation' "$FILE" || echo "OK: UnauthorizedStation test method name is consistent."Length of output: 2213
Please address the remaining Checkstyle violations and the misspelled test name
The self-check script reveals the following actionable issues in steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java:
-
Lines exceeding 120 characters (must wrap or refactor to satisfy the project’s line-length limit):
• Line 86
• Line 93
• Line 110
• Line 117
• Line 141 -
Bare
error -> fail()callbacks (each should provide a descriptive message or include the exception):
• Line 87
• Line 94
• Line 111
• Line 118
• Line 135
• Line 142
• Line 197 -
Misspelled test method name (should start with
test...):
• Line 175:public void tesWithUnauthorizedStation()→ rename totestWithUnauthorizedStation()
No stray semicolons inside lambda bodies were found. Once the above are corrected (wrapping long lines, enriching fail() calls, and renaming the test), this file will conform to style and naming conventions.
🤖 Prompt for AI Agents
In steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java around lines
39–197: wrap or refactor the five overlong lines (86, 93, 110, 117, 141) so they
do not exceed 120 characters (break long literals/arguments across multiple
concatenated strings or extract variables), replace each bare fail() inside
lambda error handlers (lines 87, 94, 111, 118, 135, 142, 197) with fail(...)
that includes a descriptive message and/or the caught exception (e.g.
fail("descriptive message", error)), and rename the misspelled test method at
line 175 from tesWithUnauthorizedStation() to testWithUnauthorizedStation()
(update any usages/annotations accordingly).
| authResponse -> assertThat(authResponse.getIdTagInfo().getStatus()).isEqualTo(ocpp.cs._2010._08.AuthorizationStatus.ACCEPTED), | ||
| error -> fail() | ||
| ); |
There was a problem hiding this comment.
Fix Checkstyle line-length and improve failure diagnostics (OCPP 1.2 authorize assertion)
Line 93 exceeds 120 chars. Suggest wrapping and including error details in fail().
- authResponse -> assertThat(authResponse.getIdTagInfo().getStatus()).isEqualTo(ocpp.cs._2010._08.AuthorizationStatus.ACCEPTED),
- error -> fail()
+ authResponse -> assertThat(authResponse.getIdTagInfo().getStatus())
+ .isEqualTo(ocpp.cs._2010._08.AuthorizationStatus.ACCEPTED),
+ error -> fail("Unexpected error during OCPP 1.2 authorize: " + error)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| authResponse -> assertThat(authResponse.getIdTagInfo().getStatus()).isEqualTo(ocpp.cs._2010._08.AuthorizationStatus.ACCEPTED), | |
| error -> fail() | |
| ); | |
| authResponse -> assertThat(authResponse.getIdTagInfo().getStatus()) | |
| .isEqualTo(ocpp.cs._2010._08.AuthorizationStatus.ACCEPTED), | |
| error -> fail("Unexpected error during OCPP 1.2 authorize: " + error) | |
| ); |
🧰 Tools
🪛 GitHub Check: checkstyle
[failure] 93-93: [checkstyle] steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java#L93 <com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck>
Line is longer than 120 characters (found 138).
🤖 Prompt for AI Agents
In steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java around lines
93 to 95, the assertion line exceeds 120 chars and the error branch just calls
fail() with no diagnostics; refactor the assertion call to wrap the chained
method calls across multiple lines so no line exceeds 120 chars, and replace the
error -> fail() lambda with a failure that includes the error details (message
and/or throwable) so the test prints diagnostics (e.g., fail with
error.getMessage() and the throwable). Ensure formatting keeps indentation
consistent and line lengths under 120 chars.
| bootResponse -> assertThat(bootResponse.getStatus()).isEqualTo(ocpp.cs._2012._06.RegistrationStatus.ACCEPTED), | ||
| error -> fail() | ||
| ); |
There was a problem hiding this comment.
Fix Checkstyle line-length and improve failure diagnostics (OCPP 1.5 boot assertion)
Line 110 exceeds 120 chars.
- bootResponse -> assertThat(bootResponse.getStatus()).isEqualTo(ocpp.cs._2012._06.RegistrationStatus.ACCEPTED),
- error -> fail()
+ bootResponse -> assertThat(bootResponse.getStatus())
+ .isEqualTo(ocpp.cs._2012._06.RegistrationStatus.ACCEPTED),
+ error -> fail("Unexpected error during OCPP 1.5 boot: " + error)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| bootResponse -> assertThat(bootResponse.getStatus()).isEqualTo(ocpp.cs._2012._06.RegistrationStatus.ACCEPTED), | |
| error -> fail() | |
| ); | |
| bootResponse -> assertThat(bootResponse.getStatus()) | |
| .isEqualTo(ocpp.cs._2012._06.RegistrationStatus.ACCEPTED), | |
| error -> fail("Unexpected error during OCPP 1.5 boot: " + error) | |
| ); |
🧰 Tools
🪛 GitHub Check: checkstyle
[failure] 110-110: [checkstyle] steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java#L110 <com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck>
Line is longer than 120 characters (found 122).
🤖 Prompt for AI Agents
In steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java around lines
110-112, the lambda lines exceed the 120-char checkstyle limit and the failure
lambda provides no diagnostics; break the long assertion into multiple lines so
each line is <=120 chars (e.g. place assertThat(...).isEqualTo(...) on separate
lines or wrap method arguments), and replace the anonymous error -> fail() with
a diagnostic failure such as error -> fail("Boot request failed: " +
error.getMessage()) (or include more context) so failures include the error
message; ensure resulting lines conform to checkstyle length.
| for (var meterValue : meterValues) { | ||
| var sampledValues = meterValue.getSampledValue(); | ||
| assertThat(sampledValues).isEmpty(); | ||
| var thisValueFound = false; | ||
| // and check, if it can be found in the DB | ||
| for (TransactionDetails.MeterValues values : details.getValues()) { | ||
| for (var values : details.getValues()) { | ||
| if (values.getValue().equals(sampledValues.get(0).getValue())) { | ||
| thisValueFound = true; |
There was a problem hiding this comment.
Bug: asserting empty sampled values but indexing sampledValues.get(0).
assertThat(sampledValues).isEmpty() contradicts the subsequent sampledValues.get(0) access and will fail. The list should be non-empty.
- var sampledValues = meterValue.getSampledValue();
- assertThat(sampledValues).isEmpty();
+ var sampledValues = meterValue.getSampledValue();
+ assertThat(sampledValues).isNotEmpty();Optionally, make the check robust against multiple sampled values:
- var thisValueFound = false;
- // and check, if it can be found in the DB
- for (var values : details.getValues()) {
- if (values.getValue().equals(sampledValues.get(0).getValue())) {
- thisValueFound = true;
- break;
- }
- }
+ var thisValueFound = details.getValues().stream()
+ .anyMatch(values ->
+ sampledValues.stream().anyMatch(sv -> values.getValue().equals(sv.getValue()))
+ );📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| for (var meterValue : meterValues) { | |
| var sampledValues = meterValue.getSampledValue(); | |
| assertThat(sampledValues).isEmpty(); | |
| var thisValueFound = false; | |
| // and check, if it can be found in the DB | |
| for (TransactionDetails.MeterValues values : details.getValues()) { | |
| for (var values : details.getValues()) { | |
| if (values.getValue().equals(sampledValues.get(0).getValue())) { | |
| thisValueFound = true; | |
| for (var meterValue : meterValues) { | |
| var sampledValues = meterValue.getSampledValue(); | |
| assertThat(sampledValues).isNotEmpty(); | |
| var thisValueFound = details.getValues().stream() | |
| .anyMatch(values -> | |
| sampledValues.stream() | |
| .anyMatch(sv -> values.getValue().equals(sv.getValue())) | |
| ); | |
| // …rest of the logic… | |
| } |
🤖 Prompt for AI Agents
In steve/src/test/java/de/rwth/idsg/steve/OperationalTestSoapOCPP16.java around
lines 597 to 604, the test currently asserts sampledValues is empty then
immediately calls sampledValues.get(0), which is contradictory and will throw;
change the assertion to assertThat(sampledValues).isNotEmpty() or remove the
emptiness assertion and instead iterate over sampledValues (for each
sampledValue check sampledValue.getValue() against the DB entries) to handle
multiple sampled values robustly; ensure you only call get(0) after confirming
the list is non-empty or replace get(0) with a loop that checks every
sampledValue.
steve/src/test/java/de/rwth/idsg/steve/utils/TransactionStopServiceHelperTest.java
Show resolved
Hide resolved
| import org.junit.jupiter.api.Assertions; | ||
| import org.junit.jupiter.api.Test; | ||
|
|
||
| import static org.assertj.core.api.Assertions.assertThat; |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Verify AssertJ test dependency is present in build.
Please ensure assertj-core is declared with test scope so these assertions compile everywhere (root and modules, if any).
Run this script from the repo root to confirm:
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Searching for assertj-core in Maven/Gradle build files..."
rg -n -C2 -i 'assertj-core' -g '*.{xml,gradle,kts}'
echo -e "\nIf Maven, show scope details:"
rg -n -C3 -e '<artifactId>\s*assertj-core\s*</artifactId>' -g 'pom.xml'
echo -e "\nIf Gradle, ensure testImplementation/testCompile is used:"
rg -n -C2 -i 'testImplementation\s*.*assertj-core|testCompile\s*.*assertj-core' -g '*gradle*'Length of output: 3318
Add test scope to AssertJ in the root POM
The module POMs (steve, steve-ocpp-websocket, steve-ocpp) already declare:
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>But in the root pom.xml (around lines 156–160) the same dependency has no <scope>test</scope>, so it’s pulled in as a compile-time dependency. To ensure all tests compile (and to avoid leaking AssertJ APIs into production code), update the root POM to:
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.27.3</version>
+ <scope>test</scope>
</dependency>After this change, AssertJ will be correctly scoped for tests only.
🤖 Prompt for AI Agents
pom.xml around lines 156-160: the org.assertj:assertj-core dependency in the
root POM is missing a <scope>test</scope>, causing AssertJ to be pulled in at
compile time; update the dependency declaration by adding <scope>test</scope> so
AssertJ is only a test-scoped dependency (leave module POMs unchanged).
| assertThat(validator.isValid(null, null)).isTrue(); | ||
| } | ||
|
|
||
| @Test | ||
| public void testAllLowercaseLetters() { | ||
| Assertions.assertTrue(validator.isValid("test", null)); | ||
| assertThat(validator.isValid("test", null)).isTrue(); | ||
| } | ||
|
|
||
| @Test | ||
| public void testAllUppercaseLetters() { | ||
| Assertions.assertTrue(validator.isValid("TEST", null)); | ||
| assertThat(validator.isValid("TEST", null)).isTrue(); | ||
| } | ||
|
|
||
| @Test | ||
| public void testMixedCaseLetters() { | ||
| Assertions.assertTrue(validator.isValid("TesT", null)); | ||
| Assertions.assertTrue(validator.isValid("tEst", null)); | ||
| assertThat(validator.isValid("TesT", null)).isTrue(); | ||
| assertThat(validator.isValid("tEst", null)).isTrue(); | ||
| } | ||
|
|
||
| @Test | ||
| public void testLettersAndNumbers() { | ||
| Assertions.assertTrue(validator.isValid("test12", null)); | ||
| Assertions.assertTrue(validator.isValid("89test", null)); | ||
| Assertions.assertTrue(validator.isValid("te9s0t", null)); | ||
| assertThat(validator.isValid("test12", null)).isTrue(); | ||
| assertThat(validator.isValid("89test", null)).isTrue(); | ||
| assertThat(validator.isValid("te9s0t", null)).isTrue(); | ||
| } | ||
|
|
||
| @Test | ||
| public void testDot() { | ||
| Assertions.assertTrue(validator.isValid(".test", null)); | ||
| Assertions.assertTrue(validator.isValid("test.", null)); | ||
| Assertions.assertTrue(validator.isValid("te..st", null)); | ||
| assertThat(validator.isValid(".test", null)).isTrue(); | ||
| assertThat(validator.isValid("test.", null)).isTrue(); | ||
| assertThat(validator.isValid("te..st", null)).isTrue(); | ||
| } | ||
|
|
||
| @Test | ||
| public void testDash() { | ||
| Assertions.assertTrue(validator.isValid("-test", null)); | ||
| Assertions.assertTrue(validator.isValid("test-", null)); | ||
| Assertions.assertTrue(validator.isValid("te--st", null)); | ||
| assertThat(validator.isValid("-test", null)).isTrue(); | ||
| assertThat(validator.isValid("test-", null)).isTrue(); | ||
| assertThat(validator.isValid("te--st", null)).isTrue(); | ||
| } | ||
|
|
||
| @Test | ||
| public void testUnderscore() { | ||
| Assertions.assertTrue(validator.isValid("_test", null)); | ||
| Assertions.assertTrue(validator.isValid("test_", null)); | ||
| Assertions.assertTrue(validator.isValid("te__st", null)); | ||
| assertThat(validator.isValid("_test", null)).isTrue(); | ||
| assertThat(validator.isValid("test_", null)).isTrue(); | ||
| assertThat(validator.isValid("te__st", null)).isTrue(); | ||
| } | ||
|
|
||
| /** | ||
| * https://github.com/steve-community/steve/issues/475 | ||
| */ | ||
| @Test | ||
| public void testColon() { | ||
| Assertions.assertTrue(validator.isValid(":test", null)); | ||
| Assertions.assertTrue(validator.isValid("test:", null)); | ||
| Assertions.assertTrue(validator.isValid("te::st", null)); | ||
| assertThat(validator.isValid(":test", null)).isTrue(); | ||
| assertThat(validator.isValid("test:", null)).isTrue(); | ||
| assertThat(validator.isValid("te::st", null)).isTrue(); | ||
|
|
||
| Assertions.assertTrue(validator.isValid("VID:00XXXXXXXXXX", null)); | ||
| assertThat(validator.isValid("VID:00XXXXXXXXXX", null)).isTrue(); | ||
| } | ||
|
|
||
| @Test | ||
| public void testPoundSign() { | ||
| Assertions.assertTrue(validator.isValid("#test", null)); | ||
| Assertions.assertTrue(validator.isValid("test#", null)); | ||
| Assertions.assertTrue(validator.isValid("te##st", null)); | ||
| assertThat(validator.isValid("#test", null)).isTrue(); | ||
| assertThat(validator.isValid("test#", null)).isTrue(); | ||
| assertThat(validator.isValid("te##st", null)).isTrue(); | ||
|
|
||
| // Tag provided by Webasto charge points | ||
| // https://github.com/steve-community/steve/pull/1322 | ||
| Assertions.assertTrue(validator.isValid("#FreeCharging", null)); | ||
| assertThat(validator.isValid("#FreeCharging", null)).isTrue(); | ||
| } | ||
|
|
||
| @Test | ||
| public void testCombined() { | ||
| Assertions.assertTrue(validator.isValid("1t.E-S_:t20#", null)); | ||
| assertThat(validator.isValid("1t.E-S_:t20#", null)).isTrue(); | ||
| } | ||
|
|
||
| } |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Add a few negative/boundary tests to harden the contract.
To protect against regressions, please add explicit invalid cases and any length boundaries enforced by IdTagValidator (spaces, slashes, at signs, unicode, empty string, too-long strings, etc.).
If helpful, run this to discover the exact allowed pattern/limits and I can draft the tests accordingly:
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Locate IdTagValidator and inspect regex/length conditions:"
fd -a IdTagValidator.java
rg -n -C3 -e 'Pattern\.compile|\\.matches\\(|regex|length|min|max' --type=javaLength of output: 485
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate IdTagValidator implementation
file=$(fd -a IdTagValidator.java)
echo "Found IdTagValidator at: $file"
echo
echo "Inspecting regex patterns and validation logic in IdTagValidator.java:"
grep -nE 'Pattern\.compile|matches\(' "$file" || true
echo
echo "Inspecting length constraints (min, max) in IdTagValidator.java:"
grep -nE 'length|min|max' "$file" || trueLength of output: 1134
🏁 Script executed:
#!/bin/bash
set -euo pipefail
file=$(fd -a IdTagValidator.java)
echo "File: $file"
echo
echo "IDTAG_PATTERN definition:"
grep -n 'IDTAG_PATTERN' -C2 "$file"
echo
echo "Complete file for context:"
sed -n '1,200p' "$file"Length of output: 2681
Add Negative and Boundary Tests for IdTagValidator
The current tests cover valid inputs, but we need to explicitly assert that invalid inputs are rejected and that the length limits are enforced. Please extend IdTagValidatorTest with cases such as:
• Empty string
assertThat(validator.isValid("", null)).isFalse();• Too-long string (21 characters)
String tooLong = "A".repeat(21);
assertThat(validator.isValid(tooLong, null)).isFalse();• Disallowed characters (e.g. space, slash, at-sign, unicode)
assertThat(validator.isValid("with space", null)).isFalse();
assertThat(validator.isValid("with/slash", null)).isFalse();
assertThat(validator.isValid("with@at", null)).isFalse();
assertThat(validator.isValid("Ωmega", null)).isFalse();• Boundary-length valid case (20 characters)
String maxAllowed = "A".repeat(20);
assertThat(validator.isValid(maxAllowed, null)).isTrue();These additions will guard against regressions in the regex pattern ^[a-zA-Z0-9.:_#-]{1,20}$ (1–20 chars, only letters, digits, . : _ # -) and ensure all edge cases are covered.
4e668ea to
33fb93c
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (12)
steve/src/test/java/de/rwth/idsg/steve/web/validation/IdTagValidatorTest.java (2)
23-24: AssertJ migration in tests looks good; confirm AssertJ is test-scoped in the root build.Static AssertJ import is correct and consistent with the project-wide shift. Please ensure
org.assertj:assertj-coreremains test-scoped in the root POM to avoid leaking it into production classpath. This was noted previously; echoing here since this file now depends on AssertJ.Run to double-check scoping in the root POM:
#!/bin/bash set -euo pipefail echo "Root POM AssertJ declaration:" rg -n -C3 -g 'pom.xml' -e '<groupId>\s*org.assertj\s*</groupId>\s*<artifactId>\s*assertj-core\s*</artifactId>' echo -e "\nScope near the dependency above (expect <scope>test</scope>):" rg -n -C1 -g 'pom.xml' -e '<scope>\s*test\s*</scope>'
35-35: Add negative and boundary tests to harden the contract (empty, 21+ chars, disallowed chars).Reiterating an earlier suggestion: explicitly assert rejection of invalid inputs and enforce length limits (1–20 chars). Below adds minimal parameterized tests without touching existing positives.
Apply this diff to extend the test suite:
import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import java.util.stream.Stream; public class IdTagValidatorTest { @@ public void testCombined() { assertThat(validator.isValid("1t.E-S_:t20#", null)).isTrue(); } } + + // --- New negative/boundary tests --- + + @ParameterizedTest(name = "invalid id ''{0}'' should be rejected") + @MethodSource("invalidIds") + void invalidInputsAreRejected(String s) { + assertThat(validator.isValid(s, null)).isFalse(); + } + + @ParameterizedTest(name = "boundary length valid: ''{0}''") + @MethodSource("validBoundaryIds") + void boundaryLengthsAccepted(String s) { + assertThat(validator.isValid(s, null)).isTrue(); + } + + private static Stream<String> invalidIds() { + return Stream.of( + "", + "with space", + "with/slash", + "with@at", + "Ωmega", + "A".repeat(21) + ); + } + + private static Stream<String> validBoundaryIds() { + return Stream.of("A".repeat(20)); + }Optionally verify the validator’s regex/limits to align test data with implementation:
#!/bin/bash set -euo pipefail file=$(fd -a 'IdTagValidator.java' | head -n1) echo "IdTagValidator: $file" rg -n -C2 -e 'Pattern\.compile|IDTAG_PATTERN|matches\(' "$file" || true rg -n -C2 -e 'length|\{1,20\}' "$file" || trueAlso applies to: 40-40, 45-45, 50-51, 56-58, 63-65, 70-72, 77-79, 87-91, 96-98, 102-102, 107-107
steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/config/OcppWebSocketConfiguration.java (1)
47-49: Drop static enum imports; fully qualify enum constants and fix whitespace before .classPrefer qualifying enum constants (OcppVersion.V_12, etc.) to avoid static imports and improve readability. Also remove the stray space in
OcppVersion .class.Apply this diff:
-import static de.rwth.idsg.steve.ocpp.OcppVersion.V_12; -import static de.rwth.idsg.steve.ocpp.OcppVersion.V_15; -import static de.rwth.idsg.steve.ocpp.OcppVersion.V_16; +// Use fully-qualified enum constants below; static imports removed - var invocationContexts = new EnumMap<OcppVersion, InvocationContext>(OcppVersion .class); - invocationContexts.put(V_12, new InvocationContext(ocpp12WebSocketEndpoint, ocpp12TypeStore, + var invocationContexts = new EnumMap<OcppVersion, InvocationContext>(OcppVersion.class); + invocationContexts.put(OcppVersion.V_12, new InvocationContext(ocpp12WebSocketEndpoint, ocpp12TypeStore, CommunicationTask::getOcpp12Request)); - invocationContexts.put(V_15, new InvocationContext(ocpp15WebSocketEndpoint, ocpp15TypeStore, + invocationContexts.put(OcppVersion.V_15, new InvocationContext(ocpp15WebSocketEndpoint, ocpp15TypeStore, CommunicationTask::getOcpp15Request)); - invocationContexts.put(V_16, new InvocationContext(ocpp16WebSocketEndpoint, ocpp16TypeStore, + invocationContexts.put(OcppVersion.V_16, new InvocationContext(ocpp16WebSocketEndpoint, ocpp16TypeStore, CommunicationTask::getOcpp16Request));Also applies to: 92-98
steve/src/test/java/de/rwth/idsg/steve/utils/TransactionStopServiceHelperTest.java (1)
44-47: Fix incorrect decimal separator in expected Wh stringThe helper returns a Java
Double.toString(...)for kWh×1000, which uses a dot as the decimal separator (not a semicolon). Update the expectation.- assertThat(actual).isEqualTo("12000;0"); + assertThat(actual).isEqualTo("12000.0");steve/src/test/java/de/rwth/idsg/steve/issues/Issue73Fix.java (1)
55-56: Pre-flight config assertions are environment-sensitive; move values into test propertiesAsserting TEST profile and auto-register=true will fail unless the active config provides those exact values. This was already called out previously; repeating here for visibility.
Create/override a test-scoped properties file so this check is stable across CI and local runs:
steve/src/test/resources/config/main.properties
profile = TEST auto.register.unknown.stations = trueQuick check:
#!/bin/bash fd -t f 'main.properties' steve/src/test/resources | xargs -I{} sh -c 'echo ">>> {}"; rg -n "profile|auto\.register\.unknown\.stations" "{}"'steve/src/test/java/de/rwth/idsg/steve/issues/Issue81.java (2)
31-32: Good migration to java.time with explicit UTCSwitching to OffsetDateTime and ZoneOffset.UTC eliminates locale/timezone flakiness seen with legacy Joda types. Nicely done.
75-80: Nice: OCPP-safe connectorId/meterStart and UTC; also trim idTag to CiString20ConnectorId ≥1 and meterStart ≥0 plus UTC timestamp address typical OCPP validation failures. To be fully schema-safe, trim idTag to ≤20 chars as well.
Apply this diff:
- .withIdTag(Helpers.getRandomString()) + .withIdTag(ci20(getRandomString()))steve-ocpp/src/test/java/de/rwth/idsg/ocpp/jaxb/JavaDateTimeConverterTest.java (4)
61-61: Remaining Checkstyle ParenPad violation.Line 61 still has a space after the opening parenthesis which violates the ParenPad check.
65-82: Fix the spelling from "marchallUtcInputs" to "marshalUtcInputs".The variable name "marchallUtcInputs" contains a typo - JAXB uses "marshal" with one 'l'. The variable should be renamed consistently throughout the code.
173-177: Correct DST expectations for America/New_York.On June 30, 2022, New York was observing Eastern Daylight Time (UTC−04:00), not Eastern Standard Time (UTC−05:00). The test expectations use incorrect offset values that will cause parsing failures.
Also applies to: 181-185
196-204: Fix the DST offset for America/New_York in withoutOffsetInputs.The
tz()helper calls for June 30, 2022 incorrectly use-05:00offset for America/New_York zone. Since this date falls within Eastern Daylight Time, the correct offset should be-04:00.steve-ocpp/src/main/java/de/rwth/idsg/ocpp/DateTimeUtils.java (1)
21-23: Remove Lombok and add explicit private constructor.The Lombok
@NoArgsConstructor(access = AccessLevel.PRIVATE)may not be recognized by Checkstyle'sHideUtilityClassConstructorCheck. Use an explicit private constructor instead.Also applies to: 32-33
🧹 Nitpick comments (24)
steve/src/test/java/de/rwth/idsg/steve/web/validation/IdTagValidatorTest.java (1)
35-35: AssertJ assertions are correct; consider parameterized tests to reduce repetition.Multiple tests iterate similar positive cases. Converting to
@ParameterizedTestwith sources will trim duplication and make additions cheaper. Happy to provide a refactor if desired.Also applies to: 40-40, 45-45, 50-51, 56-58, 63-65, 70-72, 77-79, 87-91, 96-98, 102-102, 107-107
steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/config/OcppWebSocketConfiguration.java (2)
99-100: Return an unmodifiable Map to prevent accidental mutationExpose an immutable view to avoid later accidental puts/removes by consumers.
- return invocationContexts; + return java.util.Collections.unmodifiableMap(invocationContexts);If you prefer an import:
// add import import java.util.Collections;
83-85: Avoid allowing all origins for WebSocket; externalize and use origin patterns
setAllowedOrigins("*")permits any origin and is risky. Consider configuring allowed origins via properties and usesetAllowedOriginPatterns(supports wildcards).Minimal change in this file:
- .setAllowedOrigins("*"); + .setAllowedOriginPatterns(allowedOriginPatterns);Add a configurable field (outside this hunk):
// imports to add import org.springframework.beans.factory.annotation.Value; @Value("${steve.websocket.allowed-origins:*}") private String[] allowedOriginPatterns;Example property:
- steve.websocket.allowed-origins=https://admin.example.com,https://ops.example.com
steve/src/test/java/de/rwth/idsg/steve/TypeStoreTest.java (2)
36-45: Per-test instance over singleton is safer; minor points to considerConstructing a fresh Ocpp12TypeStore per test avoids hidden shared state and improves isolation. Assertions cover both action name and response type, which is the right contract to lock down.
Optional:
- Consider adding an assertion on
findRequestClass("BootNotification")being non-null beforeisSameAs(...)to get a more precise failure message on null.- If TypeStore performs non-trivial initialization, you could cache it in a test field to shave a few milliseconds per test, but not necessary unless tests get slow.
35-45: De-duplicate with a parameterized test to reduce repetitionAll three tests follow the same pattern. Consider a single parameterized test with arguments for: TypeStore supplier, request instance, expected action, expected response class, and the BootNotification request class. This keeps the spec locked while avoiding copy-paste.
Example with JUnit 5:
@ParameterizedTest @MethodSource("typeStoreCases") void typeStoreContracts(Supplier<Object> storeSupplier, Object request, String expectedAction, Class<?> expectedResponseClass, String bootNotificationAction, Class<?> expectedBootReqClass) { var typeStore = storeSupplier.get(); var actionResponse = ((de.rwth.idsg.steve.ocpp.ws.common.TypeStore) typeStore).findActionResponse(request); assertThat(actionResponse).isNotNull(); assertThat(actionResponse.getAction()).isEqualTo(expectedAction); assertThat(actionResponse.getResponseClass()).isEqualTo(expectedResponseClass); var requestClass = ((de.rwth.idsg.steve.ocpp.ws.common.TypeStore) typeStore).findRequestClass(bootNotificationAction); assertThat(requestClass).isSameAs(expectedBootReqClass); } static Stream<Arguments> typeStoreCases() { return Stream.of( Arguments.of( (Supplier<?>) Ocpp12TypeStore::new, new ocpp.cp._2010._08.ResetRequest(), "Reset", ocpp.cp._2010._08.ResetResponse.class, "BootNotification", ocpp.cs._2010._08.BootNotificationRequest.class ), Arguments.of( (Supplier<?>) Ocpp15TypeStore::new, new ocpp.cp._2012._06.UpdateFirmwareRequest(), "UpdateFirmware", ocpp.cp._2012._06.UpdateFirmwareResponse.class, "BootNotification", ocpp.cs._2012._06.BootNotificationRequest.class ), Arguments.of( (Supplier<?>) Ocpp16TypeStore::new, new ocpp.cp._2015._10.UpdateFirmwareRequest(), "UpdateFirmware", ocpp.cp._2015._10.UpdateFirmwareResponse.class, "BootNotification", ocpp.cs._2015._10.BootNotificationRequest.class ) ); }Note: Adjust the common TypeStore interface/package name if different in your codebase.
Also applies to: 48-58, 61-71
steve/src/test/java/de/rwth/idsg/steve/utils/TransactionStopServiceHelperTest.java (2)
28-33: Clarify rounding semantics for floatingStringToIntString via parameterized testThe expectations suggest ceiling behavior (e.g., "11.01" → "12"). If that’s intentional, encode the contract with edge cases to prevent regressions.
Example (additive, not a strict diff):
// imports: org.junit.jupiter.params.ParameterizedTest; org.junit.jupiter.params.provider.CsvSource; @ParameterizedTest @CsvSource({ "11.00,11", "11.01,12", "11.49,12", "11.50,12", "234.678,235", "-0.1,0" // if negatives are possible, confirm desired behavior }) void testFloatingStringToIntString_rounding(String input, String expected) { var actual = TransactionStopServiceHelper.floatingStringToIntString(input); assertThat(actual).isEqualTo(expected); }Also applies to: 35-41
54-55: Reduce duplication in isEnergyValue tests with a single parameterized tableGreat coverage across formats/units/measurands. You can keep the same assertions while consolidating boilerplate with a parameterized test for maintainability.
Example (concise illustration):
// imports: Stream, Arguments, MethodSource, ParameterizedTest @ParameterizedTest(name = "{index}: {0}") @MethodSource("energyCases") void testIsEnergy_cases(String description, TransactionDetails.MeterValues mv, boolean expected) { assertThat(TransactionStopServiceHelper.isEnergyValue(mv)).isEqualTo(expected); } static Stream<Arguments> energyCases() { return Stream.of( Arguments.of("empty", TransactionDetails.MeterValues.builder().build(), false), Arguments.of("only value", TransactionDetails.MeterValues.builder().value("22").build(), true), Arguments.of("decimal value", TransactionDetails.MeterValues.builder().value("22.5").build(), true), Arguments.of("signed data", TransactionDetails.MeterValues.builder().value("x").format("SignedData").build(), false), Arguments.of("wrong unit", TransactionDetails.MeterValues.builder().value("22").format("Raw").unit("Celsius").build(), false), Arguments.of("wrong measurand", TransactionDetails.MeterValues.builder().value("22").format("Raw").unit("Wh").measurand("Current.Export").build(), false), Arguments.of("null format, Wh, active import", TransactionDetails.MeterValues.builder().value("22").format(null).unit("Wh").measurand("Energy.Active.Import.Register").build(), true), Arguments.of("raw, Wh, active import", TransactionDetails.MeterValues.builder().value("22").format("Raw").unit("Wh").measurand("Energy.Active.Import.Register").build(), true), Arguments.of("kWh unit, active import", TransactionDetails.MeterValues.builder().value("22").format("Raw").unit("kWh").measurand("Energy.Active.Import.Register").build(), true), Arguments.of("non-numeric value", TransactionDetails.MeterValues.builder().value("22a819()b").format("Raw").unit("Wh").measurand("Energy.Active.Import.Register").build(), false), Arguments.of("value and unit only", TransactionDetails.MeterValues.builder().value("22").unit("Wh").build(), true) ); }Also applies to: 63-64, 72-73, 82-83, 93-94, 105-106, 117-118, 129-130, 141-142, 153-154, 163-164
steve/src/test/java/de/rwth/idsg/steve/issues/Issue73Fix.java (4)
51-76: Avoid static mutable state for path; pass as a parameterStatic field can leak across parallel test runs. Keep it local and pass it down.
Apply:
- private static String path; + // (remove static mutable field) @@ - path = getPath(config); + var path = getPath(config); @@ - test(); + test(path); @@ - private static void test() { - var client = getForOcpp16(path); + private static void test(String path) { + var client = getForOcpp16(path);
76-76: Assert client creation succeededSmall guard helps fail fast if routing/bootstrap changes break the client factory.
- var client = getForOcpp16(path); + var client = getForOcpp16(path); + assertThat(client).isNotNull();
78-82: Prefer JDK collections over Guava in testsUse List.of to reduce external deps and emphasize immutability.
- var chargeBox1 = getRandomString(); - var chargeBox2 = getRandomString(); - - sendBoot(client, Lists.newArrayList(chargeBox1, chargeBox2)); + var chargeBox1 = getRandomString(); + var chargeBox2 = getRandomString(); + var chargeBoxIds = List.of(chargeBox1, chargeBox2); + sendBoot(client, chargeBoxIds);Additionally remove the Guava import:
// delete: import com.google.common.collect.Lists;
62-72: Consider makingApplicationAutoCloseable for try-with-resourcesThe current nested
try/finallycorrectly ensuresstart/stopand cleanup, but you can simplify and harden this by havingApplicationimplementAutoCloseable. That lets you replace the outertry/finallywith a single try-with-resources block:• steve/src/main/java/de/rwth/idsg/steve/Application.java (line 35):
– Current declaration:
java public class Application { // … }
– To enable try-with-resources, update to:
diff - public class Application { + public class Application implements AutoCloseable { // … + + @Override + public void close() { + stop(); + } }• steve/src/test/java/de/rwth/idsg/steve/issues/Issue73Fix.java (lines 62–72):
– Current pattern:
java var app = new Application(config); try { app.start(); test(); } finally { try { app.stop(); } finally { __DatabasePreparer__.cleanUp(); } }
– WithApplicationnowAutoCloseable, this becomes:
java try (var app = new Application(config)) { app.start(); test(); } finally { __DatabasePreparer__.cleanUp(); }No other changes are required—the existing cleanup call remains. If you prefer not to modify
Application, the current implementation is correct and safe.steve/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java (2)
128-136: Prefer MediaType over string literal for contentType.Use the type-safe overload to prevent typos and align with common style.
- .contentType(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON)
150-155: Same nit: use MediaType constant for PUT.- .contentType(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON)steve/src/test/java/de/rwth/idsg/steve/web/api/UsersRestControllerTest.java (1)
44-56: Migration to MockMvcTester is good; consider asserting body shape too.The status and content-type checks are fine. For parity with other tests (e.g., reservations), optionally assert that the body is an empty array when the service returns no users.
- assertThat(mockMvc.perform(get("/api/users"))) - .hasStatusOk() - .hasContentTypeCompatibleWith(MediaType.APPLICATION_JSON); + assertThat(mockMvc.perform(get("/api/users"))) + .hasStatusOk() + .hasContentTypeCompatibleWith(MediaType.APPLICATION_JSON) + .bodyJson() + .hasPathSatisfying("$", path -> assertThat(path).asArray().isEmpty());steve/src/test/java/de/rwth/idsg/steve/web/api/TransactionRestControllerTest.java (2)
158-159: Happy-path with full filter set is thorough and time-safe.
- Using OffsetDateTime ensures zone-aware timestamps; assertions on raw and formatted fields make the contract explicit.
- Minor optional improvement: derive "from" and "to" query params from the start variable to keep the test invariant if the seed changes.
- .param("from", "2022-10-01T00:00") - .param("to", "2022-10-08T00:00") + .param("from", start.toLocalDateTime().toString().substring(0,16)) // "yyyy-MM-ddTHH:mm" + .param("to", start.plusDays(7).toLocalDateTime().toString().substring(0,16))Also applies to: 175-194
253-260: Tighten errorJsonMatchers status assertion.Currently it only checks status is not null. As these matchers are used for 4xx/5xx cases, we can assert the status is in the error range to catch accidental 200/201.
- .hasPathSatisfying("$.status", path -> assertThat(path).isNotNull()) + .hasPathSatisfying("$.status", path -> assertThat(path).asNumber().isBetween(400, 599))If you want even stricter checks per test, consider parameterizing the expected status and asserting equality instead.
steve/src/test/java/de/rwth/idsg/steve/web/api/OcppTagsRestControllerTest.java (3)
137-180: Stabilize OffsetDateTime parse to match serialized expectation.You parse a timestamp with milliseconds (Line [139]) but assert a millisecond-less serialization (Line [175]). While semantically equivalent, making the input string mirror the asserted format avoids surprises if the Jackson config ever changes formatting flags.
Apply this minimal tweak:
- var someDate = OffsetDateTime.parse("2020-10-01T00:00:00.000Z"); + var someDate = OffsetDateTime.parse("2020-10-01T00:00:00Z");Optional: if supported by your Json assertions, asserting the field equals
someDate.toString()would couple the expectation to the source value rather than a hard-coded literal.
197-201: Status assertion style varies; consider unifying for consistency.You mix
hasStatusOk(),hasStatus4xxClientError(), andhasStatus(HttpStatus.NOT_FOUND). All valid, but adopting a single style (e.g., alwayshasStatus(HttpStatus.X)for explicitness) can reduce cognitive load.Also applies to: 214-218, 285-289, 317-318, 385-388, 410-413, 425-429
525-531: Make errorJsonMatchers stricter for the status field.Asserting only non-null allows false positives. You can safely assert the error status range while keeping this reusable for 4xx and 5xx tests.
- .hasPathSatisfying("$.status", path -> assertThat(path).isNotNull()) + .hasPathSatisfying("$.status", path -> assertThat(path).asNumber().isBetween(400, 599))If the API returns the canonical reason phrase as
errorand the numeric code asstatus, this remains correct across your error cases.steve/src/test/java/de/rwth/idsg/steve/issues/Issue81.java (5)
56-56: ThreadLocal usage per stress-thread is appropriate; consider lightweight cleanupThe per-thread state (client, ids, request) is isolated correctly. Optionally call ThreadLocal#remove() in afterRepeat() to help GC in long-lived executors.
86-90: Idempotency check is clear; consider asserting the baseline is non-null for sharper failuresOptional: assertThat(txId.get()).isNotNull() before comparing equality for a more direct assertion message if the first call fails.
// Optionally add right after txId.set(t1); assertThat(txId.get()).isNotNull();
104-106: Also assert transactionId is presentstart being non-null doesn’t guarantee a non-null transactionId. Add an assertion to catch server-side anomalies earlier.
var start = client.startTransaction(req, chargeBoxId); assertThat(start).isNotNull(); + assertThat(start.getTransactionId()).isNotNull(); return start.getTransactionId();
67-77: Minor consistency: prefer one style for getRandomString usageYou’re mixing Helpers.getRandomString() and the static import getRandomString(). Pick one (the static import is already present), e.g., replace Helpers.getRandomString() at Lines 67 and 77 with getRandomString().
68-74: Issue81 Test: reuse client, add null-check, and cap vendor/model to CiString20Based on the current implementation in
steve/src/test/java/de/rwth/idsg/steve/issues/Issue81.java(lines 68–74), I recommend the following optional refactor to:
- Avoid creating a second OCPP client for the boot notification.
- Make the test failure clearer by asserting the response is non-null.
- Guard against exceeding the OCPP 1.6 CiString20 length limit on vendor/model.
Suggested diff:
--- a/steve/src/test/java/de/rwth/idsg/steve/issues/Issue81.java +++ b/steve/src/test/java/de/rwth/idsg/steve.issues/Issue81.java @@ -65,9 +65,12 @@ protected void beforeRepeat() { - client.set(getForOcpp16(path)); - var boot = getForOcpp16(path).bootNotification( + client.set(getForOcpp16(path)); + var boot = client.get().bootNotification( new BootNotificationRequest() - .withChargePointVendor(getRandomString()) - .withChargePointModel(getRandomString()), + .withChargePointVendor(ci20(getRandomString())) + .withChargePointModel(ci20(getRandomString())), + chargeBoxId.get()); + assertThat(boot).isNotNull(); + assertThat(boot.getStatus()).isEqualTo(RegistrationStatus.ACCEPTED);Add this helper at the bottom of the test class, outside the anonymous runnable, to truncate any string over 20 characters:
/** * Ensures strings conform to CiString20 (max 20 chars) in OCPP 1.6. */ private static String ci20(String s) { return (s == null || s.length() <= 20) ? s : s.substring(0, 20); }Key locations:
- Issue81.java @ beforeRepeat(): switch
getForOcpp16(path).bootNotification(...)→client.get().bootNotification(...)- Issue81.java @ after boot invocation: insert
assertThat(boot).isNotNull();- Issue81.java: add
ci20(...)helper method
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (39)
pom.xml(2 hunks)steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/UpdateTransactionParams.java(2 hunks)steve-core/src/main/java/de/rwth/idsg/steve/utils/DateTimeUtils.java(2 hunks)steve-ocpp-websocket/pom.xml(1 hunks)steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/config/OcppWebSocketConfiguration.java(2 hunks)steve-ocpp-websocket/src/test/java/de/rwth/idsg/steve/ocpp/ws/custom/CustomStringModuleTest.java(2 hunks)steve-ocpp/pom.xml(1 hunks)steve-ocpp/src/main/java/de/rwth/idsg/ocpp/DateTimeUtils.java(1 hunks)steve-ocpp/src/test/java/de/rwth/idsg/ocpp/jaxb/JavaDateTimeConverterTest.java(1 hunks)steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/connectorStatus.jsp(2 hunks)steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/data-man/ocppTags.jsp(3 hunks)steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/data-man/transactionDetails.jsp(2 hunks)steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/data-man/transactions.jsp(2 hunks)steve/pom.xml(1 hunks)steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java(3 hunks)steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java(3 hunks)steve/src/test/java/de/rwth/idsg/steve/OperationalTestSoapOCPP16.java(12 hunks)steve/src/test/java/de/rwth/idsg/steve/StressTest.java(3 hunks)steve/src/test/java/de/rwth/idsg/steve/StressTestJsonOCPP16.java(5 hunks)steve/src/test/java/de/rwth/idsg/steve/StressTestSoapOCPP16.java(3 hunks)steve/src/test/java/de/rwth/idsg/steve/TypeStoreTest.java(2 hunks)steve/src/test/java/de/rwth/idsg/steve/issues/Issue1219.java(4 hunks)steve/src/test/java/de/rwth/idsg/steve/issues/Issue72.java(5 hunks)steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java(6 hunks)steve/src/test/java/de/rwth/idsg/steve/issues/Issue73Fix.java(4 hunks)steve/src/test/java/de/rwth/idsg/steve/issues/Issue81.java(5 hunks)steve/src/test/java/de/rwth/idsg/steve/ocpp/OcppProtocolTest.java(1 hunks)steve/src/test/java/de/rwth/idsg/steve/ocpp/OcppVersionTest.java(1 hunks)steve/src/test/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandlerTest.java(1 hunks)steve/src/test/java/de/rwth/idsg/steve/utils/StringUtilsTest.java(2 hunks)steve/src/test/java/de/rwth/idsg/steve/utils/TransactionStopServiceHelperTest.java(11 hunks)steve/src/test/java/de/rwth/idsg/steve/web/api/AbstractControllerTest.java(2 hunks)steve/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java(5 hunks)steve/src/test/java/de/rwth/idsg/steve/web/api/OcppTagsRestControllerTest.java(14 hunks)steve/src/test/java/de/rwth/idsg/steve/web/api/ReservationRestControllerTest.java(1 hunks)steve/src/test/java/de/rwth/idsg/steve/web/api/TransactionRestControllerTest.java(4 hunks)steve/src/test/java/de/rwth/idsg/steve/web/api/UsersRestControllerTest.java(1 hunks)steve/src/test/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidatorTest.java(1 hunks)steve/src/test/java/de/rwth/idsg/steve/web/validation/IdTagValidatorTest.java(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (24)
- steve/src/test/java/de/rwth/idsg/steve/ocpp/OcppProtocolTest.java
- pom.xml
- steve/src/test/java/de/rwth/idsg/steve/ocpp/OcppVersionTest.java
- steve-ocpp/pom.xml
- steve/src/test/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandlerTest.java
- steve-ocpp-websocket/pom.xml
- steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/data-man/transactionDetails.jsp
- steve/src/test/java/de/rwth/idsg/steve/issues/Issue1219.java
- steve/src/test/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidatorTest.java
- steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/UpdateTransactionParams.java
- steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/connectorStatus.jsp
- steve/src/test/java/de/rwth/idsg/steve/StressTest.java
- steve-ocpp-websocket/src/test/java/de/rwth/idsg/steve/ocpp/ws/custom/CustomStringModuleTest.java
- steve/src/test/java/de/rwth/idsg/steve/utils/StringUtilsTest.java
- steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/data-man/transactions.jsp
- steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java
- steve/src/test/java/de/rwth/idsg/steve/StressTestSoapOCPP16.java
- steve/pom.xml
- steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/data-man/ocppTags.jsp
- steve/src/test/java/de/rwth/idsg/steve/StressTestJsonOCPP16.java
- steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java
- steve-core/src/main/java/de/rwth/idsg/steve/utils/DateTimeUtils.java
- steve/src/test/java/de/rwth/idsg/steve/OperationalTestSoapOCPP16.java
- steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java
🧰 Additional context used
🧬 Code graph analysis (7)
steve/src/test/java/de/rwth/idsg/steve/web/api/UsersRestControllerTest.java (2)
steve/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java (1)
ExtendWith(50-172)steve/src/test/java/de/rwth/idsg/steve/web/api/ReservationRestControllerTest.java (1)
ExtendWith(38-61)
steve/src/test/java/de/rwth/idsg/steve/web/api/ReservationRestControllerTest.java (3)
steve/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java (1)
ExtendWith(50-172)steve/src/test/java/de/rwth/idsg/steve/web/api/TransactionRestControllerTest.java (1)
ExtendWith(50-261)steve/src/test/java/de/rwth/idsg/steve/web/api/UsersRestControllerTest.java (1)
ExtendWith(38-58)
steve-ocpp/src/main/java/de/rwth/idsg/ocpp/DateTimeUtils.java (2)
steve-core/src/main/java/de/rwth/idsg/steve/utils/DateTimeUtils.java (1)
NoArgsConstructor(34-128)steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/JspDateTimeUtils.java (1)
NoArgsConstructor(28-38)
steve/src/test/java/de/rwth/idsg/steve/issues/Issue73Fix.java (1)
steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java (1)
__DatabasePreparer__(68-202)
steve/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java (1)
steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/ChargePoint.java (1)
ChargePoint(32-49)
steve/src/test/java/de/rwth/idsg/steve/issues/Issue72.java (1)
steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java (1)
__DatabasePreparer__(68-202)
steve/src/test/java/de/rwth/idsg/steve/web/api/OcppTagsRestControllerTest.java (1)
steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/OcppTag.java (1)
OcppTag(34-77)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (18)
- GitHub Check: Analyze (java-kotlin)
- GitHub Check: Analyze (javascript-typescript)
- GitHub Check: Analyze (actions)
- GitHub Check: build (ubuntu-24.04, 21, mariadb:10.5.21)
- GitHub Check: build (ubuntu-24.04, 21, mysql:8.0)
- GitHub Check: build (ubuntu-22.04, 21, mariadb:10.3)
- GitHub Check: build (ubuntu-22.04, 21, mariadb:10.5.21)
- GitHub Check: build (ubuntu-24.04, 17, mysql:8.0)
- GitHub Check: build (ubuntu-22.04, 17, mariadb:10.5.21)
- GitHub Check: build (ubuntu-22.04, 17, mariadb:10.6.14)
- GitHub Check: build (ubuntu-22.04, 21, mariadb:10.4.30)
- GitHub Check: build (ubuntu-22.04, 21, mysql:8.0)
- GitHub Check: license-check
- GitHub Check: build (ubuntu-22.04, 17, mariadb:10.4.30)
- GitHub Check: checkstyle
- GitHub Check: build (ubuntu-22.04, 17, mysql:8.0)
- GitHub Check: build (ubuntu-24.04, 21, mariadb:10.3)
- GitHub Check: build (ubuntu-24.04, 17, mariadb:10.3)
🔇 Additional comments (48)
steve/src/test/java/de/rwth/idsg/steve/web/validation/IdTagValidatorTest.java (1)
31-31: Making the validator field private final is a good, tighter contract.Immutable fixture at class scope is fine for these stateless checks.
steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/config/OcppWebSocketConfiguration.java (1)
22-24: Imports for InvocationContext wiring look goodThe added imports align with the new per-version InvocationContext map and are scoped appropriately. No issues spotted.
Also applies to: 26-26, 28-28, 30-30, 36-36, 44-46
steve/src/test/java/de/rwth/idsg/steve/TypeStoreTest.java (4)
26-26: AssertJ migration looks goodSwitching to AssertJ’s fluent assertions improves readability and consistency across the tests.
49-58: OCPP 1.5 mapping checks are precise and adequateThe test mirrors the 1.2 case and validates both action and response class. Using the fully-qualified generated types keeps intent clear without extra imports.
62-71: OCPP 1.6 test mirrors others; consistent and clearSame coverage as 1.2/1.5: action string and response class mapping are asserted. Looks good.
36-36: Confirmed: Java 17 compliance and safe direct instantiationThe build’s pom.xml sets
<java.version>17</java.version>with the Maven Compiler Plugin enforcingsource/target17, sovaris fully supported. All threeOcpp12/15/16TypeStoreclasses declare a public no-arg constructor (callingsuper(...)) and have no required Spring-injected dependencies or post-construction hooks, so usingnew OcppXXTypeStore()directly in tests is safe. No changes required here.steve/src/test/java/de/rwth/idsg/steve/utils/TransactionStopServiceHelperTest.java (2)
24-24: Migration to AssertJ looks goodNice switch to
assertThatwith a single static import; consistent with the broader PR direction.
30-33: Java 17 toolchain confirmed—varusage is safe
- pom.xml @ lines 28–34 defines
<java.version>17</java.version>
and binds<maven.compiler.source>/<target>to${java.version}.- The maven-compiler-plugin (v3.14.0) at pom.xml line 200+ is configured to compile with source/target set to Java 17.
- No Gradle build files or toolchains.xml overrides were found—Maven is the sole build tool and is aligned to Java 17.
No CI or compilation surprises expected; you can safely keep
varin your tests.steve/src/test/java/de/rwth/idsg/steve/issues/Issue73Fix.java (4)
93-101: Boot flow checks read wellLooping over chargeBoxIds and asserting ACCEPTED is clear and correct for the scenario.
105-108: Authorize assertions are concise and correctAsserting both non-null and expected status is enough here.
111-116: .withTimestamp signature accepts OffsetDateTime – no further verification neededVerified that the fluent
withTimestampmethod accepts ajava.time.OffsetDateTimeargument, as evidenced by its usage in both test classes and converter implementations across the codebase.
If you need deterministic timestamps in future tests—especially when making time-based assertions—consider supplying a fixedClockor trimming nanoseconds for stability, e.g.:- .withTimestamp(OffsetDateTime.now()) + .withTimestamp(OffsetDateTime.now().withNano(0)) // stable to secondsGreat job also asserting the DB in-transaction state immediately after
startTransaction; that will safeguard your concurrent-transaction tests.
33-39: Please verify JAXB-generated OCPP bindings for java.time compatibilityThe java.time and AssertJ updates in Issue73Fix.java look correct. However, I couldn’t locate the XJC-generated OCPP classes (e.g. StartTransactionRequest.java) in the repository to confirm that
withTimestamp(...)now acceptsOffsetDateTime. To avoid any compile- or runtime adapter issues, please:
- Locate your generated OCPP request classes (typically under the binding/output folder or in a dependent module) and inspect the
withTimestamp(...)signatures.- Ensure your XJC binding/custom
XmlAdapteris configured to map XML timestamps tojava.time.OffsetDateTime.- Verify other request types (StopTransactionRequest, MeterValue, etc.) also use
OffsetDateTimein their builder methods.Once confirmed, we can approve the migration.
steve/src/test/java/de/rwth/idsg/steve/web/api/AbstractControllerTest.java (2)
29-29: MockMvcTester adoption looks good.Importing MockMvcTester aligns this base class with the updated AssertJ-style testing DSL used across the suite.
52-57: buildMockMvc call sites updated; no raw MockMvc usage remains
- All concrete controller tests now declare and assign to MockMvcTester (mockMvc), and call buildMockMvc(...) accordingly in UsersRestControllerTest, ReservationRestControllerTest, TransactionRestControllerTest, ChargePointsRestControllerTest, and OcppTagsRestControllerTest.
- AbstractControllerTest no longer declares a MockMvc field (only imports it); you may remove the unused import of org.springframework.test.web.servlet.MockMvc.
- Optional: consider renaming buildMockMvc to buildMockMvcTester or adding a brief Javadoc to clarify the return type and avoid confusion.
steve/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java (6)
32-41: Consistent migration to MockMvcTester and AssertJ fluent assertions.Imports, field type, and assertion style are all correctly updated to the new testing DSL.
Also applies to: 56-56
68-72: Empty list GET case reads well.The JSON body assertion using hasPathSatisfying("$", ... isEmpty()) is clear and robust.
80-85: Single-result GET case is precise.Checks both array size and the key field, which is sufficient for this path.
92-94: 404 case is correct.Asserting HttpStatus.NOT_FOUND directly is clear.
102-106: Verify JSON shape for GET one (flattened vs nested).The assertion targets "$.chargeBoxPk". Given ChargePoint.Details contains a ChargeBoxRecord chargeBox, many serializers would nest as "$.chargeBox.chargeBoxPk" unless custom flattening (e.g., @JsonUnwrapped or DTO mapping) is in place. Please confirm the controller response shape; if nested, update the path accordingly.
If nested, apply:
- .hasPathSatisfying("$.chargeBoxPk", path -> assertThat(path).asNumber().isEqualTo(1)); + .hasPathSatisfying("$.chargeBox.chargeBoxPk", path -> assertThat(path).asNumber().isEqualTo(1));
167-169: DELETE happy path is concise and clear.Status assertion plus service verification is sufficient here.
steve/src/test/java/de/rwth/idsg/steve/web/api/ReservationRestControllerTest.java (1)
44-60: LGTM on MockMvcTester migration and assertions.Clear, consistent with the updated testing DSL, and validates both content type and empty body.
steve/src/test/java/de/rwth/idsg/steve/web/api/TransactionRestControllerTest.java (11)
31-41: Good adoption of MockMvcTester + JsonContent for flexible JSON assertions.The imports set up a clean AssertJ-based testing style and enable reusable error JSON matchers.
56-56: Field type updated to MockMvcTester.Consistent with the base class change.
73-76: Empty result case is correct.The array emptiness check on "$" is appropriate.
103-107: 5xx error handling test is solid.Verifies status family and shared error payload structure via the reusable matcher.
112-118: Hidden param validation (400) covered well.Good negative test guarding against unsupported/hidden params.
123-129: Bad enum value validation (400) covered well.Typo-driven failure path is clear and deterministic.
134-140: Missing dependent params (400) covered well.Captures the FROM_TO invariants at the API boundary.
145-153: Temporal validation (to < from) is correctly asserted as 400.Good guardrail for consumer mistakes.
199-207: Invalid ISO format path (400) covered well.Clear demonstration of rejecting non-ISO local date-times.
219-229: Type translation defaulting verified.Captures that only 'type' is set while 'periodType' defaults to ALL.
241-251: PeriodType translation defaulting verified.Captures that 'periodType' is honored while 'type' defaults to ALL.
steve/src/test/java/de/rwth/idsg/steve/web/api/OcppTagsRestControllerTest.java (4)
71-76: Nice migration to MockMvcTester — setup is concise and clear.Using MockMvcTester plus the helper in AbstractControllerTest keeps the tests readable and removes brittle matcher chains. LGTM.
88-92: Fluent JSON assertions improve readability.The transition to
bodyJson().hasPathSatisfying(...)andhasStatus*reads well and keeps focus on the payload. No issues spotted in these flows.Also applies to: 104-109, 118-122, 126-133
469-479: Good use of ArgumentCaptor to validate query param translation — LGTM.Capturing
OcppTagQueryFormForApiand asserting enum mappings forexpired,inTransaction, andblockedis precise and future-proof.Also applies to: 491-501, 513-523
319-320: No changes needed—OcppTagForm already implements equals/hashCode via LombokThe concern about
eq(form)failing is unfounded:OcppTagFormis annotated with Lombok’s@EqualsAndHashCode, so the test’sforminstance and the controller-deserialized instance compare equal. Therefore, usingverify(ocppTagsService).updateOcppTag(eq(form));is valid and will pass as intended.
If you still prefer more explicit field-by-field assertions for clarity, you can optionally use an
ArgumentCaptorand assert individual properties; however, it’s not required for correctness.Likely an incorrect or invalid review comment.
steve/src/test/java/de/rwth/idsg/steve/issues/Issue81.java (3)
38-38: AssertJ adoption looks consistentThe switch to assertThat(...) improves readability and failure messages. Keep it consistent across this class and neighboring tests.
98-98: StressTester usage reads cleanlyPassing THREAD_COUNT and REPEAT_COUNT_PER_THREAD via constructor keeps the test parameters centralized. LGTM.
50-56: Java toolchain already targets Java 17;varis supportedYour Maven configuration sets the
java.versionproperty to 17 and configures the Maven Compiler Plugin accordingly:
- In
pom.xmllines 31–33:
<java.version>17</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>- In the
maven-compiler-pluginconfiguration (version 3.14.0) on lines 203–204:
<source>${java.version}</source>
<target>${java.version}</target>Since Java 17 is well above the Java 10 minimum required for
var, no changes are needed.steve-ocpp/src/main/java/de/rwth/idsg/ocpp/DateTimeUtils.java (4)
77-78: LGTM - DST-safe offset computation.The method correctly uses
fallbackZoneId.getRules().getOffset(ldt)to compute the offset from the local date-time. This is appropriate for handling offset-less input strings and provides deterministic behavior during DST transitions.
94-95: LGTM - DST-safe instant-based offset conversion.The code properly uses
zoneId.getRules().getOffset(dateTime.toInstant())to compute the target offset based on the instant, which avoids DST transition ambiguities. This approach ensures correct behavior when converting between time zones.
37-53: Well-designed flexible ISO-8601 parser.The
OCPP_DATETIME_PARSERcorrectly handles optional fractional seconds and offset components usingDateTimeFormatterBuilder. The use ofappendFractionwith 1-9 nano precision andoptionalStart/Endblocks provides the flexibility needed for OCPP datetime parsing.
58-64: Appropriate fixed-format formatter.The
OCPP_DATETIME_FORMATTERuses a consistent 3-digit millisecond format as noted in the comments. The separation of parser and formatter responsibilities is well-architected - the parser is flexible while the formatter ensures consistent output format.steve/src/test/java/de/rwth/idsg/steve/issues/Issue72.java (5)
37-37: LGTM - Clean migration to OffsetDateTime and AssertJ.The import changes properly replace Joda DateTime with
java.time.OffsetDateTimeand introduce AssertJ for more readable assertions. This aligns with the project-wide migration to Java Time APIs.Also applies to: 42-42
62-71: LGTM - Consistent use of var and OffsetDateTime.The local variables now use
varfor type inference andOffsetDateTimefor temporal values. The logic remains unchanged while adopting modern Java practices and the new time API.
73-78: LGTM - AssertJ assertion improvement.The replacement of
Assert.assertEquals(RegistrationStatus.ACCEPTED, boot.getStatus())withassertThat(boot.getStatus()).isEqualTo(RegistrationStatus.ACCEPTED)provides better readability and more informative failure messages.
80-91: LGTM - Consistent modernization pattern.The
startTransactioncall usesvarfor type inference and the assertion is updated to AssertJ'sassertThat(...).isNotNull()pattern, maintaining consistency with the rest of the modernization.
103-116: LGTM - Clean test method updates.The
toRepeat()method consistently applies the same modernization patterns:
varfor local variablesOffsetDateTimefor timestamp fields- AssertJ assertions for null checks
The test logic and flow remain unchanged while adopting the improved APIs.
Also applies to: 118-126
|
|
||
| // when | ||
| when(ocppTagsService.addOcppTag(eq(form))).thenReturn(ocppTagPk); | ||
| when(ocppTagsService.addOcppTag(form)).thenReturn(ocppTagPk); |
There was a problem hiding this comment.
Mockito stub uses a concrete form instance — likely mismatch at runtime.
The controller will receive a deserialized copy of the JSON body, not the same form instance used in the test setup. Stubbing with that concrete instance risks not matching and returning Mockito’s default (0), which can make the test pass for the wrong reasons or break if the controller logic changes.
Use a matcher:
- when(ocppTagsService.addOcppTag(form)).thenReturn(ocppTagPk);
+ when(ocppTagsService.addOcppTag(any(OcppTagForm.class))).thenReturn(ocppTagPk);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| when(ocppTagsService.addOcppTag(form)).thenReturn(ocppTagPk); | |
| ++ b/steve/src/test/java/de/rwth/idsg/steve/web/api/OcppTagsRestControllerTest.java | |
| @@ -276,1 +276,1 @@ | |
| when(ocppTagsService.addOcppTag(any(OcppTagForm.class))).thenReturn(ocppTagPk); |
🤖 Prompt for AI Agents
In
steve/src/test/java/de/rwth/idsg/steve/web/api/OcppTagsRestControllerTest.java
around line 276, the Mockito stub uses the concrete form instance `form` which
won't match the deserialized instance the controller creates; replace the
concrete argument with a matcher such as any(OcppTagForm.class) or eq/refEq(...)
as appropriate so the stub matches the controller's parameter and returns the
expected ocppTagPk.
| when(ocppTagsService.addOcppTag(form)).thenThrow(new SteveException.AlreadyExists("A user with idTag '%s' already exists.", ocppTagPk)); | ||
|
|
There was a problem hiding this comment.
Same stubbing pitfall for AlreadyExists branch — will not match deserialized form.
This test relies on throwing AlreadyExists, but the stub matches only the exact form instance. It likely won’t trigger, causing false negatives.
Make the stub robust:
- when(ocppTagsService.addOcppTag(form)).thenThrow(new SteveException.AlreadyExists("A user with idTag '%s' already exists.", ocppTagPk));
+ when(ocppTagsService.addOcppTag(any(OcppTagForm.class)))
+ .thenThrow(new SteveException.AlreadyExists("A user with idTag '%s' already exists.", ocppTagPk));📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| when(ocppTagsService.addOcppTag(form)).thenThrow(new SteveException.AlreadyExists("A user with idTag '%s' already exists.", ocppTagPk)); | |
| when(ocppTagsService.addOcppTag(any(OcppTagForm.class))) | |
| .thenThrow(new SteveException.AlreadyExists("A user with idTag '%s' already exists.", ocppTagPk)); |
🤖 Prompt for AI Agents
In
steve/src/test/java/de/rwth/idsg/steve/web/api/OcppTagsRestControllerTest.java
around lines 443-444, the stub for ocppTagsService.addOcppTag currently matches
only the exact form instance and therefore won’t match the deserialized form
used by the controller; replace the strict-instance stub with a Mockito argument
matcher (e.g., any(OcppTagForm.class) or an argThat that compares relevant
fields) so the service throws SteveException.AlreadyExists for the
incoming/deserialized form; ensure the matcher reflects required fields (or use
eq for specific field values) and keep the same exception being thrown.
steve/src/test/java/de/rwth/idsg/steve/web/api/TransactionRestControllerTest.java
Show resolved
Hide resolved
ceed558 to
d91851f
Compare
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
steve/src/test/java/de/rwth/idsg/steve/web/api/TransactionRestControllerTest.java (1)
89-94: Fix contradictory assertions: array checked empty, then indexing at [0].You assert that the root array is empty and subsequently assert on element [0]. Update the first assertion to expect size 1.
Apply this diff:
- .hasPathSatisfying("$", path -> assertThat(path).asArray().isEmpty()) + .hasPathSatisfying("$", path -> assertThat(path).asArray().hasSize(1))
🧹 Nitpick comments (6)
steve/src/test/java/de/rwth/idsg/steve/web/api/TransactionRestControllerTest.java (6)
112-118: Reduce repetition in negative-path tests via parameterization.Tests 4–7 follow the same pattern (perform -> expect 4xx -> error JSON shape). Consider a single @ParameterizedTest that feeds invalid param sets to cut duplication and ease future maintenance.
Example sketch (new code, not a diff):
@ParameterizedTest(name = "{index}: {0}") @MethodSource("invalidQueryParams") void invalidQueries_return400(String display, MultiValueMap<String,String> params) { assertThat(mockMvc.perform(get("/api/v1/transactions").params(params))) .hasStatus4xxClientError() .bodyJson() .satisfies(errorJsonMatchers()); } private static Stream<Arguments> invalidQueryParams() { return Stream.of( Arguments.of("Hidden param used", params("returnCSV","true")), Arguments.of("Typos in periodType", params("periodType","TODAYZZZ")), Arguments.of("FROM_TO without from/to", params("periodType","FROM_TO")), Arguments.of("to before from", params("periodType","FROM_TO","from","2022-10-01T00:00","to","2022-09-01T00:00")) ); } private static MultiValueMap<String,String> params(String... kv) { var map = new LinkedMultiValueMap<String,String>(); for (int i=0;i<kv.length;i+=2) map.add(kv[i], kv[i+1]); return map; }Also applies to: 123-129, 134-140, 145-153
190-190: Prefer explicit ISO formatter over toString() for stability.Using toString() is usually fine, but asserting with a fixed formatter avoids surprises across JDKs/locales and makes intent explicit.
- .hasPathSatisfying("$[0].startTimestamp", path -> assertThat(path).asString().isEqualTo(start.toString())) + .hasPathSatisfying("$[0].startTimestamp", path -> assertThat(path).asString().isEqualTo(start.format(java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME))) @@ - .hasPathSatisfying("$[0].stopTimestamp", path -> assertThat(path).asString().isEqualTo(stop.toString())) + .hasPathSatisfying("$[0].stopTimestamp", path -> assertThat(path).asString().isEqualTo(stop.format(java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME)))Also applies to: 192-192
197-198: Polish DisplayName grammar.Minor wording nit: “from and to are not ISO compliant” reads better.
- @DisplayName("from and to have are not conform with ISO") + @DisplayName("from and to are not ISO compliant")
219-223: Also assert the response body is an empty array for consistency.Since the repository returns Collections.emptyList(), assert the payload shape too (like in test1).
- assertThat(mockMvc.perform(get("/api/v1/transactions") - .param("type", "ACTIVE") - )) - .hasStatusOk(); + assertThat(mockMvc.perform(get("/api/v1/transactions") + .param("type", "ACTIVE") + )) + .hasStatusOk() + .bodyJson() + .hasPathSatisfying("$", path -> assertThat(path).asArray().isEmpty());
241-245: Mirror the body assertions here as well.Same rationale as test10: assert we returned an empty array body.
- assertThat(mockMvc.perform(get("/api/v1/transactions") - .param("periodType", "LAST_30") - )) - .hasStatusOk(); + assertThat(mockMvc.perform(get("/api/v1/transactions") + .param("periodType", "LAST_30") + )) + .hasStatusOk() + .bodyJson() + .hasPathSatisfying("$", path -> assertThat(path).asArray().isEmpty());
253-260: Tighten error JSON assertions: ensure numeric status in 4xx/5xx range.Strengthen the status check; everything else looks good.
- .hasPathSatisfying("$.status", path -> assertThat(path).isNotNull()) + .hasPathSatisfying("$.status", path -> assertThat(path).asNumber().isBetween(400, 599))
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
steve/src/test/java/de/rwth/idsg/steve/web/api/TransactionRestControllerTest.java(4 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (19)
- GitHub Check: license-check
- GitHub Check: checkstyle
- GitHub Check: build (ubuntu-24.04, 21, mariadb:10.3)
- GitHub Check: build (ubuntu-24.04, 21, mariadb:10.6.14)
- GitHub Check: build (ubuntu-24.04, 21, mariadb:10.5.21)
- GitHub Check: build (ubuntu-22.04, 21, mariadb:10.3)
- GitHub Check: build (ubuntu-22.04, 17, mariadb:10.4.30)
- GitHub Check: build (ubuntu-22.04, 21, mariadb:10.4.30)
- GitHub Check: build (ubuntu-24.04, 17, mariadb:10.4.30)
- GitHub Check: build (ubuntu-22.04, 17, mariadb:10.5.21)
- GitHub Check: build (ubuntu-22.04, 17, mariadb:10.6.14)
- GitHub Check: build (ubuntu-24.04, 17, mariadb:10.3)
- GitHub Check: build (ubuntu-22.04, 21, mariadb:10.6.14)
- GitHub Check: build (ubuntu-22.04, 17, mysql:8.0)
- GitHub Check: build (ubuntu-22.04, 21, mariadb:10.5.21)
- GitHub Check: Analyze (actions)
- GitHub Check: build (ubuntu-24.04, 21, mariadb:10.6.14)
- GitHub Check: build (ubuntu-24.04, 17, mariadb:10.4.30)
- GitHub Check: build (ubuntu-24.04, 21, mariadb:10.3)
🔇 Additional comments (3)
steve/src/test/java/de/rwth/idsg/steve/web/api/TransactionRestControllerTest.java (3)
31-40: LGTM: Imports reflect the migration to AssertJ + MockMvcTester and java.time.The added imports (JsonContent, MockMvcTester, OffsetDateTime, Consumer, AssertJ static import) are consistent with the test harness refactor and time migration. No issues spotted.
73-76: LGTM: Clear assertion for empty results.The assertion chain correctly verifies 200 OK and an empty array payload.
56-61: Confirmed return type of buildMockMvcI’ve verified that in
AbstractControllerTest.javaat line 52, the method signature is:protected MockMvcTester buildMockMvc(StandaloneMockMvcBuilder builder) { return MockMvcTester.create(…); }so it indeed returns a
MockMvcTesteras expected.• Location to check:
- steve/src/test/java/de/rwth/idsg/steve/web/api/AbstractControllerTest.java:52
Optional nitpick: for even clearer naming, you might consider renaming
buildMockMvctobuildMockMvcTester, but this is purely stylistic.
d91851f to
b643c13
Compare
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java (1)
1-203: Style and Naming Issues in ApplicationJsonTest.javaPlease address the following remaining concerns in
steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java:
Lines exceeding 120 characters
• Line 86: lambda assertion for OCPP 1.2 boot response
• Line 93: lambda assertion for OCPP 1.2 auth response
• Line 110: lambda assertion for OCPP 1.5 boot response
• Line 117: lambda assertion for OCPP 1.5 auth response
• Line 141: lambda assertion for OCPP 1.6 auth response
These should be wrapped or refactored to comply with the 120-char limit.Bare
error -> fail()callbacks (no message) at lines 87, 94, 111, 118, 135, 142, 197
Each invocation offail()should include a descriptive message to aid in diagnosing test failures.Misspelled test method name (line 175)
Changepublic void tesWithUnauthorizedStation()topublic void testWithUnauthorizedStation()for consistency.No stray semicolons in lambdas were found. Once these adjustments are made, the style check should pass.
♻️ Duplicate comments (20)
steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/config/OcppWebSocketConfiguration.java (1)
47-49: Prefer qualifying enum constants and fix stray whitespace
- Optional: drop the static constant imports and qualify with OcppVersion for clarity and to avoid future static-import collisions.
- Nit: remove the stray space before “.class”.
Proposed diff:
-import static de.rwth.idsg.steve.ocpp.OcppVersion.V_12; -import static de.rwth.idsg.steve.ocpp.OcppVersion.V_15; -import static de.rwth.idsg.steve.ocpp.OcppVersion.V_16; @@ - var invocationContexts = new EnumMap<OcppVersion, InvocationContext>(OcppVersion .class); - invocationContexts.put(V_12, new InvocationContext(ocpp12WebSocketEndpoint, ocpp12TypeStore, + var invocationContexts = new EnumMap<OcppVersion, InvocationContext>(OcppVersion.class); + invocationContexts.put(OcppVersion.V_12, new InvocationContext(ocpp12WebSocketEndpoint, ocpp12TypeStore, CommunicationTask::getOcpp12Request)); - invocationContexts.put(V_15, new InvocationContext(ocpp15WebSocketEndpoint, ocpp15TypeStore, + invocationContexts.put(OcppVersion.V_15, new InvocationContext(ocpp15WebSocketEndpoint, ocpp15TypeStore, CommunicationTask::getOcpp15Request)); - invocationContexts.put(V_16, new InvocationContext(ocpp16WebSocketEndpoint, ocpp16TypeStore, + invocationContexts.put(OcppVersion.V_16, new InvocationContext(ocpp16WebSocketEndpoint, ocpp16TypeStore, CommunicationTask::getOcpp16Request));Also applies to: 92-98
steve/src/test/java/de/rwth/idsg/steve/issues/Issue81.java (1)
75-79: Great: connectorId ≥1, meterStart ≥0, UTC timestamp — also cap idTag to 20 charsThe fixes address prior concerns about invalid values and timezone. One remaining edge: idTag in OCPP 1.6 is typically max 20 chars; bound it to avoid random rejections.
Apply this small change:
- .withIdTag(Helpers.getRandomString()) + .withIdTag(ocppBound(Helpers.getRandomString(), 20))Run this to confirm whether Helpers.getRandomString() already enforces a max length:
#!/bin/bash # Locate Helpers.getRandomString and inspect its signature/usages. fd -a Helpers.java rg -n -C3 'class\s+Helpers\b' rg -n -C3 -P '\bstatic\s+(\w+\s+)?String\s+getRandomString\s*\(' rg -n -C2 -P '\bgetRandomString\s*\(' --type=javasteve/src/test/java/de/rwth/idsg/steve/OperationalTestSoapOCPP16.java (3)
498-498: Fix Checkstyle: wrap long assertion line to <=120 chars (CI failure).The chained assertion exceeds the 120-char limit and breaks Checkstyle. Wrap it across lines.
- assertThat(t.getStartTimestamp()).isCloseTo(startTimeStamp.toLocalDateTime(), byLessThan(1, ChronoUnit.SECONDS)); + assertThat(t.getStartTimestamp()) + .isCloseTo( + startTimeStamp.toLocalDateTime(), + byLessThan(1, ChronoUnit.SECONDS) + );Run this to spot any other overly long lines in the file:
#!/usr/bin/env bash awk 'length($0) > 120 { printf("%s:%d: %s\n", FILENAME, FNR, $0) }' \ steve/src/test/java/de/rwth/idsg/steve/OperationalTestSoapOCPP16.java
549-549: Fix Checkstyle: wrap the stop-timestamp assertion line (<=120 chars).Same issue as above; the line is over the limit.
- assertThat(t.getStopTimestamp()).isCloseTo(stopTimeStamp.toLocalDateTime(), byLessThan(1, ChronoUnit.SECONDS)); + assertThat(t.getStopTimestamp()) + .isCloseTo( + stopTimeStamp.toLocalDateTime(), + byLessThan(1, ChronoUnit.SECONDS) + );
598-609: Bug: asserting empty list but then indexing sampledValues.get(0).
assertThat(sampledValues).isEmpty()contradicts the subsequentsampledValues.get(0)access and will fail. Also, only checkingget(0)is brittle when multiple sampled values exist.Minimal safe fix plus robust matching against all sampled values:
- var sampledValues = meterValue.getSampledValue(); - assertThat(sampledValues).isEmpty(); - var thisValueFound = false; - // and check, if it can be found in the DB - for (var values : details.getValues()) { - if (values.getValue().equals(sampledValues.get(0).getValue())) { - thisValueFound = true; - break; - } - } + var sampledValues = meterValue.getSampledValue(); + assertThat(sampledValues).isNotEmpty(); + // check whether ANY sampled value is present in the DB values + var thisValueFound = details.getValues().stream() + .anyMatch(values -> + sampledValues.stream() + .anyMatch(sv -> values.getValue().equals(sv.getValue())) + ); assertThat(thisValueFound).isTrue();steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java (3)
42-49: Add the missing StandardCharsets import and drop Guava MediaType import (cleanup after Content-Type fix)To support the proposed Content-Type change, ensure
StandardCharsetsis imported, and remove the now-unused GuavaMediaTypeimport.- import com.google.common.net.MediaType; + import java.nio.charset.StandardCharsets;
98-100: Fix SOAP Content-Type (invalid header + compile risk) and resolve Checkstyle L99
- Guava does not define
MediaType.SOAP_XML_UTF_8; this will not compile.MediaType#type()returns only"application", yielding an invalid header likeContent-Type: application; charset=UTF-8.- The simplified fix also shortens the line and addresses the Checkstyle long-line error reported at Line 99.
Apply:
- var contentType = ContentType.create(MediaType.SOAP_XML_UTF_8.type(), MediaType.SOAP_XML_UTF_8.charset().orNull()); + var contentType = ContentType.create("application/soap+xml", StandardCharsets.UTF_8);And adjust imports accordingly (see below).
- import com.google.common.net.MediaType; + import java.nio.charset.StandardCharsets;Run to confirm no lingering usages and that the line-length warning clears:
#!/usr/bin/env bash rg -n 'MediaType\.SOAP_XML_UTF_8|ContentType\.create\("application/soap\+xml"' steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java # Expect only the "application/soap+xml" variant. # Optional: show that line 99 no longer exceeds 120 chars after your change nl -ba steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java | sed -n '95,105p'
101-105: Do not reuse HttpUriRequest across threads; build per-iteration
HttpUriRequest(and itsHttpEntity) are not thread-safe or reusable across concurrent executions. Constructingreqonce and using it from all threads risks races andIllegalStateException. Build a fresh request (and entity) insidetoRepeat().Apply:
- var req = RequestBuilder.post(path) - .addHeader("SOAPAction", "urn://Ocpp/Cs/2015/10/StopTransaction") - .setEntity(new StringEntity(body, contentType)) - .build();try { - httpClient.execute(req, httpResponse -> { + var req = RequestBuilder.post(path) + .addHeader("SOAPAction", "urn://Ocpp/Cs/2015/10/StopTransaction") + .setEntity(new StringEntity(body, contentType)) + .build(); + httpClient.execute(req, httpResponse -> { if (httpResponse.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { throw new RuntimeException("Not OK"); } return null; }); } catch (Exception e) {
- Keeping a single
CloseableHttpClientis fine; it’s thread-safe.- Reusing the immutable
bodyString is fine; a newStringEntityis created per call above.Also applies to: 136-143
steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java (7)
175-175: Rename mis-typed test method for consistencyMethod name is
tesWithUnauthorizedStation; fix the typo for readability and consistency with neighboring tests. Behavior unaffected due to @test, but clearer naming helps.- public void tesWithUnauthorizedStation() { + public void testWithUnauthorizedStation() {
150-158: Fix lambda formatting: stray semicolon, brace placement, and wrap long assertionThere’s an empty statement after the lambda arrow (
c -> {;}) and brace/whitespace issues; also split the long assert. This mirrors a prior review.- then(thrown) - .isInstanceOf(RuntimeException.class) - .hasRootCauseInstanceOf(UpgradeException.class) - .rootCause().satisfies(c -> {; - var ue = (UpgradeException) c; - assertThat(ue.getResponseStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST.value()); - }); + then(thrown) + .isInstanceOf(RuntimeException.class) + .hasRootCauseInstanceOf(UpgradeException.class) + .rootCause() + .satisfies(c -> { + var ue = (UpgradeException) c; + assertThat(ue.getResponseStatusCode()) + .isEqualTo(HttpStatus.BAD_REQUEST.value()); + });
140-143: Wrap overlong line and include diagnostic in failure (OCPP 1.6 authorize)Resolve Checkstyle violation and remove bare fail().
- chargePoint.prepare(auth, AuthorizeResponse.class, - authResponse -> assertThat(authResponse.getIdTagInfo().getStatus()).isEqualTo(AuthorizationStatus.ACCEPTED), - error -> fail() - ); + chargePoint.prepare(auth, AuthorizeResponse.class, + authResponse -> assertThat(authResponse.getIdTagInfo().getStatus()) + .isEqualTo(AuthorizationStatus.ACCEPTED), + error -> fail("Unexpected error during OCPP 1.6 authorize: " + error) + );
85-88: Wrap overlong line and include diagnostic in failure (OCPP 1.2 boot)Break the assertion to satisfy 120-char limit and avoid a bare fail() to aid triage.
- chargePoint.prepare(boot, ocpp.cs._2010._08.BootNotificationResponse.class, - bootResponse -> assertThat(bootResponse.getStatus()).isEqualTo(ocpp.cs._2010._08.RegistrationStatus.ACCEPTED), - error -> fail() - ); + chargePoint.prepare(boot, ocpp.cs._2010._08.BootNotificationResponse.class, + bootResponse -> assertThat(bootResponse.getStatus()) + .isEqualTo(ocpp.cs._2010._08.RegistrationStatus.ACCEPTED), + error -> fail("Unexpected error during OCPP 1.2 boot: " + error) + );
109-112: Wrap overlong line and include diagnostic in failure (OCPP 1.5 boot)Fix Checkstyle and avoid a silent fail().
- chargePoint.prepare(boot, ocpp.cs._2012._06.BootNotificationResponse.class, - bootResponse -> assertThat(bootResponse.getStatus()).isEqualTo(ocpp.cs._2012._06.RegistrationStatus.ACCEPTED), - error -> fail() - ); + chargePoint.prepare(boot, ocpp.cs._2012._06.BootNotificationResponse.class, + bootResponse -> assertThat(bootResponse.getStatus()) + .isEqualTo(ocpp.cs._2012._06.RegistrationStatus.ACCEPTED), + error -> fail("Unexpected error during OCPP 1.5 boot: " + error) + );
90-95: Wrap overlong line and include diagnostic in failure (OCPP 1.2 authorize)Same issues here: long line and bare fail.
- chargePoint.prepare(auth, ocpp.cs._2010._08.AuthorizeResponse.class, - authResponse -> assertThat(authResponse.getIdTagInfo().getStatus()).isEqualTo(ocpp.cs._2010._08.AuthorizationStatus.ACCEPTED), - error -> fail() - ); + chargePoint.prepare(auth, ocpp.cs._2010._08.AuthorizeResponse.class, + authResponse -> assertThat(authResponse.getIdTagInfo().getStatus()) + .isEqualTo(ocpp.cs._2010._08.AuthorizationStatus.ACCEPTED), + error -> fail("Unexpected error during OCPP 1.2 authorize: " + error) + );
114-119: Wrap overlong line and include diagnostic in failure (OCPP 1.5 authorize)Same pattern as above.
- chargePoint.prepare(auth, ocpp.cs._2012._06.AuthorizeResponse.class, - authResponse -> assertThat(authResponse.getIdTagInfo().getStatus()).isEqualTo(ocpp.cs._2012._06.AuthorizationStatus.ACCEPTED), - error -> fail() - ); + chargePoint.prepare(auth, ocpp.cs._2012._06.AuthorizeResponse.class, + authResponse -> assertThat(authResponse.getIdTagInfo().getStatus()) + .isEqualTo(ocpp.cs._2012._06.AuthorizationStatus.ACCEPTED), + error -> fail("Unexpected error during OCPP 1.5 authorize: " + error) + );steve/src/test/java/de/rwth/idsg/steve/issues/Issue73Fix.java (1)
53-56: Pre-flight assertions depend on environment; set test-specific overrides to avoid brittlenessThese assertions will fail if
main.propertiesdoesn’t setprofile=TESTandauto.register.unknown.stations=true(a previous review already flagged this). Make the test self-contained by forcing these values before reading the config.Apply this diff to set system properties prior to reading the configuration:
- var config = SteveConfigurationReader.readSteveConfiguration("main.properties"); - assertThat(config.getProfile()).isEqualTo(ApplicationProfile.TEST); - assertThat(config.getOcpp().isAutoRegisterUnknownStations()).isTrue(); + // Force a deterministic test environment (avoids reliance on external config) + System.setProperty("profile", "TEST"); + System.setProperty("auto.register.unknown.stations", "true"); + var config = SteveConfigurationReader.readSteveConfiguration("main.properties"); + assertThat(config.getProfile()).isEqualTo(ApplicationProfile.TEST); + assertThat(config.getOcpp().isAutoRegisterUnknownStations()).isTrue();Optionally, create
steve/src/test/resources/config/main.propertieswith:profile = TEST auto.register.unknown.stations = trueQuick check script to see what’s currently set:
#!/bin/bash echo "Looking for main.properties files with relevant keys..." fd -t f 'main.properties' -g '!**/target/**' -g '!**/build/**' | while read -r f; do echo ">>> $f" rg -n '^\s*profile\s*=|^\s*auto\.register\.unknown\.stations\s*=' "$f" || true donesteve/src/test/java/de/rwth/idsg/steve/StressTestJsonOCPP16.java (1)
132-134: Fix Checkstyle: line exceeds 120 charsWrap the assertion to satisfy the 120-character limit enforced by Checkstyle.
Apply this diff:
- response -> assertThat(response.getIdTagInfo().getStatus()).isNotEqualTo(AuthorizationStatus.ACCEPTED), + response -> assertThat(response.getIdTagInfo().getStatus()) + .isNotEqualTo(AuthorizationStatus.ACCEPTED),steve/src/test/java/de/rwth/idsg/steve/web/api/OcppTagsRestControllerTest.java (2)
276-276: Stub with matcher, not the concrete form instance, to avoid mismatch with deserialized bodyThe controller creates a new OcppTagForm instance from JSON. Stubbing with the original form risks not matching, leading to Mockito’s default return (0). Use a matcher.
Apply this diff:
- when(ocppTagsService.addOcppTag(form)).thenReturn(ocppTagPk); + when(ocppTagsService.addOcppTag(any(OcppTagForm.class))).thenReturn(ocppTagPk);
443-444: Same stubbing pitfall for AlreadyExists branch — use a matcherFor the error path, the exact-instance stub likely won’t match the deserialized form. Use any(OcppTagForm.class) (or argThat(...) if you need field checks).
Apply this diff:
- when(ocppTagsService.addOcppTag(form)).thenThrow(new SteveException.AlreadyExists("A user with idTag '%s' already exists.", ocppTagPk)); + when(ocppTagsService.addOcppTag(any(OcppTagForm.class))) + .thenThrow(new SteveException.AlreadyExists("A user with idTag '%s' already exists.", ocppTagPk));steve-ocpp/src/test/java/de/rwth/idsg/ocpp/jaxb/JavaDateTimeConverterTest.java (1)
172-181: Update America/New_York offsets to EDT (UTC−04:00) in testsThe current expectations in JavaDateTimeConverterTest assume America/New_York is UTC−05:00, but on June 30, 2022 New York observes EDT (UTC−04:00). Please update all occurrences accordingly.
Files and line ranges to update:
- steve-ocpp/src/test/java/de/rwth/idsg/ocpp/jaxb/JavaDateTimeConverterTest.java:
• Lines 172–181
• Lines 185–186
• Lines 196–201
• Line 205Suggested diff for the first block (apply similarly elsewhere):
- Arguments.of("2022-06-30T01:20:52+02:00", "2022-06-29T18:20:52.000-05:00[America/New_York]"), + Arguments.of("2022-06-30T01:20:52+02:00", "2022-06-29T19:20:52.000-04:00[America/New_York]"), - Arguments.of("2022-06-30T01:20:52Z", "2022-06-29T20:20:52.000-05:00[America/New_York]"), + Arguments.of("2022-06-30T01:20:52Z", "2022-06-29T21:20:52.000-04:00[America/New_York]"), - Arguments.of("2022-06-30T01:20:52+00:00","2022-06-29T20:20:52.000-05:00[America/New_York]"), + Arguments.of("2022-06-30T01:20:52+00:00","2022-06-29T21:20:52.000-04:00[America/New_York]"), - Arguments.of("2022-06-30T01:20:52.126+05:00","2022-06-29T15:20:52.126-05:00[America/New_York]"), + Arguments.of("2022-06-30T01:20:52.126+05:00","2022-06-29T16:20:52.126-04:00[America/New_York]"),Apply the same offset adjustment (
-05:00→-04:00, hour +1) in thetz(...)helper calls on lines 185–186, 196–201, 205.
After these updates, run the test class to confirm all expectations now align with EDT.
🧹 Nitpick comments (37)
steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/config/OcppWebSocketConfiguration.java (1)
99-100: Return an unmodifiable view to prevent accidental mutation of a shared beanSince this Map is a singleton Spring bean shared across consumers, return an immutable view to avoid accidental puts at runtime.
- return invocationContexts; + return Map.copyOf(invocationContexts);steve/src/test/java/de/rwth/idsg/steve/issues/Issue72.java (6)
65-66: Prefer UTC to avoid timezone-dependent flakiness.Using
OffsetDateTime.now()ties timestamps to the executing machine’s local offset. Tests that assert or derive durations can become flaky around DST/offset changes. Consider pinning to UTC.Apply this diff:
+import java.time.ZoneOffset; @@ - var startDateTime = OffsetDateTime.now(); + var startDateTime = OffsetDateTime.now(ZoneOffset.UTC); var stopDateTime = startDateTime.plusHours(5);
80-91: Add a stronger postcondition on StartTransaction.We only check
startfor non-null. To catch regressions earlier, also assert thattransactionIdis positive.Apply this diff:
var start = getForOcpp16(path).startTransaction( @@ ); - assertThat(start).isNotNull(); + assertThat(start).isNotNull(); + assertThat(start.getTransactionId()).isPositive();
129-132: Clean up ThreadLocal after the loop.Minor hygiene: remove the per-thread client in
afterRepeat()to avoid lingering references in long-lived test runners.Apply this diff:
@Override public void afterRepeat() { - + threadLocalClient.remove(); }
103-117: Avoid magic number in sampled value for readability.The
"555"literal is opaque. Consider naming it or deriving it frommeterStart/meterStopto make the test’s intent clearer.For example:
- .withMeterValue( + .withMeterValue( new MeterValue() .withTimestamp(stopDateTime) .withSampledValue( new SampledValue() - .withValue("555") + .withValue(Integer.toString(meterStop - meterStart)) .withUnit(UnitOfMeasure.WH))),
118-127: Stopping the same transaction in every repeat can create protocol errors; consider stopping once.Placing
stopTransactioninsidetoRepeat()will attempt to stop the same transaction many times across threads. Depending on server behavior, subsequent stops may fault or be ignored, turning the stress test into a race on idempotency rather than on meter value handling. If your goal is to stress meter values only, stop once after the loop. If you do want to test idempotent stop handling, gate it to a single stop across threads.Option A — stop once after the stress run:
- var stop = threadLocalClient.get().stopTransaction( - new StopTransactionRequest() - .withTransactionId(transactionId) - .withTimestamp(stopDateTime) - .withIdTag(idTag) - .withMeterStop(meterStop), - chargeBoxId - ); - assertThat(stop).isNotNull();And below (after the stress test, before shutdown):
- tester.test(runnable); - tester.shutDown(); + tester.test(runnable); + var stop = getForOcpp16(path).stopTransaction( + new StopTransactionRequest() + .withTransactionId(transactionId) + .withTimestamp(stopDateTime) + .withIdTag(idTag) + .withMeterStop(meterStop), + chargeBoxId + ); + assertThat(stop).isNotNull(); + tester.shutDown();Option B — stop at most once across all threads:
+ var stopOnce = new java.util.concurrent.atomic.AtomicBoolean(false); @@ - var stop = threadLocalClient.get().stopTransaction( - new StopTransactionRequest() - .withTransactionId(transactionId) - .withTimestamp(stopDateTime) - .withIdTag(idTag) - .withMeterStop(meterStop), - chargeBoxId - ); - assertThat(stop).isNotNull(); + if (stopOnce.compareAndSet(false, true)) { + var stop = threadLocalClient.get().stopTransaction( + new StopTransactionRequest() + .withTransactionId(transactionId) + .withTimestamp(stopDateTime) + .withIdTag(idTag) + .withMeterStop(meterStop), + chargeBoxId + ); + assertThat(stop).isNotNull(); + }Please confirm the intended test semantics.
135-138: Ensure shutdown even on failure.Wrap the stress run in
try/finallysoshutDown()executes on assertion failures or faults.Apply this diff:
- var tester = new StressTester(THREAD_COUNT, REPEAT_COUNT_PER_THREAD); - tester.test(runnable); - tester.shutDown(); + var tester = new StressTester(THREAD_COUNT, REPEAT_COUNT_PER_THREAD); + try { + tester.test(runnable); + } finally { + tester.shutDown(); + }steve/src/test/java/de/rwth/idsg/steve/issues/Issue81.java (1)
104-105: Strengthen the StartTransaction response assertionAlso assert the transactionId is present and positive to fail fast with clearer diagnostics.
- var start = client.startTransaction(req, chargeBoxId); - assertThat(start).isNotNull(); + var start = client.startTransaction(req, chargeBoxId); + assertThat(start).isNotNull(); + assertThat(start.getTransactionId()).isNotNull(); + assertThat(start.getTransactionId()).isPositive();steve/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java (2)
117-117: Drop unnecessarythrows Exceptionfrom test methodsWith MockMvcTester, the fluent
perform(...)path doesn’t throw checked exceptions. Removingthrows Exceptionmakes the signatures cleaner.Apply this diff:
- public void testPost() throws Exception { + public void testPost() { - public void testPut() throws Exception { + public void testPut() {Also applies to: 140-140
128-136: Also verify service interaction in POSTYou already stub
addChargePoint; assert it was invoked to tighten the behavior contract.Apply this diff near the end of the POST assertions:
.hasPathSatisfying("$.chargeBoxId", path -> assertThat(path).asString().isEqualTo("test-cb")); + + verify(chargePointsService).addChargePoint(any(ChargePointForm.class));steve/src/test/java/de/rwth/idsg/steve/TypeStoreTest.java (2)
36-36: Java 17 compiler confirmsvarsupport; consider small immutability nitThe Maven build is already configured to use Java 17 for both
sourceandtarget(<java.version>17</java.version>in pom.xml), so the use ofvaris fully supported and won’t break CI.Minor suggestion (purely stylistic): in test methods, you can declare local variables as
final varto signal immutability. For example, in TypeStoreTest at lines 36, 49, and 62:- var typeStore = new Ocpp12TypeStore(); + final var typeStore = new Ocpp12TypeStore();Apply the same change to the other two
varlocals in this class.
36-45: Consolidate repeated TypeStore tests and add negative casesWe currently have three near-identical blocks testing action/response + BootNotification mappings for OCPP 1.2, 1.5, and 1.6 in TypeStoreTest.java (lines 36–45, 51–58, 64–71). Refactoring into a single JUnit 5 @ParameterizedTest with a MethodSource will eliminate duplication and make it easier to evolve mappings in one place.
Additionally, please add at least one negative/edge‐case assertion per version to codify expected behavior on unknown inputs:
- Assert that findActionResponse returns null (or throws a well-documented exception) for an unmapped request type
- Assert that findRequestClass returns null (or throws) for an unrecognized action string
We verified that each OcppXXTypeStore class has a no-arg public constructor invoking super(packageName, packageName) with no injected dependencies—so direct new OcppXXTypeStore() instantiation is safe outside Spring.
Affected locations:
• src/test/java/de/rwth/idsg/steve/TypeStoreTest.java lines 36–45, 51–58, 64–71steve/src/test/java/de/rwth/idsg/steve/OperationalTestSoapOCPP16.java (1)
480-486: Reduce time-based flakiness by using a Clock.Capturing “now” multiple times can introduce flaky timing assertions. Consider injecting a
Clockand deriving all timestamps from it for determinism.- var startTimeStamp = OffsetDateTime.now(); + var startTimeStamp = OffsetDateTime.now(clock); ... - var stopTimeStamp = OffsetDateTime.now(); + var stopTimeStamp = OffsetDateTime.now(clock);Add near the beginning of
testBody:// deterministic time source for this test var clock = java.time.Clock.systemUTC();Also applies to: 532-541
steve/src/test/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidatorTest.java (4)
31-31: Consider making the validator static (minor).JUnit 5 creates a new test instance per method by default; if the validator is stateless, a single shared instance reduces churn.
Apply this tiny change if you prefer:
- private final ChargeBoxIdValidator validator = new ChargeBoxIdValidator(); + private static final ChargeBoxIdValidator validator = new ChargeBoxIdValidator();
35-41: Reduce repetition with JUnit 5 parameterized tests.Most tests are single-assert variations on valid/invalid samples. Converting to parameterized tests will shrink boilerplate, make it easier to add more cases, and give better per-case reporting.
Add these imports:
import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.junit.jupiter.params.provider.NullAndEmptySource;Append these parameterized tests (keep or replace the existing ones as you see fit):
@@ public class ChargeBoxIdValidatorTest { @@ private final ChargeBoxIdValidator validator = new ChargeBoxIdValidator(); @@ + // Parameterized equivalents (optional refactor) + @ParameterizedTest(name = "rejects null/empty/blank-only: \"{0}\"") + @NullAndEmptySource + @ValueSource(strings = { " " }) + void rejectsNullEmptyAndAllSpaces(String s) { + assertThat(validator.isValid(s)).isFalse(); + } + + @ParameterizedTest(name = "rejects invalid symbols: \"{0}\"") + @ValueSource(strings = { + " test", "test ", // leading/trailing space + "te(st", "te)st", "te>st", "te<st", + "te/st", "te=st" + }) + void rejectsInvalidSymbols(String s) { + assertThat(validator.isValid(s)).isFalse(); + } + + @ParameterizedTest(name = "accepts valid sample: \"{0}\"") + @ValueSource(strings = { + "test", "TEST", "TesT", "tEst", + "test12", "89test", "te9s0t", + ".test", "test.", "te..st", + "-test", "test-", "te--st", + "_test", "test_", "te__st", + ":test", "test:", "te::st", "VID:00XXXXXXXXXX", + "#test", "test#", "te##st", "#FreeCharging", + "1t.E-S_:t20#", + "test1 test2", + "ÂtestÂ" + }) + void acceptsValidSamples(String s) { + assertThat(validator.isValid(s)).isTrue(); + }Also applies to: 45-51, 55-76, 80-90, 94-98, 102-111, 115-161
149-156: Broaden negative-character coverage (minor).Consider adding cases for other frequently-problematic characters if they are meant to be rejected: backslash, question mark, plus, asterisk, quotes, pipe, semicolon, at-sign. This helps prevent future regressions in the regex.
Examples to add (if they should be invalid):
- "te\st", "te?st", "te+st", "te*st", "te"st", "te' st", "te|st", "te;st", "te@st"
159-161: Clarify intent of Unicode acceptance and add non-BMP/combining cases.The current test with "ÂtestÂ" suggests non-ASCII letters are allowed. If that’s intentional, add a couple more Unicode probes (emoji, combining marks) to lock the expectation.
If allowed by requirements, append something like:
+ @Test + public void allowsEmojiAndCombiningIfIntended() { + // Adjust assertions to match the actual rule in ChargeBoxIdValidator + assertThat(validator.isValid("🔌test")).isTrue(); // surrogate pair + assertThat(validator.isValid("te\u0301st")).isTrue(); // combining acute over 'e' + }If they’re not allowed, add them as explicit invalids instead to document the policy.
steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java (2)
154-159: Nit: Consider try-with-resources for httpClientCurrent
finally { httpClient.close(); }is fine. If you want, a try-with-resources around the block can slightly simplify resource handling:- var httpClient = HttpClients.createDefault(); + var httpClient = HttpClients.createDefault(); // keep as-is if you preferOr:
- var httpClient = HttpClients.createDefault(); - try { + try (var httpClient = HttpClients.createDefault()) { var tester = new StressTester(100, 100); tester.test(runnable); tester.shutDown(); - } finally { - httpClient.close(); - } + }
162-175: Explicitly format timestamps in SOAP request for clarityMaking the timestamp formatting explicit guards against future changes in
OffsetDateTime#toString()and ensures valid XML Schemaxs:dateTimeoutput.
- Add import:
import java.time.format.DateTimeFormatter;- Update the timestamp line:
- "<timestamp>" + stop + "</timestamp>" + + "<timestamp>" + stop.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME) + "</timestamp>" +Note: The OCPP 1.6 SOAP spec relies on XML Schema
xs:dateTime, which accepts either a UTC “Z” suffix or any ISO 8601 offset (e.g.,+01:00), so this explicit formatter is fully compliant. To force UTC (always ending in “Z”)—a common interoperability recommendation—you can instead use:"<timestamp>" + stop.toInstant().toString() + "</timestamp>"steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java (3)
133-136: Add diagnostic to failure (OCPP 1.6 boot)No length issue here, but enriching the failure helps debugging.
- chargePoint.prepare(boot, BootNotificationResponse.class, - bootResponse -> assertThat(bootResponse.getStatus()).isEqualTo(RegistrationStatus.ACCEPTED), - error -> fail() - ); + chargePoint.prepare(boot, BootNotificationResponse.class, + bootResponse -> assertThat(bootResponse.getStatus()).isEqualTo(RegistrationStatus.ACCEPTED), + error -> fail("Unexpected error during OCPP 1.6 boot: " + error) + );
192-199: Include diagnostic in failure (heartbeat with null payload)Make the error branch informative for CI logs.
- chargePoint.prepare(null, "Heartbeat", HeartbeatResponse.class, - response -> assertThat(response.getCurrentTime()).isNotNull(), - error -> fail() - ); + chargePoint.prepare(null, "Heartbeat", HeartbeatResponse.class, + response -> assertThat(response.getCurrentTime()).isNotNull(), + error -> fail("Unexpected error during OCPP 1.6 heartbeat: " + error) + );
76-147: Reduce duplication with a small helper for boot+authorize flowsThree tests repeat “start -> boot -> authorize -> processAndClose”. Consider extracting a helper that accepts versioned classes and messages; keeps each test focused on version specifics.
Example sketch:
private static <B, BR, A, AR> void assertBootAndAuthorize( OcppVersion version, String chargeBoxId, Supplier<B> bootSupplier, Class<BR> bootRespType, Predicate<BR> bootOk, Supplier<A> authSupplier, Class<AR> authRespType, Predicate<AR> authOk) { var cp = new OcppJsonChargePoint(version, chargeBoxId, PATH); cp.start(); cp.prepare(bootSupplier.get(), bootRespType, br -> assertThat(bootOk.test(br)).isTrue(), e -> fail("Boot failed: " + e)); cp.prepare(authSupplier.get(), authRespType, ar -> assertThat(authOk.test(ar)).isTrue(), e -> fail("Authorize failed: " + e)); cp.processAndClose(); }Then the versioned tests become one-liners delegating to this helper.
steve/src/test/java/de/rwth/idsg/steve/issues/Issue73Fix.java (5)
76-80: Make charge box IDs more diagnosableRandom IDs are fine, but adding a deterministic prefix helps when scanning logs.
- var chargeBox1 = getRandomString(); - var chargeBox2 = getRandomString(); + var chargeBox1 = "cb-test-" + getRandomString(); + var chargeBox2 = "cb-test-" + getRandomString();
93-101: Enhance BootNotification assertions (check currentTime and interval)You already assert ACCEPTED; add a couple of low-cost checks to catch regressions in time/interval mapping.
assertThat(boot).isNotNull(); assertThat(boot.getStatus()).isEqualTo(RegistrationStatus.ACCEPTED); + // Extra sanity checks + assertThat(boot.getCurrentTime()).isNotNull(); + assertThat(boot.getInterval()).isGreaterThan(0);
111-116: ConnectorId=2 may be environment-sensitive; verify availability or default to 1Some deployments only create connector “1” by default. If connector creation is deferred (e.g., until a StatusNotification), using “2” can fail silently or change behavior.
- Confirm how connectors are provisioned on StartTransaction in your test harness.
- If uncertain, prefer
withConnectorId(1)or set up connector 2 explicitly.If you decide to change:
- .withConnectorId(2) + .withConnectorId(1)
119-122: Strengthen StartTransaction assertions (idTagInfo status) and keep DB checkYou already validate a positive transactionId and DB state. Also check the response’s idTagInfo status to catch mismatches earlier.
assertThat(start).isNotNull(); assertThat(start.getTransactionId()).isGreaterThan(0); + assertThat(start.getIdTagInfo()).isNotNull(); + assertThat(start.getIdTagInfo().getStatus()).isEqualTo(AuthorizationStatus.ACCEPTED); assertThat(__DatabasePreparer__.getOcppTagRecord(REGISTERED_OCPP_TAG).getInTransaction()).isTrue();
62-62: Make Application AutoCloseable to use try-with-resources
I didn’t find anyimplements AutoCloseableonsteve/src/main/java/de/rwth/idsg/steve/Application.java, so you can’t yet switch to a try-with-resources block. If you’d like to simplify shutdown and remove the nestedfinally, consider:• Have
ApplicationimplementAutoCloseable(orCloseable)
• Move your existing shutdown/cleanup logic into apublic void close()method
• Then refactor the test to:try (var app = new Application(config)) { app.start(); test(); }Otherwise, the current finally-based cleanup is appropriate.
pom.xml (1)
156-160: Unify Jackson versions across modules to avoid driftRoot properties define jackson.version=2.19.2, while steve-ocpp/pom.xml pins jackson-databind to 2.17.2. Consider managing Jackson with a BOM in the parent to enforce a single version across all modules and remove hardcoded versions in children. Example to add in this parent’s :
<dependency> <groupId>com.fasterxml.jackson</groupId> <artifactId>jackson-bom</artifactId> <version>${jackson.version}</version> <type>pom</type> <scope>import</scope> </dependency>Then, drop explicit Jackson versions in module POMs to rely on the BOM. This reduces the risk of mixed transitive trees and CVEs.
steve-ocpp-websocket/src/test/java/de/rwth/idsg/steve/ocpp/ws/custom/CustomStringModuleTest.java (1)
38-41: Consider parameterizing the three similar assertionsAll three tests follow the same “input JSON → escaped output” pattern. You could collapse them into a single @ParameterizedTest with (input, expected) pairs to reduce duplication while keeping clarity.
Example:
@ParameterizedTest @MethodSource("cases") void should_escape_strings_as_expected(String raw, String expectedJson) throws Exception { var output = mapper.writeValueAsString(new SimpleJsonModel(raw)); assertThat(output).isEqualTo(expectedJson); } private static Stream<Arguments> cases() { return Stream.of( Arguments.of("normal string", "{\"someText\":\"normal string\"}"), Arguments.of("<a href=\"link\">Some link</a>", "{\"someText\":\"<a href="link">Some link</a>\"}"), Arguments.of("<script src=\"http://someurl.com/script.js\"/>", "{\"someText\":\"<script src="http://someurl.com/script.js"/>\"}") ); }Also applies to: 45-48, 52-55
steve/src/test/java/de/rwth/idsg/steve/StressTestJsonOCPP16.java (1)
113-114: Propagate error context in failure callbacksUsing fail() without a message loses the underlying error, making failures harder to diagnose. Pass the error’s message (or the error itself) to fail for better diagnostics.
Apply this diff pattern at each callback:
- error -> fail() + error -> fail(String.valueOf(error))Or, if error is a Throwable:
- error -> fail() + error -> { throw new AssertionError("Callback failed", error); }Also applies to: 125-126, 134-134, 149-150, 163-164, 173-174, 184-185, 195-196
steve-core/src/main/java/de/rwth/idsg/steve/utils/DateTimeUtils.java (3)
40-46: UTC assumption in toOffsetDateTime(LocalDateTime) — confirm intended behaviorConverting LocalDateTime to OffsetDateTime using UTC is a semantic choice. If previous Joda-based callers expected “system default zone → corresponding offset,” this could shift displayed times. If UTC is intentional (and aligns with OCPP’s strong recommendation), consider renaming to toOffsetDateTimeUtc or adding an overload with a ZoneId parameter.
Possible addition (non-breaking overload):
public static OffsetDateTime toOffsetDateTime(LocalDateTime ldt, ZoneId zone) { if (ldt == null) return null; var offset = zone == null ? ZoneOffset.UTC : zone.getRules().getOffset(ldt); return ldt.atOffset(offset); }
66-91: Make humanize(LocalDateTime) testable and zone-deterministicUsing LocalDateTime.now() ties behavior to the current system clock. For deterministic tests and clearer semantics across time zones, inject a Clock or accept “now” as a parameter.
Example:
public static String humanize(LocalDateTime dt, Clock clock) { if (dt == null) return ""; var todayAtMidnight = LocalDate.now(clock).atStartOfDay(); var inputAtMidnight = dt.toLocalDate().atStartOfDay(); ... }
93-122: DST caveat in timeElapsed(LocalDateTime, LocalDateTime)Duration between two LocalDateTime values ignores time zone transitions. If callers may pass local times spanning DST changes, consider accepting OffsetDateTime/ZonedDateTime or documenting that inputs must be in the same “local timeline” context.
Minimal alternative using parts methods:
- var days = duration.toDays(); - duration = duration.minusDays(days); - var hours = duration.toHours(); - duration = duration.minusHours(hours); - var minutes = duration.toMinutes(); - duration = duration.minusMinutes(minutes); - var seconds = duration.getSeconds(); + var days = duration.toDaysPart(); + var hours = duration.toHoursPart(); + var minutes = duration.toMinutesPart(); + var seconds = duration.toSecondsPart();steve/src/test/java/de/rwth/idsg/steve/web/api/TransactionRestControllerTest.java (1)
253-260: Tighten type assertions in error matcher (status should be numeric)Minor: asserting the type helps catch regressions. Consider checking status as a number.
Apply this diff:
- .hasPathSatisfying("$.status", path -> assertThat(path).isNotNull()) + .hasPathSatisfying("$.status", path -> assertThat(path).asNumber().isPositive())steve-ocpp/src/main/java/de/rwth/idsg/ocpp/DateTimeUtils.java (1)
21-36: Avoid Lombok for utility class; add explicit private constructor to satisfy CheckstyleCheckstyle’s HideUtilityClassConstructorCheck may not honor Lombok here. Prefer an explicit private constructor and remove Lombok imports/annotation.
Apply this diff:
-import lombok.AccessLevel; -import lombok.NoArgsConstructor; +// Lombok avoided here to satisfy Checkstyle in this module. @@ -@NoArgsConstructor(access = AccessLevel.PRIVATE) public final class DateTimeUtils { + + private DateTimeUtils() { + throw new AssertionError("No instances"); + }Nit: comment says “fixed 3-digits nanosecond precision”; it’s millisecond precision. Consider updating the comment to avoid confusion.
- // ISO-8601 formatter: outputs timestamps with fixed 3-digits nanosecond precision. + // ISO-8601 formatter: outputs timestamps with fixed 3-digit millisecond precision.steve-ocpp/src/test/java/de/rwth/idsg/ocpp/jaxb/JavaDateTimeConverterTest.java (2)
58-66: Fix typos (“marshal”) and variable names for clarity*Rename “marshall/marchall” → “marshal” consistently (JAXB uses “marshal” with one ‘l’). This also improves readability.
Apply this diff:
- void testMarshalUtcValidInput(ZoneId zoneId, OffsetDateTime val, String expected, boolean marshallToUtc) { + void testMarshalUtcValidInput(ZoneId zoneId, OffsetDateTime val, String expected, boolean marshalToUtc) { - var converter = new JavaDateTimeConverter(zoneId, marshallToUtc); + var converter = new JavaDateTimeConverter(zoneId, marshalToUtc); @@ - private static Stream<Arguments> provideValidMarshallingInput() { - var marchallUtcInputs = Stream.of( // + private static Stream<Arguments> provideValidMarshallingInput() { + var marshalUtcInputs = Stream.of(// @@ - input.get()[2] // marshallToUtc + input.get()[2] // marshalToUtc ))); - var marchallTzInputs = Stream.of( // + var marshalTzInputs = Stream.of(// @@ - input.get()[3] // marshallToUtc + input.get()[3] // marshalToUtc )); - return Stream.concat(marchallUtcInputs, marchallTzInputs); + return Stream.concat(marshalUtcInputs, marshalTzInputs);Also applies to: 80-82, 114-121
65-65: Resolve Checkstyle ParenPad: remove space after '(' in Stream/Arguments callsThere are several occurrences of a space immediately after '('. Remove it to satisfy ParenPad.
Apply this diff:
- var marshalUtcInputs = Stream.of( // + var marshalUtcInputs = Stream.of(// @@ - .map(tz -> Arguments.of( // + .map(tz -> Arguments.of(// @@ - ).map(input -> Arguments.of( // + ).map(input -> Arguments.of(// @@ - private static final List<ZoneId> ZONE_IDS = Stream.of( // + private static final List<ZoneId> ZONE_IDS = Stream.of(// @@ - var validInputs = Stream.of( // + var validInputs = Stream.of(// @@ - var withoutOffsetInputs = Stream.of( // + var withoutOffsetInputs = Stream.of(//Also applies to: 76-76, 82-82, 114-114, 156-156, 170-170, 193-193
| import ocpp.cs._2015._10.HeartbeatResponse; | ||
| import ocpp.cs._2015._10.RegistrationStatus; | ||
| import org.eclipse.jetty.websocket.api.exceptions.UpgradeException; | ||
| import org.eclipse.jetty.websocket.core.exception.UpgradeException; |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Verify Jetty UpgradeException import matches project Jetty version
Import moved from api.exceptions to core.exception in Jetty 12. Please confirm the project depends on Jetty 12+ (websocket-core) and there are no lingering usages of the old package elsewhere.
Run this to verify dependency alignment and stray imports:
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Jetty deps:"
rg -nP '<groupId>org\.eclipse\.jetty</groupId>\s*\n\s*<artifactId>.*</artifactId>\s*\n\s*<version>[^<]+' -n --glob '**/pom.xml' -C2 || true
echo
echo "UpgradeException import sites:"
rg -nP 'org\.eclipse\.jetty\.websocket\.(api\.exceptions|core\.exception)\.UpgradeException'
echo
echo "Jetty websocket-core artifacts (Jetty 12+):"
rg -nP '<artifactId>jetty-websocket-(core|server|client)</artifactId>' --glob '**/pom.xml' -n -C2 || trueLength of output: 714
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Listing build.gradle and build.gradle.kts files:"
find . -maxdepth 3 -type f \( -name 'build.gradle' -o -name 'build.gradle.kts' \) || true
echo
echo "Searching for Jetty references in build files:"
rg -n "jetty" --glob "**/*.gradle*" || trueLength of output: 511
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Listing all pom.xml files:"
find . -type f -name 'pom.xml'
echo
echo "Extracting Jetty dependency blocks from pom.xml files:"
# Multiline search for Jetty dependencies in all pom.xml
rg -z -P '<dependency>.*?<groupId>org\.eclipse\.jetty.*?</dependency>' --glob '**/pom.xml' -n || trueLength of output: 604
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "First 200 lines of steve-ocpp-websocket/pom.xml"
sed -n '1,200p' steve-ocpp-websocket/pom.xmlLength of output: 2311
Critical: Missing Jetty dependency for UpgradeException import in ApplicationJsonTest.java
The test class imports
import org.eclipse.jetty.websocket.core.exception.UpgradeException;but none of the project’s POMs declare any org.eclipse.jetty dependencies, so this will fail to compile or run. You need to either add the appropriate Jetty WebSocket Core dependency (Jetty 12+) or switch to its alternative (e.g., jakarta.websocket) throughout your tests.
• steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java
– Remove or replace the Jetty import; or
– Add to the relevant module’s pom.xml (e.g., in steve-ocpp-websocket or steve-core):
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>jetty-websocket-core</artifactId>
<version>12.0.0</version> <!-- or your chosen Jetty 12+ version -->
<scope>test</scope>
</dependency>🤖 Prompt for AI Agents
steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java lines 32-32, the
test imports org.eclipse.jetty.websocket.core.exception.UpgradeException but no
Jetty dependency is declared causing compile failures; either remove or replace
that import and use a project-supported exception/type (e.g., jakarta.websocket
equivalent) in the test, or add the Jetty WebSocket Core test dependency to the
correct module pom (groupId: org.eclipse.jetty.websocket, artifactId:
jetty-websocket-core, version: 12.x or newer, scope: test) so the
UpgradeException resolves.
| var auth = client.authorize(new AuthorizeRequest().withIdTag(REGISTERED_OCPP_TAG), chargeBoxId); | ||
| assertThat(auth).isNotNull(); | ||
| assertThat(auth.getIdTagInfo().getStatus()).isEqualTo(expected); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Avoid potential NPE: assert idTagInfo is non-null before accessing status
If getIdTagInfo() is null, the chained call will throw before AssertJ can report a clear failure.
- assertThat(auth).isNotNull();
- assertThat(auth.getIdTagInfo().getStatus()).isEqualTo(expected);
+ assertThat(auth).isNotNull();
+ assertThat(auth.getIdTagInfo()).isNotNull();
+ assertThat(auth.getIdTagInfo().getStatus()).isEqualTo(expected);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| var auth = client.authorize(new AuthorizeRequest().withIdTag(REGISTERED_OCPP_TAG), chargeBoxId); | |
| assertThat(auth).isNotNull(); | |
| assertThat(auth.getIdTagInfo().getStatus()).isEqualTo(expected); | |
| } | |
| var auth = client.authorize(new AuthorizeRequest().withIdTag(REGISTERED_OCPP_TAG), chargeBoxId); | |
| assertThat(auth).isNotNull(); | |
| assertThat(auth.getIdTagInfo()).isNotNull(); | |
| assertThat(auth.getIdTagInfo().getStatus()).isEqualTo(expected); | |
| } |
🤖 Prompt for AI Agents
In steve/src/test/java/de/rwth/idsg/steve/issues/Issue73Fix.java around lines
105 to 108, the test directly calls auth.getIdTagInfo().getStatus() which can
throw an NPE and hinder clear test failure reporting; add an explicit AssertJ
assertion that auth.getIdTagInfo() is not null before asserting its status
(e.g., assertThat(auth.getIdTagInfo()).isNotNull(); then
assertThat(auth.getIdTagInfo().getStatus()).isEqualTo(expected);) to ensure a
clear failure message when idTagInfo is missing.
| var boot = getForOcpp16(path).bootNotification( | ||
| new BootNotificationRequest() | ||
| .withChargePointVendor(getRandomString()) | ||
| .withChargePointModel(getRandomString()), | ||
| chargeBoxId.get()); | ||
| Assertions.assertEquals(RegistrationStatus.ACCEPTED, boot.getStatus()); | ||
| assertThat(boot.getStatus()).isEqualTo(RegistrationStatus.ACCEPTED); | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
Reuse the created client and cap BootNotification vendor/model lengths (OCPP 1.6: max 20 chars) + add null check
- Reuse client.get() instead of creating a second client for bootNotification.
- OCPP 1.6 limits chargePointVendor and chargePointModel to 20 chars; unbounded random strings can cause rejected BootNotification and flaky tests.
- Add a simple null assertion before accessing status.
Apply this diff within the shown range:
- var boot = getForOcpp16(path).bootNotification(
+ var boot = client.get().bootNotification(
new BootNotificationRequest()
- .withChargePointVendor(getRandomString())
- .withChargePointModel(getRandomString()),
+ .withChargePointVendor(ocppBound(getRandomString(), 20))
+ .withChargePointModel(ocppBound(getRandomString(), 20)),
chargeBoxId.get());
- assertThat(boot.getStatus()).isEqualTo(RegistrationStatus.ACCEPTED);
+ assertThat(boot).isNotNull();
+ assertThat(boot.getStatus()).isEqualTo(RegistrationStatus.ACCEPTED);Additionally (outside the selected range), consider bounding chargeBoxId as well, since many stacks enforce a 20-char limit:
// Line 66 replacement (outside this hunk)
chargeBoxId.set(ocppBound(Helpers.getRandomString(), 20));Helper method to add in this class:
private static String ocppBound(String s, int max) {
if (s == null) return null;
return s.length() <= max ? s : s.substring(0, max);
}🤖 Prompt for AI Agents
In steve/src/test/java/de/rwth/idsg/steve/issues/Issue81.java around lines 68 to
74, reuse the existing client (use client.get()) for the bootNotification call
instead of creating a second client, trim chargePointVendor and chargePointModel
to at most 20 characters (OCPP 1.6 limit) before sending to avoid flaky
rejections, and add an assertion that boot is not null before accessing
boot.getStatus(); also add the suggested ocppBound helper to this class and
(outside the shown hunk) bound chargeBoxId as noted by replacing line 66 with
chargeBoxId.set(ocppBound(Helpers.getRandomString(), 20)).
| @Test | ||
| @DisplayName("GET all: Test with empty results, expected 200") | ||
| public void testGet_withEmptyList() throws Exception { | ||
| public void testGet_withEmptyList() { |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Fix Checkstyle method naming (underscores) to unblock the pipeline
These four test names violate the MethodNameCheck rule. Rename to lowerCamelCase without underscores.
Apply this diff:
- public void testGet_withEmptyList() {
+ public void testGetWithEmptyList() {
- public void testGet_withOneResult() {
+ public void testGetWithOneResult() {
- public void testGetOne_notFound() {
+ public void testGetOneNotFound() {
- public void testGetOne_found() {
+ public void testGetOneFound() {Run to verify no remaining test methods use underscores:
Also applies to: 76-76, 89-89, 98-98
🏁 Script executed:
#!/bin/bash
# List underscored test method names in test sources
rg -nP --type=java -g 'steve/src/test/java/**' 'void\s+test\w*_[A-Za-z0-9]+' -C0 || trueLength of output: 4933
Critical: Resolve remaining Checkstyle MethodNameCheck violations in test methods
The verification script has identified numerous test methods across multiple classes that still use underscores and thus violate the MethodNameCheck rule. All of these must be renamed to lowerCamelCase without underscores before the pipeline will unblock.
Affected files and methods include (but are not limited to):
-
steve/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java
- testGet_withEmptyList
- testGet_withOneResult
- testGetOne_notFound
- testGetOne_found
-
steve/src/test/java/de/rwth/idsg/steve/utils/TransactionStopServiceHelperTest.java
- testIsEnergy_empty
- testIsEnergy_onlyValue
- testIsEnergy_onlyValueDecimal
- testIsEnergy_signedData
- testIsEnergy_notEnergyUnit
- testIsEnergy_notActiveImportMeasurand
- testIsEnergy_nullFormat
- testIsEnergy_rawFormat
- testIsEnergy_kWhUnit
- testIsEnergy_nonNumericValue
- testIsEnergy_valueAndUnit
-
steve/src/test/java/de/rwth/idsg/steve/utils/StringUtilsTest.java
- testOperationName_ocpp12andMultiple
- testOperationName_ocpp15andSingle
- testOperationName_ocpp16
- testJoinByComma_inputNull
- testJoinByComma_inputEmpty
- testJoinByComma_inputOneElement
- testJoinByComma_inputTwoElements
- testJoinByComma_inputDuplicateElements
- testSplitByComma_inputNull
- testSplitByComma_inputEmpty
- testSplitByComma_inputOneElement
- testSplitByComma_inputTwoElements
-
steve/src/test/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandlerTest.java
- testGetLastBitFromUrl_empty
- testGetLastBitFromUrl_null
- testGetLastBitFromUrl_successFull
- testGetLastBitFromUrl_noPostfix
- testGetLastBitFromUrl_successPartial
- testGetLastBitFromUrl_successWithPercent
- testGetLastBitFromUrl_successWithDash
- testGetLastBitFromUrl_successWithSpace
- testGetLastBitFromUrl_successWithExtraSlash
- testGetLastBitFromUrl_successComplex
Please rename each method to adhere to lowerCamelCase naming (e.g., testGetWithEmptyList, testIsEnergyEmpty, testOperationNameOcpp12AndMultiple, testGetLastBitFromUrlEmpty, etc.). Once renamed, re-run the verification script:
rg -nP --type=java -g 'steve/src/test/java/**' 'void\s+test\w*_[A-Za-z0-9]+' -C0 || trueto confirm that no underscored test method names remain.
🧰 Tools
🪛 GitHub Check: checkstyle
[failure] 65-65: [checkstyle] steve/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java#L65 <com.puppycrawl.tools.checkstyle.checks.naming.MethodNameCheck>
Name 'testGet_withEmptyList' must match pattern '^[a-z][a-zA-Z0-9]*$'.
🤖 Prompt for AI Agents
In
steve/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java
around line 65: test method names use underscores and violate Checkstyle
MethodNameCheck; rename each underscored test method to lowerCamelCase (e.g.,
testGet_withEmptyList -> testGetWithEmptyList, and similarly
testGet_withOneResult -> testGetWithOneResult, testGetOne_notFound ->
testGetOneNotFound, testGetOne_found -> testGetOneFound), update any
references/usages, run mvn -DskipTests=false test or the provided rg
verification command to ensure no remaining underscored test names, and commit
the renames.
| assertThat(mockMvc.perform(post("/api/v1/chargeboxes") | ||
| .content(objectMapper.writeValueAsString(form)) | ||
| .contentType(MediaType.APPLICATION_JSON_VALUE)) | ||
| .andExpect(status().isCreated()) | ||
| .andExpect(jsonPath("$.chargeBoxPk").value("1")) | ||
| .andExpect(jsonPath("$.chargeBoxId").value("test-cb")); | ||
| .contentType(MediaType.APPLICATION_JSON_VALUE) | ||
| )) |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Use MediaType constant instead of string value for contentType
Using MediaType.APPLICATION_JSON_VALUE relies on an overload that may not exist for your Spring MockMvc builder version. Prefer the MediaType constant to avoid potential compile-time issues and be consistent with common Spring usage.
Apply this diff:
- .contentType(MediaType.APPLICATION_JSON_VALUE)
+ .contentType(MediaType.APPLICATION_JSON)Do the same change in the PUT test.
Scan for other occurrences across tests:
Also applies to: 150-153
🏁 Script executed:
#!/bin/bash
rg -nP --type=java -g 'steve/src/test/java/**' '\.contentType\s*\(\s*MediaType\.APPLICATION_JSON_VALUE\s*\)'Length of output: 1488
Use MediaType.APPLICATION_JSON instead of APPLICATION_JSON_VALUE in MockMvc tests
Switch to the MediaType constant overload in all .contentType(…) calls to ensure compatibility and follow common Spring conventions.
These occurrences need updating:
- steve/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java: lines 130, 152
- steve/src/test/java/de/rwth/idsg/steve/web/api/OcppTagsRestControllerTest.java: lines 231, 252, 283, 315, 336, 361, 449
Replace each instance of:
- .contentType(MediaType.APPLICATION_JSON_VALUE)
+ .contentType(MediaType.APPLICATION_JSON)Apply this change to both POST and PUT request tests.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| assertThat(mockMvc.perform(post("/api/v1/chargeboxes") | |
| .content(objectMapper.writeValueAsString(form)) | |
| .contentType(MediaType.APPLICATION_JSON_VALUE)) | |
| .andExpect(status().isCreated()) | |
| .andExpect(jsonPath("$.chargeBoxPk").value("1")) | |
| .andExpect(jsonPath("$.chargeBoxId").value("test-cb")); | |
| .contentType(MediaType.APPLICATION_JSON_VALUE) | |
| )) | |
| assertThat(mockMvc.perform(post("/api/v1/chargeboxes") | |
| .content(objectMapper.writeValueAsString(form)) | |
| .contentType(MediaType.APPLICATION_JSON) | |
| )) |
🤖 Prompt for AI Agents
steve/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java
around lines 128-131: change the MockMvc .contentType(...) calls to use
MediaType.APPLICATION_JSON (the MediaType constant) instead of
MediaType.APPLICATION_JSON_VALUE; apply the same replacement at the other
flagged locations in this project (ChargePointsRestControllerTest lines ~130 and
152 and OcppTagsRestControllerTest lines ~231, 252, 283, 315, 336, 361, 449) for
both POST and PUT request tests so all .contentType(...) invocations use the
MediaType overload rather than the string value.
steve/src/test/java/de/rwth/idsg/steve/web/api/OcppTagsRestControllerTest.java
Outdated
Show resolved
Hide resolved
steve/src/test/java/de/rwth/idsg/steve/web/api/TransactionRestControllerTest.java
Show resolved
Hide resolved
b643c13 to
0d40295
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java (1)
1-178: Replace invalid Guava SOAP constant and refine Content-Type usageAll leftover Joda-Time references have been removed (no occurrences of
org.joda.time), so no further action is needed there. However, the test still uses Guava’sMediaType.SOAP_XML_UTF_8, which is invalid and must be replaced with an explicit SOAP 1.2 media type and charset.• File:
steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java
• Remove the Guava import:
diff - import com.google.common.net.MediaType; + // removed: com.google.common.net.MediaType
• Replace at line 99:
diff - var contentType = ContentType.create( - MediaType.SOAP_XML_UTF_8.type(), - MediaType.SOAP_XML_UTF_8.charset().orNull()); + import java.nio.charset.StandardCharsets; + + var contentType = ContentType.create( + "application/soap+xml", + StandardCharsets.UTF_8);
♻️ Duplicate comments (12)
steve/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java (1)
65-65: Fix Checkstyle: rename underscored test methods to lowerCamelCaseThese names violate MethodNameCheck. Rename to unblock the pipeline.
Apply:
- public void testGet_withEmptyList() { + public void testGetWithEmptyList() { - public void testGet_withOneResult() { + public void testGetWithOneResult() { - public void testGetOne_notFound() { + public void testGetOneNotFound() { - public void testGetOne_found() { + public void testGetOneFound() {Run to ensure no remaining underscored test names:
#!/bin/bash rg -nP --type=java -g 'steve/src/test/java/**' 'void\s+test\w*_[A-Za-z0-9]+' -C0 || trueAlso applies to: 76-76, 89-89, 98-98
steve/src/test/java/de/rwth/idsg/steve/web/api/OcppTagsRestControllerTest.java (3)
231-236: MediaType.APPLICATION_JSON overload used correctlyGood switch away from APPLICATION_JSON_VALUE string overload across POST/PUT tests.
Also applies to: 283-286, 316-319
278-279: Mockito stubbing uses concrete instance — won’t match deserialized bodyThe controller receives a new OcppTagForm instance from JSON; stubbing with the original
formrisks not matching and returning default (0).Apply:
- when(ocppTagsService.addOcppTag(form)).thenReturn(ocppTagPk); + when(ocppTagsService.addOcppTag(any(OcppTagForm.class))).thenReturn(ocppTagPk);
445-446: Same stubbing pitfall in the AlreadyExists branchUse a matcher so the stub triggers for the deserialized form instance.
Apply:
- when(ocppTagsService.addOcppTag(form)).thenThrow(new SteveException.AlreadyExists("A user with idTag '%s' already exists.", ocppTagPk)); + when(ocppTagsService.addOcppTag(any(OcppTagForm.class))) + .thenThrow(new SteveException.AlreadyExists("A user with idTag '%s' already exists.", ocppTagPk));steve/src/test/java/de/rwth/idsg/steve/issues/Issue81.java (1)
68-74: Reuse the created client, cap vendor/model to 20 chars (OCPP 1.6), and assert non-null response.
- You already store a client in a ThreadLocal; reuse it here instead of creating a second one.
- OCPP 1.6 limits chargePointVendor and chargePointModel to 20 characters; unbounded random strings can cause BootNotification rejections and flaky tests.
- Add a null check before accessing status.
This echoes an earlier review on the same lines and is still applicable.
- var boot = getForOcpp16(path).bootNotification( + var boot = client.get().bootNotification( new BootNotificationRequest() - .withChargePointVendor(getRandomString()) - .withChargePointModel(getRandomString()), + .withChargePointVendor(ocppBound(getRandomString(), 20)) + .withChargePointModel(ocppBound(getRandomString(), 20)), chargeBoxId.get()); - assertThat(boot.getStatus()).isEqualTo(RegistrationStatus.ACCEPTED); + assertThat(boot).isNotNull(); + assertThat(boot.getStatus()).isEqualTo(RegistrationStatus.ACCEPTED);Outside this hunk (for robustness; many stacks limit it), consider bounding the chargeBoxId at line 66:
// Line 66 chargeBoxId.set(ocppBound(Helpers.getRandomString(), 20));Helper to add in this class:
private static String ocppBound(String s, int max) { if (s == null) return null; return s.length() <= max ? s : s.substring(0, max); }steve/src/test/java/de/rwth/idsg/steve/issues/Issue73Fix.java (2)
55-56: Decouple assertions from prod config to avoid brittle failures (duplicate of prior feedback)These assertions will fail unless the active main.properties (or overrides) set TEST and enable auto-register. Prefer a test-only config so the scenario is reproducible across CI/dev without touching production defaults.
- Option A (recommended): add steve/src/test/resources/config/main.properties:
- profile = TEST
- auto.register.unknown.stations = true
- Option B: pass system properties when launching the test runner.
Use this script to verify test resources contain the expected overrides:
#!/bin/bash set -euo pipefail echo "Looking for test main.properties with required keys..." fd -a -t f 'main.properties' steve/src/test/resources || true rg -n --hidden -g 'steve/src/**/resources/**' -e '^\s*profile\s*=' -e '^\s*auto\.register\.unknown\.stations\s*=' || true
105-108: Avoid potential NPE when idTagInfo is missing (duplicate of prior feedback)If getIdTagInfo() is null, the test will throw before producing a clear assertion failure.
Update:
- assertThat(auth).isNotNull(); - assertThat(auth.getIdTagInfo().getStatus()).isEqualTo(expected); + assertThat(auth).isNotNull(); + assertThat(auth.getIdTagInfo()).isNotNull(); + assertThat(auth.getIdTagInfo().getStatus()).isEqualTo(expected);steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java (2)
43-44: Missing import for StandardCharsets (required by Content-Type fix)Add the import to use UTF-8 explicitly when creating the SOAP Content-Type.
import org.apache.http.impl.client.HttpClients; +import java.nio.charset.StandardCharsets; import java.time.OffsetDateTime;
99-99: Invalid Content-Type construction (uses non-existent GuavaMediaType.SOAP_XML_UTF_8)
MediaType.SOAP_XML_UTF_8doesn’t exist;MediaType#type()returns just"application", producing an invalid MIME. Build the ContentType explicitly.- var contentType = ContentType.create(MediaType.SOAP_XML_UTF_8.type(), MediaType.SOAP_XML_UTF_8.charset().orNull()); + var contentType = ContentType.create("application/soap+xml", StandardCharsets.UTF_8);After this change, you can drop the
com.google.common.net.MediaTypeimport if unused elsewhere.steve/src/test/java/de/rwth/idsg/steve/OperationalTestSoapOCPP16.java (3)
498-500: Fix Checkstyle: line exceeds 120 chars. [duplicate of earlier comment; still present]Break the chained assertion to satisfy LineLengthCheck.
- assertThat(t.getStartTimestamp()).isCloseTo(startTimeStamp.toLocalDateTime(), byLessThan(1, ChronoUnit.SECONDS)); + assertThat(t.getStartTimestamp()) + .isCloseTo( + startTimeStamp.toLocalDateTime(), + byLessThan(1, ChronoUnit.SECONDS) + );
549-550: Fix Checkstyle: line exceeds 120 chars. [duplicate of earlier comment; still present]Same issue for stop timestamp assertion.
- assertThat(t.getStopTimestamp()).isCloseTo(stopTimeStamp.toLocalDateTime(), byLessThan(1, ChronoUnit.SECONDS)); + assertThat(t.getStopTimestamp()) + .isCloseTo( + stopTimeStamp.toLocalDateTime(), + byLessThan(1, ChronoUnit.SECONDS) + );
597-609: Bug: asserting emptiness then indexing sampledValues.get(0). [still present]The test will fail (or NPE) since it asserts sampledValues is empty and then accesses index 0. Also, only the first sampled value is checked; support multiple SampledValue entries.
Apply this fix:
- var sampledValues = meterValue.getSampledValue(); - assertThat(sampledValues).isEmpty(); - var thisValueFound = false; - // and check, if it can be found in the DB - for (var values : details.getValues()) { - if (values.getValue().equals(sampledValues.get(0).getValue())) { - thisValueFound = true; - break; - } - } + var sampledValues = meterValue.getSampledValue(); + assertThat(sampledValues).isNotEmpty(); + // check if any sampled value of this MeterValue exists in DB + var thisValueFound = details.getValues().stream() + .anyMatch(values -> + sampledValues.stream().anyMatch(sv -> values.getValue().equals(sv.getValue())) + ); assertThat(thisValueFound).isTrue();
🧹 Nitpick comments (24)
steve/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java (1)
128-135: POST test: also verify service interactionYou already stub addChargePoint and getDetails; add a verify to ensure the controller calls the service with a form.
Apply:
.hasPathSatisfying("$.chargeBoxId", path -> assertThat(path).asString().isEqualTo("test-cb")); + + verify(chargePointsService).addChargePoint(any(ChargePointForm.class));Additionally, consider dropping
throws Exceptionfrom the method signature for consistency with the other tests.steve/src/test/java/de/rwth/idsg/steve/web/api/OcppTagsRestControllerTest.java (2)
88-92: Minor: unify JSON assertion style (optional)You use extractingPath("$").asArray().isEmpty() here, while other tests use hasPathSatisfying(...). Consider unifying for consistency, though both are fine.
321-321: Avoid relying on equals for verify(eq(form))Unless OcppTagForm implements value-based equals/hashCode, this verify may be flaky. Prefer an argument matcher or captor that asserts fields.
For robustness:
- verify(ocppTagsService).updateOcppTag(eq(form)); + verify(ocppTagsService).updateOcppTag(argThat(f -> + f != null + && f.getOcppTagPk() != null && f.getOcppTagPk().equals(ocppTagPk) + && "id-123".equals(f.getIdTag()) + && "note-1".equals(f.getNote()) + ));If you prefer captors:
ArgumentCaptor<OcppTagForm> captor = ArgumentCaptor.forClass(OcppTagForm.class); verify(ocppTagsService).updateOcppTag(captor.capture()); assertThat(captor.getValue()) .extracting(OcppTagForm::getOcppTagPk, OcppTagForm::getIdTag, OcppTagForm::getNote) .containsExactly(ocppTagPk, "id-123", "note-1");steve/src/test/java/de/rwth/idsg/steve/issues/Issue81.java (2)
56-56: Optional: prefer explicit interface type instead ofvarfor anonymous class.
varinfers the anonymous class type, not the interface. Using the interface type makes intent clearer and prevents accidental leaking of the anonymous type.- var runnable = new StressTester.Runnable() { + StressTester.Runnable runnable = new StressTester.Runnable() {
104-106: Strengthen the assertion on the StartTransaction response.Add a positivity check for the transaction id to fail fast on invalid ids.
- assertThat(start).isNotNull(); + assertThat(start).isNotNull(); + assertThat(start.getTransactionId()).isPositive();steve/src/test/java/de/rwth/idsg/steve/issues/Issue73Fix.java (3)
78-80: Make generated charge box IDs easier to grep in logs (minor)A deterministic prefix improves traceability while keeping uniqueness.
Apply:
- var chargeBox1 = getRandomString(); - var chargeBox2 = getRandomString(); + var chargeBox1 = "issue73fix-" + getRandomString(); + var chargeBox2 = "issue73fix-" + getRandomString();
111-116: Use UTC for OCPP timestamp to ensure cross-environment determinismOffsetDateTime.now() uses the local offset, which may vary across CI and developer machines. OCPP timestamps are typically exchanged in RFC3339; standardizing on UTC removes ambiguity.
Apply:
- .withTimestamp(OffsetDateTime.now()) + .withTimestamp(OffsetDateTime.now(ZoneOffset.UTC))Add import:
// with other imports import java.time.ZoneOffset;
119-122: Strengthen DB assertion: check record existence before dereferenceThis yields clearer failures if the DB lookup unexpectedly returns null.
Apply:
- assertThat(start).isNotNull(); - assertThat(start.getTransactionId()).isGreaterThan(0); - assertThat(__DatabasePreparer__.getOcppTagRecord(REGISTERED_OCPP_TAG).getInTransaction()).isTrue(); + assertThat(start).isNotNull(); + assertThat(start.getTransactionId()).isGreaterThan(0); + var tagRecord = __DatabasePreparer__.getOcppTagRecord(REGISTERED_OCPP_TAG); + assertThat(tagRecord).as("OCPP tag record should exist").isNotNull(); + assertThat(tagRecord.getInTransaction()).isTrue();steve/src/test/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandlerTest.java (5)
42-46: Rename typo: “successFull” → “successful”.
Minor spelling nit for consistency and searchability in test reports.- public void testGetLastBitFromUrl_successFull() { + public void testGetLastBitFromUrl_successful() {Also, confirm desired behavior for query/fragment tails (e.g.,
...?x=1or#y). Should they be included in the “last bit” or stripped? Add a case accordingly.
64-66: Percent character case: decide on decoding policy and extend tests.
If the implementation ever decodes percent-escapes, inputs likeBBE%I12(invalid escape) could throw. Consider adding explicit cases for:
- Encoded slash:
%2F(should outputBBE%2FI12vsBBE/I12?)- Invalid percent sequence handling (stay literal or reject?)
78-80: Space in path segment: confirm normalization expectations.
HTTP servers typically deliver decoded paths; spaces usually arrive as%20. If upstream already decodes, this is fine; otherwise clarify contract and consider a%20variant test.
85-87: “Last bit” semantics include multiple trailing segments — clarify intent.
Expecting889/BBEI12suggests “everything afterCentralSystemService/” rather than the last single segment. Consider documenting this inOcppWebSocketHandshakeHandler#getLastBitFromUrlJavadoc and reflecting it in the test name.Would you like a follow-up PR to add Javadoc and a couple of illustrative examples to the production method?
27-96: Consider collapsing duplicate test cases into a ParameterizedTestThe series of nearly identical
testGetLastBitFromUrl_*methods can be simplified with JUnit’s parameterized tests. This reduces boilerplate, makes it easier to add new cases (e.g. query/fragment variants,%2F,ws://,wss://), and keeps your test intentions clear.• File to update:
steve/src/test/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandlerTest.java (lines 27–96)• Dependency check:
junit-jupiter-paramsis already declared in bothsteve/pom.xmlandsteve-ocpp/pom.xml, so you can use@ParameterizedTestwithout adding any new dependencies.Proposed refactor:
@org.junit.jupiter.params.ParameterizedTest(name = "{index}: getLastBitFromUrl(\"{0}\") -> \"{1}\"") @org.junit.jupiter.params.provider.MethodSource("getLastBitCases") void testGetLastBitFromUrl_parametrized(String input, String expected) { var actual = OcppWebSocketHandshakeHandler.getLastBitFromUrl(input); assertThat(actual).isEqualTo(expected); } private static java.util.stream.Stream<org.junit.jupiter.params.provider.Arguments> getLastBitCases() { return java.util.stream.Stream.of( org.junit.jupiter.params.provider.Arguments.of("", ""), org.junit.jupiter.params.provider.Arguments.of(null, ""), org.junit.jupiter.params.provider.Arguments.of("https://www.google.com/steve/websocket/CentralSystemService/BBEI12", "BBEI12"), org.junit.jupiter.params.provider.Arguments.of("/steve/websocket/CentralSystemService/", ""), org.junit.jupiter.params.provider.Arguments.of("/steve/websocket/CentralSystemService/BBEI12", "BBEI12"), org.junit.jupiter.params.provider.Arguments.of("/steve/websocket/CentralSystemService/BBE%I12", "BBE%I12"), org.junit.jupiter.params.provider.Arguments.of("/steve/websocket/CentralSystemService/BBE-I12", "BBE-I12"), org.junit.jupiter.params.provider.Arguments.of("/steve/websocket/CentralSystemService/BBE I12", "BBE I12"), org.junit.jupiter.params.provider.Arguments.of("/steve/websocket/CentralSystemService/889/BBEI12", "889/BBEI12") // Add more cases here (e.g., ws://, wss://, query/fragment once semantics are defined) ); }This change is entirely optional but will make the test suite more maintainable and extensible as new URL formats are supported.
steve/src/test/java/de/rwth/idsg/steve/utils/StringUtilsTest.java (2)
59-60: Confirm and document the null/empty vs. dedup contract for joinByComma
- Returning
nullfornulland empty inputs is a deliberate API choice; just ensure callers expect this (vs. empty string). A short Javadoc on the method would prevent future ambiguity.- The duplicate-elimination behavior is now codified by the test—good. Consider adding cases that cover trimming and case-sensitivity to lock down expectations, e.g.,
[" hey", "hey "]and["hey", "HEY"].If useful, here are minimal additional tests you could add:
@Test void testJoinByComma_trimsAndDedupes() { var val = StringUtils.joinByComma(List.of(" hey", "hey ", "ho")); assertThat(val).isEqualTo("hey,ho"); // if trimming is intended } @Test void testJoinByComma_caseSensitivity() { var val = StringUtils.joinByComma(List.of("hey", "HEY")); // Choose one of the following based on intended behavior: // assertThat(val).isEqualTo("hey,HEY"); // case-sensitive dedupe (both kept) // assertThat(val).isEqualTo("hey"); // case-insensitive dedupe }Also applies to: 65-66, 71-72, 77-78, 83-84
108-112: Avoid sorting in the test—assert the split’s order contract directlySorting the result before asserting hides any changes in the behavior of
splitByComma. If the method is intended to preserve input order, your test should assert onvalitself.• In
steve/src/test/java/de/rwth/idsg/steve/utils/StringUtilsTest.java(lines 108–112), replace:- var sortedVal = val.stream().sorted().toList(); - assertThat(sortedVal).containsExactly("1one", "2two"); + assertThat(val).containsExactly("1one", "2two");• Double-check your Java baseline: the build’s
<source>/<target>is set to${java.version}(via the maven-compiler-plugin). Ensurejava.version ≥ 16so thatStream.toList()is available; if you must stay on Java 15 or below, switch to:List<String> valList = val.stream().collect(Collectors.toList()); assertThat(valList).containsExactly("1one", "2two");steve/src/test/java/de/rwth/idsg/steve/StressTest.java (3)
76-77: Makedeltaeffectively immutable
deltaisn’t reassigned; declare itfinalfor clarity and to signal intent in this multithreaded stress context (even though used locally).- var delta = (transactionStop - transactionStart) / size; + final var delta = (transactionStop - transactionStart) / size;
82-85: Deterministic timestamps for generated MeterValuesUsing
OffsetDateTime.now()insidecreateMeterValueproduces slightly different timestamps per iteration, which can make results flaky and ordering ambiguous. Capture a base time once and increment per entry so all samples are strictly ordered and reproducible.- var list = new ArrayList<MeterValue>(size); - for (var i = 0; i < size; i++) { - var meterValue = transactionStart + delta * (i + 1); - list.add(createMeterValue(meterValue)); - } + var list = new ArrayList<MeterValue>(size); + var baseTime = OffsetDateTime.now(); + for (var i = 0; i < size; i++) { + var meterValue = transactionStart + delta * (i + 1); + list.add(createMeterValue(meterValue, baseTime.plusSeconds(i))); + }
91-93: OverloadcreateMeterValueto accept a timestampIntroduce an overload that accepts
OffsetDateTimeso callers can control determinism and ordering; keep the original as a convenience delegating tonow().- protected static MeterValue createMeterValue(int val) { - return new MeterValue().withTimestamp(OffsetDateTime.now()) - .withSampledValue(new SampledValue().withValue(Integer.toString(val))); - } + protected static MeterValue createMeterValue(int val) { + return createMeterValue(val, OffsetDateTime.now()); + } + + protected static MeterValue createMeterValue(int val, OffsetDateTime timestamp) { + return new MeterValue().withTimestamp(timestamp) + .withSampledValue(new SampledValue().withValue(Integer.toString(val))); + }steve/src/test/java/de/rwth/idsg/steve/issues/Issue72.java (2)
103-117: Consider varying MeterValues timestamps if ordering mattersAll MeterValues are sent with
stopDateTime. If the backend performs ordering/validation on timestamps, consider incrementing by a small offset per repeat to ensure monotonicity (e.g.,stopDateTime.plusSeconds(i)), or confirm backend accepts identical timestamps.
118-127: Confirm intent: stopping the same transaction on every repeat iteration
stopTransactionis called insidetoRepeat(). IfREPEAT_COUNT_PER_THREAD > 1, this attempts to stop an already-stopped transaction many times. If this is intentional to stress idempotency/error paths—great. If not, move the stop call intoafterRepeat()so it executes once per thread.Example adjustment inside this class (no diff, since lines are outside the changed hunk):
@Override public void toRepeat() { // keep meterValues here } @Override public void afterRepeat() { var stop = threadLocalClient.get().stopTransaction( new StopTransactionRequest() .withTransactionId(transactionId) .withTimestamp(stopDateTime) .withIdTag(idTag) .withMeterStop(meterStop), chargeBoxId ); assertThat(stop).isNotNull(); }steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java (2)
162-164: Explicit ISO-8601 formatting for SOAP timestamp (optional)
OffsetDateTime#toString()already uses ISO-8601. If you want to be explicit and future-proof against locale/customization, format withDateTimeFormatter.ISO_OFFSET_DATE_TIME.Example:
"<timestamp>" + stop.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME) + "</timestamp>"Also applies to: 173-175
21-21: Follow-up: remove Guava MediaType if unused after the fixOnce you switch to the explicit
"application/soap+xml"Content-Type, thecom.google.common.net.MediaTypeimport becomes dead weight.steve/src/test/java/de/rwth/idsg/steve/OperationalTestSoapOCPP16.java (2)
271-279: Minor: prefer rangeClosed iteration to avoid off-by-one mistakes.for (var i = 0; i <= numConnectors; i++) is correct but easy to regress. IntStream.rangeClosed(0, numConnectors) makes the intent explicit and avoids accidental < vs <= flips.
Apply within this block:
- for (var i = 0; i <= numConnectors; i++) { - var status = client.statusNotification( + java.util.stream.IntStream.rangeClosed(0, numConnectors).forEach(i -> { + var status = client.statusNotification( new StatusNotificationRequest() .withErrorCode(ChargePointErrorCode.NO_ERROR) .withStatus(chargePointStatus) .withConnectorId(i) .withTimestamp(OffsetDateTime.now()), REGISTERED_CHARGE_BOX_ID ); - assertThat(status).isNotNull(); - } + assertThat(status).isNotNull(); + });If desired, add at top-level imports:
import java.util.stream.IntStream;
580-590: Minor: same iteration nit as above.Using IntStream.rangeClosed(0, numConnectors) here mirrors the earlier loop and reduces off-by-one risk.
- for (var i = 0; i <= numConnectors; i++) { - var statusBoot = client.statusNotification( + java.util.stream.IntStream.rangeClosed(0, numConnectors).forEach(i -> { + var statusBoot = client.statusNotification( new StatusNotificationRequest() .withErrorCode(ChargePointErrorCode.NO_ERROR) .withStatus(ChargePointStatus.AVAILABLE) .withConnectorId(i) .withTimestamp(OffsetDateTime.now()), REGISTERED_CHARGE_BOX_ID ); - assertThat(statusBoot).isNotNull(); - } + assertThat(statusBoot).isNotNull(); + });
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (39)
pom.xml(2 hunks)steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/UpdateTransactionParams.java(2 hunks)steve-core/src/main/java/de/rwth/idsg/steve/utils/DateTimeUtils.java(2 hunks)steve-ocpp-websocket/pom.xml(1 hunks)steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/config/OcppWebSocketConfiguration.java(2 hunks)steve-ocpp-websocket/src/test/java/de/rwth/idsg/steve/ocpp/ws/custom/CustomStringModuleTest.java(2 hunks)steve-ocpp/pom.xml(1 hunks)steve-ocpp/src/main/java/de/rwth/idsg/ocpp/DateTimeUtils.java(1 hunks)steve-ocpp/src/test/java/de/rwth/idsg/ocpp/jaxb/JavaDateTimeConverterTest.java(1 hunks)steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/connectorStatus.jsp(2 hunks)steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/data-man/ocppTags.jsp(3 hunks)steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/data-man/transactionDetails.jsp(2 hunks)steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/data-man/transactions.jsp(2 hunks)steve/pom.xml(1 hunks)steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java(3 hunks)steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java(3 hunks)steve/src/test/java/de/rwth/idsg/steve/OperationalTestSoapOCPP16.java(12 hunks)steve/src/test/java/de/rwth/idsg/steve/StressTest.java(3 hunks)steve/src/test/java/de/rwth/idsg/steve/StressTestJsonOCPP16.java(5 hunks)steve/src/test/java/de/rwth/idsg/steve/StressTestSoapOCPP16.java(3 hunks)steve/src/test/java/de/rwth/idsg/steve/TypeStoreTest.java(2 hunks)steve/src/test/java/de/rwth/idsg/steve/issues/Issue1219.java(4 hunks)steve/src/test/java/de/rwth/idsg/steve/issues/Issue72.java(5 hunks)steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java(6 hunks)steve/src/test/java/de/rwth/idsg/steve/issues/Issue73Fix.java(4 hunks)steve/src/test/java/de/rwth/idsg/steve/issues/Issue81.java(5 hunks)steve/src/test/java/de/rwth/idsg/steve/ocpp/OcppProtocolTest.java(1 hunks)steve/src/test/java/de/rwth/idsg/steve/ocpp/OcppVersionTest.java(1 hunks)steve/src/test/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandlerTest.java(1 hunks)steve/src/test/java/de/rwth/idsg/steve/utils/StringUtilsTest.java(2 hunks)steve/src/test/java/de/rwth/idsg/steve/utils/TransactionStopServiceHelperTest.java(11 hunks)steve/src/test/java/de/rwth/idsg/steve/web/api/AbstractControllerTest.java(2 hunks)steve/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java(5 hunks)steve/src/test/java/de/rwth/idsg/steve/web/api/OcppTagsRestControllerTest.java(14 hunks)steve/src/test/java/de/rwth/idsg/steve/web/api/ReservationRestControllerTest.java(1 hunks)steve/src/test/java/de/rwth/idsg/steve/web/api/TransactionRestControllerTest.java(4 hunks)steve/src/test/java/de/rwth/idsg/steve/web/api/UsersRestControllerTest.java(1 hunks)steve/src/test/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidatorTest.java(1 hunks)steve/src/test/java/de/rwth/idsg/steve/web/validation/IdTagValidatorTest.java(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (28)
- pom.xml
- steve-ocpp-websocket/pom.xml
- steve/src/test/java/de/rwth/idsg/steve/web/api/AbstractControllerTest.java
- steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/data-man/transactionDetails.jsp
- steve/src/test/java/de/rwth/idsg/steve/utils/TransactionStopServiceHelperTest.java
- steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/UpdateTransactionParams.java
- steve-core/src/main/java/de/rwth/idsg/steve/utils/DateTimeUtils.java
- steve/src/test/java/de/rwth/idsg/steve/issues/Issue1219.java
- steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/data-man/ocppTags.jsp
- steve/src/test/java/de/rwth/idsg/steve/ocpp/OcppVersionTest.java
- steve/src/test/java/de/rwth/idsg/steve/web/validation/IdTagValidatorTest.java
- steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/data-man/transactions.jsp
- steve-ocpp/src/main/java/de/rwth/idsg/ocpp/DateTimeUtils.java
- steve-ocpp/pom.xml
- steve/src/test/java/de/rwth/idsg/steve/web/validation/ChargeBoxIdValidatorTest.java
- steve/pom.xml
- steve-ui-jsp/src/main/resources/webapp/WEB-INF/views/connectorStatus.jsp
- steve-ocpp/src/test/java/de/rwth/idsg/ocpp/jaxb/JavaDateTimeConverterTest.java
- steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java
- steve/src/test/java/de/rwth/idsg/steve/StressTestSoapOCPP16.java
- steve/src/test/java/de/rwth/idsg/steve/TypeStoreTest.java
- steve/src/test/java/de/rwth/idsg/steve/web/api/ReservationRestControllerTest.java
- steve-ocpp-websocket/src/test/java/de/rwth/idsg/steve/ocpp/ws/custom/CustomStringModuleTest.java
- steve/src/test/java/de/rwth/idsg/steve/web/api/UsersRestControllerTest.java
- steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java
- steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/config/OcppWebSocketConfiguration.java
- steve/src/test/java/de/rwth/idsg/steve/ocpp/OcppProtocolTest.java
- steve/src/test/java/de/rwth/idsg/steve/StressTestJsonOCPP16.java
🧰 Additional context used
🧬 Code graph analysis (6)
steve/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java (1)
steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/ChargePoint.java (1)
ChargePoint(32-49)
steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java (1)
steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java (1)
__DatabasePreparer__(68-202)
steve/src/test/java/de/rwth/idsg/steve/issues/Issue73Fix.java (1)
steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java (1)
__DatabasePreparer__(68-202)
steve/src/test/java/de/rwth/idsg/steve/issues/Issue72.java (1)
steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java (1)
__DatabasePreparer__(68-202)
steve/src/test/java/de/rwth/idsg/steve/web/api/OcppTagsRestControllerTest.java (1)
steve-core/src/main/java/de/rwth/idsg/steve/repository/dto/OcppTag.java (1)
OcppTag(34-77)
steve/src/test/java/de/rwth/idsg/steve/OperationalTestSoapOCPP16.java (1)
steve/src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java (1)
__DatabasePreparer__(68-202)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
- GitHub Check: build (ubuntu-24.04, 21, mariadb:10.5.21)
- GitHub Check: build (ubuntu-24.04, 21, mariadb:10.6.14)
- GitHub Check: build (ubuntu-24.04, 17, mariadb:10.5.21)
- GitHub Check: build (ubuntu-24.04, 21, mysql:8.0)
- GitHub Check: build (ubuntu-24.04, 21, mariadb:10.4.30)
- GitHub Check: build (ubuntu-24.04, 17, mariadb:10.6.14)
- GitHub Check: build (ubuntu-24.04, 21, mariadb:10.3)
- GitHub Check: build (ubuntu-24.04, 17, mariadb:10.4.30)
- GitHub Check: build (ubuntu-22.04, 21, mariadb:10.5.21)
- GitHub Check: build (ubuntu-22.04, 17, mariadb:10.6.14)
- GitHub Check: build (ubuntu-22.04, 21, mariadb:10.3)
- GitHub Check: build (ubuntu-22.04, 21, mariadb:10.6.14)
- GitHub Check: build (ubuntu-24.04, 17, mariadb:10.3)
- GitHub Check: build (ubuntu-22.04, 17, mysql:8.0)
- GitHub Check: build (ubuntu-22.04, 17, mariadb:10.3)
- GitHub Check: build (ubuntu-24.04, 17, mysql:8.0)
- GitHub Check: build (ubuntu-22.04, 21, mysql:8.0)
- GitHub Check: build (ubuntu-22.04, 17, mariadb:10.4.30)
- GitHub Check: build (ubuntu-22.04, 21, mariadb:10.4.30)
- GitHub Check: build (ubuntu-22.04, 17, mariadb:10.5.21)
🔇 Additional comments (43)
steve/src/test/java/de/rwth/idsg/steve/web/api/ChargePointsRestControllerTest.java (7)
56-56: Good move to MockMvcTesterSwitching the field to MockMvcTester aligns with the new AssertJ-style assertions and keeps tests concise.
68-71: AssertJ JSON path usage looks correct for empty listThe array assertion is precise and readable.
80-85: Solid assertions for single-element payloadSize and field checks are clear and type-safe.
92-93: 404 path covered correctlyAsserting explicit NOT_FOUND keeps intent clear.
102-106: 200 + payload validation reads wellDirect numeric assertion on $.chargeBoxPk is appropriate.
150-154: PUT test and MediaType usage LGTMUsing MediaType.APPLICATION_JSON (not the “_VALUE” string) is correct with MockMvcTester; status assertion is sufficient given the subsequent verify.
167-168: DELETE happy-path coveredStatus and follow-up verify ensure the flow is exercised.
steve/src/test/java/de/rwth/idsg/steve/web/api/TransactionRestControllerTest.java (5)
31-33: Good migration to MockMvcTester + JsonContent + java.timeThe import set reflects the AssertJ/MockMvcTester JSON DSL and OffsetDateTime usage. This keeps tests modern and consistent with the PR goals.
Also applies to: 35-35, 38-38
73-77: Empty-list response verified cleanlyThe array emptiness check is accurate and readable.
175-197: Timestamp assertions and hidden fields handling look rightExact ISO-8601 “Z” timestamps asserted via string equality and doesNotHavePath for the “...Formatted” fields match the DTO contract.
223-233: Captured form defaults validatedUsing an ArgumentCaptor to check QueryType/QueryPeriodType translation is the right level of verification.
257-264: Reusable error JSON matcher is tidyThis keeps 4xx/5xx tests DRY and consistent.
steve/src/test/java/de/rwth/idsg/steve/web/api/OcppTagsRestControllerTest.java (3)
175-179: Correct handling of expiryDate vs expiryDateFormattedAsserting the ISO value and explicitly asserting absence of the formatted field matches the DTO’s @JsonIgnore contract.
158-182: End-to-end GET-all happy path is thoroughCovers size and all salient fields, including boolean flags and numeric counters. Nicely exercises the serialization of OffsetDateTime to “Z” form.
527-534: Shared error JSON matcher is clean and consistentThis keeps failure-path tests uniform across the class.
steve/src/test/java/de/rwth/idsg/steve/issues/Issue81.java (4)
31-32: Good migration to java.time and explicit UTC usage downstream.Moving to OffsetDateTime/ZoneOffset is aligned with the PR goal and avoids Joda dependencies.
38-38: AssertJ adoption looks good.Static import of assertThat improves readability and is used consistently in this file.
88-89: Confirm test intent: duplicate StartTransaction expected to return the same transactionId.This asserts that sending the same StartTransactionRequest twice yields the same tx id. That behavior can be backend-specific (deduplication vs. creating a new transaction). Please confirm this is the intended invariant for Issue81 and still valid with your current CS implementation.
98-98: LGTM.Using var for the StressTester instantiation is clear and keeps noise down.
steve/src/test/java/de/rwth/idsg/steve/issues/Issue73Fix.java (3)
33-33: Good migration to java.timeOffsetDateTime here aligns with the repo’s Joda→java.time move. No issues.
39-39: AssertJ adoption looks goodStatic assertThat import is consistent with the AssertJ style used elsewhere.
93-101: Boot sequence assertions are appropriateAsserting non-null response and ACCEPTED status is sufficient here.
steve/src/test/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandlerTest.java (6)
37-39: Null-input behavior explicitly tested — good.
This locks in the contract that null yields an empty result, preventing regressions.
50-52: Trailing slash returning empty is precise and helpful.
Covers a common real-world trailing-delimiter case.
57-59: Happy path on relative URL segment looks correct.
Maintains parity with previous assertion style while clarifying intent.
71-73: Hyphenated IDs covered — good.
This protects common charger ID formats.
92-94: Complex tail scenario covered — nice edge-case coverage.
This, combined with earlier tests, provides solid confidence in path extraction.
23-24: AssertJ dependency verified and no JUnit assertion imports remain – ready to merge
assertj-coreis declared with<scope>test</scope>in all relevant modules (steve, steve-ocpp, steve-ocpp-websocket).- No lingering JUnit 4 or JUnit 5 assertion imports were found in any
src/test/javadirectories.steve/src/test/java/de/rwth/idsg/steve/utils/StringUtilsTest.java (3)
31-31: AssertJ migration and fluent assertions: LGTMConsistent, idiomatic use of
assertThatthroughout the test class. Good move away from JUnit assertions.
89-90: splitByComma null/empty semantics: LGTMReturning a non-null, empty list for
nulland""inputs is a solid, defensive contract and pairs well with thejoinByCommatests.Also applies to: 95-96
42-42: No locale-dependent logic ingetOperationName
ThegetOperationNamemethod derives its result solely from the task’s class name and a regex-based insertion of spaces; it does not use any locale-sensitive APIs or resource bundles. Therefore, the exact-string assertions in the tests cannot break under different default locales.
- The implementation in
StringUtils.java(lines 52–61) simply:
- Removes the “Task” suffix if present
- Inserts spaces between lowercase and uppercase letters via
replaceAll("(\\p{Ll})(\\p{Lu})", "$1 $2")- There are no references to
ResourceBundle,MessageSource,Locale, or similar i18n mechanisms in this method.You can safely remove the suggestion to guard against locale-dependent failures.
Likely an incorrect or invalid review comment.
steve/src/test/java/de/rwth/idsg/steve/StressTest.java (1)
55-56: Good modernization: AssertJ assertions and local var inference look consistentSwitch to AssertJ and
varimproves readability and matches the PR’s direction. No functional concerns here.Also applies to: 60-60
steve/src/test/java/de/rwth/idsg/steve/issues/Issue72.java (2)
65-67: Migration toOffsetDateTimeis correct and consistentUsing
startDateTimeand derivingstopDateTimeviaplusHoursaligns with the java.time migration and OCPP expectations for ISO-8601 timestamps. LGTM.
92-101: Thread-local client initialization is appropriateInitializing a
ThreadLocal<CentralSystemService>inbeforeRepeat()avoids cross-thread client reuse. Good call for stability under load.steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java (3)
79-85: Overall flow LGTMBoot, start, send MeterValues, and stop via raw SOAP aligns with the high-level test goal and the java.time migration. Once the Content-Type and request reuse issues are fixed, this test should be robust under stress.
Also applies to: 86-95, 120-134
71-78: Static test timestamps: good choice for reproducibilityUsing fixed
OffsetDateTime.parse(...)values keeps the SOAP envelope stable across iterations. This helps when diffing or diagnosing failures.
153-159: HTTP client lifecycle is correctCreating one
CloseableHttpClientand closing it infinallyis appropriate; Apache client is thread-safe. No changes needed.steve/src/test/java/de/rwth/idsg/steve/OperationalTestSoapOCPP16.java (6)
49-51: Good migration to java.time in tests.OffsetDateTime + ChronoUnit-based tolerances are appropriate for SOAP payloads and DB verification. Assertions using isCloseTo with byLessThan(1s) make the tests robust to processing jitter.
57-60: AssertJ modernization looks solid.Static imports for assertThat, assertThatExceptionOfType, and byLessThan simplify the tests and keep them expressive.
94-101: BeforeEach/AfterEach without throws is cleaner.Dropping checked exceptions from init/destroy methods keeps the JUnit 5 lifecycle hooks tidy, and the DB preparer calls are appropriately encapsulated.
480-486: Capturing start/stop timestamps before sending requests is correct.Holding the client-side timestamps and asserting DB values are close (rather than equal) avoids flakiness from processing time and clock skew.
Also applies to: 532-539
49-51: Verification complete: no remaining Joda-Time references found
A recursive grep for “org.joda.time” in the codebase returned no matches, confirming that Joda-Time has been fully removed.
42-42: No JAX-WS namespace inconsistencies detected
All JAX-WS APIs and CXF dependencies are on Jakarta namespaces (jakarta.xml.ws-api 3.0.1 and Apache CXF 4.1.3), and no imports ofjavax.xml.ws.WebServiceExceptionremain in the codebase. The test’s use ofimport jakarta.xml.ws.WebServiceException;is therefore correct and requires no changes.
| var req = RequestBuilder.post(path) | ||
| .addHeader("SOAPAction", "urn://Ocpp/Cs/2015/10/StopTransaction") | ||
| .setEntity(new StringEntity(body, contentType)) | ||
| .build(); |
There was a problem hiding this comment.
Don’t share HttpUriRequest across threads; build per-iteration
HttpUriRequest (and its attached entity) is not thread-safe. Construct it inside toRepeat() to avoid races and intermittent failures.
- var req = RequestBuilder.post(path)
- .addHeader("SOAPAction", "urn://Ocpp/Cs/2015/10/StopTransaction")
- .setEntity(new StringEntity(body, contentType))
- .build();Place this per-iteration right before httpClient.execute(...):
var req = RequestBuilder.post(path)
.addHeader("SOAPAction", "urn://Ocpp/Cs/2015/10/StopTransaction")
.setEntity(new StringEntity(body, contentType))
.build();
httpClient.execute(req, httpResponse -> {
if (httpResponse.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
throw new RuntimeException("Not OK");
}
return null;
});🤖 Prompt for AI Agents
In steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java around
lines 101-104, the code currently reuses a shared HttpUriRequest (and its
StringEntity) across iterations/threads which is not thread-safe; move the
RequestBuilder.post(...) call so a new HttpUriRequest (with a fresh StringEntity
and SOAPAction header) is constructed inside the toRepeat()/per-iteration code
path immediately before calling httpClient.execute(...), then pass that newly
built request to httpClient.execute and handle the response there; do not retain
or reuse the request instance across iterations or threads.
| var req = new StartTransactionRequest() | ||
| .withConnectorId(ThreadLocalRandom.current().nextInt(1, 8)) | ||
| .withIdTag(Helpers.getRandomString()) | ||
| .withTimestamp(DateTime.now()) | ||
| .withMeterStart(ThreadLocalRandom.current().nextInt()); | ||
| .withTimestamp(OffsetDateTime.now(ZoneOffset.UTC)) | ||
| .withMeterStart(ThreadLocalRandom.current().nextInt(0, 1_000_000)); | ||
| txRequest.set(req); |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Cap idTag to 20 chars; keep UTC and numeric ranges as-is.
- Great: connectorId bounded to [1..7], meterStart >= 0, and timestamp in UTC.
- Risk: idTag in OCPP 1.6 is typically max 20 characters. An unbounded random string may be rejected, causing flakiness.
Also, minor nit: unify usage of getRandomString() (you mix Helpers.getRandomString() and static import).
- .withIdTag(Helpers.getRandomString())
+ .withIdTag(ocppBound(getRandomString(), 20))📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| var req = new StartTransactionRequest() | |
| .withConnectorId(ThreadLocalRandom.current().nextInt(1, 8)) | |
| .withIdTag(Helpers.getRandomString()) | |
| .withTimestamp(DateTime.now()) | |
| .withMeterStart(ThreadLocalRandom.current().nextInt()); | |
| .withTimestamp(OffsetDateTime.now(ZoneOffset.UTC)) | |
| .withMeterStart(ThreadLocalRandom.current().nextInt(0, 1_000_000)); | |
| txRequest.set(req); | |
| var req = new StartTransactionRequest() | |
| .withConnectorId(ThreadLocalRandom.current().nextInt(1, 8)) | |
| .withIdTag(ocppBound(getRandomString(), 20)) | |
| .withTimestamp(OffsetDateTime.now(ZoneOffset.UTC)) | |
| .withMeterStart(ThreadLocalRandom.current().nextInt(0, 1_000_000)); | |
| txRequest.set(req); |
🤖 Prompt for AI Agents
In steve/src/test/java/de/rwth/idsg/steve/issues/Issue81.java around lines 75 to
80, the idTag is generated with an unbounded random string which can exceed the
OCPP 1.6 20-character limit and cause flaky test failures; change the code to
ensure idTag is at most 20 characters (e.g., trim or substring the random string
to 20 chars or call a helper that returns a max-20 string) while leaving
connectorId, meterStart and timestamp as-is, and make the getRandomString()
usage consistent (either use the static import everywhere or fully qualify
Helpers.getRandomString()).
steve/src/test/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandlerTest.java
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
steve/pom.xml (2)
170-178: Add Jackson Java Time module to serialize Instant/OffsetDateTimeJoda removal + Instant/OffsetDateTime introduction will require the JSR-310 module for JSON (otherwise serialization errors at runtime).
Add alongside existing Jackson deps:
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>${jackson.version}</version> </dependency> + <dependency> + <groupId>com.fasterxml.jackson.datatype</groupId> + <artifactId>jackson-datatype-jsr310</artifactId> + <version>${jackson.version}</version> + </dependency>
170-178: Ensure consistent Jackson JavaTimeModule registration across the applicationWe’ve confirmed that your central
BeanConfigurationalready does:
objectMapper.findAndRegisterModules()(picks upJavaTimeModule)objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)(enforces ISO-8601)and exposes this as an
@Beanfor Spring to wire. However, there are still directnew ObjectMapper()calls that bypass this global setup:• steve-ocpp-websocket/src/main/java/.../JsonObjectMapper.java:47–48
mapper = new ObjectMapper(); mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);• steve-ui-jsp/src/main/java/.../AjaxCallController.java:63–64
objectMapper = new ObjectMapper(); objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);• steve/src/main/java/.../service/GithubReleaseCheckService.java:80–81
ObjectMapper mapper = new ObjectMapper() .registerModule(new JavaTimeModule()); // no WRITE_DATES_AS_TIMESTAMPS disableAction items:
- Refactor these to inject the centrally configured
ObjectMapperbean (e.g. via@Autowiredor retrieving it from the context) instead of instantiating a new one.- If standalone construction is unavoidable, replace with:
- new ObjectMapper() + new ObjectMapper() .findAndRegisterModules() // registers JavaTimeModule .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)- In
GithubReleaseCheckService, also call.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)for serialization consistency (even if it’s primarily used for deserialization).This will guarantee that all JSON serialization/deserialization consistently handles Java 8 date/time types and uses ISO-8601 formatting.
steve/src/main/java/de/rwth/idsg/steve/utils/SteveConfigurationReader.java (1)
30-33: Add explicit timeZoneId mapping to configuration readerI verified that
SteveConfigurationdefines atimeZoneIdfield with a fixed default of"UTC"(i.e.private final String timeZoneId = "UTC";in
steve-core/src/main/java/de/rwth/idsg/steve/SteveConfiguration.java), and that the Lombok-generated builder exposes atimeZoneId(...)setter. However,SteveConfigurationReadernever invokes that setter, so the loaded configuration will always use UTC unless overridden by code elsewhere.If you intend to allow users to configure their own time zone, you should update
SteveConfigurationReader(around the builder invocation insteve/src/main/java/de/rwth/idsg/steve/utils/SteveConfigurationReader.java, lines ~38–41) to map a property—e.g.,time.zone.id—into the builder. For example:var config = SteveConfiguration.builder() .contextPath(sanitizeContextPath(p.getOptionalString("context.path").orElse(null))) .steveVersion(p.getString("steve.version")) .gitDescribe(useFallbackIfNotSet(p.getOptionalString("git.describe").orElse(null), null)) + .timeZoneId(p.getOptionalString("time.zone.id") + .orElse(TimeZone.getDefault().getID())) // …other mappings… .build();• If UTC as a fixed default is intended behavior, please document this (
timeZoneIddefaults to UTC) in your README or configuration guide.
• Otherwise, apply the above diff (or similar) to honor a configured time zone property.steve-core/src/main/java/de/rwth/idsg/steve/service/ChargePointServiceClient.java (1)
355-360: Guard against empty charge point selection before list.get(0)
list.get(0)will throw if the selection is empty. Fail fast with a clear message (and validateexpirytoo).Apply this diff inside
reserveNow:- List<ChargePointSelect> list = params.getChargePointSelectList(); + List<ChargePointSelect> list = params.getChargePointSelectList(); + if (list == null || list.isEmpty()) { + throw new IllegalArgumentException("ReserveNow requires at least one charge point selection"); + } + if (params.getExpiry() == null) { + throw new IllegalArgumentException("ReserveNow requires a non-null expiry Instant"); + }And use the already-validated values when building the reservation.
♻️ Duplicate comments (21)
steve/src/test/java/de/rwth/idsg/steve/issues/Issue73Fix.java (2)
55-56: Ensure test config actually sets TEST profile and auto-register=trueThis assertion will be brittle unless the
main.propertiesused by tests sets:
profile=TESTauto.register.unknown.stations=truePrefer adding a test-only override at
steve/src/test/resources/config/main.propertiesso CI doesn’t depend on production defaults.
105-107: Avoid potential NPE when accessing idTagInfoIf
getIdTagInfo()is null, the chained call will NPE before AssertJ can report a clear failure. Add an explicit null check.- assertThat(auth).isNotNull(); - assertThat(auth.getIdTagInfo().getStatus()).isEqualTo(expected); + assertThat(auth).isNotNull(); + assertThat(auth.getIdTagInfo()).isNotNull(); + assertThat(auth.getIdTagInfo().getStatus()).isEqualTo(expected);steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java (7)
33-33: Confirm Jetty 12+ test dependency for UpgradeException (core.exception) is presentThe import was switched to org.eclipse.jetty.websocket.core.exception.UpgradeException (Jetty 12+). Ensure the module’s POM declares the matching Jetty websocket-core artifact with scope test, otherwise this won’t compile.
Run this quick check and add the dependency if missing:
#!/bin/bash set -euo pipefail echo "Searching for Jetty websocket-core dependency..." rg -nP '<groupId>org\.eclipse\.jetty\.websocket</groupId>\s*\n\s*<artifactId>jetty-websocket-core</artifactId>' --glob '**/pom.xml' -n -C2 || echo "MISSING: Add jetty-websocket-core (scope: test) to the relevant pom.xml" echo echo "Occurrences of UpgradeException imports (old/new packages):" rg -nP 'org\.eclipse\.jetty\.websocket\.(api\.exceptions|core\.exception)\.UpgradeException'
153-159: Resolve Checkstyle violations in lambda (stray semicolon, brace placement, long line)The
c -> {;introduces EmptyStatement/LeftCurly issues and the assertion line is long. Use a proper block and wrap the equality.Apply:
- .rootCause().satisfies(c -> {; - var ue = (UpgradeException) c; - assertThat(ue.getResponseStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST.value()); - }); + .rootCause() + .satisfies(c -> { + var ue = (UpgradeException) c; + assertThat(ue.getResponseStatusCode()) + .isEqualTo(HttpStatus.BAD_REQUEST.value()); + });
141-144: Fix Checkstyle line-length and provide failure diagnostics (OCPP 1.6 authorize)This assertion likely breaches the 120-char limit; also uses a bare fail().
Apply:
- authResponse -> assertThat(authResponse.getIdTagInfo().getStatus()).isEqualTo(AuthorizationStatus.ACCEPTED), - error -> fail() + authResponse -> assertThat(authResponse.getIdTagInfo().getStatus()) + .isEqualTo(AuthorizationStatus.ACCEPTED), + error -> fail("Unexpected error during OCPP 1.6 authorize: " + error)
93-96: Fix Checkstyle line-length and provide failure diagnostics (OCPP 1.2 authorize)Same issues: long line and bare fail().
Apply:
- authResponse -> assertThat(authResponse.getIdTagInfo().getStatus()).isEqualTo(ocpp.cs._2010._08.AuthorizationStatus.ACCEPTED), - error -> fail() + authResponse -> assertThat(authResponse.getIdTagInfo().getStatus()) + .isEqualTo(ocpp.cs._2010._08.AuthorizationStatus.ACCEPTED), + error -> fail("Unexpected error during OCPP 1.2 authorize: " + error)
86-89: Fix Checkstyle line-length and provide failure diagnostics (OCPP 1.2 boot)The assertion line exceeds 120 chars and the error branch uses a bare fail(). Wrap and include error details.
Apply:
- bootResponse -> assertThat(bootResponse.getStatus()).isEqualTo(ocpp.cs._2010._08.RegistrationStatus.ACCEPTED), - error -> fail() + bootResponse -> assertThat(bootResponse.getStatus()) + .isEqualTo(ocpp.cs._2010._08.RegistrationStatus.ACCEPTED), + error -> fail("Unexpected error during OCPP 1.2 boot: " + error)
117-120: Fix Checkstyle line-length and provide failure diagnostics (OCPP 1.5 authorize)Long line + bare fail().
Apply:
- authResponse -> assertThat(authResponse.getIdTagInfo().getStatus()).isEqualTo(ocpp.cs._2012._06.AuthorizationStatus.ACCEPTED), - error -> fail() + authResponse -> assertThat(authResponse.getIdTagInfo().getStatus()) + .isEqualTo(ocpp.cs._2012._06.AuthorizationStatus.ACCEPTED), + error -> fail("Unexpected error during OCPP 1.5 authorize: " + error)
110-113: Fix Checkstyle line-length and provide failure diagnostics (OCPP 1.5 boot)Long line + bare fail().
Apply:
- bootResponse -> assertThat(bootResponse.getStatus()).isEqualTo(ocpp.cs._2012._06.RegistrationStatus.ACCEPTED), - error -> fail() + bootResponse -> assertThat(bootResponse.getStatus()) + .isEqualTo(ocpp.cs._2012._06.RegistrationStatus.ACCEPTED), + error -> fail("Unexpected error during OCPP 1.5 boot: " + error)steve/src/test/java/de/rwth/idsg/steve/OperationalTestSoapOCPP16.java (3)
499-501: Fix Checkstyle: line exceeds 120 chars (start timestamp assertion)Same issue previously flagged; please wrap to satisfy LineLengthCheck.
- assertThat(t.getStartTimestamp()).isCloseTo(startTimeStamp.toLocalDateTime(), byLessThan(1, ChronoUnit.SECONDS)); + assertThat(t.getStartTimestamp()) + .isCloseTo( + startTimeStamp.toLocalDateTime(), + byLessThan(1, ChronoUnit.SECONDS) + );
550-551: Fix Checkstyle: line exceeds 120 chars (stop timestamp assertion)Same as above for the stop timestamp assertion.
- assertThat(t.getStopTimestamp()).isCloseTo(stopTimeStamp.toLocalDateTime(), byLessThan(1, ChronoUnit.SECONDS)); + assertThat(t.getStopTimestamp()) + .isCloseTo( + stopTimeStamp.toLocalDateTime(), + byLessThan(1, ChronoUnit.SECONDS) + );
599-610: Bug: asserting empty sampled values but indexing sampledValues.get(0)
assertThat(sampledValues).isEmpty();contradicts the subsequentsampledValues.get(0)usage and will fail at runtime. Also, meter values can contain multiple sampled values; check them robustly.Apply this refactor:
- var sampledValues = meterValue.getSampledValue(); - assertThat(sampledValues).isEmpty(); - var thisValueFound = false; - // and check, if it can be found in the DB - for (var values : details.getValues()) { - if (values.getValue().equals(sampledValues.get(0).getValue())) { - thisValueFound = true; - break; - } - } + var sampledValues = meterValue.getSampledValue(); + assertThat(sampledValues).isNotEmpty(); + // Check if any sampled value is present in DB details + var thisValueFound = details.getValues().stream() + .anyMatch(values -> + sampledValues.stream().anyMatch(sv -> values.getValue().equals(sv.getValue())) + ); assertThat(thisValueFound).isTrue();steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java (3)
99-100: Fix SOAP Content-Type; current code won’t compile and builds an invalid MIME
- com.google.common.net.MediaType has no SOAP_XML_UTF_8 constant.
- MediaType#type() returns only the primary type ("application"), yielding an invalid header.
- Use a literal SOAP 1.2 media type and StandardCharsets.UTF_8.
Apply this diff:
-var contentType = ContentType.create(MediaType.SOAP_XML_UTF_8.type(), MediaType.SOAP_XML_UTF_8.charset().orNull()); +var contentType = ContentType.create("application/soap+xml", StandardCharsets.UTF_8);
43-44: Add StandardCharsets import (required by the Content-Type fix)Apply this diff:
+import java.nio.charset.StandardCharsets; import java.time.OffsetDateTime;
101-105: Do not share HttpUriRequest/StringEntity across threads; build per-iterationHttpUriRequest and attached entities are not thread-safe. The current code builds req once and reuses it in toRepeat(), which is executed by multiple threads, risking races and IllegalStateException.
Apply these diffs:
- var req = RequestBuilder.post(path) - .addHeader("SOAPAction", "urn://Ocpp/Cs/2015/10/StopTransaction") - .setEntity(new StringEntity(body, contentType)) - .build();And inside toRepeat(), immediately before httpClient.execute(...):
try { + var req = RequestBuilder.post(path) + .addHeader("SOAPAction", "urn://Ocpp/Cs/2015/10/StopTransaction") + .setEntity(new StringEntity(body, contentType)) + .build(); + httpClient.execute(req, httpResponse -> { if (httpResponse.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { throw new RuntimeException("Not OK"); } return null; });Also applies to: 136-146
steve-core/src/main/java/de/rwth/idsg/steve/utils/DateTimeUtils.java (1)
120-149: Braces added — checkstyle concerns resolved.The NeedBraces violations noted earlier are addressed; the conditional blocks now use braces.
steve/src/main/java/de/rwth/idsg/steve/repository/impl/OcppTagRepositoryImpl.java (4)
42-51: Import ZoneOffset for consistent UTC comparisonsFollow-up on earlier review: ensure you import ZoneOffset and use UTC for LocalDateTime “now” to avoid system-default timezone drift.
import java.sql.SQLIntegrityConstraintViolationException; -import java.time.LocalDateTime; +import java.time.LocalDateTime; +import java.time.ZoneOffset;
118-121: Use UTC when comparing LocalDateTime to avoid TZ-dependent driftComparing EXPIRY_DATE against LocalDateTime.now() uses the JVM default zone, which may differ from the UTC assumption used elsewhere (e.g., conversions in DateTimeUtils). Use UTC explicitly.
- selectQuery.addConditions(OCPP_TAG_ACTIVITY.EXPIRY_DATE.lessOrEqual(LocalDateTime.now())); + selectQuery.addConditions( + OCPP_TAG_ACTIVITY.EXPIRY_DATE.lessOrEqual(LocalDateTime.now(ZoneOffset.UTC)) + );
123-126: Same here: align “not expired” predicate with UTC baselineKeep both sides of the comparison on the same semantics (UTC).
- OCPP_TAG_ACTIVITY.EXPIRY_DATE.isNull() - .or(OCPP_TAG_ACTIVITY.EXPIRY_DATE.greaterThan(LocalDateTime.now())) + OCPP_TAG_ACTIVITY.EXPIRY_DATE.isNull() + .or(OCPP_TAG_ACTIVITY.EXPIRY_DATE.greaterThan(LocalDateTime.now(ZoneOffset.UTC)))
191-193: Active tags query: fix timezone baselineSame issue in active tag predicate; prefer UTC.
- .and(OCPP_TAG_ACTIVITY.EXPIRY_DATE.isNull() - .or(OCPP_TAG_ACTIVITY.EXPIRY_DATE.greaterThan(LocalDateTime.now()))) + .and( + OCPP_TAG_ACTIVITY.EXPIRY_DATE.isNull() + .or(OCPP_TAG_ACTIVITY.EXPIRY_DATE.greaterThan(LocalDateTime.now(ZoneOffset.UTC))) + )steve/src/main/java/de/rwth/idsg/steve/repository/impl/TransactionRepositoryImpl.java (1)
383-388: Line-length has been addressed as suggested earlier.The long generic signature is now wrapped, resolving the Checkstyle violation.
| public static String humanize(Instant dt, ZoneOffset timeZone) { | ||
| if (dt == null) { | ||
| return ""; | ||
| } | ||
| return humanize(dt.atOffset(timeZone).toLocalDateTime()); | ||
| } | ||
|
|
There was a problem hiding this comment.
Bug: time zone mismatch and potential NPE in humanize(Instant, ZoneOffset)
- If
timeZoneis null,dt.atOffset(timeZone)will NPE. - Even when non-null, this method converts the instant using
timeZonebuthumanize(LocalDateTime)compares againstLocalDateTime.now()in the system default zone, which can mislabel “Today/Yesterday/Tomorrow” whentimeZone≠ system zone.
Fix by keeping comparisons within the same zone and handling null timeZone.
- public static String humanize(Instant dt, ZoneOffset timeZone) {
- if (dt == null) {
- return "";
- }
- return humanize(dt.atOffset(timeZone).toLocalDateTime());
- }
+ public static String humanize(Instant dt, ZoneOffset timeZone) {
+ if (dt == null) {
+ return "";
+ }
+ if (timeZone == null) {
+ timeZone = ZoneOffset.UTC;
+ }
+ var local = dt.atOffset(timeZone).toLocalDateTime();
+ var inputAtMidnight = local.toLocalDate().atStartOfDay();
+ var todayAtMidnight = java.time.LocalDate.now(timeZone).atStartOfDay();
+ if (inputAtMidnight.equals(todayAtMidnight)) {
+ return "Today at " + TIME_FORMATTER.format(local);
+ } else if (inputAtMidnight.equals(todayAtMidnight.minusDays(1))) {
+ return "Yesterday at " + TIME_FORMATTER.format(local);
+ } else if (inputAtMidnight.equals(todayAtMidnight.plusDays(1))) {
+ return "Tomorrow at " + TIME_FORMATTER.format(local);
+ } else {
+ return HUMAN_FORMATTER.format(local);
+ }
+ }Note: This change keeps logic localized to the Zoned variant; no signature changes elsewhere. If you prefer, we can instead pass a ZoneId throughout for broader flexibility.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public static String humanize(Instant dt, ZoneOffset timeZone) { | |
| if (dt == null) { | |
| return ""; | |
| } | |
| return humanize(dt.atOffset(timeZone).toLocalDateTime()); | |
| } | |
| public static String humanize(Instant dt, ZoneOffset timeZone) { | |
| if (dt == null) { | |
| return ""; | |
| } | |
| if (timeZone == null) { | |
| timeZone = ZoneOffset.UTC; | |
| } | |
| var local = dt.atOffset(timeZone).toLocalDateTime(); | |
| var inputAtMidnight = local.toLocalDate().atStartOfDay(); | |
| var todayAtMidnight = java.time.LocalDate.now(timeZone).atStartOfDay(); | |
| if (inputAtMidnight.equals(todayAtMidnight)) { | |
| return "Today at " + TIME_FORMATTER.format(local); | |
| } else if (inputAtMidnight.equals(todayAtMidnight.minusDays(1))) { | |
| return "Yesterday at " + TIME_FORMATTER.format(local); | |
| } else if (inputAtMidnight.equals(todayAtMidnight.plusDays(1))) { | |
| return "Tomorrow at " + TIME_FORMATTER.format(local); | |
| } else { | |
| return HUMAN_FORMATTER.format(local); | |
| } | |
| } |
| if (seconds > 0 || sb.isEmpty()) { | ||
| sb.append(seconds).append(seconds == 1 ? " second" : " seconds"); | ||
| } |
There was a problem hiding this comment.
Bug: StringBuilder has no isEmpty(); use length() == 0.
This currently won’t compile on standard JDKs.
- if (seconds > 0 || sb.isEmpty()) {
+ if (seconds > 0 || sb.length() == 0) {
sb.append(seconds).append(seconds == 1 ? " second" : " seconds");
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (seconds > 0 || sb.isEmpty()) { | |
| sb.append(seconds).append(seconds == 1 ? " second" : " seconds"); | |
| } | |
| if (seconds > 0 || sb.length() == 0) { | |
| sb.append(seconds).append(seconds == 1 ? " second" : " seconds"); | |
| } |
🤖 Prompt for AI Agents
In steve-core/src/main/java/de/rwth/idsg/steve/utils/DateTimeUtils.java around
lines 144 to 146, the code calls sb.isEmpty() which does not exist on
StringBuilder and prevents compilation; replace that check with sb.length() == 0
so the condition reads (seconds > 0 || sb.length() == 0) to preserve the
original logic; update the code accordingly and run javac/tests to verify
compilation.
| import jakarta.validation.constraints.Future; | ||
| import jakarta.validation.constraints.NotEmpty; | ||
|
|
||
| import java.time.Instant; |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Inconsistent schema vs. type: Instant is UTC, but description says “without timezone”
The field is now Instant, which is always UTC. The schema text and example still reflect a timezone-less LocalDateTime. This will confuse clients and can break request binding.
- Option A (minimal): keep Instant, update description and example to a Z-suffixed ISO instant.
- Option B: if the UI truly needs a local, timezone-less value, switch the type to LocalDateTime and normalize server-side.
Apply this diff for Option A:
@@
-import java.time.Instant;
+import java.time.Instant;
+import io.swagger.v3.oas.annotations.media.Schema;
@@
- @Schema(description = "A date/time without timezone. Example: 2022-10-10T09:00")
+ @Schema(description = "UTC instant (ISO-8601). Example: 2025-08-26T09:00:00Z")
@Future(message = "Expiry Date/Time must be in future")
private Instant expiryDate;To find impacted call sites and tests expecting a timezone-less value:
Also applies to: 56-59
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n -C2 'expiryDate' --type=java --type=xml --type=properties -g '!**/target/**'
rg -n -C2 '2022-10-10T09:00' -g '!**/target/**' || trueLength of output: 137
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Search for any references to expiryDate in the codebase (excluding target directory)
rg -n -C2 'expiryDate' -g '!**/target/**'
# Search for the hardcoded example date string
rg -n -C2 '2022-10-10T09:00' -g '!**/target/**' || trueLength of output: 25586
Align @Schema annotations with UTC Instant semantics
There’s a mismatch between the Instant type (always UTC) and the @Schema descriptions/examples, which still reference a timezone-less format. This occurs in:
- steve-core/src/main/java/de/rwth/idsg/steve/web/dto/OcppTagForm.java (line 56)
- steve-core/src/main/java/de/rwth/idsg/steve/web/dto/QueryForm.java (line 49)
All tests and code paths already treat these as Z-suffixed instants (e.g. “2020-10-01T00:00:00Z”), so clients and generated docs will be confused by the old descriptions.
Recommended (Option A): keep Instant and update both @Schema annotations to UTC ISO-8601. Apply:
--- a/steve-core/src/main/java/de/rwth/idsg/steve/web/dto/OcppTagForm.java
@@ -54,3 +54,5 @@
- @Schema(description = "A date/time without timezone. Example: 2022-10-10T09:00")
+ @Schema(description = "UTC instant (ISO-8601). Example: 2025-08-26T09:00:00Z")
@Future(message = "Expiry Date/Time must be in future")
private Instant expiryDate;--- a/steve-core/src/main/java/de/rwth/idsg/steve/web/dto/QueryForm.java
@@ -47,3 +47,5 @@
- @Schema(description = "Show results that happened after this date/time. Format: ISO8601 without timezone. Example: `2022-10-10T09:00:00`")
+ @Schema(description = "UTC instant (ISO-8601). Example: 2025-08-26T09:00:00Z")
private Instant from;Alternatively (Option B), if the UI truly requires a local, timezone-less value, switch these fields to LocalDateTime and normalize server-side.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import java.time.Instant; | |
| --- a/steve-core/src/main/java/de/rwth/idsg/steve/web/dto/OcppTagForm.java | |
| @@ -54,3 +54,5 @@ | |
| @Schema(description = "UTC instant (ISO-8601). Example: 2025-08-26T09:00:00Z") | |
| @Future(message = "Expiry Date/Time must be in future") | |
| private Instant expiryDate; |
| import java.time.Instant; | |
| --- a/steve-core/src/main/java/de/rwth/idsg/steve/web/dto/QueryForm.java | |
| @@ -47,3 +47,5 @@ | |
| @Schema(description = "UTC instant (ISO-8601). Example: 2025-08-26T09:00:00Z") | |
| private Instant from; |
🤖 Prompt for AI Agents
In steve-core/src/main/java/de/rwth/idsg/steve/web/dto/OcppTagForm.java (line
~56) and steve-core/src/main/java/de/rwth/idsg/steve/web/dto/QueryForm.java
(line ~49) the @Schema annotations describe a timezone-less timestamp while the
field type is java.time.Instant (which is always UTC); update both @Schema
description and example to explicitly state ISO‑8601 UTC instants with a Z
suffix (e.g. "2020-10-01T00:00:00Z") so docs and clients reflect Z‑suffixed UTC
semantics; if the project instead needs timezone‑less local datetimes, change
the field types to java.time.LocalDateTime and handle normalization server‑side
(Option B) — prefer Option A and adjust only the annotations to mention UTC and
provide Z‑suffixed examples.
steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/WebDateTimeUtils.java
Outdated
Show resolved
Hide resolved
steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/WebDateTimeUtils.java
Outdated
Show resolved
Hide resolved
steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/GlobalControllerAdvice.java
Show resolved
Hide resolved
| var zoneId = ZoneId.of(sc.getTimeZoneId()); | ||
| TimeZone.setDefault(TimeZone.getTimeZone(zoneId)); | ||
| log.info("Date/time zone of the application is set to {}. Current date/time: {}", | ||
| sc.getTimeZoneId(), ZonedDateTime.now(zoneId)); |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Initialize JVM default time zone from config earlier and make tests consistent.
Setting the JVM default TZ is good. Two points:
- If
sc.getTimeZoneId()is invalid,ZoneId.of(...)will throw and prevent startup. Consider validating with a clearer error or a safe fallback. - Tests construct
Applicationdirectly (see ApplicationTest/ApplicationJsonTest) and won’t executemain, so the default TZ won’t be set in those paths, risking inconsistent behavior.
Suggested refactor: centralize TZ init and invoke it from both main and the constructor.
@@
- var zoneId = ZoneId.of(sc.getTimeZoneId());
- TimeZone.setDefault(TimeZone.getTimeZone(zoneId));
- log.info("Date/time zone of the application is set to {}. Current date/time: {}",
- sc.getTimeZoneId(), ZonedDateTime.now(zoneId));
+ applyTimeZone(sc);Add outside the changed hunk (illustrative):
// Place inside Application
static void applyTimeZone(SteveConfiguration sc) {
try {
var zoneId = ZoneId.of(sc.getTimeZoneId());
TimeZone.setDefault(TimeZone.getTimeZone(zoneId));
log.info("Date/time zone of the application is set to {}. Current date/time: {}",
sc.getTimeZoneId(), ZonedDateTime.now(zoneId));
} catch (Exception e) {
throw new IllegalArgumentException("Invalid time zone id in configuration: " + sc.getTimeZoneId(), e);
}
}
// And call it in the constructor as well:
public Application(SteveConfiguration config) {
applyTimeZone(config); // keeps tests consistent with main()
server = new JettyServer(config);
}Check whether tests rely on main or call the constructor directly:
🏁 Script executed:
#!/bin/bash
rg -nP 'new\s+Application\(' steve/src/test -C2Length of output: 2246
Ensure JVM default time zone is initialized in both main and the constructor
Tests in steve/src/test/java/... instantiate new Application(config) directly (e.g. StressTest, ApplicationTest, ApplicationJsonTest, OperationalTestSoapOCPP16, etc.), so they never exercise the main-level TZ setup. Without centralizing the TZ logic, tests will use the JVM’s default and may exhibit inconsistent date/time behavior.
• Tests that call the constructor directly (and thus skip main):
- steve/src/test/java/de/rwth/idsg/steve/StressTest.java:61
- steve/src/test/java/de/rwth/idsg/steve/ApplicationTest.java:59
- steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java:65
- steve/src/test/java/de/rwth/idsg/steve/OperationalTestSoapOCPP16.java:85
- steve/src/test/java/de/rwth/idsg/steve/issues/Issue73Fix.java:62
• Invalid sc.getTimeZoneId() currently bubbles up as a generic DateTimeException. It’s clearer to validate and throw an IllegalArgumentException with a message indicating the bad config value.
Suggested refactor:
--- a/steve/src/main/java/de/rwth/idsg/steve/Application.java
+++ b/steve/src/main/java/de/rwth/idsg/steve/Application.java
@@ -48,11 +48,6 @@ public class Application {
var sc = configLoader.load();
applyTimeZone(sc);
server = new JettyServer(sc);
- var zoneId = ZoneId.of(sc.getTimeZoneId());
- TimeZone.setDefault(TimeZone.getTimeZone(zoneId));
- log.info("Date/time zone of the application is set to {}. Current date/time: {}",
- sc.getTimeZoneId(), ZonedDateTime.now(zoneId));
-
}
public static void main(String[] args) throws Exception {
var configLoader = new ConfigurationLoader();
applyTimeZone(configLoader.load());
new Application(configLoader.load()).start();
}
/** Centralized JVM time-zone initialization */
static void applyTimeZone(SteveConfiguration sc) {
try {
var zoneId = ZoneId.of(sc.getTimeZoneId());
TimeZone.setDefault(TimeZone.getTimeZone(zoneId));
log.info("Date/time zone of the application is set to {}. Current date/time: {}",
sc.getTimeZoneId(), ZonedDateTime.now(zoneId));
} catch (DateTimeException e) {
- // Fallback or silent failure is dangerous; force explicit handling
- throw e;
+ throw new IllegalArgumentException(
+ "Invalid time zone ID in configuration: " + sc.getTimeZoneId(), e);
}
}This ensures:
- All code paths (including tests) use the same TZ setup.
- Invalid configs fail fast with a clear error message.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| var zoneId = ZoneId.of(sc.getTimeZoneId()); | |
| TimeZone.setDefault(TimeZone.getTimeZone(zoneId)); | |
| log.info("Date/time zone of the application is set to {}. Current date/time: {}", | |
| sc.getTimeZoneId(), ZonedDateTime.now(zoneId)); | |
| // In steve/src/main/java/de/rwth/idsg/steve/Application.java | |
| public class Application { | |
| public Application(ConfigurationLoader configLoader) { | |
| var sc = configLoader.load(); | |
| applyTimeZone(sc); | |
| server = new JettyServer(sc); | |
| } | |
| public static void main(String[] args) throws Exception { | |
| var configLoader = new ConfigurationLoader(); | |
| applyTimeZone(configLoader.load()); | |
| new Application(configLoader.load()).start(); | |
| } | |
| /** Centralized JVM time-zone initialization */ | |
| static void applyTimeZone(SteveConfiguration sc) { | |
| try { | |
| var zoneId = ZoneId.of(sc.getTimeZoneId()); | |
| TimeZone.setDefault(TimeZone.getTimeZone(zoneId)); | |
| log.info("Date/time zone of the application is set to {}. Current date/time: {}", | |
| sc.getTimeZoneId(), ZonedDateTime.now(zoneId)); | |
| } catch (DateTimeException e) { | |
| throw new IllegalArgumentException( | |
| "Invalid time zone ID in configuration: " + sc.getTimeZoneId(), e); | |
| } | |
| } | |
| // … other members … | |
| } |
| } | ||
|
|
||
| public List<OcppJsonStatus> getOcppJsonStatus() { | ||
| public List<OcppJsonStatus> getOcppJsonStatus(ZoneOffset timeZone) { |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Use ZoneId or per-event offset to avoid DST-related misrepresentation.
Passing a fixed ZoneOffset for formatting/humanization can yield incorrect “local” times around DST transitions. Prefer ZoneId so you can compute the correct offset for each openSince.
Option A — Switch API to ZoneId and compute offset per entry:
@@
-import java.time.ZoneOffset;
+import java.time.ZoneId;
@@
- public List<OcppJsonStatus> getOcppJsonStatus(ZoneOffset timeZone) {
+ public List<OcppJsonStatus> getOcppJsonStatus(ZoneId zoneId) {
@@
- private static void appendList(Map<String, Deque<SessionContext>> map, List<OcppJsonStatus> returnList,
- Instant now, ZoneOffset timeZone, OcppVersion version,
+ private static void appendList(Map<String, Deque<SessionContext>> map, List<OcppJsonStatus> returnList,
+ Instant now, ZoneId zoneId, OcppVersion version,
Map<String, Integer> primaryKeyLookup) {
@@
- var openSince = ctx.getOpenSince();
+ var openSince = ctx.getOpenSince(); // Instant expected
+ var offsetAtOpenSince = zoneId.getRules().getOffset(openSince);
OcppJsonStatus status = OcppJsonStatus.builder()
.chargeBoxPk(primaryKeyLookup.get(chargeBoxId))
.chargeBoxId(chargeBoxId)
.connectedSinceDT(openSince)
- .connectedSince(humanize(openSince, timeZone))
+ .connectedSince(humanize(openSince, offsetAtOpenSince))
.connectionDuration(timeElapsed(openSince, now))
.version(version)
.build();This keeps humanize(...) using an Offset (per-instant), ensuring correctness across DST.
If changing the public signature is costly right now, Option B is to keep ZoneOffset but add an overload that accepts ZoneId and delegates with a computed, per-event offset. Let me know if you want that patch.
Also applies to: 189-192, 199-206
steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java
Outdated
Show resolved
Hide resolved
7f6fb1e to
bef391a
Compare
There was a problem hiding this comment.
Actionable comments posted: 14
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
steve/src/main/java/de/rwth/idsg/steve/service/GithubReleaseCheckService.java (1)
70-76: Timeouts are not fully applied; requests may block longer than intendedOnly the connection request timeout is set. Connect and response (read) timeouts are missing, and the created ConnectionConfig/SocketConfig are unused. This undermines the stated goal in the class-level comment to avoid long waits.
Apply this diff to set all three timeouts and drop unused configs:
- var connectionConfig = ConnectionConfig.custom().setConnectTimeout(timeout).build(); - var socketConfig = SocketConfig.custom().setSoTimeout(timeout).build(); - var requestConfig = RequestConfig.custom().setConnectionRequestTimeout(timeout).build(); + var requestConfig = RequestConfig.custom() + .setConnectTimeout(timeout) + .setResponseTimeout(timeout) + .setConnectionRequestTimeout(timeout) + .build();Optionally, also enforce at the factory layer (Spring 6 HttpComponents5 variant supports these):
var factory = new HttpComponentsClientHttpRequestFactory(httpClient); + factory.setConnectTimeout(timeout); + factory.setConnectionRequestTimeout(timeout); + factory.setResponseTimeout(timeout);Also applies to: 78-83
steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/pipeline/Sender.java (1)
52-61: Preserve the cause and handle IllegalStateException when sendingCurrently,
new SteveException(e.getMessage())discards the stack trace and original cause. Also,WebSocketSession.sendMessagemay throwIllegalStateException(closed session). Catch it and wrap with the cause to aid debugging.- } catch (IOException e) { - // Do NOT swallow exceptions for outgoing CALLs. For others just log. - if (context.getOutgoingMessage() instanceof OcppJsonCall) { - throw new SteveException(e.getMessage()); - } else { - log.error("Could not send the outgoing message", e); - } - } + } catch (IOException | IllegalStateException e) { + // Do NOT swallow exceptions for outgoing CALLs. For others just log. + if (context.getOutgoingMessage() instanceof OcppJsonCall) { + throw new SteveException("Failed to send outgoing CALL over WebSocket", e); + } else { + log.error("Could not send the outgoing message", e); + } + }steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/pipeline/Deserializer.java (1)
93-95: Bug: exception cause lost on top-level deserialization failure
new SteveException("... %s", context.getIncomingString(), e)likely selects the varargs constructor and drops the cause. Preserve the stack trace by formatting first and passingeas the cause.- } catch (IOException e) { - throw new SteveException("Deserialization of incoming string failed: %s", context.getIncomingString(), e); - } + } catch (IOException e) { + throw new SteveException( + String.format("Deserialization of incoming string failed: %s", context.getIncomingString()), e + ); + }steve/src/test/java/de/rwth/idsg/steve/utils/OcppJsonChargePoint.java (1)
103-107: Guard concurrent access to request/response mapsresponseContextMap (LinkedHashMap for insertion order) and requestContextMap (HashMap) are accessed from both the Jetty websocket thread(s) and the test thread. While you copy values before iteration, concurrent put/remove without synchronization is still undefined for HashMap/LinkedHashMap.
Apply lightweight synchronization without losing order:
- this.responseContextMap = new LinkedHashMap<>(); // because we want to keep the insertion order of test cases - this.requestContextMap = new HashMap<>(); + this.responseContextMap = java.util.Collections.synchronizedMap(new LinkedHashMap<>()); // keep insertion order + this.requestContextMap = new java.util.concurrent.ConcurrentHashMap<>();Note: When iterating responseContextMap directly elsewhere (not here), wrap in synchronized(responseContextMap) if needed. In process() you already copy values, so it’s fine.
♻️ Duplicate comments (12)
steve/src/main/java/de/rwth/idsg/steve/service/ChargePointHelperServiceImpl.java (2)
110-125: API now takes ZoneId — resolves previous DST concern.Switching getOcppJsonStatus(...) to accept ZoneId and threading it through is the right direction and addresses the earlier feedback about fixed offsets around DST.
203-204: DST correctness: confirm humanize accepts ZoneId (not a fixed ZoneOffset).Assuming DateTimeUtils.humanize(Instant, ZoneId) computes the per-instant offset via the zone’s rules, you’re good. If it still expects a ZoneOffset, compute it per event:
var offsetAtOpenSince = timeZone.getRules().getOffset(openSince); connectedSince(humanize(openSince, offsetAtOpenSince));If humanize already takes ZoneId, no action needed.
steve/src/test/java/de/rwth/idsg/steve/OperationalTestSoapOCPP16.java (3)
498-501: Fix Checkstyle: line exceeds 120 chars.Wrap the assertion to satisfy LineLengthCheck.
- assertThat(t.getStartTimestamp()).isCloseTo(startTimeStamp.toLocalDateTime(), byLessThan(1, ChronoUnit.SECONDS)); + assertThat(t.getStartTimestamp()) + .isCloseTo( + startTimeStamp.toLocalDateTime(), + byLessThan(1, ChronoUnit.SECONDS) + );
549-551: Fix Checkstyle: line exceeds 120 chars.Same wrapping for the stop timestamp assertion.
- assertThat(t.getStopTimestamp()).isCloseTo(stopTimeStamp.toLocalDateTime(), byLessThan(1, ChronoUnit.SECONDS)); + assertThat(t.getStopTimestamp()) + .isCloseTo( + stopTimeStamp.toLocalDateTime(), + byLessThan(1, ChronoUnit.SECONDS) + );
599-610: Bug in test: asserting empty list then indexing sampledValues.get(0).This will throw when the list is empty. The test intent is to ensure at least one SampledValue exists and is persisted.
Apply this refactor:
- var sampledValues = meterValue.getSampledValue(); - assertThat(sampledValues).isEmpty(); - var thisValueFound = false; - // and check, if it can be found in the DB - for (var values : details.getValues()) { - if (values.getValue().equals(sampledValues.get(0).getValue())) { - thisValueFound = true; - break; - } - } - assertThat(thisValueFound).isTrue(); + var sampledValues = meterValue.getSampledValue(); + assertThat(sampledValues).isNotEmpty(); + // Check that at least one sampled value made it to the DB + var thisValueFound = details.getValues().stream() + .anyMatch(values -> + sampledValues.stream().anyMatch(sv -> values.getValue().equals(sv.getValue())) + ); + assertThat(thisValueFound).isTrue();steve/src/test/java/de/rwth/idsg/steve/issues/Issue73Fix.java (2)
55-57: Config assertions may be brittle across environments (duplicate from prior review)As previously noted, asserting TEST profile and autoRegisterUnknownStations=true depends on properties. Ensure test resources set these explicitly (preferred) rather than relying on production defaults.
Consider adding src/test/resources/config/main.properties with:
profile = TEST auto.register.unknown.stations = true
104-108: Avoid potential NPE: assert idTagInfo is non-null before accessing status (duplicate)Guard against a null idTagInfo so failures are clearer.
Apply:
- var auth = client.authorize(new AuthorizeRequest().withIdTag(REGISTERED_OCPP_TAG), chargeBoxId); - assertThat(auth).isNotNull(); - assertThat(auth.getIdTagInfo().getStatus()).isEqualTo(expected); + var auth = client.authorize(new AuthorizeRequest().withIdTag(REGISTERED_OCPP_TAG), chargeBoxId); + assertThat(auth).isNotNull(); + assertThat(auth.getIdTagInfo()).isNotNull(); + assertThat(auth.getIdTagInfo().getStatus()).isEqualTo(expected);steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java (2)
90-92: Wrap long assertion lines and improve failure diagnosticsSeveral lines exceed Checkstyle’s 120-char limit and use
fail()with no message, making failures hard to triage.- bootResponse -> assertThat(bootResponse.getStatus()).isEqualTo(ocpp.cs._2010._08.RegistrationStatus.ACCEPTED), - error -> fail() + bootResponse -> assertThat(bootResponse.getStatus()) + .isEqualTo(ocpp.cs._2010._08.RegistrationStatus.ACCEPTED), + error -> fail("Unexpected error during OCPP 1.2 boot: " + error) @@ - authResponse -> assertThat(authResponse.getIdTagInfo().getStatus()).isEqualTo(ocpp.cs._2010._08.AuthorizationStatus.ACCEPTED), - error -> fail() + authResponse -> assertThat(authResponse.getIdTagInfo().getStatus()) + .isEqualTo(ocpp.cs._2010._08.AuthorizationStatus.ACCEPTED), + error -> fail("Unexpected error during OCPP 1.2 authorize: " + error) @@ - bootResponse -> assertThat(bootResponse.getStatus()).isEqualTo(ocpp.cs._2012._06.RegistrationStatus.ACCEPTED), - error -> fail() + bootResponse -> assertThat(bootResponse.getStatus()) + .isEqualTo(ocpp.cs._2012._06.RegistrationStatus.ACCEPTED), + error -> fail("Unexpected error during OCPP 1.5 boot: " + error) @@ - authResponse -> assertThat(authResponse.getIdTagInfo().getStatus()).isEqualTo(ocpp.cs._2012._06.AuthorizationStatus.ACCEPTED), - error -> fail() + authResponse -> assertThat(authResponse.getIdTagInfo().getStatus()) + .isEqualTo(ocpp.cs._2012._06.AuthorizationStatus.ACCEPTED), + error -> fail("Unexpected error during OCPP 1.5 authorize: " + error) @@ - bootResponse -> assertThat(bootResponse.getStatus()).isEqualTo(RegistrationStatus.ACCEPTED), - error -> fail() + bootResponse -> assertThat(bootResponse.getStatus()) + .isEqualTo(RegistrationStatus.ACCEPTED), + error -> fail("Unexpected error during OCPP 1.6 boot: " + error) @@ - authResponse -> assertThat(authResponse.getIdTagInfo().getStatus()).isEqualTo(AuthorizationStatus.ACCEPTED), - error -> fail() + authResponse -> assertThat(authResponse.getIdTagInfo().getStatus()) + .isEqualTo(AuthorizationStatus.ACCEPTED), + error -> fail("Unexpected error during OCPP 1.6 authorize: " + error)Also applies to: 97-99, 114-116, 121-123, 138-140, 145-147
156-163: Fix Checkstyle lambda issues: stray semicolon and brace placement
rootCause().satisfies(c -> {;triggers LeftCurly/Whitespace/EmptyStatement and harms readability.- then(thrown) - .isInstanceOf(RuntimeException.class) - .hasRootCauseInstanceOf(UpgradeException.class) - .rootCause().satisfies(c -> {; - var ue = (UpgradeException) c; - assertThat(ue.getResponseStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST.value()); - }); + then(thrown) + .isInstanceOf(RuntimeException.class) + .hasRootCauseInstanceOf(UpgradeException.class) + .rootCause() + .satisfies(c -> { + var ue = (UpgradeException) c; + assertThat(ue.getResponseStatusCode()) + .isEqualTo(HttpStatus.BAD_REQUEST.value()); + });Repeat the same brace/wrap pattern for Lines 171–175 and 185–188 blocks.
steve/src/test/java/de/rwth/idsg/steve/StressTestJsonOCPP16.java (1)
136-138: Wrap long assertion line to satisfy Checkstyle (≤120 chars)This line exceeds the 120-char limit.
- response -> assertThat(response.getIdTagInfo().getStatus()).isNotEqualTo(AuthorizationStatus.ACCEPTED), + response -> assertThat(response.getIdTagInfo().getStatus()) + .isNotEqualTo(AuthorizationStatus.ACCEPTED),steve/src/test/java/de/rwth/idsg/steve/issues/Issue72LowLevelSoap.java (2)
43-44: Invalid Content-Type construction; Guava lacks SOAP constant
MediaType.SOAP_XML_UTF_8doesn’t exist andMediaType#type()returns only the primary type, yielding an invalid MIME type. Use the literal SOAP MIME withStandardCharsets.UTF_8.+import java.nio.charset.StandardCharsets; @@ - var contentType = ContentType.create(MediaType.SOAP_XML_UTF_8.type(), MediaType.SOAP_XML_UTF_8.charset().orNull()); + var contentType = ContentType.create("application/soap+xml", StandardCharsets.UTF_8);Also applies to: 99-100
101-107: Do not shareHttpUriRequestacross threads; build per-iteration
HttpUriRequestand its entity are not thread-safe. Construct the request insidetoRepeat()just before execution.- var req = RequestBuilder.post(path) - .addHeader("SOAPAction", "urn://Ocpp/Cs/2015/10/StopTransaction") - .setEntity(new StringEntity(body, contentType)) - .build(); @@ - try { - httpClient.execute(req, httpResponse -> { + try { + var req = RequestBuilder.post(path) + .addHeader("SOAPAction", "urn://Ocpp/Cs/2015/10/StopTransaction") + .setEntity(new StringEntity(body, contentType)) + .build(); + httpClient.execute(req, httpResponse -> { if (httpResponse.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { throw new RuntimeException("Not OK"); } return null; }); } catch (Exception e) {Also applies to: 136-145
🧹 Nitpick comments (42)
steve-core/src/main/java/de/rwth/idsg/steve/web/dto/WebUserAuthority.java (1)
56-68: Harden null-handling and match authorities by exact set equality.
- Guard against null/blank JSON payloads and a null mapper to avoid NPE/ambiguous IllegalArgumentException sources.
- Use set equality instead of containsAll to ensure the JSON represents exactly one enum (duplicates and order are naturally ignored). This prevents accidental matches if a subset is provided.
Apply this diff:
- public static WebUserAuthority fromJsonValue(ObjectMapper mapper, JSON v) { - try { - List<String> values = Arrays.asList(mapper.readValue(v.data(), String[].class)); - for (WebUserAuthority c: WebUserAuthority.values()) { - if (c.values.containsAll(values)) { - return c; - } - } - throw new IllegalArgumentException(v.toString()); - } catch (JsonProcessingException e) { - throw new IllegalArgumentException(v.toString()); - } - } + public static WebUserAuthority fromJsonValue(ObjectMapper mapper, JSON v) { + if (mapper == null) { + throw new IllegalArgumentException("ObjectMapper must not be null"); + } + if (v == null || v.data() == null || v.data().isBlank()) { + throw new IllegalArgumentException("Authorities JSON must not be null or empty"); + } + try { + Set<String> parsed = new HashSet<>(Arrays.asList(mapper.readValue(v.data(), String[].class))); + for (WebUserAuthority c : WebUserAuthority.values()) { + if (c.values.equals(parsed)) { + return c; + } + } + throw new IllegalArgumentException(v.toString()); + } catch (JsonProcessingException e) { + throw new IllegalArgumentException(v.toString(), e); + } + }steve-core/src/main/java/de/rwth/idsg/steve/service/WebUsersService.java (2)
326-332: Handle null/blank JSON and surface clearer errors in fromJson(JSON).
Legacy rows or partial migrations can yield null JSON columns. Guarding avoids NPEs and clarifies failure modes. Returning an empty array on null may be acceptable given downstream validation; otherwise throw with a precise message.Apply one of the following:
Option A (lenient; returns empty array)
- private String[] fromJson(JSON jsonArray) { - try { - return mapper.readValue(jsonArray.data(), String[].class); + private String[] fromJson(JSON jsonArray) { + if (jsonArray == null || jsonArray.data() == null || jsonArray.data().isBlank()) { + return new String[0]; + } + try { + return mapper.readValue(jsonArray.data(), String[].class); } catch (JsonProcessingException e) { throw new RuntimeException(e); } }Option B (strict; throws early)
- private String[] fromJson(JSON jsonArray) { - try { - return mapper.readValue(jsonArray.data(), String[].class); + private String[] fromJson(JSON jsonArray) { + if (jsonArray == null || jsonArray.data() == null || jsonArray.data().isBlank()) { + throw new IllegalArgumentException("Authorities JSON must not be null or empty"); + } + try { + return mapper.readValue(jsonArray.data(), String[].class); } catch (JsonProcessingException e) { throw new RuntimeException(e); } }If you pick Option A, ensure callers accept empty authorities (current validateAuthorities permits empties).
341-346: Minor: JSON.json is sufficient; jsonOrNull is redundant here.
Since str is never null, JSON.jsonOrNull(str) behaves the same as JSON.json(str). This is purely stylistic.- String str = mapper.writeValueAsString(auths); - return JSON.jsonOrNull(str); + String str = mapper.writeValueAsString(auths); + return JSON.json(str);steve-ui-jsp/src/main/java/de/rwth/idsg/steve/web/controller/AjaxCallController.java (3)
91-99: Guard against null lists to avoid returning the JSON literal "null".If any service unexpectedly returns null, the current code would serialize it to "null" instead of "[]". Cheap defensive check keeps the UI contract stable.
Apply this diff:
- private String serializeArray(List<?> list) { - try { - return mapper.writeValueAsString(list); - } catch (JsonProcessingException e) { - // As fallback return empty array, do not let the frontend hang - log.error("Error occurred during serialization of response. Returning empty array instead!", e); - return "[]"; - } - } + private String serializeArray(List<?> list) { + if (list == null || list.isEmpty()) { + return "[]"; + } + try { + return mapper.writeValueAsString(list); + } catch (JsonProcessingException e) { + // As fallback return empty array, do not let the frontend hang + log.error("Error occurred during serialization of response. Returning empty array instead!", e); + return "[]"; + } + }
101-107: Doc comment is slightly outdated given the DI change.We still bypass HttpMessageConverters (manual write), but now rely on the app-wide ObjectMapper configuration. A tiny doc tweak avoids confusion for future readers.
Apply this diff:
- /** - * We want to handle this JSON conversion locally, and do not want to register an application-wide - * HttpMessageConverter just for this little class. Otherwise, it might have unwanted side effects due to - * different serialization/deserialization needs of different APIs. - * - * That's why we are directly accessing the low-level HttpServletResponse and manually writing to output. - */ + /** + * We deliberately bypass Spring's HttpMessageConverters and serialize manually to keep this controller isolated. + * The ObjectMapper is injected from the application context for consistency, but we still perform the write + * ourselves using HttpServletResponse to avoid any global MVC side effects. + */
56-56: Confirm global ObjectMapper bean and surface ad-hoc mappers
- No explicit
@Bean ObjectMapperdefinitions were found in production code—Spring Boot’s auto-configuration will supply the globalObjectMapper.- Two ad-hoc mappers still use
new ObjectMapper()outside tests:
GithubReleaseCheckService#createGitHubMapper()atsteve/src/main/java/de/rwth/idsg/steve/service/GithubReleaseCheckService.java:86JsonObjectMapper#createObjectMapper()atsteve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/JsonObjectMapper.java:46- The OCPP mapper applies
JsonInclude.Include.NON_NULL; ensure that matches any UI expectations for null‐omission.- If you need per-call tweaks without mutating the global bean, consider using
mapper.copy()in controllers to keep global configuration immutable.Next steps:
- Confirm no UI logic depends on the previous “omit nulls” behavior of a local mapper.
- Optionally refactor the two local factories to inject or base off the global bean (or use
mapper.copy()) for consistent configuration.steve/src/main/java/de/rwth/idsg/steve/service/GithubReleaseCheckService.java (3)
81-81: Fix Checkstyle: line length > 120Break the long constructor call into shorter statements.
- restTemplate = new RestTemplate(Collections.singletonList(new MappingJackson2HttpMessageConverter(githubMapper))); + var jacksonConverter = new MappingJackson2HttpMessageConverter(githubMapper); + restTemplate = new RestTemplate(Collections.singletonList(jacksonConverter));
85-90: Minor: prefer the constant for snake_case strategyUse the built-in constant to avoid creating a new instance.
- .setPropertyNamingStrategy(new PropertyNamingStrategies.SnakeCaseStrategy()); + .setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
82-83: Optional hardening for GitHub API: set default headersGitHub v3 API prefers a User-Agent and a specific Accept. Adding a lightweight interceptor reduces risk of 403s/rate-limit surprises.
Example (add after restTemplate initialization):
restTemplate.getInterceptors().add((request, body, execution) -> { request.getHeaders().add("User-Agent", "steve/" + config.getSteveVersion()); request.getHeaders().setAccept(java.util.List.of(org.springframework.http.MediaType.valueOf("application/vnd.github+json"))); return execution.execute(request, body); });steve-core/src/main/java/de/rwth/idsg/steve/web/dto/QueryForm.java (2)
55-60: Boundary semantics: consider allowing equal timestamps (optional).Current validation requires strictly to > from. If inclusive upper-bound is acceptable, switch to “not before” to accept equality.
- return !isFromToSet() || to.isAfter(from); + return !isFromToSet() || !to.isBefore(from);
66-74: Treat empty/blank strings as unset (optional).The “is...Set” helpers return true for empty strings. Prefer a not-blank check to avoid constructing queries with empty filters.
public boolean isChargeBoxIdSet() { - return chargeBoxId != null; + return chargeBoxId != null && !chargeBoxId.isBlank(); } @Schema(hidden = true) public boolean isOcppIdTagSet() { - return ocppIdTag != null; + return ocppIdTag != null && !ocppIdTag.isBlank(); }steve/src/main/java/de/rwth/idsg/steve/service/ChargePointHelperServiceImpl.java (3)
118-118: Prefer injectable Clock over Instant.now() for testability.Using an injected java.time.Clock makes this deterministic and easier to unit test.
Apply this local change:
- var now = Instant.now(); + var now = Instant.now(clock);Additional changes outside this hunk:
- Add a field and import (Lombok’s @requiredargsconstructor will wire it).
// imports import java.time.Clock; // field (near other finals) private final Clock clock;
- Ensure a Clock bean exists (e.g., in a @configuration):
@Bean public Clock clock() { return Clock.systemUTC(); }
168-171: Minor: speed up contains() by using a Set when a filter is provided.For large chargeBoxIdFilter lists, contains on List is O(n). Converting once to a Set reduces filter time to O(1) per element.
- List<String> chargeBoxIdList = CollectionUtils.isEmpty(chargeBoxIdFilter) - ? jsonEndpoint.getChargeBoxIdList() - : jsonEndpoint.getChargeBoxIdList().stream().filter(chargeBoxIdFilter::contains).toList(); + List<String> chargeBoxIdList = CollectionUtils.isEmpty(chargeBoxIdFilter) + ? jsonEndpoint.getChargeBoxIdList() + : jsonEndpoint.getChargeBoxIdList().stream() + .filter(new java.util.HashSet<>(chargeBoxIdFilter)::contains) + .collect(java.util.stream.Collectors.toList());
197-197: Defensive null handling for openSince.If any SessionContext can have a missing openSince (e.g., transient states), guard to avoid NPEs.
- var openSince = ctx.getOpenSince(); + var openSince = ctx.getOpenSince(); + if (openSince == null) { + continue; + }If the type system guarantees non-null, feel free to ignore.
steve-ui-jsp/src/main/java/de/rwth/idsg/steve/utils/WebDateTimeUtils.java (4)
37-39: Null-safe toMillis for JSP sorting is pragmatic.Returning 0 on null avoids NPEs in EL and produces deterministic ordering. If, in practice, you want nulls to sort last in ascending order, consider returning Long.MAX_VALUE instead (or expose a second helper).
Here’s an optional variant:
- public static long toMillis(Instant instant) { - return instant == null ? 0 : instant.toEpochMilli(); - } + public static long toMillis(Instant instant) { + return instant == null ? 0 : instant.toEpochMilli(); + } + // Optional: place nulls last when sorting ascending + public static long toMillisNullsLast(Instant instant) { + return instant == null ? Long.MAX_VALUE : instant.toEpochMilli(); + }
44-52: Name the header variable clearly and centralize the header key.Minor readability nit:
zoneId(String) can be confused with theZoneIdtype; considerzoneIdHeader. Also, promote the header name to a constant so callers/tests share a single source of truth. Optionally accept common variants (e.g.,Time-Zone,X-TimeZone) for resilience.public class WebDateTimeUtils { + public static final String TIMEZONE_HEADER = "X-Timezone"; @@ - String zoneId = request.getHeader("X-Timezone"); - if (!Strings.isNullOrEmpty(zoneId)) { + String zoneIdHeader = request.getHeader(TIMEZONE_HEADER); + if (!Strings.isNullOrEmpty(zoneIdHeader)) { try { - return ZoneId.of(zoneId); + return ZoneId.of(zoneIdHeader); } catch (Exception e) { - log.warn("Cannot parse ZoneId from request header: {}", zoneId, e); + log.warn("Cannot parse ZoneId from request header: {}", zoneIdHeader, e); } }Optionally support additional header aliases:
private static String firstNonEmptyHeader(NativeWebRequest request, String... names) { for (String n : names) { String v = request.getHeader(n); if (!Strings.isNullOrEmpty(v)) return v; } return null; }Usage:
- String zoneIdHeader = request.getHeader(TIMEZONE_HEADER); + String zoneIdHeader = firstNonEmptyHeader(request, TIMEZONE_HEADER, "X-TimeZone", "Time-Zone");
55-61: Spring fallback is correct; just a small guardrail suggestion.
RequestContextUtils.getTimeZone(servletRequest)can be present even if it was set to GMT/UTC by default resolvers; that’s fine. If you ever add logging for diagnostics, log which branch was chosen once per request (debug level) to help trace unexpected formatting.
41-65: Add focused unit tests for the three resolution branches.
- Valid X-Timezone header (e.g., Europe/Paris) → returns that ZoneId.
- Invalid header → falls back to Spring TimeZone if available.
- No header and no Spring TimeZone → returns system default.
I can provide test scaffolding using MockNativeWebRequest/MockHttpServletRequest if helpful.
steve/src/main/java/de/rwth/idsg/steve/config/BeanConfiguration.java (1)
42-42: Marking MVC ObjectMapper bean as primary is sensible; minor robustness tweak suggested.Making the MVC ObjectMapper the primary candidate avoids accidental injection of the OCPP-specific mapper. Good call. Consider throwing a more specific exception type for missing converter to aid diagnostics.
Apply this diff to use IllegalStateException:
- @Primary - @Bean - public ObjectMapper jacksonObjectMapper(RequestMappingHandlerAdapter requestMappingHandlerAdapter) { + @Primary + @Bean + public ObjectMapper jacksonObjectMapper(RequestMappingHandlerAdapter requestMappingHandlerAdapter) { return requestMappingHandlerAdapter.getMessageConverters().stream() .filter(converter -> converter instanceof MappingJackson2HttpMessageConverter) .findAny() .map(conv -> ((MappingJackson2HttpMessageConverter) conv).getObjectMapper()) - .orElseThrow(() -> new RuntimeException("There is no MappingJackson2HttpMessageConverter in Spring context")); + .orElseThrow(() -> new IllegalStateException("No MappingJackson2HttpMessageConverter found in Spring context")); }Also applies to: 200-208
steve/src/test/java/de/rwth/idsg/steve/OperationalTestSoapOCPP16.java (1)
457-457: Minor naming consistency: use ‘Id’ not ‘ID’.Local variable: prefer usedConnectorId for Java naming consistency.
- final var usedConnectorID = 1; + final var usedConnectorId = 1;Note: Update downstream usages in this method accordingly.
steve-ocpp-soap/src/main/java/de/rwth/idsg/steve/config/OcppSoapConfiguration.java (1)
92-93: Normalize router endpoint path to avoid leading-slash mismatches.If config.getPaths().getRouterEndpointPath() were provided without a leading “/”, CXF would publish at a different address than the mediator exclusion expects. Normalize here.
- createOcppService(ocpp12Server, config.getPaths().getRouterEndpointPath(), mediator, Collections.emptyList()); + var router = config.getPaths().getRouterEndpointPath(); + if (!router.startsWith("/")) { + router = "/" + router; + } + createOcppService(ocpp12Server, router, mediator, Collections.emptyList());Also verify that Helpers.getHttpPath builds the same URL shape expected by clients when this mapping changes.
steve-ocpp-soap/src/main/java/de/rwth/idsg/steve/ocpp/soap/MediatorInInterceptor.java (1)
126-127: Ensure address comparison matches CXF-published path exactly.The equality check assumes config.getPaths().getRouterEndpointPath() matches the JAX-WS service address verbatim (including leading slash). If configuration allows either form, consider normalizing both before comparison to avoid accidental inclusion of the router service in actualServers.
steve/src/main/java/de/rwth/idsg/steve/config/SecurityConfiguration.java (1)
65-65: Path migration to config.getPaths() looks correct; verify mapping formats.Good move consolidating mappings under config.getPaths(). Please verify at runtime that:
- getManagerMapping(), getSoapMapping(), and getApiMapping() all start with “/” and do not end with “/”, to ensure matchers like "/**" behave as intended.
- CSRF ignoring covers all SOAP POST endpoints after the mapping change.
Also applies to: 76-77, 113-114, 133-134
steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/pipeline/IncomingPipeline.java (1)
50-54: DRY the serializer → sender sequenceThe same two calls appear in both the error and CALL paths. A tiny helper improves readability and reduces duplication.
Apply this diff within the two call sites:
- serializer.accept(context); - sender.accept(context); + send(context);And add this helper inside the class (outside the selected ranges):
// helper to keep the hot path concise private void send(CommunicationContext context) { serializer.accept(context); sender.accept(context); }Also applies to: 61-63
steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/pipeline/OutgoingCallPipeline.java (1)
38-42: Inline start() to reduce indirection
start()returns its argument. Inlining keeps the constructor concise without losing clarity.- chainedConsumers = OutgoingCallPipeline.start(serializer) - .andThen(sender) - .andThen(saveInStore(store)); + chainedConsumers = serializer + .andThen(sender) + .andThen(saveInStore(store)); @@ - private static Consumer<CommunicationContext> start(Consumer<CommunicationContext> starter) { - return starter; - } + // start() not needed anymoreAlso applies to: 58-60
steve/src/test/java/de/rwth/idsg/steve/utils/Helpers.java (1)
59-66: Normalize path joins and include an error messageDirect string concatenation can yield double slashes if any segment already contains a leading/trailing
/. Also, throw a descriptive exception when neither HTTP nor HTTPS is enabled.- } else { - throw new RuntimeException(); + } else { + throw new RuntimeException("Neither HTTP nor HTTPS is enabled in test configuration"); } @@ - return prefix + config.getJetty().getServerHost() + ":" + port - + config.getPaths().getContextPath() - + config.getPaths().getSoapMapping() - + config.getPaths().getRouterEndpointPath(); + return prefix + config.getJetty().getServerHost() + ":" + port + + joinPaths(config.getPaths().getContextPath(), + config.getPaths().getSoapMapping(), + config.getPaths().getRouterEndpointPath()); @@ - } else { - throw new RuntimeException(); + } else { + throw new RuntimeException("Neither HTTP nor HTTPS is enabled in test configuration"); } @@ - return prefix + config.getJetty().getServerHost() + ":" + port - + config.getPaths().getContextPath() - + config.getPaths().getWebsocketMapping() - + config.getPaths().getRouterEndpointPath() + "/"; + return prefix + config.getJetty().getServerHost() + ":" + port + + joinPaths(config.getPaths().getContextPath(), + config.getPaths().getWebsocketMapping(), + config.getPaths().getRouterEndpointPath()) + + "/";Add this helper to the class (outside the selected ranges):
private static String joinPaths(String... parts) { StringBuilder sb = new StringBuilder(); for (String p : parts) { if (p == null || p.isEmpty()) continue; if (sb.length() == 0) { sb.append(p.startsWith("/") ? p : "/" + p); } else { if (sb.charAt(sb.length() - 1) != '/') sb.append('/'); sb.append(p.startsWith("/") ? p.substring(1) : p); } } return sb.toString(); }Also applies to: 79-86
steve/src/main/java/de/rwth/idsg/steve/SteveAppContext.java (1)
148-153: Nit: redundant path + "" entry in redirect setredirectSet.add(path + "") duplicates redirectSet.add(path) behavior. Safe but noisy.
Apply:
- redirectSet.add(path + ""); + redirectSet.add(path);steve/src/test/java/de/rwth/idsg/steve/utils/OcppJsonChargePoint.java (2)
334-341: Handle unknown messageId defensively to avoid NPEsIf a CALL_RESULT arrives for an unknown/expired messageId, responseContextMap.get(messageId) is null and .responseClass will NPE. Fail fast with an actionable message.
Apply:
- Class<ResponseType> clazz = responseContextMap.get(messageId).responseClass; - ResponseType res = ocppMapper.treeToValue(responsePayload, clazz); + ResponseContext ctx = responseContextMap.get(messageId); + if (ctx == null) { + throw new IllegalStateException("No ResponseContext found for messageId=" + messageId); + } + ResponseType res = ocppMapper.treeToValue(responsePayload, ctx.responseClass);
236-246: Close should tolerate missing session / client stateIf start() failed or the connection dropped early, session may be null. Similarly, client.stop() should only be called when started.
Apply:
- // "enqueue" a graceful close - session.close(StatusCode.NORMAL, "Finished", NOOP); + // "enqueue" a graceful close + if (session != null) { + session.close(StatusCode.NORMAL, "Finished", NOOP); + } @@ - client.stop(); + if (client.isStarted()) { + client.stop(); + }steve/src/test/java/de/rwth/idsg/steve/StressTestSoapOCPP16.java (1)
116-121: Avoid potential NPE: assert idTagInfo is non-null before accessing statusclient.authorize(...) may return a response with null idTagInfo; directly accessing getStatus() would NPE and hide the real issue.
Apply:
- var auth = client.authorize( + var auth = client.authorize( new AuthorizeRequest().withIdTag(idTag), chargeBoxId ); - assertThat(auth.getIdTagInfo().getStatus()).isNotEqualTo(AuthorizationStatus.ACCEPTED); + assertThat(auth.getIdTagInfo()).isNotNull(); + assertThat(auth.getIdTagInfo().getStatus()).isNotEqualTo(AuthorizationStatus.ACCEPTED);steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/config/OcppConfiguration.java (1)
49-49: Remove unnecessary space in EnumMap constructor.There's an extra space between
OcppVersionand.classin the EnumMap constructor.- var invocationContexts = new EnumMap<OcppVersion, InvocationContext>(OcppVersion .class); + var invocationContexts = new EnumMap<OcppVersion, InvocationContext>(OcppVersion.class);steve-core/src/main/java/de/rwth/idsg/steve/SteveConfiguration.java (1)
70-70: Consider keepingpostConstruct()package-private.Changing
postConstruct()from package-private to public increases the method's visibility unnecessarily. This validation method should only be called during configuration initialization.- public void postConstruct() { + void postConstruct() {steve/src/main/java/de/rwth/idsg/steve/utils/SteveConfigurationReader.java (1)
48-48: Make time zone configurable (fallback to UTC)Hard-coding
"UTC"reduces flexibility and makes tests/config drift-prone. Prefer reading from properties with a sane default.- .timeZoneId("UTC") + .timeZoneId(p.getOptionalString("time.zone.id").orElse("UTC"))steve/src/test/java/de/rwth/idsg/steve/StressTestJsonOCPP16.java (1)
98-100: Include diagnostics infail()lambdasReplacing bare
fail()with a message (and/or throwable) greatly improves triage under stress.Example:
- error -> fail() + error -> fail("Heartbeat failed: " + error)Apply similarly across other error callbacks in this file.
Also applies to: 116-118, 128-130, 137-138, 153-154, 166-168, 176-178, 188-189, 199-200
steve-core/src/main/java/de/rwth/idsg/steve/web/dto/EndpointInfo.java (2)
40-44: Wrap long ctor assignments to satisfy CheckstyleThe three lines exceed 120 chars.
- this.webInterface = new ItemsWithInfo("Access the web interface using", config.getPaths().getManagerMapping() + "/home"); - this.ocppSoap = new ItemsWithInfo("SOAP endpoint for OCPP", config.getPaths().getSoapMapping() + config.getPaths().getRouterEndpointPath()); - this.ocppWebSocket = new ItemsWithInfo("WebSocket/JSON endpoint for OCPP", config.getPaths().getWebsocketMapping() + config.getPaths().getRouterEndpointPath() + "/(chargeBoxId)"); + this.webInterface = new ItemsWithInfo( + "Access the web interface using", + config.getPaths().getManagerMapping() + "/home"); + this.ocppSoap = new ItemsWithInfo( + "SOAP endpoint for OCPP", + config.getPaths().getSoapMapping() + config.getPaths().getRouterEndpointPath()); + this.ocppWebSocket = new ItemsWithInfo( + "WebSocket/JSON endpoint for OCPP", + config.getPaths().getWebsocketMapping() + + config.getPaths().getRouterEndpointPath() + + "/(chargeBoxId)");
59-63: Stream pipeline is fine; confirm Java baseline supportstoList()
List.toList()requires Java 16+. If the project builds on 17 (likely), this is fine; otherwise, switch toCollectors.toList().steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/ocpp15/Ocpp15WebSocketEndpoint.java (2)
86-87: Align Lombok constructor access with OCPP 1.2 for consistencyOCPP 1.2 uses
@RequiredArgsConstructor(access = AccessLevel.PRIVATE). Apply the same here to minimize API surface of the inner handler. Requires importingAccessLevel.+import lombok.AccessLevel; import lombok.RequiredArgsConstructor; @@ - @RequiredArgsConstructor + @RequiredArgsConstructor(access = AccessLevel.PRIVATE) private static class Ocpp15CallHandler extends AbstractCallHandler {Also applies to: 33-33
126-126: Include offending request type in the exception for easier diagnosticsAdding the concrete class name speeds up incident triage.
- throw new IllegalArgumentException("Unexpected RequestType, dispatch method not found"); + throw new IllegalArgumentException( + "Unexpected RequestType, dispatch method not found: " + params.getClass().getName());steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/ocpp12/Ocpp12WebSocketEndpoint.java (1)
121-123: Improve exception message with actual request typeHelps observability when unknown messages arrive.
- throw new IllegalArgumentException("Unexpected RequestType, dispatch method not found"); + throw new IllegalArgumentException( + "Unexpected RequestType, dispatch method not found: " + params.getClass().getName());steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/ocpp16/Ocpp16WebSocketEndpoint.java (3)
65-66: Minor naming consistency: usesessionContextStorelike 1.2/1.5Purely stylistic; keeps constructor signatures uniform across endpoints.
86-87: Align Lombok constructor access with 1.2 for consistencyUse a private generated constructor on the inner handler to minimize API exposure.
+import lombok.AccessLevel; import lombok.RequiredArgsConstructor; @@ - @RequiredArgsConstructor + @RequiredArgsConstructor(access = AccessLevel.PRIVATE) private static class Ocpp16CallHandler extends AbstractCallHandler {Also applies to: 33-33
126-126: Improve exception message with request typeAdds context for operators and log analysis.
- throw new IllegalArgumentException("Unexpected RequestType, dispatch method not found"); + throw new IllegalArgumentException( + "Unexpected RequestType, dispatch method not found: " + params.getClass().getName());
| @Schema(description = "Show results that happened after this date/time. Format: ISO-8601 instant. Example: `2022-10-10T09:00:00Z`") | ||
| private Instant from; | ||
|
|
||
| @Schema(description = "Show results that happened before this date/time. Format: ISO8601 without timezone. Example: `2022-10-10T12:00:00`") | ||
| private LocalDateTime to; | ||
| @Schema(description = "Show results that happened before this date/time. Format: ISO-8601 instant. Example: `2022-10-10T12:00:00Z`") | ||
| private Instant to; |
There was a problem hiding this comment.
Fix Checkstyle failures by moving examples to a separate attribute.
Lines 49 and 52 exceed the 120-char limit. Split the description and use Schema.example to avoid long lines and improve the generated OpenAPI docs.
Apply this diff:
- @Schema(description = "Show results that happened after this date/time. Format: ISO-8601 instant. Example: `2022-10-10T09:00:00Z`")
+ @Schema(
+ description = "Show results that happened after this date/time. Format: ISO-8601 instant.",
+ example = "2022-10-10T09:00:00Z"
+ )
private Instant from;
- @Schema(description = "Show results that happened before this date/time. Format: ISO-8601 instant. Example: `2022-10-10T12:00:00Z`")
+ @Schema(
+ description = "Show results that happened before this date/time. Format: ISO-8601 instant.",
+ example = "2022-10-10T12:00:00Z"
+ )
private Instant to;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @Schema(description = "Show results that happened after this date/time. Format: ISO-8601 instant. Example: `2022-10-10T09:00:00Z`") | |
| private Instant from; | |
| @Schema(description = "Show results that happened before this date/time. Format: ISO8601 without timezone. Example: `2022-10-10T12:00:00`") | |
| private LocalDateTime to; | |
| @Schema(description = "Show results that happened before this date/time. Format: ISO-8601 instant. Example: `2022-10-10T12:00:00Z`") | |
| private Instant to; | |
| @Schema( | |
| description = "Show results that happened after this date/time. Format: ISO-8601 instant.", | |
| example = "2022-10-10T09:00:00Z" | |
| ) | |
| private Instant from; | |
| @Schema( | |
| description = "Show results that happened before this date/time. Format: ISO-8601 instant.", | |
| example = "2022-10-10T12:00:00Z" | |
| ) | |
| private Instant to; |
🧰 Tools
🪛 GitHub Check: checkstyle
[failure] 49-49: [checkstyle] steve-core/src/main/java/de/rwth/idsg/steve/web/dto/QueryForm.java#L49 <com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck>
Line is longer than 120 characters (found 135).
[failure] 52-52: [checkstyle] steve-core/src/main/java/de/rwth/idsg/steve/web/dto/QueryForm.java#L52 <com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck>
Line is longer than 120 characters (found 136).
🤖 Prompt for AI Agents
In steve-core/src/main/java/de/rwth/idsg/steve/web/dto/QueryForm.java around
lines 49-53, the @Schema description strings for the 'from' and 'to' Instant
fields exceed the 120-char checkstyle limit; split the long description into a
shorter descriptive text and move the ISO-8601 example into the @Schema(example
= "...") attribute for each field so the description lines are under 120 chars
and the OpenAPI docs still show the example.
💡 Verification agent
🧩 Analysis chain
Verify JSON and MVC binding for Instant.
Ensure that:
- Jackson has JavaTimeModule registered and WRITE_DATES_AS_TIMESTAMPS disabled (or equivalent Spring configuration).
- For form/query-parameter binding, Instant parsing is handled (Spring’s default conversion service or @DateTimeFormat if needed).
Run this to auto-check repo config and usages:
If query/form binding is involved and automatic conversion isn’t present, consider annotating:
// Optional, only if MVC binding requires it
import org.springframework.format.annotation.DateTimeFormat;
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
private Instant from;
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
private Instant to;🏁 Script executed:
#!/bin/bash
# Look for Jackson JavaTime configuration
rg -nP 'JavaTimeModule|WRITE_DATES_AS_TIMESTAMPS|jackson-datatype-jsr310|spring\.jackson\.' -C2
# Check for field-level formatting annotations that may need updating
rg -nP '@(JsonFormat|DateTimeFormat)\(' -C2
# Scan for custom ObjectMapper builders
rg -nP 'ObjectMapper|Jackson2ObjectMapperBuilder' -C3Length of output: 51417
Add missing @DateTimeFormat on Instant fields in QueryForm
JSON serialization is correctly configured – the application’s primary ObjectMapper is set up to register the JavaTimeModule and to disable WRITE_DATES_AS_TIMESTAMPS. However, Spring MVC does not include a built-in String → Instant converter for query parameters, so binding Instant from and Instant to directly from URL parameters will fail at runtime (no matching converter) (stackoverflow.com).
To fix this, annotate both fields in QueryForm.java to instruct Spring’s FormattingConversionService how to parse ISO-8601 instants:
• File: steve-core/src/main/java/de/rwth/idsg/steve/web/dto/QueryForm.java
Add import:
import org.springframework.format.annotation.DateTimeFormat;Then update the fields:
@Schema(description = "Show results that happened after this date/time. Format: ISO-8601 instant. Example: `2022-10-10T09:00:00Z`")
+@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
private Instant from;
@Schema(description = "Show results that happened before this date/time. Format: ISO-8601 instant. Example: `2022-10-10T12:00:00Z`")
+@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
private Instant to;This ensures query/form-parameter binding of Instant values without requiring a custom converter bean.
🧰 Tools
🪛 GitHub Check: checkstyle
[failure] 49-49: [checkstyle] steve-core/src/main/java/de/rwth/idsg/steve/web/dto/QueryForm.java#L49 <com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck>
Line is longer than 120 characters (found 135).
[failure] 52-52: [checkstyle] steve-core/src/main/java/de/rwth/idsg/steve/web/dto/QueryForm.java#L52 <com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck>
Line is longer than 120 characters (found 136).
🤖 Prompt for AI Agents
In steve-core/src/main/java/de/rwth/idsg/steve/web/dto/QueryForm.java around
lines 49-53, the Instant fields lack Spring binding metadata causing
query-parameter binding to fail; add the import
org.springframework.format.annotation.DateTimeFormat and annotate both fields
(from and to) with @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) so
Spring’s FormattingConversionService can parse ISO-8601 instant strings from
request parameters.
| import java.util.EnumMap; | ||
| import java.util.Map; | ||
|
|
||
| import static de.rwth.idsg.steve.ocpp.OcppVersion.*; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Avoid wildcard imports.
Using wildcard imports (*) reduces code clarity and can introduce naming conflicts. Import the specific constants explicitly.
Replace the wildcard import with explicit imports:
-import static de.rwth.idsg.steve.ocpp.OcppVersion.*;
+import static de.rwth.idsg.steve.ocpp.OcppVersion.V_12;
+import static de.rwth.idsg.steve.ocpp.OcppVersion.V_15;
+import static de.rwth.idsg.steve.ocpp.OcppVersion.V_16;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import static de.rwth.idsg.steve.ocpp.OcppVersion.*; | |
| import static de.rwth.idsg.steve.ocpp.OcppVersion.V_12; | |
| import static de.rwth.idsg.steve.ocpp.OcppVersion.V_15; | |
| import static de.rwth.idsg.steve.ocpp.OcppVersion.V_16; |
🧰 Tools
🪛 GitHub Check: checkstyle
[failure] 39-39: [checkstyle] steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/config/OcppConfiguration.java#L39 <com.puppycrawl.tools.checkstyle.checks.imports.AvoidStarImportCheck>
Using the '.' form of import should be avoided - de.rwth.idsg.steve.ocpp.OcppVersion..
🤖 Prompt for AI Agents
In
steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/config/OcppConfiguration.java
around line 39, the import uses a wildcard for
de.rwth.idsg.steve.ocpp.OcppVersion.*; replace this wildcard import with
explicit imports of the specific enum constants used in this file (e.g., OCPP12,
OCPP15, OCPP16 — or whichever constants are referenced), updating the import
statement to list each constant explicitly to improve clarity and avoid
potential naming conflicts.
| @UtilityClass | ||
| public class JsonObjectMapper { | ||
|
|
||
| private final ObjectMapper mapper; | ||
|
|
||
| JsonObjectMapper() { | ||
| mapper = new ObjectMapper(); | ||
| public static ObjectMapper createObjectMapper() { | ||
| var mapper = new ObjectMapper(); |
There was a problem hiding this comment.
Checkstyle failure: utility class must hide constructor (and Lombok @UtilityClass triggers the check)
The CI checkstyle error (HideUtilityClassConstructorCheck) occurs because, in source form, there’s no explicit private constructor; using @UtilityClass doesn’t satisfy the check since Checkstyle analyzes source, not Lombok-generated bytecode.
Replace @UtilityClass with a conventional final class + private constructor.
Apply:
-import lombok.experimental.UtilityClass;
@@
-@UtilityClass
-public class JsonObjectMapper {
+public final class JsonObjectMapper {
+
+ private JsonObjectMapper() {
+ throw new AssertionError("No instances");
+ }No behavior change; createObjectMapper remains static. Also remove the now-unused Lombok import:
-import lombok.experimental.UtilityClass;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @UtilityClass | |
| public class JsonObjectMapper { | |
| private final ObjectMapper mapper; | |
| JsonObjectMapper() { | |
| mapper = new ObjectMapper(); | |
| public static ObjectMapper createObjectMapper() { | |
| var mapper = new ObjectMapper(); | |
| package de.rwth.idsg.steve.ocpp.ws; | |
| import com.fasterxml.jackson.databind.ObjectMapper; | |
| // – import lombok.experimental.UtilityClass; | |
| public final class JsonObjectMapper { | |
| private JsonObjectMapper() { | |
| throw new AssertionError("No instances"); | |
| } | |
| public static ObjectMapper createObjectMapper() { | |
| var mapper = new ObjectMapper(); | |
| // … rest of method … | |
| } | |
| } |
🧰 Tools
🪛 GitHub Check: checkstyle
[failure] 42-42: [checkstyle] steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/JsonObjectMapper.java#L42 <com.puppycrawl.tools.checkstyle.checks.design.HideUtilityClassConstructorCheck>
Utility classes should not have a public or default constructor.
🤖 Prompt for AI Agents
In
steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/JsonObjectMapper.java
around lines 42-46, CI Checkstyle fails because Lombok's @UtilityClass doesn't
hide the constructor in source form; replace the Lombok annotation with a
conventional final class declaration and add an explicit private no-arg
constructor to prevent instantiation, keep createObjectMapper() static, and
remove the now-unused Lombok import.
| import de.rwth.idsg.steve.ocpp.ws.pipeline.AbstractCallHandler; | ||
| import de.rwth.idsg.steve.ocpp.ws.pipeline.Deserializer; | ||
| import de.rwth.idsg.steve.ocpp.ws.pipeline.IncomingPipeline; | ||
| import de.rwth.idsg.steve.ocpp.ws.pipeline.*; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Replace wildcard pipeline import to satisfy Checkstyle and improve clarity
Avoid .* import; list only the used types.
-import de.rwth.idsg.steve.ocpp.ws.pipeline.*;
+import de.rwth.idsg.steve.ocpp.ws.pipeline.AbstractCallHandler;
+import de.rwth.idsg.steve.ocpp.ws.pipeline.Deserializer;
+import de.rwth.idsg.steve.ocpp.ws.pipeline.IncomingPipeline;
+import de.rwth.idsg.steve.ocpp.ws.pipeline.Sender;
+import de.rwth.idsg.steve.ocpp.ws.pipeline.Serializer;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import de.rwth.idsg.steve.ocpp.ws.pipeline.*; | |
| import de.rwth.idsg.steve.ocpp.ws.pipeline.AbstractCallHandler; | |
| import de.rwth.idsg.steve.ocpp.ws.pipeline.Deserializer; | |
| import de.rwth.idsg.steve.ocpp.ws.pipeline.IncomingPipeline; | |
| import de.rwth.idsg.steve.ocpp.ws.pipeline.Sender; | |
| import de.rwth.idsg.steve.ocpp.ws.pipeline.Serializer; |
🧰 Tools
🪛 GitHub Check: checkstyle
[failure] 31-31: [checkstyle] steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/ocpp12/Ocpp12WebSocketEndpoint.java#L31 <com.puppycrawl.tools.checkstyle.checks.imports.AvoidStarImportCheck>
Using the '.' form of import should be avoided - de.rwth.idsg.steve.ocpp.ws.pipeline..
| import de.rwth.idsg.steve.ocpp.ws.pipeline.AbstractCallHandler; | ||
| import de.rwth.idsg.steve.ocpp.ws.pipeline.Deserializer; | ||
| import de.rwth.idsg.steve.ocpp.ws.pipeline.IncomingPipeline; | ||
| import de.rwth.idsg.steve.ocpp.ws.pipeline.*; |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Replace wildcard pipeline import to satisfy Checkstyle and improve clarity
Avoid .* import; list only the used types.
-import de.rwth.idsg.steve.ocpp.ws.pipeline.*;
+import de.rwth.idsg.steve.ocpp.ws.pipeline.AbstractCallHandler;
+import de.rwth.idsg.steve.ocpp.ws.pipeline.Deserializer;
+import de.rwth.idsg.steve.ocpp.ws.pipeline.IncomingPipeline;
+import de.rwth.idsg.steve.ocpp.ws.pipeline.Sender;
+import de.rwth.idsg.steve.ocpp.ws.pipeline.Serializer;Run to ensure no remaining wildcard imports from the pipeline package:
🏁 Script executed:
#!/bin/bash
rg -n 'import\s+de\.rwth\.idsg\.steve\.ocpp\.ws\.pipeline\.\*;' -C2Length of output: 2361
Replace wildcard pipeline imports across all Ocpp WebSocket endpoints
The import de.rwth.idsg.steve.ocpp.ws.pipeline.*; statement still exists in three endpoint classes. To satisfy Checkstyle and improve clarity, please replace it with explicit imports of only the used pipeline types.
• steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/ocpp12/Ocpp12WebSocketEndpoint.java
• steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/ocpp15/Ocpp15WebSocketEndpoint.java
• steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/ocpp16/Ocpp16WebSocketEndpoint.java
Diff example (apply identically in each file):
-import de.rwth.idsg.steve.ocpp.ws.pipeline.*;
+import de.rwth.idsg.steve.ocpp.ws.pipeline.AbstractCallHandler;
+import de.rwth.idsg.steve.ocpp.ws.pipeline.Deserializer;
+import de.rwth.idsg.steve.ocpp.ws.pipeline.IncomingPipeline;
+import de.rwth.idsg.steve.ocpp.ws.pipeline.Sender;
+import de.rwth.idsg.steve.ocpp.ws.pipeline.Serializer;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import de.rwth.idsg.steve.ocpp.ws.pipeline.*; | |
| import de.rwth.idsg.steve.ocpp.ws.pipeline.AbstractCallHandler; | |
| import de.rwth.idsg.steve.ocpp.ws.pipeline.Deserializer; | |
| import de.rwth.idsg.steve.ocpp.ws.pipeline.IncomingPipeline; | |
| import de.rwth.idsg.steve.ocpp.ws.pipeline.Sender; | |
| import de.rwth.idsg.steve.ocpp.ws.pipeline.Serializer; |
🧰 Tools
🪛 GitHub Check: checkstyle
[failure] 31-31: [checkstyle] steve-ocpp-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/ocpp15/Ocpp15WebSocketEndpoint.java#L31 <com.puppycrawl.tools.checkstyle.checks.imports.AvoidStarImportCheck>
Using the '.' form of import should be avoided - de.rwth.idsg.steve.ocpp.ws.pipeline..
| .flatMap(ips -> ips.entrySet().stream()) | ||
| .toList(); | ||
| setList(list, isSecured -> isSecured ? "http" : "https", info.getWebInterface(), info.getWebInterface()); | ||
| setList(list, isSecured -> isSecured ? "ws" : "wss", info.getOcppWebSocket()); |
There was a problem hiding this comment.
Fix incorrect ternary operator logic in WebSocket protocol prefix.
Similar to the HTTP mapping, the WebSocket protocol mapping is inverted.
- setList(list, isSecured -> isSecured ? "ws" : "wss", info.getOcppWebSocket());
+ setList(list, isSecured -> isSecured ? "wss" : "ws", info.getOcppWebSocket());📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| setList(list, isSecured -> isSecured ? "ws" : "wss", info.getOcppWebSocket()); | |
| setList(list, isSecured -> isSecured ? "wss" : "ws", info.getOcppWebSocket()); |
🤖 Prompt for AI Agents
In steve/src/main/java/de/rwth/idsg/steve/JettyServer.java around line 200, the
ternary used to choose the WebSocket protocol prefix is inverted; change the
lambda from isSecured -> isSecured ? "ws" : "wss" to isSecured -> isSecured ?
"wss" : "ws" so secured connections use "wss" and unsecured use "ws".
| import java.util.Set; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| import static de.rwth.idsg.steve.utils.DateTimeUtils.*; |
There was a problem hiding this comment.
Fix Checkstyle failure: avoid static star import.
The pipeline flags AvoidStarImport on DateTimeUtils.*. Replace with explicit static imports for the used methods.
Apply this diff:
-import static de.rwth.idsg.steve.utils.DateTimeUtils.*;
+import static de.rwth.idsg.steve.utils.DateTimeUtils.humanize;
+import static de.rwth.idsg.steve.utils.DateTimeUtils.timeElapsed;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import static de.rwth.idsg.steve.utils.DateTimeUtils.*; | |
| import static de.rwth.idsg.steve.utils.DateTimeUtils.humanize; | |
| import static de.rwth.idsg.steve.utils.DateTimeUtils.timeElapsed; |
🧰 Tools
🪛 GitHub Check: checkstyle
[failure] 55-55: [checkstyle] steve/src/main/java/de/rwth/idsg/steve/service/ChargePointHelperServiceImpl.java#L55 <com.puppycrawl.tools.checkstyle.checks.imports.AvoidStarImportCheck>
Using the '.' form of import should be avoided - de.rwth.idsg.steve.utils.DateTimeUtils..
🤖 Prompt for AI Agents
In
steve/src/main/java/de/rwth/idsg/steve/service/ChargePointHelperServiceImpl.java
around line 55, replace the static star import "import static
de.rwth.idsg.steve.utils.DateTimeUtils.*" with explicit static imports: remove
the wildcard import and add one static import line for each DateTimeUtils method
actually referenced in this class (e.g., import static
de.rwth.idsg.steve.utils.DateTimeUtils.methodName;), updating imports to list
only the used static methods to satisfy the AvoidStarImport checkstyle rule.
| import com.fasterxml.jackson.databind.ObjectMapper; | ||
| import de.rwth.idsg.steve.ocpp.OcppVersion; | ||
| import de.rwth.idsg.steve.ocpp.ws.JsonObjectMapper; | ||
| import de.rwth.idsg.steve.utils.OcppJsonChargePoint; | ||
| import de.rwth.idsg.steve.utils.SteveConfigurationReader; | ||
| import de.rwth.idsg.steve.utils.__DatabasePreparer__; |
There was a problem hiding this comment.
Missing import for ApplicationProfile
ApplicationProfile.TEST is used on Line 65 but not imported, causing a compile error.
import de.rwth.idsg.steve.utils.OcppJsonChargePoint;
import de.rwth.idsg.steve.utils.SteveConfigurationReader;
+import de.rwth.idsg.steve.ApplicationProfile;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import com.fasterxml.jackson.databind.ObjectMapper; | |
| import de.rwth.idsg.steve.ocpp.OcppVersion; | |
| import de.rwth.idsg.steve.ocpp.ws.JsonObjectMapper; | |
| import de.rwth.idsg.steve.utils.OcppJsonChargePoint; | |
| import de.rwth.idsg.steve.utils.SteveConfigurationReader; | |
| import de.rwth.idsg.steve.utils.__DatabasePreparer__; | |
| import com.fasterxml.jackson.databind.ObjectMapper; | |
| import de.rwth.idsg.steve.ocpp.OcppVersion; | |
| import de.rwth.idsg.steve.ocpp.ws.JsonObjectMapper; | |
| import de.rwth.idsg.steve.utils.OcppJsonChargePoint; | |
| import de.rwth.idsg.steve.utils.SteveConfigurationReader; | |
| import de.rwth.idsg.steve.ApplicationProfile; | |
| import de.rwth.idsg.steve.utils.__DatabasePreparer__; |
🤖 Prompt for AI Agents
In steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java around lines
21 to 26, the test uses ApplicationProfile.TEST later but no import is present,
causing a compile error; add the missing import line for ApplicationProfile
(e.g. import de.rwth.idsg.steve.ApplicationProfile;) at the top with the other
imports so the enum is available to the test.
| import org.eclipse.jetty.websocket.core.exception.UpgradeException; | ||
| import org.junit.jupiter.api.AfterAll; |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Jetty UpgradeException import requires matching test dependency
The test imports org.eclipse.jetty.websocket.core.exception.UpgradeException but this module may not have a Jetty dependency declared, leading to compilation failures.
Run to verify Jetty dependencies and import consistency:
If missing, add to the relevant test module POM:
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>jetty-websocket-core</artifactId>
<version>12.0.0</version>
<scope>test</scope>
</dependency>Alternatively, replace the dependency on a Jetty-specific exception with an abstraction your code already depends on.
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Jetty deps in POMs:"
rg -nP '<groupId>org\.eclipse\.jetty' --glob '**/pom.xml' -n -C2 || true
echo
echo "Looking for websocket-core artifact:"
rg -nP '<artifactId>jetty-websocket-core</artifactId>' --glob '**/pom.xml' -n -C2 || trueLength of output: 3172
Add missing Jetty WebSocket Core test dependency
Your ApplicationJsonTest imports
org.eclipse.jetty.websocket.core.exception.UpgradeException
but there is no jetty-websocket-core on the test classpath. This will lead to compilation errors.
• In steve/pom.xml, under the <dependencies> section, add:
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>jetty-websocket-core</artifactId>
<version>${jetty.version}</version>
<scope>test</scope>
</dependency>• Verify that ${jetty.version} matches the other Jetty artifacts in this pom.
With this in place, the UpgradeException class will be available to your tests.
🤖 Prompt for AI Agents
In steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java around lines
35-36, the test imports
org.eclipse.jetty.websocket.core.exception.UpgradeException but
jetty-websocket-core is not on the test classpath; add a test-scoped dependency
for org.eclipse.jetty.websocket:jetty-websocket-core in steve/pom.xml under
<dependencies> and set its version to the same ${jetty.version} used by other
Jetty artifacts in this pom so the UpgradeException class is available to the
tests.
| private static final String PATH = "ws://localhost:8080/steve/websocket/CentralSystemService/"; | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
Avoid hard-coded WebSocket path; derive from configuration
The constant PATH hard-codes /steve/websocket/CentralSystemService/, which can drift from SteveConfiguration.Paths. Derive it from config (consistent with StressTestJsonOCPP16).
- private static final String PATH = "ws://localhost:8080/steve/websocket/CentralSystemService/";
+ private static String PATH;
@@
public static void init() throws Exception {
var config = SteveConfigurationReader.readSteveConfiguration("main.properties");
assertThat(config.getProfile()).isEqualTo(ApplicationProfile.TEST);
__DatabasePreparer__.prepare(config);
app = new Application(config);
+ PATH = de.rwth.idsg.steve.utils.Helpers.getWsPath(config);
app.start();
}Optionally add a static import to mirror the style in other tests:
+import static de.rwth.idsg.steve.utils.Helpers.getWsPath;
@@
- PATH = de.rwth.idsg.steve.utils.Helpers.getWsPath(config);
+ PATH = getWsPath(config);Also applies to: 62-70
🤖 Prompt for AI Agents
In steve/src/test/java/de/rwth/idsg/steve/ApplicationJsonTest.java around lines
55-56 (and similarly 62-70), replace the hard-coded PATH constant
"/steve/websocket/CentralSystemService/" with a value derived from the test
configuration (use the same approach as StressTestJsonOCPP16) by reading the
path from the SteveConfiguration/Paths instance used in tests; optionally add a
static import to match other tests’ style, and update any usages to build the
websocket URL from the config-derived base path instead of the literal string.
Rebase of #1
Summary by CodeRabbit
New Features
Refactor
Tests
Chores