test(attendance): guard component method surfaces
This commit is contained in:
@@ -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
|
||||
|
||||
+119
@@ -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<ConstructorContract> actual = Arrays.stream(type.getDeclaredConstructors())
|
||||
|
||||
Reference in New Issue
Block a user