package eu.dnetlib.iis.wf.affmatching;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import org.apache.spark.api.java.JavaPairRDD;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.api.java.function.Function;
import org.apache.spark.api.java.function.Function2;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;
import com.google.common.collect.Lists;
import eu.dnetlib.iis.wf.affmatching.match.AffMatchResultChooser;
import eu.dnetlib.iis.wf.affmatching.match.AffOrgMatcher;
import eu.dnetlib.iis.wf.affmatching.model.AffMatchAffiliation;
import eu.dnetlib.iis.wf.affmatching.model.AffMatchOrganization;
import eu.dnetlib.iis.wf.affmatching.model.AffMatchResult;
import eu.dnetlib.iis.wf.affmatching.normalize.AffMatchAffiliationNormalizer;
import eu.dnetlib.iis.wf.affmatching.normalize.AffMatchOrganizationNormalizer;
import eu.dnetlib.iis.wf.affmatching.orgalternativenames.AffMatchOrganizationAltNameFiller;
import eu.dnetlib.iis.wf.affmatching.read.AffiliationReader;
import eu.dnetlib.iis.wf.affmatching.read.OrganizationReader;
import eu.dnetlib.iis.wf.affmatching.write.AffMatchResultWriter;
import scala.Tuple2;
/**
* @author Ćukasz Dumiszewski
*/
@RunWith(MockitoJUnitRunner.class)
public class AffMatchingServiceTest {
//----------------------------------- SERVICES -------------------------------------------
@InjectMocks
private AffMatchingService affMatchingService = new AffMatchingService();
@Mock
private OrganizationReader organizationReader;
@Mock
private AffiliationReader affiliationReader;
@Mock
private AffMatchAffiliationNormalizer affMatchAffiliationNormalizer;
@Mock
private AffMatchOrganizationNormalizer affMatchOrganizationNormalizer;
@Mock
private AffMatchOrganizationAltNameFiller affMatchOrganizationAltNameFiller;
@Mock
private AffOrgMatcher affOrgMatcher1;
@Mock
private AffOrgMatcher affOrgMatcher2;
@Mock
private AffMatchResultChooser affMatchResultChooser;
@Mock
private AffMatchResultWriter affMatchResultWriter;
//----------------------------------- DATA -------------------------------------------
@Mock
private JavaSparkContext sc;
private String inputAffPath = "/input/affs";
private String inputOrgPath = "/input/orgs";
private String outputPath = "/output";
private String outputReportPath = "/outputReport";
@Mock
private JavaRDD<AffMatchAffiliation> affiliations;
@Mock
private JavaRDD<AffMatchOrganization> organizations;
@Mock
private JavaRDD<AffMatchAffiliation> normalizedAffiliations;
@Mock
private JavaRDD<AffMatchOrganization> normalizedOrganizations;
@Mock
private JavaRDD<AffMatchAffiliation> normalizedAndFilteredAffiliations;
@Mock
private JavaRDD<AffMatchOrganization> normalizedAndFilteredOrganizations;
@Mock
private JavaRDD<AffMatchOrganization> altNamesFilledOrganizations;
@Mock
private JavaPairRDD<Tuple2<String, String>, AffMatchResult> parallelizedAffMatchResults;
@Mock
private JavaRDD<AffMatchResult> matchedAffOrgs1;
@Mock
private JavaPairRDD<Tuple2<String, String>, AffMatchResult> matchedAffOrgsWithKey1;
@Mock
private JavaRDD<AffMatchResult> matchedAffOrgs2;
@Mock
private JavaPairRDD<Tuple2<String, String>, AffMatchResult> matchedAffOrgsWithKey2;
@Mock
private JavaPairRDD<Tuple2<String, String>, AffMatchResult> allAffMatchResults1;
@Mock
private JavaPairRDD<Tuple2<String, String>, AffMatchResult> allAffMatchResults2;
@Mock
private JavaPairRDD<Tuple2<String, String>, AffMatchResult> allUniqueAffMatchResults;
@Mock
private JavaRDD<AffMatchResult> allUniqueAffMatchResultsValues;
//----------------------------------- FUNCTIONS -------------------------------------------
@Captor
private ArgumentCaptor<Function<AffMatchAffiliation, AffMatchAffiliation>> affNormalizingFunction;
@Captor
private ArgumentCaptor<Function<AffMatchOrganization, AffMatchOrganization>> orgNormalizingFunction;
@Captor
private ArgumentCaptor<Function<AffMatchAffiliation, Boolean>> affFilterFunction;
@Captor
private ArgumentCaptor<Function<AffMatchOrganization, Boolean>> orgFilterFunction;
@Captor
private ArgumentCaptor<Function<AffMatchOrganization, AffMatchOrganization>> orgFillAltNamesFunction;
@Captor
private ArgumentCaptor<Function<AffMatchResult, Tuple2<String, String>>> matchedAffOrgs1KeyByFunction;
@Captor
private ArgumentCaptor<Function<AffMatchResult, Tuple2<String, String>>> matchedAffOrgs2KeyByFunction;
@Captor
private ArgumentCaptor<Function2<AffMatchResult, AffMatchResult, AffMatchResult>> allAffMatchResultsReduceFunction;
@Before
public void before() {
affMatchingService.setAffOrgMatchers(Lists.newArrayList(affOrgMatcher1, affOrgMatcher2));
}
//------------------------ TESTS --------------------------
@Test(expected = NullPointerException.class)
public void matchAffiliations_sparkContext_null() {
// execute
affMatchingService.matchAffiliations(null, inputAffPath, inputOrgPath, outputPath, outputReportPath);
}
@Test(expected = IllegalArgumentException.class)
public void matchAffiliations_inputAffPath_blank() {
// execute
affMatchingService.matchAffiliations(sc, " ", inputOrgPath, outputPath, outputReportPath);
}
@Test(expected = IllegalArgumentException.class)
public void matchAffiliations_inputOrgPath_blank() {
// execute
affMatchingService.matchAffiliations(sc, inputAffPath, " ", outputPath, outputReportPath);
}
@Test(expected = IllegalArgumentException.class)
public void matchAffiliations_outputPath_blank() {
// execute
affMatchingService.matchAffiliations(sc, inputAffPath, inputOrgPath, " ", outputReportPath);
}
@Test(expected = NullPointerException.class)
public void matchAffiliations_affiliationReader_null() {
// given
affMatchingService.setAffiliationReader(null);
// execute
affMatchingService.matchAffiliations(sc, inputAffPath, inputOrgPath, outputPath, outputReportPath);
}
@Test(expected = NullPointerException.class)
public void matchAffiliations_organizationReader_null() {
// given
affMatchingService.setOrganizationReader(null);
// execute
affMatchingService.matchAffiliations(sc, inputAffPath, inputOrgPath, outputPath, outputReportPath);
}
@Test(expected = NullPointerException.class)
public void matchAffiliations_affMatchResultWriter_null() {
// given
affMatchingService.setAffMatchResultWriter(null);
// execute
affMatchingService.matchAffiliations(sc, inputAffPath, inputOrgPath, outputPath, outputReportPath);
}
@Test(expected = IllegalStateException.class)
public void matchAffiliations_affOrgMatchers_empty() {
// given
affMatchingService.setAffOrgMatchers(Lists.newArrayList());
// execute
affMatchingService.matchAffiliations(sc, inputAffPath, inputOrgPath, outputPath, outputReportPath);
}
@Test
public void matchAffiliations() throws Exception {
// given
when(affiliationReader.readAffiliations(sc, inputAffPath)).thenReturn(affiliations);
when(organizationReader.readOrganizations(sc, inputOrgPath)).thenReturn(organizations);
doReturn(normalizedAffiliations).when(affiliations).map(Mockito.any());
doReturn(normalizedOrganizations).when(organizations).map(Mockito.any());
when(normalizedAffiliations.filter(Mockito.any())).thenReturn(normalizedAndFilteredAffiliations);
when(normalizedOrganizations.filter(Mockito.any())).thenReturn(normalizedAndFilteredOrganizations);
doReturn(altNamesFilledOrganizations).when(normalizedAndFilteredOrganizations).map(Mockito.any());
//--- matching
doReturn(parallelizedAffMatchResults).when(sc).parallelizePairs(new ArrayList<>());
//- first matcher
when(affOrgMatcher1.match(normalizedAndFilteredAffiliations, altNamesFilledOrganizations)).thenReturn(matchedAffOrgs1);
doReturn(matchedAffOrgsWithKey1).when(matchedAffOrgs1).keyBy(Mockito.any());
when(parallelizedAffMatchResults.union(matchedAffOrgsWithKey1)).thenReturn(allAffMatchResults1);
//- second matcher
when(affOrgMatcher2.match(normalizedAndFilteredAffiliations, altNamesFilledOrganizations)).thenReturn(matchedAffOrgs2);
doReturn(matchedAffOrgsWithKey2).when(matchedAffOrgs2).keyBy(Mockito.any());
when(allAffMatchResults1.union(matchedAffOrgsWithKey2)).thenReturn(allAffMatchResults2);
//- pick unique matches
doReturn(allUniqueAffMatchResults).when(allAffMatchResults2).reduceByKey(Mockito.any());
when(allUniqueAffMatchResults.values()).thenReturn(allUniqueAffMatchResultsValues);
// execute
affMatchingService.matchAffiliations(sc, inputAffPath, inputOrgPath, outputPath, outputReportPath);
// assert
verify(affMatchResultWriter).write(sc, allUniqueAffMatchResultsValues, outputPath, outputReportPath);
verify(affiliationReader).readAffiliations(sc, inputAffPath);
verify(organizationReader).readOrganizations(sc, inputOrgPath);
verify(affiliations).map(affNormalizingFunction.capture());
assertAffNormalizingFunction(affNormalizingFunction.getValue());
verify(organizations).map(orgNormalizingFunction.capture());
assertOrgNormalizingFunction(orgNormalizingFunction.getValue());
verify(normalizedAffiliations).filter(affFilterFunction.capture());
assertAffFilterFunction(affFilterFunction.getValue());
verify(normalizedOrganizations).filter(orgFilterFunction.capture());
assertOrgFilterFunction(orgFilterFunction.getValue());
verify(normalizedAndFilteredOrganizations).map(orgFillAltNamesFunction.capture());
assertOrgFillAltNamesFunctionFunction(orgFillAltNamesFunction.getValue());
verify(matchedAffOrgs1).keyBy(matchedAffOrgs1KeyByFunction.capture());
assertMatchedAffOrgsKeyByFunction(matchedAffOrgs1KeyByFunction.getValue());
verify(matchedAffOrgs2).keyBy(matchedAffOrgs2KeyByFunction.capture());
assertMatchedAffOrgsKeyByFunction(matchedAffOrgs2KeyByFunction.getValue());
verify(allAffMatchResults2).reduceByKey(allAffMatchResultsReduceFunction.capture());
assertAllAffMatchResultsReduceFunction(allAffMatchResultsReduceFunction.getValue());
}
//------------------------ PRIVATE --------------------------
private void assertAffFilterFunction(Function<AffMatchAffiliation, Boolean> function) throws Exception {
// given
AffMatchAffiliation affMatchAff1 = mock(AffMatchAffiliation.class);
when(affMatchAff1.getOrganizationName()).thenReturn("ICM");
AffMatchAffiliation affMatchAff2 = mock(AffMatchAffiliation.class);
when(affMatchAff2.getOrganizationName()).thenReturn(" ");
// execute & assert
assertTrue(function.call(affMatchAff1));
assertFalse(function.call(affMatchAff2));
}
private void assertOrgFilterFunction(Function<AffMatchOrganization, Boolean> function) throws Exception {
// given
AffMatchOrganization affMatchOrg1 = mock(AffMatchOrganization.class);
when(affMatchOrg1.getName()).thenReturn("ICM");
AffMatchOrganization affMatchOrg2 = mock(AffMatchOrganization.class);
when(affMatchOrg2.getName()).thenReturn(" ");
// execute & assert
assertTrue(function.call(affMatchOrg1));
assertFalse(function.call(affMatchOrg2));
}
private void assertAffNormalizingFunction(Function<AffMatchAffiliation, AffMatchAffiliation> function) throws Exception {
// given
AffMatchAffiliation aff1 = mock(AffMatchAffiliation.class);
AffMatchAffiliation aff2 = mock(AffMatchAffiliation.class);
when(affMatchAffiliationNormalizer.normalize(aff1)).thenReturn(aff2);
// execute & assert
assertTrue(aff2 == function.call(aff1));
}
private void assertOrgNormalizingFunction(Function<AffMatchOrganization, AffMatchOrganization> function) throws Exception {
// given
AffMatchOrganization org1 = mock(AffMatchOrganization.class);
AffMatchOrganization org2 = mock(AffMatchOrganization.class);
when(affMatchOrganizationNormalizer.normalize(org1)).thenReturn(org2);
// execute & assert
assertTrue(org2 == function.call(org1));
}
private void assertOrgFillAltNamesFunctionFunction(Function<AffMatchOrganization, AffMatchOrganization> function) throws Exception {
// given
AffMatchOrganization org1 = mock(AffMatchOrganization.class);
AffMatchOrganization org2 = mock(AffMatchOrganization.class);
when(affMatchOrganizationAltNameFiller.fillAlternativeNames(org1)).thenReturn(org2);
// execute & assert
assertTrue(org2 == function.call(org1));
}
private void assertMatchedAffOrgsKeyByFunction(Function<AffMatchResult, Tuple2<String, String>> function) throws Exception {
// given
AffMatchResult result = new AffMatchResult(new AffMatchAffiliation("DOC_ID", 3), new AffMatchOrganization("ORG_ID"), 0.4f);
// execute
Tuple2<String, String> retKey = function.call(result);
// assert
assertEquals("DOC_ID###3", retKey._1);
assertEquals("ORG_ID", retKey._2);
}
private void assertAllAffMatchResultsReduceFunction(Function2<AffMatchResult, AffMatchResult, AffMatchResult> function) throws Exception {
// given
AffMatchResult affMatchResult1 = mock(AffMatchResult.class);
AffMatchResult affMatchResult2 = mock(AffMatchResult.class);
when(affMatchResultChooser.chooseBetter(affMatchResult1, affMatchResult2)).thenReturn(affMatchResult2);
// assert & assert
assertTrue(affMatchResult2 == function.call(affMatchResult1, affMatchResult2));
}
}