diff --git a/refarch-backend/pom.xml b/refarch-backend/pom.xml index 0dd0cfbc3..3546ab434 100644 --- a/refarch-backend/pom.xml +++ b/refarch-backend/pom.xml @@ -58,6 +58,7 @@ 0.8.14 + 1.4.1 3.2.0 @@ -259,6 +260,12 @@ spring-boot-configuration-processor true + + com.tngtech.archunit + archunit + ${archunit.version} + test + diff --git a/refarch-backend/src/test/java/de/muenchen/refarch/archunit/ArchUnitTest.java b/refarch-backend/src/test/java/de/muenchen/refarch/archunit/ArchUnitTest.java new file mode 100644 index 000000000..88c63b195 --- /dev/null +++ b/refarch-backend/src/test/java/de/muenchen/refarch/archunit/ArchUnitTest.java @@ -0,0 +1,42 @@ +package de.muenchen.refarch.archunit; + +import com.tngtech.archunit.core.domain.JavaClasses; +import com.tngtech.archunit.core.importer.ClassFileImporter; +import com.tngtech.archunit.core.importer.ImportOption; +import com.tngtech.archunit.lang.ArchRule; +import de.muenchen.refarch.archunit.rules.Rules; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.aggregator.ArgumentsAccessor; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class ArchUnitTest { + + private static JavaClasses allTestClasses; + + @BeforeAll + static void init() { + allTestClasses = new ClassFileImporter() + .withImportOption(new ImportOption.OnlyIncludeTests()) + .importPackages(de.muenchen.refarch.MicroServiceApplication.class.getPackage().getName()); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("allTestClassesRulesToVerify") + void givenAllArchUnitRulesForAllTestClasses_thenRunArchUnitTests(final ArgumentsAccessor arguments) { + arguments.get(1, ArchRule.class).check(allTestClasses); + } + + public static Stream allTestClassesRulesToVerify() { + return Stream.of( + Arguments.of("RULE_TESTCLASSES_END_WITH_TEST_CONVENTION_MATCHED", + Rules.RULE_TESTCLASSES_END_WITH_TEST_CONVENTION_MATCHED), + Arguments.of("TEST_NAMING_CONVENTION_RULE", Rules.RULE_TEST_NAMING_CONVENTION_GIVEN_THEN_MATCHED), + Arguments.of("RULE_BEFORE_EACH_NAMING_CONVENTION_MATCHED", Rules.RULE_BEFORE_EACH_NAMING_CONVENTION_MATCHED), + Arguments.of("RULE_AFTER_EACH_NAMING_CONVENTION_MATCHED", Rules.RULE_AFTER_EACH_NAMING_CONVENTION_MATCHED), + Arguments.of("TEST_METHODS_ARE_PACKAGE_PRIVATE_CONVENTION_MATCHED", Rules.RULE_TEST_METHODS_ARE_PACKAGE_PRIVATE_CONVENTION_MATCHED)); + } + +} diff --git a/refarch-backend/src/test/java/de/muenchen/refarch/archunit/rules/Rules.java b/refarch-backend/src/test/java/de/muenchen/refarch/archunit/rules/Rules.java new file mode 100644 index 000000000..713b9fb6b --- /dev/null +++ b/refarch-backend/src/test/java/de/muenchen/refarch/archunit/rules/Rules.java @@ -0,0 +1,51 @@ +package de.muenchen.refarch.archunit.rules; + +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods; +import static de.muenchen.refarch.archunit.rules.TestClassesEndWithTestCondition.haveTopEnclosingClassEndingWithTest; + +import com.tngtech.archunit.core.domain.JavaModifier; +import com.tngtech.archunit.lang.ArchRule; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestTemplate; +import org.junit.jupiter.params.ParameterizedTest; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class Rules { + + public static final ArchRule RULE_TEST_NAMING_CONVENTION_GIVEN_THEN_MATCHED = methods() + .that().areAnnotatedWith(Test.class) + .or().areAnnotatedWith(ParameterizedTest.class) + .or().areAnnotatedWith(RepeatedTest.class) + .or() + .areAnnotatedWith(TestTemplate.class) + .should().haveNameMatching("^given[A-Z][a-zA-Z]+_then[A-Z][a-zA-Z]+$"); + + public static final ArchRule RULE_BEFORE_EACH_NAMING_CONVENTION_MATCHED = methods() + .that().areAnnotatedWith(BeforeEach.class).should().haveNameMatching("^setUp$") + .allowEmptyShould(true); + + public static final ArchRule RULE_AFTER_EACH_NAMING_CONVENTION_MATCHED = methods() + .that().areAnnotatedWith(AfterEach.class).should().haveNameMatching("^tearDown$") + .allowEmptyShould(true); + + public static final ArchRule RULE_TEST_METHODS_ARE_PACKAGE_PRIVATE_CONVENTION_MATCHED = methods() + .that().areAnnotatedWith(Test.class) + .or().areAnnotatedWith(ParameterizedTest.class) + .or().areAnnotatedWith(RepeatedTest.class).or() + .areAnnotatedWith(TestTemplate.class).should() + .notHaveModifier(JavaModifier.PROTECTED) + .andShould().notHaveModifier(JavaModifier.PRIVATE) + .andShould().notHaveModifier(JavaModifier.PUBLIC); + + public static final ArchRule RULE_TESTCLASSES_END_WITH_TEST_CONVENTION_MATCHED = methods() + .that().areAnnotatedWith(Test.class) + .or().areAnnotatedWith(ParameterizedTest.class) + .or().areAnnotatedWith(RepeatedTest.class).or() + .areAnnotatedWith(TestTemplate.class) + .should(haveTopEnclosingClassEndingWithTest); +} diff --git a/refarch-backend/src/test/java/de/muenchen/refarch/archunit/rules/TestClassesEndWithTestCondition.java b/refarch-backend/src/test/java/de/muenchen/refarch/archunit/rules/TestClassesEndWithTestCondition.java new file mode 100644 index 000000000..a6975fdfb --- /dev/null +++ b/refarch-backend/src/test/java/de/muenchen/refarch/archunit/rules/TestClassesEndWithTestCondition.java @@ -0,0 +1,33 @@ +package de.muenchen.refarch.archunit.rules; + +import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaMethod; +import com.tngtech.archunit.lang.ArchCondition; +import com.tngtech.archunit.lang.ConditionEvents; +import com.tngtech.archunit.lang.SimpleConditionEvent; + +public class TestClassesEndWithTestCondition extends ArchCondition { + + TestClassesEndWithTestCondition() { + super("have top enclosing class name ending with `Test`"); + } + + public static final ArchCondition haveTopEnclosingClassEndingWithTest = new TestClassesEndWithTestCondition(); + + @Override + public void check(JavaMethod method, ConditionEvents events) { + final var topEnclosingClass = getTopEnclosingClass(method.getOwner()); + + if (!topEnclosingClass.getSimpleName().endsWith("Test")) { + events.add(SimpleConditionEvent.violated(method, "Method %s must be declared in a class whose simple name ends with 'Test' (found: %s)" + .formatted(method.getName(), topEnclosingClass.getSimpleName()))); + } + } + + private JavaClass getTopEnclosingClass(JavaClass item) { + while (item.getEnclosingClass().isPresent()) { + item = item.getEnclosingClass().orElseThrow(); + } + return item; + } +} diff --git a/refarch-backend/src/test/java/de/muenchen/refarch/configuration/filter/CacheControlFilterTest.java b/refarch-backend/src/test/java/de/muenchen/refarch/configuration/filter/CacheControlFilterTest.java index fad824e4d..9c0bef952 100644 --- a/refarch-backend/src/test/java/de/muenchen/refarch/configuration/filter/CacheControlFilterTest.java +++ b/refarch-backend/src/test/java/de/muenchen/refarch/configuration/filter/CacheControlFilterTest.java @@ -44,7 +44,7 @@ class CacheControlFilterTest { private TestRestTemplate testRestTemplate; @Test - void testForCacheControlHeadersForEntityEndpoint() { + void givenEntityEndpoint_thenCacheControlHeadersPresent() { final ResponseEntity response = testRestTemplate.exchange(ENTITY_ENDPOINT_URL, HttpMethod.GET, null, String.class); assertEquals(HttpStatus.OK, response.getStatusCode()); assertTrue(response.getHeaders().containsKey(HttpHeaders.CACHE_CONTROL)); diff --git a/refarch-backend/src/test/java/de/muenchen/refarch/configuration/filter/UnicodeFilterConfigurationTest.java b/refarch-backend/src/test/java/de/muenchen/refarch/configuration/filter/UnicodeFilterConfigurationTest.java index ba3cea914..fb7fd0b4e 100644 --- a/refarch-backend/src/test/java/de/muenchen/refarch/configuration/filter/UnicodeFilterConfigurationTest.java +++ b/refarch-backend/src/test/java/de/muenchen/refarch/configuration/filter/UnicodeFilterConfigurationTest.java @@ -58,7 +58,7 @@ class UnicodeFilterConfigurationTest { private TheEntityRepository theEntityRepository; @Test - void testForNfcNormalization() { + void givenDecomposedString_thenCovertToNfcNormalized() { // Given // Persist entity with decomposed string. final TheEntityRequestDTO theEntityRequestDto = new TheEntityRequestDTO(TEXT_ATTRIBUTE_DECOMPOSED); diff --git a/refarch-backend/src/test/java/de/muenchen/refarch/configuration/filter/nfcconverter/NfcConverterTest.java b/refarch-backend/src/test/java/de/muenchen/refarch/configuration/filter/nfcconverter/NfcConverterTest.java index e0ec0a0d4..5025bcff6 100644 --- a/refarch-backend/src/test/java/de/muenchen/refarch/configuration/filter/nfcconverter/NfcConverterTest.java +++ b/refarch-backend/src/test/java/de/muenchen/refarch/configuration/filter/nfcconverter/NfcConverterTest.java @@ -70,7 +70,7 @@ class NfcConverterTest { // Test that request with configured ContentType is normalized to NFC. @Test - void testFilterIfContenttypeInWhitelist() throws ServletException, IOException { + void givenContenttypeInWhitelist_thenFilter() throws ServletException, IOException { mockRequest("text/plain"); filter.doFilter(req, resp, chain); @@ -90,7 +90,7 @@ void testFilterIfContenttypeInWhitelist() throws ServletException, IOException { // Test that Request not configured ContentType remains unchanged, i.e. is not normalized to NFC. @Test - void testSkipFilterIfContenttypeNotInWhitelist() throws ServletException, IOException { + void givenContenttypeNotInWhitelist_thenSkipFilter() throws ServletException, IOException { mockRequest("application/notvalid"); filter.doFilter(req, resp, chain); diff --git a/refarch-backend/src/test/java/de/muenchen/refarch/configuration/filter/nfcconverter/NfcHelperTest.java b/refarch-backend/src/test/java/de/muenchen/refarch/configuration/filter/nfcconverter/NfcHelperTest.java index 9c13e92a5..22aa1d327 100644 --- a/refarch-backend/src/test/java/de/muenchen/refarch/configuration/filter/nfcconverter/NfcHelperTest.java +++ b/refarch-backend/src/test/java/de/muenchen/refarch/configuration/filter/nfcconverter/NfcHelperTest.java @@ -29,7 +29,7 @@ class NfcHelperTest { private static final String[] NFC_OUTPUT_EXPECTED = { FIRST_NFC, SECOND_NFC, THIRD_NFC }; @Test - void nfcConverterString() { + void givenString_thenNfcConverted() { assertEquals(FIRST_NFC, NfcHelper.nfcConverter(FIRST_NFD)); assertEquals(FIRST_NFC.length(), NfcHelper.nfcConverter(FIRST_NFD).length()); @@ -41,7 +41,7 @@ void nfcConverterString() { } @Test - void nfcConverterStringBuffer() { + void givenStringBuffer_thenNfcConverted() { assertEquals(FIRST_NFC, NfcHelper.nfcConverter(new StringBuffer(FIRST_NFD)).toString()); assertEquals(FIRST_NFC.length(), NfcHelper.nfcConverter(new StringBuffer(FIRST_NFD)).length()); @@ -53,13 +53,13 @@ void nfcConverterStringBuffer() { } @Test - void nfcConverterStringArray() { + void givenStringArray_thenNfcConverted() { assertArrayEquals(NFC_OUTPUT_EXPECTED, NfcHelper.nfcConverter(NFD_INPUT)); assertEquals(NFC_OUTPUT_EXPECTED.length, NfcHelper.nfcConverter(NFD_INPUT).length); } @Test - void nfcConverterMapOfStrings() { + void givenMapOfStrings_thenNfcConverted() { final Map nfdInput = new HashMap<>(); nfdInput.put(FIRST_NFD, NFD_INPUT); nfdInput.put(SECOND_NFD, NFD_INPUT); @@ -73,7 +73,7 @@ void nfcConverterMapOfStrings() { } @Test - void nfcConverterCookie() { + void givenCookie_thenNfcConverted() { final Cookie nfcCookie = NfcHelper.nfcConverter(createNfdCookie()); assertEquals(NfcConverterTest.TOKEN, nfcCookie.getName()); @@ -84,7 +84,7 @@ void nfcConverterCookie() { @SuppressWarnings("PMD.UnitTestShouldIncludeAssert") @Test - void nfcConverterCookieArray() { + void givenCookieArray_thenNfcConverted() { final Cookie[] nfdCookies = Collections.nCopies(3, createNfdCookie()).toArray(new Cookie[3]); final Cookie[] nfcCookies = NfcHelper.nfcConverter(nfdCookies); Arrays.asList(nfcCookies).forEach(nfcCookie -> { diff --git a/refarch-backend/src/test/java/de/muenchen/refarch/configuration/security/KeycloakRolesAuthoritiesConverterTest.java b/refarch-backend/src/test/java/de/muenchen/refarch/configuration/security/KeycloakRolesAuthoritiesConverterTest.java index a3a00ba75..5ec646282 100644 --- a/refarch-backend/src/test/java/de/muenchen/refarch/configuration/security/KeycloakRolesAuthoritiesConverterTest.java +++ b/refarch-backend/src/test/java/de/muenchen/refarch/configuration/security/KeycloakRolesAuthoritiesConverterTest.java @@ -36,7 +36,7 @@ void setUp() { } @Test - void testConvert_WithRoles() { + void givenRoles_thenConvert() { // Setup final Map resourceAccessClaim = new HashMap<>(); resourceAccessClaim.put(TEST_CLIENT, Map.of("roles", List.of("admin", "user"))); @@ -54,7 +54,7 @@ void testConvert_WithRoles() { } @Test - void testConvert_WithoutRoles() { + void givenNoRoles_thenConvert() { // Setup final Map claims = new HashMap<>(); claims.put(RESOURCE_ACCESS_CLAIM, Map.of( @@ -71,7 +71,7 @@ void testConvert_WithoutRoles() { } @Test - void testConvert_ClientNotInResourceAccess() { + void givenClientNotInResourceAccess_thenConvert() { // Setup final Map resourceAccessClaim = new HashMap<>(); resourceAccessClaim.put("other-client", Map.of("roles", List.of("admin"))); @@ -87,7 +87,7 @@ void testConvert_ClientNotInResourceAccess() { } @Test - void testConvert_NullClaims() { + void givenNullClaims_thenConvert() { // Setup final Jwt jwt = mock(Jwt.class); diff --git a/refarch-backend/src/test/java/de/muenchen/refarch/configuration/security/UserInfoAuthoritiesConverterTest.java b/refarch-backend/src/test/java/de/muenchen/refarch/configuration/security/UserInfoAuthoritiesConverterTest.java index fb718fdb4..11f0e7c47 100644 --- a/refarch-backend/src/test/java/de/muenchen/refarch/configuration/security/UserInfoAuthoritiesConverterTest.java +++ b/refarch-backend/src/test/java/de/muenchen/refarch/configuration/security/UserInfoAuthoritiesConverterTest.java @@ -48,7 +48,7 @@ void setUp() { } @Test - void testConvert_WithAuthorities() { + void givenNoRoles_thenConvert() { // Setup final Jwt jwt = mock(Jwt.class); when(jwt.getSubject()).thenReturn(TEST_SUBJECT); @@ -70,7 +70,7 @@ void testConvert_WithAuthorities() { } @Test - void testConvert_NoAuthorities() { + void givenNoAuthorities_thenConvert() { // Setup final Jwt jwt = mock(Jwt.class); when(jwt.getSubject()).thenReturn(TEST_SUBJECT); @@ -89,7 +89,7 @@ void testConvert_NoAuthorities() { } @Test - void testConvert_CacheHit() { + void givenCacheHit_thenConvert() { // Setup final Jwt jwt = mock(Jwt.class); when(jwt.getSubject()).thenReturn(TEST_SUBJECT);