This is an automated email from the ASF dual-hosted git repository.
hansva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git
The following commit(s) were added to refs/heads/main by this push:
new cd007456ad Fix Standardize Phone Number country handling, parse
failures, and variable field names (#8238)
cd007456ad is described below
commit cd007456adb7ed1f5ed89d259f5c2080b51c457a
Author: Lance <[email protected]>
AuthorDate: Thu Sep 3 21:53:42 2026 +0800
Fix Standardize Phone Number country handling, parse failures, and variable
field names (#8238)
Signed-off-by: lance <[email protected]>
---
.../transforms/standardizephonenumber.adoc | 14 +-
.../0099-standardize-phone-number-edge-cases.hpl | 182 +++++++++
.../datasets/golden-standardize-phone-number.csv | 8 +
.../main-0099-standardize-phone-number.hwf | 23 +-
.../dataset/golden-standardize-phone-number.json | 40 ++
...9-standardize-phone-number-edge-cases UNIT.json | 48 +++
.../StandardizePhoneNumber.java | 174 +++++---
.../StandardizePhoneNumberMeta.java | 67 +--
.../messages/messages_en_US.properties | 11 +-
.../StandardizePhoneFieldTest.java | 107 +++++
.../StandardizePhoneNumberMetaTest.java | 329 +++++++++++++--
.../StandardizePhoneNumberTest.java | 448 +++++++++++++++++++++
.../standardize-phone-number-transform.xml | 31 ++
13 files changed, 1352 insertions(+), 130 deletions(-)
diff --git
a/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/standardizephonenumber.adoc
b/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/standardizephonenumber.adoc
index f07f15f0d9..960a360ae6 100644
---
a/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/standardizephonenumber.adoc
+++
b/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/standardizephonenumber.adoc
@@ -50,15 +50,19 @@ The transform uses
https://github.com/google/libphonenumber[Google libphonenumbe
|Transform name|Name of the transform.
|Input field|The field holding the phone numbers from the input data.
|Output field|Leave this field empty if you want to update the input field in
stream. Otherwise a new field (string) will be added to the output data.
-|Country field|The field holding the country code from the input data. The
country code is required to be in the ISO alpha-2.
-|DefaultCountry|The default country code to use if input data from country
field is empty or invalid
+|Country field|The field holding the country code from the input data. The
country code is required to be in the ISO alpha-2. Leading and trailing spaces
are ignored, and matching is case-insensitive. Leave this empty to always use
the default country.
+|DefaultCountry|The default country code to use if the country field is empty,
missing, or contains an unsupported value.
|Format|The format to be used to standardize the phone numbers. The available
options are:
* E164 (default)
* INTERNATIONAL
* NATIONAL
* RFC3966
-|Number type|The result field indicates the type of a processed phone number,
such as fixed line, mobile...
-|Is valid|The result field indicates whether a phone number is supposed to be
valid
+|Number type|The result field indicates the type of a processed phone number,
such as fixed line, mobile... Empty or unparseable numbers are set to `ERROR`.
+|Is valid|The result field indicates whether a phone number is supposed to be
valid. Empty or unparseable numbers are `false`.
|===
-TIP: If the original phone number already has an international area code, this
transform will not normalize it to another country even if the international
area code is different from the country input’s international area code.
\ No newline at end of file
+TIP: If the original phone number already has an international area code, this
transform will not normalize it to another country even if the international
area code is different from the country input’s international area code.
+
+TIP: Output, number type, and is-valid field names may contain variables (for
example `${OUTPUT_FIELD}`).
+
+TIP: If a number cannot be parsed, the original value is written to the output
field, the number type is `ERROR`, and is-valid is `false`. Numbers that parse
but are not valid (for example a too-short national number) are still
formatted; use the is-valid field to filter them.
\ No newline at end of file
diff --git
a/integration-tests/transforms/0099-standardize-phone-number-edge-cases.hpl
b/integration-tests/transforms/0099-standardize-phone-number-edge-cases.hpl
new file mode 100644
index 0000000000..ee7830ff69
--- /dev/null
+++ b/integration-tests/transforms/0099-standardize-phone-number-edge-cases.hpl
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+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.
+
+-->
+<pipeline>
+ <info>
+ <name>0099-standardize-phone-number-edge-cases</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <description>Edge cases: optional/default country, parse failure, country
trim, output field variables</description>
+ <extended_description/>
+ <pipeline_version/>
+ <pipeline_type>Normal</pipeline_type>
+ <parameters>
+ <parameter>
+ <name>OUTPUT_FIELD</name>
+ <default_value>E164_OUT</default_value>
+ <description>Resolved output field name</description>
+ </parameter>
+ </parameters>
+ <capture_transform_performance>N</capture_transform_performance>
+
<transform_performance_capturing_delay>1000</transform_performance_capturing_delay>
+
<transform_performance_capturing_size_limit>100</transform_performance_capturing_size_limit>
+ <created_user>-</created_user>
+ <created_date>2026/09/02 00:00:00.000</created_date>
+ <modified_user>-</modified_user>
+ <modified_date>2026/09/02 00:00:00.000</modified_date>
+ </info>
+ <notepads>
+ </notepads>
+ <order>
+ <hop>
+ <from>Data grid</from>
+ <to>Standardize phone number</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Standardize phone number</from>
+ <to>Preview</to>
+ <enabled>Y</enabled>
+ </hop>
+ </order>
+ <transform>
+ <name>Data grid</name>
+ <type>DataGrid</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <fields>
+ <field>
+ <set_empty_string>N</set_empty_string>
+ <length>-1</length>
+ <name>CASE</name>
+ <precision>-1</precision>
+ <type>String</type>
+ </field>
+ <field>
+ <set_empty_string>N</set_empty_string>
+ <length>-1</length>
+ <name>PHONE</name>
+ <precision>-1</precision>
+ <type>String</type>
+ </field>
+ <field>
+ <set_empty_string>N</set_empty_string>
+ <length>-1</length>
+ <name>COUNTRY</name>
+ <precision>-1</precision>
+ <type>String</type>
+ </field>
+ </fields>
+ <data>
+ <line>
+ <item>valid_be</item>
+ <item>0499500158</item>
+ <item>BE</item>
+ </line>
+ <line>
+ <item>invalid</item>
+ <item>error</item>
+ <item>BE</item>
+ </line>
+ <line>
+ <item>empty</item>
+ <item/>
+ <item>BE</item>
+ </line>
+ <line>
+ <item>be_space</item>
+ <item>0499500158</item>
+ <item>BE </item>
+ </line>
+ <line>
+ <item>be_lower</item>
+ <item>0499500158</item>
+ <item>be</item>
+ </line>
+ <line>
+ <item>no_country</item>
+ <item>0499500158</item>
+ <item/>
+ </line>
+ <line>
+ <item>junk</item>
+ <item>999</item>
+ <item>FR</item>
+ </line>
+ </data>
+ <attributes/>
+ <GUI>
+ <xloc>64</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>Standardize phone number</name>
+ <type>StandardizePhoneNumber</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <fields>
+ <field>
+ <input>PHONE</input>
+ <output>${OUTPUT_FIELD}</output>
+ <format>E164</format>
+ <country>COUNTRY</country>
+ <defaultCountry>BE</defaultCountry>
+ <numbertype>TYPE</numbertype>
+ <isvalidnumber>VALID</isvalidnumber>
+ </field>
+ </fields>
+ <attributes/>
+ <GUI>
+ <xloc>304</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>Preview</name>
+ <type>Dummy</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ <GUI>
+ <xloc>560</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform_error_handling>
+ </transform_error_handling>
+ <attributes/>
+</pipeline>
diff --git
a/integration-tests/transforms/datasets/golden-standardize-phone-number.csv
b/integration-tests/transforms/datasets/golden-standardize-phone-number.csv
new file mode 100644
index 0000000000..a37918ac3f
--- /dev/null
+++ b/integration-tests/transforms/datasets/golden-standardize-phone-number.csv
@@ -0,0 +1,8 @@
+CASE,E164_OUT,TYPE,VALID
+valid_be,+32499500158,MOBILE,Y
+invalid,error,ERROR,N
+empty,,ERROR,N
+be_space,+32499500158,MOBILE,Y
+be_lower,+32499500158,MOBILE,Y
+no_country,+32499500158,MOBILE,Y
+junk,+33999,UNKNOWN,N
diff --git
a/integration-tests/transforms/main-0099-standardize-phone-number.hwf
b/integration-tests/transforms/main-0099-standardize-phone-number.hwf
index 55c901ca0f..7c6169060c 100644
--- a/integration-tests/transforms/main-0099-standardize-phone-number.hwf
+++ b/integration-tests/transforms/main-0099-standardize-phone-number.hwf
@@ -18,7 +18,7 @@ limitations under the License.
-->
<workflow>
- <name>main-0090-sort-rows</name>
+ <name>main-0099-standardize-phone-number</name>
<name_sync_with_filename>Y</name_sync_with_filename>
<created_user>-</created_user>
<modified_user>-</modified_user>
@@ -72,6 +72,20 @@ limitations under the License.
<parallel>N</parallel>
<attributes_hac/>
</action>
+ <action>
+ <test_names>
+ <test_name>
+ <name>0099-standardize-phone-number-edge-cases UNIT</name>
+ </test_name>
+ </test_names>
+ <name>0099-standardize-phone-number-edge-cases UNIT</name>
+ <type>RunPipelineTests</type>
+ <attributes/>
+ <xloc>624</xloc>
+ <yloc>64</yloc>
+ <parallel>N</parallel>
+ <attributes_hac/>
+ </action>
</actions>
<hops>
<hop>
@@ -81,6 +95,13 @@ limitations under the License.
<unconditional>Y</unconditional>
<enabled>Y</enabled>
</hop>
+ <hop>
+ <from>0099-standardize-phone-number.hpl</from>
+ <to>0099-standardize-phone-number-edge-cases UNIT</to>
+ <evaluation>Y</evaluation>
+ <unconditional>N</unconditional>
+ <enabled>Y</enabled>
+ </hop>
</hops>
<notepads/>
<attributes/>
diff --git
a/integration-tests/transforms/metadata/dataset/golden-standardize-phone-number.json
b/integration-tests/transforms/metadata/dataset/golden-standardize-phone-number.json
new file mode 100644
index 0000000000..4c3b9621c8
--- /dev/null
+++
b/integration-tests/transforms/metadata/dataset/golden-standardize-phone-number.json
@@ -0,0 +1,40 @@
+{
+ "base_filename": "golden-standardize-phone-number.csv",
+ "name": "golden-standardize-phone-number",
+ "description": "Expected output for
0099-standardize-phone-number-edge-cases",
+ "dataset_fields": [
+ {
+ "field_comment": "",
+ "field_length": -1,
+ "field_type": 2,
+ "field_precision": -1,
+ "field_format": "",
+ "field_name": "CASE"
+ },
+ {
+ "field_comment": "",
+ "field_length": -1,
+ "field_type": 2,
+ "field_precision": -1,
+ "field_format": "",
+ "field_name": "E164_OUT"
+ },
+ {
+ "field_comment": "",
+ "field_length": -1,
+ "field_type": 2,
+ "field_precision": -1,
+ "field_format": "",
+ "field_name": "TYPE"
+ },
+ {
+ "field_comment": "",
+ "field_length": -1,
+ "field_type": 4,
+ "field_precision": -1,
+ "field_format": "",
+ "field_name": "VALID"
+ }
+ ],
+ "folder_name": ""
+}
diff --git
a/integration-tests/transforms/metadata/unit-test/0099-standardize-phone-number-edge-cases
UNIT.json
b/integration-tests/transforms/metadata/unit-test/0099-standardize-phone-number-edge-cases
UNIT.json
new file mode 100644
index 0000000000..073b94bcd8
--- /dev/null
+++
b/integration-tests/transforms/metadata/unit-test/0099-standardize-phone-number-edge-cases
UNIT.json
@@ -0,0 +1,48 @@
+{
+ "variableValues": [
+ {
+ "key": "OUTPUT_FIELD",
+ "value": "E164_OUT"
+ }
+ ],
+ "database_replacements": [],
+ "autoOpening": true,
+ "basePath": "",
+ "golden_data_sets": [
+ {
+ "field_mappings": [
+ {
+ "transform_field": "CASE",
+ "data_set_field": "CASE"
+ },
+ {
+ "transform_field": "E164_OUT",
+ "data_set_field": "E164_OUT"
+ },
+ {
+ "transform_field": "TYPE",
+ "data_set_field": "TYPE"
+ },
+ {
+ "transform_field": "VALID",
+ "data_set_field": "VALID"
+ }
+ ],
+ "field_order": [
+ "CASE",
+ "E164_OUT",
+ "TYPE",
+ "VALID"
+ ],
+ "transform_name": "Preview",
+ "data_set_name": "golden-standardize-phone-number"
+ }
+ ],
+ "input_data_sets": [],
+ "name": "0099-standardize-phone-number-edge-cases UNIT",
+ "description": "Standardize phone number edge cases: parse failure, empty
number, country trim, variables",
+ "persist_filename": "",
+ "trans_test_tweaks": [],
+ "pipeline_filename": "./0099-standardize-phone-number-edge-cases.hpl",
+ "test_type": "UNIT_TEST"
+}
diff --git
a/plugins/transforms/standardizephonenumber/src/main/java/org/apache/hop/pipeline/transforms/standardizephonenumber/StandardizePhoneNumber.java
b/plugins/transforms/standardizephonenumber/src/main/java/org/apache/hop/pipeline/transforms/standardizephonenumber/StandardizePhoneNumber.java
index f9da3af078..3c9f313539 100644
---
a/plugins/transforms/standardizephonenumber/src/main/java/org/apache/hop/pipeline/transforms/standardizephonenumber/StandardizePhoneNumber.java
+++
b/plugins/transforms/standardizephonenumber/src/main/java/org/apache/hop/pipeline/transforms/standardizephonenumber/StandardizePhoneNumber.java
@@ -22,6 +22,7 @@ import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
import java.util.Arrays;
+import java.util.Locale;
import java.util.Set;
import org.apache.hop.core.exception.HopException;
import org.apache.hop.core.row.IRowMeta;
@@ -36,6 +37,8 @@ public class StandardizePhoneNumber
extends BaseTransform<StandardizePhoneNumberMeta,
StandardizePhoneNumberData> {
private static final Class<?> PKG = StandardizePhoneNumber.class;
+ static final String NUMBER_TYPE_ERROR = "ERROR";
+
private PhoneNumberUtil phoneNumberService;
private Set<String> supportedRegions;
@@ -87,94 +90,61 @@ public class StandardizePhoneNumber
Object[] outputRow = Arrays.copyOf(row, data.outputRowMeta.size());
for (StandardizePhoneField standardize : meta.getFields()) {
+ String inputField = resolve(standardize.getInputField());
+ String outputField = resolve(standardize.getOutputField());
+ String countryField = resolve(standardize.getCountryField());
+ String numberTypeField = resolve(standardize.getNumberTypeField());
+ String isValidNumberField = resolve(standardize.getIsValidNumberField());
+ String numberFormat = resolve(standardize.getNumberFormat());
- // Default region
- String region = standardize.getDefaultCountry();
- if (!Utils.isEmpty(standardize.getCountryField())) {
-
- int index = inputRowMeta.indexOfValue(standardize.getCountryField());
-
- // if country field not found
- if (index < 0) {
- logError(
- BaseMessages.getString(
- PKG,
- "StandardizePhoneNumber.Log.CountryFieldNotFound",
- standardize.getCountryField()));
- this.setErrors(1);
- return false;
- }
-
- String country = inputRowMeta.getString(row, index);
- if (country == null || Utils.isEmpty(country)) {
- region = standardize.getDefaultCountry();
- } else if (supportedRegions.contains(country.toUpperCase())) {
- region = country.toUpperCase();
- } else {
- logError(
- BaseMessages.getString(
- PKG, "StandardizePhoneNumber.Log.RegionNotSupported",
country));
- region = standardize.getDefaultCountry();
- }
- }
+ String region = resolveRegion(standardize, countryField, inputRowMeta,
row);
- // Parse phone number
- String value = null;
- int index = inputRowMeta.indexOfValue(standardize.getInputField());
+ int inputIndex = inputRowMeta.indexOfValue(inputField);
// if input field not found
- if (index < 0) {
+ if (inputIndex < 0) {
this.logError(
BaseMessages.getString(
- PKG, "StandardizePhoneNumber.Log.InputFieldNotFound",
standardize.getInputField()));
+ PKG, "StandardizePhoneNumber.Log.InputFieldNotFound",
inputField));
this.setErrors(1);
return false;
}
- value = inputRowMeta.getString(row, index);
- if (value != null && !Utils.isEmpty(value)) {
+ int outputIndex = inputIndex;
+ if (!Utils.isEmpty(outputField)) {
+ int resolvedOutputIndex = data.outputRowMeta.indexOfValue(outputField);
+ if (resolvedOutputIndex >= 0) {
+ outputIndex = resolvedOutputIndex;
+ }
+ }
+
+ String originalValue = inputRowMeta.getString(row, inputIndex);
+ if (!Utils.isEmpty(originalValue)) {
PhoneNumber phoneNumber = null;
try {
- // Replace unsupported character wit blank
- value = value.replace(',', ' ');
+ // Replace unsupported character with blank
+ String value = originalValue.replace(',', ' ');
- // Format
- PhoneNumberFormat format =
getPhoneNumberFormat(standardize.getNumberFormat());
-
- // Parse phone number
+ PhoneNumberFormat format = getPhoneNumberFormat(numberFormat);
phoneNumber = phoneNumberService.parse(value, region);
- if (!Utils.isEmpty(standardize.getOutputField())) {
- index =
data.outputRowMeta.indexOfValue(standardize.getOutputField());
- }
- outputRow[index] = phoneNumberService.format(phoneNumber, format);
+ outputRow[outputIndex] = phoneNumberService.format(phoneNumber,
format);
} catch (NumberParseException e) {
+ outputRow[outputIndex] = originalValue;
if (isRowLevel()) {
logRowlevel(
BaseMessages.getString(
PKG,
"StandardizePhoneNumber.Log.ProcessPhoneNumberError",
- standardize.getInputField(),
- value));
+ inputField,
+ originalValue));
}
}
- if (!Utils.isEmpty(standardize.getNumberTypeField())) {
- int i =
data.outputRowMeta.indexOfValue(standardize.getNumberTypeField());
- if (phoneNumber != null) {
- outputRow[i] =
phoneNumberService.getNumberType(phoneNumber).toString();
- } else outputRow[i] = "ERROR";
- }
-
- if (!Utils.isEmpty(standardize.getIsValidNumberField())) {
- int i =
data.outputRowMeta.indexOfValue(standardize.getIsValidNumberField());
- if (phoneNumber != null) outputRow[i] =
phoneNumberService.isValidNumber(phoneNumber);
- else outputRow[i] = false;
- }
+ setNumberType(outputRow, numberTypeField, phoneNumber);
+ setIsValid(outputRow, isValidNumberField, phoneNumber);
} else {
- if (!Utils.isEmpty(standardize.getIsValidNumberField())) {
- int i =
data.outputRowMeta.indexOfValue(standardize.getIsValidNumberField());
- outputRow[i] = false;
- }
+ setNumberType(outputRow, numberTypeField, null);
+ setIsValid(outputRow, isValidNumberField, null);
}
}
@@ -196,6 +166,82 @@ public class StandardizePhoneNumber
return true;
}
+ /**
+ * Normalize a country / region code: trim whitespace and upper-case using
{@link Locale#ROOT}.
+ *
+ * @param country raw country code, may be null
+ * @return ISO alpha-2 region or {@code null} if empty
+ */
+ static String normalizeRegion(String country) {
+ if (country == null) {
+ return null;
+ }
+ String normalized = country.trim().toUpperCase(Locale.ROOT);
+ return normalized.isEmpty() ? null : normalized;
+ }
+
+ private String resolveRegion(
+ StandardizePhoneField standardize, String countryField, IRowMeta
inputRowMeta, Object[] row)
+ throws HopException {
+ String region = normalizeRegion(resolve(standardize.getDefaultCountry()));
+ if (Utils.isEmpty(countryField)) {
+ return region;
+ }
+
+ int index = inputRowMeta.indexOfValue(countryField);
+
+ // if country field not found
+ if (index < 0) {
+ String message =
+ BaseMessages.getString(
+ PKG, "StandardizePhoneNumber.Log.CountryFieldNotFound",
countryField);
+ logError(message);
+ this.setErrors(1);
+ throw new HopException(message);
+ }
+
+ String country = inputRowMeta.getString(row, index);
+ String normalized = normalizeRegion(country);
+ if (normalized == null) {
+ return region;
+ }
+ if (supportedRegions.contains(normalized)) {
+ return normalized;
+ }
+ logError(BaseMessages.getString(PKG,
"StandardizePhoneNumber.Log.RegionNotSupported", country));
+ return region;
+ }
+
+ private void setNumberType(Object[] outputRow, String numberTypeField,
PhoneNumber phoneNumber) {
+ if (Utils.isEmpty(numberTypeField)) {
+ return;
+ }
+ int i = data.outputRowMeta.indexOfValue(numberTypeField);
+ if (i < 0) {
+ return;
+ }
+ if (phoneNumber != null) {
+ outputRow[i] = phoneNumberService.getNumberType(phoneNumber).toString();
+ } else {
+ outputRow[i] = NUMBER_TYPE_ERROR;
+ }
+ }
+
+ private void setIsValid(Object[] outputRow, String isValidNumberField,
PhoneNumber phoneNumber) {
+ if (Utils.isEmpty(isValidNumberField)) {
+ return;
+ }
+ int i = data.outputRowMeta.indexOfValue(isValidNumberField);
+ if (i < 0) {
+ return;
+ }
+ if (phoneNumber != null) {
+ outputRow[i] = phoneNumberService.isValidNumber(phoneNumber);
+ } else {
+ outputRow[i] = false;
+ }
+ }
+
@Override
public boolean init() {
diff --git
a/plugins/transforms/standardizephonenumber/src/main/java/org/apache/hop/pipeline/transforms/standardizephonenumber/StandardizePhoneNumberMeta.java
b/plugins/transforms/standardizephonenumber/src/main/java/org/apache/hop/pipeline/transforms/standardizephonenumber/StandardizePhoneNumberMeta.java
index 02d54e6164..e800d09ea7 100644
---
a/plugins/transforms/standardizephonenumber/src/main/java/org/apache/hop/pipeline/transforms/standardizephonenumber/StandardizePhoneNumberMeta.java
+++
b/plugins/transforms/standardizephonenumber/src/main/java/org/apache/hop/pipeline/transforms/standardizephonenumber/StandardizePhoneNumberMeta.java
@@ -92,35 +92,34 @@ public class StandardizePhoneNumberMeta
try {
// add the extra fields if specified
for (StandardizePhoneField standardize : this.getFields()) {
+ String inputField = resolveName(variables,
standardize.getInputField());
+ String outputField = resolveName(variables,
standardize.getOutputField());
+ String numberTypeField = resolveName(variables,
standardize.getNumberTypeField());
+ String isValidNumberField = resolveName(variables,
standardize.getIsValidNumberField());
// add the output fields if specified
- int index = inputRowMeta.indexOfValue(standardize.getInputField());
- IValueMeta valueMeta = inputRowMeta.getValueMeta(index);
- if (!Utils.isEmpty(standardize.getOutputField())
- &&
!standardize.getOutputField().equals(standardize.getInputField())) {
+ int index = inputRowMeta.indexOfValue(inputField);
+ IValueMeta valueMeta = index >= 0 ? inputRowMeta.getValueMeta(index) :
null;
+ if (!Utils.isEmpty(outputField) && !outputField.equals(inputField)) {
// created output field only if name changed
- valueMeta =
- ValueMetaFactory.createValueMeta(
- standardize.getOutputField(), IValueMeta.TYPE_STRING);
+ valueMeta = ValueMetaFactory.createValueMeta(outputField,
IValueMeta.TYPE_STRING);
inputRowMeta.addValueMeta(valueMeta);
}
- valueMeta.setOrigin(name);
+ if (valueMeta != null) {
+ valueMeta.setOrigin(name);
+ }
// add result phone number type
- if (!Utils.isEmpty(standardize.getNumberTypeField())) {
- valueMeta =
- ValueMetaFactory.createValueMeta(
- standardize.getNumberTypeField(), IValueMeta.TYPE_STRING);
+ if (!Utils.isEmpty(numberTypeField)) {
+ valueMeta = ValueMetaFactory.createValueMeta(numberTypeField,
IValueMeta.TYPE_STRING);
valueMeta.setOrigin(name);
inputRowMeta.addValueMeta(valueMeta);
}
// add result is valid number
- if (!Utils.isEmpty(standardize.getIsValidNumberField())) {
- valueMeta =
- ValueMetaFactory.createValueMeta(
- standardize.getIsValidNumberField(),
IValueMeta.TYPE_BOOLEAN);
+ if (!Utils.isEmpty(isValidNumberField)) {
+ valueMeta = ValueMetaFactory.createValueMeta(isValidNumberField,
IValueMeta.TYPE_BOOLEAN);
valueMeta.setOrigin(name);
inputRowMeta.addValueMeta(valueMeta);
}
@@ -173,32 +172,35 @@ public class StandardizePhoneNumberMeta
// Check only if input fields
for (StandardizePhoneField standardize : fields) {
+ String inputField = resolveName(variables,
standardize.getInputField());
+ String outputField = resolveName(variables,
standardize.getOutputField());
+ String countryField = resolveName(variables,
standardize.getCountryField());
// See if there are missing input streams
IValueMeta valueMeta = null;
if (prev != null) {
- valueMeta = prev.searchValueMeta(standardize.getInputField());
+ valueMeta = prev.searchValueMeta(inputField);
}
if (valueMeta == null) {
String message =
BaseMessages.getString(
PKG,
"StandardizePhoneNumberMeta.CheckResult.MissingInputField",
- Const.NVL(standardize.getInputField(),
standardize.getOutputField()));
+ Const.NVL(inputField, outputField));
remarks.add(new CheckResult(ICheckResult.TYPE_RESULT_ERROR, message,
transformMeta));
}
- // See if there are missing input streams
- if (prev != null) {
- valueMeta = prev.searchValueMeta(standardize.getCountryField());
- }
- if (valueMeta == null) {
- String message =
- BaseMessages.getString(
- PKG,
- "StandardizePhoneNumberMeta.CheckResult.MissingCountryField",
- standardize.getCountryField());
- remarks.add(new CheckResult(ICheckResult.TYPE_RESULT_ERROR, message,
transformMeta));
+ // Country field is optional when a default country is used
+ if (!Utils.isEmpty(countryField)) {
+ valueMeta = prev != null ? prev.searchValueMeta(countryField) : null;
+ if (valueMeta == null) {
+ String message =
+ BaseMessages.getString(
+ PKG,
+
"StandardizePhoneNumberMeta.CheckResult.MissingCountryField",
+ countryField);
+ remarks.add(new CheckResult(ICheckResult.TYPE_RESULT_ERROR,
message, transformMeta));
+ }
}
}
@@ -242,4 +244,11 @@ public class StandardizePhoneNumberMeta
public void setFields(final List<StandardizePhoneField> standardizes) {
this.fields = standardizes;
}
+
+ static String resolveName(IVariables variables, String name) {
+ if (Utils.isEmpty(name) || variables == null) {
+ return name;
+ }
+ return variables.resolve(name);
+ }
}
diff --git
a/plugins/transforms/standardizephonenumber/src/main/resources/org/apache/hop/pipeline/transforms/standardizephonenumber/messages/messages_en_US.properties
b/plugins/transforms/standardizephonenumber/src/main/resources/org/apache/hop/pipeline/transforms/standardizephonenumber/messages/messages_en_US.properties
index cff0ce4a11..b5c42802e9 100644
---
a/plugins/transforms/standardizephonenumber/src/main/resources/org/apache/hop/pipeline/transforms/standardizephonenumber/messages/messages_en_US.properties
+++
b/plugins/transforms/standardizephonenumber/src/main/resources/org/apache/hop/pipeline/transforms/standardizephonenumber/messages/messages_en_US.properties
@@ -21,6 +21,7 @@
StandardizePhoneNumber.Description=Normalize phone number in a standardized
and consistent manner using Google libphonenumber library.
StandardizePhoneNumber.Injection.CountryField=Country field
StandardizePhoneNumber.Injection.DefaultCountry=Default country
+StandardizePhoneNumber.Injection.Field=Field to standardize
StandardizePhoneNumber.Injection.Fields=Fields to standardize
StandardizePhoneNumber.Injection.InputField=Input field
StandardizePhoneNumber.Injection.IsValidNumberField=Is valid
@@ -35,19 +36,19 @@ StandardizePhoneNumber.Log.StartedProcessing=Started
transform processing
StandardizePhoneNumber.Log.WroteRowToNextTransform=Wrote row to next
transform: {0}
StandardizePhoneNumber.Name=Standardize phone number
StandardizePhoneNumberDialog.ColumnInfo.CountryField.Label=Country field
-StandardizePhoneNumberDialog.ColumnInfo.CountryField.Tooltip=The field holding
the country code from the input data. The country code is required to be in the
ISO alpha-2.
+StandardizePhoneNumberDialog.ColumnInfo.CountryField.Tooltip=The field holding
the country code from the input data. The country code is required to be in the
ISO alpha-2. Leading and trailing spaces are ignored. This field is optional
when a default country is set.
StandardizePhoneNumberDialog.ColumnInfo.DefaultCountry.Label=Default country
-StandardizePhoneNumberDialog.ColumnInfo.DefaultCountry.Tooltip=The default
country code to use if input data from country field is empty or invalid
+StandardizePhoneNumberDialog.ColumnInfo.DefaultCountry.Tooltip=The default
country code to use if the country field is empty, missing, or contains an
unsupported value
StandardizePhoneNumberDialog.ColumnInfo.InputField.Label=Input field
StandardizePhoneNumberDialog.ColumnInfo.InputField.Tooltip=The field holding
the phone numbers from the input data.
StandardizePhoneNumberDialog.ColumnInfo.IsValidNumberField.Label=Is valid field
-StandardizePhoneNumberDialog.ColumnInfo.IsValidNumberField.Tooltip=The result
field indicates whether a phone number is supposed to be valid
+StandardizePhoneNumberDialog.ColumnInfo.IsValidNumberField.Tooltip=The result
field indicates whether a phone number is supposed to be valid. Empty or
unparseable numbers are false.
StandardizePhoneNumberDialog.ColumnInfo.NumberFormat.Label=Format
StandardizePhoneNumberDialog.ColumnInfo.NumberFormat.Tooltip=The format to be
used to standardize the phone number\n E164 +41446681800\n INTERNATIONAL +41 44
668 18 00\n NATIONAL 044 668 18 00\n RFC3966 tel:+41-44-668-18-00
StandardizePhoneNumberDialog.ColumnInfo.NumberTypeField.Label=Number type field
-StandardizePhoneNumberDialog.ColumnInfo.NumberTypeField.Tooltip=The result
field indicates the type of a processed phone number, such as fixed line,
mobile...
+StandardizePhoneNumberDialog.ColumnInfo.NumberTypeField.Tooltip=The result
field indicates the type of a processed phone number, such as fixed line,
mobile... Empty or unparseable numbers are ERROR.
StandardizePhoneNumberDialog.ColumnInfo.OutputField.Label=Output field
-StandardizePhoneNumberDialog.ColumnInfo.OutputField.Tooltip=Leave this field
empty if you want to update the input field in stream\n. Otherwise a new field
(string) will be added to the output data.
+StandardizePhoneNumberDialog.ColumnInfo.OutputField.Tooltip=Leave this field
empty if you want to update the input field in stream\n. Otherwise a new field
(string) will be added to the output data. Field names may contain variables.
If parsing fails, the original value is copied to this field.
StandardizePhoneNumberDialog.Fields.Label=Fields to standardize
StandardizePhoneNumberDialog.Shell.Title=Standardize phone number
StandardizePhoneNumberMeta.CheckResult.MissingCountryField=Missing country
input field: [{0}]
diff --git
a/plugins/transforms/standardizephonenumber/src/test/java/org/apache/hop/pipeline/transforms/standardizephonenumber/StandardizePhoneFieldTest.java
b/plugins/transforms/standardizephonenumber/src/test/java/org/apache/hop/pipeline/transforms/standardizephonenumber/StandardizePhoneFieldTest.java
new file mode 100644
index 0000000000..247371742f
--- /dev/null
+++
b/plugins/transforms/standardizephonenumber/src/test/java/org/apache/hop/pipeline/transforms/standardizephonenumber/StandardizePhoneFieldTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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.hop.pipeline.transforms.standardizephonenumber;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
+import org.junit.jupiter.api.Test;
+
+/** Unit test for {@link StandardizePhoneField} */
+class StandardizePhoneFieldTest {
+
+ @Test
+ void defaultsUseE164Format() {
+ StandardizePhoneField field = new StandardizePhoneField();
+ assertEquals(PhoneNumberFormat.E164.name(), field.getNumberFormat());
+ assertNull(field.getInputField());
+ assertNull(field.getOutputField());
+ assertNull(field.getCountryField());
+ assertNull(field.getNumberTypeField());
+ assertNull(field.getIsValidNumberField());
+ }
+
+ @Test
+ void settersStripBlankValuesToNull() {
+ StandardizePhoneField field = new StandardizePhoneField();
+ field.setInputField(" ");
+ field.setOutputField("");
+ field.setCountryField("\t");
+ field.setNumberTypeField(" ");
+ field.setIsValidNumberField(" ");
+
+ assertNull(field.getInputField());
+ assertNull(field.getOutputField());
+ assertNull(field.getCountryField());
+ assertNull(field.getNumberTypeField());
+ assertNull(field.getIsValidNumberField());
+ }
+
+ @Test
+ void settersTrimSurroundingWhitespace() {
+ StandardizePhoneField field = new StandardizePhoneField();
+ field.setInputField(" PHONE ");
+ field.setOutputField(" E164 ");
+ field.setCountryField(" COUNTRY ");
+ field.setNumberTypeField(" TYPE ");
+ field.setIsValidNumberField(" VALID ");
+
+ assertEquals("PHONE", field.getInputField());
+ assertEquals("E164", field.getOutputField());
+ assertEquals("COUNTRY", field.getCountryField());
+ assertEquals("TYPE", field.getNumberTypeField());
+ assertEquals("VALID", field.getIsValidNumberField());
+ }
+
+ @Test
+ void cloneCopiesAllProperties() {
+ StandardizePhoneField original = new StandardizePhoneField();
+ original.setInputField("PHONE");
+ original.setOutputField("PHONE_E164");
+ original.setNumberFormat("NATIONAL");
+ original.setCountryField("COUNTRY");
+ original.setDefaultCountry("BE");
+ original.setNumberTypeField("TYPE");
+ original.setIsValidNumberField("VALID");
+
+ StandardizePhoneField copy = (StandardizePhoneField) original.clone();
+
+ assertNotSame(original, copy);
+ assertEquals(original.getInputField(), copy.getInputField());
+ assertEquals(original.getOutputField(), copy.getOutputField());
+ assertEquals(original.getNumberFormat(), copy.getNumberFormat());
+ assertEquals(original.getCountryField(), copy.getCountryField());
+ assertEquals(original.getDefaultCountry(), copy.getDefaultCountry());
+ assertEquals(original.getNumberTypeField(), copy.getNumberTypeField());
+ assertEquals(original.getIsValidNumberField(),
copy.getIsValidNumberField());
+ }
+
+ @Test
+ void copyConstructorMatchesClone() {
+ StandardizePhoneField original = new StandardizePhoneField();
+ original.setInputField("PHONE");
+ original.setOutputField("OUT");
+ original.setDefaultCountry("FR");
+
+ StandardizePhoneField copy = new StandardizePhoneField(original);
+ assertEquals("PHONE", copy.getInputField());
+ assertEquals("OUT", copy.getOutputField());
+ assertEquals("FR", copy.getDefaultCountry());
+ }
+}
diff --git
a/plugins/transforms/standardizephonenumber/src/test/java/org/apache/hop/pipeline/transforms/standardizephonenumber/StandardizePhoneNumberMetaTest.java
b/plugins/transforms/standardizephonenumber/src/test/java/org/apache/hop/pipeline/transforms/standardizephonenumber/StandardizePhoneNumberMetaTest.java
index 58a6acb347..52316358d2 100644
---
a/plugins/transforms/standardizephonenumber/src/test/java/org/apache/hop/pipeline/transforms/standardizephonenumber/StandardizePhoneNumberMetaTest.java
+++
b/plugins/transforms/standardizephonenumber/src/test/java/org/apache/hop/pipeline/transforms/standardizephonenumber/StandardizePhoneNumberMetaTest.java
@@ -14,43 +14,320 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package org.apache.hop.pipeline.transforms.standardizephonenumber;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
+import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
-import org.apache.hop.core.exception.HopException;
-import org.apache.hop.junit.rules.RestoreHopEngineEnvironmentExtension;
-import org.apache.hop.pipeline.transforms.loadsave.LoadSaveTester;
-import
org.apache.hop.pipeline.transforms.loadsave.validator.IFieldLoadSaveValidator;
-import org.junit.jupiter.api.extension.RegisterExtension;
+import org.apache.hop.core.HopClientEnvironment;
+import org.apache.hop.core.ICheckResult;
+import org.apache.hop.core.row.IValueMeta;
+import org.apache.hop.core.row.RowMeta;
+import org.apache.hop.core.row.value.ValueMetaString;
+import org.apache.hop.core.variables.Variables;
+import org.apache.hop.i18n.BaseMessages;
+import org.apache.hop.pipeline.PipelineMeta;
+import org.apache.hop.pipeline.transform.TransformMeta;
+import org.apache.hop.pipeline.transform.TransformSerializationTestUtil;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+/** Unit test for {@link StandardizePhoneNumberMeta} */
class StandardizePhoneNumberMetaTest {
- @RegisterExtension
- static RestoreHopEngineEnvironmentExtension env = new
RestoreHopEngineEnvironmentExtension();
- /**
- * @throws HopException
- */
- public void test() throws HopException {
- List<String> attributes = Arrays.asList("fields");
+ @BeforeEach
+ void setUp() throws Exception {
+ HopClientEnvironment.init();
+ }
+
+ @Test
+ void testSerializationRoundTrip() throws Exception {
+ StandardizePhoneNumberMeta meta =
+ TransformSerializationTestUtil.testSerialization(
+ "/standardize-phone-number-transform.xml",
StandardizePhoneNumberMeta.class);
+
+ assertEquals(1, meta.getFields().size());
+ StandardizePhoneField field = meta.getFields().get(0);
+ assertEquals("PHONE", field.getInputField());
+ assertEquals("PHONE_CLEANED", field.getOutputField());
+ assertEquals("E164", field.getNumberFormat());
+ assertEquals("COUNTRY", field.getCountryField());
+ assertEquals("BE", field.getDefaultCountry());
+ assertEquals("TYPE", field.getNumberTypeField());
+ assertEquals("VALID", field.getIsValidNumberField());
+ }
+
+ @Test
+ void setDefaultLeavesFieldsEmpty() {
+ StandardizePhoneNumberMeta meta = new StandardizePhoneNumberMeta();
+ meta.setDefault();
+ assertTrue(meta.getFields().isEmpty());
+ }
+
+ @Test
+ void getAndSetFieldsRoundTrip() {
+ StandardizePhoneNumberMeta meta = new StandardizePhoneNumberMeta();
+ StandardizePhoneField field = new StandardizePhoneField();
+ field.setInputField("PHONE");
+ List<StandardizePhoneField> fields = List.of(field);
+ meta.setFields(fields);
+ assertEquals(fields, meta.getFields());
+ }
+
+ @Test
+ void checkDoesNotRequireCountryField() {
+ StandardizePhoneNumberMeta meta = metaWith("PHONE", "PHONE_E164", null);
+
+ List<ICheckResult> remarks = check(meta, phonePrev(), new String[]
{"prev"});
+
+ assertFalse(
+ remarks.stream()
+ .anyMatch(
+ remark ->
+ remark.getType() == ICheckResult.TYPE_RESULT_ERROR
+ && remark.getText() != null
+ && remark.getText().contains("country")),
+ "Optional country field should not produce a check error");
+ }
+
+ @Test
+ void checkReportsMissingCountryFieldWhenConfigured() {
+ StandardizePhoneNumberMeta meta = metaWith("PHONE", "PHONE_E164",
"COUNTRY");
+
+ List<ICheckResult> remarks = check(meta, phonePrev(), new String[]
{"prev"});
- Map<String, String> getterMap = new HashMap<>();
- Map<String, String> setterMap = new HashMap<>();
+ assertTrue(
+ remarks.stream()
+ .anyMatch(
+ remark ->
+ remark.getType() == ICheckResult.TYPE_RESULT_ERROR
+ && remark.getText() != null
+ && remark.getText().contains("COUNTRY")));
+ }
+
+ @Test
+ void checkReportsMissingInputField() {
+ StandardizePhoneNumberMeta meta = metaWith("MISSING", "OUT", null);
+
+ List<ICheckResult> remarks = check(meta, phonePrev(), new String[]
{"prev"});
+
+ assertTrue(
+ remarks.stream()
+ .anyMatch(
+ remark ->
+ remark.getType() == ICheckResult.TYPE_RESULT_ERROR
+ && remark.getText() != null
+ && remark.getText().contains("MISSING")));
+ }
+
+ @Test
+ void checkWarnsWhenPreviousFieldsAreMissing() {
+ StandardizePhoneNumberMeta meta = new StandardizePhoneNumberMeta();
+
+ List<ICheckResult> remarks = check(meta, new RowMeta(), new String[]
{"prev"});
+
+ assertTrue(
+ remarks.stream().anyMatch(remark -> remark.getType() ==
ICheckResult.TYPE_RESULT_WARNING));
+ }
+
+ @Test
+ void checkErrorsWhenNoInputStreams() {
+ StandardizePhoneNumberMeta meta = metaWith("PHONE", "OUT", null);
- Map<String, IFieldLoadSaveValidator<?>> fieldLoadSaveValidatorAttributeMap
= new HashMap<>();
+ List<ICheckResult> remarks = check(meta, phonePrev(), new String[] {});
- LoadSaveTester<StandardizePhoneNumberMeta> loadSaveTester =
- new LoadSaveTester<>(
+ String expected =
+ BaseMessages.getString(
StandardizePhoneNumberMeta.class,
- attributes,
- getterMap,
- setterMap,
- fieldLoadSaveValidatorAttributeMap,
- new HashMap<>());
+
"StandardizePhoneNumberMeta.CheckResult.NotReceivingInfoFromOtherTransforms");
+ assertTrue(
+ remarks.stream()
+ .anyMatch(
+ remark ->
+ remark.getType() == ICheckResult.TYPE_RESULT_ERROR
+ && expected.equals(remark.getText())));
+ }
+
+ @Test
+ void checkOkWhenReceivingFieldsAndInput() {
+ StandardizePhoneNumberMeta meta = metaWith("PHONE", "OUT", "COUNTRY");
+ RowMeta prev = phonePrev();
+ prev.addValueMeta(new ValueMetaString("COUNTRY"));
+
+ List<ICheckResult> remarks = check(meta, prev, new String[] {"prev"});
+
+ assertTrue(
+ remarks.stream().anyMatch(remark -> remark.getType() ==
ICheckResult.TYPE_RESULT_OK));
+ assertFalse(
+ remarks.stream().anyMatch(remark -> remark.getType() ==
ICheckResult.TYPE_RESULT_ERROR));
+ }
+
+ @Test
+ void checkResolvesVariableFieldNames() {
+ StandardizePhoneNumberMeta meta = metaWith("${IN}", "OUT", "${COUNTRY}");
+ RowMeta prev = phonePrev();
+ prev.addValueMeta(new ValueMetaString("COUNTRY"));
+
+ Variables variables = new Variables();
+ variables.setVariable("IN", "PHONE");
+ variables.setVariable("COUNTRY", "COUNTRY");
+
+ List<ICheckResult> remarks = new ArrayList<>();
+ meta.check(
+ remarks,
+ new PipelineMeta(),
+ new TransformMeta("standardize", meta),
+ prev,
+ new String[] {"prev"},
+ new String[] {},
+ null,
+ variables,
+ null);
+
+ assertFalse(remarks.stream().anyMatch(r -> r.getType() ==
ICheckResult.TYPE_RESULT_ERROR));
+ }
+
+ @Test
+ void getFieldsResolvesOutputFieldVariables() throws Exception {
+ StandardizePhoneNumberMeta meta = new StandardizePhoneNumberMeta();
+ StandardizePhoneField field = new StandardizePhoneField();
+ field.setInputField("PHONE");
+ field.setOutputField("${OUTPUT_FIELD}");
+ field.setNumberTypeField("${TYPE_FIELD}");
+ field.setIsValidNumberField("${VALID_FIELD}");
+ meta.setFields(List.of(field));
+
+ Variables variables = new Variables();
+ variables.setVariable("OUTPUT_FIELD", "E164_OUT");
+ variables.setVariable("TYPE_FIELD", "TYPE");
+ variables.setVariable("VALID_FIELD", "VALID");
+
+ RowMeta rowMeta = new RowMeta();
+ rowMeta.addValueMeta(new ValueMetaString("PHONE"));
+ meta.getFields(rowMeta, "standardize", null, null, variables, null);
+
+ assertEquals("PHONE", rowMeta.getValueMeta(0).getName());
+ assertEquals("E164_OUT", rowMeta.getValueMeta(1).getName());
+ assertEquals(IValueMeta.TYPE_STRING, rowMeta.getValueMeta(1).getType());
+ assertEquals("standardize", rowMeta.getValueMeta(1).getOrigin());
+ assertEquals("TYPE", rowMeta.getValueMeta(2).getName());
+ assertEquals(IValueMeta.TYPE_STRING, rowMeta.getValueMeta(2).getType());
+ assertEquals("VALID", rowMeta.getValueMeta(3).getName());
+ assertEquals(IValueMeta.TYPE_BOOLEAN, rowMeta.getValueMeta(3).getType());
+ }
+
+ @Test
+ void getFieldsDoesNotAddOutputWhenOverwritingInput() throws Exception {
+ StandardizePhoneNumberMeta meta = metaWith("PHONE", "PHONE", null);
+
+ RowMeta rowMeta = phonePrev();
+ meta.getFields(rowMeta, "standardize", null, null, new Variables(), null);
+
+ assertEquals(1, rowMeta.size());
+ assertEquals("PHONE", rowMeta.getValueMeta(0).getName());
+ assertEquals("standardize", rowMeta.getValueMeta(0).getOrigin());
+ }
+
+ @Test
+ void getFieldsDoesNotAddEmptyOptionalFields() throws Exception {
+ StandardizePhoneNumberMeta meta = new StandardizePhoneNumberMeta();
+ StandardizePhoneField field = new StandardizePhoneField();
+ field.setInputField("PHONE");
+ field.setOutputField("E164");
+ meta.setFields(List.of(field));
+
+ RowMeta rowMeta = phonePrev();
+ meta.getFields(rowMeta, "standardize", null, null, new Variables(), null);
+
+ assertEquals(2, rowMeta.size());
+ assertEquals("PHONE", rowMeta.getValueMeta(0).getName());
+ assertEquals("E164", rowMeta.getValueMeta(1).getName());
+ }
+
+ @Test
+ void getFieldsAddsOutputWhenInputIsMissing() throws Exception {
+ StandardizePhoneNumberMeta meta = metaWith("MISSING", "E164", null);
+
+ RowMeta rowMeta = phonePrev();
+ meta.getFields(rowMeta, "standardize", null, null, new Variables(), null);
+
+ assertEquals("E164", rowMeta.searchValueMeta("E164").getName());
+ assertEquals("standardize", rowMeta.searchValueMeta("E164").getOrigin());
+ }
+
+ @Test
+ void getSupportedFormatsListsLibphonenumberFormats() {
+ String[] formats = new StandardizePhoneNumberMeta().getSupportedFormats();
+ assertArrayEquals(
+ new String[] {
+ PhoneNumberFormat.E164.name(),
+ PhoneNumberFormat.INTERNATIONAL.name(),
+ PhoneNumberFormat.NATIONAL.name(),
+ PhoneNumberFormat.RFC3966.name()
+ },
+ formats);
+ }
+
+ @Test
+ void getSupportedCountriesIsSortedAndIncludesCommonRegions() {
+ String[] countries = new
StandardizePhoneNumberMeta().getSupportedCountries();
+ assertTrue(countries.length > 10);
+ assertTrue(Arrays.asList(countries).contains("BE"));
+ assertTrue(Arrays.asList(countries).contains("US"));
+ assertTrue(Arrays.asList(countries).contains("FR"));
+ String[] sorted = countries.clone();
+ Arrays.sort(sorted);
+ assertArrayEquals(sorted, countries);
+ }
+
+ @Test
+ void resolveNameHandlesNullsAndVariables() {
+ assertNull(StandardizePhoneNumberMeta.resolveName(new Variables(), null));
+ assertEquals("", StandardizePhoneNumberMeta.resolveName(new Variables(),
""));
+ assertEquals("${X}", StandardizePhoneNumberMeta.resolveName(null, "${X}"));
+
+ Variables variables = new Variables();
+ variables.setVariable("X", "PHONE");
+ assertEquals("PHONE", StandardizePhoneNumberMeta.resolveName(variables,
"${X}"));
+ }
+
+ private static StandardizePhoneNumberMeta metaWith(String input, String
output, String country) {
+ StandardizePhoneNumberMeta meta = new StandardizePhoneNumberMeta();
+ StandardizePhoneField field = new StandardizePhoneField();
+ field.setInputField(input);
+ field.setOutputField(output);
+ field.setCountryField(country);
+ field.setDefaultCountry("BE");
+ meta.setFields(List.of(field));
+ return meta;
+ }
+
+ private static RowMeta phonePrev() {
+ RowMeta prev = new RowMeta();
+ prev.addValueMeta(new ValueMetaString("PHONE"));
+ return prev;
+ }
- loadSaveTester.testSerialization();
+ private static List<ICheckResult> check(
+ StandardizePhoneNumberMeta meta, RowMeta prev, String[] input) {
+ List<ICheckResult> remarks = new ArrayList<>();
+ meta.check(
+ remarks,
+ new PipelineMeta(),
+ new TransformMeta("standardize", meta),
+ prev,
+ input,
+ new String[] {},
+ null,
+ new Variables(),
+ null);
+ return remarks;
}
}
diff --git
a/plugins/transforms/standardizephonenumber/src/test/java/org/apache/hop/pipeline/transforms/standardizephonenumber/StandardizePhoneNumberTest.java
b/plugins/transforms/standardizephonenumber/src/test/java/org/apache/hop/pipeline/transforms/standardizephonenumber/StandardizePhoneNumberTest.java
new file mode 100644
index 0000000000..e832c2cd1c
--- /dev/null
+++
b/plugins/transforms/standardizephonenumber/src/test/java/org/apache/hop/pipeline/transforms/standardizephonenumber/StandardizePhoneNumberTest.java
@@ -0,0 +1,448 @@
+/*
+ * 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.hop.pipeline.transforms.standardizephonenumber;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+import com.google.i18n.phonenumbers.PhoneNumberUtil;
+import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
+import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
+import java.util.List;
+import java.util.Locale;
+import org.apache.hop.core.HopEnvironment;
+import org.apache.hop.core.IRowSet;
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.logging.ILoggingObject;
+import org.apache.hop.core.row.RowMeta;
+import org.apache.hop.core.row.value.ValueMetaString;
+import org.apache.hop.junit.rules.RestoreHopEngineEnvironmentExtension;
+import org.apache.hop.pipeline.PipelineTestingUtil;
+import org.apache.hop.pipeline.transforms.mock.TransformMockHelper;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+/** Unit test for {@link StandardizePhoneNumber} */
+class StandardizePhoneNumberTest {
+
+ @RegisterExtension
+ static RestoreHopEngineEnvironmentExtension env = new
RestoreHopEngineEnvironmentExtension();
+
+ private TransformMockHelper<StandardizePhoneNumberMeta,
StandardizePhoneNumberData> mockHelper;
+
+ @BeforeEach
+ void setUp() throws HopException {
+ HopEnvironment.init();
+ mockHelper =
+ new TransformMockHelper<>(
+ "StandardizePhoneNumber",
+ StandardizePhoneNumberMeta.class,
+ StandardizePhoneNumberData.class);
+ when(mockHelper.logChannelFactory.create(any(), any(ILoggingObject.class)))
+ .thenReturn(mockHelper.iLogChannel);
+ when(mockHelper.pipeline.isRunning()).thenReturn(true);
+ }
+
+ @AfterEach
+ void tearDown() {
+ mockHelper.cleanUp();
+ }
+
+ @Test
+ void normalizeRegionTrimsAndUsesRootLocale() {
+ assertEquals("BE", StandardizePhoneNumber.normalizeRegion("BE"));
+ assertEquals("BE", StandardizePhoneNumber.normalizeRegion("be"));
+ assertEquals("BE", StandardizePhoneNumber.normalizeRegion(" BE "));
+ assertNull(StandardizePhoneNumber.normalizeRegion(" "));
+ assertNull(StandardizePhoneNumber.normalizeRegion(null));
+
+ Locale original = Locale.getDefault();
+ try {
+ Locale.setDefault(Locale.forLanguageTag("tr"));
+ assertEquals("IN", StandardizePhoneNumber.normalizeRegion("in"));
+ } finally {
+ Locale.setDefault(original);
+ }
+ }
+
+ @Test
+ void processRowFormatsValidNumber() throws Exception {
+ List<Object[]> rows = execute(createMeta("PHONE_CLEANED"), new Object[]
{"0499500158", "BE"});
+
+ Object[] out = rows.get(0);
+ assertEquals("0499500158", out[0]);
+ assertEquals("BE", out[1]);
+ assertEquals("+32499500158", out[2]);
+ assertEquals("MOBILE", out[3]);
+ assertEquals(Boolean.TRUE, out[4]);
+ }
+
+ @Test
+ void processRowKeepsOriginalOnParseFailure() throws Exception {
+ List<Object[]> rows = execute(createMeta("PHONE_CLEANED"), new Object[]
{"error", "BE"});
+
+ Object[] out = rows.get(0);
+ assertEquals("error", out[2]);
+ assertEquals(StandardizePhoneNumber.NUMBER_TYPE_ERROR, out[3]);
+ assertEquals(Boolean.FALSE, out[4]);
+ }
+
+ @Test
+ void processRowSetsErrorTypeForEmptyNumber() throws Exception {
+ List<Object[]> rows = execute(createMeta("PHONE_CLEANED"), new Object[]
{null, "BE"});
+
+ Object[] out = rows.get(0);
+ assertNull(out[2]);
+ assertEquals(StandardizePhoneNumber.NUMBER_TYPE_ERROR, out[3]);
+ assertEquals(Boolean.FALSE, out[4]);
+ }
+
+ @Test
+ void processRowSetsErrorTypeForEmptyStringNumber() throws Exception {
+ List<Object[]> rows = execute(createMeta("PHONE_CLEANED"), new Object[]
{"", "BE"});
+
+ Object[] out = rows.get(0);
+ assertNull(out[2]);
+ assertEquals(StandardizePhoneNumber.NUMBER_TYPE_ERROR, out[3]);
+ assertEquals(Boolean.FALSE, out[4]);
+ }
+
+ @Test
+ void processRowKeepsWhitespaceOnlyNumberAsParseFailure() throws Exception {
+ List<Object[]> rows = execute(createMeta("PHONE_CLEANED"), new Object[] {"
", "BE"});
+
+ Object[] out = rows.get(0);
+ assertEquals(" ", out[2]);
+ assertEquals(StandardizePhoneNumber.NUMBER_TYPE_ERROR, out[3]);
+ assertEquals(Boolean.FALSE, out[4]);
+ }
+
+ @Test
+ void processRowTrimsCountryCodeWithSpaces() throws Exception {
+ List<Object[]> rows = execute(createMeta("PHONE_CLEANED"), new Object[]
{"0499500158", "BE "});
+
+ Object[] out = rows.get(0);
+ assertEquals("+32499500158", out[2]);
+ assertEquals("MOBILE", out[3]);
+ assertEquals(Boolean.TRUE, out[4]);
+ }
+
+ @Test
+ void processRowAcceptsLowercaseCountryCode() throws Exception {
+ List<Object[]> rows = execute(createMeta("PHONE_CLEANED"), new Object[]
{"0499500158", "be"});
+
+ assertEquals("+32499500158", rows.get(0)[2]);
+ }
+
+ @Test
+ void processRowResolvesOutputFieldVariable() throws Exception {
+ StandardizePhoneNumberMeta meta = createMeta("${OUTPUT_FIELD}");
+ StandardizePhoneNumberData data = new StandardizePhoneNumberData();
+ StandardizePhoneNumber transform =
+ new StandardizePhoneNumber(
+ mockHelper.transformMeta, meta, data, 0, mockHelper.pipelineMeta,
mockHelper.pipeline);
+ transform.init();
+ transform.setVariable("OUTPUT_FIELD", "E164_OUT");
+
+ RowMeta rowMeta = inputRowMeta();
+ IRowSet rowSet = mockHelper.getMockInputRowSet(new Object[] {"0499500158",
"BE"});
+ when(rowSet.getRowMeta()).thenReturn(rowMeta);
+ transform.addRowSetToInputRowSets(rowSet);
+ transform.setInputRowMeta(rowMeta);
+
+ List<Object[]> rows = PipelineTestingUtil.execute(transform, 1, false);
+ assertEquals("+32499500158", rows.get(0)[2]);
+ assertEquals("E164_OUT", data.outputRowMeta.getValueMeta(2).getName());
+ }
+
+ @Test
+ void processRowResolvesTypeAndValidFieldVariables() throws Exception {
+ StandardizePhoneNumberMeta meta = createMeta("PHONE_CLEANED");
+ meta.getFields().get(0).setNumberTypeField("${TYPE_FIELD}");
+ meta.getFields().get(0).setIsValidNumberField("${VALID_FIELD}");
+
+ StandardizePhoneNumberData data = new StandardizePhoneNumberData();
+ StandardizePhoneNumber transform =
+ new StandardizePhoneNumber(
+ mockHelper.transformMeta, meta, data, 0, mockHelper.pipelineMeta,
mockHelper.pipeline);
+ transform.init();
+ transform.setVariable("TYPE_FIELD", "TYPE");
+ transform.setVariable("VALID_FIELD", "VALID");
+
+ RowMeta rowMeta = inputRowMeta();
+ IRowSet rowSet = mockHelper.getMockInputRowSet(new Object[] {"0499500158",
"BE"});
+ when(rowSet.getRowMeta()).thenReturn(rowMeta);
+ transform.addRowSetToInputRowSets(rowSet);
+ transform.setInputRowMeta(rowMeta);
+
+ List<Object[]> rows = PipelineTestingUtil.execute(transform, 1, false);
+ assertEquals("MOBILE", rows.get(0)[3]);
+ assertEquals(Boolean.TRUE, rows.get(0)[4]);
+ assertEquals("TYPE", data.outputRowMeta.getValueMeta(3).getName());
+ assertEquals("VALID", data.outputRowMeta.getValueMeta(4).getName());
+ }
+
+ @Test
+ void processRowUsesDefaultCountryWhenCountryFieldEmpty() throws Exception {
+ StandardizePhoneNumberMeta meta = createMeta("PHONE_CLEANED");
+ meta.getFields().get(0).setCountryField(null);
+
+ List<Object[]> rows = execute(meta, new Object[] {"0499500158", "US"});
+ assertEquals("+32499500158", rows.get(0)[2]);
+ }
+
+ @Test
+ void processRowUsesDefaultCountryWhenCountryValueBlank() throws Exception {
+ List<Object[]> rows = execute(createMeta("PHONE_CLEANED"), new Object[]
{"0499500158", " "});
+ assertEquals("+32499500158", rows.get(0)[2]);
+ }
+
+ @Test
+ void processRowFallsBackToDefaultForUnsupportedRegion() throws Exception {
+ List<Object[]> rows = execute(createMeta("PHONE_CLEANED"), new Object[]
{"0499500158", "XX"});
+ assertEquals("+32499500158", rows.get(0)[2]);
+ }
+
+ @Test
+ void processRowTrimsDefaultCountry() throws Exception {
+ StandardizePhoneNumberMeta meta = createMeta("PHONE_CLEANED");
+ meta.getFields().get(0).setCountryField(null);
+ meta.getFields().get(0).setDefaultCountry(" be ");
+
+ List<Object[]> rows = execute(meta, new Object[] {"0499500158", "US"});
+ assertEquals("+32499500158", rows.get(0)[2]);
+ }
+
+ @Test
+ void processRowOverwritesInputWhenOutputMatchesInput() throws Exception {
+ StandardizePhoneNumberMeta meta = createMeta("PHONE");
+
+ List<Object[]> rows = execute(meta, new Object[] {"0499500158", "BE"});
+ Object[] out = rows.get(0);
+ assertEquals("+32499500158", out[0]);
+ assertEquals("MOBILE", out[2]);
+ assertEquals(Boolean.TRUE, out[3]);
+ }
+
+ @Test
+ void processRowOverwritesInputWhenOutputFieldBlank() throws Exception {
+ StandardizePhoneNumberMeta meta = createMeta(null);
+
+ List<Object[]> rows = execute(meta, new Object[] {"0499500158", "BE"});
+ assertEquals("+32499500158", rows.get(0)[0]);
+ }
+
+ @Test
+ void processRowAppliesAllSupportedFormats() throws Exception {
+ PhoneNumberUtil util = PhoneNumberUtil.getInstance();
+ PhoneNumber parsed = util.parse("0499500158", "BE");
+
+ for (PhoneNumberFormat format :
+ List.of(
+ PhoneNumberFormat.E164,
+ PhoneNumberFormat.INTERNATIONAL,
+ PhoneNumberFormat.NATIONAL,
+ PhoneNumberFormat.RFC3966)) {
+ StandardizePhoneNumberMeta meta = createMeta("PHONE_CLEANED");
+ meta.getFields().get(0).setNumberFormat(format.name());
+
+ List<Object[]> rows = execute(meta, new Object[] {"0499500158", "BE"});
+ assertEquals(util.format(parsed, format), rows.get(0)[2], format.name());
+ }
+ }
+
+ @Test
+ void processRowFallsBackToE164ForUnknownFormat() throws Exception {
+ StandardizePhoneNumberMeta meta = createMeta("PHONE_CLEANED");
+ meta.getFields().get(0).setNumberFormat("NOT_A_FORMAT");
+
+ List<Object[]> rows = execute(meta, new Object[] {"0499500158", "BE"});
+ assertEquals("+32499500158", rows.get(0)[2]);
+ }
+
+ @Test
+ void processRowReplacesCommaBeforeParsing() throws Exception {
+ List<Object[]> rows = execute(createMeta("PHONE_CLEANED"), new Object[]
{"0499,500158", "BE"});
+ assertEquals("+32499500158", rows.get(0)[2]);
+ }
+
+ @Test
+ void processRowMarksParsedButInvalidNumber() throws Exception {
+ List<Object[]> rows = execute(createMeta("PHONE_CLEANED"), new Object[]
{"999", "FR"});
+
+ Object[] out = rows.get(0);
+ assertEquals("+33999", out[2]);
+ assertEquals("UNKNOWN", out[3]);
+ assertEquals(Boolean.FALSE, out[4]);
+ }
+
+ @Test
+ void processRowSkipsOptionalTypeAndValidFields() throws Exception {
+ StandardizePhoneNumberMeta meta = createMeta("PHONE_CLEANED");
+ meta.getFields().get(0).setNumberTypeField(null);
+ meta.getFields().get(0).setIsValidNumberField(null);
+
+ List<Object[]> rows = execute(meta, new Object[] {"0499500158", "BE"});
+ assertEquals("+32499500158", rows.get(0)[2]);
+ assertEquals(3, rows.get(0).length);
+ }
+
+ @Test
+ void processRowStandardizesMultipleFields() throws Exception {
+ StandardizePhoneField first = field("PHONE", "E164", "COUNTRY", "BE");
+ StandardizePhoneField second = field("PHONE2", "E164_2", "COUNTRY", "BE");
+ second.setNumberTypeField("TYPE2");
+ second.setIsValidNumberField("VALID2");
+
+ StandardizePhoneNumberMeta meta = new StandardizePhoneNumberMeta();
+ meta.setFields(List.of(first, second));
+
+ RowMeta rowMeta = inputRowMeta();
+ rowMeta.addValueMeta(new ValueMetaString("PHONE2"));
+
+ List<Object[]> rows = execute(meta, rowMeta, new Object[] {"0499500158",
"BE", "0470123456"});
+ Object[] out = rows.get(0);
+ assertEquals("+32499500158", out[3]);
+ assertEquals("+32470123456", out[6]);
+ }
+
+ @Test
+ void processRowReturnsFalseWhenInputFieldMissing() throws Exception {
+ StandardizePhoneNumberMeta meta = createMeta("PHONE_CLEANED");
+ meta.getFields().get(0).setInputField("MISSING");
+
+ StandardizePhoneNumber transform =
+ ready(meta, inputRowMeta(), new Object[] {"0499500158", "BE"});
+ assertFalse(transform.processRow());
+ assertEquals(1, transform.getErrors());
+ }
+
+ @Test
+ void processRowThrowsWhenCountryFieldMissing() {
+ StandardizePhoneNumberMeta meta = createMeta("PHONE_CLEANED");
+ meta.getFields().get(0).setCountryField("ISO");
+
+ StandardizePhoneNumber transform =
+ ready(meta, inputRowMeta(), new Object[] {"0499500158", "BE"});
+ HopException exception = assertThrows(HopException.class,
transform::processRow);
+ assertTrue(exception.getMessage().contains("ISO"));
+ assertEquals(1, transform.getErrors());
+ }
+
+ @Test
+ void processRowReturnsFalseWhenNoMoreRows() throws Exception {
+ StandardizePhoneNumber transform = ready(createMeta("PHONE_CLEANED"),
inputRowMeta());
+ assertFalse(transform.processRow());
+ }
+
+ @Test
+ void processRowHandlesMultipleInputRows() throws Exception {
+ List<Object[]> rows =
+ execute(
+ createMeta("PHONE_CLEANED"),
+ new Object[] {"0499500158", "BE"},
+ new Object[] {"error", "BE"});
+
+ assertEquals(2, rows.size());
+ assertEquals("+32499500158", rows.get(0)[2]);
+ assertEquals("error", rows.get(1)[2]);
+ }
+
+ @Test
+ void getPhoneNumberFormatParsesKnownValuesAndFallsBack() {
+ StandardizePhoneNumber transform =
createTransform(createMeta("PHONE_CLEANED"));
+ assertEquals(PhoneNumberFormat.NATIONAL,
transform.getPhoneNumberFormat("NATIONAL"));
+ assertEquals(PhoneNumberFormat.E164,
transform.getPhoneNumberFormat("bogus"));
+ assertEquals(PhoneNumberFormat.E164, transform.getPhoneNumberFormat(null));
+ }
+
+ @Test
+ void initReturnsTrue() {
+ StandardizePhoneNumberData data = new StandardizePhoneNumberData();
+ StandardizePhoneNumber transform =
+ new StandardizePhoneNumber(
+ mockHelper.transformMeta,
+ createMeta("PHONE_CLEANED"),
+ data,
+ 0,
+ mockHelper.pipelineMeta,
+ mockHelper.pipeline);
+ assertTrue(transform.init());
+ }
+
+ private static StandardizePhoneNumberMeta createMeta(String outputField) {
+ StandardizePhoneNumberMeta meta = new StandardizePhoneNumberMeta();
+ meta.setFields(List.of(field("PHONE", outputField, "COUNTRY", "BE")));
+ return meta;
+ }
+
+ private static StandardizePhoneField field(
+ String input, String output, String country, String defaultCountry) {
+ StandardizePhoneField result = new StandardizePhoneField();
+ result.setInputField(input);
+ result.setOutputField(output);
+ result.setCountryField(country);
+ result.setDefaultCountry(defaultCountry);
+ result.setNumberFormat("E164");
+ result.setNumberTypeField("TYPE");
+ result.setIsValidNumberField("VALID");
+ return result;
+ }
+
+ private static RowMeta inputRowMeta() {
+ RowMeta rowMeta = new RowMeta();
+ rowMeta.addValueMeta(new ValueMetaString("PHONE"));
+ rowMeta.addValueMeta(new ValueMetaString("COUNTRY"));
+ return rowMeta;
+ }
+
+ private StandardizePhoneNumber createTransform(StandardizePhoneNumberMeta
meta) {
+ StandardizePhoneNumberData data = new StandardizePhoneNumberData();
+ StandardizePhoneNumber transform =
+ new StandardizePhoneNumber(
+ mockHelper.transformMeta, meta, data, 0, mockHelper.pipelineMeta,
mockHelper.pipeline);
+ transform.init();
+ return transform;
+ }
+
+ private StandardizePhoneNumber ready(
+ StandardizePhoneNumberMeta meta, RowMeta rowMeta, Object[]... inputRows)
{
+ StandardizePhoneNumber transform = createTransform(meta);
+ IRowSet rowSet = mockHelper.getMockInputRowSet(inputRows);
+ when(rowSet.getRowMeta()).thenReturn(rowMeta);
+ transform.addRowSetToInputRowSets(rowSet);
+ transform.setInputRowMeta(rowMeta);
+ return transform;
+ }
+
+ private List<Object[]> execute(StandardizePhoneNumberMeta meta, Object[]...
inputRows)
+ throws Exception {
+ return execute(meta, inputRowMeta(), inputRows);
+ }
+
+ private List<Object[]> execute(
+ StandardizePhoneNumberMeta meta, RowMeta rowMeta, Object[]... inputRows)
throws Exception {
+ return PipelineTestingUtil.execute(ready(meta, rowMeta, inputRows),
inputRows.length, false);
+ }
+}
diff --git
a/plugins/transforms/standardizephonenumber/src/test/resources/standardize-phone-number-transform.xml
b/plugins/transforms/standardizephonenumber/src/test/resources/standardize-phone-number-transform.xml
new file mode 100644
index 0000000000..3874c451ca
--- /dev/null
+++
b/plugins/transforms/standardizephonenumber/src/test/resources/standardize-phone-number-transform.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+ ~
+ -->
+<transform>
+ <fields>
+ <field>
+ <input>PHONE</input>
+ <output>PHONE_CLEANED</output>
+ <format>E164</format>
+ <country>COUNTRY</country>
+ <defaultCountry>BE</defaultCountry>
+ <numbertype>TYPE</numbertype>
+ <isvalidnumber>VALID</isvalidnumber>
+ </field>
+ </fields>
+</transform>