From b357b5a01fddc0c8879a0da63cea1806edff1ee5 Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Sun, 18 Aug 2024 17:23:09 -0400 Subject: [PATCH] Add AutoId DU / tests --- src/Common/Library.fs | 60 ++- src/Tests.CSharp/CommonCSharpTests.cs | 49 ++ src/Tests/CommonTests.fs | 695 ++++++++++++++------------ src/Tests/SqliteTests.fs | 26 - 4 files changed, 495 insertions(+), 335 deletions(-) diff --git a/src/Common/Library.fs b/src/Common/Library.fs index 4a9f171..2e1a64d 100644 --- a/src/Common/Library.fs +++ b/src/Common/Library.fs @@ -1,5 +1,7 @@ namespace BitBadger.Documents +open System.Security.Cryptography + /// The types of logical operations available for JSON fields [] type Op = @@ -148,6 +150,36 @@ type ParameterName() = currentIdx <- currentIdx + 1 $"@field{currentIdx}" +#if NET6_0 +open System.Text +#endif + +/// Automatically-generated document ID options +[] +type AutoId = + /// No automatic IDs will be generated + | Disabled + /// Generate a MAX-plus-1 numeric value for documents + | Number + /// Generate a GUID for each document (as a lowercase, no-dashes, 32-character string) + | Guid + /// Generate a random string of hexadecimal characters for each document + | RandomString +with + /// Generate a GUID string + static member GenerateGuid() = + System.Guid.NewGuid().ToString "N" + + /// Generate a string of random hexadecimal characters + static member GenerateRandomString(length: int) = +#if NET8_0_OR_GREATER + RandomNumberGenerator.GetHexString(length, lowercase = true) +#else + RandomNumberGenerator.GetBytes((length / 2) + 1) + |> Array.fold (fun (str: StringBuilder) byt -> str.Append(byt.ToString "x2")) (StringBuilder length) + |> function it -> it.Length <- length; it.ToString() +#endif + /// The required document serialization implementation type IDocumentSerializer = @@ -200,7 +232,7 @@ module Configuration = serializerValue /// The serialized name of the ID field for documents - let mutable idFieldValue = "Id" + let mutable private idFieldValue = "Id" /// Specify the name of the ID field for documents [] @@ -211,6 +243,32 @@ module Configuration = [] let idField () = idFieldValue + + /// The automatic ID strategy used by the library + let mutable private autoIdValue = Disabled + + /// Specify the automatic ID generation strategy used by the library + [] + let useAutoIdStrategy it = + autoIdValue <- it + + /// Retrieve the currently configured automatic ID generation strategy + [] + let autoIdStrategy () = + autoIdValue + + /// The length of automatically generated random strings + let mutable private idStringLengthValue = 16 + + /// Specify the length of automatically generated random strings + [] + let useIdStringLength length = + idStringLengthValue <- length + + /// Retrieve the currently configured length of automatically generated random strings + [] + let idStringLength () = + idStringLengthValue /// Query construction functions diff --git a/src/Tests.CSharp/CommonCSharpTests.cs b/src/Tests.CSharp/CommonCSharpTests.cs index f0d81f9..0f07f8f 100644 --- a/src/Tests.CSharp/CommonCSharpTests.cs +++ b/src/Tests.CSharp/CommonCSharpTests.cs @@ -70,6 +70,34 @@ public static class CommonCSharpTests { Configuration.UseIdField("Id"); } + }), + TestCase("UseAutoIdStrategy / AutoIdStrategy succeeds", () => + { + try + { + Expect.equal(Configuration.AutoIdStrategy(), AutoId.Disabled, + "The default auto-ID strategy was incorrect"); + Configuration.UseAutoIdStrategy(AutoId.Guid); + Expect.equal(Configuration.AutoIdStrategy(), AutoId.Guid, + "The auto-ID strategy was not set correctly"); + } + finally + { + Configuration.UseAutoIdStrategy(AutoId.Disabled); + } + }), + TestCase("UseIdStringLength / IdStringLength succeeds", () => + { + try + { + Expect.equal(Configuration.IdStringLength(), 16, "The default ID string length was incorrect"); + Configuration.UseIdStringLength(33); + Expect.equal(Configuration.IdStringLength(), 33, "The ID string length was not set correctly"); + } + finally + { + Configuration.UseIdStringLength(16); + } }) ])), TestList("Op", @@ -295,6 +323,27 @@ public static class CommonCSharpTests "Counter should have advanced from previous call"); }) ]), + TestList("AutoId", + [ + TestCase("GenerateGuid succeeds", () => + { + var autoId = AutoId.GenerateGuid(); + Expect.isNotNull(autoId, "The GUID auto-ID should not have been null"); + Expect.stringHasLength(autoId, 32, "The GUID auto-ID should have been 32 characters long"); + Expect.equal(autoId, autoId.ToLowerInvariant(), "The GUID auto-ID should have been lowercase"); + }), + TestCase("GenerateRandomString succeeds", () => + { + foreach (var length in (int[])[6, 8, 12, 20, 32, 57, 64]) + { + var autoId = AutoId.GenerateRandomString(length); + Expect.isNotNull(autoId, $"Random string ({length}) should not have been null"); + Expect.stringHasLength(autoId, length, $"Random string should have been {length} characters long"); + Expect.equal(autoId, autoId.ToLowerInvariant(), + $"Random string ({length}) should have been lowercase"); + } + }) + ]), TestList("Query", [ TestCase("StatementWhere succeeds", () => diff --git a/src/Tests/CommonTests.fs b/src/Tests/CommonTests.fs index 853f4b2..46b69c9 100644 --- a/src/Tests/CommonTests.fs +++ b/src/Tests/CommonTests.fs @@ -6,320 +6,399 @@ open Expecto /// Test table name let tbl = "test_table" -/// Tests which do not hit the database -let all = - testList "Common" [ - testList "Op" [ - test "EQ succeeds" { - Expect.equal (string EQ) "=" "The equals operator was not correct" - } - test "GT succeeds" { - Expect.equal (string GT) ">" "The greater than operator was not correct" - } - test "GE succeeds" { - Expect.equal (string GE) ">=" "The greater than or equal to operator was not correct" - } - test "LT succeeds" { - Expect.equal (string LT) "<" "The less than operator was not correct" - } - test "LE succeeds" { - Expect.equal (string LE) "<=" "The less than or equal to operator was not correct" - } - test "NE succeeds" { - Expect.equal (string NE) "<>" "The not equal to operator was not correct" - } - test "BT succeeds" { - Expect.equal (string BT) "BETWEEN" """The "between" operator was not correct""" - } - test "EX succeeds" { - Expect.equal (string EX) "IS NOT NULL" """The "exists" operator was not correct""" - } - test "NEX succeeds" { - Expect.equal (string NEX) "IS NULL" """The "not exists" operator was not correct""" - } - ] - testList "Field" [ - test "EQ succeeds" { - let field = Field.EQ "Test" 14 - Expect.equal field.Name "Test" "Field name incorrect" - Expect.equal field.Op EQ "Operator incorrect" - Expect.equal field.Value 14 "Value incorrect" - Expect.isNone field.ParameterName "The default parameter name should be None" - Expect.isNone field.Qualifier "The default table qualifier should be None" - } - test "GT succeeds" { - let field = Field.GT "Great" "night" - Expect.equal field.Name "Great" "Field name incorrect" - Expect.equal field.Op GT "Operator incorrect" - Expect.equal field.Value "night" "Value incorrect" - Expect.isNone field.ParameterName "The default parameter name should be None" - Expect.isNone field.Qualifier "The default table qualifier should be None" - } - test "GE succeeds" { - let field = Field.GE "Nice" 88L - Expect.equal field.Name "Nice" "Field name incorrect" - Expect.equal field.Op GE "Operator incorrect" - Expect.equal field.Value 88L "Value incorrect" - Expect.isNone field.ParameterName "The default parameter name should be None" - Expect.isNone field.Qualifier "The default table qualifier should be None" - } - test "LT succeeds" { - let field = Field.LT "Lesser" "seven" - Expect.equal field.Name "Lesser" "Field name incorrect" - Expect.equal field.Op LT "Operator incorrect" - Expect.equal field.Value "seven" "Value incorrect" - Expect.isNone field.ParameterName "The default parameter name should be None" - Expect.isNone field.Qualifier "The default table qualifier should be None" - } - test "LE succeeds" { - let field = Field.LE "Nobody" "KNOWS"; - Expect.equal field.Name "Nobody" "Field name incorrect" - Expect.equal field.Op LE "Operator incorrect" - Expect.equal field.Value "KNOWS" "Value incorrect" - Expect.isNone field.ParameterName "The default parameter name should be None" - Expect.isNone field.Qualifier "The default table qualifier should be None" - } - test "NE succeeds" { - let field = Field.NE "Park" "here" - Expect.equal field.Name "Park" "Field name incorrect" - Expect.equal field.Op NE "Operator incorrect" - Expect.equal field.Value "here" "Value incorrect" - Expect.isNone field.ParameterName "The default parameter name should be None" - Expect.isNone field.Qualifier "The default table qualifier should be None" - } - test "BT succeeds" { - let field = Field.BT "Age" 18 49 - Expect.equal field.Name "Age" "Field name incorrect" - Expect.equal field.Op BT "Operator incorrect" - Expect.sequenceEqual (field.Value :?> obj list) [ 18; 49 ] "Value incorrect" - Expect.isNone field.ParameterName "The default parameter name should be None" - Expect.isNone field.Qualifier "The default table qualifier should be None" - } - test "EX succeeds" { - let field = Field.EX "Groovy" - Expect.equal field.Name "Groovy" "Field name incorrect" - Expect.equal field.Op EX "Operator incorrect" - Expect.isNone field.ParameterName "The default parameter name should be None" - Expect.isNone field.Qualifier "The default table qualifier should be None" - } - test "NEX succeeds" { - let field = Field.NEX "Rad" - Expect.equal field.Name "Rad" "Field name incorrect" - Expect.equal field.Op NEX "Operator incorrect" - Expect.isNone field.ParameterName "The default parameter name should be None" - Expect.isNone field.Qualifier "The default table qualifier should be None" - } - testList "NameToPath" [ - test "succeeds for PostgreSQL and a simple name" { - Expect.equal - "data->>'Simple'" (Field.NameToPath "Simple" PostgreSQL) "Path not constructed correctly" - } - test "succeeds for SQLite and a simple name" { - Expect.equal - "data->>'Simple'" (Field.NameToPath "Simple" SQLite) "Path not constructed correctly" - } - test "succeeds for PostgreSQL and a nested name" { - Expect.equal - "data#>>'{A,Long,Path,to,the,Property}'" - (Field.NameToPath "A.Long.Path.to.the.Property" PostgreSQL) - "Path not constructed correctly" - } - test "succeeds for SQLite and a nested name" { - Expect.equal - "data->>'A'->>'Long'->>'Path'->>'to'->>'the'->>'Property'" - (Field.NameToPath "A.Long.Path.to.the.Property" SQLite) - "Path not constructed correctly" - } - ] - test "WithParameterName succeeds" { - let field = (Field.EQ "Bob" "Tom").WithParameterName "@name" - Expect.isSome field.ParameterName "The parameter name should have been filled" - Expect.equal "@name" field.ParameterName.Value "The parameter name is incorrect" - } - test "WithQualifier succeeds" { - let field = (Field.EQ "Bill" "Matt").WithQualifier "joe" - Expect.isSome field.Qualifier "The table qualifier should have been filled" - Expect.equal "joe" field.Qualifier.Value "The table qualifier is incorrect" - } - testList "Path" [ - test "succeeds for a PostgreSQL single field with no qualifier" { - let field = Field.GE "SomethingCool" 18 - Expect.equal "data->>'SomethingCool'" (field.Path PostgreSQL) "The PostgreSQL path is incorrect" - } - test "succeeds for a PostgreSQL single field with a qualifier" { - let field = { Field.LT "SomethingElse" 9 with Qualifier = Some "this" } - Expect.equal - "this.data->>'SomethingElse'" (field.Path PostgreSQL) "The PostgreSQL path is incorrect" - } - test "succeeds for a PostgreSQL nested field with no qualifier" { - let field = Field.EQ "My.Nested.Field" "howdy" - Expect.equal "data#>>'{My,Nested,Field}'" (field.Path PostgreSQL) "The PostgreSQL path is incorrect" - } - test "succeeds for a PostgreSQL nested field with a qualifier" { - let field = { Field.EQ "Nest.Away" "doc" with Qualifier = Some "bird" } - Expect.equal "bird.data#>>'{Nest,Away}'" (field.Path PostgreSQL) "The PostgreSQL path is incorrect" - } - test "succeeds for a SQLite single field with no qualifier" { - let field = Field.GE "SomethingCool" 18 - Expect.equal "data->>'SomethingCool'" (field.Path SQLite) "The SQLite path is incorrect" - } - test "succeeds for a SQLite single field with a qualifier" { - let field = { Field.LT "SomethingElse" 9 with Qualifier = Some "this" } - Expect.equal "this.data->>'SomethingElse'" (field.Path SQLite) "The SQLite path is incorrect" - } - test "succeeds for a SQLite nested field with no qualifier" { - let field = Field.EQ "My.Nested.Field" "howdy" - Expect.equal "data->>'My'->>'Nested'->>'Field'" (field.Path SQLite) "The SQLite path is incorrect" - } - test "succeeds for a SQLite nested field with a qualifier" { - let field = { Field.EQ "Nest.Away" "doc" with Qualifier = Some "bird" } - Expect.equal "bird.data->>'Nest'->>'Away'" (field.Path SQLite) "The SQLite path is incorrect" - } - ] - ] - testList "FieldMatch.ToString" [ - test "succeeds for Any" { - Expect.equal (string Any) "OR" "SQL for Any is incorrect" - } - test "succeeds for All" { - Expect.equal (string All) "AND" "SQL for All is incorrect" - } - ] - testList "ParameterName.Derive" [ - test "succeeds with existing name" { - let name = ParameterName() - Expect.equal (name.Derive(Some "@taco")) "@taco" "Name should have been @taco" - Expect.equal (name.Derive None) "@field0" "Counter should not have advanced for named field" - } - test "succeeds with non-existent name" { - let name = ParameterName() - Expect.equal (name.Derive None) "@field0" "Anonymous field name should have been returned" - Expect.equal (name.Derive None) "@field1" "Counter should have advanced from previous call" - Expect.equal (name.Derive None) "@field2" "Counter should have advanced from previous call" - Expect.equal (name.Derive None) "@field3" "Counter should have advanced from previous call" - } - ] - testList "Query" [ - test "statementWhere succeeds" { - Expect.equal (Query.statementWhere "x" "y") "x WHERE y" "Statements not combined correctly" - } - testList "Definition" [ - test "ensureTableFor succeeds" { - Expect.equal - (Query.Definition.ensureTableFor "my.table" "JSONB") - "CREATE TABLE IF NOT EXISTS my.table (data JSONB NOT NULL)" - "CREATE TABLE statement not constructed correctly" - } - testList "ensureKey" [ - test "succeeds when a schema is present" { - Expect.equal - (Query.Definition.ensureKey "test.table" PostgreSQL) - "CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON test.table ((data->>'Id'))" - "CREATE INDEX for key statement with schema not constructed correctly" - } - test "succeeds when a schema is not present" { - Expect.equal - (Query.Definition.ensureKey "table" SQLite) - "CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON table ((data->>'Id'))" - "CREATE INDEX for key statement without schema not constructed correctly" - } - ] - testList "ensureIndexOn" [ - test "succeeds for multiple fields and directions" { - Expect.equal - (Query.Definition.ensureIndexOn - "test.table" "gibberish" [ "taco"; "guac DESC"; "salsa ASC" ] PostgreSQL) - ([ "CREATE INDEX IF NOT EXISTS idx_table_gibberish ON test.table " - "((data->>'taco'), (data->>'guac') DESC, (data->>'salsa') ASC)" ] - |> String.concat "") - "CREATE INDEX for multiple field statement incorrect" - } - test "succeeds for nested PostgreSQL field" { - Expect.equal - (Query.Definition.ensureIndexOn tbl "nest" [ "a.b.c" ] PostgreSQL) - $"CREATE INDEX IF NOT EXISTS idx_{tbl}_nest ON {tbl} ((data#>>'{{a,b,c}}'))" - "CREATE INDEX for nested PostgreSQL field incorrect" - } - test "succeeds for nested SQLite field" { - Expect.equal - (Query.Definition.ensureIndexOn tbl "nest" [ "a.b.c" ] SQLite) - $"CREATE INDEX IF NOT EXISTS idx_{tbl}_nest ON {tbl} ((data->>'a'->>'b'->>'c'))" - "CREATE INDEX for nested SQLite field incorrect" - } - ] - ] - test "insert succeeds" { - Expect.equal (Query.insert tbl) $"INSERT INTO {tbl} VALUES (@data)" "INSERT statement not correct" - } - test "save succeeds" { +/// Unit tests for the Op DU +let opTests = testList "Op" [ + test "EQ succeeds" { + Expect.equal (string EQ) "=" "The equals operator was not correct" + } + test "GT succeeds" { + Expect.equal (string GT) ">" "The greater than operator was not correct" + } + test "GE succeeds" { + Expect.equal (string GE) ">=" "The greater than or equal to operator was not correct" + } + test "LT succeeds" { + Expect.equal (string LT) "<" "The less than operator was not correct" + } + test "LE succeeds" { + Expect.equal (string LE) "<=" "The less than or equal to operator was not correct" + } + test "NE succeeds" { + Expect.equal (string NE) "<>" "The not equal to operator was not correct" + } + test "BT succeeds" { + Expect.equal (string BT) "BETWEEN" """The "between" operator was not correct""" + } + test "EX succeeds" { + Expect.equal (string EX) "IS NOT NULL" """The "exists" operator was not correct""" + } + test "NEX succeeds" { + Expect.equal (string NEX) "IS NULL" """The "not exists" operator was not correct""" + } +] + +/// Unit tests for the Field class +let fieldTests = testList "Field" [ + test "EQ succeeds" { + let field = Field.EQ "Test" 14 + Expect.equal field.Name "Test" "Field name incorrect" + Expect.equal field.Op EQ "Operator incorrect" + Expect.equal field.Value 14 "Value incorrect" + Expect.isNone field.ParameterName "The default parameter name should be None" + Expect.isNone field.Qualifier "The default table qualifier should be None" + } + test "GT succeeds" { + let field = Field.GT "Great" "night" + Expect.equal field.Name "Great" "Field name incorrect" + Expect.equal field.Op GT "Operator incorrect" + Expect.equal field.Value "night" "Value incorrect" + Expect.isNone field.ParameterName "The default parameter name should be None" + Expect.isNone field.Qualifier "The default table qualifier should be None" + } + test "GE succeeds" { + let field = Field.GE "Nice" 88L + Expect.equal field.Name "Nice" "Field name incorrect" + Expect.equal field.Op GE "Operator incorrect" + Expect.equal field.Value 88L "Value incorrect" + Expect.isNone field.ParameterName "The default parameter name should be None" + Expect.isNone field.Qualifier "The default table qualifier should be None" + } + test "LT succeeds" { + let field = Field.LT "Lesser" "seven" + Expect.equal field.Name "Lesser" "Field name incorrect" + Expect.equal field.Op LT "Operator incorrect" + Expect.equal field.Value "seven" "Value incorrect" + Expect.isNone field.ParameterName "The default parameter name should be None" + Expect.isNone field.Qualifier "The default table qualifier should be None" + } + test "LE succeeds" { + let field = Field.LE "Nobody" "KNOWS"; + Expect.equal field.Name "Nobody" "Field name incorrect" + Expect.equal field.Op LE "Operator incorrect" + Expect.equal field.Value "KNOWS" "Value incorrect" + Expect.isNone field.ParameterName "The default parameter name should be None" + Expect.isNone field.Qualifier "The default table qualifier should be None" + } + test "NE succeeds" { + let field = Field.NE "Park" "here" + Expect.equal field.Name "Park" "Field name incorrect" + Expect.equal field.Op NE "Operator incorrect" + Expect.equal field.Value "here" "Value incorrect" + Expect.isNone field.ParameterName "The default parameter name should be None" + Expect.isNone field.Qualifier "The default table qualifier should be None" + } + test "BT succeeds" { + let field = Field.BT "Age" 18 49 + Expect.equal field.Name "Age" "Field name incorrect" + Expect.equal field.Op BT "Operator incorrect" + Expect.sequenceEqual (field.Value :?> obj list) [ 18; 49 ] "Value incorrect" + Expect.isNone field.ParameterName "The default parameter name should be None" + Expect.isNone field.Qualifier "The default table qualifier should be None" + } + test "EX succeeds" { + let field = Field.EX "Groovy" + Expect.equal field.Name "Groovy" "Field name incorrect" + Expect.equal field.Op EX "Operator incorrect" + Expect.isNone field.ParameterName "The default parameter name should be None" + Expect.isNone field.Qualifier "The default table qualifier should be None" + } + test "NEX succeeds" { + let field = Field.NEX "Rad" + Expect.equal field.Name "Rad" "Field name incorrect" + Expect.equal field.Op NEX "Operator incorrect" + Expect.isNone field.ParameterName "The default parameter name should be None" + Expect.isNone field.Qualifier "The default table qualifier should be None" + } + testList "NameToPath" [ + test "succeeds for PostgreSQL and a simple name" { + Expect.equal "data->>'Simple'" (Field.NameToPath "Simple" PostgreSQL) "Path not constructed correctly" + } + test "succeeds for SQLite and a simple name" { + Expect.equal "data->>'Simple'" (Field.NameToPath "Simple" SQLite) "Path not constructed correctly" + } + test "succeeds for PostgreSQL and a nested name" { + Expect.equal + "data#>>'{A,Long,Path,to,the,Property}'" + (Field.NameToPath "A.Long.Path.to.the.Property" PostgreSQL) + "Path not constructed correctly" + } + test "succeeds for SQLite and a nested name" { + Expect.equal + "data->>'A'->>'Long'->>'Path'->>'to'->>'the'->>'Property'" + (Field.NameToPath "A.Long.Path.to.the.Property" SQLite) + "Path not constructed correctly" + } + ] + test "WithParameterName succeeds" { + let field = (Field.EQ "Bob" "Tom").WithParameterName "@name" + Expect.isSome field.ParameterName "The parameter name should have been filled" + Expect.equal "@name" field.ParameterName.Value "The parameter name is incorrect" + } + test "WithQualifier succeeds" { + let field = (Field.EQ "Bill" "Matt").WithQualifier "joe" + Expect.isSome field.Qualifier "The table qualifier should have been filled" + Expect.equal "joe" field.Qualifier.Value "The table qualifier is incorrect" + } + testList "Path" [ + test "succeeds for a PostgreSQL single field with no qualifier" { + let field = Field.GE "SomethingCool" 18 + Expect.equal "data->>'SomethingCool'" (field.Path PostgreSQL) "The PostgreSQL path is incorrect" + } + test "succeeds for a PostgreSQL single field with a qualifier" { + let field = { Field.LT "SomethingElse" 9 with Qualifier = Some "this" } + Expect.equal "this.data->>'SomethingElse'" (field.Path PostgreSQL) "The PostgreSQL path is incorrect" + } + test "succeeds for a PostgreSQL nested field with no qualifier" { + let field = Field.EQ "My.Nested.Field" "howdy" + Expect.equal "data#>>'{My,Nested,Field}'" (field.Path PostgreSQL) "The PostgreSQL path is incorrect" + } + test "succeeds for a PostgreSQL nested field with a qualifier" { + let field = { Field.EQ "Nest.Away" "doc" with Qualifier = Some "bird" } + Expect.equal "bird.data#>>'{Nest,Away}'" (field.Path PostgreSQL) "The PostgreSQL path is incorrect" + } + test "succeeds for a SQLite single field with no qualifier" { + let field = Field.GE "SomethingCool" 18 + Expect.equal "data->>'SomethingCool'" (field.Path SQLite) "The SQLite path is incorrect" + } + test "succeeds for a SQLite single field with a qualifier" { + let field = { Field.LT "SomethingElse" 9 with Qualifier = Some "this" } + Expect.equal "this.data->>'SomethingElse'" (field.Path SQLite) "The SQLite path is incorrect" + } + test "succeeds for a SQLite nested field with no qualifier" { + let field = Field.EQ "My.Nested.Field" "howdy" + Expect.equal "data->>'My'->>'Nested'->>'Field'" (field.Path SQLite) "The SQLite path is incorrect" + } + test "succeeds for a SQLite nested field with a qualifier" { + let field = { Field.EQ "Nest.Away" "doc" with Qualifier = Some "bird" } + Expect.equal "bird.data->>'Nest'->>'Away'" (field.Path SQLite) "The SQLite path is incorrect" + } + ] +] + +/// Unit tests for the FieldMatch DU +let fieldMatchTests = testList "FieldMatch.ToString" [ + test "succeeds for Any" { + Expect.equal (string Any) "OR" "SQL for Any is incorrect" + } + test "succeeds for All" { + Expect.equal (string All) "AND" "SQL for All is incorrect" + } +] + +/// Unit tests for the ParameterName class +let parameterNameTests = testList "ParameterName.Derive" [ + test "succeeds with existing name" { + let name = ParameterName() + Expect.equal (name.Derive(Some "@taco")) "@taco" "Name should have been @taco" + Expect.equal (name.Derive None) "@field0" "Counter should not have advanced for named field" + } + test "succeeds with non-existent name" { + let name = ParameterName() + Expect.equal (name.Derive None) "@field0" "Anonymous field name should have been returned" + Expect.equal (name.Derive None) "@field1" "Counter should have advanced from previous call" + Expect.equal (name.Derive None) "@field2" "Counter should have advanced from previous call" + Expect.equal (name.Derive None) "@field3" "Counter should have advanced from previous call" + } +] + +/// Unit tests for the AutoId DU +let autoIdTests = testList "AutoId" [ + test "GenerateGuid succeeds" { + let autoId = AutoId.GenerateGuid() + Expect.isNotNull autoId "The GUID auto-ID should not have been null" + Expect.stringHasLength autoId 32 "The GUID auto-ID should have been 32 characters long" + Expect.equal autoId (autoId.ToLowerInvariant ()) "The GUID auto-ID should have been lowercase" + } + test "GenerateRandomString succeeds" { + [ 6; 8; 12; 20; 32; 57; 64 ] + |> List.iter (fun length -> + let autoId = AutoId.GenerateRandomString length + Expect.isNotNull autoId $"Random string ({length}) should not have been null" + Expect.stringHasLength autoId length $"Random string should have been {length} characters long" + Expect.equal autoId (autoId.ToLowerInvariant ()) $"Random string ({length}) should have been lowercase") + } +] + +/// Unit tests for the Query module +let queryTests = testList "Query" [ + test "statementWhere succeeds" { + Expect.equal (Query.statementWhere "x" "y") "x WHERE y" "Statements not combined correctly" + } + testList "Definition" [ + test "ensureTableFor succeeds" { + Expect.equal + (Query.Definition.ensureTableFor "my.table" "JSONB") + "CREATE TABLE IF NOT EXISTS my.table (data JSONB NOT NULL)" + "CREATE TABLE statement not constructed correctly" + } + testList "ensureKey" [ + test "succeeds when a schema is present" { Expect.equal - (Query.save tbl) - $"INSERT INTO {tbl} VALUES (@data) ON CONFLICT ((data->>'Id')) DO UPDATE SET data = EXCLUDED.data" - "INSERT ON CONFLICT UPDATE statement not correct" + (Query.Definition.ensureKey "test.table" PostgreSQL) + "CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON test.table ((data->>'Id'))" + "CREATE INDEX for key statement with schema not constructed correctly" } - test "count succeeds" { - Expect.equal (Query.count tbl) $"SELECT COUNT(*) AS it FROM {tbl}" "Count query not correct" - } - test "exists succeeds" { + test "succeeds when a schema is not present" { Expect.equal - (Query.exists tbl "turkey") - $"SELECT EXISTS (SELECT 1 FROM {tbl} WHERE turkey) AS it" - "Exists query not correct" + (Query.Definition.ensureKey "table" SQLite) + "CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON table ((data->>'Id'))" + "CREATE INDEX for key statement without schema not constructed correctly" } - test "find succeeds" { - Expect.equal (Query.find tbl) $"SELECT data FROM {tbl}" "Find query not correct" + ] + testList "ensureIndexOn" [ + test "succeeds for multiple fields and directions" { + Expect.equal + (Query.Definition.ensureIndexOn + "test.table" "gibberish" [ "taco"; "guac DESC"; "salsa ASC" ] PostgreSQL) + ([ "CREATE INDEX IF NOT EXISTS idx_table_gibberish ON test.table " + "((data->>'taco'), (data->>'guac') DESC, (data->>'salsa') ASC)" ] + |> String.concat "") + "CREATE INDEX for multiple field statement incorrect" } - test "update succeeds" { - Expect.equal (Query.update tbl) $"UPDATE {tbl} SET data = @data" "Update query not correct" + test "succeeds for nested PostgreSQL field" { + Expect.equal + (Query.Definition.ensureIndexOn tbl "nest" [ "a.b.c" ] PostgreSQL) + $"CREATE INDEX IF NOT EXISTS idx_{tbl}_nest ON {tbl} ((data#>>'{{a,b,c}}'))" + "CREATE INDEX for nested PostgreSQL field incorrect" } - test "delete succeeds" { - Expect.equal (Query.delete tbl) $"DELETE FROM {tbl}" "Delete query not correct" + test "succeeds for nested SQLite field" { + Expect.equal + (Query.Definition.ensureIndexOn tbl "nest" [ "a.b.c" ] SQLite) + $"CREATE INDEX IF NOT EXISTS idx_{tbl}_nest ON {tbl} ((data->>'a'->>'b'->>'c'))" + "CREATE INDEX for nested SQLite field incorrect" } - testList "orderBy" [ - test "succeeds for no fields" { - Expect.equal (Query.orderBy [] PostgreSQL) "" "Order By should have been blank (PostgreSQL)" - Expect.equal (Query.orderBy [] SQLite) "" "Order By should have been blank (SQLite)" - } - test "succeeds for PostgreSQL with one field and no direction" { - Expect.equal - (Query.orderBy [ Field.Named "TestField" ] PostgreSQL) - " ORDER BY data->>'TestField'" - "Order By not constructed correctly" - } - test "succeeds for SQLite with one field and no direction" { - Expect.equal - (Query.orderBy [ Field.Named "TestField" ] SQLite) - " ORDER BY data->>'TestField'" - "Order By not constructed correctly" - } - test "succeeds for PostgreSQL with multiple fields and direction" { - Expect.equal - (Query.orderBy - [ Field.Named "Nested.Test.Field DESC"; Field.Named "AnotherField"; Field.Named "It DESC" ] - PostgreSQL) - " ORDER BY data#>>'{Nested,Test,Field}' DESC, data->>'AnotherField', data->>'It' DESC" - "Order By not constructed correctly" - } - test "succeeds for SQLite with multiple fields and direction" { - Expect.equal - (Query.orderBy - [ Field.Named "Nested.Test.Field DESC"; Field.Named "AnotherField"; Field.Named "It DESC" ] - SQLite) - " ORDER BY data->>'Nested'->>'Test'->>'Field' DESC, data->>'AnotherField', data->>'It' DESC" - "Order By not constructed correctly" - } - test "succeeds for PostgreSQL numeric fields" { - Expect.equal - (Query.orderBy [ Field.Named "n:Test" ] PostgreSQL) - " ORDER BY (data->>'Test')::numeric" - "Order By not constructed correctly for numeric field" - } - test "succeeds for SQLite numeric fields" { - Expect.equal - (Query.orderBy [ Field.Named "n:Test" ] SQLite) - " ORDER BY data->>'Test'" - "Order By not constructed correctly for numeric field" - } - ] ] ] + test "insert succeeds" { + Expect.equal (Query.insert tbl) $"INSERT INTO {tbl} VALUES (@data)" "INSERT statement not correct" + } + test "save succeeds" { + Expect.equal + (Query.save tbl) + $"INSERT INTO {tbl} VALUES (@data) ON CONFLICT ((data->>'Id')) DO UPDATE SET data = EXCLUDED.data" + "INSERT ON CONFLICT UPDATE statement not correct" + } + test "count succeeds" { + Expect.equal (Query.count tbl) $"SELECT COUNT(*) AS it FROM {tbl}" "Count query not correct" + } + test "exists succeeds" { + Expect.equal + (Query.exists tbl "turkey") + $"SELECT EXISTS (SELECT 1 FROM {tbl} WHERE turkey) AS it" + "Exists query not correct" + } + test "find succeeds" { + Expect.equal (Query.find tbl) $"SELECT data FROM {tbl}" "Find query not correct" + } + test "update succeeds" { + Expect.equal (Query.update tbl) $"UPDATE {tbl} SET data = @data" "Update query not correct" + } + test "delete succeeds" { + Expect.equal (Query.delete tbl) $"DELETE FROM {tbl}" "Delete query not correct" + } + testList "orderBy" [ + test "succeeds for no fields" { + Expect.equal (Query.orderBy [] PostgreSQL) "" "Order By should have been blank (PostgreSQL)" + Expect.equal (Query.orderBy [] SQLite) "" "Order By should have been blank (SQLite)" + } + test "succeeds for PostgreSQL with one field and no direction" { + Expect.equal + (Query.orderBy [ Field.Named "TestField" ] PostgreSQL) + " ORDER BY data->>'TestField'" + "Order By not constructed correctly" + } + test "succeeds for SQLite with one field and no direction" { + Expect.equal + (Query.orderBy [ Field.Named "TestField" ] SQLite) + " ORDER BY data->>'TestField'" + "Order By not constructed correctly" + } + test "succeeds for PostgreSQL with multiple fields and direction" { + Expect.equal + (Query.orderBy + [ Field.Named "Nested.Test.Field DESC"; Field.Named "AnotherField"; Field.Named "It DESC" ] + PostgreSQL) + " ORDER BY data#>>'{Nested,Test,Field}' DESC, data->>'AnotherField', data->>'It' DESC" + "Order By not constructed correctly" + } + test "succeeds for SQLite with multiple fields and direction" { + Expect.equal + (Query.orderBy + [ Field.Named "Nested.Test.Field DESC"; Field.Named "AnotherField"; Field.Named "It DESC" ] + SQLite) + " ORDER BY data->>'Nested'->>'Test'->>'Field' DESC, data->>'AnotherField', data->>'It' DESC" + "Order By not constructed correctly" + } + test "succeeds for PostgreSQL numeric fields" { + Expect.equal + (Query.orderBy [ Field.Named "n:Test" ] PostgreSQL) + " ORDER BY (data->>'Test')::numeric" + "Order By not constructed correctly for numeric field" + } + test "succeeds for SQLite numeric fields" { + Expect.equal + (Query.orderBy [ Field.Named "n:Test" ] SQLite) + " ORDER BY data->>'Test'" + "Order By not constructed correctly for numeric field" + } + ] +] + +/// Unit tests for the Configuration module +let configurationTests = testList "Configuration" [ + test "useSerializer succeeds" { + try + Configuration.useSerializer + { new IDocumentSerializer with + member _.Serialize<'T>(it: 'T) : string = """{"Overridden":true}""" + member _.Deserialize<'T>(it: string) : 'T = Unchecked.defaultof<'T> + } + + let serialized = Configuration.serializer().Serialize {| Foo = "howdy"; Bar = "bye" |} + Expect.equal serialized """{"Overridden":true}""" "Specified serializer was not used" + + let deserialized = Configuration.serializer().Deserialize """{"Something":"here"}""" + Expect.isNull deserialized "Specified serializer should have returned null" + finally + Configuration.useSerializer DocumentSerializer.``default`` + } + test "serializer returns configured serializer" { + Expect.isTrue (obj.ReferenceEquals(DocumentSerializer.``default``, Configuration.serializer ())) + "Serializer should have been the same" + } + test "useIdField / idField succeeds" { + try + Expect.equal (Configuration.idField ()) "Id" "The default configured ID field was incorrect" + Configuration.useIdField "id" + Expect.equal (Configuration.idField ()) "id" "useIdField did not set the ID field" + finally + Configuration.useIdField "Id" + } + test "useAutoIdStrategy / autoIdStrategy succeeds" { + try + Expect.equal (Configuration.autoIdStrategy ()) Disabled "The default auto-ID strategy was incorrect" + Configuration.useAutoIdStrategy Guid + Expect.equal (Configuration.autoIdStrategy ()) Guid "The auto-ID strategy was not set correctly" + finally + Configuration.useAutoIdStrategy Disabled + } + test "useIdStringLength / idStringLength succeeds" { + try + Expect.equal (Configuration.idStringLength ()) 16 "The default ID string length was incorrect" + Configuration.useIdStringLength 33 + Expect.equal (Configuration.idStringLength ()) 33 "The ID string length was not set correctly" + finally + Configuration.useIdStringLength 16 + } +] + +/// Tests which do not hit the database +let all = testList "Common" [ + opTests + fieldTests + fieldMatchTests + parameterNameTests + autoIdTests + queryTests + testSequenced configurationTests +] diff --git a/src/Tests/SqliteTests.fs b/src/Tests/SqliteTests.fs index 1754e72..16b91eb 100644 --- a/src/Tests/SqliteTests.fs +++ b/src/Tests/SqliteTests.fs @@ -144,32 +144,6 @@ let configurationTests = testList "Configuration" [ finally Configuration.useConnectionString "Data Source=:memory:" } - test "useSerializer succeeds" { - try - Configuration.useSerializer - { new IDocumentSerializer with - member _.Serialize<'T>(it: 'T) : string = """{"Overridden":true}""" - member _.Deserialize<'T>(it: string) : 'T = Unchecked.defaultof<'T> - } - - let serialized = Configuration.serializer().Serialize { Foo = "howdy"; Bar = "bye"} - Expect.equal serialized """{"Overridden":true}""" "Specified serializer was not used" - - let deserialized = Configuration.serializer().Deserialize """{"Something":"here"}""" - Expect.isNull deserialized "Specified serializer should have returned null" - finally - Configuration.useSerializer DocumentSerializer.``default`` - } - test "serializer returns configured serializer" { - Expect.isTrue (obj.ReferenceEquals(DocumentSerializer.``default``, Configuration.serializer ())) - "Serializer should have been the same" - } - test "useIdField / idField succeeds" { - Expect.equal (Configuration.idField ()) "Id" "The default configured ID field was incorrect" - Configuration.useIdField "id" - Expect.equal (Configuration.idField ()) "id" "useIdField did not set the ID field" - Configuration.useIdField "Id" - } ] /// Integration tests for the Custom module of the SQLite library