Implement auto IDs for both dbs
- Move SQLite WithConn doc into Document module
This commit is contained in:
parent
b357b5a01f
commit
f41fa1245a
|
@ -154,7 +154,7 @@ type ParameterName() =
|
||||||
open System.Text
|
open System.Text
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/// Automatically-generated document ID options
|
/// Automatically-generated document ID strategies
|
||||||
[<Struct>]
|
[<Struct>]
|
||||||
type AutoId =
|
type AutoId =
|
||||||
/// No automatic IDs will be generated
|
/// No automatic IDs will be generated
|
||||||
|
@ -180,6 +180,40 @@ with
|
||||||
|> function it -> it.Length <- length; it.ToString()
|
|> function it -> it.Length <- length; it.ToString()
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/// Does the given document need an automatic ID generated?
|
||||||
|
static member NeedsAutoId<'T> strategy (document: 'T) idProp =
|
||||||
|
match strategy with
|
||||||
|
| Disabled -> false
|
||||||
|
| _ ->
|
||||||
|
let prop = document.GetType().GetProperty idProp
|
||||||
|
if isNull prop then invalidOp $"{idProp} not found in document"
|
||||||
|
else
|
||||||
|
match strategy with
|
||||||
|
| Number ->
|
||||||
|
if prop.PropertyType = typeof<int8> then
|
||||||
|
let value = prop.GetValue document :?> int8
|
||||||
|
value = int8 0
|
||||||
|
elif prop.PropertyType = typeof<int16> then
|
||||||
|
let value = prop.GetValue document :?> int16
|
||||||
|
value = int16 0
|
||||||
|
elif prop.PropertyType = typeof<int> then
|
||||||
|
let value = prop.GetValue document :?> int
|
||||||
|
value = 0
|
||||||
|
elif prop.PropertyType = typeof<int64> then
|
||||||
|
let value = prop.GetValue document :?> int64
|
||||||
|
value = int64 0
|
||||||
|
else invalidOp "Document ID was not a number; cannot auto-generate a Number ID"
|
||||||
|
| Guid | RandomString ->
|
||||||
|
if prop.PropertyType = typeof<string> then
|
||||||
|
let value =
|
||||||
|
prop.GetValue document
|
||||||
|
|> Option.ofObj
|
||||||
|
|> Option.map (fun it -> it :?> string)
|
||||||
|
|> Option.defaultValue ""
|
||||||
|
value = ""
|
||||||
|
else invalidOp "Document ID was not a string; cannot auto-generate GUID or random string"
|
||||||
|
| Disabled -> false
|
||||||
|
|
||||||
|
|
||||||
/// The required document serialization implementation
|
/// The required document serialization implementation
|
||||||
type IDocumentSerializer =
|
type IDocumentSerializer =
|
||||||
|
|
|
@ -312,7 +312,23 @@ module WithProps =
|
||||||
/// Insert a new document
|
/// Insert a new document
|
||||||
[<CompiledName "Insert">]
|
[<CompiledName "Insert">]
|
||||||
let insert<'TDoc> tableName (document: 'TDoc) sqlProps =
|
let insert<'TDoc> tableName (document: 'TDoc) sqlProps =
|
||||||
Custom.nonQuery (Query.insert tableName) [ jsonParam "@data" document ] sqlProps
|
let query =
|
||||||
|
match Configuration.autoIdStrategy () with
|
||||||
|
| Disabled -> Query.insert tableName
|
||||||
|
| strategy ->
|
||||||
|
let idField = Configuration.idField ()
|
||||||
|
let dataParam =
|
||||||
|
if AutoId.NeedsAutoId strategy document idField then
|
||||||
|
match strategy with
|
||||||
|
| Number ->
|
||||||
|
$"' || (SELECT COALESCE(MAX((data->>'{idField}')::numeric), 0) + 1 FROM {tableName}) || '"
|
||||||
|
| Guid -> $"\"{AutoId.GenerateGuid()}\""
|
||||||
|
| RandomString -> $"\"{AutoId.GenerateRandomString(Configuration.idStringLength ())}\""
|
||||||
|
| Disabled -> "@data"
|
||||||
|
|> function it -> $"""@data::jsonb || ('{{"{idField}":{it}}}')::jsonb"""
|
||||||
|
else "@data"
|
||||||
|
(Query.insert tableName).Replace("@data", dataParam)
|
||||||
|
Custom.nonQuery query [ jsonParam "@data" document ] sqlProps
|
||||||
|
|
||||||
/// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
|
/// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
|
||||||
[<CompiledName "Save">]
|
[<CompiledName "Save">]
|
||||||
|
|
|
@ -35,11 +35,11 @@ module Extensions =
|
||||||
|
|
||||||
/// Insert a new document
|
/// Insert a new document
|
||||||
member conn.insert<'TDoc> tableName (document: 'TDoc) =
|
member conn.insert<'TDoc> tableName (document: 'TDoc) =
|
||||||
WithConn.insert<'TDoc> tableName document conn
|
WithConn.Document.insert<'TDoc> tableName document conn
|
||||||
|
|
||||||
/// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
|
/// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
|
||||||
member conn.save<'TDoc> tableName (document: 'TDoc) =
|
member conn.save<'TDoc> tableName (document: 'TDoc) =
|
||||||
WithConn.save tableName document conn
|
WithConn.Document.save tableName document conn
|
||||||
|
|
||||||
/// Count all documents in a table
|
/// Count all documents in a table
|
||||||
member conn.countAll tableName =
|
member conn.countAll tableName =
|
||||||
|
@ -159,12 +159,12 @@ type SqliteConnectionCSharpExtensions =
|
||||||
/// Insert a new document
|
/// Insert a new document
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline Insert<'TDoc>(conn, tableName, document: 'TDoc) =
|
static member inline Insert<'TDoc>(conn, tableName, document: 'TDoc) =
|
||||||
WithConn.insert<'TDoc> tableName document conn
|
WithConn.Document.insert<'TDoc> tableName document conn
|
||||||
|
|
||||||
/// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
|
/// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline Save<'TDoc>(conn, tableName, document: 'TDoc) =
|
static member inline Save<'TDoc>(conn, tableName, document: 'TDoc) =
|
||||||
WithConn.save<'TDoc> tableName document conn
|
WithConn.Document.save<'TDoc> tableName document conn
|
||||||
|
|
||||||
/// Count all documents in a table
|
/// Count all documents in a table
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
|
|
|
@ -258,10 +258,29 @@ module WithConn =
|
||||||
let ensureFieldIndex tableName indexName fields conn =
|
let ensureFieldIndex tableName indexName fields conn =
|
||||||
Custom.nonQuery (Query.Definition.ensureIndexOn tableName indexName fields SQLite) [] conn
|
Custom.nonQuery (Query.Definition.ensureIndexOn tableName indexName fields SQLite) [] conn
|
||||||
|
|
||||||
|
/// Commands to add documents
|
||||||
|
[<AutoOpen>]
|
||||||
|
module Document =
|
||||||
|
|
||||||
/// Insert a new document
|
/// Insert a new document
|
||||||
[<CompiledName "Insert">]
|
[<CompiledName "Insert">]
|
||||||
let insert<'TDoc> tableName (document: 'TDoc) conn =
|
let insert<'TDoc> tableName (document: 'TDoc) conn =
|
||||||
Custom.nonQuery (Query.insert tableName) [ jsonParam "@data" document ] conn
|
let query =
|
||||||
|
match Configuration.autoIdStrategy () with
|
||||||
|
| Disabled -> Query.insert tableName
|
||||||
|
| strategy ->
|
||||||
|
let idField = Configuration.idField ()
|
||||||
|
let dataParam =
|
||||||
|
if AutoId.NeedsAutoId strategy document idField then
|
||||||
|
match strategy with
|
||||||
|
| Number -> $"(SELECT coalesce(max(data->>'{idField}'), 0) + 1 FROM {tableName})"
|
||||||
|
| Guid -> $"'{AutoId.GenerateGuid()}'"
|
||||||
|
| RandomString -> $"'{AutoId.GenerateRandomString(Configuration.idStringLength ())}'"
|
||||||
|
| Disabled -> "@data"
|
||||||
|
|> function it -> $"json_set(@data, '$.{idField}', {it})"
|
||||||
|
else "@data"
|
||||||
|
(Query.insert tableName).Replace("@data", dataParam)
|
||||||
|
Custom.nonQuery query [ jsonParam "@data" document ] conn
|
||||||
|
|
||||||
/// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
|
/// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
|
||||||
[<CompiledName "Save">]
|
[<CompiledName "Save">]
|
||||||
|
@ -547,13 +566,13 @@ module Document =
|
||||||
[<CompiledName "Insert">]
|
[<CompiledName "Insert">]
|
||||||
let insert<'TDoc> tableName (document: 'TDoc) =
|
let insert<'TDoc> tableName (document: 'TDoc) =
|
||||||
use conn = Configuration.dbConn ()
|
use conn = Configuration.dbConn ()
|
||||||
WithConn.insert tableName document conn
|
WithConn.Document.insert tableName document conn
|
||||||
|
|
||||||
/// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
|
/// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
|
||||||
[<CompiledName "Save">]
|
[<CompiledName "Save">]
|
||||||
let save<'TDoc> tableName (document: 'TDoc) =
|
let save<'TDoc> tableName (document: 'TDoc) =
|
||||||
use conn = Configuration.dbConn ()
|
use conn = Configuration.dbConn ()
|
||||||
WithConn.save tableName document conn
|
WithConn.Document.save tableName document conn
|
||||||
|
|
||||||
|
|
||||||
/// Commands to count documents
|
/// Commands to count documents
|
||||||
|
|
|
@ -22,85 +22,9 @@ internal class TestSerializer : IDocumentSerializer
|
||||||
public static class CommonCSharpTests
|
public static class CommonCSharpTests
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unit tests
|
/// Unit tests for the Op enum
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Tests]
|
private static readonly Test OpTests = TestList("Op",
|
||||||
public static readonly Test Unit = TestList("Common.C# Unit",
|
|
||||||
[
|
|
||||||
TestSequenced(
|
|
||||||
TestList("Configuration",
|
|
||||||
[
|
|
||||||
TestCase("UseSerializer succeeds", () =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Configuration.UseSerializer(new TestSerializer());
|
|
||||||
|
|
||||||
var serialized = Configuration.Serializer().Serialize(new SubDocument
|
|
||||||
{
|
|
||||||
Foo = "howdy",
|
|
||||||
Bar = "bye"
|
|
||||||
});
|
|
||||||
Expect.equal(serialized, "{\"Overridden\":true}", "Specified serializer was not used");
|
|
||||||
|
|
||||||
var deserialized = Configuration.Serializer()
|
|
||||||
.Deserialize<object>("{\"Something\":\"here\"}");
|
|
||||||
Expect.isNull(deserialized, "Specified serializer should have returned null");
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Configuration.UseSerializer(DocumentSerializer.Default);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
TestCase("Serializer returns configured serializer", () =>
|
|
||||||
{
|
|
||||||
Expect.isTrue(ReferenceEquals(DocumentSerializer.Default, Configuration.Serializer()),
|
|
||||||
"Serializer should have been the same");
|
|
||||||
}),
|
|
||||||
TestCase("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");
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
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",
|
|
||||||
[
|
[
|
||||||
TestCase("EQ succeeds", () =>
|
TestCase("EQ succeeds", () =>
|
||||||
{
|
{
|
||||||
|
@ -138,8 +62,12 @@ public static class CommonCSharpTests
|
||||||
{
|
{
|
||||||
Expect.equal(Op.NEX.ToString(), "IS NULL", "The \"not exists\" operator was not correct");
|
Expect.equal(Op.NEX.ToString(), "IS NULL", "The \"not exists\" operator was not correct");
|
||||||
})
|
})
|
||||||
]),
|
]);
|
||||||
TestList("Field",
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unit tests for the Field class
|
||||||
|
/// </summary>
|
||||||
|
private static readonly Test FieldTests = TestList("Field",
|
||||||
[
|
[
|
||||||
TestCase("EQ succeeds", () =>
|
TestCase("EQ succeeds", () =>
|
||||||
{
|
{
|
||||||
|
@ -273,8 +201,7 @@ public static class CommonCSharpTests
|
||||||
TestCase("succeeds for a SQLite single field with a qualifier", () =>
|
TestCase("succeeds for a SQLite single field with a qualifier", () =>
|
||||||
{
|
{
|
||||||
var field = Field.LT("SomethingElse", 9).WithQualifier("this");
|
var field = Field.LT("SomethingElse", 9).WithQualifier("this");
|
||||||
Expect.equal("this.data->>'SomethingElse'", field.Path(Dialect.SQLite),
|
Expect.equal("this.data->>'SomethingElse'", field.Path(Dialect.SQLite), "The SQLite path is incorrect");
|
||||||
"The SQLite path is incorrect");
|
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds for a SQLite nested field with no qualifier", () =>
|
TestCase("succeeds for a SQLite nested field with no qualifier", () =>
|
||||||
{
|
{
|
||||||
|
@ -285,12 +212,15 @@ public static class CommonCSharpTests
|
||||||
TestCase("succeeds for a SQLite nested field with a qualifier", () =>
|
TestCase("succeeds for a SQLite nested field with a qualifier", () =>
|
||||||
{
|
{
|
||||||
var field = Field.EQ("Nest.Away", "doc").WithQualifier("bird");
|
var field = Field.EQ("Nest.Away", "doc").WithQualifier("bird");
|
||||||
Expect.equal("bird.data->>'Nest'->>'Away'", field.Path(Dialect.SQLite),
|
Expect.equal("bird.data->>'Nest'->>'Away'", field.Path(Dialect.SQLite), "The SQLite path is incorrect");
|
||||||
"The SQLite path is incorrect");
|
|
||||||
})
|
})
|
||||||
])
|
])
|
||||||
]),
|
]);
|
||||||
TestList("FieldMatch.ToString",
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unit tests for the FieldMatch enum
|
||||||
|
/// </summary>
|
||||||
|
private static readonly Test FieldMatchTests = TestList("FieldMatch.ToString",
|
||||||
[
|
[
|
||||||
TestCase("succeeds for Any", () =>
|
TestCase("succeeds for Any", () =>
|
||||||
{
|
{
|
||||||
|
@ -300,8 +230,12 @@ public static class CommonCSharpTests
|
||||||
{
|
{
|
||||||
Expect.equal(FieldMatch.All.ToString(), "AND", "SQL for All is incorrect");
|
Expect.equal(FieldMatch.All.ToString(), "AND", "SQL for All is incorrect");
|
||||||
})
|
})
|
||||||
]),
|
]);
|
||||||
TestList("ParameterName.Derive",
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unit tests for the ParameterName class
|
||||||
|
/// </summary>
|
||||||
|
private static readonly Test ParameterNameTests = TestList("ParameterName.Derive",
|
||||||
[
|
[
|
||||||
TestCase("succeeds with existing name", () =>
|
TestCase("succeeds with existing name", () =>
|
||||||
{
|
{
|
||||||
|
@ -322,8 +256,12 @@ public static class CommonCSharpTests
|
||||||
Expect.equal(name.Derive(FSharpOption<string>.None), "@field3",
|
Expect.equal(name.Derive(FSharpOption<string>.None), "@field3",
|
||||||
"Counter should have advanced from previous call");
|
"Counter should have advanced from previous call");
|
||||||
})
|
})
|
||||||
]),
|
]);
|
||||||
TestList("AutoId",
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unit tests for the AutoId enum
|
||||||
|
/// </summary>
|
||||||
|
private static readonly Test AutoIdTests = TestList("AutoId",
|
||||||
[
|
[
|
||||||
TestCase("GenerateGuid succeeds", () =>
|
TestCase("GenerateGuid succeeds", () =>
|
||||||
{
|
{
|
||||||
|
@ -342,9 +280,205 @@ public static class CommonCSharpTests
|
||||||
Expect.equal(autoId, autoId.ToLowerInvariant(),
|
Expect.equal(autoId, autoId.ToLowerInvariant(),
|
||||||
$"Random string ({length}) should have been lowercase");
|
$"Random string ({length}) should have been lowercase");
|
||||||
}
|
}
|
||||||
|
}),
|
||||||
|
TestList("NeedsAutoId",
|
||||||
|
[
|
||||||
|
TestCase("succeeds when no auto ID is configured", () =>
|
||||||
|
{
|
||||||
|
Expect.isFalse(AutoId.NeedsAutoId(AutoId.Disabled, new object(), "id"),
|
||||||
|
"Disabled auto-ID never needs an automatic ID");
|
||||||
|
}),
|
||||||
|
TestCase("fails for any when the ID property is not found", () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_ = AutoId.NeedsAutoId(AutoId.Number, new { Key = "" }, "Id");
|
||||||
|
Expect.isTrue(false, "Non-existent ID property should have thrown an exception");
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException)
|
||||||
|
{
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
TestCase("succeeds for byte when the ID is zero", () =>
|
||||||
|
{
|
||||||
|
Expect.isTrue(AutoId.NeedsAutoId(AutoId.Number, new { Id = (sbyte)0 }, "Id"),
|
||||||
|
"Zero ID should have returned true");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds for byte when the ID is non-zero", () =>
|
||||||
|
{
|
||||||
|
Expect.isFalse(AutoId.NeedsAutoId(AutoId.Number, new { Id = (sbyte)4 }, "Id"),
|
||||||
|
"Non-zero ID should have returned false");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds for short when the ID is zero", () =>
|
||||||
|
{
|
||||||
|
Expect.isTrue(AutoId.NeedsAutoId(AutoId.Number, new { Id = (short)0 }, "Id"),
|
||||||
|
"Zero ID should have returned true");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds for short when the ID is non-zero", () =>
|
||||||
|
{
|
||||||
|
Expect.isFalse(AutoId.NeedsAutoId(AutoId.Number, new { Id = (short)7 }, "Id"),
|
||||||
|
"Non-zero ID should have returned false");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds for int when the ID is zero", () =>
|
||||||
|
{
|
||||||
|
Expect.isTrue(AutoId.NeedsAutoId(AutoId.Number, new { Id = 0 }, "Id"),
|
||||||
|
"Zero ID should have returned true");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds for int when the ID is non-zero", () =>
|
||||||
|
{
|
||||||
|
Expect.isFalse(AutoId.NeedsAutoId(AutoId.Number, new { Id = 32 }, "Id"),
|
||||||
|
"Non-zero ID should have returned false");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds for long when the ID is zero", () =>
|
||||||
|
{
|
||||||
|
Expect.isTrue(AutoId.NeedsAutoId(AutoId.Number, new { Id = 0L }, "Id"),
|
||||||
|
"Zero ID should have returned true");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds for long when the ID is non-zero", () =>
|
||||||
|
{
|
||||||
|
Expect.isFalse(AutoId.NeedsAutoId(AutoId.Number, new { Id = 80L }, "Id"),
|
||||||
|
"Non-zero ID should have returned false");
|
||||||
|
}),
|
||||||
|
TestCase("fails for number when the ID is not a number", () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_ = AutoId.NeedsAutoId(AutoId.Number, new { Id = "" }, "Id");
|
||||||
|
Expect.isTrue(false, "Numeric ID against a string should have thrown an exception");
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException)
|
||||||
|
{
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
TestCase("succeeds for GUID when the ID is blank", () =>
|
||||||
|
{
|
||||||
|
Expect.isTrue(AutoId.NeedsAutoId(AutoId.Guid, new { Id = "" }, "Id"),
|
||||||
|
"Blank ID should have returned true");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds for GUID when the ID is filled", () =>
|
||||||
|
{
|
||||||
|
Expect.isFalse(AutoId.NeedsAutoId(AutoId.Guid, new { Id = "abc" }, "Id"),
|
||||||
|
"Filled ID should have returned false");
|
||||||
|
}),
|
||||||
|
TestCase("fails for GUID when the ID is not a string", () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_ = AutoId.NeedsAutoId(AutoId.Guid, new { Id = 8 }, "Id");
|
||||||
|
Expect.isTrue(false, "String ID against a number should have thrown an exception");
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException)
|
||||||
|
{
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
TestCase("succeeds for RandomString when the ID is blank", () =>
|
||||||
|
{
|
||||||
|
Expect.isTrue(AutoId.NeedsAutoId(AutoId.RandomString, new { Id = "" }, "Id"),
|
||||||
|
"Blank ID should have returned true");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds for RandomString when the ID is filled", () =>
|
||||||
|
{
|
||||||
|
Expect.isFalse(AutoId.NeedsAutoId(AutoId.RandomString, new { Id = "x" }, "Id"),
|
||||||
|
"Filled ID should have returned false");
|
||||||
|
}),
|
||||||
|
TestCase("fails for RandomString when the ID is not a string", () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_ = AutoId.NeedsAutoId(AutoId.RandomString, new { Id = 33 }, "Id");
|
||||||
|
Expect.isTrue(false, "String ID against a number should have thrown an exception");
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException)
|
||||||
|
{
|
||||||
|
// pass
|
||||||
|
}
|
||||||
})
|
})
|
||||||
]),
|
])
|
||||||
TestList("Query",
|
]);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unit tests for the Configuration static class
|
||||||
|
/// </summary>
|
||||||
|
private static readonly Test ConfigurationTests = TestList("Configuration",
|
||||||
|
[
|
||||||
|
TestCase("UseSerializer succeeds", () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Configuration.UseSerializer(new TestSerializer());
|
||||||
|
|
||||||
|
var serialized = Configuration.Serializer().Serialize(new SubDocument
|
||||||
|
{
|
||||||
|
Foo = "howdy",
|
||||||
|
Bar = "bye"
|
||||||
|
});
|
||||||
|
Expect.equal(serialized, "{\"Overridden\":true}", "Specified serializer was not used");
|
||||||
|
|
||||||
|
var deserialized = Configuration.Serializer()
|
||||||
|
.Deserialize<object>("{\"Something\":\"here\"}");
|
||||||
|
Expect.isNull(deserialized, "Specified serializer should have returned null");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Configuration.UseSerializer(DocumentSerializer.Default);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
TestCase("Serializer returns configured serializer", () =>
|
||||||
|
{
|
||||||
|
Expect.isTrue(ReferenceEquals(DocumentSerializer.Default, Configuration.Serializer()),
|
||||||
|
"Serializer should have been the same");
|
||||||
|
}),
|
||||||
|
TestCase("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");
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unit tests for the Query static class
|
||||||
|
/// </summary>
|
||||||
|
private static readonly Test QueryTests = TestList("Query",
|
||||||
[
|
[
|
||||||
TestCase("StatementWhere succeeds", () =>
|
TestCase("StatementWhere succeeds", () =>
|
||||||
{
|
{
|
||||||
|
@ -435,8 +569,7 @@ public static class CommonCSharpTests
|
||||||
[
|
[
|
||||||
TestCase("succeeds for no fields", () =>
|
TestCase("succeeds for no fields", () =>
|
||||||
{
|
{
|
||||||
Expect.equal(Query.OrderBy([], Dialect.PostgreSQL), "",
|
Expect.equal(Query.OrderBy([], Dialect.PostgreSQL), "", "Order By should have been blank (PostgreSQL)");
|
||||||
"Order By should have been blank (PostgreSQL)");
|
|
||||||
Expect.equal(Query.OrderBy([], Dialect.SQLite), "", "Order By should have been blank (SQLite)");
|
Expect.equal(Query.OrderBy([], Dialect.SQLite), "", "Order By should have been blank (SQLite)");
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds for PostgreSQL with one field and no direction", () =>
|
TestCase("succeeds for PostgreSQL with one field and no direction", () =>
|
||||||
|
@ -456,8 +589,7 @@ public static class CommonCSharpTests
|
||||||
[
|
[
|
||||||
Field.Named("Nested.Test.Field DESC"), Field.Named("AnotherField"),
|
Field.Named("Nested.Test.Field DESC"), Field.Named("AnotherField"),
|
||||||
Field.Named("It DESC")
|
Field.Named("It DESC")
|
||||||
],
|
], Dialect.PostgreSQL),
|
||||||
Dialect.PostgreSQL),
|
|
||||||
" ORDER BY data#>>'{Nested,Test,Field}' DESC, data->>'AnotherField', data->>'It' DESC",
|
" ORDER BY data#>>'{Nested,Test,Field}' DESC, data->>'AnotherField', data->>'It' DESC",
|
||||||
"Order By not constructed correctly");
|
"Order By not constructed correctly");
|
||||||
}),
|
}),
|
||||||
|
@ -468,8 +600,7 @@ public static class CommonCSharpTests
|
||||||
[
|
[
|
||||||
Field.Named("Nested.Test.Field DESC"), Field.Named("AnotherField"),
|
Field.Named("Nested.Test.Field DESC"), Field.Named("AnotherField"),
|
||||||
Field.Named("It DESC")
|
Field.Named("It DESC")
|
||||||
],
|
], Dialect.SQLite),
|
||||||
Dialect.SQLite),
|
|
||||||
" ORDER BY data->>'Nested'->>'Test'->>'Field' DESC, data->>'AnotherField', data->>'It' DESC",
|
" ORDER BY data->>'Nested'->>'Test'->>'Field' DESC, data->>'AnotherField', data->>'It' DESC",
|
||||||
"Order By not constructed correctly");
|
"Order By not constructed correctly");
|
||||||
}),
|
}),
|
||||||
|
@ -484,6 +615,20 @@ public static class CommonCSharpTests
|
||||||
"Order By not constructed correctly for numeric field");
|
"Order By not constructed correctly for numeric field");
|
||||||
})
|
})
|
||||||
])
|
])
|
||||||
])
|
]);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unit tests
|
||||||
|
/// </summary>
|
||||||
|
[Tests]
|
||||||
|
public static readonly Test Unit = TestList("Common.C# Unit",
|
||||||
|
[
|
||||||
|
OpTests,
|
||||||
|
FieldTests,
|
||||||
|
FieldMatchTests,
|
||||||
|
ParameterNameTests,
|
||||||
|
AutoIdTests,
|
||||||
|
QueryTests,
|
||||||
|
TestSequenced(ConfigurationTests)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -170,7 +170,11 @@ public static class PostgresCSharpTests
|
||||||
Expect.isTrue(false, "The parameter was not a StringArray type");
|
Expect.isTrue(false, "The parameter was not a StringArray type");
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
])
|
]),
|
||||||
|
TestCase("None succeeds", () =>
|
||||||
|
{
|
||||||
|
Expect.isEmpty(Parameters.None, "The no-params sequence should be empty");
|
||||||
|
})
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -310,15 +314,6 @@ public static class PostgresCSharpTests
|
||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Tests which do not hit the database
|
|
||||||
/// </summary>
|
|
||||||
private static readonly Test Unit = TestList("Unit",
|
|
||||||
[
|
|
||||||
ParametersTests,
|
|
||||||
QueryTests
|
|
||||||
]);
|
|
||||||
|
|
||||||
private static readonly List<JsonDocument> TestDocuments =
|
private static readonly List<JsonDocument> TestDocuments =
|
||||||
[
|
[
|
||||||
new() { Id = "one", Value = "FIRST!", NumValue = 0 },
|
new() { Id = "one", Value = "FIRST!", NumValue = 0 },
|
||||||
|
@ -548,6 +543,83 @@ public static class PostgresCSharpTests
|
||||||
{
|
{
|
||||||
// This is what should have happened
|
// This is what should have happened
|
||||||
}
|
}
|
||||||
|
}),
|
||||||
|
TestCase("succeeds when adding a numeric auto ID", async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Configuration.UseAutoIdStrategy(AutoId.Number);
|
||||||
|
Configuration.UseIdField("Key");
|
||||||
|
await using var db = PostgresDb.BuildDb();
|
||||||
|
var before = await Count.All(PostgresDb.TableName);
|
||||||
|
Expect.equal(before, 0, "There should be no documents in the table");
|
||||||
|
|
||||||
|
await Document.Insert(PostgresDb.TableName, new NumIdDocument { Text = "one" });
|
||||||
|
await Document.Insert(PostgresDb.TableName, new NumIdDocument { Text = "two" });
|
||||||
|
await Document.Insert(PostgresDb.TableName, new NumIdDocument { Key = 77, Text = "three" });
|
||||||
|
await Document.Insert(PostgresDb.TableName, new NumIdDocument { Text = "four" });
|
||||||
|
|
||||||
|
var after = await Find.AllOrdered<NumIdDocument>(PostgresDb.TableName, [Field.Named("n:Key")]);
|
||||||
|
Expect.hasLength(after, 4, "There should have been 4 documents returned");
|
||||||
|
Expect.sequenceEqual(after.Select(x => x.Key), [1, 2, 77, 78],
|
||||||
|
"The IDs were not generated correctly");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Configuration.UseAutoIdStrategy(AutoId.Disabled);
|
||||||
|
Configuration.UseIdField("Id");
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
TestCase("succeeds when adding a GUID auto ID", async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Configuration.UseAutoIdStrategy(AutoId.Guid);
|
||||||
|
await using var db = PostgresDb.BuildDb();
|
||||||
|
var before = await Count.All(PostgresDb.TableName);
|
||||||
|
Expect.equal(before, 0, "There should be no documents in the table");
|
||||||
|
|
||||||
|
await Document.Insert(PostgresDb.TableName, new JsonDocument { Value = "one" });
|
||||||
|
await Document.Insert(PostgresDb.TableName, new JsonDocument { Value = "two" });
|
||||||
|
await Document.Insert(PostgresDb.TableName, new JsonDocument { Id = "abc123", Value = "three" });
|
||||||
|
await Document.Insert(PostgresDb.TableName, new JsonDocument { Value = "four" });
|
||||||
|
|
||||||
|
var after = await Find.All<JsonDocument>(PostgresDb.TableName);
|
||||||
|
Expect.hasLength(after, 4, "There should have been 4 documents returned");
|
||||||
|
Expect.equal(after.Count(x => x.Id.Length == 32), 3, "Three of the IDs should have been GUIDs");
|
||||||
|
Expect.equal(after.Count(x => x.Id == "abc123"), 1, "The provided ID should have been used as-is");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Configuration.UseAutoIdStrategy(AutoId.Disabled);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
TestCase("succeeds when adding a RandomString auto ID", async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Configuration.UseAutoIdStrategy(AutoId.RandomString);
|
||||||
|
Configuration.UseIdStringLength(44);
|
||||||
|
await using var db = PostgresDb.BuildDb();
|
||||||
|
var before = await Count.All(PostgresDb.TableName);
|
||||||
|
Expect.equal(before, 0, "There should be no documents in the table");
|
||||||
|
|
||||||
|
await Document.Insert(PostgresDb.TableName, new JsonDocument { Value = "one" });
|
||||||
|
await Document.Insert(PostgresDb.TableName, new JsonDocument { Value = "two" });
|
||||||
|
await Document.Insert(PostgresDb.TableName, new JsonDocument { Id = "abc123", Value = "three" });
|
||||||
|
await Document.Insert(PostgresDb.TableName, new JsonDocument { Value = "four" });
|
||||||
|
|
||||||
|
var after = await Find.All<JsonDocument>(PostgresDb.TableName);
|
||||||
|
Expect.hasLength(after, 4, "There should have been 4 documents returned");
|
||||||
|
Expect.equal(after.Count(x => x.Id.Length == 44), 3,
|
||||||
|
"Three of the IDs should have been 44-character random strings");
|
||||||
|
Expect.equal(after.Count(x => x.Id == "abc123"), 1, "The provided ID should have been used as-is");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Configuration.UseAutoIdStrategy(AutoId.Disabled);
|
||||||
|
Configuration.UseIdStringLength(16);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
]),
|
]),
|
||||||
TestList("Save",
|
TestList("Save",
|
||||||
|
|
|
@ -325,9 +325,86 @@ public static class SqliteCSharpTests
|
||||||
{
|
{
|
||||||
// This is what is supposed to happen
|
// This is what is supposed to happen
|
||||||
}
|
}
|
||||||
|
}),
|
||||||
|
TestCase("succeeds when adding a numeric auto ID", async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Configuration.UseAutoIdStrategy(AutoId.Number);
|
||||||
|
Configuration.UseIdField("Key");
|
||||||
|
await using var db = await SqliteDb.BuildDb();
|
||||||
|
var before = await Count.All(SqliteDb.TableName);
|
||||||
|
Expect.equal(before, 0L, "There should be no documents in the table");
|
||||||
|
|
||||||
|
await Document.Insert(SqliteDb.TableName, new NumIdDocument { Text = "one" });
|
||||||
|
await Document.Insert(SqliteDb.TableName, new NumIdDocument { Text = "two" });
|
||||||
|
await Document.Insert(SqliteDb.TableName, new NumIdDocument { Key = 77, Text = "three" });
|
||||||
|
await Document.Insert(SqliteDb.TableName, new NumIdDocument { Text = "four" });
|
||||||
|
|
||||||
|
var after = await Find.AllOrdered<NumIdDocument>(SqliteDb.TableName, [Field.Named("Key")]);
|
||||||
|
Expect.hasLength(after, 4, "There should have been 4 documents returned");
|
||||||
|
Expect.sequenceEqual(after.Select(x => x.Key), [1, 2, 77, 78],
|
||||||
|
"The IDs were not generated correctly");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Configuration.UseAutoIdStrategy(AutoId.Disabled);
|
||||||
|
Configuration.UseIdField("Id");
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
TestCase("succeeds when adding a GUID auto ID", async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Configuration.UseAutoIdStrategy(AutoId.Guid);
|
||||||
|
await using var db = await SqliteDb.BuildDb();
|
||||||
|
var before = await Count.All(SqliteDb.TableName);
|
||||||
|
Expect.equal(before, 0L, "There should be no documents in the table");
|
||||||
|
|
||||||
|
await Document.Insert(SqliteDb.TableName, new JsonDocument { Value = "one" });
|
||||||
|
await Document.Insert(SqliteDb.TableName, new JsonDocument { Value = "two" });
|
||||||
|
await Document.Insert(SqliteDb.TableName, new JsonDocument { Id = "abc123", Value = "three" });
|
||||||
|
await Document.Insert(SqliteDb.TableName, new JsonDocument { Value = "four" });
|
||||||
|
|
||||||
|
var after = await Find.All<JsonDocument>(SqliteDb.TableName);
|
||||||
|
Expect.hasLength(after, 4, "There should have been 4 documents returned");
|
||||||
|
Expect.equal(after.Count(x => x.Id.Length == 32), 3, "Three of the IDs should have been GUIDs");
|
||||||
|
Expect.equal(after.Count(x => x.Id == "abc123"), 1, "The provided ID should have been used as-is");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Configuration.UseAutoIdStrategy(AutoId.Disabled);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
TestCase("succeeds when adding a RandomString auto ID", async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Configuration.UseAutoIdStrategy(AutoId.RandomString);
|
||||||
|
Configuration.UseIdStringLength(44);
|
||||||
|
await using var db = await SqliteDb.BuildDb();
|
||||||
|
var before = await Count.All(SqliteDb.TableName);
|
||||||
|
Expect.equal(before, 0L, "There should be no documents in the table");
|
||||||
|
|
||||||
|
await Document.Insert(SqliteDb.TableName, new JsonDocument { Value = "one" });
|
||||||
|
await Document.Insert(SqliteDb.TableName, new JsonDocument { Value = "two" });
|
||||||
|
await Document.Insert(SqliteDb.TableName, new JsonDocument { Id = "abc123", Value = "three" });
|
||||||
|
await Document.Insert(SqliteDb.TableName, new JsonDocument { Value = "four" });
|
||||||
|
|
||||||
|
var after = await Find.All<JsonDocument>(SqliteDb.TableName);
|
||||||
|
Expect.hasLength(after, 4, "There should have been 4 documents returned");
|
||||||
|
Expect.equal(after.Count(x => x.Id.Length == 44), 3,
|
||||||
|
"Three of the IDs should have been 44-character random strings");
|
||||||
|
Expect.equal(after.Count(x => x.Id == "abc123"), 1, "The provided ID should have been used as-is");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Configuration.UseAutoIdStrategy(AutoId.Disabled);
|
||||||
|
Configuration.UseIdStringLength(16);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
]),
|
]),
|
||||||
TestList("Document.Save",
|
TestList("Save",
|
||||||
[
|
[
|
||||||
TestCase("succeeds when a document is inserted", async () =>
|
TestCase("succeeds when a document is inserted", async () =>
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
namespace BitBadger.Documents.Tests.CSharp;
|
namespace BitBadger.Documents.Tests.CSharp;
|
||||||
|
|
||||||
|
public class NumIdDocument
|
||||||
|
{
|
||||||
|
public int Key { get; set; } = 0;
|
||||||
|
public string Text { get; set; } = "";
|
||||||
|
}
|
||||||
|
|
||||||
public class SubDocument
|
public class SubDocument
|
||||||
{
|
{
|
||||||
public string Foo { get; set; } = "";
|
public string Foo { get; set; } = "";
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="CommonTests.fs" />
|
|
||||||
<Compile Include="Types.fs" />
|
<Compile Include="Types.fs" />
|
||||||
|
<Compile Include="CommonTests.fs" />
|
||||||
<Compile Include="PostgresTests.fs" />
|
<Compile Include="PostgresTests.fs" />
|
||||||
<Compile Include="PostgresExtensionTests.fs" />
|
<Compile Include="PostgresExtensionTests.fs" />
|
||||||
<Compile Include="SqliteTests.fs" />
|
<Compile Include="SqliteTests.fs" />
|
||||||
|
|
|
@ -217,6 +217,115 @@ let autoIdTests = testList "AutoId" [
|
||||||
Expect.stringHasLength autoId length $"Random string should have been {length} characters long"
|
Expect.stringHasLength autoId length $"Random string should have been {length} characters long"
|
||||||
Expect.equal autoId (autoId.ToLowerInvariant ()) $"Random string ({length}) should have been lowercase")
|
Expect.equal autoId (autoId.ToLowerInvariant ()) $"Random string ({length}) should have been lowercase")
|
||||||
}
|
}
|
||||||
|
testList "NeedsAutoId" [
|
||||||
|
test "succeeds when no auto ID is configured" {
|
||||||
|
Expect.isFalse (AutoId.NeedsAutoId Disabled (obj ()) "id") "Disabled auto-ID never needs an automatic ID"
|
||||||
|
}
|
||||||
|
test "fails for any when the ID property is not found" {
|
||||||
|
Expect.throwsT<System.InvalidOperationException>
|
||||||
|
(fun () -> AutoId.NeedsAutoId Number {| Key = "" |} "Id" |> ignore)
|
||||||
|
"Non-existent ID property should have thrown an exception"
|
||||||
|
}
|
||||||
|
test "succeeds for byte when the ID is zero" {
|
||||||
|
Expect.isTrue (AutoId.NeedsAutoId Number {| Id = int8 0 |} "Id") "Zero ID should have returned true"
|
||||||
|
}
|
||||||
|
test "succeeds for byte when the ID is non-zero" {
|
||||||
|
Expect.isFalse (AutoId.NeedsAutoId Number {| Id = int8 4 |} "Id") "Non-zero ID should have returned false"
|
||||||
|
}
|
||||||
|
test "succeeds for short when the ID is zero" {
|
||||||
|
Expect.isTrue (AutoId.NeedsAutoId Number {| Id = int16 0 |} "Id") "Zero ID should have returned true"
|
||||||
|
}
|
||||||
|
test "succeeds for short when the ID is non-zero" {
|
||||||
|
Expect.isFalse (AutoId.NeedsAutoId Number {| Id = int16 7 |} "Id") "Non-zero ID should have returned false"
|
||||||
|
}
|
||||||
|
test "succeeds for int when the ID is zero" {
|
||||||
|
Expect.isTrue (AutoId.NeedsAutoId Number {| Id = 0 |} "Id") "Zero ID should have returned true"
|
||||||
|
}
|
||||||
|
test "succeeds for int when the ID is non-zero" {
|
||||||
|
Expect.isFalse (AutoId.NeedsAutoId Number {| Id = 32 |} "Id") "Non-zero ID should have returned false"
|
||||||
|
}
|
||||||
|
test "succeeds for long when the ID is zero" {
|
||||||
|
Expect.isTrue (AutoId.NeedsAutoId Number {| Id = 0L |} "Id") "Zero ID should have returned true"
|
||||||
|
}
|
||||||
|
test "succeeds for long when the ID is non-zero" {
|
||||||
|
Expect.isFalse (AutoId.NeedsAutoId Number {| Id = 80L |} "Id") "Non-zero ID should have returned false"
|
||||||
|
}
|
||||||
|
test "fails for number when the ID is not a number" {
|
||||||
|
Expect.throwsT<System.InvalidOperationException>
|
||||||
|
(fun () -> AutoId.NeedsAutoId Number {| Id = "" |} "Id" |> ignore)
|
||||||
|
"Numeric ID against a string should have thrown an exception"
|
||||||
|
}
|
||||||
|
test "succeeds for GUID when the ID is blank" {
|
||||||
|
Expect.isTrue (AutoId.NeedsAutoId Guid {| Id = "" |} "Id") "Blank ID should have returned true"
|
||||||
|
}
|
||||||
|
test "succeeds for GUID when the ID is filled" {
|
||||||
|
Expect.isFalse (AutoId.NeedsAutoId Guid {| Id = "abc" |} "Id") "Filled ID should have returned false"
|
||||||
|
}
|
||||||
|
test "fails for GUID when the ID is not a string" {
|
||||||
|
Expect.throwsT<System.InvalidOperationException>
|
||||||
|
(fun () -> AutoId.NeedsAutoId Guid {| Id = 8 |} "Id" |> ignore)
|
||||||
|
"String ID against a number should have thrown an exception"
|
||||||
|
}
|
||||||
|
test "succeeds for RandomString when the ID is blank" {
|
||||||
|
Expect.isTrue (AutoId.NeedsAutoId RandomString {| Id = "" |} "Id") "Blank ID should have returned true"
|
||||||
|
}
|
||||||
|
test "succeeds for RandomString when the ID is filled" {
|
||||||
|
Expect.isFalse (AutoId.NeedsAutoId RandomString {| Id = "x" |} "Id") "Filled ID should have returned false"
|
||||||
|
}
|
||||||
|
test "fails for RandomString when the ID is not a string" {
|
||||||
|
Expect.throwsT<System.InvalidOperationException>
|
||||||
|
(fun () -> AutoId.NeedsAutoId RandomString {| Id = 33 |} "Id" |> ignore)
|
||||||
|
"String ID against a number should have thrown an exception"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
/// Unit tests for the Query module
|
/// Unit tests for the Query module
|
||||||
|
@ -344,54 +453,6 @@ let queryTests = testList "Query" [
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
/// 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
|
/// Tests which do not hit the database
|
||||||
let all = testList "Common" [
|
let all = testList "Common" [
|
||||||
opTests
|
opTests
|
||||||
|
|
|
@ -252,18 +252,9 @@ let queryTests = testList "Query" [
|
||||||
open ThrowawayDb.Postgres
|
open ThrowawayDb.Postgres
|
||||||
open Types
|
open Types
|
||||||
|
|
||||||
/// Documents to use for integration tests
|
|
||||||
let documents = [
|
|
||||||
{ Id = "one"; Value = "FIRST!"; NumValue = 0; Sub = None }
|
|
||||||
{ Id = "two"; Value = "another"; NumValue = 10; Sub = Some { Foo = "green"; Bar = "blue" } }
|
|
||||||
{ Id = "three"; Value = ""; NumValue = 4; Sub = None }
|
|
||||||
{ Id = "four"; Value = "purple"; NumValue = 17; Sub = Some { Foo = "green"; Bar = "red" } }
|
|
||||||
{ Id = "five"; Value = "purple"; NumValue = 18; Sub = None }
|
|
||||||
]
|
|
||||||
|
|
||||||
/// Load the test documents into the database
|
/// Load the test documents into the database
|
||||||
let loadDocs () = backgroundTask {
|
let loadDocs () = backgroundTask {
|
||||||
for doc in documents do do! insert PostgresDb.TableName doc
|
for doc in testDocuments do do! insert PostgresDb.TableName doc
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Integration tests for the Configuration module of the PostgreSQL library
|
/// Integration tests for the Configuration module of the PostgreSQL library
|
||||||
|
@ -435,6 +426,68 @@ let documentTests = testList "Document" [
|
||||||
|> Async.RunSynchronously)
|
|> Async.RunSynchronously)
|
||||||
"An exception should have been raised for duplicate document ID insert"
|
"An exception should have been raised for duplicate document ID insert"
|
||||||
}
|
}
|
||||||
|
testTask "succeeds when adding a numeric auto ID" {
|
||||||
|
try
|
||||||
|
Configuration.useAutoIdStrategy Number
|
||||||
|
Configuration.useIdField "Key"
|
||||||
|
use db = PostgresDb.BuildDb()
|
||||||
|
let! before = Count.all PostgresDb.TableName
|
||||||
|
Expect.equal before 0 "There should be no documents in the table"
|
||||||
|
|
||||||
|
do! insert PostgresDb.TableName { Key = 0; Text = "one" }
|
||||||
|
do! insert PostgresDb.TableName { Key = 0; Text = "two" }
|
||||||
|
do! insert PostgresDb.TableName { Key = 77; Text = "three" }
|
||||||
|
do! insert PostgresDb.TableName { Key = 0; Text = "four" }
|
||||||
|
|
||||||
|
let! after = Find.allOrdered<NumIdDocument> PostgresDb.TableName [ Field.Named "n:Key" ]
|
||||||
|
Expect.hasLength after 4 "There should have been 4 documents returned"
|
||||||
|
Expect.equal (after |> List.map _.Key) [ 1; 2; 77; 78 ] "The IDs were not generated correctly"
|
||||||
|
finally
|
||||||
|
Configuration.useAutoIdStrategy Disabled
|
||||||
|
Configuration.useIdField "Id"
|
||||||
|
}
|
||||||
|
testTask "succeeds when adding a GUID auto ID" {
|
||||||
|
try
|
||||||
|
Configuration.useAutoIdStrategy Guid
|
||||||
|
use db = PostgresDb.BuildDb()
|
||||||
|
let! before = Count.all PostgresDb.TableName
|
||||||
|
Expect.equal before 0 "There should be no documents in the table"
|
||||||
|
|
||||||
|
do! insert PostgresDb.TableName { emptyDoc with Value = "one" }
|
||||||
|
do! insert PostgresDb.TableName { emptyDoc with Value = "two" }
|
||||||
|
do! insert PostgresDb.TableName { emptyDoc with Id = "abc123"; Value = "three" }
|
||||||
|
do! insert PostgresDb.TableName { emptyDoc with Value = "four" }
|
||||||
|
|
||||||
|
let! after = Find.all<JsonDocument> PostgresDb.TableName
|
||||||
|
Expect.hasLength after 4 "There should have been 4 documents returned"
|
||||||
|
Expect.hasCountOf after 3u (fun doc -> doc.Id.Length = 32) "Three of the IDs should have been GUIDs"
|
||||||
|
Expect.hasCountOf after 1u (fun doc -> doc.Id = "abc123") "The provided ID should have been used as-is"
|
||||||
|
finally
|
||||||
|
Configuration.useAutoIdStrategy Disabled
|
||||||
|
}
|
||||||
|
testTask "succeeds when adding a RandomString auto ID" {
|
||||||
|
try
|
||||||
|
Configuration.useAutoIdStrategy RandomString
|
||||||
|
Configuration.useIdStringLength 44
|
||||||
|
use db = PostgresDb.BuildDb()
|
||||||
|
let! before = Count.all PostgresDb.TableName
|
||||||
|
Expect.equal before 0 "There should be no documents in the table"
|
||||||
|
|
||||||
|
do! insert PostgresDb.TableName { emptyDoc with Value = "one" }
|
||||||
|
do! insert PostgresDb.TableName { emptyDoc with Value = "two" }
|
||||||
|
do! insert PostgresDb.TableName { emptyDoc with Id = "abc123"; Value = "three" }
|
||||||
|
do! insert PostgresDb.TableName { emptyDoc with Value = "four" }
|
||||||
|
|
||||||
|
let! after = Find.all<JsonDocument> PostgresDb.TableName
|
||||||
|
Expect.hasLength after 4 "There should have been 4 documents returned"
|
||||||
|
Expect.hasCountOf
|
||||||
|
after 3u (fun doc -> doc.Id.Length = 44)
|
||||||
|
"Three of the IDs should have been 44-character random strings"
|
||||||
|
Expect.hasCountOf after 1u (fun doc -> doc.Id = "abc123") "The provided ID should have been used as-is"
|
||||||
|
finally
|
||||||
|
Configuration.useAutoIdStrategy Disabled
|
||||||
|
Configuration.useIdStringLength 16
|
||||||
|
}
|
||||||
]
|
]
|
||||||
testList "save" [
|
testList "save" [
|
||||||
testTask "succeeds when a document is inserted" {
|
testTask "succeeds when a document is inserted" {
|
||||||
|
|
|
@ -118,18 +118,9 @@ let parametersTests = testList "Parameters" [
|
||||||
|
|
||||||
(** INTEGRATION TESTS **)
|
(** INTEGRATION TESTS **)
|
||||||
|
|
||||||
/// Documents used for integration tests
|
|
||||||
let documents = [
|
|
||||||
{ Id = "one"; Value = "FIRST!"; NumValue = 0; Sub = None }
|
|
||||||
{ Id = "two"; Value = "another"; NumValue = 10; Sub = Some { Foo = "green"; Bar = "blue" } }
|
|
||||||
{ Id = "three"; Value = ""; NumValue = 4; Sub = None }
|
|
||||||
{ Id = "four"; Value = "purple"; NumValue = 17; Sub = Some { Foo = "green"; Bar = "red" } }
|
|
||||||
{ Id = "five"; Value = "purple"; NumValue = 18; Sub = None }
|
|
||||||
]
|
|
||||||
|
|
||||||
/// Load a table with the test documents
|
/// Load a table with the test documents
|
||||||
let loadDocs () = backgroundTask {
|
let loadDocs () = backgroundTask {
|
||||||
for doc in documents do do! insert SqliteDb.TableName doc
|
for doc in testDocuments do do! insert SqliteDb.TableName doc
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Integration tests for the Configuration module of the SQLite library
|
/// Integration tests for the Configuration module of the SQLite library
|
||||||
|
@ -283,6 +274,68 @@ let documentTests = testList "Document" [
|
||||||
insert SqliteDb.TableName {emptyDoc with Id = "test" } |> Async.AwaitTask |> Async.RunSynchronously)
|
insert SqliteDb.TableName {emptyDoc with Id = "test" } |> Async.AwaitTask |> Async.RunSynchronously)
|
||||||
"An exception should have been raised for duplicate document ID insert"
|
"An exception should have been raised for duplicate document ID insert"
|
||||||
}
|
}
|
||||||
|
testTask "succeeds when adding a numeric auto ID" {
|
||||||
|
try
|
||||||
|
Configuration.useAutoIdStrategy Number
|
||||||
|
Configuration.useIdField "Key"
|
||||||
|
use! db = SqliteDb.BuildDb()
|
||||||
|
let! before = Count.all SqliteDb.TableName
|
||||||
|
Expect.equal before 0L "There should be no documents in the table"
|
||||||
|
|
||||||
|
do! insert SqliteDb.TableName { Key = 0; Text = "one" }
|
||||||
|
do! insert SqliteDb.TableName { Key = 0; Text = "two" }
|
||||||
|
do! insert SqliteDb.TableName { Key = 77; Text = "three" }
|
||||||
|
do! insert SqliteDb.TableName { Key = 0; Text = "four" }
|
||||||
|
|
||||||
|
let! after = Find.allOrdered<NumIdDocument> SqliteDb.TableName [ Field.Named "Key" ]
|
||||||
|
Expect.hasLength after 4 "There should have been 4 documents returned"
|
||||||
|
Expect.equal (after |> List.map _.Key) [ 1; 2; 77; 78 ] "The IDs were not generated correctly"
|
||||||
|
finally
|
||||||
|
Configuration.useAutoIdStrategy Disabled
|
||||||
|
Configuration.useIdField "Id"
|
||||||
|
}
|
||||||
|
testTask "succeeds when adding a GUID auto ID" {
|
||||||
|
try
|
||||||
|
Configuration.useAutoIdStrategy Guid
|
||||||
|
use! db = SqliteDb.BuildDb()
|
||||||
|
let! before = Count.all SqliteDb.TableName
|
||||||
|
Expect.equal before 0L "There should be no documents in the table"
|
||||||
|
|
||||||
|
do! insert SqliteDb.TableName { emptyDoc with Value = "one" }
|
||||||
|
do! insert SqliteDb.TableName { emptyDoc with Value = "two" }
|
||||||
|
do! insert SqliteDb.TableName { emptyDoc with Id = "abc123"; Value = "three" }
|
||||||
|
do! insert SqliteDb.TableName { emptyDoc with Value = "four" }
|
||||||
|
|
||||||
|
let! after = Find.all<JsonDocument> SqliteDb.TableName
|
||||||
|
Expect.hasLength after 4 "There should have been 4 documents returned"
|
||||||
|
Expect.hasCountOf after 3u (fun doc -> doc.Id.Length = 32) "Three of the IDs should have been GUIDs"
|
||||||
|
Expect.hasCountOf after 1u (fun doc -> doc.Id = "abc123") "The provided ID should have been used as-is"
|
||||||
|
finally
|
||||||
|
Configuration.useAutoIdStrategy Disabled
|
||||||
|
}
|
||||||
|
testTask "succeeds when adding a RandomString auto ID" {
|
||||||
|
try
|
||||||
|
Configuration.useAutoIdStrategy RandomString
|
||||||
|
Configuration.useIdStringLength 44
|
||||||
|
use! db = SqliteDb.BuildDb()
|
||||||
|
let! before = Count.all SqliteDb.TableName
|
||||||
|
Expect.equal before 0L "There should be no documents in the table"
|
||||||
|
|
||||||
|
do! insert SqliteDb.TableName { emptyDoc with Value = "one" }
|
||||||
|
do! insert SqliteDb.TableName { emptyDoc with Value = "two" }
|
||||||
|
do! insert SqliteDb.TableName { emptyDoc with Id = "abc123"; Value = "three" }
|
||||||
|
do! insert SqliteDb.TableName { emptyDoc with Value = "four" }
|
||||||
|
|
||||||
|
let! after = Find.all<JsonDocument> SqliteDb.TableName
|
||||||
|
Expect.hasLength after 4 "There should have been 4 documents returned"
|
||||||
|
Expect.hasCountOf
|
||||||
|
after 3u (fun doc -> doc.Id.Length = 44)
|
||||||
|
"Three of the IDs should have been 44-character random strings"
|
||||||
|
Expect.hasCountOf after 1u (fun doc -> doc.Id = "abc123") "The provided ID should have been used as-is"
|
||||||
|
finally
|
||||||
|
Configuration.useAutoIdStrategy Disabled
|
||||||
|
Configuration.useIdStringLength 16
|
||||||
|
}
|
||||||
]
|
]
|
||||||
testList "save" [
|
testList "save" [
|
||||||
testTask "succeeds when a document is inserted" {
|
testTask "succeeds when a document is inserted" {
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
module Types
|
module Types
|
||||||
|
|
||||||
|
type NumIdDocument =
|
||||||
|
{ Key: int
|
||||||
|
Text: string }
|
||||||
|
|
||||||
type SubDocument =
|
type SubDocument =
|
||||||
{ Foo: string
|
{ Foo: string
|
||||||
Bar: string }
|
Bar: string }
|
||||||
|
|
Loading…
Reference in New Issue
Block a user