Version 4 #6
|
@ -1,5 +1,7 @@
|
||||||
namespace BitBadger.Documents
|
namespace BitBadger.Documents
|
||||||
|
|
||||||
|
open System.Security.Cryptography
|
||||||
|
|
||||||
/// The types of logical operations available for JSON fields
|
/// The types of logical operations available for JSON fields
|
||||||
[<Struct>]
|
[<Struct>]
|
||||||
type Op =
|
type Op =
|
||||||
|
@ -148,6 +150,36 @@ type ParameterName() =
|
||||||
currentIdx <- currentIdx + 1
|
currentIdx <- currentIdx + 1
|
||||||
$"@field{currentIdx}"
|
$"@field{currentIdx}"
|
||||||
|
|
||||||
|
#if NET6_0
|
||||||
|
open System.Text
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// Automatically-generated document ID options
|
||||||
|
[<Struct>]
|
||||||
|
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
|
/// The required document serialization implementation
|
||||||
type IDocumentSerializer =
|
type IDocumentSerializer =
|
||||||
|
@ -200,7 +232,7 @@ module Configuration =
|
||||||
serializerValue
|
serializerValue
|
||||||
|
|
||||||
/// The serialized name of the ID field for documents
|
/// 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
|
/// Specify the name of the ID field for documents
|
||||||
[<CompiledName "UseIdField">]
|
[<CompiledName "UseIdField">]
|
||||||
|
@ -212,6 +244,32 @@ module Configuration =
|
||||||
let idField () =
|
let idField () =
|
||||||
idFieldValue
|
idFieldValue
|
||||||
|
|
||||||
|
/// The automatic ID strategy used by the library
|
||||||
|
let mutable private autoIdValue = Disabled
|
||||||
|
|
||||||
|
/// Specify the automatic ID generation strategy used by the library
|
||||||
|
[<CompiledName "UseAutoIdStrategy">]
|
||||||
|
let useAutoIdStrategy it =
|
||||||
|
autoIdValue <- it
|
||||||
|
|
||||||
|
/// Retrieve the currently configured automatic ID generation strategy
|
||||||
|
[<CompiledName "AutoIdStrategy">]
|
||||||
|
let autoIdStrategy () =
|
||||||
|
autoIdValue
|
||||||
|
|
||||||
|
/// The length of automatically generated random strings
|
||||||
|
let mutable private idStringLengthValue = 16
|
||||||
|
|
||||||
|
/// Specify the length of automatically generated random strings
|
||||||
|
[<CompiledName "UseIdStringLength">]
|
||||||
|
let useIdStringLength length =
|
||||||
|
idStringLengthValue <- length
|
||||||
|
|
||||||
|
/// Retrieve the currently configured length of automatically generated random strings
|
||||||
|
[<CompiledName "IdStringLength">]
|
||||||
|
let idStringLength () =
|
||||||
|
idStringLengthValue
|
||||||
|
|
||||||
|
|
||||||
/// Query construction functions
|
/// Query construction functions
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
|
|
|
@ -70,6 +70,34 @@ public static class CommonCSharpTests
|
||||||
{
|
{
|
||||||
Configuration.UseIdField("Id");
|
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",
|
TestList("Op",
|
||||||
|
@ -295,6 +323,27 @@ public static class CommonCSharpTests
|
||||||
"Counter should have advanced from previous call");
|
"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",
|
TestList("Query",
|
||||||
[
|
[
|
||||||
TestCase("StatementWhere succeeds", () =>
|
TestCase("StatementWhere succeeds", () =>
|
||||||
|
|
|
@ -6,10 +6,8 @@ open Expecto
|
||||||
/// Test table name
|
/// Test table name
|
||||||
let tbl = "test_table"
|
let tbl = "test_table"
|
||||||
|
|
||||||
/// Tests which do not hit the database
|
/// Unit tests for the Op DU
|
||||||
let all =
|
let opTests = testList "Op" [
|
||||||
testList "Common" [
|
|
||||||
testList "Op" [
|
|
||||||
test "EQ succeeds" {
|
test "EQ succeeds" {
|
||||||
Expect.equal (string EQ) "=" "The equals operator was not correct"
|
Expect.equal (string EQ) "=" "The equals operator was not correct"
|
||||||
}
|
}
|
||||||
|
@ -38,7 +36,9 @@ let all =
|
||||||
Expect.equal (string NEX) "IS NULL" """The "not exists" operator was not correct"""
|
Expect.equal (string NEX) "IS NULL" """The "not exists" operator was not correct"""
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
testList "Field" [
|
|
||||||
|
/// Unit tests for the Field class
|
||||||
|
let fieldTests = testList "Field" [
|
||||||
test "EQ succeeds" {
|
test "EQ succeeds" {
|
||||||
let field = Field.EQ "Test" 14
|
let field = Field.EQ "Test" 14
|
||||||
Expect.equal field.Name "Test" "Field name incorrect"
|
Expect.equal field.Name "Test" "Field name incorrect"
|
||||||
|
@ -111,12 +111,10 @@ let all =
|
||||||
}
|
}
|
||||||
testList "NameToPath" [
|
testList "NameToPath" [
|
||||||
test "succeeds for PostgreSQL and a simple name" {
|
test "succeeds for PostgreSQL and a simple name" {
|
||||||
Expect.equal
|
Expect.equal "data->>'Simple'" (Field.NameToPath "Simple" PostgreSQL) "Path not constructed correctly"
|
||||||
"data->>'Simple'" (Field.NameToPath "Simple" PostgreSQL) "Path not constructed correctly"
|
|
||||||
}
|
}
|
||||||
test "succeeds for SQLite and a simple name" {
|
test "succeeds for SQLite and a simple name" {
|
||||||
Expect.equal
|
Expect.equal "data->>'Simple'" (Field.NameToPath "Simple" SQLite) "Path not constructed correctly"
|
||||||
"data->>'Simple'" (Field.NameToPath "Simple" SQLite) "Path not constructed correctly"
|
|
||||||
}
|
}
|
||||||
test "succeeds for PostgreSQL and a nested name" {
|
test "succeeds for PostgreSQL and a nested name" {
|
||||||
Expect.equal
|
Expect.equal
|
||||||
|
@ -148,8 +146,7 @@ let all =
|
||||||
}
|
}
|
||||||
test "succeeds for a PostgreSQL single field with a qualifier" {
|
test "succeeds for a PostgreSQL single field with a qualifier" {
|
||||||
let field = { Field.LT "SomethingElse" 9 with Qualifier = Some "this" }
|
let field = { Field.LT "SomethingElse" 9 with Qualifier = Some "this" }
|
||||||
Expect.equal
|
Expect.equal "this.data->>'SomethingElse'" (field.Path PostgreSQL) "The PostgreSQL path is incorrect"
|
||||||
"this.data->>'SomethingElse'" (field.Path PostgreSQL) "The PostgreSQL path is incorrect"
|
|
||||||
}
|
}
|
||||||
test "succeeds for a PostgreSQL nested field with no qualifier" {
|
test "succeeds for a PostgreSQL nested field with no qualifier" {
|
||||||
let field = Field.EQ "My.Nested.Field" "howdy"
|
let field = Field.EQ "My.Nested.Field" "howdy"
|
||||||
|
@ -177,7 +174,9 @@ let all =
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
testList "FieldMatch.ToString" [
|
|
||||||
|
/// Unit tests for the FieldMatch DU
|
||||||
|
let fieldMatchTests = testList "FieldMatch.ToString" [
|
||||||
test "succeeds for Any" {
|
test "succeeds for Any" {
|
||||||
Expect.equal (string Any) "OR" "SQL for Any is incorrect"
|
Expect.equal (string Any) "OR" "SQL for Any is incorrect"
|
||||||
}
|
}
|
||||||
|
@ -185,7 +184,9 @@ let all =
|
||||||
Expect.equal (string All) "AND" "SQL for All is incorrect"
|
Expect.equal (string All) "AND" "SQL for All is incorrect"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
testList "ParameterName.Derive" [
|
|
||||||
|
/// Unit tests for the ParameterName class
|
||||||
|
let parameterNameTests = testList "ParameterName.Derive" [
|
||||||
test "succeeds with existing name" {
|
test "succeeds with existing name" {
|
||||||
let name = ParameterName()
|
let name = ParameterName()
|
||||||
Expect.equal (name.Derive(Some "@taco")) "@taco" "Name should have been @taco"
|
Expect.equal (name.Derive(Some "@taco")) "@taco" "Name should have been @taco"
|
||||||
|
@ -199,7 +200,27 @@ let all =
|
||||||
Expect.equal (name.Derive None) "@field3" "Counter should have advanced from previous call"
|
Expect.equal (name.Derive None) "@field3" "Counter should have advanced from previous call"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
testList "Query" [
|
|
||||||
|
/// 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" {
|
test "statementWhere succeeds" {
|
||||||
Expect.equal (Query.statementWhere "x" "y") "x WHERE y" "Statements not combined correctly"
|
Expect.equal (Query.statementWhere "x" "y") "x WHERE y" "Statements not combined correctly"
|
||||||
}
|
}
|
||||||
|
@ -322,4 +343,62 @@ let all =
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
/// 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<obj> """{"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
|
||||||
]
|
]
|
||||||
|
|
|
@ -144,32 +144,6 @@ let configurationTests = testList "Configuration" [
|
||||||
finally
|
finally
|
||||||
Configuration.useConnectionString "Data Source=:memory:"
|
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<obj> """{"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
|
/// Integration tests for the Custom module of the SQLite library
|
||||||
|
|
Loading…
Reference in New Issue
Block a user