Add getAtIdentifier(:PdxInstance) method and strategy for resolving ids from PdxInstances.

Add postProcess(:PdxInstance) method to allow subclasses to override the method in order to post process the PdxInstance(s) generated from JSON.

Edit Javadoc.

Rename getIdField(:PdxInstance) to getId(:PdxInstance).

Resolves gh-67.
This commit is contained in:
John Blum
2020-05-13 00:13:26 -07:00
parent 2536585de9
commit a63d792898
3 changed files with 423 additions and 59 deletions

View File

@@ -25,6 +25,7 @@ import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@@ -37,6 +38,9 @@ import java.util.stream.Collectors;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.junit.After;
import org.junit.Before;
@@ -51,6 +55,7 @@ import org.apache.geode.pdx.JSONFormatter;
import org.apache.geode.pdx.JSONFormatterException;
import org.apache.geode.pdx.PdxInstance;
import org.apache.geode.pdx.PdxInstanceFactory;
import org.apache.geode.pdx.PdxSerializationException;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
@@ -64,6 +69,7 @@ import org.springframework.data.gemfire.tests.integration.IntegrationTestsSuppor
import org.springframework.geode.core.util.ObjectUtils;
import org.springframework.geode.pdx.PdxInstanceBuilder;
import org.springframework.lang.NonNull;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.StringUtils;
import example.app.crm.model.Customer;
@@ -351,18 +357,19 @@ public class JsonCacheDataImporterExporterIntegrationTests extends IntegrationTe
assertExampleRegion(example);
assertThat(example).isEmpty();
Customer jonDoe = Customer.newCustomer(42L, "Play Doe");
Customer playDoe = Customer.newCustomer(42L, "Play Doe");
example.put(jonDoe.getId(), jonDoe);
example.put(playDoe.getId(), playDoe);
assertThat(example).hasSize(1);
assertThat(example.get(42L)).isEqualTo(jonDoe);
assertThat(example.get(42L)).isEqualTo(playDoe);
closeApplicationContext();
String actualJson = writer.toString();
String expectedJson = String.format("[{\"@type\":\"%s\",\"id\":42,\"name\":\"Play Doe\"}]",
jonDoe.getClass().getName());
playDoe.getClass().getName());
assertThat(actualJson).isEqualTo(expectedJson);
}
@@ -446,7 +453,7 @@ public class JsonCacheDataImporterExporterIntegrationTests extends IntegrationTe
}
@Test
public void exportImportWithRegionContainingMixOfObjectsAndPdxInstances() throws IOException {
public void exportImportWithRegionContainingObjectsAndPdxInstances() throws IOException {
try {
// EXPORT
@@ -508,27 +515,36 @@ public class JsonCacheDataImporterExporterIntegrationTests extends IntegrationTe
}
}
// APACHE GEODE BUG!!!
// APACHE GEODE BUG 1!!!
@Test(expected = JSONFormatterException.class)
public void geodeJsonFormatterCannotHandleTypedJsonObjects() throws JsonProcessingException {
public void geodeJsonFormatterFromJsonCannotParseArrays() throws IOException {
Customer sourDoe = Customer.newCustomer(1L, "Sour Doe");
try {
ObjectMapper objectMapper = newObjectMapper()
.activateDefaultTypingAsProperty(null, ObjectMapper.DefaultTyping.EVERYTHING, "$class");
byte[] json = FileCopyUtils.copyToByteArray(
new ClassPathResource("data-example-doefamily.json").getInputStream());
String json = objectMapper.writeValueAsString(sourDoe);
assertThat(json).isNotNull();
assertThat(json).isNotEmpty();
assertThat(json).isNotEmpty();
assertThat(json).describedAs("Actual JSON [%s]", json)
.contains(String.format("\"$class\":\"%s\"", sourDoe.getClass().getName()));
JSONFormatter.fromJSON(json);
}
catch (JSONFormatterException expected) {
JSONFormatter.fromJSON(json);
// Caused because the JSONFormatter.fromJSON(..) method's JsonParser is not configured correctly!
assertThat(expected).hasMessageStartingWith("Could not parse JSON document");
assertThat(expected).hasCauseInstanceOf(IllegalStateException.class);
assertThat(expected.getCause()).hasMessageStartingWith("Array start called when state is NONE");
assertThat(expected.getCause()).hasNoCause();
throw expected;
}
}
// APACHE GEODE BUG!!!
// APACHE GEODE BUG 2!!!
@Test
public void geodeJsonFormatterCannotRememberPdxInstanceClass() {
public void geodeJsonFormatterToJsonDoesNotGenerateAtTypeJsonObjectPropertyFromPdxInstanceGetClassName() {
Cache peerCache = newApplicationContext(TestGeodeConfiguration.class).getBean(Cache.class);
@@ -556,6 +572,94 @@ public class JsonCacheDataImporterExporterIntegrationTests extends IntegrationTe
assertThat(value).isInstanceOf(PdxInstance.class);
assertThat(value).isNotEqualTo(doeDoe);
// Causes ClassCastException!
// Bug caused by the JSONFormatter.toJSON(:PdxInstance) method not properly setting the '@type' JSON object
// property from the PdxInstance.getClassName() when the class name is a valid Java class!
//Customer jonDoeAgain = (Customer) value;
}
// APACHE GEODE BUG 3!!!
@Test(expected = PdxSerializationException.class)
public void geodePdxInstanceObjectMapperCannotDeserializeJava8Types() {
try {
Cache peerCache = newApplicationContext(TestGeodeConfiguration.class).getBean(Cache.class);
ObjectMapper objectMapper = newObjectMapper();
TimedType value = TimedType.create().with(LocalDate.now());
ObjectNode objectNode = objectMapper.valueToTree(value);
objectNode.put("@type", value.getClass().getName());
String json = objectNode.toString();
PdxInstance pdx = JSONFormatter.fromJSON(json);
// BOOM!
pdx.getObject();
}
catch (PdxSerializationException expected) {
// Caused because the PdxInstance ObjectMapper is not properly configured (to findAndRegisterModules()
// or Jackson Module Extensions on the classpath)!
assertThat(expected).hasMessageStartingWith("Could not deserialize as java class '%s'",
TimedType.class.getName());
assertThat(expected.getCause()).isInstanceOf(InvalidDefinitionException.class);
assertThat(expected.getCause()).hasMessageStartingWith("Cannot construct instance of `java.time.LocalDate`");
assertThat(expected.getCause()).hasNoCause();
throw expected;
}
}
// APACHE GEODE BUG 4!!!
@Test(expected = PdxSerializationException.class)
public void geodePdxInstanceObjectMapperCannotDeserializeTypedJsonObjects()
throws JsonProcessingException {
try {
Cache peerCache = newApplicationContext(TestGeodeConfiguration.class).getBean(Cache.class);
PurchaseOrder purchaseOrder = new PurchaseOrder()
.add(LineItem.newLineItem(Product.newProduct("Test Product")
.havingPrice(BigDecimal.valueOf(39.99))
.in(Product.Category.UNSOUGHT))
.withQuantity(2))
.identifiedAs(1L);
ObjectMapper objectMapper = newObjectMapper()
.activateDefaultTypingAsProperty(null, ObjectMapper.DefaultTyping.EVERYTHING, "@type");
String json = objectMapper.writeValueAsString(purchaseOrder);
assertThat(json).isNotEmpty();
assertThat(json).describedAs("Actual JSON [%s]", json)
.contains(String.format("\"@type\":\"%s\"", purchaseOrder.getClass().getName()));
PdxInstance pdx = JSONFormatter.fromJSON(json);
// BOOM!
pdx.getObject();
}
catch (PdxSerializationException expected) {
// Caused because the PdxInstance.getObject() method's ObjectMapper is not properly configured!
assertThat(expected).hasMessageStartingWith("Could not deserialize as java class '%s'",
PurchaseOrder.class.getName());
assertThat(expected.getCause()).isInstanceOf(MismatchedInputException.class);
assertThat(expected.getCause())
.hasMessageStartingWith("Cannot deserialize instance of `java.lang.Long` out of START_ARRAY token");
assertThat(expected.getCause()).hasNoCause();
throw expected;
}
}
@PeerCacheApplication
@@ -589,4 +693,22 @@ public class JsonCacheDataImporterExporterIntegrationTests extends IntegrationTe
};
}
}
public static class TimedType {
public static TimedType create() {
return new TimedType();
}
private LocalDate time;
public LocalDate getTime() {
return this.time;
}
public TimedType with(LocalDate time) {
this.time = time;
return this;
}
}
}

