Version 4 #6

Merged
danieljsummers merged 30 commits from version-four into main 2024-08-19 23:30:39 +00:00
2 changed files with 1348 additions and 1257 deletions
Showing only changes of commit d993f71788 - Show all commits

View File

@ -1,6 +1,5 @@
using Expecto.CSharp;
using Expecto;
using Microsoft.Data.Sqlite;
using Microsoft.FSharp.Core;
using BitBadger.Documents.Sqlite;
@ -14,19 +13,16 @@ using static Runner;
public static class SqliteCSharpTests
{
/// <summary>
/// Unit tests for the SQLite library
/// Unit tests for the Query module of the SQLite library
/// </summary>
private static readonly Test Unit = TestList("Unit",
[
TestList("Query",
private static readonly Test QueryTests = TestList("Query",
[
TestList("WhereByFields",
[
TestCase("succeeds for a single field when a logical operator is passed", () =>
{
Expect.equal(
Sqlite.Query.WhereByFields(FieldMatch.Any,
[Field.GT("theField", 0).WithParameterName("@test")]),
Sqlite.Query.WhereByFields(FieldMatch.Any, [Field.GT("theField", 0).WithParameterName("@test")]),
"data->>'theField' > @test", "WHERE clause not correct");
}),
TestCase("succeeds for a single field when an existence operator is passed", () =>
@ -44,8 +40,7 @@ public static class SqliteCSharpTests
TestCase("succeeds for all multiple fields with logical operators", () =>
{
Expect.equal(
Sqlite.Query.WhereByFields(FieldMatch.All,
[Field.EQ("theFirst", "1"), Field.EQ("numberTwo", "2")]),
Sqlite.Query.WhereByFields(FieldMatch.All, [Field.EQ("theFirst", "1"), Field.EQ("numberTwo", "2")]),
"data->>'theFirst' = @field0 AND data->>'numberTwo' = @field1", "WHERE clause not correct");
}),
TestCase("succeeds for any multiple fields with an existence operator", () =>
@ -80,8 +75,7 @@ public static class SqliteCSharpTests
}),
TestCase("ById succeeds", () =>
{
Expect.equal(Sqlite.Query.ById("test", "14"), "test WHERE data->>'Id' = @id",
"By-ID query not correct");
Expect.equal(Sqlite.Query.ById("test", "14"), "test WHERE data->>'Id' = @id", "By-ID query not correct");
}),
TestCase("ByFields succeeds", () =>
{
@ -93,8 +87,12 @@ public static class SqliteCSharpTests
Expect.equal(Sqlite.Query.Definition.EnsureTable("tbl"),
"CREATE TABLE IF NOT EXISTS tbl (data TEXT NOT NULL)", "CREATE TABLE statement not correct");
})
]),
TestList("Parameters",
]);
/// <summary>
/// Unit tests for the Parameters module of the SQLite library
/// </summary>
private static readonly Test ParametersTests = TestList("Parameters",
[
TestCase("Id succeeds", () =>
{
@ -127,10 +125,13 @@ public static class SqliteCSharpTests
{
Expect.isEmpty(Parameters.None, "The parameter list should have been empty");
})
])
// Results are exhaustively executed in the context of other tests
]);
// Results are exhaustively executed in the context of other tests
/// <summary>
/// Documents used for integration tests
/// </summary>
private static readonly List<JsonDocument> TestDocuments =
[
new() { Id = "one", Value = "FIRST!", NumValue = 0 },
@ -148,9 +149,10 @@ public static class SqliteCSharpTests
foreach (var doc in TestDocuments) await Document.Insert(SqliteDb.TableName, doc);
}
private static readonly Test Integration = TestList("Integration",
[
TestCase("Configuration.UseConnectionString succeeds", () =>
/// <summary>
/// Integration tests for the Configuration module of the SQLite library
/// </summary>
private static readonly Test ConfigurationTests = TestCase("Configuration.UseConnectionString succeeds", () =>
{
try
{
@ -162,8 +164,12 @@ public static class SqliteCSharpTests
{
Sqlite.Configuration.UseConnectionString("Data Source=:memory:");
}
}),
TestList("Custom",
});
/// <summary>
/// Integration tests for the Custom module of the SQLite library
/// </summary>
private static readonly Test CustomTests = TestList("Custom",
[
TestList("Single",
[
@ -173,7 +179,7 @@ public static class SqliteCSharpTests
await LoadDocs();
var doc = await Custom.Single($"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'Id' = @id",
new[] { Parameters.Id("one") }, Results.FromData<JsonDocument>);
[Parameters.Id("one")], Results.FromData<JsonDocument>);
Expect.isNotNull(doc, "There should have been a document returned");
Expect.equal(doc!.Id, "one", "The incorrect document was returned");
}),
@ -183,7 +189,7 @@ public static class SqliteCSharpTests
await LoadDocs();
var doc = await Custom.Single($"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'Id' = @id",
new[] { Parameters.Id("eighty") }, Results.FromData<JsonDocument>);
[Parameters.Id("eighty")], Results.FromData<JsonDocument>);
Expect.isNull(doc, "There should not have been a document returned");
})
]),
@ -204,8 +210,8 @@ public static class SqliteCSharpTests
await LoadDocs();
var docs = await Custom.List(
$"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'NumValue' > @value",
new[] { new SqliteParameter("@value", 100) }, Results.FromData<JsonDocument>);
$"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'NumValue' > @value", [new("@value", 100)],
Results.FromData<JsonDocument>);
Expect.isEmpty(docs, "There should have been no documents returned");
})
]),
@ -227,7 +233,7 @@ public static class SqliteCSharpTests
await LoadDocs();
await Custom.NonQuery($"DELETE FROM {SqliteDb.TableName} WHERE data ->> 'NumValue' > @value",
new[] { new SqliteParameter("@value", 100) });
[new("@value", 100)]);
var remaining = await Count.All(SqliteDb.TableName);
Expect.equal(remaining, 5L, "There should be 5 documents remaining in the table");
@ -240,8 +246,12 @@ public static class SqliteCSharpTests
var nbr = await Custom.Scalar("SELECT 5 AS test_value", Parameters.None, rdr => rdr.GetInt32(0));
Expect.equal(nbr, 5, "The query should have returned the number 5");
})
]),
TestList("Definition",
]);
/// <summary>
/// Integration tests for the Definition module of the SQLite library
/// </summary>
private static readonly Test DefinitionTests = TestList("Definition",
[
TestCase("EnsureTable succeeds", async () =>
{
@ -262,9 +272,8 @@ public static class SqliteCSharpTests
async ValueTask<bool> ItExists(string name)
{
return await Custom.Scalar(
$"SELECT EXISTS (SELECT 1 FROM {SqliteDb.Catalog} WHERE name = @name) AS it",
new SqliteParameter[] { new("@name", name) }, Results.ToExists);
return await Custom.Scalar($"SELECT EXISTS (SELECT 1 FROM {SqliteDb.Catalog} WHERE name = @name) AS it",
[new("@name", name)], Results.ToExists);
}
}),
TestCase("EnsureFieldIndex succeeds", async () =>
@ -275,7 +284,7 @@ public static class SqliteCSharpTests
Expect.isFalse(exists, "The index should not exist already");
await Definition.EnsureTable("ensured");
await Definition.EnsureFieldIndex("ensured", "test", new[] { "Id", "Category" });
await Definition.EnsureFieldIndex("ensured", "test", ["Id", "Category"]);
exists = await IndexExists();
Expect.isTrue(exists, "The index should now exist");
return;
@ -284,8 +293,14 @@ public static class SqliteCSharpTests
$"SELECT EXISTS (SELECT 1 FROM {SqliteDb.Catalog} WHERE name = 'idx_ensured_test') AS it",
Parameters.None, Results.ToExists);
})
]),
TestList("Document.Insert",
]);
/// <summary>
/// Integration tests for the Document module of the SQLite library
/// </summary>
private static readonly Test DocumentTests = TestList("Document",
[
TestList("Insert",
[
TestCase("succeeds", async () =>
{
@ -344,8 +359,13 @@ public static class SqliteCSharpTests
Expect.equal(after!.Id, "test", "The updated document is not correct");
Expect.isNull(after.Sub, "There should not have been a sub-document in the updated document");
})
]),
TestList("Count",
])
]);
/// <summary>
/// Integration tests for the Count module of the SQLite library
/// </summary>
private static readonly Test CountTests = TestList("Count",
[
TestCase("All succeeds", async () =>
{
@ -362,8 +382,7 @@ public static class SqliteCSharpTests
await using var db = await SqliteDb.BuildDb();
await LoadDocs();
var theCount = await Count.ByFields(SqliteDb.TableName, FieldMatch.Any,
[Field.BT("NumValue", 10, 20)]);
var theCount = await Count.ByFields(SqliteDb.TableName, FieldMatch.Any, [Field.BT("NumValue", 10, 20)]);
Expect.equal(theCount, 3L, "There should have been 3 matching documents");
}),
TestCase("succeeds for non-numeric range", async () =>
@ -376,8 +395,12 @@ public static class SqliteCSharpTests
Expect.equal(theCount, 1L, "There should have been 1 matching document");
})
])
]),
TestList("Exists",
]);
/// <summary>
/// Integration tests for the Exists module of the SQLite library
/// </summary>
private static readonly Test ExistsTests = TestList("Exists",
[
TestList("ById",
[
@ -413,13 +436,16 @@ public static class SqliteCSharpTests
await using var db = await SqliteDb.BuildDb();
await LoadDocs();
var exists = await Exists.ByFields(SqliteDb.TableName, FieldMatch.Any,
[Field.EQ("Nothing", "none")]);
var exists = await Exists.ByFields(SqliteDb.TableName, FieldMatch.Any, [Field.EQ("Nothing", "none")]);
Expect.isFalse(exists, "There should not have been any existing documents");
})
])
]),
TestList("Find",
]);
/// <summary>
/// Integration tests for the Find module of the SQLite library
/// </summary>
private static readonly Test FindTests = TestList("Find",
[
TestList("All",
[
@ -502,7 +528,7 @@ public static class SqliteCSharpTests
var doc = await Find.FirstByFields<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
[Field.EQ("Sub.Foo", "green")]);
Expect.isNotNull(doc, "There should have been a document returned");
Expect.contains(new[] { "two", "four" }, doc!.Id, "An incorrect document was returned");
Expect.contains(["two", "four"], doc!.Id, "An incorrect document was returned");
}),
TestCase("succeeds when a document is not found", async () =>
{
@ -514,8 +540,12 @@ public static class SqliteCSharpTests
Expect.isNull(doc, "There should not have been a document returned");
})
])
]),
TestList("Update",
]);
/// <summary>
/// Integration tests for the Update module of the SQLite library
/// </summary>
private static readonly Test UpdateTests = TestList("Update",
[
TestList("ById",
[
@ -573,8 +603,12 @@ public static class SqliteCSharpTests
new JsonDocument { Id = "one", Value = "le un", NumValue = 1 });
})
]),
]),
TestList("Patch",
]);
/// <summary>
/// Integration tests for the Patch module of the SQLite library
/// </summary>
private static readonly Test PatchTests = TestList("Patch",
[
TestList("ById",
[
@ -624,8 +658,12 @@ public static class SqliteCSharpTests
new { Foo = "green" });
})
])
]),
TestList("RemoveFields",
]);
/// <summary>
/// Integration tests for the RemoveFields module of the SQLite library
/// </summary>
private static readonly Test RemoveFieldsTests = TestList("RemoveFields",
[
TestList("ById",
[
@ -634,7 +672,7 @@ public static class SqliteCSharpTests
await using var db = await SqliteDb.BuildDb();
await LoadDocs();
await RemoveFields.ById(SqliteDb.TableName, "two", new[] { "Sub", "Value" });
await RemoveFields.ById(SqliteDb.TableName, "two", ["Sub", "Value"]);
var updated = await Find.ById<string, JsonDocument>(SqliteDb.TableName, "two");
Expect.isNotNull(updated, "The updated document should have been retrieved");
Expect.equal(updated.Value, "", "The string value should have been removed");
@ -646,14 +684,14 @@ public static class SqliteCSharpTests
await LoadDocs();
// This not raising an exception is the test
await RemoveFields.ById(SqliteDb.TableName, "two", new[] { "AFieldThatIsNotThere" });
await RemoveFields.ById(SqliteDb.TableName, "two", ["AFieldThatIsNotThere"]);
}),
TestCase("succeeds when no document is matched", async () =>
{
await using var db = await SqliteDb.BuildDb();
// This not raising an exception is the test
await RemoveFields.ById(SqliteDb.TableName, "two", new[] { "Value" });
await RemoveFields.ById(SqliteDb.TableName, "two", ["Value"]);
})
]),
TestList("ByFields",
@ -663,8 +701,7 @@ public static class SqliteCSharpTests
await using var db = await SqliteDb.BuildDb();
await LoadDocs();
await RemoveFields.ByFields(SqliteDb.TableName, FieldMatch.Any, [Field.EQ("NumValue", 17)],
new[] { "Sub" });
await RemoveFields.ByFields(SqliteDb.TableName, FieldMatch.Any, [Field.EQ("NumValue", 17)], ["Sub"]);
var updated = await Find.ById<string, JsonDocument>(SqliteDb.TableName, "four");
Expect.isNotNull(updated, "The updated document should have been retrieved");
Expect.isNull(updated.Sub, "The sub-document should have been removed");
@ -676,7 +713,7 @@ public static class SqliteCSharpTests
// This not raising an exception is the test
await RemoveFields.ByFields(SqliteDb.TableName, FieldMatch.Any, [Field.EQ("NumValue", 17)],
new[] { "Nothing" });
["Nothing"]);
}),
TestCase("succeeds when no document is matched", async () =>
{
@ -684,11 +721,15 @@ public static class SqliteCSharpTests
// This not raising an exception is the test
await RemoveFields.ByFields(SqliteDb.TableName, FieldMatch.Any, [Field.NE("Abracadabra", "apple")],
new[] { "Value" });
["Value"]);
})
])
]),
TestList("Delete",
]);
/// <summary>
/// Integration tests for the Delete module of the SQLite library
/// </summary>
private static readonly Test DeleteTests = TestList("Delete",
[
TestList("ById",
[
@ -732,13 +773,29 @@ public static class SqliteCSharpTests
Expect.equal(remaining, 5L, "There should have been 5 documents remaining");
})
])
]),
TestCase("Clean up database", () => Sqlite.Configuration.UseConnectionString("data source=:memory:"))
]);
/// <summary>
/// All tests for SQLite C# functions and methods
/// </summary>
[Tests]
public static readonly Test All = TestList("Sqlite.C#", [Unit, TestSequenced(Integration)]);
public static readonly Test All = TestList("Sqlite.C#",
[
TestList("Unit", [QueryTests, ParametersTests]),
TestSequenced(TestList("Integration",
[
ConfigurationTests,
CustomTests,
DefinitionTests,
DocumentTests,
CountTests,
ExistsTests,
FindTests,
UpdateTests,
PatchTests,
RemoveFieldsTests,
DeleteTests,
TestCase("Clean up database", () => Sqlite.Configuration.UseConnectionString("data source=:memory:"))
]))
]);
}

