From a24ca35bd7417acae9cd9b5c16324c09f0993c1f Mon Sep 17 00:00:00 2001
From: Michael Hoennig <michael@hoennig.de>
Date: Fri, 19 Apr 2019 10:46:40 +0200
Subject: [PATCH] JSonSerializerWithAccessFilterUnitTest

---
 .../JSonSerializerWithAccessFilter.java       |  24 ++--
 ...SonSerializerWithAccessFilterUnitTest.java | 114 ++++++++++++++++++
 .../accessfilter/MockSecurityContext.java     |  24 ++++
 .../service/dto/CustomerDTOUnitTest.java      |  16 +--
 4 files changed, 151 insertions(+), 27 deletions(-)
 create mode 100644 src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonSerializerWithAccessFilterUnitTest.java
 create mode 100644 src/test/java/org/hostsharing/hsadminng/service/accessfilter/MockSecurityContext.java

diff --git a/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonSerializerWithAccessFilter.java b/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonSerializerWithAccessFilter.java
index 76a4734b..2fd08c51 100644
--- a/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonSerializerWithAccessFilter.java
+++ b/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonSerializerWithAccessFilter.java
@@ -6,7 +6,6 @@ import com.fasterxml.jackson.databind.JsonSerializer;
 import com.fasterxml.jackson.databind.SerializerProvider;
 import org.apache.commons.lang3.NotImplementedException;
 import org.hostsharing.hsadminng.security.SecurityUtils;
-import org.hostsharing.hsadminng.service.dto.CustomerDTO;
 import org.springframework.boot.jackson.JsonComponent;
 
 import java.io.IOException;
