1
0

produce client-error for unspecified-properties (#166)

Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/166
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
This commit is contained in:
Michael Hoennig
2025-03-20 12:04:57 +01:00
parent 4994bac101
commit e6b32eda88
21 changed files with 148 additions and 47 deletions

View File

@@ -555,7 +555,6 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
.contentType(ContentType.JSON)
.body("""
{
"validFrom": "2020-06-05",
"validTo": "2022-12-31",
"resources": {
"Traffic": 500,

View File

@@ -2,6 +2,7 @@ package net.hostsharing.hsadminng.hs.booking.item;
import net.hostsharing.hsadminng.config.JsonObjectMapperConfiguration;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingItemInsertResource;
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectRealEntity;
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectRealRepository;
import net.hostsharing.hsadminng.mapper.StrictMapper;
@@ -35,6 +36,7 @@ import static org.hamcrest.Matchers.matchesRegex;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -112,7 +114,6 @@ class HsBookingItemControllerRestTest {
"type": "MANAGED_SERVER",
"caption": "some new booking",
"validTo": "{validTo}",
"garbage": "should not be accepted",
"resources": { "CPU": 12, "RAM": 4, "SSD": 100, "Traffic": 250 }
}
"""
@@ -120,6 +121,7 @@ class HsBookingItemControllerRestTest {
.replace("{validTo}", LocalDate.now().plusMonths(1).toString())
)
.accept(MediaType.APPLICATION_JSON))
.andDo(print())
// then
.andExpect(status().isCreated())
@@ -161,7 +163,7 @@ class HsBookingItemControllerRestTest {
"project.uuid": "{projectUuid}",
"type": "MANAGED_SERVER",
"caption": "some new booking",
"validFrom": "{validFrom}",
"validFrom": "{validFrom}", // not specified => not accepted
"resources": { "CPU": 12, "RAM": 4, "SSD": 100, "Traffic": 250 }
}
"""
@@ -169,24 +171,15 @@ class HsBookingItemControllerRestTest {
.replace("{validFrom}", LocalDate.now().plusMonths(1).toString())
)
.accept(MediaType.APPLICATION_JSON))
.andDo(print())
// then
// TODO.test: MockMvc does not seem to validate additionalProperties=false
// .andExpect(status().is4xxClientError())
.andExpect(status().isCreated())
.andExpect(status().is4xxClientError())
.andExpect(jsonPath("$", lenientlyEquals("""
{
"type": "MANAGED_SERVER",
"caption": "some new booking",
"validFrom": "{today}",
"validTo": null,
"resources": { "CPU": 12, "SSD": 100, "Traffic": 250 }
}
"""
.replace("{today}", LocalDate.now().toString())
.replace("{todayPlus1Month}", LocalDate.now().plusMonths(1).toString()))
))
.andExpect(header().string("Location", matchesRegex("http://localhost/api/hs/booking/items/[^/]*")));
"message": "ERROR: [400] JSON parse error: Unrecognized field \\"validFrom\\" (class ${resourceClass}), not marked as ignorable"
}
""".replace("${resourceClass}", HsBookingItemInsertResource.class.getName()))));
}
}
}

View File

@@ -50,6 +50,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -667,10 +668,8 @@ public class HsHostingAssetControllerRestTest {
.contentType(MediaType.APPLICATION_JSON)
.content("""
{
"type": "DOMAIN_HTTP_SETUP",
"identifier": "updated example.org|HTTP",
"caption": "some updated fake Domain-HTTP-Setup",
"alarmContact": null,
"alarmContact.uuid": null,
"config": {
"autoconfig": true,
"multiviews": true,
@@ -682,6 +681,7 @@ public class HsHostingAssetControllerRestTest {
}
""")
.accept(MediaType.APPLICATION_JSON))
.andDo(print())
// then
.andExpect(status().is2xxSuccessful())

View File

