Copilot commented on code in PR #6044:
URL: https://github.com/apache/fineract/pull/6044#discussion_r3479565029


##########
fineract-provider/src/test/java/org/apache/fineract/architecture/AllModulesCrossFeatureBoundaryTest.java:
##########
@@ -0,0 +1,211 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.architecture;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import com.tngtech.archunit.core.domain.JavaClass;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.modulith.core.ApplicationModule;
+import org.springframework.modulith.core.ApplicationModuleDependency;
+import org.springframework.modulith.core.ApplicationModules;
+
+class AllModulesCrossFeatureBoundaryTest {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(AllModulesCrossFeatureBoundaryTest.class);
+
+    private static final String BASE = "org.apache.fineract";
+
+    private static final Set<String> FOUNDATION_ARTIFACTS = 
Set.of("fineract-core", "fineract-command");
+
+    private static final Pattern FINERACT_ARTIFACT = 
Pattern.compile("(?<=/)fineract-[a-z0-9-]+");
+
+    private static ApplicationModules modules;
+
+    private static ApplicationModules modules() {
+        if (modules == null) {
+            modules = ApplicationModules.of(BASE);
+        }
+        return modules;
+    }
+
+    private static String featureKey(String typeName) {
+        String prefix = BASE + ".";
+        if (!typeName.startsWith(prefix)) {
+            return typeName;
+        }
+        String[] parts = typeName.substring(prefix.length()).split("\\.");
+        return parts.length >= 2 ? parts[0] + "." + parts[1] : parts[0];
+    }
+
+    private static String owningArtifact(JavaClass type) {
+        return type.getSource().map(source -> 
source.getUri().toString()).map(uri -> {
+            Matcher matcher = FINERACT_ARTIFACT.matcher(uri);
+            String artifact = null;
+            while (matcher.find()) {
+                artifact = matcher.group();
+            }
+            return artifact == null ? uri : artifact.replaceAll("-\\d.*$", "");
+        }).orElse("(unknown-source)");
+    }
+
+    private static boolean isFoundation(JavaClass type) {
+        return type.getSource() //
+                .map(source -> source.getUri().toString()) //
+                .map(uri -> 
FOUNDATION_ARTIFACTS.stream().anyMatch(uri::contains)) //
+                .orElse(false);
+    }
+
+    @Test
+    void printAllModulesCrossFeatureDependencyReport() {

Review Comment:
   `printAllModulesCrossFeatureDependencyReport()` is annotated as a normal 
test but only produces a potentially very large INFO log without assertions. 
This will run on every build and can add noticeable time/log noise; consider 
gating it behind a system property (or disabling) so it only runs when 
explicitly requested.



##########
fineract-provider/src/test/java/org/apache/fineract/architecture/AllModulesCrossFeatureBoundaryTest.java:
##########
@@ -0,0 +1,211 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.architecture;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import com.tngtech.archunit.core.domain.JavaClass;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.modulith.core.ApplicationModule;
+import org.springframework.modulith.core.ApplicationModuleDependency;
+import org.springframework.modulith.core.ApplicationModules;
+
+class AllModulesCrossFeatureBoundaryTest {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(AllModulesCrossFeatureBoundaryTest.class);
+
+    private static final String BASE = "org.apache.fineract";
+
+    private static final Set<String> FOUNDATION_ARTIFACTS = 
Set.of("fineract-core", "fineract-command");
+
+    private static final Pattern FINERACT_ARTIFACT = 
Pattern.compile("(?<=/)fineract-[a-z0-9-]+");
+
+    private static ApplicationModules modules;
+
+    private static ApplicationModules modules() {
+        if (modules == null) {
+            modules = ApplicationModules.of(BASE);
+        }
+        return modules;
+    }
+
+    private static String featureKey(String typeName) {
+        String prefix = BASE + ".";
+        if (!typeName.startsWith(prefix)) {
+            return typeName;
+        }
+        String[] parts = typeName.substring(prefix.length()).split("\\.");
+        return parts.length >= 2 ? parts[0] + "." + parts[1] : parts[0];
+    }
+
+    private static String owningArtifact(JavaClass type) {
+        return type.getSource().map(source -> 
source.getUri().toString()).map(uri -> {
+            Matcher matcher = FINERACT_ARTIFACT.matcher(uri);
+            String artifact = null;
+            while (matcher.find()) {
+                artifact = matcher.group();
+            }
+            return artifact == null ? uri : artifact.replaceAll("-\\d.*$", "");
+        }).orElse("(unknown-source)");
+    }
+
+    private static boolean isFoundation(JavaClass type) {
+        return type.getSource() //
+                .map(source -> source.getUri().toString()) //
+                .map(uri -> 
FOUNDATION_ARTIFACTS.stream().anyMatch(uri::contains)) //
+                .orElse(false);
+    }
+
+    @Test
+    void printAllModulesCrossFeatureDependencyReport() {
+        Map<String, ApplicationModule> sortedModules = new TreeMap<>();
+        modules().forEach(module -> 
sortedModules.put(module.getBasePackage().getName(), module));
+
+        int totalViolations = 0;
+        int cleanModules = 0;
+
+        for (Map.Entry<String, ApplicationModule> moduleEntry : 
sortedModules.entrySet()) {
+            String moduleName = moduleEntry.getKey();
+            ApplicationModule module = moduleEntry.getValue();
+
+            Map<String, Set<String>> sourceTypeToTargets = new TreeMap<>();
+            Set<String> violationFeatures = new TreeSet<>();
+            Set<String> allowedFromCore = new TreeSet<>();
+
+            
module.getDirectDependencies(modules()).stream().forEach((ApplicationModuleDependency
 dependency) -> {
+                JavaClass targetType = dependency.getTargetType();
+                String sourceType = dependency.getSourceType().getName();
+                String featureKey = featureKey(targetType.getName());
+                String artifact = owningArtifact(targetType);
+                boolean foundation = isFoundation(targetType);
+
+                String label = featureKey + "  [" + artifact + (foundation ? " 
: foundation]" : " : VIOLATION]");
+                sourceTypeToTargets.computeIfAbsent(sourceType, key -> new 
TreeSet<>()).add(label);
+
+                if (foundation) {
+                    allowedFromCore.add(featureKey + " (" + artifact + ")");
+                } else {
+                    violationFeatures.add(featureKey + " (" + artifact + ")");
+                }
+            });
+
+            LOG.info("==== " + moduleName + " cross-feature dependency report 
(base = " + BASE + ") ====");
+            LOG.info("-- source type -> referenced feature packages [owning 
artifact : status] --");
+            if (sourceTypeToTargets.isEmpty()) {
+                LOG.info("  (no outgoing dependencies)");
+            } else {
+                sourceTypeToTargets.forEach((source, targets) -> LOG.info("  " 
+ source + "  ->  " + targets));
+            }
+            LOG.info("-- allowed (in fineract-core / fineract-command) --");
+            LOG.info("  " + allowedFromCore);
+            LOG.info("-- VIOLATIONS (in some other fineract-* module) --");
+            LOG.info("  " + violationFeatures);
+
+            totalViolations += violationFeatures.size();
+            if (violationFeatures.isEmpty()) {
+                cleanModules++;
+            }
+        }
+
+        LOG.info("==== SUMMARY ====");
+        LOG.info("modules total        : " + sortedModules.size());
+        LOG.info("clean modules        : " + cleanModules);
+        LOG.info("modules with issues  : " + (sortedModules.size() - 
cleanModules));
+        LOG.info("total violations     : " + totalViolations);
+    }
+
+    @Test
+    void printModuleToModuleDependencyViolations() {

Review Comment:
   `printModuleToModuleDependencyViolations()` is a report-only method 
annotated as a normal test, so it will always run and emit large INFO logs. 
Consider gating it behind a system property (or disabling) so CI runs only the 
enforcement test by default.



##########
fineract-provider/dependencies.gradle:
##########
@@ -25,6 +25,9 @@ dependencies {
     }
 
     implementation(project(path: ':fineract-core'))
+    implementation 'org.springframework.modulith:spring-modulith-api'
+    testImplementation 'org.springframework.modulith:spring-modulith-core'
+    testImplementation 'org.springframework.modulith:spring-modulith-docs'

Review Comment:
   The Spring Modulith dependencies are only used from test sources in this 
module, but `spring-modulith-api` is added as an `implementation` dependency 
(runtime classpath) and `spring-modulith-docs` appears unused. Keeping these in 
test scope avoids pulling extra libs into the production runtime and keeps 
dependency graph smaller.



##########
fineract-provider/src/test/java/org/apache/fineract/architecture/AllModulesCrossFeatureBoundaryTest.java:
##########
@@ -0,0 +1,211 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.architecture;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import com.tngtech.archunit.core.domain.JavaClass;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.modulith.core.ApplicationModule;
+import org.springframework.modulith.core.ApplicationModuleDependency;
+import org.springframework.modulith.core.ApplicationModules;
+
+class AllModulesCrossFeatureBoundaryTest {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(AllModulesCrossFeatureBoundaryTest.class);
+
+    private static final String BASE = "org.apache.fineract";
+
+    private static final Set<String> FOUNDATION_ARTIFACTS = 
Set.of("fineract-core", "fineract-command");
+
+    private static final Pattern FINERACT_ARTIFACT = 
Pattern.compile("(?<=/)fineract-[a-z0-9-]+");
+
+    private static ApplicationModules modules;
+
+    private static ApplicationModules modules() {
+        if (modules == null) {
+            modules = ApplicationModules.of(BASE);
+        }
+        return modules;
+    }
+
+    private static String featureKey(String typeName) {
+        String prefix = BASE + ".";
+        if (!typeName.startsWith(prefix)) {
+            return typeName;
+        }
+        String[] parts = typeName.substring(prefix.length()).split("\\.");
+        return parts.length >= 2 ? parts[0] + "." + parts[1] : parts[0];
+    }
+
+    private static String owningArtifact(JavaClass type) {
+        return type.getSource().map(source -> 
source.getUri().toString()).map(uri -> {
+            Matcher matcher = FINERACT_ARTIFACT.matcher(uri);
+            String artifact = null;
+            while (matcher.find()) {
+                artifact = matcher.group();
+            }
+            return artifact == null ? uri : artifact.replaceAll("-\\d.*$", "");
+        }).orElse("(unknown-source)");
+    }
+
+    private static boolean isFoundation(JavaClass type) {
+        return type.getSource() //
+                .map(source -> source.getUri().toString()) //
+                .map(uri -> 
FOUNDATION_ARTIFACTS.stream().anyMatch(uri::contains)) //
+                .orElse(false);
+    }

Review Comment:
   `isFoundation()` uses substring matching (`uri.contains(...)`) which will 
incorrectly treat artifacts like `fineract-command-audit` or 
`fineract-command-jdbc` as allowed foundation dependencies because they contain 
`fineract-command`. This undermines the boundary rule and can let real 
cross-feature violations pass undetected. Compare against the normalized 
artifact name from `owningArtifact()` instead of substring-matching the raw URI.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to