/*
* Copyright 2002-2016 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.web.util;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import java.lang.reflect.InvocationTargetException;
import org.junit.Before;
import org.junit.Test;
/**
* Test cases for {@link ThrowableAnalyzer}.
*
* @author Andreas Senft
*/
@SuppressWarnings("unchecked")
public class ThrowableAnalyzerTests {
/**
* Exception for testing purposes. The cause is not retrievable by {@link #getCause()}
* .
*/
public static final class NonStandardException extends Exception {
private Throwable cause;
public NonStandardException(String message, Throwable cause) {
super(message);
this.cause = cause;
}
public Throwable resolveCause() {
return this.cause;
}
}
/**
* <code>ThrowableCauseExtractor</code> for handling <code>NonStandardException</code>
* instances.
*/
public static final class NonStandardExceptionCauseExtractor
implements ThrowableCauseExtractor {
public Throwable extractCause(Throwable throwable) {
ThrowableAnalyzer.verifyThrowableHierarchy(throwable,
NonStandardException.class);
return ((NonStandardException) throwable).resolveCause();
}
}
/**
* An array of nested throwables for testing. The cause of element 0 is element 1, the
* cause of element 1 is element 2 and so on.
*/
private Throwable[] testTrace;
/**
* Plain <code>ThrowableAnalyzer</code>.
*/
private ThrowableAnalyzer standardAnalyzer;
/**
* Enhanced <code>ThrowableAnalyzer</code> capable to process
* <code>NonStandardException</code>s.
*/
private ThrowableAnalyzer nonstandardAnalyzer;
@Before
public void setUp() throws Exception {
// Set up test trace
this.testTrace = new Throwable[7];
this.testTrace[6] = new IllegalArgumentException("Test_6");
this.testTrace[5] = new Throwable("Test_5", this.testTrace[6]);
this.testTrace[4] = new InvocationTargetException(this.testTrace[5], "Test_4");
this.testTrace[3] = new Exception("Test_3", this.testTrace[4]);
this.testTrace[2] = new NonStandardException("Test_2", this.testTrace[3]);
this.testTrace[1] = new RuntimeException("Test_1", this.testTrace[2]);
this.testTrace[0] = new Exception("Test_0", this.testTrace[1]);
// Set up standard analyzer
this.standardAnalyzer = new ThrowableAnalyzer();
// Set up nonstandard analyzer
this.nonstandardAnalyzer = new ThrowableAnalyzer() {
/**
* @see org.springframework.security.web.util.ThrowableAnalyzer#initExtractorMap()
*/
@Override
protected void initExtractorMap() {
super.initExtractorMap();
// register extractor for NonStandardException
registerExtractor(NonStandardException.class,
new NonStandardExceptionCauseExtractor());
}
};
}
@Test
public void testRegisterExtractorWithInvalidExtractor() {
try {
new ThrowableAnalyzer() {
/**
* @see org.springframework.security.web.util.ThrowableAnalyzer#initExtractorMap()
*/
@Override
protected void initExtractorMap() {
// null is no valid extractor
super.registerExtractor(Exception.class, null);
}
};
fail("IllegalArgumentExpected");
}
catch (IllegalArgumentException e) {
// ok
}
}
@Test
public void testGetRegisteredTypes() {
Class[] registeredTypes = this.nonstandardAnalyzer.getRegisteredTypes();
for (int i = 0; i < registeredTypes.length; ++i) {
Class clazz = registeredTypes[i];
// The most specific types have to occur first.
for (int j = 0; j < i; ++j) {
Class prevClazz = registeredTypes[j];
assertThat(prevClazz.isAssignableFrom(clazz)).withFailMessage(
"Unexpected order of registered classes: " + prevClazz
+ " is assignable from " + clazz).isFalse();
}
}
}
@Test
public void testDetermineCauseChainWithNoExtractors() {
ThrowableAnalyzer analyzer = new ThrowableAnalyzer() {
/**
* @see org.springframework.security.web.util.ThrowableAnalyzer#initExtractorMap()
*/
@Override
protected void initExtractorMap() {
// skip default initialization
}
};
assertThat(analyzer.getRegisteredTypes().length).withFailMessage(
"Unexpected number of registered types").isEqualTo(0);
Throwable t = this.testTrace[0];
Throwable[] chain = analyzer.determineCauseChain(t);
// Without extractors only the root throwable is available
assertThat(chain.length).as("Unexpected chain size").isEqualTo(1);
assertThat(chain[0]).as("Unexpected chain entry").isEqualTo(t);
}
@Test
public void testDetermineCauseChainWithDefaultExtractors() {
ThrowableAnalyzer analyzer = this.standardAnalyzer;
assertThat(analyzer.getRegisteredTypes().length).withFailMessage(
"Unexpected number of registered types").isEqualTo(2);
Throwable[] chain = analyzer.determineCauseChain(this.testTrace[0]);
// Element at index 2 is a NonStandardException which cannot be analyzed further
// by default
assertThat(chain.length).as("Unexpected chain size").isEqualTo(3);
for (int i = 0; i < 3; ++i) {
assertThat(chain[i]).withFailMessage(
"Unexpected chain entry: " + i).isEqualTo(this.testTrace[i]);
}
}
@Test
public void testDetermineCauseChainWithCustomExtractors() {
ThrowableAnalyzer analyzer = this.nonstandardAnalyzer;
Throwable[] chain = analyzer.determineCauseChain(this.testTrace[0]);
assertThat(chain.length).as("Unexpected chain size").isEqualTo(
this.testTrace.length);
for (int i = 0; i < chain.length; ++i) {
assertThat(chain[i]).withFailMessage(
"Unexpected chain entry: " + i).isEqualTo(this.testTrace[i]);
}
}
@Test
public void testGetFirstThrowableOfTypeWithSuccess1() {
ThrowableAnalyzer analyzer = this.nonstandardAnalyzer;
Throwable[] chain = analyzer.determineCauseChain(this.testTrace[0]);
Throwable result = analyzer.getFirstThrowableOfType(Exception.class, chain);
assertThat(result).as("null not expected").isNotNull();
assertThat(result).as("Unexpected throwable found").isEqualTo(this.testTrace[0]);
}
@Test
public void testGetFirstThrowableOfTypeWithSuccess2() {
ThrowableAnalyzer analyzer = this.nonstandardAnalyzer;
Throwable[] chain = analyzer.determineCauseChain(this.testTrace[0]);
Throwable result = analyzer.getFirstThrowableOfType(NonStandardException.class,
chain);
assertThat(result).as("null not expected").isNotNull();
assertThat(result).as("Unexpected throwable found").isEqualTo(this.testTrace[2]);
}
@Test
public void testGetFirstThrowableOfTypeWithFailure() {
ThrowableAnalyzer analyzer = this.nonstandardAnalyzer;
Throwable[] chain = analyzer.determineCauseChain(this.testTrace[0]);
// IllegalStateException not in trace
Throwable result = analyzer.getFirstThrowableOfType(IllegalStateException.class,
chain);
assertThat(result).as("null expected").isNull();
}
@Test
public void testVerifyThrowableHierarchyWithExactType() {
Throwable throwable = new IllegalStateException("Test");
ThrowableAnalyzer.verifyThrowableHierarchy(throwable,
IllegalStateException.class);
// No exception expected
}
@Test
public void testVerifyThrowableHierarchyWithCompatibleType() {
Throwable throwable = new IllegalStateException("Test");
ThrowableAnalyzer.verifyThrowableHierarchy(throwable, Exception.class);
// No exception expected
}
@Test
public void testVerifyThrowableHierarchyWithNull() {
try {
ThrowableAnalyzer.verifyThrowableHierarchy(null, Throwable.class);
fail("IllegalArgumentException expected");
}
catch (IllegalArgumentException e) {
// ok
}
}
@Test
public void testVerifyThrowableHierarchyWithNonmatchingType() {
Throwable throwable = new IllegalStateException("Test");
try {
ThrowableAnalyzer.verifyThrowableHierarchy(throwable,
InvocationTargetException.class);
fail("IllegalArgumentException expected");
}
catch (IllegalArgumentException e) {
// ok
}
}
}