From 82ad8202fd31f77db8c3932a902dba07cee70894 Mon Sep 17 00:00:00 2001 From: sechmachine <97589681+sechmachine727@users.noreply.github.com> Date: Sat, 15 Aug 2026 11:34:11 +0700 Subject: [PATCH] refactor(attendance): target lombok boilerplate --- .../unit/lombok-attendance-boilerplate.md | 105 ++++++++++++++++++ .../controller/AttendanceController.java | 9 +- .../controller/CalendarController.java | 12 +- .../model/entity/AttendancePolicyEntity.java | 8 +- .../model/entity/AttendanceRecordEntity.java | 8 +- .../entity/GlobalCalendarEventEntity.java | 8 +- .../model/entity/LeaveRequestDayEntity.java | 8 +- .../model/entity/LeaveRequestDayId.java | 8 +- .../model/entity/LeaveRequestEntity.java | 8 +- .../service/AttendanceApplicationService.java | 22 +--- .../service/AttendanceCurrentUserService.java | 7 +- .../attendance/service/AttendanceService.java | 5 +- .../service/CalendarApplicationService.java | 12 +- .../AttendanceLombokBoilerplateTest.java | 95 ++++++++++++++++ 14 files changed, 237 insertions(+), 78 deletions(-) create mode 100644 docs/tests/unit/lombok-attendance-boilerplate.md create mode 100644 src/test/java/com/lab/labtimesheet/feature/attendance/AttendanceLombokBoilerplateTest.java diff --git a/docs/tests/unit/lombok-attendance-boilerplate.md b/docs/tests/unit/lombok-attendance-boilerplate.md new file mode 100644 index 0000000..ee1e0dd --- /dev/null +++ b/docs/tests/unit/lombok-attendance-boilerplate.md @@ -0,0 +1,105 @@ +# Test Evidence: Attendance targeted Lombok boilerplate retrofit + +- **Test type:** Unit source-contract audit +- **Requirement IDs:** `ATT-001`–`ATT-012`, `CAL-001`, `CAL-006`–`CAL-009` +- **Scenario IDs:** `AC-ATT-001`–`AC-ATT-005`, `AC-CAL-003`, `AC-CAL-004` +- **Test class/method:** `com.lab.labtimesheet.feature.attendance.AttendanceLombokBoilerplateTest` +- **Implementation commit:** `pending` + +## Protected behavior + +Attendance uses the installed Lombok processor only for mechanical constructors while preserving package-level +Spring injection access, protected JPA construction, immutable records, domain constructors and mutations, raw punch +and attached-policy history rules, composite-key identity, and existing public API names. + +## Test method + +The source-contract test inspects only `feature.attendance` production Java. It enumerates the exact injection-only +components and JPA/stateless no-argument constructors eligible for Lombok, rejects retained handwritten equivalents +and blanket `@Data`, and asserts that records and business-significant methods remain explicit. + +## Hand-derived expected result + +Five injection-only components use package-scoped `@RequiredArgsConstructor`; six JPA/embeddable types use protected +`@NoArgsConstructor`; the stateless domain service uses a package-scoped `@NoArgsConstructor`. No record is replaced, +and no validated constructor, state mutation, identity method, or stable domain-style accessor is generated away. + +## RED + +**Command** + +```text +export JAVA_HOME=/opt/homebrew/opt/openjdk@25 +export PATH="$JAVA_HOME/bin:$PATH" +./mvnw -Dtest=AttendanceLombokBoilerplateTest test +``` + +**Observed result** + +```text +Tests run: 3, Failures: 2, Errors: 0, Skipped: 0 +The injection-component assertion first failed on AttendanceController because the required package-scoped +@RequiredArgsConstructor and Lombok imports were absent. The JPA/stateless assertion first failed on +AttendancePolicyEntity because the protected @NoArgsConstructor and Lombok imports were absent. The record and +business-method retention guard passed. +BUILD FAILURE +Process exited 1. +``` + +## GREEN + +**Command** + +```text +export JAVA_HOME=/opt/homebrew/opt/openjdk@25 +export PATH="$JAVA_HOME/bin:$PATH" +./mvnw -Dtest=AttendanceLombokBoilerplateTest test +``` + +**Observed result** + +```text +Tests run: 3, Failures: 0, Errors: 0, Skipped: 0 +BUILD SUCCESS +Process exited 0. +``` + +## Affected suite + +**Command and result** + +```text +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 +PostgreSQL 18.4 started and Flyway applied V1 for the persistence and concurrency contexts. +BUILD SUCCESS +Process exited 0. +``` + +## External-test boundaries + +The source audit does not replace compilation, reflection/JPA bootstrapping, MVC property access, PostgreSQL +persistence, or Javadoc/doclint. Those checks are required as affected verification after the source contract turns +GREEN. No application behavior or public API is intentionally changed by this retrofit. + +Additional verification on the same source tree: + +```text +./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 + +javadoc -quiet -Xdoclint:all -d target/attendance-javadocs -classpath "target/classes:" -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 +expand Lombok constructors; repository policy exempts generated trivial constructors from duplicate Javadoc. +``` diff --git a/src/main/java/com/lab/labtimesheet/feature/attendance/controller/AttendanceController.java b/src/main/java/com/lab/labtimesheet/feature/attendance/controller/AttendanceController.java index aed3970..7e08e61 100644 --- a/src/main/java/com/lab/labtimesheet/feature/attendance/controller/AttendanceController.java +++ b/src/main/java/com/lab/labtimesheet/feature/attendance/controller/AttendanceController.java @@ -7,6 +7,8 @@ import com.lab.labtimesheet.feature.attendance.service.AttendanceApplicationServ import com.lab.labtimesheet.feature.attendance.service.AttendanceCurrentUserService; import java.security.Principal; import java.time.LocalDate; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.security.access.AccessDeniedException; import org.springframework.stereotype.Controller; @@ -22,17 +24,12 @@ import org.springframework.web.servlet.mvc.support.RedirectAttributes; */ @Controller @RequestMapping("/attendance") +@RequiredArgsConstructor(access = AccessLevel.PACKAGE) public class AttendanceController { private final AttendanceApplicationService attendance; private final AttendanceCurrentUserService currentUsers; - AttendanceController( - AttendanceApplicationService attendance, AttendanceCurrentUserService currentUsers) { - this.attendance = attendance; - this.currentUsers = currentUsers; - } - /** * Renders the authenticated Intern's inclusive attendance history, defaulting to the current month. * diff --git a/src/main/java/com/lab/labtimesheet/feature/attendance/controller/CalendarController.java b/src/main/java/com/lab/labtimesheet/feature/attendance/controller/CalendarController.java index 6babad1..d43a453 100644 --- a/src/main/java/com/lab/labtimesheet/feature/attendance/controller/CalendarController.java +++ b/src/main/java/com/lab/labtimesheet/feature/attendance/controller/CalendarController.java @@ -7,6 +7,8 @@ import com.lab.labtimesheet.feature.attendance.service.AttendanceCurrentUserServ import com.lab.labtimesheet.feature.attendance.service.CalendarApplicationService; import java.security.Principal; import java.time.LocalDate; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.security.access.AccessDeniedException; import org.springframework.stereotype.Controller; @@ -23,21 +25,13 @@ import org.springframework.web.servlet.mvc.support.RedirectAttributes; */ @Controller @RequestMapping("/attendance/calendar") +@RequiredArgsConstructor(access = AccessLevel.PACKAGE) public class CalendarController { private final CalendarApplicationService calendar; private final AttendanceApplicationService attendance; private final AttendanceCurrentUserService currentUsers; - CalendarController( - CalendarApplicationService calendar, - AttendanceApplicationService attendance, - AttendanceCurrentUserService currentUsers) { - this.calendar = calendar; - this.attendance = attendance; - this.currentUsers = currentUsers; - } - /** * Renders the next year of locally stored calendar events for an authenticated Admin. * diff --git a/src/main/java/com/lab/labtimesheet/feature/attendance/model/entity/AttendancePolicyEntity.java b/src/main/java/com/lab/labtimesheet/feature/attendance/model/entity/AttendancePolicyEntity.java index 59e98b9..e1cad6b 100644 --- a/src/main/java/com/lab/labtimesheet/feature/attendance/model/entity/AttendancePolicyEntity.java +++ b/src/main/java/com/lab/labtimesheet/feature/attendance/model/entity/AttendancePolicyEntity.java @@ -19,12 +19,15 @@ import java.time.LocalTime; import java.time.ZoneId; import java.util.Set; import java.util.stream.Collectors; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; /** * JPA mapping of an immutable-on-effective attendance policy version and its configured workdays. */ @Entity @Table(name = "attendance_policy_versions") +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class AttendancePolicyEntity { @Id @@ -65,11 +68,6 @@ public class AttendancePolicyEntity { @Version private long version; - /** - * Required by JPA; application code resolves existing effective-dated versions instead of constructing them here. - */ - protected AttendancePolicyEntity() {} - /** * Converts the persisted version to the immutable policy used for historical boundary calculations. * diff --git a/src/main/java/com/lab/labtimesheet/feature/attendance/model/entity/AttendanceRecordEntity.java b/src/main/java/com/lab/labtimesheet/feature/attendance/model/entity/AttendanceRecordEntity.java index 73302d9..22253cc 100644 --- a/src/main/java/com/lab/labtimesheet/feature/attendance/model/entity/AttendanceRecordEntity.java +++ b/src/main/java/com/lab/labtimesheet/feature/attendance/model/entity/AttendanceRecordEntity.java @@ -13,12 +13,15 @@ import jakarta.persistence.Table; import jakarta.persistence.Version; import java.time.Instant; import java.time.LocalDate; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; /** * JPA persistence model for one Intern/work-date punch row with its permanently attached policy version. */ @Entity @Table(name = "attendance_records") +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class AttendanceRecordEntity { @Id @@ -44,11 +47,6 @@ public class AttendanceRecordEntity { @Version private long version; - /** - * Required by JPA. - */ - protected AttendanceRecordEntity() {} - /** * Creates a new persistence row from server-authoritative raw punch values. * diff --git a/src/main/java/com/lab/labtimesheet/feature/attendance/model/entity/GlobalCalendarEventEntity.java b/src/main/java/com/lab/labtimesheet/feature/attendance/model/entity/GlobalCalendarEventEntity.java index c70abd6..27da114 100644 --- a/src/main/java/com/lab/labtimesheet/feature/attendance/model/entity/GlobalCalendarEventEntity.java +++ b/src/main/java/com/lab/labtimesheet/feature/attendance/model/entity/GlobalCalendarEventEntity.java @@ -10,12 +10,15 @@ import jakarta.persistence.Id; import jakarta.persistence.Table; import jakarta.persistence.Version; import java.time.LocalDate; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; /** * JPA model for the locally authoritative global calendar decision. */ @Entity @Table(name = "global_calendar_events") +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class GlobalCalendarEventEntity { @Id @@ -43,11 +46,6 @@ public class GlobalCalendarEventEntity { @Version private long version; - /** - * Required by JPA. - */ - protected GlobalCalendarEventEntity() {} - /** * Creates a custom calendar event attributed to the Admin actor. * diff --git a/src/main/java/com/lab/labtimesheet/feature/attendance/model/entity/LeaveRequestDayEntity.java b/src/main/java/com/lab/labtimesheet/feature/attendance/model/entity/LeaveRequestDayEntity.java index 94cdf04..5215c59 100644 --- a/src/main/java/com/lab/labtimesheet/feature/attendance/model/entity/LeaveRequestDayEntity.java +++ b/src/main/java/com/lab/labtimesheet/feature/attendance/model/entity/LeaveRequestDayEntity.java @@ -9,12 +9,15 @@ import jakarta.persistence.ManyToOne; import jakarta.persistence.MapsId; import jakarta.persistence.Table; import java.time.LocalDate; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; /** * JPA mapping of an immutable leave-day allocation whose exact date, policy, and quota snapshot remain historical. */ @Entity @Table(name = "leave_request_days") +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class LeaveRequestDayEntity { @EmbeddedId @@ -35,11 +38,6 @@ public class LeaveRequestDayEntity { @Column(name = "monthly_quota_snapshot", nullable = false) private int monthlyQuotaSnapshot; - /** - * Required by JPA. - */ - protected LeaveRequestDayEntity() {} - LeaveRequestDayEntity( LeaveRequestEntity request, LocalDate leaveDate, diff --git a/src/main/java/com/lab/labtimesheet/feature/attendance/model/entity/LeaveRequestDayId.java b/src/main/java/com/lab/labtimesheet/feature/attendance/model/entity/LeaveRequestDayId.java index a4d59dc..20bc1fe 100644 --- a/src/main/java/com/lab/labtimesheet/feature/attendance/model/entity/LeaveRequestDayId.java +++ b/src/main/java/com/lab/labtimesheet/feature/attendance/model/entity/LeaveRequestDayId.java @@ -5,11 +5,14 @@ import jakarta.persistence.Embeddable; import java.io.Serializable; import java.time.LocalDate; import java.util.Objects; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; /** * Composite identifier of one frozen quota-consuming date within a leave request. */ @Embeddable +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class LeaveRequestDayId implements Serializable { /** Parent request identity used by the composite primary key. */ @@ -20,11 +23,6 @@ public class LeaveRequestDayId implements Serializable { @Column(name = "leave_date", nullable = false) private LocalDate leaveDate; - /** - * Required by JPA. - */ - protected LeaveRequestDayId() {} - /** * Creates the identity for an already-persisted request and its exact allocated date. * diff --git a/src/main/java/com/lab/labtimesheet/feature/attendance/model/entity/LeaveRequestEntity.java b/src/main/java/com/lab/labtimesheet/feature/attendance/model/entity/LeaveRequestEntity.java index e40e4ed..0209325 100644 --- a/src/main/java/com/lab/labtimesheet/feature/attendance/model/entity/LeaveRequestEntity.java +++ b/src/main/java/com/lab/labtimesheet/feature/attendance/model/entity/LeaveRequestEntity.java @@ -9,12 +9,15 @@ import jakarta.persistence.Table; import jakarta.persistence.Version; import java.time.Instant; import java.time.LocalDate; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; /** * Minimal Attendance-owned JPA mapping of leave request state used when evaluating frozen leave-day allocations. */ @Entity @Table(name = "leave_requests") +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class LeaveRequestEntity { @Id @@ -51,11 +54,6 @@ public class LeaveRequestEntity { @Version private long version; - /** - * Required by JPA. - */ - protected LeaveRequestEntity() {} - LeaveRequestEntity( long internUserId, LocalDate startDate, diff --git a/src/main/java/com/lab/labtimesheet/feature/attendance/service/AttendanceApplicationService.java b/src/main/java/com/lab/labtimesheet/feature/attendance/service/AttendanceApplicationService.java index 0882159..ce38e10 100644 --- a/src/main/java/com/lab/labtimesheet/feature/attendance/service/AttendanceApplicationService.java +++ b/src/main/java/com/lab/labtimesheet/feature/attendance/service/AttendanceApplicationService.java @@ -20,9 +20,11 @@ import java.time.Instant; import java.time.LocalDate; import java.util.List; import java.util.Optional; -import org.springframework.security.access.AccessDeniedException; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.orm.ObjectOptimisticLockingFailureException; +import org.springframework.security.access.AccessDeniedException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -31,6 +33,7 @@ import org.springframework.transaction.annotation.Transactional; * Account eligibility is obtained only through {@link AccountService}; raw rows retain their attached policy. */ @Service +@RequiredArgsConstructor(access = AccessLevel.PACKAGE) public class AttendanceApplicationService { private final Clock clock; @@ -41,23 +44,6 @@ public class AttendanceApplicationService { private final CalendarApplicationService calendar; private final AttendanceService attendance; - AttendanceApplicationService( - Clock clock, - AttendancePolicyRepository policyEntities, - AttendanceRecordRepository recordEntities, - AttendanceQueryRepository queries, - AccountService accounts, - CalendarApplicationService calendar, - AttendanceService attendance) { - this.clock = clock; - this.policyEntities = policyEntities; - this.recordEntities = recordEntities; - this.queries = queries; - this.accounts = accounts; - this.calendar = calendar; - this.attendance = attendance; - } - /** * Records the sole server-time check-in for the effective policy-local date. * Eligibility, workday, calendar, and exact frozen leave allocation are evaluated in the transaction; diff --git a/src/main/java/com/lab/labtimesheet/feature/attendance/service/AttendanceCurrentUserService.java b/src/main/java/com/lab/labtimesheet/feature/attendance/service/AttendanceCurrentUserService.java index 1572f94..a3063e1 100644 --- a/src/main/java/com/lab/labtimesheet/feature/attendance/service/AttendanceCurrentUserService.java +++ b/src/main/java/com/lab/labtimesheet/feature/attendance/service/AttendanceCurrentUserService.java @@ -6,6 +6,8 @@ import com.lab.labtimesheet.feature.account.service.AccountService; import com.lab.labtimesheet.feature.attendance.model.AttendanceActor; import com.lab.labtimesheet.feature.attendance.model.AttendanceRole; import java.security.Principal; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; import org.springframework.security.access.AccessDeniedException; import org.springframework.stereotype.Service; @@ -13,14 +15,11 @@ import org.springframework.stereotype.Service; * Converts Spring Security principals into active Attendance authorization contexts through AccountService DTOs. */ @Service +@RequiredArgsConstructor(access = AccessLevel.PACKAGE) public class AttendanceCurrentUserService { private final AccountService accounts; - AttendanceCurrentUserService(AccountService accounts) { - this.accounts = accounts; - } - /** * Resolves the authenticated email through the Account feature and rejects missing or inactive identities. * diff --git a/src/main/java/com/lab/labtimesheet/feature/attendance/service/AttendanceService.java b/src/main/java/com/lab/labtimesheet/feature/attendance/service/AttendanceService.java index cca61f6..77a27cc 100644 --- a/src/main/java/com/lab/labtimesheet/feature/attendance/service/AttendanceService.java +++ b/src/main/java/com/lab/labtimesheet/feature/attendance/service/AttendanceService.java @@ -8,16 +8,17 @@ import com.lab.labtimesheet.feature.attendance.model.AttendanceRecord; import java.time.Instant; import java.time.LocalDate; import java.util.Optional; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; import org.springframework.stereotype.Service; /** * Pure attendance punch rules over immutable policy, date context, and raw record values. */ @Service +@NoArgsConstructor(access = AccessLevel.PACKAGE) public final class AttendanceService { - AttendanceService() {} - /** * Creates the sole raw check-in for an eligible Intern/date using the supplied server instant. * Equality at the grace boundary is accepted; violation classification remains attached-policy based. diff --git a/src/main/java/com/lab/labtimesheet/feature/attendance/service/CalendarApplicationService.java b/src/main/java/com/lab/labtimesheet/feature/attendance/service/CalendarApplicationService.java index 72953b1..0183b9f 100644 --- a/src/main/java/com/lab/labtimesheet/feature/attendance/service/CalendarApplicationService.java +++ b/src/main/java/com/lab/labtimesheet/feature/attendance/service/CalendarApplicationService.java @@ -12,6 +12,8 @@ import com.lab.labtimesheet.feature.attendance.repository.GlobalCalendarEventRep import java.time.Clock; import java.time.LocalDate; import java.util.List; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; import org.springframework.security.access.AccessDeniedException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -20,21 +22,13 @@ import org.springframework.transaction.annotation.Transactional; * Transactional boundary for the locally authoritative global calendar and its cross-feature day-off decision. */ @Service +@RequiredArgsConstructor(access = AccessLevel.PACKAGE) public class CalendarApplicationService { private final Clock clock; private final AttendancePolicyRepository policies; private final GlobalCalendarEventRepository events; - CalendarApplicationService( - Clock clock, - AttendancePolicyRepository policies, - GlobalCalendarEventRepository events) { - this.clock = clock; - this.policies = policies; - this.events = events; - } - /** * Creates an Admin-authored custom event on a non-past policy-local date. * diff --git a/src/test/java/com/lab/labtimesheet/feature/attendance/AttendanceLombokBoilerplateTest.java b/src/test/java/com/lab/labtimesheet/feature/attendance/AttendanceLombokBoilerplateTest.java new file mode 100644 index 0000000..fd736eb --- /dev/null +++ b/src/test/java/com/lab/labtimesheet/feature/attendance/AttendanceLombokBoilerplateTest.java @@ -0,0 +1,95 @@ +package com.lab.labtimesheet.feature.attendance; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import org.junit.jupiter.api.Test; + +class AttendanceLombokBoilerplateTest { + + private static final Path SOURCE_ROOT = + Path.of("src/main/java/com/lab/labtimesheet/feature/attendance"); + + @Test + void injectionOnlyComponentsUsePackageScopedRequiredArgsConstructors() throws IOException { + for (String relativePath : List.of( + "controller/AttendanceController.java", + "controller/CalendarController.java", + "service/AttendanceApplicationService.java", + "service/AttendanceCurrentUserService.java", + "service/CalendarApplicationService.java")) { + String source = source(relativePath); + String typeName = typeName(relativePath); + + assertThat(source) + .as(relativePath) + .contains( + "import lombok.AccessLevel;", + "import lombok.RequiredArgsConstructor;", + "@RequiredArgsConstructor(access = AccessLevel.PACKAGE)") + .doesNotContain(typeName + "("); + } + } + + @Test + void jpaTypesAndStatelessServiceUseTargetedNoArgsConstructors() throws IOException { + for (String relativePath : List.of( + "model/entity/AttendancePolicyEntity.java", + "model/entity/AttendanceRecordEntity.java", + "model/entity/GlobalCalendarEventEntity.java", + "model/entity/LeaveRequestDayEntity.java", + "model/entity/LeaveRequestDayId.java", + "model/entity/LeaveRequestEntity.java")) { + String source = source(relativePath); + String typeName = typeName(relativePath); + + assertThat(source) + .as(relativePath) + .contains( + "import lombok.AccessLevel;", + "import lombok.NoArgsConstructor;", + "@NoArgsConstructor(access = AccessLevel.PROTECTED)") + .doesNotContain("protected " + typeName + "()"); + } + + assertThat(source("service/AttendanceService.java")) + .contains( + "import lombok.AccessLevel;", + "import lombok.NoArgsConstructor;", + "@NoArgsConstructor(access = AccessLevel.PACKAGE)") + .doesNotContain("AttendanceService()"); + } + + @Test + void auditRetainsRecordsAndExplicitBusinessMethodsWithoutBlanketData() throws IOException { + try (var sources = Files.walk(SOURCE_ROOT)) { + for (Path sourcePath : sources.filter(path -> path.toString().endsWith(".java")).toList()) { + assertThat(Files.readString(sourcePath)).as(sourcePath.toString()).doesNotContain("@Data"); + } + } + + assertThat(source("model/AttendancePolicy.java")).contains("public record AttendancePolicy("); + assertThat(source("model/AttendanceRecord.java")) + .contains("public record AttendanceRecord(", "public AttendanceRecord checkOut(Instant at)"); + assertThat(source("model/dto/AttendanceHistoryItem.java")) + .contains("public record AttendanceHistoryItem("); + assertThat(source("model/entity/GlobalCalendarEventEntity.java")) + .contains("public void update(", "public LocalDate calendarDate()", "public long version()"); + assertThat(source("model/entity/AttendanceRecordEntity.java")) + .contains("public void setCheckOutAt(", "public LocalDate workDate()"); + assertThat(source("model/entity/LeaveRequestDayId.java")) + .contains("public boolean equals(", "public int hashCode()"); + } + + private static String source(String relativePath) throws IOException { + return Files.readString(SOURCE_ROOT.resolve(relativePath)); + } + + private static String typeName(String relativePath) { + String fileName = Path.of(relativePath).getFileName().toString(); + return fileName.substring(0, fileName.length() - ".java".length()); + } +}