http://git-wip-us.apache.org/repos/asf/nifi/blob/a5fecda5/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/ParseEvtxTest.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/ParseEvtxTest.java
 
b/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/ParseEvtxTest.java
new file mode 100644
index 0000000..2e5e90d
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/ParseEvtxTest.java
@@ -0,0 +1,481 @@
+/*
+ * 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.evtx;
+
+import org.apache.nifi.flowfile.FlowFile;
+import org.apache.nifi.flowfile.attributes.CoreAttributes;
+import org.apache.nifi.logging.ComponentLog;
+import org.apache.nifi.processor.ProcessSession;
+import org.apache.nifi.processor.io.OutputStreamCallback;
+import org.apache.nifi.processors.evtx.parser.ChunkHeader;
+import org.apache.nifi.processors.evtx.parser.FileHeader;
+import org.apache.nifi.processors.evtx.parser.FileHeaderFactory;
+import org.apache.nifi.processors.evtx.parser.MalformedChunkException;
+import org.apache.nifi.processors.evtx.parser.Record;
+import org.apache.nifi.processors.evtx.parser.bxml.RootNode;
+import org.apache.nifi.util.MockFlowFile;
+import org.apache.nifi.util.TestRunner;
+import org.apache.nifi.util.TestRunners;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.stream.XMLStreamException;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.isA;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ParseEvtxTest {
+    public static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = 
DocumentBuilderFactory.newInstance();
+    public static final String USER_DATA = "UserData";
+    public static final String EVENT_DATA = "EventData";
+    public static final Set DATA_TAGS = new 
HashSet<>(Arrays.asList(EVENT_DATA, USER_DATA));
+
+    @Mock
+    FileHeaderFactory fileHeaderFactory;
+
+    @Mock
+    MalformedChunkHandler malformedChunkHandler;
+
+    @Mock
+    RootNodeHandlerFactory rootNodeHandlerFactory;
+
+    @Mock
+    ResultProcessor resultProcessor;
+
+    @Mock
+    ComponentLog componentLog;
+
+    @Mock
+    InputStream in;
+
+    @Mock
+    OutputStream out;
+
+    @Mock
+    FileHeader fileHeader;
+
+    ParseEvtx parseEvtx;
+
+    @Before
+    public void setup() throws XMLStreamException, IOException {
+        parseEvtx = new ParseEvtx(fileHeaderFactory, malformedChunkHandler, 
rootNodeHandlerFactory, resultProcessor);
+        when(fileHeaderFactory.create(in, 
componentLog)).thenReturn(fileHeader);
+    }
+
+    @Test
+    public void testGetNameFile() {
+        String basename = "basename";
+        assertEquals(basename + ".xml", parseEvtx.getName(basename, null, 
null, ParseEvtx.XML_EXTENSION));
+    }
+
+    @Test
+    public void testGetNameFileChunk() {
+        String basename = "basename";
+        assertEquals(basename + "-chunk1.xml", parseEvtx.getName(basename, 1, 
null, ParseEvtx.XML_EXTENSION));
+    }
+
+    @Test
+    public void testGetNameFileChunkRecord() {
+        String basename = "basename";
+        assertEquals(basename + "-chunk1-record2.xml", 
parseEvtx.getName(basename, 1, 2, ParseEvtx.XML_EXTENSION));
+    }
+
+    @Test
+    public void testGetBasenameEvtxExtension() {
+        String basename = "basename";
+        FlowFile flowFile = mock(FlowFile.class);
+
+        
when(flowFile.getAttribute(CoreAttributes.FILENAME.key())).thenReturn(basename 
+ ".evtx");
+
+        assertEquals(basename, parseEvtx.getBasename(flowFile, componentLog));
+        verifyNoMoreInteractions(componentLog);
+    }
+
+    @Test
+    public void testGetBasenameExtension() {
+        String basename = "basename.wrongextension";
+        FlowFile flowFile = mock(FlowFile.class);
+        ComponentLog componentLog = mock(ComponentLog.class);
+
+        
when(flowFile.getAttribute(CoreAttributes.FILENAME.key())).thenReturn(basename);
+
+        assertEquals(basename, parseEvtx.getBasename(flowFile, componentLog));
+        verify(componentLog).warn(anyString(), isA(Object[].class));
+    }
+
+    @Test
+    public void testGetRelationships() {
+        assertEquals(ParseEvtx.RELATIONSHIPS, parseEvtx.getRelationships());
+    }
+
+    @Test
+    public void testGetSupportedPropertyDescriptors() {
+        assertEquals(ParseEvtx.PROPERTY_DESCRIPTORS, 
parseEvtx.getSupportedPropertyDescriptors());
+    }
+
+    @Test
+    public void testProcessFileGranularity() throws IOException, 
MalformedChunkException, XMLStreamException {
+        String basename = "basename";
+        int chunkNum = 5;
+        int offset = 10001;
+        byte[] badChunk = {8};
+
+        RootNodeHandler rootNodeHandler = mock(RootNodeHandler.class);
+        when(rootNodeHandlerFactory.create(out)).thenReturn(rootNodeHandler);
+        ChunkHeader chunkHeader1 = mock(ChunkHeader.class);
+        ChunkHeader chunkHeader2 = mock(ChunkHeader.class);
+        Record record1 = mock(Record.class);
+        Record record2 = mock(Record.class);
+        Record record3 = mock(Record.class);
+        RootNode rootNode1 = mock(RootNode.class);
+        RootNode rootNode2 = mock(RootNode.class);
+        RootNode rootNode3 = mock(RootNode.class);
+        ProcessSession session = mock(ProcessSession.class);
+        FlowFile flowFile = mock(FlowFile.class);
+        AtomicReference<Exception> reference = new AtomicReference<>();
+        MalformedChunkException malformedChunkException = new 
MalformedChunkException("Test", null, offset, chunkNum, badChunk);
+
+        when(record1.getRootNode()).thenReturn(rootNode1);
+        when(record2.getRootNode()).thenReturn(rootNode2);
+        when(record3.getRootNode()).thenReturn(rootNode3);
+
+        
when(fileHeader.hasNext()).thenReturn(true).thenReturn(true).thenReturn(true).thenReturn(false);
+        
when(fileHeader.next()).thenThrow(malformedChunkException).thenReturn(chunkHeader1).thenReturn(chunkHeader2).thenReturn(null);
+
+        when(chunkHeader1.hasNext()).thenReturn(true).thenReturn(false);
+        when(chunkHeader1.next()).thenReturn(record1).thenReturn(null);
+
+        
when(chunkHeader2.hasNext()).thenReturn(true).thenReturn(true).thenReturn(false);
+        
when(chunkHeader2.next()).thenReturn(record2).thenReturn(record3).thenReturn(null);
+
+        parseEvtx.processFileGranularity(session, componentLog, flowFile, 
basename, reference, in, out);
+
+        verify(malformedChunkHandler).handle(flowFile, session, 
parseEvtx.getName(basename, chunkNum, null, ParseEvtx.EVTX_EXTENSION), 
badChunk);
+        verify(rootNodeHandler).handle(rootNode1);
+        verify(rootNodeHandler).handle(rootNode2);
+        verify(rootNodeHandler).handle(rootNode3);
+        verify(rootNodeHandler).close();
+    }
+
+    @Test
+    public void testProcessChunkGranularity() throws IOException, 
MalformedChunkException, XMLStreamException {
+        String basename = "basename";
+        int chunkNum = 5;
+        int offset = 10001;
+        byte[] badChunk = {8};
+
+        RootNodeHandler rootNodeHandler1 = mock(RootNodeHandler.class);
+        RootNodeHandler rootNodeHandler2 = mock(RootNodeHandler.class);
+        OutputStream out2 = mock(OutputStream.class);
+        when(rootNodeHandlerFactory.create(out)).thenReturn(rootNodeHandler1);
+        when(rootNodeHandlerFactory.create(out2)).thenReturn(rootNodeHandler2);
+        ChunkHeader chunkHeader1 = mock(ChunkHeader.class);
+        ChunkHeader chunkHeader2 = mock(ChunkHeader.class);
+        Record record1 = mock(Record.class);
+        Record record2 = mock(Record.class);
+        Record record3 = mock(Record.class);
+        RootNode rootNode1 = mock(RootNode.class);
+        RootNode rootNode2 = mock(RootNode.class);
+        RootNode rootNode3 = mock(RootNode.class);
+        ProcessSession session = mock(ProcessSession.class);
+        FlowFile flowFile = mock(FlowFile.class);
+        FlowFile created1 = mock(FlowFile.class);
+        FlowFile updated1 = mock(FlowFile.class);
+        FlowFile created2 = mock(FlowFile.class);
+        FlowFile updated2 = mock(FlowFile.class);
+        MalformedChunkException malformedChunkException = new 
MalformedChunkException("Test", null, offset, chunkNum, badChunk);
+
+        
when(session.create(flowFile)).thenReturn(created1).thenReturn(created2).thenReturn(null);
+
+        when(session.write(eq(created1), 
any(OutputStreamCallback.class))).thenAnswer(invocation -> {
+            ((OutputStreamCallback) invocation.getArguments()[1]).process(out);
+            return updated1;
+        });
+
+        when(session.write(eq(created2), 
any(OutputStreamCallback.class))).thenAnswer(invocation -> {
+            ((OutputStreamCallback) 
invocation.getArguments()[1]).process(out2);
+            return updated2;
+        });
+
+        when(record1.getRootNode()).thenReturn(rootNode1);
+        when(record2.getRootNode()).thenReturn(rootNode2);
+        when(record3.getRootNode()).thenReturn(rootNode3);
+
+        
when(fileHeader.hasNext()).thenReturn(true).thenReturn(true).thenReturn(true).thenReturn(false);
+        
when(fileHeader.next()).thenThrow(malformedChunkException).thenReturn(chunkHeader1).thenReturn(chunkHeader2).thenReturn(null);
+
+        when(chunkHeader1.hasNext()).thenReturn(true).thenReturn(false);
+        when(chunkHeader1.next()).thenReturn(record1).thenReturn(null);
+
+        
when(chunkHeader2.hasNext()).thenReturn(true).thenReturn(true).thenReturn(false);
+        
when(chunkHeader2.next()).thenReturn(record2).thenReturn(record3).thenReturn(null);
+
+        parseEvtx.processChunkGranularity(session, componentLog, flowFile, 
basename, in);
+
+        verify(malformedChunkHandler).handle(flowFile, session, 
parseEvtx.getName(basename, chunkNum, null, ParseEvtx.EVTX_EXTENSION), 
badChunk);
+        verify(rootNodeHandler1).handle(rootNode1);
+        verify(rootNodeHandler1).close();
+        verify(rootNodeHandler2).handle(rootNode2);
+        verify(rootNodeHandler2).handle(rootNode3);
+        verify(rootNodeHandler2).close();
+    }
+
+    @Test
+    public void testProcess1RecordGranularity() throws IOException, 
MalformedChunkException, XMLStreamException {
+        String basename = "basename";
+        int chunkNum = 5;
+        int offset = 10001;
+        byte[] badChunk = {8};
+
+        RootNodeHandler rootNodeHandler1 = mock(RootNodeHandler.class);
+        RootNodeHandler rootNodeHandler2 = mock(RootNodeHandler.class);
+        RootNodeHandler rootNodeHandler3 = mock(RootNodeHandler.class);
+        OutputStream out2 = mock(OutputStream.class);
+        OutputStream out3 = mock(OutputStream.class);
+        when(rootNodeHandlerFactory.create(out)).thenReturn(rootNodeHandler1);
+        when(rootNodeHandlerFactory.create(out2)).thenReturn(rootNodeHandler2);
+        when(rootNodeHandlerFactory.create(out3)).thenReturn(rootNodeHandler3);
+        ChunkHeader chunkHeader1 = mock(ChunkHeader.class);
+        ChunkHeader chunkHeader2 = mock(ChunkHeader.class);
+        Record record1 = mock(Record.class);
+        Record record2 = mock(Record.class);
+        Record record3 = mock(Record.class);
+        RootNode rootNode1 = mock(RootNode.class);
+        RootNode rootNode2 = mock(RootNode.class);
+        RootNode rootNode3 = mock(RootNode.class);
+        ProcessSession session = mock(ProcessSession.class);
+        FlowFile flowFile = mock(FlowFile.class);
+        FlowFile created1 = mock(FlowFile.class);
+        FlowFile updated1 = mock(FlowFile.class);
+        FlowFile created2 = mock(FlowFile.class);
+        FlowFile updated2 = mock(FlowFile.class);
+        FlowFile created3 = mock(FlowFile.class);
+        FlowFile updated3 = mock(FlowFile.class);
+        MalformedChunkException malformedChunkException = new 
MalformedChunkException("Test", null, offset, chunkNum, badChunk);
+
+        
when(session.create(flowFile)).thenReturn(created1).thenReturn(created2).thenReturn(created3).thenReturn(null);
+
+        when(session.write(eq(created1), 
any(OutputStreamCallback.class))).thenAnswer(invocation -> {
+            ((OutputStreamCallback) invocation.getArguments()[1]).process(out);
+            return updated1;
+        });
+
+        when(session.write(eq(created2), 
any(OutputStreamCallback.class))).thenAnswer(invocation -> {
+            ((OutputStreamCallback) 
invocation.getArguments()[1]).process(out2);
+            return updated2;
+        });
+
+        when(session.write(eq(created3), 
any(OutputStreamCallback.class))).thenAnswer(invocation -> {
+            ((OutputStreamCallback) 
invocation.getArguments()[1]).process(out3);
+            return updated3;
+        });
+
+        when(record1.getRootNode()).thenReturn(rootNode1);
+        when(record2.getRootNode()).thenReturn(rootNode2);
+        when(record3.getRootNode()).thenReturn(rootNode3);
+
+        
when(fileHeader.hasNext()).thenReturn(true).thenReturn(true).thenReturn(true).thenReturn(false);
+        
when(fileHeader.next()).thenThrow(malformedChunkException).thenReturn(chunkHeader1).thenReturn(chunkHeader2).thenReturn(null);
+
+        when(chunkHeader1.hasNext()).thenReturn(true).thenReturn(false);
+        when(chunkHeader1.next()).thenReturn(record1).thenReturn(null);
+
+        
when(chunkHeader2.hasNext()).thenReturn(true).thenReturn(true).thenReturn(false);
+        
when(chunkHeader2.next()).thenReturn(record2).thenReturn(record3).thenReturn(null);
+
+        parseEvtx.processRecordGranularity(session, componentLog, flowFile, 
basename, in);
+
+        verify(malformedChunkHandler).handle(flowFile, session, 
parseEvtx.getName(basename, chunkNum, null, ParseEvtx.EVTX_EXTENSION), 
badChunk);
+        verify(rootNodeHandler1).handle(rootNode1);
+        verify(rootNodeHandler1).close();
+        verify(rootNodeHandler2).handle(rootNode2);
+        verify(rootNodeHandler2).close();
+        verify(rootNodeHandler3).handle(rootNode3);
+        verify(rootNodeHandler3).close();
+    }
+
+    @Test
+    public void fileGranularityLifecycleTest() throws IOException, 
ParserConfigurationException, SAXException {
+        String baseName = "testFileName";
+        String name = baseName + ".evtx";
+        TestRunner testRunner = TestRunners.newTestRunner(ParseEvtx.class);
+        testRunner.setProperty(ParseEvtx.GRANULARITY, ParseEvtx.FILE);
+        Map<String, String> attributes = new HashMap<>();
+        attributes.put(CoreAttributes.FILENAME.key(), name);
+        
testRunner.enqueue(this.getClass().getClassLoader().getResourceAsStream("application-logs.evtx"),
 attributes);
+        testRunner.run();
+
+        List<MockFlowFile> originalFlowFiles = 
testRunner.getFlowFilesForRelationship(ParseEvtx.REL_ORIGINAL);
+        assertEquals(1, originalFlowFiles.size());
+        MockFlowFile originalFlowFile = originalFlowFiles.get(0);
+        originalFlowFile.assertAttributeEquals(CoreAttributes.FILENAME.key(), 
name);
+        
originalFlowFile.assertContentEquals(this.getClass().getClassLoader().getResourceAsStream("application-logs.evtx"));
+
+        // We expect the same bad chunks no matter the granularity
+        List<MockFlowFile> badChunkFlowFiles = 
testRunner.getFlowFilesForRelationship(ParseEvtx.REL_BAD_CHUNK);
+        assertEquals(2, badChunkFlowFiles.size());
+        
badChunkFlowFiles.get(0).assertAttributeEquals(CoreAttributes.FILENAME.key(), 
parseEvtx.getName(baseName, 1, null, ParseEvtx.EVTX_EXTENSION));
+        
badChunkFlowFiles.get(1).assertAttributeEquals(CoreAttributes.FILENAME.key(), 
parseEvtx.getName(baseName, 2, null, ParseEvtx.EVTX_EXTENSION));
+
+        List<MockFlowFile> failureFlowFiles = 
testRunner.getFlowFilesForRelationship(ParseEvtx.REL_FAILURE);
+        assertEquals(1, failureFlowFiles.size());
+        validateFlowFiles(failureFlowFiles);
+        // We expect the same number of records to come out no matter the 
granularity
+        assertEquals(960, validateFlowFiles(failureFlowFiles));
+
+        // Whole file fails if there is a failure parsing
+        List<MockFlowFile> successFlowFiles = 
testRunner.getFlowFilesForRelationship(ParseEvtx.REL_SUCCESS);
+        assertEquals(0, successFlowFiles.size());
+    }
+
+    @Test
+    public void chunkGranularityLifecycleTest() throws IOException, 
ParserConfigurationException, SAXException {
+        String baseName = "testFileName";
+        String name = baseName + ".evtx";
+        TestRunner testRunner = TestRunners.newTestRunner(ParseEvtx.class);
+        Map<String, String> attributes = new HashMap<>();
+        attributes.put(CoreAttributes.FILENAME.key(), name);
+        
testRunner.enqueue(this.getClass().getClassLoader().getResourceAsStream("application-logs.evtx"),
 attributes);
+        testRunner.run();
+
+        List<MockFlowFile> originalFlowFiles = 
testRunner.getFlowFilesForRelationship(ParseEvtx.REL_ORIGINAL);
+        assertEquals(1, originalFlowFiles.size());
+        MockFlowFile originalFlowFile = originalFlowFiles.get(0);
+        originalFlowFile.assertAttributeEquals(CoreAttributes.FILENAME.key(), 
name);
+        
originalFlowFile.assertContentEquals(this.getClass().getClassLoader().getResourceAsStream("application-logs.evtx"));
+
+        // We expect the same bad chunks no matter the granularity
+        List<MockFlowFile> badChunkFlowFiles = 
testRunner.getFlowFilesForRelationship(ParseEvtx.REL_BAD_CHUNK);
+        assertEquals(2, badChunkFlowFiles.size());
+        
badChunkFlowFiles.get(0).assertAttributeEquals(CoreAttributes.FILENAME.key(), 
parseEvtx.getName(baseName, 1, null, ParseEvtx.EVTX_EXTENSION));
+        
badChunkFlowFiles.get(1).assertAttributeEquals(CoreAttributes.FILENAME.key(), 
parseEvtx.getName(baseName, 2, null, ParseEvtx.EVTX_EXTENSION));
+
+        List<MockFlowFile> failureFlowFiles = 
testRunner.getFlowFilesForRelationship(ParseEvtx.REL_FAILURE);
+        assertEquals(1, failureFlowFiles.size());
+
+        List<MockFlowFile> successFlowFiles = 
testRunner.getFlowFilesForRelationship(ParseEvtx.REL_SUCCESS);
+        assertEquals(8, successFlowFiles.size());
+
+        // We expect the same number of records to come out no matter the 
granularity
+        assertEquals(960, validateFlowFiles(successFlowFiles) + 
validateFlowFiles(failureFlowFiles));
+    }
+
+    @Test
+    public void recordGranularityLifecycleTest() throws IOException, 
ParserConfigurationException, SAXException {
+        String baseName = "testFileName";
+        String name = baseName + ".evtx";
+        TestRunner testRunner = TestRunners.newTestRunner(ParseEvtx.class);
+        testRunner.setProperty(ParseEvtx.GRANULARITY, ParseEvtx.RECORD);
+        Map<String, String> attributes = new HashMap<>();
+        attributes.put(CoreAttributes.FILENAME.key(), name);
+        
testRunner.enqueue(this.getClass().getClassLoader().getResourceAsStream("application-logs.evtx"),
 attributes);
+        testRunner.run();
+
+        List<MockFlowFile> originalFlowFiles = 
testRunner.getFlowFilesForRelationship(ParseEvtx.REL_ORIGINAL);
+        assertEquals(1, originalFlowFiles.size());
+        MockFlowFile originalFlowFile = originalFlowFiles.get(0);
+        originalFlowFile.assertAttributeEquals(CoreAttributes.FILENAME.key(), 
name);
+        
originalFlowFile.assertContentEquals(this.getClass().getClassLoader().getResourceAsStream("application-logs.evtx"));
+
+        // We expect the same bad chunks no matter the granularity
+        List<MockFlowFile> badChunkFlowFiles = 
testRunner.getFlowFilesForRelationship(ParseEvtx.REL_BAD_CHUNK);
+        assertEquals(2, badChunkFlowFiles.size());
+        
badChunkFlowFiles.get(0).assertAttributeEquals(CoreAttributes.FILENAME.key(), 
parseEvtx.getName(baseName, 1, null, ParseEvtx.EVTX_EXTENSION));
+        
badChunkFlowFiles.get(1).assertAttributeEquals(CoreAttributes.FILENAME.key(), 
parseEvtx.getName(baseName, 2, null, ParseEvtx.EVTX_EXTENSION));
+
+        List<MockFlowFile> failureFlowFiles = 
testRunner.getFlowFilesForRelationship(ParseEvtx.REL_FAILURE);
+        assertEquals(0, failureFlowFiles.size());
+
+        // Whole file fails if there is a failure parsing
+        List<MockFlowFile> successFlowFiles = 
testRunner.getFlowFilesForRelationship(ParseEvtx.REL_SUCCESS);
+        assertEquals(960, successFlowFiles.size());
+
+        // We expect the same number of records to come out no matter the 
granularity
+        assertEquals(960, validateFlowFiles(successFlowFiles));
+    }
+
+    private int validateFlowFiles(List<MockFlowFile> successFlowFiles) throws 
SAXException, IOException, ParserConfigurationException {
+        assertTrue(successFlowFiles.size() > 0);
+        int totalSize = 0;
+        for (MockFlowFile successFlowFile : successFlowFiles) {
+            // Verify valid XML output
+            Document document = 
DOCUMENT_BUILDER_FACTORY.newDocumentBuilder().parse(new 
ByteArrayInputStream(successFlowFile.toByteArray()));
+            Element documentElement = document.getDocumentElement();
+            assertEquals(XmlRootNodeHandler.EVENTS, 
documentElement.getTagName());
+            NodeList eventNodes = documentElement.getChildNodes();
+            int length = eventNodes.getLength();
+            totalSize += length;
+            assertTrue(length > 0);
+            for (int i = 0; i < length; i++) {
+                Node eventNode = eventNodes.item(i);
+                assertEquals("Event", eventNode.getNodeName());
+
+                NodeList eventChildNodes = eventNode.getChildNodes();
+                assertEquals(2, eventChildNodes.getLength());
+
+                Node systemNode = eventChildNodes.item(0);
+                assertEquals("System", systemNode.getNodeName());
+
+                NodeList childNodes = systemNode.getChildNodes();
+                String userId = "";
+                for (int i1 = 0; i1 < childNodes.getLength(); i1++) {
+                    Node systemChild = childNodes.item(i1);
+                    if ("Security".equals(systemChild.getNodeName())) {
+                        userId = 
systemChild.getAttributes().getNamedItem("UserID").getNodeValue();
+                    }
+                }
+
+                Node eventDataNode = eventChildNodes.item(1);
+                String eventDataNodeNodeName = eventDataNode.getNodeName();
+                assertTrue(DATA_TAGS.contains(eventDataNodeNodeName));
+                assertTrue(userId.length() == 0 || userId.startsWith("S-"));
+            }
+        }
+        return totalSize;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/a5fecda5/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/ResultProcessorTest.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/ResultProcessorTest.java
 
b/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/ResultProcessorTest.java
new file mode 100644
index 0000000..117eade
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/ResultProcessorTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.evtx;
+
+import com.google.common.net.MediaType;
+import org.apache.nifi.flowfile.FlowFile;
+import org.apache.nifi.flowfile.attributes.CoreAttributes;
+import org.apache.nifi.logging.ComponentLog;
+import org.apache.nifi.processor.ProcessSession;
+import org.apache.nifi.processor.Relationship;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.runners.MockitoJUnitRunner;
+
+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.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ResultProcessorTest {
+    Relationship successRelationship;
+    Relationship failureRelationship;
+
+    ResultProcessor resultProcessor;
+
+    @Before
+    public void setup() {
+        successRelationship = new Relationship.Builder().build();
+        failureRelationship = new Relationship.Builder().build();
+        resultProcessor = new ResultProcessor(successRelationship, 
failureRelationship);
+    }
+
+    @Test
+    public void testProcessResultFileSuccess() {
+        ProcessSession processSession = mock(ProcessSession.class);
+        ComponentLog componentLog = mock(ComponentLog.class);
+        FlowFile flowFile = mock(FlowFile.class);
+        Exception exception = null;
+        String name = "basename";
+
+        when(processSession.putAttribute(eq(flowFile), anyString(), 
anyString())).thenReturn(flowFile);
+
+        resultProcessor.process(processSession, componentLog, flowFile, 
exception, name);
+        verify(processSession).putAttribute(flowFile, 
CoreAttributes.FILENAME.key(), name);
+        verify(processSession).putAttribute(flowFile, 
CoreAttributes.MIME_TYPE.key(), MediaType.APPLICATION_XML_UTF_8.toString());
+        verify(processSession).transfer(flowFile, successRelationship);
+        verifyNoMoreInteractions(componentLog);
+    }
+
+    @Test
+    public void testProcessResultFileFalure() {
+        ProcessSession processSession = mock(ProcessSession.class);
+        ComponentLog componentLog = mock(ComponentLog.class);
+        FlowFile flowFile = mock(FlowFile.class);
+        Exception exception = new Exception();
+        String name = "name";
+
+        when(processSession.putAttribute(eq(flowFile), anyString(), 
anyString())).thenReturn(flowFile);
+
+        resultProcessor.process(processSession, componentLog, flowFile, 
exception, name);
+        verify(processSession).putAttribute(flowFile, 
CoreAttributes.FILENAME.key(), name);
+        verify(processSession).putAttribute(flowFile, 
CoreAttributes.MIME_TYPE.key(), MediaType.APPLICATION_XML_UTF_8.toString());
+        verify(processSession).transfer(flowFile, failureRelationship);
+        
verify(componentLog).error(eq(ResultProcessor.UNABLE_TO_PROCESS_DUE_TO), 
any(Object[].class), eq(exception));
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/a5fecda5/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/XmlBxmlNodeVisitorTest.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/XmlBxmlNodeVisitorTest.java
 
b/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/XmlBxmlNodeVisitorTest.java
new file mode 100644
index 0000000..35c760f
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/XmlBxmlNodeVisitorTest.java
@@ -0,0 +1,325 @@
+/*
+ * 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.evtx;
+
+import org.apache.nifi.processors.evtx.parser.BxmlNodeVisitor;
+import org.apache.nifi.processors.evtx.parser.bxml.AttributeNode;
+import org.apache.nifi.processors.evtx.parser.bxml.BxmlNode;
+import org.apache.nifi.processors.evtx.parser.bxml.CDataSectionNode;
+import org.apache.nifi.processors.evtx.parser.bxml.ConditionalSubstitutionNode;
+import org.apache.nifi.processors.evtx.parser.bxml.EntityReferenceNode;
+import org.apache.nifi.processors.evtx.parser.bxml.NormalSubstitutionNode;
+import org.apache.nifi.processors.evtx.parser.bxml.OpenStartElementNode;
+import org.apache.nifi.processors.evtx.parser.bxml.RootNode;
+import org.apache.nifi.processors.evtx.parser.bxml.TemplateInstanceNode;
+import org.apache.nifi.processors.evtx.parser.bxml.TemplateNode;
+import org.apache.nifi.processors.evtx.parser.bxml.ValueNode;
+import org.apache.nifi.processors.evtx.parser.bxml.value.BXmlTypeNode;
+import org.apache.nifi.processors.evtx.parser.bxml.value.VariantTypeNode;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class XmlBxmlNodeVisitorTest {
+    @Mock
+    private XMLStreamWriter xmlStreamWriter;
+
+    @Mock
+    private RootNode rootNode;
+
+    @Mock
+    private BxmlNode bxmlNode;
+    private List<VariantTypeNode> substitutions;
+    private List<BxmlNode> children;
+    private XmlBxmlNodeVisitor xmlBxmlNodeVisitor;
+
+    @Before
+    public void setup() throws IOException {
+        substitutions = new ArrayList<>();
+        children = new ArrayList<>(Arrays.asList(bxmlNode));
+        when(rootNode.getSubstitutions()).thenReturn(substitutions);
+        when(rootNode.getChildren()).thenReturn(children);
+        xmlBxmlNodeVisitor = new XmlBxmlNodeVisitor(xmlStreamWriter, rootNode);
+    }
+
+    @Test
+    public void testConstructor() throws IOException {
+        verify(bxmlNode).accept(xmlBxmlNodeVisitor);
+    }
+
+    @Test
+    public void testVisitOpenStartElementNode() throws IOException, 
XMLStreamException {
+        String tagName = "open";
+        OpenStartElementNode openStartElementNode = 
mock(OpenStartElementNode.class);
+        AttributeNode attributeNode = mock(AttributeNode.class);
+        AttributeNode attributeNode2 = mock(AttributeNode.class);
+        BxmlNode bxmlNode = mock(BxmlNode.class);
+
+        when(openStartElementNode.getTagName()).thenReturn(tagName);
+        
when(openStartElementNode.getChildren()).thenReturn(Arrays.asList(attributeNode,
 bxmlNode, attributeNode2));
+
+        xmlBxmlNodeVisitor.visit(openStartElementNode);
+
+        InOrder inOrder = inOrder(xmlStreamWriter, attributeNode, 
attributeNode2, bxmlNode);
+        inOrder.verify(xmlStreamWriter).writeStartElement(tagName);
+        inOrder.verify(attributeNode).accept(xmlBxmlNodeVisitor);
+        inOrder.verify(attributeNode2).accept(xmlBxmlNodeVisitor);
+        inOrder.verify(bxmlNode).accept(xmlBxmlNodeVisitor);
+        inOrder.verify(xmlStreamWriter).writeEndElement();
+    }
+
+    @Test
+    public void testVisitAttributeNodeValueType() throws IOException, 
XMLStreamException {
+        String attributeName = "attributeName";
+
+        AttributeNode attributeNode = mock(AttributeNode.class);
+        ValueNode valueNode = mock(ValueNode.class);
+        BxmlNode child = mock(BxmlNode.class);
+
+        when(attributeNode.getAttributeName()).thenReturn(attributeName);
+        when(attributeNode.getValue()).thenReturn(valueNode);
+        when(valueNode.getChildren()).thenReturn(Arrays.asList(child));
+        doAnswer(invocation -> {
+            ((BxmlNodeVisitor) invocation.getArguments()[0]).visit(valueNode);
+            return null;
+        }).when(valueNode).accept(any(BxmlNodeVisitor.class));
+
+        xmlBxmlNodeVisitor.visit(attributeNode);
+
+        verify(xmlStreamWriter).writeAttribute(attributeName, null);
+        verify(child).accept(any(BxmlNodeVisitor.class));
+    }
+
+    @Test
+    public void testVisitAttributeNodeVariantType() throws IOException, 
XMLStreamException {
+        String attributeName = "attributeName";
+        String attributeValue = "attributeValue";
+
+        AttributeNode attributeNode = mock(AttributeNode.class);
+        VariantTypeNode variantTypeNode = mock(VariantTypeNode.class);
+
+        when(attributeNode.getAttributeName()).thenReturn(attributeName);
+        when(attributeNode.getValue()).thenReturn(variantTypeNode);
+        doAnswer(invocation -> {
+            ((BxmlNodeVisitor) 
invocation.getArguments()[0]).visit(variantTypeNode);
+            return null;
+        }).when(variantTypeNode).accept(any(BxmlNodeVisitor.class));
+        when(variantTypeNode.getValue()).thenReturn(attributeValue);
+
+        xmlBxmlNodeVisitor.visit(attributeNode);
+
+        verify(xmlStreamWriter).writeAttribute(attributeName, attributeValue);
+    }
+
+    @Test
+    public void testVisitAttributeNormalSubstitutionNode() throws IOException, 
XMLStreamException {
+        String attributeName = "attributeName";
+        String attributeValue = "attributeValue";
+
+        VariantTypeNode sub = mock(VariantTypeNode.class);
+        when(sub.getValue()).thenReturn(attributeValue);
+        substitutions.add(sub);
+
+        AttributeNode attributeNode = mock(AttributeNode.class);
+        NormalSubstitutionNode normalSubstitutionNode = 
mock(NormalSubstitutionNode.class);
+
+        when(attributeNode.getAttributeName()).thenReturn(attributeName);
+        when(attributeNode.getValue()).thenReturn(normalSubstitutionNode);
+        doAnswer(invocation -> {
+            ((BxmlNodeVisitor) 
invocation.getArguments()[0]).visit(normalSubstitutionNode);
+            return null;
+        }).when(normalSubstitutionNode).accept(any(BxmlNodeVisitor.class));
+        when(normalSubstitutionNode.getIndex()).thenReturn(0);
+
+        xmlBxmlNodeVisitor.visit(attributeNode);
+
+        verify(xmlStreamWriter).writeAttribute(attributeName, attributeValue);
+    }
+
+    @Test
+    public void testVisitAttributeConditionalSubstitutionNode() throws 
IOException, XMLStreamException {
+        String attributeName = "attributeName";
+        String attributeValue = "attributeValue";
+
+        VariantTypeNode sub = mock(VariantTypeNode.class);
+        when(sub.getValue()).thenReturn(attributeValue);
+        substitutions.add(sub);
+
+        AttributeNode attributeNode = mock(AttributeNode.class);
+        ConditionalSubstitutionNode conditionalSubstitutionNode = 
mock(ConditionalSubstitutionNode.class);
+
+        when(attributeNode.getAttributeName()).thenReturn(attributeName);
+        when(attributeNode.getValue()).thenReturn(conditionalSubstitutionNode);
+        doAnswer(invocation -> {
+            ((BxmlNodeVisitor) 
invocation.getArguments()[0]).visit(conditionalSubstitutionNode);
+            return null;
+        
}).when(conditionalSubstitutionNode).accept(any(BxmlNodeVisitor.class));
+        when(conditionalSubstitutionNode.getIndex()).thenReturn(0);
+
+        xmlBxmlNodeVisitor.visit(attributeNode);
+
+        verify(xmlStreamWriter).writeAttribute(attributeName, attributeValue);
+    }
+
+    @Test
+    public void testVisitTemplateInstanceNode() throws IOException {
+        TemplateInstanceNode templateInstanceNode = 
mock(TemplateInstanceNode.class);
+        TemplateNode templateNode = mock(TemplateNode.class);
+
+        when(templateInstanceNode.getTemplateNode()).thenReturn(templateNode);
+
+        xmlBxmlNodeVisitor.visit(templateInstanceNode);
+        verify(templateNode).accept(xmlBxmlNodeVisitor);
+    }
+
+    @Test
+    public void testVisitTemplateNode() throws IOException {
+        TemplateNode templateNode = mock(TemplateNode.class);
+        BxmlNode child = mock(BxmlNode.class);
+
+        when(templateNode.getChildren()).thenReturn(Arrays.asList(child));
+
+        xmlBxmlNodeVisitor.visit(templateNode);
+
+        verify(child).accept(xmlBxmlNodeVisitor);
+    }
+
+    @Test
+    public void testVisitCDataSectionNode() throws IOException, 
XMLStreamException {
+        String cdata = "cdata";
+        CDataSectionNode cDataSectionNode = mock(CDataSectionNode.class);
+
+        when(cDataSectionNode.getCdata()).thenReturn(cdata);
+
+        xmlBxmlNodeVisitor.visit(cDataSectionNode);
+
+        verify(xmlStreamWriter).writeCData(cdata);
+    }
+
+    @Test
+    public void testVisitEntityReferenceNode() throws IOException, 
XMLStreamException {
+        String value = "value";
+        EntityReferenceNode entityReferenceNode = 
mock(EntityReferenceNode.class);
+
+        when(entityReferenceNode.getValue()).thenReturn(value);
+
+        xmlBxmlNodeVisitor.visit(entityReferenceNode);
+
+        verify(xmlStreamWriter).writeCharacters(value);
+    }
+
+    @Test
+    public void testVisitValueNode() throws IOException {
+        ValueNode valueNode = mock(ValueNode.class);
+        BxmlNode child = mock(BxmlNode.class);
+
+        when(valueNode.getChildren()).thenReturn(Arrays.asList(child));
+
+        xmlBxmlNodeVisitor.visit(valueNode);
+
+        verify(child).accept(xmlBxmlNodeVisitor);
+    }
+
+    @Test
+    public void testVisitConditionalSubstitutionNode() throws IOException {
+        ConditionalSubstitutionNode conditionalSubstitutionNode = 
mock(ConditionalSubstitutionNode.class);
+        VariantTypeNode sub = mock(VariantTypeNode.class);
+
+        substitutions.add(sub);
+        when(conditionalSubstitutionNode.getIndex()).thenReturn(0);
+
+        xmlBxmlNodeVisitor.visit(conditionalSubstitutionNode);
+
+        verify(sub).accept(xmlBxmlNodeVisitor);
+    }
+
+    @Test
+    public void testVisitNormalSubstitutionNode() throws IOException {
+        NormalSubstitutionNode normalSubstitutionNode = 
mock(NormalSubstitutionNode.class);
+        VariantTypeNode sub = mock(VariantTypeNode.class);
+
+        substitutions.add(sub);
+        when(normalSubstitutionNode.getIndex()).thenReturn(0);
+
+        xmlBxmlNodeVisitor.visit(normalSubstitutionNode);
+
+        verify(sub).accept(xmlBxmlNodeVisitor);
+    }
+
+    @Test
+    public void testVisitBxmlTypeNode() throws IOException {
+        BXmlTypeNode bXmlTypeNode = mock(BXmlTypeNode.class);
+        RootNode rootNode = mock(RootNode.class);
+
+        when(bXmlTypeNode.getRootNode()).thenReturn(rootNode);
+
+        xmlBxmlNodeVisitor.visit(bXmlTypeNode);
+
+        verify(rootNode).accept(xmlBxmlNodeVisitor);
+    }
+
+    @Test
+    public void testVisitVariantTypeNode() throws IOException, 
XMLStreamException {
+        String variantValue = "variantValue";
+        VariantTypeNode variantTypeNode = mock(VariantTypeNode.class);
+
+        when(variantTypeNode.getValue()).thenReturn(variantValue);
+
+        xmlBxmlNodeVisitor.visit(variantTypeNode);
+
+        verify(xmlStreamWriter).writeCharacters(variantValue);
+    }
+
+    @Test
+    public void testVisitRootNode() throws IOException {
+        RootNode rootNode = mock(RootNode.class);
+        BxmlNode child = mock(BxmlNode.class);
+
+        when(rootNode.getChildren()).thenReturn(Arrays.asList(child));
+
+        xmlBxmlNodeVisitor.visit(rootNode);
+
+        ArgumentCaptor<BxmlNodeVisitor> captor = 
ArgumentCaptor.forClass(BxmlNodeVisitor.class);
+        verify(child).accept(captor.capture());
+
+        BxmlNodeVisitor value = captor.getValue();
+        assertTrue(value instanceof XmlBxmlNodeVisitor);
+        assertNotEquals(xmlBxmlNodeVisitor, value);
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/a5fecda5/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/XmlRootNodeHandlerTest.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/XmlRootNodeHandlerTest.java
 
b/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/XmlRootNodeHandlerTest.java
new file mode 100644
index 0000000..07a1f86
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/XmlRootNodeHandlerTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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.evtx;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import java.io.IOException;
+
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+@RunWith(MockitoJUnitRunner.class)
+public class XmlRootNodeHandlerTest {
+    @Mock
+    XMLStreamWriter xmlStreamWriter;
+
+    @Mock
+    XmlBxmlNodeVisitorFactory xmlBxmlNodeVisitorFactory;
+
+    XmlRootNodeHandler xmlRootNodeHandler;
+
+    @Before
+    public void setup() throws IOException {
+        xmlRootNodeHandler = new XmlRootNodeHandler(xmlStreamWriter, 
xmlBxmlNodeVisitorFactory);
+    }
+
+    @Test
+    public void testConstructor() throws XMLStreamException {
+        verify(xmlStreamWriter).writeStartDocument();
+        verify(xmlStreamWriter).writeStartElement(XmlRootNodeHandler.EVENTS);
+    }
+
+    @Test(expected = IOException.class)
+    public void testConstructorException() throws XMLStreamException, 
IOException {
+        xmlStreamWriter = mock(XMLStreamWriter.class);
+        doThrow(new 
XMLStreamException()).when(xmlStreamWriter).writeStartElement(XmlRootNodeHandler.EVENTS);
+        new XmlRootNodeHandler(xmlStreamWriter, xmlBxmlNodeVisitorFactory);
+    }
+
+    @Test
+    public void testClose() throws IOException, XMLStreamException {
+        xmlRootNodeHandler.close();
+        verify(xmlStreamWriter).writeEndElement();
+        verify(xmlStreamWriter).close();
+    }
+
+    @Test(expected = IOException.class)
+    public void testCloseException() throws IOException, XMLStreamException {
+        doThrow(new XMLStreamException()).when(xmlStreamWriter).close();
+        xmlRootNodeHandler.close();
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/a5fecda5/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/parser/BinaryReaderTest.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/parser/BinaryReaderTest.java
 
b/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/parser/BinaryReaderTest.java
new file mode 100644
index 0000000..10d403c
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/parser/BinaryReaderTest.java
@@ -0,0 +1,208 @@
+/*
+ * 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.evtx.parser;
+
+import com.google.common.base.Charsets;
+import com.google.common.primitives.UnsignedInteger;
+import com.google.common.primitives.UnsignedLong;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.Date;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+public class BinaryReaderTest {
+    private TestBinaryReaderBuilder testBinaryReaderBuilder;
+
+    @Before
+    public void setup() {
+        testBinaryReaderBuilder = new TestBinaryReaderBuilder();
+    }
+
+    @Test
+    public void testRead() throws IOException {
+        byte b = 0x23;
+        BinaryReader binaryReader = testBinaryReaderBuilder.put(b).build();
+        assertEquals(b, binaryReader.read());
+        assertEquals(1, binaryReader.getPosition());
+    }
+
+    @Test
+    public void testPeek() throws IOException {
+        byte b = 0x23;
+        BinaryReader binaryReader = testBinaryReaderBuilder.put(new 
byte[]{b}).build();
+        assertEquals(b, binaryReader.peek());
+        assertEquals(0, binaryReader.getPosition());
+    }
+
+    @Test
+    public void testReadBytesJustLength() throws IOException {
+        byte[] bytes = "Hello world".getBytes(Charsets.US_ASCII);
+        BinaryReader binaryReader = testBinaryReaderBuilder.put(bytes).build();
+        assertArrayEquals(Arrays.copyOfRange(bytes, 0, 5), 
binaryReader.readBytes(5));
+        assertEquals(5, binaryReader.getPosition());
+    }
+
+    @Test
+    public void testPeekBytes() throws IOException {
+        byte[] bytes = "Hello world".getBytes(Charsets.US_ASCII);
+        BinaryReader binaryReader = testBinaryReaderBuilder.put(bytes).build();
+        assertArrayEquals(Arrays.copyOfRange(bytes, 0, 5), 
binaryReader.peekBytes(5));
+        assertEquals(0, binaryReader.getPosition());
+    }
+
+    @Test
+    public void testReadBytesBufOffsetLength() throws IOException {
+        byte[] bytes = "Hello world".getBytes(Charsets.US_ASCII);
+        byte[] buf = new byte[5];
+
+        BinaryReader binaryReader = testBinaryReaderBuilder.put(bytes).build();
+        binaryReader.readBytes(buf, 0, 5);
+        assertArrayEquals(Arrays.copyOfRange(bytes, 0, 5), buf);
+        assertEquals(5, binaryReader.getPosition());
+    }
+
+    @Test
+    public void testReadGuid() throws IOException {
+        String guid = "33323130-3534-3736-3839-616263646566";
+        BinaryReader binaryReader = 
testBinaryReaderBuilder.putGuid(guid).build();
+        assertEquals(guid, binaryReader.readGuid());
+        assertEquals(16, binaryReader.getPosition());
+    }
+
+    @Test(expected = IOException.class)
+    public void testReadStringNotNullTerminated() throws IOException {
+        String value = "Hello world";
+
+        BinaryReader binaryReader = 
testBinaryReaderBuilder.put(value.getBytes(Charsets.US_ASCII)).build();
+        binaryReader.readString(value.length());
+    }
+
+    @Test
+    public void testReadString() throws IOException {
+        String value = "Hello world";
+
+        BinaryReader binaryReader = 
testBinaryReaderBuilder.putString(value).build();
+        assertEquals(value, binaryReader.readString(value.length() + 1));
+        assertEquals(value.length() + 1, binaryReader.getPosition());
+    }
+
+    @Test
+    public void testReadWString() throws IOException {
+        String value = "Hello world";
+        BinaryReader binaryReader = 
testBinaryReaderBuilder.putWString(value).build();
+
+        assertEquals(value, binaryReader.readWString(value.length()));
+        assertEquals(value.length() * 2, binaryReader.getPosition());
+    }
+
+    @Test
+    public void testReadQWord() throws IOException {
+        UnsignedLong longValue = UnsignedLong.fromLongBits(Long.MAX_VALUE + 
500);
+        BinaryReader binaryReader = 
testBinaryReaderBuilder.putQWord(longValue).build();
+
+        assertEquals(longValue, binaryReader.readQWord());
+        assertEquals(8, binaryReader.getPosition());
+    }
+
+    @Test
+    public void testReadDWord() throws IOException {
+        UnsignedInteger intValue = 
UnsignedInteger.fromIntBits(Integer.MAX_VALUE + 500);
+        BinaryReader binaryReader = 
testBinaryReaderBuilder.putDWord(intValue).build();
+
+        assertEquals(intValue, binaryReader.readDWord());
+        assertEquals(4, binaryReader.getPosition());
+    }
+
+    @Test
+    public void testReadDWordBE() throws IOException {
+        UnsignedInteger intValue = 
UnsignedInteger.fromIntBits(Integer.MAX_VALUE + 500);
+        BinaryReader binaryReader = 
testBinaryReaderBuilder.putDWordBE(intValue).build();
+
+        assertEquals(intValue, binaryReader.readDWordBE());
+        assertEquals(4, binaryReader.getPosition());
+    }
+
+    @Test
+    public void testReadWord() throws IOException {
+        int intValue = Short.MAX_VALUE + 500;
+        BinaryReader binaryReader = 
testBinaryReaderBuilder.putWord(intValue).build();
+
+        assertEquals(intValue, binaryReader.readWord());
+        assertEquals(2, binaryReader.getPosition());
+    }
+
+    @Test
+    public void testReadWordBE() throws IOException {
+        int intValue = Short.MAX_VALUE + 500;
+        BinaryReader binaryReader = 
testBinaryReaderBuilder.putWordBE(intValue).build();
+
+        assertEquals(intValue, binaryReader.readWordBE());
+        assertEquals(2, binaryReader.getPosition());
+    }
+
+    @Test
+    public void testReadFileTIme() throws IOException {
+        Date date = new Date();
+        BinaryReader binaryReader = 
testBinaryReaderBuilder.putFileTime(date).build();
+
+        assertEquals(date.getTime(), binaryReader.readFileTime().getTime());
+        assertEquals(8, binaryReader.getPosition());
+    }
+
+    @Test
+    public void testReadAndBase64EncodeBinary() throws IOException {
+        String orig = "Hello World";
+        String stringValue = 
Base64.getEncoder().encodeToString(orig.getBytes(Charsets.US_ASCII));
+        BinaryReader binaryReader = 
testBinaryReaderBuilder.putBase64EncodedBinary(stringValue).build();
+
+        assertEquals(stringValue, 
binaryReader.readAndBase64EncodeBinary(orig.length()));
+        assertEquals(orig.length(), binaryReader.getPosition());
+    }
+
+    @Test
+    public void testSkip() throws IOException {
+        BinaryReader binaryReader = new BinaryReader(null);
+        binaryReader.skip(10);
+        assertEquals(10, binaryReader.getPosition());
+    }
+
+    @Test
+    public void testReaderPositionConstructor() throws IOException {
+        String value = "Hello world";
+        BinaryReader binaryReader = new BinaryReader(new 
BinaryReader(value.getBytes(Charsets.UTF_16LE)), 2);
+
+        assertEquals(value.substring(1), 
binaryReader.readWString(value.length() - 1));
+        assertEquals(value.length() * 2, binaryReader.getPosition());
+    }
+
+    @Test
+    public void testInputStreamSizeConstructor() throws IOException {
+        String value = "Hello world";
+        BinaryReader binaryReader = new BinaryReader(new 
ByteArrayInputStream(value.getBytes(Charsets.UTF_16LE)), 10);
+
+        assertEquals(value.substring(0, 5), binaryReader.readWString(5));
+        assertEquals(10, binaryReader.getPosition());
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/a5fecda5/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/parser/ChunkHeaderTest.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/parser/ChunkHeaderTest.java
 
b/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/parser/ChunkHeaderTest.java
new file mode 100644
index 0000000..1cb52db
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/parser/ChunkHeaderTest.java
@@ -0,0 +1,145 @@
+/*
+ * 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.evtx.parser;
+
+import com.google.common.primitives.UnsignedInteger;
+import org.apache.nifi.logging.ComponentLog;
+import org.apache.nifi.processors.evtx.parser.bxml.BxmlNode;
+import org.apache.nifi.processors.evtx.parser.bxml.EndOfStreamNode;
+import org.apache.nifi.processors.evtx.parser.bxml.NameStringNode;
+import org.apache.nifi.processors.evtx.parser.bxml.NameStringNodeTest;
+import org.apache.nifi.processors.evtx.parser.bxml.RootNode;
+import org.apache.nifi.processors.evtx.parser.bxml.TemplateNode;
+import org.apache.nifi.processors.evtx.parser.bxml.TemplateNodeTest;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.TreeMap;
+import java.util.zip.CRC32;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+public class ChunkHeaderTest {
+    private final Random random = new Random();
+    private int headerOffset = 101;
+    private int chunkNumber = 102;
+    private ChunkHeader chunkHeader;
+    private int fileFirstRecordNumber = 103;
+    private int fileLastRecordNumber = 105;
+    private int logFirstRecordNumber = 106;
+    private int logLastRecordNumber = 107;
+    private int headerSize = 112;
+    private int lastRecordOffset = 12380;
+    private int nextRecordOffset = 2380;
+    private int dataChecksum = 1111;
+    private List<String> guids;
+
+    @Before
+    public void setup() throws IOException {
+        TestBinaryReaderBuilder testBinaryReaderBuilder = new 
TestBinaryReaderBuilder();
+        testBinaryReaderBuilder.putString(ChunkHeader.ELF_CHNK);
+        testBinaryReaderBuilder.putQWord(fileFirstRecordNumber);
+        testBinaryReaderBuilder.putQWord(fileLastRecordNumber);
+        testBinaryReaderBuilder.putQWord(logFirstRecordNumber);
+        testBinaryReaderBuilder.putQWord(logLastRecordNumber);
+        testBinaryReaderBuilder.putDWord(headerSize);
+        testBinaryReaderBuilder.putDWord(lastRecordOffset);
+        testBinaryReaderBuilder.putDWord(nextRecordOffset);
+        testBinaryReaderBuilder.putDWord(dataChecksum);
+        byte[] bytes = new byte[67];
+        random.nextBytes(bytes);
+        testBinaryReaderBuilder.put(bytes);
+        testBinaryReaderBuilder.put((byte) 0);
+
+        // Checksum placeholder
+        testBinaryReaderBuilder.putDWord(0);
+
+        TestBinaryReaderBuilder dataBuilder = new TestBinaryReaderBuilder();
+        int offset = 545;
+        for (int i = 0; i < 64; i++) {
+            String string = Integer.toString(i);
+            testBinaryReaderBuilder.putDWord(offset);
+            offset += NameStringNodeTest.putNode(dataBuilder, 0, 
string.hashCode(), string);
+        }
+
+        guids = new ArrayList<>();
+        for (int i = 0; i < 32; i++) {
+            testBinaryReaderBuilder.putDWord(offset + 10);
+            dataBuilder.put((byte) 0x0C);
+            dataBuilder.put(new byte[5]);
+            dataBuilder.putDWord(offset + 10);
+            byte[] guidBytes = new byte[16];
+            random.nextBytes(guidBytes);
+            String guid = new 
TestBinaryReaderBuilder().put(guidBytes).build().readGuid();
+            guids.add(guid);
+            offset += TemplateNodeTest.putNode(dataBuilder, 0, guid, i);
+            dataBuilder.put((byte) BxmlNode.END_OF_STREAM_TOKEN);
+            offset += 11;
+        }
+
+        RecordTest.putNode(testBinaryReaderBuilder, fileLastRecordNumber, new 
Date());
+
+        testBinaryReaderBuilder.put(dataBuilder.toByteArray());
+
+        CRC32 dataChecksum = new CRC32();
+        dataChecksum.update(testBinaryReaderBuilder.toByteArray(), 512, 
nextRecordOffset - 512);
+        
testBinaryReaderBuilder.putDWordAt(UnsignedInteger.valueOf(dataChecksum.getValue()).intValue(),
 52);
+
+        CRC32 headerChecksum = new CRC32();
+        byte[] array = testBinaryReaderBuilder.toByteArray();
+        headerChecksum.update(array, 0, 120);
+        headerChecksum.update(array, 128, 384);
+        
testBinaryReaderBuilder.putDWordAt(UnsignedInteger.valueOf(headerChecksum.getValue()).intValue(),
 124);
+        chunkHeader = new ChunkHeader(testBinaryReaderBuilder.build(), 
mock(ComponentLog.class), headerOffset, chunkNumber);
+    }
+
+    @Test
+    public void testInit() throws IOException {
+        int count = 0;
+        for (Map.Entry<Integer, NameStringNode> integerNameStringNodeEntry : 
new TreeMap<>(chunkHeader.getNameStrings()).entrySet()) {
+            assertEquals(Integer.toString(count++), 
integerNameStringNodeEntry.getValue().getString());
+        }
+
+        Iterator<String> iterator = guids.iterator();
+        for (Map.Entry<Integer, TemplateNode> integerTemplateNodeEntry : new 
TreeMap<>(chunkHeader.getTemplateNodes()).entrySet()) {
+            assertEquals(iterator.next(), 
integerTemplateNodeEntry.getValue().getGuid());
+        }
+
+        assertTrue(chunkHeader.hasNext());
+
+        Record next = chunkHeader.next();
+        assertEquals(fileLastRecordNumber, next.getRecordNum().intValue());
+        RootNode rootNode = next.getRootNode();
+        List<BxmlNode> children = rootNode.getChildren();
+        assertEquals(1, children.size());
+        assertTrue(children.get(0) instanceof EndOfStreamNode);
+        assertEquals(0, rootNode.getSubstitutions().size());
+
+        assertFalse(chunkHeader.hasNext());
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/a5fecda5/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/parser/FileHeaderTest.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/parser/FileHeaderTest.java
 
b/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/parser/FileHeaderTest.java
new file mode 100644
index 0000000..7a07981
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/parser/FileHeaderTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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.evtx.parser;
+
+import com.google.common.primitives.UnsignedInteger;
+import org.apache.nifi.logging.ComponentLog;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.Random;
+import java.util.zip.CRC32;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+
+public class FileHeaderTest {
+    private final Random random = new Random();
+    private FileHeader fileHeader;
+    private int oldestChunk = 111;
+    private int currentChunkNumber = 112;
+    private int nextRecordNumber = 113;
+    private int headerSize = 114;
+    private int minorVersion = 1;
+    private int majorVersion = 3;
+    private int headerChunkSize = 4096;
+    private int chunkCount = 0;
+    private int flags = 340942;
+
+    @Before
+    public void setup() throws IOException {
+        TestBinaryReaderBuilder testBinaryReaderBuilder = new 
TestBinaryReaderBuilder();
+        testBinaryReaderBuilder.putString(FileHeader.ELF_FILE);
+        testBinaryReaderBuilder.putQWord(oldestChunk);
+        testBinaryReaderBuilder.putQWord(currentChunkNumber);
+        testBinaryReaderBuilder.putQWord(nextRecordNumber);
+        testBinaryReaderBuilder.putDWord(headerSize);
+        testBinaryReaderBuilder.putWord(minorVersion);
+        testBinaryReaderBuilder.putWord(majorVersion);
+        testBinaryReaderBuilder.putWord(headerChunkSize);
+        testBinaryReaderBuilder.putWord(chunkCount);
+        byte[] unused = new byte[75];
+        random.nextBytes(unused);
+        testBinaryReaderBuilder.put(unused);
+        testBinaryReaderBuilder.put((byte) 0);
+
+        CRC32 crc32 = new CRC32();
+        crc32.update(testBinaryReaderBuilder.toByteArray());
+
+        testBinaryReaderBuilder.putDWord(flags);
+        
testBinaryReaderBuilder.putDWord(UnsignedInteger.valueOf(crc32.getValue()));
+
+        fileHeader = new FileHeader(new 
ByteArrayInputStream(testBinaryReaderBuilder.toByteArray(4096)), 
mock(ComponentLog.class));
+    }
+
+    @Test
+    public void testInit() {
+        assertEquals(FileHeader.ELF_FILE, fileHeader.getMagicString());
+        assertEquals(oldestChunk, fileHeader.getOldestChunk().intValue());
+        assertEquals(currentChunkNumber, 
fileHeader.getCurrentChunkNumber().intValue());
+        assertEquals(nextRecordNumber, 
fileHeader.getNextRecordNumber().intValue());
+        assertEquals(headerSize, fileHeader.getHeaderSize().intValue());
+        assertEquals(minorVersion, fileHeader.getMinorVersion());
+        assertEquals(majorVersion, fileHeader.getMajorVersion());
+        assertEquals(headerChunkSize, fileHeader.getHeaderChunkSize());
+        assertEquals(chunkCount, fileHeader.getChunkCount());
+        assertEquals(flags, fileHeader.getFlags().intValue());
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/a5fecda5/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/parser/RecordTest.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/parser/RecordTest.java
 
b/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/parser/RecordTest.java
new file mode 100644
index 0000000..4d4996f
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/parser/RecordTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.evtx.parser;
+
+import org.apache.nifi.processors.evtx.parser.bxml.BxmlNode;
+import org.apache.nifi.processors.evtx.parser.bxml.EndOfStreamNode;
+import org.apache.nifi.processors.evtx.parser.bxml.RootNode;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import java.io.IOException;
+import java.util.Date;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(MockitoJUnitRunner.class)
+public class RecordTest {
+    @Mock
+    ChunkHeader chunkHeader;
+
+    private Record record;
+    private int recordNum = 120;
+
+    public static void putNode(TestBinaryReaderBuilder 
testBinaryReaderBuilder, int recordNum, Date fileTime) {
+        testBinaryReaderBuilder.putDWord(10794);
+        int size = 20;
+        testBinaryReaderBuilder.putDWord(size);
+        testBinaryReaderBuilder.putQWord(recordNum);
+        testBinaryReaderBuilder.putFileTime(new Date());
+
+        testBinaryReaderBuilder.put((byte) BxmlNode.END_OF_STREAM_TOKEN);
+        testBinaryReaderBuilder.putDWord(0);
+        testBinaryReaderBuilder.putDWord(size);
+    }
+
+    @Before
+    public void setup() throws IOException {
+        TestBinaryReaderBuilder testBinaryReaderBuilder = new 
TestBinaryReaderBuilder();
+        putNode(testBinaryReaderBuilder, recordNum, new Date());
+
+        record = new Record(testBinaryReaderBuilder.build(), chunkHeader);
+    }
+
+    @Test
+    public void testInit() {
+        assertEquals(recordNum, record.getRecordNum().intValue());
+        RootNode rootNode = record.getRootNode();
+        List<BxmlNode> children = rootNode.getChildren();
+        assertEquals(1, children.size());
+        assertTrue(children.get(0) instanceof EndOfStreamNode);
+        assertEquals(0, rootNode.getSubstitutions().size());
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/a5fecda5/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/parser/TestBinaryReaderBuilder.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/parser/TestBinaryReaderBuilder.java
 
b/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/parser/TestBinaryReaderBuilder.java
new file mode 100644
index 0000000..8230fa4
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/parser/TestBinaryReaderBuilder.java
@@ -0,0 +1,168 @@
+/*
+ * 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.evtx.parser;
+
+import com.google.common.base.Charsets;
+import com.google.common.primitives.UnsignedInteger;
+import com.google.common.primitives.UnsignedLong;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+public class TestBinaryReaderBuilder {
+    private final List<byte[]> data = new ArrayList<>();
+
+    public TestBinaryReaderBuilder put(byte val) {
+        data.add(new byte[]{val});
+        return this;
+    }
+
+    public TestBinaryReaderBuilder put(byte[] bytes) {
+        data.add(bytes);
+        return this;
+    }
+
+    public TestBinaryReaderBuilder putGuid(String guid) {
+        byte[] bytes = new byte[16];
+        String[] split = guid.split("-");
+        int count = 0;
+        int offset = 0;
+        int[][] indexArrays = BinaryReader.INDEX_ARRAYS;
+        for (int i = 0; i < indexArrays.length; i++) {
+            String segment = split[i];
+            for (int o = 0; o < indexArrays[i].length; o++) {
+                int beginIndex = (indexArrays[i][o] * 2) - offset;
+                bytes[count++] = (byte) 
Integer.parseInt(segment.substring(beginIndex, beginIndex + 2), 16);
+            }
+            offset += segment.length();
+        }
+        put(bytes);
+        return this;
+    }
+
+    public TestBinaryReaderBuilder putString(String val) {
+        data.add(val.getBytes(Charsets.US_ASCII));
+        data.add(new byte[]{0});
+        return this;
+    }
+
+    public TestBinaryReaderBuilder putWString(String val) {
+        data.add(val.getBytes(Charsets.UTF_16LE));
+        return this;
+    }
+
+    public TestBinaryReaderBuilder putQWord(long longBits) {
+        data.add(ByteBuffer.wrap(new 
byte[8]).order(ByteOrder.LITTLE_ENDIAN).putLong(longBits).array());
+        return this;
+    }
+
+    public TestBinaryReaderBuilder putQWord(UnsignedLong val) {
+        return putQWord(val.longValue());
+    }
+
+    public TestBinaryReaderBuilder putDWord(int intBits) {
+        data.add(ByteBuffer.wrap(new 
byte[4]).order(ByteOrder.LITTLE_ENDIAN).putInt(intBits).array());
+        return this;
+    }
+
+    public TestBinaryReaderBuilder putDWordAt(int intBits, int position) 
throws IOException {
+        ByteBuffer.wrap(toByteArray(), position, 
4).order(ByteOrder.LITTLE_ENDIAN).putInt(intBits);
+        return this;
+    }
+
+    public TestBinaryReaderBuilder putDWord(UnsignedInteger val) {
+        return putDWord(val.intValue());
+    }
+
+    public TestBinaryReaderBuilder putDWordBE(int intBits) {
+        data.add(ByteBuffer.wrap(new 
byte[4]).order(ByteOrder.BIG_ENDIAN).putInt(intBits).array());
+        return this;
+    }
+
+    public TestBinaryReaderBuilder putDWordBE(UnsignedInteger val) {
+        return putDWordBE(val.intValue());
+    }
+
+    public TestBinaryReaderBuilder putWord(int val) {
+        data.add(ByteBuffer.wrap(new 
byte[2]).order(ByteOrder.LITTLE_ENDIAN).putShort((short) val).array());
+        return this;
+    }
+
+    public TestBinaryReaderBuilder putWordBE(int val) {
+        data.add(ByteBuffer.wrap(new 
byte[2]).order(ByteOrder.BIG_ENDIAN).putShort((short) val).array());
+        return this;
+    }
+
+    public TestBinaryReaderBuilder putFileTime(Date date) {
+        UnsignedLong javaMillis = UnsignedLong.valueOf(date.getTime());
+        UnsignedLong windowsMillis = 
javaMillis.plus(UnsignedLong.valueOf(BinaryReader.EPOCH_OFFSET));
+        UnsignedLong windowsStamp = 
windowsMillis.times(UnsignedLong.valueOf(10000));
+        return putQWord(windowsStamp);
+    }
+
+    public TestBinaryReaderBuilder putSystemtime(Calendar calendar) {
+        putWord(calendar.get(Calendar.YEAR));
+        putWord(calendar.get(Calendar.MONTH));
+        putWord(calendar.get(Calendar.DAY_OF_WEEK));
+        putWord(calendar.get(Calendar.DAY_OF_MONTH));
+        putWord(calendar.get(Calendar.HOUR_OF_DAY));
+        putWord(calendar.get(Calendar.MINUTE));
+        putWord(calendar.get(Calendar.SECOND));
+        putWord(calendar.get(Calendar.MILLISECOND));
+        return this;
+    }
+
+    public TestBinaryReaderBuilder putBase64EncodedBinary(String 
base64EncodedBinary) {
+        return put(Base64.getDecoder().decode(base64EncodedBinary));
+    }
+
+    public BinaryReader build() throws IOException {
+        return new BinaryReader(toByteArray());
+    }
+
+    public byte[] toByteArray() throws IOException {
+        if (data.size() == 0) {
+            return new byte[0];
+        } else if (data.size() == 1) {
+            return data.get(0);
+        } else {
+            ByteArrayOutputStream byteArrayOutputStream = new 
ByteArrayOutputStream();
+            for (byte[] bytes : data) {
+                byteArrayOutputStream.write(bytes);
+            }
+            byte[] bytes = byteArrayOutputStream.toByteArray();
+            data.clear();
+            data.add(bytes);
+            return bytes;
+        }
+    }
+
+    public byte[] toByteArray(int size) throws IOException {
+        byte[] bytes = toByteArray();
+        byte[] result = new byte[size];
+        System.arraycopy(bytes, 0, result, 0, Math.min(bytes.length, 
result.length));
+        return result;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/a5fecda5/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/parser/bxml/AttributeNodeTest.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/parser/bxml/AttributeNodeTest.java
 
b/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/parser/bxml/AttributeNodeTest.java
new file mode 100644
index 0000000..d6eb07d
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/parser/bxml/AttributeNodeTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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.evtx.parser.bxml;
+
+import org.apache.nifi.processors.evtx.parser.BinaryReader;
+import org.apache.nifi.processors.evtx.parser.BxmlNodeVisitor;
+import org.apache.nifi.processors.evtx.parser.bxml.value.NullTypeNode;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+public class AttributeNodeTest extends BxmlNodeWithTokenAndStringTestBase {
+    public static final String ATTRIBUTE_NAME = "AttributeName";
+    private BinaryReader binaryReader;
+    private AttributeNode attributeNode;
+
+    @Override
+    public void setup() throws IOException {
+        super.setup();
+        testBinaryReaderBuilder.put((byte) BxmlNode.VALUE_TOKEN);
+        testBinaryReaderBuilder.put((byte) 0);
+        attributeNode = new AttributeNode(testBinaryReaderBuilder.build(), 
chunkHeader, parent);
+    }
+
+    @Override
+    protected byte getToken() {
+        return BxmlNode.ATTRIBUTE_TOKEN;
+    }
+
+    @Override
+    protected String getString() {
+        return ATTRIBUTE_NAME;
+    }
+
+    @Test
+    public void testInit() {
+        assertEquals(ATTRIBUTE_NAME, attributeNode.getAttributeName());
+        BxmlNode attributeNodeValue = attributeNode.getValue();
+        assertTrue(attributeNodeValue instanceof ValueNode);
+        List<BxmlNode> children = ((ValueNode) 
attributeNodeValue).getChildren();
+        assertEquals(1, children.size());
+        assertTrue(children.get(0) instanceof NullTypeNode);
+    }
+
+    @Test
+    public void testVisit() throws IOException {
+        BxmlNodeVisitor mock = mock(BxmlNodeVisitor.class);
+        attributeNode.accept(mock);
+        verify(mock).visit(attributeNode);
+        verifyNoMoreInteractions(mock);
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/a5fecda5/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/parser/bxml/BxmlNodeTestBase.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/parser/bxml/BxmlNodeTestBase.java
 
b/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/parser/bxml/BxmlNodeTestBase.java
new file mode 100644
index 0000000..4c7c8ea
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/parser/bxml/BxmlNodeTestBase.java
@@ -0,0 +1,43 @@
+/*
+ * 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.evtx.parser.bxml;
+
+import org.apache.nifi.processors.evtx.parser.ChunkHeader;
+import org.apache.nifi.processors.evtx.parser.TestBinaryReaderBuilder;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import java.io.IOException;
+
+@RunWith(MockitoJUnitRunner.class)
+public abstract class BxmlNodeTestBase {
+    public TestBinaryReaderBuilder testBinaryReaderBuilder;
+
+    @Mock
+    public ChunkHeader chunkHeader;
+
+    @Mock
+    public BxmlNode parent;
+
+    @Before
+    public void setup() throws IOException {
+        testBinaryReaderBuilder = new TestBinaryReaderBuilder();
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/a5fecda5/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/parser/bxml/BxmlNodeWithTokenAndStringTestBase.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/parser/bxml/BxmlNodeWithTokenAndStringTestBase.java
 
b/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/parser/bxml/BxmlNodeWithTokenAndStringTestBase.java
new file mode 100644
index 0000000..94d638d
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/parser/bxml/BxmlNodeWithTokenAndStringTestBase.java
@@ -0,0 +1,34 @@
+/*
+ * 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.evtx.parser.bxml;
+
+import java.io.IOException;
+
+import static org.mockito.Mockito.when;
+
+public abstract class BxmlNodeWithTokenAndStringTestBase extends 
BxmlNodeWithTokenTestBase {
+    @Override
+    public void setup() throws IOException {
+        super.setup();
+        testBinaryReaderBuilder.putDWord(0);
+        when(chunkHeader.getOffset()).thenReturn(4096L);
+        when(chunkHeader.getString(0)).thenReturn(getString());
+    }
+
+    protected abstract String getString();
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/a5fecda5/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/parser/bxml/BxmlNodeWithTokenTestBase.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/parser/bxml/BxmlNodeWithTokenTestBase.java
 
b/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/parser/bxml/BxmlNodeWithTokenTestBase.java
new file mode 100644
index 0000000..742c70d
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-evtx-bundle/nifi-evtx-processors/src/test/java/org/apache/nifi/processors/evtx/parser/bxml/BxmlNodeWithTokenTestBase.java
@@ -0,0 +1,30 @@
+/*
+ * 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.evtx.parser.bxml;
+
+import java.io.IOException;
+
+public abstract class BxmlNodeWithTokenTestBase extends BxmlNodeTestBase {
+    @Override
+    public void setup() throws IOException {
+        super.setup();
+        testBinaryReaderBuilder.put(getToken());
+    }
+
+    protected abstract byte getToken();
+}

Reply via email to