View File

@ -10,10 +10,10 @@ open Types
#nowarn "0044"
/// Unit tests for the SQLite library
let unitTests =
testList "Unit" [
testList "Query" [
(** UNIT TESTS **)
/// Unit tests for the Query module of the SQLite library
let queryTests = testList "Query" [
testList "whereByFields" [
test "succeeds for a single field when a logical operator is passed" {
Expect.equal
@ -82,8 +82,10 @@ let unitTests =
"CREATE TABLE IF NOT EXISTS tbl (data TEXT NOT NULL)"
"CREATE TABLE statement not correct"
}
]
testList "Parameters" [
]
/// Unit tests for the Parameters module of the SQLite library
let parametersTests = testList "Parameters" [
test "idParam succeeds" {
let theParam = idParam 7
Expect.equal theParam.ParameterName "@id" "The parameter name is incorrect"
@ -110,24 +112,28 @@ let unitTests =
test "noParams succeeds" {
Expect.isEmpty noParams "The parameter list should have been empty"
}
]
// Results are exhaustively executed in the context of other tests
]
]
// Results are exhaustively executed in the context of other tests
/// These tests each use a fresh copy of a SQLite database
let integrationTests =
let documents = [
(** 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 }
]
let loadDocs () = backgroundTask {
]
/// Load a table with the test documents
let loadDocs () = backgroundTask {
for doc in documents do do! insert SqliteDb.TableName doc
}
testList "Integration" [
testList "Configuration" [
}
/// Integration tests for the Configuration module of the SQLite library
let configurationTests = testList "Configuration" [
test "useConnectionString / connectionString succeed" {
try
Configuration.useConnectionString "Data Source=test.db"
@ -164,8 +170,10 @@ let integrationTests =
Expect.equal (Configuration.idField ()) "id" "useIdField did not set the ID field"
Configuration.useIdField "Id"
}
]
testList "Custom" [
]
/// Integration tests for the Custom module of the SQLite library
let customTests = testList "Custom" [
testList "single" [
testTask "succeeds when a row is found" {
use! db = SqliteDb.BuildDb()
@ -196,7 +204,7 @@ let integrationTests =
use! db = SqliteDb.BuildDb()
do! loadDocs ()
let! docs = Custom.list (Query.selectFromTable SqliteDb.TableName) [] fromData<JsonDocument>
let! docs = Custom.list (Query.find SqliteDb.TableName) [] fromData<JsonDocument>
Expect.hasCountOf docs 5u (fun _ -> true) "There should have been 5 documents returned"
}
testTask "succeeds when data is not found" {
@ -239,8 +247,10 @@ let integrationTests =
let! nbr = Custom.scalar "SELECT 5 AS test_value" [] _.GetInt32(0)
Expect.equal nbr 5 "The query should have returned the number 5"
}
]
testList "Definition" [
]
/// Integration tests for the Definition module of the SQLite library
let definitionTests = testList "Definition" [
testTask "ensureTable succeeds" {
use! db = SqliteDb.BuildDb()
let itExists (name: string) =
@ -276,7 +286,10 @@ let integrationTests =
let! exists' = indexExists ()
Expect.isTrue exists' "The index should now exist"
}
]
]
/// Integration tests for the (auto-opened) Document module of the SQLite library
let documentTests = testList "Document" [
testList "insert" [
testTask "succeeds" {
use! db = SqliteDb.BuildDb()
@ -324,7 +337,10 @@ let integrationTests =
Expect.equal after.Value upd8Doc "The updated document is not correct"
}
]
testList "Count" [
]
/// Integration tests for the Count module of the SQLite library
let countTests = testList "Count" [
testTask "all succeeds" {
use! db = SqliteDb.BuildDb()
do! loadDocs ()
@ -348,8 +364,10 @@ let integrationTests =
Expect.equal theCount 1L "There should have been 1 matching document"
}
]
]
testList "Exists" [
]
/// Integration tests for the Exists module of the SQLite library
let existsTests = testList "Exists" [
testList "byId" [
testTask "succeeds when a document exists" {
use! db = SqliteDb.BuildDb()
@ -382,8 +400,10 @@ let integrationTests =
Expect.isFalse exists "There should not have been any existing documents"
}
]
]
testList "Find" [
]
/// Integration tests for the Find module of the SQLite library
let findTests = testList "Find" [
testList "all" [
testTask "succeeds when there is data" {
use! db = SqliteDb.BuildDb()
@ -393,12 +413,10 @@ let integrationTests =
do! insert SqliteDb.TableName { Foo = "five"; Bar = "six" }
let! results = Find.all<SubDocument> SqliteDb.TableName
let expected = [
{ Foo = "one"; Bar = "two" }
{ Foo = "three"; Bar = "four" }
{ Foo = "five"; Bar = "six" }
]
Expect.equal results expected "There should have been 3 documents returned"
Expect.equal
results
[ { Foo = "one"; Bar = "two" }; { Foo = "three"; Bar = "four" }; { Foo = "five"; Bar = "six" } ]
"There should have been 3 documents returned"
}
testTask "succeeds when there is no data" {
use! db = SqliteDb.BuildDb()
@ -464,8 +482,10 @@ let integrationTests =
Expect.isFalse (Option.isSome doc) "There should not have been a document returned"
}
]
]
testList "Update" [
]
/// Integration tests for the Update module of the SQLite library
let updateTests = testList "Update" [
testList "byId" [
testTask "succeeds when a document is updated" {
use! db = SqliteDb.BuildDb()
@ -485,9 +505,7 @@ let integrationTests =
// This not raising an exception is the test
do! Update.byId
SqliteDb.TableName
"test"
{ emptyDoc with Id = "x"; Sub = Some { Foo = "blue"; Bar = "red" } }
SqliteDb.TableName "test" { emptyDoc with Id = "x"; Sub = Some { Foo = "blue"; Bar = "red" } }
}
]
testList "byFunc" [
@ -495,8 +513,7 @@ let integrationTests =
use! db = SqliteDb.BuildDb()
do! loadDocs ()
do! Update.byFunc
SqliteDb.TableName (_.Id) { Id = "one"; Value = "le un"; NumValue = 1; Sub = None }
do! Update.byFunc SqliteDb.TableName (_.Id) { Id = "one"; Value = "le un"; NumValue = 1; Sub = None }
let! after = Find.byId<string, JsonDocument> SqliteDb.TableName "one"
Expect.isSome after "There should have been a document returned post-update"
Expect.equal
@ -511,12 +528,13 @@ let integrationTests =
Expect.isEmpty before "There should have been no documents returned"
// This not raising an exception is the test
do! Update.byFunc
SqliteDb.TableName (_.Id) { Id = "one"; Value = "le un"; NumValue = 1; Sub = None }
do! Update.byFunc SqliteDb.TableName (_.Id) { Id = "one"; Value = "le un"; NumValue = 1; Sub = None }
}
]
]
testList "Patch" [
]
/// Integration tests for the Patch module of the SQLite library
let patchTests = testList "Patch" [
testList "byId" [
testTask "succeeds when a document is updated" {
use! db = SqliteDb.BuildDb()
@ -556,8 +574,10 @@ let integrationTests =
do! Patch.byFields SqliteDb.TableName Any [ Field.EQ "Value" "burgundy" ] {| Foo = "green" |}
}
]
]
testList "RemoveFields" [
]
/// Integration tests for the RemoveFields module of the SQLite library
let removeFieldsTests = testList "RemoveFields" [
testList "byId" [
testTask "succeeds when fields is removed" {
use! db = SqliteDb.BuildDb()
@ -612,8 +632,10 @@ let integrationTests =
do! RemoveFields.byFields SqliteDb.TableName Any [ Field.NE "Abracadabra" "apple" ] [ "Value" ]
}
]
]
testList "Delete" [
]
/// Integration tests for the Delete module of the SQLite library
let deleteTests = testList "Delete" [
testList "byId" [
testTask "succeeds when a document is deleted" {
use! db = SqliteDb.BuildDb()
@ -650,11 +672,23 @@ let integrationTests =
Expect.equal remaining 5L "There should have been 5 documents remaining"
}
]
]
test "clean up database" {
Configuration.useConnectionString "data source=:memory:"
}
]
|> testSequenced
]
let all = testList "Sqlite" [ unitTests; integrationTests ]
/// All tests for the SQLite library
let all = testList "Sqlite" [
testList "Unit" [ queryTests; parametersTests ]
testSequenced <| testList "Integration" [
configurationTests
customTests
definitionTests
documentTests
countTests
existsTests
findTests
updateTests
patchTests
removeFieldsTests
deleteTests
test "clean up database" { Configuration.useConnectionString "data source=:memory:" }
]
]