package com.forter.contracts.testng; import com.forter.contracts.ContractConverter; import com.forter.contracts.ContractFactory; import com.forter.contracts.validation.ContractValidator; import com.forter.contracts.validation.ValidatedContract; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.MappingIterator; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.TextNode; import com.fasterxml.jackson.dataformat.csv.CsvMapper; import com.fasterxml.jackson.dataformat.csv.CsvSchema; import com.google.common.base.Function; import com.google.common.base.Preconditions; import com.google.common.base.Throwables; import com.google.common.collect.Iterators; import com.google.common.io.Resources; import com.google.common.reflect.Invokable; import org.testng.annotations.DataProvider; import java.io.File; import java.io.IOException; import java.lang.reflect.Method; import java.net.URL; import java.util.Iterator; import java.util.Map; /** * TestNG data provider */ public class TestDataProvider { /** * Expects a csv file with headers matching those of the input attributes and output attributes. * For example, given an input object with x attribute and output object with y attribute the csv file would be: * * input.x,output.y * 1,2 * 3,4 * */ @DataProvider(name = "csv") public static Iterator<Object[]> getDataFromCsvFile(Method testMethod) throws IOException { Invokable<?, Object> invokable = Invokable.from(testMethod); final Class<?> inputClass = invokable.getParameters().get(0).getType().getRawType(); final Class<?> outputClass = invokable.getParameters().get(1).getType().getRawType(); return csvToContracts(inputClass, outputClass, getCsvIterator(invokable)); } private static MappingIterator<Object> getCsvIterator(Invokable<?, Object> invokable) throws IOException { CsvMapper csvMapper = new CsvMapper(); CsvSchema bootstrapSchema = CsvSchema.emptySchema().withHeader(); String resourceName = invokable.getDeclaringClass().getName().replace('.', File.separatorChar)+".csv"; URL csvFile = Resources.getResource(resourceName); return csvMapper.reader(Map.class).with(bootstrapSchema).readValues(csvFile); } private static Iterator<Object[]> csvToContracts(Class<?> inputClass, Class<?> outputClass, MappingIterator<Object> csvIterator) { final ContractFactory<?> outputFactory = new ContractFactory(outputClass); final ContractFactory<?> inputFactory = new ContractFactory(inputClass); return Iterators.transform(csvIterator, new Function<Object, Object[]>() { @Override public Object[] apply(Object o) { final ObjectNode inputNode = JsonNodeFactory.instance.objectNode(); final ObjectNode outputNode = JsonNodeFactory.instance.objectNode(); for (Map.Entry<String, Object> entry : ((Map<String, Object>) o).entrySet()) { String value = (String) entry.getValue(); if (entry.getKey().startsWith("input.")) { inputNode.put(entry.getKey().substring("input.".length()), value); } else if (entry.getKey().startsWith("output.")) { outputNode.put(entry.getKey().substring("output.".length()), value); } else { Preconditions.checkArgument(false, "CSV header " + entry.getKey() + " must start with " + "input. or output."); } } convertNullsOrJson(inputNode); convertNullsOrJson(outputNode); final Object input = createAndValidateContract(inputNode, inputFactory); final Object output = createAndValidateContract(outputNode, outputFactory); return new Object[]{input, output}; } }); } private static void convertNullsOrJson(ObjectNode node) { Iterator<String> iterator = node.fieldNames(); ObjectMapper mapper = new ObjectMapper(); while(iterator.hasNext()) { String fieldName = iterator.next(); TextNode textNode = (TextNode) node.get(fieldName); if ((textNode.textValue().startsWith("{") && textNode.textValue().endsWith("}")) || (textNode.textValue().startsWith("[") && textNode.textValue().endsWith("]"))) { JsonNode actualObj = null; try { actualObj = mapper.readTree(textNode.textValue()); } catch (IOException e) { throw Throwables.propagate(e); } node.set(fieldName, actualObj); } else if (textNode.textValue().equals("__NULL__")) { node.set(fieldName, null); } } } private static Object createAndValidateContract(ObjectNode node, ContractFactory<?> factory) { try { Object contract = ContractConverter.instance().convertObjectNodeToContract(node, factory); validate(contract); return contract; } catch (JsonProcessingException e) { throw Throwables.propagate(e); } } private static void validate(Object contract) { ValidatedContract<Object> inputValidationResult = ContractValidator.instance().validate(contract); if (!inputValidationResult.isValid()) { throw new AssertionError("Input validation assertion error: " + inputValidationResult.toString()); } } }