/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.nifi.processors.standard;

import org.apache.nifi.flowfile.attributes.CoreAttributes;
import org.apache.nifi.util.MockFlowFile;
import org.apache.nifi.util.PropertyMigrationResult;
import org.apache.nifi.util.TestRunner;
import org.apache.nifi.util.TestRunners;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class TestAttributesToCSV {

    private static final String OUTPUT_NEW_ATTRIBUTE = "flowfile-attribute";
    private static final String OUTPUT_OVERWRITE_CONTENT = "flowfile-content";
    private static final String OUTPUT_ATTRIBUTE_NAME = "CSVData";
    private static final String OUTPUT_SEPARATOR = ",";
    private static final String OUTPUT_MIME_TYPE = "text/csv";
    private static final String SPLIT_REGEX = OUTPUT_SEPARATOR + "(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)";
    private static final String newline = System.lineSeparator();
    private TestRunner testRunner;

    @BeforeEach
    void setUp() {
        testRunner = TestRunners.newTestRunner(new AttributesToCSV());
    }

    @Test
    public void testAttrListNoCoreNullOffNewAttrToAttribute() {
        testRunner.setProperty(AttributesToCSV.DESTINATION, OUTPUT_NEW_ATTRIBUTE);
        testRunner.setProperty(AttributesToCSV.INCLUDE_CORE_ATTRIBUTES, "false");
        testRunner.setProperty(AttributesToCSV.NULL_VALUE_FOR_EMPTY_STRING, "false");

        final String nonPresentAttributeKey = "beach-type";
        testRunner.setProperty(AttributesToCSV.ATTRIBUTES_LIST, nonPresentAttributeKey);
        testRunner.enqueue(new byte[0]);
        testRunner.run();

        testRunner.getFlowFilesForRelationship(AttributesToCSV.REL_SUCCESS).getFirst()
                .assertAttributeExists("CSVData");
        testRunner.assertTransferCount(AttributesToCSV.REL_SUCCESS, 1);
        testRunner.assertTransferCount(AttributesToCSV.REL_FAILURE, 0);

        testRunner.getFlowFilesForRelationship(AttributesToCSV.REL_SUCCESS)
                .getFirst().assertAttributeEquals("CSVData", "");
    }

    @Test
    public void testAttrListNoCoreNullOffNewAttrToContent() {
        //set the destination of the csv string to be an attribute
        testRunner.setProperty(AttributesToCSV.DESTINATION, OUTPUT_NEW_ATTRIBUTE);
        testRunner.setProperty(AttributesToCSV.INCLUDE_CORE_ATTRIBUTES, "false");
        testRunner.setProperty(AttributesToCSV.NULL_VALUE_FOR_EMPTY_STRING, "false");

        //use only one attribute, which does not exist, as the list of attributes to convert to csv
        final String nonPresentAttributeKey = "beach-type";
        testRunner.setProperty(AttributesToCSV.ATTRIBUTES_LIST, nonPresentAttributeKey);
        testRunner.enqueue(new byte[0]);
        testRunner.run();

        testRunner.getFlowFilesForRelationship(AttributesToCSV.REL_SUCCESS).getFirst()
                .assertAttributeExists("CSVData");
        testRunner.assertTransferCount(AttributesToCSV.REL_SUCCESS, 1);
        testRunner.assertTransferCount(AttributesToCSV.REL_FAILURE, 0);

        testRunner.getFlowFilesForRelationship(AttributesToCSV.REL_SUCCESS)
                .getFirst().assertAttributeEquals("CSVData", "");
    }

    @Test
    public void testAttrListNoCoreNullOffTwoNewAttrToAttribute() {
        testRunner.setProperty(AttributesToCSV.DESTINATION, OUTPUT_NEW_ATTRIBUTE);
        testRunner.setProperty(AttributesToCSV.INCLUDE_CORE_ATTRIBUTES, "false");
        testRunner.setProperty(AttributesToCSV.NULL_VALUE_FOR_EMPTY_STRING, "false");

        final String nonPresentAttributeKey = "beach-type,beach-length";
        testRunner.setProperty(AttributesToCSV.ATTRIBUTES_LIST, nonPresentAttributeKey);
        testRunner.enqueue(new byte[0]);
        testRunner.run();

        testRunner.getFlowFilesForRelationship(AttributesToCSV.REL_SUCCESS).getFirst()
                .assertAttributeExists("CSVData");
        testRunner.assertTransferCount(AttributesToCSV.REL_SUCCESS, 1);
        testRunner.assertTransferCount(AttributesToCSV.REL_FAILURE, 0);

        testRunner.getFlowFilesForRelationship(AttributesToCSV.REL_SUCCESS)
                .getFirst().assertAttributeEquals("CSVData", ",");
    }

    @Test
    public void testAttrListNoCoreNullTwoNewAttrToAttribute() {
        testRunner.setProperty(AttributesToCSV.DESTINATION, OUTPUT_NEW_ATTRIBUTE);
        testRunner.setProperty(AttributesToCSV.INCLUDE_CORE_ATTRIBUTES, "false");
        testRunner.setProperty(AttributesToCSV.NULL_VALUE_FOR_EMPTY_STRING, "true");

        final String nonPresentAttributeKey = "beach-type,beach-length";
        testRunner.setProperty(AttributesToCSV.ATTRIBUTES_LIST, nonPresentAttributeKey);
        testRunner.enqueue(new byte[0]);
        testRunner.run();

        testRunner.getFlowFilesForRelationship(AttributesToCSV.REL_SUCCESS).getFirst()
                .assertAttributeExists("CSVData");
        testRunner.assertTransferCount(AttributesToCSV.REL_SUCCESS, 1);
        testRunner.assertTransferCount(AttributesToCSV.REL_FAILURE, 0);

        testRunner.getFlowFilesForRelationship(AttributesToCSV.REL_SUCCESS)
                .getFirst().assertAttributeEquals("CSVData", "null,null");
    }

    @Test
    public void testNoAttrListNoCoreNullOffToAttribute() {
        //set the destination of the csv string to be an attribute
        testRunner.setProperty(AttributesToCSV.DESTINATION, OUTPUT_NEW_ATTRIBUTE);
        testRunner.setProperty(AttributesToCSV.INCLUDE_CORE_ATTRIBUTES, "false");
        testRunner.setProperty(AttributesToCSV.NULL_VALUE_FOR_EMPTY_STRING, "false");
        testRunner.enqueue(new byte[0]);
        testRunner.run();

        testRunner.getFlowFilesForRelationship(AttributesToCSV.REL_SUCCESS).getFirst()
                .assertAttributeExists("CSVData");
        testRunner.assertTransferCount(AttributesToCSV.REL_SUCCESS, 1);
        testRunner.assertTransferCount(AttributesToCSV.REL_FAILURE, 0);

        testRunner.getFlowFilesForRelationship(AttributesToCSV.REL_SUCCESS)
                .getFirst().assertAttributeEquals("CSVData", "");
    }

    @Test
    public void testNoAttrListNoCoreNullToAttribute() {
        testRunner.setProperty(AttributesToCSV.DESTINATION, OUTPUT_NEW_ATTRIBUTE);
        testRunner.setProperty(AttributesToCSV.INCLUDE_CORE_ATTRIBUTES, "false");
        testRunner.setProperty(AttributesToCSV.NULL_VALUE_FOR_EMPTY_STRING, "true");
        testRunner.enqueue(new byte[0]);
        testRunner.run();

        testRunner.getFlowFilesForRelationship(AttributesToCSV.REL_SUCCESS).getFirst()
                .assertAttributeExists("CSVData");
        testRunner.assertTransferCount(AttributesToCSV.REL_SUCCESS, 1);
        testRunner.assertTransferCount(AttributesToCSV.REL_FAILURE, 0);

        testRunner.getFlowFilesForRelationship(AttributesToCSV.REL_SUCCESS)
                .getFirst().assertAttributeEquals("CSVData", "");
    }

    @Test
    public void testNoAttrListCoreNullOffToContent() {
        testRunner.setProperty(AttributesToCSV.DESTINATION, OUTPUT_OVERWRITE_CONTENT);
        testRunner.setProperty(AttributesToCSV.INCLUDE_CORE_ATTRIBUTES, "true");
        testRunner.setProperty(AttributesToCSV.NULL_VALUE_FOR_EMPTY_STRING, "false");

        final Map<String, String> attrs = Map.of("beach-name", "Malibu Beach", "beach-location", "California, US",
            "beach-endorsement", "This is our family's favorite beach. We highly recommend it. \n\nThanks, Jim");

        testRunner.enqueue(new byte[0], attrs);
        testRunner.run();

        List<MockFlowFile> flowFilesForRelationship = testRunner.getFlowFilesForRelationship(AttributesToCSV.REL_SUCCESS);

        testRunner.assertTransferCount(AttributesToCSV.REL_FAILURE, 0);
        testRunner.assertTransferCount(AttributesToCSV.REL_SUCCESS, 1);

        MockFlowFile flowFile = flowFilesForRelationship.getFirst();

        assertEquals(OUTPUT_MIME_TYPE, flowFile.getAttribute(CoreAttributes.MIME_TYPE.key()));

        final byte[] contentData = testRunner.getContentAsByteArray(flowFile);

        final String contentDataString = new String(contentData, StandardCharsets.UTF_8);

        Set<String> contentValues = new HashSet<>(getStrings(contentDataString));

        assertEquals(6, contentValues.size());

        assertTrue(contentValues.contains("Malibu Beach"));
        assertTrue(contentValues.contains("\"California, US\""));
        assertTrue(contentValues.contains("\"This is our family's favorite beach. We highly recommend it. \n\nThanks, Jim\""));
        assertTrue(contentValues.contains(flowFile.getAttribute("filename")));
        assertTrue(contentValues.contains(flowFile.getAttribute("path")));
        assertTrue(contentValues.contains(flowFile.getAttribute("uuid")));
    }

    @Test
    public void testNoAttrListCoreNullOffToAttribute() {
        testRunner.setProperty(AttributesToCSV.INCLUDE_CORE_ATTRIBUTES, "true");
        testRunner.setProperty(AttributesToCSV.NULL_VALUE_FOR_EMPTY_STRING, "false");

        Map<String, String> attrs = Map.of("beach-name", "Malibu Beach", "beach-location", "California, US",
                    "beach-endorsement", "This is our family's favorite beach. We highly recommend it. \n\nThanks, Jim");

        testRunner.enqueue(new byte[0], attrs);
        testRunner.run();

        List<MockFlowFile> flowFilesForRelationship = testRunner.getFlowFilesForRelationship(AttributesToCSV.REL_SUCCESS);

        testRunner.assertTransferCount(AttributesToCSV.REL_FAILURE, 0);
        testRunner.assertTransferCount(AttributesToCSV.REL_SUCCESS, 1);

        MockFlowFile flowFile = flowFilesForRelationship.getFirst();

        assertNull(flowFile.getAttribute(CoreAttributes.MIME_TYPE.key()));

        final String attributeData = flowFile.getAttribute(OUTPUT_ATTRIBUTE_NAME);

        Set<String> csvAttributeValues = new HashSet<>(getStrings(attributeData));

        assertEquals(6, csvAttributeValues.size());

        assertTrue(csvAttributeValues.contains("Malibu Beach"));
        assertTrue(csvAttributeValues.contains("\"California, US\""));
        assertTrue(csvAttributeValues.contains("\"This is our family's favorite beach. We highly recommend it. \n\nThanks, Jim\""));

        assertTrue(csvAttributeValues.contains(flowFile.getAttribute("filename")));
        assertTrue(csvAttributeValues.contains(flowFile.getAttribute("path")));
        assertTrue(csvAttributeValues.contains(flowFile.getAttribute("uuid")));
    }

    @Test
    public void testNoAttrListNoCoreNullOffToContent() {
        testRunner.setProperty(AttributesToCSV.DESTINATION, OUTPUT_OVERWRITE_CONTENT);
        testRunner.setProperty(AttributesToCSV.INCLUDE_CORE_ATTRIBUTES, "false");
        testRunner.setProperty(AttributesToCSV.NULL_VALUE_FOR_EMPTY_STRING, "false");

        Map<String, String> attrs = Map.of("beach-name", "Malibu Beach", "beach-location", "California, US",
                    "beach-endorsement", "This is our family's favorite beach. We highly recommend it. \n\nThanks, Jim");

        testRunner.enqueue(new byte[0], attrs);
        testRunner.run();

        List<MockFlowFile> flowFilesForRelationship = testRunner.getFlowFilesForRelationship(AttributesToCSV.REL_SUCCESS);

        testRunner.assertTransferCount(AttributesToCSV.REL_FAILURE, 0);
        testRunner.assertTransferCount(AttributesToCSV.REL_SUCCESS, 1);

        MockFlowFile flowFile = flowFilesForRelationship.getFirst();

        assertEquals(OUTPUT_MIME_TYPE, flowFile.getAttribute(CoreAttributes.MIME_TYPE.key()));

        final byte[] contentData = testRunner.getContentAsByteArray(flowFile);

        final String contentDataString = new String(contentData, StandardCharsets.UTF_8);
        Set<String> contentValues = new HashSet<>(getStrings(contentDataString));

        assertEquals(3, contentValues.size());

        assertTrue(contentValues.contains("Malibu Beach"));
        assertTrue(contentValues.contains("\"California, US\""));
        assertTrue(contentValues.contains("\"This is our family's favorite beach. We highly recommend it. \n\nThanks, Jim\""));
    }

    @Test
    public void testAttrListNoCoreNullOffToAttribute() {
        testRunner.setProperty(AttributesToCSV.DESTINATION, OUTPUT_NEW_ATTRIBUTE);
        testRunner.setProperty(AttributesToCSV.INCLUDE_CORE_ATTRIBUTES, "false");
        testRunner.setProperty(AttributesToCSV.ATTRIBUTES_LIST, "beach-name,beach-location,beach-endorsement");
        testRunner.setProperty(AttributesToCSV.NULL_VALUE_FOR_EMPTY_STRING, "false");

        Map<String, String> attrs = Map.of("beach-name", "Malibu Beach", "beach-location", "California, US",
                    "beach-endorsement", "This is our family's favorite beach. We highly recommend it. \n\nThanks, Jim",
                    "attribute-should-be-eliminated", "This should not be in CSVAttribute!");

        testRunner.enqueue(new byte[0], attrs);
        testRunner.run();

        List<MockFlowFile> flowFilesForRelationship = testRunner.getFlowFilesForRelationship(AttributesToCSV.REL_SUCCESS);

        testRunner.assertTransferCount(AttributesToCSV.REL_FAILURE, 0);
        testRunner.assertTransferCount(AttributesToCSV.REL_SUCCESS, 1);

        MockFlowFile flowFile = flowFilesForRelationship.getFirst();

        assertNull(flowFile.getAttribute(CoreAttributes.MIME_TYPE.key()));

        final String attributeData = flowFile.getAttribute(OUTPUT_ATTRIBUTE_NAME);

        Set<String> csvDataValues = new HashSet<>(getStrings(attributeData));

        assertEquals(3, csvDataValues.size());

        assertTrue(csvDataValues.contains("Malibu Beach"));
        assertTrue(csvDataValues.contains("\"California, US\""));
        assertTrue(csvDataValues.contains("\"This is our family's favorite beach. We highly recommend it. \n\nThanks, Jim\""));

    }

    @Test
    public void testAttrListCoreNullOffToAttribute() {
        testRunner.setProperty(AttributesToCSV.DESTINATION, OUTPUT_NEW_ATTRIBUTE);
        testRunner.setProperty(AttributesToCSV.INCLUDE_CORE_ATTRIBUTES, "true");
        testRunner.setProperty(AttributesToCSV.ATTRIBUTES_LIST, "beach-name,beach-location,beach-endorsement");
        testRunner.setProperty(AttributesToCSV.NULL_VALUE_FOR_EMPTY_STRING, "false");

        Map<String, String> attrs = Map.of("beach-name", "Malibu Beach",
                    "beach-location", "California, US",
                    "beach-endorsement", "This is our family's favorite beach. We highly recommend it. \n\nThanks, Jim",
                    "attribute-should-be-eliminated", "This should not be in CSVData!");

        testRunner.enqueue(new byte[0], attrs);
        testRunner.run();

        List<MockFlowFile> flowFilesForRelationship = testRunner.getFlowFilesForRelationship(AttributesToCSV.REL_SUCCESS);

        testRunner.assertTransferCount(AttributesToCSV.REL_FAILURE, 0);
        testRunner.assertTransferCount(AttributesToCSV.REL_SUCCESS, 1);

        MockFlowFile flowFile = flowFilesForRelationship.getFirst();

        assertNull(flowFile.getAttribute(CoreAttributes.MIME_TYPE.key()));

        final String attributeData = flowFile.getAttribute(OUTPUT_ATTRIBUTE_NAME);

        Set<String> csvDataValues = new HashSet<>(getStrings(attributeData));

        assertEquals(6, csvDataValues.size());

        assertTrue(csvDataValues.contains("Malibu Beach"));
        assertTrue(csvDataValues.contains("\"California, US\""));
        assertTrue(csvDataValues.contains("\"This is our family's favorite beach. We highly recommend it. \n\nThanks, Jim\""));

        assertTrue(csvDataValues.contains(flowFile.getAttribute("filename")));
        assertTrue(csvDataValues.contains(flowFile.getAttribute("path")));
        assertTrue(csvDataValues.contains(flowFile.getAttribute("uuid")));
    }

    @Test
    public void testAttrListNoCoreNullOffOverrideCoreByAttrListToAttribute() {
        testRunner.setProperty(AttributesToCSV.DESTINATION, OUTPUT_NEW_ATTRIBUTE);
        testRunner.setProperty(AttributesToCSV.INCLUDE_CORE_ATTRIBUTES, "false");
        testRunner.setProperty(AttributesToCSV.ATTRIBUTES_LIST, "beach-name,beach-location,beach-endorsement,uuid");
        testRunner.setProperty(AttributesToCSV.NULL_VALUE_FOR_EMPTY_STRING, "false");

        Map<String, String> attrs = Map.of("beach-name", "Malibu Beach",
                    "beach-location", "California, US",
                    "beach-endorsement", "This is our family's favorite beach. We highly recommend it. \n\nThanks, Jim",
                    "attribute-should-be-eliminated", "This should not be in CSVData!");

        testRunner.enqueue(new byte[0], attrs);
        testRunner.run();

        List<MockFlowFile> flowFilesForRelationship = testRunner.getFlowFilesForRelationship(AttributesToCSV.REL_SUCCESS);

        testRunner.assertTransferCount(AttributesToCSV.REL_FAILURE, 0);
        testRunner.assertTransferCount(AttributesToCSV.REL_SUCCESS, 1);

        MockFlowFile flowFile = flowFilesForRelationship.getFirst();

        assertNull(flowFile.getAttribute(CoreAttributes.MIME_TYPE.key()));

        final String attributeData = flowFile.getAttribute(OUTPUT_ATTRIBUTE_NAME);

        Set<String> csvDataValues = new HashSet<>(getStrings(attributeData));

        assertEquals(4, csvDataValues.size());

        assertTrue(csvDataValues.contains("Malibu Beach"));
        assertTrue(csvDataValues.contains("\"California, US\""));
        assertTrue(csvDataValues.contains("\"This is our family's favorite beach. We highly recommend it. \n\nThanks, Jim\""));

        assertFalse(csvDataValues.contains(flowFile.getAttribute("filename")));
        assertFalse(csvDataValues.contains(flowFile.getAttribute("path")));
        assertTrue(csvDataValues.contains(flowFile.getAttribute("uuid")));
    }

    @Test
    public void testAttrListFromExpCoreNullOffToAttribute() {
        testRunner.setProperty(AttributesToCSV.DESTINATION, OUTPUT_NEW_ATTRIBUTE);
        testRunner.setProperty(AttributesToCSV.INCLUDE_CORE_ATTRIBUTES, "true");
        testRunner.setProperty(AttributesToCSV.ATTRIBUTES_LIST, "${myAttribs}");
        testRunner.setProperty(AttributesToCSV.NULL_VALUE_FOR_EMPTY_STRING, "false");

        Map<String, String> attrs = Map.of("beach-name", "Malibu Beach",
                    "beach-location", "California, US",
                    "beach-endorsement", "This is our family's favorite beach. We highly recommend it. \n\nThanks, Jim",
                    "attribute-should-be-eliminated", "This should not be in CSVData!",
                    "myAttribs", "beach-name,beach-location,beach-endorsement");

        testRunner.enqueue(new byte[0], attrs);
        testRunner.run();

        List<MockFlowFile> flowFilesForRelationship = testRunner.getFlowFilesForRelationship(AttributesToCSV.REL_SUCCESS);

        testRunner.assertTransferCount(AttributesToCSV.REL_FAILURE, 0);
        testRunner.assertTransferCount(AttributesToCSV.REL_SUCCESS, 1);

        //Test flow file 0 with ATTRIBUTE_LIST populated from expression language
        MockFlowFile flowFile = flowFilesForRelationship.getFirst();

        assertNull(flowFile.getAttribute(CoreAttributes.MIME_TYPE.key()));

        String attributeData = flowFile.getAttribute(OUTPUT_ATTRIBUTE_NAME);

        Set<String> csvDataValues = new HashSet<>(getStrings(attributeData));

        assertEquals(6, csvDataValues.size());

        assertTrue(csvDataValues.contains("Malibu Beach"));
        assertTrue(csvDataValues.contains("\"California, US\""));
        assertTrue(csvDataValues.contains("\"This is our family's favorite beach. We highly recommend it. \n\nThanks, Jim\""));

        assertTrue(csvDataValues.contains(flowFile.getAttribute("filename")));
        assertTrue(csvDataValues.contains(flowFile.getAttribute("path")));
        assertTrue(csvDataValues.contains(flowFile.getAttribute("uuid")));

        //Test flow file 1 with ATTRIBUTE_LIST populated from expression language containing commas (output should be he same)
        flowFile = flowFilesForRelationship.getFirst();

        assertNull(flowFile.getAttribute(CoreAttributes.MIME_TYPE.key()));

        attributeData = flowFile.getAttribute(OUTPUT_ATTRIBUTE_NAME);

        csvDataValues = new HashSet<>(getStrings(attributeData));

        assertEquals(6, csvDataValues.size());

        assertTrue(csvDataValues.contains("Malibu Beach"));
        assertTrue(csvDataValues.contains("\"California, US\""));
        assertTrue(csvDataValues.contains("\"This is our family's favorite beach. We highly recommend it. \n\nThanks, Jim\""));

        assertTrue(csvDataValues.contains(flowFile.getAttribute("filename")));
        assertTrue(csvDataValues.contains(flowFile.getAttribute("path")));
        assertTrue(csvDataValues.contains(flowFile.getAttribute("uuid")));
    }

    @Test
    public void testAttrListWithCommasInNameFromExpCoreNullOffToAttribute() {
        testRunner.setProperty(AttributesToCSV.DESTINATION, OUTPUT_NEW_ATTRIBUTE);
        testRunner.setProperty(AttributesToCSV.INCLUDE_CORE_ATTRIBUTES, "true");
        testRunner.setProperty(AttributesToCSV.ATTRIBUTES_LIST, "${myAttribs}");
        testRunner.setProperty(AttributesToCSV.NULL_VALUE_FOR_EMPTY_STRING, "false");

        Map<String, String> attrsCommaInName = Map.of("beach,name", "Malibu Beach",
                    "beach,location", "California, US",
                    "beach,endorsement", "This is our family's favorite beach. We highly recommend it. \n\nThanks, Jim",
                    "attribute-should-be-eliminated", "This should not be in CSVData!",
                    "myAttribs", "\"beach,name\",\"beach,location\",\"beach,endorsement\"");

        testRunner.enqueue(new byte[0], attrsCommaInName);
        testRunner.run();

        List<MockFlowFile> flowFilesForRelationship = testRunner.getFlowFilesForRelationship(AttributesToCSV.REL_SUCCESS);

        testRunner.assertTransferCount(AttributesToCSV.REL_FAILURE, 0);
        testRunner.assertTransferCount(AttributesToCSV.REL_SUCCESS, 1);

        //Test flow file 0 with ATTRIBUTE_LIST populated from expression language
        MockFlowFile flowFile = flowFilesForRelationship.getFirst();

        assertNull(flowFile.getAttribute(CoreAttributes.MIME_TYPE.key()));

        String attributeData = flowFile.getAttribute(OUTPUT_ATTRIBUTE_NAME);

        Set<String> csvDataValues = new HashSet<>(getStrings(attributeData));

        assertEquals(6, csvDataValues.size());

        assertTrue(csvDataValues.contains("Malibu Beach"));
        assertTrue(csvDataValues.contains("\"California, US\""));
        assertTrue(csvDataValues.contains("\"This is our family's favorite beach. We highly recommend it. \n\nThanks, Jim\""));

        assertTrue(csvDataValues.contains(flowFile.getAttribute("filename")));
        assertTrue(csvDataValues.contains(flowFile.getAttribute("path")));
        assertTrue(csvDataValues.contains(flowFile.getAttribute("uuid")));

        //Test flow file 1 with ATTRIBUTE_LIST populated from expression language containing commas (output should be he same)
        flowFile = flowFilesForRelationship.getFirst();

        assertNull(flowFile.getAttribute(CoreAttributes.MIME_TYPE.key()));

        attributeData = flowFile.getAttribute(OUTPUT_ATTRIBUTE_NAME);

        csvDataValues = new HashSet<>(getStrings(attributeData));

        assertEquals(6, csvDataValues.size());

        assertTrue(csvDataValues.contains("Malibu Beach"));
        assertTrue(csvDataValues.contains("\"California, US\""));
        assertTrue(csvDataValues.contains("\"This is our family's favorite beach. We highly recommend it. \n\nThanks, Jim\""));

        assertTrue(csvDataValues.contains(flowFile.getAttribute("filename")));
        assertTrue(csvDataValues.contains(flowFile.getAttribute("path")));
        assertTrue(csvDataValues.contains(flowFile.getAttribute("uuid")));
    }

    @Test
    public void testAttrListFromExpNoCoreNullOffOverrideCoreByAttrListToAttribute() {
        testRunner.setProperty(AttributesToCSV.DESTINATION, OUTPUT_NEW_ATTRIBUTE);
        testRunner.setProperty(AttributesToCSV.INCLUDE_CORE_ATTRIBUTES, "false");
        testRunner.setProperty(AttributesToCSV.ATTRIBUTES_LIST, "${myAttribs}");
        testRunner.setProperty(AttributesToCSV.NULL_VALUE_FOR_EMPTY_STRING, "false");

        Map<String, String> attrs = Map.of("beach-name", "Malibu Beach",
                    "beach-location", "California, US",
                    "beach-endorsement", "This is our family's favorite beach. We highly recommend it. \n\nThanks, Jim",
                    "attribute-should-be-eliminated", "This should not be in CSVData!",
                    "myAttribs", "beach-name,beach-location,beach-endorsement");

        testRunner.enqueue(new byte[0], attrs);
        testRunner.run();

        List<MockFlowFile> flowFilesForRelationship = testRunner.getFlowFilesForRelationship(AttributesToCSV.REL_SUCCESS);

        testRunner.assertTransferCount(AttributesToCSV.REL_FAILURE, 0);
        testRunner.assertTransferCount(AttributesToCSV.REL_SUCCESS, 1);

        MockFlowFile flowFile = flowFilesForRelationship.getFirst();

        assertNull(flowFile.getAttribute(CoreAttributes.MIME_TYPE.key()));

        final String attributeData = flowFile.getAttribute(OUTPUT_ATTRIBUTE_NAME);

        Set<String> csvDataValues = new HashSet<>(getStrings(attributeData));

        assertEquals(3, csvDataValues.size());

        assertTrue(csvDataValues.contains("Malibu Beach"));
        assertTrue(csvDataValues.contains("\"California, US\""));
        assertTrue(csvDataValues.contains("\"This is our family's favorite beach. We highly recommend it. \n\nThanks, Jim\""));

        assertFalse(csvDataValues.contains(flowFile.getAttribute("filename")));
        assertFalse(csvDataValues.contains(flowFile.getAttribute("path")));
        assertFalse(csvDataValues.contains(flowFile.getAttribute("uuid")));
    }

    @Test
    public void testAttributesRegex() {
        testRunner.setProperty(AttributesToCSV.DESTINATION, OUTPUT_NEW_ATTRIBUTE);
        testRunner.setProperty(AttributesToCSV.INCLUDE_CORE_ATTRIBUTES, "false");
        testRunner.setProperty(AttributesToCSV.ATTRIBUTES_REGEX, "${myRegEx}");
        testRunner.setProperty(AttributesToCSV.NULL_VALUE_FOR_EMPTY_STRING, "false");

        Map<String, String> attrs = Map.of("beach-name", "Malibu Beach",
                    "beach-location", "California, US",
                    "beach-endorsement", "This is our family's favorite beach. We highly recommend it. \n\nThanks, Jim",
                    "attribute-should-be-eliminated", "This should not be in CSVData!",
                    "myRegEx", "beach-.*");

        testRunner.enqueue(new byte[0], attrs);
        testRunner.run();

        List<MockFlowFile> flowFilesForRelationship = testRunner.getFlowFilesForRelationship(AttributesToCSV.REL_SUCCESS);

        testRunner.assertTransferCount(AttributesToCSV.REL_FAILURE, 0);
        testRunner.assertTransferCount(AttributesToCSV.REL_SUCCESS, 1);

        MockFlowFile flowFile = flowFilesForRelationship.getFirst();

        assertNull(flowFile.getAttribute(CoreAttributes.MIME_TYPE.key()));

        final String attributeData = flowFile.getAttribute(OUTPUT_ATTRIBUTE_NAME);

        Set<String> csvDataValues = new HashSet<>(getStrings(attributeData));

        assertEquals(3, csvDataValues.size());

        assertTrue(csvDataValues.contains("Malibu Beach"));
        assertTrue(csvDataValues.contains("\"California, US\""));
        assertTrue(csvDataValues.contains("\"This is our family's favorite beach. We highly recommend it. \n\nThanks, Jim\""));

        assertFalse(csvDataValues.contains(flowFile.getAttribute("filename")));
        assertFalse(csvDataValues.contains(flowFile.getAttribute("path")));
        assertFalse(csvDataValues.contains(flowFile.getAttribute("uuid")));
    }

    @Test
    public void testAttributesRegexAndList() {
        testRunner.setProperty(AttributesToCSV.DESTINATION, OUTPUT_NEW_ATTRIBUTE);
        testRunner.setProperty(AttributesToCSV.INCLUDE_CORE_ATTRIBUTES, "false");
        testRunner.setProperty(AttributesToCSV.ATTRIBUTES_REGEX, "${myRegEx}");
        testRunner.setProperty(AttributesToCSV.ATTRIBUTES_LIST, "moreInfo1,moreInfo2");
        testRunner.setProperty(AttributesToCSV.NULL_VALUE_FOR_EMPTY_STRING, "false");

        Map<String, String> attrs = Map.of("beach-name", "Malibu Beach",
                    "beach-location", "California, US",
                    "beach-endorsement", "This is our family's favorite beach. We highly recommend it. \n\nThanks, Jim",
                    "attribute-should-be-eliminated", "This should not be in CSVData!",
                    "myRegEx", "beach-.*",
                    "moreInfo1", "A+ Rating",
                    "moreInfo2", "Avg Temp: 61f");

        testRunner.enqueue(new byte[0], attrs);
        testRunner.run();

        List<MockFlowFile> flowFilesForRelationship = testRunner.getFlowFilesForRelationship(AttributesToCSV.REL_SUCCESS);

        testRunner.assertTransferCount(AttributesToCSV.REL_FAILURE, 0);
        testRunner.assertTransferCount(AttributesToCSV.REL_SUCCESS, 1);

        MockFlowFile flowFile = flowFilesForRelationship.getFirst();

        assertNull(flowFile.getAttribute(CoreAttributes.MIME_TYPE.key()));

        final String attributeData = flowFile.getAttribute(OUTPUT_ATTRIBUTE_NAME);

        Set<String> csvDataValues = new HashSet<>(getStrings(attributeData));

        assertEquals(5, csvDataValues.size());

        assertTrue(csvDataValues.contains("Malibu Beach"));
        assertTrue(csvDataValues.contains("\"California, US\""));
        assertTrue(csvDataValues.contains("\"This is our family's favorite beach. We highly recommend it. \n\nThanks, Jim\""));
        assertTrue(csvDataValues.contains("A+ Rating"));
        assertTrue(csvDataValues.contains("Avg Temp: 61f"));

        assertFalse(csvDataValues.contains(flowFile.getAttribute("filename")));
        assertFalse(csvDataValues.contains(flowFile.getAttribute("path")));
        assertFalse(csvDataValues.contains(flowFile.getAttribute("uuid")));
    }

    @Test
    public void testSchemaToAttribute() {
        testRunner.setProperty(AttributesToCSV.DESTINATION, OUTPUT_NEW_ATTRIBUTE);
        testRunner.setProperty(AttributesToCSV.INCLUDE_CORE_ATTRIBUTES, "false");
        testRunner.setProperty(AttributesToCSV.NULL_VALUE_FOR_EMPTY_STRING, "false");
        testRunner.setProperty(AttributesToCSV.INCLUDE_SCHEMA, "true");
        testRunner.setProperty(AttributesToCSV.ATTRIBUTES_REGEX, "beach-.*");

        Map<String, String> attrs = new LinkedHashMap<>();
        attrs.put("beach-name", "Malibu Beach");
        attrs.put("beach-location", "California, US");
        attrs.put("attribute-should-be-eliminated", "This should not be in CSVData!");

        testRunner.enqueue(new byte[0], attrs);
        testRunner.run();

        testRunner.getFlowFilesForRelationship(AttributesToCSV.REL_SUCCESS).getFirst()
                .assertAttributeExists("CSVData");
        testRunner.getFlowFilesForRelationship(AttributesToCSV.REL_SUCCESS).getFirst()
                .assertAttributeExists("CSVSchema");
        testRunner.assertTransferCount(AttributesToCSV.REL_SUCCESS, 1);
        testRunner.assertTransferCount(AttributesToCSV.REL_FAILURE, 0);

        testRunner.getFlowFilesForRelationship(AttributesToCSV.REL_SUCCESS)
                .getFirst().assertAttributeEquals("CSVData", "Malibu Beach,\"California, US\"");
        testRunner.getFlowFilesForRelationship(AttributesToCSV.REL_SUCCESS)
                .getFirst().assertAttributeEquals("CSVSchema", "beach-name,beach-location");
    }

    @Test
    public void testSchemaToContent() {
        //set the destination of the csv string to be an attribute
        testRunner.setProperty(AttributesToCSV.DESTINATION, OUTPUT_OVERWRITE_CONTENT);
        testRunner.setProperty(AttributesToCSV.INCLUDE_CORE_ATTRIBUTES, "false");
        testRunner.setProperty(AttributesToCSV.NULL_VALUE_FOR_EMPTY_STRING, "false");
        testRunner.setProperty(AttributesToCSV.INCLUDE_SCHEMA, "true");
        testRunner.setProperty(AttributesToCSV.ATTRIBUTES_REGEX, "beach-.*");

        Map<String, String> attrs = new LinkedHashMap<>();
        attrs.put("beach-name", "Malibu Beach");
        attrs.put("beach-location", "California, US");
        attrs.put("attribute-should-be-eliminated", "This should not be in CSVData!");

        testRunner.enqueue(new byte[0], attrs);
        testRunner.run();

        testRunner.assertTransferCount(AttributesToCSV.REL_SUCCESS, 1);
        testRunner.assertTransferCount(AttributesToCSV.REL_FAILURE, 0);

        MockFlowFile flowFile = testRunner.getFlowFilesForRelationship(AttributesToCSV.REL_SUCCESS).getFirst();
        flowFile.assertAttributeNotExists("CSVData");
        flowFile.assertAttributeNotExists("CSVSchema");

        final byte[] contentData = testRunner.getContentAsByteArray(flowFile);

        final String contentDataString = new String(contentData, StandardCharsets.UTF_8);
        assertEquals(contentDataString.split(newline)[0], "beach-name,beach-location");
        assertEquals(contentDataString.split(newline)[1], "Malibu Beach,\"California, US\"");
    }

    @Test
    public void testSchemaWithCoreAttribuesToAttribute() {
        testRunner.setProperty(AttributesToCSV.DESTINATION, OUTPUT_NEW_ATTRIBUTE);
        testRunner.setProperty(AttributesToCSV.INCLUDE_CORE_ATTRIBUTES, "true");
        testRunner.setProperty(AttributesToCSV.NULL_VALUE_FOR_EMPTY_STRING, "false");
        testRunner.setProperty(AttributesToCSV.INCLUDE_SCHEMA, "true");
        testRunner.setProperty(AttributesToCSV.ATTRIBUTES_REGEX, "beach-.*");

        Map<String, String> attrs = new LinkedHashMap<>();
        attrs.put("beach-name", "Malibu Beach");
        attrs.put("beach-location", "California, US");
        attrs.put("attribute-should-be-eliminated", "This should not be in CSVData!");

        testRunner.enqueue(new byte[0], attrs);
        testRunner.run();

        testRunner.assertTransferCount(AttributesToCSV.REL_SUCCESS, 1);
        testRunner.assertTransferCount(AttributesToCSV.REL_FAILURE, 0);

        MockFlowFile flowFile = testRunner.getFlowFilesForRelationship(AttributesToCSV.REL_SUCCESS).getFirst();
        flowFile.assertAttributeExists("CSVData");
        flowFile.assertAttributeExists("CSVSchema");

        final String path = flowFile.getAttribute("path");
        final String filename = flowFile.getAttribute("filename");
        final String uuid = flowFile.getAttribute("uuid");

        flowFile.assertAttributeEquals("CSVData", "Malibu Beach,\"California, US\"," + path + "," + filename + "," + uuid);
        flowFile.assertAttributeEquals("CSVSchema", "beach-name,beach-location,path,filename,uuid");
    }

    @Test
    public void testSchemaWithCoreAttribuesToContent() {
        //set the destination of the csv string to be an attribute
        testRunner.setProperty(AttributesToCSV.DESTINATION, OUTPUT_OVERWRITE_CONTENT);
        testRunner.setProperty(AttributesToCSV.INCLUDE_CORE_ATTRIBUTES, "true");
        testRunner.setProperty(AttributesToCSV.NULL_VALUE_FOR_EMPTY_STRING, "false");
        testRunner.setProperty(AttributesToCSV.INCLUDE_SCHEMA, "true");
        testRunner.setProperty(AttributesToCSV.ATTRIBUTES_REGEX, "beach-.*");

        Map<String, String> attrs = new LinkedHashMap<>();
        attrs.put("beach-name", "Malibu Beach");
        attrs.put("beach-location", "California, US");
        attrs.put("attribute-should-be-eliminated", "This should not be in CSVData!");

        testRunner.enqueue(new byte[0], attrs);
        testRunner.run();

        testRunner.assertTransferCount(AttributesToCSV.REL_SUCCESS, 1);
        testRunner.assertTransferCount(AttributesToCSV.REL_FAILURE, 0);

        MockFlowFile flowFile = testRunner.getFlowFilesForRelationship(AttributesToCSV.REL_SUCCESS).getFirst();
        flowFile.assertAttributeNotExists("CSVData");
        flowFile.assertAttributeNotExists("CSVSchema");

        final String path = flowFile.getAttribute("path");
        final String filename = flowFile.getAttribute("filename");
        final String uuid = flowFile.getAttribute("uuid");

        final byte[] contentData = testRunner.getContentAsByteArray(flowFile);

        final String contentDataString = new String(contentData, StandardCharsets.UTF_8);
        assertEquals(contentDataString.split(newline)[0], "beach-name,beach-location,path,filename,uuid");
        assertEquals(contentDataString.split(newline)[1], "Malibu Beach,\"California, US\"," + path + "," + filename + "," + uuid);
    }

    @Test
    void testMigrateProperties() {
        final Map<String, String> expectedRenamed = Map.ofEntries(
                Map.entry("attribute-list", AttributesToCSV.ATTRIBUTES_LIST.getName()),
                Map.entry("attributes-regex", AttributesToCSV.ATTRIBUTES_REGEX.getName()),
                Map.entry("destination", AttributesToCSV.DESTINATION.getName()),
                Map.entry("include-core-attributes", AttributesToCSV.INCLUDE_CORE_ATTRIBUTES.getName()),
                Map.entry("null-value", AttributesToCSV.NULL_VALUE_FOR_EMPTY_STRING.getName()),
                Map.entry("include-schema", AttributesToCSV.INCLUDE_SCHEMA.getName())
        );

        final PropertyMigrationResult propertyMigrationResult = testRunner.migrateProperties();
        assertEquals(expectedRenamed, propertyMigrationResult.getPropertiesRenamed());
    }

    private List<String> getStrings(String sdata) {
        return Arrays.asList(Pattern.compile(SPLIT_REGEX).split(sdata));
    }
}
