diff --git a/dataloader.properties b/dataloader.properties index 80fa34a9..1832dce0 100755 --- a/dataloader.properties +++ b/dataloader.properties @@ -93,6 +93,7 @@ loginUrl=https://rest.bullhornstaffing.com/rest-services/login #clientCorporationCustomObjectInstance8ExistField=clientCorporation.externalID,text1 #clientCorporationCustomObjectInstance9ExistField=clientCorporation.externalID,text1 #clientCorporationCustomObjectInstance10ExistField=clientCorporation.externalID,text1 +#clientCorporationCustomObjectInstance35ExistField=clientCorporation.externalID,text1 #jobOrderCustomObjectInstance1ExistField=jobOrder.externalID,text1 #jobOrderCustomObjectInstance2ExistField=jobOrder.externalID,text1 #jobOrderCustomObjectInstance3ExistField=jobOrder.externalID,text1 diff --git a/examples/load/README.md b/examples/README.md similarity index 88% rename from examples/load/README.md rename to examples/README.md index 3a26dad4..b1f12b31 100644 --- a/examples/load/README.md +++ b/examples/README.md @@ -56,6 +56,8 @@ These example CSV files reference several reference only entities that must exis The maximal number of fields have been filled out in these examples, for as many entities as can be loaded. These files are as interconnected as possible, making use of as many association fields as possible. The `externalID` field is used if present, otherwise `customText1` is used to denote the external unique identifier for data that is being loaded. These are the same default exist fields that are used in section 3 of the `dataloader.properties` file, so that updating instead of inserting using these example files requires simply uncommenting the commented out exist fields in the properties file. +In order to ensure that all fields have been utilized, after updating the SDK-REST, run the command: `dataloader template ` for each example file in order to see which fields are missing or should be removed from the example file. For example: `dataloader template examples/load/Candidate.csv`. Not all fields can be added to the example file, but most can be. + ### Integration Test To perform a manual integration test (testing the integration between the DataLoader and the actual Rest API), do the following: diff --git a/src/main/java/com/bullhorn/dataloader/data/Result.java b/src/main/java/com/bullhorn/dataloader/data/Result.java index 5ef69bad..61d6c6f4 100644 --- a/src/main/java/com/bullhorn/dataloader/data/Result.java +++ b/src/main/java/com/bullhorn/dataloader/data/Result.java @@ -8,11 +8,11 @@ */ public class Result { - private Status status = Status.NOT_SET; - private Action action = Action.NOT_SET; - private Integer bullhornId = -1; + private Status status; + private Action action; + private Integer bullhornId; private Integer bullhornParentId = -1; - private String failureText = ""; + private String failureText; public Result(Status status, Action action, Integer bullhornId, String failureText) { this.status = status; @@ -99,6 +99,15 @@ public static Result skip() { return new Result(Status.SUCCESS, Action.SKIP, -1, ""); } + /** + * Convert convenience constructor + * + * @return The new Result object + */ + public static Result export(Integer bullhornId) { + return new Result(Status.SUCCESS, Action.EXPORT, bullhornId, ""); + } + /** * Failure convenience constructor * @@ -242,6 +251,7 @@ public enum Action { DELETE, CONVERT, SKIP, + EXPORT, FAILURE } } diff --git a/src/main/java/com/bullhorn/dataloader/enums/Command.java b/src/main/java/com/bullhorn/dataloader/enums/Command.java index 83d8b995..ea9976b7 100644 --- a/src/main/java/com/bullhorn/dataloader/enums/Command.java +++ b/src/main/java/com/bullhorn/dataloader/enums/Command.java @@ -5,13 +5,14 @@ */ public enum Command { - HELP("help"), - TEMPLATE("template"), CONVERT_ATTACHMENTS("convertAttachments"), - LOAD("load"), DELETE("delete"), + DELETE_ATTACHMENTS("deleteAttachments"), + EXPORT("export"), + HELP("help"), + LOAD("load"), LOAD_ATTACHMENTS("loadAttachments"), - DELETE_ATTACHMENTS("deleteAttachments"); + TEMPLATE("template"); private final String methodName; diff --git a/src/main/java/com/bullhorn/dataloader/rest/Field.java b/src/main/java/com/bullhorn/dataloader/rest/Field.java index d57eb2f9..a5406b4c 100644 --- a/src/main/java/com/bullhorn/dataloader/rest/Field.java +++ b/src/main/java/com/bullhorn/dataloader/rest/Field.java @@ -8,11 +8,13 @@ import com.bullhornsdk.data.model.entity.embedded.Address; import com.bullhornsdk.data.model.entity.embedded.OneToMany; import com.google.common.collect.Lists; +import org.joda.time.DateTime; import org.joda.time.format.DateTimeFormatter; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.text.ParseException; +import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -28,10 +30,10 @@ public class Field { private final Cell cell; private Boolean existField; private final DateTimeFormatter dateTimeFormatter; - private Method getMethod = null; - private Method setMethod = null; - private Method getAssociationMethod = null; - private Method setAssociationMethod = null; + private Method getMethod; + private Method setMethod; + private Method getAssociationMethod; + private Method setAssociationMethod; /** * Constructor which takes the type of entity and the raw cell data. @@ -100,10 +102,19 @@ public Boolean isToMany() { * @return the name of the field (the direct field on this entity, or the direct field on the associated entity) */ public String getName() { - if (cell.isAssociation()) { - return cell.getAssociationFieldName(); - } - return cell.getName(); + return cell.isAssociation() ? cell.getAssociationFieldName() : cell.getName(); + } + + /** + * Returns the name of the field that is valid within the field parameter of a Get call. + * + * For direct fields, just the name of the field: firstName + * For compound fields, the name of the field + * + * @return the name of the field (the direct field on this entity, or the direct field on the associated entity) + */ + public String getFieldParameterName() { + return cell.isAssociation() ? cell.getAssociationBaseName() + "(" + cell.getAssociationFieldName() + ")" : cell.getName(); } /** @@ -112,21 +123,13 @@ public String getName() { * @return this entity if direct, an associated entity if To-One or To-Many. */ public EntityInfo getFieldEntity() { - if (cell.isAssociation()) { - return AssociationUtil.getFieldEntity(entityInfo, cell); - } - return entityInfo; + return cell.isAssociation() ? AssociationUtil.getFieldEntity(entityInfo, cell) : entityInfo; } /** * Returns the type of the field that this cell represents. * - * This will be the simple data type. If an assoc public String getName() { - * if (cell.isAssociation()) { - * return cell.getAssociationFieldName(); - * } - * return cell.getName(); - * iation, the data type will be that of the association's field. + * This will be the simple data type. If an association, the data type will be that of the association's field. * * @return cannot be null, since that would throw an exception in the constructor. */ @@ -165,10 +168,58 @@ public List split(String delimiter) { return Lists.newArrayList(getStringValue().split(delimiter)).stream().distinct().collect(Collectors.toList()); } + /** + * Calls the appropriate get method on the given SDK-REST entity object in order to get the value from an entity. + * + * For Association fields, behaves differently when given a parent entity vs. the field entity. + * For Example, when dealing with the Note field: `candidates.id`: + * - When given a note entity, uses the given delimiter to combine all candidate ID values into one delimited string. + * - When given an individual Candidate entity, calls getId() method on the Candidate object. + * + * @param entity the entity to pull data from + * @param delimiter the character(s) to split on + * @return the string value of this field on the given entity + */ + public String getStringValueFromEntity(Object entity, String delimiter) throws InvocationTargetException, IllegalAccessException { + if (cell.isAssociation() && entityInfo.getEntityClass().equals(entity.getClass())) { + if (isToMany()) { + List values = new ArrayList<>(); + OneToMany toManyAssociation = (OneToMany) getAssociationMethod.invoke(entity); + if (toManyAssociation != null) { + for (Object association : toManyAssociation.getData()) { + Object value = getMethod.invoke(association); + if (value != null) { + String stringValue = String.valueOf(value); + values.add(stringValue); + } + } + } + return String.join(delimiter, values); + } else { + Object toOneAssociation = getAssociationMethod.invoke(entity); + if (toOneAssociation == null) { + return ""; + } + Object value = getMethod.invoke(toOneAssociation); + return value != null ? String.valueOf(value) : ""; + } + } + + Object value = getMethod.invoke(entity); + if (value == null) { + return ""; + } + if (DateTime.class.equals(value.getClass())) { + DateTime dateTime = (DateTime) value; + return dateTimeFormatter.print(dateTime); + } + return String.valueOf(value); + } + /** * Calls the appropriate set method on the given SDK-REST entity object in order to send the entity in a REST call. * - * This only applies to direct or compound (address) fields, that have a simple value type. + * This only applies to direct or compound (address) fields that have a simple value type. * * @param entity the entity object to populate */ @@ -195,9 +246,7 @@ public void populateFieldOnEntity(Object entity) throws ParseException, Invocati public void populateAssociationOnEntity(BullhornEntity entity, BullhornEntity associatedEntity) throws ParseException, InvocationTargetException, IllegalAccessException { setMethod.invoke(associatedEntity, getValue()); - if (isToOne()) { - setAssociationMethod.invoke(entity, associatedEntity); - } else { + if (isToMany()) { OneToMany oneToMany = (OneToMany) getAssociationMethod.invoke(entity); if (oneToMany == null) { oneToMany = new OneToMany<>(); @@ -206,15 +255,28 @@ public void populateAssociationOnEntity(BullhornEntity entity, BullhornEntity as associations.add(associatedEntity); oneToMany.setData(associations); setAssociationMethod.invoke(entity, oneToMany); + } else { + setAssociationMethod.invoke(entity, associatedEntity); } } /** - * Calls the appropriate get method on the given SDK-REST entity object in order to get the value from an entity. + * Returns the oneToMany object for a To-Many association. + * + * @param entity the entity object to get the association value from. + */ + @SuppressWarnings("unchecked") + public OneToMany getOneToManyFromEntity(BullhornEntity entity) throws InvocationTargetException, IllegalAccessException { + return (OneToMany) getAssociationMethod.invoke(entity); + } + + /** + * Sets a To-Many field of a given entity to the given object. * - * @param entity the entity object to get the value of the field from + * @param entity the entity object to populate + * @param oneToMany the OneToMany object for this To-Many field */ - public Object getValueFromEntity(BullhornEntity entity) throws InvocationTargetException, IllegalAccessException { - return getMethod.invoke(entity); + public void populateOneToManyOnEntity(BullhornEntity entity, OneToMany oneToMany) throws InvocationTargetException, IllegalAccessException { + setAssociationMethod.invoke(entity, oneToMany); } } diff --git a/src/main/java/com/bullhorn/dataloader/rest/Record.java b/src/main/java/com/bullhorn/dataloader/rest/Record.java index 739aa074..cad20209 100644 --- a/src/main/java/com/bullhorn/dataloader/rest/Record.java +++ b/src/main/java/com/bullhorn/dataloader/rest/Record.java @@ -8,6 +8,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; /** @@ -73,4 +74,13 @@ public List getToManyFields() { return fields.stream().filter(field -> field.isToMany() && !field.getStringValue().isEmpty()).collect(Collectors.toList()); } } + + /** + * Returns all fields in a format that can be passed as the fields parameter of a Get call. + * + * @return the set of fields that will pull from Rest + */ + public Set getFieldsParameter() { + return fields.stream().map(Field::getFieldParameterName).collect(Collectors.toSet()); + } } diff --git a/src/main/java/com/bullhorn/dataloader/rest/RestApi.java b/src/main/java/com/bullhorn/dataloader/rest/RestApi.java index 22e82bb8..84635b83 100644 --- a/src/main/java/com/bullhorn/dataloader/rest/RestApi.java +++ b/src/main/java/com/bullhorn/dataloader/rest/RestApi.java @@ -33,6 +33,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; /** * Encapsulation of the standard SDK-REST BullhornData class for interacting with Bullhorn's REST API Provides an extra layer of functionality needed @@ -78,18 +79,20 @@ public List searchForList(Class type, String query, Set fieldSet, SearchParams params) { - printUtil.log(Level.DEBUG, "Find(" + type.getSimpleName() + " Search): " + query); - Boolean isSupportedEntity = type != JobOrder.class && type != Lead.class && type != Opportunity.class; + Set correctedFieldSet = FindUtil.getCorrectedFieldSet(fieldSet); + printUtil.log(Level.DEBUG, "Find(" + type.getSimpleName() + " Search): " + query + + ", fields: " + correctedFieldSet.stream().sorted().collect(Collectors.toList())); + boolean isSupportedEntity = type != JobOrder.class && type != Lead.class && type != Opportunity.class; String externalId = FindUtil.getExternalIdValue(query); if (isSupportedEntity && !externalId.isEmpty()) { - SearchResult searchResult = restApiExtension.getByExternalId(this, type, externalId, fieldSet); + SearchResult searchResult = restApiExtension.getByExternalId(this, type, externalId, correctedFieldSet); if (searchResult.getSuccess()) { return searchResult.getList(); } } List list = new ArrayList<>(); params.setCount(MAX_RECORDS_TO_RETURN_IN_ONE_PULL); - recursiveSearchPull(list, type, query, fieldSet, params); + recursiveSearchPull(list, type, query, correctedFieldSet, params); return list; } @@ -97,10 +100,12 @@ public List queryForList(Class type, String where, Set fieldSet, QueryParams params) { - printUtil.log(Level.DEBUG, "Find(" + type.getSimpleName() + " Query): " + where); + Set correctedFieldSet = FindUtil.getCorrectedFieldSet(fieldSet); + printUtil.log(Level.DEBUG, "Find(" + type.getSimpleName() + " Query): " + where + + ", fields: " + correctedFieldSet.stream().sorted().collect(Collectors.toList())); List list = new ArrayList<>(); params.setCount(MAX_RECORDS_TO_RETURN_IN_ONE_PULL); - recursiveQueryPull(list, type, where, fieldSet, params); + recursiveQueryPull(list, type, where, correctedFieldSet, params); return list; } // endregion @@ -140,7 +145,7 @@ public List getAllAss Class type, Set entityIds, AssociationField associationName, Set fieldSet, AssociationParams params) { printUtil.log(Level.DEBUG, "FindAssociations(" + type.getSimpleName() + "): #" + entityIds + " - " - + associationName.getAssociationFieldName()); + + associationName.getAssociationFieldName() + ", fields: " + fieldSet.stream().sorted().collect(Collectors.toList())); ListWrapper listWrapper = bullhornData.getAllAssociations(type, entityIds, associationName, fieldSet, params); return listWrapper == null ? Collections.emptyList() : listWrapper.getData(); } diff --git a/src/main/java/com/bullhorn/dataloader/service/AbstractService.java b/src/main/java/com/bullhorn/dataloader/service/AbstractService.java index ed182616..7a1bc99d 100644 --- a/src/main/java/com/bullhorn/dataloader/service/AbstractService.java +++ b/src/main/java/com/bullhorn/dataloader/service/AbstractService.java @@ -9,7 +9,6 @@ import com.bullhorn.dataloader.util.ValidationUtil; import java.io.File; -import java.io.IOException; import java.io.InputStream; import java.util.List; import java.util.Map; @@ -39,7 +38,7 @@ public AbstractService(PrintUtil printUtil, RestSession restSession, ProcessRunner processRunner, InputStream inputStream, - Timer timer) throws IOException { + Timer timer) { this.printUtil = printUtil; this.propertyFileUtil = propertyFileUtil; this.validationUtil = validationUtil; @@ -64,7 +63,7 @@ protected Boolean promptUserForMultipleFiles(String filePath, SortedMap 1)) { printUtil.printAndLog("Ready to process the following CSV files from the " + filePath + " directory in the following order:"); - Integer count = 1; + int count = 1; for (Map.Entry> entityFileEntry : entityToFileListMap.entrySet()) { String entityName = entityFileEntry.getKey().getEntityName(); for (String fileName : entityFileEntry.getValue()) { @@ -75,7 +74,7 @@ protected Boolean promptUserForMultipleFiles(String filePath, SortedMap> entityToFileListMap = FileUtil.getValidCsvFiles(filePath, validationUtil, EntityInfo.loadOrderComparator); + if (promptUserForMultipleFiles(filePath, entityToFileListMap)) { + for (Map.Entry> entityFileEntry : entityToFileListMap.entrySet()) { + EntityInfo entityInfo = entityFileEntry.getKey(); + for (String fileName : entityFileEntry.getValue()) { + printUtil.printAndLog("Exporting " + entityInfo.getEntityName() + " records from: " + fileName + "..."); + timer.start(); + ActionTotals actionTotals = processRunner.run(Command.EXPORT, entityInfo, fileName); + printUtil.printAndLog("Finished exporting " + entityInfo.getEntityName() + " records in " + timer.getDurationStringHms()); + completeUtil.complete(Command.EXPORT, fileName, entityInfo, actionTotals); + } + } + } + } + + @Override + public boolean isValidArguments(String[] args) { + if (!validationUtil.isNumParametersValid(args, 2)) { + return false; + } + + String filePath = args[1]; + File file = new File(filePath); + if (file.isDirectory()) { + if (FileUtil.getValidCsvFiles(filePath, validationUtil, EntityInfo.loadOrderComparator).isEmpty()) { + printUtil.printAndLog("ERROR: Could not find any valid CSV files (with entity name) to export from directory: " + filePath); + return false; + } + } else { + if (!validationUtil.isValidCsvFile(filePath)) { + return false; + } + + EntityInfo entityInfo = FileUtil.extractEntityFromFileName(filePath); + if (entityInfo == null) { + printUtil.printAndLog("ERROR: Could not determine entity from file name: " + filePath); + return false; + } + } + + return true; + } +} diff --git a/src/main/java/com/bullhorn/dataloader/service/LoadAttachmentsService.java b/src/main/java/com/bullhorn/dataloader/service/LoadAttachmentsService.java index 7c5370e8..640a5942 100644 --- a/src/main/java/com/bullhorn/dataloader/service/LoadAttachmentsService.java +++ b/src/main/java/com/bullhorn/dataloader/service/LoadAttachmentsService.java @@ -28,7 +28,7 @@ public LoadAttachmentsService(PrintUtil printUtil, RestSession restSession, ProcessRunner processRunner, InputStream inputStream, - Timer timer) throws IOException { + Timer timer) { super(printUtil, propertyFileUtil, validationUtil, completeUtil, restSession, processRunner, inputStream, timer); } @@ -43,7 +43,7 @@ public void run(String[] args) throws IOException, InterruptedException { printUtil.printAndLog("Loading " + entityInfo.getEntityName() + " attachments from: " + filePath + "..."); timer.start(); - ActionTotals actionTotals = processRunner.runLoadAttachmentsProcess(entityInfo, filePath); + ActionTotals actionTotals = processRunner.run(Command.LOAD_ATTACHMENTS, entityInfo, filePath); printUtil.printAndLog("Finished loading " + entityInfo.getEntityName() + " attachments in " + timer.getDurationStringHms()); completeUtil.complete(Command.LOAD_ATTACHMENTS, filePath, entityInfo, actionTotals); } diff --git a/src/main/java/com/bullhorn/dataloader/service/LoadService.java b/src/main/java/com/bullhorn/dataloader/service/LoadService.java index a98e1775..668eb07d 100644 --- a/src/main/java/com/bullhorn/dataloader/service/LoadService.java +++ b/src/main/java/com/bullhorn/dataloader/service/LoadService.java @@ -33,7 +33,7 @@ public LoadService(PrintUtil printUtil, RestSession restSession, ProcessRunner processRunner, InputStream inputStream, - Timer timer) throws IOException { + Timer timer) { super(printUtil, propertyFileUtil, validationUtil, completeUtil, restSession, processRunner, inputStream, timer); } @@ -51,7 +51,7 @@ public void run(String[] args) throws InterruptedException, IOException { for (String fileName : entityFileEntry.getValue()) { printUtil.printAndLog("Loading " + entityInfo.getEntityName() + " records from: " + fileName + "..."); timer.start(); - ActionTotals actionTotals = processRunner.runLoadProcess(entityInfo, fileName); + ActionTotals actionTotals = processRunner.run(Command.LOAD, entityInfo, fileName); printUtil.printAndLog("Finished loading " + entityInfo.getEntityName() + " records in " + timer.getDurationStringHms()); completeUtil.complete(Command.LOAD, fileName, entityInfo, actionTotals); } diff --git a/src/main/java/com/bullhorn/dataloader/service/ProcessRunner.java b/src/main/java/com/bullhorn/dataloader/service/ProcessRunner.java index 2b62befa..6cfba7aa 100644 --- a/src/main/java/com/bullhorn/dataloader/service/ProcessRunner.java +++ b/src/main/java/com/bullhorn/dataloader/service/ProcessRunner.java @@ -12,11 +12,7 @@ import com.bullhorn.dataloader.rest.RestApi; import com.bullhorn.dataloader.rest.RestSession; import com.bullhorn.dataloader.task.AbstractTask; -import com.bullhorn.dataloader.task.ConvertAttachmentTask; -import com.bullhorn.dataloader.task.DeleteAttachmentTask; -import com.bullhorn.dataloader.task.DeleteTask; -import com.bullhorn.dataloader.task.LoadAttachmentTask; -import com.bullhorn.dataloader.task.LoadTask; +import com.bullhorn.dataloader.task.TaskFactory; import com.bullhorn.dataloader.util.PrintUtil; import com.bullhorn.dataloader.util.PropertyFileUtil; import com.bullhorn.dataloader.util.ThreadPoolUtil; @@ -55,119 +51,21 @@ public ProcessRunner(RestSession restSession, this.completeUtil = completeUtil; } - ActionTotals runLoadProcess(EntityInfo entityInfo, String filePath) throws IOException, InterruptedException { + ActionTotals run(Command command, EntityInfo entityInfo, String filePath) throws IOException, InterruptedException { RestApi restApi = restSession.getRestApi(); ExecutorService executorService = threadPoolUtil.getExecutorService(); CsvFileReader csvFileReader = new CsvFileReader(filePath, propertyFileUtil, printUtil); - CsvFileWriter csvFileWriter = new CsvFileWriter(Command.LOAD, filePath, csvFileReader.getHeaders()); + CsvFileWriter csvFileWriter = new CsvFileWriter(command, filePath, csvFileReader.getHeaders()); ActionTotals actionTotals = new ActionTotals(); + TaskFactory taskFactory = new TaskFactory(entityInfo, csvFileWriter, propertyFileUtil, restApi, printUtil, actionTotals, completeUtil); // Loop over each row in the file while (csvFileReader.readRecord()) { - // Create an individual task runner (thread) for the row - Row row = preloader.convertRow(csvFileReader.getRow()); - AbstractTask task = new LoadTask(entityInfo, row, csvFileWriter, propertyFileUtil, restApi, printUtil, - actionTotals, completeUtil); - - // Put the task in the thread pool so that it can be processed when a thread is available - executorService.execute(task); - } - // Use Shutdown and AwaitTermination Wait to allow all current threads to complete and then print totals - executorService.shutdown(); - while (!executorService.awaitTermination(1, TimeUnit.MINUTES)) { - } - printUtil.printActionTotals(Command.LOAD, actionTotals); - return actionTotals; - } - - ActionTotals runDeleteProcess(EntityInfo entityInfo, String filePath) throws IOException, InterruptedException { - RestApi restApi = restSession.getRestApi(); - ExecutorService executorService = threadPoolUtil.getExecutorService(); - CsvFileReader csvFileReader = new CsvFileReader(filePath, propertyFileUtil, printUtil); - CsvFileWriter csvFileWriter = new CsvFileWriter(Command.DELETE, filePath, csvFileReader.getHeaders()); - ActionTotals actionTotals = new ActionTotals(); - - // Loop over each row in the file - while (csvFileReader.readRecord()) { - // Create an individual task runner (thread) for the row - Row row = csvFileReader.getRow(); - AbstractTask task = new DeleteTask(entityInfo, row, csvFileWriter, propertyFileUtil, restApi, printUtil, - actionTotals, completeUtil); + // Run preloader before loading only + Row row = command == Command.LOAD ? preloader.convertRow(csvFileReader.getRow()) : csvFileReader.getRow(); - // Put the task in the thread pool so that it can be processed when a thread is available - executorService.execute(task); - } - // Use Shutdown and AwaitTermination Wait to allow all current threads to complete and then print totals - executorService.shutdown(); - while (!executorService.awaitTermination(1, TimeUnit.MINUTES)) { - } - printUtil.printActionTotals(Command.DELETE, actionTotals); - return actionTotals; - } - - ActionTotals runLoadAttachmentsProcess(EntityInfo entityInfo, String filePath) throws IOException, InterruptedException { - RestApi restApi = restSession.getRestApi(); - ExecutorService executorService = threadPoolUtil.getExecutorService(); - CsvFileReader csvFileReader = new CsvFileReader(filePath, propertyFileUtil, printUtil); - CsvFileWriter csvFileWriter = new CsvFileWriter(Command.LOAD_ATTACHMENTS, filePath, csvFileReader.getHeaders()); - ActionTotals actionTotals = new ActionTotals(); - - // Loop over each row in the file - while (csvFileReader.readRecord()) { - // Create an individual task runner (thread) for the row - Row row = csvFileReader.getRow(); - LoadAttachmentTask task = new LoadAttachmentTask(entityInfo, row, csvFileWriter, propertyFileUtil, - restApi, printUtil, actionTotals, completeUtil); - - // Put the task in the thread pool so that it can be processed when a thread is available - executorService.execute(task); - } - // Use Shutdown and AwaitTermination Wait to allow all current threads to complete and then print totals - executorService.shutdown(); - while (!executorService.awaitTermination(1, TimeUnit.MINUTES)) { - } - printUtil.printActionTotals(Command.LOAD_ATTACHMENTS, actionTotals); - return actionTotals; - } - - ActionTotals runConvertAttachmentsProcess(EntityInfo entityInfo, String filePath) throws IOException, InterruptedException { - RestApi restApi = restSession.getRestApi(); - ExecutorService executorService = threadPoolUtil.getExecutorService(); - CsvFileReader csvFileReader = new CsvFileReader(filePath, propertyFileUtil, printUtil); - CsvFileWriter csvFileWriter = new CsvFileWriter(Command.CONVERT_ATTACHMENTS, filePath, csvFileReader.getHeaders()); - ActionTotals actionTotals = new ActionTotals(); - - // Loop over each row in the file - while (csvFileReader.readRecord()) { - // Create an individual task runner (thread) for the row - Row row = csvFileReader.getRow(); - ConvertAttachmentTask task = new ConvertAttachmentTask(entityInfo, row, csvFileWriter, - propertyFileUtil, restApi, printUtil, actionTotals, completeUtil); - - // Put the task in the thread pool so that it can be processed when a thread is available - executorService.execute(task); - } - // Use Shutdown and AwaitTermination Wait to allow all current threads to complete and then print totals - executorService.shutdown(); - while (!executorService.awaitTermination(1, TimeUnit.MINUTES)) { - } - printUtil.printActionTotals(Command.CONVERT_ATTACHMENTS, actionTotals); - return actionTotals; - } - - ActionTotals runDeleteAttachmentsProcess(EntityInfo entityInfo, String filePath) throws IOException, InterruptedException { - RestApi restApi = restSession.getRestApi(); - ExecutorService executorService = threadPoolUtil.getExecutorService(); - CsvFileReader csvFileReader = new CsvFileReader(filePath, propertyFileUtil, printUtil); - CsvFileWriter csvFileWriter = new CsvFileWriter(Command.DELETE_ATTACHMENTS, filePath, csvFileReader.getHeaders()); - ActionTotals actionTotals = new ActionTotals(); - - // Loop over each row in the file - while (csvFileReader.readRecord()) { // Create an individual task runner (thread) for the row - Row row = csvFileReader.getRow(); - DeleteAttachmentTask task = new DeleteAttachmentTask(entityInfo, row, csvFileWriter, propertyFileUtil, - restApi, printUtil, actionTotals, completeUtil); + AbstractTask task = taskFactory.getTask(command, row); // Put the task in the thread pool so that it can be processed when a thread is available executorService.execute(task); @@ -176,7 +74,7 @@ ActionTotals runDeleteAttachmentsProcess(EntityInfo entityInfo, String filePath) executorService.shutdown(); while (!executorService.awaitTermination(1, TimeUnit.MINUTES)) { } - printUtil.printActionTotals(Command.DELETE_ATTACHMENTS, actionTotals); + printUtil.printActionTotals(command, actionTotals); return actionTotals; } } diff --git a/src/main/java/com/bullhorn/dataloader/task/AbstractTask.java b/src/main/java/com/bullhorn/dataloader/task/AbstractTask.java index 8f9b96d2..68189132 100644 --- a/src/main/java/com/bullhorn/dataloader/task/AbstractTask.java +++ b/src/main/java/com/bullhorn/dataloader/task/AbstractTask.java @@ -31,14 +31,15 @@ public abstract class AbstractTask implements Runnable { static AtomicInteger rowProcessedCount = new AtomicInteger(0); + protected ActionTotals actionTotals; + protected CompleteUtil completeUtil; protected EntityInfo entityInfo; - protected Row row; + protected Integer entityId; + protected PrintUtil printUtil; protected PropertyFileUtil propertyFileUtil; protected RestApi restApi; - protected PrintUtil printUtil; - protected ActionTotals actionTotals; - protected Integer entityId; - protected CompleteUtil completeUtil; + protected Row row; + private CsvFileWriter csvFileWriter; AbstractTask(EntityInfo entityInfo, @@ -128,7 +129,7 @@ private void writeToResultCsv(Result result) { * * Find calls must be different between primary and associated entities. This affects custom objects, primarily. * Consider the column: `person.externalID` on the PersonCustomObjectInstance1 entity: - * - When looking for existing Person records to check for existance, we need a Person lookup for `externalID=` + * - When looking for existing Person records to check for existence, we need a Person lookup for `externalID=` * - When looking for existing PersonCustomObjectInstance1 records, we need a PersonCustomObjectInstance1 lookup for `person.externalID=` * * @param entityExistFields the key/value pairs that make up the search/query string diff --git a/src/main/java/com/bullhorn/dataloader/task/ExportTask.java b/src/main/java/com/bullhorn/dataloader/task/ExportTask.java new file mode 100644 index 00000000..68894e69 --- /dev/null +++ b/src/main/java/com/bullhorn/dataloader/task/ExportTask.java @@ -0,0 +1,85 @@ +package com.bullhorn.dataloader.task; + +import com.bullhorn.dataloader.data.ActionTotals; +import com.bullhorn.dataloader.data.Cell; +import com.bullhorn.dataloader.data.CsvFileWriter; +import com.bullhorn.dataloader.data.Result; +import com.bullhorn.dataloader.data.Row; +import com.bullhorn.dataloader.enums.EntityInfo; +import com.bullhorn.dataloader.rest.CompleteUtil; +import com.bullhorn.dataloader.rest.Field; +import com.bullhorn.dataloader.rest.Record; +import com.bullhorn.dataloader.rest.RestApi; +import com.bullhorn.dataloader.util.AssociationUtil; +import com.bullhorn.dataloader.util.FindUtil; +import com.bullhorn.dataloader.util.PrintUtil; +import com.bullhorn.dataloader.util.PropertyFileUtil; +import com.bullhornsdk.data.exception.RestApiException; +import com.bullhornsdk.data.model.entity.core.type.AssociationEntity; +import com.bullhornsdk.data.model.entity.core.type.BullhornEntity; +import com.bullhornsdk.data.model.entity.embedded.OneToMany; +import com.bullhornsdk.data.model.parameter.standard.ParamFactory; +import com.google.common.collect.Sets; + +import java.util.List; + +/** + * Finds current values for existing entity in Rest and overwrites the value in the row with values from Rest. + */ +public class ExportTask extends AbstractTask { + + public ExportTask(EntityInfo entityInfo, + Row row, + CsvFileWriter csvFileWriter, + PropertyFileUtil propertyFileUtil, + RestApi restApi, + PrintUtil printUtil, + ActionTotals actionTotals, + CompleteUtil completeUtil) { + super(entityInfo, row, csvFileWriter, propertyFileUtil, restApi, printUtil, actionTotals, completeUtil); + } + + @SuppressWarnings("unchecked") + protected Result handle() throws Exception { + Record record = new Record(entityInfo, row, propertyFileUtil); + List entityExistFields = record.getEntityExistFields(); + if (entityExistFields.isEmpty()) { + throw new RestApiException("Cannot perform export because exist field is not specified for entity: " + entityInfo.getEntityName()); + } + + // Lookup existing entities + List foundEntityList = findEntities(entityExistFields, record.getFieldsParameter(), true); + if (foundEntityList.isEmpty()) { + throw new RestApiException(FindUtil.getNoMatchingRecordsExistMessage(entityInfo, record.getEntityExistFields())); + } else if (foundEntityList.size() > 1) { + throw new RestApiException(FindUtil.getMultipleRecordsExistMessage(entityInfo, record.getEntityExistFields(), foundEntityList.size())); + } + BullhornEntity entity = foundEntityList.get(0); + entityId = entity.getId(); + + // Follow on query for associated entities that have not returned the full number of records + for (Field field : record.getToManyFields()) { + OneToMany existingToMany = field.getOneToManyFromEntity(entity); + if (existingToMany != null && existingToMany.getTotal() > existingToMany.getData().size()) { + List associations = restApi.getAllAssociationsList((Class) entityInfo.getEntityClass(), + Sets.newHashSet(entityId), AssociationUtil.getToManyField(field), Sets.newHashSet(field.getName()), + ParamFactory.associationParams()); + OneToMany newToMany = new OneToMany(); + newToMany.setTotal(existingToMany.getTotal()); + newToMany.setData(associations); + field.populateOneToManyOnEntity(entity, newToMany); + } + } + + // Replace row with current data from Rest + Row updatedRow = new Row(row.getFilePath(), row.getNumber()); + for (Field field : record.getFields()) { + String value = field.getStringValueFromEntity(entity, propertyFileUtil.getListDelimiter()); + Cell updatedCell = new Cell(field.getCell().getName(), value); + updatedRow.addCell(updatedCell); + } + row = updatedRow; + + return Result.export(entityId); + } +} diff --git a/src/main/java/com/bullhorn/dataloader/task/LoadTask.java b/src/main/java/com/bullhorn/dataloader/task/LoadTask.java index f012583d..45ab26ea 100644 --- a/src/main/java/com/bullhorn/dataloader/task/LoadTask.java +++ b/src/main/java/com/bullhorn/dataloader/task/LoadTask.java @@ -213,7 +213,8 @@ private List findAssociations(Field field) throws InvocationTarg if (!propertyFileUtil.getWildcardMatching() && associations.size() != values.size()) { Set existingAssociationValues = new HashSet<>(); for (BullhornEntity entity : associations) { - existingAssociationValues.add(String.valueOf(field.getValueFromEntity(entity))); + String value = field.getStringValueFromEntity(entity, propertyFileUtil.getListDelimiter()); + existingAssociationValues.add(value); } if (associations.size() > values.size()) { String duplicates = existingAssociationValues.stream().map(n -> "\t" + n).collect(Collectors.joining("\n")); diff --git a/src/main/java/com/bullhorn/dataloader/task/TaskFactory.java b/src/main/java/com/bullhorn/dataloader/task/TaskFactory.java new file mode 100644 index 00000000..46fe3d7a --- /dev/null +++ b/src/main/java/com/bullhorn/dataloader/task/TaskFactory.java @@ -0,0 +1,65 @@ +package com.bullhorn.dataloader.task; + +import com.bullhorn.dataloader.data.ActionTotals; +import com.bullhorn.dataloader.data.CsvFileWriter; +import com.bullhorn.dataloader.data.Row; +import com.bullhorn.dataloader.enums.Command; +import com.bullhorn.dataloader.enums.EntityInfo; +import com.bullhorn.dataloader.rest.CompleteUtil; +import com.bullhorn.dataloader.rest.RestApi; +import com.bullhorn.dataloader.util.PrintUtil; +import com.bullhorn.dataloader.util.PropertyFileUtil; + +/** + * Given a command, this class returns a task that can execute part of that command. + */ +public class TaskFactory { + + private final EntityInfo entityInfo; + private final CsvFileWriter csvFileWriter; + private final PropertyFileUtil propertyFileUtil; + private final RestApi restApi; + private final PrintUtil printUtil; + private final ActionTotals actionTotals; + private final CompleteUtil completeUtil; + + public TaskFactory(EntityInfo entityInfo, + CsvFileWriter csvFileWriter, + PropertyFileUtil propertyFileUtil, + RestApi restApi, + PrintUtil printUtil, + ActionTotals actionTotals, + CompleteUtil completeUtil) { + this.entityInfo = entityInfo; + this.csvFileWriter = csvFileWriter; + this.propertyFileUtil = propertyFileUtil; + this.restApi = restApi; + this.printUtil = printUtil; + this.actionTotals = actionTotals; + this.completeUtil = completeUtil; + } + + /** + * Given a command enum, this returns the task that will accomplish part of that command. + * + * @param command The user's command + * @return The corresponding task + */ + public AbstractTask getTask(Command command, Row row) { + AbstractTask task = null; + if (command.equals(Command.CONVERT_ATTACHMENTS)) { + task = new ConvertAttachmentTask(entityInfo, row, csvFileWriter, propertyFileUtil, restApi, printUtil, actionTotals, completeUtil); + } else if (command.equals(Command.DELETE)) { + task = new DeleteTask(entityInfo, row, csvFileWriter, propertyFileUtil, restApi, printUtil, actionTotals, completeUtil); + } else if (command.equals(Command.DELETE_ATTACHMENTS)) { + task = new DeleteAttachmentTask(entityInfo, row, csvFileWriter, propertyFileUtil, restApi, printUtil, actionTotals, completeUtil); + } else if (command.equals(Command.EXPORT)) { + task = new ExportTask(entityInfo, row, csvFileWriter, propertyFileUtil, restApi, printUtil, actionTotals, completeUtil); + } else if (command.equals(Command.LOAD)) { + task = new LoadTask(entityInfo, row, csvFileWriter, propertyFileUtil, restApi, printUtil, actionTotals, completeUtil); + } else if (command.equals(Command.LOAD_ATTACHMENTS)) { + task = new LoadAttachmentTask(entityInfo, row, csvFileWriter, propertyFileUtil, restApi, printUtil, actionTotals, completeUtil); + } + return task; + } +} diff --git a/src/main/java/com/bullhorn/dataloader/util/FindUtil.java b/src/main/java/com/bullhorn/dataloader/util/FindUtil.java index aa3a9e78..904c85a1 100644 --- a/src/main/java/com/bullhorn/dataloader/util/FindUtil.java +++ b/src/main/java/com/bullhorn/dataloader/util/FindUtil.java @@ -5,16 +5,20 @@ import com.bullhornsdk.data.exception.RestApiException; import com.bullhornsdk.data.model.entity.core.standard.Person; import com.bullhornsdk.data.model.entity.core.type.BullhornEntity; +import com.google.common.collect.Sets; import org.joda.time.DateTime; import java.math.BigDecimal; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; /** * Utility for constructing find call syntax (Search/Query statements) */ public class FindUtil { + private static final Integer MAX_NUM_FIELDS = 30; // A safe, low number, since actual number depends on total length of the query string + // Returns the format of a single term in a lucene search public static String getLuceneSearch(String field, String value, Class fieldType, EntityInfo fieldEntityInfo, PropertyFileUtil propertyFileUtil) { // Fix for the Note entity doing it's own thing when it comes to the 'id' field @@ -43,7 +47,7 @@ public static String getLuceneSearch(String field, String value, Class fieldType * * @param entityExistFields the key/value pair list of fields to search on * @param propertyFileUtil the propertyFile settings - * @param isPrimaryEntity true = lookup for entity that we are loading, false = lookup for association + * @param isPrimaryEntity true = lookup for entity that we are loading, false = lookup for association * @return the formatted lucene search string */ public static String getLuceneSearch(List entityExistFields, PropertyFileUtil propertyFileUtil, Boolean isPrimaryEntity) { @@ -60,7 +64,7 @@ public static String getLuceneSearch(List entityExistFields, PropertyFile * For to-many fields: (name:Jack OR name:Jill OR name:Spot) * * TODO: For to-many id fields, improve search string syntax by only including ids with spaces - * separating them, like: "id: 1 2 3 4 5" in order to save space in Query String + * separating them, like: "id: 1 2 3 4 5" in order to save space in Query String */ private static String getLuceneSearch(Field field, PropertyFileUtil propertyFileUtil, Boolean isPrimaryEntity) { if (field.isToMany()) { @@ -107,7 +111,7 @@ public static String getSqlQuery(String field, String value, Class fieldType, Pr * * @param entityExistFields the key/value pair list of fields to search on * @param propertyFileUtil the propertyFile settings - * @param isPrimaryEntity true = lookup for entity that we are loading, false = lookup for association + * @param isPrimaryEntity true = lookup for entity that we are loading, false = lookup for association * @return the formatted where clause for the query string */ public static String getSqlQuery(List entityExistFields, PropertyFileUtil propertyFileUtil, Boolean isPrimaryEntity) { @@ -162,10 +166,19 @@ public static Boolean isPersonActive(BullhornEntity entity) { } /** - * Simple method for getting a nicely formatted user message about multiple existing records to choose from. + * Returns a nicely formatted user message about no matching records. + */ + public static String getNoMatchingRecordsExistMessage(EntityInfo entityInfo, List entityExistFields) { + return "No Matching " + entityInfo.getEntityName() + " Records Exist with ExistField criteria of: " + + entityExistFields.stream().map(field -> field.getCell().getName() + "=" + field.getStringValue()) + .collect(Collectors.joining(" AND ")); + } + + /** + * Returns a nicely formatted user message about multiple existing records to choose from. */ public static String getMultipleRecordsExistMessage(EntityInfo entityInfo, List entityExistFields, Integer numRecords) { - return "Cannot Perform Update - Multiple Records Exist. Found " + return "Multiple Records Exist. Found " + numRecords + " " + entityInfo.getEntityName() + " records with the same ExistField criteria of: " + entityExistFields.stream() .map(field -> field.getCell().getName() + "=" + field.getStringValue()) @@ -189,4 +202,23 @@ public static String getExternalIdValue(String searchString) { } return externalId; } + + /** + * Ensures that the fieldSet will work properly in rest calls and is not too long or missing an id. + * + * Returns the given set of fields as is or converts the set to a search for all fields (*) if the fields parameter is too large. + * A field parameter that is too large will result in the call failing because it goes beyond the supported query param string length. + * Also, adds the id field if it is not already specified, so that any follow on calls that require the id will work. + */ + public static Set getCorrectedFieldSet(Set fieldSet) { + if (fieldSet == null || fieldSet.size() > MAX_NUM_FIELDS) { + return Sets.newHashSet(StringConsts.ALL_FIELDS); + } + if (!fieldSet.contains(StringConsts.ALL_FIELDS) && !fieldSet.contains(StringConsts.ID)) { + Set correctedFieldSet = Sets.newHashSet(fieldSet); + correctedFieldSet.add(StringConsts.ID); + return correctedFieldSet; + } + return fieldSet; + } } diff --git a/src/main/java/com/bullhorn/dataloader/util/PrintUtil.java b/src/main/java/com/bullhorn/dataloader/util/PrintUtil.java index 397124ce..4d64440e 100644 --- a/src/main/java/com/bullhorn/dataloader/util/PrintUtil.java +++ b/src/main/java/com/bullhorn/dataloader/util/PrintUtil.java @@ -31,15 +31,16 @@ public void recordStart(String[] args) { public void printUsage() { print(""); print("Usage: "); - print(" Load: dataloader load path/to/.csv"); - print(" dataloader load path/to/directory"); - print(" Delete: dataloader delete path/to/.csv"); - print(" dataloader delete path/to/directory"); - print(" Convert Attachments: dataloader convertAttachments path/to/.csv"); - print(" Load Attachments: dataloader loadAttachments path/to/.csv"); - print(" Delete Attachments: dataloader deleteAttachments path/to/.csv"); - print(" Create Template: dataloader template "); - print(" Compare Example File: dataloader template .csv"); + print(" Load: dataloader load path/to/.csv"); + print(" dataloader load path/to/directory"); + print(" Delete: dataloader delete path/to/.csv"); + print(" dataloader delete path/to/directory"); + print(" Export: dataloader export path/to/.csv"); + print(" dataloader export path/to/directory"); + print(" Convert Attachments: dataloader convertAttachments path/to/.csv"); + print(" Load Attachments: dataloader loadAttachments path/to/.csv"); + print(" Delete Attachments: dataloader deleteAttachments path/to/.csv"); + print(" Create Template: dataloader template "); print(""); print("where is one of the supported entities listed at:"); print(" https://github.com/bullhorn/dataloader/wiki/Supported-Entities"); @@ -58,7 +59,9 @@ public void printActionTotals(Command command, ActionTotals actionTotals) { printAndLog("End time: " + dateFormat.format(endTime)); printAndLog("Args: " + String.join(" ", args)); printAndLog("Total records processed: " + totalRecords); - if (command.equals(Command.CONVERT_ATTACHMENTS)) { + if (command.equals(Command.EXPORT)) { + printAndLog("Total records exported: " + actionTotals.getActionTotal(Result.Action.EXPORT)); + } else if (command.equals(Command.CONVERT_ATTACHMENTS)) { printAndLog("Total records converted: " + actionTotals.getActionTotal(Result.Action.CONVERT)); printAndLog("Total records skipped: " + actionTotals.getActionTotal(Result.Action.SKIP)); } else { diff --git a/src/main/java/com/bullhorn/dataloader/util/StringConsts.java b/src/main/java/com/bullhorn/dataloader/util/StringConsts.java index 3b405ea6..9f612b8c 100644 --- a/src/main/java/com/bullhorn/dataloader/util/StringConsts.java +++ b/src/main/java/com/bullhorn/dataloader/util/StringConsts.java @@ -8,6 +8,7 @@ */ public class StringConsts { public static final List ADDRESS_FIELDS = Arrays.asList("address1", "address2", "city", "state", "zip", "countryId", "countryName"); + public static final String ALL_FIELDS = "*"; public static final String COLUMN_NAME_ALIAS_SUFFIX = "Column"; public static final String CONVERTED_ATTACHMENTS_DIRECTORY = "convertedAttachments"; public static final String CORPORATE_USER = "CorporateUser"; diff --git a/src/test/java/com/bullhorn/dataloader/TestUtils.java b/src/test/java/com/bullhorn/dataloader/TestUtils.java index dab8da65..7a1d267d 100644 --- a/src/test/java/com/bullhorn/dataloader/TestUtils.java +++ b/src/test/java/com/bullhorn/dataloader/TestUtils.java @@ -8,7 +8,9 @@ import com.bullhorn.dataloader.enums.Command; import com.bullhornsdk.data.model.entity.core.standard.Country; import com.bullhornsdk.data.model.entity.core.standard.Person; +import com.bullhornsdk.data.model.entity.core.standard.Skill; import com.bullhornsdk.data.model.entity.core.type.BullhornEntity; +import com.bullhornsdk.data.model.entity.embedded.OneToMany; import com.bullhornsdk.data.model.enums.ChangeType; import com.bullhornsdk.data.model.response.crud.AbstractCrudResponse; import com.bullhornsdk.data.model.response.crud.Message; @@ -81,6 +83,19 @@ public static ListWrapper getListWrapper(Class return listWrapper; } + /** + * Given a total count and a list of entities, constructs the OneToMany object for the given entities + * + * @param total The total number of records that exist, even if they are not present in the entityList + * @param entityList The entities that are present in the data portion of the OneToMany + * @return The oneToMany object that holds entity associations in the SDK-REST + */ + public static OneToMany getOneToMany(Integer total, B... entityList) { + OneToMany oneToMany = new OneToMany<>(entityList); + oneToMany.setTotal(total); + return oneToMany; + } + /** * Convenience method for mocking a CreateResponse from SDK-REST * @@ -233,9 +248,12 @@ public static void checkCommandLineOutput(String output, Result.Action action) { Assert.assertTrue("There are failures during " + actionString + " step", output.contains("failed: 0")); if (action == Result.Action.CONVERT) { - return; // Convert does not output inserted/updated/deleted, only converted/skipped + return; // Convert does not output inserted/updated/deleted, only converted/skipped/failed + } + if (action == Result.Action.EXPORT) { + Assert.assertFalse("No exports performed", output.contains("exported: 0")); + return; // Export does not output inserted/updated/deleted, only exported/failed } - if (action != Result.Action.INSERT) { Assert.assertTrue("Insert performed during " + actionString + " step", output.contains("inserted: 0")); } @@ -318,4 +336,18 @@ public static Person createPerson(Integer id, String subType, Boolean isDeleted) person.setIsDeleted(isDeleted); return person; } + + /** + * Convenience constructor that builds up the required Skill object data. + * + * @param id the id of the skill + * @param name the name of the skill + * @return the new Skill object + */ + public static Skill createSkill(Integer id, String name) { + Skill skill = new Skill(); + skill.setId(id); + skill.setName(name); + return skill; + } } diff --git a/src/test/java/com/bullhorn/dataloader/integration/IntegrationTest.java b/src/test/java/com/bullhorn/dataloader/integration/IntegrationTest.java index bb453748..3eaf89ef 100644 --- a/src/test/java/com/bullhorn/dataloader/integration/IntegrationTest.java +++ b/src/test/java/com/bullhorn/dataloader/integration/IntegrationTest.java @@ -14,26 +14,8 @@ import java.util.UUID; /** - * The purpose of this integration test is to: - * - * 1. Allow for TravisCI to run as part of every build check, using `maven verify`, which goes beyond `maven test` to - * also run the integration test. Uses a test corp on SL9 (BhNext) with hidden credentials in TravisCI Environment - * Variables. - * - * 2. Tests the entire Examples directory, which contains all possible values for all loadable entities and their - * attachments. The unique IDs of all of the entities are changed from `-ext-1` to something unique, after the examples - * have been cloned to a test folder. - * - * 3. INSERT the entire examples/load/ folder by performing the load command the first time. - * - * 4. UPDATE the entire examples/load/ folder by performing the load command a second time, with all exist fields - * properly set in the integrationTest.properties file. - * - * 5. DELETE all entered records by targeting the entire results directory. - * - * 6. Test assertions of both command line output and results files created. We are not making calls against the CRM - * itself to verify the presence or absence of records, since these steps will cover the presence of records in the - * index and database. + * TravisCI runs this as part of every build, using `maven verify`, which goes beyond `maven test` to run the integration test. + * Uses a test corp on SL9 (BhNext) with hidden credentials in TravisCI Environment Variables. */ public class IntegrationTest { @@ -43,7 +25,8 @@ public class IntegrationTest { private ConsoleOutputCapturer consoleOutputCapturer; /** - * Runs the integration test, first with a very simple sanity check, then the full examples directory. + * Tests all directories under: src/test/java/resources/integrationTest for specific use cases. Also tests the entire examples + * directory, which contains all possible values for all loadable entities and their attachments (See examples/README.md). * * @throws IOException For directory cloning */ @@ -56,52 +39,66 @@ public void testIntegration() throws IOException { consoleOutputCapturer = new ConsoleOutputCapturer(); // Sanity to catch quick and obvious failures - insertUpdateDeleteFromDirectory(TestUtils.getResourceFilePath("sanity"), false); + runAllCommandsAgainstDirectory(TestUtils.getResourceFilePath("sanity"), false); // Special character test to ensure that we are supporting them in query/search calls - insertUpdateDeleteFromDirectory(TestUtils.getResourceFilePath("specialCharacters"), true); + runAllCommandsAgainstDirectory(TestUtils.getResourceFilePath("specialCharacters"), true); // Test using more than 100,000 characters in a field - insertUpdateDeleteFromDirectory(TestUtils.getResourceFilePath("longFields"), false); + runAllCommandsAgainstDirectory(TestUtils.getResourceFilePath("longFields"), false); // Test using more than 500 associations in a To-Many field - requires that wildcard matching is enabled System.setProperty("wildcardMatching", "true"); - insertUpdateDeleteFromDirectory(TestUtils.getResourceFilePath("associationsOver500"), false); + runAllCommandsAgainstDirectory(TestUtils.getResourceFilePath("associationsOver500"), false); System.setProperty("wildcardMatching", "false"); // Test for ignoring soft deleted entities - insertUpdateDeleteFromDirectory(TestUtils.getResourceFilePath("softDeletes"), true); + runAllCommandsAgainstDirectory(TestUtils.getResourceFilePath("softDeletes"), true); // Test that column header name mapping is working properly - insertUpdateDeleteFromDirectory(TestUtils.getResourceFilePath("columnMapping"), false); + runAllCommandsAgainstDirectory(TestUtils.getResourceFilePath("columnMapping"), false); // Test that the byte order mark is ignored when it's present in the input file as the first (hidden) character - insertUpdateDeleteFromDirectory(TestUtils.getResourceFilePath("byteOrderMark"), false); + runAllCommandsAgainstDirectory(TestUtils.getResourceFilePath("byteOrderMark"), false); // Test for wildcard associations for candidates in a note System.setProperty("wildcardMatching", "true"); - insertUpdateDeleteFromDirectory(TestUtils.getResourceFilePath("wildcardMatching"), false); + runAllCommandsAgainstDirectory(TestUtils.getResourceFilePath("wildcardMatching"), false); System.setProperty("wildcardMatching", "false"); // Run a test for processing empty association fields (with the setting turned on) System.setProperty("processEmptyAssociations", "true"); - insertUpdateDeleteFromDirectory(TestUtils.getResourceFilePath("processEmptyFields"), false); + runAllCommandsAgainstDirectory(TestUtils.getResourceFilePath("processEmptyFields"), false); System.setProperty("processEmptyAssociations", "false"); // Run the full test of all example files - insertUpdateDeleteFromDirectory("examples/load", false); + runAllCommandsAgainstDirectory("examples/load", false); } /** - * Given a directory path, this method will attempt to load twice (insert, then update) then delete all CSV files - * found in that directory. + * Given a directory path, this method will attempt to run all commands against CSV input files in that directory: + * + * 1. Load - Insert + * 2. Convert Attachments + * 3. Load Attachments - Insert + * 4. Load Attachments - Update + * 5. Delete Attachments + * 6. Export + * 7. Load - Update + * 8. Delete + * + * The unique IDs of all of the entities are changed from `-ext-1` to something unique, after the examples have been + * cloned to a test folder. + * + * Test assertions of both command line output and results files created. These steps cover the presence of records + * in the index and database, so if indexing is lagging behind in production, it will cause the build to fail. * * @param directoryPath The path to the directory to load * @param skipDelete Set to true if the test directory contains intentionally deleted records * @throws IOException For directory cloning */ @SuppressWarnings("ConstantConditions") - private void insertUpdateDeleteFromDirectory(String directoryPath, Boolean skipDelete) throws IOException { + private void runAllCommandsAgainstDirectory(String directoryPath, Boolean skipDelete) throws IOException { // region SETUP // Copy example files to a temp directory located at: 'dataloader/target/test-classes/integrationTest_1234567890' long secondsSinceEpoch = System.currentTimeMillis() / 1000; @@ -117,11 +114,11 @@ private void insertUpdateDeleteFromDirectory(String directoryPath, Boolean skipD // Replace all UUIDs in with unique ones String uuid = UUID.randomUUID().toString(); TestUtils.replaceTextInFiles(tempDirectory, uuid, EXAMPLE_UUID); - // endregion SETUP + // endregion - // region INSERT + // region LOAD - INSERT FileUtils.deleteQuietly(new File(CsvFileWriter.RESULTS_DIR)); // Cleanup from previous runs - System.setIn(IOUtils.toInputStream("yes", "UTF-8")); // For accepting the load/delete from directory + System.setIn(IOUtils.toInputStream("yes", "UTF-8")); // Accepts command for entire directory consoleOutputCapturer.start(); Main.main(new String[]{"load", tempDirPath}); TestUtils.checkCommandLineOutput(consoleOutputCapturer.stop(), Result.Action.INSERT); @@ -139,14 +136,14 @@ private void insertUpdateDeleteFromDirectory(String directoryPath, Boolean skipD TestUtils.checkResultsFiles(tempAttachmentsDirectory, Command.CONVERT_ATTACHMENTS); // endregion - // region INSERT ATTACHMENTS + // region LOAD ATTACHMENTS - INSERT FileUtils.deleteQuietly(new File(CsvFileWriter.RESULTS_DIR)); // Cleanup from previous runs consoleOutputCapturer.start(); Main.main(new String[]{"loadAttachments", tempAttachmentsDirectory.getPath() + "/Candidate.csv"}); TestUtils.checkCommandLineOutput(consoleOutputCapturer.stop(), Result.Action.INSERT); // endregion - // region UPDATE ATTACHMENTS + // region LOAD ATTACHMENTS - UPDATE // Do not cleanup from previous run here - both Candidate and CandidateUpdate need to be present for delete step consoleOutputCapturer.start(); Main.main(new String[]{"loadAttachments", tempAttachmentsDirectory.getPath() + "/CandidateUpdate.csv"}); @@ -163,9 +160,18 @@ private void insertUpdateDeleteFromDirectory(String directoryPath, Boolean skipD // endregion } - // region UPDATE + // region EXPORT + FileUtils.deleteQuietly(new File(CsvFileWriter.RESULTS_DIR)); // Cleanup from previous runs + System.setIn(IOUtils.toInputStream("yes", "UTF-8")); // Accepts command for entire directory + consoleOutputCapturer.start(); + Main.main(new String[]{"export", tempDirPath}); + TestUtils.checkCommandLineOutput(consoleOutputCapturer.stop(), Result.Action.EXPORT); + TestUtils.checkResultsFiles(tempDirectory, Command.EXPORT); + // endregion + + // region LOAD - UPDATE FileUtils.deleteQuietly(new File(CsvFileWriter.RESULTS_DIR)); // Cleanup from previous runs - System.setIn(IOUtils.toInputStream("yes", "UTF-8")); + System.setIn(IOUtils.toInputStream("yes", "UTF-8")); // Accepts command for entire directory consoleOutputCapturer.start(); Main.main(new String[]{"load", tempDirPath}); TestUtils.checkCommandLineOutput(consoleOutputCapturer.stop(), Result.Action.UPDATE); @@ -186,7 +192,7 @@ private void insertUpdateDeleteFromDirectory(String directoryPath, Boolean skipD // Capture results file directory state File[] resultsFiles = resultsDir.listFiles(); - System.setIn(IOUtils.toInputStream("yes", "UTF-8")); + System.setIn(IOUtils.toInputStream("yes", "UTF-8")); // Accepts command for entire directory consoleOutputCapturer.start(); Main.main(new String[]{"delete", CsvFileWriter.RESULTS_DIR}); TestUtils.checkCommandLineOutput(consoleOutputCapturer.stop(), Result.Action.DELETE); diff --git a/src/test/java/com/bullhorn/dataloader/rest/FieldTest.java b/src/test/java/com/bullhorn/dataloader/rest/FieldTest.java index 2c2341a8..80c592ce 100644 --- a/src/test/java/com/bullhorn/dataloader/rest/FieldTest.java +++ b/src/test/java/com/bullhorn/dataloader/rest/FieldTest.java @@ -16,7 +16,6 @@ import org.junit.Before; import org.junit.Test; -import java.lang.reflect.InvocationTargetException; import java.math.BigDecimal; import java.text.ParseException; @@ -30,7 +29,7 @@ public void setup() { } @Test - public void testDirectStringField() throws ParseException, InvocationTargetException, IllegalAccessException { + public void testDirectStringField() throws Exception { Cell cell = new Cell("firstName", "Jack"); Field field = new Field(EntityInfo.CANDIDATE, cell, false, dateTimeFormatter); @@ -39,6 +38,7 @@ public void testDirectStringField() throws ParseException, InvocationTargetExcep Assert.assertEquals(field.isToOne(), false); Assert.assertEquals(field.isToMany(), false); Assert.assertEquals(field.getName(), "firstName"); + Assert.assertEquals(field.getFieldParameterName(), "firstName"); Assert.assertEquals(field.getFieldEntity(), EntityInfo.CANDIDATE); Assert.assertEquals(field.getFieldType(), String.class); Assert.assertEquals(field.getValue(), "Jack"); @@ -46,19 +46,20 @@ public void testDirectStringField() throws ParseException, InvocationTargetExcep Candidate candidate = new Candidate(); - Assert.assertEquals(candidate.getFirstName(), null); + Assert.assertNull(candidate.getFirstName()); + Assert.assertEquals(field.getStringValueFromEntity(candidate, ";"), ""); field.populateFieldOnEntity(candidate); Assert.assertEquals(candidate.getFirstName(), "Jack"); - Assert.assertEquals(field.getValueFromEntity(candidate), "Jack"); + Assert.assertEquals(field.getStringValueFromEntity(candidate, ";"), "Jack"); field.setExistField(true); Assert.assertEquals(field.isExistField(), true); } @Test - public void testDirectBooleanField() throws ParseException, InvocationTargetException, IllegalAccessException { + public void testDirectBooleanField() throws Exception { Cell cell = new Cell("isDeleted", ""); Field field = new Field(EntityInfo.JOB_SUBMISSION, cell, true, dateTimeFormatter); @@ -67,6 +68,7 @@ public void testDirectBooleanField() throws ParseException, InvocationTargetExce Assert.assertEquals(field.isToOne(), false); Assert.assertEquals(field.isToMany(), false); Assert.assertEquals(field.getName(), "isDeleted"); + Assert.assertEquals(field.getFieldParameterName(), "isDeleted"); Assert.assertEquals(field.getFieldEntity(), EntityInfo.JOB_SUBMISSION); Assert.assertEquals(field.getFieldType(), Boolean.class); Assert.assertEquals(field.getValue(), false); @@ -74,16 +76,17 @@ public void testDirectBooleanField() throws ParseException, InvocationTargetExce JobSubmission jobSubmission = new JobSubmission(); - Assert.assertEquals(jobSubmission.getIsDeleted(), null); + Assert.assertNull(jobSubmission.getIsDeleted()); + Assert.assertEquals(field.getStringValueFromEntity(jobSubmission, ";"), ""); field.populateFieldOnEntity(jobSubmission); Assert.assertEquals(jobSubmission.getIsDeleted(), false); - Assert.assertEquals(field.getValueFromEntity(jobSubmission), false); + Assert.assertEquals(field.getStringValueFromEntity(jobSubmission, ";"), "false"); } @Test - public void testDirectDateTimeField() throws ParseException, InvocationTargetException, IllegalAccessException { + public void testDirectDateTimeField() throws Exception { Cell cell = new Cell("dateAvailable", "02/09/2001"); Field field = new Field(EntityInfo.CANDIDATE, cell, false, dateTimeFormatter); @@ -92,6 +95,7 @@ public void testDirectDateTimeField() throws ParseException, InvocationTargetExc Assert.assertEquals(field.isToOne(), false); Assert.assertEquals(field.isToMany(), false); Assert.assertEquals(field.getName(), "dateAvailable"); + Assert.assertEquals(field.getFieldParameterName(), "dateAvailable"); Assert.assertEquals(field.getFieldEntity(), EntityInfo.CANDIDATE); Assert.assertEquals(field.getFieldType(), DateTime.class); Assert.assertEquals(field.getValue(), dateTimeFormatter.parseDateTime("02/09/2001")); @@ -99,16 +103,17 @@ public void testDirectDateTimeField() throws ParseException, InvocationTargetExc Candidate candidate = new Candidate(); - Assert.assertEquals(candidate.getDateAvailable(), null); + Assert.assertNull(candidate.getDateAvailable()); + Assert.assertEquals(field.getStringValueFromEntity(candidate, ";"), ""); field.populateFieldOnEntity(candidate); Assert.assertEquals(candidate.getDateAvailable(), dateTimeFormatter.parseDateTime("02/09/2001")); - Assert.assertEquals(field.getValueFromEntity(candidate), dateTimeFormatter.parseDateTime("02/09/2001")); + Assert.assertEquals(field.getStringValueFromEntity(candidate, ";"), "02/09/2001"); } @Test - public void testToOneBooleanField() throws ParseException, InvocationTargetException, IllegalAccessException { + public void testToOneBooleanField() throws Exception { Cell cell = new Cell("candidate.isDeleted", "true"); Field field = new Field(EntityInfo.CANDIDATE_WORK_HISTORY, cell, true, dateTimeFormatter); @@ -117,6 +122,7 @@ public void testToOneBooleanField() throws ParseException, InvocationTargetExcep Assert.assertEquals(field.isToOne(), true); Assert.assertEquals(field.isToMany(), false); Assert.assertEquals(field.getName(), "isDeleted"); + Assert.assertEquals(field.getFieldParameterName(), "candidate(isDeleted)"); Assert.assertEquals(field.getFieldEntity(), EntityInfo.CANDIDATE); Assert.assertEquals(field.getFieldType(), Boolean.class); Assert.assertEquals(field.getValue(), true); @@ -125,16 +131,19 @@ public void testToOneBooleanField() throws ParseException, InvocationTargetExcep CandidateWorkHistory candidateWorkHistory = new CandidateWorkHistory(); Candidate candidate = new Candidate(); - Assert.assertEquals(candidateWorkHistory.getCandidate(), null); + Assert.assertNull(candidateWorkHistory.getCandidate()); + Assert.assertEquals(field.getStringValueFromEntity(candidateWorkHistory, ";"), ""); + Assert.assertEquals(field.getStringValueFromEntity(candidate, ";"), ""); field.populateAssociationOnEntity(candidateWorkHistory, candidate); Assert.assertEquals(candidateWorkHistory.getCandidate().getIsDeleted(), true); - Assert.assertEquals(field.getValueFromEntity(candidate), true); + Assert.assertEquals(field.getStringValueFromEntity(candidateWorkHistory, ";"), "true"); + Assert.assertEquals(field.getStringValueFromEntity(candidate, ";"), "true"); } @Test - public void testToOneBigDecimalField() throws ParseException, InvocationTargetException, IllegalAccessException { + public void testToOneBigDecimalField() throws Exception { Cell cell = new Cell("candidate.salary", "123.45"); Field field = new Field(EntityInfo.CANDIDATE_WORK_HISTORY, cell, false, dateTimeFormatter); @@ -143,6 +152,7 @@ public void testToOneBigDecimalField() throws ParseException, InvocationTargetEx Assert.assertEquals(field.isToOne(), true); Assert.assertEquals(field.isToMany(), false); Assert.assertEquals(field.getName(), "salary"); + Assert.assertEquals(field.getFieldParameterName(), "candidate(salary)"); Assert.assertEquals(field.getFieldEntity(), EntityInfo.CANDIDATE); Assert.assertEquals(field.getFieldType(), BigDecimal.class); BigDecimal actual = (BigDecimal) field.getValue(); @@ -152,18 +162,20 @@ public void testToOneBigDecimalField() throws ParseException, InvocationTargetEx CandidateWorkHistory candidateWorkHistory = new CandidateWorkHistory(); Candidate candidate = new Candidate(); - Assert.assertEquals(candidateWorkHistory.getCandidate(), null); + Assert.assertNull(candidateWorkHistory.getCandidate()); + Assert.assertEquals(field.getStringValueFromEntity(candidateWorkHistory, ";"), ""); + Assert.assertEquals(field.getStringValueFromEntity(candidate, ";"), ""); field.populateAssociationOnEntity(candidateWorkHistory, candidate); Assert.assertEquals(candidateWorkHistory.getCandidate().getSalary().doubleValue(), 123.45, 0.1); - BigDecimal valueFromEntity = (BigDecimal) field.getValueFromEntity(candidate); - Assert.assertEquals(valueFromEntity.doubleValue(), 123.45, 0.1); + Assert.assertEquals(field.getStringValueFromEntity(candidateWorkHistory, ";"), "123.45"); + Assert.assertEquals(field.getStringValueFromEntity(candidate, ";"), "123.45"); } @Test - public void testToManyIntegerField() throws ParseException, InvocationTargetException, IllegalAccessException { - Cell cell = new Cell("candidates.id", "1"); + public void testToManyIntegerField() throws Exception { + Cell cell = new Cell("candidates.id", "101"); Field field = new Field(EntityInfo.NOTE, cell, false, dateTimeFormatter); Assert.assertEquals(field.getEntityInfo(), EntityInfo.NOTE); @@ -171,25 +183,43 @@ public void testToManyIntegerField() throws ParseException, InvocationTargetExce Assert.assertEquals(field.isToOne(), false); Assert.assertEquals(field.isToMany(), true); Assert.assertEquals(field.getName(), "id"); + Assert.assertEquals(field.getFieldParameterName(), "candidates(id)"); Assert.assertEquals(field.getFieldEntity(), EntityInfo.CANDIDATE); Assert.assertEquals(field.getFieldType(), Integer.class); - Assert.assertEquals(field.getValue(), 1); - Assert.assertEquals(field.getStringValue(), "1"); + Assert.assertEquals(field.getValue(), 101); + Assert.assertEquals(field.getStringValue(), "101"); Note note = new Note(); - Candidate candidate = new Candidate(); - - Assert.assertEquals(candidate.getId(), null); - Assert.assertEquals(note.getCandidates(), null); - - field.populateAssociationOnEntity(note, candidate); - - Assert.assertEquals(note.getCandidates().getData().get(0).getId(), new Integer(1)); - Assert.assertEquals(field.getValueFromEntity(candidate), 1); + Candidate candidate1 = new Candidate(); + Candidate candidate2 = new Candidate(); + Candidate candidate3 = new Candidate(); + + Assert.assertNull(candidate1.getId()); + Assert.assertNull(candidate2.getId()); + Assert.assertNull(candidate3.getId()); + Assert.assertNull(note.getCandidates()); + Assert.assertEquals(field.getStringValueFromEntity(note, ";"), ""); + Assert.assertEquals(field.getStringValueFromEntity(candidate1, ";"), ""); + Assert.assertEquals(field.getStringValueFromEntity(candidate2, ";"), ""); + Assert.assertEquals(field.getStringValueFromEntity(candidate3, ";"), ""); + + field.populateAssociationOnEntity(note, candidate1); + field.populateAssociationOnEntity(note, candidate2); + field.populateAssociationOnEntity(note, candidate3); + + Assert.assertEquals(note.getCandidates().getData().get(0).getId(), new Integer(101)); + Assert.assertEquals(note.getCandidates().getData().get(1).getId(), new Integer(101)); + Assert.assertEquals(note.getCandidates().getData().get(2).getId(), new Integer(101)); + + // The single value in the field will be set for each populated To-Many object + Assert.assertEquals(field.getStringValueFromEntity(note, ";"), "101;101;101"); + Assert.assertEquals(field.getStringValueFromEntity(candidate1, ";"), "101"); + Assert.assertEquals(field.getStringValueFromEntity(candidate2, ";"), "101"); + Assert.assertEquals(field.getStringValueFromEntity(candidate3, ";"), "101"); } @Test - public void testToManyBooleanField() throws ParseException, InvocationTargetException, IllegalAccessException { + public void testToManyBooleanField() throws Exception { Cell cell = new Cell("clientContacts.isDeleted", "1"); Field field = new Field(EntityInfo.NOTE, cell, true, dateTimeFormatter); @@ -198,25 +228,42 @@ public void testToManyBooleanField() throws ParseException, InvocationTargetExce Assert.assertEquals(field.isToOne(), false); Assert.assertEquals(field.isToMany(), true); Assert.assertEquals(field.getName(), "isDeleted"); + Assert.assertEquals(field.getFieldParameterName(), "clientContacts(isDeleted)"); Assert.assertEquals(field.getFieldEntity(), EntityInfo.CLIENT_CONTACT); Assert.assertEquals(field.getFieldType(), Boolean.class); Assert.assertEquals(field.getValue(), true); Assert.assertEquals(field.getStringValue(), "1"); Note note = new Note(); - ClientContact clientContact = new ClientContact(); - - Assert.assertEquals(clientContact.getIsDeleted(), null); - Assert.assertEquals(note.getCandidates(), null); - - field.populateAssociationOnEntity(note, clientContact); - - Assert.assertEquals(note.getClientContacts().getData().get(0).getIsDeleted(), true); - Assert.assertEquals(field.getValueFromEntity(clientContact), true); + ClientContact contact1 = new ClientContact(); + ClientContact contact2 = new ClientContact(); + ClientContact contact3 = new ClientContact(); + + Assert.assertNull(contact1.getIsDeleted()); + Assert.assertNull(contact2.getIsDeleted()); + Assert.assertNull(contact3.getIsDeleted()); + Assert.assertEquals(field.getStringValueFromEntity(note, ";"), ""); + Assert.assertEquals(field.getStringValueFromEntity(contact1, ";"), ""); + Assert.assertEquals(field.getStringValueFromEntity(contact2, ";"), ""); + Assert.assertEquals(field.getStringValueFromEntity(contact3, ";"), ""); + + field.populateAssociationOnEntity(note, contact1); + field.populateAssociationOnEntity(note, contact2); + field.populateAssociationOnEntity(note, contact3); + + Assert.assertEquals(true, note.getClientContacts().getData().get(0).getIsDeleted()); + Assert.assertEquals(true, note.getClientContacts().getData().get(1).getIsDeleted()); + Assert.assertEquals(true, note.getClientContacts().getData().get(2).getIsDeleted()); + + // The single value in the field will be set for each populated To-Many object + Assert.assertEquals(field.getStringValueFromEntity(note, ";"), "true;true;true"); + Assert.assertEquals(field.getStringValueFromEntity(contact1, ";"), "true"); + Assert.assertEquals(field.getStringValueFromEntity(contact2, ";"), "true"); + Assert.assertEquals(field.getStringValueFromEntity(contact3, ";"), "true"); } @Test - public void testEmptyInteger() throws ParseException, InvocationTargetException, IllegalAccessException { + public void testEmptyInteger() throws ParseException { Cell cell = new Cell("id", ""); Field field = new Field(EntityInfo.CANDIDATE, cell, false, dateTimeFormatter); @@ -226,7 +273,7 @@ public void testEmptyInteger() throws ParseException, InvocationTargetException, } @Test - public void testAddressField() throws ParseException, InvocationTargetException, IllegalAccessException { + public void testAddressField() throws Exception { Cell cell = new Cell("address.address1", "100 Summer St."); Field field = new Field(EntityInfo.CANDIDATE, cell, true, dateTimeFormatter); @@ -235,6 +282,7 @@ public void testAddressField() throws ParseException, InvocationTargetException, Assert.assertEquals(field.isToOne(), false); Assert.assertEquals(field.isToMany(), false); Assert.assertEquals(field.getName(), "address1"); + Assert.assertEquals(field.getFieldParameterName(), "address(address1)"); Assert.assertEquals(field.getFieldEntity(), EntityInfo.ADDRESS); Assert.assertEquals(field.getFieldType(), String.class); Assert.assertEquals(field.getValue(), "100 Summer St."); @@ -242,7 +290,7 @@ public void testAddressField() throws ParseException, InvocationTargetException, Candidate candidate = new Candidate(); - Assert.assertEquals(candidate.getAddress(), null); + Assert.assertNull(candidate.getAddress()); field.populateFieldOnEntity(candidate); @@ -250,7 +298,7 @@ public void testAddressField() throws ParseException, InvocationTargetException, } @Test - public void testAddressFieldCountryId() throws ParseException, InvocationTargetException, IllegalAccessException { + public void testAddressFieldCountryId() throws Exception { Cell cell = new Cell("address.countryId", "1234"); Field field = new Field(EntityInfo.CANDIDATE, cell, true, dateTimeFormatter); @@ -259,6 +307,7 @@ public void testAddressFieldCountryId() throws ParseException, InvocationTargetE Assert.assertEquals(field.isToOne(), false); Assert.assertEquals(field.isToMany(), false); Assert.assertEquals(field.getName(), "countryId"); + Assert.assertEquals(field.getFieldParameterName(), "address(countryId)"); Assert.assertEquals(field.getFieldEntity(), EntityInfo.ADDRESS); Assert.assertEquals(field.getFieldType(), Integer.class); Assert.assertEquals(field.getValue(), 1234); @@ -266,7 +315,7 @@ public void testAddressFieldCountryId() throws ParseException, InvocationTargetE Candidate candidate = new Candidate(); - Assert.assertEquals(candidate.getAddress(), null); + Assert.assertNull(candidate.getAddress()); field.populateFieldOnEntity(candidate); @@ -274,7 +323,7 @@ public void testAddressFieldCountryId() throws ParseException, InvocationTargetE } @Test - public void testMultipleAddressFields() throws ParseException, InvocationTargetException, IllegalAccessException { + public void testMultipleAddressFields() throws Exception { Cell address1Cell = new Cell("address.address1", "100 Summer St."); Cell cityCell = new Cell("address.city", "Boston"); Cell stateCell = new Cell("address.state", "MA"); @@ -291,8 +340,8 @@ public void testMultipleAddressFields() throws ParseException, InvocationTargetE Candidate candidate = new Candidate(); - Assert.assertEquals(candidate.getAddress(), null); - Assert.assertEquals(candidate.getSecondaryAddress(), null); + Assert.assertNull(candidate.getAddress()); + Assert.assertNull(candidate.getSecondaryAddress()); address1.populateFieldOnEntity(candidate); city.populateFieldOnEntity(candidate); @@ -310,7 +359,7 @@ public void testMultipleAddressFields() throws ParseException, InvocationTargetE } @Test - public void testMalformedAddressField() throws ParseException, InvocationTargetException, IllegalAccessException { + public void testMalformedAddressField() { RestApiException expectedException = new RestApiException( "Invalid address field format: 'countryName'. Must use: 'address.countryName' to set an address field."); RestApiException actualException = null; @@ -327,7 +376,7 @@ public void testMalformedAddressField() throws ParseException, InvocationTargetE } @Test - public void testMalformedAddressFieldCaseInsensitive() throws ParseException, InvocationTargetException, IllegalAccessException { + public void testMalformedAddressFieldCaseInsensitive() { RestApiException expectedException = new RestApiException( "Invalid address field format: 'countryname'. Must use: 'address.countryName' to set an address field."); RestApiException actualException = null; @@ -344,7 +393,7 @@ public void testMalformedAddressFieldCaseInsensitive() throws ParseException, In } @Test - public void testDirectCityStateFieldsDoNotThrowException() throws ParseException, InvocationTargetException, IllegalAccessException { + public void testDirectCityStateFieldsDoNotThrowException() throws Exception { Cell cell = new Cell("state", "MO"); Field field = new Field(EntityInfo.CANDIDATE_EDUCATION, cell, false, dateTimeFormatter); @@ -353,6 +402,7 @@ public void testDirectCityStateFieldsDoNotThrowException() throws ParseException Assert.assertEquals(field.isToOne(), false); Assert.assertEquals(field.isToMany(), false); Assert.assertEquals(field.getName(), "state"); + Assert.assertEquals(field.getFieldParameterName(), "state"); Assert.assertEquals(field.getFieldEntity(), EntityInfo.CANDIDATE_EDUCATION); Assert.assertEquals(field.getFieldType(), String.class); Assert.assertEquals(field.getValue(), "MO"); @@ -360,10 +410,20 @@ public void testDirectCityStateFieldsDoNotThrowException() throws ParseException CandidateEducation candidateEducation = new CandidateEducation(); - Assert.assertEquals(candidateEducation.getState(), null); + Assert.assertNull(candidateEducation.getState()); field.populateFieldOnEntity(candidateEducation); Assert.assertEquals(candidateEducation.getState(), "MO"); } + + @Test(expected = IllegalArgumentException.class) + public void testWrongEntityTypeException() throws Exception { + Cell cell = new Cell("firstName", "Jack"); + Field field = new Field(EntityInfo.CANDIDATE, cell, false, dateTimeFormatter); + + ClientContact clientContact = new ClientContact(); + + Assert.assertEquals(field.getStringValueFromEntity(clientContact, ";"), ""); + } } diff --git a/src/test/java/com/bullhorn/dataloader/rest/RecordTest.java b/src/test/java/com/bullhorn/dataloader/rest/RecordTest.java index cd441e19..dc2d49fc 100644 --- a/src/test/java/com/bullhorn/dataloader/rest/RecordTest.java +++ b/src/test/java/com/bullhorn/dataloader/rest/RecordTest.java @@ -5,6 +5,7 @@ import com.bullhorn.dataloader.enums.EntityInfo; import com.bullhorn.dataloader.util.PropertyFileUtil; import com.google.common.collect.Lists; +import com.google.common.collect.Sets; import org.joda.time.format.DateTimeFormat; import org.junit.Assert; import org.junit.Before; @@ -12,6 +13,7 @@ import java.io.IOException; import java.util.Arrays; +import java.util.Set; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -36,11 +38,13 @@ public void testGetters() throws IOException { "ext-1,Data,Loader,Data Loader,dloader@example.com,1;2;3"); Record record = new Record(EntityInfo.CANDIDATE, row, propertyFileUtilMock); + Set expectedParameters = Sets.newHashSet("externalID", "firstName", "lastName", "name", "email", "primarySkills(id)"); Assert.assertEquals(EntityInfo.CANDIDATE, record.getEntityInfo()); Assert.assertEquals(new Integer(1), record.getNumber()); Assert.assertEquals(6, record.getFields().size()); Assert.assertEquals(1, record.getToManyFields().size()); + Assert.assertEquals(expectedParameters, record.getFieldsParameter()); } @Test diff --git a/src/test/java/com/bullhorn/dataloader/rest/RestApiTest.java b/src/test/java/com/bullhorn/dataloader/rest/RestApiTest.java index 45d91728..cd6e1d06 100644 --- a/src/test/java/com/bullhorn/dataloader/rest/RestApiTest.java +++ b/src/test/java/com/bullhorn/dataloader/rest/RestApiTest.java @@ -17,12 +17,12 @@ import com.bullhornsdk.data.model.file.standard.StandardFileMeta; import com.bullhornsdk.data.model.parameter.standard.ParamFactory; import com.bullhornsdk.data.model.response.crud.CrudResponse; +import com.google.common.collect.Sets; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import java.util.Arrays; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -71,10 +71,10 @@ public void testGetMetaData() { public void testSearchForListNoExternalID() throws InstantiationException, IllegalAccessException { when(bullhornDataMock.search(eq(Candidate.class), any(), any(), any())). thenReturn(TestUtils.getListWrapper(Candidate.class, 0, 10, IntStream.rangeClosed(1, 10).toArray())); - restApi.searchForList(Candidate.class, "name:\"Data Loader\"", null, ParamFactory.searchParams()); + restApi.searchForList(Candidate.class, "name:\"Data Loader\"", Sets.newHashSet("*"), ParamFactory.searchParams()); verify(restApiExtensionMock, never()).getByExternalId(any(), any(), any(), any()); - verify(bullhornDataMock, times(1)).search(eq(Candidate.class), eq("name:\"Data Loader\""), eq(null), any()); - verify(printUtilMock, times(1)).log(any(), eq("Find(Candidate Search): name:\"Data Loader\"")); + verify(bullhornDataMock, times(1)).search(eq(Candidate.class), eq("name:\"Data Loader\""), eq(Sets.newHashSet("*")), any()); + verify(printUtilMock, times(1)).log(any(), eq("Find(Candidate Search): name:\"Data Loader\", fields: [*]")); } @Test @@ -90,40 +90,40 @@ public void testSearchForListExternalIdUnsupportedEntity() throws InstantiationE restApi.searchForList(Opportunity.class, "externalID:\"ext 1\"", null, ParamFactory.searchParams()); restApi.searchForList(JobOrder.class, "externalID:\"ext 1\"", null, ParamFactory.searchParams()); - verify(bullhornDataMock, times(1)).search(eq(Lead.class), eq("externalID:\"ext 1\""), eq(null), any()); - verify(bullhornDataMock, times(1)).search(eq(Opportunity.class), eq("externalID:\"ext 1\""), eq(null), any()); - verify(bullhornDataMock, times(1)).search(eq(JobOrder.class), eq("externalID:\"ext 1\""), eq(null), any()); + verify(bullhornDataMock, times(1)).search(eq(Lead.class), eq("externalID:\"ext 1\""), eq(Sets.newHashSet("*")), any()); + verify(bullhornDataMock, times(1)).search(eq(Opportunity.class), eq("externalID:\"ext 1\""), eq(Sets.newHashSet("*")), any()); + verify(bullhornDataMock, times(1)).search(eq(JobOrder.class), eq("externalID:\"ext 1\""), eq(Sets.newHashSet("*")), any()); verify(restApiExtensionMock, never()).getByExternalId(any(), any(), any(), any()); - verify(printUtilMock, times(1)).log(any(), eq("Find(Lead Search): externalID:\"ext 1\"")); + verify(printUtilMock, times(1)).log(any(), eq("Find(Lead Search): externalID:\"ext 1\", fields: [*]")); } @Test public void testSearchForListExternalIdSuccess() throws InstantiationException, IllegalAccessException { SearchResult searchResult = new SearchResult<>(); searchResult.setList(TestUtils.getList(Candidate.class, 1)); - when(restApiExtensionMock.getByExternalId(eq(restApi), eq(Candidate.class), eq("ext 1"), eq(null))).thenReturn(searchResult); + when(restApiExtensionMock.getByExternalId(eq(restApi), eq(Candidate.class), eq("ext 1"), eq(Sets.newHashSet("id", "name")))).thenReturn(searchResult); - restApi.searchForList(Candidate.class, "externalID:\"ext 1\"", null, ParamFactory.searchParams()); + restApi.searchForList(Candidate.class, "externalID:\"ext 1\"", Sets.newHashSet("id", "name"), ParamFactory.searchParams()); - verify(restApiExtensionMock, times(1)).getByExternalId(eq(restApi), eq(Candidate.class), eq("ext 1"), eq(null)); + verify(restApiExtensionMock, times(1)).getByExternalId(eq(restApi), eq(Candidate.class), eq("ext 1"), eq(Sets.newHashSet("id", "name"))); verify(bullhornDataMock, never()).searchForList(any(), any(), any(), any()); - verify(printUtilMock, times(1)).log(any(), eq("Find(Candidate Search): externalID:\"ext 1\"")); + verify(printUtilMock, times(1)).log(any(), eq("Find(Candidate Search): externalID:\"ext 1\", fields: [id, name]")); } @Test public void testSearchForListExternalIdFailure() throws InstantiationException, IllegalAccessException { SearchResult searchResult = new SearchResult<>(); searchResult.setSuccess(false); - when(restApiExtensionMock.getByExternalId(eq(restApi), eq(Candidate.class), eq("ext 1"), eq(null))) + when(restApiExtensionMock.getByExternalId(eq(restApi), eq(Candidate.class), eq("ext 1"), any())) .thenReturn(searchResult); when(bullhornDataMock.search(eq(Candidate.class), any(), any(), any())). thenReturn(TestUtils.getListWrapper(Candidate.class, 0, 10, IntStream.rangeClosed(1, 10).toArray())); - restApi.searchForList(Candidate.class, "externalID:\"ext 1\"", null, ParamFactory.searchParams()); + restApi.searchForList(Candidate.class, "externalID:\"ext 1\"", Sets.newHashSet("lastName", "firstName", "email"), ParamFactory.searchParams()); - verify(restApiExtensionMock, times(1)).getByExternalId(eq(restApi), eq(Candidate.class), eq("ext 1"), eq(null)); - verify(bullhornDataMock, times(1)).search(eq(Candidate.class), eq("externalID:\"ext 1\""), eq(null), any()); - verify(printUtilMock, times(1)).log(any(), eq("Find(Candidate Search): externalID:\"ext 1\"")); + verify(restApiExtensionMock, times(1)).getByExternalId(eq(restApi), eq(Candidate.class), eq("ext 1"), eq(Sets.newHashSet("lastName", "firstName", "email", "id"))); + verify(bullhornDataMock, times(1)).search(eq(Candidate.class), eq("externalID:\"ext 1\""), eq(Sets.newHashSet("lastName", "firstName", "email", "id")), any()); + verify(printUtilMock, times(1)).log(any(), eq("Find(Candidate Search): externalID:\"ext 1\", fields: [email, firstName, id, lastName]")); } @Test @@ -132,8 +132,8 @@ public void testSearchForListMultipleCalls() throws InstantiationException, Ille TestUtils.getListWrapper(ClientContact.class, 0, 600, IntStream.rangeClosed(1, 500).toArray()), TestUtils.getListWrapper(ClientContact.class, 500, 600, IntStream.rangeClosed(501, 600).toArray())); List list = restApi.searchForList(ClientContact.class, "name='Data Loader'", null, ParamFactory.searchParams()); - verify(bullhornDataMock, times(2)).search(eq(ClientContact.class), eq("name='Data Loader'"), eq(null), any()); - verify(printUtilMock, times(1)).log(any(), eq("Find(ClientContact Search): name='Data Loader'")); + verify(bullhornDataMock, times(2)).search(eq(ClientContact.class), eq("name='Data Loader'"), eq(Sets.newHashSet("*")), any()); + verify(printUtilMock, times(1)).log(any(), eq("Find(ClientContact Search): name='Data Loader', fields: [*]")); verify(printUtilMock, times(1)).log(any(), eq("--> Follow On Find(500 - 600)")); Assert.assertEquals(600, list.size()); } @@ -145,8 +145,8 @@ public void testSearchForListMultipleCallsPartialReturn() throws InstantiationEx TestUtils.getListWrapper(ClientContact.class, 200, 600, IntStream.rangeClosed(201, 400).toArray()), TestUtils.getListWrapper(ClientContact.class, 400, 600, IntStream.rangeClosed(401, 600).toArray())); List list = restApi.searchForList(ClientContact.class, "name='Data Loader'", null, ParamFactory.searchParams()); - verify(bullhornDataMock, times(3)).search(eq(ClientContact.class), eq("name='Data Loader'"), eq(null), any()); - verify(printUtilMock, times(1)).log(any(), eq("Find(ClientContact Search): name='Data Loader'")); + verify(bullhornDataMock, times(3)).search(eq(ClientContact.class), eq("name='Data Loader'"), eq(Sets.newHashSet("*")), any()); + verify(printUtilMock, times(1)).log(any(), eq("Find(ClientContact Search): name='Data Loader', fields: [*]")); verify(printUtilMock, times(1)).log(any(), eq("--> Follow On Find(200 - 600)")); verify(printUtilMock, times(1)).log(any(), eq("--> Follow On Find(400 - 600)")); Assert.assertEquals(600, list.size()); @@ -159,8 +159,8 @@ public void testSearchForListMultipleCallsEmptyReturn() throws InstantiationExce TestUtils.getListWrapper(ClientContact.class, 200, 600, IntStream.rangeClosed(201, 400).toArray()), TestUtils.getListWrapper(ClientContact.class, 400, 600)); List list = restApi.searchForList(ClientContact.class, "name='Data Loader'", null, ParamFactory.searchParams()); - verify(bullhornDataMock, times(3)).search(eq(ClientContact.class), eq("name='Data Loader'"), eq(null), any()); - verify(printUtilMock, times(1)).log(any(), eq("Find(ClientContact Search): name='Data Loader'")); + verify(bullhornDataMock, times(3)).search(eq(ClientContact.class), eq("name='Data Loader'"), eq(Sets.newHashSet("*")), any()); + verify(printUtilMock, times(1)).log(any(), eq("Find(ClientContact Search): name='Data Loader', fields: [*]")); verify(printUtilMock, times(1)).log(any(), eq("--> Follow On Find(200 - 600)")); Assert.assertEquals(400, list.size()); } @@ -170,8 +170,8 @@ public void testQueryForList() throws InstantiationException, IllegalAccessExcep when(bullhornDataMock.query(eq(ClientContact.class), any(), any(), any())). thenReturn(TestUtils.getListWrapper(ClientContact.class, 0, 10, IntStream.rangeClosed(1, 10).toArray())); restApi.queryForList(ClientContact.class, "name='Data Loader'", null, ParamFactory.queryParams()); - verify(bullhornDataMock, times(1)).query(eq(ClientContact.class), eq("name='Data Loader'"), eq(null), any()); - verify(printUtilMock, times(1)).log(any(), eq("Find(ClientContact Query): name='Data Loader'")); + verify(bullhornDataMock, times(1)).query(eq(ClientContact.class), eq("name='Data Loader'"), eq(Sets.newHashSet("*")), any()); + verify(printUtilMock, times(1)).log(any(), eq("Find(ClientContact Query): name='Data Loader', fields: [*]")); } @Test @@ -179,8 +179,8 @@ public void testQueryForListNullStartValue() throws InstantiationException, Ille when(bullhornDataMock.query(eq(JobSubmissionHistory.class), any(), any(), any())). thenReturn(TestUtils.getListWrapper(JobSubmissionHistory.class, null, 1, IntStream.rangeClosed(1, 10).toArray())); restApi.queryForList(JobSubmissionHistory.class, "name='Data Loader'", null, ParamFactory.queryParams()); - verify(bullhornDataMock, times(1)).query(eq(JobSubmissionHistory.class), eq("name='Data Loader'"), eq(null), any()); - verify(printUtilMock, times(1)).log(any(), eq("Find(JobSubmissionHistory Query): name='Data Loader'")); + verify(bullhornDataMock, times(1)).query(eq(JobSubmissionHistory.class), eq("name='Data Loader'"), eq(Sets.newHashSet("*")), any()); + verify(printUtilMock, times(1)).log(any(), eq("Find(JobSubmissionHistory Query): name='Data Loader', fields: [*]")); } @Test @@ -188,8 +188,8 @@ public void testQueryForListNullTotalValue() throws InstantiationException, Ille when(bullhornDataMock.query(eq(JobSubmissionHistory.class), any(), any(), any())). thenReturn(TestUtils.getListWrapper(JobSubmissionHistory.class, 0, null, IntStream.rangeClosed(1, 10).toArray())); restApi.queryForList(JobSubmissionHistory.class, "name='Data Loader'", null, ParamFactory.queryParams()); - verify(bullhornDataMock, times(1)).query(eq(JobSubmissionHistory.class), eq("name='Data Loader'"), eq(null), any()); - verify(printUtilMock, times(1)).log(any(), eq("Find(JobSubmissionHistory Query): name='Data Loader'")); + verify(bullhornDataMock, times(1)).query(eq(JobSubmissionHistory.class), eq("name='Data Loader'"), eq(Sets.newHashSet("*")), any()); + verify(printUtilMock, times(1)).log(any(), eq("Find(JobSubmissionHistory Query): name='Data Loader', fields: [*]")); } @Test @@ -197,8 +197,8 @@ public void testQueryForListNullStartAndTotalValue() throws InstantiationExcepti when(bullhornDataMock.query(eq(JobSubmissionHistory.class), any(), any(), any())). thenReturn(TestUtils.getListWrapper(JobSubmissionHistory.class, null, null, IntStream.rangeClosed(1, 10).toArray())); restApi.queryForList(JobSubmissionHistory.class, "name='Data Loader'", null, ParamFactory.queryParams()); - verify(bullhornDataMock, times(1)).query(eq(JobSubmissionHistory.class), eq("name='Data Loader'"), eq(null), any()); - verify(printUtilMock, times(1)).log(any(), eq("Find(JobSubmissionHistory Query): name='Data Loader'")); + verify(bullhornDataMock, times(1)).query(eq(JobSubmissionHistory.class), eq("name='Data Loader'"), eq(Sets.newHashSet("*")), any()); + verify(printUtilMock, times(1)).log(any(), eq("Find(JobSubmissionHistory Query): name='Data Loader', fields: [*]")); } @Test @@ -207,8 +207,8 @@ public void testQueryForListMultipleCalls() throws InstantiationException, Illeg TestUtils.getListWrapper(ClientContact.class, 0, 600, IntStream.rangeClosed(1, 500).toArray()), TestUtils.getListWrapper(ClientContact.class, 500, 600, IntStream.rangeClosed(501, 600).toArray())); List list = restApi.queryForList(ClientContact.class, "name='Data Loader'", null, ParamFactory.queryParams()); - verify(bullhornDataMock, times(2)).query(eq(ClientContact.class), eq("name='Data Loader'"), eq(null), any()); - verify(printUtilMock, times(1)).log(any(), eq("Find(ClientContact Query): name='Data Loader'")); + verify(bullhornDataMock, times(2)).query(eq(ClientContact.class), eq("name='Data Loader'"), eq(Sets.newHashSet("*")), any()); + verify(printUtilMock, times(1)).log(any(), eq("Find(ClientContact Query): name='Data Loader', fields: [*]")); Assert.assertEquals(600, list.size()); } @@ -273,8 +273,8 @@ public void testQueryForListMaximumReturnSize() throws InstantiationException, I List list = restApi.queryForList(ClientContact.class, "name='Data Loader'", null, ParamFactory.queryParams()); - verify(bullhornDataMock, times(40)).query(eq(ClientContact.class), eq("name='Data Loader'"), eq(null), any()); - verify(printUtilMock, times(1)).log(any(), eq("Find(ClientContact Query): name='Data Loader'")); + verify(bullhornDataMock, times(40)).query(eq(ClientContact.class), eq("name='Data Loader'"), eq(Sets.newHashSet("*")), any()); + verify(printUtilMock, times(1)).log(any(), eq("Find(ClientContact Query): name='Data Loader', fields: [*]")); verify(printUtilMock, never()).log(any(), eq("--> Follow On Find(0 - 500)")); verify(printUtilMock, times(1)).log(any(), eq("--> Follow On Find(500 - 1000)")); verify(printUtilMock, times(1)).log(any(), eq("--> Follow On Find(1000 - 1500)")); @@ -318,13 +318,14 @@ public void testDeleteEntity() { @Test public void testGetAllAssociationsList() { Set entityIDs = new HashSet<>(Arrays.asList(1, 2, 3)); - Set fields = new HashSet<>(Collections.singletonList("primarySkills")); + Set fields = new HashSet<>(Arrays.asList("id", "name")); restApi.getAllAssociationsList(Candidate.class, entityIDs, CandidateAssociations.getInstance().primarySkills(), fields, ParamFactory.associationParams()); verify(bullhornDataMock, times(1)).getAllAssociations(eq(Candidate.class), eq(entityIDs), eq(CandidateAssociations.getInstance().primarySkills()), eq(fields), any()); + verify(printUtilMock, times(1)).log(any(), eq("FindAssociations(Candidate): #[1, 2, 3] - primarySkills, fields: [id, name]")); } @Test diff --git a/src/test/java/com/bullhorn/dataloader/service/ActionFactoryTest.java b/src/test/java/com/bullhorn/dataloader/service/ActionFactoryTest.java index 0733757a..e5abb69d 100644 --- a/src/test/java/com/bullhorn/dataloader/service/ActionFactoryTest.java +++ b/src/test/java/com/bullhorn/dataloader/service/ActionFactoryTest.java @@ -21,7 +21,7 @@ public class ActionFactoryTest { private ActionFactory actionFactory; @Before - public void setup() throws Exception { + public void setup() { CompleteUtil completeUtilMock = mock(CompleteUtil.class); InputStream inputStreamMock = mock(InputStream.class); PrintUtil printUtilMock = mock(PrintUtil.class); @@ -35,65 +35,58 @@ public void setup() throws Exception { } @Test - public void getAction_HELP() throws Exception { + public void getActionHelp() throws Exception { Class expectedResult = HelpService.class; - Action actualResult = actionFactory.getAction(Command.HELP); - Assert.assertThat(actualResult.getClass(), new ReflectionEquals(expectedResult)); } @Test - public void getAction_TEMPLATE() throws Exception { + public void getActionTemplate() throws Exception { Class expectedResult = TemplateService.class; - Action actualResult = actionFactory.getAction(Command.TEMPLATE); - Assert.assertThat(actualResult.getClass(), new ReflectionEquals(expectedResult)); } @Test - public void getAction_CONVERT_ATTACHMENTS() throws Exception { + public void getActionConvertAttachments() throws Exception { Class expectedResult = ConvertAttachmentsService.class; - Action actualResult = actionFactory.getAction(Command.CONVERT_ATTACHMENTS); - Assert.assertThat(actualResult.getClass(), new ReflectionEquals(expectedResult)); } @Test - public void getAction_LOAD() throws Exception { + public void getActionLoad() throws Exception { Class expectedResult = LoadService.class; - Action actualResult = actionFactory.getAction(Command.LOAD); + Assert.assertThat(actualResult.getClass(), new ReflectionEquals(expectedResult)); + } + @Test + public void getActionExport() throws Exception { + Class expectedResult = ExportService.class; + Action actualResult = actionFactory.getAction(Command.EXPORT); Assert.assertThat(actualResult.getClass(), new ReflectionEquals(expectedResult)); } @Test - public void getAction_DELETE() throws Exception { + public void getActionDelete() throws Exception { Class expectedResult = DeleteService.class; - Action actualResult = actionFactory.getAction(Command.DELETE); - Assert.assertThat(actualResult.getClass(), new ReflectionEquals(expectedResult)); } @Test - public void getAction_LOAD_ATTACHMENTS() throws Exception { + public void getActionLoadAttachments() throws Exception { Class expectedResult = LoadAttachmentsService.class; - Action actualResult = actionFactory.getAction(Command.LOAD_ATTACHMENTS); - Assert.assertThat(actualResult.getClass(), new ReflectionEquals(expectedResult)); } @Test - public void getAction_DELETE_ATTACHMENTS() throws Exception { + public void getActionDeleteAttachments() throws Exception { Class expectedResult = DeleteAttachmentsService.class; - Action actualResult = actionFactory.getAction(Command.DELETE_ATTACHMENTS); - Assert.assertThat(actualResult.getClass(), new ReflectionEquals(expectedResult)); } } diff --git a/src/test/java/com/bullhorn/dataloader/service/ConvertAttachmentsServiceTest.java b/src/test/java/com/bullhorn/dataloader/service/ConvertAttachmentsServiceTest.java index 34893a23..04d0482a 100644 --- a/src/test/java/com/bullhorn/dataloader/service/ConvertAttachmentsServiceTest.java +++ b/src/test/java/com/bullhorn/dataloader/service/ConvertAttachmentsServiceTest.java @@ -48,7 +48,7 @@ public void setup() throws Exception { convertAttachmentsService = new ConvertAttachmentsService(printUtilMock, propertyFileUtilMock, validationUtil, completeUtilMock, restSessionMock, processRunnerMock, inputStreamMock, timerMock); - doReturn(actionTotalsMock).when(processRunnerMock).runConvertAttachmentsProcess(any(), any()); + doReturn(actionTotalsMock).when(processRunnerMock).run(any(), any(), any()); } @Test @@ -58,7 +58,7 @@ public void testRunSuccess() throws Exception { convertAttachmentsService.run(testArgs); - verify(processRunnerMock, times(1)).runConvertAttachmentsProcess(EntityInfo.CANDIDATE, filePath); + verify(processRunnerMock, times(1)).run(Command.CONVERT_ATTACHMENTS, EntityInfo.CANDIDATE, filePath); verify(completeUtilMock, times(1)).complete(Command.CONVERT_ATTACHMENTS, filePath, EntityInfo.CANDIDATE, actionTotalsMock); verify(printUtilMock, times(2)).printAndLog(anyString()); verify(printUtilMock, never()).printAndLog((Exception) any()); @@ -111,7 +111,7 @@ public void testIsValidArgumentsMissingArgument() { } @Test - public void testIsValidArgumentsTooManyArgments() { + public void testIsValidArgumentsTooManyArguments() { final String filePath = "Candidate.csv"; final String[] testArgs = {Command.CONVERT_ATTACHMENTS.getMethodName(), filePath, "tooMany"}; diff --git a/src/test/java/com/bullhorn/dataloader/service/DeleteAttachmentsServiceTest.java b/src/test/java/com/bullhorn/dataloader/service/DeleteAttachmentsServiceTest.java index b4516bdc..6a060cf8 100644 --- a/src/test/java/com/bullhorn/dataloader/service/DeleteAttachmentsServiceTest.java +++ b/src/test/java/com/bullhorn/dataloader/service/DeleteAttachmentsServiceTest.java @@ -48,7 +48,7 @@ public void setup() throws IOException, InterruptedException { deleteAttachmentsService = new DeleteAttachmentsService(printUtilMock, propertyFileUtilMock, validationUtil, completeUtilMock, restSessionMock, processRunnerMock, inputStreamMock, timerMock); - doReturn(actionTotalsMock).when(processRunnerMock).runDeleteAttachmentsProcess(any(), any()); + doReturn(actionTotalsMock).when(processRunnerMock).run(any(), any(), any()); } @Test @@ -58,7 +58,7 @@ public void testRunSuccess() throws IOException, InterruptedException { deleteAttachmentsService.run(testArgs); - verify(processRunnerMock, times(1)).runDeleteAttachmentsProcess(EntityInfo.CANDIDATE, filePath); + verify(processRunnerMock, times(1)).run(Command.DELETE_ATTACHMENTS, EntityInfo.CANDIDATE, filePath); verify(completeUtilMock, times(1)).complete(Command.DELETE_ATTACHMENTS, filePath, EntityInfo.CANDIDATE, actionTotalsMock); verify(printUtilMock, times(2)).printAndLog(anyString()); } @@ -109,7 +109,7 @@ public void testIsValidArgumentsMissingArgument() { } @Test - public void testIsValidArgumentsTooManyArgments() { + public void testIsValidArgumentsTooManyArguments() { final String filePath = "Candidate.csv"; final String[] testArgs = {Command.DELETE_ATTACHMENTS.getMethodName(), filePath, "tooMany"}; diff --git a/src/test/java/com/bullhorn/dataloader/service/DeleteServiceTest.java b/src/test/java/com/bullhorn/dataloader/service/DeleteServiceTest.java index 7914ee3d..55e745df 100644 --- a/src/test/java/com/bullhorn/dataloader/service/DeleteServiceTest.java +++ b/src/test/java/com/bullhorn/dataloader/service/DeleteServiceTest.java @@ -50,7 +50,7 @@ public void setup() throws IOException, InterruptedException { deleteService = new DeleteService(printUtilMock, propertyFileUtilMock, validationUtil, completeUtilMock, restSessionMock, processRunnerMock, inputStreamFake, timerMock); - doReturn(actionTotalsMock).when(processRunnerMock).runDeleteProcess(any(), any()); + doReturn(actionTotalsMock).when(processRunnerMock).run(any(), any(), any()); } @Test @@ -60,7 +60,7 @@ public void testRunFile() throws IOException, InterruptedException { deleteService.run(testArgs); - verify(processRunnerMock, times(1)).runDeleteProcess(EntityInfo.CANDIDATE, filePath); + verify(processRunnerMock, times(1)).run(Command.DELETE, EntityInfo.CANDIDATE, filePath); verify(printUtilMock, times(2)).printAndLog(anyString()); verify(completeUtilMock, times(1)).complete(Command.DELETE, filePath, EntityInfo.CANDIDATE, actionTotalsMock); } @@ -74,7 +74,7 @@ public void testRunDirectoryOneFile() throws IOException, InterruptedException { deleteService.run(testArgs); - verify(processRunnerMock, times(1)).runDeleteProcess(EntityInfo.CLIENT_CONTACT, expectedFileName); + verify(processRunnerMock, times(1)).run(Command.DELETE, EntityInfo.CLIENT_CONTACT, expectedFileName); verify(printUtilMock, times(2)).printAndLog(anyString()); } @@ -89,8 +89,8 @@ public void testRunDirectoryFourFiles() throws IOException, InterruptedException deleteService.run(testArgs); - verify(processRunnerMock, times(1)).runDeleteProcess(EntityInfo.CANDIDATE, expectedCandidateFileName); - verify(processRunnerMock, times(1)).runDeleteProcess(EntityInfo.CANDIDATE_WORK_HISTORY, expectedCandidateWorkHistoryFileName); + verify(processRunnerMock, times(1)).run(Command.DELETE, EntityInfo.CANDIDATE, expectedCandidateFileName); + verify(processRunnerMock, times(1)).run(Command.DELETE, EntityInfo.CANDIDATE_WORK_HISTORY, expectedCandidateWorkHistoryFileName); verify(printUtilMock, times(7)).printAndLog(anyString()); } @@ -133,7 +133,7 @@ public void testIsValidArgumentsMissingArgument() { } @Test - public void testIsValidArgumentsTooManyArgments() { + public void testIsValidArgumentsTooManyArguments() { final String filePath = "Candidate.csv"; final String[] testArgs = {Command.DELETE.getMethodName(), filePath, "tooMany"}; diff --git a/src/test/java/com/bullhorn/dataloader/service/ExportServiceTest.java b/src/test/java/com/bullhorn/dataloader/service/ExportServiceTest.java new file mode 100644 index 00000000..8b638127 --- /dev/null +++ b/src/test/java/com/bullhorn/dataloader/service/ExportServiceTest.java @@ -0,0 +1,243 @@ +package com.bullhorn.dataloader.service; + +import com.bullhorn.dataloader.TestUtils; +import com.bullhorn.dataloader.data.ActionTotals; +import com.bullhorn.dataloader.enums.Command; +import com.bullhorn.dataloader.enums.EntityInfo; +import com.bullhorn.dataloader.rest.CompleteUtil; +import com.bullhorn.dataloader.rest.RestSession; +import com.bullhorn.dataloader.util.PrintUtil; +import com.bullhorn.dataloader.util.PropertyFileUtil; +import com.bullhorn.dataloader.util.Timer; +import com.bullhorn.dataloader.util.ValidationUtil; +import org.apache.commons.io.IOUtils; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.io.File; +import java.io.InputStream; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class ExportServiceTest { + + private ActionTotals actionTotalsMock; + private CompleteUtil completeUtilMock; + private RestSession restSessionMock; + private InputStream inputStreamFake; + private ExportService exportService; + private PrintUtil printUtilMock; + private ProcessRunner processRunnerMock; + private PropertyFileUtil propertyFileUtilMock; + private Timer timerMock; + private ValidationUtil validationUtil; + + @Before + public void setup() throws Exception { + actionTotalsMock = mock(ActionTotals.class); + completeUtilMock = mock(CompleteUtil.class); + restSessionMock = mock(RestSession.class); + inputStreamFake = IOUtils.toInputStream("Yes!", "UTF-8"); + printUtilMock = mock(PrintUtil.class); + processRunnerMock = mock(ProcessRunner.class); + propertyFileUtilMock = mock(PropertyFileUtil.class); + timerMock = mock(Timer.class); + validationUtil = new ValidationUtil(printUtilMock); + + exportService = new ExportService(printUtilMock, propertyFileUtilMock, validationUtil, completeUtilMock, restSessionMock, processRunnerMock, inputStreamFake, timerMock); + + when(processRunnerMock.run(any(), any(), any())).thenReturn(actionTotalsMock); + } + + @Test + public void testRunFile() throws Exception { + final String filePath = TestUtils.getResourceFilePath("Candidate_Valid_File.csv"); + final String[] testArgs = {Command.EXPORT.getMethodName(), filePath}; + + exportService.run(testArgs); + + verify(processRunnerMock, times(1)).run(Command.EXPORT, EntityInfo.CANDIDATE, filePath); + verify(printUtilMock, times(2)).printAndLog(anyString()); + verify(completeUtilMock, times(1)).complete(Command.EXPORT, filePath, EntityInfo.CANDIDATE, actionTotalsMock); + } + + @Test + public void testRunDirectoryWithOneFile() throws Exception { + final String directoryPath = TestUtils.getResourceFilePath("loadFromDirectory/ClientContact"); + File file = new File(directoryPath, "ClientContact.csv"); + final String filePath = file.getPath(); + final String[] testArgs = {Command.EXPORT.getMethodName(), directoryPath}; + + exportService.run(testArgs); + + verify(processRunnerMock, times(1)).run(Command.EXPORT, EntityInfo.CLIENT_CONTACT, filePath); + verify(printUtilMock, times(2)).printAndLog(anyString()); + verify(completeUtilMock, times(1)).complete(Command.EXPORT, filePath, EntityInfo.CLIENT_CONTACT, actionTotalsMock); + } + + @Test + public void testRunDirectoryWithFourFilesSameEntity() throws Exception { + final String filePath = TestUtils.getResourceFilePath("loadFromDirectory/opportunity"); + final String[] testArgs = {Command.EXPORT.getMethodName(), filePath}; + + exportService.run(testArgs); + + verify(processRunnerMock, times(4)).run(eq(Command.EXPORT), eq(EntityInfo.OPPORTUNITY), any()); + verify(printUtilMock, times(13)).printAndLog(anyString()); + verify(printUtilMock, times(1)).printAndLog(" 1. Opportunity records from Opportunity1.csv"); + verify(printUtilMock, times(1)).printAndLog(" 2. Opportunity records from Opportunity2.csv"); + verify(printUtilMock, times(1)).printAndLog(" 3. Opportunity records from OpportunityA.csv"); + verify(printUtilMock, times(1)).printAndLog(" 4. Opportunity records from OpportunityB.csv"); + } + + @Test + public void testRunDirectoryFourFiles() throws Exception { + final String filePath = TestUtils.getResourceFilePath("loadFromDirectory"); + final String[] testArgs = {Command.EXPORT.getMethodName(), filePath}; + + exportService.run(testArgs); + + verify(processRunnerMock, times(4)).run(eq(Command.EXPORT), any(), any()); + verify(printUtilMock, times(13)).printAndLog(anyString()); + verify(printUtilMock, times(1)).printAndLog(" 1. ClientCorporation records from ClientCorporation_1.csv"); + verify(printUtilMock, times(1)).printAndLog(" 2. ClientCorporation records from ClientCorporation_2.csv"); + verify(printUtilMock, times(1)).printAndLog(" 3. Candidate records from Candidate_Valid_File.csv"); + verify(printUtilMock, times(1)).printAndLog(" 4. CandidateWorkHistory records from CandidateWorkHistory.csv"); + } + + @Test + public void testRunDirectoryFourFilesContinueNo() throws Exception { + inputStreamFake = IOUtils.toInputStream("No", "UTF-8"); + exportService = new ExportService(printUtilMock, propertyFileUtilMock, validationUtil, completeUtilMock, restSessionMock, processRunnerMock, inputStreamFake, timerMock); + + final String filePath = TestUtils.getResourceFilePath("loadFromDirectory"); + final String[] testArgs = {Command.EXPORT.getMethodName(), filePath}; + + exportService.run(testArgs); + + verify(processRunnerMock, never()).run(any(), any(), any()); + verify(printUtilMock, times(5)).printAndLog(anyString()); + } + + @Test(expected = IllegalStateException.class) + public void testRunInvalidThrowsException() throws Exception { + final String[] testArgs = {Command.EXPORT.getMethodName()}; + exportService.run(testArgs); + } + + @Test + public void testIsValidArgumentsFile() { + final String filePath = TestUtils.getResourceFilePath("Candidate_Valid_File.csv"); + final String[] testArgs = {Command.EXPORT.getMethodName(), filePath}; + + final boolean actualResult = exportService.isValidArguments(testArgs); + + Assert.assertTrue(actualResult); + verify(printUtilMock, never()).printAndLog(anyString()); + } + + @Test + public void testIsValidArgumentsBadEntity() { + final String filePath = TestUtils.getResourceFilePath("Invalid_Candidate_File.csv"); + final String[] testArgs = {Command.EXPORT.getMethodName(), filePath}; + + final boolean actualResult = exportService.isValidArguments(testArgs); + + Assert.assertFalse(actualResult); + verify(printUtilMock, times(1)).printAndLog(anyString()); + } + + @Test + public void testIsValidArgumentsMissingArgument() { + final String[] testArgs = {Command.EXPORT.getMethodName()}; + + final boolean actualResult = exportService.isValidArguments(testArgs); + + Assert.assertFalse(actualResult); + verify(printUtilMock, times(1)).printAndLog(anyString()); + } + + @Test + public void testIsValidArgumentsTooManyArguments() { + final String filePath = "Candidate.csv"; + final String[] testArgs = {Command.EXPORT.getMethodName(), filePath, "tooMany"}; + + final boolean actualResult = exportService.isValidArguments(testArgs); + + Assert.assertFalse(actualResult); + verify(printUtilMock, times(1)).printAndLog(anyString()); + } + + @Test + public void testIsValidArgumentsInvalidFile() { + final String filePath = "filePath"; + final String[] testArgs = {Command.EXPORT.getMethodName(), filePath}; + + final boolean actualResult = exportService.isValidArguments(testArgs); + + Assert.assertFalse(actualResult); + verify(printUtilMock, times(2)).printAndLog(anyString()); + } + + @Test + public void testIsValidArgumentsEmptyFile() { + final String filePath = ""; + final String[] testArgs = {Command.EXPORT.getMethodName(), filePath}; + + final boolean actualResult = exportService.isValidArguments(testArgs); + + Assert.assertFalse(actualResult); + verify(printUtilMock, times(2)).printAndLog(anyString()); + } + + @Test + public void testIsValidArgumentsReadOnlyEntity() { + final String filePath = TestUtils.getResourceFilePath("BusinessSector.csv"); + final String[] testArgs = {Command.EXPORT.getMethodName(), filePath}; + + final boolean actualResult = exportService.isValidArguments(testArgs); + + Assert.assertTrue(actualResult); + verify(printUtilMock, never()).printAndLog(anyString()); + } + + @Test + public void testIsValidArgumentsDirectory() { + final String filePath = TestUtils.getResourceFilePath("loadFromDirectory"); + final String[] testArgs = {Command.EXPORT.getMethodName(), filePath}; + + final boolean actualResult = exportService.isValidArguments(testArgs); + + Assert.assertTrue(actualResult); + verify(printUtilMock, never()).printAndLog(anyString()); + } + + @Test + public void testIsValidArgumentsNoCsvFiles() { + final String filePath = TestUtils.getResourceFilePath("testResume"); + final String[] testArgs = {Command.EXPORT.getMethodName(), filePath}; + + final boolean actualResult = exportService.isValidArguments(testArgs); + + Assert.assertFalse(actualResult); + verify(printUtilMock, times(1)).printAndLog(anyString()); + } + + @Test + public void testIsValidArgumentsBusinessSectors() { + final String filePath = TestUtils.getResourceFilePath("loadFromDirectory/businessSector"); + final String[] testArgs = {Command.EXPORT.getMethodName(), filePath}; + + final boolean actualResult = exportService.isValidArguments(testArgs); + + Assert.assertTrue(actualResult); + } +} diff --git a/src/test/java/com/bullhorn/dataloader/service/LoadAttachmentsServiceTest.java b/src/test/java/com/bullhorn/dataloader/service/LoadAttachmentsServiceTest.java index e2b83e10..c882edcb 100644 --- a/src/test/java/com/bullhorn/dataloader/service/LoadAttachmentsServiceTest.java +++ b/src/test/java/com/bullhorn/dataloader/service/LoadAttachmentsServiceTest.java @@ -48,7 +48,7 @@ public void setup() throws IOException, InterruptedException { loadAttachmentsService = new LoadAttachmentsService(printUtilMock, propertyFileUtilMock, validationUtil, completeUtilMock, restSessionMock, processRunnerMock, inputStreamMock, timerMock); - doReturn(actionTotalsMock).when(processRunnerMock).runLoadAttachmentsProcess(any(), any()); + doReturn(actionTotalsMock).when(processRunnerMock).run(any(), any(), any()); } @Test @@ -58,7 +58,7 @@ public void testRun() throws IOException, InterruptedException { loadAttachmentsService.run(testArgs); - verify(processRunnerMock, times(1)).runLoadAttachmentsProcess(EntityInfo.CANDIDATE, filePath); + verify(processRunnerMock, times(1)).run(Command.LOAD_ATTACHMENTS, EntityInfo.CANDIDATE, filePath); verify(completeUtilMock, times(1)).complete(Command.LOAD_ATTACHMENTS, filePath, EntityInfo.CANDIDATE, actionTotalsMock); verify(printUtilMock, times(2)).printAndLog(anyString()); } @@ -109,7 +109,7 @@ public void testIsValidArgumentsMissingArgument() { } @Test - public void testIsValidArgumentsTooManyArgments() { + public void testIsValidArgumentsTooManyArguments() { final String filePath = "Candidate.csv"; final String[] testArgs = {Command.LOAD_ATTACHMENTS.getMethodName(), filePath, "tooMany"}; diff --git a/src/test/java/com/bullhorn/dataloader/service/LoadServiceTest.java b/src/test/java/com/bullhorn/dataloader/service/LoadServiceTest.java index d5818c0d..56f5a5e5 100644 --- a/src/test/java/com/bullhorn/dataloader/service/LoadServiceTest.java +++ b/src/test/java/com/bullhorn/dataloader/service/LoadServiceTest.java @@ -55,7 +55,7 @@ public void setup() throws IOException, InterruptedException { loadService = new LoadService(printUtilMock, propertyFileUtilMock, validationUtil, completeUtilMock, restSessionMock, processRunnerMock, inputStreamFake, timerMock); - doReturn(actionTotalsMock).when(processRunnerMock).runLoadProcess(any(), any()); + doReturn(actionTotalsMock).when(processRunnerMock).run(any(), any(), any()); } @Test @@ -65,7 +65,7 @@ public void testRunFile() throws IOException, InterruptedException { loadService.run(testArgs); - verify(processRunnerMock, times(1)).runLoadProcess(EntityInfo.CANDIDATE, filePath); + verify(processRunnerMock, times(1)).run(Command.LOAD, EntityInfo.CANDIDATE, filePath); verify(printUtilMock, times(2)).printAndLog(anyString()); verify(completeUtilMock, times(1)).complete(Command.LOAD, filePath, EntityInfo.CANDIDATE, actionTotalsMock); } @@ -79,7 +79,7 @@ public void testRunDirectoryOneFile() throws IOException, InterruptedException { loadService.run(testArgs); - verify(processRunnerMock, times(1)).runLoadProcess(EntityInfo.CLIENT_CONTACT, filePath); + verify(processRunnerMock, times(1)).run(Command.LOAD, EntityInfo.CLIENT_CONTACT, filePath); verify(printUtilMock, times(2)).printAndLog(anyString()); verify(completeUtilMock, times(1)).complete(Command.LOAD, filePath, EntityInfo.CLIENT_CONTACT, actionTotalsMock); } @@ -94,7 +94,7 @@ public void testRunDirectoryOneFileWithWait() throws IOException, InterruptedExc loadService.run(testArgs); - verify(processRunnerMock, times(1)).runLoadProcess(EntityInfo.CLIENT_CONTACT, filePath); + verify(processRunnerMock, times(1)).run(Command.LOAD, EntityInfo.CLIENT_CONTACT, filePath); verify(printUtilMock, times(3)).printAndLog(anyString()); verify(printUtilMock, times(1)).printAndLog("...Waiting 2 seconds for indexers to catch up..."); verify(completeUtilMock, times(1)).complete(Command.LOAD, filePath, EntityInfo.CLIENT_CONTACT, actionTotalsMock); @@ -107,7 +107,7 @@ public void testRunDirectoryFourFilesSameEntity() throws IOException, Interrupte loadService.run(testArgs); - verify(processRunnerMock, times(4)).runLoadProcess(eq(EntityInfo.OPPORTUNITY), any()); + verify(processRunnerMock, times(4)).run(eq(Command.LOAD), eq(EntityInfo.OPPORTUNITY), any()); verify(printUtilMock, times(13)).printAndLog(anyString()); verify(printUtilMock, times(1)).printAndLog(" 1. Opportunity records from Opportunity1.csv"); verify(printUtilMock, times(1)).printAndLog(" 2. Opportunity records from Opportunity2.csv"); @@ -122,7 +122,7 @@ public void testRunDirectoryFourFiles() throws IOException, InterruptedException loadService.run(testArgs); - verify(processRunnerMock, times(4)).runLoadProcess(any(), any()); + verify(processRunnerMock, times(4)).run(eq(Command.LOAD), any(), any()); verify(printUtilMock, times(13)).printAndLog(anyString()); verify(printUtilMock, times(1)).printAndLog(" 1. ClientCorporation records from ClientCorporation_1.csv"); verify(printUtilMock, times(1)).printAndLog(" 2. ClientCorporation records from ClientCorporation_2.csv"); @@ -140,7 +140,7 @@ public void testRunDirectoryFourFilesContinueNo() throws IOException, Interrupte loadService.run(testArgs); - verify(processRunnerMock, never()).runLoadProcess(any(), any()); + verify(processRunnerMock, never()).run(any(), any(), any()); verify(printUtilMock, times(5)).printAndLog(anyString()); } @@ -183,7 +183,7 @@ public void testIsValidArgumentsMissingArgument() { } @Test - public void testIsValidArgumentsTooManyArgments() { + public void testIsValidArgumentsTooManyArguments() { final String filePath = "Candidate.csv"; final String[] testArgs = {Command.LOAD.getMethodName(), filePath, "tooMany"}; diff --git a/src/test/java/com/bullhorn/dataloader/service/ProcessRunnerTest.java b/src/test/java/com/bullhorn/dataloader/service/ProcessRunnerTest.java index abcea4ae..62719738 100644 --- a/src/test/java/com/bullhorn/dataloader/service/ProcessRunnerTest.java +++ b/src/test/java/com/bullhorn/dataloader/service/ProcessRunnerTest.java @@ -12,6 +12,7 @@ import com.bullhorn.dataloader.task.ConvertAttachmentTask; import com.bullhorn.dataloader.task.DeleteAttachmentTask; import com.bullhorn.dataloader.task.DeleteTask; +import com.bullhorn.dataloader.task.ExportTask; import com.bullhorn.dataloader.task.LoadAttachmentTask; import com.bullhorn.dataloader.task.LoadTask; import com.bullhorn.dataloader.util.PrintUtil; @@ -68,41 +69,41 @@ public void setup() throws InterruptedException { } @Test - public void runLoadProcessTest() throws IOException, InterruptedException { - String filePath = TestUtils.getResourceFilePath("Candidate.csv"); + public void testRunConvertAttachments() throws IOException, InterruptedException { + String filePath = TestUtils.getResourceFilePath("CandidateAttachments.csv"); ArgumentCaptor taskCaptor = ArgumentCaptor.forClass(AbstractTask.class); - ActionTotals actualTotals = processRunner.runLoadProcess(EntityInfo.CANDIDATE, filePath); + ActionTotals actualTotals = processRunner.run(Command.CONVERT_ATTACHMENTS, EntityInfo.CANDIDATE, filePath); verify(executorServiceMock, times(1)).execute(any()); verify(executorServiceMock, times(1)).shutdown(); verify(executorServiceMock).execute((Runnable) taskCaptor.capture()); - verify(printUtilMock, times(1)).printActionTotals(eq(Command.LOAD), eq(actualTotals)); + verify(printUtilMock, times(1)).printActionTotals(eq(Command.CONVERT_ATTACHMENTS), eq(actualTotals)); AbstractTask actualTask = (AbstractTask) taskCaptor.getValue(); - Assert.assertEquals(actualTask.getClass(), LoadTask.class); + Assert.assertEquals(actualTask.getClass(), ConvertAttachmentTask.class); } @Test - public void runCustomObjectLoadProcessTest() throws IOException, InterruptedException { - String filePath = TestUtils.getResourceFilePath("ClientCorporationCustomObjectInstance1.csv"); + public void testRunDelete() throws IOException, InterruptedException { + String filePath = TestUtils.getResourceFilePath("Candidate.csv"); ArgumentCaptor taskCaptor = ArgumentCaptor.forClass(AbstractTask.class); - ActionTotals actualTotals = processRunner.runLoadProcess(EntityInfo.CLIENT_CORPORATION_CUSTOM_OBJECT_INSTANCE_1, filePath); + ActionTotals actualTotals = processRunner.run(Command.DELETE, EntityInfo.CANDIDATE, filePath); verify(executorServiceMock, times(1)).execute(any()); verify(executorServiceMock, times(1)).shutdown(); verify(executorServiceMock).execute((Runnable) taskCaptor.capture()); - verify(printUtilMock, times(1)).printActionTotals(eq(Command.LOAD), eq(actualTotals)); + verify(printUtilMock, times(1)).printActionTotals(eq(Command.DELETE), eq(actualTotals)); AbstractTask actualTask = (AbstractTask) taskCaptor.getValue(); - Assert.assertEquals(actualTask.getClass(), LoadTask.class); + Assert.assertEquals(actualTask.getClass(), DeleteTask.class); } @Test - public void runDeleteProcessTest() throws IOException, InterruptedException { - String filePath = TestUtils.getResourceFilePath("Candidate.csv"); + public void testRunDeleteCustomObject() throws IOException, InterruptedException { + String filePath = TestUtils.getResourceFilePath("ClientCorporationCustomObjectInstance1.csv"); ArgumentCaptor taskCaptor = ArgumentCaptor.forClass(AbstractTask.class); - ActionTotals actualTotals = processRunner.runDeleteProcess(EntityInfo.CANDIDATE, filePath); + ActionTotals actualTotals = processRunner.run(Command.DELETE, EntityInfo.CLIENT_CORPORATION_CUSTOM_OBJECT_INSTANCE_1, filePath); verify(executorServiceMock, times(1)).execute(any()); verify(executorServiceMock, times(1)).shutdown(); @@ -113,62 +114,77 @@ public void runDeleteProcessTest() throws IOException, InterruptedException { } @Test - public void runDeleteProcessTest_CustomObject() throws IOException, InterruptedException { - String filePath = TestUtils.getResourceFilePath("ClientCorporationCustomObjectInstance1.csv"); + public void testRunDeleteAttachments() throws IOException, InterruptedException { + String filePath = TestUtils.getResourceFilePath("CandidateAttachments_success.csv"); ArgumentCaptor taskCaptor = ArgumentCaptor.forClass(AbstractTask.class); - ActionTotals actualTotals = processRunner.runDeleteProcess(EntityInfo.CLIENT_CORPORATION_CUSTOM_OBJECT_INSTANCE_1, filePath); + ActionTotals actualTotals = processRunner.run(Command.DELETE_ATTACHMENTS, EntityInfo.CANDIDATE, filePath); verify(executorServiceMock, times(1)).execute(any()); verify(executorServiceMock, times(1)).shutdown(); verify(executorServiceMock).execute((Runnable) taskCaptor.capture()); - verify(printUtilMock, times(1)).printActionTotals(eq(Command.DELETE), eq(actualTotals)); + verify(printUtilMock, times(1)).printActionTotals(eq(Command.DELETE_ATTACHMENTS), eq(actualTotals)); AbstractTask actualTask = (AbstractTask) taskCaptor.getValue(); - Assert.assertEquals(actualTask.getClass(), DeleteTask.class); + Assert.assertEquals(actualTask.getClass(), DeleteAttachmentTask.class); } @Test - public void runLoadAttachmentsProcessTest() throws IOException, InterruptedException { - String filePath = TestUtils.getResourceFilePath("CandidateAttachments.csv"); + public void testRunExport() throws IOException, InterruptedException { + String filePath = TestUtils.getResourceFilePath("Candidate.csv"); ArgumentCaptor taskCaptor = ArgumentCaptor.forClass(AbstractTask.class); - ActionTotals actualTotals = processRunner.runLoadAttachmentsProcess(EntityInfo.CANDIDATE, filePath); + ActionTotals actualTotals = processRunner.run(Command.EXPORT, EntityInfo.CANDIDATE, filePath); verify(executorServiceMock, times(1)).execute(any()); verify(executorServiceMock, times(1)).shutdown(); verify(executorServiceMock).execute((Runnable) taskCaptor.capture()); - verify(printUtilMock, times(1)).printActionTotals(eq(Command.LOAD_ATTACHMENTS), eq(actualTotals)); + verify(printUtilMock, times(1)).printActionTotals(eq(Command.EXPORT), eq(actualTotals)); AbstractTask actualTask = (AbstractTask) taskCaptor.getValue(); - Assert.assertEquals(actualTask.getClass(), LoadAttachmentTask.class); + Assert.assertEquals(actualTask.getClass(), ExportTask.class); } @Test - public void runConvertAttachmentsProcessTest() throws IOException, InterruptedException { - String filePath = TestUtils.getResourceFilePath("CandidateAttachments.csv"); + public void testRunLoad() throws IOException, InterruptedException { + String filePath = TestUtils.getResourceFilePath("Candidate.csv"); ArgumentCaptor taskCaptor = ArgumentCaptor.forClass(AbstractTask.class); - ActionTotals actualTotals = processRunner.runConvertAttachmentsProcess(EntityInfo.CANDIDATE, filePath); + ActionTotals actualTotals = processRunner.run(Command.LOAD, EntityInfo.CANDIDATE, filePath); verify(executorServiceMock, times(1)).execute(any()); verify(executorServiceMock, times(1)).shutdown(); verify(executorServiceMock).execute((Runnable) taskCaptor.capture()); - verify(printUtilMock, times(1)).printActionTotals(eq(Command.CONVERT_ATTACHMENTS), eq(actualTotals)); + verify(printUtilMock, times(1)).printActionTotals(eq(Command.LOAD), eq(actualTotals)); AbstractTask actualTask = (AbstractTask) taskCaptor.getValue(); - Assert.assertEquals(actualTask.getClass(), ConvertAttachmentTask.class); + Assert.assertEquals(actualTask.getClass(), LoadTask.class); } @Test - public void runDeleteAttachmentsProcessTest() throws IOException, InterruptedException { - String filePath = TestUtils.getResourceFilePath("CandidateAttachments_success.csv"); + public void testRunLoadCustomObject() throws IOException, InterruptedException { + String filePath = TestUtils.getResourceFilePath("ClientCorporationCustomObjectInstance1.csv"); ArgumentCaptor taskCaptor = ArgumentCaptor.forClass(AbstractTask.class); - ActionTotals actualTotals = processRunner.runDeleteAttachmentsProcess(EntityInfo.CANDIDATE, filePath); + ActionTotals actualTotals = processRunner.run(Command.LOAD, EntityInfo.CLIENT_CORPORATION_CUSTOM_OBJECT_INSTANCE_1, filePath); verify(executorServiceMock, times(1)).execute(any()); verify(executorServiceMock, times(1)).shutdown(); verify(executorServiceMock).execute((Runnable) taskCaptor.capture()); - verify(printUtilMock, times(1)).printActionTotals(eq(Command.DELETE_ATTACHMENTS), eq(actualTotals)); + verify(printUtilMock, times(1)).printActionTotals(eq(Command.LOAD), eq(actualTotals)); AbstractTask actualTask = (AbstractTask) taskCaptor.getValue(); - Assert.assertEquals(actualTask.getClass(), DeleteAttachmentTask.class); + Assert.assertEquals(actualTask.getClass(), LoadTask.class); + } + + @Test + public void testRunLoadAttachments() throws IOException, InterruptedException { + String filePath = TestUtils.getResourceFilePath("CandidateAttachments.csv"); + ArgumentCaptor taskCaptor = ArgumentCaptor.forClass(AbstractTask.class); + + ActionTotals actualTotals = processRunner.run(Command.LOAD_ATTACHMENTS, EntityInfo.CANDIDATE, filePath); + + verify(executorServiceMock, times(1)).execute(any()); + verify(executorServiceMock, times(1)).shutdown(); + verify(executorServiceMock).execute((Runnable) taskCaptor.capture()); + verify(printUtilMock, times(1)).printActionTotals(eq(Command.LOAD_ATTACHMENTS), eq(actualTotals)); + AbstractTask actualTask = (AbstractTask) taskCaptor.getValue(); + Assert.assertEquals(actualTask.getClass(), LoadAttachmentTask.class); } } diff --git a/src/test/java/com/bullhorn/dataloader/service/TemplateServiceTest.java b/src/test/java/com/bullhorn/dataloader/service/TemplateServiceTest.java index 972f578b..92aeb04a 100644 --- a/src/test/java/com/bullhorn/dataloader/service/TemplateServiceTest.java +++ b/src/test/java/com/bullhorn/dataloader/service/TemplateServiceTest.java @@ -222,7 +222,7 @@ public void testIsValidArgumentsMissingArgument() { } @Test - public void testIsValidArgumentsTooManyArgments() { + public void testIsValidArgumentsTooManyArguments() { String[] testArgs = {Command.TEMPLATE.getMethodName(), "Candidate", "tooMany"}; boolean actualResult = templateService.isValidArguments(testArgs); diff --git a/src/test/java/com/bullhorn/dataloader/task/ExportTaskTest.java b/src/test/java/com/bullhorn/dataloader/task/ExportTaskTest.java new file mode 100644 index 00000000..d74771e8 --- /dev/null +++ b/src/test/java/com/bullhorn/dataloader/task/ExportTaskTest.java @@ -0,0 +1,189 @@ +package com.bullhorn.dataloader.task; + +import com.bullhorn.dataloader.TestUtils; +import com.bullhorn.dataloader.data.ActionTotals; +import com.bullhorn.dataloader.data.CsvFileWriter; +import com.bullhorn.dataloader.data.Result; +import com.bullhorn.dataloader.data.Row; +import com.bullhorn.dataloader.enums.EntityInfo; +import com.bullhorn.dataloader.rest.CompleteUtil; +import com.bullhorn.dataloader.rest.RestApi; +import com.bullhorn.dataloader.util.PrintUtil; +import com.bullhorn.dataloader.util.PropertyFileUtil; +import com.bullhornsdk.data.model.entity.association.standard.CandidateAssociations; +import com.bullhornsdk.data.model.entity.core.standard.Candidate; +import com.bullhornsdk.data.model.entity.embedded.Address; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +import java.util.Arrays; +import java.util.Collections; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class ExportTaskTest { + + private ActionTotals actionTotalsMock; + private RestApi restApiMock; + private CsvFileWriter csvFileWriterMock; + private PrintUtil printUtilMock; + private PropertyFileUtil propertyFileUtilMock; + private CompleteUtil completeUtilMock; + + @Before + public void setup() { + actionTotalsMock = mock(ActionTotals.class); + restApiMock = mock(RestApi.class); + csvFileWriterMock = mock(CsvFileWriter.class); + printUtilMock = mock(PrintUtil.class); + propertyFileUtilMock = mock(PropertyFileUtil.class); + completeUtilMock = mock(CompleteUtil.class); + + when(propertyFileUtilMock.getListDelimiter()).thenReturn(";"); + } + + @Test + public void testRunSuccessCandidate() throws Exception { + Row row = TestUtils.createRow( + "externalID,customDate1,firstName,lastName,email,primarySkills.name,address.address1,address.countryID,owner.id", + "11,2016-08-30,Data,Loader,dloader@bullhorn.com,hacking skills;ninja skills,test,1,1,"); + ArgumentCaptor rowArgumentCaptor = ArgumentCaptor.forClass(Row.class); + when(propertyFileUtilMock.getEntityExistFields(EntityInfo.CANDIDATE)) + .thenReturn(Collections.singletonList("externalID")); + Candidate fakeCandidate = new Candidate(1); + fakeCandidate.setExternalID("11"); + fakeCandidate.setFirstName("Sir"); + fakeCandidate.setLastName("Lancelot"); + fakeCandidate.setEmail("lancelot@spam.egg"); + Address fakeAddress = new Address(); + fakeAddress.setCountryID(12345); + fakeCandidate.setAddress(fakeAddress); + fakeCandidate.setPrimarySkills(TestUtils.getOneToMany(3, TestUtils.createSkill(1001, "bo staff skills"), + TestUtils.createSkill(1002, "hacking skills"), TestUtils.createSkill(1003, null))); + when(restApiMock.searchForList(eq(Candidate.class), eq("externalID:\"11\""), any(), any())) + .thenReturn(TestUtils.getList(fakeCandidate)); + + ExportTask task = new ExportTask(EntityInfo.CANDIDATE, row, csvFileWriterMock, + propertyFileUtilMock, restApiMock, printUtilMock, actionTotalsMock, completeUtilMock); + task.run(); + + Result expectedResult = new Result(Result.Status.SUCCESS, Result.Action.EXPORT, 1, ""); + verify(csvFileWriterMock, times(1)).writeRow(rowArgumentCaptor.capture(), eq(expectedResult)); + TestUtils.verifyActionTotals(actionTotalsMock, Result.Action.EXPORT, 1); + Row actualRow = rowArgumentCaptor.getValue(); + Assert.assertEquals("path/to/fake/file.csv", actualRow.getFilePath()); + Assert.assertEquals(new Integer(1), actualRow.getNumber()); + Assert.assertEquals(row.getNames(), actualRow.getNames()); + Assert.assertEquals(Arrays.asList("11", "", "Sir", "Lancelot", "lancelot@spam.egg", "bo staff skills;hacking skills", "", "12345", ""), actualRow.getValues()); + } + + @Test + public void testRunSuccessCandidateMoreThanFiveAssociations() throws Exception { + Row row = TestUtils.createRow("externalID,primarySkills.name", "ext-1001,New Skill 1;New Skill 2;New Skill 3"); + ArgumentCaptor rowArgumentCaptor = ArgumentCaptor.forClass(Row.class); + when(propertyFileUtilMock.getEntityExistFields(EntityInfo.CANDIDATE)) + .thenReturn(Collections.singletonList("externalID")); + Candidate fakeCandidate = new Candidate(1); + fakeCandidate.setExternalID("ext-1001"); + fakeCandidate.setPrimarySkills(TestUtils.getOneToMany(7, + TestUtils.createSkill(1001, "skill_1"), + TestUtils.createSkill(1002, "skill_2"), + TestUtils.createSkill(1003, "skill_3"), + TestUtils.createSkill(1004, "skill_4"), + TestUtils.createSkill(1005, "skill_5"))); + when(restApiMock.searchForList(eq(Candidate.class), eq("externalID:\"ext-1001\""), any(), any())) + .thenReturn(TestUtils.getList(fakeCandidate)); + when(restApiMock.getAllAssociationsList(eq(Candidate.class), any(), + eq(CandidateAssociations.getInstance().primarySkills()), any(), any())) + .thenReturn(TestUtils.getList( + TestUtils.createSkill(1001, "skill_1"), + TestUtils.createSkill(1002, "skill_2"), + TestUtils.createSkill(1003, "skill_3"), + TestUtils.createSkill(1004, "skill_4"), + TestUtils.createSkill(1005, "skill_5"), + TestUtils.createSkill(1006, "skill_6"), + TestUtils.createSkill(1007, "skill_7"))); + + ExportTask task = new ExportTask(EntityInfo.CANDIDATE, row, csvFileWriterMock, + propertyFileUtilMock, restApiMock, printUtilMock, actionTotalsMock, completeUtilMock); + task.run(); + + Result expectedResult = new Result(Result.Status.SUCCESS, Result.Action.EXPORT, 1, ""); + verify(csvFileWriterMock, times(1)).writeRow(rowArgumentCaptor.capture(), eq(expectedResult)); + TestUtils.verifyActionTotals(actionTotalsMock, Result.Action.EXPORT, 1); + Row actualRow = rowArgumentCaptor.getValue(); + Assert.assertEquals("path/to/fake/file.csv", actualRow.getFilePath()); + Assert.assertEquals(new Integer(1), actualRow.getNumber()); + Assert.assertEquals(row.getNames(), actualRow.getNames()); + Assert.assertEquals(Arrays.asList("ext-1001", "skill_1;skill_2;skill_3;skill_4;skill_5;skill_6;skill_7"), actualRow.getValues()); + } + + @Test + public void testRunFailureNoExistField() throws Exception { + Row row = TestUtils.createRow( + "externalID,customDate1,firstName,lastName,email,primarySkills.id,address.address1,address.countryID,owner.id", + "11,2016-08-30,Data,Loader,dloader@bullhorn.com,1,test,1,1,"); + + ExportTask task = new ExportTask(EntityInfo.CANDIDATE, row, csvFileWriterMock, + propertyFileUtilMock, restApiMock, printUtilMock, actionTotalsMock, completeUtilMock); + task.run(); + + Result expectedResult = new Result(Result.Status.FAILURE, Result.Action.FAILURE, -1, + "com.bullhornsdk.data.exception.RestApiException: " + + "Cannot perform export because exist field is not specified for entity: Candidate"); + verify(csvFileWriterMock, times(1)).writeRow(any(), eq(expectedResult)); + TestUtils.verifyActionTotals(actionTotalsMock, Result.Action.FAILURE, 1); + } + + @Test + public void testRunFailureRecordNotFound() throws Exception { + Row row = TestUtils.createRow( + "externalID,customDate1,firstName,lastName,email,primarySkills.id,address.address1,address.countryID,owner.id", + "11,2016-08-30,Data,Loader,dloader@bullhorn.com,1,test,1,1,"); + when(propertyFileUtilMock.getEntityExistFields(EntityInfo.CANDIDATE)) + .thenReturn(Arrays.asList("firstName", "lastName", "email")); + when(restApiMock.searchForList(eq(Candidate.class), + eq("firstName:\"Data\" AND lastName:\"Loader\" AND email:\"dloader@bullhorn.com\""), any(), any())) + .thenReturn(TestUtils.getList(Candidate.class)); + + ExportTask task = new ExportTask(EntityInfo.CANDIDATE, row, csvFileWriterMock, + propertyFileUtilMock, restApiMock, printUtilMock, actionTotalsMock, completeUtilMock); + task.run(); + + Result expectedResult = new Result(Result.Status.FAILURE, Result.Action.FAILURE, -1, + "com.bullhornsdk.data.exception.RestApiException: No Matching Candidate Records Exist with ExistField criteria of: " + + "firstName=Data AND lastName=Loader AND email=dloader@bullhorn.com"); + verify(csvFileWriterMock, times(1)).writeRow(any(), eq(expectedResult)); + TestUtils.verifyActionTotals(actionTotalsMock, Result.Action.FAILURE, 1); + } + + @Test + public void testRunFailureTooManyRecordsFound() throws Exception { + Row row = TestUtils.createRow( + "externalID,customDate1,firstName,lastName,email,primarySkills.id,address.address1,address.countryID,owner.id", + "11,2016-08-30,Data,Loader,dloader@bullhorn.com,1,test,1,1,"); + when(propertyFileUtilMock.getEntityExistFields(EntityInfo.CANDIDATE)) + .thenReturn(Arrays.asList("firstName", "lastName", "email")); + when(restApiMock.searchForList(eq(Candidate.class), + eq("firstName:\"Data\" AND lastName:\"Loader\" AND email:\"dloader@bullhorn.com\""), any(), any())) + .thenReturn(TestUtils.getList(Candidate.class, 101, 102)); + + ExportTask task = new ExportTask(EntityInfo.CANDIDATE, row, csvFileWriterMock, + propertyFileUtilMock, restApiMock, printUtilMock, actionTotalsMock, completeUtilMock); + task.run(); + + Result expectedResult = new Result(Result.Status.FAILURE, Result.Action.FAILURE, -1, + "com.bullhornsdk.data.exception.RestApiException: Multiple Records Exist. " + + "Found 2 Candidate records with the same ExistField criteria of: " + + "firstName=Data AND lastName=Loader AND email=dloader@bullhorn.com"); + verify(csvFileWriterMock, times(1)).writeRow(any(), eq(expectedResult)); + TestUtils.verifyActionTotals(actionTotalsMock, Result.Action.FAILURE, 1); + } +} diff --git a/src/test/java/com/bullhorn/dataloader/task/LoadAttachmentTaskTest.java b/src/test/java/com/bullhorn/dataloader/task/LoadAttachmentTaskTest.java index 461b9232..fe89eddd 100644 --- a/src/test/java/com/bullhorn/dataloader/task/LoadAttachmentTaskTest.java +++ b/src/test/java/com/bullhorn/dataloader/task/LoadAttachmentTaskTest.java @@ -174,7 +174,7 @@ public void testRunFailureMultipleParentEntitiesFound() throws Exception { task.run(); Result expectedResult = new Result(Result.Status.FAILURE, Result.Action.FAILURE, -1, - "com.bullhornsdk.data.exception.RestApiException: Cannot Perform Update - Multiple Records Exist. " + "com.bullhornsdk.data.exception.RestApiException: Multiple Records Exist. " + "Found 2 Candidate records with the same ExistField criteria of: externalID=2011Ext AND isDeleted=0"); verify(csvFileWriterMock, times(1)).writeRow(any(), eq(expectedResult)); } diff --git a/src/test/java/com/bullhorn/dataloader/task/LoadTaskTest.java b/src/test/java/com/bullhorn/dataloader/task/LoadTaskTest.java index 832403bb..ef87ce4c 100644 --- a/src/test/java/com/bullhorn/dataloader/task/LoadTaskTest.java +++ b/src/test/java/com/bullhorn/dataloader/task/LoadTaskTest.java @@ -888,7 +888,7 @@ public void testRunMultipleExistingRecords() throws Exception { task.run(); Result expectedResult = new Result(Result.Status.FAILURE, Result.Action.FAILURE, -1, - "com.bullhornsdk.data.exception.RestApiException: Cannot Perform Update - Multiple Records Exist. " + "com.bullhornsdk.data.exception.RestApiException: Multiple Records Exist. " + "Found 2 Candidate records with the same ExistField criteria of: externalID=11"); verify(csvFileWriterMock, times(1)).writeRow(any(), eq(expectedResult)); } @@ -1070,14 +1070,8 @@ public void testRunDuplicateToManyAssociationsExist() throws Exception { Row row = TestUtils.createRow("externalID,primarySkills.name", "11,hacking"); RestApiException restApiException = new RestApiException("Some Duplicate Warning from REST"); when(restApiMock.insertEntity(any())).thenReturn(TestUtils.getResponse(ChangeType.INSERT, 1)); - Skill skill1 = new Skill(); - skill1.setId(1001); - skill1.setName("hacking"); - Skill skill2 = new Skill(); - skill2.setId(1002); - skill2.setName("hacking"); when(restApiMock.queryForList(eq(Skill.class), any(), any(), any())) - .thenReturn(TestUtils.getList(skill1, skill2)); + .thenReturn(TestUtils.getList(TestUtils.createSkill(1001, "hacking"), TestUtils.createSkill(1002, "hacking"))); when(restApiMock.associateWithEntity(eq(Candidate.class), eq(1), eq(CandidateAssociations.getInstance().primarySkills()), any())).thenThrow(restApiException); diff --git a/src/test/java/com/bullhorn/dataloader/util/FindUtilTest.java b/src/test/java/com/bullhorn/dataloader/util/FindUtilTest.java index 31731fbf..1e8c35c0 100644 --- a/src/test/java/com/bullhorn/dataloader/util/FindUtilTest.java +++ b/src/test/java/com/bullhorn/dataloader/util/FindUtilTest.java @@ -1,8 +1,11 @@ package com.bullhorn.dataloader.util; +import com.google.common.collect.Sets; import org.junit.Assert; import org.junit.Test; +import java.util.Set; + public class FindUtilTest { @Test @@ -16,6 +19,28 @@ public void testGetExternalIdValueSuccess() { public void testGetExternalIdValueMissing() { String externalIdValue = FindUtil.getExternalIdValue( "firstName:\"Data\" AND lastName:\"Loader\""); - Assert.assertEquals(externalIdValue, ""); + Assert.assertEquals("", externalIdValue); + } + + @Test + public void testGetCorrectedFieldSet() { + Set correctedFieldSet = FindUtil.getCorrectedFieldSet(Sets.newHashSet("id", "firstName", "lastName")); + Assert.assertEquals(Sets.newHashSet("id", "firstName", "lastName"), correctedFieldSet); + } + + @Test + public void testGetCorrectedFieldSetTooManyFields() { + Set originalFieldSet = Sets.newHashSet(); + for (int i = 0; i < 50; i++) { + originalFieldSet.add("customText" + i); + } + Set correctedFieldSet = FindUtil.getCorrectedFieldSet(originalFieldSet); + Assert.assertEquals(Sets.newHashSet("*"), correctedFieldSet); + } + + @Test + public void testGetCorrectedFieldSetMissingId() { + Set correctedFieldSet = FindUtil.getCorrectedFieldSet(Sets.newHashSet("firstName", "lastName", "email")); + Assert.assertEquals(Sets.newHashSet("id", "firstName", "lastName", "email"), correctedFieldSet); } } diff --git a/src/test/java/com/bullhorn/dataloader/util/PrintUtilTest.java b/src/test/java/com/bullhorn/dataloader/util/PrintUtilTest.java index 7bc41494..706d90dc 100644 --- a/src/test/java/com/bullhorn/dataloader/util/PrintUtilTest.java +++ b/src/test/java/com/bullhorn/dataloader/util/PrintUtilTest.java @@ -35,50 +35,70 @@ public void testPrintUsage() { } @Test - public void testPrintActionTotals() { + public void testPrintActionTotalsConvertAttachments() { final PrintUtil printUtil = spy(PrintUtil.class); ActionTotals totals = new ActionTotals(); - final Integer total = 0; - final String[] args = {"load", "candidate.csv"}; + final int total = 0; + final String[] args = {"convertAttachments", "candidateAttachFile.csv"}; doNothing().when(printUtil).printAndLog(anyString()); printUtil.recordStart(args); - printUtil.printActionTotals(Command.LOAD, totals); + printUtil.printActionTotals(Command.CONVERT_ATTACHMENTS, totals); verify(printUtil, times(1)).printAndLog("Results of DataLoader run"); verify(printUtil, times(1)).printAndLog(Matchers.startsWith("Start time: ")); verify(printUtil, times(1)).printAndLog(Matchers.startsWith("End time: ")); - verify(printUtil, times(1)).printAndLog("Args: load candidate.csv"); + verify(printUtil, times(1)).printAndLog("Args: convertAttachments candidateAttachFile.csv"); verify(printUtil, times(1)).printAndLog("Total records processed: " + total); - verify(printUtil, times(1)).printAndLog("Total records inserted: " + totals.getActionTotal(Result.Action.INSERT)); - verify(printUtil, times(1)).printAndLog("Total records updated: " + totals.getActionTotal(Result.Action.UPDATE)); - verify(printUtil, times(1)).printAndLog("Total records deleted: " + totals.getActionTotal(Result.Action.DELETE)); + verify(printUtil, times(1)).printAndLog("Total records converted: " + totals.getActionTotal(Result.Action.CONVERT)); + verify(printUtil, times(1)).printAndLog("Total records skipped: " + totals.getActionTotal(Result.Action.SKIP)); verify(printUtil, times(1)).printAndLog("Total records failed: " + totals.getActionTotal(Result.Action.FAILURE)); } @Test - public void testPrintActionTotals_CONVERT_ATTACHMENTS() { + public void testPrintActionTotalsExport() { final PrintUtil printUtil = spy(PrintUtil.class); ActionTotals totals = new ActionTotals(); - final Integer total = 0; - final String[] args = {"convertAttachments", "candidateAttachFile.csv"}; + final int total = 0; + final String[] args = {"export", "candidate.csv"}; doNothing().when(printUtil).printAndLog(anyString()); printUtil.recordStart(args); - printUtil.printActionTotals(Command.CONVERT_ATTACHMENTS, totals); + printUtil.printActionTotals(Command.EXPORT, totals); verify(printUtil, times(1)).printAndLog("Results of DataLoader run"); verify(printUtil, times(1)).printAndLog(Matchers.startsWith("Start time: ")); verify(printUtil, times(1)).printAndLog(Matchers.startsWith("End time: ")); - verify(printUtil, times(1)).printAndLog("Args: convertAttachments candidateAttachFile.csv"); + verify(printUtil, times(1)).printAndLog("Args: export candidate.csv"); verify(printUtil, times(1)).printAndLog("Total records processed: " + total); - verify(printUtil, times(1)).printAndLog("Total records converted: " + totals.getActionTotal(Result.Action.CONVERT)); - verify(printUtil, times(1)).printAndLog("Total records skipped: " + totals.getActionTotal(Result.Action.SKIP)); + verify(printUtil, times(1)).printAndLog("Total records exported: " + totals.getActionTotal(Result.Action.CONVERT)); + verify(printUtil, times(1)).printAndLog("Total records failed: " + totals.getActionTotal(Result.Action.FAILURE)); + } + + @Test + public void testPrintActionTotalsLoad() { + final PrintUtil printUtil = spy(PrintUtil.class); + ActionTotals totals = new ActionTotals(); + final int total = 0; + final String[] args = {"load", "candidate.csv"}; + doNothing().when(printUtil).printAndLog(anyString()); + + printUtil.recordStart(args); + printUtil.printActionTotals(Command.LOAD, totals); + + verify(printUtil, times(1)).printAndLog("Results of DataLoader run"); + verify(printUtil, times(1)).printAndLog(Matchers.startsWith("Start time: ")); + verify(printUtil, times(1)).printAndLog(Matchers.startsWith("End time: ")); + verify(printUtil, times(1)).printAndLog("Args: load candidate.csv"); + verify(printUtil, times(1)).printAndLog("Total records processed: " + total); + verify(printUtil, times(1)).printAndLog("Total records inserted: " + totals.getActionTotal(Result.Action.INSERT)); + verify(printUtil, times(1)).printAndLog("Total records updated: " + totals.getActionTotal(Result.Action.UPDATE)); + verify(printUtil, times(1)).printAndLog("Total records deleted: " + totals.getActionTotal(Result.Action.DELETE)); verify(printUtil, times(1)).printAndLog("Total records failed: " + totals.getActionTotal(Result.Action.FAILURE)); } @Test - public void testPrintActionTotals_noRecordStart() { + public void testPrintActionTotalsNoRecordStart() { final PrintUtil printUtil = spy(PrintUtil.class); ActionTotals totals = new ActionTotals(); diff --git a/src/test/resources/integrationTest/integrationTest.properties b/src/test/resources/integrationTest/integrationTest.properties index d23d6b0d..0ae58c6d 100644 --- a/src/test/resources/integrationTest/integrationTest.properties +++ b/src/test/resources/integrationTest/integrationTest.properties @@ -56,6 +56,7 @@ clientCorporationCustomObjectInstance7ExistField=clientCorporation.externalID,te clientCorporationCustomObjectInstance8ExistField=clientCorporation.externalID,text1 clientCorporationCustomObjectInstance9ExistField=clientCorporation.externalID,text1 clientCorporationCustomObjectInstance10ExistField=clientCorporation.externalID,text1 +clientCorporationCustomObjectInstance35ExistField=clientCorporation.externalID,text1 jobOrderCustomObjectInstance1ExistField=jobOrder.externalID,text1 jobOrderCustomObjectInstance2ExistField=jobOrder.externalID,text1 jobOrderCustomObjectInstance3ExistField=jobOrder.externalID,text1