diff --git a/src/main/java/org/hostsharing/hsadminng/domain/Asset.java b/src/main/java/org/hostsharing/hsadminng/domain/Asset.java index 97410eba..ef7fa87e 100644 --- a/src/main/java/org/hostsharing/hsadminng/domain/Asset.java +++ b/src/main/java/org/hostsharing/hsadminng/domain/Asset.java @@ -22,7 +22,7 @@ public class Asset implements Serializable { private static final long serialVersionUID = 1L; public static final String ENTITY_NAME = "asset"; - + @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator") @SequenceGenerator(name = "sequenceGenerator") @@ -59,6 +59,11 @@ public class Asset implements Serializable { return id; } + public Asset id(Long id) { + this.id = id; + return this; + } + public void setId(Long id) { this.id = id; } diff --git a/src/main/java/org/hostsharing/hsadminng/service/dto/AssetDTO.java b/src/main/java/org/hostsharing/hsadminng/service/dto/AssetDTO.java index 87dca80e..cfeb355e 100644 --- a/src/main/java/org/hostsharing/hsadminng/service/dto/AssetDTO.java +++ b/src/main/java/org/hostsharing/hsadminng/service/dto/AssetDTO.java @@ -2,7 +2,7 @@ package org.hostsharing.hsadminng.service.dto; import org.hostsharing.hsadminng.domain.enumeration.AssetAction; import org.hostsharing.hsadminng.service.AssetService; -import org.hostsharing.hsadminng.service.CustomerService; +import org.hostsharing.hsadminng.service.MembershipService; import org.hostsharing.hsadminng.service.accessfilter.*; import org.springframework.boot.jackson.JsonComponent; import org.springframework.context.ApplicationContext; @@ -40,13 +40,15 @@ public class AssetDTO implements Serializable, AccessMappings { private BigDecimal amount; @Size(max = 160) - @AccessFor(init = Role.ADMIN, update = Role.ADMIN, read = Role.ADMIN) + @AccessFor(init = Role.ADMIN, update = Role.ADMIN, read = Role.SUPPORTER) private String remark; - @ParentId(resolver = CustomerService.class) + @ParentId(resolver = MembershipService.class) @AccessFor(init = Role.ADMIN, update = Role.ADMIN, read = {Role.CONTRACTUAL_CONTACT, Role.FINANCIAL_CONTACT}) private Long membershipId; + // TODO: these init/update rights actually mean "ignore", we might want to express this in a better way + // background: there is no converter for any display label in DTOs to entity field values anyway @AccessFor(init=Role.ANYBODY, update = Role.ANYBODY, read = {Role.CONTRACTUAL_CONTACT, Role.FINANCIAL_CONTACT}) private String membershipDisplayLabel; diff --git a/src/main/java/org/hostsharing/hsadminng/service/mapper/MembershipMapper.java b/src/main/java/org/hostsharing/hsadminng/service/mapper/MembershipMapper.java index ef2e5a47..0ae815e1 100644 --- a/src/main/java/org/hostsharing/hsadminng/service/mapper/MembershipMapper.java +++ b/src/main/java/org/hostsharing/hsadminng/service/mapper/MembershipMapper.java @@ -19,8 +19,8 @@ public interface MembershipMapper extends EntityMapper... properties) { final StringBuilder json = new StringBuilder(); @@ -14,15 +17,8 @@ public class JSonBuilder { json.append(": "); if (prop.right instanceof Number) { json.append(prop.right); - } else if (prop.right instanceof List) { - json.append("["); - for ( int n = 0; n < ((List)prop.right).size(); ++n ) { - if ( n > 0 ) { - json.append(","); - } - json.append(((List)prop.right).get(n)); - } - json.append("]"); + } else if (prop.right instanceof List) { + json.append(toJSonArray(prop.right)); } else { json.append(inQuotes(prop.right)); } @@ -31,6 +27,50 @@ public class JSonBuilder { return "{\n" + json.substring(0, json.length() - 2) + "\n}"; } + public JSonBuilder withFieldValue(String name, String value) { + json.append(inQuotes(name) + ":" + (value != null ? inQuotes(value) : "null") + ","); + return this; + } + + public JSonBuilder withFieldValue(String name, Number value) { + json.append(inQuotes(name) + ":" + (value != null ? value : "null") + ","); + return this; + } + + public JSonBuilder toJSonNullFieldDefinition(String name) { + json.append(inQuotes(name) + ":null,"); + return this; + } + + public JSonBuilder withFieldValueIfPresent(String name, String value) { + json.append(value != null ? inQuotes(name) + ":" + inQuotes(value) + "," : ""); + return this; + } + + public JSonBuilder withFieldValueIfPresent(String name, Number value) { + json.append(value != null ? inQuotes(name) + ":" + value + "," : ""); + return this; + } + + @Override + public String toString() { + return "{" + StringUtils.removeEnd(json.toString(), ",") + "}"; + } + + @SuppressWarnings("unchecked") + // currently just for the case of date values represented as arrays of integer + private static String toJSonArray(final Object value) { + final StringBuilder jsonArray = new StringBuilder("["); + for (int n = 0; n < ((List) value).size(); ++n) { + if (n > 0) { + jsonArray.append(","); + } + jsonArray.append(((List) value).get(n)); + } + return jsonArray.toString() + "]"; + } + + private static String inQuotes(Object value) { return value != null ? "\"" + value.toString() + "\"" : "null"; } diff --git a/src/test/java/org/hostsharing/hsadminng/service/dto/AssetDTOUnitTest.java b/src/test/java/org/hostsharing/hsadminng/service/dto/AssetDTOUnitTest.java new file mode 100644 index 00000000..234a190a --- /dev/null +++ b/src/test/java/org/hostsharing/hsadminng/service/dto/AssetDTOUnitTest.java @@ -0,0 +1,214 @@ +package org.hostsharing.hsadminng.service.dto; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang3.RandomUtils; +import org.hostsharing.hsadminng.domain.Asset; +import org.hostsharing.hsadminng.domain.Customer; +import org.hostsharing.hsadminng.domain.Membership; +import org.hostsharing.hsadminng.domain.enumeration.AssetAction; +import org.hostsharing.hsadminng.repository.AssetRepository; +import org.hostsharing.hsadminng.repository.CustomerRepository; +import org.hostsharing.hsadminng.repository.MembershipRepository; +import org.hostsharing.hsadminng.service.AssetService; +import org.hostsharing.hsadminng.service.AssetValidator; +import org.hostsharing.hsadminng.service.MembershipValidator; +import org.hostsharing.hsadminng.service.accessfilter.JSonBuilder; +import org.hostsharing.hsadminng.service.accessfilter.Role; +import org.hostsharing.hsadminng.service.mapper.AssetMapper; +import org.hostsharing.hsadminng.service.mapper.AssetMapperImpl; +import org.hostsharing.hsadminng.service.mapper.CustomerMapperImpl; +import org.hostsharing.hsadminng.service.mapper.MembershipMapperImpl; +import org.hostsharing.hsadminng.web.rest.errors.BadRequestAlertException; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.json.JsonTest; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.junit4.SpringRunner; + +import javax.persistence.EntityManager; +import java.io.IOException; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; +import static org.hostsharing.hsadminng.service.accessfilter.MockSecurityContext.givenAuthenticatedUser; +import static org.hostsharing.hsadminng.service.accessfilter.MockSecurityContext.givenUserHavingRole; +import static org.junit.Assert.assertEquals; +import static org.mockito.BDDMockito.given; + + +@JsonTest +@SpringBootTest(classes = { + AssetMapperImpl.class, + AssetDTO.AssetJsonSerializer.class, + AssetDTO.AssetJsonDeserializer.class, + + MembershipMapperImpl.class, + + CustomerMapperImpl.class +}) +@RunWith(SpringRunner.class) +public class AssetDTOUnitTest { + + + private static final Long SOME_CUSTOMER_ID = RandomUtils.nextLong(100, 199); + private static final Integer SOME_CUSTOMER_REFERENCE = 10001; + private static final String SOME_CUSTOMER_PREFIX = "abc"; + private static final String SOME_CUSTOMER_NAME = "Some Customer Name"; + private static final Customer SOME_CUSTOMER = new Customer().id(SOME_CUSTOMER_ID).reference(SOME_CUSTOMER_REFERENCE).prefix(SOME_CUSTOMER_PREFIX).name(SOME_CUSTOMER_NAME); + + private static final Long SOME_MEMBERSHIP_ID = RandomUtils.nextLong(200, 299); + private static final LocalDate SOME_MEMBER_FROM_DATE = LocalDate.parse("2000-12-06") ; + private static final Membership SOME_MEMBERSHIP = new Membership().id(SOME_MEMBERSHIP_ID).customer(SOME_CUSTOMER).memberFromDate(SOME_MEMBER_FROM_DATE); + public static final String SOME_MEMBERSHIP_DISPLAY_LABEL = "Some Customer Name [10001:abc] 2000-12-06 - ..."; + + private static final Long SOME_ASSET_ID = RandomUtils.nextLong(300, 399); + private static final Asset SOME_ASSET = new Asset().id(SOME_ASSET_ID).membership(SOME_MEMBERSHIP); + + @Rule + public MockitoRule mockito = MockitoJUnit.rule(); + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private AssetMapper assetMapper; + + @MockBean + private AssetRepository assetRepository; + + @MockBean + private AssetValidator assetValidator; + + @MockBean + private CustomerRepository customerRepository; + + @MockBean + private MembershipRepository membershipRepository; + + @MockBean + private MembershipValidator membershipValidator; + + @MockBean + private AssetService assetService; + + @MockBean + private EntityManager em; + + @Before + public void init() { + given(customerRepository.findById(SOME_CUSTOMER_ID)).willReturn(Optional.of(SOME_CUSTOMER)); + given(membershipRepository.findById(SOME_MEMBERSHIP_ID)).willReturn(Optional.of(SOME_MEMBERSHIP)); + given(assetRepository.findById(SOME_ASSET_ID)).willReturn((Optional.of(SOME_ASSET))); + } + + @Test + public void shouldSerializePartiallyForFinancialCustomerContact() throws JsonProcessingException { + + // given + givenAuthenticatedUser(); + givenUserHavingRole(CustomerDTO.class, SOME_CUSTOMER_ID, Role.FINANCIAL_CONTACT); + final AssetDTO given = createSomeAssetDTO(SOME_ASSET_ID); + + // when + final String actual = objectMapper.writeValueAsString(given); + + // then + given.setRemark(null); + assertEquals(createExpectedJSon(given), actual); + } + + @Test + public void shouldSerializeCompletelyForSupporter() throws JsonProcessingException { + + // given + givenAuthenticatedUser(); + givenUserHavingRole(Role.SUPPORTER); + final AssetDTO given = createSomeAssetDTO(SOME_ASSET_ID); + + // when + final String actual = objectMapper.writeValueAsString(given); + + // then + assertEquals(createExpectedJSon(given), actual); + } + + @Test + public void shouldNotDeserializeForContractualCustomerContact() { + // given + givenAuthenticatedUser(); + givenUserHavingRole(CustomerDTO.class, SOME_CUSTOMER_ID, Role.CONTRACTUAL_CONTACT); + final String json = new JSonBuilder() + .withFieldValue("id", SOME_ASSET_ID) + .withFieldValue("remark", "Updated Remark") + .toString(); + + // when + final Throwable actual = catchThrowable(() -> objectMapper.readValue(json, AssetDTO.class)); + + // then + assertThat(actual).isInstanceOfSatisfying(BadRequestAlertException.class, bre -> + assertThat(bre.getMessage()).isEqualTo("Update of field AssetDTO.remark prohibited for current user role CONTRACTUAL_CONTACT") + ); + } + + @Test + public void shouldDeserializeForAdminIfRemarkIsChanged() throws IOException { + // given + givenAuthenticatedUser(); + givenUserHavingRole(Role.ADMIN); + final String json = new JSonBuilder() + .withFieldValue("id", SOME_ASSET_ID) + .withFieldValue("remark", "Updated Remark") + .toString(); + + // when + final AssetDTO actual = objectMapper.readValue(json, AssetDTO.class); + + // then + final AssetDTO expected = new AssetDTO(); + expected.setId(SOME_ASSET_ID); + expected.setMembershipId(SOME_MEMBERSHIP_ID); + expected.setRemark("Updated Remark"); + expected.setMembershipDisplayLabel(SOME_MEMBERSHIP_DISPLAY_LABEL); + assertThat(actual).isEqualToIgnoringGivenFields(expected, "displayLabel"); + } + + // --- only test fixture below --- + + private String createExpectedJSon(AssetDTO dto) { + return new JSonBuilder() + .withFieldValueIfPresent("id", dto.getId()) + .withFieldValueIfPresent("documentDate", dto.getDocumentDate().toString()) + .withFieldValueIfPresent("valueDate", dto.getValueDate().toString()) + .withFieldValueIfPresent("action", dto.getAction().name()) + .withFieldValueIfPresent("amount", dto.getAmount().doubleValue()) + .withFieldValueIfPresent("remark", dto.getRemark()) + .withFieldValueIfPresent("membershipId", dto.getMembershipId()) + .withFieldValue("membershipDisplayLabel", dto.getMembershipDisplayLabel()) + .toString(); + } + + + private AssetDTO createSomeAssetDTO(final long id) { + final AssetDTO given = new AssetDTO(); + given.setId(id); + given.setAction(AssetAction.PAYMENT); + given.setAmount(new BigDecimal("512.01")); + given.setDocumentDate(LocalDate.parse("2019-04-27")); + given.setValueDate(LocalDate.parse("2019-04-28")); + given.setMembershipId(SOME_MEMBERSHIP_ID); + given.setRemark("Some Remark"); + given.setMembershipDisplayLabel("Display Label for Membership #" + SOME_MEMBERSHIP_ID); + return given; + } +} diff --git a/src/test/java/org/hostsharing/hsadminng/service/dto/CustomerDTOUnitTest.java b/src/test/java/org/hostsharing/hsadminng/service/dto/CustomerDTOUnitTest.java index e60ef40a..22368d88 100644 --- a/src/test/java/org/hostsharing/hsadminng/service/dto/CustomerDTOUnitTest.java +++ b/src/test/java/org/hostsharing/hsadminng/service/dto/CustomerDTOUnitTest.java @@ -7,6 +7,7 @@ import org.hostsharing.hsadminng.domain.enumeration.CustomerKind; import org.hostsharing.hsadminng.domain.enumeration.VatRegion; import org.hostsharing.hsadminng.repository.CustomerRepository; import org.hostsharing.hsadminng.service.CustomerService; +import org.hostsharing.hsadminng.service.accessfilter.JSonBuilder; import org.hostsharing.hsadminng.service.accessfilter.Role; import org.hostsharing.hsadminng.service.mapper.CustomerMapper; import org.hostsharing.hsadminng.service.mapper.CustomerMapperImpl; @@ -78,13 +79,12 @@ public class CustomerDTOUnitTest { String actual = objectMapper.writeValueAsString(given); // then - final String expectedJSon = "{" + - toJSonFieldDefinition("id", given.getId()) + "," + - toJSonFieldDefinition("reference", given.getReference()) + "," + - toJSonFieldDefinition("prefix", given.getPrefix()) + "," + - toJSonFieldDefinition("name", given.getName()) + "," + - toJSonFieldDefinition("displayLabel", given.getDisplayLabel()) + - "}"; + final String expectedJSon = new JSonBuilder() + .withFieldValue("id", given.getId()) + .withFieldValue("reference", given.getReference()) + .withFieldValue("prefix", given.getPrefix()) + .withFieldValue("name", given.getName()) + .withFieldValue("displayLabel", given.getDisplayLabel()).toString(); assertEquals(expectedJSon, actual); } @@ -125,49 +125,24 @@ public class CustomerDTOUnitTest { // --- only test fixture below --- private String createExpectedJSon(CustomerDTO dto) { - String json = // the fields in alphanumeric order: - toJSonFieldDefinitionIfPresent("id", dto.getId()) + - toJSonFieldDefinitionIfPresent("reference", dto.getReference()) + - toJSonFieldDefinitionIfPresent("prefix", dto.getPrefix()) + - toJSonFieldDefinitionIfPresent("name", dto.getName()) + - toJSonFieldDefinitionIfPresent("kind", "LEGAL") + - toJSonNullFieldDefinition("birthDate") + - toJSonNullFieldDefinition("birthPlace") + - toJSonFieldDefinitionIfPresent("registrationCourt", "Registergericht") + - toJSonFieldDefinitionIfPresent("registrationNumber", "Registernummer") + - toJSonFieldDefinitionIfPresent("vatRegion", "DOMESTIC") + - toJSonFieldDefinitionIfPresent("vatNumber", "DE1234") + - toJSonFieldDefinitionIfPresent("contractualSalutation", dto.getContractualSalutation()) + - toJSonFieldDefinitionIfPresent("contractualAddress", dto.getContractualAddress()) + - toJSonFieldDefinitionIfPresent("billingSalutation", dto.getBillingSalutation()) + - toJSonFieldDefinitionIfPresent("billingAddress", dto.getBillingAddress()) + - toJSonFieldDefinitionIfPresent("remark", dto.getRemark()) + - toJSonFieldDefinitionIfPresent("displayLabel", dto.getDisplayLabel()); - return "{" + json.substring(0, json.length() - 1) + "}"; - } - - private String toJSonFieldDefinition(String name, String value) { - return inQuotes(name) + ":" + (value != null ? inQuotes(value) : "null"); - } - - private String toJSonFieldDefinition(String name, Number value) { - return inQuotes(name) + ":" + (value != null ? value : "null"); - } - - private String toJSonNullFieldDefinition(String name) { - return inQuotes(name) + ":null,"; - } - - private String toJSonFieldDefinitionIfPresent(String name, String value) { - return value != null ? inQuotes(name) + ":" + inQuotes(value) + "," : ""; - } - - private String toJSonFieldDefinitionIfPresent(String name, Number value) { - return value != null ? inQuotes(name) + ":" + value + "," : ""; - } - - private String inQuotes(Object value) { - return "\"" + value.toString() + "\""; + return new JSonBuilder() + .withFieldValueIfPresent("id", dto.getId()) + .withFieldValueIfPresent("reference", dto.getReference()) + .withFieldValueIfPresent("prefix", dto.getPrefix()) + .withFieldValueIfPresent("name", dto.getName()) + .withFieldValueIfPresent("kind", "LEGAL") + .toJSonNullFieldDefinition("birthDate") + .toJSonNullFieldDefinition("birthPlace") + .withFieldValueIfPresent("registrationCourt", "Registergericht") + .withFieldValueIfPresent("registrationNumber", "Registernummer") + .withFieldValueIfPresent("vatRegion", "DOMESTIC") + .withFieldValueIfPresent("vatNumber", "DE1234") + .withFieldValueIfPresent("contractualSalutation", dto.getContractualSalutation()) + .withFieldValueIfPresent("contractualAddress", dto.getContractualAddress()) + .withFieldValueIfPresent("billingSalutation", dto.getBillingSalutation()) + .withFieldValueIfPresent("billingAddress", dto.getBillingAddress()) + .withFieldValueIfPresent("remark", dto.getRemark()) + .withFieldValueIfPresent("displayLabel", dto.getDisplayLabel()).toString(); } private CustomerDTO createSomeCustomerDTO(final long id) {