gt:         final AtomicBoolean foundPair = new AtomicBoolean(false);
context: package org.eol.globi.process;
  
  import org.eol.globi.data.DatasetImporterForMetaTable;
  import org.eol.globi.data.DatasetImporterForTSV;
  import org.eol.globi.data.GraphDBTestCase;
  import org.eol.globi.data.NodeFactoryException;
  import org.eol.globi.data.StudyImporterException;
  import org.eol.globi.domain.InteractType;
  import org.eol.globi.domain.LocationNode;
  import org.eol.globi.domain.LogContext;
  import org.eol.globi.domain.RelTypes;
  import org.eol.globi.domain.SpecimenConstant;
  import org.eol.globi.domain.SpecimenNode;
  import org.eol.globi.domain.StudyNode;
  import org.eol.globi.domain.TaxonNode;
  import org.eol.globi.geo.LatLng;
  import org.eol.globi.service.GeoNamesService;
  import org.eol.globi.tool.NullImportLogger;
  import org.eol.globi.util.DateUtil;
  import org.eol.globi.util.NodeTypeDirection;
  import org.eol.globi.util.NodeUtil;
  import org.junit.Test;
  import org.neo4j.graphdb.Direction;
  import org.neo4j.graphdb.Node;
  import org.neo4j.graphdb.Relationship;
  
  import java.io.IOException;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Date;
  import java.util.List;
  import java.util.TreeMap;
  import java.util.concurrent.atomic.AtomicBoolean;
  import java.util.concurrent.atomic.AtomicInteger;
  
  import static org.eol.globi.data.DatasetImporterForTSV.ARGUMENT_TYPE_ID;
  import static org.eol.globi.data.DatasetImporterForTSV.DATASET_CITATION;
  import static org.eol.globi.data.DatasetImporterForTSV.DECIMAL_LATITUDE;
  import static org.eol.globi.data.DatasetImporterForTSV.DECIMAL_LONGITUDE;
  import static org.eol.globi.data.DatasetImporterForTSV.LOCALITY_ID;
  import static org.eol.globi.data.DatasetImporterForTSV.LOCALITY_NAME;
  import static org.eol.globi.data.DatasetImporterForTSV.REFERENCE_CITATION;
  import static org.eol.globi.data.DatasetImporterForTSV.REFERENCE_DOI;
  import static org.eol.globi.data.DatasetImporterForTSV.REFERENCE_ID;
  import static org.eol.globi.data.DatasetImporterForTSV.SOURCE_BODY_PART_ID;
  import static org.eol.globi.data.DatasetImporterForTSV.SOURCE_BODY_PART_NAME;
  import static org.eol.globi.data.DatasetImporterForTSV.SOURCE_CATALOG_NUMBER;
  import static org.eol.globi.data.DatasetImporterForTSV.SOURCE_COLLECTION_CODE;
  import static org.eol.globi.data.DatasetImporterForTSV.SOURCE_COLLECTION_ID;
  import static org.eol.globi.data.DatasetImporterForTSV.SOURCE_INSTITUTION_CODE;
  import static org.eol.globi.data.DatasetImporterForTSV.SOURCE_LIFE_STAGE_ID;
  import static org.eol.globi.data.DatasetImporterForTSV.SOURCE_LIFE_STAGE_NAME;
  import static org.eol.globi.data.DatasetImporterForTSV.SOURCE_OCCURRENCE_ID;
  import static org.eol.globi.data.DatasetImporterForTSV.SOURCE_SEX_ID;
  import static org.eol.globi.data.DatasetImporterForTSV.SOURCE_SEX_NAME;
  import static org.eol.globi.data.DatasetImporterForTSV.TARGET_BODY_PART_ID;
  import static org.eol.globi.data.DatasetImporterForTSV.TARGET_BODY_PART_NAME;
  import static org.eol.globi.data.DatasetImporterForTSV.TARGET_CATALOG_NUMBER;
  import static org.eol.globi.data.DatasetImporterForTSV.TARGET_COLLECTION_CODE;
  import static org.eol.globi.data.DatasetImporterForTSV.TARGET_COLLECTION_ID;
  import static org.eol.globi.data.DatasetImporterForTSV.TARGET_INSTITUTION_CODE;
  import static org.eol.globi.data.DatasetImporterForTSV.TARGET_OCCURRENCE_ID;
  import static org.eol.globi.data.DatasetImporterForTSV.TARGET_SEX_ID;
  import static org.eol.globi.data.DatasetImporterForTSV.TARGET_SEX_NAME;
  import static org.eol.globi.domain.PropertyAndValueDictionary.CATALOG_NUMBER;
  import static org.eol.globi.domain.PropertyAndValueDictionary.COLLECTION_CODE;
  import static org.eol.globi.domain.PropertyAndValueDictionary.COLLECTION_ID;
  import static org.eol.globi.domain.PropertyAndValueDictionary.INSTITUTION_CODE;
  import static org.eol.globi.domain.PropertyAndValueDictionary.OCCURRENCE_ID;
  import static org.eol.globi.service.TaxonUtil.SOURCE_TAXON_ID;
  import static org.eol.globi.service.TaxonUtil.SOURCE_TAXON_NAME;
  import static org.eol.globi.service.TaxonUtil.SOURCE_TAXON_PATH;
  import static org.eol.globi.service.TaxonUtil.SOURCE_TAXON_PATH_IDS;
  import static org.eol.globi.service.TaxonUtil.SOURCE_TAXON_PATH_NAMES;
  import static org.eol.globi.service.TaxonUtil.SOURCE_TAXON_RANK;
  import static org.eol.globi.service.TaxonUtil.SOURCE_TAXON_SPECIFIC_EPITHET;
  import static org.eol.globi.service.TaxonUtil.TARGET_TAXON_ID;
  import static org.eol.globi.service.TaxonUtil.TARGET_TAXON_NAME;
  import static org.eol.globi.service.TaxonUtil.TARGET_TAXON_PATH_IDS;
  import static org.eol.globi.service.TaxonUtil.TARGET_TAXON_RANK;
  import static org.hamcrest.MatcherAssert.assertThat;
  import static org.hamcrest.core.Is.is;
  import static org.hamcrest.core.IsNull.notNullValue;
  import static org.hamcrest.core.StringStartsWith.startsWith;
  import static org.junit.Assert.assertFalse;
  import static org.junit.Assert.assertTrue;
  import static org.junit.Assert.fail;
  
  public class InteractionImporterTest extends GraphDBTestCase {
  
      @Test
      public void malformedDOI() {
          final List<String> msgs = new ArrayList<>();
          InteractionListener interactionListener = new InteractionImporter(nodeFactory, new GeoNamesService() {
  
              @Override
              public boolean hasTermForLocale(String locality) {
                  return true;
              }
  
              @Override
              public LatLng findLatLng(String locality) throws IOException {
                  throw new IOException("kaboom!");
              }
          }, new NullImportLogger() {
              @Override
              public void warn(LogContext ctx, String message) {
                  msgs.add(message);
              }
  
          });
  
          final TreeMap<String, String> link = new TreeMap<>();
          link.put(SOURCE_TAXON_NAME, "donald");
          link.put(DatasetImporterForTSV.INTERACTION_TYPE_ID, "http://purl.obolibrary.org/obo/RO_0002470");
          link.put(TARGET_TAXON_NAME, "mini");
          link.put(REFERENCE_ID, "123");
          link.put(DATASET_CITATION, "some source ref");
          link.put(REFERENCE_CITATION, "");
          link.put(REFERENCE_DOI, "bla:XXX");
          try {
              interactionListener.on(link);
              assertThat(msgs.size(), is(1));
              assertThat(msgs.get(0), startsWith("found malformed doi [bla:XXX]"));
          } catch (StudyImporterException ex) {
              fail("should not throw on failing geoname lookup");
          }
      }
  
      @Test
      public void malformedDateRange() {
          final List<String> msgs = new ArrayList<>();
          InteractionListener interactionListener = new InteractionImporter(
                  nodeFactory, new GeoNamesService() {
  
              @Override
              public boolean hasTermForLocale(String locality) {
                  return true;
              }
  
              @Override
              public LatLng findLatLng(String locality) throws IOException {
                  throw new IOException("kaboom!");
              }
          }, new NullImportLogger() {
              @Override
              public void warn(LogContext ctx, String message) {
                  msgs.add(message);
              }
  
          });
  
          final TreeMap<String, String> link = new TreeMap<>();
          link.put(SOURCE_TAXON_NAME, "donald");
          link.put(DatasetImporterForTSV.INTERACTION_TYPE_ID, "http://purl.obolibrary.org/obo/RO_0002470");
          link.put(TARGET_TAXON_NAME, "mini");
          link.put(REFERENCE_ID, "123");
          link.put(DATASET_CITATION, "some source ref");
          link.put(REFERENCE_CITATION, "");
          link.put(REFERENCE_DOI, "10.12/123");
          link.put(DatasetImporterForMetaTable.EVENT_DATE, "2009-09/2003-09");
          try {
              interactionListener.on(link);
              assertThat(msgs.size(), is(2));
              assertThat(msgs.get(0), startsWith("date range [2009-09/2003-09] appears to start after it ends."));
              assertThat(msgs.get(1), startsWith("date range [2009-09/2003-09] appears to start after it ends."));
          } catch (StudyImporterException ex) {
              fail("should not throw on failing geoname lookup");
          }
      }
  
  
      @Test
      public void throwingGeoNamesService() {
          final List<String> msgs = new ArrayList<>();
          InteractionListener interactionListener = new InteractionImporter(nodeFactory, new GeoNamesService() {
  
              @Override
              public boolean hasTermForLocale(String locality) {
                  return true;
              }
  
              @Override
              public LatLng findLatLng(String locality) throws IOException {
                  throw new IOException("kaboom!");
              }
          }, new NullImportLogger() {
              @Override
              public void warn(LogContext ctx, String message) {
                  msgs.add(message);
              }
          });
  
          final TreeMap<String, String> link = new TreeMap<>();
          link.put(SOURCE_TAXON_NAME, "donald");
          link.put(DatasetImporterForTSV.INTERACTION_TYPE_ID, "http://purl.obolibrary.org/obo/RO_0002470");
          link.put(TARGET_TAXON_NAME, "mini");
          link.put(LOCALITY_ID, "bla:123");
          link.put(LOCALITY_NAME, "my back yard");
          link.put(REFERENCE_ID, "123");
          link.put(DATASET_CITATION, "some source ref");
          link.put(REFERENCE_CITATION, "");
          try {
              interactionListener.on(link);
              assertThat(msgs.size(), is(1));
              assertThat(msgs.get(0), startsWith("failed to lookup [bla:123]"));
          } catch (StudyImporterException ex) {
              fail("should not throw on failing geoname lookup");
          }
      }
  
  
      @Test
      public void importBlankCitation() throws StudyImporterException {
          final InteractionListener listener = new InteractionImporter(
                  nodeFactory,
                  (GeoNamesService) null,
                  null);
  
          final TreeMap<String, String> link = new TreeMap<String, String>();
          link.put(SOURCE_OCCURRENCE_ID, "123");
          link.put(SOURCE_CATALOG_NUMBER, "catalogNumber123");
          link.put(SOURCE_COLLECTION_CODE, "collectionCode123");
          link.put(SOURCE_COLLECTION_ID, "collectionId123");
          link.put(SOURCE_INSTITUTION_CODE, "institutionCode123");
          link.put(TARGET_OCCURRENCE_ID, "456");
          link.put(TARGET_CATALOG_NUMBER, "targetCatalogNumber123");
          link.put(TARGET_COLLECTION_CODE, "targetCollectionCode123");
          link.put(TARGET_COLLECTION_ID, "targetCollectionId123");
          link.put(TARGET_INSTITUTION_CODE, "targetInstitutionCode123");
          link.put(SOURCE_TAXON_NAME, "donald");
          link.put(SOURCE_TAXON_ID, "duck");
          link.put(SOURCE_BODY_PART_ID, "bla:123");
          link.put(SOURCE_BODY_PART_NAME, "snout");
          link.put(SOURCE_LIFE_STAGE_ID, "some:stage");
          link.put(SOURCE_LIFE_STAGE_NAME, "stage");
          link.put(TARGET_TAXON_NAME, "mini");
          link.put(TARGET_TAXON_ID, "mouse");
          link.put(TARGET_BODY_PART_ID, "bla:345");
          link.put(TARGET_BODY_PART_NAME, "tail");
          link.put(DatasetImporterForMetaTable.EVENT_DATE, "20160404T21:31:40Z");
          link.put(DatasetImporterForMetaTable.LATITUDE, "12.1");
          link.put(DatasetImporterForMetaTable.LONGITUDE, "13.2");
          link.put(DatasetImporterForTSV.INTERACTION_TYPE_ID, "http://purl.obolibrary.org/obo/RO_0002470");
          link.put(REFERENCE_ID, "123");
          link.put(DATASET_CITATION, "some source ref");
          link.put(REFERENCE_CITATION, "");
          link.put(REFERENCE_DOI, "doi:1234");
          listener.on(link);
  
  