Added tests

This commit is contained in:
Andreas Dahm committed 2026-05-11 14:31:38 +02:00
1 parent 6b91463c4a
commit 1eb445e7a0
6 files changed
+187

No files matched your search

@@ -0,0 +1,102 @@
package core_service.architecture;
import core_service.shared.test.AbstractArchitectureTest;
import com.tngtech.archunit.core.domain.JavaClass;
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 package boundaries in core-service.
*
* Notes on Core-specific structure (differs from auth-service):
* - JPA entities live in `..domain..` (not `..persistence..`).
* - Repository interfaces live in `..repo..`.
* - `steam_adapter` is an outbound port consumed by feature modules
* (e.g. `library`), so strict slice independence is relaxed for it.
*/
@AnalyzeClasses(packages = "core_service", importOptions = ImportOption.DoNotIncludeTests.class)
class ArchitectureTest extends AbstractArchitectureTest {
// ===================================================================================
// 1. MODULE INDEPENDENCE (Slices)
// ===================================================================================
@ArchTest
static final ArchRule modules_should_not_depend_on_each_other = SlicesRuleDefinition.slices()
.matching("core_service.modules.(*)..")
.should().notDependOnEachOther()
.ignoreDependency(
JavaClass.Predicates.resideInAPackage("core_service.modules.library.."),
JavaClass.Predicates.resideInAPackage("core_service.modules.steam_adapter.."))
.because("Feature modules should be isolated; steam_adapter is an explicit shared port.");
@ArchTest
static final ArchRule no_cyclic_dependencies = SlicesRuleDefinition.slices()
.matching("core_service.(*)..")
.should().beFreeOfCycles()
.because("Cyclic dependencies make the code unmaintainable and tightly coupled.");
// ===================================================================================
// 2. LAYER DEPENDENCY DIRECTION
// ===================================================================================
@ArchTest
static final ArchRule apis_should_not_depend_on_repos = noClasses()
.that().resideInAPackage("..api..")
.should().dependOnClassesThat().resideInAPackage("..repo..")
.because("Controllers must delegate to Services and not access repositories directly.");
@ArchTest
static final ArchRule repos_should_not_depend_on_apis = noClasses()
.that().resideInAPackage("..repo..")
.should().dependOnClassesThat().resideInAPackage("..api..")
.because("Persistence layer must be independent of the API layer.");
@ArchTest
static final ArchRule repos_should_be_interfaces_named_repository = classes()
.that().resideInAPackage("..repo..")
.should().haveSimpleNameEndingWith("Repository")
.because("Repository interfaces are the persistence boundary; naming makes them explicit.");
// ===================================================================================
// 3. SHARED / CONFIGURATION RULES
// ===================================================================================
@ArchTest
static final ArchRule shared_should_not_depend_on_modules = noClasses()
.that().resideInAPackage("core_service.shared..")
.should().dependOnClassesThat().resideInAPackage("core_service.modules..")
.because("Shared classes should be independent of specific modules.");
@ArchTest
static final ArchRule modules_should_not_depend_on_global_configuration = noClasses()
.that().resideInAPackage("core_service.modules..")
.should().dependOnClassesThat().resideInAPackage("core_service.configuration..")
.because("Module logic should not depend on global Spring configuration classes.");
// ===================================================================================
// 4. SPRING ANNOTATION CONVENTIONS
// ===================================================================================
@ArchTest
static final ArchRule services_should_be_annotated = classes()
.that().resideInAPackage("..modules..service..")
.and().haveSimpleNameEndingWith("Service")
.should().beAnnotatedWith(org.springframework.stereotype.Service.class)
.because("Classes named *Service in modules should be registered as Spring @Service beans.");
@ArchTest
static final ArchRule controllers_should_be_annotated = classes()
.that().resideInAPackage("..api..")
.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 in 'api' package named *Controller should be Spring REST controllers.");
}
@@ -0,0 +1,10 @@
package core_service.shared.test;
import org.junit.jupiter.api.Tag;
/**
* Base class for ArchUnit tests.
*/
@Tag("ArchitectureTest")
public abstract class AbstractArchitectureTest {
}
@@ -0,0 +1,24 @@
package core_service.shared.test;
import org.junit.jupiter.api.Tag;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.test.context.ActiveProfiles;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
/**
* Base class for integration tests.
* Full Spring context with Testcontainers.
*/
@Tag("IntegrationTest")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("dev")
@Testcontainers
public abstract class AbstractIntegrationTest {
@Container
@ServiceConnection
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:17-alpine");
}
@@ -0,0 +1,24 @@
package core_service.shared.test;
import org.junit.jupiter.api.Tag;
import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest;
import org.springframework.boot.jdbc.test.autoconfigure.AutoConfigureTestDatabase;
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;
/**
* Base class for persistence tests.
* Slices only the persistence layer with Testcontainers.
*/
@Tag("PersistenceTest")
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Testcontainers
public abstract class AbstractPersistenceTest {
@Container
@ServiceConnection
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:17-alpine");
}
@@ -0,0 +1,14 @@
package core_service.shared.test;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
/**
* Base class for unit tests.
* Isolated from Spring context.
*/
@Tag("UnitTest")
@ExtendWith(MockitoExtension.class)
public abstract class AbstractUnitTest {
}
@@ -0,0 +1,13 @@
package core_service.shared.test;
import org.junit.jupiter.api.Tag;
import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest;
/**
* Base class for WebMvc tests.
* Slices the web layer.
*/
@Tag("WebMvcTest")
@WebMvcTest
public abstract class AbstractWebMvcTest {
}