
com.nike.backstopper.apierror.projectspecificinfo.ProjectApiErrorsTestBase Maven / Gradle / Ivy
Show all versions of backstopper-reusable-tests Show documentation
package com.nike.backstopper.apierror.projectspecificinfo;
import com.nike.backstopper.apierror.ApiError;
import com.nike.backstopper.apierror.ApiErrorBase;
import com.nike.backstopper.apierror.testutil.BarebonesCoreApiErrorForTesting;
import com.nike.backstopper.apierror.testutil.ProjectApiErrorsForTesting;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.UUID;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
/**
* Reusable test that verifies the functionality of your project's {@link ProjectApiErrors} helper methods and objects.
* This is intended to be extended for each project with {@link #getProjectApiErrors()} implemented to return that
* project's implementation.
*
* NOTE: To take advantage of this prebuilt test class all you have to do is extend it and fill in the abstract
* {@link #getProjectApiErrors()} method to have it return your project's {@link ProjectApiErrors}. As long as
* your unit test runner picks it up and runs the test methods in this base class you should be good to go.
* This is especially important if your project is using TestNG instead of JUnit, for example, as this base
* prebuilt unit test is annotated with JUnit {@code @Test} annotations. You may need to create a new test
* method that is guaranteed to get fired during your unit tests and manually call the parent methods to perform
* the checks. For example:
*
* public class MyProjectApiErrorsTest extends ProjectApiErrorsTestBase {
*
* @org.testng.annotations.Test
* @Override
* public void verifyGetStatusCodePriorityOrderMethodContainsAllRelevantCodes() {
* super.verifyGetStatusCodePriorityOrderMethodContainsAllRelevantCodes();
* }
*
* @org.testng.annotations.Test
* @Override
* public void determineHighestPriorityHttpStatusCodeShouldReturnNullForEmptyErrorCollection() {
* super.determineHighestPriorityHttpStatusCodeShouldReturnNullForEmptyErrorCollection();
* }
*
* // ... etc
* }
*
*
* If you're using JUnit then it should be pretty trivial to set this up for your project's
* {@link ProjectApiErrors}. Here's a copy-paste of
* {@code com.nike.backstopper.apierror.SampleProjectApiErrorsBaseTest} (a unit test class living in the
* backstopper-core library's test source area) as a concrete complete real-world example of how simple it is to
* set up for a JUnit environment where the base class' tests get picked up automatically:
*
* public class SampleProjectApiErrorsBaseTest extends ProjectApiErrorsTestBase {
*
* private static final ProjectApiErrors testProjectApiErrors = new SampleProjectApiErrorsBase() {
* @Override
* protected List getProjectSpecificApiErrors() {
* return null;
* }
*
* @Override
* protected ProjectSpecificErrorCodeRange getProjectSpecificErrorCodeRange() {
* return null;
* }
* };
*
* @Override
* protected ProjectApiErrors getProjectApiErrors() {
* return testProjectApiErrors;
* }
* }
*
* You would of course return your project's {@link ProjectApiErrors} rather than an anonymous class, but in the
* case of this example we're only testing the base class with its core errors, so this works fine since by
* default {@link ProjectApiErrors} includes all the {@link ProjectApiErrors#getCoreApiErrors()}.
*
* @deprecated This is the JUnit 4 version and will not be maintained long term. Please migrate to the JUnit 5 module: backstopper-reusable-tests-junit5
* @author Nic Munroe
*/
@Deprecated
@SuppressWarnings("WeakerAccess")
public abstract class ProjectApiErrorsTestBase {
protected ApiError findRandomApiErrorWithHttpStatusCode(int httpStatusCode) {
for (ApiError error : getProjectApiErrors().getProjectApiErrors()) {
if (error.getHttpStatusCode() == httpStatusCode)
return error;
}
throw new IllegalStateException("Couldn't find ApiError with HTTP status code: " + httpStatusCode);
}
protected abstract ProjectApiErrors getProjectApiErrors();
@Test
public void verifyGetStatusCodePriorityOrderMethodContainsAllRelevantCodes() {
for (ApiError error : getProjectApiErrors().getProjectApiErrors()) {
int relevantCode = error.getHttpStatusCode();
boolean containsRelevantCode = getProjectApiErrors().getStatusCodePriorityOrder().contains(relevantCode);
if (!containsRelevantCode) {
throw new AssertionError(
"getStatusCodePriorityOrder() did not contain HTTP Status Code: " + relevantCode + " for "
+ getProjectApiErrors().getClass().getName() + "'s ApiError: " + error
);
}
}
}
@Test
public void determineHighestPriorityHttpStatusCodeShouldReturnNullForNullErrorCollection() {
assertThat(getProjectApiErrors().determineHighestPriorityHttpStatusCode(null), nullValue());
}
@Test
public void determineHighestPriorityHttpStatusCodeShouldReturnNullForEmptyErrorCollection() {
assertThat(getProjectApiErrors().determineHighestPriorityHttpStatusCode(Collections.emptyList()),
nullValue());
}
@Test
public void determineHighestPriorityHttpStatusCodeShouldReturnTheSameValueRegardlessOfErrorOrder() {
List list = Arrays.asList(
findRandomApiErrorWithHttpStatusCode(getProjectApiErrors().getStatusCodePriorityOrder()
.get(0)),
findRandomApiErrorWithHttpStatusCode(getProjectApiErrors().getStatusCodePriorityOrder()
.get(1)));
int returnValNormalOrder = getProjectApiErrors().determineHighestPriorityHttpStatusCode(list);
Collections.reverse(list);
int returnValReverseOrder = getProjectApiErrors().determineHighestPriorityHttpStatusCode(list);
assertThat(returnValNormalOrder, is(returnValReverseOrder));
}
@Test
public void determineHighestPriorityHttpStatusCodeShouldReturnTheCorrectValueWithAMixedList() {
List list = new ArrayList<>(Arrays.asList(
findRandomApiErrorWithHttpStatusCode(getProjectApiErrors().getStatusCodePriorityOrder()
.get(2)),
findRandomApiErrorWithHttpStatusCode(getProjectApiErrors().getStatusCodePriorityOrder()
.get(3))));
assertThat(getProjectApiErrors().determineHighestPriorityHttpStatusCode(list),
is(getProjectApiErrors().getStatusCodePriorityOrder().get(2)));
list.add(findRandomApiErrorWithHttpStatusCode(getProjectApiErrors().getStatusCodePriorityOrder().get(1)));
assertThat(getProjectApiErrors().determineHighestPriorityHttpStatusCode(list),
is(getProjectApiErrors().getStatusCodePriorityOrder().get(1)));
}
@Test
public void determineHighestPriorityHttpStatusCodeShouldReturnNullIfNoApiErrorsYouPassItHasHttpStatusCodeInPriorityOrderList() {
ApiError mockApiError1 = mock(ApiError.class);
ApiError mockApiError2 = mock(ApiError.class);
doReturn(414141).when(mockApiError1).getHttpStatusCode();
doReturn(424242).when(mockApiError2).getHttpStatusCode();
List list = Arrays.asList(mockApiError1, mockApiError2);
assertThat(getProjectApiErrors().determineHighestPriorityHttpStatusCode(list), nullValue());
}
@Test
public void
determineHighestPriorityHttpStatusCodeShouldReturnStatusCodeIfAtLeastOneApiErrorInListYouPassItHasHttpStatusCodeInPriorityOrderList() {
ApiError mockApiError1 = mock(ApiError.class);
ApiError mockApiError2 = mock(ApiError.class);
doReturn(424242).when(mockApiError1).getHttpStatusCode();
doReturn(400).when(mockApiError2).getHttpStatusCode();
List list = Arrays.asList(mockApiError1, mockApiError2);
assertThat(getProjectApiErrors().determineHighestPriorityHttpStatusCode(list), is(400));
}
@Test
public void determineHighestPriorityHttpStatusCodeShouldReturnStatusCodeIfOnlyApiError() {
ApiError mockApiError = mock(ApiError.class);
doReturn(400).when(mockApiError).getHttpStatusCode();
assertThat(getProjectApiErrors().determineHighestPriorityHttpStatusCode(Collections.singleton(mockApiError)),
is(400));
}
@Test
public void
determineHighestPriorityHttpStatusCodeShouldReturnStatusCodeIfOnlyApiErrorEvenIfNotInPriorityOrderList() {
ApiError mockApiError = mock(ApiError.class);
doReturn(424242).when(mockApiError).getHttpStatusCode();
assertThat(getProjectApiErrors().determineHighestPriorityHttpStatusCode(Collections.singleton(mockApiError)),
is(424242));
}
@Test
public void getSublistContainingOnlyHttpStatusCodeShouldReturnEmptyListForNullErrorCollection() {
assertThat(getProjectApiErrors()
.getSublistContainingOnlyHttpStatusCode(null, getProjectApiErrors().getStatusCodePriorityOrder()
.get(0)).size(), is(0));
}
@Test
public void getSublistContainingOnlyHttpStatusCodeShouldReturnEmptyListForNullStatusCode() {
ApiError randomError = getProjectApiErrors().getProjectApiErrors().get(0);
assertThat(
getProjectApiErrors().getSublistContainingOnlyHttpStatusCode(Collections.singletonList(randomError), null)
.size(), is(0));
}
@Test
public void getSublistContainingOnlyHttpStatusCodeShouldFilterOutExpectedValues() {
List mixedList = Arrays.asList(
findRandomApiErrorWithHttpStatusCode(getProjectApiErrors().getStatusCodePriorityOrder()
.get(0)),
findRandomApiErrorWithHttpStatusCode(getProjectApiErrors().getStatusCodePriorityOrder()
.get(1)));
List filteredList = getProjectApiErrors()
.getSublistContainingOnlyHttpStatusCode(mixedList, getProjectApiErrors().getStatusCodePriorityOrder()
.get(1));
for (ApiError error : filteredList) {
assertThat(error.getHttpStatusCode(), is(getProjectApiErrors().getStatusCodePriorityOrder().get(1)));
}
}
@Test
public void convertToApiErrorShouldReturnNullIfYouPassItNull() {
assertThat(getProjectApiErrors().convertToApiError(null), nullValue());
}
@Test
public void convertToApiErrorShouldReturnExpectedResultIfPassedValidNames() {
for (ApiError apiError : getProjectApiErrors().getProjectApiErrors()) {
assertThat("Did not get back the same instance for ApiError with name: " + apiError.getName()
+ ". This is usually because you have duplicate ApiError names - see the output of the "
+ "shouldNotContainDuplicateNamedApiErrors() test to be sure. If that's not the case then "
+ "you'll probably need to do some breakpoint debugging.",
getProjectApiErrors().convertToApiError(apiError.getName()), is(apiError));
}
}
@Test
public void convertToApiErrorShouldReturnNullIfYouPassItGarbage() {
assertThat(getProjectApiErrors().convertToApiError(UUID.randomUUID().toString()), nullValue());
}
@Test
public void convertToApiErrorShouldUseFallbackOnNullValue() {
ApiError fallback = BarebonesCoreApiErrorForTesting.GENERIC_SERVICE_ERROR;
assertThat(getProjectApiErrors().convertToApiError(null, fallback), is(fallback));
}
@Test
public void convertToApiErrorShouldUseFallbackOnInvalidValue() {
ApiError fallback = BarebonesCoreApiErrorForTesting.GENERIC_SERVICE_ERROR;
assertThat(getProjectApiErrors().convertToApiError("notavaliderror", fallback), is(fallback));
}
@Test(expected = IllegalStateException.class)
public void verifyErrorsAreInRangeShouldThrowExceptionIfListIncludesNonCoreApiErrorAndRangeIsNull() {
ProjectApiErrorsForTesting
.withProjectSpecificData(Collections.singletonList(new ApiErrorBase("blah", 99001, "stuff", 400)),
null);
}
@Test
public void verifyErrorsAreInRangeShouldNotThrowExceptionIfListIncludesCoreApiErrors() {
ProjectApiErrors pae = ProjectApiErrorsForTesting.withProjectSpecificData(null, null);
assertThat(pae, notNullValue());
assertThat(pae.getProjectApiErrors().contains(BarebonesCoreApiErrorForTesting.GENERIC_SERVICE_ERROR), is(true));
}
@Test
public void verifyErrorsAreInRangeShouldNotThrowExceptionIfListIncludesCoreApiErrorWrapper() {
ApiError coreApiError = BarebonesCoreApiErrorForTesting.GENERIC_SERVICE_ERROR;
final ApiError coreApiErrorWrapper =
new ApiErrorBase("blah", coreApiError.getErrorCode(), coreApiError.getMessage(),
coreApiError.getHttpStatusCode());
ProjectApiErrors pae =
ProjectApiErrorsForTesting.withProjectSpecificData(Collections.singletonList(coreApiErrorWrapper), null);
assertThat(pae, notNullValue());
assertThat(pae.getProjectApiErrors().contains(coreApiErrorWrapper), is(true));
}
@Test(expected = IllegalStateException.class)
public void verifyErrorsAreInRangeShouldThrowExceptionIfListIncludesErrorOutOfRange() {
ProjectApiErrorsForTesting.withProjectSpecificData(
Collections.singletonList(new ApiErrorBase("blah", 1, "stuff", 400)),
new ProjectSpecificErrorCodeRange() {
@Override
public boolean isInRange(ApiError error) {
return "42".equals(error.getErrorCode());
}
@Override
public String getName() {
return "test error range";
}
}
);
}
@Test
public void shouldNotContainDuplicateNamedApiErrors() {
Map nameToCountMap = new HashMap<>();
SortedSet duplicateErrorNames = new TreeSet<>();
for (ApiError apiError : getProjectApiErrors().getProjectApiErrors()) {
Integer currentCount = nameToCountMap.get(apiError.getName());
if (currentCount == null)
currentCount = 0;
Integer newCount = currentCount + 1;
nameToCountMap.put(apiError.getName(), newCount);
if (newCount > 1)
duplicateErrorNames.add(apiError.getName());
}
if (!duplicateErrorNames.isEmpty()) {
StringBuilder sb = new StringBuilder();
sb.append(
"There are ApiError instances in the ProjectApiErrors that share duplicate names. [name, count]: ");
boolean first = true;
for (String dup : duplicateErrorNames) {
if (!first)
sb.append(", ");
sb.append("[").append(dup).append(", ").append(nameToCountMap.get(dup)).append("]");
first = false;
}
throw new AssertionError(sb.toString());
}
}
/**
* Override this if the should_not_contain_same_error_codes_for_different_instances_that_are_not_wrappers test is
* failing and you *really* want to allow one or more of your error codes to have duplicate ApiErrors that are
* not wrappers. This should be used with care.
*/
protected Set allowedDuplicateErrorCodes() {
return Collections.emptySet();
}
@Test
public void should_not_contain_same_error_codes_for_different_instances_that_are_not_wrappers() {
Set allowedDuplicateErrorCodes = allowedDuplicateErrorCodes();
Map codeToErrorMap = new HashMap<>();
for (ApiError apiError : getProjectApiErrors().getProjectApiErrors()) {
ApiError errorWithSameCode = codeToErrorMap.get(apiError.getErrorCode());
if (errorWithSameCode != null && !areWrappersOfEachOther(apiError, errorWithSameCode)
&& !allowedDuplicateErrorCodes.contains(apiError.getErrorCode())) {
throw new AssertionError(
"There are ApiError instances in the ProjectApiErrors that share duplicate error codes and are not "
+ "wrappers of each other. error_code=" + apiError.getErrorCode() + ", conflicting_api_errors=["
+ apiError.getName() + ", " + errorWithSameCode.getName() + "]"
);
}
codeToErrorMap.put(apiError.getErrorCode(), apiError);
}
}
private boolean areWrappersOfEachOther(ApiError error1, ApiError error2) {
boolean errorCodeMatches = Objects.equals(error1.getErrorCode(), error2.getErrorCode());
boolean messageMatches = Objects.equals(error1.getMessage(), error2.getMessage());
boolean httpStatusCodeMatches = error1.getHttpStatusCode() == error2.getHttpStatusCode();
//noinspection RedundantIfStatement
if (errorCodeMatches && messageMatches && httpStatusCodeMatches) {
return true;
}
return false;
}
@Test
public void allErrorsShouldBeCoreApiErrorsOrCoreApiErrorWrappersOrFallInProjectSpecificErrorRange() {
ProjectSpecificErrorCodeRange projectSpecificErrorCodeRange =
getProjectApiErrors().getProjectSpecificErrorCodeRange();
for (ApiError error : getProjectApiErrors().getProjectApiErrors()) {
boolean valid = false;
if (getProjectApiErrors().getCoreApiErrors().contains(error) || getProjectApiErrors()
.isWrapperAroundCoreError(error, getProjectApiErrors().getCoreApiErrors()))
valid = true;
else if (projectSpecificErrorCodeRange != null && projectSpecificErrorCodeRange.isInRange(error))
valid = true;
if (!valid) {
throw new AssertionError(
"Found an ApiError in the ProjectApiErrors that is not a core error or wrapper around a core error, and its error code does not fall in the "
+
"range of getProjectApiErrors().getProjectSpecificErrorCodeRange(). getProjectApiErrors().getProjectSpecificErrorCodeRange(): "
+ projectSpecificErrorCodeRange +
". Offending error info: name=" + error.getName() + ", errorCode=" + error.getErrorCode()
+ ", message=\"" + error.getMessage() + "\", httpStatusCode=" +
error.getHttpStatusCode() + ", class=" + error.getClass().getName());
}
}
}
}