+ * These rules enforce structural integrity and code quality patterns: + *
+ * Every REST controller in SW360 must: + *
+ * SW360 REST follows a consistent pattern where: + *
+ * SW360 prefers constructor injection via Lombok's + * {@code @RequiredArgsConstructor} over field-level {@code @Autowired}. + *
+ * Note: Lombok annotations ({@code @RequiredArgsConstructor}, {@code @NonNull})
+ * have {@code @Retention(SOURCE)} and are erased after compilation, so ArchUnit
+ * cannot verify their presence. Instead, we validate the absence of
+ * field-level {@code @Autowired} as a proxy for constructor injection.
+ */
+@DisplayName("Dependency Injection Rules")
+class DependencyInjectionRulesTest extends SW360ArchitectureTest {
+
+ @Test
+ @DisplayName("Service classes should not use field injection with @Autowired")
+ void serviceClassesShouldNotUseFieldInjection() {
+ ArchRule rule = noFields()
+ .that().areDeclaredInClassesThat().areAnnotatedWith(Service.class)
+ .and().areDeclaredInClassesThat().resideOutsideOfPackage("..resourceserver.security..")
+ .should().beAnnotatedWith(Autowired.class)
+ .as("@Service classes (outside security package) should not use field-level @Autowired; " +
+ "use constructor injection via @RequiredArgsConstructor instead");
+
+ rule.check(restClasses);
+ }
+
+ @Test
+ @DisplayName("Spring beans should prefer constructor injection over field injection")
+ void springBeansShouldPreferConstructorInjection() {
+ ArchCondition
+ * The canonical layer flow is:
+ *
+ * Note: The {@code core} package contains {@code JacksonCustomizations} which
+ * intentionally references domain-specific mixin types for JSON serialization.
+ * Therefore core-to-domain dependency checks exclude {@code JacksonCustomizations}.
+ */
+@DisplayName("Layered Architecture Rules")
+class LayerDependencyRulesTest extends SW360ArchitectureTest {
+
+ @Test
+ @DisplayName("Security package should not depend on any specific controller")
+ void securityShouldNotDependOnControllers() {
+ ArchRule rule = noClasses()
+ .that().resideInAPackage("..resourceserver.security..")
+ .should().dependOnClassesThat()
+ .resideInAnyPackage(
+ "..resourceserver.project..",
+ "..resourceserver.component..",
+ "..resourceserver.release..",
+ "..resourceserver.license..",
+ "..resourceserver.vulnerability..",
+ "..resourceserver.packages..",
+ "..resourceserver.obligation..",
+ "..resourceserver.vendor..",
+ "..resourceserver.attachment..",
+ "..resourceserver.changelog..",
+ "..resourceserver.clearingrequest..",
+ "..resourceserver.moderationrequest..",
+ "..resourceserver.ecc..",
+ "..resourceserver.report..",
+ "..resourceserver.schedule..",
+ "..resourceserver.search..",
+ "..resourceserver.department..",
+ "..resourceserver.databasesanitation..",
+ "..resourceserver.importexport..",
+ "..resourceserver.licenseinfo..",
+ "..resourceserver.admin.."
+ )
+ .as("Security classes should not depend on any domain-specific controller or service package");
+
+ rule.check(restClasses);
+ }
+
+ @Test
+ @DisplayName("Core package (except JacksonCustomizations and RestControllerHelper) should not depend on domain packages")
+ void coreShouldNotDependOnDomainPackages() {
+ ArchRule rule = noClasses()
+ .that().resideInAPackage("..resourceserver.core..")
+ .and().doNotHaveSimpleName("RestControllerHelper")
+ .and().doNotHaveSimpleName("AwareOfRestServices")
+ .and().doNotHaveSimpleName("ThriftServiceProvider")
+ .and().haveNameNotMatching(".*JacksonCustomizations.*")
+ .and().haveNameNotMatching(".*Serializer")
+ .should().dependOnClassesThat()
+ .resideInAnyPackage(
+ "..resourceserver.project..",
+ "..resourceserver.component..",
+ "..resourceserver.release..",
+ "..resourceserver.license..",
+ "..resourceserver.vulnerability..",
+ "..resourceserver.packages..",
+ "..resourceserver.obligation..",
+ "..resourceserver.vendor..",
+ "..resourceserver.changelog..",
+ "..resourceserver.clearingrequest..",
+ "..resourceserver.moderationrequest.."
+ )
+ .as("Core classes (except JacksonCustomizations, RestControllerHelper, AwareOfRestServices, " +
+ "and custom Serializers) should not depend on domain-specific packages");
+
+ rule.check(restClasses);
+ }
+
+ @Test
+ @DisplayName("Filter package should not depend on domain-specific packages")
+ void filterShouldNotDependOnDomainPackages() {
+ ArchRule rule = noClasses()
+ .that().resideInAPackage("..resourceserver.filter..")
+ .should().dependOnClassesThat()
+ .resideInAnyPackage(
+ "..resourceserver.project..",
+ "..resourceserver.component..",
+ "..resourceserver.release..",
+ "..resourceserver.license..",
+ "..resourceserver.vulnerability..",
+ "..resourceserver.packages..",
+ "..resourceserver.obligation.."
+ )
+ .as("Filter classes should not depend on domain-specific packages");
+
+ rule.check(restClasses);
+ }
+}
diff --git a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/architecture/LoggingStandardRulesTest.java b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/architecture/LoggingStandardRulesTest.java
new file mode 100644
index 0000000000..c6f9c7a6bf
--- /dev/null
+++ b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/architecture/LoggingStandardRulesTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright Siemens AG, 2025. Part of the SW360 Portal Project.
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.sw360.rest.resourceserver.architecture;
+
+import com.tngtech.archunit.core.domain.JavaClass;
+import com.tngtech.archunit.lang.ArchCondition;
+import com.tngtech.archunit.lang.ArchRule;
+import com.tngtech.archunit.lang.ConditionEvents;
+import com.tngtech.archunit.lang.SimpleConditionEvent;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
+
+/**
+ * Validates that SW360 REST module follows proper logging standards.
+ *
+ * SW360 uses Log4j2 ({@code LogManager.getLogger()}) or
+ * Lombok ({@code @Slf4j}) for logging.
+ * The following are strictly prohibited:
+ *
+ * These rules enforce the naming patterns documented in
+ * {@code .github/instructions/sw360_backend.instructions.md}:
+ *
+ * SW360 uses SpringDoc OpenAPI for API documentation. Every public REST
+ * endpoint should be annotated with {@code @Operation} to ensure
+ * comprehensive, auto-generated API documentation.
+ */
+@DisplayName("OpenAPI Documentation Rules")
+class OpenApiDocumentationRulesTest extends SW360ArchitectureTest {
+
+ @Test
+ @DisplayName("REST endpoint methods should be annotated with @Operation")
+ void endpointMethodsShouldHaveOperationAnnotation() {
+ ArchCondition
+ * Each domain entity (project, component, release, etc.) has its own
+ * sub-package under {@code ..rest.resourceserver.
+ * Note: SW360 domain packages have intentional cross-dependencies
+ * (e.g., a project references releases, components reference projects, etc.)
+ * so cyclic dependency checks are not applicable at the REST module level.
+ */
+@DisplayName("Package Structure Rules")
+class PackageStructureRulesTest extends SW360ArchitectureTest {
+
+ @Test
+ @DisplayName("Controller classes should reside in their domain sub-package, not in core")
+ void controllersShouldResideInDomainPackages() {
+ ArchRule rule = classes()
+ .that().haveSimpleNameEndingWith("Controller")
+ .should().resideOutsideOfPackage("..resourceserver.core..")
+ .as("Controller classes should reside in domain packages " +
+ "(e.g., ..project.ProjectController), not in the core package");
+
+ rule.check(restClasses);
+ }
+
+ @Test
+ @DisplayName("Serializer classes should reside in the core.serializer package")
+ void serializersShouldResideInCoreSerializerPackage() {
+ ArchRule rule = classes()
+ .that().haveSimpleNameEndingWith("Serializer")
+ .should().resideInAPackage("..resourceserver.core.serializer..")
+ .as("Custom JSON serializer classes should reside in the core.serializer package");
+
+ rule.check(restClasses);
+ }
+
+ @Test
+ @DisplayName("Exception classes should reside in the core package")
+ void exceptionClassesShouldResideInCore() {
+ ArchRule rule = classes()
+ .that().areAssignableTo(Exception.class)
+ .and().resideInAPackage("..rest.resourceserver..")
+ .and().doNotHaveFullyQualifiedName(Exception.class.getName())
+ .should().resideInAPackage("..resourceserver.core..")
+ .as("Custom exception classes should reside in the core package");
+
+ rule.check(restClasses);
+ }
+
+ @Test
+ @DisplayName("Security-related classes should reside in the security package")
+ void securityClassesShouldResideInSecurityPackage() {
+ ArchRule rule = classes()
+ .that().haveSimpleNameContaining("Authentication")
+ .and().resideInAPackage("..rest.resourceserver..")
+ .should().resideInAPackage("..resourceserver.security..")
+ .orShould().resideInAPackage("..resourceserver.core..")
+ .as("Authentication-related classes should reside in the security or core package");
+
+ rule.check(restClasses);
+ }
+
+ @Test
+ @DisplayName("No class should depend on internal JDK sun.* packages")
+ void noClassesShouldDependOnInternalJdkPackages() {
+ ArchRule rule = noClasses()
+ .that().resideInAPackage("..rest.resourceserver..")
+ .should().dependOnClassesThat()
+ .resideInAPackage("sun..")
+ .as("No class should depend on internal JDK (sun..) packages");
+
+ rule.check(restClasses);
+ }
+}
diff --git a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/architecture/SW360ArchitectureTest.java b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/architecture/SW360ArchitectureTest.java
new file mode 100644
index 0000000000..895ccce46d
--- /dev/null
+++ b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/architecture/SW360ArchitectureTest.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright Siemens AG, 2025. Part of the SW360 Portal Project.
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.sw360.rest.resourceserver.architecture;
+
+import com.tngtech.archunit.core.domain.JavaClasses;
+import com.tngtech.archunit.core.importer.ClassFileImporter;
+import com.tngtech.archunit.core.importer.ImportOption;
+import org.junit.jupiter.api.BeforeAll;
+
+/**
+ * Base configuration for SW360 ArchUnit architecture tests.
+ *
+ * Provides a shared {@link JavaClasses} instance that imports the production
+ * classes of the REST resource-server module. All concrete architecture test
+ * classes should reference {@link #restClasses} for their rule checks.
+ */
+abstract class SW360ArchitectureTest {
+
+ static JavaClasses restClasses;
+
+ @BeforeAll
+ static void importClasses() {
+ restClasses = new ClassFileImporter()
+ .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
+ .importPackages("org.eclipse.sw360.rest.resourceserver");
+ }
+}
diff --git a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/architecture/SecurityAnnotationRulesTest.java b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/architecture/SecurityAnnotationRulesTest.java
new file mode 100644
index 0000000000..504725e151
--- /dev/null
+++ b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/architecture/SecurityAnnotationRulesTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright Siemens AG, 2025. Part of the SW360 Portal Project.
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.sw360.rest.resourceserver.architecture;
+
+import com.tngtech.archunit.core.domain.JavaClass;
+import com.tngtech.archunit.lang.ArchCondition;
+import com.tngtech.archunit.lang.ArchRule;
+import com.tngtech.archunit.lang.ConditionEvents;
+import com.tngtech.archunit.lang.SimpleConditionEvent;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.security.access.prepost.PreAuthorize;
+
+import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
+import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
+
+/**
+ * Validates security annotation patterns in the SW360 REST module.
+ *
+ * SW360 enforces authorization at two levels:
+ *
+ * Important: Class-level {@code @PreAuthorize} should be avoided on
+ * most controllers because it overrides the filter chain's per-HTTP-method rules and
+ * would block READ-only users from accessing GET endpoints.
+ *
+ * These rules focus on:
+ *
+ * These rules ensure proper use of Spring stereotypes, configuration,
+ * web-layer annotations, and bean definition patterns.
+ */
+@DisplayName("Spring Framework Best Practice Rules")
+class SpringFrameworkRulesTest extends SW360ArchitectureTest {
+
+ @Test
+ @DisplayName("No class should use @Controller — use @RestController instead")
+ void noClassShouldUseControllerAnnotation() {
+ ArchRule rule = noClasses()
+ .that().resideInAPackage("..rest.resourceserver..")
+ .should().beAnnotatedWith(org.springframework.stereotype.Controller.class)
+ .as("Use @RestController (not @Controller) for REST API endpoints");
+
+ rule.check(restClasses);
+ }
+
+ @Test
+ @DisplayName("@SpringBootApplication should only exist in the root package")
+ void springBootApplicationShouldBeInRootPackage() {
+ ArchRule rule = classes()
+ .that().areAnnotatedWith(SpringBootApplication.class)
+ .should().resideInAPackage("..rest.resourceserver")
+ .as("@SpringBootApplication class should reside in the root resourceserver package");
+
+ rule.check(restClasses);
+ }
+
+ @Test
+ @DisplayName("@Configuration classes should not implement business logic interfaces")
+ void configurationClassesShouldNotBeServices() {
+ ArchRule rule = noClasses()
+ .that().areAnnotatedWith(Configuration.class)
+ .should().beAnnotatedWith(Service.class)
+ .as("@Configuration classes should not also be @Service — separate concerns");
+
+ rule.check(restClasses);
+ }
+
+ @Test
+ @DisplayName("@Service classes should not be annotated with @RestController")
+ void servicesShouldNotBeControllers() {
+ ArchRule rule = noClasses()
+ .that().areAnnotatedWith(Service.class)
+ .should().beAnnotatedWith(RestController.class)
+ .as("@Service classes should not also be @RestController — keep layers separate");
+
+ rule.check(restClasses);
+ }
+
+ @Test
+ @DisplayName("@Component classes should not be annotated with @Service or @RestController")
+ void componentsShouldNotBeServicesOrControllers() {
+ ArchRule rule = noClasses()
+ .that().areAnnotatedWith(Component.class)
+ .should().beAnnotatedWith(Service.class)
+ .orShould().beAnnotatedWith(RestController.class)
+ .as("@Component should not also be @Service or @RestController — " +
+ "use the most specific stereotype");
+
+ rule.check(restClasses);
+ }
+
+ @Test
+ @DisplayName("@RestController classes should not use @ResponseBody — it is implied")
+ void restControllersShouldNotUseResponseBody() {
+ ArchRule rule = noClasses()
+ .that().areAnnotatedWith(RestController.class)
+ .should().beAnnotatedWith(
+ org.springframework.web.bind.annotation.ResponseBody.class)
+ .as("@RestController already implies @ResponseBody — do not add it explicitly");
+
+ rule.check(restClasses);
+ }
+
+ @Test
+ @DisplayName("@RestController classes should not define @Bean methods — use @Configuration instead")
+ void controllersShouldNotDefineBeans() {
+ ArchCondition
+ * In the SW360 architecture, the REST module communicates with backend services
+ * exclusively through Thrift clients (via {@code ThriftClients}).
+ * Direct access to:
+ *
+ * Controller → Service → Core / Security / Filter
+ *
+ * The {@code security} and {@code filter} packages should not depend on specific
+ * domain packages (project, component, release, etc.).
+ *
+ *
+ */
+@DisplayName("Logging Standard Rules")
+class LoggingStandardRulesTest extends SW360ArchitectureTest {
+
+ @Test
+ @DisplayName("No class should use System.out")
+ void noClassShouldUseSystemOut() {
+ ArchRule rule = noClasses()
+ .should().accessFieldWhere(
+ com.tngtech.archunit.core.domain.JavaFieldAccess.Predicates
+ .target(com.tngtech.archunit.base.DescribedPredicate.describe(
+ "System.out",
+ target -> target.getOwner().isEquivalentTo(System.class)
+ && target.getName().equals("out")))
+ )
+ .as("No class should use System.out — use Log4j2 logger instead");
+
+ rule.check(restClasses);
+ }
+
+ @Test
+ @DisplayName("No class should use System.err")
+ void noClassShouldUseSystemErr() {
+ ArchRule rule = noClasses()
+ .should().accessFieldWhere(
+ com.tngtech.archunit.core.domain.JavaFieldAccess.Predicates
+ .target(com.tngtech.archunit.base.DescribedPredicate.describe(
+ "System.err",
+ target -> target.getOwner().isEquivalentTo(System.class)
+ && target.getName().equals("err")))
+ )
+ .as("No class should use System.err — use Log4j2 logger instead");
+
+ rule.check(restClasses);
+ }
+
+ @Test
+ @DisplayName("No class should call printStackTrace()")
+ void noClassShouldCallPrintStackTrace() {
+ ArchCondition
+ *
+ */
+@DisplayName("Naming Convention Rules")
+class NamingConventionRulesTest extends SW360ArchitectureTest {
+
+ @Test
+ @DisplayName("Classes annotated with @RestController should have name ending with 'Controller'")
+ void restControllersShouldBeNamedController() {
+ ArchRule rule = classes()
+ .that().areAnnotatedWith(RestController.class)
+ .should().haveSimpleNameEndingWith("Controller")
+ .as("All @RestController classes should be named *Controller");
+
+ rule.check(restClasses);
+ }
+
+ @Test
+ @DisplayName("Classes annotated with @BasePathAwareController should have name ending with 'Controller'")
+ void basePathAwareControllersShouldBeNamedController() {
+ ArchRule rule = classes()
+ .that().areAnnotatedWith(BasePathAwareController.class)
+ .should().haveSimpleNameEndingWith("Controller")
+ .as("All @BasePathAwareController classes should be named *Controller");
+
+
+ rule.check(restClasses);
+ }
+
+ @Test
+ @DisplayName("Service classes in domain packages should have name ending with 'Service'")
+ void serviceClassesShouldBeNamedWithService() {
+ ArchRule rule = classes()
+ .that().areAnnotatedWith(org.springframework.stereotype.Service.class)
+ .and().resideOutsideOfPackage("..resourceserver.core..")
+ .and().resideOutsideOfPackage("..resourceserver.security..")
+ .should().haveSimpleNameEndingWith("Service")
+ .orShould().haveSimpleNameEndingWith("Services")
+ .as("@Service classes in domain packages should have a name ending with 'Service' or 'Services'");
+
+ rule.check(restClasses);
+ }
+
+ @Test
+ @DisplayName("Configuration classes should have name ending with 'Configuration' or 'Customizations'")
+ void configurationClassesShouldBeNamedProperly() {
+ ArchRule rule = classes()
+ .that().areAnnotatedWith(org.springframework.context.annotation.Configuration.class)
+ .and().resideOutsideOfPackage("..resourceserver")
+ .should().haveSimpleNameEndingWith("Configuration")
+ .orShould().haveSimpleNameEndingWith("Customizations")
+ .as("@Configuration classes should be named *Configuration or *Customizations");
+
+ rule.check(restClasses);
+ }
+
+ @Test
+ @DisplayName("Resource Processors should have name ending with 'ResourceProcessor'")
+ void resourceProcessorsShouldBeNamedProperly() {
+ ArchRule rule = classes()
+ .that().haveSimpleNameEndingWith("ResourceProcessor")
+ .should().beAnnotatedWith(org.springframework.stereotype.Component.class)
+ .as("ResourceProcessor classes should be Spring @Component beans");
+
+ rule.check(restClasses);
+ }
+}
diff --git a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/architecture/OpenApiDocumentationRulesTest.java b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/architecture/OpenApiDocumentationRulesTest.java
new file mode 100644
index 0000000000..fb1fb00304
--- /dev/null
+++ b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/architecture/OpenApiDocumentationRulesTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright Siemens AG, 2025. Part of the SW360 Portal Project.
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.sw360.rest.resourceserver.architecture;
+
+import com.tngtech.archunit.core.domain.JavaClass;
+import com.tngtech.archunit.lang.ArchCondition;
+import com.tngtech.archunit.lang.ArchRule;
+import com.tngtech.archunit.lang.ConditionEvents;
+import com.tngtech.archunit.lang.SimpleConditionEvent;
+import io.swagger.v3.oas.annotations.Operation;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.web.bind.annotation.*;
+
+import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
+
+/**
+ * Validates OpenAPI documentation standards in the SW360 REST module.
+ *
+ *
+ *
+ *
+ */
+@DisplayName("Security Annotation Rules")
+class SecurityAnnotationRulesTest extends SW360ArchitectureTest {
+
+
+ @Test
+ @DisplayName("No class should use deprecated @Secured — use @PreAuthorize instead")
+ void noClassShouldUseSecuredAnnotation() {
+ ArchRule rule = noClasses()
+ .that().resideInAPackage("..rest.resourceserver..")
+ .should().beAnnotatedWith(
+ org.springframework.security.access.annotation.Secured.class)
+ .as("Use @PreAuthorize (not @Secured) — SW360 standardizes on @PreAuthorize " +
+ "for method-level security");
+
+ rule.check(restClasses);
+ }
+
+ @Test
+ @DisplayName("No class should use @RolesAllowed — use @PreAuthorize instead")
+ void noClassShouldUseRolesAllowedAnnotation() {
+ ArchRule rule = noClasses()
+ .that().resideInAPackage("..rest.resourceserver..")
+ .should().dependOnClassesThat()
+ .haveFullyQualifiedName("jakarta.annotation.security.RolesAllowed")
+ .as("Use @PreAuthorize (not @RolesAllowed) — SW360 standardizes on @PreAuthorize " +
+ "for method-level security");
+
+ rule.check(restClasses);
+ }
+
+ @Test
+ @DisplayName("No class should use deprecated @EnableGlobalMethodSecurity — use @EnableMethodSecurity")
+ void noClassShouldUseDeprecatedMethodSecurityAnnotation() {
+ ArchRule rule = noClasses()
+ .that().resideInAPackage("..rest.resourceserver..")
+ .should().dependOnClassesThat()
+ .haveFullyQualifiedName(
+ "org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity")
+ .as("Use @EnableMethodSecurity (not deprecated @EnableGlobalMethodSecurity) — " +
+ "SW360 uses Spring Security 6.x");
+
+ rule.check(restClasses);
+ }
+
+ @Test
+ @DisplayName("@PreAuthorize values should only use known SW360 authorities")
+ void preAuthorizeValuesShouldUseKnownAuthorities() {
+ ArchCondition
+ *
+ * is prohibited from the REST layer.
+ */
+@DisplayName("Thrift Service Boundary Rules")
+class ThriftServiceBoundaryRulesTest extends SW360ArchitectureTest {
+
+ @Test
+ @DisplayName("REST module should not directly access database handlers")
+ void restModuleShouldNotAccessDatabaseHandlers() {
+ ArchRule rule = noClasses()
+ .that().resideInAPackage("..rest.resourceserver..")
+ .should().dependOnClassesThat()
+ .haveSimpleNameEndingWith("DatabaseHandler")
+ .as("REST module must not bypass Thrift services to access database handlers directly");
+
+ rule.check(restClasses);
+ }
+
+ @Test
+ @DisplayName("REST module should not directly access repository classes")
+ void restModuleShouldNotAccessRepositories() {
+ ArchRule rule = noClasses()
+ .that().resideInAPackage("..rest.resourceserver..")
+ .should().dependOnClassesThat()
+ .resideInAPackage("..datahandler.db..")
+ .as("REST module must not access backend repository classes in the datahandler.db package");
+
+ rule.check(restClasses);
+ }
+
+ @Test
+ @DisplayName("REST module (except health indicator) should not directly use Cloudant/CouchDB client classes")
+ void restModuleShouldNotAccessCouchDbDirectly() {
+ ArchRule rule = noClasses()
+ .that().resideInAPackage("..rest.resourceserver..")
+ .and().doNotHaveSimpleName("SW360RestHealthIndicator")
+ .should().dependOnClassesThat()
+ .resideInAPackage("..datahandler.cloudantclient..")
+ .as("REST module (except health indicator) must not access CouchDB/Cloudant client classes directly — " +
+ "use Thrift services via ThriftClients");
+
+ rule.check(restClasses);
+ }
+}