@@ -627,13 +627,12 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
.contentType(ContentType.JSON)
.body("""
{
"contactUuid": "%s",
"vatId": "VAT222222",
"vatCountryCode": "AA",
"vatBusiness": true,
"defaultPrefix": "for"
}
""".formatted(givenContact.getUuid()))
""")
.port(port)
.when()
.patch("http://localhost/api/hs/office/debitors/" + givenDebitor.getUuid())

View File

@@ -157,7 +157,7 @@ class HsOfficePartnerControllerAcceptanceTest extends ContextBasedTestWithCleanu
final var givenMandantPerson = personRealRepo.findPersonByOptionalNameLike("Hostsharing eG").get(0);
final var givenPerson = personRealRepo.findPersonByOptionalNameLike("Third").get(0);
final var location = RestAssured // @formatter:off
RestAssured // @formatter:off
.given()
.header("Authorization", "Bearer superuser-alex@hostsharing.net")
.contentType(ContentType.JSON)
@@ -169,8 +169,6 @@ class HsOfficePartnerControllerAcceptanceTest extends ContextBasedTestWithCleanu
"holder.uuid": "%s",
"contact.uuid": "%s"
},
"person.uuid": "%s",
"contact.uuid": "%s",
"details": {}
}
""".formatted(
@@ -207,8 +205,6 @@ class HsOfficePartnerControllerAcceptanceTest extends ContextBasedTestWithCleanu
"holder.uuid": "%s",
"contact.uuid": "%s"
},
"person.uuid": "%s",
"contact.uuid": "%s",
"details": {}
}
""".formatted(
@@ -324,7 +320,6 @@ class HsOfficePartnerControllerAcceptanceTest extends ContextBasedTestWithCleanu
.contentType(ContentType.JSON)
.body("""
{
"partnerNumber": "P-20011",
"partnerRel": {
"holder.uuid": "%s"
},

View File

@@ -10,28 +10,28 @@ import net.hostsharing.hsadminng.mapper.StrictMapper;
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.openapitools.jackson.nullable.JsonNullable;
import jakarta.validation.ValidationException;
import java.time.LocalDate;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
import static org.assertj.core.api.Assertions.catchThrowable;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.lenient;
@TestInstance(PER_CLASS)
@ExtendWith(MockitoExtension.class)
// This test class does not subclass PatchUnitTestBase because it has no directly patchable properties.
// But the factory-structure is kept, so PatchUnitTestBase could easily be plugged back in if needed.
class HsOfficePartnerEntityPatcherUnitTest {
private static final UUID INITIAL_PARTNER_UUID = UUID.randomUUID();
private static final int INITIAL_PARTNER_NUMBER = 12345;
private static final UUID INITIAL_CONTACT_UUID = UUID.randomUUID();
private static final UUID INITIAL_PARTNER_PERSON_UUID = UUID.randomUUID();
private static final UUID INITIAL_DETAILS_UUID = UUID.randomUUID();
@@ -50,7 +50,9 @@ class HsOfficePartnerEntityPatcherUnitTest {
@Mock
private EntityManagerWrapper emw;
private StrictMapper mapper = new StrictMapper(emw);
private final StrictMapper mapper = new StrictMapper(emw);
private final HsOfficePartnerPatchResource patchResource = newPatchResource();
private final HsOfficePartnerRbacEntity entity = newInitialEntity();
@BeforeEach
void initMocks() {
@@ -60,14 +62,88 @@ class HsOfficePartnerEntityPatcherUnitTest {
HsOfficeContactRealEntity.builder().uuid(invocation.getArgument(1)).build());
}
@Test
void ignorePartnerUuidIfNotGiven() {
// given
patchResource.setUuid(null);
// when
createPatcher(entity).apply(patchResource);
// then
assertThat(entity.getUuid()).isEqualTo(INITIAL_PARTNER_UUID);
}
@Test
void ignoreUnchangedPartnerUuid() {
// given
patchResource.setUuid(JsonNullable.of(INITIAL_PARTNER_UUID));
// when
createPatcher(entity).apply(patchResource);
// then
assertThat(entity.getUuid()).isEqualTo(INITIAL_PARTNER_UUID);
}
@Test
void rejectChangingThePartnerUuid() {
// given
patchResource.setUuid(JsonNullable.of(UUID.randomUUID()));
// when
final var exception = catchThrowable(() -> createPatcher(entity).apply(patchResource));
// then
assertThat(exception).isInstanceOf(ValidationException.class).hasMessageContaining(
"uuid cannot be changed, either leave empty or leave unchanged as " + INITIAL_PARTNER_UUID
);
}
@Test
void ignorePartnerNumberIfNotGiven() {
// given
patchResource.setPartnerNumber(null);
// when
createPatcher(entity).apply(patchResource);
// then
assertThat(entity.getPartnerNumber()).isEqualTo(INITIAL_PARTNER_NUMBER);
}
@Test
void ignoreUnchangedPartnerNumber() {
// given
patchResource.setPartnerNumber(JsonNullable.of(String.valueOf(INITIAL_PARTNER_NUMBER)));
// when
createPatcher(entity).apply(patchResource);
// then
assertThat(entity.getPartnerNumber()).isEqualTo(INITIAL_PARTNER_NUMBER);
}
@Test
void rejectChangingThePartnerNumber() {
// given
patchResource.setPartnerNumber(JsonNullable.of("99999"));
// when
final var exception = catchThrowable(() -> createPatcher(entity).apply(patchResource));
// then
assertThat(exception).isInstanceOf(ValidationException.class).hasMessageContaining(
"partnerNumber cannot be changed, either leave empty or leave unchanged as " + INITIAL_PARTNER_NUMBER
);
}
@Test
void patchPartnerPerson() {
// given
final var patchResource = newPatchResource();
final var newHolderUuid = UUID.randomUUID();
patchResource.setPartnerRel(new HsOfficeRelationPatchResource());
patchResource.getPartnerRel().setHolderUuid(JsonNullable.of(newHolderUuid));
final var entity = newInitialEntity();
// when
createPatcher(entity).apply(patchResource);
@@ -79,11 +155,9 @@ class HsOfficePartnerEntityPatcherUnitTest {
@Test
void patchPartnerContact() {
// given
final var patchResource = newPatchResource();
final var newContactUuid = UUID.randomUUID();
patchResource.setPartnerRel(new HsOfficeRelationPatchResource());
patchResource.getPartnerRel().setContactUuid(JsonNullable.of(newContactUuid));
final var entity = newInitialEntity();
// when
createPatcher(entity).apply(patchResource);
@@ -95,11 +169,9 @@ class HsOfficePartnerEntityPatcherUnitTest {
@Test
void patchPartnerDetails() {
// given
final var patchResource = newPatchResource();
final var newDateOfBirth = LocalDate.now();
patchResource.setDetails(new HsOfficePartnerDetailsPatchResource());
patchResource.getDetails().setDateOfDeath(JsonNullable.of(newDateOfBirth));
final var entity = newInitialEntity();
// when
createPatcher(entity).apply(patchResource);
@@ -111,7 +183,7 @@ class HsOfficePartnerEntityPatcherUnitTest {
protected HsOfficePartnerRbacEntity newInitialEntity() {
final var entity = HsOfficePartnerRbacEntity.builder()
.uuid(INITIAL_PARTNER_UUID)
.partnerNumber(12345)
.partnerNumber(INITIAL_PARTNER_NUMBER)
.partnerRel(HsOfficeRelationRealEntity.builder()
.holder(givenInitialPartnerPerson)
.contact(givenInitialContact)

View File

@@ -398,7 +398,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
void shouldInvalidateSepaMandateForDebitor() {
new InvalidateSepaMandateForDebitor(scenarioTest)
.given("bankAccountIBAN", "DE02701500000000594937")
.given("mandateValidUntil", "2025-09-30")
.given("mandateValidTo", "2025-09-30")
.doRun();
}

View File

@@ -25,7 +25,7 @@ public class InvalidateSepaMandateForDebitor extends UseCase<InvalidateSepaManda
return withTitle("Patch the End of the Mandate into the SEPA-Mandate", () ->
httpPatch("/api/hs/office/sepamandates/&{SEPA-Mandate: %{bankAccountIBAN}}", usingJsonBody("""
{
"validUntil": ${mandateValidUntil}
"validTo": ${mandateValidTo}
}
"""))
.expecting(OK).expecting(JSON)

View File

@@ -34,18 +34,15 @@ public class ReplaceDeceasedPartnerWithCommunityOfHeirs extends UseCase<ReplaceD
() -> httpPatch("/api/hs/office/partners/%{Partner: %{partnerNumber}}",
usingJsonBody("""
{
"wrong1": false,
"partnerRel": {
"wrong2": false,
"holder": {
"personType": "UNINCORPORATED_FIRM",
"tradeName": "Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}",
},
"contact": {
"wrong3": false,
"caption": "Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}",
"postalAddress": {
"wrong4": false,
"whatever": "any key is allowed here",
"name": "Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}",
"co": "%{representativeGivenName} %{representativeFamilyName}",
%{communityOfHeirsPostalAddress}