merge(iteration-1): adopt attendance lombok conventions
This commit is contained in:
@@ -0,0 +1,108 @@
|
|||||||
|
# Test Evidence: Attendance targeted Lombok boilerplate retrofit
|
||||||
|
|
||||||
|
- **Test type:** Unit compiled-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#generatedConstructorsPreserveParameterListsAndVisibility`,
|
||||||
|
`com.lab.labtimesheet.feature.attendance.AttendanceLombokBoilerplateTest#immutableModelsRemainRecordsWithTheirComponentContracts`,
|
||||||
|
`com.lab.labtimesheet.feature.attendance.AttendanceLombokBoilerplateTest#entitiesExposeOnlyIntentionalPublicAndProtectedDeclaredMethods`,
|
||||||
|
`com.lab.labtimesheet.feature.attendance.AttendanceLombokBoilerplateTest#componentsExposeOnlyIntentionalPublicAndProtectedDeclaredMethods`
|
||||||
|
- **Implementation commit:** `82ad8202fd31f77db8c3932a902dba07cee70894`
|
||||||
|
|
||||||
|
## Protected behavior
|
||||||
|
|
||||||
|
Attendance uses the installed Lombok processor only for mechanical constructors while preserving the compiled API:
|
||||||
|
package-level Spring injection, protected JPA construction, immutable record components, domain constructors and
|
||||||
|
mutations, raw punch and attached-policy history rules, composite-key identity, and existing public method names.
|
||||||
|
|
||||||
|
## Test method
|
||||||
|
|
||||||
|
Reflection inspects compiled `feature.attendance` classes rather than source spelling. It verifies every constructor's
|
||||||
|
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. Exact controller and service surfaces likewise prevent Lombok from exposing
|
||||||
|
collaborator getters/setters or generated `equals`/`hashCode`/`toString` methods.
|
||||||
|
|
||||||
|
## Hand-derived expected result
|
||||||
|
|
||||||
|
Five injection-only components expose only their package-scoped dependency constructors. Six JPA/embeddable types
|
||||||
|
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`. Both controllers and all four Attendance
|
||||||
|
services expose only their existing route or application/domain operations, never their injected collaborators.
|
||||||
|
|
||||||
|
## 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 initial source audit first failed on AttendanceController because its mechanical dependency constructor remained,
|
||||||
|
and on AttendancePolicyEntity because its mechanical protected JPA constructor remained. The immutable-record and
|
||||||
|
business-method retention guard passed. After this RED established the retrofit gap, the permanent regression was
|
||||||
|
replaced with compiled reflection/API checks so formatting or annotation spelling cannot affect the result.
|
||||||
|
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: 4, 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: 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.
|
||||||
|
```
|
||||||
|
|
||||||
|
## External-test boundaries
|
||||||
|
|
||||||
|
The reflection audit does not replace Spring/JPA bootstrapping, MVC property access, PostgreSQL persistence, or
|
||||||
|
Javadoc/doclint. Those checks remain affected verification. 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 -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
|
||||||
|
expand Lombok constructors; repository policy exempts generated trivial constructors from duplicate Javadoc.
|
||||||
|
```
|
||||||
+3
-6
@@ -7,6 +7,8 @@ import com.lab.labtimesheet.feature.attendance.service.AttendanceApplicationServ
|
|||||||
import com.lab.labtimesheet.feature.attendance.service.AttendanceCurrentUserService;
|
import com.lab.labtimesheet.feature.attendance.service.AttendanceCurrentUserService;
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.format.annotation.DateTimeFormat;
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
import org.springframework.security.access.AccessDeniedException;
|
import org.springframework.security.access.AccessDeniedException;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
@@ -22,17 +24,12 @@ import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
|||||||
*/
|
*/
|
||||||
@Controller
|
@Controller
|
||||||
@RequestMapping("/attendance")
|
@RequestMapping("/attendance")
|
||||||
|
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
|
||||||
public class AttendanceController {
|
public class AttendanceController {
|
||||||
|
|
||||||
private final AttendanceApplicationService attendance;
|
private final AttendanceApplicationService attendance;
|
||||||
private final AttendanceCurrentUserService currentUsers;
|
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.
|
* Renders the authenticated Intern's inclusive attendance history, defaulting to the current month.
|
||||||
*
|
*
|
||||||
|
|||||||
+3
-9
@@ -7,6 +7,8 @@ import com.lab.labtimesheet.feature.attendance.service.AttendanceCurrentUserServ
|
|||||||
import com.lab.labtimesheet.feature.attendance.service.CalendarApplicationService;
|
import com.lab.labtimesheet.feature.attendance.service.CalendarApplicationService;
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.format.annotation.DateTimeFormat;
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
import org.springframework.security.access.AccessDeniedException;
|
import org.springframework.security.access.AccessDeniedException;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
@@ -23,21 +25,13 @@ import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
|||||||
*/
|
*/
|
||||||
@Controller
|
@Controller
|
||||||
@RequestMapping("/attendance/calendar")
|
@RequestMapping("/attendance/calendar")
|
||||||
|
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
|
||||||
public class CalendarController {
|
public class CalendarController {
|
||||||
|
|
||||||
private final CalendarApplicationService calendar;
|
private final CalendarApplicationService calendar;
|
||||||
private final AttendanceApplicationService attendance;
|
private final AttendanceApplicationService attendance;
|
||||||
private final AttendanceCurrentUserService currentUsers;
|
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.
|
* Renders the next year of locally stored calendar events for an authenticated Admin.
|
||||||
*
|
*
|
||||||
|
|||||||
+3
-5
@@ -19,12 +19,15 @@ import java.time.LocalTime;
|
|||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
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.
|
* JPA mapping of an immutable-on-effective attendance policy version and its configured workdays.
|
||||||
*/
|
*/
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "attendance_policy_versions")
|
@Table(name = "attendance_policy_versions")
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||||
public class AttendancePolicyEntity {
|
public class AttendancePolicyEntity {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@@ -65,11 +68,6 @@ public class AttendancePolicyEntity {
|
|||||||
@Version
|
@Version
|
||||||
private long 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.
|
* Converts the persisted version to the immutable policy used for historical boundary calculations.
|
||||||
*
|
*
|
||||||
|
|||||||
+3
-5
@@ -13,12 +13,15 @@ import jakarta.persistence.Table;
|
|||||||
import jakarta.persistence.Version;
|
import jakarta.persistence.Version;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.LocalDate;
|
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.
|
* JPA persistence model for one Intern/work-date punch row with its permanently attached policy version.
|
||||||
*/
|
*/
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "attendance_records")
|
@Table(name = "attendance_records")
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||||
public class AttendanceRecordEntity {
|
public class AttendanceRecordEntity {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@@ -44,11 +47,6 @@ public class AttendanceRecordEntity {
|
|||||||
@Version
|
@Version
|
||||||
private long version;
|
private long version;
|
||||||
|
|
||||||
/**
|
|
||||||
* Required by JPA.
|
|
||||||
*/
|
|
||||||
protected AttendanceRecordEntity() {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new persistence row from server-authoritative raw punch values.
|
* Creates a new persistence row from server-authoritative raw punch values.
|
||||||
*
|
*
|
||||||
|
|||||||
+3
-5
@@ -10,12 +10,15 @@ import jakarta.persistence.Id;
|
|||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
import jakarta.persistence.Version;
|
import jakarta.persistence.Version;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JPA model for the locally authoritative global calendar decision.
|
* JPA model for the locally authoritative global calendar decision.
|
||||||
*/
|
*/
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "global_calendar_events")
|
@Table(name = "global_calendar_events")
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||||
public class GlobalCalendarEventEntity {
|
public class GlobalCalendarEventEntity {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@@ -43,11 +46,6 @@ public class GlobalCalendarEventEntity {
|
|||||||
@Version
|
@Version
|
||||||
private long version;
|
private long version;
|
||||||
|
|
||||||
/**
|
|
||||||
* Required by JPA.
|
|
||||||
*/
|
|
||||||
protected GlobalCalendarEventEntity() {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a custom calendar event attributed to the Admin actor.
|
* Creates a custom calendar event attributed to the Admin actor.
|
||||||
*
|
*
|
||||||
|
|||||||
+3
-5
@@ -9,12 +9,15 @@ import jakarta.persistence.ManyToOne;
|
|||||||
import jakarta.persistence.MapsId;
|
import jakarta.persistence.MapsId;
|
||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
import java.time.LocalDate;
|
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.
|
* JPA mapping of an immutable leave-day allocation whose exact date, policy, and quota snapshot remain historical.
|
||||||
*/
|
*/
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "leave_request_days")
|
@Table(name = "leave_request_days")
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||||
public class LeaveRequestDayEntity {
|
public class LeaveRequestDayEntity {
|
||||||
|
|
||||||
@EmbeddedId
|
@EmbeddedId
|
||||||
@@ -35,11 +38,6 @@ public class LeaveRequestDayEntity {
|
|||||||
@Column(name = "monthly_quota_snapshot", nullable = false)
|
@Column(name = "monthly_quota_snapshot", nullable = false)
|
||||||
private int monthlyQuotaSnapshot;
|
private int monthlyQuotaSnapshot;
|
||||||
|
|
||||||
/**
|
|
||||||
* Required by JPA.
|
|
||||||
*/
|
|
||||||
protected LeaveRequestDayEntity() {}
|
|
||||||
|
|
||||||
LeaveRequestDayEntity(
|
LeaveRequestDayEntity(
|
||||||
LeaveRequestEntity request,
|
LeaveRequestEntity request,
|
||||||
LocalDate leaveDate,
|
LocalDate leaveDate,
|
||||||
|
|||||||
+3
-5
@@ -5,11 +5,14 @@ import jakarta.persistence.Embeddable;
|
|||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Composite identifier of one frozen quota-consuming date within a leave request.
|
* Composite identifier of one frozen quota-consuming date within a leave request.
|
||||||
*/
|
*/
|
||||||
@Embeddable
|
@Embeddable
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||||
public class LeaveRequestDayId implements Serializable {
|
public class LeaveRequestDayId implements Serializable {
|
||||||
|
|
||||||
/** Parent request identity used by the composite primary key. */
|
/** Parent request identity used by the composite primary key. */
|
||||||
@@ -20,11 +23,6 @@ public class LeaveRequestDayId implements Serializable {
|
|||||||
@Column(name = "leave_date", nullable = false)
|
@Column(name = "leave_date", nullable = false)
|
||||||
private LocalDate leaveDate;
|
private LocalDate leaveDate;
|
||||||
|
|
||||||
/**
|
|
||||||
* Required by JPA.
|
|
||||||
*/
|
|
||||||
protected LeaveRequestDayId() {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the identity for an already-persisted request and its exact allocated date.
|
* Creates the identity for an already-persisted request and its exact allocated date.
|
||||||
*
|
*
|
||||||
|
|||||||
+3
-5
@@ -9,12 +9,15 @@ import jakarta.persistence.Table;
|
|||||||
import jakarta.persistence.Version;
|
import jakarta.persistence.Version;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.LocalDate;
|
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.
|
* Minimal Attendance-owned JPA mapping of leave request state used when evaluating frozen leave-day allocations.
|
||||||
*/
|
*/
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "leave_requests")
|
@Table(name = "leave_requests")
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||||
public class LeaveRequestEntity {
|
public class LeaveRequestEntity {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@@ -51,11 +54,6 @@ public class LeaveRequestEntity {
|
|||||||
@Version
|
@Version
|
||||||
private long version;
|
private long version;
|
||||||
|
|
||||||
/**
|
|
||||||
* Required by JPA.
|
|
||||||
*/
|
|
||||||
protected LeaveRequestEntity() {}
|
|
||||||
|
|
||||||
LeaveRequestEntity(
|
LeaveRequestEntity(
|
||||||
long internUserId,
|
long internUserId,
|
||||||
LocalDate startDate,
|
LocalDate startDate,
|
||||||
|
|||||||
+4
-18
@@ -20,9 +20,11 @@ import java.time.Instant;
|
|||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import org.springframework.security.access.AccessDeniedException;
|
import lombok.AccessLevel;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.dao.DataIntegrityViolationException;
|
import org.springframework.dao.DataIntegrityViolationException;
|
||||||
import org.springframework.orm.ObjectOptimisticLockingFailureException;
|
import org.springframework.orm.ObjectOptimisticLockingFailureException;
|
||||||
|
import org.springframework.security.access.AccessDeniedException;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
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.
|
* Account eligibility is obtained only through {@link AccountService}; raw rows retain their attached policy.
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
|
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
|
||||||
public class AttendanceApplicationService {
|
public class AttendanceApplicationService {
|
||||||
|
|
||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
@@ -41,23 +44,6 @@ public class AttendanceApplicationService {
|
|||||||
private final CalendarApplicationService calendar;
|
private final CalendarApplicationService calendar;
|
||||||
private final AttendanceService attendance;
|
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.
|
* 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;
|
* Eligibility, workday, calendar, and exact frozen leave allocation are evaluated in the transaction;
|
||||||
|
|||||||
+3
-4
@@ -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.AttendanceActor;
|
||||||
import com.lab.labtimesheet.feature.attendance.model.AttendanceRole;
|
import com.lab.labtimesheet.feature.attendance.model.AttendanceRole;
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.security.access.AccessDeniedException;
|
import org.springframework.security.access.AccessDeniedException;
|
||||||
import org.springframework.stereotype.Service;
|
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.
|
* Converts Spring Security principals into active Attendance authorization contexts through AccountService DTOs.
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
|
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
|
||||||
public class AttendanceCurrentUserService {
|
public class AttendanceCurrentUserService {
|
||||||
|
|
||||||
private final AccountService accounts;
|
private final AccountService accounts;
|
||||||
|
|
||||||
AttendanceCurrentUserService(AccountService accounts) {
|
|
||||||
this.accounts = accounts;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves the authenticated email through the Account feature and rejects missing or inactive identities.
|
* Resolves the authenticated email through the Account feature and rejects missing or inactive identities.
|
||||||
*
|
*
|
||||||
|
|||||||
+3
-2
@@ -8,16 +8,17 @@ import com.lab.labtimesheet.feature.attendance.model.AttendanceRecord;
|
|||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pure attendance punch rules over immutable policy, date context, and raw record values.
|
* Pure attendance punch rules over immutable policy, date context, and raw record values.
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PACKAGE)
|
||||||
public final class AttendanceService {
|
public final class AttendanceService {
|
||||||
|
|
||||||
AttendanceService() {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the sole raw check-in for an eligible Intern/date using the supplied server instant.
|
* 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.
|
* Equality at the grace boundary is accepted; violation classification remains attached-policy based.
|
||||||
|
|||||||
+3
-9
@@ -12,6 +12,8 @@ import com.lab.labtimesheet.feature.attendance.repository.GlobalCalendarEventRep
|
|||||||
import java.time.Clock;
|
import java.time.Clock;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.security.access.AccessDeniedException;
|
import org.springframework.security.access.AccessDeniedException;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
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.
|
* Transactional boundary for the locally authoritative global calendar and its cross-feature day-off decision.
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
|
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
|
||||||
public class CalendarApplicationService {
|
public class CalendarApplicationService {
|
||||||
|
|
||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
private final AttendancePolicyRepository policies;
|
private final AttendancePolicyRepository policies;
|
||||||
private final GlobalCalendarEventRepository events;
|
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.
|
* Creates an Admin-authored custom event on a non-past policy-local date.
|
||||||
*
|
*
|
||||||
|
|||||||
+398
@@ -0,0 +1,398 @@
|
|||||||
|
package com.lab.labtimesheet.feature.attendance;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
import com.lab.labtimesheet.feature.account.service.AccountService;
|
||||||
|
import com.lab.labtimesheet.feature.attendance.controller.AttendanceController;
|
||||||
|
import com.lab.labtimesheet.feature.attendance.controller.CalendarController;
|
||||||
|
import com.lab.labtimesheet.feature.attendance.model.AttendanceActor;
|
||||||
|
import com.lab.labtimesheet.feature.attendance.model.AttendanceDayContext;
|
||||||
|
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;
|
||||||
|
import com.lab.labtimesheet.feature.attendance.model.entity.AttendanceRecordEntity;
|
||||||
|
import com.lab.labtimesheet.feature.attendance.model.entity.GlobalCalendarEventEntity;
|
||||||
|
import com.lab.labtimesheet.feature.attendance.model.entity.LeaveRequestDayEntity;
|
||||||
|
import com.lab.labtimesheet.feature.attendance.model.entity.LeaveRequestDayId;
|
||||||
|
import com.lab.labtimesheet.feature.attendance.model.entity.LeaveRequestEntity;
|
||||||
|
import com.lab.labtimesheet.feature.attendance.repository.AttendancePolicyRepository;
|
||||||
|
import com.lab.labtimesheet.feature.attendance.repository.AttendanceQueryRepository;
|
||||||
|
import com.lab.labtimesheet.feature.attendance.repository.AttendanceRecordRepository;
|
||||||
|
import com.lab.labtimesheet.feature.attendance.repository.GlobalCalendarEventRepository;
|
||||||
|
import com.lab.labtimesheet.feature.attendance.service.AttendanceApplicationService;
|
||||||
|
import com.lab.labtimesheet.feature.attendance.service.AttendanceCurrentUserService;
|
||||||
|
import com.lab.labtimesheet.feature.attendance.service.AttendanceService;
|
||||||
|
import com.lab.labtimesheet.feature.attendance.service.CalendarApplicationService;
|
||||||
|
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;
|
||||||
|
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 {
|
||||||
|
|
||||||
|
private static final int PACKAGE_PRIVATE = 0;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void generatedConstructorsPreserveParameterListsAndVisibility() {
|
||||||
|
assertConstructors(
|
||||||
|
AttendanceController.class,
|
||||||
|
constructor(
|
||||||
|
PACKAGE_PRIVATE,
|
||||||
|
AttendanceApplicationService.class,
|
||||||
|
AttendanceCurrentUserService.class));
|
||||||
|
assertConstructors(
|
||||||
|
CalendarController.class,
|
||||||
|
constructor(
|
||||||
|
PACKAGE_PRIVATE,
|
||||||
|
CalendarApplicationService.class,
|
||||||
|
AttendanceApplicationService.class,
|
||||||
|
AttendanceCurrentUserService.class));
|
||||||
|
assertConstructors(
|
||||||
|
AttendanceApplicationService.class,
|
||||||
|
constructor(
|
||||||
|
PACKAGE_PRIVATE,
|
||||||
|
Clock.class,
|
||||||
|
AttendancePolicyRepository.class,
|
||||||
|
AttendanceRecordRepository.class,
|
||||||
|
AttendanceQueryRepository.class,
|
||||||
|
AccountService.class,
|
||||||
|
CalendarApplicationService.class,
|
||||||
|
AttendanceService.class));
|
||||||
|
assertConstructors(
|
||||||
|
AttendanceCurrentUserService.class,
|
||||||
|
constructor(PACKAGE_PRIVATE, AccountService.class));
|
||||||
|
assertConstructors(
|
||||||
|
CalendarApplicationService.class,
|
||||||
|
constructor(
|
||||||
|
PACKAGE_PRIVATE,
|
||||||
|
Clock.class,
|
||||||
|
AttendancePolicyRepository.class,
|
||||||
|
GlobalCalendarEventRepository.class));
|
||||||
|
assertConstructors(AttendanceService.class, constructor(PACKAGE_PRIVATE));
|
||||||
|
|
||||||
|
assertConstructors(AttendancePolicyEntity.class, constructor(Modifier.PROTECTED));
|
||||||
|
assertConstructors(
|
||||||
|
AttendanceRecordEntity.class,
|
||||||
|
constructor(Modifier.PROTECTED),
|
||||||
|
constructor(
|
||||||
|
Modifier.PUBLIC,
|
||||||
|
long.class,
|
||||||
|
LocalDate.class,
|
||||||
|
AttendancePolicyEntity.class,
|
||||||
|
Instant.class,
|
||||||
|
Instant.class));
|
||||||
|
assertConstructors(
|
||||||
|
GlobalCalendarEventEntity.class,
|
||||||
|
constructor(Modifier.PROTECTED),
|
||||||
|
constructor(
|
||||||
|
Modifier.PUBLIC,
|
||||||
|
LocalDate.class,
|
||||||
|
String.class,
|
||||||
|
boolean.class,
|
||||||
|
long.class));
|
||||||
|
assertConstructors(
|
||||||
|
LeaveRequestDayEntity.class,
|
||||||
|
constructor(Modifier.PROTECTED),
|
||||||
|
constructor(
|
||||||
|
PACKAGE_PRIVATE,
|
||||||
|
LeaveRequestEntity.class,
|
||||||
|
LocalDate.class,
|
||||||
|
AttendancePolicyEntity.class,
|
||||||
|
int.class));
|
||||||
|
assertConstructors(
|
||||||
|
LeaveRequestDayId.class,
|
||||||
|
constructor(Modifier.PROTECTED),
|
||||||
|
constructor(Modifier.PUBLIC, long.class, LocalDate.class));
|
||||||
|
assertConstructors(
|
||||||
|
LeaveRequestEntity.class,
|
||||||
|
constructor(Modifier.PROTECTED),
|
||||||
|
constructor(
|
||||||
|
PACKAGE_PRIVATE,
|
||||||
|
long.class,
|
||||||
|
LocalDate.class,
|
||||||
|
LocalDate.class,
|
||||||
|
String.class,
|
||||||
|
Instant.class,
|
||||||
|
Instant.class,
|
||||||
|
long.class,
|
||||||
|
Instant.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void immutableModelsRemainRecordsWithTheirComponentContracts() {
|
||||||
|
assertRecordComponents(
|
||||||
|
AttendanceActor.class,
|
||||||
|
component("userId", long.class),
|
||||||
|
component("role", AttendanceRole.class));
|
||||||
|
assertRecordComponents(
|
||||||
|
AttendanceDayContext.class,
|
||||||
|
component("activeIntern", boolean.class),
|
||||||
|
component("globalDayOff", boolean.class),
|
||||||
|
component("approvedLeave", boolean.class));
|
||||||
|
assertRecordComponents(
|
||||||
|
AttendancePolicy.class,
|
||||||
|
component("id", long.class),
|
||||||
|
component("effectiveFrom", LocalDate.class),
|
||||||
|
component("zoneId", ZoneId.class),
|
||||||
|
component("scheduledStart", LocalTime.class),
|
||||||
|
component("scheduledEnd", LocalTime.class),
|
||||||
|
component("checkInGraceMinutes", int.class),
|
||||||
|
component("checkoutGraceMinutes", int.class),
|
||||||
|
component("monthlyLeaveQuota", int.class),
|
||||||
|
component("violationPenalty", BigDecimal.class),
|
||||||
|
component("workdays", Set.class));
|
||||||
|
assertRecordComponents(
|
||||||
|
AttendanceRecord.class,
|
||||||
|
component("internId", long.class),
|
||||||
|
component("workDate", LocalDate.class),
|
||||||
|
component("policy", AttendancePolicy.class),
|
||||||
|
component("checkInAt", Instant.class),
|
||||||
|
component("checkOutAt", Instant.class));
|
||||||
|
assertRecordComponents(
|
||||||
|
AttendanceViolations.class,
|
||||||
|
component("late", boolean.class),
|
||||||
|
component("earlyDeparture", boolean.class),
|
||||||
|
component("missingCheckout", boolean.class));
|
||||||
|
assertRecordComponents(
|
||||||
|
AttendanceHistoryItem.class,
|
||||||
|
component("workDate", LocalDate.class),
|
||||||
|
component("checkInAt", Instant.class),
|
||||||
|
component("checkOutAt", Instant.class),
|
||||||
|
component("policy", AttendancePolicy.class),
|
||||||
|
component("violations", AttendanceViolations.class));
|
||||||
|
assertRecordComponents(
|
||||||
|
GlobalCalendarEvent.class,
|
||||||
|
component("id", long.class),
|
||||||
|
component("date", LocalDate.class),
|
||||||
|
component("name", String.class),
|
||||||
|
component("dayOff", boolean.class),
|
||||||
|
component("version", long.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void entitiesExposeOnlyIntentionalPublicAndProtectedDeclaredMethods() {
|
||||||
|
assertMethodSurface(
|
||||||
|
AttendancePolicyEntity.class,
|
||||||
|
method(Modifier.PUBLIC, "toDomain", AttendancePolicy.class));
|
||||||
|
assertMethodSurface(
|
||||||
|
AttendanceRecordEntity.class,
|
||||||
|
method(Modifier.PUBLIC, "toDomain", AttendanceRecord.class),
|
||||||
|
method(Modifier.PUBLIC, "setCheckOutAt", void.class, Instant.class),
|
||||||
|
method(Modifier.PUBLIC, "workDate", LocalDate.class));
|
||||||
|
assertMethodSurface(
|
||||||
|
GlobalCalendarEventEntity.class,
|
||||||
|
method(
|
||||||
|
Modifier.PUBLIC,
|
||||||
|
"update",
|
||||||
|
void.class,
|
||||||
|
LocalDate.class,
|
||||||
|
String.class,
|
||||||
|
boolean.class,
|
||||||
|
long.class),
|
||||||
|
method(Modifier.PUBLIC, "toDomain", GlobalCalendarEvent.class),
|
||||||
|
method(Modifier.PUBLIC, "calendarDate", LocalDate.class),
|
||||||
|
method(Modifier.PUBLIC, "version", long.class));
|
||||||
|
assertMethodSurface(LeaveRequestDayEntity.class);
|
||||||
|
assertMethodSurface(
|
||||||
|
LeaveRequestDayId.class,
|
||||||
|
method(Modifier.PUBLIC, "equals", boolean.class, Object.class),
|
||||||
|
method(Modifier.PUBLIC, "hashCode", int.class));
|
||||||
|
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())
|
||||||
|
.map(ConstructorContract::from)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
assertThat(actual).as(type.getName()).containsExactlyInAnyOrder(expectedConstructors);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void assertRecordComponents(
|
||||||
|
Class<?> type, RecordComponentContract... expectedComponents) {
|
||||||
|
assertThat(type.isRecord()).as(type.getName()).isTrue();
|
||||||
|
assertThat(Arrays.stream(type.getRecordComponents())
|
||||||
|
.map(component -> new RecordComponentContract(component.getName(), component.getType()))
|
||||||
|
.toList())
|
||||||
|
.as(type.getName())
|
||||||
|
.containsExactly(expectedComponents);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void assertMethodSurface(Class<?> type, MethodContract... expectedMethods) {
|
||||||
|
List<MethodContract> actual = Arrays.stream(type.getDeclaredMethods())
|
||||||
|
.filter(method -> Modifier.isPublic(method.getModifiers())
|
||||||
|
|| Modifier.isProtected(method.getModifiers()))
|
||||||
|
.map(MethodContract::from)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
assertThat(actual).as(type.getName()).containsExactlyInAnyOrder(expectedMethods);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ConstructorContract constructor(int modifiers, Class<?>... parameterTypes) {
|
||||||
|
return new ConstructorContract(modifiers, List.of(parameterTypes));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RecordComponentContract component(String name, Class<?> type) {
|
||||||
|
return new RecordComponentContract(name, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MethodContract method(
|
||||||
|
int modifiers, String name, Class<?> returnType, Class<?>... parameterTypes) {
|
||||||
|
return new MethodContract(modifiers, name, returnType, List.of(parameterTypes));
|
||||||
|
}
|
||||||
|
|
||||||
|
private record ConstructorContract(int modifiers, List<Class<?>> parameterTypes) {
|
||||||
|
|
||||||
|
private static ConstructorContract from(Constructor<?> constructor) {
|
||||||
|
return new ConstructorContract(
|
||||||
|
constructor.getModifiers(), List.of(constructor.getParameterTypes()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private record RecordComponentContract(String name, Class<?> type) {}
|
||||||
|
|
||||||
|
private record MethodContract(
|
||||||
|
int modifiers, String name, Class<?> returnType, List<Class<?>> parameterTypes) {
|
||||||
|
|
||||||
|
private static MethodContract from(Method method) {
|
||||||
|
return new MethodContract(
|
||||||
|
method.getModifiers(),
|
||||||
|
method.getName(),
|
||||||
|
method.getReturnType(),
|
||||||
|
List.of(method.getParameterTypes()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user