diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/PatternReplacingContentModifier.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/PatternReplacingContentModifier.java index d0b9f4a2..1c70d3a3 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/PatternReplacingContentModifier.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/PatternReplacingContentModifier.java @@ -65,6 +65,9 @@ public byte[] modifyContent(byte[] content, @Nullable MediaType contentType) { while (matcher.find()) { String prefix; if (matcher.groupCount() > 0) { + if (matcher.start(1) == -1) { + continue; + } prefix = original.substring(previous, matcher.start(1)); previous = matcher.end(1); } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/PatternReplacingContentModifierTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/PatternReplacingContentModifierTests.java index 6865eeec..d6399f58 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/PatternReplacingContentModifierTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/PatternReplacingContentModifierTests.java @@ -40,6 +40,30 @@ void contentThatDoesNotMatchIsUnchanged() { .isEqualTo("{\"id\" : \"CA76-ED42-11CE-BACD\"}".getBytes()); } + @Test + void whenPatternHasAGroupOnlyTheFirstGroupIsReplaced() { + Pattern pattern = Pattern.compile("\"id\":\"([^\"]+)\""); + PatternReplacingContentModifier contentModifier = new PatternReplacingContentModifier(pattern, "<>"); + assertThat(contentModifier.modifyContent("{\"id\":\"abc\",\"name\":\"alpha\"}".getBytes(), null)) + .isEqualTo("{\"id\":\"<>\",\"name\":\"alpha\"}".getBytes()); + } + + @Test + void matchInWhichTheFirstGroupDoesNotParticipateIsUnchanged() { + Pattern pattern = Pattern.compile("\"id\":\"([^\"]+)\"|\"created\":\"[^\"]+\""); + PatternReplacingContentModifier contentModifier = new PatternReplacingContentModifier(pattern, "<>"); + assertThat(contentModifier.modifyContent("{\"created\":\"2026-10-16\",\"id\":\"abc\"}".getBytes(), null)) + .isEqualTo("{\"created\":\"2026-10-16\",\"id\":\"<>\"}".getBytes()); + } + + @Test + void matchInWhichAnOptionalFirstGroupIsAbsentIsUnchanged() { + Pattern pattern = Pattern.compile("[0-9]+(-SNAPSHOT)?"); + PatternReplacingContentModifier contentModifier = new PatternReplacingContentModifier(pattern, "-RELEASE"); + assertThat(contentModifier.modifyContent("1.0 2.0-SNAPSHOT".getBytes(), null)) + .isEqualTo("1.0 2.0-RELEASE".getBytes()); + } + @Test void encodingIsPreservedUsingCharsetFromContentType() { String japaneseContent = "\u30b3\u30f3\u30c6\u30f3\u30c4";