Skip to content

Commit da6496b

Browse files
Rylan-cgiclaude
andcommitted
fix(schedule10): resolve SonarCloud findings on PR #305
All three gate failures fixed, plus the warnings worth acting on. TRANSACTIONAL SELF-INVOCATION (6 failures). Each of the seven write methods and checkStatus called getSchedule10 via this, and a this-call never passes through the Spring proxy -- so that method's @transactional was silently ignored and its reads simply joined the caller's transaction. The behaviour was correct; the annotation was not what made it so. The assembly body is extracted into a private, un-annotated assembleDocument(...) that every entry point calls, so the transaction is opened by the entry point and nothing self-invokes. The class javadoc previously admitted this subtlety in prose; the code now states it. DUPLICATED LITERALS (2 failures). "percentageConverterErrorMsg" appeared five times across the new Schedule 10 converter entries and is now the constant PERCENTAGE_CONVERTER. "999.9" appeared three times as the upper bound of the one-decimal width and distance rules and is now WIDTH_MAX. MONSTER CLASS (warning, 21 dependencies against a limit of 20). The two exception types added by the code review pushed the count over. Rather than suppress it, the single-argument persist(Runnable) overload turned out to be dead: once every one of the seven call sites carried a resource-specific message key, nothing reached the generic form. Removing it deletes dead code and drops the ScheduleNotSavedException dependency back to 20. COMMENTED-OUT CODE (warning). This was the deliberate 52B demotion from the code review -- a commented-out case label kept as a record. Reworded as prose so the reasoning survives without a commented-out statement. WEAK ASSERTION (warning). assertThat(keysOf(issues)).contains( "missingRequiredFieldMsg") was satisfied by any of the eight issues in that outcome, so it never bound the key to the Material Code Type field. The code review had flagged the same line independently. It now asserts the key on ballastMaterialCode specifically and pins the exact ordered set with containsExactly. The keysOf helper became unused and is removed. ASSERTION STYLE (warnings). 24 assertThat(map.get(k)).isEqualTo(v) forms rewritten as assertThat(map).containsEntry(k, v). Multi-line forms and those carrying an .as(...) description were left alone deliberately. Also rebased onto main, which had moved twice: the second move bumped checkstyle from 13.9.0 to 13.10.0 and touched backend/. That exposed a hole in the local verification -- the checkstyle gate had been run with -o (offline), so after the version bump it failed to resolve the plugin and never actually ran. Re-run online against 13.10.0: 0 violations in schedule10 (1235 pre-existing elsewhere, up from 1234 through main's own new code). Verification: 1150 unit + 879 integration tests, 0 failures. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent ba6c89a commit da6496b

8 files changed

Lines changed: 94 additions & 76 deletions

File tree

backend/src/main/java/ca/bc/gov/nrs/ilcr/exception/GlobalExceptionHandler.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,9 @@ private static String converterKeyForField(String causeMessage) {
320320
* collection
321321
* hops ({@code CulvertSaveAllRequest["culverts"]->…->CulvertRequest["spanSize"]}).
322322
*/
323+
/** Shared by all five Schedule 10 material percentages, which fail identically. */
324+
private static final String PERCENTAGE_CONVERTER = "percentageConverterErrorMsg";
325+
323326
private static final Map<String, String> CONVERTER_KEYS_BY_TARGET = Map.ofEntries(
324327
Map.entry("CulvertRequest[\"spanSize\"]", "culvertSpanConverterErrorMsg"),
325328
Map.entry("CulvertRequest[\"riseSize\"]", "culvertRiseConverterErrorMsg"),
@@ -330,11 +333,11 @@ private static String converterKeyForField(String causeMessage) {
330333
Map.entry("RoadDetailRequest[\"sideSlopePct\"]", "sideSlopePercentageConverterErrorMsg"),
331334
Map.entry("RoadDetailRequest[\"endHaulVolume\"]", "volumeConverterErrorMsg"),
332335
Map.entry("RoadDetailRequest[\"overlandVolume\"]", "volumeConverterErrorMsg"),
333-
Map.entry("MaterialCompositionRequest[\"solidRockPct\"]", "percentageConverterErrorMsg"),
334-
Map.entry("MaterialCompositionRequest[\"rippableRockPct\"]", "percentageConverterErrorMsg"),
335-
Map.entry("MaterialCompositionRequest[\"coarsePct\"]", "percentageConverterErrorMsg"),
336-
Map.entry("MaterialCompositionRequest[\"finePct\"]", "percentageConverterErrorMsg"),
337-
Map.entry("MaterialCompositionRequest[\"organicPct\"]", "percentageConverterErrorMsg"));
336+
Map.entry("MaterialCompositionRequest[\"solidRockPct\"]", PERCENTAGE_CONVERTER),
337+
Map.entry("MaterialCompositionRequest[\"rippableRockPct\"]", PERCENTAGE_CONVERTER),
338+
Map.entry("MaterialCompositionRequest[\"coarsePct\"]", PERCENTAGE_CONVERTER),
339+
Map.entry("MaterialCompositionRequest[\"finePct\"]", PERCENTAGE_CONVERTER),
340+
Map.entry("MaterialCompositionRequest[\"organicPct\"]", PERCENTAGE_CONVERTER));
338341

339342
/**
340343
* Handles authorization denials from method security ({@code @PreAuthorize}). Without this

backend/src/main/java/ca/bc/gov/nrs/ilcr/schedule10/RoadGroup10Lookup.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -369,11 +369,11 @@ private static String rg10ByTflNumberCode(String tflNumberCode) {
369369
case "03", "23", "33", "55", "56":
370370
roadGroup = "11";
371371
break;
372-
// "52B" is legacy-live but unreachable, and is demoted to a comment here for the reason
373-
// schedule6.RoadGroupLookup already records: TFL_NUMBER_CODE is VARCHAR2(2) on both sides,
374-
// and ConstructionPageRequest.tflNumberCode carries @Size(max = 2), so a 3-character TFL
375-
// never reaches this switch on read or on save. As a live case it read as an accepted value
376-
// that the request contract rejects (code review 2026-08-18). case "52B": roadGroup = "5";
372+
// Legacy maps the three-character TFL "52B" to this same road group. It is deliberately NOT
373+
// reproduced: TFL_NUMBER_CODE is VARCHAR2(2) on both sides and the request constrains the
374+
// field to two characters, so such a value cannot reach this switch on read or on save.
375+
// Including it would read as an accepted value the request contract rejects.
376+
// schedule6.RoadGroupLookup dropped it earlier for the same reason (code review 2026-08-18).
377377
case "05":
378378
roadGroup = "5";
379379
break;

backend/src/main/java/ca/bc/gov/nrs/ilcr/schedule10/Schedule10CheckStatus.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ final class Schedule10CheckStatus {
9191
private static final String FMT_MONEY_NARROW = "###,###";
9292

9393
private static final BigDecimal PCT_MAX = new BigDecimal("100");
94+
/** Shared upper bound for the three one-decimal width/distance rules. */
95+
private static final BigDecimal WIDTH_MAX = new BigDecimal("999.9");
9496
private static final BigDecimal SEVEN_DIGITS = new BigDecimal("9999999");
9597
private static final BigDecimal EIGHT_DIGITS = new BigDecimal("99999999");
9698
private static final BigDecimal ZERO = BigDecimal.ZERO;
@@ -216,7 +218,7 @@ static DetailOutcome evaluateRoadDetail(
216218
requireRange(issues, "subGradeLength", prefix + " Sub-Grade: Length (km)",
217219
field(subGrade, SubGrade::length), ZERO, FMT_3DP, PCT_MAX, false);
218220
requireRange(issues, "subGradeSurfaceWidth", prefix + " Sub-Grade: Surface Width (m)",
219-
field(subGrade, SubGrade::surfaceWidth), ZERO, FMT_1DP, new BigDecimal("999.9"), false);
221+
field(subGrade, SubGrade::surfaceWidth), ZERO, FMT_1DP, WIDTH_MAX, false);
220222
requireRange(issues, "subGradeActualCost", prefix + " Sub-Grade: Actual Cost ($)",
221223
field(subGrade, SubGrade::actualCost), ZERO, FMT_MONEY, SEVEN_DIGITS, false);
222224
requireRange(issues, "subGradeTtTransfer", prefix + " Sub-Grade: TtT Transfer ($)",
@@ -255,14 +257,14 @@ static DetailOutcome evaluateRoadDetail(
255257
field(stabilizing, Stabilizing::length), ZERO, FMT_3DP, new BigDecimal("999.999"), crushed);
256258
requireRange(issues, "stabilizingSurfaceWidth",
257259
prefix + " Additional Stabilizing: Surface Width (m)",
258-
field(stabilizing, Stabilizing::surfaceWidth), ZERO, FMT_1DP, new BigDecimal("999.9"),
260+
field(stabilizing, Stabilizing::surfaceWidth), ZERO, FMT_1DP, WIDTH_MAX,
259261
crushed);
260262
requireRange(issues, "stabilizingDepth", prefix + " Additional Stabilizing: Depth (m)",
261263
field(stabilizing, Stabilizing::depth), ZERO, FMT_2DP_SMALL, new BigDecimal("99.9"),
262264
crushed);
263265
requireRange(issues, "stabilizingDistanceToSource",
264266
prefix + " Additional Stabilizing: Distance to Source (km)",
265-
field(stabilizing, Stabilizing::distanceToSource), ZERO, FMT_1DP, new BigDecimal("999.9"),
267+
field(stabilizing, Stabilizing::distanceToSource), ZERO, FMT_1DP, WIDTH_MAX,
266268
crushed);
267269

268270
if (!crushed) {

backend/src/main/java/ca/bc/gov/nrs/ilcr/schedule10/Schedule10Service.java

Lines changed: 43 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
import ca.bc.gov.nrs.ilcr.dto.base.CodeDescriptionDto;
99
import ca.bc.gov.nrs.ilcr.schedule1.ScheduleNotEditableException;
10-
import ca.bc.gov.nrs.ilcr.schedule1.ScheduleNotSavedException;
1110
import ca.bc.gov.nrs.ilcr.schedule1.StaleRevisionException;
1211
import ca.bc.gov.nrs.ilcr.schedule10.Schedule10Repository.BecClassificationRow;
1312
import ca.bc.gov.nrs.ilcr.schedule10.Schedule10Repository.CodeRow;
@@ -50,14 +49,17 @@
5049
* lookup, two BEC queries and five code-list queries; the three-query property is about the nested
5150
* body, not the request as a whole.
5251
*
53-
* <p>{@link #getSchedule10} runs in one read-only transaction so those queries observe a single
54-
* consistent snapshot. Without it a concurrent write between the page and detail reads yields a
55-
* silently torn document — details belonging to a page that is not in the result are dropped with
56-
* no error. Note the scope of that guarantee: the seven write methods call {@code getSchedule10} to
57-
* build their response, and Spring's default {@code REQUIRED} propagation makes it JOIN the
58-
* caller's read-write transaction, so {@code readOnly} does not apply on those paths. They still
59-
* get a single consistent snapshot — their own transaction — but calling the read read-only there
60-
* would be wrong to claim (corrected at code review 2026-08-18).
52+
* <p>Assembly always runs inside a transaction so those queries observe a single consistent
53+
* snapshot. Without one, a concurrent write between the page and detail reads yields a silently
54+
* torn document — details belonging to a page not in the result are dropped with no error.
55+
*
56+
* <p>The transaction is opened by the ENTRY POINT, not by the assembly: {@link #getSchedule10} and
57+
* {@link #checkStatus} open a read-only one, and each write method opens a read-write one, then all
58+
* of them call the un-annotated {@code assembleDocument}. An earlier revision had the write methods
59+
* call {@code getSchedule10} directly, which never passes through the Spring proxy — so its
60+
* {@code @Transactional} was silently ignored and the reads simply joined the caller's transaction.
61+
* The behaviour was correct; the annotation just was not what made it so. Restructured at code
62+
* review follow-up 2026-08-18 so the code states it rather than depending on a proxy subtlety.
6163
*
6264
* <p>Derivation rules that matter:
6365
* <ul>
@@ -140,6 +142,25 @@ public Schedule10Service(Schedule10Repository repository) {
140142
*/
141143
@Transactional(readOnly = true)
142144
public Schedule10Response getSchedule10(long millId, int year, boolean callerMayEdit) {
145+
return assembleDocument(millId, year, callerMayEdit);
146+
}
147+
148+
/**
149+
* Assembles the document with NO transaction annotation of its own.
150+
*
151+
* <p>This exists so the seven write methods and {@link #checkStatus} can build their response
152+
* without self-invoking {@link #getSchedule10}. A {@code this.}-call never passes through the
153+
* Spring proxy, so the inner {@code @Transactional} was silently ignored — the reads simply
154+
* joined the caller's transaction, which is correct behaviour but not what the annotation
155+
* appeared to promise. Extracting the body states that directly rather than relying on a
156+
* proxy subtlety, and
157+
* removes the self-invocation Sonar flags (code review follow-up 2026-08-18).
158+
*
159+
* <p>Every caller is already inside a transaction: {@code getSchedule10} and {@code checkStatus}
160+
* open a read-only one, and each write method opens a read-write one. Nothing calls this
161+
* unwrapped, so the single-snapshot guarantee is unchanged.
162+
*/
163+
private Schedule10Response assembleDocument(long millId, int year, boolean callerMayEdit) {
143164
List<RoadConstructionReportEntity> pageRows = repository.findPages(millId, year);
144165
List<RoadConstructionReportDetailEntity> detailRows = repository.findRoadDetails(millId, year);
145166
List<CostLineRow> costRows = repository.findCostLines(millId, year);
@@ -513,7 +534,7 @@ public Schedule10Response addPage(
513534
int pageId = repository.nextPageId();
514535
persist(() -> repository.insertPage(
515536
toPageEntity(pageId, millId, year, request, location), millId, year, user), PAGE_NOT_SAVED);
516-
return getSchedule10(millId, year, callerMayEdit);
537+
return assembleDocument(millId, year, callerMayEdit);
517538
}
518539

519540
/**
@@ -549,7 +570,7 @@ public Schedule10Response updatePage(
549570
throw new StaleRevisionException();
550571
}
551572
}, PAGE_NOT_SAVED);
552-
return getSchedule10(millId, year, callerMayEdit);
573+
return assembleDocument(millId, year, callerMayEdit);
553574
}
554575

555576
/**
@@ -581,7 +602,7 @@ public Schedule10Response copyPage(
581602
source.constructionDivisionName(), source.ilcrForestRegionCode(), source.tsbNumberCode(),
582603
source.tsaNumber(), source.tflNumberCode(), 0);
583604
persist(() -> repository.insertPage(copy, millId, year, user), PAGE_NOT_SAVED);
584-
return getSchedule10(millId, year, callerMayEdit);
605+
return assembleDocument(millId, year, callerMayEdit);
585606
}
586607

587608
/**
@@ -611,7 +632,7 @@ public Schedule10Response deletePage(
611632
throw new ConstructionPageNotFoundException();
612633
}
613634
}, PAGE_NOT_DELETED);
614-
return getSchedule10(millId, year, callerMayEdit);
635+
return assembleDocument(millId, year, callerMayEdit);
615636
}
616637

617638
/**
@@ -642,7 +663,7 @@ public Schedule10Response addRoadDetail(
642663
moisture.asmCode(), user);
643664
writeCostLines(roadDetailId, coupled, user, millId, year);
644665
}, DETAIL_NOT_SAVED);
645-
return getSchedule10(millId, year, callerMayEdit);
666+
return assembleDocument(millId, year, callerMayEdit);
646667
}
647668

648669
/**
@@ -681,7 +702,7 @@ public Schedule10Response updateRoadDetail(
681702
}
682703
writeCostLines(roadDetailId, coupled, user, millId, year);
683704
}, DETAIL_NOT_SAVED);
684-
return getSchedule10(millId, year, callerMayEdit);
705+
return assembleDocument(millId, year, callerMayEdit);
685706
}
686707

687708
/**
@@ -705,7 +726,7 @@ public Schedule10Response deleteRoadDetail(
705726
throw new RoadDetailNotFoundException();
706727
}
707728
}, DETAIL_NOT_DELETED);
708-
return getSchedule10(millId, year, callerMayEdit);
729+
return assembleDocument(millId, year, callerMayEdit);
709730
}
710731

711732
/**
@@ -726,7 +747,7 @@ public Schedule10Response deleteRoadDetail(
726747
public Schedule10CheckStatus.Outcome checkStatus(long millId, int year) {
727748
// callerMayEdit is irrelevant to the rules, and passing false keeps this read from implying any
728749
// edit authority in the document it evaluates.
729-
return Schedule10CheckStatus.evaluate(getSchedule10(millId, year, false));
750+
return Schedule10CheckStatus.evaluate(assembleDocument(millId, year, false));
730751
}
731752

732753
// -----------------------------------------------------------------------------------------------
@@ -1079,26 +1100,15 @@ private void cost(
10791100
}
10801101

10811102
/**
1082-
* Runs a write, translating a data-access failure into the house save error.
1103+
* Runs a write, translating a data-access failure into the resource-specific legacy save error.
10831104
*
10841105
* <p>Only the exception TYPE is logged. Never the values: a rejected write would otherwise put
10851106
* cost, volume or comment content into the log, which the data-sensitivity rules forbid.
1086-
*/
1087-
private void persist(Runnable write) {
1088-
try {
1089-
write.run();
1090-
} catch (DataAccessException ex) {
1091-
LOG.warn("Schedule 10 write failed [{}]", ex.getClass().getSimpleName());
1092-
throw new ScheduleNotSavedException();
1093-
}
1094-
}
1095-
1096-
/**
1097-
* Runs a write, translating a data-access failure into the resource-specific legacy save error.
10981107
*
1099-
* <p>Same contract as {@link #persist(Runnable)} — type-only logging, never values — but names
1100-
* the resource that failed, which is what § Validation rules directs the write path to do
1101-
* wherever the failing resource is known (code review 2026-08-18: the four keys were dead).
1108+
* <p>Every write names the resource that failed, which is what § Validation rules directs the
1109+
* write path to do wherever that resource is known (code review 2026-08-18: the four keys were
1110+
* declared but dead). A generic no-key overload existed alongside this one until all seven call
1111+
* sites carried a key, at which point it was unreachable and was removed.
11021112
*
11031113
* @param write the write to run
11041114
* @param messageKey one of the {@link Schedule10PersistenceException} constants

backend/src/test/java/ca/bc/gov/nrs/ilcr/schedule10/Schedule10CheckStatusTest.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,6 @@ private static ConstructionPage page(List<RoadDetail> details) {
7676
details.size(), 0, details);
7777
}
7878

79-
private static List<String> keysOf(List<Issue> issues) {
80-
return issues.stream().map(Issue::messageKey).toList();
81-
}
82-
8379
private static Issue issueFor(List<Issue> issues, String field) {
8480
return issues.stream()
8581
.filter(issue -> field.equals(issue.field()))
@@ -286,11 +282,18 @@ void crushedRequiresEverything() {
286282
DetailOutcome outcome =
287283
Schedule10CheckStatus.evaluateRoadDetail(detail, PAGE_LABEL, ALLOWABLE);
288284

289-
assertThat(outcome.issues()).extracting(Issue::field).contains(
285+
// containsExactly, not contains: the sub-grade and material figures are all clean here, so the
286+
// gated stabilizing block is the WHOLE outcome, and its order is contractual.
287+
assertThat(outcome.issues()).extracting(Issue::field).containsExactly(
290288
"stabilizingLength", "stabilizingSurfaceWidth", "stabilizingDepth",
291289
"stabilizingDistanceToSource", "ballastMaterialCode", "stabilizingActualCost",
292290
"stabilizingTtTransfer", "stabilizingOtherTransfer");
293-
assertThat(keysOf(outcome.issues())).contains("missingRequiredFieldMsg");
291+
// The key is bound to the FIELD. The previous form asserted only that
292+
// "missingRequiredFieldMsg" appeared somewhere in the outcome, which any of the eight issues
293+
// satisfied — so it never pinned the material-type rule to its own message (flagged by both the
294+
// code review and Sonar, 2026-08-18).
295+
assertThat(issueFor(outcome.issues(), "ballastMaterialCode").messageKey())
296+
.isEqualTo("missingRequiredFieldMsg");
294297
}
295298

296299
@Test

backend/src/test/java/ca/bc/gov/nrs/ilcr/schedule10/Schedule10CopyIT.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,11 @@ void copyCarriesHeaderOnly() throws Exception {
7777
var stored = jdbc.queryForMap(
7878
"SELECT ENTRY_USERID, UPDATE_USERID, TSB_NUMBER_CODE, TFL_NUMBER_CODE, REVISION_COUNT"
7979
+ " FROM THE.ROAD_CONSTRUCTION_REPRT WHERE ROAD_CONSTRUCTION_REPRT_ID = ?", copyId);
80-
assertThat(stored.get("ENTRY_USERID")).isEqualTo("dev-submitter");
81-
assertThat(stored.get("UPDATE_USERID")).isEqualTo("dev-submitter");
80+
assertThat(stored).containsEntry("ENTRY_USERID", "dev-submitter");
81+
assertThat(stored).containsEntry("UPDATE_USERID", "dev-submitter");
8282
assertThat(((Number) stored.get("REVISION_COUNT")).intValue()).isZero();
8383
// The location legs are carried across verbatim — the supply block was asserted nowhere before.
84-
assertThat(stored.get("TSB_NUMBER_CODE")).isEqualTo("01A");
84+
assertThat(stored).containsEntry("TSB_NUMBER_CODE", "01A");
8585
assertThat(stored.get("TFL_NUMBER_CODE")).isNull();
8686
}
8787

backend/src/test/java/ca/bc/gov/nrs/ilcr/schedule10/Schedule10PageWriteIT.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -97,13 +97,13 @@ void createsTsaLocatedPage() throws Exception {
9797
"SELECT ILCR_CATEGORY_ID, REVISION_COUNT, ENTRY_USERID, ENTRY_TIMESTAMP, UPDATE_USERID,"
9898
+ " UPDATE_TIMESTAMP, CONSTRUCTION_DATE FROM THE.ROAD_CONSTRUCTION_REPRT"
9999
+ " WHERE ROAD_CONSTRUCTION_REPRT_ID = ?", pageId);
100-
assertThat(stored.get("ILCR_CATEGORY_ID")).isEqualTo("10");
101-
assertThat(stored.get("ENTRY_USERID")).isEqualTo("dev-submitter");
102-
assertThat(stored.get("UPDATE_USERID")).isEqualTo("dev-submitter");
100+
assertThat(stored).containsEntry("ILCR_CATEGORY_ID", "10");
101+
assertThat(stored).containsEntry("ENTRY_USERID", "dev-submitter");
102+
assertThat(stored).containsEntry("UPDATE_USERID", "dev-submitter");
103103
assertThat(((Number) stored.get("REVISION_COUNT")).intValue()).isZero();
104104
// A fresh insert stamps both from the same SYSDATE, so they are equal — and both sit in the
105105
// present, which a defaulted or absent value would not.
106-
assertThat(stored.get("ENTRY_TIMESTAMP")).isEqualTo(stored.get("UPDATE_TIMESTAMP"));
106+
assertThat(stored).containsEntry("ENTRY_TIMESTAMP", stored.get("UPDATE_TIMESTAMP"));
107107
assertThat((Timestamp) stored.get("ENTRY_TIMESTAMP"))
108108
.isAfter(Timestamp.valueOf("2020-01-01 00:00:00"));
109109
// Legacy never writes this column and every real delivery page holds NULL.
@@ -133,7 +133,7 @@ void tflPageClearsSupplyBlockCounterpart() throws Exception {
133133
+ " WHERE ROAD_CONSTRUCTION_REPRT_ID = ?", page.get("pageId").asInt());
134134
assertThat(stored.get("TSA_NUMBER")).isNull();
135135
assertThat(stored.get("TSB_NUMBER_CODE")).isNull();
136-
assertThat(stored.get("TFL_NUMBER_CODE")).isEqualTo("08");
136+
assertThat(stored).containsEntry("TFL_NUMBER_CODE", "08");
137137
}
138138

139139
@Test
@@ -194,8 +194,8 @@ void editBumpsRevisionAndRestampsUpdateColumns() throws Exception {
194194
"SELECT ENTRY_USERID, UPDATE_USERID FROM THE.ROAD_CONSTRUCTION_REPRT"
195195
+ " WHERE ROAD_CONSTRUCTION_REPRT_ID = 8956");
196196
// ENTRY_* must survive an update untouched; only UPDATE_* is restamped.
197-
assertThat(stored.get("ENTRY_USERID")).isEqualTo(entryBefore);
198-
assertThat(stored.get("UPDATE_USERID")).isEqualTo("dev-submitter");
197+
assertThat(stored).containsEntry("ENTRY_USERID", entryBefore);
198+
assertThat(stored).containsEntry("UPDATE_USERID", "dev-submitter");
199199
}
200200

201201
@Test

0 commit comments

Comments
 (0)