This is an automated email from the ASF dual-hosted git repository.
pvillard pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git
The following commit(s) were added to refs/heads/main by this push:
new 1e8c4e68fe NIFI-14336: Creating processor to list box folder contents
1e8c4e68fe is described below
commit 1e8c4e68fe86e5af52079aafa3d5912e59d5f846
Author: Noah Cover <[email protected]>
AuthorDate: Thu Mar 6 16:50:55 2025 -0800
NIFI-14336: Creating processor to list box folder contents
Signed-off-by: Pierre Villard <[email protected]>
This closes #9784.
---
.../nifi-box-bundle/nifi-box-processors/pom.xml | 9 +-
.../nifi/processors/box/ListBoxFileInfo.java | 281 +++++++++++++++++++++
.../services/org.apache.nifi.processor.Processor | 1 +
.../nifi/processors/box/ListBoxFileInfoTest.java | 197 +++++++++++++++
4 files changed, 486 insertions(+), 2 deletions(-)
diff --git a/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/pom.xml
b/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/pom.xml
index ceb7a250b2..8aacfe64d3 100644
--- a/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/pom.xml
+++ b/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/pom.xml
@@ -13,7 +13,8 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
@@ -50,7 +51,6 @@
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-record-serialization-service-api</artifactId>
- <scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
@@ -64,5 +64,10 @@
<artifactId>box-java-sdk</artifactId>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-mock-record-utils</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
</project>
diff --git
a/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/ListBoxFileInfo.java
b/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/ListBoxFileInfo.java
new file mode 100644
index 0000000000..bd2b6a2051
--- /dev/null
+++
b/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/main/java/org/apache/nifi/processors/box/ListBoxFileInfo.java
@@ -0,0 +1,281 @@
+/*
+ * 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.box;
+
+import com.box.sdk.BoxAPIConnection;
+import com.box.sdk.BoxAPIResponseException;
+import com.box.sdk.BoxFile;
+import com.box.sdk.BoxFolder;
+import com.box.sdk.BoxItem;
+import org.apache.nifi.annotation.behavior.InputRequirement;
+import org.apache.nifi.annotation.behavior.WritesAttribute;
+import org.apache.nifi.annotation.behavior.WritesAttributes;
+import org.apache.nifi.annotation.documentation.CapabilityDescription;
+import org.apache.nifi.annotation.documentation.SeeAlso;
+import org.apache.nifi.annotation.documentation.Tags;
+import org.apache.nifi.annotation.lifecycle.OnScheduled;
+import org.apache.nifi.box.controllerservices.BoxClientService;
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.expression.ExpressionLanguageScope;
+import org.apache.nifi.flowfile.FlowFile;
+import org.apache.nifi.flowfile.attributes.CoreAttributes;
+import org.apache.nifi.processor.AbstractProcessor;
+import org.apache.nifi.processor.ProcessContext;
+import org.apache.nifi.processor.ProcessSession;
+import org.apache.nifi.processor.Relationship;
+import org.apache.nifi.processor.exception.ProcessException;
+import org.apache.nifi.processor.util.StandardValidators;
+import org.apache.nifi.schema.access.SchemaNotFoundException;
+import org.apache.nifi.serialization.RecordSetWriter;
+import org.apache.nifi.serialization.RecordSetWriterFactory;
+import org.apache.nifi.serialization.SimpleRecordSchema;
+import org.apache.nifi.serialization.WriteResult;
+import org.apache.nifi.serialization.record.MapRecord;
+import org.apache.nifi.serialization.record.Record;
+import org.apache.nifi.serialization.record.RecordField;
+import org.apache.nifi.serialization.record.RecordFieldType;
+import org.apache.nifi.serialization.record.RecordSchema;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.sql.Timestamp;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import static java.lang.String.valueOf;
+import static org.apache.nifi.processors.box.BoxFileAttributes.ERROR_CODE;
+import static org.apache.nifi.processors.box.BoxFileAttributes.ERROR_CODE_DESC;
+import static org.apache.nifi.processors.box.BoxFileAttributes.ERROR_MESSAGE;
+import static
org.apache.nifi.processors.box.BoxFileAttributes.ERROR_MESSAGE_DESC;
+
+@InputRequirement(InputRequirement.Requirement.INPUT_REQUIRED)
+@Tags({"box", "storage", "fetch", "folder", "files"})
+@CapabilityDescription("Fetches file metadata for each file in a Box Folder.
Takes a flowFile with a folder ID attribute and outputs flowFiles with records
containing all file metadata.")
+@SeeAlso({ListBoxFile.class, FetchBoxFile.class, PutBoxFile.class})
+@WritesAttributes({
+ @WritesAttribute(attribute = "box.folder.id", description = "The ID of
the folder from which files were fetched"),
+ @WritesAttribute(attribute = "record.count", description = "The number
of records in the FlowFile"),
+ @WritesAttribute(attribute = "mime.type", description = "The MIME Type
specified by the Record Writer"),
+ @WritesAttribute(attribute = ERROR_CODE, description =
ERROR_CODE_DESC),
+ @WritesAttribute(attribute = ERROR_MESSAGE, description =
ERROR_MESSAGE_DESC)
+})
+public class ListBoxFileInfo extends AbstractProcessor {
+
+ public static final PropertyDescriptor FOLDER_ID = new
PropertyDescriptor.Builder()
+ .name("Folder ID")
+ .description("The ID of the folder from which to fetch files.")
+ .required(true)
+ .defaultValue("${box.folder.id}")
+
.expressionLanguageSupported(ExpressionLanguageScope.FLOWFILE_ATTRIBUTES)
+ .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+ .build();
+
+ public static final PropertyDescriptor RECURSIVE_SEARCH = new
PropertyDescriptor.Builder()
+ .name("Search Recursively")
+ .description("When 'true', will include files from sub-folders." +
+ " Otherwise, will return only files that are within the
folder defined by the 'Folder ID' property.")
+ .required(true)
+ .defaultValue("true")
+ .allowableValues("true", "false")
+ .build();
+
+ public static final PropertyDescriptor MIN_AGE = new
PropertyDescriptor.Builder()
+ .name("Minimum File Age")
+ .description("The minimum age a file must be in order to be
considered; any files younger than this will be ignored.")
+ .required(true)
+ .addValidator(StandardValidators.TIME_PERIOD_VALIDATOR)
+ .defaultValue("0 sec")
+ .build();
+
+ public static final PropertyDescriptor RECORD_WRITER = new
PropertyDescriptor.Builder()
+ .name("Record Writer")
+ .description("Specifies the Controller Service to use for writing
the metadata records. Must be set.")
+ .identifiesControllerService(RecordSetWriterFactory.class)
+ .required(true)
+ .build();
+
+ public static final Relationship REL_SUCCESS = new Relationship.Builder()
+ .name("success")
+ .description("A FlowFile containing the file metadata records will
be routed to this relationship upon successful processing.")
+ .build();
+
+ public static final Relationship REL_FAILURE = new Relationship.Builder()
+ .name("failure")
+ .description("A FlowFile will be routed here if there is an error
fetching file metadata from the folder.")
+ .build();
+
+ public static final Set<Relationship> RELATIONSHIPS = Set.of(
+ REL_SUCCESS,
+ REL_FAILURE
+ );
+
+ private static final List<PropertyDescriptor> PROPERTY_DESCRIPTORS =
List.of(
+ BoxClientService.BOX_CLIENT_SERVICE,
+ FOLDER_ID,
+ RECURSIVE_SEARCH,
+ MIN_AGE,
+ RECORD_WRITER
+ );
+
+ private static final RecordSchema RECORD_SCHEMA = new
SimpleRecordSchema(List.of(
+ new RecordField("id", RecordFieldType.STRING.getDataType(), false),
+ new RecordField("filename", RecordFieldType.STRING.getDataType(),
false),
+ new RecordField("path", RecordFieldType.STRING.getDataType(),
false),
+ new RecordField("size", RecordFieldType.LONG.getDataType(), false),
+ new RecordField("timestamp",
RecordFieldType.TIMESTAMP.getDataType(), false)
+ ));
+
+ private volatile BoxAPIConnection boxAPIConnection;
+
+ @Override
+ protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
+ return PROPERTY_DESCRIPTORS;
+ }
+
+ @Override
+ public Set<Relationship> getRelationships() {
+ return RELATIONSHIPS;
+ }
+
+ @OnScheduled
+ public void onScheduled(final ProcessContext context) {
+ final BoxClientService boxClientService =
context.getProperty(BoxClientService.BOX_CLIENT_SERVICE)
+ .asControllerService(BoxClientService.class);
+ boxAPIConnection = boxClientService.getBoxApiConnection();
+ }
+
+ @Override
+ public void onTrigger(final ProcessContext context, final ProcessSession
session) throws ProcessException {
+ FlowFile flowFile = session.get();
+ if (flowFile == null) {
+ return;
+ }
+
+ final String folderId =
context.getProperty(FOLDER_ID).evaluateAttributeExpressions(flowFile).getValue();
+ final Boolean recursive =
context.getProperty(RECURSIVE_SEARCH).asBoolean();
+ final Long minAge =
context.getProperty(MIN_AGE).asTimePeriod(TimeUnit.MILLISECONDS);
+ final RecordSetWriterFactory writerFactory =
context.getProperty(RECORD_WRITER).asControllerService(RecordSetWriterFactory.class);
+
+ try {
+ final long startNanos = System.nanoTime();
+ long createdAtMax = Instant.now().toEpochMilli() - minAge;
+ final List<BoxFile.Info> fileInfos = new ArrayList<>();
+
+ listFolder(fileInfos, folderId, recursive, createdAtMax);
+
+ if (fileInfos.isEmpty()) {
+ flowFile = session.putAttribute(flowFile, "box.folder.id",
folderId);
+ session.transfer(flowFile, REL_SUCCESS);
+ return;
+ }
+
+ flowFile = session.putAttribute(flowFile, "box.folder.id",
folderId);
+
+ try {
+ final WriteResult writeResult;
+ final String mimeType;
+
+ try (final OutputStream out = session.write(flowFile);
+ final RecordSetWriter writer =
writerFactory.createWriter(getLogger(), RECORD_SCHEMA, out, flowFile)) {
+
+ writer.beginRecordSet();
+
+ for (final BoxFile.Info fileInfo : fileInfos) {
+ final Map<String, Object> values = Map.of(
+ "id", fileInfo.getID(),
+ "filename", fileInfo.getName(),
+ "path", BoxFileUtils.getParentPath(fileInfo),
+ "size", fileInfo.getSize(),
+ "timestamp", new
Timestamp(fileInfo.getModifiedAt().getTime())
+ );
+
+ final Record record = new MapRecord(RECORD_SCHEMA,
values);
+ writer.write(record);
+ }
+
+ writeResult = writer.finishRecordSet();
+ mimeType = writer.getMimeType();
+ }
+
+ final Map<String, String> recordAttributes = new
HashMap<>(writeResult.getAttributes());
+ recordAttributes.put("record.count",
String.valueOf(writeResult.getRecordCount()));
+ recordAttributes.put(CoreAttributes.MIME_TYPE.key(), mimeType);
+ flowFile = session.putAllAttributes(flowFile,
recordAttributes);
+
+ final long transferMillis =
TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanos);
+ session.getProvenanceReporter().receive(flowFile,
BoxFileUtils.BOX_URL + folderId, transferMillis);
+
+ session.transfer(flowFile, REL_SUCCESS);
+ } catch (final SchemaNotFoundException | IOException e) {
+ getLogger().error("Failed writing records for files from
folder [{}]", folderId, e);
+ flowFile = session.putAttribute(flowFile, ERROR_MESSAGE,
e.getMessage());
+ flowFile = session.penalize(flowFile);
+ session.transfer(flowFile, REL_FAILURE);
+ }
+
+ } catch (final BoxAPIResponseException e) {
+ getLogger().error("Couldn't fetch files from folder with id [{}]",
folderId, e);
+ flowFile = session.putAttribute(flowFile, ERROR_CODE,
valueOf(e.getResponseCode()));
+ flowFile = session.putAttribute(flowFile, ERROR_MESSAGE,
e.getMessage());
+ flowFile = session.penalize(flowFile);
+ session.transfer(flowFile, REL_FAILURE);
+ }
+ }
+
+ private void listFolder(final List<BoxFile.Info> fileInfos,
+ final String folderId,
+ final Boolean recursive,
+ final long createdAtMax) {
+ final BoxFolder folder = getFolder(folderId);
+ for (final BoxItem.Info itemInfo : folder.getChildren(
+ "id",
+ "name",
+ "item_status",
+ "size",
+ "created_at",
+ "modified_at",
+ "content_created_at",
+ "content_modified_at",
+ "path_collection"
+ )) {
+ if (itemInfo instanceof BoxFile.Info fileInfo) {
+ long createdAt = itemInfo.getCreatedAt().getTime();
+
+ if (createdAt <= createdAtMax) {
+ fileInfos.add(fileInfo);
+ }
+ } else if (recursive && itemInfo instanceof BoxFolder.Info
subFolderInfo) {
+ listFolder(fileInfos, subFolderInfo.getID(), recursive,
createdAtMax);
+ }
+ }
+ }
+
+ /**
+ * Returns a BoxFolder object for the given folder ID.
+ *
+ * @param folderId The ID of the folder.
+ * @return A BoxFolder object for the given folder ID.
+ */
+ BoxFolder getFolder(final String folderId) {
+ return new BoxFolder(boxAPIConnection, folderId);
+ }
+}
diff --git
a/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor
b/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor
index 4c62ac0aa2..cf5335907b 100644
---
a/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor
+++
b/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor
@@ -21,4 +21,5 @@ org.apache.nifi.processors.box.FetchBoxFileRepresentation
org.apache.nifi.processors.box.GetBoxFileCollaborators
org.apache.nifi.processors.box.GetBoxGroupMembers
org.apache.nifi.processors.box.ListBoxFile
+org.apache.nifi.processors.box.ListBoxFileInfo
org.apache.nifi.processors.box.PutBoxFile
diff --git
a/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/ListBoxFileInfoTest.java
b/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/ListBoxFileInfoTest.java
new file mode 100644
index 0000000000..25e4d747b9
--- /dev/null
+++
b/nifi-extension-bundles/nifi-box-bundle/nifi-box-processors/src/test/java/org/apache/nifi/processors/box/ListBoxFileInfoTest.java
@@ -0,0 +1,197 @@
+/*
+ * 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.box;
+
+import com.box.sdk.BoxAPIResponseException;
+import com.box.sdk.BoxFolder;
+import org.apache.nifi.serialization.record.MockRecordWriter;
+import org.apache.nifi.util.MockFlowFile;
+import org.apache.nifi.util.TestRunners;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.apache.nifi.processors.box.BoxFileAttributes.ERROR_MESSAGE;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+
+@ExtendWith(MockitoExtension.class)
+public class ListBoxFileInfoTest extends AbstractBoxFileTest implements
FileListingTestTrait {
+
+ private static final String RECORD_WRITER_ID = "record-writer";
+
+ @BeforeEach
+ void setUp() throws Exception {
+ final ListBoxFileInfo testSubject = new ListBoxFileInfo() {
+ @Override
+ BoxFolder getFolder(final String folderId) {
+ return mockBoxFolder;
+ }
+ };
+
+ testRunner = TestRunners.newTestRunner(testSubject);
+
+ final MockRecordWriter writerService = new
MockRecordWriter("id,filename,path,size,timestamp", false);
+ testRunner.addControllerService(RECORD_WRITER_ID, writerService);
+ testRunner.enableControllerService(writerService);
+ testRunner.setProperty(ListBoxFileInfo.RECORD_WRITER,
RECORD_WRITER_ID);
+
+ super.setUp();
+ }
+
+ @Test
+ void testFetchMetadataFromFolderWithFolderIdProperty() {
+ testRunner.setProperty(ListBoxFileInfo.FOLDER_ID, TEST_FOLDER_ID);
+ testRunner.setProperty(ListBoxFileInfo.RECURSIVE_SEARCH, "false");
+
+ final List<String> pathParts = Arrays.asList("path", "to", "file");
+ mockFetchedFileList(TEST_FILE_ID, TEST_FILENAME, pathParts, TEST_SIZE,
CREATED_TIME, MODIFIED_TIME);
+
+ testRunner.enqueue("test file");
+ testRunner.run();
+
+ testRunner.assertTransferCount(ListBoxFileInfo.REL_SUCCESS, 1);
+ testRunner.assertTransferCount(ListBoxFileInfo.REL_FAILURE, 0);
+
+ final List<MockFlowFile> successFiles =
testRunner.getFlowFilesForRelationship(ListBoxFileInfo.REL_SUCCESS);
+ final MockFlowFile outputFlowFile = successFiles.getFirst();
+
+ outputFlowFile.assertAttributeExists("record.count");
+ assertEquals("1", outputFlowFile.getAttribute("record.count"));
+ outputFlowFile.assertAttributeEquals("box.folder.id", TEST_FOLDER_ID);
+
+ final String content = new String(outputFlowFile.toByteArray());
+ assertTrue(content.contains(TEST_FILE_ID));
+ assertTrue(content.contains(TEST_FILENAME));
+ assertTrue(content.contains("/path"));
+ assertTrue(content.contains(String.valueOf(TEST_SIZE)));
+ }
+
+ @Test
+ void testFetchMetadataFromFolderWithFolderIdAttributeExpression() {
+ testRunner.setProperty(ListBoxFileInfo.FOLDER_ID, "${box.folder.id}");
+ testRunner.setProperty(ListBoxFileInfo.RECURSIVE_SEARCH, "true");
+
+ final List<String> pathParts = Arrays.asList("path", "to", "file");
+ mockFetchedFileList(TEST_FILE_ID, TEST_FILENAME, pathParts, TEST_SIZE,
CREATED_TIME, MODIFIED_TIME);
+
+ final Map<String, String> attributeMap = new HashMap<>();
+ attributeMap.put("box.folder.id", TEST_FOLDER_ID);
+
+ testRunner.enqueue("test file", attributeMap);
+ testRunner.run();
+
+ testRunner.assertTransferCount(ListBoxFileInfo.REL_SUCCESS, 1);
+ testRunner.assertTransferCount(ListBoxFileInfo.REL_FAILURE, 0);
+
+ final List<MockFlowFile> successFiles =
testRunner.getFlowFilesForRelationship(ListBoxFileInfo.REL_SUCCESS);
+ final MockFlowFile outputFlowFile = successFiles.getFirst();
+
+ outputFlowFile.assertAttributeExists("record.count");
+ assertEquals("1", outputFlowFile.getAttribute("record.count"));
+ outputFlowFile.assertAttributeEquals("box.folder.id", TEST_FOLDER_ID);
+
+ final String content = new String(outputFlowFile.toByteArray());
+ assertTrue(content.contains(TEST_FILE_ID));
+ assertTrue(content.contains(TEST_FILENAME));
+ assertTrue(content.contains("/path"));
+ assertTrue(content.contains(String.valueOf(TEST_SIZE)));
+ }
+
+ @Test
+ void testProcessingMultipleFiles() {
+ testRunner.setProperty(ListBoxFileInfo.FOLDER_ID, TEST_FOLDER_ID);
+ testRunner.setProperty(ListBoxFileInfo.RECURSIVE_SEARCH, "false");
+ mockMultipleFilesResponse();
+
+ testRunner.enqueue("test file");
+ testRunner.run();
+
+ testRunner.assertTransferCount(ListBoxFileInfo.REL_SUCCESS, 1);
+ testRunner.assertTransferCount(ListBoxFileInfo.REL_FAILURE, 0);
+
+ final List<MockFlowFile> successFiles =
testRunner.getFlowFilesForRelationship(ListBoxFileInfo.REL_SUCCESS);
+ final MockFlowFile outputFlowFile = successFiles.getFirst();
+ outputFlowFile.assertAttributeEquals("record.count", "3");
+ outputFlowFile.assertAttributeEquals("box.folder.id", TEST_FOLDER_ID);
+ final String content = new String(outputFlowFile.toByteArray());
+ assertTrue(content.contains(TEST_FILE_ID + "1"));
+ assertTrue(content.contains(TEST_FILE_ID + "2"));
+ assertTrue(content.contains(TEST_FILE_ID + "3"));
+ }
+
+ @Test
+ void testBoxAPIResponseException() {
+ testRunner.setProperty(ListBoxFileInfo.FOLDER_ID, TEST_FOLDER_ID);
+
+ final BoxAPIResponseException apiException = new
BoxAPIResponseException("API Error", 404, "Not Found", null);
+ doThrow(apiException).when(mockBoxFolder).getChildren(
+ "id",
+ "name",
+ "item_status",
+ "size",
+ "created_at",
+ "modified_at",
+ "content_created_at",
+ "content_modified_at",
+ "path_collection");
+
+ final MockFlowFile inputFlowFile = new MockFlowFile(0);
+ testRunner.enqueue(inputFlowFile);
+ testRunner.run();
+
+ testRunner.assertTransferCount(ListBoxFileInfo.REL_FAILURE, 1);
+ testRunner.assertTransferCount(ListBoxFileInfo.REL_SUCCESS, 0);
+
+ final List<MockFlowFile> failureFiles =
testRunner.getFlowFilesForRelationship(ListBoxFileInfo.REL_FAILURE);
+ final MockFlowFile failureFlowFile = failureFiles.getFirst();
+ failureFlowFile.assertAttributeEquals(BoxFileAttributes.ERROR_CODE,
"404");
+ failureFlowFile.assertAttributeExists(ERROR_MESSAGE);
+ }
+
+ private void mockMultipleFilesResponse() {
+ List<String> pathParts = Arrays.asList("path", "to", "file");
+
+ doReturn(Arrays.asList(
+ createFileInfo(TEST_FILE_ID + "1", TEST_FILENAME + "1",
pathParts, TEST_SIZE, CREATED_TIME, MODIFIED_TIME),
+ createFileInfo(TEST_FILE_ID + "2", TEST_FILENAME + "2",
pathParts, TEST_SIZE, CREATED_TIME, MODIFIED_TIME),
+ createFileInfo(TEST_FILE_ID + "3", TEST_FILENAME + "3",
pathParts, TEST_SIZE, CREATED_TIME, MODIFIED_TIME)
+ )).when(mockBoxFolder).getChildren(
+ "id",
+ "name",
+ "item_status",
+ "size",
+ "created_at",
+ "modified_at",
+ "content_created_at",
+ "content_modified_at",
+ "path_collection");
+ }
+
+ @Override
+ public BoxFolder getMockBoxFolder() {
+ return mockBoxFolder;
+ }
+}