diff --git a/src/main/java/io/lenses/connect/smt/header/MultiDateTimeFormatter.java b/src/main/java/io/lenses/connect/smt/header/MultiDateTimeFormatter.java index 582f4aa..53e0535 100644 --- a/src/main/java/io/lenses/connect/smt/header/MultiDateTimeFormatter.java +++ b/src/main/java/io/lenses/connect/smt/header/MultiDateTimeFormatter.java @@ -1,7 +1,15 @@ +/** + * 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 io.lenses.connect.smt.header; -import org.apache.kafka.common.config.ConfigException; - import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; @@ -10,81 +18,77 @@ import java.util.List; import java.util.Locale; import java.util.stream.Collectors; +import org.apache.kafka.common.config.ConfigException; class MultiDateTimeFormatter { - private final List formatters; - private final List patterns; - private final Boolean returnNowIfNull; + private final List formatters; + private final List patterns; + private final Boolean returnNowIfNull; - public MultiDateTimeFormatter( - List patterns, - List formatters, - Boolean returnNowIfNull - ) { - this.patterns = patterns; - this.formatters = formatters; - this.returnNowIfNull = returnNowIfNull; - } + public MultiDateTimeFormatter( + List patterns, List formatters, Boolean returnNowIfNull) { + this.patterns = patterns; + this.formatters = formatters; + this.returnNowIfNull = returnNowIfNull; + } - public Instant format(String value, ZoneId zoneId) { - if (value == null && returnNowIfNull) { - return LocalDateTime.now().atZone(zoneId).toInstant(); - } else if (value == null) { - throw new DateTimeParseException("No valid date time provided", "null", 0); - } - for (DateTimeFormatter formatter : formatters) { - try { - LocalDateTime localDateTime = LocalDateTime.parse( value, formatter); - return localDateTime.atZone(zoneId).toInstant(); - } catch (DateTimeParseException dtpe) { - // ignore exception and use fallback - } - } - throw new DateTimeParseException("Cannot parse date with any formats", value, 0); + public Instant format(String value, ZoneId zoneId) { + if (value == null && returnNowIfNull) { + return LocalDateTime.now().atZone(zoneId).toInstant(); + } else if (value == null) { + throw new DateTimeParseException("No valid date time provided", "null", 0); } - - - public String getDisplayPatterns() { - return String.join(", ", patterns); + for (DateTimeFormatter formatter : formatters) { + try { + LocalDateTime localDateTime = LocalDateTime.parse(value, formatter); + return localDateTime.atZone(zoneId).toInstant(); + } catch (DateTimeParseException dtpe) { + // ignore exception and use fallback + } } + throw new DateTimeParseException("Cannot parse date with any formats", value, 0); + } + public String getDisplayPatterns() { + return String.join(", ", patterns); + } - private static DateTimeFormatter createFormatter(String pattern, String configName, Locale locale, ZoneId zoneId) { + private static DateTimeFormatter createFormatter( + String pattern, String configName, Locale locale, ZoneId zoneId) { try { - DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern); - if (locale != null) { - formatter = formatter.withLocale(locale); - } - if (zoneId != null) { - formatter = formatter.withZone(zoneId); - } - return formatter; + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern); + if (locale != null) { + formatter = formatter.withLocale(locale); + } + if (zoneId != null) { + formatter = formatter.withZone(zoneId); + } + return formatter; } catch (IllegalArgumentException e) { - throw new ConfigException("Configuration '" + configName + "' is not a valid date format."); + throw new ConfigException("Configuration '" + configName + "' is not a valid date format."); } -} + } -public static MultiDateTimeFormatter createDateTimeFormatter( - List patternConfigs, String configName, Locale locale) { + public static MultiDateTimeFormatter createDateTimeFormatter( + List patternConfigs, String configName, Locale locale) { return new MultiDateTimeFormatter( - patternConfigs, - patternConfigs.stream() - .map(patternConfig -> createFormatter(patternConfig, configName, locale, null)) - .collect(Collectors.toUnmodifiableList()), - false - ); -} + patternConfigs, + patternConfigs.stream() + .map(patternConfig -> createFormatter(patternConfig, configName, locale, null)) + .collect(Collectors.toUnmodifiableList()), + false); + } -public static MultiDateTimeFormatter createDateTimeFormatter( - List patternConfigs, String configName, ZoneId zoneId) { + public static MultiDateTimeFormatter createDateTimeFormatter( + List patternConfigs, String configName, ZoneId zoneId) { return new MultiDateTimeFormatter( - patternConfigs, - patternConfigs.stream() - .map(patternConfig -> createFormatter(patternConfig, configName, null, zoneId)) - .collect(Collectors.toUnmodifiableList()), - true); -} + patternConfigs, + patternConfigs.stream() + .map(patternConfig -> createFormatter(patternConfig, configName, null, zoneId)) + .collect(Collectors.toUnmodifiableList()), + true); + } } diff --git a/src/main/java/io/lenses/connect/smt/header/PropsFormatter.java b/src/main/java/io/lenses/connect/smt/header/PropsFormatter.java index 25cdf39..8bbf64f 100644 --- a/src/main/java/io/lenses/connect/smt/header/PropsFormatter.java +++ b/src/main/java/io/lenses/connect/smt/header/PropsFormatter.java @@ -10,39 +10,41 @@ */ package io.lenses.connect.smt.header; -import org.apache.kafka.connect.transforms.util.SimpleConfig; - -import java.util.Comparator; import java.util.Map; +import org.apache.kafka.connect.transforms.util.SimpleConfig; /** - * This class is responsible for formatting properties from a SimpleConfig object. - * It converts the properties into a string representation in a json-like format. + * This class is responsible for formatting properties from a SimpleConfig object. It converts the + * properties into a string representation in a json-like format. */ public class PropsFormatter { - private final SimpleConfig simpleConfig; + private final SimpleConfig simpleConfig; - /** - * Constructs a new PropsFormatter with the given SimpleConfig. - * - * @param simpleConfig the SimpleConfig object containing the properties to be formatted - */ - public PropsFormatter(SimpleConfig simpleConfig) { - this.simpleConfig = simpleConfig; - } + /** + * Constructs a new PropsFormatter with the given SimpleConfig. + * + * @param simpleConfig the SimpleConfig object containing the properties to be formatted + */ + public PropsFormatter(SimpleConfig simpleConfig) { + this.simpleConfig = simpleConfig; + } - /** - * Formats the properties from the SimpleConfig object into a string. - * The properties are represented as key-value pairs in the format: "key: "value"". - * All properties are enclosed in curly braces. - * - * @return a string representation of the properties - */ - public String apply() { - StringBuilder sb = new StringBuilder("{"); - simpleConfig.originalsStrings().entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(entry -> sb.append(entry.getKey()).append(": \"").append(entry.getValue()).append("\", ")); - sb.delete(sb.length() - 2, sb.length()); - return sb.append("}").toString(); - } -} \ No newline at end of file + /** + * Formats the properties from the SimpleConfig object into a string. The properties are + * represented as key-value pairs in the format: "key: "value"". All properties are enclosed in + * curly braces. + * + * @return a string representation of the properties + */ + public String apply() { + StringBuilder sb = new StringBuilder("{"); + simpleConfig.originalsStrings().entrySet().stream() + .sorted(Map.Entry.comparingByKey()) + .forEach( + entry -> + sb.append(entry.getKey()).append(": \"").append(entry.getValue()).append("\", ")); + sb.delete(sb.length() - 2, sb.length()); + return sb.append("}").toString(); + } +} diff --git a/src/main/java/io/lenses/connect/smt/header/RecordFieldTimestamp.java b/src/main/java/io/lenses/connect/smt/header/RecordFieldTimestamp.java index c57521a..7c341c1 100644 --- a/src/main/java/io/lenses/connect/smt/header/RecordFieldTimestamp.java +++ b/src/main/java/io/lenses/connect/smt/header/RecordFieldTimestamp.java @@ -21,7 +21,6 @@ import java.time.Instant; import java.time.ZoneId; -import java.time.format.DateTimeFormatter; import java.util.Arrays; import java.util.Locale; import java.util.Optional; @@ -110,7 +109,8 @@ public Instant getInstant(R r) { + " instead."); } - return convertToTimestamp(extractedValue, unixPrecision, fromPattern, timeZone, propsFormatter); + return convertToTimestamp( + extractedValue, unixPrecision, fromPattern, timeZone, propsFormatter); } } @@ -155,7 +155,12 @@ public static > RecordFieldTimestamp create( MultiDateTimeFormatter.createDateTimeFormatter( patterns, FORMAT_FROM_CONFIG, locale)); - return new RecordFieldTimestamp<>(fieldTypeAndFields, fromPattern, unixPrecision, zoneId, Optional.of(new PropsFormatter(config))); + return new RecordFieldTimestamp<>( + fieldTypeAndFields, + fromPattern, + unixPrecision, + zoneId, + Optional.of(new PropsFormatter(config))); } public static ConfigDef extendConfigDef(ConfigDef from) { diff --git a/src/main/java/io/lenses/connect/smt/header/TimestampConverter.java b/src/main/java/io/lenses/connect/smt/header/TimestampConverter.java index b79a841..90e550e 100644 --- a/src/main/java/io/lenses/connect/smt/header/TimestampConverter.java +++ b/src/main/java/io/lenses/connect/smt/header/TimestampConverter.java @@ -430,10 +430,12 @@ public void configure(Map configs) { throw new ConfigException("TimestampConverter requires header key to be specified"); } - MultiDateTimeFormatter fromPattern = Optional - .ofNullable(simpleConfig.getList(FORMAT_FROM_CONFIG)) - .map(fromFormatPattern -> MultiDateTimeFormatter.createDateTimeFormatter( - fromFormatPattern, FORMAT_FROM_CONFIG, Constants.UTC.toZoneId())) + MultiDateTimeFormatter fromPattern = + Optional.ofNullable(simpleConfig.getList(FORMAT_FROM_CONFIG)) + .map( + fromFormatPattern -> + MultiDateTimeFormatter.createDateTimeFormatter( + fromFormatPattern, FORMAT_FROM_CONFIG, Constants.UTC.toZoneId())) .orElse(null); String toFormatPattern = simpleConfig.getString(FORMAT_TO_CONFIG); diff --git a/src/main/java/io/lenses/connect/smt/header/Utils.java b/src/main/java/io/lenses/connect/smt/header/Utils.java index 5cbcc44..ffc1e1d 100644 --- a/src/main/java/io/lenses/connect/smt/header/Utils.java +++ b/src/main/java/io/lenses/connect/smt/header/Utils.java @@ -17,7 +17,6 @@ import static org.apache.kafka.connect.transforms.util.Requirements.requireMap; import java.time.Instant; -import java.time.LocalDateTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.Arrays; @@ -25,7 +24,6 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.TimeUnit; - import org.apache.kafka.common.config.ConfigException; import org.apache.kafka.connect.data.Field; import org.apache.kafka.connect.data.Struct; @@ -33,8 +31,12 @@ class Utils { - static Instant convertToTimestamp( - Object value, String unixPrecision, Optional fromPattern, ZoneId zoneId, Optional propsFormatter) { + static Instant convertToTimestamp( + Object value, + String unixPrecision, + Optional fromPattern, + ZoneId zoneId, + Optional propsFormatter) { if (value == null) { return Instant.now(); } @@ -74,7 +76,13 @@ static Instant convertToTimestamp( try { return Instant.ofEpochMilli(Long.parseLong((String) value)); } catch (NumberFormatException e) { - throw new DataException("Expected a long, but found " + value + ". Props: " + propsFormatter.map(PropsFormatter::apply).orElse("(No props formatter)")); + throw new DataException( + "Expected a long, but found " + + value + + ". Props: " + + propsFormatter + .map(PropsFormatter::apply) + .orElse("(No props formatter)")); } }); } diff --git a/src/test/java/io/lenses/connect/smt/header/ConvertToTimestampTest.java b/src/test/java/io/lenses/connect/smt/header/ConvertToTimestampTest.java index 77feba1..0103a13 100644 --- a/src/test/java/io/lenses/connect/smt/header/ConvertToTimestampTest.java +++ b/src/test/java/io/lenses/connect/smt/header/ConvertToTimestampTest.java @@ -27,8 +27,8 @@ class ConvertToTimestampTest { @Test void convertToTimestampReturnsCurrentTimeWhenValueIsNull() { Instant result = - Utils.convertToTimestamp(null, "seconds", Optional.empty(), ZoneId.systemDefault(), Optional.empty() - ); + Utils.convertToTimestamp( + null, "seconds", Optional.empty(), ZoneId.systemDefault(), Optional.empty()); assertNotNull(result); } @@ -36,7 +36,8 @@ void convertToTimestampReturnsCurrentTimeWhenValueIsNull() { void convertToTimestampReturnsSameInstantWhenValueIsInstant() { Instant instant = Instant.now(); Instant result = - Utils.convertToTimestamp(instant, "seconds", Optional.empty(), ZoneId.systemDefault(), Optional.empty()); + Utils.convertToTimestamp( + instant, "seconds", Optional.empty(), ZoneId.systemDefault(), Optional.empty()); assertEquals(instant, result); } @@ -44,7 +45,8 @@ void convertToTimestampReturnsSameInstantWhenValueIsInstant() { void convertToTimestampReturnsCorrectInstantWhenValueIsLong() { long value = 1633097000L; // corresponds to 2021-10-01T11:30:00Z Instant result = - Utils.convertToTimestamp(value, "seconds", Optional.empty(), ZoneId.systemDefault(), Optional.empty()); + Utils.convertToTimestamp( + value, "seconds", Optional.empty(), ZoneId.systemDefault(), Optional.empty()); assertEquals(Instant.ofEpochSecond(value), result); } @@ -53,19 +55,13 @@ void convertToTimestampReturnsCorrectInstantWhenValueIsString() { String value = "2021-10-01T11:30:00Z"; Instant result = Utils.convertToTimestamp( - value, - "seconds", - Optional.of(createMultiDateTimeFormatter()), - UTC, - Optional.empty()); + value, "seconds", Optional.of(createMultiDateTimeFormatter()), UTC, Optional.empty()); assertEquals(Instant.parse(value), result); } private static MultiDateTimeFormatter createMultiDateTimeFormatter() { return MultiDateTimeFormatter.createDateTimeFormatter( - List.of("yyyy-MM-dd'T'HH:mm:ssZZZZZ"), - "Unit test", - UTC); + List.of("yyyy-MM-dd'T'HH:mm:ssZZZZZ"), "Unit test", UTC); } @Test @@ -74,11 +70,7 @@ void convertToTimestampThrowsDataExceptionWhenValueIsInvalidString() { assertThrows( DataException.class, () -> - convertToTimestamp( - value, - "seconds", - Optional.of(createMultiDateTimeFormatter()), - UTC)); + convertToTimestamp(value, "seconds", Optional.of(createMultiDateTimeFormatter()), UTC)); } @Test @@ -110,14 +102,15 @@ void convertToTimestampReturnsCorrectInstantWhenValueIsEpochAndPrecisionIsMillis @Test void convertToTimestampReturnsCorrectInstantWhenValueIsEpochAndPrecisionIsSeconds() { Long value = 1633097000L; // corresponds to 2021-10-01T11:30:00Z - Instant result = - convertToTimestamp(value, "seconds", Optional.empty(), ZoneId.systemDefault()); + Instant result = convertToTimestamp(value, "seconds", Optional.empty(), ZoneId.systemDefault()); assertEquals(Instant.ofEpochSecond(1633097000L, 0), result); } - static Instant convertToTimestamp( - Object value, String unixPrecision, Optional fromPattern, ZoneId zoneId) { + Object value, + String unixPrecision, + Optional fromPattern, + ZoneId zoneId) { return Utils.convertToTimestamp(value, unixPrecision, fromPattern, zoneId, Optional.empty()); } } diff --git a/src/test/java/io/lenses/connect/smt/header/InsertRollingFieldTimestampHeadersTest.java b/src/test/java/io/lenses/connect/smt/header/InsertRollingFieldTimestampHeadersTest.java index d4b780d..0d08d55 100644 --- a/src/test/java/io/lenses/connect/smt/header/InsertRollingFieldTimestampHeadersTest.java +++ b/src/test/java/io/lenses/connect/smt/header/InsertRollingFieldTimestampHeadersTest.java @@ -13,7 +13,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import java.time.Instant; -import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -405,62 +404,71 @@ void testRollingWindowEvery12Seconds() { }); } - @Test - void testMultipleDateFormats() { - // one format with millis, one without. Do we fallback to the backup format? - String pattern1 = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; - String pattern2 = "yyyy-MM-dd'T'HH:mm:ss'Z'"; - - List> scenarios = List.of( - new Tuple5<>(("2020-01-01T01:00:00.999Z"), 15, "2020-01-01 01:00", "01", "00"), - new Tuple5<>(("2020-01-01T01:00:01Z"), 15, "2020-01-01 01:00", "01", "00"), - new Tuple5<>(("2020-01-01T01:14:59.000Z"), 15, "2020-01-01 01:00", "01", "00"), - new Tuple5<>(("2020-01-01T01:15:00Z"), 15, "2020-01-01 01:15", "01", "15"), - new Tuple5<>(("2020-01-01T01:15:01.000Z"), 15, "2020-01-01 01:15", "01", "15"), - new Tuple5<>(("2020-01-01T01:29:59Z"), 15, "2020-01-01 01:15", "01", "15"), - new Tuple5<>(("2020-01-01T01:30:00.000Z"), 15, "2020-01-01 01:30", "01", "30"), - new Tuple5<>(("2020-01-01T01:30:01Z"), 15, "2020-01-01 01:30", "01", "30"), - new Tuple5<>(("2020-01-01T01:44:59.000Z"), 15, "2020-01-01 01:30", "01", "30"), - new Tuple5<>(("2020-01-01T01:45:00Z"), 15, "2020-01-01 01:45", "01", "45"), - new Tuple5<>(("2020-01-01T01:45:01.000Z"), 15, "2020-01-01 01:45", "01", "45"), - new Tuple5<>(("2020-01-01T01:59:59Z"), 15, "2020-01-01 01:45", "01", "45") - ); - scenarios.forEach( - scenario -> { - Map configs = Map.of( - "header.prefix.name", "wallclock_", - "date.format", "yyyy-MM-dd HH:mm", - "format.from.pattern", pattern1 + "," + pattern2, - "window.size", scenario.second.toString(), - "window.type", "minutes", - "field", "_value" - ); - - final SourceRecord transformed; - try (InsertRollingFieldTimestampHeaders transformer = new InsertRollingFieldTimestampHeaders<>()) { - transformer.configure(configs); - - transformed = transformer.apply( - new SourceRecord( - null, null, "topic", 0, Schema.STRING_SCHEMA, "key", null, scenario.first, 0L, new ConnectHeaders()) - ); - } - final String actualDate = - transformed.headers().lastWithName("wallclock_date").value().toString(); - assertEquals(actualDate, scenario.third); - - final String actualHour = - transformed.headers().lastWithName("wallclock_hour").value().toString(); - assertEquals(actualHour, scenario.fourth); - - final String actualMinute = - transformed.headers().lastWithName("wallclock_minute").value().toString(); - assertEquals(actualMinute, scenario.fifth); - }); - } + @Test + void testMultipleDateFormats() { + // one format with millis, one without. Do we fallback to the backup format? + String pattern1 = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; + String pattern2 = "yyyy-MM-dd'T'HH:mm:ss'Z'"; + + List> scenarios = + List.of( + new Tuple5<>(("2020-01-01T01:00:00.999Z"), 15, "2020-01-01 01:00", "01", "00"), + new Tuple5<>(("2020-01-01T01:00:01Z"), 15, "2020-01-01 01:00", "01", "00"), + new Tuple5<>(("2020-01-01T01:14:59.000Z"), 15, "2020-01-01 01:00", "01", "00"), + new Tuple5<>(("2020-01-01T01:15:00Z"), 15, "2020-01-01 01:15", "01", "15"), + new Tuple5<>(("2020-01-01T01:15:01.000Z"), 15, "2020-01-01 01:15", "01", "15"), + new Tuple5<>(("2020-01-01T01:29:59Z"), 15, "2020-01-01 01:15", "01", "15"), + new Tuple5<>(("2020-01-01T01:30:00.000Z"), 15, "2020-01-01 01:30", "01", "30"), + new Tuple5<>(("2020-01-01T01:30:01Z"), 15, "2020-01-01 01:30", "01", "30"), + new Tuple5<>(("2020-01-01T01:44:59.000Z"), 15, "2020-01-01 01:30", "01", "30"), + new Tuple5<>(("2020-01-01T01:45:00Z"), 15, "2020-01-01 01:45", "01", "45"), + new Tuple5<>(("2020-01-01T01:45:01.000Z"), 15, "2020-01-01 01:45", "01", "45"), + new Tuple5<>(("2020-01-01T01:59:59Z"), 15, "2020-01-01 01:45", "01", "45")); + scenarios.forEach( + scenario -> { + Map configs = + Map.of( + "header.prefix.name", "wallclock_", + "date.format", "yyyy-MM-dd HH:mm", + "format.from.pattern", pattern1 + "," + pattern2, + "window.size", scenario.second.toString(), + "window.type", "minutes", + "field", "_value"); + + final SourceRecord transformed; + try (InsertRollingFieldTimestampHeaders transformer = + new InsertRollingFieldTimestampHeaders<>()) { + transformer.configure(configs); + + transformed = + transformer.apply( + new SourceRecord( + null, + null, + "topic", + 0, + Schema.STRING_SCHEMA, + "key", + null, + scenario.first, + 0L, + new ConnectHeaders())); + } + final String actualDate = + transformed.headers().lastWithName("wallclock_date").value().toString(); + assertEquals(actualDate, scenario.third); + + final String actualHour = + transformed.headers().lastWithName("wallclock_hour").value().toString(); + assertEquals(actualHour, scenario.fourth); + final String actualMinute = + transformed.headers().lastWithName("wallclock_minute").value().toString(); + assertEquals(actualMinute, scenario.fifth); + }); + } - static class Tuple5 { + static class Tuple5 { private final A first; private final B second; private final C third; diff --git a/src/test/java/io/lenses/connect/smt/header/MultiDateTimeFormatterTest.java b/src/test/java/io/lenses/connect/smt/header/MultiDateTimeFormatterTest.java index 07cd5b1..cb83a01 100644 --- a/src/test/java/io/lenses/connect/smt/header/MultiDateTimeFormatterTest.java +++ b/src/test/java/io/lenses/connect/smt/header/MultiDateTimeFormatterTest.java @@ -1,114 +1,125 @@ +/** + * 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 io.lenses.connect.smt.header; -import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + import java.time.Instant; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.util.List; import java.util.Locale; -import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; class MultiDateTimeFormatterTest { - @Test - void testFormatWithValidDateString() { - MultiDateTimeFormatter formatter = MultiDateTimeFormatter.createDateTimeFormatter( - List.of("yyyy-MM-dd'T'HH:mm:ss", "yyyy-MM-dd HH:mm:ss"), - "TestConfig", - ZoneId.of("UTC") - ); - - Instant expected = Instant.parse("2021-10-01T11:30:00Z"); - Instant result = formatter.format("2021-10-01T11:30:00", ZoneId.of("UTC")); - assertEquals(expected, result); - } - - @Test - void testFormatWithInvalidDateString() { - MultiDateTimeFormatter formatter = MultiDateTimeFormatter.createDateTimeFormatter( - List.of("yyyy-MM-dd'T'HH:mm:ss", "yyyy-MM-dd HH:mm:ss"), - "TestConfig", - ZoneId.of("UTC") - ); - - assertThrows(DateTimeParseException.class, () -> { - formatter.format("invalid-date", ZoneId.of("UTC")); + @Test + void testFormatWithValidDateString() { + MultiDateTimeFormatter formatter = + MultiDateTimeFormatter.createDateTimeFormatter( + List.of("yyyy-MM-dd'T'HH:mm:ss", "yyyy-MM-dd HH:mm:ss"), + "TestConfig", + ZoneId.of("UTC")); + + Instant expected = Instant.parse("2021-10-01T11:30:00Z"); + Instant result = formatter.format("2021-10-01T11:30:00", ZoneId.of("UTC")); + assertEquals(expected, result); + } + + @Test + void testFormatWithInvalidDateString() { + MultiDateTimeFormatter formatter = + MultiDateTimeFormatter.createDateTimeFormatter( + List.of("yyyy-MM-dd'T'HH:mm:ss", "yyyy-MM-dd HH:mm:ss"), + "TestConfig", + ZoneId.of("UTC")); + + assertThrows( + DateTimeParseException.class, + () -> { + formatter.format("invalid-date", ZoneId.of("UTC")); }); - } - - @Test - void testFormatWithNullValueAndReturnNowIfNullTrue() { - MultiDateTimeFormatter formatter = new MultiDateTimeFormatter( - List.of("yyyy-MM-dd'T'HH:mm:ss", "yyyy-MM-dd HH:mm:ss"), - List.of(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss")), - true - ); - - Instant result = formatter.format(null, ZoneId.of("UTC")); - assertNotNull(result); - } - - @Test - void testFormatWithNullValueAndReturnNowIfNullFalse() { - MultiDateTimeFormatter formatter = new MultiDateTimeFormatter( - List.of("yyyy-MM-dd'T'HH:mm:ss", "yyyy-MM-dd HH:mm:ss"), - List.of(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss")), - false - ); - - assertThrows(DateTimeParseException.class, () -> { - formatter.format(null, ZoneId.of("UTC")); + } + + @Test + void testFormatWithNullValueAndReturnNowIfNullTrue() { + MultiDateTimeFormatter formatter = + new MultiDateTimeFormatter( + List.of("yyyy-MM-dd'T'HH:mm:ss", "yyyy-MM-dd HH:mm:ss"), + List.of(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss")), + true); + + Instant result = formatter.format(null, ZoneId.of("UTC")); + assertNotNull(result); + } + + @Test + void testFormatWithNullValueAndReturnNowIfNullFalse() { + MultiDateTimeFormatter formatter = + new MultiDateTimeFormatter( + List.of("yyyy-MM-dd'T'HH:mm:ss", "yyyy-MM-dd HH:mm:ss"), + List.of(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss")), + false); + + assertThrows( + DateTimeParseException.class, + () -> { + formatter.format(null, ZoneId.of("UTC")); }); - } - - @Test - void testGetDisplayPatterns() { - MultiDateTimeFormatter formatter = MultiDateTimeFormatter.createDateTimeFormatter( - List.of("yyyy-MM-dd'T'HH:mm:ss", "yyyy-MM-dd HH:mm:ss"), - "TestConfig", - Locale.US - ); - - String expected = "yyyy-MM-dd'T'HH:mm:ss, yyyy-MM-dd HH:mm:ss"; - String result = formatter.getDisplayPatterns(); - assertEquals(expected, result); - } - - @Test -void testFormatWithEmptyListOfDateStrings() { - MultiDateTimeFormatter formatter = new MultiDateTimeFormatter( - List.of(), - List.of(), - false - ); - - assertThrows(DateTimeParseException.class, () -> formatter.format("2021-10-01T11:30:00", ZoneId.of("UTC"))); -} + } + + @Test + void testGetDisplayPatterns() { + MultiDateTimeFormatter formatter = + MultiDateTimeFormatter.createDateTimeFormatter( + List.of("yyyy-MM-dd'T'HH:mm:ss", "yyyy-MM-dd HH:mm:ss"), "TestConfig", Locale.US); + + String expected = "yyyy-MM-dd'T'HH:mm:ss, yyyy-MM-dd HH:mm:ss"; + String result = formatter.getDisplayPatterns(); + assertEquals(expected, result); + } -@Test -void testFormatWithMultiplePatternsTargetingFirst() { - MultiDateTimeFormatter formatter = MultiDateTimeFormatter.createDateTimeFormatter( + @Test + void testFormatWithEmptyListOfDateStrings() { + MultiDateTimeFormatter formatter = new MultiDateTimeFormatter(List.of(), List.of(), false); + + assertThrows( + DateTimeParseException.class, + () -> formatter.format("2021-10-01T11:30:00", ZoneId.of("UTC"))); + } + + @Test + void testFormatWithMultiplePatternsTargetingFirst() { + MultiDateTimeFormatter formatter = + MultiDateTimeFormatter.createDateTimeFormatter( List.of("yyyy-MM-dd'T'HH:mm:ss", "yyyy-MM-dd HH:mm:ss"), "TestConfig", - ZoneId.of("UTC") - ); + ZoneId.of("UTC")); Instant expected = Instant.parse("2021-10-01T11:30:00Z"); Instant result = formatter.format("2021-10-01T11:30:00", ZoneId.of("UTC")); assertEquals(expected, result); -} + } -@Test -void testFormatWithMultiplePatternsTargetingSecond() { - MultiDateTimeFormatter formatter = MultiDateTimeFormatter.createDateTimeFormatter( + @Test + void testFormatWithMultiplePatternsTargetingSecond() { + MultiDateTimeFormatter formatter = + MultiDateTimeFormatter.createDateTimeFormatter( List.of("yyyy-MM-dd'T'HH:mm:ss", "yyyy-MM-dd HH:mm:ss"), "TestConfig", - ZoneId.of("UTC") - ); + ZoneId.of("UTC")); Instant expected = Instant.parse("2021-10-01T11:30:00Z"); Instant result = formatter.format("2021-10-01 11:30:00", ZoneId.of("UTC")); assertEquals(expected, result); + } } -} \ No newline at end of file diff --git a/src/test/java/io/lenses/connect/smt/header/PropsFormatterTest.java b/src/test/java/io/lenses/connect/smt/header/PropsFormatterTest.java index cd7e17d..f8d3cef 100644 --- a/src/test/java/io/lenses/connect/smt/header/PropsFormatterTest.java +++ b/src/test/java/io/lenses/connect/smt/header/PropsFormatterTest.java @@ -10,27 +10,26 @@ */ package io.lenses.connect.smt.header; +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Map; import org.apache.kafka.common.config.ConfigDef; import org.apache.kafka.connect.transforms.util.SimpleConfig; import org.junit.jupiter.api.Test; -import java.util.Map; - -import static org.junit.jupiter.api.Assertions.*; - class PropsFormatterTest { - @Test - void singleEntry() { - Map props = Map.of("something", "else"); - PropsFormatter writer = new PropsFormatter(new SimpleConfig(new ConfigDef(), props)); - assertEquals("{something: \"else\"}", writer.apply()); - } + @Test + void singleEntry() { + Map props = Map.of("something", "else"); + PropsFormatter writer = new PropsFormatter(new SimpleConfig(new ConfigDef(), props)); + assertEquals("{something: \"else\"}", writer.apply()); + } - @Test - void multipleEntries() { - Map props = Map.of("first", "item", "something", "else"); - PropsFormatter writer = new PropsFormatter(new SimpleConfig(new ConfigDef(), props)); - assertEquals("{first: \"item\", something: \"else\"}", writer.apply()); - } -} \ No newline at end of file + @Test + void multipleEntries() { + Map props = Map.of("first", "item", "something", "else"); + PropsFormatter writer = new PropsFormatter(new SimpleConfig(new ConfigDef(), props)); + assertEquals("{first: \"item\", something: \"else\"}", writer.apply()); + } +} diff --git a/src/test/java/io/lenses/connect/smt/header/TimestampConverterTest.java b/src/test/java/io/lenses/connect/smt/header/TimestampConverterTest.java index a3fd7ad..937b567 100644 --- a/src/test/java/io/lenses/connect/smt/header/TimestampConverterTest.java +++ b/src/test/java/io/lenses/connect/smt/header/TimestampConverterTest.java @@ -281,9 +281,8 @@ void testSchemalessTimestampToStringTargeting() { String headerValue = (String) header.value(); assertTrue( - headerValue.equals( "1970 01 01 18 00 01 234 CST") || - headerValue.equals( "1970 01 01 18 00 01 234 GMT-06:00") - ); + headerValue.equals("1970 01 01 18 00 01 234 CST") + || headerValue.equals("1970 01 01 18 00 01 234 GMT-06:00")); } // Conversions without schemas (core types -> most flexible Timestamp format) diff --git a/src/test/java/io/lenses/connect/smt/header/UtilsTimestampTest.java b/src/test/java/io/lenses/connect/smt/header/UtilsTimestampTest.java index 348d8d5..baa58aa 100644 --- a/src/test/java/io/lenses/connect/smt/header/UtilsTimestampTest.java +++ b/src/test/java/io/lenses/connect/smt/header/UtilsTimestampTest.java @@ -10,46 +10,51 @@ */ package io.lenses.connect.smt.header; -import org.apache.kafka.common.config.ConfigDef; -import org.apache.kafka.connect.errors.DataException; -import org.apache.kafka.connect.transforms.util.SimpleConfig; -import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import java.time.ZoneId; import java.util.Map; import java.util.Optional; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; +import org.apache.kafka.common.config.ConfigDef; +import org.apache.kafka.connect.errors.DataException; +import org.apache.kafka.connect.transforms.util.SimpleConfig; +import org.junit.jupiter.api.Test; class UtilsTimestampTest { - private static final String TIMESTAMP = "2024-08-16T04:30:00.232Z"; - private static final String PRECISION = "milliseconds"; - - @Test - void convertToTimestampShouldWritePropsOnFailure() { - PropsFormatter propsFormatter = new PropsFormatter(new SimpleConfig(new ConfigDef(), Map.of("some", "props", "for", "2" ) )); - DataException dataException = assertThrows(DataException.class, () -> Utils.convertToTimestamp( - TIMESTAMP, - PRECISION, - Optional.empty(), - ZoneId.of("UTC"), - Optional.of(propsFormatter) - )); - assertEquals("Expected a long, but found 2024-08-16T04:30:00.232Z. Props: {for: \"2\", some: \"props\"}",dataException.getMessage()); - } - - @Test - void convertToTimestampShouldNotFailWhenNoPropsFormatter() { - DataException dataException = assertThrows(DataException.class, () -> Utils.convertToTimestamp( - TIMESTAMP, - PRECISION, - Optional.empty(), - ZoneId.of("UTC"), - Optional.empty() - )); - assertEquals("Expected a long, but found 2024-08-16T04:30:00.232Z. Props: (No props formatter)",dataException.getMessage()); - } - -} \ No newline at end of file + private static final String TIMESTAMP = "2024-08-16T04:30:00.232Z"; + private static final String PRECISION = "milliseconds"; + + @Test + void convertToTimestampShouldWritePropsOnFailure() { + PropsFormatter propsFormatter = + new PropsFormatter(new SimpleConfig(new ConfigDef(), Map.of("some", "props", "for", "2"))); + DataException dataException = + assertThrows( + DataException.class, + () -> + Utils.convertToTimestamp( + TIMESTAMP, + PRECISION, + Optional.empty(), + ZoneId.of("UTC"), + Optional.of(propsFormatter))); + assertEquals( + "Expected a long, but found 2024-08-16T04:30:00.232Z. Props: {for: \"2\", some: \"props\"}", + dataException.getMessage()); + } + + @Test + void convertToTimestampShouldNotFailWhenNoPropsFormatter() { + DataException dataException = + assertThrows( + DataException.class, + () -> + Utils.convertToTimestamp( + TIMESTAMP, PRECISION, Optional.empty(), ZoneId.of("UTC"), Optional.empty())); + assertEquals( + "Expected a long, but found 2024-08-16T04:30:00.232Z. Props: (No props formatter)", + dataException.getMessage()); + } +}