View File

@@ -70,8 +70,12 @@ import example.app.pos.model.LineItem;
* @see org.apache.geode.cache.Region
* @see org.apache.geode.pdx.PdxInstance
* @see org.springframework.context.ApplicationContext
* @see org.springframework.core.io.ClassPathResource
* @see org.springframework.core.io.Resource
* @see org.springframework.geode.data.json.JsonCacheDataImporterExporter
* @see org.springframework.geode.data.json.converter.JsonToPdxArrayConverter
* @see org.springframework.geode.data.json.converter.JsonToPdxConverter
* @see org.springframework.geode.data.json.converter.ObjectToJsonConverter
* @since 1.3.0
*/
@RunWith(MockitoJUnitRunner.class)
@@ -236,7 +240,9 @@ public class JsonCacheDataImporterExporterUnitTests {
.getResource(eq(mockRegion), eq(JsonCacheDataImporterExporter.CLASSPATH_RESOURCE_PREFIX));
verify(this.importer, times(1)).getContent(eq(mockResource));
verify(this.importer, times(1)).toPdxArray(eq(json));
verify(this.importer, times(1)).postProcess(eq(mockPdxInstanceOne));
verify(this.importer, times(1)).getIdentifier(eq(mockPdxInstanceOne));
verify(this.importer, times(1)).postProcess(eq(mockPdxInstanceTwo));
verify(this.importer, times(1)).getIdentifier(eq(mockPdxInstanceTwo));
verify(mockResource, times(1)).exists();
verify(mockRegion, times(1)).put(eq(1), eq(mockPdxInstanceOne));
@@ -408,7 +414,7 @@ public class JsonCacheDataImporterExporterUnitTests {
}
@Test
public void getIdentifierFromPdxInstance() {
public void getIdentifierFromPdxInstanceHavingIdentity() {
PdxInstance mockPdxInstance = mock(PdxInstance.class);
@@ -418,7 +424,7 @@ public class JsonCacheDataImporterExporterUnitTests {
assertThat(this.importer.getIdentifier(mockPdxInstance)).isEqualTo(42);
verify(this.importer, never()).getIdField(any(PdxInstance.class));
verify(this.importer, never()).getId(any(PdxInstance.class));
verify(mockPdxInstance, times(1)).getFieldNames();
verify(mockPdxInstance, times(1)).isIdentityField(eq("age"));
verify(mockPdxInstance, times(1)).isIdentityField(eq("id"));
@@ -432,11 +438,11 @@ public class JsonCacheDataImporterExporterUnitTests {
PdxInstance mockPdxInstance = mock(PdxInstance.class);
doReturn(null).when(mockPdxInstance).getFieldNames();
doReturn(69).when(this.importer).getIdField(eq(mockPdxInstance));
doReturn(69).when(this.importer).getId(eq(mockPdxInstance));
assertThat(this.importer.getIdentifier(mockPdxInstance)).isEqualTo(69);
verify(this.importer, times(1)).getIdField(eq(mockPdxInstance));
verify(this.importer, times(1)).getId(eq(mockPdxInstance));
verify(mockPdxInstance, times(1)).getFieldNames();
verify(mockPdxInstance, never()).isIdentityField(anyString());
verify(mockPdxInstance, never()).getField(anyString());
@@ -448,12 +454,12 @@ public class JsonCacheDataImporterExporterUnitTests {
PdxInstance mockPdxInstance = mock(PdxInstance.class);
doReturn(Arrays.asList("", "age", null, "name", " ")).when(mockPdxInstance).getFieldNames();
doReturn(false).when(mockPdxInstance).isIdentityField(anyString());
doReturn(99).when(this.importer).getIdField(eq(mockPdxInstance));
doReturn(false).when(mockPdxInstance).isIdentityField(any());
doReturn(99).when(this.importer).getId(eq(mockPdxInstance));
assertThat(this.importer.getIdentifier(mockPdxInstance)).isEqualTo(99);
verify(this.importer, times(1)).getIdField(eq(mockPdxInstance));
verify(this.importer, times(1)).getId(eq(mockPdxInstance));
verify(mockPdxInstance, times(1)).getFieldNames();
verify(mockPdxInstance, times(1)).isIdentityField(eq("age"));
verify(mockPdxInstance, times(1)).isIdentityField(eq("name"));
@@ -470,14 +476,14 @@ public class JsonCacheDataImporterExporterUnitTests {
doReturn(Collections.singletonList("name")).when(mockPdxInstance).getFieldNames();
doReturn(false).when(mockPdxInstance).isIdentityField(anyString());
doThrow(new IllegalStateException("TEST")).when(this.importer).getIdField(eq(mockPdxInstance));
doThrow(new IllegalStateException("NO ID")).when(this.importer).getId(eq(mockPdxInstance));
try {
this.importer.getIdentifier(mockPdxInstance);
}
catch (IllegalStateException expected) {
assertThat(expected).hasMessage("TEST");
assertThat(expected).hasMessage("NO ID");
assertThat(expected).hasNoCause();
throw expected;
@@ -505,35 +511,122 @@ public class JsonCacheDataImporterExporterUnitTests {
}
@Test
public void getIdFieldFromPdxInstance() {
public void getIdFromPdxInstanceHavingIdField() {
PdxInstance mockPdxInstance = mock(PdxInstance.class);
doReturn(true).when(mockPdxInstance).hasField(eq(JsonCacheDataImporterExporter.ID_FIELD_NAME));
doReturn(42).when(mockPdxInstance).getField(eq(JsonCacheDataImporterExporter.ID_FIELD_NAME));
assertThat(this.importer.getIdField(mockPdxInstance)).isEqualTo(42);
assertThat(this.importer.getId(mockPdxInstance)).isEqualTo(42);
verify(mockPdxInstance, times(1))
.hasField(eq(JsonCacheDataImporterExporter.ID_FIELD_NAME));
verify(mockPdxInstance, times(1))
.getField(eq(JsonCacheDataImporterExporter.ID_FIELD_NAME));
verify(this.importer, never()).getAtIdentifier(any());
}
@Test
public void getIdFromPdxInstanceHavingIdFieldWithNoValueCallsGetAtIdentifier() {
PdxInstance mockPdxInstance = mock(PdxInstance.class);
doReturn(true).when(mockPdxInstance).hasField(eq(JsonCacheDataImporterExporter.ID_FIELD_NAME));
doReturn(null).when(mockPdxInstance).getField(eq(JsonCacheDataImporterExporter.ID_FIELD_NAME));
doReturn(69).when(this.importer).getAtIdentifier(eq(mockPdxInstance));
assertThat(this.importer.getId(mockPdxInstance)).isEqualTo(69);
verify(mockPdxInstance, times(1))
.hasField(eq(JsonCacheDataImporterExporter.ID_FIELD_NAME));
verify(mockPdxInstance, times(1))
.getField(eq(JsonCacheDataImporterExporter.ID_FIELD_NAME));
verify(this.importer, times(1)).getAtIdentifier(any());
}
@Test
public void getIdFromPdxInstanceWithNoIdFieldCallsGetAtIdentifier() {
PdxInstance mockPdxInstance = mock(PdxInstance.class);
doReturn(false).when(mockPdxInstance).hasField(any());
doReturn(99).when(this.importer).getAtIdentifier(eq(mockPdxInstance));
assertThat(this.importer.getId(mockPdxInstance)).isEqualTo(99);
verify(mockPdxInstance, times(1))
.hasField(eq(JsonCacheDataImporterExporter.ID_FIELD_NAME));
verify(mockPdxInstance, never()).getField(eq(JsonCacheDataImporterExporter.ID_FIELD_NAME));
verify(this.importer, times(1)).getAtIdentifier(eq(mockPdxInstance));
}
@Test(expected = IllegalArgumentException.class)
public void getIdFromNullPdxInstance() {
try {
this.importer.getId(null);
}
catch (IllegalArgumentException expected) {
assertThat(expected).hasMessage("PdxInstance must not be null");
assertThat(expected).hasNoCause();
throw expected;
}
}
@Test
public void getAtIdentifierFromPdxInstance() {
PdxInstance mockPdxInstance = mock(PdxInstance.class);
doReturn(true).when(mockPdxInstance)
.hasField(eq(JsonCacheDataImporterExporter.AT_IDENTIFIER_FIELD_NAME));
doReturn(true).when(mockPdxInstance).hasField(eq("isbn"));
doReturn("isbn").when(mockPdxInstance)
.getField(eq(JsonCacheDataImporterExporter.AT_IDENTIFIER_FIELD_NAME));
doReturn("123456789").when(mockPdxInstance).getField(eq("isbn"));
assertThat(this.importer.getAtIdentifier(mockPdxInstance)).isEqualTo("123456789");
verify(mockPdxInstance, times(1))
.hasField(eq(JsonCacheDataImporterExporter.AT_IDENTIFIER_FIELD_NAME));
verify(mockPdxInstance, times(1))
.getField(eq(JsonCacheDataImporterExporter.AT_IDENTIFIER_FIELD_NAME));
verify(mockPdxInstance, times(1)).hasField(eq("isbn"));
verify(mockPdxInstance, times(1)).getField(eq("isbn"));
}
@Test(expected = IllegalArgumentException.class)
public void getAtIdentifierWithNullPdxInstance() {
try {
this.importer.getAtIdentifier(null);
}
catch (IllegalArgumentException expected) {
assertThat(expected).hasMessage("PdxInstance must not be null");
assertThat(expected).hasNoCause();
throw expected;
}
}
@Test(expected = IllegalStateException.class)
public void getIdFieldFromPdxInstanceWithNoIdField() {
public void getAtIdentifierFromPdxInstanceWithNoDeclaredIdentity() {
PdxInstance mockPdxInstance = mock(PdxInstance.class);
doReturn(LineItem.class.getName()).when(mockPdxInstance).getClassName();
doReturn(false).when(mockPdxInstance).hasField(anyString());
doReturn(false).when(mockPdxInstance).hasField(any());
try {
this.importer.getIdField(mockPdxInstance);
this.importer.getAtIdentifier(mockPdxInstance);
}
catch (IllegalStateException expected) {
assertThat(expected).hasMessage("PdxInstance for type [%s] has no declared identity field",
assertThat(expected).hasMessage("PdxInstance for type [%s] has no declared identifier",
LineItem.class.getName());
assertThat(expected).hasNoCause();
@@ -542,22 +635,23 @@ public class JsonCacheDataImporterExporterUnitTests {
finally {
verify(mockPdxInstance, times(1)).getClassName();
verify(mockPdxInstance, times(2))
.hasField(eq(JsonCacheDataImporterExporter.AT_IDENTIFIER_FIELD_NAME));
verify(mockPdxInstance, times(1))
.hasField(eq(JsonCacheDataImporterExporter.ID_FIELD_NAME));
verify(mockPdxInstance, never()).getField(anyString());
}
}
@Test(expected = IllegalStateException.class)
public void getIdFieldFromPdxInstanceWithNoId() {
public void getAtIdentifierFromPdxInstanceWithNoId() {
PdxInstance mockPdxInstance = mock(PdxInstance.class);
doReturn(true).when(mockPdxInstance).hasField(anyString());
doReturn(null).when(mockPdxInstance).getField(eq(JsonCacheDataImporterExporter.ID_FIELD_NAME));
doReturn(LineItem.class.getName()).when(mockPdxInstance).getClassName();
doReturn(true).when(mockPdxInstance).hasField(eq(JsonCacheDataImporterExporter.ID_FIELD_NAME));
try {
this.importer.getIdField(mockPdxInstance);
this.importer.getAtIdentifier(mockPdxInstance);
}
catch (IllegalStateException expected) {
@@ -570,25 +664,92 @@ public class JsonCacheDataImporterExporterUnitTests {
finally {
verify(mockPdxInstance, times(1)).getClassName();
verify(mockPdxInstance, times(1))
.getField(eq(JsonCacheDataImporterExporter.ID_FIELD_NAME));
verify(mockPdxInstance, times(2))
.hasField(eq(JsonCacheDataImporterExporter.AT_IDENTIFIER_FIELD_NAME));
verify(mockPdxInstance, times(1))
.hasField(eq(JsonCacheDataImporterExporter.ID_FIELD_NAME));
verify(mockPdxInstance, never()).getField(any());
}
}
@Test(expected = IllegalArgumentException.class)
public void getIdFieldFromNullPdxInstance() {
@Test(expected = IllegalStateException.class)
public void getAtIdentifierFromPdxInstanceWithValidAtIdentifierAndIdentifierFieldButNoId() {
PdxInstance mockPdxInstance = mock(PdxInstance.class);
doReturn(Customer.class.getName()).when(mockPdxInstance).getClassName();
doReturn(true).when(mockPdxInstance)
.hasField(eq(JsonCacheDataImporterExporter.AT_IDENTIFIER_FIELD_NAME));
doReturn(false).when(mockPdxInstance)
.hasField(eq(JsonCacheDataImporterExporter.ID_FIELD_NAME));
doReturn(true).when(mockPdxInstance).hasField(eq("ssn"));
doReturn("ssn").when(mockPdxInstance)
.getField(eq(JsonCacheDataImporterExporter.AT_IDENTIFIER_FIELD_NAME));
doReturn(null).when(mockPdxInstance).getField(eq("ssn"));
try {
this.importer.getIdField(null);
this.importer.getAtIdentifier(mockPdxInstance);
}
catch (IllegalArgumentException expected) {
catch (IllegalStateException expected) {
assertThat(expected).hasMessage("PdxInstance must not be null");
assertThat(expected)
.hasMessage("PdxInstance for type [%s] has no value [null] for field [ssn] declared in [%s]",
Customer.class.getName(), JsonCacheDataImporterExporter.AT_IDENTIFIER_FIELD_NAME);
assertThat(expected).hasNoCause();
throw expected;
}
finally {
verify(mockPdxInstance, times(1)).getClassName();
verify(mockPdxInstance, times(2))
.hasField(eq(JsonCacheDataImporterExporter.AT_IDENTIFIER_FIELD_NAME));
verify(mockPdxInstance, times(1))
.hasField(eq(JsonCacheDataImporterExporter.ID_FIELD_NAME));
verify(mockPdxInstance, times(2)).hasField(eq("ssn"));
verify(mockPdxInstance, times(2))
.getField(eq(JsonCacheDataImporterExporter.AT_IDENTIFIER_FIELD_NAME));
verify(mockPdxInstance, times(2)).getField(eq("ssn"));
verifyNoMoreInteractions(mockPdxInstance);
}
}
@Test(expected = IllegalStateException.class)
public void getAtIdentifierFromPdxInstanceWithAtIdentifierReferringToInvalidIdentifierField() {
PdxInstance mockPdxInstance = mock(PdxInstance.class);
doReturn(Customer.class.getName()).when(mockPdxInstance).getClassName();
doReturn(true).when(mockPdxInstance)
.hasField(eq(JsonCacheDataImporterExporter.AT_IDENTIFIER_FIELD_NAME));
doReturn(false).when(mockPdxInstance)
.hasField(eq(JsonCacheDataImporterExporter.ID_FIELD_NAME));
doReturn(false).when(mockPdxInstance).hasField(eq("ssn"));
doReturn("ssn").when(mockPdxInstance)
.getField(eq(JsonCacheDataImporterExporter.AT_IDENTIFIER_FIELD_NAME));
try {
this.importer.getAtIdentifier(mockPdxInstance);
}
catch (IllegalStateException expected) {
assertThat(expected)
.hasMessage("PdxInstance for type [%s] has no field [ssn] declared in [%s]",
Customer.class.getName(), JsonCacheDataImporterExporter.AT_IDENTIFIER_FIELD_NAME);
assertThat(expected).hasNoCause();
throw expected;
}
finally {
verify(mockPdxInstance, times(1)).getClassName();
verify(mockPdxInstance, times(2))
.hasField(eq(JsonCacheDataImporterExporter.AT_IDENTIFIER_FIELD_NAME));
verify(mockPdxInstance, times(1))
.hasField(eq(JsonCacheDataImporterExporter.ID_FIELD_NAME));
verify(mockPdxInstance, times(2)).hasField(eq("ssn"));
verify(mockPdxInstance, times(2))
.getField(eq(JsonCacheDataImporterExporter.AT_IDENTIFIER_FIELD_NAME));
verify(mockPdxInstance, never()).getField(eq("ssn"));
verifyNoMoreInteractions(mockPdxInstance);
}
}
@Test