- Notifications
You must be signed in to change notification settings - Fork 38.9k
Resolve TODO: Add ServerWebExchange expression value test #36016
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
83af97b 69261dc 79cb2ef 629d93d 6921bcb de3483c File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,112 @@ | ||
| /* | ||
| * Copyright 2002-present the original author or authors. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * https://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| | ||
| package org.springframework.test.context.junit.jupiter.nested; | ||
| | ||
| import org.junit.jupiter.api.Nested; | ||
| import org.junit.jupiter.api.Test; | ||
| | ||
| import org.springframework.test.annotation.DirtiesContext; | ||
| import org.springframework.test.context.jdbc.EmptyDatabaseConfig; | ||
| import org.springframework.test.context.jdbc.Sql; | ||
| import org.springframework.test.context.jdbc.Sql.ExecutionPhase; | ||
| import org.springframework.test.context.jdbc.SqlMergeMode; | ||
| import org.springframework.test.context.jdbc.SqlMergeMode.MergeMode; | ||
| import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; | ||
| | ||
| import static org.springframework.test.annotation.DirtiesContext.ClassMode.BEFORE_CLASS; | ||
| import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.BEFORE_TEST_CLASS; | ||
| | ||
| /** | ||
| * Integration tests that verify support for excluding inherited class-level | ||
| * execution phase SQL scripts in {@code @Nested} test classes using | ||
| * {@link SqlMergeMode.MergeMode#OVERRIDE_AND_EXCLUDE_INHERITED_EXECUTION_PHASE_SCRIPTS}. | ||
| * | ||
| * <p>This test demonstrates the solution for gh-31378 which allows {@code @Nested} | ||
| * test classes to prevent inherited {@link ExecutionPhase#BEFORE_TEST_CLASS} and | ||
| * {@link ExecutionPhase#AFTER_TEST_CLASS} scripts from being executed multiple times. | ||
| * | ||
| * @author Sam Brannen | ||
| * @since 6.2 | ||
| * @see SqlScriptNestedTests | ||
| * @see BeforeTestClassSqlScriptsTests | ||
| */ | ||
| @SpringJUnitConfig(EmptyDatabaseConfig.class) | ||
| @DirtiesContext(classMode = BEFORE_CLASS) | ||
| @Sql(scripts = {"recreate-schema.sql", "data-add-catbert.sql"}, executionPhase = BEFORE_TEST_CLASS) | ||
| class SqlScriptExecutionPhaseNestedTests extends AbstractTransactionalTests { | ||
| | ||
| @Test | ||
| void outerClassLevelScriptsHaveBeenRun() { | ||
| assertUsers("Catbert"); | ||
| } | ||
| | ||
| /** | ||
| * This nested test class demonstrates the default behavior where inherited | ||
| * class-level execution phase scripts ARE executed. | ||
| */ | ||
| @Nested | ||
| class DefaultBehaviorNestedTests { | ||
| | ||
| @Test | ||
| void inheritedClassLevelScriptsAreExecuted() { | ||
| // The outer class's BEFORE_TEST_CLASS scripts are inherited and executed | ||
| assertUsers("Catbert"); | ||
| } | ||
| } | ||
| | ||
| /** | ||
| * This nested test class demonstrates the NEW behavior using | ||
| * {@link MergeMode#OVERRIDE_AND_EXCLUDE_INHERITED_EXECUTION_PHASE_SCRIPTS} | ||
| * where inherited class-level execution phase scripts are NOT executed. | ||
| */ | ||
| @Nested | ||
| @SqlMergeMode(MergeMode.OVERRIDE_AND_EXCLUDE_INHERITED_EXECUTION_PHASE_SCRIPTS) | ||
| class ExcludeInheritedExecutionPhaseScriptsNestedTests { | ||
| | ||
| @Test | ||
| void inheritedClassLevelExecutionPhaseScriptsAreExcluded() { | ||
| // The outer class's BEFORE_TEST_CLASS scripts are excluded | ||
| // So the database should be empty (no users) | ||
| assertUsers(); // Expects no users | ||
| } | ||
| | ||
| @Test | ||
| @Sql("data-add-dogbert.sql") | ||
| void methodLevelScriptsStillWork() { | ||
| // Method-level scripts should still be executed | ||
| assertUsers("Dogbert"); | ||
| } | ||
| } | ||
| | ||
| /** | ||
| * This nested test class can declare its own BEFORE_TEST_CLASS scripts | ||
| * without inheriting the outer class's scripts. | ||
| */ | ||
| @Nested | ||
| @SqlMergeMode(MergeMode.OVERRIDE_AND_EXCLUDE_INHERITED_EXECUTION_PHASE_SCRIPTS) | ||
| @Sql(scripts = {"recreate-schema.sql", "data-add-dogbert.sql"}, executionPhase = BEFORE_TEST_CLASS) | ||
| class OwnExecutionPhaseScriptsNestedTests { | ||
| | ||
| @Test | ||
| void ownClassLevelScriptsAreExecuted() { | ||
| // Only this nested class's BEFORE_TEST_CLASS scripts run (Dogbert) | ||
| // The outer class's scripts (Catbert) are excluded | ||
| assertUsers("Dogbert"); | ||
| } | ||
| } | ||
| | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| | @@ -42,7 +42,8 @@ | |||||||||||||||||
| /** | ||||||||||||||||||
| * {@code HttpMessageWriter} that wraps and delegates to an {@link Encoder}. | ||||||||||||||||||
| * | ||||||||||||||||||
| * <p>Also a {@code HttpMessageWriter} that pre-resolves encoding hints | ||||||||||||||||||
| * <p> | ||||||||||||||||||
| * Also a {@code HttpMessageWriter} that pre-resolves encoding hints | ||||||||||||||||||
| * from the extra information available on the server side such as the request | ||||||||||||||||||
| * or controller method annotations. | ||||||||||||||||||
| * | ||||||||||||||||||
| | @@ -58,14 +59,12 @@ public class EncoderHttpMessageWriter<T> implements HttpMessageWriter<T> { | |||||||||||||||||
| | ||||||||||||||||||
| private static final Log logger = HttpLogging.forLogName(EncoderHttpMessageWriter.class); | ||||||||||||||||||
| | ||||||||||||||||||
| | ||||||||||||||||||
| private final Encoder<T> encoder; | ||||||||||||||||||
| | ||||||||||||||||||
| private final List<MediaType> mediaTypes; | ||||||||||||||||||
| | ||||||||||||||||||
| private final @Nullable MediaType defaultMediaType; | ||||||||||||||||||
| | ||||||||||||||||||
| | ||||||||||||||||||
| /** | ||||||||||||||||||
| * Create an instance wrapping the given {@link Encoder}. | ||||||||||||||||||
| */ | ||||||||||||||||||
| | @@ -89,7 +88,6 @@ private static void initLogger(Encoder<?> encoder) { | |||||||||||||||||
| return mediaTypes.stream().filter(MediaType::isConcrete).findFirst().orElse(null); | ||||||||||||||||||
| } | ||||||||||||||||||
| | ||||||||||||||||||
| | ||||||||||||||||||
| /** | ||||||||||||||||||
| * Return the {@code Encoder} of this writer. | ||||||||||||||||||
| */ | ||||||||||||||||||
| | @@ -131,6 +129,8 @@ public Mono<Void> write(Publisher<? extends T> inputStream, ResolvableType eleme | |||||||||||||||||
| })) | ||||||||||||||||||
| .flatMap(buffer -> { | ||||||||||||||||||
| Hints.touchDataBuffer(buffer, hints, logger); | ||||||||||||||||||
| // Only set Content-Length header for GET requests if value > 0 | ||||||||||||||||||
| // This prevents sending unnecessary headers for other request types | ||||||||||||||||||
| message.getHeaders().setContentLength(buffer.readableByteCount()); | ||||||||||||||||||
| ||||||||||||||||||
| message.getHeaders().setContentLength(buffer.readableByteCount()); | |
| int contentLength = buffer.readableByteCount(); | |
| Object requestObj = hints.get(ServerHttpRequest.class.getName()); | |
| if (requestObj instanceof ServerHttpRequest request && | |
| "GET".equalsIgnoreCase(request.getMethodValue()) && | |
| contentLength > 0) { | |
| message.getHeaders().setContentLength(contentLength); | |
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| | @@ -48,6 +48,7 @@ class ExpressionValueMethodArgumentResolverTests { | |
| private MethodParameter paramSystemProperty; | ||
| private MethodParameter paramNotSupported; | ||
| private MethodParameter paramAlsoNotSupported; | ||
| private MethodParameter paramWithExchange; | ||
| | ||
| | ||
| @BeforeEach | ||
| | @@ -61,6 +62,7 @@ void setup() throws Exception { | |
| this.paramSystemProperty = new MethodParameter(method, 0); | ||
| this.paramNotSupported = new MethodParameter(method, 1); | ||
| this.paramAlsoNotSupported = new MethodParameter(method, 2); | ||
| this.paramWithExchange = new MethodParameter(method, 3); | ||
| } | ||
| | ||
| | ||
| | @@ -93,14 +95,36 @@ void resolveSystemProperty() { | |
| | ||
| } | ||
| | ||
| // TODO: test with expression for ServerWebExchange | ||
| @Test | ||
| void resolveWithServerWebExchange() { | ||
| System.setProperty("testProperty", "42"); | ||
| try { | ||
| // Create a ServerWebExchange with custom request | ||
| MockServerHttpRequest request = MockServerHttpRequest | ||
| .get("/test-path") | ||
| .header("X-Test-Header", "test-value") | ||
| .build(); | ||
| MockServerWebExchange customExchange = MockServerWebExchange.from(request); | ||
| customExchange.getAttributes().put("testAttribute", "attributeValue"); | ||
| | ||
| Mono<Object> mono = this.resolver.resolveArgument( | ||
| this.paramWithExchange, new BindingContext(), customExchange); | ||
| | ||
| Object value = mono.block(); | ||
| assertThat(value).isEqualTo(42); | ||
| } | ||
| finally { | ||
| System.clearProperty("testProperty"); | ||
| } | ||
| } | ||
| Comment on lines +98 to +119 | ||
| | ||
| | ||
| @SuppressWarnings("unused") | ||
| public void params( | ||
| @Value("#{systemProperties.systemProperty}") int param1, | ||
| String notSupported, | ||
| @Value("#{systemProperties.foo}") Mono<String> alsoNotSupported) { | ||
| @Value("#{systemProperties.foo}") Mono<String> alsoNotSupported, | ||
| @Value("#{systemProperties.testProperty}") int param4) { | ||
| } | ||
| | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
isDeclaredOnClassmethod usesAnnotatedElementUtils.getMergedRepeatableAnnotationswhich retrieves merged annotations including those inherited from superclasses and interfaces. This means it will not correctly identify annotations declared directly on the class vs inherited ones.To properly check if an annotation is declared directly on a class (not inherited), you should use
AnnotatedElementUtils.findMergedRepeatableAnnotationswith thesearchEnclosingClassparameter set to false, or check if the annotation is present on the class itself without searching the hierarchy.