diff --git a/src/BitBadger.Documents.sln b/src/BitBadger.Documents.sln index f3d49d0..00d5639 100644 --- a/src/BitBadger.Documents.sln +++ b/src/BitBadger.Documents.sln @@ -13,6 +13,8 @@ Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "BitBadger.Documents.Sqlite" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BitBadger.Documents.Tests.CSharp", "Tests.CSharp\BitBadger.Documents.Tests.CSharp.csproj", "{AB58418C-7F90-467E-8F67-F4E0AD9D8875}" EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "BitBadger.Documents.Sqlite.Extensions", "Sqlite.Extensions\BitBadger.Documents.Sqlite.Extensions.fsproj", "{D416A5C8-B746-4FDF-8EC9-9CA0B8DA1384}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -42,5 +44,9 @@ Global {AB58418C-7F90-467E-8F67-F4E0AD9D8875}.Debug|Any CPU.Build.0 = Debug|Any CPU {AB58418C-7F90-467E-8F67-F4E0AD9D8875}.Release|Any CPU.ActiveCfg = Release|Any CPU {AB58418C-7F90-467E-8F67-F4E0AD9D8875}.Release|Any CPU.Build.0 = Release|Any CPU + {D416A5C8-B746-4FDF-8EC9-9CA0B8DA1384}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D416A5C8-B746-4FDF-8EC9-9CA0B8DA1384}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D416A5C8-B746-4FDF-8EC9-9CA0B8DA1384}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D416A5C8-B746-4FDF-8EC9-9CA0B8DA1384}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/src/Sqlite.Extensions/BitBadger.Documents.Sqlite.Extensions.fsproj b/src/Sqlite.Extensions/BitBadger.Documents.Sqlite.Extensions.fsproj new file mode 100644 index 0000000..e01f9dc --- /dev/null +++ b/src/Sqlite.Extensions/BitBadger.Documents.Sqlite.Extensions.fsproj @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/Sqlite.Extensions/Library.fs b/src/Sqlite.Extensions/Library.fs new file mode 100644 index 0000000..011746e --- /dev/null +++ b/src/Sqlite.Extensions/Library.fs @@ -0,0 +1,216 @@ +namespace BitBadger.Documents.Sqlite + +open Microsoft.Data.Sqlite + +/// F# extensions for the SqliteConnection type +[] +module Extensions = + + type SqliteConnection with + + /// Execute a query that returns a list of results + member conn.customList<'TDoc> query parameters mapFunc = + WithConn.Custom.list<'TDoc> query parameters mapFunc conn + + /// Execute a query that returns one or no results + member conn.customSingle<'TDoc> query parameters mapFunc = + WithConn.Custom.single<'TDoc> query parameters mapFunc conn + + /// Execute a query that does not return a value + member conn.customNonQuery query parameters = + WithConn.Custom.nonQuery query parameters conn + + /// Execute a query that returns a scalar value + member conn.customScalar<'T when 'T: struct> query parameters mapFunc = + WithConn.Custom.scalar<'T> query parameters mapFunc conn + + /// Create a document table + member conn.ensureTable name = + WithConn.Definition.ensureTable name conn + + /// Create an index on a document table + member conn.ensureIndex tableName indexName fields = + WithConn.Definition.ensureIndex tableName indexName fields conn + + /// Insert a new document + member conn.insert<'TDoc> tableName (document: 'TDoc) = + WithConn.insert<'TDoc> tableName document conn + + /// 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) = + WithConn.save tableName document conn + + /// Count all documents in a table + member conn.countAll tableName = + WithConn.Count.all tableName conn + + /// Count matching documents using a comparison on a JSON field + member conn.countByField tableName fieldName op (value: obj) = + WithConn.Count.byField tableName fieldName op value conn + + /// Determine if a document exists for the given ID + member conn.existsById tableName (docId: 'TKey) = + WithConn.Exists.byId tableName docId conn + + /// Determine if a document exists using a comparison on a JSON field + member conn.existsByField tableName fieldName op (value: obj) = + WithConn.Exists.byField tableName fieldName op value conn + + /// Retrieve all documents in the given table + member conn.findAll<'TDoc> tableName = + WithConn.Find.all<'TDoc> tableName conn + + /// Retrieve a document by its ID + member conn.findById<'TKey, 'TDoc> tableName (docId: 'TKey) = + WithConn.Find.byId<'TKey, 'TDoc> tableName docId conn + + /// Retrieve documents via a comparison on a JSON field + member conn.findByField<'TDoc> tableName fieldName op (value: obj) = + WithConn.Find.byField<'TDoc> tableName fieldName op value conn + + /// Retrieve documents via a comparison on a JSON field, returning only the first result + member conn.findFirstByField<'TDoc> tableName fieldName op (value: obj) = + WithConn.Find.firstByField<'TDoc> tableName fieldName op value conn + + /// Update an entire document + member conn.updateFull tableName (docId: 'TKey) (document: 'TDoc) = + WithConn.Update.full tableName docId document conn + + /// Update an entire document + member conn.updateFullFunc tableName (idFunc: 'TDoc -> 'TKey) (document: 'TDoc) = + WithConn.Update.fullFunc tableName idFunc document conn + + /// Update a partial document + member conn.updatePartialById tableName (docId: 'TKey) (partial: 'TPatch) = + WithConn.Update.partialById tableName docId partial conn + + /// Update partial documents using a comparison on a JSON field + member conn.updatePartialByField tableName fieldName op (value: obj) (partial: 'TPatch) = + WithConn.Update.partialByField tableName fieldName op value partial conn + + /// Delete a document by its ID + member conn.deleteById tableName (docId: 'TKey) = + WithConn.Delete.byId tableName docId conn + + /// Delete documents by matching a comparison on a JSON field + member conn.deleteByField tableName fieldName op (value: obj) = + WithConn.Delete.byField tableName fieldName op value conn + + +open System.Runtime.CompilerServices + +/// C# extensions on the SqliteConnection type +[] +type SqliteConnectionCSharpExtensions = + + /// Execute a query that returns a list of results + [] + static member inline CustomList<'TDoc>(conn, query, parameters, mapFunc: System.Func) = + WithConn.Custom.List<'TDoc>(query, parameters, mapFunc, conn) + + /// Execute a query that returns one or no results + [] + static member inline CustomSingle<'TDoc when 'TDoc: null>( + conn, query, parameters, mapFunc: System.Func) = + WithConn.Custom.Single<'TDoc>(query, parameters, mapFunc, conn) + + /// Execute a query that does not return a value + [] + static member inline CustomNonQuery(conn, query, parameters) = + WithConn.Custom.nonQuery query parameters conn + + /// Execute a query that returns a scalar value + [] + static member inline CustomScalar<'T when 'T: struct>( + conn, query, parameters, mapFunc: System.Func) = + WithConn.Custom.Scalar<'T>(query, parameters, mapFunc, conn) + + /// Create a document table + [] + static member inline EnsureTable(conn, name) = + WithConn.Definition.ensureTable name conn + + /// Create an index on one or more fields in a document table + [] + static member inline EnsureIndex(conn, tableName, indexName, fields) = + WithConn.Definition.ensureIndex tableName indexName fields conn + + /// Insert a new document + [] + static member inline Insert<'TDoc>(conn, tableName, document: 'TDoc) = + WithConn.insert<'TDoc> tableName document conn + + /// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert") + [] + static member inline Save<'TDoc>(conn, tableName, document: 'TDoc) = + WithConn.save<'TDoc> tableName document conn + + /// Count all documents in a table + [] + static member inline CountAll(conn, tableName) = + WithConn.Count.all tableName conn + + /// Count matching documents using a comparison on a JSON field + [] + static member inline CountByField(conn, tableName, fieldName, op, value: obj) = + WithConn.Count.byField tableName fieldName op value conn + + /// Determine if a document exists for the given ID + [] + static member inline ExistsById<'TKey>(conn, tableName, docId: 'TKey) = + WithConn.Exists.byId tableName docId conn + + /// Determine if a document exists using a comparison on a JSON field + [] + static member inline ExistsByField(conn, tableName, fieldName, op, value: obj) = + WithConn.Exists.byField tableName fieldName op value conn + + /// Retrieve all documents in the given table + [] + static member inline FindAll<'TDoc>(conn, tableName) = + WithConn.Find.All<'TDoc>(tableName, conn) + + /// Retrieve a document by its ID + [] + static member inline FindById<'TKey, 'TDoc when 'TDoc: null>(conn, tableName, docId: 'TKey) = + WithConn.Find.ById<'TKey, 'TDoc>(tableName, docId, conn) + + /// Retrieve documents via a comparison on a JSON field + [] + static member inline FindByField<'TDoc>(conn, tableName, fieldName, op, value) = + WithConn.Find.ByField<'TDoc>(tableName, fieldName, op, value, conn) + + /// Retrieve documents via a comparison on a JSON field, returning only the first result + [] + static member inline FindFirstByField<'TDoc when 'TDoc: null>(conn, tableName, fieldName, op, value: obj) = + WithConn.Find.FirstByField<'TDoc>(tableName, fieldName, op, value, conn) + + /// Update an entire document + [] + static member inline UpdateFull<'TKey, 'TDoc>(conn, tableName, docId: 'TKey, document: 'TDoc) = + WithConn.Update.full tableName docId document conn + + /// Update an entire document + [] + static member inline UpdateFullFunc<'TKey, 'TDoc>(conn, tableName, idFunc: System.Func<'TDoc, 'TKey>, doc: 'TDoc) = + WithConn.Update.FullFunc(tableName, idFunc, doc, conn) + + /// Update a partial document + [] + static member inline UpdatePartialById<'TKey, 'TPatch>(conn, tableName, docId: 'TKey, partial: 'TPatch) = + WithConn.Update.partialById tableName docId partial conn + + /// Update partial documents using a comparison on a JSON field + [] + static member inline UpdatePartialByField<'TPatch>(conn, tableName, fieldName, op, value: obj, partial: 'TPatch) = + WithConn.Update.partialByField tableName fieldName op value partial conn + + /// Delete a document by its ID + [] + static member inline DeleteById<'TKey>(conn, tableName, docId: 'TKey) = + WithConn.Delete.byId tableName docId conn + + /// Delete documents by matching a comparison on a JSON field + [] + static member inline DeleteByField(conn, tableName, fieldName, op, value: obj) = + WithConn.Delete.byField tableName fieldName op value conn diff --git a/src/Sqlite/Library.fs b/src/Sqlite/Library.fs index abee0aa..7f372db 100644 --- a/src/Sqlite/Library.fs +++ b/src/Sqlite/Library.fs @@ -519,217 +519,3 @@ module Delete = let byField tableName fieldName op (value: obj) = use conn = Configuration.dbConn () WithConn.Delete.byField tableName fieldName op value conn - - -/// F# extensions for the SqliteConnection type -[] -module Extensions = - - type SqliteConnection with - - /// Execute a query that returns a list of results - member conn.customList<'TDoc> query parameters mapFunc = - WithConn.Custom.list<'TDoc> query parameters mapFunc conn - - /// Execute a query that returns one or no results - member conn.customSingle<'TDoc> query parameters mapFunc = - WithConn.Custom.single<'TDoc> query parameters mapFunc conn - - /// Execute a query that does not return a value - member conn.customNonQuery query parameters = - WithConn.Custom.nonQuery query parameters conn - - /// Execute a query that returns a scalar value - member conn.customScalar<'T when 'T: struct> query parameters mapFunc = - WithConn.Custom.scalar<'T> query parameters mapFunc conn - - /// Create a document table - member conn.ensureTable name = - WithConn.Definition.ensureTable name conn - - /// Create an index on a document table - member conn.ensureIndex tableName indexName fields = - WithConn.Definition.ensureIndex tableName indexName fields conn - - /// Insert a new document - member conn.insert<'TDoc> tableName (document: 'TDoc) = - WithConn.insert<'TDoc> tableName document conn - - /// 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) = - WithConn.save tableName document conn - - /// Count all documents in a table - member conn.countAll tableName = - WithConn.Count.all tableName conn - - /// Count matching documents using a comparison on a JSON field - member conn.countByField tableName fieldName op (value: obj) = - WithConn.Count.byField tableName fieldName op value conn - - /// Determine if a document exists for the given ID - member conn.existsById tableName (docId: 'TKey) = - WithConn.Exists.byId tableName docId conn - - /// Determine if a document exists using a comparison on a JSON field - member conn.existsByField tableName fieldName op (value: obj) = - WithConn.Exists.byField tableName fieldName op value conn - - /// Retrieve all documents in the given table - member conn.findAll<'TDoc> tableName = - WithConn.Find.all<'TDoc> tableName conn - - /// Retrieve a document by its ID - member conn.findById<'TKey, 'TDoc> tableName (docId: 'TKey) = - WithConn.Find.byId<'TKey, 'TDoc> tableName docId conn - - /// Retrieve documents via a comparison on a JSON field - member conn.findByField<'TDoc> tableName fieldName op (value: obj) = - WithConn.Find.byField<'TDoc> tableName fieldName op value conn - - /// Retrieve documents via a comparison on a JSON field, returning only the first result - member conn.findFirstByField<'TDoc> tableName fieldName op (value: obj) = - WithConn.Find.firstByField<'TDoc> tableName fieldName op value conn - - /// Update an entire document - member conn.updateFull tableName (docId: 'TKey) (document: 'TDoc) = - WithConn.Update.full tableName docId document conn - - /// Update an entire document - member conn.updateFullFunc tableName (idFunc: 'TDoc -> 'TKey) (document: 'TDoc) = - WithConn.Update.fullFunc tableName idFunc document conn - - /// Update a partial document - member conn.updatePartialById tableName (docId: 'TKey) (partial: 'TPatch) = - WithConn.Update.partialById tableName docId partial conn - - /// Update partial documents using a comparison on a JSON field - member conn.updatePartialByField tableName fieldName op (value: obj) (partial: 'TPatch) = - WithConn.Update.partialByField tableName fieldName op value partial conn - - /// Delete a document by its ID - member conn.deleteById tableName (docId: 'TKey) = - WithConn.Delete.byId tableName docId conn - - /// Delete documents by matching a comparison on a JSON field - member conn.deleteByField tableName fieldName op (value: obj) = - WithConn.Delete.byField tableName fieldName op value conn - - -open System.Runtime.CompilerServices - -/// C# extensions on the SqliteConnection type -[] -type SqliteConnectionCSharpExtensions = - - /// Execute a query that returns a list of results - [] - static member inline CustomList<'TDoc>(conn, query, parameters, mapFunc: System.Func) = - WithConn.Custom.List<'TDoc>(query, parameters, mapFunc, conn) - - /// Execute a query that returns one or no results - [] - static member inline CustomSingle<'TDoc when 'TDoc: null>( - conn, query, parameters, mapFunc: System.Func) = - WithConn.Custom.Single<'TDoc>(query, parameters, mapFunc, conn) - - /// Execute a query that does not return a value - [] - static member inline CustomNonQuery(conn, query, parameters) = - WithConn.Custom.nonQuery query parameters conn - - /// Execute a query that returns a scalar value - [] - static member inline CustomScalar<'T when 'T: struct>( - conn, query, parameters, mapFunc: System.Func) = - WithConn.Custom.Scalar<'T>(query, parameters, mapFunc, conn) - - /// Create a document table - [] - static member inline EnsureTable(conn, name) = - WithConn.Definition.ensureTable name conn - - /// Create an index on one or more fields in a document table - [] - static member inline EnsureIndex(conn, tableName, indexName, fields) = - WithConn.Definition.ensureIndex tableName indexName fields conn - - /// Insert a new document - [] - static member inline Insert<'TDoc>(conn, tableName, document: 'TDoc) = - WithConn.insert<'TDoc> tableName document conn - - /// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert") - [] - static member inline Save<'TDoc>(conn, tableName, document: 'TDoc) = - WithConn.save<'TDoc> tableName document conn - - /// Count all documents in a table - [] - static member inline CountAll(conn, tableName) = - WithConn.Count.all tableName conn - - /// Count matching documents using a comparison on a JSON field - [] - static member inline CountByField(conn, tableName, fieldName, op, value: obj) = - WithConn.Count.byField tableName fieldName op value conn - - /// Determine if a document exists for the given ID - [] - static member inline ExistsById<'TKey>(conn, tableName, docId: 'TKey) = - WithConn.Exists.byId tableName docId conn - - /// Determine if a document exists using a comparison on a JSON field - [] - static member inline ExistsByField(conn, tableName, fieldName, op, value: obj) = - WithConn.Exists.byField tableName fieldName op value conn - - /// Retrieve all documents in the given table - [] - static member inline FindAll<'TDoc>(conn, tableName) = - WithConn.Find.All<'TDoc>(tableName, conn) - - /// Retrieve a document by its ID - [] - static member inline FindById<'TKey, 'TDoc when 'TDoc: null>(conn, tableName, docId: 'TKey) = - WithConn.Find.ById<'TKey, 'TDoc>(tableName, docId, conn) - - /// Retrieve documents via a comparison on a JSON field - [] - static member inline FindByField<'TDoc>(conn, tableName, fieldName, op, value) = - WithConn.Find.ByField<'TDoc>(tableName, fieldName, op, value, conn) - - /// Retrieve documents via a comparison on a JSON field, returning only the first result - [] - static member inline FindFirstByField<'TDoc when 'TDoc: null>(conn, tableName, fieldName, op, value: obj) = - WithConn.Find.FirstByField<'TDoc>(tableName, fieldName, op, value, conn) - - /// Update an entire document - [] - static member inline UpdateFull<'TKey, 'TDoc>(conn, tableName, docId: 'TKey, document: 'TDoc) = - WithConn.Update.full tableName docId document conn - - /// Update an entire document - [] - static member inline UpdateFullFunc<'TKey, 'TDoc>(conn, tableName, idFunc: System.Func<'TDoc, 'TKey>, doc: 'TDoc) = - WithConn.Update.FullFunc(tableName, idFunc, doc, conn) - - /// Update a partial document - [] - static member inline UpdatePartialById<'TKey, 'TPatch>(conn, tableName, docId: 'TKey, partial: 'TPatch) = - WithConn.Update.partialById tableName docId partial conn - - /// Update partial documents using a comparison on a JSON field - [] - static member inline UpdatePartialByField<'TPatch>(conn, tableName, fieldName, op, value: obj, partial: 'TPatch) = - WithConn.Update.partialByField tableName fieldName op value partial conn - - /// Delete a document by its ID - [] - static member inline DeleteById<'TKey>(conn, tableName, docId: 'TKey) = - WithConn.Delete.byId tableName docId conn - - /// Delete documents by matching a comparison on a JSON field - [] - static member inline DeleteByField(conn, tableName, fieldName, op, value: obj) = - WithConn.Delete.byField tableName fieldName op value conn diff --git a/src/Tests.CSharp/BitBadger.Documents.Tests.CSharp.csproj b/src/Tests.CSharp/BitBadger.Documents.Tests.CSharp.csproj index c70e5b0..bb60bcd 100644 --- a/src/Tests.CSharp/BitBadger.Documents.Tests.CSharp.csproj +++ b/src/Tests.CSharp/BitBadger.Documents.Tests.CSharp.csproj @@ -7,6 +7,7 @@ + diff --git a/src/Tests.CSharp/SqliteCSharpExtensionTests.cs b/src/Tests.CSharp/SqliteCSharpExtensionTests.cs new file mode 100644 index 0000000..cabe980 --- /dev/null +++ b/src/Tests.CSharp/SqliteCSharpExtensionTests.cs @@ -0,0 +1,511 @@ +using Expecto.CSharp; +using Expecto; +using Microsoft.Data.Sqlite; +using BitBadger.Documents.Sqlite; + +namespace BitBadger.Documents.Tests.CSharp; + +using static Runner; + +/// +/// C# tests for the extensions on the SqliteConnection class +/// +public static class SqliteCSharpExtensionTests +{ + private static Task LoadDocs() => SqliteCSharpTests.LoadDocs(); + + [Tests] + public static Test Integration = + TestList("Extensions", new[] + { + TestList("CustomSingle", new[] + { + TestCase("succeeds when a row is found", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + await LoadDocs(); + + var doc = await conn.CustomSingle( + $"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'Id' = @id", + new[] { Parameters.Id("one") }, Results.FromData); + Expect.isNotNull(doc, "There should have been a document returned"); + Expect.equal(doc!.Id, "one", "The incorrect document was returned"); + }), + TestCase("succeeds when a row is not found", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + await LoadDocs(); + + var doc = await conn.CustomSingle( + $"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'Id' = @id", + new[] { Parameters.Id("eighty") }, Results.FromData); + Expect.isNull(doc, "There should not have been a document returned"); + }) + }), + TestList("CustomList", new[] + { + TestCase("succeeds when data is found", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + await LoadDocs(); + + var docs = await conn.CustomList(Query.SelectFromTable(SqliteDb.TableName), Parameters.None, + Results.FromData); + Expect.equal(docs.Count, 5, "There should have been 5 documents returned"); + }), + TestCase("succeeds when data is not found", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + await LoadDocs(); + + var docs = await conn.CustomList( + $"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'NumValue' > @value", + new[] { new SqliteParameter("@value", 100) }, Results.FromData); + Expect.isEmpty(docs, "There should have been no documents returned"); + }) + }), + TestList("CustomNonQuery", new[] + { + TestCase("succeeds when operating on data", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + await LoadDocs(); + + await conn.CustomNonQuery($"DELETE FROM {SqliteDb.TableName}", Parameters.None); + + var remaining = await conn.CountAll(SqliteDb.TableName); + Expect.equal(remaining, 0L, "There should be no documents remaining in the table"); + }), + TestCase("succeeds when no data matches where clause", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + await LoadDocs(); + + await conn.CustomNonQuery( + $"DELETE FROM {SqliteDb.TableName} WHERE data ->> 'NumValue' > @value", + new[] { new SqliteParameter("@value", 100) }); + + var remaining = await conn.CountAll(SqliteDb.TableName); + Expect.equal(remaining, 5L, "There should be 5 documents remaining in the table"); + }) + }), + TestCase("CustomScalar succeeds", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + + var nbr = await conn.CustomScalar("SELECT 5 AS test_value", Parameters.None, rdr => rdr.GetInt32(0)); + Expect.equal(nbr, 5, "The query should have returned the number 5"); + }), + TestCase("EnsureTable succeeds", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + + Func> itExists = async name => + { + var result = await conn.CustomScalar( + $"SELECT EXISTS (SELECT 1 FROM {SqliteDb.Catalog} WHERE name = @name) AS it", + new SqliteParameter[] { new("@name", name) }, rdr => rdr.GetInt64(0)); + return result > 0L; + }; + + var exists = await itExists("ensured"); + var alsoExists = await itExists("idx_ensured_key"); + Expect.isFalse(exists, "The table should not exist already"); + Expect.isFalse(alsoExists, "The key index should not exist already"); + + await conn.EnsureTable("ensured"); + + exists = await itExists("ensured"); + alsoExists = await itExists("idx_ensured_key"); + Expect.isTrue(exists, "The table should now exist"); + Expect.isTrue(alsoExists, "The key index should now exist"); + }), + TestList("Insert", new[] + { + TestCase("succeeds", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + var before = await conn.FindAll(SqliteDb.TableName); + Expect.isEmpty(before, "There should be no documents in the table"); + await conn.Insert(SqliteDb.TableName, + new JsonDocument { Id = "turkey", Sub = new() { Foo = "gobble", Bar = "gobble" } }); + var after = await conn.FindAll(SqliteDb.TableName); + Expect.equal(after.Count, 1, "There should have been one document inserted"); + }), + TestCase("fails for duplicate key", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + await conn.Insert(SqliteDb.TableName, new JsonDocument { Id = "test" }); + try + { + await Document.Insert(SqliteDb.TableName, new JsonDocument { Id = "test" }); + Expect.isTrue(false, "An exception should have been raised for duplicate document ID insert"); + } + catch (Exception) + { + // This is what is supposed to happen + } + }) + }), + TestList("Save", new[] + { + TestCase("succeeds when a document is inserted", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + var before = await conn.FindAll(SqliteDb.TableName); + Expect.isEmpty(before, "There should be no documents in the table"); + + await conn.Save(SqliteDb.TableName, + new JsonDocument { Id = "test", Sub = new() { Foo = "a", Bar = "b" } }); + var after = await conn.FindAll(SqliteDb.TableName); + Expect.equal(after.Count, 1, "There should have been one document inserted"); + }), + TestCase("succeeds when a document is updated", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + await conn.Insert(SqliteDb.TableName, + new JsonDocument { Id = "test", Sub = new() { Foo = "a", Bar = "b" } }); + + var before = await conn.FindById(SqliteDb.TableName, "test"); + Expect.isNotNull(before, "There should have been a document returned"); + Expect.equal(before!.Id, "test", "The document is not correct"); + Expect.isNotNull(before.Sub, "There should have been a sub-document"); + Expect.equal(before.Sub!.Foo, "a", "The document is not correct"); + Expect.equal(before.Sub.Bar, "b", "The document is not correct"); + + await conn.Save(SqliteDb.TableName, new JsonDocument { Id = "test" }); + var after = await conn.FindById(SqliteDb.TableName, "test"); + Expect.isNotNull(after, "There should have been a document returned post-update"); + 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"); + }) + }), + TestCase("CountAll succeeds", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + await LoadDocs(); + + var theCount = await conn.CountAll(SqliteDb.TableName); + Expect.equal(theCount, 5L, "There should have been 5 matching documents"); + }), + TestCase("CountByField succeeds", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + await LoadDocs(); + + var theCount = await conn.CountByField(SqliteDb.TableName, "Value", Op.EQ, "purple"); + Expect.equal(theCount, 2L, "There should have been 2 matching documents"); + }), + TestList("ExistsById", new[] + { + TestCase("succeeds when a document exists", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + await LoadDocs(); + + var exists = await conn.ExistsById(SqliteDb.TableName, "three"); + Expect.isTrue(exists, "There should have been an existing document"); + }), + TestCase("succeeds when a document does not exist", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + await LoadDocs(); + + var exists = await conn.ExistsById(SqliteDb.TableName, "seven"); + Expect.isFalse(exists, "There should not have been an existing document"); + }) + }), + TestList("ExistsByField", new[] + { + TestCase("succeeds when documents exist", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + await LoadDocs(); + + var exists = await conn.ExistsByField(SqliteDb.TableName, "NumValue", Op.GE, 10); + Expect.isTrue(exists, "There should have been existing documents"); + }), + TestCase("succeeds when no matching documents exist", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + await LoadDocs(); + + var exists = await conn.ExistsByField(SqliteDb.TableName, "Nothing", Op.EQ, "none"); + Expect.isFalse(exists, "There should not have been any existing documents"); + }) + }), + TestList("FindAll", new[] + { + TestCase("succeeds when there is data", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + + await conn.Insert(SqliteDb.TableName, new JsonDocument { Id = "one", Value = "two" }); + await conn.Insert(SqliteDb.TableName, new JsonDocument { Id = "three", Value = "four" }); + await conn.Insert(SqliteDb.TableName, new JsonDocument { Id = "five", Value = "six" }); + + var results = await conn.FindAll(SqliteDb.TableName); + Expect.equal(results.Count, 3, "There should have been 3 documents returned"); + }), + TestCase("succeeds when there is no data", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + var results = await conn.FindAll(SqliteDb.TableName); + Expect.isEmpty(results, "There should have been no documents returned"); + }) + }), + TestList("FindById", new[] + { + TestCase("succeeds when a document is found", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + await LoadDocs(); + + var doc = await conn.FindById(SqliteDb.TableName, "two"); + Expect.isNotNull(doc, "There should have been a document returned"); + Expect.equal(doc!.Id, "two", "The incorrect document was returned"); + }), + TestCase("succeeds when a document is not found", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + await LoadDocs(); + + var doc = await conn.FindById(SqliteDb.TableName, "eighty-seven"); + Expect.isNull(doc, "There should not have been a document returned"); + }) + }), + TestList("FindByField", new[] + { + TestCase("succeeds when documents are found", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + await LoadDocs(); + + var docs = await conn.FindByField(SqliteDb.TableName, "NumValue", Op.GT, 15); + Expect.equal(docs.Count, 2, "There should have been two documents returned"); + }), + TestCase("succeeds when documents are not found", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + await LoadDocs(); + + var docs = await conn.FindByField(SqliteDb.TableName, "Value", Op.EQ, "mauve"); + Expect.isEmpty(docs, "There should have been no documents returned"); + }) + }), + TestList("FindFirstByField", new[] + { + TestCase("succeeds when a document is found", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + await LoadDocs(); + + var doc = await conn.FindFirstByField(SqliteDb.TableName, "Value", Op.EQ, + "another"); + Expect.isNotNull(doc, "There should have been a document returned"); + Expect.equal(doc!.Id, "two", "The incorrect document was returned"); + }), + TestCase("succeeds when multiple documents are found", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + await LoadDocs(); + + var doc = await conn.FindFirstByField(SqliteDb.TableName, "Sub.Foo", Op.EQ, + "green"); + Expect.isNotNull(doc, "There should have been a document returned"); + Expect.contains(new[] { "two", "four" }, doc!.Id, "An incorrect document was returned"); + }), + TestCase("succeeds when a document is not found", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + await LoadDocs(); + + var doc = await conn.FindFirstByField(SqliteDb.TableName, "Value", Op.EQ, + "absent"); + Expect.isNull(doc, "There should not have been a document returned"); + }) + }), + TestList("UpdateFull", new[] + { + TestCase("succeeds when a document is updated", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + await LoadDocs(); + + var testDoc = new JsonDocument { Id = "one", Sub = new() { Foo = "blue", Bar = "red" } }; + await conn.UpdateFull(SqliteDb.TableName, "one", testDoc); + var after = await conn.FindById(SqliteDb.TableName, "one"); + Expect.isNotNull(after, "There should have been a document returned post-update"); + Expect.equal(after.Id, "one", "The updated document is not correct"); + Expect.isNotNull(after.Sub, "The updated document should have had a sub-document"); + Expect.equal(after.Sub!.Foo, "blue", "The updated sub-document is not correct"); + Expect.equal(after.Sub.Bar, "red", "The updated sub-document is not correct"); + }), + TestCase("succeeds when no document is updated", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + var before = await conn.FindAll(SqliteDb.TableName); + Expect.isEmpty(before, "There should have been no documents returned"); + + // This not raising an exception is the test + await conn.UpdateFull(SqliteDb.TableName, "test", + new JsonDocument { Id = "x", Sub = new() { Foo = "blue", Bar = "red" } }); + }) + }), + TestList("UpdateFullFunc", new[] + { + TestCase("succeeds when a document is updated", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + await LoadDocs(); + + await conn.UpdateFullFunc(SqliteDb.TableName, doc => doc.Id, + new JsonDocument { Id = "one", Value = "le un", NumValue = 1 }); + var after = await conn.FindById(SqliteDb.TableName, "one"); + Expect.isNotNull(after, "There should have been a document returned post-update"); + Expect.equal(after.Id, "one", "The updated document is incorrect"); + Expect.equal(after.Value, "le un", "The updated document is incorrect"); + Expect.equal(after.NumValue, 1, "The updated document is incorrect"); + Expect.isNull(after.Sub, "The updated document should not have a sub-document"); + }), + TestCase("succeeds when no document is updated", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + var before = await conn.FindAll(SqliteDb.TableName); + Expect.isEmpty(before, "There should have been no documents returned"); + + // This not raising an exception is the test + await conn.UpdateFullFunc(SqliteDb.TableName, doc => doc.Id, + new JsonDocument { Id = "one", Value = "le un", NumValue = 1 }); + }) + }), + TestList("UpdatePartialById", new[] + { + TestCase("succeeds when a document is updated", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + await LoadDocs(); + + await conn.UpdatePartialById(SqliteDb.TableName, "one", new { NumValue = 44 }); + var after = await conn.FindById(SqliteDb.TableName, "one"); + Expect.isNotNull(after, "There should have been a document returned post-update"); + Expect.equal(after.Id, "one", "The updated document is not correct"); + Expect.equal(after.NumValue, 44, "The updated document is not correct"); + }), + TestCase("succeeds when no document is updated", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + var before = await conn.FindAll(SqliteDb.TableName); + Expect.isEmpty(before, "There should have been no documents returned"); + + // This not raising an exception is the test + await conn.UpdatePartialById(SqliteDb.TableName, "test", new { Foo = "green" }); + }) + }), + TestList("UpdatePartialByField", new[] + { + TestCase("succeeds when a document is updated", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + await LoadDocs(); + + await conn.UpdatePartialByField(SqliteDb.TableName, "Value", Op.EQ, "purple", + new { NumValue = 77 }); + var after = await conn.CountByField(SqliteDb.TableName, "NumValue", Op.EQ, 77); + Expect.equal(after, 2L, "There should have been 2 documents returned"); + }), + TestCase("succeeds when no document is updated", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + var before = await conn.FindAll(SqliteDb.TableName); + Expect.isEmpty(before, "There should have been no documents returned"); + + // This not raising an exception is the test + await conn.UpdatePartialByField(SqliteDb.TableName, "Value", Op.EQ, "burgundy", + new { Foo = "green" }); + }) + }), + TestList("DeleteById", new[] + { + TestCase("succeeds when a document is deleted", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + await LoadDocs(); + + await conn.DeleteById(SqliteDb.TableName, "four"); + var remaining = await conn.CountAll(SqliteDb.TableName); + Expect.equal(remaining, 4L, "There should have been 4 documents remaining"); + }), + TestCase("succeeds when a document is not deleted", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + await LoadDocs(); + + await conn.DeleteById(SqliteDb.TableName, "thirty"); + var remaining = await conn.CountAll(SqliteDb.TableName); + Expect.equal(remaining, 5L, "There should have been 5 documents remaining"); + }) + }), + TestList("DeleteByField", new[] + { + TestCase("succeeds when documents are deleted", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + await LoadDocs(); + + await conn.DeleteByField(SqliteDb.TableName, "Value", Op.NE, "purple"); + var remaining = await conn.CountAll(SqliteDb.TableName); + Expect.equal(remaining, 2L, "There should have been 2 documents remaining"); + }), + TestCase("succeeds when documents are not deleted", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + await LoadDocs(); + + await conn.DeleteByField(SqliteDb.TableName, "Value", Op.EQ, "crimson"); + var remaining = await conn.CountAll(SqliteDb.TableName); + Expect.equal(remaining, 5L, "There should have been 5 documents remaining"); + }) + }), + TestCase("Clean up database", () => Sqlite.Configuration.UseConnectionString("data source=:memory:")) + }); +} diff --git a/src/Tests.CSharp/SqliteCSharpTests.cs b/src/Tests.CSharp/SqliteCSharpTests.cs index 73baea0..f1c35c6 100644 --- a/src/Tests.CSharp/SqliteCSharpTests.cs +++ b/src/Tests.CSharp/SqliteCSharpTests.cs @@ -56,7 +56,7 @@ public static class SqliteCSharpTests new() { Id = "five", Value = "purple", NumValue = 18 } }; - private static async Task LoadDocs() + internal static async Task LoadDocs() { foreach (var doc in TestDocuments) await Document.Insert(SqliteDb.TableName, doc); } @@ -552,500 +552,6 @@ public static class SqliteCSharpTests }) }) }), - TestList("Extensions", new[] - { - TestList("CustomSingle", new[] - { - TestCase("succeeds when a row is found", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - await LoadDocs(); - - var doc = await conn.CustomSingle( - $"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'Id' = @id", - new[] { Parameters.Id("one") }, Results.FromData); - Expect.isNotNull(doc, "There should have been a document returned"); - Expect.equal(doc!.Id, "one", "The incorrect document was returned"); - }), - TestCase("succeeds when a row is not found", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - await LoadDocs(); - - var doc = await conn.CustomSingle( - $"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'Id' = @id", - new[] { Parameters.Id("eighty") }, Results.FromData); - Expect.isNull(doc, "There should not have been a document returned"); - }) - }), - TestList("CustomList", new[] - { - TestCase("succeeds when data is found", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - await LoadDocs(); - - var docs = await conn.CustomList(Query.SelectFromTable(SqliteDb.TableName), Parameters.None, - Results.FromData); - Expect.equal(docs.Count, 5, "There should have been 5 documents returned"); - }), - TestCase("succeeds when data is not found", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - await LoadDocs(); - - var docs = await conn.CustomList( - $"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'NumValue' > @value", - new[] { new SqliteParameter("@value", 100) }, Results.FromData); - Expect.isEmpty(docs, "There should have been no documents returned"); - }) - }), - TestList("CustomNonQuery", new[] - { - TestCase("succeeds when operating on data", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - await LoadDocs(); - - await conn.CustomNonQuery($"DELETE FROM {SqliteDb.TableName}", Parameters.None); - - var remaining = await conn.CountAll(SqliteDb.TableName); - Expect.equal(remaining, 0L, "There should be no documents remaining in the table"); - }), - TestCase("succeeds when no data matches where clause", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - await LoadDocs(); - - await conn.CustomNonQuery( - $"DELETE FROM {SqliteDb.TableName} WHERE data ->> 'NumValue' > @value", - new[] { new SqliteParameter("@value", 100) }); - - var remaining = await conn.CountAll(SqliteDb.TableName); - Expect.equal(remaining, 5L, "There should be 5 documents remaining in the table"); - }) - }), - TestCase("CustomScalar succeeds", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - - var nbr = await conn.CustomScalar("SELECT 5 AS test_value", Parameters.None, - rdr => rdr.GetInt32(0)); - Expect.equal(nbr, 5, "The query should have returned the number 5"); - }), - TestCase("EnsureTable succeeds", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - - Func> itExists = async name => - { - var result = await conn.CustomScalar( - $"SELECT EXISTS (SELECT 1 FROM {SqliteDb.Catalog} WHERE name = @name) AS it", - new SqliteParameter[] { new("@name", name) }, - rdr => rdr.GetInt64(0)); - return result > 0L; - }; - - var exists = await itExists("ensured"); - var alsoExists = await itExists("idx_ensured_key"); - Expect.isFalse(exists, "The table should not exist already"); - Expect.isFalse(alsoExists, "The key index should not exist already"); - - await conn.EnsureTable("ensured"); - - exists = await itExists("ensured"); - alsoExists = await itExists("idx_ensured_key"); - Expect.isTrue(exists, "The table should now exist"); - Expect.isTrue(alsoExists, "The key index should now exist"); - }), - TestList("Insert", new[] - { - TestCase("succeeds", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - var before = await conn.FindAll(SqliteDb.TableName); - Expect.isEmpty(before, "There should be no documents in the table"); - await conn.Insert(SqliteDb.TableName, - new JsonDocument { Id = "turkey", Sub = new() { Foo = "gobble", Bar = "gobble" } }); - var after = await conn.FindAll(SqliteDb.TableName); - Expect.equal(after.Count, 1, "There should have been one document inserted"); - }), - TestCase("fails for duplicate key", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - await conn.Insert(SqliteDb.TableName, new JsonDocument { Id = "test" }); - try - { - await Document.Insert(SqliteDb.TableName, new JsonDocument { Id = "test" }); - Expect.isTrue(false, - "An exception should have been raised for duplicate document ID insert"); - } - catch (Exception) - { - // This is what is supposed to happen - } - }) - }), - TestList("Save", new[] - { - TestCase("succeeds when a document is inserted", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - var before = await conn.FindAll(SqliteDb.TableName); - Expect.isEmpty(before, "There should be no documents in the table"); - - await conn.Save(SqliteDb.TableName, - new JsonDocument { Id = "test", Sub = new() { Foo = "a", Bar = "b" } }); - var after = await conn.FindAll(SqliteDb.TableName); - Expect.equal(after.Count, 1, "There should have been one document inserted"); - }), - TestCase("succeeds when a document is updated", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - await conn.Insert(SqliteDb.TableName, - new JsonDocument { Id = "test", Sub = new() { Foo = "a", Bar = "b" } }); - - var before = await conn.FindById(SqliteDb.TableName, "test"); - Expect.isNotNull(before, "There should have been a document returned"); - Expect.equal(before!.Id, "test", "The document is not correct"); - Expect.isNotNull(before.Sub, "There should have been a sub-document"); - Expect.equal(before.Sub!.Foo, "a", "The document is not correct"); - Expect.equal(before.Sub.Bar, "b", "The document is not correct"); - - await conn.Save(SqliteDb.TableName, new JsonDocument { Id = "test" }); - var after = await conn.FindById(SqliteDb.TableName, "test"); - Expect.isNotNull(after, "There should have been a document returned post-update"); - 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"); - }) - }), - TestCase("CountAll succeeds", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - await LoadDocs(); - - var theCount = await conn.CountAll(SqliteDb.TableName); - Expect.equal(theCount, 5L, "There should have been 5 matching documents"); - }), - TestCase("CountByField succeeds", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - await LoadDocs(); - - var theCount = await conn.CountByField(SqliteDb.TableName, "Value", Op.EQ, "purple"); - Expect.equal(theCount, 2L, "There should have been 2 matching documents"); - }), - TestList("ExistsById", new[] - { - TestCase("succeeds when a document exists", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - await LoadDocs(); - - var exists = await conn.ExistsById(SqliteDb.TableName, "three"); - Expect.isTrue(exists, "There should have been an existing document"); - }), - TestCase("succeeds when a document does not exist", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - await LoadDocs(); - - var exists = await conn.ExistsById(SqliteDb.TableName, "seven"); - Expect.isFalse(exists, "There should not have been an existing document"); - }) - }), - TestList("ExistsByField", new[] - { - TestCase("succeeds when documents exist", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - await LoadDocs(); - - var exists = await conn.ExistsByField(SqliteDb.TableName, "NumValue", Op.GE, 10); - Expect.isTrue(exists, "There should have been existing documents"); - }), - TestCase("succeeds when no matching documents exist", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - await LoadDocs(); - - var exists = await conn.ExistsByField(SqliteDb.TableName, "Nothing", Op.EQ, "none"); - Expect.isFalse(exists, "There should not have been any existing documents"); - }) - }), - TestList("FindAll", new[] - { - TestCase("succeeds when there is data", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - - await conn.Insert(SqliteDb.TableName, new JsonDocument { Id = "one", Value = "two" }); - await conn.Insert(SqliteDb.TableName, new JsonDocument { Id = "three", Value = "four" }); - await conn.Insert(SqliteDb.TableName, new JsonDocument { Id = "five", Value = "six" }); - - var results = await conn.FindAll(SqliteDb.TableName); - Expect.equal(results.Count, 3, "There should have been 3 documents returned"); - }), - TestCase("succeeds when there is no data", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - var results = await conn.FindAll(SqliteDb.TableName); - Expect.isEmpty(results, "There should have been no documents returned"); - }) - }), - TestList("FindById", new[] - { - TestCase("succeeds when a document is found", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - await LoadDocs(); - - var doc = await conn.FindById(SqliteDb.TableName, "two"); - Expect.isNotNull(doc, "There should have been a document returned"); - Expect.equal(doc!.Id, "two", "The incorrect document was returned"); - }), - TestCase("succeeds when a document is not found", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - await LoadDocs(); - - var doc = await conn.FindById(SqliteDb.TableName, "eighty-seven"); - Expect.isNull(doc, "There should not have been a document returned"); - }) - }), - TestList("FindByField", new[] - { - TestCase("succeeds when documents are found", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - await LoadDocs(); - - var docs = await conn.FindByField(SqliteDb.TableName, "NumValue", Op.GT, 15); - Expect.equal(docs.Count, 2, "There should have been two documents returned"); - }), - TestCase("succeeds when documents are not found", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - await LoadDocs(); - - var docs = await conn.FindByField(SqliteDb.TableName, "Value", Op.EQ, "mauve"); - Expect.isEmpty(docs, "There should have been no documents returned"); - }) - }), - TestList("FindFirstByField", new[] - { - TestCase("succeeds when a document is found", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - await LoadDocs(); - - var doc = await conn.FindFirstByField(SqliteDb.TableName, "Value", Op.EQ, - "another"); - Expect.isNotNull(doc, "There should have been a document returned"); - Expect.equal(doc!.Id, "two", "The incorrect document was returned"); - }), - TestCase("succeeds when multiple documents are found", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - await LoadDocs(); - - var doc = await conn.FindFirstByField(SqliteDb.TableName, "Sub.Foo", Op.EQ, - "green"); - Expect.isNotNull(doc, "There should have been a document returned"); - Expect.contains(new[] { "two", "four" }, doc!.Id, "An incorrect document was returned"); - }), - TestCase("succeeds when a document is not found", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - await LoadDocs(); - - var doc = await conn.FindFirstByField(SqliteDb.TableName, "Value", Op.EQ, - "absent"); - Expect.isNull(doc, "There should not have been a document returned"); - }) - }), - TestList("UpdateFull", new[] - { - TestCase("succeeds when a document is updated", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - await LoadDocs(); - - var testDoc = new JsonDocument { Id = "one", Sub = new() { Foo = "blue", Bar = "red" } }; - await conn.UpdateFull(SqliteDb.TableName, "one", testDoc); - var after = await conn.FindById(SqliteDb.TableName, "one"); - Expect.isNotNull(after, "There should have been a document returned post-update"); - Expect.equal(after.Id, "one", "The updated document is not correct"); - Expect.isNotNull(after.Sub, "The updated document should have had a sub-document"); - Expect.equal(after.Sub!.Foo, "blue", "The updated sub-document is not correct"); - Expect.equal(after.Sub.Bar, "red", "The updated sub-document is not correct"); - }), - TestCase("succeeds when no document is updated", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - var before = await conn.FindAll(SqliteDb.TableName); - Expect.isEmpty(before, "There should have been no documents returned"); - - // This not raising an exception is the test - await conn.UpdateFull(SqliteDb.TableName, "test", - new JsonDocument { Id = "x", Sub = new() { Foo = "blue", Bar = "red" } }); - }) - }), - TestList("UpdateFullFunc", new[] - { - TestCase("succeeds when a document is updated", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - await LoadDocs(); - - await conn.UpdateFullFunc(SqliteDb.TableName, doc => doc.Id, - new JsonDocument { Id = "one", Value = "le un", NumValue = 1 }); - var after = await conn.FindById(SqliteDb.TableName, "one"); - Expect.isNotNull(after, "There should have been a document returned post-update"); - Expect.equal(after.Id, "one", "The updated document is incorrect"); - Expect.equal(after.Value, "le un", "The updated document is incorrect"); - Expect.equal(after.NumValue, 1, "The updated document is incorrect"); - Expect.isNull(after.Sub, "The updated document should not have a sub-document"); - }), - TestCase("succeeds when no document is updated", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - var before = await conn.FindAll(SqliteDb.TableName); - Expect.isEmpty(before, "There should have been no documents returned"); - - // This not raising an exception is the test - await conn.UpdateFullFunc(SqliteDb.TableName, doc => doc.Id, - new JsonDocument { Id = "one", Value = "le un", NumValue = 1 }); - }) - }), - TestList("UpdatePartialById", new[] - { - TestCase("succeeds when a document is updated", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - await LoadDocs(); - - await conn.UpdatePartialById(SqliteDb.TableName, "one", new { NumValue = 44 }); - var after = await conn.FindById(SqliteDb.TableName, "one"); - Expect.isNotNull(after, "There should have been a document returned post-update"); - Expect.equal(after.Id, "one", "The updated document is not correct"); - Expect.equal(after.NumValue, 44, "The updated document is not correct"); - }), - TestCase("succeeds when no document is updated", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - var before = await conn.FindAll(SqliteDb.TableName); - Expect.isEmpty(before, "There should have been no documents returned"); - - // This not raising an exception is the test - await conn.UpdatePartialById(SqliteDb.TableName, "test", new { Foo = "green" }); - }) - }), - TestList("UpdatePartialByField", new[] - { - TestCase("succeeds when a document is updated", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - await LoadDocs(); - - await conn.UpdatePartialByField(SqliteDb.TableName, "Value", Op.EQ, "purple", - new { NumValue = 77 }); - var after = await conn.CountByField(SqliteDb.TableName, "NumValue", Op.EQ, 77); - Expect.equal(after, 2L, "There should have been 2 documents returned"); - }), - TestCase("succeeds when no document is updated", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - var before = await conn.FindAll(SqliteDb.TableName); - Expect.isEmpty(before, "There should have been no documents returned"); - - // This not raising an exception is the test - await conn.UpdatePartialByField(SqliteDb.TableName, "Value", Op.EQ, "burgundy", - new { Foo = "green" }); - }) - }), - TestList("DeleteById", new[] - { - TestCase("succeeds when a document is deleted", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - await LoadDocs(); - - await conn.DeleteById(SqliteDb.TableName, "four"); - var remaining = await conn.CountAll(SqliteDb.TableName); - Expect.equal(remaining, 4L, "There should have been 4 documents remaining"); - }), - TestCase("succeeds when a document is not deleted", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - await LoadDocs(); - - await conn.DeleteById(SqliteDb.TableName, "thirty"); - var remaining = await conn.CountAll(SqliteDb.TableName); - Expect.equal(remaining, 5L, "There should have been 5 documents remaining"); - }) - }), - TestList("DeleteByField", new[] - { - TestCase("succeeds when documents are deleted", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - await LoadDocs(); - - await conn.DeleteByField(SqliteDb.TableName, "Value", Op.NE, "purple"); - var remaining = await conn.CountAll(SqliteDb.TableName); - Expect.equal(remaining, 2L, "There should have been 2 documents remaining"); - }), - TestCase("succeeds when documents are not deleted", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - await LoadDocs(); - - await conn.DeleteByField(SqliteDb.TableName, "Value", Op.EQ, "crimson"); - var remaining = await conn.CountAll(SqliteDb.TableName); - Expect.equal(remaining, 5L, "There should have been 5 documents remaining"); - }) - }) - }), TestCase("Clean up database", () => Sqlite.Configuration.UseConnectionString("data source=:memory:")) }); diff --git a/src/Tests/BitBadger.Documents.Tests.fsproj b/src/Tests/BitBadger.Documents.Tests.fsproj index 9dae8da..4792e95 100644 --- a/src/Tests/BitBadger.Documents.Tests.fsproj +++ b/src/Tests/BitBadger.Documents.Tests.fsproj @@ -6,7 +6,9 @@ + + @@ -16,6 +18,7 @@ + diff --git a/src/Tests/Program.fs b/src/Tests/Program.fs index 9480cb0..3d185f5 100644 --- a/src/Tests/Program.fs +++ b/src/Tests/Program.fs @@ -7,7 +7,9 @@ let allTests = [ CommonTests.all CommonCSharpTests.Unit SqliteTests.all - SqliteCSharpTests.All ] + testSequenced SqliteExtensionTests.integrationTests + SqliteCSharpTests.All + testSequenced SqliteCSharpExtensionTests.Integration ] [] let main args = runTestsWithCLIArgs [] args allTests diff --git a/src/Tests/SqliteExtensionTests.fs b/src/Tests/SqliteExtensionTests.fs new file mode 100644 index 0000000..dedf167 --- /dev/null +++ b/src/Tests/SqliteExtensionTests.fs @@ -0,0 +1,467 @@ +module SqliteExtensionTests + +open BitBadger.Documents +open BitBadger.Documents.Sqlite +open BitBadger.Documents.Tests +open Expecto +open Microsoft.Data.Sqlite +open Types + +/// Integration tests for the F# extensions on the SqliteConnection data type +let integrationTests = + 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 { + for doc in documents do do! insert SqliteDb.TableName doc + } + testList "Extensions" [ + testTask "ensureTable succeeds" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + let itExists (name: string) = task { + let! result = + conn.customScalar + $"SELECT EXISTS (SELECT 1 FROM {SqliteDb.Catalog} WHERE name = @name) AS it" + [ SqliteParameter("@name", name) ] + _.GetInt64(0) + return result > 0 + } + + let! exists = itExists "ensured" + let! alsoExists = itExists "idx_ensured_key" + Expect.isFalse exists "The table should not exist already" + Expect.isFalse alsoExists "The key index should not exist already" + + do! conn.ensureTable "ensured" + let! exists' = itExists "ensured" + let! alsoExists' = itExists "idx_ensured_key" + Expect.isTrue exists' "The table should now exist" + Expect.isTrue alsoExists' "The key index should now exist" + } + testList "insert" [ + testTask "succeeds" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + let! before = conn.findAll SqliteDb.TableName + Expect.equal before [] "There should be no documents in the table" + + let testDoc = { emptyDoc with Id = "turkey"; Sub = Some { Foo = "gobble"; Bar = "gobble" } } + do! conn.insert SqliteDb.TableName testDoc + let! after = conn.findAll SqliteDb.TableName + Expect.equal after [ testDoc ] "There should have been one document inserted" + } + testTask "fails for duplicate key" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + do! conn.insert SqliteDb.TableName { emptyDoc with Id = "test" } + Expect.throws + (fun () -> + conn.insert SqliteDb.TableName {emptyDoc with Id = "test" } + |> Async.AwaitTask + |> Async.RunSynchronously) + "An exception should have been raised for duplicate document ID insert" + } + ] + testList "save" [ + testTask "succeeds when a document is inserted" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + let! before = conn.findAll SqliteDb.TableName + Expect.equal before [] "There should be no documents in the table" + + let testDoc = { emptyDoc with Id = "test"; Sub = Some { Foo = "a"; Bar = "b" } } + do! conn.save SqliteDb.TableName testDoc + let! after = conn.findAll SqliteDb.TableName + Expect.equal after [ testDoc ] "There should have been one document inserted" + } + testTask "succeeds when a document is updated" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + let testDoc = { emptyDoc with Id = "test"; Sub = Some { Foo = "a"; Bar = "b" } } + do! conn.insert SqliteDb.TableName testDoc + + let! before = conn.findById SqliteDb.TableName "test" + if Option.isNone before then Expect.isTrue false "There should have been a document returned" + Expect.equal before.Value testDoc "The document is not correct" + + let upd8Doc = { testDoc with Sub = Some { Foo = "c"; Bar = "d" } } + do! conn.save SqliteDb.TableName upd8Doc + let! after = conn.findById SqliteDb.TableName "test" + if Option.isNone after then + Expect.isTrue false "There should have been a document returned post-update" + Expect.equal after.Value upd8Doc "The updated document is not correct" + } + ] + testTask "countAll succeeds" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + do! loadDocs () + + let! theCount = conn.countAll SqliteDb.TableName + Expect.equal theCount 5L "There should have been 5 matching documents" + } + testTask "countByField succeeds" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + do! loadDocs () + + let! theCount = conn.countByField SqliteDb.TableName "Value" EQ "purple" + Expect.equal theCount 2L "There should have been 2 matching documents" + } + testList "existsById" [ + testTask "succeeds when a document exists" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + do! loadDocs () + + let! exists = conn.existsById SqliteDb.TableName "three" + Expect.isTrue exists "There should have been an existing document" + } + testTask "succeeds when a document does not exist" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + do! loadDocs () + + let! exists = conn.existsById SqliteDb.TableName "seven" + Expect.isFalse exists "There should not have been an existing document" + } + ] + testList "existsByField" [ + testTask "succeeds when documents exist" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + do! loadDocs () + + let! exists = conn.existsByField SqliteDb.TableName "NumValue" EQ 10 + Expect.isTrue exists "There should have been existing documents" + } + testTask "succeeds when no matching documents exist" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + do! loadDocs () + + let! exists = conn.existsByField SqliteDb.TableName "Nothing" EQ "none" + Expect.isFalse exists "There should not have been any existing documents" + } + ] + testList "findAll" [ + testTask "succeeds when there is data" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + + do! insert SqliteDb.TableName { Foo = "one"; Bar = "two" } + do! insert SqliteDb.TableName { Foo = "three"; Bar = "four" } + do! insert SqliteDb.TableName { Foo = "five"; Bar = "six" } + + let! results = conn.findAll 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" + } + testTask "succeeds when there is no data" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + let! results = conn.findAll SqliteDb.TableName + Expect.equal results [] "There should have been no documents returned" + } + ] + testList "findById" [ + testTask "succeeds when a document is found" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + do! loadDocs () + + let! doc = conn.findById SqliteDb.TableName "two" + Expect.isTrue (Option.isSome doc) "There should have been a document returned" + Expect.equal doc.Value.Id "two" "The incorrect document was returned" + } + testTask "succeeds when a document is not found" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + do! loadDocs () + + let! doc = conn.findById SqliteDb.TableName "three hundred eighty-seven" + Expect.isFalse (Option.isSome doc) "There should not have been a document returned" + } + ] + testList "findByField" [ + testTask "succeeds when documents are found" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + do! loadDocs () + + let! docs = conn.findByField SqliteDb.TableName "Sub.Foo" EQ "green" + Expect.equal (List.length docs) 2 "There should have been two documents returned" + } + testTask "succeeds when documents are not found" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + do! loadDocs () + + let! docs = conn.findByField SqliteDb.TableName "Value" EQ "mauve" + Expect.isTrue (List.isEmpty docs) "There should have been no documents returned" + } + ] + testList "findFirstByField" [ + testTask "succeeds when a document is found" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + do! loadDocs () + + let! doc = conn.findFirstByField SqliteDb.TableName "Value" EQ "another" + Expect.isTrue (Option.isSome doc) "There should have been a document returned" + Expect.equal doc.Value.Id "two" "The incorrect document was returned" + } + testTask "succeeds when multiple documents are found" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + do! loadDocs () + + let! doc = conn.findFirstByField SqliteDb.TableName "Sub.Foo" EQ "green" + Expect.isTrue (Option.isSome doc) "There should have been a document returned" + Expect.contains [ "two"; "four" ] doc.Value.Id "An incorrect document was returned" + } + testTask "succeeds when a document is not found" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + do! loadDocs () + + let! doc = conn.findFirstByField SqliteDb.TableName "Value" EQ "absent" + Expect.isFalse (Option.isSome doc) "There should not have been a document returned" + } + ] + testList "updateFull" [ + testTask "succeeds when a document is updated" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + do! loadDocs () + + let testDoc = { emptyDoc with Id = "one"; Sub = Some { Foo = "blue"; Bar = "red" } } + do! conn.updateFull SqliteDb.TableName "one" testDoc + let! after = conn.findById SqliteDb.TableName "one" + if Option.isNone after then + Expect.isTrue false "There should have been a document returned post-update" + Expect.equal after.Value testDoc "The updated document is not correct" + } + testTask "succeeds when no document is updated" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + + let! before = conn.findAll SqliteDb.TableName + Expect.isEmpty before "There should have been no documents returned" + + // This not raising an exception is the test + do! conn.updateFull + SqliteDb.TableName + "test" + { emptyDoc with Id = "x"; Sub = Some { Foo = "blue"; Bar = "red" } } + } + ] + testList "updateFullFunc" [ + testTask "succeeds when a document is updated" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + do! loadDocs () + + do! conn.updateFullFunc + SqliteDb.TableName + (_.Id) + { Id = "one"; Value = "le un"; NumValue = 1; Sub = None } + let! after = conn.findById SqliteDb.TableName "one" + if Option.isNone after then + Expect.isTrue false "There should have been a document returned post-update" + Expect.equal + after.Value + { Id = "one"; Value = "le un"; NumValue = 1; Sub = None } + "The updated document is not correct" + } + testTask "succeeds when no document is updated" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + + let! before = conn.findAll SqliteDb.TableName + Expect.isEmpty before "There should have been no documents returned" + + // This not raising an exception is the test + do! conn.updateFullFunc + SqliteDb.TableName + (_.Id) + { Id = "one"; Value = "le un"; NumValue = 1; Sub = None } + } + ] + testList "updatePartialById" [ + testTask "succeeds when a document is updated" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + do! loadDocs () + + do! conn.updatePartialById SqliteDb.TableName "one" {| NumValue = 44 |} + let! after = conn.findById SqliteDb.TableName "one" + if Option.isNone after then + Expect.isTrue false "There should have been a document returned post-update" + Expect.equal after.Value.NumValue 44 "The updated document is not correct" + } + testTask "succeeds when no document is updated" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + + let! before = conn.findAll SqliteDb.TableName + Expect.isEmpty before "There should have been no documents returned" + + // This not raising an exception is the test + do! conn.updatePartialById SqliteDb.TableName "test" {| Foo = "green" |} + } + ] + testList "updatePartialByField" [ + testTask "succeeds when a document is updated" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + do! loadDocs () + + do! conn.updatePartialByField SqliteDb.TableName "Value" EQ "purple" {| NumValue = 77 |} + let! after = conn.countByField SqliteDb.TableName "NumValue" EQ 77 + Expect.equal after 2L "There should have been 2 documents returned" + } + testTask "succeeds when no document is updated" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + + let! before = conn.findAll SqliteDb.TableName + Expect.isEmpty before "There should have been no documents returned" + + // This not raising an exception is the test + do! conn.updatePartialByField SqliteDb.TableName "Value" EQ "burgundy" {| Foo = "green" |} + } + ] + testList "deleteById" [ + testTask "succeeds when a document is deleted" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + do! loadDocs () + + do! conn.deleteById SqliteDb.TableName "four" + let! remaining = conn.countAll SqliteDb.TableName + Expect.equal remaining 4L "There should have been 4 documents remaining" + } + testTask "succeeds when a document is not deleted" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + do! loadDocs () + + do! conn.deleteById SqliteDb.TableName "thirty" + let! remaining = conn.countAll SqliteDb.TableName + Expect.equal remaining 5L "There should have been 5 documents remaining" + } + ] + testList "deleteByField" [ + testTask "succeeds when documents are deleted" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + do! loadDocs () + + do! conn.deleteByField SqliteDb.TableName "Value" NE "purple" + let! remaining = conn.countAll SqliteDb.TableName + Expect.equal remaining 2L "There should have been 2 documents remaining" + } + testTask "succeeds when documents are not deleted" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + do! loadDocs () + + do! conn.deleteByField SqliteDb.TableName "Value" EQ "crimson" + let! remaining = conn.countAll SqliteDb.TableName + Expect.equal remaining 5L "There should have been 5 documents remaining" + } + ] + testList "customSingle" [ + testTask "succeeds when a row is found" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + do! loadDocs () + + let! doc = + conn.customSingle + $"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'Id' = @id" + [ SqliteParameter("@id", "one") ] + fromData + Expect.isSome doc "There should have been a document returned" + Expect.equal doc.Value.Id "one" "The incorrect document was returned" + } + testTask "succeeds when a row is not found" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + do! loadDocs () + + let! doc = + conn.customSingle + $"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'Id' = @id" + [ SqliteParameter("@id", "eighty") ] + fromData + Expect.isNone doc "There should not have been a document returned" + } + ] + testList "customList" [ + testTask "succeeds when data is found" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + do! loadDocs () + + let! docs = conn.customList (Query.selectFromTable SqliteDb.TableName) [] fromData + Expect.hasCountOf docs 5u (fun _ -> true) "There should have been 5 documents returned" + } + testTask "succeeds when data is not found" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + do! loadDocs () + + let! docs = + conn.customList + $"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'NumValue' > @value" + [ SqliteParameter("@value", 100) ] + fromData + Expect.isEmpty docs "There should have been no documents returned" + } + ] + testList "customNonQuery" [ + testTask "succeeds when operating on data" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + do! loadDocs () + + do! conn.customNonQuery $"DELETE FROM {SqliteDb.TableName}" [] + + let! remaining = conn.countAll SqliteDb.TableName + Expect.equal remaining 0L "There should be no documents remaining in the table" + } + testTask "succeeds when no data matches where clause" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + do! loadDocs () + + do! conn.customNonQuery + $"DELETE FROM {SqliteDb.TableName} WHERE data ->> 'NumValue' > @value" + [ SqliteParameter("@value", 100) ] + + let! remaining = conn.countAll SqliteDb.TableName + Expect.equal remaining 5L "There should be 5 documents remaining in the table" + } + ] + testTask "customScalar succeeds" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + + let! nbr = conn.customScalar "SELECT 5 AS test_value" [] _.GetInt32(0) + Expect.equal nbr 5 "The query should have returned the number 5" + } + test "clean up database" { + Configuration.useConnectionString "data source=:memory:" + } + ] diff --git a/src/Tests/SqliteTests.fs b/src/Tests/SqliteTests.fs index 56d925a..a632a35 100644 --- a/src/Tests/SqliteTests.fs +++ b/src/Tests/SqliteTests.fs @@ -1,26 +1,11 @@ module SqliteTests -type SubDocument = - { Foo: string - Bar: string } - -type JsonDocument = - { Id: string - Value: string - NumValue: int - Sub: SubDocument option } - -let emptyDoc = { Id = ""; Value = ""; NumValue = 0; Sub = None } - -/// A function that always returns true -let isTrue<'T> (_ : 'T) = true - - open BitBadger.Documents open BitBadger.Documents.Sqlite open BitBadger.Documents.Tests open Expecto open Microsoft.Data.Sqlite +open Types /// Unit tests for the SQLite library let unitTests = @@ -99,6 +84,81 @@ let integrationTests = Configuration.useIdField "Id" } ] + testList "Custom" [ + testList "single" [ + testTask "succeeds when a row is found" { + use! db = SqliteDb.BuildDb() + do! loadDocs () + + let! doc = + Custom.single + $"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'Id' = @id" + [ SqliteParameter("@id", "one") ] + fromData + Expect.isSome doc "There should have been a document returned" + Expect.equal doc.Value.Id "one" "The incorrect document was returned" + } + testTask "succeeds when a row is not found" { + use! db = SqliteDb.BuildDb() + do! loadDocs () + + let! doc = + Custom.single + $"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'Id' = @id" + [ SqliteParameter("@id", "eighty") ] + fromData + Expect.isNone doc "There should not have been a document returned" + } + ] + testList "list" [ + testTask "succeeds when data is found" { + use! db = SqliteDb.BuildDb() + do! loadDocs () + + let! docs = Custom.list (Query.selectFromTable SqliteDb.TableName) [] fromData + Expect.hasCountOf docs 5u (fun _ -> true) "There should have been 5 documents returned" + } + testTask "succeeds when data is not found" { + use! db = SqliteDb.BuildDb() + do! loadDocs () + + let! docs = + Custom.list + $"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'NumValue' > @value" + [ SqliteParameter("@value", 100) ] + fromData + Expect.isEmpty docs "There should have been no documents returned" + } + ] + testList "nonQuery" [ + testTask "succeeds when operating on data" { + use! db = SqliteDb.BuildDb() + do! loadDocs () + + do! Custom.nonQuery $"DELETE FROM {SqliteDb.TableName}" [] + + let! remaining = Count.all SqliteDb.TableName + Expect.equal remaining 0L "There should be no documents remaining in the table" + } + testTask "succeeds when no data matches where clause" { + use! db = SqliteDb.BuildDb() + do! loadDocs () + + do! Custom.nonQuery + $"DELETE FROM {SqliteDb.TableName} WHERE data ->> 'NumValue' > @value" + [ SqliteParameter("@value", 100) ] + + let! remaining = Count.all SqliteDb.TableName + Expect.equal remaining 5L "There should be 5 documents remaining in the table" + } + ] + testTask "scalar succeeds" { + use! db = SqliteDb.BuildDb() + + 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" [ testTask "ensureTable succeeds" { use! db = SqliteDb.BuildDb() @@ -160,13 +220,13 @@ let integrationTests = do! insert SqliteDb.TableName testDoc let! before = Find.byId SqliteDb.TableName "test" - if Option.isNone before then Expect.isTrue false "There should have been a document returned" + Expect.isSome before "There should have been a document returned" Expect.equal before.Value testDoc "The document is not correct" let upd8Doc = { testDoc with Sub = Some { Foo = "c"; Bar = "d" } } do! save SqliteDb.TableName upd8Doc let! after = Find.byId SqliteDb.TableName "test" - if Option.isNone after then Expect.isTrue false "There should have been a document returned post-update" + Expect.isSome after "There should have been a document returned post-update" Expect.equal after.Value upd8Doc "The updated document is not correct" } ] @@ -311,15 +371,14 @@ let integrationTests = let testDoc = { emptyDoc with Id = "one"; Sub = Some { Foo = "blue"; Bar = "red" } } do! Update.full SqliteDb.TableName "one" testDoc let! after = Find.byId SqliteDb.TableName "one" - if Option.isNone after then - Expect.isTrue false "There should have been a document returned post-update" + Expect.isSome after "There should have been a document returned post-update" Expect.equal after.Value testDoc "The updated document is not correct" } testTask "succeeds when no document is updated" { use! db = SqliteDb.BuildDb() let! before = Find.all SqliteDb.TableName - Expect.hasCountOf before 0u isTrue "There should have been no documents returned" + Expect.isEmpty before "There should have been no documents returned" // This not raising an exception is the test do! Update.full @@ -335,8 +394,7 @@ let integrationTests = do! Update.fullFunc SqliteDb.TableName (_.Id) { Id = "one"; Value = "le un"; NumValue = 1; Sub = None } let! after = Find.byId SqliteDb.TableName "one" - if Option.isNone after then - Expect.isTrue false "There should have been a document returned post-update" + Expect.isSome after "There should have been a document returned post-update" Expect.equal after.Value { Id = "one"; Value = "le un"; NumValue = 1; Sub = None } @@ -346,7 +404,7 @@ let integrationTests = use! db = SqliteDb.BuildDb() let! before = Find.all SqliteDb.TableName - Expect.hasCountOf before 0u isTrue "There should have been no documents returned" + Expect.isEmpty before "There should have been no documents returned" // This not raising an exception is the test do! Update.fullFunc SqliteDb.TableName (_.Id) { Id = "one"; Value = "le un"; NumValue = 1; Sub = None } @@ -359,15 +417,14 @@ let integrationTests = do! Update.partialById SqliteDb.TableName "one" {| NumValue = 44 |} let! after = Find.byId SqliteDb.TableName "one" - if Option.isNone after then - Expect.isTrue false "There should have been a document returned post-update" + Expect.isSome after "There should have been a document returned post-update" Expect.equal after.Value.NumValue 44 "The updated document is not correct" } testTask "succeeds when no document is updated" { use! db = SqliteDb.BuildDb() let! before = Find.all SqliteDb.TableName - Expect.hasCountOf before 0u isTrue "There should have been no documents returned" + Expect.isEmpty before "There should have been no documents returned" // This not raising an exception is the test do! Update.partialById SqliteDb.TableName "test" {| Foo = "green" |} @@ -386,7 +443,7 @@ let integrationTests = use! db = SqliteDb.BuildDb() let! before = Find.all SqliteDb.TableName - Expect.hasCountOf before 0u isTrue "There should have been no documents returned" + Expect.isEmpty before "There should have been no documents returned" // This not raising an exception is the test do! Update.partialByField SqliteDb.TableName "Value" EQ "burgundy" {| Foo = "green" |} @@ -431,524 +488,6 @@ let integrationTests = } ] ] - testList "Custom" [ - testList "single" [ - testTask "succeeds when a row is found" { - use! db = SqliteDb.BuildDb() - do! loadDocs () - - let! doc = - Custom.single - $"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'Id' = @id" - [ SqliteParameter("@id", "one") ] - fromData - Expect.isSome doc "There should have been a document returned" - Expect.equal doc.Value.Id "one" "The incorrect document was returned" - } - testTask "succeeds when a row is not found" { - use! db = SqliteDb.BuildDb() - do! loadDocs () - - let! doc = - Custom.single - $"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'Id' = @id" - [ SqliteParameter("@id", "eighty") ] - fromData - Expect.isNone doc "There should not have been a document returned" - } - ] - testList "list" [ - testTask "succeeds when data is found" { - use! db = SqliteDb.BuildDb() - do! loadDocs () - - let! docs = Custom.list (Query.selectFromTable SqliteDb.TableName) [] fromData - Expect.hasCountOf docs 5u isTrue "There should have been 5 documents returned" - } - testTask "succeeds when data is not found" { - use! db = SqliteDb.BuildDb() - do! loadDocs () - - let! docs = - Custom.list - $"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'NumValue' > @value" - [ SqliteParameter("@value", 100) ] - fromData - Expect.isEmpty docs "There should have been no documents returned" - } - ] - testList "nonQuery" [ - testTask "succeeds when operating on data" { - use! db = SqliteDb.BuildDb() - do! loadDocs () - - do! Custom.nonQuery $"DELETE FROM {SqliteDb.TableName}" [] - - let! remaining = Count.all SqliteDb.TableName - Expect.equal remaining 0L "There should be no documents remaining in the table" - } - testTask "succeeds when no data matches where clause" { - use! db = SqliteDb.BuildDb() - do! loadDocs () - - do! Custom.nonQuery - $"DELETE FROM {SqliteDb.TableName} WHERE data ->> 'NumValue' > @value" - [ SqliteParameter("@value", 100) ] - - let! remaining = Count.all SqliteDb.TableName - Expect.equal remaining 5L "There should be 5 documents remaining in the table" - } - ] - testTask "scalar succeeds" { - use! db = SqliteDb.BuildDb() - - let! nbr = Custom.scalar "SELECT 5 AS test_value" [] _.GetInt32(0) - Expect.equal nbr 5 "The query should have returned the number 5" - } - ] - testList "Extensions" [ - testTask "ensureTable succeeds" { - use! db = SqliteDb.BuildDb() - use conn = Configuration.dbConn () - let itExists (name: string) = task { - let! result = - conn.customScalar - $"SELECT EXISTS (SELECT 1 FROM {SqliteDb.Catalog} WHERE name = @name) AS it" - [ SqliteParameter("@name", name) ] - _.GetInt64(0) - return result > 0 - } - - let! exists = itExists "ensured" - let! alsoExists = itExists "idx_ensured_key" - Expect.isFalse exists "The table should not exist already" - Expect.isFalse alsoExists "The key index should not exist already" - - do! conn.ensureTable "ensured" - let! exists' = itExists "ensured" - let! alsoExists' = itExists "idx_ensured_key" - Expect.isTrue exists' "The table should now exist" - Expect.isTrue alsoExists' "The key index should now exist" - } - testList "insert" [ - testTask "succeeds" { - use! db = SqliteDb.BuildDb() - use conn = Configuration.dbConn () - let! before = conn.findAll SqliteDb.TableName - Expect.equal before [] "There should be no documents in the table" - - let testDoc = { emptyDoc with Id = "turkey"; Sub = Some { Foo = "gobble"; Bar = "gobble" } } - do! conn.insert SqliteDb.TableName testDoc - let! after = conn.findAll SqliteDb.TableName - Expect.equal after [ testDoc ] "There should have been one document inserted" - } - testTask "fails for duplicate key" { - use! db = SqliteDb.BuildDb() - use conn = Configuration.dbConn () - do! conn.insert SqliteDb.TableName { emptyDoc with Id = "test" } - Expect.throws - (fun () -> - conn.insert SqliteDb.TableName {emptyDoc with Id = "test" } - |> Async.AwaitTask - |> Async.RunSynchronously) - "An exception should have been raised for duplicate document ID insert" - } - ] - testList "save" [ - testTask "succeeds when a document is inserted" { - use! db = SqliteDb.BuildDb() - use conn = Configuration.dbConn () - let! before = conn.findAll SqliteDb.TableName - Expect.equal before [] "There should be no documents in the table" - - let testDoc = { emptyDoc with Id = "test"; Sub = Some { Foo = "a"; Bar = "b" } } - do! conn.save SqliteDb.TableName testDoc - let! after = conn.findAll SqliteDb.TableName - Expect.equal after [ testDoc ] "There should have been one document inserted" - } - testTask "succeeds when a document is updated" { - use! db = SqliteDb.BuildDb() - use conn = Configuration.dbConn () - let testDoc = { emptyDoc with Id = "test"; Sub = Some { Foo = "a"; Bar = "b" } } - do! conn.insert SqliteDb.TableName testDoc - - let! before = conn.findById SqliteDb.TableName "test" - if Option.isNone before then Expect.isTrue false "There should have been a document returned" - Expect.equal before.Value testDoc "The document is not correct" - - let upd8Doc = { testDoc with Sub = Some { Foo = "c"; Bar = "d" } } - do! conn.save SqliteDb.TableName upd8Doc - let! after = conn.findById SqliteDb.TableName "test" - if Option.isNone after then - Expect.isTrue false "There should have been a document returned post-update" - Expect.equal after.Value upd8Doc "The updated document is not correct" - } - ] - testTask "countAll succeeds" { - use! db = SqliteDb.BuildDb() - use conn = Configuration.dbConn () - do! loadDocs () - - let! theCount = conn.countAll SqliteDb.TableName - Expect.equal theCount 5L "There should have been 5 matching documents" - } - testTask "countByField succeeds" { - use! db = SqliteDb.BuildDb() - use conn = Configuration.dbConn () - do! loadDocs () - - let! theCount = conn.countByField SqliteDb.TableName "Value" EQ "purple" - Expect.equal theCount 2L "There should have been 2 matching documents" - } - testList "existsById" [ - testTask "succeeds when a document exists" { - use! db = SqliteDb.BuildDb() - use conn = Configuration.dbConn () - do! loadDocs () - - let! exists = conn.existsById SqliteDb.TableName "three" - Expect.isTrue exists "There should have been an existing document" - } - testTask "succeeds when a document does not exist" { - use! db = SqliteDb.BuildDb() - use conn = Configuration.dbConn () - do! loadDocs () - - let! exists = conn.existsById SqliteDb.TableName "seven" - Expect.isFalse exists "There should not have been an existing document" - } - ] - testList "existsByField" [ - testTask "succeeds when documents exist" { - use! db = SqliteDb.BuildDb() - use conn = Configuration.dbConn () - do! loadDocs () - - let! exists = conn.existsByField SqliteDb.TableName "NumValue" EQ 10 - Expect.isTrue exists "There should have been existing documents" - } - testTask "succeeds when no matching documents exist" { - use! db = SqliteDb.BuildDb() - use conn = Configuration.dbConn () - do! loadDocs () - - let! exists = conn.existsByField SqliteDb.TableName "Nothing" EQ "none" - Expect.isFalse exists "There should not have been any existing documents" - } - ] - testList "findAll" [ - testTask "succeeds when there is data" { - use! db = SqliteDb.BuildDb() - use conn = Configuration.dbConn () - - do! insert SqliteDb.TableName { Foo = "one"; Bar = "two" } - do! insert SqliteDb.TableName { Foo = "three"; Bar = "four" } - do! insert SqliteDb.TableName { Foo = "five"; Bar = "six" } - - let! results = conn.findAll 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" - } - testTask "succeeds when there is no data" { - use! db = SqliteDb.BuildDb() - use conn = Configuration.dbConn () - let! results = conn.findAll SqliteDb.TableName - Expect.equal results [] "There should have been no documents returned" - } - ] - testList "findById" [ - testTask "succeeds when a document is found" { - use! db = SqliteDb.BuildDb() - use conn = Configuration.dbConn () - do! loadDocs () - - let! doc = conn.findById SqliteDb.TableName "two" - Expect.isTrue (Option.isSome doc) "There should have been a document returned" - Expect.equal doc.Value.Id "two" "The incorrect document was returned" - } - testTask "succeeds when a document is not found" { - use! db = SqliteDb.BuildDb() - use conn = Configuration.dbConn () - do! loadDocs () - - let! doc = conn.findById SqliteDb.TableName "three hundred eighty-seven" - Expect.isFalse (Option.isSome doc) "There should not have been a document returned" - } - ] - testList "findByField" [ - testTask "succeeds when documents are found" { - use! db = SqliteDb.BuildDb() - use conn = Configuration.dbConn () - do! loadDocs () - - let! docs = conn.findByField SqliteDb.TableName "Sub.Foo" EQ "green" - Expect.equal (List.length docs) 2 "There should have been two documents returned" - } - testTask "succeeds when documents are not found" { - use! db = SqliteDb.BuildDb() - use conn = Configuration.dbConn () - do! loadDocs () - - let! docs = conn.findByField SqliteDb.TableName "Value" EQ "mauve" - Expect.isTrue (List.isEmpty docs) "There should have been no documents returned" - } - ] - testList "findFirstByField" [ - testTask "succeeds when a document is found" { - use! db = SqliteDb.BuildDb() - use conn = Configuration.dbConn () - do! loadDocs () - - let! doc = conn.findFirstByField SqliteDb.TableName "Value" EQ "another" - Expect.isTrue (Option.isSome doc) "There should have been a document returned" - Expect.equal doc.Value.Id "two" "The incorrect document was returned" - } - testTask "succeeds when multiple documents are found" { - use! db = SqliteDb.BuildDb() - use conn = Configuration.dbConn () - do! loadDocs () - - let! doc = conn.findFirstByField SqliteDb.TableName "Sub.Foo" EQ "green" - Expect.isTrue (Option.isSome doc) "There should have been a document returned" - Expect.contains [ "two"; "four" ] doc.Value.Id "An incorrect document was returned" - } - testTask "succeeds when a document is not found" { - use! db = SqliteDb.BuildDb() - use conn = Configuration.dbConn () - do! loadDocs () - - let! doc = conn.findFirstByField SqliteDb.TableName "Value" EQ "absent" - Expect.isFalse (Option.isSome doc) "There should not have been a document returned" - } - ] - testList "updateFull" [ - testTask "succeeds when a document is updated" { - use! db = SqliteDb.BuildDb() - use conn = Configuration.dbConn () - do! loadDocs () - - let testDoc = { emptyDoc with Id = "one"; Sub = Some { Foo = "blue"; Bar = "red" } } - do! conn.updateFull SqliteDb.TableName "one" testDoc - let! after = conn.findById SqliteDb.TableName "one" - if Option.isNone after then - Expect.isTrue false "There should have been a document returned post-update" - Expect.equal after.Value testDoc "The updated document is not correct" - } - testTask "succeeds when no document is updated" { - use! db = SqliteDb.BuildDb() - use conn = Configuration.dbConn () - - let! before = conn.findAll SqliteDb.TableName - Expect.hasCountOf before 0u isTrue "There should have been no documents returned" - - // This not raising an exception is the test - do! conn.updateFull - SqliteDb.TableName - "test" - { emptyDoc with Id = "x"; Sub = Some { Foo = "blue"; Bar = "red" } } - } - ] - testList "updateFullFunc" [ - testTask "succeeds when a document is updated" { - use! db = SqliteDb.BuildDb() - use conn = Configuration.dbConn () - do! loadDocs () - - do! conn.updateFullFunc - SqliteDb.TableName - (_.Id) - { Id = "one"; Value = "le un"; NumValue = 1; Sub = None } - let! after = conn.findById SqliteDb.TableName "one" - if Option.isNone after then - Expect.isTrue false "There should have been a document returned post-update" - Expect.equal - after.Value - { Id = "one"; Value = "le un"; NumValue = 1; Sub = None } - "The updated document is not correct" - } - testTask "succeeds when no document is updated" { - use! db = SqliteDb.BuildDb() - use conn = Configuration.dbConn () - - let! before = conn.findAll SqliteDb.TableName - Expect.hasCountOf before 0u isTrue "There should have been no documents returned" - - // This not raising an exception is the test - do! conn.updateFullFunc - SqliteDb.TableName - (_.Id) - { Id = "one"; Value = "le un"; NumValue = 1; Sub = None } - } - ] - testList "updatePartialById" [ - testTask "succeeds when a document is updated" { - use! db = SqliteDb.BuildDb() - use conn = Configuration.dbConn () - do! loadDocs () - - do! conn.updatePartialById SqliteDb.TableName "one" {| NumValue = 44 |} - let! after = conn.findById SqliteDb.TableName "one" - if Option.isNone after then - Expect.isTrue false "There should have been a document returned post-update" - Expect.equal after.Value.NumValue 44 "The updated document is not correct" - } - testTask "succeeds when no document is updated" { - use! db = SqliteDb.BuildDb() - use conn = Configuration.dbConn () - - let! before = conn.findAll SqliteDb.TableName - Expect.hasCountOf before 0u isTrue "There should have been no documents returned" - - // This not raising an exception is the test - do! conn.updatePartialById SqliteDb.TableName "test" {| Foo = "green" |} - } - ] - testList "updatePartialByField" [ - testTask "succeeds when a document is updated" { - use! db = SqliteDb.BuildDb() - use conn = Configuration.dbConn () - do! loadDocs () - - do! conn.updatePartialByField SqliteDb.TableName "Value" EQ "purple" {| NumValue = 77 |} - let! after = conn.countByField SqliteDb.TableName "NumValue" EQ 77 - Expect.equal after 2L "There should have been 2 documents returned" - } - testTask "succeeds when no document is updated" { - use! db = SqliteDb.BuildDb() - use conn = Configuration.dbConn () - - let! before = conn.findAll SqliteDb.TableName - Expect.hasCountOf before 0u isTrue "There should have been no documents returned" - - // This not raising an exception is the test - do! conn.updatePartialByField SqliteDb.TableName "Value" EQ "burgundy" {| Foo = "green" |} - } - ] - testList "deleteById" [ - testTask "succeeds when a document is deleted" { - use! db = SqliteDb.BuildDb() - use conn = Configuration.dbConn () - do! loadDocs () - - do! conn.deleteById SqliteDb.TableName "four" - let! remaining = conn.countAll SqliteDb.TableName - Expect.equal remaining 4L "There should have been 4 documents remaining" - } - testTask "succeeds when a document is not deleted" { - use! db = SqliteDb.BuildDb() - use conn = Configuration.dbConn () - do! loadDocs () - - do! conn.deleteById SqliteDb.TableName "thirty" - let! remaining = conn.countAll SqliteDb.TableName - Expect.equal remaining 5L "There should have been 5 documents remaining" - } - ] - testList "deleteByField" [ - testTask "succeeds when documents are deleted" { - use! db = SqliteDb.BuildDb() - use conn = Configuration.dbConn () - do! loadDocs () - - do! conn.deleteByField SqliteDb.TableName "Value" NE "purple" - let! remaining = conn.countAll SqliteDb.TableName - Expect.equal remaining 2L "There should have been 2 documents remaining" - } - testTask "succeeds when documents are not deleted" { - use! db = SqliteDb.BuildDb() - use conn = Configuration.dbConn () - do! loadDocs () - - do! conn.deleteByField SqliteDb.TableName "Value" EQ "crimson" - let! remaining = conn.countAll SqliteDb.TableName - Expect.equal remaining 5L "There should have been 5 documents remaining" - } - ] - testList "customSingle" [ - testTask "succeeds when a row is found" { - use! db = SqliteDb.BuildDb() - use conn = Configuration.dbConn () - do! loadDocs () - - let! doc = - conn.customSingle - $"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'Id' = @id" - [ SqliteParameter("@id", "one") ] - fromData - Expect.isSome doc "There should have been a document returned" - Expect.equal doc.Value.Id "one" "The incorrect document was returned" - } - testTask "succeeds when a row is not found" { - use! db = SqliteDb.BuildDb() - use conn = Configuration.dbConn () - do! loadDocs () - - let! doc = - conn.customSingle - $"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'Id' = @id" - [ SqliteParameter("@id", "eighty") ] - fromData - Expect.isNone doc "There should not have been a document returned" - } - ] - testList "customList" [ - testTask "succeeds when data is found" { - use! db = SqliteDb.BuildDb() - use conn = Configuration.dbConn () - do! loadDocs () - - let! docs = conn.customList (Query.selectFromTable SqliteDb.TableName) [] fromData - Expect.hasCountOf docs 5u isTrue "There should have been 5 documents returned" - } - testTask "succeeds when data is not found" { - use! db = SqliteDb.BuildDb() - use conn = Configuration.dbConn () - do! loadDocs () - - let! docs = - conn.customList - $"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'NumValue' > @value" - [ SqliteParameter("@value", 100) ] - fromData - Expect.isEmpty docs "There should have been no documents returned" - } - ] - testList "customNonQuery" [ - testTask "succeeds when operating on data" { - use! db = SqliteDb.BuildDb() - use conn = Configuration.dbConn () - do! loadDocs () - - do! conn.customNonQuery $"DELETE FROM {SqliteDb.TableName}" [] - - let! remaining = conn.countAll SqliteDb.TableName - Expect.equal remaining 0L "There should be no documents remaining in the table" - } - testTask "succeeds when no data matches where clause" { - use! db = SqliteDb.BuildDb() - use conn = Configuration.dbConn () - do! loadDocs () - - do! conn.customNonQuery - $"DELETE FROM {SqliteDb.TableName} WHERE data ->> 'NumValue' > @value" - [ SqliteParameter("@value", 100) ] - - let! remaining = conn.countAll SqliteDb.TableName - Expect.equal remaining 5L "There should be 5 documents remaining in the table" - } - ] - testTask "customScalar succeeds" { - use! db = SqliteDb.BuildDb() - use conn = Configuration.dbConn () - - let! nbr = conn.customScalar "SELECT 5 AS test_value" [] _.GetInt32(0) - Expect.equal nbr 5 "The query should have returned the number 5" - } - ] test "clean up database" { Configuration.useConnectionString "data source=:memory:" } diff --git a/src/Tests/Types.fs b/src/Tests/Types.fs new file mode 100644 index 0000000..e696a2a --- /dev/null +++ b/src/Tests/Types.fs @@ -0,0 +1,13 @@ +module Types + +type SubDocument = + { Foo: string + Bar: string } + +type JsonDocument = + { Id: string + Value: string + NumValue: int + Sub: SubDocument option } + +let emptyDoc = { Id = ""; Value = ""; NumValue = 0; Sub = None }