Browse Source

SEC-3074: Add Test Meta Annotation Support

Rob Winch 10 years ago
parent
commit
567c51e109

+ 27 - 0
docs/manual/src/docs/asciidoc/_includes/test.adoc

@@ -249,6 +249,33 @@ final class WithUserDetailsSecurityContextFactory
 }
 }
 ----
 ----
 
 
+[[test-method-meta-annotations]]
+=== Test Meta Annotations
+
+If you reuse the same user within your tests often, it is not ideal to have to repeatedly specify the attributes.
+For example, if there are many tests related to an administrative user with the username "admin" and the roles `ROLE_USER` and `ROLE_ADMIN` you would have to write:
+
+[source,java]
+----
+@WithMockUser(username="admin",roles={"USER","ADMIN"})
+----
+
+Rather than repeating this everywhere, we can use a meta annotation.
+For example, we could create a meta annotation named `WithMockAdmin`:
+
+[source,java]
+----
+@Retention(RetentionPolicy.RUNTIME)
+@WithMockUser(value="rob",roles="ADMIN")
+public @interface WithMockAdmin { }
+----
+
+Now we can use `@WithMockAdmin` in the same way as the more verbose `@WithMockUser`.
+
+Meta annotations work with any of the testing annotations described above.
+For example, this means we could create a meta annotation for `@WithUserDetails("admin")` as well.
+
+
 [[test-mockmvc]]
 [[test-mockmvc]]
 == Spring MVC Test Integration
 == Spring MVC Test Integration
 
 

+ 5 - 1
docs/manual/src/docs/asciidoc/index.adoc

@@ -367,7 +367,11 @@ git clone https://github.com/spring-projects/spring-security.git
 This will give you access to the entire project history (including all releases and branches) on your local machine.
 This will give you access to the entire project history (including all releases and branches) on your local machine.
 
 
 [[new]]
 [[new]]
-== What's new in Spring Security 4.0
+== What's new in Spring Security 4.1
+
+* <<test-method-meta-annotations>>
+
+=== What's new in Spring Security 4.0
 
 
 There are http://goo.gl/ui9GCl[175+ tickets resolved] with the Spring Security 4.0 release.
 There are http://goo.gl/ui9GCl[175+ tickets resolved] with the Spring Security 4.0 release.
 
 

+ 18 - 18
test/src/main/java/org/springframework/security/test/context/support/WithSecurityContextTestExecutionListener.java

@@ -16,10 +16,13 @@
 package org.springframework.security.test.context.support;
 package org.springframework.security.test.context.support;
 
 
 import java.lang.annotation.Annotation;
 import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.TypeVariable;
 
 
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.BeanUtils;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.support.StaticApplicationContext;
 import org.springframework.context.support.StaticApplicationContext;
+import org.springframework.core.GenericTypeResolver;
 import org.springframework.core.annotation.AnnotationUtils;
 import org.springframework.core.annotation.AnnotationUtils;
 import org.springframework.core.annotation.Order;
 import org.springframework.core.annotation.Order;
 import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.core.context.SecurityContext;
@@ -59,13 +62,10 @@ public class WithSecurityContextTestExecutionListener extends
 	 */
 	 */
 	@Override
 	@Override
 	public void beforeTestMethod(TestContext testContext) throws Exception {
 	public void beforeTestMethod(TestContext testContext) throws Exception {
-		Annotation[] methodAnnotations = AnnotationUtils.getAnnotations(testContext
-				.getTestMethod());
-		SecurityContext securityContext = createSecurityContext(methodAnnotations,
+		SecurityContext securityContext = createSecurityContext(testContext.getTestMethod(),
 				testContext);
 				testContext);
 		if (securityContext == null) {
 		if (securityContext == null) {
-			Annotation[] classAnnotations = testContext.getTestClass().getAnnotations();
-			securityContext = createSecurityContext(classAnnotations, testContext);
+			securityContext = createSecurityContext(testContext.getTestClass(), testContext);
 		}
 		}
 		if (securityContext != null) {
 		if (securityContext != null) {
 			TestSecurityContextHolder.setContext(securityContext);
 			TestSecurityContextHolder.setContext(securityContext);
@@ -73,20 +73,20 @@ public class WithSecurityContextTestExecutionListener extends
 	}
 	}
 
 
 	@SuppressWarnings({ "rawtypes", "unchecked" })
 	@SuppressWarnings({ "rawtypes", "unchecked" })
-	private SecurityContext createSecurityContext(Annotation[] annotations,
+	private SecurityContext createSecurityContext(AnnotatedElement annotated,
 			TestContext context) {
 			TestContext context) {
-		for (Annotation a : annotations) {
-			WithSecurityContext withUser = AnnotationUtils.findAnnotation(
-					a.annotationType(), WithSecurityContext.class);
-			if (withUser != null) {
-				WithSecurityContextFactory factory = createFactory(withUser, context);
-				try {
-					return factory.createSecurityContext(a);
-				}
-				catch (RuntimeException e) {
-					throw new IllegalStateException(
-							"Unable to create SecurityContext using " + a, e);
-				}
+		WithSecurityContext withUser = AnnotationUtils.findAnnotation(
+				annotated, WithSecurityContext.class);
+		if (withUser != null) {
+			WithSecurityContextFactory factory = createFactory(withUser, context);
+			Class<? extends Annotation> type = (Class<? extends Annotation>) GenericTypeResolver.resolveTypeArgument(factory.getClass(), WithSecurityContextFactory.class);
+			Annotation annotation  = AnnotationUtils.findAnnotation(annotated, type);
+			try {
+				return factory.createSecurityContext(annotation);
+			}
+			catch (RuntimeException e) {
+				throw new IllegalStateException(
+						"Unable to create SecurityContext using " + annotation, e);
 			}
 			}
 		}
 		}
 		return null;
 		return null;

+ 33 - 0
test/src/test/java/org/springframework/security/test/web/servlet/showcase/secured/WithAdminRob.java

@@ -0,0 +1,33 @@
+/*
+ * Copyright 2002-2015 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
+ *
+ *      http://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.security.test.web.servlet.showcase.secured;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.security.test.context.support.WithMockUser;
+
+@Target({ ElementType.METHOD, ElementType.TYPE })
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+@Documented
+@WithMockUser(value="rob",roles="ADMIN")
+public @interface WithAdminRob {
+}

+ 10 - 0
test/src/test/java/org/springframework/security/test/web/servlet/showcase/secured/WithUserAuthenticationTests.java

@@ -64,6 +64,16 @@ public class WithUserAuthenticationTests {
 				.andExpect(authenticated().withUsername("user"));
 				.andExpect(authenticated().withUsername("user"));
 	}
 	}
 
 
+	@Test
+	@WithAdminRob
+	public void requestProtectedUrlWithAdminRob() throws Exception {
+		mvc.perform(get("/"))
+		// Ensure we got past Security
+				.andExpect(status().isNotFound())
+				// Ensure it appears we are authenticated with user
+				.andExpect(authenticated().withUsername("rob").withRoles("ADMIN"));
+	}
+
 	@Test
 	@Test
 	@WithMockUser(roles = "ADMIN")
 	@WithMockUser(roles = "ADMIN")
 	public void requestProtectedUrlWithAdmin() throws Exception {
 	public void requestProtectedUrlWithAdmin() throws Exception {