@@ -18,20 +17,22 @@ import java.lang.reflect.Method;
 public class JSonSerializerWithAccessFilter extends JsonSerializer<Object> {
 
     @Override
-    public void serialize(Object dto, JsonGenerator jsonGenerator,
-                          SerializerProvider serializerProvider) throws IOException {
+    public void serialize(final Object dto, final JsonGenerator jsonGenerator,
+                          final SerializerProvider serializerProvider) throws IOException {
 
+        // TODO: move the implementation to an (if necessary, inner) class
         jsonGenerator.writeStartObject();
-        for (Field prop : CustomerDTO.class.getDeclaredFields()) {
+        for (Field prop : dto.getClass().getDeclaredFields()) {
             toJSon(dto, jsonGenerator, prop);
         }
 
         jsonGenerator.writeEndObject();
     }
 
-    private void toJSon(Object dto, JsonGenerator jsonGenerator, Field prop) throws IOException {
+    private void toJSon(final Object dto, final JsonGenerator jsonGenerator, final Field prop) throws IOException {
         if (getLoginUserRole().isAllowedToRead(prop)) {
             final String fieldName = prop.getName();
+            // TODO: maybe replace by serializerProvider.defaultSerialize...()?
             if (Integer.class.isAssignableFrom(prop.getType()) || int.class.isAssignableFrom(prop.getType())) {
                 jsonGenerator.writeNumberField(fieldName, (int) get(dto, prop));
             } else if (Long.class.isAssignableFrom(prop.getType()) || long.class.isAssignableFrom(prop.getType())) {
@@ -44,25 +45,16 @@ public class JSonSerializerWithAccessFilter extends JsonSerializer<Object> {
         }
     }
 
-    private Object get(Object dto, Field field) {
+    private Object get(final Object dto, final Field field) {
         try {
             field.setAccessible(true);
             return field.get(dto);
         } catch (IllegalAccessException e) {
-            throw new RuntimeException(e);
+            throw new RuntimeException("getting field " + field + " failed", e);
         }
     }
 
     private Role getLoginUserRole() {
         return SecurityUtils.getCurrentUserLogin().map(u -> Role.valueOf(u.toUpperCase())).orElse(Role.ANYBODY);
     }
-
-    private Object invoke(Object dto, Method method) {
-        try {
-            return method.invoke(dto);
-        } catch (IllegalAccessException | InvocationTargetException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
 }
diff --git a/src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonSerializerWithAccessFilterUnitTest.java b/src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonSerializerWithAccessFilterUnitTest.java
new file mode 100644
index 00000000..9a5e838f
--- /dev/null
+++ b/src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonSerializerWithAccessFilterUnitTest.java
@@ -0,0 +1,114 @@
+package org.hostsharing.hsadminng.service.accessfilter;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import org.apache.commons.lang3.NotImplementedException;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.apache.commons.lang3.RandomUtils;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.io.IOException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.catchThrowable;
+import static org.hostsharing.hsadminng.service.accessfilter.MockSecurityContext.givenLoginUserWithRole;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+public class JSonSerializerWithAccessFilterUnitTest {
+
+    @Rule
+    public MockitoRule mockitoRule = MockitoJUnit.rule();
+
+    @Mock
+    public JsonGenerator jsonGenerator;
+
+    private final GivenDto givenDTO = createSampleDto();
+
+    @Before
+    public void init() {
+        givenLoginUserWithRole(Role.ANY_CUSTOMER_USER);
+    }
+
+    @Test
+    public void shouldSerializeStringField() throws IOException {
+    // when
+        new JSonSerializerWithAccessFilter().serialize(givenDTO, jsonGenerator, null);
+
+        // then
+        verify(jsonGenerator).writeStringField("openStringField", givenDTO.openStringField);
+    }
+
+    @Test
+    public void shouldSerializeRestrictedFieldIfRequiredRoleIsCoveredByUser() throws IOException {
+
+        // given
+        givenLoginUserWithRole(Role.FINANCIAL_CONTACT);
+
+        // when
+        new JSonSerializerWithAccessFilter().serialize(givenDTO, jsonGenerator, null);
+
+        // then
+       verify(jsonGenerator).writeStringField("restrictedField", givenDTO.restrictedField);
+    }
+
+    @Test
+    public void shouldNotSerializeRestrictedFieldIfRequiredRoleIsNotCoveredByUser() throws IOException {
+
+        // given
+        givenLoginUserWithRole(Role.ANY_CUSTOMER_USER);
+
+        // when
+        new JSonSerializerWithAccessFilter().serialize(givenDTO, jsonGenerator, null);
+
+        // then
+        verify(jsonGenerator, never()).writeStringField("restrictedField", givenDTO.restrictedField);
+    }
+
+    @Test
+    public void shouldThrowExceptionForUnimplementedFieldType() throws IOException {
+
+        // given
+        class Arbitrary {}
+        class GivenDtoWithUnimplementedFieldType {
+            @AccessFor(read = Role.ANYBODY)
+            Arbitrary fieldWithUnimplementedType;
+        }
+        final GivenDtoWithUnimplementedFieldType givenDto = new GivenDtoWithUnimplementedFieldType();
+
+        // when
+        Throwable actual = catchThrowable(() -> new JSonSerializerWithAccessFilter().serialize(givenDto, jsonGenerator, null));
+
+        // then
+        assertThat(actual).isInstanceOf(NotImplementedException.class);
+    }
+
+    // --- fixture code below ---
+
+    private GivenDto createSampleDto() {
+        final GivenDto dto = new GivenDto();
+        dto.restrictedField = RandomStringUtils.randomAlphabetic(10);
+        dto.openStringField = RandomStringUtils.randomAlphabetic(10);
+        dto.openIntegerField = RandomUtils.nextInt();
+        dto.openLongField = RandomUtils.nextLong();
+        return dto;
+    }
+
+    private static class GivenDto {
+        @AccessFor(read = {Role.TECHNICAL_CONTACT, Role.FINANCIAL_CONTACT})
+        String restrictedField;
+
+        @AccessFor(read = Role.ANYBODY)
+        String openStringField;
+
+        @AccessFor(read = Role.ANYBODY)
+        Integer openIntegerField;
+
+        @AccessFor(read = Role.ANYBODY)
+        Long openLongField;
+    }
+}
diff --git a/src/test/java/org/hostsharing/hsadminng/service/accessfilter/MockSecurityContext.java b/src/test/java/org/hostsharing/hsadminng/service/accessfilter/MockSecurityContext.java
new file mode 100644
index 00000000..5a656c77
--- /dev/null
+++ b/src/test/java/org/hostsharing/hsadminng/service/accessfilter/MockSecurityContext.java
@@ -0,0 +1,24 @@
+package org.hostsharing.hsadminng.service.accessfilter;
+
+import org.hostsharing.hsadminng.security.SecurityUtils;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+import java.util.Optional;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class MockSecurityContext {
+
+    public static void givenLoginUserWithRole(final Role userRole) {
+        final String fakeUserName = userRole.name();
+
+        SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
+        securityContext.setAuthentication(new UsernamePasswordAuthenticationToken(fakeUserName, "dummy"));
+        SecurityContextHolder.setContext(securityContext);
+        Optional<String> login = SecurityUtils.getCurrentUserLogin();
+
+        assertThat(login).describedAs("precondition failed").contains(fakeUserName);
+    }
+}
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 6ef88849..7686c234 100644
--- a/src/test/java/org/hostsharing/hsadminng/service/dto/CustomerDTOUnitTest.java
+++ b/src/test/java/org/hostsharing/hsadminng/service/dto/CustomerDTOUnitTest.java
@@ -3,6 +3,7 @@ package org.hostsharing.hsadminng.service.dto;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import org.hostsharing.hsadminng.security.SecurityUtils;
+import org.hostsharing.hsadminng.service.accessfilter.Role;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -16,6 +17,7 @@ import java.io.IOException;
 import java.util.Optional;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.hostsharing.hsadminng.service.accessfilter.MockSecurityContext.givenLoginUserWithRole;
 import static org.junit.Assert.assertEquals;
 
 @JsonTest
@@ -30,7 +32,7 @@ public class CustomerDTOUnitTest {
 
         // given
         CustomerDTO given = createSomeCustomerDTO();
-        givenLoginUserWithRole("ANY_CUSTOMER_USER");
+        givenLoginUserWithRole(Role.ANY_CUSTOMER_USER);
 
         // when
         String actual = objectMapper.writeValueAsString(given);
@@ -49,7 +51,7 @@ public class CustomerDTOUnitTest {
 
         // given
         CustomerDTO given = createSomeCustomerDTO();
-        givenLoginUserWithRole("SUPPORTER");
+        givenLoginUserWithRole(Role.SUPPORTER);
 
         // when
         String actual = objectMapper.writeValueAsString(given);
@@ -62,7 +64,7 @@ public class CustomerDTOUnitTest {
     public void testDeserializeAsContractualCustomerContact() throws IOException {
         // given
         String json = "{\"id\":1234,\"reference\":10001,\"prefix\":\"abc\",\"name\":\"Mein Name\",\"contractualAddress\":\"Eine Adresse\",\"contractualSalutation\":\"Hallo\",\"billingAddress\":\"Noch eine Adresse\",\"billingSalutation\":\"Moin\",\"remark\":\"Eine Bemerkung\"}";
-        givenLoginUserWithRole("CONTRACTUAL_CONTACT");
+        givenLoginUserWithRole(Role.CONTRACTUAL_CONTACT);
 
         // when
         CustomerDTO actual = objectMapper.readValue(json, CustomerDTO.class);
@@ -120,12 +122,4 @@ public class CustomerDTOUnitTest {
         given.setRemark("Eine Bemerkung");
         return given;
     }
-
-    private void givenLoginUserWithRole(String userName) {
-        SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
-        securityContext.setAuthentication(new UsernamePasswordAuthenticationToken(userName, userName));
-        SecurityContextHolder.setContext(securityContext);
-        Optional<String> login = SecurityUtils.getCurrentUserLogin();
-        assertThat(login).describedAs("precondition failed").contains(userName);
-    }
 }