Added arch unit tests

This commit is contained in:
lobo committed 2026-04-04 11:22:26 +02:00
1 parent 7ba38fbbce
commit c028e8d382
6 files changed
+151 -13

No files matched your search

@@ -1,15 +1,29 @@
package auth_service;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
@SpringBootTest
@Disabled("Tests temporarily disabled until DB config is ready")
@Testcontainers
class AuthServiceApplicationTests {
/**
* Define the PostgreSQL container.
*
* @ServiceConnection automatically configures the application properties
* (URL, username, password) for this specific container.
*/
@Container
@ServiceConnection
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:17.9");
@Test
void contextLoads() {
// If the context fails to load (e.g. Flyway migration fails or beans are missing),
// this test will fail automatically. No explicit assertions needed.
}
}
@@ -0,0 +1,93 @@
package auth_service.architecture;
import com.tngtech.archunit.core.importer.ImportOption;
import com.tngtech.archunit.junit.AnalyzeClasses;
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.lang.ArchRule;
import com.tngtech.archunit.library.dependencies.SlicesRuleDefinition;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
/**
* ArchUnit tests to enforce Clean Architecture and package boundaries.
* Analyzes all classes in the 'auth_service' package, ignoring test classes.
*/
@AnalyzeClasses(packages = "auth_service", importOptions = ImportOption.DoNotIncludeTests.class)
class ArchitectureTest {
// ===================================================================================
// 1. DOMAIN INDEPENDENCE (Slices)
// ===================================================================================
@ArchTest
static final ArchRule domains_should_not_depend_on_each_other = SlicesRuleDefinition.slices()
.matching("auth_service.domain.(*)..")
.should().notDependOnEachOther()
.because("Domains (e.g. login, user) should be strictly isolated and independent.");
// ===================================================================================
// 2. CLEAN ARCHITECTURE DEPENDENCY DIRECTION (Inside out)
// ===================================================================================
@ArchTest
static final ArchRule domain_core_should_not_depend_on_outside = noClasses()
.that().resideInAnyPackage("..model..", "..service..")
.should().dependOnClassesThat().resideInAnyPackage("..controller..", "..persistence.entities..", "..persistence.mapper..")
.because("Domain core (Models, Services) must not depend on Web/Controller or Persistence details.");
@ArchTest
static final ArchRule persistence_interfaces_belong_to_domain = classes()
.that().resideInAPackage("..persistence..")
.and().areInterfaces()
.should().haveSimpleNameEndingWith("Repository")
.because("Repository interfaces are the ports and belong to the domain/persistence boundary.");
// ===================================================================================
// 3. ENCAPSULATION OF DTOs AND ENTITIES
// ===================================================================================
@ArchTest
static final ArchRule dtos_should_only_be_used_in_controllers = classes()
.that().resideInAPackage("..controller.dto..")
.should().onlyBeAccessed().byAnyPackage("..controller..")
.because("DTOs are Web/API concerns and must not leak into Services or Persistence.");
@ArchTest
static final ArchRule entities_should_only_be_used_in_persistence = classes()
.that().resideInAPackage("..persistence.entities..")
.should().onlyBeAccessed().byAnyPackage("..persistence..")
.because("Database Entities must not leak into the Domain Core or Controllers.");
// ===================================================================================
// 4. ADDITIONAL BEST PRACTICES
// ===================================================================================
@ArchTest
static final ArchRule no_cyclic_dependencies = SlicesRuleDefinition.slices()
.matching("auth_service.(*)..")
.should().beFreeOfCycles()
.because("Cyclic dependencies make the code unmaintainable and tightly coupled.");
@ArchTest
static final ArchRule controllers_should_only_call_services = noClasses()
.that().resideInAPackage("..controller..")
.should().dependOnClassesThat().resideInAPackage("..persistence..")
.because("Controllers must delegate to Services and should not access Persistence directly.");
@ArchTest
static final ArchRule services_should_be_annotated = classes()
.that().resideInAPackage("..service..")
.and().haveSimpleNameEndingWith("Service")
.should().beAnnotatedWith(org.springframework.stereotype.Service.class)
.because("Classes named *Service should be registered as Spring @Service beans.");
@ArchTest
static final ArchRule controllers_should_be_annotated = classes()
.that().resideInAPackage("..controller..")
.and().haveSimpleNameEndingWith("Controller")
.should().beAnnotatedWith(org.springframework.web.bind.annotation.RestController.class)
.orShould().beAnnotatedWith(org.springframework.web.bind.annotation.ControllerAdvice.class)
.orShould().beAnnotatedWith(org.springframework.web.bind.annotation.RestControllerAdvice.class)
.because("Classes named *Controller should be registered as Spring @RestController or @ControllerAdvice or @RestControllerAdvice beans.");
}