diff --git a/docs/tests/unit/lombok-attendance-boilerplate.md b/docs/tests/unit/lombok-attendance-boilerplate.md index 4a4748e..7a836f3 100644 --- a/docs/tests/unit/lombok-attendance-boilerplate.md +++ b/docs/tests/unit/lombok-attendance-boilerplate.md @@ -6,7 +6,8 @@ - **Test class/method:** `com.lab.labtimesheet.feature.attendance.AttendanceLombokBoilerplateTest#generatedConstructorsPreserveParameterListsAndVisibility`, `com.lab.labtimesheet.feature.attendance.AttendanceLombokBoilerplateTest#immutableModelsRemainRecordsWithTheirComponentContracts`, - `com.lab.labtimesheet.feature.attendance.AttendanceLombokBoilerplateTest#entitiesExposeOnlyIntentionalPublicAndProtectedDeclaredMethods` + `com.lab.labtimesheet.feature.attendance.AttendanceLombokBoilerplateTest#entitiesExposeOnlyIntentionalPublicAndProtectedDeclaredMethods`, + `com.lab.labtimesheet.feature.attendance.AttendanceLombokBoilerplateTest#componentsExposeOnlyIntentionalPublicAndProtectedDeclaredMethods` - **Implementation commit:** `82ad8202fd31f77db8c3932a902dba07cee70894` ## Protected behavior @@ -21,7 +22,8 @@ Reflection inspects compiled `feature.attendance` classes rather than source spe parameter order and modifier, every immutable model's record components, and the exact public/protected declared method surface of each Attendance entity. The entity surface prevents generated bean getters/setters or entity `equals`/`hashCode`/`toString` widening while explicitly retaining `AttendanceRecordEntity#setCheckOutAt` and the -`LeaveRequestDayId` identity methods. +`LeaveRequestDayId` identity methods. Exact controller and service surfaces likewise prevent Lombok from exposing +collaborator getters/setters or generated `equals`/`hashCode`/`toString` methods. ## Hand-derived expected result @@ -29,7 +31,8 @@ Five injection-only components expose only their package-scoped dependency const retain protected no-argument construction alongside their intentional domain constructors, and the stateless domain service remains package-scoped. Seven immutable models remain records with the same component order and types. Entity method surfaces contain only intentional domain conversion/access/mutation methods; only the composite key owns -`equals` and `hashCode`, and no Attendance entity declares `toString`. +`equals` and `hashCode`, and no Attendance entity declares `toString`. Both controllers and all four Attendance +services expose only their existing route or application/domain operations, never their injected collaborators. ## RED @@ -66,7 +69,7 @@ export PATH="$JAVA_HOME/bin:$PATH" **Observed result** ```text -Tests run: 3, Failures: 0, Errors: 0, Skipped: 0 +Tests run: 4, Failures: 0, Errors: 0, Skipped: 0 BUILD SUCCESS Process exited 0. ``` @@ -80,7 +83,7 @@ export JAVA_HOME=/opt/homebrew/opt/openjdk@25 export PATH="$JAVA_HOME/bin:$PATH" export DOCKER_HOST=unix:///Users/sechmachine/.orbstack/run/docker.sock ./mvnw -Dtest='*Attendance*Test' test -Tests run: 38, Failures: 0, Errors: 0, Skipped: 0 +Tests run: 39, Failures: 0, Errors: 0, Skipped: 0 PostgreSQL 18.4 started and Flyway applied V1 for the persistence and concurrency contexts. BUILD SUCCESS Process exited 0. @@ -98,14 +101,6 @@ Additional verification on the same source tree: ./mvnw -DskipTests compile BUILD SUCCESS -./mvnw -Dtest=AttendanceLombokBoilerplateTest,AttendanceLayerStructureTest,AttendancePolicyTest,AttendanceServiceTest,AttendanceApplicationServiceTest,AttendanceControllerTest test -Tests run: 27, Failures: 0, Errors: 0, Skipped: 0 -BUILD SUCCESS - -./mvnw -Dtest=AttendancePersistenceIntegrationTest,AttendanceConcurrencyIntegrationTest test -Tests run: 8, Failures: 0, Errors: 0, Skipped: 0 -PostgreSQL 18.4; BUILD SUCCESS - ./mvnw -q -DskipTests compile dependency:build-classpath -Dmdep.outputFile=target/attendance-javadoc-classpath.txt javadoc -quiet -Xdoclint:all -d target/attendance-javadocs -classpath "target/classes:$(tr -d '\n' < target/attendance-javadoc-classpath.txt)" -sourcepath src/main/java -subpackages com.lab.labtimesheet.feature.attendance Process exited 0. The source frontend reported seven generated-constructor missing-comment warnings because it does not diff --git a/src/test/java/com/lab/labtimesheet/feature/attendance/AttendanceLombokBoilerplateTest.java b/src/test/java/com/lab/labtimesheet/feature/attendance/AttendanceLombokBoilerplateTest.java index 256b01f..5764e20 100644 --- a/src/test/java/com/lab/labtimesheet/feature/attendance/AttendanceLombokBoilerplateTest.java +++ b/src/test/java/com/lab/labtimesheet/feature/attendance/AttendanceLombokBoilerplateTest.java @@ -11,6 +11,7 @@ import com.lab.labtimesheet.feature.attendance.model.AttendancePolicy; import com.lab.labtimesheet.feature.attendance.model.AttendanceRecord; import com.lab.labtimesheet.feature.attendance.model.AttendanceRole; import com.lab.labtimesheet.feature.attendance.model.AttendanceViolations; +import com.lab.labtimesheet.feature.attendance.model.dto.AttendanceCurrentState; import com.lab.labtimesheet.feature.attendance.model.dto.AttendanceHistoryItem; import com.lab.labtimesheet.feature.attendance.model.dto.GlobalCalendarEvent; import com.lab.labtimesheet.feature.attendance.model.entity.AttendancePolicyEntity; @@ -31,6 +32,7 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.math.BigDecimal; +import java.security.Principal; import java.time.Clock; import java.time.Instant; import java.time.LocalDate; @@ -38,8 +40,11 @@ import java.time.LocalTime; import java.time.ZoneId; import java.util.Arrays; import java.util.List; +import java.util.Optional; import java.util.Set; import org.junit.jupiter.api.Test; +import org.springframework.ui.Model; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; class AttendanceLombokBoilerplateTest { @@ -213,6 +218,120 @@ class AttendanceLombokBoilerplateTest { assertMethodSurface(LeaveRequestEntity.class); } + @Test + void componentsExposeOnlyIntentionalPublicAndProtectedDeclaredMethods() { + assertMethodSurface( + AttendanceController.class, + method( + Modifier.PUBLIC, + "ownHistory", + String.class, + Principal.class, + LocalDate.class, + LocalDate.class, + Model.class), + method( + Modifier.PUBLIC, + "inspectHistory", + String.class, + Principal.class, + long.class, + LocalDate.class, + LocalDate.class, + Model.class), + method( + Modifier.PUBLIC, + "checkIn", + String.class, + Principal.class, + RedirectAttributes.class), + method( + Modifier.PUBLIC, + "checkOut", + String.class, + Principal.class, + RedirectAttributes.class)); + assertMethodSurface( + CalendarController.class, + method(Modifier.PUBLIC, "calendar", String.class, Principal.class, Model.class), + method( + Modifier.PUBLIC, + "create", + String.class, + Principal.class, + LocalDate.class, + String.class, + boolean.class, + RedirectAttributes.class), + method( + Modifier.PUBLIC, + "update", + String.class, + Principal.class, + long.class, + long.class, + LocalDate.class, + String.class, + boolean.class, + RedirectAttributes.class)); + assertMethodSurface( + AttendanceApplicationService.class, + method(Modifier.PUBLIC, "checkIn", AttendanceRecord.class, long.class), + method(Modifier.PUBLIC, "checkOut", AttendanceRecord.class, long.class), + method(Modifier.PUBLIC, "currentState", AttendanceCurrentState.class, long.class), + method( + Modifier.PUBLIC, + "history", + List.class, + AttendanceActor.class, + long.class, + LocalDate.class, + LocalDate.class), + method(Modifier.PUBLIC, "currentBusinessDate", LocalDate.class)); + assertMethodSurface( + AttendanceCurrentUserService.class, + method(Modifier.PUBLIC, "actor", AttendanceActor.class, Principal.class)); + assertMethodSurface( + AttendanceService.class, + method( + Modifier.PUBLIC, + "checkIn", + AttendanceRecord.class, + long.class, + Instant.class, + AttendancePolicy.class, + AttendanceDayContext.class, + Optional.class), + method( + Modifier.PUBLIC, + "checkOut", + AttendanceRecord.class, + Optional.class, + Instant.class)); + assertMethodSurface( + CalendarApplicationService.class, + method( + Modifier.PUBLIC, + "createManual", + GlobalCalendarEvent.class, + AttendanceActor.class, + LocalDate.class, + String.class, + boolean.class), + method( + Modifier.PUBLIC, + "updateManual", + GlobalCalendarEvent.class, + AttendanceActor.class, + long.class, + long.class, + LocalDate.class, + String.class, + boolean.class), + method(Modifier.PUBLIC, "list", List.class, LocalDate.class, LocalDate.class), + method(Modifier.PUBLIC, "isGlobalDayOff", boolean.class, LocalDate.class)); + } + private static void assertConstructors( Class type, ConstructorContract... expectedConstructors) { List actual = Arrays.stream(type.getDeclaredConstructors())