From 2d8e8df8c386a42e27cf61d7f64962013ffc4277 Mon Sep 17 00:00:00 2001 From: Jun Nemoto Date: Tue, 22 Jul 2025 18:21:52 +0900 Subject: [PATCH 1/2] Support time-related data types in generic function --- .../dl/genericcontracts/object/Constants.java | 74 ++++++++++- .../scripts/objects-table-schema.json | 2 +- ...ntractObjectAndCollectionEndToEndTest.java | 59 +++++++-- .../object/PutToMutableDatabase.java | 69 ++++++++++ .../object/PutToMutableDatabaseTest.java | 119 +++++++++++++++++- 5 files changed, 312 insertions(+), 11 deletions(-) diff --git a/common/src/main/java/com/scalar/dl/genericcontracts/object/Constants.java b/common/src/main/java/com/scalar/dl/genericcontracts/object/Constants.java index 9853931d..10c6ea65 100644 --- a/common/src/main/java/com/scalar/dl/genericcontracts/object/Constants.java +++ b/common/src/main/java/com/scalar/dl/genericcontracts/object/Constants.java @@ -1,5 +1,20 @@ package com.scalar.dl.genericcontracts.object; +import static java.time.temporal.ChronoField.DAY_OF_MONTH; +import static java.time.temporal.ChronoField.HOUR_OF_DAY; +import static java.time.temporal.ChronoField.MINUTE_OF_HOUR; +import static java.time.temporal.ChronoField.MONTH_OF_YEAR; +import static java.time.temporal.ChronoField.NANO_OF_SECOND; +import static java.time.temporal.ChronoField.SECOND_OF_MINUTE; +import static java.time.temporal.ChronoField.YEAR; + +import java.time.ZoneOffset; +import java.time.chrono.IsoChronology; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.ResolverStyle; +import java.time.format.SignStyle; + public class Constants { // Object authenticity management @@ -49,5 +64,62 @@ public class Constants { public static final String COLLECTION_ID_IS_MISSING_OR_INVALID = "The collection ID is not specified in the arguments or is invalid."; public static final String INVALID_PUT_MUTABLE_FUNCTION_ARGUMENT_FORMAT = - "The specified format of the PutMutable function argument is invalid."; + "The specified format of the PutToMutableDatabase function argument is invalid."; + + /** A formatter for a DATE literal. The format is "YYYY-MM-DD". For example, "2020-03-04". */ + public static final DateTimeFormatter DATE_FORMATTER = + new DateTimeFormatterBuilder() + .appendValue(YEAR, 4, 4, SignStyle.NEVER) + .appendLiteral('-') + .appendValue(MONTH_OF_YEAR, 2) + .appendLiteral('-') + .appendValue(DAY_OF_MONTH, 2) + .toFormatter() + .withResolverStyle(ResolverStyle.STRICT) + .withChronology(IsoChronology.INSTANCE); + /** + * A formatter for a TIME literal. The format is "HH:MM:SS[.FFFFFF]". For example, + * "12:34:56.123456". The fractional second is optional. + */ + public static final DateTimeFormatter TIME_FORMATTER = + new DateTimeFormatterBuilder() + .appendValue(HOUR_OF_DAY, 2) + .appendLiteral(':') + .appendValue(MINUTE_OF_HOUR, 2) + .optionalStart() + .appendLiteral(':') + .appendValue(SECOND_OF_MINUTE, 2) + .optionalStart() + .appendFraction(NANO_OF_SECOND, 0, 6, true) + .toFormatter() + .withResolverStyle(ResolverStyle.STRICT) + .withChronology(IsoChronology.INSTANCE); + /** + * A formatter for a TIMESTAMP literal. The format is "YYYY-MM-DD HH:MM:SS[.FFF]". For example, + * "2020-03-04 12:34:56.123". The fractional second is optional. + */ + public static final DateTimeFormatter TIMESTAMP_FORMATTER = + new DateTimeFormatterBuilder() + .append(DATE_FORMATTER) + .appendLiteral(' ') + .appendValue(HOUR_OF_DAY, 2) + .appendLiteral(':') + .appendValue(MINUTE_OF_HOUR, 2) + .optionalStart() + .appendLiteral(':') + .appendValue(SECOND_OF_MINUTE, 2) + .optionalStart() + .appendFraction(NANO_OF_SECOND, 0, 3, true) + .toFormatter(); + /** + * A formatter for a TIMESTAMPTZ literal. The format is "YYYY-MM-DD HH:MM:SS[.FFF] Z". For + * example, "2020-03-04 12:34:56.123 Z". The fractional second is optional. + */ + public static final DateTimeFormatter TIMESTAMPTZ_FORMATTER = + new DateTimeFormatterBuilder() + .append(TIMESTAMP_FORMATTER) + .appendLiteral(' ') + .appendLiteral('Z') + .toFormatter() + .withZone(ZoneOffset.UTC); } diff --git a/generic-contracts/scripts/objects-table-schema.json b/generic-contracts/scripts/objects-table-schema.json index 3150440a..4cd2b918 100644 --- a/generic-contracts/scripts/objects-table-schema.json +++ b/generic-contracts/scripts/objects-table-schema.json @@ -11,7 +11,7 @@ "object_id": "TEXT", "version": "TEXT", "status": "INT", - "registered_at": "BIGINT" + "registered_at": "TIMESTAMP" }, "compaction-strategy": "LCS" } diff --git a/generic-contracts/src/integration-test/java/com/scalar/dl/genericcontracts/GenericContractObjectAndCollectionEndToEndTest.java b/generic-contracts/src/integration-test/java/com/scalar/dl/genericcontracts/GenericContractObjectAndCollectionEndToEndTest.java index f1f5e348..e78bc586 100644 --- a/generic-contracts/src/integration-test/java/com/scalar/dl/genericcontracts/GenericContractObjectAndCollectionEndToEndTest.java +++ b/generic-contracts/src/integration-test/java/com/scalar/dl/genericcontracts/GenericContractObjectAndCollectionEndToEndTest.java @@ -64,12 +64,14 @@ import com.scalar.db.io.Key; import com.scalar.dl.client.exception.ClientException; import com.scalar.dl.client.service.GenericContractClientService; +import com.scalar.dl.genericcontracts.object.Constants; import com.scalar.dl.ledger.error.LedgerError; import com.scalar.dl.ledger.model.ContractExecutionResult; import com.scalar.dl.ledger.model.LedgerValidationResult; import com.scalar.dl.ledger.service.StatusCode; import com.scalar.dl.ledger.util.JacksonSerDe; import java.io.IOException; +import java.time.LocalDateTime; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -90,7 +92,7 @@ public class GenericContractObjectAndCollectionEndToEndTest private static final String ASSET_AGE = "age"; private static final String ASSET_OUTPUT = "output"; private static final String DATA_TYPE_INT = "INT"; - private static final String DATA_TYPE_BIGINT = "BIGINT"; + private static final String DATA_TYPE_TIMESTAMP = "TIMESTAMP"; private static final String DATA_TYPE_TEXT = "TEXT"; private static final String PACKAGE_OBJECT = "object"; @@ -138,6 +140,8 @@ public class GenericContractObjectAndCollectionEndToEndTest private static final String SOME_COLUMN_NAME_2 = "version"; private static final String SOME_COLUMN_NAME_3 = "status"; private static final String SOME_COLUMN_NAME_4 = "registered_at"; + private static final String SOME_TIMESTAMP_TEXT = "2021-02-03 05:45:00"; + private static final LocalDateTime SOME_TIMESTAMP_VALUE = LocalDateTime.of(2021, 2, 3, 5, 45); private static final String SOME_COLLECTION_ID = "set"; private static final ArrayNode SOME_DEFAULT_OBJECT_IDS = mapper.createArrayNode().add("object1").add("object2").add("object3").add("object4"); @@ -241,20 +245,20 @@ private JsonNode createColumn(String name, int value) { .put(DATA_TYPE, DATA_TYPE_INT); } - private JsonNode createColumn(String name, long value) { + private JsonNode createColumn(String name, String value) { return mapper .createObjectNode() .put(COLUMN_NAME, name) .put(VALUE, value) - .put(DATA_TYPE, DATA_TYPE_BIGINT); + .put(DATA_TYPE, DATA_TYPE_TEXT); } - private JsonNode createColumn(String name, String value) { + private JsonNode createTimestampColumn(String name, String value) { return mapper .createObjectNode() .put(COLUMN_NAME, name) .put(VALUE, value) - .put(DATA_TYPE, DATA_TYPE_TEXT); + .put(DATA_TYPE, DATA_TYPE_TIMESTAMP); } private JsonNode createFunctionArguments( @@ -267,7 +271,7 @@ private JsonNode createFunctionArguments( mapper .createArrayNode() .add(createColumn(SOME_COLUMN_NAME_3, status)) - .add(createColumn(SOME_COLUMN_NAME_4, registeredAt)); + .add(createTimestampColumn(SOME_COLUMN_NAME_4, SOME_TIMESTAMP_TEXT)); ObjectNode arguments = mapper.createObjectNode(); arguments.put(NAMESPACE, getFunctionNamespace()); @@ -574,11 +578,11 @@ public void putObject_FunctionArgumentsGiven_ShouldPutRecordToFunctionTable() assertThat(results.get(0).getText(SOME_COLUMN_NAME_1)).isEqualTo(SOME_OBJECT_ID); assertThat(results.get(0).getText(SOME_COLUMN_NAME_2)).isEqualTo(SOME_VERSION_ID_0); assertThat(results.get(0).getInt(SOME_COLUMN_NAME_3)).isEqualTo(0); - assertThat(results.get(0).getBigInt(SOME_COLUMN_NAME_4)).isEqualTo(1L); + assertThat(results.get(0).getTimestamp(SOME_COLUMN_NAME_4)).isEqualTo(SOME_TIMESTAMP_VALUE); assertThat(results.get(1).getText(SOME_COLUMN_NAME_1)).isEqualTo(SOME_OBJECT_ID); assertThat(results.get(1).getText(SOME_COLUMN_NAME_2)).isEqualTo(SOME_VERSION_ID_1); assertThat(results.get(1).getInt(SOME_COLUMN_NAME_3)).isEqualTo(1); - assertThat(results.get(1).getBigInt(SOME_COLUMN_NAME_4)).isEqualTo(1234567890123L); + assertThat(results.get(1).getTimestamp(SOME_COLUMN_NAME_4)).isEqualTo(SOME_TIMESTAMP_VALUE); } catch (IOException e) { throw new RuntimeException(e); } @@ -662,6 +666,45 @@ public void putObject_FunctionArgumentsGiven_ShouldPutRecordToFunctionTable() .isEqualTo(StatusCode.CONFLICT); } + @Test + public void + putObject_FunctionArgumentsWithInvalidTimeRelatedTypeFormatGiven_ShouldThrowClientException() { + // Arrange + JsonNode contractArguments = + mapper + .createObjectNode() + .put(OBJECT_ID, SOME_OBJECT_ID) + .put(HASH_VALUE, SOME_HASH_VALUE_0) + .set(METADATA, SOME_METADATA_0); + ObjectNode functionArguments = + mapper + .createObjectNode() + .put(NAMESPACE, getFunctionNamespace()) + .put(TABLE, getFunctionTable()); + functionArguments.set( + PARTITION_KEY, + mapper.createArrayNode().add(createColumn(SOME_COLUMN_NAME_1, SOME_OBJECT_ID))); + functionArguments.set( + CLUSTERING_KEY, + mapper.createArrayNode().add(createColumn(SOME_COLUMN_NAME_2, SOME_VERSION_ID_0))); + functionArguments.set( + COLUMNS, + mapper + .createArrayNode() + .add(createColumn(SOME_COLUMN_NAME_3, 0)) + .add(createTimestampColumn(SOME_COLUMN_NAME_4, "2024-05-19"))); + + // Act Assert + assertThatThrownBy( + () -> + clientService.executeContract( + ID_OBJECT_PUT, contractArguments, ID_OBJECT_PUT_MUTABLE, functionArguments)) + .isExactlyInstanceOf(ClientException.class) + .hasMessage(Constants.INVALID_PUT_MUTABLE_FUNCTION_ARGUMENT_FORMAT) + .extracting("code") + .isEqualTo(StatusCode.CONTRACT_CONTEXTUAL_ERROR); + } + @Test public void putObject_InvalidMetadataGiven_ShouldThrowClientException() { // Arrange diff --git a/generic-contracts/src/main/java/com/scalar/dl/genericcontracts/object/PutToMutableDatabase.java b/generic-contracts/src/main/java/com/scalar/dl/genericcontracts/object/PutToMutableDatabase.java index 91b5b85d..8e674f80 100644 --- a/generic-contracts/src/main/java/com/scalar/dl/genericcontracts/object/PutToMutableDatabase.java +++ b/generic-contracts/src/main/java/com/scalar/dl/genericcontracts/object/PutToMutableDatabase.java @@ -13,15 +13,24 @@ import com.scalar.db.io.BooleanColumn; import com.scalar.db.io.Column; import com.scalar.db.io.DataType; +import com.scalar.db.io.DateColumn; import com.scalar.db.io.DoubleColumn; import com.scalar.db.io.FloatColumn; import com.scalar.db.io.IntColumn; import com.scalar.db.io.Key; import com.scalar.db.io.TextColumn; +import com.scalar.db.io.TimeColumn; +import com.scalar.db.io.TimestampColumn; +import com.scalar.db.io.TimestampTZColumn; import com.scalar.dl.ledger.database.Database; import com.scalar.dl.ledger.exception.ContractContextException; import com.scalar.dl.ledger.function.JacksonBasedFunction; import java.io.IOException; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeParseException; import java.util.ArrayList; import java.util.List; import javax.annotation.Nullable; @@ -193,6 +202,42 @@ private Column getColumn(JsonNode jsonColumn) { } } + if (dataType.equals(DataType.DATE) + || dataType.equals(DataType.TIME) + || dataType.equals(DataType.TIMESTAMP) + || dataType.equals(DataType.TIMESTAMPTZ)) { + if (!value.isTextual()) { + throw new ContractContextException(Constants.INVALID_PUT_MUTABLE_FUNCTION_ARGUMENT_FORMAT); + } + return getTimeRelatedColumn(columnName, value.textValue(), dataType); + } + + throw new ContractContextException(Constants.INVALID_PUT_MUTABLE_FUNCTION_ARGUMENT_FORMAT); + } + + private Column getTimeRelatedColumn(String columnName, String value, DataType dataType) { + try { + if (dataType.equals(DataType.DATE)) { + return DateColumn.of(columnName, LocalDate.parse(value, Constants.DATE_FORMATTER)); + } + + if (dataType.equals(DataType.TIME)) { + return TimeColumn.of(columnName, LocalTime.parse(value, Constants.TIME_FORMATTER)); + } + + if (dataType.equals(DataType.TIMESTAMP)) { + return TimestampColumn.of( + columnName, LocalDateTime.parse(value, Constants.TIMESTAMP_FORMATTER)); + } + + if (dataType.equals(DataType.TIMESTAMPTZ)) { + return TimestampTZColumn.of( + columnName, Constants.TIMESTAMPTZ_FORMATTER.parse(value, Instant::from)); + } + } catch (DateTimeParseException e) { + throw new ContractContextException(Constants.INVALID_PUT_MUTABLE_FUNCTION_ARGUMENT_FORMAT); + } + throw new ContractContextException(Constants.INVALID_PUT_MUTABLE_FUNCTION_ARGUMENT_FORMAT); } @@ -227,6 +272,22 @@ private Column createNullColumn(String columnName, DataType dataType) { return BlobColumn.ofNull(columnName); } + if (dataType.equals(DataType.DATE)) { + return DateColumn.ofNull(columnName); + } + + if (dataType.equals(DataType.TIME)) { + return TimeColumn.ofNull(columnName); + } + + if (dataType.equals(DataType.TIMESTAMP)) { + return TimestampColumn.ofNull(columnName); + } + + if (dataType.equals(DataType.TIMESTAMPTZ)) { + return TimestampTZColumn.ofNull(columnName); + } + throw new ContractContextException(Constants.INVALID_PUT_MUTABLE_FUNCTION_ARGUMENT_FORMAT); } @@ -246,6 +307,14 @@ private DataType getDataType(String dataType) { return DataType.TEXT; case "BLOB": return DataType.BLOB; + case "DATE": + return DataType.DATE; + case "TIME": + return DataType.TIME; + case "TIMESTAMP": + return DataType.TIMESTAMP; + case "TIMESTAMPTZ": + return DataType.TIMESTAMPTZ; default: throw new ContractContextException(Constants.INVALID_PUT_MUTABLE_FUNCTION_ARGUMENT_FORMAT); } diff --git a/generic-contracts/src/test/java/com/scalar/dl/genericcontracts/object/PutToMutableDatabaseTest.java b/generic-contracts/src/test/java/com/scalar/dl/genericcontracts/object/PutToMutableDatabaseTest.java index b187decf..c7cd9627 100644 --- a/generic-contracts/src/test/java/com/scalar/dl/genericcontracts/object/PutToMutableDatabaseTest.java +++ b/generic-contracts/src/test/java/com/scalar/dl/genericcontracts/object/PutToMutableDatabaseTest.java @@ -20,6 +20,11 @@ import com.scalar.db.io.Key; import com.scalar.dl.ledger.database.Database; import com.scalar.dl.ledger.exception.ContractContextException; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneOffset; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; @@ -47,6 +52,21 @@ public class PutToMutableDatabaseTest { private static final double SOME_DOUBLE_COLUMN_VALUE = 1.23; private static final String SOME_BLOB_COLUMN_NAME = "blob_column"; private static final byte[] SOME_BLOB_COLUMN_VALUE = {10, 20, 30}; + private static final String SOME_DATE_COLUMN_NAME = "date_column"; + private static final String SOME_DATE_COLUMN_TEXT = "2021-02-03"; + private static final LocalDate SOME_DATE_COLUMN_VALUE = LocalDate.of(2021, 2, 3); + private static final String SOME_TIME_COLUMN_NAME = "time_column"; + private static final String SOME_TIME_COLUMN_TEXT = "05:45:00"; + private static final LocalTime SOME_TIME_COLUMN_VALUE = LocalTime.of(5, 45); + private static final String SOME_TIMESTAMP_COLUMN_NAME = "timestamp_column"; + private static final String SOME_TIMESTAMP_COLUMN_TEXT = "2021-02-03 05:45:00"; + private static final LocalDateTime SOME_TIMESTAMP_COLUMN_VALUE = + LocalDateTime.of(2021, 2, 3, 5, 45); + private static final String SOME_TIMESTAMPTZ_COLUMN_NAME = "timestamptz_column"; + private static final String SOME_TIMESTAMPTZ_COLUMN_TEXT = "2021-02-04 05:45:00 Z"; + private static final Instant SOME_TIMESTAMPTZ_COLUMN_VALUE = + LocalDateTime.of(2021, 2, 4, 5, 45).toInstant(ZoneOffset.UTC); + private static final JsonNode SOME_TEXT_COLUMN1 = mapper .createObjectNode() @@ -101,6 +121,30 @@ public class PutToMutableDatabaseTest { .put(Constants.COLUMN_NAME, SOME_COLUMN_NAME) .put(Constants.VALUE, (byte[]) null) .put(Constants.DATA_TYPE, "BLOB"); + private static final JsonNode SOME_DATE_COLUMN = + mapper + .createObjectNode() + .put(Constants.COLUMN_NAME, SOME_DATE_COLUMN_NAME) + .put(Constants.VALUE, SOME_DATE_COLUMN_TEXT) + .put(Constants.DATA_TYPE, "DATE"); + private static final JsonNode SOME_TIME_COLUMN = + mapper + .createObjectNode() + .put(Constants.COLUMN_NAME, SOME_TIME_COLUMN_NAME) + .put(Constants.VALUE, SOME_TIME_COLUMN_TEXT) + .put(Constants.DATA_TYPE, "TIME"); + private static final JsonNode SOME_TIMESTAMP_COLUMN = + mapper + .createObjectNode() + .put(Constants.COLUMN_NAME, SOME_TIMESTAMP_COLUMN_NAME) + .put(Constants.VALUE, SOME_TIMESTAMP_COLUMN_TEXT) + .put(Constants.DATA_TYPE, "TIMESTAMP"); + private static final JsonNode SOME_TIMESTAMPTZ_COLUMN = + mapper + .createObjectNode() + .put(Constants.COLUMN_NAME, SOME_TIMESTAMPTZ_COLUMN_NAME) + .put(Constants.VALUE, SOME_TIMESTAMPTZ_COLUMN_TEXT) + .put(Constants.DATA_TYPE, "TIMESTAMPTZ"); private static final JsonNode SOME_COLUMN_WITHOUT_VALUE = mapper.createObjectNode().put(Constants.COLUMN_NAME, SOME_COLUMN_NAME); private static final JsonNode SOME_COLUMN_WITH_INVALID_TYPE = @@ -155,7 +199,11 @@ public void invoke_AllArgumentsGiven_ShouldPutRecord() { .add(SOME_BIGINT_COLUMN) .add(SOME_FLOAT_COLUMN) .add(SOME_DOUBLE_COLUMN) - .add(SOME_BLOB_COLUMN); + .add(SOME_BLOB_COLUMN) + .add(SOME_DATE_COLUMN) + .add(SOME_TIME_COLUMN) + .add(SOME_TIMESTAMP_COLUMN) + .add(SOME_TIMESTAMPTZ_COLUMN); ObjectNode arguments = mapper.createObjectNode(); arguments.put(Constants.NAMESPACE, SOME_NAMESPACE); arguments.put(Constants.TABLE, SOME_TABLE); @@ -174,6 +222,10 @@ public void invoke_AllArgumentsGiven_ShouldPutRecord() { .floatValue(SOME_FLOAT_COLUMN_NAME, SOME_FLOAT_COLUMN_VALUE) .doubleValue(SOME_DOUBLE_COLUMN_NAME, SOME_DOUBLE_COLUMN_VALUE) .blobValue(SOME_BLOB_COLUMN_NAME, SOME_BLOB_COLUMN_VALUE) + .dateValue(SOME_DATE_COLUMN_NAME, SOME_DATE_COLUMN_VALUE) + .timeValue(SOME_TIME_COLUMN_NAME, SOME_TIME_COLUMN_VALUE) + .timestampValue(SOME_TIMESTAMP_COLUMN_NAME, SOME_TIMESTAMP_COLUMN_VALUE) + .timestampTZValue(SOME_TIMESTAMPTZ_COLUMN_NAME, SOME_TIMESTAMPTZ_COLUMN_VALUE) .build(); // Act @@ -535,4 +587,69 @@ private void invoke_ColumnsWithUnmatchedTypeGiven_ShouldThrowContractContextExce .hasMessage(Constants.INVALID_PUT_MUTABLE_FUNCTION_ARGUMENT_FORMAT); verify(database, never()).put(any()); } + + @Test + public void + invoke_ColumnsWithInvalidTimeRelatedFormatGiven_ShouldThrowContractContextException() { + // Arrange + Builder builder = ImmutableMap.builder(); + builder + .put( + mapper + .createObjectNode() + .put(Constants.COLUMN_NAME, SOME_DATE_COLUMN_NAME) + .put(Constants.VALUE, "2025-07") + .put(Constants.DATA_TYPE, "DATE"), + "DATE without day") + .put( + mapper + .createObjectNode() + .put(Constants.COLUMN_NAME, SOME_TIME_COLUMN_NAME) + .put(Constants.VALUE, "10:20 PM") + .put(Constants.DATA_TYPE, "TIME"), + "TIME with unexpected am/pm") + .put( + mapper + .createObjectNode() + .put(Constants.COLUMN_NAME, SOME_TIMESTAMP_COLUMN_NAME) + .put(Constants.VALUE, "2025-07 10:20:00") + .put(Constants.DATA_TYPE, "TIMESTAMP"), + "TIMESTAMP without day") + .put( + mapper + .createObjectNode() + .put(Constants.COLUMN_NAME, SOME_TIMESTAMPTZ_COLUMN_NAME) + .put(Constants.VALUE, "2027-07-01 12:34") + .put(Constants.DATA_TYPE, "TIMESTAMPTZ"), + "TIMESTAMPTZ without Z"); + + // Act Assert + builder + .build() + .entrySet() + .parallelStream() + .forEach( + entry -> + invoke_ColumnsWithUnmatchedTypeGiven_ShouldThrowContractContextException( + entry.getKey(), entry.getValue())); + } + + private void invoke_ColumnsWithInvalidTimeRelatedFormatGiven_ShouldThrowContractContextException( + JsonNode column, String description) { + // Arrange + ArrayNode partitionKey = mapper.createArrayNode().add(SOME_TEXT_COLUMN1); + ArrayNode columns = mapper.createArrayNode().add(column); + ObjectNode arguments = mapper.createObjectNode(); + arguments.put(Constants.NAMESPACE, SOME_NAMESPACE); + arguments.put(Constants.TABLE, SOME_TABLE); + arguments.set(Constants.PARTITION_KEY, partitionKey); + arguments.set(Constants.COLUMNS, columns); + + // Act Assert + assertThatThrownBy( + () -> putToMutableDatabase.invoke(database, arguments, null, null), description) + .isExactlyInstanceOf(ContractContextException.class) + .hasMessage(Constants.INVALID_PUT_MUTABLE_FUNCTION_ARGUMENT_FORMAT); + verify(database, never()).put(any()); + } } From 19015775fe6bceb1eb351ffe3cd2ed18c1c20b1f Mon Sep 17 00:00:00 2001 From: Jun Nemoto Date: Tue, 22 Jul 2025 18:33:07 +0900 Subject: [PATCH 2/2] Refactor tests --- .../object/PutToMutableDatabaseTest.java | 25 +++---------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/generic-contracts/src/test/java/com/scalar/dl/genericcontracts/object/PutToMutableDatabaseTest.java b/generic-contracts/src/test/java/com/scalar/dl/genericcontracts/object/PutToMutableDatabaseTest.java index c7cd9627..38b18bd6 100644 --- a/generic-contracts/src/test/java/com/scalar/dl/genericcontracts/object/PutToMutableDatabaseTest.java +++ b/generic-contracts/src/test/java/com/scalar/dl/genericcontracts/object/PutToMutableDatabaseTest.java @@ -565,29 +565,10 @@ public void invoke_ColumnsWithUnmatchedTypeGiven_ShouldThrowContractContextExcep .parallelStream() .forEach( entry -> - invoke_ColumnsWithUnmatchedTypeGiven_ShouldThrowContractContextException( + invoke_ColumnsWithInvalidArguments_ShouldThrowContractContextException( entry.getKey(), entry.getValue())); } - private void invoke_ColumnsWithUnmatchedTypeGiven_ShouldThrowContractContextException( - JsonNode column, String description) { - // Arrange - ArrayNode partitionKey = mapper.createArrayNode().add(SOME_TEXT_COLUMN1); - ArrayNode columns = mapper.createArrayNode().add(column); - ObjectNode arguments = mapper.createObjectNode(); - arguments.put(Constants.NAMESPACE, SOME_NAMESPACE); - arguments.put(Constants.TABLE, SOME_TABLE); - arguments.set(Constants.PARTITION_KEY, partitionKey); - arguments.set(Constants.COLUMNS, columns); - - // Act Assert - assertThatThrownBy( - () -> putToMutableDatabase.invoke(database, arguments, null, null), description) - .isExactlyInstanceOf(ContractContextException.class) - .hasMessage(Constants.INVALID_PUT_MUTABLE_FUNCTION_ARGUMENT_FORMAT); - verify(database, never()).put(any()); - } - @Test public void invoke_ColumnsWithInvalidTimeRelatedFormatGiven_ShouldThrowContractContextException() { @@ -630,11 +611,11 @@ private void invoke_ColumnsWithUnmatchedTypeGiven_ShouldThrowContractContextExce .parallelStream() .forEach( entry -> - invoke_ColumnsWithUnmatchedTypeGiven_ShouldThrowContractContextException( + invoke_ColumnsWithInvalidArguments_ShouldThrowContractContextException( entry.getKey(), entry.getValue())); } - private void invoke_ColumnsWithInvalidTimeRelatedFormatGiven_ShouldThrowContractContextException( + private void invoke_ColumnsWithInvalidArguments_ShouldThrowContractContextException( JsonNode column, String description) { // Arrange ArrayNode partitionKey = mapper.createArrayNode().add(SOME_TEXT_COLUMN1);