This is an automated email from the ASF dual-hosted git repository. exceptionfactory 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 5cc857b99c NIFI-12017 Added Line Length Properties to EncodeContent 5cc857b99c is described below commit 5cc857b99c780afb0a28112a73d73ae130b7b1fa Author: knguyen <yuram...@gmail.com> AuthorDate: Thu Feb 15 19:35:13 2024 +0000 NIFI-12017 Added Line Length Properties to EncodeContent This closes #8417 Signed-off-by: David Handermann <exceptionfact...@apache.org> --- .../nifi/processors/standard/EncodeContent.java | 148 ++++++--- .../processors/standard/encoding/EncodingMode.java | 47 +++ .../processors/standard/encoding/EncodingType.java | 50 +++ .../standard/encoding/LineOutputMode.java | 47 +++ .../processors/standard/TestEncodeContent.java | 348 ++++++++++++++++----- 5 files changed, 520 insertions(+), 120 deletions(-) diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/EncodeContent.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/EncodeContent.java index d6e49725b9..29f23ee3ed 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/EncodeContent.java +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/EncodeContent.java @@ -16,6 +16,14 @@ */ package org.apache.nifi.processors.standard; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.concurrent.TimeUnit; + import org.apache.commons.codec.DecoderException; import org.apache.commons.codec.binary.Base32InputStream; import org.apache.commons.codec.binary.Base32OutputStream; @@ -29,25 +37,22 @@ import org.apache.nifi.annotation.behavior.SupportsBatching; import org.apache.nifi.annotation.documentation.CapabilityDescription; import org.apache.nifi.annotation.documentation.Tags; import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.expression.ExpressionLanguageScope; import org.apache.nifi.flowfile.FlowFile; 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.io.StreamCallback; +import org.apache.nifi.processor.util.StandardValidators; +import org.apache.nifi.processors.standard.encoding.EncodingMode; +import org.apache.nifi.processors.standard.encoding.EncodingType; +import org.apache.nifi.processors.standard.encoding.LineOutputMode; import org.apache.nifi.processors.standard.util.ValidatingBase32InputStream; import org.apache.nifi.processors.standard.util.ValidatingBase64InputStream; import org.apache.nifi.stream.io.StreamUtils; import org.apache.nifi.util.StopWatch; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.Arrays; -import java.util.List; -import java.util.Set; -import java.util.concurrent.TimeUnit; - @SideEffectFree @SupportsBatching @InputRequirement(Requirement.INPUT_REQUIRED) @@ -55,33 +60,52 @@ import java.util.concurrent.TimeUnit; @CapabilityDescription("Encode or decode the contents of a FlowFile using Base64, Base32, or hex encoding schemes") public class EncodeContent extends AbstractProcessor { - public static final String ENCODE_MODE = "Encode"; - public static final String DECODE_MODE = "Decode"; - - public static final String BASE64_ENCODING = "base64"; - public static final String BASE32_ENCODING = "base32"; - public static final String HEX_ENCODING = "hex"; - public static final PropertyDescriptor MODE = new PropertyDescriptor.Builder() .name("Mode") - .description("Specifies whether the content should be encoded or decoded") + .description("Specifies whether the content should be encoded or decoded.") .required(true) - .allowableValues(ENCODE_MODE, DECODE_MODE) - .defaultValue(ENCODE_MODE) + .allowableValues(EncodingMode.class) + .defaultValue(EncodingMode.ENCODE) .build(); public static final PropertyDescriptor ENCODING = new PropertyDescriptor.Builder() .name("Encoding") - .description("Specifies the type of encoding used") + .description("Specifies the type of encoding used.") + .required(true) + .allowableValues(EncodingType.class) + .defaultValue(EncodingType.BASE64) + .build(); + + public static final PropertyDescriptor LINE_OUTPUT_MODE = new PropertyDescriptor.Builder() + .name("Line Output Mode") + .displayName("Line Output Mode") + .description("Controls the line formatting for encoded content based on selected property values.") .required(true) - .allowableValues(BASE64_ENCODING, BASE32_ENCODING, HEX_ENCODING) - .defaultValue(BASE64_ENCODING) + .defaultValue(LineOutputMode.SINGLE_LINE) + .allowableValues(LineOutputMode.class) + .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) + .dependsOn(MODE, EncodingMode.ENCODE) + .dependsOn(ENCODING, EncodingType.BASE64, EncodingType.BASE32) + .build(); + + public static final PropertyDescriptor ENCODED_LINE_LENGTH = new PropertyDescriptor.Builder() + .name("Encoded Line Length") + .displayName("Encoded Line Length") + .description("Each line of encoded data will contain up to the configured number of characters, rounded down to the nearest multiple of 4.") + .required(true) + .defaultValue("76") + .expressionLanguageSupported(ExpressionLanguageScope.FLOWFILE_ATTRIBUTES) + .addValidator(StandardValidators.POSITIVE_INTEGER_VALIDATOR) + .dependsOn(MODE, EncodingMode.ENCODE) + .dependsOn(ENCODING, EncodingType.BASE64, EncodingType.BASE32) + .dependsOn(LINE_OUTPUT_MODE, LineOutputMode.MULTIPLE_LINES) .build(); public static final Relationship REL_SUCCESS = new Relationship.Builder() .name("success") .description("Any FlowFile that is successfully encoded or decoded will be routed to success") .build(); + public static final Relationship REL_FAILURE = new Relationship.Builder() .name("failure") .description("Any FlowFile that cannot be encoded or decoded will be routed to failure") @@ -89,11 +113,16 @@ public class EncodeContent extends AbstractProcessor { private static final int BUFFER_SIZE = 8192; - private static final List<PropertyDescriptor> properties = List.of(MODE, - ENCODING); + private static final String LINE_FEED_SEPARATOR = "\n"; + + private static final List<PropertyDescriptor> properties = List.of( + MODE, + ENCODING, + LINE_OUTPUT_MODE, + ENCODED_LINE_LENGTH + ); - private static final Set<Relationship> relationships = Set.of(REL_SUCCESS, - REL_FAILURE); + private static final Set<Relationship> relationships = Set.of(REL_SUCCESS, REL_FAILURE); @Override public Set<Relationship> getRelationships() { @@ -112,9 +141,12 @@ public class EncodeContent extends AbstractProcessor { return; } - final boolean encode = context.getProperty(MODE).getValue().equalsIgnoreCase(ENCODE_MODE); - final String encoding = context.getProperty(ENCODING).getValue(); - final StreamCallback callback = getStreamCallback(encode, encoding); + final boolean encode = context.getProperty(MODE).getValue().equals(EncodingMode.ENCODE.getValue()); + final EncodingType encoding = getEncodingType(context.getProperty(ENCODING).getValue()); + final boolean singleLineOutput = context.getProperty(LINE_OUTPUT_MODE).getValue().equals(LineOutputMode.SINGLE_LINE.getValue()); + final int lineLength = singleLineOutput ? -1 : context.getProperty(ENCODED_LINE_LENGTH).evaluateAttributeExpressions(flowFile).asInteger(); + + final StreamCallback callback = getStreamCallback(encode, encoding, lineLength); try { final StopWatch stopWatch = new StopWatch(true); @@ -129,31 +161,31 @@ public class EncodeContent extends AbstractProcessor { } } - private static StreamCallback getStreamCallback(final boolean encode, final String encoding) { - if (encode) { - if (encoding.equalsIgnoreCase(BASE64_ENCODING)) { - return new EncodeBase64(); - } else if (encoding.equalsIgnoreCase(BASE32_ENCODING)) { - return new EncodeBase32(); - } else { - return new EncodeHex(); - } - } else { - if (encoding.equalsIgnoreCase(BASE64_ENCODING)) { - return new DecodeBase64(); - } else if (encoding.equalsIgnoreCase(BASE32_ENCODING)) { - return new DecodeBase32(); - } else { - return new DecodeHex(); - } - } + private static StreamCallback getStreamCallback(final boolean encode, final EncodingType encoding, final int lineLength) { + return switch (encoding) { + case BASE64 -> encode ? new EncodeBase64(lineLength, LINE_FEED_SEPARATOR) : new DecodeBase64(); + case BASE32 -> encode ? new EncodeBase32(lineLength, LINE_FEED_SEPARATOR) : new DecodeBase32(); + default -> encode ? new EncodeHex() : new DecodeHex(); + }; } private static class EncodeBase64 implements StreamCallback { + private final int lineLength; + private final String lineSeparator; + + private EncodeBase64(final int lineLength, + final String lineSeparator) { + this.lineLength = lineLength; + this.lineSeparator = lineSeparator; + } + @Override public void process(final InputStream in, final OutputStream out) throws IOException { - try (Base64OutputStream bos = new Base64OutputStream(out)) { + try (Base64OutputStream bos = new Base64OutputStream(out, + true, + this.lineLength, + this.lineSeparator.getBytes())) { StreamUtils.copy(in, bos); } } @@ -171,9 +203,19 @@ public class EncodeContent extends AbstractProcessor { private static class EncodeBase32 implements StreamCallback { + private final int lineLength; + private final String lineSeparator; + + public EncodeBase32(final int lineLength, + final String lineSeparator) { + + this.lineLength = lineLength; + this.lineSeparator = lineSeparator; + } + @Override public void process(final InputStream in, final OutputStream out) throws IOException { - try (Base32OutputStream bos = new Base32OutputStream(out)) { + try (Base32OutputStream bos = new Base32OutputStream(out, true, this.lineLength, this.lineSeparator.getBytes())) { StreamUtils.copy(in, bos); } } @@ -237,4 +279,14 @@ public class EncodeContent extends AbstractProcessor { out.flush(); } } + + private static EncodingType getEncodingType(final String encodingTypeValue) { + if (EncodingType.BASE64.getValue().equals(encodingTypeValue)) { + return EncodingType.BASE64; + } else if (EncodingType.BASE32.getValue().equals(encodingTypeValue)) { + return EncodingType.BASE32; + } else { + return EncodingType.HEXADECIMAL; + } + } } diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/encoding/EncodingMode.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/encoding/EncodingMode.java new file mode 100644 index 0000000000..710c5ac1b7 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/encoding/EncodingMode.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.processors.standard.encoding; + +import org.apache.nifi.components.DescribedValue; + +public enum EncodingMode implements DescribedValue { + ENCODE("Encode", "Transform original input to encoded representation"), + DECODE("Decode", "Transform encoded input to original representation"); + + EncodingMode(String value, String description) { + this.value = value; + this.description = description; + } + + private final String value; + private final String description; + + @Override + public String getValue() { + return value; + } + + @Override + public String getDisplayName() { + return value; + } + + @Override + public String getDescription() { + return description; + } + } diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/encoding/EncodingType.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/encoding/EncodingType.java new file mode 100644 index 0000000000..aea183a4d2 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/encoding/EncodingType.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.processors.standard.encoding; + +import org.apache.nifi.components.DescribedValue; + +public enum EncodingType implements DescribedValue { + BASE64("base64", "Base64", "Encode or decode using Base64 set of characters"), + BASE32("base32", "Base32", "Encode or decode using Base32 set of characters"), + HEXADECIMAL("hex", "Hexadecimal", "Encode or decode using hexadecimal set of characters"); + + private final String value; + private final String displayName; + private final String description; + + EncodingType(String value, String displayName, String description) { + this.value = value; + this.displayName = displayName; + this.description = description; + } + + @Override + public String getValue() { + return value; + } + + @Override + public String getDisplayName() { + return displayName; + } + + @Override + public String getDescription() { + return description; + } +} diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/encoding/LineOutputMode.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/encoding/LineOutputMode.java new file mode 100644 index 0000000000..71062cf519 --- /dev/null +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/encoding/LineOutputMode.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.processors.standard.encoding; + +import org.apache.nifi.components.DescribedValue; + +public enum LineOutputMode implements DescribedValue { + SINGLE_LINE("Single Line", "The encoded content will be written as a single line."), + MULTIPLE_LINES("Multiple Lines", "The encoded content will be written as multiple lines."); + + private final String displayName; + private final String description; + + LineOutputMode(String displayName, String description) { + this.displayName = displayName; + this.description = description; + } + + @Override + public String getValue() { + return name(); + } + + @Override + public String getDisplayName() { + return displayName; + } + + @Override + public String getDescription() { + return description; + } +} diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestEncodeContent.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestEncodeContent.java index b8da70c754..602b2e8340 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestEncodeContent.java +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestEncodeContent.java @@ -16,25 +16,59 @@ */ package org.apache.nifi.processors.standard; +import static org.junit.jupiter.api.Assertions.assertEquals; + import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.stream.Stream; +import org.apache.nifi.components.DescribedValue; +import org.apache.nifi.processor.Relationship; +import org.apache.nifi.processors.standard.encoding.EncodingMode; +import org.apache.nifi.processors.standard.encoding.EncodingType; +import org.apache.nifi.processors.standard.encoding.LineOutputMode; import org.apache.nifi.util.MockFlowFile; +import org.apache.nifi.util.StringUtils; import org.apache.nifi.util.TestRunner; import org.apache.nifi.util.TestRunners; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.MethodSource; + +class TestEncodeContent { -public class TestEncodeContent { + private static final String LOREM_IPSUM = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."; private static final Path FILE_PATH = Paths.get("src/test/resources/hello.txt"); + private TestRunner testRunner; + + @BeforeEach + void setUp() { + testRunner = TestRunners.newTestRunner(EncodeContent.class); + } + @Test - public void testBase64RoundTrip() throws IOException { - final TestRunner testRunner = TestRunners.newTestRunner(new EncodeContent()); + void testFailDecodeNotBase64ButIsAMultipleOfFourBytes() { + testRunner.setProperty(EncodeContent.MODE, EncodingMode.DECODE); + testRunner.setProperty(EncodeContent.ENCODING, EncodingType.BASE64); - testRunner.setProperty(EncodeContent.MODE, EncodeContent.ENCODE_MODE); - testRunner.setProperty(EncodeContent.ENCODING, EncodeContent.BASE64_ENCODING); + testRunner.enqueue("four@@@@multiple".getBytes()); + testRunner.clearTransferState(); + testRunner.run(); + + testRunner.assertAllFlowFilesTransferred(EncodeContent.REL_FAILURE, 1); + } + + @ParameterizedTest + @EnumSource(value = EncodingType.class) + void testRoundTrip(final EncodingType encoding) throws IOException { + testRunner.setProperty(EncodeContent.MODE, EncodingMode.ENCODE); + testRunner.setProperty(EncodeContent.ENCODING, encoding); testRunner.enqueue(FILE_PATH); testRunner.clearTransferState(); @@ -45,7 +79,7 @@ public class TestEncodeContent { MockFlowFile flowFile = testRunner.getFlowFilesForRelationship(EncodeContent.REL_SUCCESS).get(0); testRunner.assertQueueEmpty(); - testRunner.setProperty(EncodeContent.MODE, EncodeContent.DECODE_MODE); + testRunner.setProperty(EncodeContent.MODE, EncodingMode.DECODE); testRunner.enqueue(flowFile); testRunner.clearTransferState(); testRunner.run(); @@ -55,12 +89,11 @@ public class TestEncodeContent { flowFile.assertContentEquals(FILE_PATH); } - @Test - public void testFailDecodeNotBase64() throws IOException { - final TestRunner testRunner = TestRunners.newTestRunner(new EncodeContent()); - - testRunner.setProperty(EncodeContent.MODE, EncodeContent.DECODE_MODE); - testRunner.setProperty(EncodeContent.ENCODING, EncodeContent.BASE64_ENCODING); + @ParameterizedTest + @EnumSource(value = EncodingType.class) + void testDecodeFailure(final EncodingType encoding) throws IOException { + testRunner.setProperty(EncodeContent.MODE, EncodingMode.DECODE); + testRunner.setProperty(EncodeContent.ENCODING, encoding); testRunner.enqueue(FILE_PATH); testRunner.clearTransferState(); @@ -70,96 +103,267 @@ public class TestEncodeContent { } @Test - public void testFailDecodeNotBase64ButIsAMultipleOfFourBytes() { - final TestRunner testRunner = TestRunners.newTestRunner(new EncodeContent()); - - testRunner.setProperty(EncodeContent.MODE, EncodeContent.DECODE_MODE); - testRunner.setProperty(EncodeContent.ENCODING, EncodeContent.BASE64_ENCODING); + void testEncodeDecodeSpecialCharsBase64() { + final String specialChars = "!@#$%^&*()_+{}:\"<>?[];',./~`-="; + final String expectedOutput = "IUAjJCVeJiooKV8re306Ijw+P1tdOycsLi9+YC09\n"; - testRunner.enqueue("four@@@@multiple".getBytes()); - testRunner.clearTransferState(); - testRunner.run(); + executeTestSuccessHelper(EncodingMode.ENCODE, EncodingType.BASE64, specialChars, expectedOutput); + testRunner.clearTransferState(); // clear the state for the next test + executeTestSuccessHelper(EncodingMode.DECODE, EncodingType.BASE64, expectedOutput, specialChars); + } - testRunner.assertAllFlowFilesTransferred(EncodeContent.REL_FAILURE, 1); + @ParameterizedTest + @MethodSource("encodeBase32Args") + void testBasicDecodeBase32(final String input, final String expectedOutput) { + // use the same args from `encodeBase32Args`, only flip around input and output + executeTestSuccessHelper(EncodingMode.DECODE, EncodingType.BASE32, expectedOutput, input); } - @Test - public void testBase32RoundTrip() throws IOException { - final TestRunner testRunner = TestRunners.newTestRunner(new EncodeContent()); + @ParameterizedTest + @MethodSource("encodeBase64Args") + void testBasicDecodeBase64(final String input, final String expectedOutput) { + // use the same args from `encodeBase64Args`, only flip around input and output + executeTestSuccessHelper(EncodingMode.DECODE, EncodingType.BASE64, expectedOutput, input); + } - testRunner.setProperty(EncodeContent.MODE, EncodeContent.ENCODE_MODE); - testRunner.setProperty(EncodeContent.ENCODING, EncodeContent.BASE32_ENCODING); + @ParameterizedTest + @MethodSource("encodeHexArgs") + void testBasicDecodeHex(final String input, final String expectedOutput) { + // use the same args from `encodeHexArgs`, only flip around input and output + executeTestSuccessHelper(EncodingMode.DECODE, EncodingType.HEXADECIMAL, expectedOutput, input); + } - testRunner.enqueue(FILE_PATH); - testRunner.clearTransferState(); - testRunner.run(); + @ParameterizedTest + @MethodSource("encodeHexArgs") + void testBasicEncodeHex(final String input, final String expectedOutput) { + executeTestSuccessHelper(EncodingMode.ENCODE, EncodingType.HEXADECIMAL, input, expectedOutput); + } - testRunner.assertAllFlowFilesTransferred(EncodeContent.REL_SUCCESS, 1); + private static Stream<Arguments> encodeHexArgs() { + return Stream.of( + Arguments.of("hello", "68656C6C6F"), + Arguments.of("foo", "666F6F"), + Arguments.of("你好", "E4BDA0E5A5BD"), + Arguments.of("Здравствуйте", "D097D0B4D180D0B0D0B2D181D182D0B2D183D0B9D182D0B5") + ); + } + + @ParameterizedTest + @MethodSource("encodeBase32Args") + void testBasicEncodeBase32(final String input, final String expectedOutput) { + executeTestSuccessHelper(EncodingMode.ENCODE, EncodingType.BASE32, input, expectedOutput); + } - MockFlowFile flowFile = testRunner.getFlowFilesForRelationship(EncodeContent.REL_SUCCESS).get(0); - testRunner.assertQueueEmpty(); + private static Stream<Arguments> encodeBase32Args() { + return Stream.of( + Arguments.of("hello", "NBSWY3DP\n"), + Arguments.of("foo", "MZXW6===\n"), + Arguments.of("你好", "4S62BZNFXU======\n"), + Arguments.of("Здравствуйте", "2CL5BNGRQDILBUFS2GA5DAWQWLIYHUFZ2GBNBNI=\n") + ); + } + + @ParameterizedTest + @MethodSource("encodeBase64Args") + void testBasicEncodeBase64(final String input, final String expectedOutput) { + executeTestSuccessHelper(EncodingMode.ENCODE, EncodingType.BASE64, input, expectedOutput); + } - testRunner.setProperty(EncodeContent.MODE, EncodeContent.DECODE_MODE); - testRunner.enqueue(flowFile); - testRunner.clearTransferState(); - testRunner.run(); - testRunner.assertAllFlowFilesTransferred(EncodeContent.REL_SUCCESS, 1); + private static Stream<Arguments> encodeBase64Args() { + return Stream.of( + Arguments.of("hello", "aGVsbG8=\n"), + Arguments.of("foo", "Zm9v\n"), + Arguments.of("你好", "5L2g5aW9\n"), + Arguments.of("Здравствуйте", "0JfQtNGA0LDQstGB0YLQstGD0LnRgtC1\n") + ); + } - flowFile = testRunner.getFlowFilesForRelationship(EncodeContent.REL_SUCCESS).get(0); - flowFile.assertContentEquals(FILE_PATH); + @Test + void testBlankValueShouldNotFail() { + executeTestSuccessHelper(EncodingMode.ENCODE, + EncodingType.BASE64, + StringUtils.EMPTY, + StringUtils.EMPTY); } @Test - public void testFailDecodeNotBase32() throws IOException { - final TestRunner testRunner = TestRunners.newTestRunner(new EncodeContent()); + void testEncodeContentMultipleLinesBase64() { + // this input is greater than 57 bytes, sure to generate multiple lines in base64 + final String expectedOutput = "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdCwg\n" + + "c2VkIGRvIGVpdXNtb2QgdGVtcG9yIGluY2lkaWR1bnQgdXQgbGFib3JlIGV0IGRvbG9yZSBtYWdu\n" + + "YSBhbGlxdWEu\n"; + + // Execute the test using the helper method + executeTestHelper(EncodingMode.ENCODE, + EncodingType.BASE64, + LOREM_IPSUM, + LineOutputMode.MULTIPLE_LINES, + expectedOutput, + EncodeContent.REL_SUCCESS); + } - testRunner.setProperty(EncodeContent.MODE, EncodeContent.DECODE_MODE); - testRunner.setProperty(EncodeContent.ENCODING, EncodeContent.BASE32_ENCODING); + @Test + void testEncodeContentSingleLineBase64() { + // this input is greater than 57 bytes, sure to generate multiple lines in base64 + final String expectedOutput = "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdCwg" + + "c2VkIGRvIGVpdXNtb2QgdGVtcG9yIGluY2lkaWR1bnQgdXQgbGFib3JlIGV0IGRvbG9yZSBtYWdu" + + "YSBhbGlxdWEu"; + + // Execute the test using the helper method + executeTestHelper(EncodingMode.ENCODE, + EncodingType.BASE64, + LOREM_IPSUM, + LineOutputMode.SINGLE_LINE, + expectedOutput, + EncodeContent.REL_SUCCESS); + } - testRunner.enqueue(FILE_PATH); - testRunner.clearTransferState(); - testRunner.run(); + @Test + void testEncodeContentSingleLineBase32() { + // this input is greater than 57 bytes, sure to generate multiple lines in base64 + final String expectedOutput = "JRXXEZLNEBUXA43VNUQGI33MN5ZCA43JOQQGC3LFOQWCAY3PNZZWKY3UMV2HK4RAMFSGS4DJONRWS3THEBSWY2LUF" + + "QQHGZLEEBSG6IDFNF2XG3LPMQQHIZLNOBXXEIDJNZRWSZDJMR2W45BAOV2CA3DBMJXXEZJAMV2CAZDPNRXXEZJANVQWO3TBEBQWY2LROVQS4==="; + + // Execute the test using the helper method + executeTestHelper(EncodingMode.ENCODE, + EncodingType.BASE32, + LOREM_IPSUM, + LineOutputMode.SINGLE_LINE, + expectedOutput, + EncodeContent.REL_SUCCESS); + } - testRunner.assertAllFlowFilesTransferred(EncodeContent.REL_FAILURE, 1); + @Test + void testEncodeContentMultipleLinesBase32() { + // this input is greater than 57 bytes, sure to generate multiple lines in base64 + final String expectedOutput = "JRXXEZLNEBUXA43VNUQGI33MN5ZCA43JOQQGC3LFOQWCAY3PNZZWKY3UMV2HK4RAMFSGS4DJ\n" + + "ONRWS3THEBSWY2LUFQQHGZLEEBSG6IDFNF2XG3LPMQQHIZLNOBXXEIDJNZRWSZDJMR2W45BA\n" + + "OV2CA3DBMJXXEZJAMV2CAZDPNRXXEZJANVQWO3TBEBQWY2LROVQS4===\n"; + + // Execute the test using the helper method + executeTestHelper(EncodingMode.ENCODE, + EncodingType.BASE32, + LOREM_IPSUM, + LineOutputMode.MULTIPLE_LINES, // set false to output multiple lines + expectedOutput, + EncodeContent.REL_SUCCESS); } @Test - public void testHexRoundTrip() throws IOException { - final TestRunner testRunner = TestRunners.newTestRunner(new EncodeContent()); + void testEncodeContentMultipleLinesNonStandardLengthBase32() { + // this input is greater than 57 bytes, sure to generate multiple lines in base64 + final String expectedOutput = "JRXXEZLNEBUXA43VNUQGI33MN5ZCA43JOQQGC3LFOQWCAY3PNZZWKY3UMV2HK4RAMFSGS4DJONRWS3TH\n" + + "EBSWY2LUFQQHGZLEEBSG6IDFNF2XG3LPMQQHIZLNOBXXEIDJNZRWSZDJMR2W45BAOV2CA3DBMJXXEZJA\n" + + "MV2CAZDPNRXXEZJANVQWO3TBEBQWY2LROVQS4===\n"; + + // Execute the test using the helper method + executeTestHelper(EncodingMode.ENCODE, + EncodingType.BASE32, + LOREM_IPSUM, + LineOutputMode.MULTIPLE_LINES, + 80, + expectedOutput, + EncodeContent.REL_SUCCESS); + } - testRunner.setProperty(EncodeContent.MODE, EncodeContent.ENCODE_MODE); - testRunner.setProperty(EncodeContent.ENCODING, EncodeContent.HEX_ENCODING); + @Test + void testThatLineLengthIsIgnoredIfSingleLineOutputTrueBase32() { + // this input is greater than 57 bytes, sure to generate multiple lines in base64 + final String expectedOutput = "JRXXEZLNEBUXA43VNUQGI33MN5ZCA43JOQQGC3LFOQWCAY3PNZZWKY3UMV2HK4RAMFSGS4DJONRWS3THEBSWY2LUFQQHGZLEEB" + + "SG6IDFNF2XG3LPMQQHIZLNOBXXEIDJNZRWSZDJMR2W45BAOV2CA3DBMJXXEZJAMV2CAZDPNRXXEZJANVQWO3TBEBQWY2LROVQS4==="; + + // Setting a low value for `lineLength` but single line true ensures that `lineLength` is ignored + executeTestHelper(EncodingMode.ENCODE, + EncodingType.BASE32, + LOREM_IPSUM, + LineOutputMode.SINGLE_LINE, + 2, // set a low value >= 0 + expectedOutput, + EncodeContent.REL_SUCCESS); + } - testRunner.enqueue(FILE_PATH); - testRunner.clearTransferState(); - testRunner.run(); + @Test + void testEncodeContentMultipleLinesNonStandardLengthBase64() { + // this input is greater than 57 bytes, sure to generate multiple lines in base64 + final String expectedOutput = "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdCwgc2Vk\n" + + "IGRvIGVpdXNtb2QgdGVtcG9yIGluY2lkaWR1bnQgdXQgbGFib3JlIGV0IGRvbG9yZSBtYWduYSBhbGlx\n" + + "dWEu\n"; + + // Execute the test using the helper method + executeTestHelper(EncodingMode.ENCODE, + EncodingType.BASE64, + LOREM_IPSUM, + LineOutputMode.MULTIPLE_LINES, // set false to output multiple lines + 80, + expectedOutput, + EncodeContent.REL_SUCCESS); + } - testRunner.assertAllFlowFilesTransferred(EncodeContent.REL_SUCCESS, 1); + @Test + void testThatLineLengthIsIgnoredIfSingleLineOutputTrueBase64() { + // this input is greater than 57 bytes, sure to generate multiple lines in base64 + final String expectedOutput = "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdCwg" + + "c2VkIGRvIGVpdXNtb2QgdGVtcG9yIGluY2lkaWR1bnQgdXQgbGFib3JlIGV0IGRvbG9yZSBtYWdu" + + "YSBhbGlxdWEu"; + + // Setting a low value for `lineLength` but single line true ensures that `lineLength` is ignored + executeTestHelper(EncodingMode.ENCODE, + EncodingType.BASE64, + LOREM_IPSUM, + LineOutputMode.SINGLE_LINE, // set true to output single line + 2, // set a low value >= 0 + expectedOutput, + EncodeContent.REL_SUCCESS); + } - MockFlowFile flowFile = testRunner.getFlowFilesForRelationship(EncodeContent.REL_SUCCESS).get(0); - testRunner.assertQueueEmpty(); + private void executeTestSuccessHelper(final DescribedValue mode, + final DescribedValue encodingType, + final String input, + final String expectedOutput) { + executeTestSuccessHelper(mode, encodingType, input, LineOutputMode.MULTIPLE_LINES, expectedOutput); + } - testRunner.setProperty(EncodeContent.MODE, EncodeContent.DECODE_MODE); - testRunner.enqueue(flowFile); - testRunner.clearTransferState(); - testRunner.run(); - testRunner.assertAllFlowFilesTransferred(EncodeContent.REL_SUCCESS, 1); + private void executeTestSuccessHelper(final DescribedValue mode, + final DescribedValue encodingType, + final String input, + final DescribedValue outputToSingleLine, + final String expectedOutput) { + executeTestHelper(mode, encodingType, input, outputToSingleLine, expectedOutput, EncodeContent.REL_SUCCESS); + } - flowFile = testRunner.getFlowFilesForRelationship(EncodeContent.REL_SUCCESS).get(0); - flowFile.assertContentEquals(FILE_PATH); + private void executeTestHelper(final DescribedValue mode, + final DescribedValue encodingType, + final String input, + final DescribedValue outputToSingleLine, + final String expectedOutput, + final Relationship routedTo) { + executeTestHelper(mode, + encodingType, + input, + outputToSingleLine, + 76, + expectedOutput, + routedTo); } - @Test - public void testFailDecodeNotHex() throws IOException { - final TestRunner testRunner = TestRunners.newTestRunner(new EncodeContent()); + private void executeTestHelper(final DescribedValue mode, + final DescribedValue encodingType, + final String input, + final DescribedValue outputToSingleLine, + final Integer lineLength, + final String expectedOutput, + final Relationship routedTo) { - testRunner.setProperty(EncodeContent.MODE, EncodeContent.DECODE_MODE); - testRunner.setProperty(EncodeContent.ENCODING, EncodeContent.HEX_ENCODING); + testRunner.setProperty(EncodeContent.MODE, mode); + testRunner.setProperty(EncodeContent.ENCODING, encodingType); + testRunner.setProperty(EncodeContent.LINE_OUTPUT_MODE, outputToSingleLine); + testRunner.setProperty(EncodeContent.ENCODED_LINE_LENGTH, Integer.toString(lineLength)); - testRunner.enqueue(FILE_PATH); - testRunner.clearTransferState(); + testRunner.enqueue(input); testRunner.run(); - testRunner.assertAllFlowFilesTransferred(EncodeContent.REL_FAILURE, 1); + final MockFlowFile result = testRunner.getFlowFilesForRelationship(routedTo).get(0); + assertEquals(expectedOutput, result.getContent()); + testRunner.assertAllFlowFilesTransferred(routedTo, 1); } }