From ac1a3940b1c38ce6c26ec153326e229f7d31e4be Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Tue, 18 Mar 2025 17:02:30 -0400 Subject: [PATCH] Move Scala tests to JUnit; begin work onintegration tests --- .idea/libraries/Maven__scala_sdk_3_1_3.xml | 26 + .idea/libraries/Maven__scala_sdk_3_3_3.xml | 25 + .idea/libraries/Maven__scala_sdk_3_5_2.xml | 26 + src/jvm/pom.xml | 24 +- .../groovy/query/RemoveFieldsQueryTest.groovy | 107 ++++ .../documents/groovy/query/WhereTest.groovy | 175 ++++++ .../documents/scala/AutoIdSpec.scala | 86 --- .../documents/scala/AutoIdTest.scala | 128 +++++ .../documents/scala/ClearConfiguration.scala | 11 - .../documents/scala/ConfigurationSpec.scala | 38 -- .../documents/scala/ConfigurationTest.scala | 35 ++ .../documents/scala/DialectSpec.scala | 27 - .../documents/scala/DialectTest.scala | 34 ++ .../documents/scala/DocumentIndexSpec.scala | 17 - .../documents/scala/DocumentIndexTest.scala | 19 + .../documents/scala/FieldMatchSpec.scala | 20 - .../documents/scala/FieldMatchTest.scala | 22 + .../bitbadger/documents/scala/FieldSpec.scala | 428 -------------- .../bitbadger/documents/scala/FieldTest.scala | 539 ++++++++++++++++++ .../bitbadger/documents/scala/OpSpec.scala | 44 -- .../bitbadger/documents/scala/OpTest.scala | 64 +++ .../documents/scala/ParameterNameSpec.scala | 23 - .../documents/scala/ParameterNameTest.scala | 25 + .../documents/scala/ParameterSpec.scala | 26 - .../documents/scala/ParameterTest.scala | 30 + .../scala/query/CountQuerySpec.scala | 56 -- .../scala/query/CountQueryTest.scala | 69 +++ .../scala/query/DefinitionQuerySpec.scala | 81 --- .../scala/query/DefinitionQueryTest.scala | 107 ++++ .../scala/query/DeleteQuerySpec.scala | 61 -- .../scala/query/DeleteQueryTest.scala | 77 +++ .../scala/query/DocumentQuerySpec.scala | 85 --- .../scala/query/DocumentQueryTest.scala | 113 ++++ .../scala/query/ExistsQuerySpec.scala | 64 --- .../scala/query/ExistsQueryTest.scala | 77 +++ .../documents/scala/query/FindQuerySpec.scala | 67 --- .../documents/scala/query/FindQueryTest.scala | 82 +++ .../scala/query/PatchQuerySpec.scala | 63 -- .../scala/query/PatchQueryTest.scala | 75 +++ .../scala/query/QueryUtilsSpec.scala | 103 ---- .../scala/query/QueryUtilsTest.scala | 138 +++++ .../scala/query/RemoveFieldsQueryTest.scala | 85 +++ .../documents/scala/query/WhereTest.scala | 143 +++++ .../scala/support/JsonDocument.scala | 20 + .../documents/scala/support/SubDocument.scala | 3 + 45 files changed, 2247 insertions(+), 1321 deletions(-) create mode 100644 .idea/libraries/Maven__scala_sdk_3_1_3.xml create mode 100644 .idea/libraries/Maven__scala_sdk_3_3_3.xml create mode 100644 .idea/libraries/Maven__scala_sdk_3_5_2.xml create mode 100644 src/jvm/src/test/groovy/solutions/bitbadger/documents/groovy/query/RemoveFieldsQueryTest.groovy create mode 100644 src/jvm/src/test/groovy/solutions/bitbadger/documents/groovy/query/WhereTest.groovy delete mode 100644 src/jvm/src/test/scala/solutions/bitbadger/documents/scala/AutoIdSpec.scala create mode 100644 src/jvm/src/test/scala/solutions/bitbadger/documents/scala/AutoIdTest.scala delete mode 100644 src/jvm/src/test/scala/solutions/bitbadger/documents/scala/ClearConfiguration.scala delete mode 100644 src/jvm/src/test/scala/solutions/bitbadger/documents/scala/ConfigurationSpec.scala create mode 100644 src/jvm/src/test/scala/solutions/bitbadger/documents/scala/ConfigurationTest.scala delete mode 100644 src/jvm/src/test/scala/solutions/bitbadger/documents/scala/DialectSpec.scala create mode 100644 src/jvm/src/test/scala/solutions/bitbadger/documents/scala/DialectTest.scala delete mode 100644 src/jvm/src/test/scala/solutions/bitbadger/documents/scala/DocumentIndexSpec.scala create mode 100644 src/jvm/src/test/scala/solutions/bitbadger/documents/scala/DocumentIndexTest.scala delete mode 100644 src/jvm/src/test/scala/solutions/bitbadger/documents/scala/FieldMatchSpec.scala create mode 100644 src/jvm/src/test/scala/solutions/bitbadger/documents/scala/FieldMatchTest.scala delete mode 100644 src/jvm/src/test/scala/solutions/bitbadger/documents/scala/FieldSpec.scala create mode 100644 src/jvm/src/test/scala/solutions/bitbadger/documents/scala/FieldTest.scala delete mode 100644 src/jvm/src/test/scala/solutions/bitbadger/documents/scala/OpSpec.scala create mode 100644 src/jvm/src/test/scala/solutions/bitbadger/documents/scala/OpTest.scala delete mode 100644 src/jvm/src/test/scala/solutions/bitbadger/documents/scala/ParameterNameSpec.scala create mode 100644 src/jvm/src/test/scala/solutions/bitbadger/documents/scala/ParameterNameTest.scala delete mode 100644 src/jvm/src/test/scala/solutions/bitbadger/documents/scala/ParameterSpec.scala create mode 100644 src/jvm/src/test/scala/solutions/bitbadger/documents/scala/ParameterTest.scala delete mode 100644 src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/CountQuerySpec.scala create mode 100644 src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/CountQueryTest.scala delete mode 100644 src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/DefinitionQuerySpec.scala create mode 100644 src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/DefinitionQueryTest.scala delete mode 100644 src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/DeleteQuerySpec.scala create mode 100644 src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/DeleteQueryTest.scala delete mode 100644 src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/DocumentQuerySpec.scala create mode 100644 src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/DocumentQueryTest.scala delete mode 100644 src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/ExistsQuerySpec.scala create mode 100644 src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/ExistsQueryTest.scala delete mode 100644 src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/FindQuerySpec.scala create mode 100644 src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/FindQueryTest.scala delete mode 100644 src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/PatchQuerySpec.scala create mode 100644 src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/PatchQueryTest.scala delete mode 100644 src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/QueryUtilsSpec.scala create mode 100644 src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/QueryUtilsTest.scala create mode 100644 src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/RemoveFieldsQueryTest.scala create mode 100644 src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/WhereTest.scala create mode 100644 src/jvm/src/test/scala/solutions/bitbadger/documents/scala/support/JsonDocument.scala create mode 100644 src/jvm/src/test/scala/solutions/bitbadger/documents/scala/support/SubDocument.scala diff --git a/.idea/libraries/Maven__scala_sdk_3_1_3.xml b/.idea/libraries/Maven__scala_sdk_3_1_3.xml new file mode 100644 index 0000000..17f32de --- /dev/null +++ b/.idea/libraries/Maven__scala_sdk_3_1_3.xml @@ -0,0 +1,26 @@ + + + + Scala_3_1 + + + + + + + + + + + + + + + + file://$MAVEN_REPOSITORY$/org/scala-lang/scala3-sbt-bridge/3.1.3/scala3-sbt-bridge-3.1.3.jar + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__scala_sdk_3_3_3.xml b/.idea/libraries/Maven__scala_sdk_3_3_3.xml new file mode 100644 index 0000000..a753719 --- /dev/null +++ b/.idea/libraries/Maven__scala_sdk_3_3_3.xml @@ -0,0 +1,25 @@ + + + + Scala_3_3 + + + + + + + + + + + + + + + file://$MAVEN_REPOSITORY$/org/scala-lang/scala3-sbt-bridge/3.3.3/scala3-sbt-bridge-3.3.3.jar + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__scala_sdk_3_5_2.xml b/.idea/libraries/Maven__scala_sdk_3_5_2.xml new file mode 100644 index 0000000..edaffc7 --- /dev/null +++ b/.idea/libraries/Maven__scala_sdk_3_5_2.xml @@ -0,0 +1,26 @@ + + + + Scala_3_5 + + + + + + + + + + + + + + + + file://$MAVEN_REPOSITORY$/org/scala-lang/scala3-sbt-bridge/3.5.2/scala3-sbt-bridge-3.5.2.jar + + + + + + \ No newline at end of file diff --git a/src/jvm/pom.xml b/src/jvm/pom.xml index 674731b..343f18b 100644 --- a/src/jvm/pom.xml +++ b/src/jvm/pom.xml @@ -37,9 +37,9 @@ test - org.scalatest - scalatest_3 - 3.2.9 + org.scala-lang + scala3-library_3 + 3.5.2 test @@ -134,24 +134,6 @@ --add-opens java.base/java.lang=ALL-UNNAMED - - org.scalatest - scalatest-maven-plugin - 2.2.0 - - ${project.build.directory}/surefire-reports - . - WDF TestSuite.txt - - - - test - - test - - - - maven-failsafe-plugin 2.22.2 diff --git a/src/jvm/src/test/groovy/solutions/bitbadger/documents/groovy/query/RemoveFieldsQueryTest.groovy b/src/jvm/src/test/groovy/solutions/bitbadger/documents/groovy/query/RemoveFieldsQueryTest.groovy new file mode 100644 index 0000000..44acb89 --- /dev/null +++ b/src/jvm/src/test/groovy/solutions/bitbadger/documents/groovy/query/RemoveFieldsQueryTest.groovy @@ -0,0 +1,107 @@ +package solutions.bitbadger.documents.groovy.query + +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +//import solutions.bitbadger.documents.DocumentException +import solutions.bitbadger.documents.Field +import solutions.bitbadger.documents.Parameter +import solutions.bitbadger.documents.ParameterType +import solutions.bitbadger.documents.query.RemoveFieldsQuery +import solutions.bitbadger.documents.support.ForceDialect + +import static solutions.bitbadger.documents.support.TypesKt.TEST_TABLE +import static groovy.test.GroovyAssert.* + +/** + * Unit tests for the `RemoveFields` object + */ +@DisplayName('JVM | Groovy | Query | RemoveFieldsQuery') +class RemoveFieldsQueryTest { + + /** + * Reset the dialect + */ + @AfterEach + void cleanUp() { + ForceDialect.none() + } + + @Test + @DisplayName('byId generates correctly | PostgreSQL') + void byIdPostgres() { + ForceDialect.postgres() + assertEquals('Remove Fields query not constructed correctly', + "UPDATE $TEST_TABLE SET data = data - :name::text[] WHERE data->>'id' = :id".toString(), + RemoveFieldsQuery.byId(TEST_TABLE, List.of(new Parameter(':name', ParameterType.STRING, '{a,z}')))) + } + + @Test + @DisplayName('byId generates correctly | SQLite') + void byIdSQLite() { + ForceDialect.sqlite() + assertEquals('Remove Field query not constructed correctly', + "UPDATE $TEST_TABLE SET data = json_remove(data, :name0, :name1) WHERE data->>'id' = :id".toString(), + RemoveFieldsQuery.byId(TEST_TABLE, List.of(new Parameter(':name0', ParameterType.STRING, 'a'), + new Parameter(':name1', ParameterType.STRING, 'z')))) + } + + @Test + @DisplayName('byFields generates correctly | PostgreSQL') + void byFieldsPostgres() { + ForceDialect.postgres() + assertEquals('Remove Field query not constructed correctly', + "UPDATE $TEST_TABLE SET data = data - :name::text[] WHERE data->>'f' > :g".toString(), + RemoveFieldsQuery.byFields(TEST_TABLE, List.of(new Parameter(':name', ParameterType.STRING, '{b,c}')), + List.of(Field.greater('f', '', ':g')))) + } + + @Test + @DisplayName('byFields generates correctly | SQLite') + void byFieldsSQLite() { + ForceDialect.sqlite() + assertEquals('Remove Field query not constructed correctly', + "UPDATE $TEST_TABLE SET data = json_remove(data, :name0, :name1) WHERE data->>'f' > :g".toString(), + RemoveFieldsQuery.byFields(TEST_TABLE, + List.of(new Parameter(':name0', ParameterType.STRING, 'b'), + new Parameter(':name1', ParameterType.STRING, 'c')), + List.of(Field.greater('f', '', ':g')))) + } + + @Test + @DisplayName('byContains generates correctly | PostgreSQL') + void byContainsPostgres() { + ForceDialect.postgres() + assertEquals('Remove Field query not constructed correctly', + "UPDATE $TEST_TABLE SET data = data - :name::text[] WHERE data @> :criteria".toString(), + RemoveFieldsQuery.byContains(TEST_TABLE, + List.of(new Parameter(':name', ParameterType.STRING, '{m,n}')))) + } + +// TODO: resolve java.base open issue +// @Test +// @DisplayName('byContains fails | SQLite') +// void byContainsSQLite() { +// ForceDialect.sqlite() +// assertThrows(DocumentException) { RemoveFieldsQuery.byContains(TEST_TABLE, List.of()) } +// } + + @Test + @DisplayName('byJsonPath generates correctly | PostgreSQL') + void byJsonPathPostgres() { + ForceDialect.postgres() + assertEquals('Remove Field query not constructed correctly', + "UPDATE $TEST_TABLE SET data = data - :name::text[] WHERE jsonb_path_exists(data, :path::jsonpath)" + .toString(), + RemoveFieldsQuery.byJsonPath(TEST_TABLE, + List.of(new Parameter(':name', ParameterType.STRING, '{o,p}')))) + } + +// TODO: resolve java.base open issue +// @Test +// @DisplayName('byJsonPath fails | SQLite') +// void byJsonPathSQLite() { +// ForceDialect.sqlite() +// assertThrows(DocumentException) { RemoveFieldsQuery.byJsonPath(TEST_TABLE, List.of()) } +// } +} diff --git a/src/jvm/src/test/groovy/solutions/bitbadger/documents/groovy/query/WhereTest.groovy b/src/jvm/src/test/groovy/solutions/bitbadger/documents/groovy/query/WhereTest.groovy new file mode 100644 index 0000000..d65a115 --- /dev/null +++ b/src/jvm/src/test/groovy/solutions/bitbadger/documents/groovy/query/WhereTest.groovy @@ -0,0 +1,175 @@ +package solutions.bitbadger.documents.groovy.query + +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +//import solutions.bitbadger.documents.DocumentException +import solutions.bitbadger.documents.Field +import solutions.bitbadger.documents.FieldMatch +import solutions.bitbadger.documents.query.Where +import solutions.bitbadger.documents.support.ForceDialect + +import static groovy.test.GroovyAssert.* + +/** + * Unit tests for the `Where` object + */ +@DisplayName('JVM | Groovy | Query | Where') +class WhereTest { + + /** + * Clear the connection string (resets Dialect) + */ + @AfterEach + void cleanUp() { + ForceDialect.none() + } + + @Test + @DisplayName('byFields is blank when given no fields') + void byFieldsBlankIfEmpty() { + assertEquals('', Where.byFields(List.of())) + } + + @Test + @DisplayName('byFields generates one numeric field | PostgreSQL') + void byFieldsOneFieldPostgres() { + ForceDialect.postgres() + assertEquals("(data->>'it')::numeric = :that", Where.byFields(List.of(Field.equal('it', 9, ':that')))) + } + + @Test + @DisplayName('byFields generates one alphanumeric field | PostgreSQL') + void byFieldsOneAlphaFieldPostgres() { + ForceDialect.postgres() + assertEquals("data->>'it' = :that", Where.byFields(List.of(Field.equal('it', '', ':that')))) + } + + @Test + @DisplayName('byFields generates one field | SQLite') + void byFieldsOneFieldSQLite() { + ForceDialect.sqlite() + assertEquals("data->>'it' = :that", Where.byFields(List.of(Field.equal('it', '', ':that')))) + } + + @Test + @DisplayName('byFields generates multiple fields w/ default match | PostgreSQL') + void byFieldsMultipleDefaultPostgres() { + ForceDialect.postgres() + assertEquals("data->>'1' = :one AND (data->>'2')::numeric = :two AND data->>'3' = :three", + Where.byFields( + List.of(Field.equal('1', '', ':one'), Field.equal('2', 0L, ':two'), + Field.equal('3', '', ':three')))) + } + + @Test + @DisplayName('byFields generates multiple fields w/ default match | SQLite') + void byFieldsMultipleDefaultSQLite() { + ForceDialect.sqlite() + assertEquals("data->>'1' = :one AND data->>'2' = :two AND data->>'3' = :three", + Where.byFields( + List.of(Field.equal('1', '', ':one'), Field.equal('2', 0L, ':two'), + Field.equal('3', '', ':three')))) + } + + @Test + @DisplayName('byFields generates multiple fields w/ ANY match | PostgreSQL') + void byFieldsMultipleAnyPostgres() { + ForceDialect.postgres() + assertEquals("data->>'1' = :one OR (data->>'2')::numeric = :two OR data->>'3' = :three", + Where.byFields( + List.of(Field.equal('1', '', ':one'), Field.equal('2', 0L, ':two'), + Field.equal('3', '', ':three')), + FieldMatch.ANY)) + } + + @Test + @DisplayName('byFields generates multiple fields w/ ANY match | SQLite') + void byFieldsMultipleAnySQLite() { + ForceDialect.sqlite() + assertEquals("data->>'1' = :one OR data->>'2' = :two OR data->>'3' = :three", + Where.byFields( + List.of(Field.equal('1', '', ':one'), Field.equal('2', 0L, ':two'), + Field.equal('3', '', ':three')), + FieldMatch.ANY)) + } + + @Test + @DisplayName('byId generates defaults for alphanumeric key | PostgreSQL') + void byIdDefaultAlphaPostgres() { + ForceDialect.postgres() + assertEquals("data->>'id' = :id", Where.byId()) + } + + @Test + @DisplayName('byId generates defaults for numeric key | PostgreSQL') + void byIdDefaultNumericPostgres() { + ForceDialect.postgres() + assertEquals("(data->>'id')::numeric = :id", Where.byId(":id", 5)) + } + + @Test + @DisplayName('byId generates defaults | SQLite') + void byIdDefaultSQLite() { + ForceDialect.sqlite() + assertEquals("data->>'id' = :id", Where.byId()) + } + + @Test + @DisplayName('byId generates named ID | PostgreSQL') + void byIdDefaultNamedPostgres() { + ForceDialect.postgres() + assertEquals("data->>'id' = :key", Where.byId(':key')) + } + + @Test + @DisplayName('byId generates named ID | SQLite') + void byIdDefaultNamedSQLite() { + ForceDialect.sqlite() + assertEquals("data->>'id' = :key", Where.byId(':key')) + } + + @Test + @DisplayName('jsonContains generates defaults | PostgreSQL') + void jsonContainsDefaultPostgres() { + ForceDialect.postgres() + assertEquals('data @> :criteria', Where.jsonContains()) + } + + @Test + @DisplayName('jsonContains generates named parameter | PostgreSQL') + void jsonContainsNamedPostgres() { + ForceDialect.postgres() + assertEquals('data @> :it', Where.jsonContains(':it')) + } + +// TODO: resolve java.base open issue +// @Test +// @DisplayName('jsonContains fails | SQLite') +// void jsonContainsFailsSQLite() { +// ForceDialect.sqlite() +// assertThrows(DocumentException) { Where.jsonContains() } +// } + + @Test + @DisplayName('jsonPathMatches generates defaults | PostgreSQL') + void jsonPathMatchDefaultPostgres() { + ForceDialect.postgres() + assertEquals('jsonb_path_exists(data, :path::jsonpath)', Where.jsonPathMatches()) + } + + @Test + @DisplayName('jsonPathMatches generates named parameter | PostgreSQL') + void jsonPathMatchNamedPostgres() { + ForceDialect.postgres() + assertEquals('jsonb_path_exists(data, :jp::jsonpath)', Where.jsonPathMatches(':jp')) + } + +// TODO: resolve java.base open issue +// @Test +// @DisplayName('jsonPathMatches fails | SQLite') +// void jsonPathFailsSQLite() { +// ForceDialect.sqlite() +// assertThrows(DocumentException) { Where.jsonPathMatches() } +// } +} diff --git a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/AutoIdSpec.scala b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/AutoIdSpec.scala deleted file mode 100644 index 7653c91..0000000 --- a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/AutoIdSpec.scala +++ /dev/null @@ -1,86 +0,0 @@ -package solutions.bitbadger.documents.scala - -import org.scalatest.funspec.AnyFunSpec -import org.scalatest.matchers.should.Matchers -import solutions.bitbadger.documents.scala.support.{ByteIdClass, IntIdClass, LongIdClass, ShortIdClass, StringIdClass} -import solutions.bitbadger.documents.{AutoId, DocumentException} - -class AutoIdSpec extends AnyFunSpec with Matchers { - - describe("generateUUID") { - it("generates a UUID string") { - AutoId.generateUUID().length shouldEqual 32 - } - } - - describe("generateRandomString") { - it("generates a random hex character string of an even length") { - AutoId.generateRandomString(8).length shouldEqual 8 - } - it("generates a random hex character string of an odd length") { - AutoId.generateRandomString(11).length shouldEqual 11 - } - it("generates different random hex character strings") { - val result1 = AutoId.generateRandomString(16) - val result2 = AutoId.generateRandomString(16) - result1 should not be theSameInstanceAs (result2) - } - } - - describe("needsAutoId") { - it("fails for null document") { - a [DocumentException] should be thrownBy AutoId.needsAutoId(AutoId.DISABLED, null, "id") - } - it("fails for missing ID property") { - a [DocumentException] should be thrownBy AutoId.needsAutoId(AutoId.UUID, IntIdClass(0), "Id") - } - it("returns false if disabled") { - AutoId.needsAutoId(AutoId.DISABLED, "", "") shouldBe false - } - it("returns true for Number strategy and byte ID of 0") { - AutoId.needsAutoId(AutoId.NUMBER, ByteIdClass(0), "id") shouldBe true - } - it("returns false for Number strategy and byte ID of non-0") { - AutoId.needsAutoId(AutoId.NUMBER, ByteIdClass(77), "id") shouldBe false - } - it("returns true for Number strategy and short ID of 0") { - AutoId.needsAutoId(AutoId.NUMBER, ShortIdClass(0), "id") shouldBe true - } - it("returns false for Number strategy and short ID of non-0") { - AutoId.needsAutoId(AutoId.NUMBER, ShortIdClass(31), "id") shouldBe false - } - it("returns true for Number strategy and int ID of 0") { - AutoId.needsAutoId(AutoId.NUMBER, IntIdClass(0), "id") shouldBe true - } - it("returns false for Number strategy and int ID of non-0") { - AutoId.needsAutoId(AutoId.NUMBER, IntIdClass(6), "id") shouldBe false - } - it("returns true for Number strategy and long ID of 0") { - AutoId.needsAutoId(AutoId.NUMBER, LongIdClass(0), "id") shouldBe true - } - it("returns false for Number strategy and long ID of non-0") { - AutoId.needsAutoId(AutoId.NUMBER, LongIdClass(2), "id") shouldBe false - } - it("fails for Number strategy and non-number ID") { - a [DocumentException] should be thrownBy AutoId.needsAutoId(AutoId.NUMBER, StringIdClass(""), "id") - } - it("returns true for UUID strategy and blank ID") { - AutoId.needsAutoId(AutoId.UUID, StringIdClass(""), "id") shouldBe true - } - it("returns false for UUID strategy and non-blank ID") { - AutoId.needsAutoId(AutoId.UUID, StringIdClass("howdy"), "id") shouldBe false - } - it("fails for UUID strategy and non-string ID") { - a [DocumentException] should be thrownBy AutoId.needsAutoId(AutoId.UUID, IntIdClass(5), "id") - } - it("returns true for Random String strategy and blank ID") { - AutoId.needsAutoId(AutoId.RANDOM_STRING, StringIdClass(""), "id") shouldBe true - } - it("returns false for Random String strategy and non-blank ID") { - AutoId.needsAutoId(AutoId.RANDOM_STRING, StringIdClass("full"), "id") shouldBe false - } - it("fails for Random String strategy and non-string ID") { - a [DocumentException] should be thrownBy AutoId.needsAutoId(AutoId.RANDOM_STRING, ShortIdClass(55), "id") - } - } -} diff --git a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/AutoIdTest.scala b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/AutoIdTest.scala new file mode 100644 index 0000000..6c41e8f --- /dev/null +++ b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/AutoIdTest.scala @@ -0,0 +1,128 @@ +package solutions.bitbadger.documents.scala + +import org.junit.jupiter.api.Assertions._ +import org.junit.jupiter.api.{DisplayName, Test} +import solutions.bitbadger.documents.scala.support.{ByteIdClass, IntIdClass, LongIdClass, ShortIdClass, StringIdClass} +import solutions.bitbadger.documents.{AutoId, DocumentException} + +@DisplayName("JVM | Scala | AutoId") +class AutoIdTest { + + @Test + @DisplayName("Generates a UUID string") + def generateUUID(): Unit = + assertEquals(32, AutoId.generateUUID().length(), "The UUID should have been a 32-character string") + + @Test + @DisplayName("Generates a random hex character string of an even length") + def generateRandomStringEven(): Unit = + val result = AutoId.generateRandomString(8) + assertEquals(8, result.length(), s"There should have been 8 characters in $result") + + @Test + @DisplayName("Generates a random hex character string of an odd length") + def generateRandomStringOdd(): Unit = + val result = AutoId.generateRandomString(11) + assertEquals(11, result.length(), s"There should have been 11 characters in $result") + + @Test + @DisplayName("Generates different random hex character strings") + def generateRandomStringIsRandom(): Unit = + val result1 = AutoId.generateRandomString(16) + val result2 = AutoId.generateRandomString(16) + assertNotEquals(result1, result2, "There should have been 2 different strings generated") + + @Test + @DisplayName("needsAutoId fails for null document") + def needsAutoIdFailsForNullDocument(): Unit = + assertThrows(classOf[DocumentException], () => AutoId.needsAutoId(AutoId.DISABLED, null, "id")) + + @Test + @DisplayName("needsAutoId fails for missing ID property") + def needsAutoIdFailsForMissingId(): Unit = + assertThrows(classOf[DocumentException], () => AutoId.needsAutoId(AutoId.UUID, IntIdClass(0), "Id")) + + + @Test + @DisplayName("needsAutoId returns false if disabled") + def needsAutoIdFalseIfDisabled(): Unit = + assertFalse(AutoId.needsAutoId(AutoId.DISABLED, "", ""), "Disabled Auto ID should always return false") + + @Test + @DisplayName("needsAutoId returns true for Number strategy and byte ID of 0") + def needsAutoIdTrueForByteWithZero(): Unit = + assertTrue(AutoId.needsAutoId(AutoId.NUMBER, ByteIdClass(0), "id"), "Number Auto ID with 0 should return true") + + @Test + @DisplayName("needsAutoId returns false for Number strategy and byte ID of non-0") + def needsAutoIdFalseForByteWithNonZero(): Unit = + assertFalse(AutoId.needsAutoId(AutoId.NUMBER, ByteIdClass(77), "id"), "Number Auto ID with 77 should return false") + + @Test + @DisplayName("needsAutoId returns true for Number strategy and short ID of 0") + def needsAutoIdTrueForShortWithZero(): Unit = + assertTrue(AutoId.needsAutoId(AutoId.NUMBER, ShortIdClass(0), "id"), "Number Auto ID with 0 should return true") + + @Test + @DisplayName("needsAutoId returns false for Number strategy and short ID of non-0") + def needsAutoIdFalseForShortWithNonZero(): Unit = + assertFalse(AutoId.needsAutoId(AutoId.NUMBER, ShortIdClass(31), "id"), "Number Auto ID with 31 should return false") + + @Test + @DisplayName("needsAutoId returns true for Number strategy and int ID of 0") + def needsAutoIdTrueForIntWithZero(): Unit = + assertTrue(AutoId.needsAutoId(AutoId.NUMBER, IntIdClass(0), "id"), "Number Auto ID with 0 should return true") + + @Test + @DisplayName("needsAutoId returns false for Number strategy and int ID of non-0") + def needsAutoIdFalseForIntWithNonZero(): Unit = + assertFalse(AutoId.needsAutoId(AutoId.NUMBER, IntIdClass(6), "id"), "Number Auto ID with 6 should return false") + + @Test + @DisplayName("needsAutoId returns true for Number strategy and long ID of 0") + def needsAutoIdTrueForLongWithZero(): Unit = + assertTrue(AutoId.needsAutoId(AutoId.NUMBER, LongIdClass(0), "id"), "Number Auto ID with 0 should return true") + + @Test + @DisplayName("needsAutoId returns false for Number strategy and long ID of non-0") + def needsAutoIdFalseForLongWithNonZero(): Unit = + assertFalse(AutoId.needsAutoId(AutoId.NUMBER, LongIdClass(2), "id"), "Number Auto ID with 2 should return false") + + @Test + @DisplayName("needsAutoId fails for Number strategy and non-number ID") + def needsAutoIdFailsForNumberWithStringId(): Unit = + assertThrows(classOf[DocumentException], () => AutoId.needsAutoId(AutoId.NUMBER, StringIdClass(""), "id")) + + @Test + @DisplayName("needsAutoId returns true for UUID strategy and blank ID") + def needsAutoIdTrueForUUIDWithBlank(): Unit = + assertTrue(AutoId.needsAutoId(AutoId.UUID, StringIdClass(""), "id"), "UUID Auto ID with blank should return true") + + @Test + @DisplayName("needsAutoId returns false for UUID strategy and non-blank ID") + def needsAutoIdFalseForUUIDNotBlank(): Unit = + assertFalse(AutoId.needsAutoId(AutoId.UUID, StringIdClass("howdy"), "id"), + "UUID Auto ID with non-blank should return false") + + @Test + @DisplayName("needsAutoId fails for UUID strategy and non-string ID") + def needsAutoIdFailsForUUIDNonString(): Unit = + assertThrows(classOf[DocumentException], () => AutoId.needsAutoId(AutoId.UUID, IntIdClass(5), "id")) + + @Test + @DisplayName("needsAutoId returns true for Random String strategy and blank ID") + def needsAutoIdTrueForRandomWithBlank(): Unit = + assertTrue(AutoId.needsAutoId(AutoId.RANDOM_STRING, StringIdClass(""), "id"), + "Random String Auto ID with blank should return true") + + @Test + @DisplayName("needsAutoId returns false for Random String strategy and non-blank ID") + def needsAutoIdFalseForRandomNotBlank(): Unit = + assertFalse(AutoId.needsAutoId(AutoId.RANDOM_STRING, StringIdClass("full"), "id"), + "Random String Auto ID with non-blank should return false") + + @Test + @DisplayName("needsAutoId fails for Random String strategy and non-string ID") + def needsAutoIdFailsForRandomNonString(): Unit = + assertThrows(classOf[DocumentException], () => AutoId.needsAutoId(AutoId.RANDOM_STRING, ShortIdClass(55), "id")) +} diff --git a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/ClearConfiguration.scala b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/ClearConfiguration.scala deleted file mode 100644 index b2b64cf..0000000 --- a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/ClearConfiguration.scala +++ /dev/null @@ -1,11 +0,0 @@ -package solutions.bitbadger.documents.scala - -import org.scalatest.{BeforeAndAfterEach, Suite} -import solutions.bitbadger.documents.support.ForceDialect - -trait ClearConfiguration extends BeforeAndAfterEach { this: Suite => - - override def afterEach(): Unit = - try super.afterEach () - finally ForceDialect.none() -} diff --git a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/ConfigurationSpec.scala b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/ConfigurationSpec.scala deleted file mode 100644 index 183f4b6..0000000 --- a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/ConfigurationSpec.scala +++ /dev/null @@ -1,38 +0,0 @@ -package solutions.bitbadger.documents.scala - -import org.scalatest.funspec.AnyFunSpec -import org.scalatest.matchers.should.Matchers -import solutions.bitbadger.documents.{AutoId, Configuration, Dialect, DocumentException} - -class ConfigurationSpec extends AnyFunSpec with Matchers { - - describe("idField") { - it("defaults to `id`") { - Configuration.idField shouldEqual "id" - } - } - - describe("autoIdStrategy") { - it("defaults to `DISABLED`") { - Configuration.autoIdStrategy shouldEqual AutoId.DISABLED - } - } - - describe("idStringLength") { - it("defaults to 16") { - Configuration.idStringLength shouldEqual 16 - } - } - - describe("dialect") { - it("is derived from connection string") { - try { - a [DocumentException] should be thrownBy Configuration.dialect() - Configuration.setConnectionString("jdbc:postgresql:db") - Configuration.dialect() shouldEqual Dialect.POSTGRESQL - } finally { - Configuration.setConnectionString(null) - } - } - } -} diff --git a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/ConfigurationTest.scala b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/ConfigurationTest.scala new file mode 100644 index 0000000..1864216 --- /dev/null +++ b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/ConfigurationTest.scala @@ -0,0 +1,35 @@ +package solutions.bitbadger.documents.scala + +import org.junit.jupiter.api.{DisplayName, Test} +import org.junit.jupiter.api.Assertions._ +import solutions.bitbadger.documents.{AutoId, Configuration, Dialect, DocumentException} + +@DisplayName("JVM | Scala | Configuration") +class ConfigurationTest { + + @Test + @DisplayName("Default ID field is `id`") + def defaultIdField(): Unit = + assertEquals("id", Configuration.idField, "Default ID field incorrect") + + @Test + @DisplayName("Default Auto ID strategy is `DISABLED`") + def defaultAutoId(): Unit = + assertEquals(AutoId.DISABLED, Configuration.autoIdStrategy, "Default Auto ID strategy should be `disabled`") + + @Test + @DisplayName("Default ID string length should be 16") + def defaultIdStringLength(): Unit = + assertEquals(16, Configuration.idStringLength, "Default ID string length should be 16") + + @Test + @DisplayName("Dialect is derived from connection string") + def dialectIsDerived(): Unit = + try { + assertThrows(classOf[DocumentException], () => Configuration.dialect()) + Configuration.setConnectionString("jdbc:postgresql:db") + assertEquals(Dialect.POSTGRESQL, Configuration.dialect()) + } finally { + Configuration.setConnectionString(null) + } +} diff --git a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/DialectSpec.scala b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/DialectSpec.scala deleted file mode 100644 index 7321fe0..0000000 --- a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/DialectSpec.scala +++ /dev/null @@ -1,27 +0,0 @@ -package solutions.bitbadger.documents.scala - -import org.scalatest.funspec.AnyFunSpec -import org.scalatest.matchers.should.Matchers -import solutions.bitbadger.documents.{Dialect, DocumentException} - -class DialectSpec extends AnyFunSpec with Matchers { - - describe("deriveFromConnectionString") { - it("derives PostgreSQL correctly") { - Dialect.deriveFromConnectionString("jdbc:postgresql:db") shouldEqual Dialect.POSTGRESQL - } - it("derives SQLite correctly") { - Dialect.deriveFromConnectionString("jdbc:sqlite:memory") shouldEqual Dialect.SQLITE - } - it("fails when the connection string is unknown") { - try { - Dialect.deriveFromConnectionString("SQL Server") - fail("Dialect derivation should have failed") - } catch { - case ex: DocumentException => - ex.getMessage should not be null - ex.getMessage should include ("[SQL Server]") - } - } - } -} diff --git a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/DialectTest.scala b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/DialectTest.scala new file mode 100644 index 0000000..5089914 --- /dev/null +++ b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/DialectTest.scala @@ -0,0 +1,34 @@ +package solutions.bitbadger.documents.scala + +import org.junit.jupiter.api.{DisplayName, Test} +import org.junit.jupiter.api.Assertions.* +import solutions.bitbadger.documents.{Dialect, DocumentException} + +@DisplayName("JVM | Scala | Dialect") +class DialectTest { + + @Test + @DisplayName("deriveFromConnectionString derives PostgreSQL correctly") + def derivesPostgres(): Unit = + assertEquals(Dialect.POSTGRESQL, Dialect.deriveFromConnectionString("jdbc:postgresql:db"), + "Dialect should have been PostgreSQL") + + @Test + @DisplayName("deriveFromConnectionString derives SQLite correctly") + def derivesSQLite(): Unit = + assertEquals(Dialect.SQLITE, Dialect.deriveFromConnectionString("jdbc:sqlite:memory"), + "Dialect should have been SQLite") + + @Test + @DisplayName("deriveFromConnectionString fails when the connection string is unknown") + def deriveFailsWhenUnknown(): Unit = + try { + Dialect.deriveFromConnectionString("SQL Server") + fail("Dialect derivation should have failed") + } catch { + case ex: DocumentException => + assertNotNull(ex.getMessage, "The exception message should not have been null") + assertTrue(ex.getMessage.contains("[SQL Server]"), + "The connection string should have been in the exception message") + } +} diff --git a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/DocumentIndexSpec.scala b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/DocumentIndexSpec.scala deleted file mode 100644 index 3e21f8e..0000000 --- a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/DocumentIndexSpec.scala +++ /dev/null @@ -1,17 +0,0 @@ -package solutions.bitbadger.documents.scala - -import org.scalatest.funspec.AnyFunSpec -import org.scalatest.matchers.should.Matchers -import solutions.bitbadger.documents.DocumentIndex - -class DocumentIndexSpec extends AnyFunSpec with Matchers { - - describe("sql") { - it("returns blank for FULL") { - DocumentIndex.FULL.getSql shouldEqual "" - } - it("returns jsonb_path_ops for OPTIMIZED") { - DocumentIndex.OPTIMIZED.getSql shouldEqual " jsonb_path_ops" - } - } -} diff --git a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/DocumentIndexTest.scala b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/DocumentIndexTest.scala new file mode 100644 index 0000000..fd372bf --- /dev/null +++ b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/DocumentIndexTest.scala @@ -0,0 +1,19 @@ +package solutions.bitbadger.documents.scala + +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.{DisplayName, Test} +import solutions.bitbadger.documents.DocumentIndex + +@DisplayName("JVM | Scala | DocumentIndex") +class DocumentIndexTest { + + @Test + @DisplayName("FULL uses proper SQL") + def fullSQL(): Unit = + assertEquals("", DocumentIndex.FULL.getSql, "The SQL for Full is incorrect") + + @Test + @DisplayName("OPTIMIZED uses proper SQL") + def optimizedSQL(): Unit = + assertEquals(" jsonb_path_ops", DocumentIndex.OPTIMIZED.getSql, "The SQL for Optimized is incorrect") +} diff --git a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/FieldMatchSpec.scala b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/FieldMatchSpec.scala deleted file mode 100644 index 07d0843..0000000 --- a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/FieldMatchSpec.scala +++ /dev/null @@ -1,20 +0,0 @@ -package solutions.bitbadger.documents.scala - -import org.scalatest.funspec.AnyFunSpec -import org.scalatest.matchers.should.Matchers -import solutions.bitbadger.documents.FieldMatch - -/** - * Unit tests for the `FieldMatch` enum - */ -class FieldMatchSpec extends AnyFunSpec with Matchers { - - describe("sql") { - it("returns OR for ANY") { - FieldMatch.ANY.getSql shouldEqual "OR" - } - it("returns AND for ALL") { - FieldMatch.ALL.getSql shouldEqual "AND" - } - } -} diff --git a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/FieldMatchTest.scala b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/FieldMatchTest.scala new file mode 100644 index 0000000..4b2440b --- /dev/null +++ b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/FieldMatchTest.scala @@ -0,0 +1,22 @@ +package solutions.bitbadger.documents.scala + +import org.junit.jupiter.api.Assertions._ +import org.junit.jupiter.api.{DisplayName, Test} +import solutions.bitbadger.documents.FieldMatch + +/** + * Unit tests for the `FieldMatch` enum + */ +@DisplayName("JVM | Scala | FieldMatch") +class FieldMatchTest { + + @Test + @DisplayName("ANY uses proper SQL") + def any(): Unit = + assertEquals("OR", FieldMatch.ANY.getSql, "ANY should use OR") + + @Test + @DisplayName("ALL uses proper SQL") + def all(): Unit = + assertEquals("AND", FieldMatch.ALL.getSql, "ALL should use AND") +} diff --git a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/FieldSpec.scala b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/FieldSpec.scala deleted file mode 100644 index d54390b..0000000 --- a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/FieldSpec.scala +++ /dev/null @@ -1,428 +0,0 @@ -package solutions.bitbadger.documents.scala - -import org.scalatest.funspec.AnyFunSpec -import org.scalatest.matchers.should.Matchers -import solutions.bitbadger.documents.support.ForceDialect -import solutions.bitbadger.documents.{Dialect, DocumentException, Field, FieldFormat, Op} - -import scala.jdk.CollectionConverters.* - -class FieldSpec extends AnyFunSpec with ClearConfiguration with Matchers { - - // ~~~ INSTANCE METHODS ~~~ - - describe("withParameterName") { - it("fails for invalid name") { - a [DocumentException] should be thrownBy Field.equal("it", "").withParameterName("2424") - } - it("works with colon prefix") { - val field = Field.equal("abc", "22").withQualifier("me") - val withParam = field.withParameterName(":test") - withParam should not be theSameInstanceAs (field) - withParam.getName shouldEqual field.getName - withParam.getComparison shouldEqual field.getComparison - withParam.getParameterName shouldEqual ":test" - withParam.getQualifier shouldEqual field.getQualifier - } - it("works with at-sign prefix") { - val field = Field.equal("def", "44") - val withParam = field.withParameterName("@unit") - withParam should not be theSameInstanceAs (field) - withParam.getName shouldEqual field.getName - withParam.getComparison shouldEqual field.getComparison - withParam.getParameterName shouldEqual "@unit" - withParam.getQualifier shouldEqual field.getQualifier - } - } - - describe("withQualifier") { - it("sets qualifier correctly") { - val field = Field.equal("j", "k") - val withQual = field.withQualifier("test") - withQual should not be theSameInstanceAs (field) - withQual.getName shouldEqual field.getName - withQual.getComparison shouldEqual field.getComparison - withQual.getParameterName shouldEqual field.getParameterName - withQual.getQualifier shouldEqual "test" - } - } - - describe("path") { - it("generates simple unqualified PostgreSQL field") { - Field.greaterOrEqual("SomethingCool", 18).path(Dialect.POSTGRESQL, FieldFormat.SQL) shouldEqual - "data->>'SomethingCool'" - } - it("generates simple qualified PostgreSQL field") { - Field.less("SomethingElse", 9).withQualifier("this").path(Dialect.POSTGRESQL, FieldFormat.SQL) shouldEqual - "this.data->>'SomethingElse'" - } - it("generates nested unqualified PostgreSQL field") { - Field.equal("My.Nested.Field", "howdy").path(Dialect.POSTGRESQL, FieldFormat.SQL) shouldEqual - "data#>>'{My,Nested,Field}'" - } - it("generates nested qualified PostgreSQL field") { - Field.equal("Nest.Away", "doc").withQualifier("bird").path(Dialect.POSTGRESQL, FieldFormat.SQL) shouldEqual - "bird.data#>>'{Nest,Away}'" - } - it("generates simple unqualified SQLite field") { - Field.greaterOrEqual("SomethingCool", 18).path(Dialect.SQLITE, FieldFormat.SQL) shouldEqual - "data->>'SomethingCool'" - } - it("generates simple qualified SQLite field") { - Field.less("SomethingElse", 9).withQualifier("this").path(Dialect.SQLITE, FieldFormat.SQL) shouldEqual - "this.data->>'SomethingElse'" - } - it("generates nested unqualified SQLite field") { - Field.equal("My.Nested.Field", "howdy").path(Dialect.SQLITE, FieldFormat.SQL) shouldEqual - "data->'My'->'Nested'->>'Field'" - } - it("generates nested qualified SQLite field") { - Field.equal("Nest.Away", "doc").withQualifier("bird").path(Dialect.SQLITE, FieldFormat.SQL) shouldEqual - "bird.data->'Nest'->>'Away'" - } - } - - describe("toWhere") { - it("generates exists w/o qualifier | PostgreSQL") { - ForceDialect.postgres() - Field.exists("that_field").toWhere shouldEqual "data->>'that_field' IS NOT NULL" - } - it("generates exists w/o qualifier | SQLite") { - ForceDialect.sqlite() - Field.exists("that_field").toWhere shouldEqual "data->>'that_field' IS NOT NULL" - } - it("generates not-exists w/o qualifier | PostgreSQL") { - ForceDialect.postgres() - Field.notExists("a_field").toWhere shouldEqual "data->>'a_field' IS NULL" - } - it("generates not-exists w/o qualifier | SQLite") { - ForceDialect.sqlite() - Field.notExists("a_field").toWhere shouldEqual "data->>'a_field' IS NULL" - } - it("generates BETWEEN w/o qualifier, numeric range | PostgreSQL") { - ForceDialect.postgres() - Field.between("age", 13, 17, "@age").toWhere shouldEqual "(data->>'age')::numeric BETWEEN @agemin AND @agemax" - } - it("generates BETWEEN w/o qualifier, alphanumeric range | PostgreSQL") { - ForceDialect.postgres() - Field.between("city", "Atlanta", "Chicago", ":city").toWhere shouldEqual - "data->>'city' BETWEEN :citymin AND :citymax" - } - it("generates BETWEEN w/o qualifier | SQLite") { - ForceDialect.sqlite() - Field.between("age", 13, 17, "@age").toWhere shouldEqual "data->>'age' BETWEEN @agemin AND @agemax" - } - it("generates BETWEEN w/ qualifier, numeric range | PostgreSQL") { - ForceDialect.postgres() - Field.between("age", 13, 17, "@age").withQualifier("test").toWhere shouldEqual - "(test.data->>'age')::numeric BETWEEN @agemin AND @agemax" - } - it("generates BETWEEN w/ qualifier, alphanumeric range | PostgreSQL") { - ForceDialect.postgres() - Field.between("city", "Atlanta", "Chicago", ":city").withQualifier("unit").toWhere shouldEqual - "unit.data->>'city' BETWEEN :citymin AND :citymax" - } - it("generates BETWEEN w/ qualifier | SQLite") { - ForceDialect.sqlite() - Field.between("age", 13, 17, "@age").withQualifier("my").toWhere shouldEqual - "my.data->>'age' BETWEEN @agemin AND @agemax" - } - it("generates IN/any, numeric values | PostgreSQL") { - ForceDialect.postgres() - Field.any("even", List(2, 4, 6).asJava, ":nbr").toWhere shouldEqual - "(data->>'even')::numeric IN (:nbr_0, :nbr_1, :nbr_2)" - } - it("generates IN/any, alphanumeric values | PostgreSQL") { - ForceDialect.postgres() - Field.any("test", List("Atlanta", "Chicago").asJava, ":city").toWhere shouldEqual - "data->>'test' IN (:city_0, :city_1)" - } - it("generates IN/any | SQLite") { - ForceDialect.sqlite() - Field.any("test", List("Atlanta", "Chicago").asJava, ":city").toWhere shouldEqual - "data->>'test' IN (:city_0, :city_1)" - } - it("generates inArray | PostgreSQL") { - ForceDialect.postgres() - Field.inArray("even", "tbl", List(2, 4, 6, 8).asJava, ":it").toWhere shouldEqual - "data->'even' ??| ARRAY[:it_0, :it_1, :it_2, :it_3]" - } - it("generates inArray | SQLite") { - ForceDialect.sqlite() - Field.inArray("test", "tbl", List("Atlanta", "Chicago").asJava, ":city").toWhere shouldEqual - "EXISTS (SELECT 1 FROM json_each(tbl.data, '$.test') WHERE value IN (:city_0, :city_1))" - } - it("generates others w/o qualifier | PostgreSQL") { - ForceDialect.postgres() - Field.equal("some_field", "", ":value").toWhere shouldEqual "data->>'some_field' = :value" - } - it("generates others w/o qualifier | SQLite") { - ForceDialect.sqlite() - Field.equal("some_field", "", ":value").toWhere shouldEqual "data->>'some_field' = :value" - } - it("generates no-parameter w/ qualifier | PostgreSQL") { - ForceDialect.postgres() - Field.exists("no_field").withQualifier("test").toWhere shouldEqual "test.data->>'no_field' IS NOT NULL" - } - it("generates no-parameter w/ qualifier | SQLite") { - ForceDialect.sqlite() - Field.exists("no_field").withQualifier("test").toWhere shouldEqual "test.data->>'no_field' IS NOT NULL" - } - it("generates parameter w/ qualifier | PostgreSQL") { - ForceDialect.postgres() - Field.lessOrEqual("le_field", 18, ":it").withQualifier("q").toWhere shouldEqual - "(q.data->>'le_field')::numeric <= :it" - } - it("generates parameter w/ qualifier | SQLite") { - ForceDialect.sqlite() - Field.lessOrEqual("le_field", 18, ":it").withQualifier("q").toWhere shouldEqual "q.data->>'le_field' <= :it" - } - } - - // ~~~ STATIC CONSTRUCTOR TESTS ~~~ - - describe("equal") { - it("constructs a field w/o parameter name") { - val field = Field.equal("Test", 14) - field.getName shouldEqual "Test" - field.getComparison.getOp shouldEqual Op.EQUAL - field.getComparison.getValue shouldEqual 14 - field.getParameterName should be (null) - field.getQualifier should be (null) - } - it("constructs a field w/ parameter name") { - val field = Field.equal("Test", 14, ":w") - field.getName shouldEqual "Test" - field.getComparison.getOp shouldEqual Op.EQUAL - field.getComparison.getValue shouldEqual 14 - field.getParameterName shouldEqual ":w" - field.getQualifier should be (null) - } - } - - describe("greater") { - it("constructs a field w/o parameter name") { - val field = Field.greater("Great", "night") - field.getName shouldEqual "Great" - field.getComparison.getOp shouldEqual Op.GREATER - field.getComparison.getValue shouldEqual "night" - field.getParameterName should be (null) - field.getQualifier should be (null) - } - it("constructs a field w/ parameter name") { - val field = Field.greater("Great", "night", ":yeah") - field.getName shouldEqual "Great" - field.getComparison.getOp shouldEqual Op.GREATER - field.getComparison.getValue shouldEqual "night" - field.getParameterName shouldEqual ":yeah" - field.getQualifier should be (null) - } - } - - describe("greaterOrEqual") { - it("constructs a field w/o parameter name") { - val field = Field.greaterOrEqual("Nice", 88L) - field.getName shouldEqual "Nice" - field.getComparison.getOp shouldEqual Op.GREATER_OR_EQUAL - field.getComparison.getValue shouldEqual 88L - field.getParameterName should be (null) - field.getQualifier should be (null) - } - it("constructs a field w/ parameter name") { - val field = Field.greaterOrEqual("Nice", 88L, ":nice") - field.getName shouldEqual "Nice" - field.getComparison.getOp shouldEqual Op.GREATER_OR_EQUAL - field.getComparison.getValue shouldEqual 88L - field.getParameterName shouldEqual ":nice" - field.getQualifier should be (null) - } - } - - describe("less") { - it("constructs a field w/o parameter name") { - val field = Field.less("Lesser", "seven") - field.getName shouldEqual "Lesser" - field.getComparison.getOp shouldEqual Op.LESS - field.getComparison.getValue shouldEqual "seven" - field.getParameterName should be (null) - field.getQualifier should be (null) - } - it("constructs a field w/ parameter name") { - val field = Field.less("Lesser", "seven", ":max") - field.getName shouldEqual "Lesser" - field.getComparison.getOp shouldEqual Op.LESS - field.getComparison.getValue shouldEqual "seven" - field.getParameterName shouldEqual ":max" - field.getQualifier should be (null) - } - } - - describe("lessOrEqual") { - it("constructs a field w/o parameter name") { - val field = Field.lessOrEqual("Nobody", "KNOWS") - field.getName shouldEqual "Nobody" - field.getComparison.getOp shouldEqual Op.LESS_OR_EQUAL - field.getComparison.getValue shouldEqual "KNOWS" - field.getParameterName should be (null) - field.getQualifier should be (null) - } - it("constructs a field w/ parameter name") { - val field = Field.lessOrEqual("Nobody", "KNOWS", ":nope") - field.getName shouldEqual "Nobody" - field.getComparison.getOp shouldEqual Op.LESS_OR_EQUAL - field.getComparison.getValue shouldEqual "KNOWS" - field.getParameterName shouldEqual ":nope" - field.getQualifier should be (null) - } - } - - describe("notEqual") { - it("constructs a field w/o parameter name") { - val field = Field.notEqual("Park", "here") - field.getName shouldEqual "Park" - field.getComparison.getOp shouldEqual Op.NOT_EQUAL - field.getComparison.getValue shouldEqual "here" - field.getParameterName should be (null) - field.getQualifier should be (null) - } - it("constructs a field w/ parameter name") { - val field = Field.notEqual("Park", "here", ":now") - field.getName shouldEqual "Park" - field.getComparison.getOp shouldEqual Op.NOT_EQUAL - field.getComparison.getValue shouldEqual "here" - field.getParameterName shouldEqual ":now" - field.getQualifier should be (null) - } - } - - describe("between") { - it("constructs a field w/o parameter name") { - val field = Field.between("Age", 18, 49) - field.getName shouldEqual "Age" - field.getComparison.getOp shouldEqual Op.BETWEEN - field.getComparison.getValue.getFirst shouldEqual 18 - field.getComparison.getValue.getSecond shouldEqual 49 - field.getParameterName should be (null) - field.getQualifier should be (null) - } - it("constructs a field w/ parameter name") { - val field = Field.between("Age", 18, 49, ":limit") - field.getName shouldEqual "Age" - field.getComparison.getOp shouldEqual Op.BETWEEN - field.getComparison.getValue.getFirst shouldEqual 18 - field.getComparison.getValue.getSecond shouldEqual 49 - field.getParameterName shouldEqual ":limit" - field.getQualifier should be (null) - } - } - - describe("any") { - it("constructs a field w/o parameter name") { - val field = Field.any("Here", List(8, 16, 32).asJava) - field.getName shouldEqual "Here" - field.getComparison.getOp shouldEqual Op.IN - field.getComparison.getValue should be (List(8, 16, 32).asJava) - field.getParameterName should be (null) - field.getQualifier should be (null) - } - it("constructs a field w/ parameter name") { - val field = Field.any("Here", List(8, 16, 32).asJava, ":list") - field.getName shouldEqual "Here" - field.getComparison.getOp shouldEqual Op.IN - field.getComparison.getValue should be (List(8, 16, 32).asJava) - field.getParameterName shouldEqual ":list" - field.getQualifier should be (null) - } - } - - describe("inArray") { - it("constructs a field w/o parameter name") { - val field = Field.inArray("ArrayField", "table", List("z").asJava) - field.getName shouldEqual "ArrayField" - field.getComparison.getOp shouldEqual Op.IN_ARRAY - field.getComparison.getValue.getFirst shouldEqual "table" - field.getComparison.getValue.getSecond should be (List("z").asJava) - field.getParameterName should be (null) - field.getQualifier should be (null) - } - it("constructs a field w/ parameter name") { - val field = Field.inArray("ArrayField", "table", List("z").asJava, ":a") - field.getName shouldEqual "ArrayField" - field.getComparison.getOp shouldEqual Op.IN_ARRAY - field.getComparison.getValue.getFirst shouldEqual "table" - field.getComparison.getValue.getSecond should be (List("z").asJava) - field.getParameterName shouldEqual ":a" - field.getQualifier should be (null) - } - } - - describe("exists") { - it("constructs a field") { - val field = Field.exists("Groovy") - field.getName shouldEqual "Groovy" - field.getComparison.getOp shouldEqual Op.EXISTS - field.getComparison.getValue shouldEqual "" - field.getParameterName should be (null) - field.getQualifier should be (null) - } - } - - describe("notExists") { - it("constructs a field") { - val field = Field.notExists("Groovy") - field.getName shouldEqual "Groovy" - field.getComparison.getOp shouldEqual Op.NOT_EXISTS - field.getComparison.getValue shouldEqual "" - field.getParameterName should be (null) - field.getQualifier should be (null) - } - } - - describe("named") { - it("named constructs a field") { - val field = Field.named("Tacos") - field.getName shouldEqual "Tacos" - field.getComparison.getOp shouldEqual Op.EQUAL - field.getComparison.getValue shouldEqual "" - field.getParameterName should be (null) - field.getQualifier should be (null) - } - } - - describe("static constructors") { - it("fail for invalid parameter name") { - a [DocumentException] should be thrownBy Field.equal("a", "b", "that ain't it, Jack...") - } - } - - describe("nameToPath") { - it("creates a simple PostgreSQL SQL name") { - Field.nameToPath("Simple", Dialect.POSTGRESQL, FieldFormat.SQL) shouldEqual "data->>'Simple'" - } - it("creates a simple SQLite SQL name") { - Field.nameToPath("Simple", Dialect.SQLITE, FieldFormat.SQL) shouldEqual "data->>'Simple'" - } - it("creates a nested PostgreSQL SQL name") { - Field.nameToPath("A.Long.Path.to.the.Property", Dialect.POSTGRESQL, FieldFormat.SQL) shouldEqual - "data#>>'{A,Long,Path,to,the,Property}'" - } - it("creates a nested SQLite SQL name") { - Field.nameToPath("A.Long.Path.to.the.Property", Dialect.SQLITE, FieldFormat.SQL) shouldEqual - "data->'A'->'Long'->'Path'->'to'->'the'->>'Property'" - } - it("creates a simple PostgreSQL JSON name") { - Field.nameToPath("Simple", Dialect.POSTGRESQL, FieldFormat.JSON) shouldEqual "data->'Simple'" - } - it("creates a simple SQLite JSON name") { - Field.nameToPath("Simple", Dialect.SQLITE, FieldFormat.JSON) shouldEqual "data->'Simple'" - } - it("creates a nested PostgreSQL JSON name") { - Field.nameToPath("A.Long.Path.to.the.Property", Dialect.POSTGRESQL, FieldFormat.JSON) shouldEqual - "data#>'{A,Long,Path,to,the,Property}'" - } - it("creates a nested SQLite JSON name") { - Field.nameToPath("A.Long.Path.to.the.Property", Dialect.SQLITE, FieldFormat.JSON) shouldEqual - "data->'A'->'Long'->'Path'->'to'->'the'->'Property'" - } - } -} diff --git a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/FieldTest.scala b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/FieldTest.scala new file mode 100644 index 0000000..3923561 --- /dev/null +++ b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/FieldTest.scala @@ -0,0 +1,539 @@ +package solutions.bitbadger.documents.scala + +import org.junit.jupiter.api.{AfterEach, DisplayName, Test} +import org.junit.jupiter.api.Assertions.* +import solutions.bitbadger.documents.support.ForceDialect +import solutions.bitbadger.documents.{Dialect, DocumentException, Field, FieldFormat, Op} + +import scala.jdk.CollectionConverters.* + +@DisplayName("JVM | Scala | Field") +class FieldTest { + + /** + * Clear the connection string (resets Dialect) + */ + @AfterEach + def cleanUp(): Unit = + ForceDialect.none() + + // ~~~ INSTANCE METHODS ~~~ + + @Test + @DisplayName("withParameterName fails for invalid name") + def withParamNameFails(): Unit = + assertThrows(classOf[DocumentException], () => Field.equal("it", "").withParameterName("2424")) + + @Test + @DisplayName("withParameterName works with colon prefix") + def withParamNameColon(): Unit = + val field = Field.equal("abc", "22").withQualifier("me") + val withParam = field.withParameterName(":test") + assertNotSame(field, withParam, "A new Field instance should have been created") + assertEquals(field.getName, withParam.getName, "Name should have been preserved") + assertEquals(field.getComparison, withParam.getComparison, "Comparison should have been preserved") + assertEquals(":test", withParam.getParameterName, "Parameter name not set correctly") + assertEquals(field.getQualifier, withParam.getQualifier, "Qualifier should have been preserved") + + @Test + @DisplayName("withParameterName works with at-sign prefix") + def withParamNameAtSign(): Unit = + val field = Field.equal("def", "44") + val withParam = field.withParameterName("@unit") + assertNotSame(field, withParam, "A new Field instance should have been created") + assertEquals(field.getName, withParam.getName, "Name should have been preserved") + assertEquals(field.getComparison, withParam.getComparison, "Comparison should have been preserved") + assertEquals("@unit", withParam.getParameterName, "Parameter name not set correctly") + assertEquals(field.getQualifier, withParam.getQualifier, "Qualifier should have been preserved") + + @Test + @DisplayName("withQualifier sets qualifier correctly") + def withQualifier(): Unit = + val field = Field.equal("j", "k") + val withQual = field.withQualifier("test") + assertNotSame(field, withQual, "A new Field instance should have been created") + assertEquals(field.getName, withQual.getName, "Name should have been preserved") + assertEquals(field.getComparison, withQual.getComparison, "Comparison should have been preserved") + assertEquals(field.getParameterName, withQual.getParameterName, "Parameter Name should have been preserved") + assertEquals("test", withQual.getQualifier, "Qualifier not set correctly") + + @Test + @DisplayName("path generates for simple unqualified PostgreSQL field") + def pathPostgresSimpleUnqualified(): Unit = + assertEquals("data->>'SomethingCool'", + Field.greaterOrEqual("SomethingCool", 18).path(Dialect.POSTGRESQL, FieldFormat.SQL), "Path not correct") + + @Test + @DisplayName("path generates for simple qualified PostgreSQL field") + def pathPostgresSimpleQualified(): Unit = + assertEquals("this.data->>'SomethingElse'", + Field.less("SomethingElse", 9).withQualifier("this").path(Dialect.POSTGRESQL, FieldFormat.SQL), + "Path not correct") + + @Test + @DisplayName("path generates for nested unqualified PostgreSQL field") + def pathPostgresNestedUnqualified(): Unit = + assertEquals("data#>>'{My,Nested,Field}'", + Field.equal("My.Nested.Field", "howdy").path(Dialect.POSTGRESQL, FieldFormat.SQL), "Path not correct") + + @Test + @DisplayName("path generates for nested qualified PostgreSQL field") + def pathPostgresNestedQualified(): Unit = + assertEquals("bird.data#>>'{Nest,Away}'", + Field.equal("Nest.Away", "doc").withQualifier("bird").path(Dialect.POSTGRESQL, FieldFormat.SQL), + "Path not correct") + + @Test + @DisplayName("path generates for simple unqualified SQLite field") + def pathSQLiteSimpleUnqualified(): Unit = + assertEquals("data->>'SomethingCool'", + Field.greaterOrEqual("SomethingCool", 18).path(Dialect.SQLITE, FieldFormat.SQL), "Path not correct") + + @Test + @DisplayName("path generates for simple qualified SQLite field") + def pathSQLiteSimpleQualified(): Unit = + assertEquals("this.data->>'SomethingElse'", + Field.less("SomethingElse", 9).withQualifier("this").path(Dialect.SQLITE, FieldFormat.SQL), + "Path not correct") + + @Test + @DisplayName("path generates for nested unqualified SQLite field") + def pathSQLiteNestedUnqualified(): Unit = + assertEquals("data->'My'->'Nested'->>'Field'", + Field.equal("My.Nested.Field", "howdy").path(Dialect.SQLITE, FieldFormat.SQL), "Path not correct") + + @Test + @DisplayName("path generates for nested qualified SQLite field") + def pathSQLiteNestedQualified(): Unit = + assertEquals("bird.data->'Nest'->>'Away'", + Field.equal("Nest.Away", "doc").withQualifier("bird").path(Dialect.SQLITE, FieldFormat.SQL), + "Path not correct") + + @Test + @DisplayName("toWhere generates for exists w/o qualifier | PostgreSQL") + def toWhereExistsNoQualPostgres(): Unit = + ForceDialect.postgres() + assertEquals("data->>'that_field' IS NOT NULL", Field.exists("that_field").toWhere, + "Field WHERE clause not generated correctly") + + @Test + @DisplayName("toWhere generates for exists w/o qualifier | SQLite") + def toWhereExistsNoQualSQLite(): Unit = + ForceDialect.sqlite() + assertEquals("data->>'that_field' IS NOT NULL", Field.exists("that_field").toWhere, + "Field WHERE clause not generated correctly") + + @Test + @DisplayName("toWhere generates for not-exists w/o qualifier | PostgreSQL") + def toWhereNotExistsNoQualPostgres(): Unit = + ForceDialect.postgres() + assertEquals("data->>'a_field' IS NULL", Field.notExists("a_field").toWhere, + "Field WHERE clause not generated correctly") + + @Test + @DisplayName("toWhere generates for not-exists w/o qualifier | SQLite") + def toWhereNotExistsNoQualSQLite(): Unit = + ForceDialect.sqlite() + assertEquals("data->>'a_field' IS NULL", Field.notExists("a_field").toWhere, + "Field WHERE clause not generated correctly") + + @Test + @DisplayName("toWhere generates for BETWEEN w/o qualifier, numeric range | PostgreSQL") + def toWhereBetweenNoQualNumericPostgres(): Unit = + ForceDialect.postgres() + assertEquals("(data->>'age')::numeric BETWEEN @agemin AND @agemax", + Field.between("age", 13, 17, "@age").toWhere, "Field WHERE clause not generated correctly") + + @Test + @DisplayName("toWhere generates for BETWEEN w/o qualifier, alphanumeric range | PostgreSQL") + def toWhereBetweenNoQualAlphaPostgres(): Unit = + ForceDialect.postgres() + assertEquals("data->>'city' BETWEEN :citymin AND :citymax", + Field.between("city", "Atlanta", "Chicago", ":city").toWhere, "Field WHERE clause not generated correctly") + + @Test + @DisplayName("toWhere generates for BETWEEN w/o qualifier | SQLite") + def toWhereBetweenNoQualSQLite(): Unit = + ForceDialect.sqlite() + assertEquals("data->>'age' BETWEEN @agemin AND @agemax", Field.between("age", 13, 17, "@age").toWhere, + "Field WHERE clause not generated correctly") + + @Test + @DisplayName("toWhere generates for BETWEEN w/ qualifier, numeric range | PostgreSQL") + def toWhereBetweenQualNumericPostgres(): Unit = + ForceDialect.postgres() + assertEquals("(test.data->>'age')::numeric BETWEEN @agemin AND @agemax", + Field.between("age", 13, 17, "@age").withQualifier("test").toWhere, "Field WHERE clause not generated correctly") + + @Test + @DisplayName("toWhere generates for BETWEEN w/ qualifier, alphanumeric range | PostgreSQL") + def toWhereBetweenQualAlphaPostgres(): Unit = + ForceDialect.postgres() + assertEquals("unit.data->>'city' BETWEEN :citymin AND :citymax", + Field.between("city", "Atlanta", "Chicago", ":city").withQualifier("unit").toWhere, + "Field WHERE clause not generated correctly") + + @Test + @DisplayName("toWhere generates for BETWEEN w/ qualifier | SQLite") + def toWhereBetweenQualSQLite(): Unit = + ForceDialect.sqlite() + assertEquals("my.data->>'age' BETWEEN @agemin AND @agemax", + Field.between("age", 13, 17, "@age").withQualifier("my").toWhere, "Field WHERE clause not generated correctly") + + @Test + @DisplayName("toWhere generates for IN/any, numeric values | PostgreSQL") + def toWhereAnyNumericPostgres(): Unit = + ForceDialect.postgres() + assertEquals("(data->>'even')::numeric IN (:nbr_0, :nbr_1, :nbr_2)", + Field.any("even", List(2, 4, 6).asJava, ":nbr").toWhere, "Field WHERE clause not generated correctly") + + @Test + @DisplayName("toWhere generates for IN/any, alphanumeric values | PostgreSQL") + def toWhereAnyAlphaPostgres(): Unit = + ForceDialect.postgres() + assertEquals("data->>'test' IN (:city_0, :city_1)", + Field.any("test", List("Atlanta", "Chicago").asJava, ":city").toWhere, + "Field WHERE clause not generated correctly") + + @Test + @DisplayName("toWhere generates for IN/any | SQLite") + def toWhereAnySQLite(): Unit = + ForceDialect.sqlite() + assertEquals("data->>'test' IN (:city_0, :city_1)", + Field.any("test", List("Atlanta", "Chicago").asJava, ":city").toWhere, + "Field WHERE clause not generated correctly") + + @Test + @DisplayName("toWhere generates for inArray | PostgreSQL") + def toWhereInArrayPostgres(): Unit = + ForceDialect.postgres() + assertEquals("data->'even' ??| ARRAY[:it_0, :it_1, :it_2, :it_3]", + Field.inArray("even", "tbl", List(2, 4, 6, 8).asJava, ":it").toWhere, + "Field WHERE clause not generated correctly") + + @Test + @DisplayName("toWhere generates for inArray | SQLite") + def toWhereInArraySQLite(): Unit = + ForceDialect.sqlite() + assertEquals("EXISTS (SELECT 1 FROM json_each(tbl.data, '$.test') WHERE value IN (:city_0, :city_1))", + Field.inArray("test", "tbl", List("Atlanta", "Chicago").asJava, ":city").toWhere, + "Field WHERE clause not generated correctly") + + @Test + @DisplayName("toWhere generates for others w/o qualifier | PostgreSQL") + def toWhereOtherNoQualPostgres(): Unit = + ForceDialect.postgres() + assertEquals("data->>'some_field' = :value", Field.equal("some_field", "", ":value").toWhere, + "Field WHERE clause not generated correctly") + + @Test + @DisplayName("toWhere generates for others w/o qualifier | SQLite") + def toWhereOtherNoQualSQLite(): Unit = + ForceDialect.sqlite() + assertEquals("data->>'some_field' = :value", Field.equal("some_field", "", ":value").toWhere, + "Field WHERE clause not generated correctly") + + @Test + @DisplayName("toWhere generates no-parameter w/ qualifier | PostgreSQL") + def toWhereNoParamWithQualPostgres(): Unit = + ForceDialect.postgres() + assertEquals("test.data->>'no_field' IS NOT NULL", Field.exists("no_field").withQualifier("test").toWhere, + "Field WHERE clause not generated correctly") + + @Test + @DisplayName("toWhere generates no-parameter w/ qualifier | SQLite") + def toWhereNoParamWithQualSQLite(): Unit = + ForceDialect.sqlite() + assertEquals("test.data->>'no_field' IS NOT NULL", Field.exists("no_field").withQualifier("test").toWhere, + "Field WHERE clause not generated correctly") + + @Test + @DisplayName("toWhere generates parameter w/ qualifier | PostgreSQL") + def toWhereParamWithQualPostgres(): Unit = + ForceDialect.postgres() + assertEquals("(q.data->>'le_field')::numeric <= :it", + Field.lessOrEqual("le_field", 18, ":it").withQualifier("q").toWhere, "Field WHERE clause not generated correctly") + + @Test + @DisplayName("toWhere generates parameter w/ qualifier | SQLite") + def toWhereParamWithQualSQLite(): Unit = + ForceDialect.sqlite() + assertEquals("q.data->>'le_field' <= :it", + Field.lessOrEqual("le_field", 18, ":it").withQualifier("q").toWhere, + "Field WHERE clause not generated correctly") + + // ~~~ STATIC CONSTRUCTOR TESTS ~~~ + + @Test + @DisplayName("equal constructs a field w/o parameter name") + def equalCtor(): Unit = + val field = Field.equal("Test", 14) + assertEquals("Test", field.getName, "Field name not filled correctly") + assertEquals(Op.EQUAL, field.getComparison.getOp, "Field comparison operation not filled correctly") + assertEquals(14, field.getComparison.getValue, "Field comparison value not filled correctly") + assertNull(field.getParameterName, "The parameter name should have been null") + assertNull(field.getQualifier, "The qualifier should have been null") + + @Test + @DisplayName("equal constructs a field w/ parameter name") + def equalParameterCtor(): Unit = + val field = Field.equal("Test", 14, ":w") + assertEquals("Test", field.getName, "Field name not filled correctly") + assertEquals(Op.EQUAL, field.getComparison.getOp, "Field comparison operation not filled correctly") + assertEquals(14, field.getComparison.getValue, "Field comparison value not filled correctly") + assertEquals(":w", field.getParameterName, "Field parameter name not filled correctly") + assertNull(field.getQualifier, "The qualifier should have been null") + + @Test + @DisplayName("greater constructs a field w/o parameter name") + def greaterCtor(): Unit = + val field = Field.greater("Great", "night") + assertEquals("Great", field.getName, "Field name not filled correctly") + assertEquals(Op.GREATER, field.getComparison.getOp, "Field comparison operation not filled correctly") + assertEquals("night", field.getComparison.getValue, "Field comparison value not filled correctly") + assertNull(field.getParameterName, "The parameter name should have been null") + assertNull(field.getQualifier, "The qualifier should have been null") + + @Test + @DisplayName("greater constructs a field w/ parameter name") + def greaterParameterCtor(): Unit = + val field = Field.greater("Great", "night", ":yeah") + assertEquals("Great", field.getName, "Field name not filled correctly") + assertEquals(Op.GREATER, field.getComparison.getOp, "Field comparison operation not filled correctly") + assertEquals("night", field.getComparison.getValue, "Field comparison value not filled correctly") + assertEquals(":yeah", field.getParameterName, "Field parameter name not filled correctly") + assertNull(field.getQualifier, "The qualifier should have been null") + + @Test + @DisplayName("greaterOrEqual constructs a field w/o parameter name") + def greaterOrEqualCtor(): Unit = + val field = Field.greaterOrEqual("Nice", 88L) + assertEquals("Nice", field.getName, "Field name not filled correctly") + assertEquals(Op.GREATER_OR_EQUAL, field.getComparison.getOp, "Field comparison operation not filled correctly") + assertEquals(88L, field.getComparison.getValue, "Field comparison value not filled correctly") + assertNull(field.getParameterName, "The parameter name should have been null") + assertNull(field.getQualifier, "The qualifier should have been null") + + @Test + @DisplayName("greaterOrEqual constructs a field w/ parameter name") + def greaterOrEqualParameterCtor(): Unit = + val field = Field.greaterOrEqual("Nice", 88L, ":nice") + assertEquals("Nice", field.getName, "Field name not filled correctly") + assertEquals(Op.GREATER_OR_EQUAL, field.getComparison.getOp, "Field comparison operation not filled correctly") + assertEquals(88L, field.getComparison.getValue, "Field comparison value not filled correctly") + assertEquals(":nice", field.getParameterName, "Field parameter name not filled correctly") + assertNull(field.getQualifier, "The qualifier should have been null") + + @Test + @DisplayName("less constructs a field w/o parameter name") + def lessCtor(): Unit = + val field = Field.less("Lesser", "seven") + assertEquals("Lesser", field.getName, "Field name not filled correctly") + assertEquals(Op.LESS, field.getComparison.getOp, "Field comparison operation not filled correctly") + assertEquals("seven", field.getComparison.getValue, "Field comparison value not filled correctly") + assertNull(field.getParameterName, "The parameter name should have been null") + assertNull(field.getQualifier, "The qualifier should have been null") + + @Test + @DisplayName("less constructs a field w/ parameter name") + def lessParameterCtor(): Unit = + val field = Field.less("Lesser", "seven", ":max") + assertEquals("Lesser", field.getName, "Field name not filled correctly") + assertEquals(Op.LESS, field.getComparison.getOp, "Field comparison operation not filled correctly") + assertEquals("seven", field.getComparison.getValue, "Field comparison value not filled correctly") + assertEquals(":max", field.getParameterName, "Field parameter name not filled correctly") + assertNull(field.getQualifier, "The qualifier should have been null") + + @Test + @DisplayName("lessOrEqual constructs a field w/o parameter name") + def lessOrEqualCtor(): Unit = + val field = Field.lessOrEqual("Nobody", "KNOWS") + assertEquals("Nobody", field.getName, "Field name not filled correctly") + assertEquals(Op.LESS_OR_EQUAL, field.getComparison.getOp, "Field comparison operation not filled correctly") + assertEquals("KNOWS", field.getComparison.getValue, "Field comparison value not filled correctly") + assertNull(field.getParameterName, "The parameter name should have been null") + assertNull(field.getQualifier, "The qualifier should have been null") + + @Test + @DisplayName("lessOrEqual constructs a field w/ parameter name") + def lessOrEqualParameterCtor(): Unit = + val field = Field.lessOrEqual("Nobody", "KNOWS", ":nope") + assertEquals("Nobody", field.getName, "Field name not filled correctly") + assertEquals(Op.LESS_OR_EQUAL, field.getComparison.getOp, "Field comparison operation not filled correctly") + assertEquals("KNOWS", field.getComparison.getValue, "Field comparison value not filled correctly") + assertEquals(":nope", field.getParameterName, "Field parameter name not filled correctly") + assertNull(field.getQualifier, "The qualifier should have been null") + + @Test + @DisplayName("notEqual constructs a field w/o parameter name") + def notEqualCtor(): Unit = + val field = Field.notEqual("Park", "here") + assertEquals("Park", field.getName, "Field name not filled correctly") + assertEquals(Op.NOT_EQUAL, field.getComparison.getOp, "Field comparison operation not filled correctly") + assertEquals("here", field.getComparison.getValue, "Field comparison value not filled correctly") + assertNull(field.getParameterName, "The parameter name should have been null") + assertNull(field.getQualifier, "The qualifier should have been null") + + @Test + @DisplayName("notEqual constructs a field w/ parameter name") + def notEqualParameterCtor(): Unit = + val field = Field.notEqual("Park", "here", ":now") + assertEquals("Park", field.getName, "Field name not filled correctly") + assertEquals(Op.NOT_EQUAL, field.getComparison.getOp, "Field comparison operation not filled correctly") + assertEquals("here", field.getComparison.getValue, "Field comparison value not filled correctly") + assertEquals(":now", field.getParameterName, "Field parameter name not filled correctly") + assertNull(field.getQualifier, "The qualifier should have been null") + + @Test + @DisplayName("between constructs a field w/o parameter name") + def betweenCtor(): Unit = + val field = Field.between("Age", 18, 49) + assertEquals("Age", field.getName, "Field name not filled correctly") + assertEquals(Op.BETWEEN, field.getComparison.getOp, "Field comparison operation not filled correctly") + assertEquals(18, field.getComparison.getValue.getFirst, "Field comparison min value not filled correctly") + assertEquals(49, field.getComparison.getValue.getSecond, "Field comparison max value not filled correctly") + assertNull(field.getParameterName, "The parameter name should have been null") + assertNull(field.getQualifier, "The qualifier should have been null") + + @Test + @DisplayName("between constructs a field w/ parameter name") + def betweenParameterCtor(): Unit = + val field = Field.between("Age", 18, 49, ":limit") + assertEquals("Age", field.getName, "Field name not filled correctly") + assertEquals(Op.BETWEEN, field.getComparison.getOp, "Field comparison operation not filled correctly") + assertEquals(18, field.getComparison.getValue.getFirst, "Field comparison min value not filled correctly") + assertEquals(49, field.getComparison.getValue.getSecond, "Field comparison max value not filled correctly") + assertEquals(":limit", field.getParameterName, "Field parameter name not filled correctly") + assertNull(field.getQualifier, "The qualifier should have been null") + + @Test + @DisplayName("any constructs a field w/o parameter name") + def anyCtor(): Unit = + val field = Field.any("Here", List(8, 16, 32).asJava) + assertEquals("Here", field.getName, "Field name not filled correctly") + assertEquals(Op.IN, field.getComparison.getOp, "Field comparison operation not filled correctly") + assertEquals(List(8, 16, 32).asJava, field.getComparison.getValue, "Field comparison value not filled correctly") + assertNull(field.getParameterName, "The parameter name should have been null") + assertNull(field.getQualifier, "The qualifier should have been null") + + @Test + @DisplayName("any constructs a field w/ parameter name") + def anyParameterCtor(): Unit = + val field = Field.any("Here", List(8, 16, 32).asJava, ":list") + assertEquals("Here", field.getName, "Field name not filled correctly") + assertEquals(Op.IN, field.getComparison.getOp, "Field comparison operation not filled correctly") + assertEquals(List(8, 16, 32).asJava, field.getComparison.getValue, "Field comparison value not filled correctly") + assertEquals(":list", field.getParameterName, "Field parameter name not filled correctly") + assertNull(field.getQualifier, "The qualifier should have been null") + + @Test + @DisplayName("inArray constructs a field w/o parameter name") + def inArrayCtor(): Unit = + val field = Field.inArray("ArrayField", "table", List("z").asJava) + assertEquals("ArrayField", field.getName, "Field name not filled correctly") + assertEquals(Op.IN_ARRAY, field.getComparison.getOp, "Field comparison operation not filled correctly") + assertEquals("table", field.getComparison.getValue.getFirst, "Field comparison table not filled correctly") + assertEquals(List("z").asJava, field.getComparison.getValue.getSecond, + "Field comparison values not filled correctly") + assertNull(field.getParameterName, "The parameter name should have been null") + assertNull(field.getQualifier, "The qualifier should have been null") + + @Test + @DisplayName("inArray constructs a field w/ parameter name") + def inArrayParameterCtor(): Unit = + val field = Field.inArray("ArrayField", "table", List("z").asJava, ":a") + assertEquals("ArrayField", field.getName, "Field name not filled correctly") + assertEquals(Op.IN_ARRAY, field.getComparison.getOp, "Field comparison operation not filled correctly") + assertEquals("table", field.getComparison.getValue.getFirst, "Field comparison table not filled correctly") + assertEquals(List("z").asJava, field.getComparison.getValue.getSecond, + "Field comparison values not filled correctly") + assertEquals(":a", field.getParameterName, "Field parameter name not filled correctly") + assertNull(field.getQualifier, "The qualifier should have been null") + + @Test + @DisplayName("exists constructs a field") + def existsCtor(): Unit = + val field = Field.exists("Groovy") + assertEquals("Groovy", field.getName, "Field name not filled correctly") + assertEquals(Op.EXISTS, field.getComparison.getOp, "Field comparison operation not filled correctly") + assertEquals("", field.getComparison.getValue, "Field comparison value not filled correctly") + assertNull(field.getParameterName, "The parameter name should have been null") + assertNull(field.getQualifier, "The qualifier should have been null") + + @Test + @DisplayName("notExists constructs a field") + def notExistsCtor(): Unit = + val field = Field.notExists("Groovy") + assertEquals("Groovy", field.getName, "Field name not filled correctly") + assertEquals(Op.NOT_EXISTS, field.getComparison.getOp, "Field comparison operation not filled correctly") + assertEquals("", field.getComparison.getValue, "Field comparison value not filled correctly") + assertNull(field.getParameterName, "The parameter name should have been null") + assertNull(field.getQualifier, "The qualifier should have been null") + + @Test + @DisplayName("named constructs a field") + def namedCtor(): Unit = + val field = Field.named("Tacos") + assertEquals("Tacos", field.getName, "Field name not filled correctly") + assertEquals(Op.EQUAL, field.getComparison.getOp, "Field comparison operation not filled correctly") + assertEquals("", field.getComparison.getValue, "Field comparison value not filled correctly") + assertNull(field.getParameterName, "The parameter name should have been null") + assertNull(field.getQualifier, "The qualifier should have been null") + + @Test + @DisplayName("static constructors fail for invalid parameter name") + def staticCtorsFailOnParamName(): Unit = + assertThrows(classOf[DocumentException], () => Field.equal("a", "b", "that ain't it, Jack...")) + + @Test + @DisplayName("nameToPath creates a simple PostgreSQL SQL name") + def nameToPathPostgresSimpleSQL(): Unit = + assertEquals("data->>'Simple'", Field.nameToPath("Simple", Dialect.POSTGRESQL, FieldFormat.SQL), + "Path not constructed correctly") + + @Test + @DisplayName("nameToPath creates a simple SQLite SQL name") + def nameToPathSQLiteSimpleSQL(): Unit = + assertEquals("data->>'Simple'", Field.nameToPath("Simple", Dialect.SQLITE, FieldFormat.SQL), + "Path not constructed correctly") + + @Test + @DisplayName("nameToPath creates a nested PostgreSQL SQL name") + def nameToPathPostgresNestedSQL(): Unit = + assertEquals("data#>>'{A,Long,Path,to,the,Property}'", + Field.nameToPath("A.Long.Path.to.the.Property", Dialect.POSTGRESQL, FieldFormat.SQL), + "Path not constructed correctly") + + @Test + @DisplayName("nameToPath creates a nested SQLite SQL name") + def nameToPathSQLiteNestedSQL(): Unit = + assertEquals("data->'A'->'Long'->'Path'->'to'->'the'->>'Property'", + Field.nameToPath("A.Long.Path.to.the.Property", Dialect.SQLITE, FieldFormat.SQL), + "Path not constructed correctly") + + @Test + @DisplayName("nameToPath creates a simple PostgreSQL JSON name") + def nameToPathPostgresSimpleJSON(): Unit = + assertEquals("data->'Simple'", Field.nameToPath("Simple", Dialect.POSTGRESQL, FieldFormat.JSON), + "Path not constructed correctly") + + @Test + @DisplayName("nameToPath creates a simple SQLite JSON name") + def nameToPathSQLiteSimpleJSON(): Unit = + assertEquals("data->'Simple'", Field.nameToPath("Simple", Dialect.SQLITE, FieldFormat.JSON), + "Path not constructed correctly") + + @Test + @DisplayName("nameToPath creates a nested PostgreSQL JSON name") + def nameToPathPostgresNestedJSON(): Unit = + assertEquals("data#>'{A,Long,Path,to,the,Property}'", + Field.nameToPath("A.Long.Path.to.the.Property", Dialect.POSTGRESQL, FieldFormat.JSON), + "Path not constructed correctly") + + @Test + @DisplayName("nameToPath creates a nested SQLite JSON name") + def nameToPathSQLiteNestedJSON(): Unit = + assertEquals("data->'A'->'Long'->'Path'->'to'->'the'->'Property'", + Field.nameToPath("A.Long.Path.to.the.Property", Dialect.SQLITE, FieldFormat.JSON), + "Path not constructed correctly") +} diff --git a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/OpSpec.scala b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/OpSpec.scala deleted file mode 100644 index aea074f..0000000 --- a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/OpSpec.scala +++ /dev/null @@ -1,44 +0,0 @@ -package solutions.bitbadger.documents.scala - -import org.scalatest.funspec.AnyFunSpec -import org.scalatest.matchers.should.Matchers -import solutions.bitbadger.documents.Op - -class OpSpec extends AnyFunSpec with Matchers { - - describe("sql") { - it("returns = for EQUAL") { - Op.EQUAL.getSql shouldEqual "=" - } - it("returns > for GREATER") { - Op.GREATER.getSql shouldEqual ">" - } - it("returns >= for GREATER_OR_EQUAL") { - Op.GREATER_OR_EQUAL.getSql shouldEqual ">=" - } - it("returns < for LESS") { - Op.LESS.getSql shouldEqual "<" - } - it("returns <= for LESS_OR_EQUAL") { - Op.LESS_OR_EQUAL.getSql shouldEqual "<=" - } - it("returns <> for NOT_EQUAL") { - Op.NOT_EQUAL.getSql shouldEqual "<>" - } - it("returns BETWEEN for BETWEEN") { - Op.BETWEEN.getSql shouldEqual "BETWEEN" - } - it("returns IN for IN") { - Op.IN.getSql shouldEqual "IN" - } - it("returns ??| for IN_ARRAY") { - Op.IN_ARRAY.getSql shouldEqual "??|" - } - it("returns IS NOT NULL for EXISTS") { - Op.EXISTS.getSql shouldEqual "IS NOT NULL" - } - it("returns IS NULL for NOT_EXISTS") { - Op.NOT_EXISTS.getSql shouldEqual "IS NULL" - } - } -} diff --git a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/OpTest.scala b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/OpTest.scala new file mode 100644 index 0000000..62cb683 --- /dev/null +++ b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/OpTest.scala @@ -0,0 +1,64 @@ +package solutions.bitbadger.documents.scala + +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.{DisplayName, Test} +import solutions.bitbadger.documents.Op + +@DisplayName("JVM | Kotlin | Op") +class OpTest { + + @Test + @DisplayName("EQUAL uses proper SQL") + def equalSQL(): Unit = + assertEquals("=", Op.EQUAL.getSql, "The SQL for equal is incorrect") + + @Test + @DisplayName("GREATER uses proper SQL") + def greaterSQL(): Unit = + assertEquals(">", Op.GREATER.getSql, "The SQL for greater is incorrect") + + @Test + @DisplayName("GREATER_OR_EQUAL uses proper SQL") + def greaterOrEqualSQL(): Unit = + assertEquals(">=", Op.GREATER_OR_EQUAL.getSql, "The SQL for greater-or-equal is incorrect") + + @Test + @DisplayName("LESS uses proper SQL") + def lessSQL(): Unit = + assertEquals("<", Op.LESS.getSql, "The SQL for less is incorrect") + + @Test + @DisplayName("LESS_OR_EQUAL uses proper SQL") + def lessOrEqualSQL(): Unit = + assertEquals("<=", Op.LESS_OR_EQUAL.getSql, "The SQL for less-or-equal is incorrect") + + @Test + @DisplayName("NOT_EQUAL uses proper SQL") + def notEqualSQL(): Unit = + assertEquals("<>", Op.NOT_EQUAL.getSql, "The SQL for not-equal is incorrect") + + @Test + @DisplayName("BETWEEN uses proper SQL") + def betweenSQL(): Unit = + assertEquals("BETWEEN", Op.BETWEEN.getSql, "The SQL for between is incorrect") + + @Test + @DisplayName("IN uses proper SQL") + def inSQL(): Unit = + assertEquals("IN", Op.IN.getSql, "The SQL for in is incorrect") + + @Test + @DisplayName("IN_ARRAY uses proper SQL") + def inArraySQL(): Unit = + assertEquals("??|", Op.IN_ARRAY.getSql, "The SQL for in-array is incorrect") + + @Test + @DisplayName("EXISTS uses proper SQL") + def existsSQL(): Unit = + assertEquals("IS NOT NULL", Op.EXISTS.getSql, "The SQL for exists is incorrect") + + @Test + @DisplayName("NOT_EXISTS uses proper SQL") + def notExistsSQL(): Unit = + assertEquals("IS NULL", Op.NOT_EXISTS.getSql, "The SQL for not-exists is incorrect") +} diff --git a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/ParameterNameSpec.scala b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/ParameterNameSpec.scala deleted file mode 100644 index 35188e8..0000000 --- a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/ParameterNameSpec.scala +++ /dev/null @@ -1,23 +0,0 @@ -package solutions.bitbadger.documents.scala - -import org.scalatest.funspec.AnyFunSpec -import org.scalatest.matchers.should.Matchers -import solutions.bitbadger.documents.ParameterName - -class ParameterNameSpec extends AnyFunSpec with Matchers { - - describe("derive") { - it("works when given existing names") { - val names = new ParameterName() - names.derive(":taco") shouldEqual ":taco" - names.derive(null) shouldEqual ":field0" - } - it("works when given all anonymous fields") { - val names = new ParameterName() - names.derive(null) shouldEqual ":field0" - names.derive(null) shouldEqual ":field1" - names.derive(null) shouldEqual ":field2" - names.derive(null) shouldEqual ":field3" - } - } -} diff --git a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/ParameterNameTest.scala b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/ParameterNameTest.scala new file mode 100644 index 0000000..d58c4fa --- /dev/null +++ b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/ParameterNameTest.scala @@ -0,0 +1,25 @@ +package solutions.bitbadger.documents.scala + +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.{DisplayName, Test} +import solutions.bitbadger.documents.ParameterName + +@DisplayName("JVM | Scala | ParameterName") +class ParameterNameTest { + + @Test + @DisplayName("derive works when given existing names") + def withExisting(): Unit = + val names = ParameterName() + assertEquals(":taco", names.derive(":taco"), "Name should have been :taco") + assertEquals(":field0", names.derive(null), "Counter should not have advanced for named field") + + @Test + @DisplayName("derive works when given all anonymous fields") + def allAnonymous(): Unit = + val names = ParameterName() + assertEquals(":field0", names.derive(null), "Anonymous field name should have been returned") + assertEquals(":field1", names.derive(null), "Counter should have advanced from previous call") + assertEquals(":field2", names.derive(null), "Counter should have advanced from previous call") + assertEquals(":field3", names.derive(null), "Counter should have advanced from previous call") +} diff --git a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/ParameterSpec.scala b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/ParameterSpec.scala deleted file mode 100644 index 3e393b7..0000000 --- a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/ParameterSpec.scala +++ /dev/null @@ -1,26 +0,0 @@ -package solutions.bitbadger.documents.scala - -import org.scalatest.funspec.AnyFunSpec -import org.scalatest.matchers.should.Matchers -import solutions.bitbadger.documents.{DocumentException, Parameter, ParameterType} - -class ParameterSpec extends AnyFunSpec with Matchers { - - describe("constructor") { - it("succeeds with colon-prefixed name") { - val p = new Parameter(":test", ParameterType.STRING, "ABC") - p.getName shouldEqual ":test" - p.getType shouldEqual ParameterType.STRING - p.getValue shouldEqual "ABC" - } - it("succeeds with at-sign-prefixed name") { - val p = Parameter("@yo", ParameterType.NUMBER, null) - p.getName shouldEqual "@yo" - p.getType shouldEqual ParameterType.NUMBER - p.getValue should be (null) - } - it("fails with incorrect prefix") { - a [DocumentException] should be thrownBy Parameter("it", ParameterType.JSON, "") - } - } -} diff --git a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/ParameterTest.scala b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/ParameterTest.scala new file mode 100644 index 0000000..0c3c689 --- /dev/null +++ b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/ParameterTest.scala @@ -0,0 +1,30 @@ +package solutions.bitbadger.documents.scala + +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.{DisplayName, Test} +import solutions.bitbadger.documents.{DocumentException, Parameter, ParameterType} + +@DisplayName("JVM | Scala | Parameter") +class ParameterTest { + + @Test + @DisplayName("Construction with colon-prefixed name") + def ctorWithColon(): Unit = + val p = Parameter(":test", ParameterType.STRING, "ABC") + assertEquals(":test", p.getName, "Parameter name was incorrect") + assertEquals(ParameterType.STRING, p.getType, "Parameter type was incorrect") + assertEquals("ABC", p.getValue, "Parameter value was incorrect") + + @Test + @DisplayName("Construction with at-sign-prefixed name") + def ctorWithAtSign(): Unit = + val p = Parameter("@yo", ParameterType.NUMBER, null) + assertEquals("@yo", p.getName, "Parameter name was incorrect") + assertEquals(ParameterType.NUMBER, p.getType, "Parameter type was incorrect") + assertNull(p.getValue, "Parameter value was incorrect") + + @Test + @DisplayName("Construction fails with incorrect prefix") + def ctorFailsForPrefix(): Unit = + assertThrows(classOf[DocumentException], () => Parameter("it", ParameterType.JSON, "")) +} diff --git a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/CountQuerySpec.scala b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/CountQuerySpec.scala deleted file mode 100644 index 6a24e4c..0000000 --- a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/CountQuerySpec.scala +++ /dev/null @@ -1,56 +0,0 @@ -package solutions.bitbadger.documents.scala.query - -import org.scalatest.funspec.AnyFunSpec -import org.scalatest.matchers.should.Matchers -import solutions.bitbadger.documents.{DocumentException, Field} -import solutions.bitbadger.documents.query.CountQuery -import solutions.bitbadger.documents.scala.ClearConfiguration -import solutions.bitbadger.documents.support.ForceDialect -import solutions.bitbadger.documents.support.TypesKt.TEST_TABLE - -import scala.jdk.CollectionConverters.* - -class CountQuerySpec extends AnyFunSpec with ClearConfiguration with Matchers { - - describe("all") { - it("generates correctly") { - CountQuery.all(TEST_TABLE) shouldEqual s"SELECT COUNT(*) AS it FROM $TEST_TABLE" - } - } - - describe("byFields") { - it("generates correctly | PostgreSQL") { - ForceDialect.postgres() - CountQuery.byFields(TEST_TABLE, List(Field.equal("test", "", ":field0")).asJava) shouldEqual - s"SELECT COUNT(*) AS it FROM $TEST_TABLE WHERE data->>'test' = :field0" - } - it("generates correctly | SQLite") { - ForceDialect.sqlite() - CountQuery.byFields(TEST_TABLE, List(Field.equal("test", "", ":field0")).asJava) shouldEqual - s"SELECT COUNT(*) AS it FROM $TEST_TABLE WHERE data->>'test' = :field0" - } - } - - describe("byContains") { - it("generates correctly | PostgreSQL") { - ForceDialect.postgres() - CountQuery.byContains(TEST_TABLE) shouldEqual s"SELECT COUNT(*) AS it FROM $TEST_TABLE WHERE data @> :criteria" - } - it("fails | SQLite") { - ForceDialect.sqlite() - a [DocumentException] should be thrownBy CountQuery.byContains(TEST_TABLE) - } - } - - describe("byJsonPath") { - it("generates correctly | PostgreSQL") { - ForceDialect.postgres() - CountQuery.byJsonPath(TEST_TABLE) shouldEqual - s"SELECT COUNT(*) AS it FROM $TEST_TABLE WHERE jsonb_path_exists(data, :path::jsonpath)" - } - it("fails | SQLite") { - ForceDialect.sqlite() - a [DocumentException] should be thrownBy CountQuery.byJsonPath(TEST_TABLE) - } - } -} diff --git a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/CountQueryTest.scala b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/CountQueryTest.scala new file mode 100644 index 0000000..1b601b0 --- /dev/null +++ b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/CountQueryTest.scala @@ -0,0 +1,69 @@ +package solutions.bitbadger.documents.scala.query + +import org.junit.jupiter.api.{AfterEach, DisplayName, Test} +import org.junit.jupiter.api.Assertions.* +import solutions.bitbadger.documents.{DocumentException, Field} +import solutions.bitbadger.documents.query.CountQuery +import solutions.bitbadger.documents.support.ForceDialect +import solutions.bitbadger.documents.support.TypesKt.TEST_TABLE + +import scala.jdk.CollectionConverters.* + +@DisplayName("JVM | Scala | Query | CountQuery") +class CountQueryTest { + + /** + * Clear the connection string (resets Dialect) + */ + @AfterEach + def cleanUp(): Unit = + ForceDialect.none() + + @Test + @DisplayName("all generates correctly") + def all(): Unit = + assertEquals(s"SELECT COUNT(*) AS it FROM $TEST_TABLE", CountQuery.all(TEST_TABLE), + "Count query not constructed correctly") + + @Test + @DisplayName("byFields generates correctly | PostgreSQL") + def byFieldsPostgres(): Unit = + ForceDialect.postgres() + assertEquals(s"SELECT COUNT(*) AS it FROM $TEST_TABLE WHERE data->>'test' = :field0", + CountQuery.byFields(TEST_TABLE, List(Field.equal("test", "", ":field0")).asJava), + "Count query not constructed correctly") + + @Test + @DisplayName("byFields generates correctly | SQLite") + def byFieldsSQLite(): Unit = + ForceDialect.sqlite() + assertEquals(s"SELECT COUNT(*) AS it FROM $TEST_TABLE WHERE data->>'test' = :field0", + CountQuery.byFields(TEST_TABLE, List(Field.equal("test", "", ":field0")).asJava), + "Count query not constructed correctly") + + @Test + @DisplayName("byContains generates correctly | PostgreSQL") + def byContainsPostgres(): Unit = + ForceDialect.postgres() + assertEquals(s"SELECT COUNT(*) AS it FROM $TEST_TABLE WHERE data @> :criteria", CountQuery.byContains(TEST_TABLE), + "Count query not constructed correctly") + + @Test + @DisplayName("byContains fails | SQLite") + def byContainsSQLite(): Unit = + ForceDialect.sqlite() + assertThrows(classOf[DocumentException], () => CountQuery.byContains(TEST_TABLE)) + + @Test + @DisplayName("byJsonPath generates correctly | PostgreSQL") + def byJsonPathPostgres(): Unit = + ForceDialect.postgres() + assertEquals(s"SELECT COUNT(*) AS it FROM $TEST_TABLE WHERE jsonb_path_exists(data, :path::jsonpath)", + CountQuery.byJsonPath(TEST_TABLE), "Count query not constructed correctly") + + @Test + @DisplayName("byJsonPath fails | SQLite") + def byJsonPathSQLite(): Unit = + ForceDialect.sqlite() + assertThrows(classOf[DocumentException], () => CountQuery.byJsonPath(TEST_TABLE)) +} diff --git a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/DefinitionQuerySpec.scala b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/DefinitionQuerySpec.scala deleted file mode 100644 index dbf6aad..0000000 --- a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/DefinitionQuerySpec.scala +++ /dev/null @@ -1,81 +0,0 @@ -package solutions.bitbadger.documents.scala.query - -import org.scalatest.funspec.AnyFunSpec -import org.scalatest.matchers.should.Matchers -import solutions.bitbadger.documents.{Dialect, DocumentException, DocumentIndex} -import solutions.bitbadger.documents.query.DefinitionQuery -import solutions.bitbadger.documents.scala.ClearConfiguration -import solutions.bitbadger.documents.support.ForceDialect -import solutions.bitbadger.documents.support.TypesKt.TEST_TABLE - -import scala.jdk.CollectionConverters.* - -class DefinitionQuerySpec extends AnyFunSpec with ClearConfiguration with Matchers { - - describe("ensureTableFor") { - it("generates correctly") { - DefinitionQuery.ensureTableFor("my.table", "JSONB") shouldEqual - "CREATE TABLE IF NOT EXISTS my.table (data JSONB NOT NULL)" - } - } - - describe("ensureTable") { - it("generates correctly | PostgreSQL") { - ForceDialect.postgres() - DefinitionQuery.ensureTable(TEST_TABLE) shouldEqual - s"CREATE TABLE IF NOT EXISTS $TEST_TABLE (data JSONB NOT NULL)" - } - it("generates correctly | SQLite") { - ForceDialect.sqlite() - DefinitionQuery.ensureTable(TEST_TABLE) shouldEqual - s"CREATE TABLE IF NOT EXISTS $TEST_TABLE (data TEXT NOT NULL)" - } - it("fails when no dialect is set") { - a [DocumentException] should be thrownBy DefinitionQuery.ensureTable(TEST_TABLE) - } - } - - describe("ensureKey") { - it("generates correctly with schema") { - DefinitionQuery.ensureKey("test.table", Dialect.POSTGRESQL) shouldEqual - "CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON test.table ((data->>'id'))" - } - it("generates correctly without schema") { - DefinitionQuery.ensureKey(TEST_TABLE, Dialect.SQLITE) shouldEqual - s"CREATE UNIQUE INDEX IF NOT EXISTS idx_${TEST_TABLE}_key ON $TEST_TABLE ((data->>'id'))" - } - } - - describe("ensureIndexOn") { - it("generates multiple fields and directions") { - DefinitionQuery.ensureIndexOn("test.table", "gibberish", List("taco", "guac DESC", "salsa ASC").asJava, - Dialect.POSTGRESQL) shouldEqual - "CREATE INDEX IF NOT EXISTS idx_table_gibberish ON test.table ((data->>'taco'), (data->>'guac') DESC, (data->>'salsa') ASC)" - } - it("generates nested field | PostgreSQL") { - DefinitionQuery.ensureIndexOn(TEST_TABLE, "nest", List("a.b.c").asJava, Dialect.POSTGRESQL) shouldEqual - s"CREATE INDEX IF NOT EXISTS idx_${TEST_TABLE}_nest ON $TEST_TABLE ((data#>>'{a,b,c}'))" - } - it("generates nested field | SQLite") { - DefinitionQuery.ensureIndexOn(TEST_TABLE, "nest", List("a.b.c").asJava, Dialect.SQLITE) shouldEqual - s"CREATE INDEX IF NOT EXISTS idx_${TEST_TABLE}_nest ON $TEST_TABLE ((data->'a'->'b'->>'c'))" - } - } - - describe("ensureDocumentOn") { - it("generates Full | PostgreSQL") { - ForceDialect.postgres() - DefinitionQuery.ensureDocumentIndexOn(TEST_TABLE, DocumentIndex.FULL) shouldEqual - s"CREATE INDEX IF NOT EXISTS idx_${TEST_TABLE}_document ON $TEST_TABLE USING GIN (data)" - } - it("generates Optimized | PostgreSQL") { - ForceDialect.postgres() - DefinitionQuery.ensureDocumentIndexOn(TEST_TABLE, DocumentIndex.OPTIMIZED) shouldEqual - s"CREATE INDEX IF NOT EXISTS idx_${TEST_TABLE}_document ON $TEST_TABLE USING GIN (data jsonb_path_ops)" - } - it("fails | SQLite") { - ForceDialect.sqlite() - a [DocumentException] should be thrownBy DefinitionQuery.ensureDocumentIndexOn(TEST_TABLE, DocumentIndex.FULL) - } - } -} diff --git a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/DefinitionQueryTest.scala b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/DefinitionQueryTest.scala new file mode 100644 index 0000000..d963970 --- /dev/null +++ b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/DefinitionQueryTest.scala @@ -0,0 +1,107 @@ +package solutions.bitbadger.documents.scala.query + +import org.junit.jupiter.api.{AfterEach, DisplayName, Test} +import org.junit.jupiter.api.Assertions.* +import solutions.bitbadger.documents.{Dialect, DocumentException, DocumentIndex} +import solutions.bitbadger.documents.query.DefinitionQuery +import solutions.bitbadger.documents.support.ForceDialect +import solutions.bitbadger.documents.support.TypesKt.TEST_TABLE + +import scala.jdk.CollectionConverters.* + +@DisplayName("JVM | Scala | Query | DefinitionQuery") +class DefinitionQueryTest { + + /** + * Clear the connection string (resets Dialect) + */ + @AfterEach + def cleanUp(): Unit = + ForceDialect.none() + + @Test + @DisplayName("ensureTableFor generates correctly") + def ensureTableFor(): Unit = + assertEquals("CREATE TABLE IF NOT EXISTS my.table (data JSONB NOT NULL)", + DefinitionQuery.ensureTableFor("my.table", "JSONB"), "CREATE TABLE statement not constructed correctly") + + @Test + @DisplayName("ensureTable generates correctly | PostgreSQL") + def ensureTablePostgres(): Unit = + ForceDialect.postgres() + assertEquals(s"CREATE TABLE IF NOT EXISTS $TEST_TABLE (data JSONB NOT NULL)", + DefinitionQuery.ensureTable(TEST_TABLE)) + + @Test + @DisplayName("ensureTable generates correctly | SQLite") + def ensureTableSQLite(): Unit = + ForceDialect.sqlite() + assertEquals(s"CREATE TABLE IF NOT EXISTS $TEST_TABLE (data TEXT NOT NULL)", + DefinitionQuery.ensureTable(TEST_TABLE)) + + @Test + @DisplayName("ensureTable fails when no dialect is set") + def ensureTableFailsUnknown(): Unit = + assertThrows(classOf[DocumentException], () => DefinitionQuery.ensureTable(TEST_TABLE)) + + @Test + @DisplayName("ensureKey generates correctly with schema") + def ensureKeyWithSchema(): Unit = + assertEquals("CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON test.table ((data->>'id'))", + DefinitionQuery.ensureKey("test.table", Dialect.POSTGRESQL), + "CREATE INDEX for key statement with schema not constructed correctly") + + @Test + @DisplayName("ensureKey generates correctly without schema") + def ensureKeyWithoutSchema(): Unit = + assertEquals(s"CREATE UNIQUE INDEX IF NOT EXISTS idx_${TEST_TABLE}_key ON $TEST_TABLE ((data->>'id'))", + DefinitionQuery.ensureKey(TEST_TABLE, Dialect.SQLITE), + "CREATE INDEX for key statement without schema not constructed correctly") + + @Test + @DisplayName("ensureIndexOn generates multiple fields and directions") + def ensureIndexOnMultipleFields(): Unit = + assertEquals("CREATE INDEX IF NOT EXISTS idx_table_gibberish ON test.table " + + "((data->>'taco'), (data->>'guac') DESC, (data->>'salsa') ASC)", + DefinitionQuery.ensureIndexOn("test.table", "gibberish", List("taco", "guac DESC", "salsa ASC").asJava, + Dialect.POSTGRESQL), + "CREATE INDEX for multiple field statement not constructed correctly") + + @Test + @DisplayName("ensureIndexOn generates nested field | PostgreSQL") + def ensureIndexOnNestedPostgres(): Unit = + assertEquals(s"CREATE INDEX IF NOT EXISTS idx_${TEST_TABLE}_nest ON $TEST_TABLE ((data#>>'{a,b,c}'))", + DefinitionQuery.ensureIndexOn(TEST_TABLE, "nest", List("a.b.c").asJava, Dialect.POSTGRESQL), + "CREATE INDEX for nested PostgreSQL field incorrect") + + @Test + @DisplayName("ensureIndexOn generates nested field | SQLite") + def ensureIndexOnNestedSQLite(): Unit = + assertEquals(s"CREATE INDEX IF NOT EXISTS idx_${TEST_TABLE}_nest ON $TEST_TABLE ((data->'a'->'b'->>'c'))", + DefinitionQuery.ensureIndexOn(TEST_TABLE, "nest", List("a.b.c").asJava, Dialect.SQLITE), + "CREATE INDEX for nested SQLite field incorrect") + + @Test + @DisplayName("ensureDocumentIndexOn generates Full | PostgreSQL") + def ensureDocumentIndexOnFullPostgres(): Unit = + ForceDialect.postgres() + assertEquals(s"CREATE INDEX IF NOT EXISTS idx_${TEST_TABLE}_document ON $TEST_TABLE USING GIN (data)", + DefinitionQuery.ensureDocumentIndexOn(TEST_TABLE, DocumentIndex.FULL), + "CREATE INDEX for full document index incorrect") + + @Test + @DisplayName("ensureDocumentIndexOn generates Optimized | PostgreSQL") + def ensureDocumentIndexOnOptimizedPostgres(): Unit = + ForceDialect.postgres() + assertEquals( + s"CREATE INDEX IF NOT EXISTS idx_${TEST_TABLE}_document ON $TEST_TABLE USING GIN (data jsonb_path_ops)", + DefinitionQuery.ensureDocumentIndexOn(TEST_TABLE, DocumentIndex.OPTIMIZED), + "CREATE INDEX for optimized document index incorrect") + + @Test + @DisplayName("ensureDocumentIndexOn fails | SQLite") + def ensureDocumentIndexOnFailsSQLite(): Unit = + ForceDialect.sqlite() + assertThrows(classOf[DocumentException], + () => DefinitionQuery.ensureDocumentIndexOn(TEST_TABLE, DocumentIndex.FULL)) +} diff --git a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/DeleteQuerySpec.scala b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/DeleteQuerySpec.scala deleted file mode 100644 index 728d1cd..0000000 --- a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/DeleteQuerySpec.scala +++ /dev/null @@ -1,61 +0,0 @@ -package solutions.bitbadger.documents.scala.query - -import org.scalatest.funspec.AnyFunSpec -import org.scalatest.matchers.should.Matchers -import solutions.bitbadger.documents.{DocumentException, Field} -import solutions.bitbadger.documents.query.DeleteQuery -import solutions.bitbadger.documents.scala.ClearConfiguration -import solutions.bitbadger.documents.support.ForceDialect -import solutions.bitbadger.documents.support.TypesKt.TEST_TABLE - -import scala.jdk.CollectionConverters.* - -class DeleteQuerySpec extends AnyFunSpec with ClearConfiguration with Matchers { - - describe("byId") { - it("generates correctly | PostgreSQL") { - ForceDialect.postgres() - DeleteQuery.byId(TEST_TABLE) shouldEqual s"DELETE FROM $TEST_TABLE WHERE data->>'id' = :id" - } - it("generates correctly | SQLite") { - ForceDialect.sqlite() - DeleteQuery.byId(TEST_TABLE) shouldEqual s"DELETE FROM $TEST_TABLE WHERE data->>'id' = :id" - } - } - - describe("byFields") { - it("generates correctly | PostgreSQL") { - ForceDialect.postgres() - DeleteQuery.byFields(TEST_TABLE, List(Field.equal("a", "", ":b")).asJava) shouldEqual - s"DELETE FROM $TEST_TABLE WHERE data->>'a' = :b" - } - it("generates correctly | SQLite") { - ForceDialect.sqlite() - DeleteQuery.byFields(TEST_TABLE, List(Field.equal("a", "", ":b")).asJava) shouldEqual - s"DELETE FROM $TEST_TABLE WHERE data->>'a' = :b" - } - } - - describe("byContains") { - it("generates correctly | PostgreSQL") { - ForceDialect.postgres() - DeleteQuery.byContains(TEST_TABLE) shouldEqual s"DELETE FROM $TEST_TABLE WHERE data @> :criteria" - } - it("fails | SQLite") { - ForceDialect.sqlite() - a [DocumentException] should be thrownBy DeleteQuery.byContains(TEST_TABLE) - } - } - - describe("byJsonPath") { - it("generates correctly | PostgreSQL") { - ForceDialect.postgres() - DeleteQuery.byJsonPath(TEST_TABLE) shouldEqual - s"DELETE FROM $TEST_TABLE WHERE jsonb_path_exists(data, :path::jsonpath)" - } - it("fails | SQLite") { - ForceDialect.sqlite() - a [DocumentException] should be thrownBy DeleteQuery.byJsonPath(TEST_TABLE) - } - } -} diff --git a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/DeleteQueryTest.scala b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/DeleteQueryTest.scala new file mode 100644 index 0000000..1106867 --- /dev/null +++ b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/DeleteQueryTest.scala @@ -0,0 +1,77 @@ +package solutions.bitbadger.documents.scala.query + +import org.junit.jupiter.api.{AfterEach, DisplayName, Test} +import org.junit.jupiter.api.Assertions.* +import solutions.bitbadger.documents.{DocumentException, Field} +import solutions.bitbadger.documents.query.DeleteQuery +import solutions.bitbadger.documents.support.ForceDialect +import solutions.bitbadger.documents.support.TypesKt.TEST_TABLE + +import scala.jdk.CollectionConverters.* + +@DisplayName("JVM | Scala | Query | DeleteQuery") +class DeleteQueryTest { + + /** + * Clear the connection string (resets Dialect) + */ + @AfterEach + def cleanUp(): Unit = + ForceDialect.none() + + @Test + @DisplayName("byId generates correctly | PostgreSQL") + def byIdPostgres(): Unit = + ForceDialect.postgres() + assertEquals(s"DELETE FROM $TEST_TABLE WHERE data->>'id' = :id", DeleteQuery.byId(TEST_TABLE), + "Delete query not constructed correctly") + + @Test + @DisplayName("byId generates correctly | SQLite") + def byIdSQLite(): Unit = + ForceDialect.sqlite() + assertEquals(s"DELETE FROM $TEST_TABLE WHERE data->>'id' = :id", DeleteQuery.byId(TEST_TABLE), + "Delete query not constructed correctly") + + @Test + @DisplayName("byFields generates correctly | PostgreSQL") + def byFieldsPostgres(): Unit = + ForceDialect.postgres() + assertEquals(s"DELETE FROM $TEST_TABLE WHERE data->>'a' = :b", + DeleteQuery.byFields(TEST_TABLE, List(Field.equal("a", "", ":b")).asJava), + "Delete query not constructed correctly") + + @Test + @DisplayName("byFields generates correctly | SQLite") + def byFieldsSQLite(): Unit = + ForceDialect.sqlite() + assertEquals(s"DELETE FROM $TEST_TABLE WHERE data->>'a' = :b", + DeleteQuery.byFields(TEST_TABLE, List(Field.equal("a", "", ":b")).asJava), + "Delete query not constructed correctly") + + @Test + @DisplayName("byContains generates correctly | PostgreSQL") + def byContainsPostgres(): Unit = + ForceDialect.postgres() + assertEquals(s"DELETE FROM $TEST_TABLE WHERE data @> :criteria", DeleteQuery.byContains(TEST_TABLE), + "Delete query not constructed correctly") + + @Test + @DisplayName("byContains fails | SQLite") + def byContainsSQLite(): Unit = + ForceDialect.sqlite() + assertThrows(classOf[DocumentException], () => DeleteQuery.byContains(TEST_TABLE)) + + @Test + @DisplayName("byJsonPath generates correctly | PostgreSQL") + def byJsonPathPostgres(): Unit = + ForceDialect.postgres() + assertEquals(s"DELETE FROM $TEST_TABLE WHERE jsonb_path_exists(data, :path::jsonpath)", + DeleteQuery.byJsonPath(TEST_TABLE), "Delete query not constructed correctly") + + @Test + @DisplayName("byJsonPath fails | SQLite") + def byJsonPathSQLite(): Unit = + ForceDialect.sqlite() + assertThrows(classOf[DocumentException], () => DeleteQuery.byJsonPath(TEST_TABLE)) +} diff --git a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/DocumentQuerySpec.scala b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/DocumentQuerySpec.scala deleted file mode 100644 index adbef90..0000000 --- a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/DocumentQuerySpec.scala +++ /dev/null @@ -1,85 +0,0 @@ -package solutions.bitbadger.documents.scala.query - -import org.scalatest.funspec.AnyFunSpec -import org.scalatest.matchers.should.Matchers -import solutions.bitbadger.documents.{AutoId, Configuration, DocumentException} -import solutions.bitbadger.documents.query.DocumentQuery -import solutions.bitbadger.documents.scala.ClearConfiguration -import solutions.bitbadger.documents.support.ForceDialect -import solutions.bitbadger.documents.support.TypesKt.TEST_TABLE - -class DocumentQuerySpec extends AnyFunSpec with ClearConfiguration with Matchers { - - describe("insert") { - it("generates no auto ID | PostgreSQL") { - ForceDialect.postgres() - DocumentQuery.insert(TEST_TABLE) shouldEqual s"INSERT INTO $TEST_TABLE VALUES (:data)" - } - it("generates no auto ID | SQLite") { - ForceDialect.sqlite() - DocumentQuery.insert(TEST_TABLE) shouldEqual s"INSERT INTO $TEST_TABLE VALUES (:data)" - } - it("generates auto number | PostgreSQL") { - ForceDialect.postgres() - DocumentQuery.insert(TEST_TABLE, AutoId.NUMBER) shouldEqual - s"INSERT INTO $TEST_TABLE VALUES (:data::jsonb || ('{\"id\":' " + - s"|| (SELECT COALESCE(MAX((data->>'id')::numeric), 0) + 1 FROM $TEST_TABLE) || '}')::jsonb)" - } - it("generates auto number | SQLite") { - ForceDialect.sqlite() - DocumentQuery.insert(TEST_TABLE, AutoId.NUMBER) shouldEqual - s"INSERT INTO $TEST_TABLE VALUES (json_set(:data, '$$.id', " + - s"(SELECT coalesce(max(data->>'id'), 0) + 1 FROM $TEST_TABLE)))" - } - it("generates auto UUID | PostgreSQL") { - ForceDialect.postgres() - val query = DocumentQuery.insert(TEST_TABLE, AutoId.UUID) - query should startWith (s"INSERT INTO $TEST_TABLE VALUES (:data::jsonb || '{\"id\":\"") - query should endWith ("\"}')") - } - it("generates auto UUID | SQLite") { - ForceDialect.sqlite() - val query = DocumentQuery.insert(TEST_TABLE, AutoId.UUID) - query should startWith (s"INSERT INTO $TEST_TABLE VALUES (json_set(:data, '$$.id', '") - query should endWith ("'))") - } - it("generates auto random string | PostgreSQL") { - try { - ForceDialect.postgres() - Configuration.idStringLength = 8 - val query = DocumentQuery.insert(TEST_TABLE, AutoId.RANDOM_STRING) - query should startWith (s"INSERT INTO $TEST_TABLE VALUES (:data::jsonb || '{\"id\":\"") - query should endWith ("\"}')") - query.replace(s"INSERT INTO $TEST_TABLE VALUES (:data::jsonb || '{\"id\":\"", "").replace("\"}')", "") - .length shouldEqual 8 - } finally { - Configuration.idStringLength = 16 - } - } - it("insert generates auto random string | SQLite") { - ForceDialect.sqlite() - val query = DocumentQuery.insert(TEST_TABLE, AutoId.RANDOM_STRING) - query should startWith (s"INSERT INTO $TEST_TABLE VALUES (json_set(:data, '$$.id', '") - query should endWith ("'))") - query.replace(s"INSERT INTO $TEST_TABLE VALUES (json_set(:data, '$$.id', '", "").replace("'))", "") - .length shouldEqual Configuration.idStringLength - } - it("fails when no dialect is set") { - a [DocumentException] should be thrownBy DocumentQuery.insert(TEST_TABLE) - } - } - - describe("save") { - it("generates correctly") { - ForceDialect.postgres() - DocumentQuery.save(TEST_TABLE) shouldEqual - s"INSERT INTO $TEST_TABLE VALUES (:data) ON CONFLICT ((data->>'id')) DO UPDATE SET data = EXCLUDED.data" - } - } - - describe("update") { - it("generates successfully") { - DocumentQuery.update(TEST_TABLE) shouldEqual s"UPDATE $TEST_TABLE SET data = :data" - } - } -} diff --git a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/DocumentQueryTest.scala b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/DocumentQueryTest.scala new file mode 100644 index 0000000..2410773 --- /dev/null +++ b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/DocumentQueryTest.scala @@ -0,0 +1,113 @@ +package solutions.bitbadger.documents.scala.query + +import org.junit.jupiter.api.{AfterEach, DisplayName, Test} +import org.junit.jupiter.api.Assertions._ +import solutions.bitbadger.documents.{AutoId, Configuration, DocumentException} +import solutions.bitbadger.documents.query.DocumentQuery +import solutions.bitbadger.documents.support.ForceDialect +import solutions.bitbadger.documents.support.TypesKt.TEST_TABLE + +@DisplayName("JVM | Scala | Query | DocumentQuery") +class DocumentQueryTest { + + /** + * Clear the connection string (resets Dialect) + */ + @AfterEach + def cleanUp(): Unit = + ForceDialect.none() + + @Test + @DisplayName("insert generates no auto ID | PostgreSQL") + def insertNoAutoPostgres(): Unit = + ForceDialect.postgres() + assertEquals(s"INSERT INTO $TEST_TABLE VALUES (:data)", DocumentQuery.insert(TEST_TABLE)) + + @Test + @DisplayName("insert generates no auto ID | SQLite") + def insertNoAutoSQLite(): Unit = + ForceDialect.sqlite() + assertEquals(s"INSERT INTO $TEST_TABLE VALUES (:data)", DocumentQuery.insert(TEST_TABLE)) + + @Test + @DisplayName("insert generates auto number | PostgreSQL") + def insertAutoNumberPostgres(): Unit = + ForceDialect.postgres() + assertEquals(s"INSERT INTO $TEST_TABLE VALUES (:data::jsonb || ('{\"id\":' " + + s"|| (SELECT COALESCE(MAX((data->>'id')::numeric), 0) + 1 FROM $TEST_TABLE) || '}')::jsonb)", + DocumentQuery.insert(TEST_TABLE, AutoId.NUMBER)) + + @Test + @DisplayName("insert generates auto number | SQLite") + def insertAutoNumberSQLite(): Unit = + ForceDialect.sqlite() + assertEquals(s"INSERT INTO $TEST_TABLE VALUES (json_set(:data, '$$.id', " + + s"(SELECT coalesce(max(data->>'id'), 0) + 1 FROM $TEST_TABLE)))", + DocumentQuery.insert(TEST_TABLE, AutoId.NUMBER)) + + @Test + @DisplayName("insert generates auto UUID | PostgreSQL") + def insertAutoUUIDPostgres(): Unit = + ForceDialect.postgres() + val query = DocumentQuery.insert(TEST_TABLE, AutoId.UUID) + assertTrue(query.startsWith(s"INSERT INTO $TEST_TABLE VALUES (:data::jsonb || '{\"id\":\""), + s"Query start not correct (actual: $query)") + assertTrue(query.endsWith("\"}')"), "Query end not correct") + + @Test + @DisplayName("insert generates auto UUID | SQLite") + def insertAutoUUIDSQLite(): Unit = + ForceDialect.sqlite() + val query = DocumentQuery.insert(TEST_TABLE, AutoId.UUID) + assertTrue(query.startsWith(s"INSERT INTO $TEST_TABLE VALUES (json_set(:data, '$$.id', '"), + s"Query start not correct (actual: $query)") + assertTrue(query.endsWith("'))"), "Query end not correct") + + @Test + @DisplayName("insert generates auto random string | PostgreSQL") + def insertAutoRandomPostgres(): Unit = + try { + ForceDialect.postgres() + Configuration.idStringLength = 8 + val query = DocumentQuery.insert(TEST_TABLE, AutoId.RANDOM_STRING) + assertTrue(query.startsWith(s"INSERT INTO $TEST_TABLE VALUES (:data::jsonb || '{\"id\":\""), + s"Query start not correct (actual: $query)") + assertTrue(query.endsWith("\"}')"), "Query end not correct") + assertEquals(8, query.replace(s"INSERT INTO $TEST_TABLE VALUES (:data::jsonb || '{\"id\":\"", "") + .replace("\"}')", "").length, + "Random string length incorrect") + } finally { + Configuration.idStringLength = 16 + } + + @Test + @DisplayName("insert generates auto random string | SQLite") + def insertAutoRandomSQLite(): Unit = + ForceDialect.sqlite() + val query = DocumentQuery.insert(TEST_TABLE, AutoId.RANDOM_STRING) + assertTrue(query.startsWith(s"INSERT INTO $TEST_TABLE VALUES (json_set(:data, '$$.id', '"), + s"Query start not correct (actual: $query)") + assertTrue(query.endsWith("'))"), "Query end not correct") + assertEquals(Configuration.idStringLength, + query.replace(s"INSERT INTO $TEST_TABLE VALUES (json_set(:data, '$$.id', '", "").replace("'))", "").length, + "Random string length incorrect") + + @Test + @DisplayName("insert fails when no dialect is set") + def insertFailsUnknown(): Unit = + assertThrows(classOf[DocumentException], () => DocumentQuery.insert(TEST_TABLE)) + + @Test + @DisplayName("save generates correctly") + def save(): Unit = + ForceDialect.postgres() + assertEquals( + s"INSERT INTO $TEST_TABLE VALUES (:data) ON CONFLICT ((data->>'id')) DO UPDATE SET data = EXCLUDED.data", + DocumentQuery.save(TEST_TABLE), "INSERT ON CONFLICT UPDATE statement not constructed correctly") + + @Test + @DisplayName("update generates successfully") + def update(): Unit = + assertEquals(s"UPDATE $TEST_TABLE SET data = :data", DocumentQuery.update(TEST_TABLE), + "Update query not constructed correctly") +} diff --git a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/ExistsQuerySpec.scala b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/ExistsQuerySpec.scala deleted file mode 100644 index d539543..0000000 --- a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/ExistsQuerySpec.scala +++ /dev/null @@ -1,64 +0,0 @@ -package solutions.bitbadger.documents.scala.query - -import org.scalatest.funspec.AnyFunSpec -import org.scalatest.matchers.should.Matchers -import solutions.bitbadger.documents.{DocumentException, Field} -import solutions.bitbadger.documents.query.ExistsQuery -import solutions.bitbadger.documents.scala.ClearConfiguration -import solutions.bitbadger.documents.support.ForceDialect -import solutions.bitbadger.documents.support.TypesKt.TEST_TABLE - -import scala.jdk.CollectionConverters.* - -class ExistsQuerySpec extends AnyFunSpec with ClearConfiguration with Matchers { - - describe("byId") { - it("generates correctly | PostgreSQL") { - ForceDialect.postgres() - ExistsQuery.byId(TEST_TABLE) shouldEqual - s"SELECT EXISTS (SELECT 1 FROM $TEST_TABLE WHERE data->>'id' = :id) AS it" - } - it("generates correctly | SQLite") { - ForceDialect.sqlite() - ExistsQuery.byId(TEST_TABLE) shouldEqual - s"SELECT EXISTS (SELECT 1 FROM $TEST_TABLE WHERE data->>'id' = :id) AS it" - } - } - - describe("byFields") { - it("generates correctly | PostgreSQL") { - ForceDialect.postgres() - ExistsQuery.byFields(TEST_TABLE, List(Field.equal("it", 7, ":test")).asJava) shouldEqual - s"SELECT EXISTS (SELECT 1 FROM $TEST_TABLE WHERE (data->>'it')::numeric = :test) AS it" - } - it("generates correctly | SQLite") { - ForceDialect.sqlite() - ExistsQuery.byFields(TEST_TABLE, List(Field.equal("it", 7, ":test")).asJava) shouldEqual - s"SELECT EXISTS (SELECT 1 FROM $TEST_TABLE WHERE data->>'it' = :test) AS it" - } - } - - describe("byContains") { - it("generates correctly | PostgreSQL") { - ForceDialect.postgres() - ExistsQuery.byContains(TEST_TABLE) shouldEqual - s"SELECT EXISTS (SELECT 1 FROM $TEST_TABLE WHERE data @> :criteria) AS it" - } - it("fails | SQLite") { - ForceDialect.sqlite() - a [DocumentException] should be thrownBy ExistsQuery.byContains(TEST_TABLE) - } - } - - describe("byJsonPath") { - it("generates correctly | PostgreSQL") { - ForceDialect.postgres() - ExistsQuery.byJsonPath(TEST_TABLE) shouldEqual - s"SELECT EXISTS (SELECT 1 FROM $TEST_TABLE WHERE jsonb_path_exists(data, :path::jsonpath)) AS it" - } - it("fails | SQLite") { - ForceDialect.sqlite() - a [DocumentException] should be thrownBy ExistsQuery.byJsonPath(TEST_TABLE) - } - } -} diff --git a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/ExistsQueryTest.scala b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/ExistsQueryTest.scala new file mode 100644 index 0000000..3ab3992 --- /dev/null +++ b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/ExistsQueryTest.scala @@ -0,0 +1,77 @@ +package solutions.bitbadger.documents.scala.query + +import org.junit.jupiter.api.{AfterEach, DisplayName, Test} +import org.junit.jupiter.api.Assertions._ +import solutions.bitbadger.documents.{DocumentException, Field} +import solutions.bitbadger.documents.query.ExistsQuery +import solutions.bitbadger.documents.support.ForceDialect +import solutions.bitbadger.documents.support.TypesKt.TEST_TABLE + +import scala.jdk.CollectionConverters.* + +@DisplayName("JVM | Scala | Query | ExistsQuery") +class ExistsQueryTest { + + /** + * Clear the connection string (resets Dialect) + */ + @AfterEach + def cleanUp(): Unit = + ForceDialect.none() + + @Test + @DisplayName("byId generates correctly | PostgreSQL") + def byIdPostgres(): Unit = + ForceDialect.postgres() + assertEquals(s"SELECT EXISTS (SELECT 1 FROM $TEST_TABLE WHERE data->>'id' = :id) AS it", + ExistsQuery.byId(TEST_TABLE), "Exists query not constructed correctly") + + @Test + @DisplayName("byId generates correctly | SQLite") + def byIdSQLite(): Unit = + ForceDialect.sqlite() + assertEquals(s"SELECT EXISTS (SELECT 1 FROM $TEST_TABLE WHERE data->>'id' = :id) AS it", + ExistsQuery.byId(TEST_TABLE), "Exists query not constructed correctly") + + @Test + @DisplayName("byFields generates correctly | PostgreSQL") + def byFieldsPostgres(): Unit = + ForceDialect.postgres() + assertEquals(s"SELECT EXISTS (SELECT 1 FROM $TEST_TABLE WHERE (data->>'it')::numeric = :test) AS it", + ExistsQuery.byFields(TEST_TABLE, List(Field.equal("it", 7, ":test")).asJava), + "Exists query not constructed correctly") + + @Test + @DisplayName("byFields generates correctly | SQLite") + def byFieldsSQLite(): Unit = + ForceDialect.sqlite() + assertEquals(s"SELECT EXISTS (SELECT 1 FROM $TEST_TABLE WHERE data->>'it' = :test) AS it", + ExistsQuery.byFields(TEST_TABLE, List(Field.equal("it", 7, ":test")).asJava), + "Exists query not constructed correctly") + + @Test + @DisplayName("byContains generates correctly | PostgreSQL") + def byContainsPostgres(): Unit = + ForceDialect.postgres() + assertEquals(s"SELECT EXISTS (SELECT 1 FROM $TEST_TABLE WHERE data @> :criteria) AS it", + ExistsQuery.byContains(TEST_TABLE), "Exists query not constructed correctly") + + @Test + @DisplayName("byContains fails | SQLite") + def byContainsSQLite(): Unit = + ForceDialect.sqlite() + assertThrows(classOf[DocumentException], () => ExistsQuery.byContains(TEST_TABLE)) + + @Test + @DisplayName("byJsonPath generates correctly | PostgreSQL") + def byJsonPathPostgres(): Unit = + ForceDialect.postgres() + assertEquals(s"SELECT EXISTS (SELECT 1 FROM $TEST_TABLE WHERE jsonb_path_exists(data, :path::jsonpath)) AS it", + ExistsQuery.byJsonPath(TEST_TABLE), "Exists query not constructed correctly") + + @Test + @DisplayName("byJsonPath fails | SQLite") + def byJsonPathSQLite(): Unit = + ForceDialect.sqlite() + assertThrows(classOf[DocumentException], () => ExistsQuery.byJsonPath(TEST_TABLE)) +} diff --git a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/FindQuerySpec.scala b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/FindQuerySpec.scala deleted file mode 100644 index 5d8a585..0000000 --- a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/FindQuerySpec.scala +++ /dev/null @@ -1,67 +0,0 @@ -package solutions.bitbadger.documents.scala.query - -import org.scalatest.funspec.AnyFunSpec -import org.scalatest.matchers.should.Matchers -import solutions.bitbadger.documents.{DocumentException, Field} -import solutions.bitbadger.documents.query.FindQuery -import solutions.bitbadger.documents.scala.ClearConfiguration -import solutions.bitbadger.documents.support.ForceDialect -import solutions.bitbadger.documents.support.TypesKt.TEST_TABLE - -import scala.jdk.CollectionConverters.* - -class FindQuerySpec extends AnyFunSpec with ClearConfiguration with Matchers { - - describe("all") { - it("generates correctly") { - FindQuery.all(TEST_TABLE) shouldEqual s"SELECT data FROM $TEST_TABLE" - } - } - - describe("byId") { - it("generates correctly | PostgreSQL") { - ForceDialect.postgres() - FindQuery.byId(TEST_TABLE) shouldEqual s"SELECT data FROM $TEST_TABLE WHERE data->>'id' = :id" - } - it("generates correctly | SQLite") { - ForceDialect.sqlite() - FindQuery.byId(TEST_TABLE) shouldEqual s"SELECT data FROM $TEST_TABLE WHERE data->>'id' = :id" - } - } - - describe("byFields") { - it("generates correctly | PostgreSQL") { - ForceDialect.postgres() - FindQuery.byFields(TEST_TABLE, List(Field.equal("a", "", ":b"), Field.less("c", 14, ":d")).asJava) shouldEqual - s"SELECT data FROM $TEST_TABLE WHERE data->>'a' = :b AND (data->>'c')::numeric < :d" - } - it("generates correctly | SQLite") { - ForceDialect.sqlite() - FindQuery.byFields(TEST_TABLE, List(Field.equal("a", "", ":b"), Field.less("c", 14, ":d")).asJava) shouldEqual - s"SELECT data FROM $TEST_TABLE WHERE data->>'a' = :b AND data->>'c' < :d" - } - } - - describe("byContains") { - it("generates correctly | PostgreSQL") { - ForceDialect.postgres() - FindQuery.byContains(TEST_TABLE) shouldEqual s"SELECT data FROM $TEST_TABLE WHERE data @> :criteria" - } - it("fails | SQLite") { - ForceDialect.sqlite() - a [DocumentException] should be thrownBy FindQuery.byContains(TEST_TABLE) - } - } - - describe("byJsonPath") { - it("generates correctly | PostgreSQL") { - ForceDialect.postgres() - FindQuery.byJsonPath(TEST_TABLE) shouldEqual - s"SELECT data FROM $TEST_TABLE WHERE jsonb_path_exists(data, :path::jsonpath)" - } - it("fails | SQLite") { - ForceDialect.sqlite() - a [DocumentException] should be thrownBy FindQuery.byJsonPath(TEST_TABLE) - } - } -} diff --git a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/FindQueryTest.scala b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/FindQueryTest.scala new file mode 100644 index 0000000..06e1586 --- /dev/null +++ b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/FindQueryTest.scala @@ -0,0 +1,82 @@ +package solutions.bitbadger.documents.scala.query + +import org.junit.jupiter.api.{AfterEach, DisplayName, Test} +import org.junit.jupiter.api.Assertions.* +import solutions.bitbadger.documents.{DocumentException, Field} +import solutions.bitbadger.documents.query.FindQuery +import solutions.bitbadger.documents.support.ForceDialect +import solutions.bitbadger.documents.support.TypesKt.TEST_TABLE + +import scala.jdk.CollectionConverters.* + +@DisplayName("JVM | Scala | Query | FindQuery") +class FindQueryTest { + + /** + * Clear the connection string (resets Dialect) + */ + @AfterEach + def cleanUp(): Unit = + ForceDialect.none() + + @Test + @DisplayName("all generates correctly") + def all(): Unit = + assertEquals(s"SELECT data FROM $TEST_TABLE", FindQuery.all(TEST_TABLE), "Find query not constructed correctly") + + @Test + @DisplayName("byId generates correctly | PostgreSQL") + def byIdPostgres(): Unit = + ForceDialect.postgres() + assertEquals(s"SELECT data FROM $TEST_TABLE WHERE data->>'id' = :id", FindQuery.byId(TEST_TABLE), + "Find query not constructed correctly") + + @Test + @DisplayName("byId generates correctly | SQLite") + def byIdSQLite(): Unit = + ForceDialect.sqlite() + assertEquals(s"SELECT data FROM $TEST_TABLE WHERE data->>'id' = :id", FindQuery.byId(TEST_TABLE), + "Find query not constructed correctly") + + @Test + @DisplayName("byFields generates correctly | PostgreSQL") + def byFieldsPostgres(): Unit = + ForceDialect.postgres() + assertEquals(s"SELECT data FROM $TEST_TABLE WHERE data->>'a' = :b AND (data->>'c')::numeric < :d", + FindQuery.byFields(TEST_TABLE, List(Field.equal("a", "", ":b"), Field.less("c", 14, ":d")).asJava), + "Find query not constructed correctly") + + @Test + @DisplayName("byFields generates correctly | SQLite") + def byFieldsSQLite(): Unit = + ForceDialect.sqlite() + assertEquals(s"SELECT data FROM $TEST_TABLE WHERE data->>'a' = :b AND data->>'c' < :d", + FindQuery.byFields(TEST_TABLE, List(Field.equal("a", "", ":b"), Field.less("c", 14, ":d")).asJava), + "Find query not constructed correctly") + + @Test + @DisplayName("byContains generates correctly | PostgreSQL") + def byContainsPostgres(): Unit = + ForceDialect.postgres() + assertEquals(s"SELECT data FROM $TEST_TABLE WHERE data @> :criteria", FindQuery.byContains(TEST_TABLE), + "Find query not constructed correctly") + + @Test + @DisplayName("byContains fails | SQLite") + def byContainsSQLite(): Unit = + ForceDialect.sqlite() + assertThrows(classOf[DocumentException], () => FindQuery.byContains(TEST_TABLE)) + + @Test + @DisplayName("byJsonPath generates correctly | PostgreSQL") + def byJsonPathPostgres(): Unit = + ForceDialect.postgres() + assertEquals(s"SELECT data FROM $TEST_TABLE WHERE jsonb_path_exists(data, :path::jsonpath)", + FindQuery.byJsonPath(TEST_TABLE), "Find query not constructed correctly") + + @Test + @DisplayName("byJsonPath fails | SQLite") + def byJsonPathSQLite(): Unit = + ForceDialect.sqlite() + assertThrows(classOf[DocumentException], () => FindQuery.byJsonPath(TEST_TABLE)) +} diff --git a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/PatchQuerySpec.scala b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/PatchQuerySpec.scala deleted file mode 100644 index a37a8b3..0000000 --- a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/PatchQuerySpec.scala +++ /dev/null @@ -1,63 +0,0 @@ -package solutions.bitbadger.documents.scala.query - -import org.scalatest.funspec.AnyFunSpec -import org.scalatest.matchers.should.Matchers -import solutions.bitbadger.documents.{DocumentException, Field} -import solutions.bitbadger.documents.query.PatchQuery -import solutions.bitbadger.documents.scala.ClearConfiguration -import solutions.bitbadger.documents.support.ForceDialect -import solutions.bitbadger.documents.support.TypesKt.TEST_TABLE - -import scala.jdk.CollectionConverters.* - -class PatchQuerySpec extends AnyFunSpec with ClearConfiguration with Matchers { - - describe("byId") { - it("generates correctly | PostgreSQL") { - ForceDialect.postgres() - PatchQuery.byId(TEST_TABLE) shouldEqual s"UPDATE $TEST_TABLE SET data = data || :data WHERE data->>'id' = :id" - } - it("generates correctly | SQLite") { - ForceDialect.sqlite() - PatchQuery.byId(TEST_TABLE) shouldEqual - s"UPDATE $TEST_TABLE SET data = json_patch(data, json(:data)) WHERE data->>'id' = :id" - } - } - - describe("byFields") { - it("generates correctly | PostgreSQL") { - ForceDialect.postgres() - PatchQuery.byFields(TEST_TABLE, List(Field.equal("z", "", ":y")).asJava) shouldEqual - s"UPDATE $TEST_TABLE SET data = data || :data WHERE data->>'z' = :y" - } - it("generates correctly | SQLite") { - ForceDialect.sqlite() - PatchQuery.byFields(TEST_TABLE, List(Field.equal("z", "", ":y")).asJava) shouldEqual - s"UPDATE $TEST_TABLE SET data = json_patch(data, json(:data)) WHERE data->>'z' = :y" - } - } - - describe("byContains") { - it("generates correctly | PostgreSQL") { - ForceDialect.postgres() - PatchQuery.byContains(TEST_TABLE) shouldEqual - s"UPDATE $TEST_TABLE SET data = data || :data WHERE data @> :criteria" - } - it("fails | SQLite") { - ForceDialect.sqlite() - a [DocumentException] should be thrownBy PatchQuery.byContains(TEST_TABLE) - } - } - - describe("byJsonPath") { - it("generates correctly | PostgreSQL") { - ForceDialect.postgres() - PatchQuery.byJsonPath(TEST_TABLE) shouldEqual - s"UPDATE $TEST_TABLE SET data = data || :data WHERE jsonb_path_exists(data, :path::jsonpath)" - } - it("fails | SQLite") { - ForceDialect.sqlite() - a [DocumentException] should be thrownBy PatchQuery.byJsonPath(TEST_TABLE) - } - } -} diff --git a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/PatchQueryTest.scala b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/PatchQueryTest.scala new file mode 100644 index 0000000..2741f2d --- /dev/null +++ b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/PatchQueryTest.scala @@ -0,0 +1,75 @@ +package solutions.bitbadger.documents.scala.query + +import org.junit.jupiter.api.{AfterEach, DisplayName, Test} +import org.junit.jupiter.api.Assertions._ +import solutions.bitbadger.documents.{DocumentException, Field} +import solutions.bitbadger.documents.query.PatchQuery +import solutions.bitbadger.documents.support.ForceDialect +import solutions.bitbadger.documents.support.TypesKt.TEST_TABLE + +import scala.jdk.CollectionConverters.* + +@DisplayName("JVM | Scala | Query | PatchQuery") +class PatchQueryTest { + + /** + * Reset the dialect + */ + @AfterEach + def cleanUp(): Unit = + ForceDialect.none() + + @Test + @DisplayName("byId generates correctly | PostgreSQL") + def byIdPostgres(): Unit = + ForceDialect.postgres() + assertEquals(s"UPDATE $TEST_TABLE SET data = data || :data WHERE data->>'id' = :id", PatchQuery.byId(TEST_TABLE), + "Patch query not constructed correctly") + + @Test + @DisplayName("byId generates correctly | SQLite") + def byIdSQLite(): Unit = + ForceDialect.sqlite() + assertEquals(s"UPDATE $TEST_TABLE SET data = json_patch(data, json(:data)) WHERE data->>'id' = :id", + PatchQuery.byId(TEST_TABLE), "Patch query not constructed correctly") + + @Test + @DisplayName("byFields generates correctly | PostgreSQL") + def byFieldsPostgres(): Unit = + ForceDialect.postgres() + assertEquals(s"UPDATE $TEST_TABLE SET data = data || :data WHERE data->>'z' = :y", + PatchQuery.byFields(TEST_TABLE, List(Field.equal("z", "", ":y")).asJava), "Patch query not constructed correctly") + + @Test + @DisplayName("byFields generates correctly | SQLite") + def byFieldsSQLite(): Unit = + ForceDialect.sqlite() + assertEquals(s"UPDATE $TEST_TABLE SET data = json_patch(data, json(:data)) WHERE data->>'z' = :y", + PatchQuery.byFields(TEST_TABLE, List(Field.equal("z", "", ":y")).asJava), "Patch query not constructed correctly") + + @Test + @DisplayName("byContains generates correctly | PostgreSQL") + def byContainsPostgres(): Unit = + ForceDialect.postgres() + assertEquals(s"UPDATE $TEST_TABLE SET data = data || :data WHERE data @> :criteria", + PatchQuery.byContains(TEST_TABLE), "Patch query not constructed correctly" ) + + @Test + @DisplayName("byContains fails | SQLite") + def byContainsSQLite(): Unit = + ForceDialect.sqlite() + assertThrows(classOf[DocumentException], () => PatchQuery.byContains(TEST_TABLE)) + + @Test + @DisplayName("byJsonPath generates correctly | PostgreSQL") + def byJsonPathPostgres(): Unit = + ForceDialect.postgres() + assertEquals(s"UPDATE $TEST_TABLE SET data = data || :data WHERE jsonb_path_exists(data, :path::jsonpath)", + PatchQuery.byJsonPath(TEST_TABLE), "Patch query not constructed correctly") + + @Test + @DisplayName("byJsonPath fails | SQLite") + def byJsonPathSQLite(): Unit = + ForceDialect.sqlite() + assertThrows(classOf[DocumentException], () => PatchQuery.byJsonPath(TEST_TABLE)) +} diff --git a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/QueryUtilsSpec.scala b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/QueryUtilsSpec.scala deleted file mode 100644 index 0b58169..0000000 --- a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/QueryUtilsSpec.scala +++ /dev/null @@ -1,103 +0,0 @@ -package solutions.bitbadger.documents.scala.query - -import org.scalatest.funspec.AnyFunSpec -import org.scalatest.matchers.should.Matchers -import solutions.bitbadger.documents.{Dialect, Field, FieldMatch} -import solutions.bitbadger.documents.query.QueryUtils -import solutions.bitbadger.documents.scala.ClearConfiguration -import solutions.bitbadger.documents.support.ForceDialect - -import scala.jdk.CollectionConverters.* - -class QueryUtilsSpec extends AnyFunSpec with ClearConfiguration with Matchers { - - describe("statementWhere") { - it("generates correctly") { - QueryUtils.statementWhere("x", "y") shouldEqual "x WHERE y" - } - } - - describe("byId") { - it("generates a numeric ID query | PostgreSQL") { - ForceDialect.postgres() - QueryUtils.byId("test", 9) shouldEqual "test WHERE (data->>'id')::numeric = :id" - } - it("generates an alphanumeric ID query | PostgreSQL") { - ForceDialect.postgres() - QueryUtils.byId("unit", "18") shouldEqual "unit WHERE data->>'id' = :id" - } - it("generates ID query | SQLite") { - ForceDialect.sqlite() - QueryUtils.byId("yo", 27) shouldEqual "yo WHERE data->>'id' = :id" - } - } - - describe("byFields") { - it("generates default field query | PostgreSQL") { - ForceDialect.postgres() - QueryUtils.byFields("this", - List(Field.equal("a", "", ":the_a"), Field.equal("b", 0, ":b_value")).asJava) shouldEqual - "this WHERE data->>'a' = :the_a AND (data->>'b')::numeric = :b_value" - } - it("generates default field query | SQLite") { - ForceDialect.sqlite() - QueryUtils.byFields("this", - List(Field.equal("a", "", ":the_a"), Field.equal("b", 0, ":b_value")).asJava) shouldEqual - "this WHERE data->>'a' = :the_a AND data->>'b' = :b_value" - } - it("generates ANY field query | PostgreSQL") { - ForceDialect.postgres() - QueryUtils.byFields("that", List(Field.equal("a", "", ":the_a"), Field.equal("b", 0, ":b_value")).asJava, - FieldMatch.ANY) shouldEqual - "that WHERE data->>'a' = :the_a OR (data->>'b')::numeric = :b_value" - } - it("generates ANY field query | SQLite") { - ForceDialect.sqlite() - QueryUtils.byFields("that", List(Field.equal("a", "", ":the_a"), Field.equal("b", 0, ":b_value")).asJava, - FieldMatch.ANY) shouldEqual - "that WHERE data->>'a' = :the_a OR data->>'b' = :b_value" - } - } - - describe("orderBy") { - it("generates for no fields") { - QueryUtils.orderBy(List().asJava, Dialect.POSTGRESQL) shouldEqual "" - QueryUtils.orderBy(List().asJava, Dialect.SQLITE) shouldEqual "" - } - it("generates single, no direction | PostgreSQL") { - QueryUtils.orderBy(List(Field.named("TestField")).asJava, Dialect.POSTGRESQL) shouldEqual - " ORDER BY data->>'TestField'" - } - it("generates single, no direction | SQLite") { - QueryUtils.orderBy(List(Field.named("TestField")).asJava, Dialect.SQLITE) shouldEqual - " ORDER BY data->>'TestField'" - } - it("generates multiple with direction | PostgreSQL") { - QueryUtils.orderBy( - List(Field.named("Nested.Test.Field DESC"), Field.named("AnotherField"), Field.named("It DESC")).asJava, - Dialect.POSTGRESQL) shouldEqual - " ORDER BY data#>>'{Nested,Test,Field}' DESC, data->>'AnotherField', data->>'It' DESC" - } - it("generates multiple with direction | SQLite") { - QueryUtils.orderBy( - List(Field.named("Nested.Test.Field DESC"), Field.named("AnotherField"), Field.named("It DESC")).asJava, - Dialect.SQLITE) shouldEqual - " ORDER BY data->'Nested'->'Test'->>'Field' DESC, data->>'AnotherField', data->>'It' DESC" - } - it("generates numeric ordering | PostgreSQL") { - QueryUtils.orderBy(List(Field.named("n:Test")).asJava, Dialect.POSTGRESQL) shouldEqual - " ORDER BY (data->>'Test')::numeric" - } - it("generates numeric ordering | SQLite") { - QueryUtils.orderBy(List(Field.named("n:Test")).asJava, Dialect.SQLITE) shouldEqual " ORDER BY data->>'Test'" - } - it("generates case-insensitive ordering | PostgreSQL") { - QueryUtils.orderBy(List(Field.named("i:Test.Field DESC NULLS FIRST")).asJava, Dialect.POSTGRESQL) shouldEqual - " ORDER BY LOWER(data#>>'{Test,Field}') DESC NULLS FIRST" - } - it("generates case-insensitive ordering | SQLite") { - QueryUtils.orderBy(List(Field.named("i:Test.Field ASC NULLS LAST")).asJava, Dialect.SQLITE) shouldEqual - " ORDER BY data->'Test'->>'Field' COLLATE NOCASE ASC NULLS LAST" - } - } -} diff --git a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/QueryUtilsTest.scala b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/QueryUtilsTest.scala new file mode 100644 index 0000000..3076708 --- /dev/null +++ b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/QueryUtilsTest.scala @@ -0,0 +1,138 @@ +package solutions.bitbadger.documents.scala.query + +import org.junit.jupiter.api.{AfterEach, DisplayName, Test} +import org.junit.jupiter.api.Assertions._ +import solutions.bitbadger.documents.{Dialect, Field, FieldMatch} +import solutions.bitbadger.documents.query.QueryUtils +import solutions.bitbadger.documents.support.ForceDialect + +import scala.jdk.CollectionConverters.* + +@DisplayName("JVM | Scala | Query | Package Functions") +class QueryUtilsTest { + + /** + * Clear the connection string (resets Dialect) + */ + @AfterEach + def cleanUp(): Unit = + ForceDialect.none() + + @Test + @DisplayName("statementWhere generates correctly") + def statementWhere(): Unit = + assertEquals("x WHERE y", QueryUtils.statementWhere("x", "y"), "Statements not combined correctly") + + @Test + @DisplayName("byId generates a numeric ID query | PostgreSQL") + def byIdNumericPostgres(): Unit = + ForceDialect.postgres() + assertEquals("test WHERE (data->>'id')::numeric = :id", QueryUtils.byId("test", 9)) + + @Test + @DisplayName("byId generates an alphanumeric ID query | PostgreSQL") + def byIdAlphaPostgres(): Unit = + ForceDialect.postgres() + assertEquals("unit WHERE data->>'id' = :id", QueryUtils.byId("unit", "18")) + + @Test + @DisplayName("byId generates ID query | SQLite") + def byIdSQLite(): Unit = + ForceDialect.sqlite() + assertEquals("yo WHERE data->>'id' = :id", QueryUtils.byId("yo", 27)) + + @Test + @DisplayName("byFields generates default field query | PostgreSQL") + def byFieldsMultipleDefaultPostgres(): Unit = + ForceDialect.postgres() + assertEquals("this WHERE data->>'a' = :the_a AND (data->>'b')::numeric = :b_value", + QueryUtils.byFields("this", List(Field.equal("a", "", ":the_a"), Field.equal("b", 0, ":b_value")).asJava)) + + @Test + @DisplayName("byFields generates default field query | SQLite") + def byFieldsMultipleDefaultSQLite(): Unit = + ForceDialect.sqlite() + assertEquals("this WHERE data->>'a' = :the_a AND data->>'b' = :b_value", + QueryUtils.byFields("this", List(Field.equal("a", "", ":the_a"), Field.equal("b", 0, ":b_value")).asJava)) + + @Test + @DisplayName("byFields generates ANY field query | PostgreSQL") + def byFieldsMultipleAnyPostgres(): Unit = + ForceDialect.postgres() + assertEquals("that WHERE data->>'a' = :the_a OR (data->>'b')::numeric = :b_value", + QueryUtils.byFields("that", List(Field.equal("a", "", ":the_a"), Field.equal("b", 0, ":b_value")).asJava, + FieldMatch.ANY)) + + @Test + @DisplayName("byFields generates ANY field query | SQLite") + def byFieldsMultipleAnySQLite(): Unit = + ForceDialect.sqlite() + assertEquals("that WHERE data->>'a' = :the_a OR data->>'b' = :b_value", + QueryUtils.byFields("that", List(Field.equal("a", "", ":the_a"), Field.equal("b", 0, ":b_value")).asJava, + FieldMatch.ANY)) + + @Test + @DisplayName("orderBy generates for no fields") + def orderByNone(): Unit = + assertEquals("", QueryUtils.orderBy(List().asJava, Dialect.POSTGRESQL), + "ORDER BY should have been blank (PostgreSQL)") + assertEquals("", QueryUtils.orderBy(List().asJava, Dialect.SQLITE), "ORDER BY should have been blank (SQLite)") + + @Test + @DisplayName("orderBy generates single, no direction | PostgreSQL") + def orderBySinglePostgres(): Unit = + assertEquals(" ORDER BY data->>'TestField'", + QueryUtils.orderBy(List(Field.named("TestField")).asJava, Dialect.POSTGRESQL), + "ORDER BY not constructed correctly") + + @Test + @DisplayName("orderBy generates single, no direction | SQLite") + def orderBySingleSQLite(): Unit = + assertEquals(" ORDER BY data->>'TestField'", + QueryUtils.orderBy(List(Field.named("TestField")).asJava, Dialect.SQLITE), + "ORDER BY not constructed correctly") + + @Test + @DisplayName("orderBy generates multiple with direction | PostgreSQL") + def orderByMultiplePostgres(): Unit = + assertEquals(" ORDER BY data#>>'{Nested,Test,Field}' DESC, data->>'AnotherField', data->>'It' DESC", + QueryUtils.orderBy( + List(Field.named("Nested.Test.Field DESC"), Field.named("AnotherField"), Field.named("It DESC")).asJava, + Dialect.POSTGRESQL), + "ORDER BY not constructed correctly") + + @Test + @DisplayName("orderBy generates multiple with direction | SQLite") + def orderByMultipleSQLite(): Unit = + assertEquals(" ORDER BY data->'Nested'->'Test'->>'Field' DESC, data->>'AnotherField', data->>'It' DESC", + QueryUtils.orderBy( + List(Field.named("Nested.Test.Field DESC"), Field.named("AnotherField"), Field.named("It DESC")).asJava, + Dialect.SQLITE), + "ORDER BY not constructed correctly") + + @Test + @DisplayName("orderBy generates numeric ordering | PostgreSQL") + def orderByNumericPostgres(): Unit = + assertEquals(" ORDER BY (data->>'Test')::numeric", + QueryUtils.orderBy(List(Field.named("n:Test")).asJava, Dialect.POSTGRESQL), "ORDER BY not constructed correctly") + + @Test + @DisplayName("orderBy generates numeric ordering | SQLite") + def orderByNumericSQLite(): Unit = + assertEquals(" ORDER BY data->>'Test'", QueryUtils.orderBy(List(Field.named("n:Test")).asJava, Dialect.SQLITE), + "ORDER BY not constructed correctly") + + @Test + @DisplayName("orderBy generates case-insensitive ordering | PostgreSQL") + def orderByCIPostgres(): Unit = + assertEquals(" ORDER BY LOWER(data#>>'{Test,Field}') DESC NULLS FIRST", + QueryUtils.orderBy(List(Field.named("i:Test.Field DESC NULLS FIRST")).asJava, Dialect.POSTGRESQL), + "ORDER BY not constructed correctly") + + @Test + @DisplayName("orderBy generates case-insensitive ordering | SQLite") + def orderByCISQLite(): Unit = + assertEquals(" ORDER BY data->'Test'->>'Field' COLLATE NOCASE ASC NULLS LAST", + QueryUtils.orderBy(List(Field.named("i:Test.Field ASC NULLS LAST")).asJava, Dialect.SQLITE), + "ORDER BY not constructed correctly") +} diff --git a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/RemoveFieldsQueryTest.scala b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/RemoveFieldsQueryTest.scala new file mode 100644 index 0000000..e5e93e0 --- /dev/null +++ b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/RemoveFieldsQueryTest.scala @@ -0,0 +1,85 @@ +package solutions.bitbadger.documents.scala.query + +import org.junit.jupiter.api.{AfterEach, DisplayName, Test} +import org.junit.jupiter.api.Assertions._ +import solutions.bitbadger.documents.{DocumentException, Field, Parameter, ParameterType} +import solutions.bitbadger.documents.query.RemoveFieldsQuery +import solutions.bitbadger.documents.support.ForceDialect +import solutions.bitbadger.documents.support.TypesKt.TEST_TABLE + +import scala.jdk.CollectionConverters.* + +@DisplayName("JVM | Scala | Query | RemoveFieldsQuery") +class RemoveFieldsQueryTest { + + /** + * Reset the dialect + */ + @AfterEach + def cleanUp(): Unit = + ForceDialect.none() + + @Test + @DisplayName("byId generates correctly | PostgreSQL") + def byIdPostgres(): Unit = + ForceDialect.postgres() + assertEquals(s"UPDATE $TEST_TABLE SET data = data - :name::text[] WHERE data->>'id' = :id", + RemoveFieldsQuery.byId(TEST_TABLE, List(Parameter(":name", ParameterType.STRING, "{a,z}")).asJava), + "Remove Fields query not constructed correctly") + + @Test + @DisplayName("byId generates correctly | SQLite") + def byIdSQLite(): Unit = + ForceDialect.sqlite() + assertEquals(s"UPDATE $TEST_TABLE SET data = json_remove(data, :name0, :name1) WHERE data->>'id' = :id", + RemoveFieldsQuery.byId(TEST_TABLE, + List(Parameter(":name0", ParameterType.STRING, "a"),Parameter(":name1", ParameterType.STRING, "z")).asJava), + "Remove Field query not constructed correctly") + + @Test + @DisplayName("byFields generates correctly | PostgreSQL") + def byFieldsPostgres(): Unit = + ForceDialect.postgres() + assertEquals(s"UPDATE $TEST_TABLE SET data = data - :name::text[] WHERE data->>'f' > :g", + RemoveFieldsQuery.byFields(TEST_TABLE, List(Parameter(":name", ParameterType.STRING, "{b,c}")).asJava, + List(Field.greater("f", "", ":g")).asJava), + "Remove Field query not constructed correctly") + + @Test + @DisplayName("byFields generates correctly | SQLite") + def byFieldsSQLite(): Unit = + ForceDialect.sqlite() + assertEquals(s"UPDATE $TEST_TABLE SET data = json_remove(data, :name0, :name1) WHERE data->>'f' > :g", + RemoveFieldsQuery.byFields(TEST_TABLE, + List(Parameter(":name0", ParameterType.STRING, "b"), Parameter(":name1", ParameterType.STRING, "c")).asJava, + List(Field.greater("f", "", ":g")).asJava), + "Remove Field query not constructed correctly") + + @Test + @DisplayName("byContains generates correctly | PostgreSQL") + def byContainsPostgres(): Unit = + ForceDialect.postgres() + assertEquals(s"UPDATE $TEST_TABLE SET data = data - :name::text[] WHERE data @> :criteria", + RemoveFieldsQuery.byContains(TEST_TABLE, List(Parameter(":name", ParameterType.STRING, "{m,n}")).asJava), + "Remove Field query not constructed correctly") + + @Test + @DisplayName("byContains fails | SQLite") + def byContainsSQLite(): Unit = + ForceDialect.sqlite() + assertThrows(classOf[DocumentException], () => RemoveFieldsQuery.byContains(TEST_TABLE, List().asJava)) + + @Test + @DisplayName("byJsonPath generates correctly | PostgreSQL") + def byJsonPathPostgres(): Unit = + ForceDialect.postgres() + assertEquals(s"UPDATE $TEST_TABLE SET data = data - :name::text[] WHERE jsonb_path_exists(data, :path::jsonpath)", + RemoveFieldsQuery.byJsonPath(TEST_TABLE, List(Parameter(":name", ParameterType.STRING, "{o,p}")).asJava), + "Remove Field query not constructed correctly") + + @Test + @DisplayName("byJsonPath fails | SQLite") + def byJsonPathSQLite(): Unit = + ForceDialect.sqlite() + assertThrows(classOf[DocumentException], () => RemoveFieldsQuery.byJsonPath(TEST_TABLE, List().asJava)) +} diff --git a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/WhereTest.scala b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/WhereTest.scala new file mode 100644 index 0000000..26acf2c --- /dev/null +++ b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/query/WhereTest.scala @@ -0,0 +1,143 @@ +package solutions.bitbadger.documents.scala.query + +import org.junit.jupiter.api.{AfterEach, DisplayName, Test} +import org.junit.jupiter.api.Assertions._ +import solutions.bitbadger.documents.{DocumentException, Field, FieldMatch} +import solutions.bitbadger.documents.query.Where +import solutions.bitbadger.documents.support.ForceDialect + +import scala.jdk.CollectionConverters.* + +@DisplayName("JVM | Scala | Query | Where") +class WhereTest { + + /** + * Clear the connection string (resets Dialect) + */ + @AfterEach + def cleanUp (): Unit = + ForceDialect.none() + + @Test + @DisplayName("byFields is blank when given no fields") + def byFieldsBlankIfEmpty(): Unit = + assertEquals("", Where.byFields(List().asJava)) + + @Test + @DisplayName("byFields generates one numeric field | PostgreSQL") + def byFieldsOneFieldPostgres(): Unit = + ForceDialect.postgres() + assertEquals("(data->>'it')::numeric = :that", Where.byFields(List(Field.equal("it", 9, ":that")).asJava)) + + @Test + @DisplayName("byFields generates one alphanumeric field | PostgreSQL") + def byFieldsOneAlphaFieldPostgres(): Unit = + ForceDialect.postgres() + assertEquals("data->>'it' = :that", Where.byFields(List(Field.equal("it", "", ":that")).asJava)) + + @Test + @DisplayName("byFields generates one field | SQLite") + def byFieldsOneFieldSQLite(): Unit = + ForceDialect.sqlite() + assertEquals("data->>'it' = :that", Where.byFields(List(Field.equal("it", "", ":that")).asJava)) + + @Test + @DisplayName("byFields generates multiple fields w/ default match | PostgreSQL") + def byFieldsMultipleDefaultPostgres(): Unit = + ForceDialect.postgres() + assertEquals("data->>'1' = :one AND (data->>'2')::numeric = :two AND data->>'3' = :three", + Where.byFields( + List(Field.equal("1", "", ":one"), Field.equal("2", 0L, ":two"), Field.equal("3", "", ":three")).asJava)) + + @Test + @DisplayName("byFields generates multiple fields w/ default match | SQLite") + def byFieldsMultipleDefaultSQLite(): Unit = + ForceDialect.sqlite() + assertEquals("data->>'1' = :one AND data->>'2' = :two AND data->>'3' = :three", + Where.byFields( + List(Field.equal("1", "", ":one"), Field.equal("2", 0L, ":two"), Field.equal("3", "", ":three")).asJava)) + + @Test + @DisplayName("byFields generates multiple fields w/ ANY match | PostgreSQL") + def byFieldsMultipleAnyPostgres(): Unit = + ForceDialect.postgres() + assertEquals("data->>'1' = :one OR (data->>'2')::numeric = :two OR data->>'3' = :three", + Where.byFields( + List(Field.equal("1", "", ":one"), Field.equal("2", 0L, ":two"), Field.equal("3", "", ":three")).asJava, + FieldMatch.ANY)) + + @Test + @DisplayName("byFields generates multiple fields w/ ANY match | SQLite") + def byFieldsMultipleAnySQLite(): Unit = + ForceDialect.sqlite() + assertEquals("data->>'1' = :one OR data->>'2' = :two OR data->>'3' = :three", + Where.byFields( + List(Field.equal("1", "", ":one"), Field.equal("2", 0L, ":two"), Field.equal("3", "", ":three")).asJava, + FieldMatch.ANY)) + + @Test + @DisplayName("byId generates defaults for alphanumeric key | PostgreSQL") + def byIdDefaultAlphaPostgres(): Unit = + ForceDialect.postgres() + assertEquals("data->>'id' = :id", Where.byId()) + + @Test + @DisplayName("byId generates defaults for numeric key | PostgreSQL") + def byIdDefaultNumericPostgres(): Unit = + ForceDialect.postgres() + assertEquals("(data->>'id')::numeric = :id", Where.byId(":id", 5)) + + @Test + @DisplayName("byId generates defaults | SQLite") + def byIdDefaultSQLite(): Unit = + ForceDialect.sqlite() + assertEquals("data->>'id' = :id", Where.byId()) + + @Test + @DisplayName("byId generates named ID | PostgreSQL") + def byIdDefaultNamedPostgres(): Unit = + ForceDialect.postgres() + assertEquals("data->>'id' = :key", Where.byId(":key")) + + @Test + @DisplayName("byId generates named ID | SQLite") + def byIdDefaultNamedSQLite(): Unit = + ForceDialect.sqlite() + assertEquals("data->>'id' = :key", Where.byId(":key")) + + @Test + @DisplayName("jsonContains generates defaults | PostgreSQL") + def jsonContainsDefaultPostgres(): Unit = + ForceDialect.postgres() + assertEquals("data @> :criteria", Where.jsonContains()) + + @Test + @DisplayName("jsonContains generates named parameter | PostgreSQL") + def jsonContainsNamedPostgres(): Unit = + ForceDialect.postgres() + assertEquals("data @> :it", Where.jsonContains(":it")) + + @Test + @DisplayName("jsonContains fails | SQLite") + def jsonContainsFailsSQLite(): Unit = + ForceDialect.sqlite() + assertThrows(classOf[DocumentException], () => Where.jsonContains()) + + @Test + @DisplayName("jsonPathMatches generates defaults | PostgreSQL") + def jsonPathMatchDefaultPostgres(): Unit = + ForceDialect.postgres() + assertEquals("jsonb_path_exists(data, :path::jsonpath)", Where.jsonPathMatches()) + + @Test + @DisplayName("jsonPathMatches generates named parameter | PostgreSQL") + def jsonPathMatchNamedPostgres(): Unit = + ForceDialect.postgres() + assertEquals("jsonb_path_exists(data, :jp::jsonpath)", Where.jsonPathMatches(":jp")) + + @Test + @DisplayName("jsonPathMatches fails | SQLite") + def jsonPathFailsSQLite(): Unit = + ForceDialect.sqlite() + assertThrows(classOf[DocumentException], () => Where.jsonPathMatches()) +} diff --git a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/support/JsonDocument.scala b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/support/JsonDocument.scala new file mode 100644 index 0000000..861eeab --- /dev/null +++ b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/support/JsonDocument.scala @@ -0,0 +1,20 @@ +package solutions.bitbadger.documents.scala.support + +import solutions.bitbadger.documents.jvm.Document +import solutions.bitbadger.documents.support.ThrowawayDatabase +import solutions.bitbadger.documents.support.TypesKt.TEST_TABLE + +class JsonDocument(val id: String = "", val value: String = "", val numValue: Int = 0, val sub: SubDocument = null) + +object JsonDocument: + + /** Documents to use for testing */ + private val testDocuments = List( + JsonDocument("one", "FIRST!", 0, null), + JsonDocument("two", "another", 10, SubDocument("green", "blue")), + JsonDocument("three", "", 4, null), + JsonDocument("four", "purple", 17, SubDocument("green", "red")), + JsonDocument("five", "purple", 18, null)) + + def load(db: ThrowawayDatabase, tableName: String = TEST_TABLE): Unit = + testDocuments.foreach { it => Document.insert(tableName, it, db.getConn) } diff --git a/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/support/SubDocument.scala b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/support/SubDocument.scala new file mode 100644 index 0000000..9ec17d5 --- /dev/null +++ b/src/jvm/src/test/scala/solutions/bitbadger/documents/scala/support/SubDocument.scala @@ -0,0 +1,3 @@ +package solutions.bitbadger.documents.scala.support + +class SubDocument(val foo: String = "", val bar: String = "")