Keep payment violations domain-shaped (#55)
This commit is contained in:
1 parent
9054cc62d3
commit
41b086fb33
14 files changed
+106
-69
No files matched your search
+2
-1
@@ -103,7 +103,8 @@ ResponseEntity<ValidationApiError> unreadableRequest() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
|
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
|
||||||
ResponseEntity<ValidationApiError> invalidParameter(MethodArgumentTypeMismatchException exception) {
|
ResponseEntity<ValidationApiError> invalidParameter(
|
||||||
|
MethodArgumentTypeMismatchException exception) {
|
||||||
return validation(exception.getName(), "invalid");
|
return validation(exception.getName(), "invalid");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
@@ -14,6 +14,8 @@ import java.time.LocalDate;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
final class RentPaymentApi {
|
final class RentPaymentApi {
|
||||||
|
static final String CONCURRENT_CHANGE_CODE = "concurrent_change";
|
||||||
|
|
||||||
private RentPaymentApi() {}
|
private RentPaymentApi() {}
|
||||||
|
|
||||||
record ConcurrentChangeResponse(String code, String message) {}
|
record ConcurrentChangeResponse(String code, String message) {}
|
||||||
|
|||||||
+22
-3
@@ -68,7 +68,7 @@ ResponseEntity<RentPaymentApi.ConcurrentChangeResponse> concurrentChange(
|
|||||||
return ResponseEntity.status(HttpStatus.CONFLICT)
|
return ResponseEntity.status(HttpStatus.CONFLICT)
|
||||||
.body(
|
.body(
|
||||||
new RentPaymentApi.ConcurrentChangeResponse(
|
new RentPaymentApi.ConcurrentChangeResponse(
|
||||||
ConcurrentChange.CODE,
|
RentPaymentApi.CONCURRENT_CHANGE_CODE,
|
||||||
"La ressource a été modifiée ; rechargez-la puis réessayez."));
|
"La ressource a été modifiée ; rechargez-la puis réessayez."));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,8 +78,27 @@ ResponseEntity<RentPaymentApi.ValidationFailureResponse> invalidPayment(
|
|||||||
return ResponseEntity.badRequest()
|
return ResponseEntity.badRequest()
|
||||||
.body(
|
.body(
|
||||||
new RentPaymentApi.ValidationFailureResponse(
|
new RentPaymentApi.ValidationFailureResponse(
|
||||||
"validation_failed",
|
"validation_failed", List.of(validationError(failure.rule()))));
|
||||||
List.of(new RentPaymentApi.FieldErrorResponse(failure.field(), failure.code()))));
|
}
|
||||||
|
|
||||||
|
private static RentPaymentApi.FieldErrorResponse validationError(
|
||||||
|
RentPaymentValidationFailure.Rule rule) {
|
||||||
|
return switch (rule) {
|
||||||
|
case RECEIVED_AT_MUST_NOT_BE_FUTURE ->
|
||||||
|
new RentPaymentApi.FieldErrorResponse("receivedAt", "future_date");
|
||||||
|
case REFUNDED_AT_MUST_NOT_BE_FUTURE ->
|
||||||
|
new RentPaymentApi.FieldErrorResponse("refundedAt", "future_date");
|
||||||
|
case RENT_DUE_MUST_BELONG_TO_PAYMENT_LEASE ->
|
||||||
|
new RentPaymentApi.FieldErrorResponse("rentDueId", "different_lease");
|
||||||
|
case RENT_DUE_MUST_NOT_ALREADY_BE_ALLOCATED ->
|
||||||
|
new RentPaymentApi.FieldErrorResponse("rentDueId", "already_allocated");
|
||||||
|
case AMOUNT_MUST_NOT_EXCEED_PAYMENT_BALANCE ->
|
||||||
|
new RentPaymentApi.FieldErrorResponse("amount", "exceeds_payment_balance");
|
||||||
|
case AMOUNT_MUST_NOT_EXCEED_RENT_DUE_BALANCE ->
|
||||||
|
new RentPaymentApi.FieldErrorResponse("amount", "exceeds_rent_due_balance");
|
||||||
|
case AMOUNT_MUST_NOT_EXCEED_UNALLOCATED_BALANCE ->
|
||||||
|
new RentPaymentApi.FieldErrorResponse("amount", "exceeds_unallocated_balance");
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
|
|||||||
+4
-8
@@ -79,15 +79,13 @@ public RentPayment allocate(
|
|||||||
}
|
}
|
||||||
if (!payment.leaseId().equals(due.leaseId())) {
|
if (!payment.leaseId().equals(due.leaseId())) {
|
||||||
throw new RentPaymentValidationFailure(
|
throw new RentPaymentValidationFailure(
|
||||||
"rentDueId",
|
RentPaymentValidationFailure.Rule.RENT_DUE_MUST_BELONG_TO_PAYMENT_LEASE,
|
||||||
"different_lease",
|
|
||||||
"Une affectation doit viser une échéance du même bail.");
|
"Une affectation doit viser une échéance du même bail.");
|
||||||
}
|
}
|
||||||
if (payment.allocations().stream()
|
if (payment.allocations().stream()
|
||||||
.anyMatch(allocation -> allocation.rentDueId().equals(due.id()))) {
|
.anyMatch(allocation -> allocation.rentDueId().equals(due.id()))) {
|
||||||
throw new RentPaymentValidationFailure(
|
throw new RentPaymentValidationFailure(
|
||||||
"rentDueId",
|
RentPaymentValidationFailure.Rule.RENT_DUE_MUST_NOT_ALREADY_BE_ALLOCATED,
|
||||||
"already_allocated",
|
|
||||||
"Cette échéance a déjà reçu une affectation de ce règlement.");
|
"Cette échéance a déjà reçu une affectation de ce règlement.");
|
||||||
}
|
}
|
||||||
if (due.settlement() == RentDueSettlement.PAID) {
|
if (due.settlement() == RentDueSettlement.PAID) {
|
||||||
@@ -96,14 +94,12 @@ public RentPayment allocate(
|
|||||||
}
|
}
|
||||||
if (amount != null && amount.compareTo(payment.unallocatedBalance()) > 0) {
|
if (amount != null && amount.compareTo(payment.unallocatedBalance()) > 0) {
|
||||||
throw new RentPaymentValidationFailure(
|
throw new RentPaymentValidationFailure(
|
||||||
"amount",
|
RentPaymentValidationFailure.Rule.AMOUNT_MUST_NOT_EXCEED_PAYMENT_BALANCE,
|
||||||
"exceeds_payment_balance",
|
|
||||||
"L'affectation dépasse le solde non affecté du règlement.");
|
"L'affectation dépasse le solde non affecté du règlement.");
|
||||||
}
|
}
|
||||||
if (amount != null && amount.compareTo(due.remainingAmount()) > 0) {
|
if (amount != null && amount.compareTo(due.remainingAmount()) > 0) {
|
||||||
throw new RentPaymentValidationFailure(
|
throw new RentPaymentValidationFailure(
|
||||||
"amount",
|
RentPaymentValidationFailure.Rule.AMOUNT_MUST_NOT_EXCEED_RENT_DUE_BALANCE,
|
||||||
"exceeds_rent_due_balance",
|
|
||||||
"L'affectation dépasse le solde restant de l'échéance.");
|
"L'affectation dépasse le solde restant de l'échéance.");
|
||||||
}
|
}
|
||||||
PaymentAllocation allocation =
|
PaymentAllocation allocation =
|
||||||
|
|||||||
+1
-2
@@ -88,8 +88,7 @@ private RentPayment recordInTransaction(
|
|||||||
LocalDate today = LocalDate.now(clock.withZone(timeZone));
|
LocalDate today = LocalDate.now(clock.withZone(timeZone));
|
||||||
if (receivedAt == null || receivedAt.isAfter(today)) {
|
if (receivedAt == null || receivedAt.isAfter(today)) {
|
||||||
throw new RentPaymentValidationFailure(
|
throw new RentPaymentValidationFailure(
|
||||||
"receivedAt",
|
RentPaymentValidationFailure.Rule.RECEIVED_AT_MUST_NOT_BE_FUTURE,
|
||||||
"future_date",
|
|
||||||
"La date de réception du règlement ne peut pas être future.");
|
"La date de réception du règlement ne peut pas être future.");
|
||||||
}
|
}
|
||||||
RentDue due =
|
RentDue due =
|
||||||
|
|||||||
+3
-3
@@ -72,7 +72,8 @@ private RentPayment refundInTransaction(
|
|||||||
LocalDate today = LocalDate.now(clock.withZone(timeZone));
|
LocalDate today = LocalDate.now(clock.withZone(timeZone));
|
||||||
if (refundedAt == null || refundedAt.isAfter(today)) {
|
if (refundedAt == null || refundedAt.isAfter(today)) {
|
||||||
throw new RentPaymentValidationFailure(
|
throw new RentPaymentValidationFailure(
|
||||||
"refundedAt", "future_date", "La date du remboursement ne peut pas être future.");
|
RentPaymentValidationFailure.Rule.REFUNDED_AT_MUST_NOT_BE_FUTURE,
|
||||||
|
"La date du remboursement ne peut pas être future.");
|
||||||
}
|
}
|
||||||
RentPayment payment = payment(landlordId, paymentId);
|
RentPayment payment = payment(landlordId, paymentId);
|
||||||
if (payment.version() != paymentVersion) {
|
if (payment.version() != paymentVersion) {
|
||||||
@@ -83,8 +84,7 @@ private RentPayment refundInTransaction(
|
|||||||
}
|
}
|
||||||
if (amount != null && amount.compareTo(payment.unallocatedBalance()) > 0) {
|
if (amount != null && amount.compareTo(payment.unallocatedBalance()) > 0) {
|
||||||
throw new RentPaymentValidationFailure(
|
throw new RentPaymentValidationFailure(
|
||||||
"amount",
|
RentPaymentValidationFailure.Rule.AMOUNT_MUST_NOT_EXCEED_UNALLOCATED_BALANCE,
|
||||||
"exceeds_unallocated_balance",
|
|
||||||
"Le remboursement dépasse le solde non affecté du règlement.");
|
"Le remboursement dépasse le solde non affecté du règlement.");
|
||||||
}
|
}
|
||||||
RentPaymentRefund refund = RentPaymentRefund.recorded(refundId, payment, amount, refundedAt);
|
RentPaymentRefund refund = RentPaymentRefund.recorded(refundId, payment, amount, refundedAt);
|
||||||
|
|||||||
+15
-11
@@ -3,20 +3,24 @@ package fr.nessar.lmnporganizer.rental.application.rent;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
public final class RentPaymentValidationFailure extends RuntimeException {
|
public final class RentPaymentValidationFailure extends RuntimeException {
|
||||||
private final String field;
|
public enum Rule {
|
||||||
private final String code;
|
RECEIVED_AT_MUST_NOT_BE_FUTURE,
|
||||||
|
REFUNDED_AT_MUST_NOT_BE_FUTURE,
|
||||||
|
RENT_DUE_MUST_BELONG_TO_PAYMENT_LEASE,
|
||||||
|
RENT_DUE_MUST_NOT_ALREADY_BE_ALLOCATED,
|
||||||
|
AMOUNT_MUST_NOT_EXCEED_PAYMENT_BALANCE,
|
||||||
|
AMOUNT_MUST_NOT_EXCEED_RENT_DUE_BALANCE,
|
||||||
|
AMOUNT_MUST_NOT_EXCEED_UNALLOCATED_BALANCE
|
||||||
|
}
|
||||||
|
|
||||||
public RentPaymentValidationFailure(String field, String code, String message) {
|
private final Rule rule;
|
||||||
|
|
||||||
|
public RentPaymentValidationFailure(Rule rule, String message) {
|
||||||
super(message);
|
super(message);
|
||||||
this.field = Objects.requireNonNull(field);
|
this.rule = Objects.requireNonNull(rule);
|
||||||
this.code = Objects.requireNonNull(code);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String field() {
|
public Rule rule() {
|
||||||
return field;
|
return rule;
|
||||||
}
|
|
||||||
|
|
||||||
public String code() {
|
|
||||||
return code;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
-2
@@ -1,8 +1,6 @@
|
|||||||
package fr.nessar.lmnporganizer.rental.application.shared;
|
package fr.nessar.lmnporganizer.rental.application.shared;
|
||||||
|
|
||||||
public final class ConcurrentChange extends RuntimeException {
|
public final class ConcurrentChange extends RuntimeException {
|
||||||
public static final String CODE = "concurrent_change";
|
|
||||||
|
|
||||||
public ConcurrentChange(String resource) {
|
public ConcurrentChange(String resource) {
|
||||||
super(resource + " changed concurrently; reload it and retry.");
|
super(resource + " changed concurrently; reload it and retry.");
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-5
@@ -148,8 +148,7 @@ void authenticateLandlordAndFixTime() {
|
|||||||
LeaseClauseCatalog.load(getClass().getResourceAsStream("/lease-clauses.yml"));
|
LeaseClauseCatalog.load(getClass().getResourceAsStream("/lease-clauses.yml"));
|
||||||
when(leaseClauseCatalog.available(anyString(), any()))
|
when(leaseClauseCatalog.available(anyString(), any()))
|
||||||
.thenAnswer(
|
.thenAnswer(
|
||||||
invocation ->
|
invocation -> clauses.available(invocation.getArgument(0), invocation.getArgument(1)));
|
||||||
clauses.available(invocation.getArgument(0), invocation.getArgument(1)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -444,9 +443,7 @@ void leaseDraftRequestsRejectAnImpossibleAnnualRevisionDay() throws Exception {
|
|||||||
"""))
|
"""))
|
||||||
.andExpect(status().isBadRequest())
|
.andExpect(status().isBadRequest())
|
||||||
.andExpect(jsonPath("$.code").value("validation_failed"))
|
.andExpect(jsonPath("$.code").value("validation_failed"))
|
||||||
.andExpect(
|
.andExpect(jsonPath("$.errors[0].field").value("legalTerms.rentRevision.annualRevisionDay"))
|
||||||
jsonPath("$.errors[0].field")
|
|
||||||
.value("legalTerms.rentRevision.annualRevisionDay"))
|
|
||||||
.andExpect(jsonPath("$.errors[0].code").value("invalid_date"));
|
.andExpect(jsonPath("$.errors[0].code").value("invalid_date"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+12
@@ -3,6 +3,7 @@ package fr.nessar.lmnporganizer.rental.adapter.in.web.common;
|
|||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
import fr.nessar.lmnporganizer.rental.application.rent.RentPaymentConflict;
|
import fr.nessar.lmnporganizer.rental.application.rent.RentPaymentConflict;
|
||||||
|
import fr.nessar.lmnporganizer.rental.application.shared.ConcurrentChange;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
|
|
||||||
@@ -16,4 +17,15 @@ void keepsBusinessConflictsAsUncoded409Responses() {
|
|||||||
assertEquals(
|
assertEquals(
|
||||||
new RentalApiExceptionHandler.ApiError("Échéance déjà réglée"), response.getBody());
|
new RentalApiExceptionHandler.ApiError("Échéance déjà réglée"), response.getBody());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void keepsSharedConcurrencyFailuresAsSafeUncoded409Responses() {
|
||||||
|
var response = new RentalApiExceptionHandler().concurrent(new ConcurrentChange("Rent payment"));
|
||||||
|
|
||||||
|
assertEquals(HttpStatus.CONFLICT, response.getStatusCode());
|
||||||
|
assertEquals(
|
||||||
|
new RentalApiExceptionHandler.ApiError(
|
||||||
|
"La ressource a été modifiée ; rechargez-la puis réessayez."),
|
||||||
|
response.getBody());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+29
-22
@@ -13,34 +13,41 @@ import fr.nessar.lmnporganizer.rental.application.rent.RentPaymentValidationFail
|
|||||||
import fr.nessar.lmnporganizer.rental.application.rent.ReverseRentPaymentService;
|
import fr.nessar.lmnporganizer.rental.application.rent.ReverseRentPaymentService;
|
||||||
import fr.nessar.lmnporganizer.rental.application.shared.ConcurrentChange;
|
import fr.nessar.lmnporganizer.rental.application.shared.ConcurrentChange;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
|
|
||||||
class RentPaymentControllerErrorTest {
|
class RentPaymentControllerErrorTest {
|
||||||
@Test
|
@Test
|
||||||
void mapsPaymentInputFailuresToTheStableValidationContract() {
|
void mapsPaymentRulesToTheStableValidationContract() {
|
||||||
RentPaymentController controller =
|
Map<RentPaymentValidationFailure.Rule, RentPaymentApi.FieldErrorResponse> expected =
|
||||||
new RentPaymentController(
|
Map.of(
|
||||||
mock(RentPaymentHistoryService.class),
|
RentPaymentValidationFailure.Rule.RECEIVED_AT_MUST_NOT_BE_FUTURE,
|
||||||
mock(RecordRentPaymentService.class),
|
new RentPaymentApi.FieldErrorResponse("receivedAt", "future_date"),
|
||||||
mock(AllocateRentPaymentService.class),
|
RentPaymentValidationFailure.Rule.REFUNDED_AT_MUST_NOT_BE_FUTURE,
|
||||||
mock(RefundRentPaymentService.class),
|
new RentPaymentApi.FieldErrorResponse("refundedAt", "future_date"),
|
||||||
mock(ReverseRentPaymentService.class),
|
RentPaymentValidationFailure.Rule.RENT_DUE_MUST_BELONG_TO_PAYMENT_LEASE,
|
||||||
mock(GeneratePaymentReceiptService.class),
|
new RentPaymentApi.FieldErrorResponse("rentDueId", "different_lease"),
|
||||||
mock(AuthenticatedLandlord.class));
|
RentPaymentValidationFailure.Rule.RENT_DUE_MUST_NOT_ALREADY_BE_ALLOCATED,
|
||||||
|
new RentPaymentApi.FieldErrorResponse("rentDueId", "already_allocated"),
|
||||||
|
RentPaymentValidationFailure.Rule.AMOUNT_MUST_NOT_EXCEED_PAYMENT_BALANCE,
|
||||||
|
new RentPaymentApi.FieldErrorResponse("amount", "exceeds_payment_balance"),
|
||||||
|
RentPaymentValidationFailure.Rule.AMOUNT_MUST_NOT_EXCEED_RENT_DUE_BALANCE,
|
||||||
|
new RentPaymentApi.FieldErrorResponse("amount", "exceeds_rent_due_balance"),
|
||||||
|
RentPaymentValidationFailure.Rule.AMOUNT_MUST_NOT_EXCEED_UNALLOCATED_BALANCE,
|
||||||
|
new RentPaymentApi.FieldErrorResponse("amount", "exceeds_unallocated_balance"));
|
||||||
|
|
||||||
var response =
|
expected.forEach(
|
||||||
controller.invalidPayment(
|
(rule, error) -> {
|
||||||
new RentPaymentValidationFailure(
|
var response =
|
||||||
"amount", "exceeds_unallocated_balance", "Le remboursement dépasse le solde."));
|
controller()
|
||||||
|
.invalidPayment(new RentPaymentValidationFailure(rule, "internal detail"));
|
||||||
|
|
||||||
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
|
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
|
||||||
assertEquals(
|
assertEquals(
|
||||||
new RentPaymentApi.ValidationFailureResponse(
|
new RentPaymentApi.ValidationFailureResponse("validation_failed", List.of(error)),
|
||||||
"validation_failed",
|
response.getBody());
|
||||||
List.of(
|
});
|
||||||
new RentPaymentApi.FieldErrorResponse("amount", "exceeds_unallocated_balance"))),
|
|
||||||
response.getBody());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -48,7 +55,7 @@ void mapsOnlyConcurrentChangesToTheCodedStaleContract() {
|
|||||||
var response = controller().concurrentChange(new ConcurrentChange("Rent payment"));
|
var response = controller().concurrentChange(new ConcurrentChange("Rent payment"));
|
||||||
|
|
||||||
assertEquals(HttpStatus.CONFLICT, response.getStatusCode());
|
assertEquals(HttpStatus.CONFLICT, response.getStatusCode());
|
||||||
assertEquals(ConcurrentChange.CODE, response.getBody().code());
|
assertEquals(RentPaymentApi.CONCURRENT_CHANGE_CODE, response.getBody().code());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static RentPaymentController controller() {
|
private static RentPaymentController controller() {
|
||||||
|
|||||||
+7
-6
@@ -271,8 +271,8 @@ void allocationMapsDuplicateAndBalanceFailuresToFields() {
|
|||||||
() ->
|
() ->
|
||||||
service.allocate(
|
service.allocate(
|
||||||
LANDLORD_ID, PAYMENT_ID, "operation-2", 4, DUE_ID, 2, new BigDecimal("10")));
|
LANDLORD_ID, PAYMENT_ID, "operation-2", 4, DUE_ID, 2, new BigDecimal("10")));
|
||||||
assertEquals("rentDueId", duplicate.field());
|
assertEquals(
|
||||||
assertEquals("already_allocated", duplicate.code());
|
RentPaymentValidationFailure.Rule.RENT_DUE_MUST_NOT_ALREADY_BE_ALLOCATED, duplicate.rule());
|
||||||
|
|
||||||
RentPayment otherPayment =
|
RentPayment otherPayment =
|
||||||
RentPayment.reconstitute(
|
RentPayment.reconstitute(
|
||||||
@@ -293,16 +293,17 @@ void allocationMapsDuplicateAndBalanceFailuresToFields() {
|
|||||||
() ->
|
() ->
|
||||||
service.allocate(
|
service.allocate(
|
||||||
LANDLORD_ID, PAYMENT_ID, "operation-3", 4, DUE_ID, 2, new BigDecimal("101")));
|
LANDLORD_ID, PAYMENT_ID, "operation-3", 4, DUE_ID, 2, new BigDecimal("101")));
|
||||||
assertEquals("amount", balance.field());
|
assertEquals(
|
||||||
assertEquals("exceeds_payment_balance", balance.code());
|
RentPaymentValidationFailure.Rule.AMOUNT_MUST_NOT_EXCEED_PAYMENT_BALANCE, balance.rule());
|
||||||
RentPaymentValidationFailure dueBalance =
|
RentPaymentValidationFailure dueBalance =
|
||||||
assertThrows(
|
assertThrows(
|
||||||
RentPaymentValidationFailure.class,
|
RentPaymentValidationFailure.class,
|
||||||
() ->
|
() ->
|
||||||
service.allocate(
|
service.allocate(
|
||||||
LANDLORD_ID, PAYMENT_ID, "operation-4", 4, DUE_ID, 2, new BigDecimal("41")));
|
LANDLORD_ID, PAYMENT_ID, "operation-4", 4, DUE_ID, 2, new BigDecimal("41")));
|
||||||
assertEquals("amount", dueBalance.field());
|
assertEquals(
|
||||||
assertEquals("exceeds_rent_due_balance", dueBalance.code());
|
RentPaymentValidationFailure.Rule.AMOUNT_MUST_NOT_EXCEED_RENT_DUE_BALANCE,
|
||||||
|
dueBalance.rule());
|
||||||
|
|
||||||
verify(rentDues, never()).save(any());
|
verify(rentDues, never()).save(any());
|
||||||
verify(payments, never()).save(any());
|
verify(payments, never()).save(any());
|
||||||
|
|||||||
+2
-2
@@ -219,8 +219,8 @@ void staleOrPaidDueAndCashFromTheNextParisDayCreateNoPayment() {
|
|||||||
LocalDate.of(2026, 2, 11),
|
LocalDate.of(2026, 2, 11),
|
||||||
PARIS,
|
PARIS,
|
||||||
new BigDecimal("1000")));
|
new BigDecimal("1000")));
|
||||||
assertEquals("receivedAt", futureDate.field());
|
assertEquals(
|
||||||
assertEquals("future_date", futureDate.code());
|
RentPaymentValidationFailure.Rule.RECEIVED_AT_MUST_NOT_BE_FUTURE, futureDate.rule());
|
||||||
due.allocate(due.total());
|
due.allocate(due.total());
|
||||||
assertThrows(
|
assertThrows(
|
||||||
RentPaymentConflict.class,
|
RentPaymentConflict.class,
|
||||||
|
|||||||
+5
-4
@@ -179,8 +179,8 @@ void futureDateAndStalePaymentVersionCreateNoRefund() {
|
|||||||
new BigDecimal("50"),
|
new BigDecimal("50"),
|
||||||
LocalDate.of(2026, 2, 11),
|
LocalDate.of(2026, 2, 11),
|
||||||
PARIS));
|
PARIS));
|
||||||
assertEquals("refundedAt", futureDate.field());
|
assertEquals(
|
||||||
assertEquals("future_date", futureDate.code());
|
RentPaymentValidationFailure.Rule.REFUNDED_AT_MUST_NOT_BE_FUTURE, futureDate.rule());
|
||||||
assertThrows(
|
assertThrows(
|
||||||
ConcurrentChange.class,
|
ConcurrentChange.class,
|
||||||
() ->
|
() ->
|
||||||
@@ -204,8 +204,9 @@ void futureDateAndStalePaymentVersionCreateNoRefund() {
|
|||||||
new BigDecimal("1001"),
|
new BigDecimal("1001"),
|
||||||
LocalDate.of(2026, 2, 9),
|
LocalDate.of(2026, 2, 9),
|
||||||
PARIS));
|
PARIS));
|
||||||
assertEquals("amount", excessAmount.field());
|
assertEquals(
|
||||||
assertEquals("exceeds_unallocated_balance", excessAmount.code());
|
RentPaymentValidationFailure.Rule.AMOUNT_MUST_NOT_EXCEED_UNALLOCATED_BALANCE,
|
||||||
|
excessAmount.rule());
|
||||||
|
|
||||||
verify(payments, never()).save(any());
|
verify(payments, never()).save(any());
|
||||||
verify(refunds, never()).save(any());
|
verify(refunds, never()).save(any());
|
||||||
|
|||||||
Reference in new issue
Block a user