From 2d6d42ba247fc1d4b01d8716c1b411508c61c5bf Mon Sep 17 00:00:00 2001 From: michaelb Date: Fri, 12 Oct 2018 15:20:02 +0100 Subject: [PATCH] SDK-580: Return structured_postal_address when postal_address is missing --- .../api/attributes/AttributeConstants.java | 6 ++ .../client/spi/remote/AddressTransformer.java | 42 ++++++++++ .../spi/remote/AttributeListConverter.java | 34 +++++++- .../spi/remote/AddressTransformerTest.java | 80 +++++++++++++++++++ .../remote/AttributeListConverterTest.java | 80 +++++++++++++++++-- 5 files changed, 233 insertions(+), 9 deletions(-) create mode 100644 yoti-sdk-impl/src/main/java/com/yoti/api/client/spi/remote/AddressTransformer.java create mode 100644 yoti-sdk-impl/src/test/java/com/yoti/api/client/spi/remote/AddressTransformerTest.java diff --git a/yoti-sdk-api/src/main/java/com/yoti/api/attributes/AttributeConstants.java b/yoti-sdk-api/src/main/java/com/yoti/api/attributes/AttributeConstants.java index 50eaae804..94179ca04 100644 --- a/yoti-sdk-api/src/main/java/com/yoti/api/attributes/AttributeConstants.java +++ b/yoti-sdk-api/src/main/java/com/yoti/api/attributes/AttributeConstants.java @@ -22,6 +22,12 @@ public static final class HumanProfileAttributes { public static final String SELFIE = "selfie"; public static final String EMAIL_ADDRESS = "email_address"; public static final String DOCUMENT_DETAILS = "document_details"; + + public static final class Keys { + + public static final String FORMATTED_ADDRESS = "formatted_address"; + } + } public static final class ApplicationProfileAttributes { diff --git a/yoti-sdk-impl/src/main/java/com/yoti/api/client/spi/remote/AddressTransformer.java b/yoti-sdk-impl/src/main/java/com/yoti/api/client/spi/remote/AddressTransformer.java new file mode 100644 index 000000000..92245e215 --- /dev/null +++ b/yoti-sdk-impl/src/main/java/com/yoti/api/client/spi/remote/AddressTransformer.java @@ -0,0 +1,42 @@ +package com.yoti.api.client.spi.remote; + +import java.util.Map; + +import com.yoti.api.attributes.AttributeConstants.HumanProfileAttributes; +import com.yoti.api.client.Attribute; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class AddressTransformer { + + private static final Logger LOG = LoggerFactory.getLogger(AttributeListConverter.class); + + private AddressTransformer() {} + + static final AddressTransformer newInstance() { + return new AddressTransformer(); + } + + public Attribute transform(Attribute structuredAddress) { + Attribute transformedAddress = null; + try { + Attribute> address = (Attribute>) structuredAddress; + if (address.getValue() != null) { + Object formattedAddress = address.getValue().get(HumanProfileAttributes.Keys.FORMATTED_ADDRESS); + if (formattedAddress != null) { + transformedAddress = new SimpleAttribute(HumanProfileAttributes.POSTAL_ADDRESS, + String.valueOf(formattedAddress), + structuredAddress.getSources(), + structuredAddress.getVerifiers(), + structuredAddress.getAnchors()); + } + } + } catch (Exception e) { + LOG.warn("Failed to transform attribute '{}' in place of missing '{}' due to '{}'", structuredAddress.getName(), + HumanProfileAttributes.POSTAL_ADDRESS, e.getMessage()); + } + return transformedAddress; + } + +} diff --git a/yoti-sdk-impl/src/main/java/com/yoti/api/client/spi/remote/AttributeListConverter.java b/yoti-sdk-impl/src/main/java/com/yoti/api/client/spi/remote/AttributeListConverter.java index e1ba1b75c..84edfa280 100644 --- a/yoti-sdk-impl/src/main/java/com/yoti/api/client/spi/remote/AttributeListConverter.java +++ b/yoti-sdk-impl/src/main/java/com/yoti/api/client/spi/remote/AttributeListConverter.java @@ -1,5 +1,8 @@ package com.yoti.api.client.spi.remote; +import static com.yoti.api.attributes.AttributeConstants.HumanProfileAttributes.POSTAL_ADDRESS; +import static com.yoti.api.attributes.AttributeConstants.HumanProfileAttributes.STRUCTURED_POSTAL_ADDRESS; + import java.io.IOException; import java.text.ParseException; import java.util.ArrayList; @@ -20,13 +23,15 @@ class AttributeListConverter { private static final Logger LOG = LoggerFactory.getLogger(AttributeListConverter.class); private final AttributeConverter attributeConverter; + private final AddressTransformer addressTransformer; - private AttributeListConverter(AttributeConverter attributeConverter) { + private AttributeListConverter(AttributeConverter attributeConverter, AddressTransformer addressTransformer) { this.attributeConverter = attributeConverter; + this.addressTransformer = addressTransformer; } static AttributeListConverter newInstance() { - return new AttributeListConverter(AttributeConverter.newInstance()); + return new AttributeListConverter(AttributeConverter.newInstance(), AddressTransformer.newInstance()); } List> parseAttributeList(byte[] attributeListBytes) throws ProfileException { @@ -37,6 +42,7 @@ List> parseAttributeList(byte[] attributeListBytes) throws ProfileE AttributeListProto.AttributeList attributeList = parseProto(attributeListBytes); List> attributes = parseAttributes(attributeList); LOG.debug("{} out of {} attribute(s) parsed successfully ", attributes.size(), attributeList.getAttributesCount()); + ensurePostalAddress(attributes); return attributes; } @@ -54,10 +60,32 @@ private List> parseAttributes(AttributeListProto.AttributeList mess try { parsedAttributes.add(attributeConverter.convertAttribute(attribute)); } catch (IOException | ParseException e) { - LOG.warn("Failed to parse attribute '{}'", attribute.getName()); + LOG.warn("Failed to parse attribute '{}' due to '{}'", attribute.getName(), e.getMessage()); } } return parsedAttributes; } + private void ensurePostalAddress(List> attributes) { + if (findAttribute(POSTAL_ADDRESS, attributes) == null) { + Attribute structuredAddress = findAttribute(STRUCTURED_POSTAL_ADDRESS, attributes); + if (structuredAddress != null) { + Attribute transformedAddress = addressTransformer.transform(structuredAddress); + if (transformedAddress != null) { + LOG.debug("Substituting '{}' in place of missing '{}'", STRUCTURED_POSTAL_ADDRESS, POSTAL_ADDRESS); + attributes.add(transformedAddress); + } + } + } + } + + private Attribute findAttribute(String name, List> attributes) { + for (Attribute attribute : attributes) { + if (name.equals(attribute.getName())) { + return attribute; + } + } + return null; + } + } diff --git a/yoti-sdk-impl/src/test/java/com/yoti/api/client/spi/remote/AddressTransformerTest.java b/yoti-sdk-impl/src/test/java/com/yoti/api/client/spi/remote/AddressTransformerTest.java new file mode 100644 index 000000000..b001c2ed5 --- /dev/null +++ b/yoti-sdk-impl/src/test/java/com/yoti/api/client/spi/remote/AddressTransformerTest.java @@ -0,0 +1,80 @@ +package com.yoti.api.client.spi.remote; + +import static java.util.Arrays.asList; + +import static org.hamcrest.Matchers.hasItems; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.when; + +import java.util.HashMap; +import java.util.Map; + +import com.yoti.api.attributes.AttributeConstants.HumanProfileAttributes; +import com.yoti.api.client.Anchor; +import com.yoti.api.client.Attribute; + +import org.hamcrest.Matchers; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class AddressTransformerTest { + + private static final String SOME_FORMATTED_ADDRESS = "someFormattedAddress"; + + @InjectMocks AddressTransformer testObj; + + @Mock Attribute structuredAddressMock; + @Mock Attribute badAttributeMock; + @Mock Anchor sourceAnchorMock; + @Mock Anchor verifierAnchorMock; + + @Test + public void shouldReturnNullWhenAddressValueIsNull() { + Attribute result = testObj.transform(structuredAddressMock); + + assertThat(result, is(nullValue())); + } + + @Test + public void shouldReturnNullWhenFormattedAddressIsNull() { + when(structuredAddressMock.getValue()).thenReturn(new HashMap()); + + Attribute result = testObj.transform(structuredAddressMock); + + assertThat(result, is(nullValue())); + } + + @Test + public void shouldReturnNullWhenTheresAnException() { + when(badAttributeMock.getValue()).thenReturn(new Object()); + + Attribute result = testObj.transform(badAttributeMock); + + assertThat(result, is(nullValue())); + } + + @Test + public void shouldReturnTransformedAddress() { + Map addressMap = new HashMap<>(); + addressMap.put(HumanProfileAttributes.Keys.FORMATTED_ADDRESS, SOME_FORMATTED_ADDRESS); + when(structuredAddressMock.getValue()).thenReturn(addressMap); + when(structuredAddressMock.getSources()).thenReturn(asList(sourceAnchorMock)); + when(structuredAddressMock.getVerifiers()).thenReturn(asList(verifierAnchorMock)); + when(structuredAddressMock.getAnchors()).thenReturn(asList(sourceAnchorMock, verifierAnchorMock)); + + Attribute result = testObj.transform(structuredAddressMock); + + assertThat(result.getName(), Matchers.is(HumanProfileAttributes.POSTAL_ADDRESS)); + assertThat(result.getValue(), is(SOME_FORMATTED_ADDRESS)); + assertThat(result.getSources(), hasItems(sourceAnchorMock)); + assertThat(result.getVerifiers(), hasItems(verifierAnchorMock)); + assertThat(result.getAnchors(), hasItems(sourceAnchorMock, verifierAnchorMock)); + } + +} diff --git a/yoti-sdk-impl/src/test/java/com/yoti/api/client/spi/remote/AttributeListConverterTest.java b/yoti-sdk-impl/src/test/java/com/yoti/api/client/spi/remote/AttributeListConverterTest.java index 46528466b..e636b1553 100644 --- a/yoti-sdk-impl/src/test/java/com/yoti/api/client/spi/remote/AttributeListConverterTest.java +++ b/yoti-sdk-impl/src/test/java/com/yoti/api/client/spi/remote/AttributeListConverterTest.java @@ -6,13 +6,16 @@ import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import java.io.IOException; import java.text.ParseException; +import java.util.Arrays; import java.util.List; import java.util.Map; +import com.yoti.api.attributes.AttributeConstants; import com.yoti.api.client.Attribute; import com.yoti.api.client.Date; import com.yoti.api.client.ProfileException; @@ -20,6 +23,7 @@ import com.yoti.api.client.spi.remote.proto.AttributeListProto; import com.google.protobuf.InvalidProtocolBufferException; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; @@ -45,20 +49,31 @@ public class AttributeListConverterTest { .setName(JSON_ATTRIBUTE_NAME) .build(); - private static final byte[] PROFILE_DATA = AttributeListProto.AttributeList.newBuilder() - .addAttributes(STRING_ATTRIBUTE_PROTO) - .addAttributes(DATE_ATTRIBUTE_PROTO) - .addAttributes(JSON_ATTRIBUTE_PROTO) - .build() - .toByteArray(); + private static final AttrProto.Attribute POSTAL_ADDRESS_PROTO = AttrProto.Attribute.newBuilder() + .setName(AttributeConstants.HumanProfileAttributes.POSTAL_ADDRESS) + .build(); + + private static final AttrProto.Attribute STRUCTURED_ADDRESS_PROTO = AttrProto.Attribute.newBuilder() + .setName(AttributeConstants.HumanProfileAttributes.STRUCTURED_POSTAL_ADDRESS) + .build(); + + private static final byte[] PROFILE_DATA = createProfileData(STRING_ATTRIBUTE_PROTO, DATE_ATTRIBUTE_PROTO, JSON_ATTRIBUTE_PROTO); @InjectMocks AttributeListConverter testObj; @Mock AttributeConverter attributeConverterMock; + @Mock AddressTransformer addressTransformerMock; @Mock Attribute stringAttributeMock; @Mock Attribute dateAttributeMock; @Mock Attribute jsonAttributeMock; + @Mock Attribute postalAddressMock; + @Mock Attribute structuredAddressMock; + + @Before + public void setUp() throws Exception { + when(structuredAddressMock.getName()).thenReturn(AttributeConstants.HumanProfileAttributes.STRUCTURED_POSTAL_ADDRESS); + } @Test public void shouldReturnEmptyListForNullProfileData() throws Exception { @@ -109,4 +124,57 @@ public void shouldTolerateFailureToParseSomeAttributes() throws Exception { assertThat(result, hasItem(stringAttributeMock)); } + @Test + public void shouldNotSubstituteStructuredAddressWhenPostalAddressIsPresent() throws Exception { + when(attributeConverterMock.convertAttribute(POSTAL_ADDRESS_PROTO)).thenReturn(postalAddressMock); + + List> result = testObj.parseAttributeList(createProfileData(POSTAL_ADDRESS_PROTO)); + + assertThat(result, hasSize(1)); + assertThat(result, hasItem(postalAddressMock)); + verifyZeroInteractions(addressTransformerMock); + } + + @Test + public void shouldNotSubstituteStructuredAddressWhenItsNotPresent() throws Exception { + when(attributeConverterMock.convertAttribute(STRING_ATTRIBUTE_PROTO)).thenReturn(stringAttributeMock); + when(attributeConverterMock.convertAttribute(DATE_ATTRIBUTE_PROTO)).thenReturn(dateAttributeMock); + when(attributeConverterMock.convertAttribute(JSON_ATTRIBUTE_PROTO)).thenReturn(jsonAttributeMock); + + List> result = testObj.parseAttributeList(PROFILE_DATA); + + assertThat(result, hasSize(3)); + assertThat(result, hasItems(stringAttributeMock, dateAttributeMock, jsonAttributeMock)); + verifyZeroInteractions(addressTransformerMock); + } + + @Test + public void shouldSubstituteStructuredAddressForMissingPostalAddress() throws Exception { + when(attributeConverterMock.convertAttribute(STRUCTURED_ADDRESS_PROTO)).thenReturn(structuredAddressMock); + when(addressTransformerMock.transform(structuredAddressMock)).thenReturn(postalAddressMock); + + List> result = testObj.parseAttributeList(createProfileData(STRUCTURED_ADDRESS_PROTO)); + + assertThat(result, hasSize(2)); + assertThat(result, hasItems(structuredAddressMock, postalAddressMock)); + } + + @Test + public void shouldNotAddNullTransformedAddress() throws Exception { + when(attributeConverterMock.convertAttribute(STRUCTURED_ADDRESS_PROTO)).thenReturn(structuredAddressMock); + when(addressTransformerMock.transform(structuredAddressMock)).thenReturn(null); + + List> result = testObj.parseAttributeList(createProfileData(STRUCTURED_ADDRESS_PROTO)); + + assertThat(result, hasSize(1)); + assertThat(result, hasItem(structuredAddressMock)); + } + + private static byte[] createProfileData(AttrProto.Attribute... attributes) { + return AttributeListProto.AttributeList.newBuilder() + .addAllAttributes(Arrays.asList(attributes)) + .build() + .toByteArray(); + } + }