From b2a08229995cacdbc093c85b9eb7fcf9dd2503db Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Tue, 17 Sep 2024 17:26:01 -0700 Subject: [PATCH 1/2] Fixes #1429: add ITs to verify handling of "fractional ints" (allowed/non-allowed) --- .../tables/FindOneTableIntegrationTest.java | 72 ++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/tables/FindOneTableIntegrationTest.java b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/tables/FindOneTableIntegrationTest.java index d39e94171..718f87289 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/tables/FindOneTableIntegrationTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/tables/FindOneTableIntegrationTest.java @@ -20,6 +20,7 @@ public class FindOneTableIntegrationTest extends AbstractTableIntegrationTestBase { static final String TABLE_WITH_STRING_ID_AGE_NAME = "findOneSingleStringKeyTable"; static final String TABLE_WITH_TEXT_COLUMNS = "findOneTextColumnsTable"; + static final String TABLE_WITH_INT_COLUMNS = "findOneIntColumnsTable"; @BeforeAll public final void createDefaultTables() { @@ -43,6 +44,22 @@ public final void createDefaultTables() { "varcharText", Map.of("type", "text")), "idText"); + createTableWithColumns( + TABLE_WITH_INT_COLUMNS, + Map.of( + "id", + Map.of("type", "text"), + "intValue", + Map.of("type", "int"), + "longValue", + Map.of("type", "bigint"), + "shortValue", + Map.of("type", "smallint"), + "byteValue", + Map.of("type", "tinyint"), + "bigIntegerValue", + Map.of("type", "varint")), + "id"); } // On-empty tests to be run before ones that populate tables @@ -205,7 +222,6 @@ public void findOneDocIdKey() { @Nested @Order(3) - @TestClassOrder(ClassOrderer.OrderAnnotation.class) class FindOneTextColumns { public final String STRING_UTF8_WITH_2BYTE_CHAR = "utf8-2-byte-\u00a2"; // cent symbol public final String STRING_UTF8_WITH_3BYTE_CHAR = "utf8-3-byte-\u20ac"; // euro symbol @@ -251,6 +267,60 @@ void failTryingToInsertNonAscii() { @Nested @Order(4) + class FindOneIntColumns { + // [data-api#1429]: Test to verify that all-zero fractional parts are ok for int types + @Test + void insertWithIntColumnsZeroFractional() { + // In goes 5.00, out comes 5: + insertOneInTable(TABLE_WITH_INT_COLUMNS, numDoc("zero-fraction", "5.00")); + DataApiCommandSenders.assertTableCommand(keyspaceName, TABLE_WITH_INT_COLUMNS) + .postFindOne("{ \"filter\": { \"id\": \"zero-fraction\" } }") + .hasNoErrors() + .hasJSONField("data.document", numDoc("zero-fraction", "5")); + } + + // [data-api#1429]: Test to verify that scientific is allowed for int types if (and only if) + // the fractional part is zero + @Test + void insertWithIntColumnsScientificNotation() { + // In goes 1.23E+02, out comes 123: + insertOneInTable(TABLE_WITH_INT_COLUMNS, numDoc("scientific-but-int", "1.23E+02")); + DataApiCommandSenders.assertTableCommand(keyspaceName, TABLE_WITH_INT_COLUMNS) + .postFindOne("{ \"filter\": { \"id\": \"scientific-but-int\" } }") + .hasNoErrors() + .hasJSONField("data.document", numDoc("scientific-but-int", "123")); + } + + // [data-api#1429]: Test to verify that should there be real fraction, insert fails + @Test + void failWithNonZeroFractionPlain() { + // Try with 12.5, should fail + DataApiCommandSenders.assertTableCommand(keyspaceName, TABLE_WITH_INT_COLUMNS) + .postInsertOne(numDoc("non-zero-fraction", "12.5")) + .hasSingleApiError( + DocumentException.Code.INVALID_COLUMN_VALUES, + DocumentException.class, + "Root cause: Rounding necessary"); + } + + private String numDoc(String id, String num) { + return + """ + { + "id": "%s", + "byteValue": %s, + "shortValue": %s, + "intValue": %s, + "longValue": %s, + "bigIntegerValue": %s + } + """ + .formatted(id, num, num, num, num, num); + } + } + + @Nested + @Order(5) class FindOneFail { @Test @Order(1) From d3016476ba6480881d4b36a8467dd4d17f509f6a Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Tue, 17 Sep 2024 18:21:33 -0700 Subject: [PATCH 2/2] Refactor tests a bit by adding InsertOneTableIntegrationTest, splitting from FindOne - test --- .../tables/FindOneTableIntegrationTest.java | 128 --------------- .../tables/InsertOneTableIntegrationTest.java | 154 ++++++++++++++++++ 2 files changed, 154 insertions(+), 128 deletions(-) create mode 100644 src/test/java/io/stargate/sgv2/jsonapi/api/v1/tables/InsertOneTableIntegrationTest.java diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/tables/FindOneTableIntegrationTest.java b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/tables/FindOneTableIntegrationTest.java index 718f87289..cf4a55f79 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/tables/FindOneTableIntegrationTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/tables/FindOneTableIntegrationTest.java @@ -3,7 +3,6 @@ import io.quarkus.test.common.WithTestResource; import io.quarkus.test.junit.QuarkusIntegrationTest; import io.stargate.sgv2.jsonapi.api.v1.util.DataApiCommandSenders; -import io.stargate.sgv2.jsonapi.exception.DocumentException; import io.stargate.sgv2.jsonapi.exception.ErrorCodeV1; import io.stargate.sgv2.jsonapi.testresource.DseTestResource; import java.util.Map; @@ -19,8 +18,6 @@ @TestClassOrder(ClassOrderer.OrderAnnotation.class) public class FindOneTableIntegrationTest extends AbstractTableIntegrationTestBase { static final String TABLE_WITH_STRING_ID_AGE_NAME = "findOneSingleStringKeyTable"; - static final String TABLE_WITH_TEXT_COLUMNS = "findOneTextColumnsTable"; - static final String TABLE_WITH_INT_COLUMNS = "findOneIntColumnsTable"; @BeforeAll public final void createDefaultTables() { @@ -34,32 +31,6 @@ public final void createDefaultTables() { "name", Map.of("type", "text")), "id"); - createTableWithColumns( - TABLE_WITH_TEXT_COLUMNS, - Map.of( - "idText", - Map.of("type", "text"), - "asciiText", - Map.of("type", "ascii"), - "varcharText", - Map.of("type", "text")), - "idText"); - createTableWithColumns( - TABLE_WITH_INT_COLUMNS, - Map.of( - "id", - Map.of("type", "text"), - "intValue", - Map.of("type", "int"), - "longValue", - Map.of("type", "bigint"), - "shortValue", - Map.of("type", "smallint"), - "byteValue", - Map.of("type", "tinyint"), - "bigIntegerValue", - Map.of("type", "varint")), - "id"); } // On-empty tests to be run before ones that populate tables @@ -222,105 +193,6 @@ public void findOneDocIdKey() { @Nested @Order(3) - class FindOneTextColumns { - public final String STRING_UTF8_WITH_2BYTE_CHAR = "utf8-2-byte-\u00a2"; // cent symbol - public final String STRING_UTF8_WITH_3BYTE_CHAR = "utf8-3-byte-\u20ac"; // euro symbol - - @Test - void insertWithTextColumnsAndFind() { - final String DOC_JSON = - """ - { - "idText": "abc", - "asciiText": "safe value", - "varcharText": "%s/%s" - } - """ - .formatted(STRING_UTF8_WITH_2BYTE_CHAR, STRING_UTF8_WITH_3BYTE_CHAR); - insertOneInTable(TABLE_WITH_TEXT_COLUMNS, DOC_JSON); - - DataApiCommandSenders.assertTableCommand(keyspaceName, TABLE_WITH_TEXT_COLUMNS) - .postFindOne("{ \"filter\": { \"idText\": \"abc\" } }") - .hasNoErrors() - .hasJSONField("data.document", DOC_JSON); - } - - @Test - void failTryingToInsertNonAscii() { - final String DOC_JSON = - """ - { - "idText": "def", - "asciiText": "%s", - "varcharText": "safe value" - } - """ - .formatted(STRING_UTF8_WITH_2BYTE_CHAR); - DataApiCommandSenders.assertTableCommand(keyspaceName, TABLE_WITH_TEXT_COLUMNS) - .postInsertOne(DOC_JSON) - .hasSingleApiError( - DocumentException.Code.INVALID_COLUMN_VALUES, - DocumentException.class, - "String contains non-ASCII character"); - } - } - - @Nested - @Order(4) - class FindOneIntColumns { - // [data-api#1429]: Test to verify that all-zero fractional parts are ok for int types - @Test - void insertWithIntColumnsZeroFractional() { - // In goes 5.00, out comes 5: - insertOneInTable(TABLE_WITH_INT_COLUMNS, numDoc("zero-fraction", "5.00")); - DataApiCommandSenders.assertTableCommand(keyspaceName, TABLE_WITH_INT_COLUMNS) - .postFindOne("{ \"filter\": { \"id\": \"zero-fraction\" } }") - .hasNoErrors() - .hasJSONField("data.document", numDoc("zero-fraction", "5")); - } - - // [data-api#1429]: Test to verify that scientific is allowed for int types if (and only if) - // the fractional part is zero - @Test - void insertWithIntColumnsScientificNotation() { - // In goes 1.23E+02, out comes 123: - insertOneInTable(TABLE_WITH_INT_COLUMNS, numDoc("scientific-but-int", "1.23E+02")); - DataApiCommandSenders.assertTableCommand(keyspaceName, TABLE_WITH_INT_COLUMNS) - .postFindOne("{ \"filter\": { \"id\": \"scientific-but-int\" } }") - .hasNoErrors() - .hasJSONField("data.document", numDoc("scientific-but-int", "123")); - } - - // [data-api#1429]: Test to verify that should there be real fraction, insert fails - @Test - void failWithNonZeroFractionPlain() { - // Try with 12.5, should fail - DataApiCommandSenders.assertTableCommand(keyspaceName, TABLE_WITH_INT_COLUMNS) - .postInsertOne(numDoc("non-zero-fraction", "12.5")) - .hasSingleApiError( - DocumentException.Code.INVALID_COLUMN_VALUES, - DocumentException.class, - "Root cause: Rounding necessary"); - } - - private String numDoc(String id, String num) { - return - """ - { - "id": "%s", - "byteValue": %s, - "shortValue": %s, - "intValue": %s, - "longValue": %s, - "bigIntegerValue": %s - } - """ - .formatted(id, num, num, num, num, num); - } - } - - @Nested - @Order(5) class FindOneFail { @Test @Order(1) diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/tables/InsertOneTableIntegrationTest.java b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/tables/InsertOneTableIntegrationTest.java new file mode 100644 index 000000000..ff4b84bab --- /dev/null +++ b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/tables/InsertOneTableIntegrationTest.java @@ -0,0 +1,154 @@ +package io.stargate.sgv2.jsonapi.api.v1.tables; + +import io.quarkus.test.common.WithTestResource; +import io.quarkus.test.junit.QuarkusIntegrationTest; +import io.stargate.sgv2.jsonapi.api.v1.util.DataApiCommandSenders; +import io.stargate.sgv2.jsonapi.exception.DocumentException; +import io.stargate.sgv2.jsonapi.testresource.DseTestResource; +import java.util.Map; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.ClassOrderer; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestClassOrder; + +@QuarkusIntegrationTest +@WithTestResource(value = DseTestResource.class, restrictToAnnotatedClass = false) +@TestClassOrder(ClassOrderer.OrderAnnotation.class) +public class InsertOneTableIntegrationTest extends AbstractTableIntegrationTestBase { + static final String TABLE_WITH_TEXT_COLUMNS = "findOneTextColumnsTable"; + static final String TABLE_WITH_INT_COLUMNS = "findOneIntColumnsTable"; + + @BeforeAll + public final void createDefaultTables() { + createTableWithColumns( + TABLE_WITH_TEXT_COLUMNS, + Map.of( + "idText", + Map.of("type", "text"), + "asciiText", + Map.of("type", "ascii"), + "varcharText", + Map.of("type", "text")), + "idText"); + createTableWithColumns( + TABLE_WITH_INT_COLUMNS, + Map.of( + "id", + Map.of("type", "text"), + "intValue", + Map.of("type", "int"), + "longValue", + Map.of("type", "bigint"), + "shortValue", + Map.of("type", "smallint"), + "byteValue", + Map.of("type", "tinyint"), + "bigIntegerValue", + Map.of("type", "varint")), + "id"); + } + + @Nested + @Order(1) + class InsertTextColumns { + public final String STRING_UTF8_WITH_2BYTE_CHAR = "utf8-2-byte-\u00a2"; // cent symbol + public final String STRING_UTF8_WITH_3BYTE_CHAR = "utf8-3-byte-\u20ac"; // euro symbol + + @Test + void insertWithTextColumns() { + final String DOC_JSON = + """ + { + "idText": "abc", + "asciiText": "safe value", + "varcharText": "%s/%s" + } + """ + .formatted(STRING_UTF8_WITH_2BYTE_CHAR, STRING_UTF8_WITH_3BYTE_CHAR); + insertOneInTable(TABLE_WITH_TEXT_COLUMNS, DOC_JSON); + + // And verify that we can read it back + DataApiCommandSenders.assertTableCommand(keyspaceName, TABLE_WITH_TEXT_COLUMNS) + .postFindOne("{ \"filter\": { \"idText\": \"abc\" } }") + .hasNoErrors() + .hasJSONField("data.document", DOC_JSON); + } + + @Test + void failTryingToInsertNonAscii() { + final String DOC_JSON = + """ + { + "idText": "def", + "asciiText": "%s", + "varcharText": "safe value" + } + """ + .formatted(STRING_UTF8_WITH_2BYTE_CHAR); + DataApiCommandSenders.assertTableCommand(keyspaceName, TABLE_WITH_TEXT_COLUMNS) + .postInsertOne(DOC_JSON) + .hasSingleApiError( + DocumentException.Code.INVALID_COLUMN_VALUES, + DocumentException.class, + "String contains non-ASCII character"); + } + } + + @Nested + @Order(2) + class InsertIntColumns { + // [data-api#1429]: Test to verify that all-zero fractional parts are ok for int types + @Test + void insertWithIntColumnsZeroFractional() { + // In goes 5.00 + insertOneInTable(TABLE_WITH_INT_COLUMNS, numDoc("zero-fraction", "5.00")); + // and out comes 5 + DataApiCommandSenders.assertTableCommand(keyspaceName, TABLE_WITH_INT_COLUMNS) + .postFindOne("{ \"filter\": { \"id\": \"zero-fraction\" } }") + .hasNoErrors() + .hasJSONField("data.document", numDoc("zero-fraction", "5")); + } + + // [data-api#1429]: Test to verify that scientific is allowed for int types if (and only if) + // the fractional part is zero + @Test + void insertWithIntColumnsScientificNotation() { + // In goes 1.23E+02 + insertOneInTable(TABLE_WITH_INT_COLUMNS, numDoc("scientific-but-int", "1.23E+02")); + // and out comes 123 + DataApiCommandSenders.assertTableCommand(keyspaceName, TABLE_WITH_INT_COLUMNS) + .postFindOne("{ \"filter\": { \"id\": \"scientific-but-int\" } }") + .hasNoErrors() + .hasJSONField("data.document", numDoc("scientific-but-int", "123")); + } + + // [data-api#1429]: Test to verify that should there be real fraction, insert fails + @Test + void failWithNonZeroFractionPlain() { + // Try with 12.5, should fail + DataApiCommandSenders.assertTableCommand(keyspaceName, TABLE_WITH_INT_COLUMNS) + .postInsertOne(numDoc("non-zero-fraction", "12.5")) + .hasSingleApiError( + DocumentException.Code.INVALID_COLUMN_VALUES, + DocumentException.class, + "Root cause: Rounding necessary"); + } + + private String numDoc(String id, String num) { + return + """ + { + "id": "%s", + "byteValue": %s, + "shortValue": %s, + "intValue": %s, + "longValue": %s, + "bigIntegerValue": %s + } + """ + .formatted(id, num, num, num, num, num); + } + } +}