diff --git a/src/Postgres/Library.fs b/src/Postgres/Library.fs index e189f83..1ea570a 100644 --- a/src/Postgres/Library.fs +++ b/src/Postgres/Library.fs @@ -18,11 +18,13 @@ module Configuration = let mutable private dataSourceValue : NpgsqlDataSource option = None /// Register a data source to use for query execution (disposes the current one if it exists) + [] let useDataSource source = if Option.isSome dataSourceValue then dataSourceValue.Value.Dispose() dataSourceValue <- Some source /// Retrieve the currently configured data source + [] let dataSource () = match dataSourceValue with | Some source -> source @@ -85,8 +87,8 @@ module Query = Query.Definition.ensureTableFor name "JSONB" /// SQL statement to create an index on JSON documents in the specified table - [] - let ensureJsonIndex (name: string) idxType = + [] + let ensureDocumentIndex (name: string) idxType = let extraOps = match idxType with Full -> "" | Optimized -> " jsonb_path_ops" let tableName = name.Split '.' |> Array.last $"CREATE INDEX IF NOT EXISTS idx_{tableName}_document ON {name} USING GIN (data{extraOps})" @@ -266,9 +268,9 @@ module WithProps = } /// Create an index on documents in the specified table - [] - let ensureJsonIndex name idxType sqlProps = - Custom.nonQuery (Query.Definition.ensureJsonIndex name idxType) [] sqlProps + [] + let ensureDocumentIndex name idxType sqlProps = + Custom.nonQuery (Query.Definition.ensureDocumentIndex name idxType) [] sqlProps /// Create an index on field(s) within documents in the specified table [] @@ -549,9 +551,9 @@ module Definition = WithProps.Definition.ensureTable name (fromDataSource ()) /// Create an index on documents in the specified table - [] - let ensureJsonIndex name idxType = - WithProps.Definition.ensureJsonIndex name idxType (fromDataSource ()) + [] + let ensureDocumentIndex name idxType = + WithProps.Definition.ensureDocumentIndex name idxType (fromDataSource ()) /// Create an index on field(s) within documents in the specified table [] diff --git a/src/Tests.CSharp/CommonCSharpTests.cs b/src/Tests.CSharp/CommonCSharpTests.cs index 4d755a5..ecfb9f9 100644 --- a/src/Tests.CSharp/CommonCSharpTests.cs +++ b/src/Tests.CSharp/CommonCSharpTests.cs @@ -23,218 +23,216 @@ public static class CommonCSharpTests /// Unit tests /// [Tests] - public static Test Unit = - TestList("Common.C# Unit", new[] - { - TestSequenced( - TestList("Configuration", new[] - { - TestCase("UseSerializer succeeds", () => - { - try - { - Configuration.UseSerializer(new TestSerializer()); - - var serialized = Configuration.Serializer().Serialize(new SubDocument - { - Foo = "howdy", - Bar = "bye" - }); - Expect.equal(serialized, "{\"Overridden\":true}", "Specified serializer was not used"); - - var deserialized = Configuration.Serializer() - .Deserialize("{\"Something\":\"here\"}"); - Expect.isNull(deserialized, "Specified serializer should have returned null"); - } - finally - { - Configuration.UseSerializer(DocumentSerializer.Default); - } - }), - TestCase("Serializer returns configured serializer", () => - { - Expect.isTrue(ReferenceEquals(DocumentSerializer.Default, Configuration.Serializer()), - "Serializer should have been the same"); - }), - TestCase("UseIdField / IdField succeeds", () => - { - try - { - Expect.equal(Configuration.IdField(), "Id", - "The default configured ID field was incorrect"); - Configuration.UseIdField("id"); - Expect.equal(Configuration.IdField(), "id", "UseIdField did not set the ID field"); - } - finally - { - Configuration.UseIdField("Id"); - } - }) - })), - TestList("Op", new[] + public static readonly Test Unit = TestList("Common.C# Unit", new[] + { + TestSequenced( + TestList("Configuration", new[] { - TestCase("EQ succeeds", () => + TestCase("UseSerializer succeeds", () => { - Expect.equal(Op.EQ.ToString(), "=", "The equals operator was not correct"); + try + { + Configuration.UseSerializer(new TestSerializer()); + + var serialized = Configuration.Serializer().Serialize(new SubDocument + { + Foo = "howdy", + Bar = "bye" + }); + Expect.equal(serialized, "{\"Overridden\":true}", "Specified serializer was not used"); + + var deserialized = Configuration.Serializer() + .Deserialize("{\"Something\":\"here\"}"); + Expect.isNull(deserialized, "Specified serializer should have returned null"); + } + finally + { + Configuration.UseSerializer(DocumentSerializer.Default); + } }), - TestCase("GT succeeds", () => + TestCase("Serializer returns configured serializer", () => { - Expect.equal(Op.GT.ToString(), ">", "The greater than operator was not correct"); + Expect.isTrue(ReferenceEquals(DocumentSerializer.Default, Configuration.Serializer()), + "Serializer should have been the same"); }), - TestCase("GE succeeds", () => + TestCase("UseIdField / IdField succeeds", () => { - Expect.equal(Op.GE.ToString(), ">=", "The greater than or equal to operator was not correct"); + try + { + Expect.equal(Configuration.IdField(), "Id", + "The default configured ID field was incorrect"); + Configuration.UseIdField("id"); + Expect.equal(Configuration.IdField(), "id", "UseIdField did not set the ID field"); + } + finally + { + Configuration.UseIdField("Id"); + } + }) + })), + TestList("Op", new[] + { + TestCase("EQ succeeds", () => + { + Expect.equal(Op.EQ.ToString(), "=", "The equals operator was not correct"); + }), + TestCase("GT succeeds", () => + { + Expect.equal(Op.GT.ToString(), ">", "The greater than operator was not correct"); + }), + TestCase("GE succeeds", () => + { + Expect.equal(Op.GE.ToString(), ">=", "The greater than or equal to operator was not correct"); + }), + TestCase("LT succeeds", () => + { + Expect.equal(Op.LT.ToString(), "<", "The less than operator was not correct"); + }), + TestCase("LE succeeds", () => + { + Expect.equal(Op.LE.ToString(), "<=", "The less than or equal to operator was not correct"); + }), + TestCase("NE succeeds", () => + { + Expect.equal(Op.NE.ToString(), "<>", "The not equal to operator was not correct"); + }), + TestCase("EX succeeds", () => + { + Expect.equal(Op.EX.ToString(), "IS NOT NULL", "The \"exists\" operator was not correct"); + }), + TestCase("NEX succeeds", () => + { + Expect.equal(Op.NEX.ToString(), "IS NULL", "The \"not exists\" operator was not correct"); + }) + }), + TestList("Query", new[] + { + TestCase("SelectFromTable succeeds", () => + { + Expect.equal(Query.SelectFromTable("test.table"), "SELECT data FROM test.table", + "SELECT statement not correct"); + }), + TestCase("WhereById succeeds", () => + { + Expect.equal(Query.WhereById("@id"), "data ->> 'Id' = @id", "WHERE clause not correct"); + }), + TestList("WhereByField", new[] + { + TestCase("succeeds when a logical operator is passed", () => + { + Expect.equal(Query.WhereByField("theField", Op.GT, "@test"), "data ->> 'theField' > @test", + "WHERE clause not correct"); }), - TestCase("LT succeeds", () => + TestCase("succeeds when an existence operator is passed", () => { - Expect.equal(Op.LT.ToString(), "<", "The less than operator was not correct"); - }), - TestCase("LE succeeds", () => - { - Expect.equal(Op.LE.ToString(), "<=", "The less than or equal to operator was not correct"); - }), - TestCase("NE succeeds", () => - { - Expect.equal(Op.NE.ToString(), "<>", "The not equal to operator was not correct"); - }), - TestCase("EX succeeds", () => - { - Expect.equal(Op.EX.ToString(), "IS NOT NULL", "The \"exists\" operator was not correct"); - }), - TestCase("NEX succeeds", () => - { - Expect.equal(Op.NEX.ToString(), "IS NULL", "The \"not exists\" operator was not correct"); + Expect.equal(Query.WhereByField("thatField", Op.NEX, ""), "data ->> 'thatField' IS NULL", + "WHERE clause not correct"); }) }), - TestList("Query", new[] + TestList("Definition", new[] { - TestCase("SelectFromTable succeeds", () => + TestCase("EnsureTableFor succeeds", () => { - Expect.equal(Query.SelectFromTable("test.table"), "SELECT data FROM test.table", - "SELECT statement not correct"); + Expect.equal(Query.Definition.EnsureTableFor("my.table", "JSONB"), + "CREATE TABLE IF NOT EXISTS my.table (data JSONB NOT NULL)", + "CREATE TABLE statement not constructed correctly"); }), - TestCase("WhereById succeeds", () => + TestList("EnsureKey", new[] { - Expect.equal(Query.WhereById("@id"), "data ->> 'Id' = @id", "WHERE clause not correct"); - }), - TestList("WhereByField", new[] - { - TestCase("succeeds when a logical operator is passed", () => + TestCase("succeeds when a schema is present", () => { - Expect.equal(Query.WhereByField("theField", Op.GT, "@test"), "data ->> 'theField' > @test", - "WHERE clause not correct"); + Expect.equal(Query.Definition.EnsureKey("test.table"), + "CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON test.table ((data ->> 'Id'))", + "CREATE INDEX for key statement with schema not constructed correctly"); }), - TestCase("succeeds when an existence operator is passed", () => + TestCase("succeeds when a schema is not present", () => { - Expect.equal(Query.WhereByField("thatField", Op.NEX, ""), "data ->> 'thatField' IS NULL", - "WHERE clause not correct"); + Expect.equal(Query.Definition.EnsureKey("table"), + "CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON table ((data ->> 'Id'))", + "CREATE INDEX for key statement without schema not constructed correctly"); }) }), - TestList("Definition", new[] + TestCase("EnsureIndexOn succeeds for multiple fields and directions", () => { - TestCase("EnsureTableFor succeeds", () => - { - Expect.equal(Query.Definition.EnsureTableFor("my.table", "JSONB"), - "CREATE TABLE IF NOT EXISTS my.table (data JSONB NOT NULL)", - "CREATE TABLE statement not constructed correctly"); - }), - TestList("EnsureKey", new[] - { - TestCase("succeeds when a schema is present", () => - { - Expect.equal(Query.Definition.EnsureKey("test.table"), - "CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON test.table ((data ->> 'Id'))", - "CREATE INDEX for key statement with schema not constructed correctly"); - }), - TestCase("succeeds when a schema is not present", () => - { - Expect.equal(Query.Definition.EnsureKey("table"), - "CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON table ((data ->> 'Id'))", - "CREATE INDEX for key statement without schema not constructed correctly"); - }) - }), - TestCase("EnsureIndexOn succeeds for multiple fields and directions", () => - { - Expect.equal( - Query.Definition.EnsureIndexOn("test.table", "gibberish", - new[] { "taco", "guac DESC", "salsa ASC" }), - "CREATE INDEX IF NOT EXISTS idx_table_gibberish ON test.table " - + "((data ->> 'taco'), (data ->> 'guac') DESC, (data ->> 'salsa') ASC)", - "CREATE INDEX for multiple field statement incorrect"); - }) + Expect.equal( + Query.Definition.EnsureIndexOn("test.table", "gibberish", + new[] { "taco", "guac DESC", "salsa ASC" }), + "CREATE INDEX IF NOT EXISTS idx_table_gibberish ON test.table " + + "((data ->> 'taco'), (data ->> 'guac') DESC, (data ->> 'salsa') ASC)", + "CREATE INDEX for multiple field statement incorrect"); + }) + }), + TestCase("Insert succeeds", () => + { + Expect.equal(Query.Insert("tbl"), "INSERT INTO tbl VALUES (@data)", "INSERT statement not correct"); + }), + TestCase("Save succeeds", () => + { + Expect.equal(Query.Save("tbl"), + $"INSERT INTO tbl VALUES (@data) ON CONFLICT ((data ->> 'Id')) DO UPDATE SET data = EXCLUDED.data", + "INSERT ON CONFLICT UPDATE statement not correct"); + }), + TestList("Count", new[] + { + TestCase("All succeeds", () => + { + Expect.equal(Query.Count.All("tbl"), "SELECT COUNT(*) AS it FROM tbl", "Count query not correct"); }), - TestCase("Insert succeeds", () => + TestCase("ByField succeeds", () => { - Expect.equal(Query.Insert("tbl"), "INSERT INTO tbl VALUES (@data)", "INSERT statement not correct"); + Expect.equal(Query.Count.ByField("tbl", "thatField", Op.EQ), + "SELECT COUNT(*) AS it FROM tbl WHERE data ->> 'thatField' = @field", + "JSON field text comparison count query not correct"); + }) + }), + TestList("Exists", new[] + { + TestCase("ById succeeds", () => + { + Expect.equal(Query.Exists.ById("tbl"), + "SELECT EXISTS (SELECT 1 FROM tbl WHERE data ->> 'Id' = @id) AS it", + "ID existence query not correct"); }), - TestCase("Save succeeds", () => + TestCase("ByField succeeds", () => { - Expect.equal(Query.Save("tbl"), - $"INSERT INTO tbl VALUES (@data) ON CONFLICT ((data ->> 'Id')) DO UPDATE SET data = EXCLUDED.data", - "INSERT ON CONFLICT UPDATE statement not correct"); + Expect.equal(Query.Exists.ByField("tbl", "Test", Op.LT), + "SELECT EXISTS (SELECT 1 FROM tbl WHERE data ->> 'Test' < @field) AS it", + "JSON field text comparison exists query not correct"); + }) + }), + TestList("Find", new[] + { + TestCase("ById succeeds", () => + { + Expect.equal(Query.Find.ById("tbl"), "SELECT data FROM tbl WHERE data ->> 'Id' = @id", + "SELECT by ID query not correct"); }), - TestList("Count", new[] + TestCase("ByField succeeds", () => { - TestCase("All succeeds", () => - { - Expect.equal(Query.Count.All("tbl"), "SELECT COUNT(*) AS it FROM tbl", - "Count query not correct"); - }), - TestCase("ByField succeeds", () => - { - Expect.equal(Query.Count.ByField("tbl", "thatField", Op.EQ), - "SELECT COUNT(*) AS it FROM tbl WHERE data ->> 'thatField' = @field", - "JSON field text comparison count query not correct"); - }) + Expect.equal(Query.Find.ByField("tbl", "Golf", Op.GE), + "SELECT data FROM tbl WHERE data ->> 'Golf' >= @field", + "SELECT by JSON comparison query not correct"); + }) + }), + TestCase("Update.Full succeeds", () => + { + Expect.equal(Query.Update.Full("tbl"), "UPDATE tbl SET data = @data WHERE data ->> 'Id' = @id", + "UPDATE full statement not correct"); + }), + TestList("Delete", new[] + { + TestCase("ById succeeds", () => + { + Expect.equal(Query.Delete.ById("tbl"), "DELETE FROM tbl WHERE data ->> 'Id' = @id", + "DELETE by ID query not correct"); }), - TestList("Exists", new[] + TestCase("ByField succeeds", () => { - TestCase("ById succeeds", () => - { - Expect.equal(Query.Exists.ById("tbl"), - "SELECT EXISTS (SELECT 1 FROM tbl WHERE data ->> 'Id' = @id) AS it", - "ID existence query not correct"); - }), - TestCase("ByField succeeds", () => - { - Expect.equal(Query.Exists.ByField("tbl", "Test", Op.LT), - "SELECT EXISTS (SELECT 1 FROM tbl WHERE data ->> 'Test' < @field) AS it", - "JSON field text comparison exists query not correct"); - }) - }), - TestList("Find", new[] - { - TestCase("ById succeeds", () => - { - Expect.equal(Query.Find.ById("tbl"), "SELECT data FROM tbl WHERE data ->> 'Id' = @id", - "SELECT by ID query not correct"); - }), - TestCase("ByField succeeds", () => - { - Expect.equal(Query.Find.ByField("tbl", "Golf", Op.GE), - "SELECT data FROM tbl WHERE data ->> 'Golf' >= @field", - "SELECT by JSON comparison query not correct"); - }) - }), - TestCase("Update.Full succeeds", () => - { - Expect.equal(Query.Update.Full("tbl"), "UPDATE tbl SET data = @data WHERE data ->> 'Id' = @id", - "UPDATE full statement not correct"); - }), - TestList("Delete", new[] - { - TestCase("ById succeeds", () => - { - Expect.equal(Query.Delete.ById("tbl"), "DELETE FROM tbl WHERE data ->> 'Id' = @id", - "DELETE by ID query not correct"); - }), - TestCase("ByField succeeds", () => - { - Expect.equal(Query.Delete.ByField("tbl", "gone", Op.NEX), - "DELETE FROM tbl WHERE data ->> 'gone' IS NULL", - "DELETE by JSON comparison query not correct"); - }) + Expect.equal(Query.Delete.ByField("tbl", "gone", Op.NEX), + "DELETE FROM tbl WHERE data ->> 'gone' IS NULL", + "DELETE by JSON comparison query not correct"); }) }) - }); + }) + }); } diff --git a/src/Tests.CSharp/PostgresCSharpTests.cs b/src/Tests.CSharp/PostgresCSharpTests.cs index 34a623c..72af8cb 100644 --- a/src/Tests.CSharp/PostgresCSharpTests.cs +++ b/src/Tests.CSharp/PostgresCSharpTests.cs @@ -1,10 +1,11 @@ using Expecto.CSharp; using Expecto; using BitBadger.Documents.Postgres; -using Npgsql.FSharp; +using ThrowawayDb.Postgres; namespace BitBadger.Documents.Tests.CSharp; +using static CommonExtensionsAndTypesForNpgsqlFSharp; using static Runner; /// @@ -12,152 +13,161 @@ using static Runner; /// public class PostgresCSharpTests { - public static Test Unit = - TestList("Unit", new[] + /// + /// Tests which do not hit the database + /// + private static readonly Test Unit = TestList("Unit", new[] + { + TestList("Parameters", new[] { - TestList("Parameters", new[] + TestCase("Id succeeds", () => { - TestCase("Id succeeds", () => { - Expect.equal(Parameters.Id(88).Item1, "@id", "ID parameter not constructed correctly"); - }), - TestCase("Json succeeds", () => + var it = Parameters.Id(88); + Expect.equal(it.Item1, "@id", "ID parameter not constructed correctly"); + Expect.equal(it.Item2, Sql.@string("88"), "ID parameter value incorrect"); + }), + TestCase("Json succeeds", () => + { + var it = Parameters.Json("@test", new { Something = "good" }); + Expect.equal(it.Item1, "@test", "JSON parameter not constructed correctly"); + Expect.equal(it.Item2, Sql.jsonb("{\"Something\":\"good\"}"), "JSON parameter value incorrect"); + }), + TestCase("Field succeeds", () => + { + var it = Parameters.Field(242); + Expect.equal(it.Item1, "@field", "Field parameter not constructed correctly"); + Expect.isTrue(it.Item2.IsParameter, "Field parameter value incorrect"); + }), + TestCase("None succeeds", () => + { + Expect.isEmpty(Parameters.None, "The no-params sequence should be empty"); + }) + }), + TestList("Query", new[] + { + TestList("Definition", new[] + { + TestCase("EnsureTable succeeds", () => { - Expect.equal(Parameters.Json("@test", new { Something = "good" }).Item1, "@test", - "JSON parameter not constructed correctly"); + Expect.equal(Postgres.Query.Definition.EnsureTable(PostgresDb.TableName), + $"CREATE TABLE IF NOT EXISTS {PostgresDb.TableName} (data JSONB NOT NULL)", + "CREATE TABLE statement not constructed correctly"); }), - TestCase("Field succeeds", () => + TestCase("EnsureDocumentIndex succeeds for full index", () => { - Expect.equal(Parameters.Field(242).Item1, "@field", "Field parameter not constructed correctly"); + Expect.equal(Postgres.Query.Definition.EnsureDocumentIndex("schema.tbl", DocumentIndex.Full), + "CREATE INDEX IF NOT EXISTS idx_tbl_document ON schema.tbl USING GIN (data)", + "CREATE INDEX statement not constructed correctly"); }), - TestCase("None succeeds", () => { - Expect.isEmpty(Parameters.None, "The no-params sequence should be empty"); + TestCase("EnsureDocumentIndex succeeds for JSONB Path Ops index", () => + { + Expect.equal( + Postgres.Query.Definition.EnsureDocumentIndex(PostgresDb.TableName, DocumentIndex.Optimized), + string.Format( + "CREATE INDEX IF NOT EXISTS idx_{0}_document ON {0} USING GIN (data jsonb_path_ops)", + PostgresDb.TableName), + "CREATE INDEX statement not constructed correctly"); }) }), - TestList("Query", new[] + TestCase("WhereDataContains succeeds", () => { - TestList("Definition", new[] + Expect.equal(Postgres.Query.WhereDataContains("@test"), "data @> @test", + "WHERE clause not correct"); + }), + TestCase("WhereJsonPathMatches succeeds", () => + { + Expect.equal(Postgres.Query.WhereJsonPathMatches("@path"), "data @? @path::jsonpath", + "WHERE clause not correct"); + }), + TestList("Count", new[] + { + TestCase("ByContains succeeds", () => { - TestCase("EnsureTable succeeds", () => - { - Expect.equal(Postgres.Query.Definition.EnsureTable(PostgresDb.TableName), - $"CREATE TABLE IF NOT EXISTS {PostgresDb.TableName} (data JSONB NOT NULL)", - "CREATE TABLE statement not constructed correctly"); - }), - TestCase("EnsureJsonIndex succeeds for full index", () => - { - Expect.equal(Postgres.Query.Definition.EnsureJsonIndex("schema.tbl", DocumentIndex.Full), - "CREATE INDEX IF NOT EXISTS idx_tbl_document ON schema.tbl USING GIN (data)", - "CREATE INDEX statement not constructed correctly"); - }), - TestCase("EnsureJsonIndex succeeds for JSONB Path Ops index", () => - { - Expect.equal( - Postgres.Query.Definition.EnsureJsonIndex(PostgresDb.TableName, DocumentIndex.Optimized), - string.Format( - "CREATE INDEX IF NOT EXISTS idx_{0}_document ON {0} USING GIN (data jsonb_path_ops)", - PostgresDb.TableName), - "CREATE INDEX statement not constructed correctly"); - }) + Expect.equal(Postgres.Query.Count.ByContains(PostgresDb.TableName), + $"SELECT COUNT(*) AS it FROM {PostgresDb.TableName} WHERE data @> @criteria", + "JSON containment count query not correct"); }), - TestCase("WhereDataContains succeeds", () => + TestCase("ByJsonPath succeeds", () => { - Expect.equal(Postgres.Query.WhereDataContains("@test"), "data @> @test", - "WHERE clause not correct"); + Expect.equal(Postgres.Query.Count.ByJsonPath(PostgresDb.TableName), + $"SELECT COUNT(*) AS it FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath", + "JSON Path match count query not correct"); + }) + }), + TestList("Exists", new[] + { + TestCase("ByContains succeeds", () => + { + Expect.equal(Postgres.Query.Exists.ByContains(PostgresDb.TableName), + $"SELECT EXISTS (SELECT 1 FROM {PostgresDb.TableName} WHERE data @> @criteria) AS it", + "JSON containment exists query not correct"); }), - TestCase("WhereJsonPathMatches succeeds", () => + TestCase("byJsonPath succeeds", () => { - Expect.equal(Postgres.Query.WhereJsonPathMatches("@path"), "data @? @path::jsonpath", - "WHERE clause not correct"); + Expect.equal(Postgres.Query.Exists.ByJsonPath(PostgresDb.TableName), + $"SELECT EXISTS (SELECT 1 FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath) AS it", + "JSON Path match existence query not correct"); + }) + }), + TestList("Find", new[] + { + TestCase("byContains succeeds", () => + { + Expect.equal(Postgres.Query.Find.ByContains(PostgresDb.TableName), + $"SELECT data FROM {PostgresDb.TableName} WHERE data @> @criteria", + "SELECT by JSON containment query not correct"); }), - TestList("Count", new[] + TestCase("byJsonPath succeeds", () => { - TestCase("ByContains succeeds", () => - { - Expect.equal(Postgres.Query.Count.ByContains(PostgresDb.TableName), - $"SELECT COUNT(*) AS it FROM {PostgresDb.TableName} WHERE data @> @criteria", - "JSON containment count query not correct"); - }), - TestCase("ByJsonPath succeeds", () => - { - Expect.equal(Postgres.Query.Count.ByJsonPath(PostgresDb.TableName), - $"SELECT COUNT(*) AS it FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath", - "JSON Path match count query not correct"); - }) + Expect.equal(Postgres.Query.Find.ByJsonPath(PostgresDb.TableName), + $"SELECT data FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath", + "SELECT by JSON Path match query not correct"); + }) + }), + TestList("Update", new[] + { + TestCase("partialById succeeds", () => + { + Expect.equal(Postgres.Query.Update.PartialById(PostgresDb.TableName), + $"UPDATE {PostgresDb.TableName} SET data = data || @data WHERE data ->> 'Id' = @id", + "UPDATE partial by ID statement not correct"); }), - TestList("Exists", new[] + TestCase("partialByField succeeds", () => { - TestCase("ByContains succeeds", () => - { - Expect.equal(Postgres.Query.Exists.ByContains(PostgresDb.TableName), - $"SELECT EXISTS (SELECT 1 FROM {PostgresDb.TableName} WHERE data @> @criteria) AS it", - "JSON containment exists query not correct"); - }), - TestCase("byJsonPath succeeds", () => - { - Expect.equal(Postgres.Query.Exists.ByJsonPath(PostgresDb.TableName), - $"SELECT EXISTS (SELECT 1 FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath) AS it", - "JSON Path match existence query not correct"); - }) + Expect.equal(Postgres.Query.Update.PartialByField(PostgresDb.TableName, "Snail", Op.LT), + $"UPDATE {PostgresDb.TableName} SET data = data || @data WHERE data ->> 'Snail' < @field", + "UPDATE partial by ID statement not correct"); }), - TestList("Find", new[] + TestCase("partialByContains succeeds", () => { - TestCase("byContains succeeds", () => - { - Expect.equal(Postgres.Query.Find.ByContains(PostgresDb.TableName), - $"SELECT data FROM {PostgresDb.TableName} WHERE data @> @criteria", - "SELECT by JSON containment query not correct"); - }), - TestCase("byJsonPath succeeds", () => - { - Expect.equal(Postgres.Query.Find.ByJsonPath(PostgresDb.TableName), - $"SELECT data FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath", - "SELECT by JSON Path match query not correct"); - }) + Expect.equal(Postgres.Query.Update.PartialByContains(PostgresDb.TableName), + $"UPDATE {PostgresDb.TableName} SET data = data || @data WHERE data @> @criteria", + "UPDATE partial by JSON containment statement not correct"); }), - TestList("Update", new[] + TestCase("partialByJsonPath succeeds", () => { - TestCase("partialById succeeds", () => - { - Expect.equal(Postgres.Query.Update.PartialById(PostgresDb.TableName), - $"UPDATE {PostgresDb.TableName} SET data = data || @data WHERE data ->> 'Id' = @id", - "UPDATE partial by ID statement not correct"); - }), - TestCase("partialByField succeeds", () => - { - Expect.equal(Postgres.Query.Update.PartialByField(PostgresDb.TableName, "Snail", Op.LT), - $"UPDATE {PostgresDb.TableName} SET data = data || @data WHERE data ->> 'Snail' < @field", - "UPDATE partial by ID statement not correct"); - }), - TestCase("partialByContains succeeds", () => - { - Expect.equal(Postgres.Query.Update.PartialByContains(PostgresDb.TableName), - $"UPDATE {PostgresDb.TableName} SET data = data || @data WHERE data @> @criteria", - "UPDATE partial by JSON containment statement not correct"); - }), - TestCase("partialByJsonPath succeeds", () => - { - Expect.equal(Postgres.Query.Update.PartialByJsonPath(PostgresDb.TableName), - $"UPDATE {PostgresDb.TableName} SET data = data || @data WHERE data @? @path::jsonpath", - "UPDATE partial by JSON Path statement not correct"); - }) + Expect.equal(Postgres.Query.Update.PartialByJsonPath(PostgresDb.TableName), + $"UPDATE {PostgresDb.TableName} SET data = data || @data WHERE data @? @path::jsonpath", + "UPDATE partial by JSON Path statement not correct"); + }) + }), + TestList("Delete", new[] + { + TestCase("byContains succeeds", () => + { + Expect.equal(Postgres.Query.Delete.ByContains(PostgresDb.TableName), + $"DELETE FROM {PostgresDb.TableName} WHERE data @> @criteria", + "DELETE by JSON containment query not correct"); }), - TestList("Delete", new[] + TestCase("byJsonPath succeeds", () => { - TestCase("byContains succeeds", () => - { - Expect.equal(Postgres.Query.Delete.ByContains(PostgresDb.TableName), - $"DELETE FROM {PostgresDb.TableName} WHERE data @> @criteria", - "DELETE by JSON containment query not correct"); - }), - TestCase("byJsonPath succeeds", () => - { - Expect.equal(Postgres.Query.Delete.ByJsonPath(PostgresDb.TableName), - $"DELETE FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath", - "DELETE by JSON Path match query not correct"); - }) + Expect.equal(Postgres.Query.Delete.ByJsonPath(PostgresDb.TableName), + $"DELETE FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath", + "DELETE by JSON Path match query not correct"); }) }) - }); + }) + }); private static readonly List TestDocuments = new() { @@ -168,13 +178,794 @@ public class PostgresCSharpTests new() { Id = "five", Value = "purple", NumValue = 18 } }; + /// + /// Add the test documents to the database + /// internal static async Task LoadDocs() { foreach (var doc in TestDocuments) await Document.Insert(SqliteDb.TableName, doc); } + /// + /// Integration tests for the PostgreSQL library + /// + private static readonly Test Integration = TestList("Integration", new[] + { + TestList("Configuration", new[] + { + TestCase("UseDataSource disposes existing source", () => + { + using var db1 = ThrowawayDatabase.Create(PostgresDb.ConnStr.Value); + var source = PostgresDb.MkDataSource(db1.ConnectionString); + Postgres.Configuration.UseDataSource(source); + + using var db2 = ThrowawayDatabase.Create(PostgresDb.ConnStr.Value); + Postgres.Configuration.UseDataSource(PostgresDb.MkDataSource(db2.ConnectionString)); + try + { + _ = source.OpenConnection(); + Expect.isTrue(false, "Data source should have been disposed"); + } + catch (Exception) + { + // This is what should have happened + } + }), + TestCase("DataSource returns configured data source", () => + { + using var db = ThrowawayDatabase.Create(PostgresDb.ConnStr.Value); + var source = PostgresDb.MkDataSource(db.ConnectionString); + Postgres.Configuration.UseDataSource(source); + + Expect.isTrue(ReferenceEquals(source, Postgres.Configuration.DataSource()), + "Data source should have been the same"); + }) + }), + TestList("Custom", new[] + { + TestList("List", new[] + { + TestCase("succeeds when data is found", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var docs = await Custom.List(Query.SelectFromTable(PostgresDb.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 = PostgresDb.BuildDb(); + await LoadDocs(); + + var docs = await Custom.List( + $"SELECT data FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath", + new[] { Tuple.Create("@path", Sql.@string("$.NumValue ? (@ > 100)")) }, + Results.FromData); + Expect.isEmpty(docs, "There should have been no documents returned"); + }) + }), + TestList("Single", new[] + { + TestCase("succeeds when a row is found", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var doc = await Custom.Single($"SELECT data FROM {PostgresDb.TableName} WHERE data ->> 'Id' = @id", + new[] { Tuple.Create("@id", Sql.@string("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 = PostgresDb.BuildDb(); + await LoadDocs(); + + var doc = await Custom.Single($"SELECT data FROM {PostgresDb.TableName} WHERE data ->> 'Id' = @id", + new[] { Tuple.Create("@id", Sql.@string("eighty")) }, Results.FromData); + Expect.isNull(doc, "There should not have been a document returned"); + }) + }), + TestList("NonQuery", new[] + { + TestCase("succeeds when operating on data", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + await Custom.NonQuery($"DELETE FROM {PostgresDb.TableName}", Parameters.None); + + var remaining = await Count.All(PostgresDb.TableName); + Expect.equal(remaining, 0, "There should be no documents remaining in the table"); + }), + TestCase("succeeds when no data matches where clause", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + await Custom.NonQuery($"DELETE FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath", + new[] { Tuple.Create("@path", Sql.@string("$.NumValue ? (@ > 100)")) }); + + var remaining = await Count.All(PostgresDb.TableName); + Expect.equal(remaining, 5, "There should be 5 documents remaining in the table"); + }) + }), + TestCase("Scalar succeeds", async () => + { + await using var db = PostgresDb.BuildDb(); + + var nbr = await Custom.Scalar("SELECT 5 AS test_value", Parameters.None, row => row.@int("test_value")); + Expect.equal(nbr, 5, "The query should have returned the number 5"); + }) + }), + TestList("Definition", new[] + { + TestCase("EnsureTable succeeds", async () => + { + await using var db = PostgresDb.BuildDb(); + var tableExists = () => Custom.Scalar( + "SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'ensured') AS it", Parameters.None, + Results.ToExists); + var keyExists = () => Custom.Scalar( + "SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'idx_ensured_key') AS it", Parameters.None, + Results.ToExists); + + var exists = await tableExists(); + var alsoExists = await keyExists(); + Expect.isFalse(exists, "The table should not exist already"); + Expect.isFalse(alsoExists, "The key index should not exist already"); + + await Definition.EnsureTable("ensured"); + exists = await tableExists(); + alsoExists = await keyExists(); + Expect.isTrue(exists, "The table should now exist"); + Expect.isTrue(alsoExists, "The key index should now exist"); + }), + TestCase("EnsureDocumentIndex succeeds", async () => + { + await using var db = PostgresDb.BuildDb(); + var indexExists = () => Custom.Scalar( + "SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'idx_ensured_document') AS it", + Parameters.None, Results.ToExists); + + var exists = await indexExists(); + Expect.isFalse(exists, "The index should not exist already"); + + await Definition.EnsureTable("ensured"); + await Definition.EnsureDocumentIndex("ensured", DocumentIndex.Optimized); + exists = await indexExists(); + Expect.isTrue(exists, "The index should now exist"); + }), + TestCase("EnsureFieldIndex succeeds", async () => + { + await using var db = PostgresDb.BuildDb(); + var indexExists = () => Custom.Scalar( + "SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'idx_ensured_test') AS it", Parameters.None, + Results.ToExists); + + var exists = await indexExists(); + Expect.isFalse(exists, "The index should not exist already"); + + await Definition.EnsureTable("ensured"); + await Definition.EnsureFieldIndex("ensured", "test", new[] { "Id", "Category" }); + exists = await indexExists(); + Expect.isTrue(exists, "The index should now exist"); + }) + }), + TestList("Document", new[] + { + TestList("Insert", new[] + { + TestCase("succeeds", async () => + { + await using var db = PostgresDb.BuildDb(); + var before = await Count.All(PostgresDb.TableName); + Expect.equal(before, 0, "There should be no documents in the table"); + + await Document.Insert(PostgresDb.TableName, + new JsonDocument { Id = "turkey", Sub = new() { Foo = "gobble", Bar = "gobble" } }); + var after = await Count.All(PostgresDb.TableName); + Expect.equal(after, 1, "There should have been one document inserted"); + }), + TestCase("fails for duplicate key", async () => + { + await using var db = PostgresDb.BuildDb(); + await Document.Insert(PostgresDb.TableName, new JsonDocument { Id = "test" }); + try + { + await Document.Insert(PostgresDb.TableName, new JsonDocument { Id = "test" }); + Expect.isTrue(false, "An exception should have been raised for duplicate document ID insert"); + } + catch (Exception) + { + // This is what should have happened + } + }) + }), + TestList("Save", new[] + { + TestCase("succeeds when a document is inserted", async () => + { + await using var db = PostgresDb.BuildDb(); + var before = await Count.All(PostgresDb.TableName); + Expect.equal(before, 0, "There should be no documents in the table"); + + await Document.Save(PostgresDb.TableName, + new JsonDocument { Id = "test", Sub = new() { Foo = "a", Bar = "b" } }); + var after = await Count.All(PostgresDb.TableName); + Expect.equal(after, 1, "There should have been one document inserted"); + }), + TestCase("succeeds when a document is updated", async () => + { + await using var db = PostgresDb.BuildDb(); + await Document.Insert(PostgresDb.TableName, + new JsonDocument { Id = "test", Sub = new() { Foo = "a", Bar = "b" } }); + + var before = await Find.ById(PostgresDb.TableName, "test"); + Expect.isNotNull(before, "There should have been a document returned"); + Expect.equal(before.Id, "test", "The document is not correct"); + + before.Sub = new() { Foo = "c", Bar = "d" }; + await Document.Save(PostgresDb.TableName, before); + var after = await Find.ById(PostgresDb.TableName, "test"); + Expect.isNotNull(after, "There should have been a document returned post-update"); + Expect.equal(after.Id, "test", "The document is not correct"); + Expect.equal(after.Sub!.Foo, "c", "The updated document is not correct"); + }) + }) + }), + TestList("Count", new[] + { + TestCase("All succeeds", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var theCount = await Count.All(PostgresDb.TableName); + Expect.equal(theCount, 5, "There should have been 5 matching documents"); + }), + TestCase("ByField succeeds", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var theCount = await Count.ByField(PostgresDb.TableName, "Value", Op.EQ, "purple"); + Expect.equal(theCount, 2, "There should have been 2 matching documents"); + }), + TestCase("ByContains succeeds", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var theCount = await Count.ByContains(PostgresDb.TableName, new { Value = "purple" }); + Expect.equal(theCount, 2, "There should have been 2 matching documents"); + }), + TestCase("ByJsonPath succeeds", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var theCount = await Count.ByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ > 5)"); + Expect.equal(theCount, 3, "There should have been 3 matching documents"); + }) + }), + TestList("Exists", new[] + { + TestList("ById", new[] + { + TestCase("succeeds when a document exists", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var exists = await Exists.ById(PostgresDb.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 = PostgresDb.BuildDb(); + await LoadDocs(); + + var exists = await Exists.ById(PostgresDb.TableName, "seven"); + Expect.isFalse(exists, "There should not have been an existing document"); + }) + }), + TestList("ByField", new[] + { + TestCase("succeeds when documents exist", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var exists = await Exists.ByField(PostgresDb.TableName, "Sub", Op.NEX, ""); + Expect.isTrue(exists, "There should have been existing documents"); + }), + TestCase("succeeds when documents do not exist", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var exists = await Exists.ByField(PostgresDb.TableName, "NumValue", Op.EQ, "six"); + Expect.isFalse(exists, "There should not have been existing documents"); + }) + }), + TestList("ByContains", new[] + { + TestCase("succeeds when documents exist", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var exists = await Exists.ByContains(PostgresDb.TableName, new { NumValue = 10 }); + Expect.isTrue(exists, "There should have been existing documents"); + }), + TestCase("succeeds when no matching documents exist", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var exists = await Exists.ByContains(PostgresDb.TableName, new { Nothing = "none" }); + Expect.isFalse(exists, "There should not have been any existing documents"); + }) + }), + TestList("ByJsonPath", new[] { + TestCase("succeeds when documents exist", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var exists = await Exists.ByJsonPath(PostgresDb.TableName, "$.Sub.Foo ? (@ == \"green\")"); + Expect.isTrue(exists, "There should have been existing documents"); + }), + TestCase("succeeds when no matching documents exist", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var exists = await Exists.ByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ > 1000)"); + Expect.isFalse(exists, "There should not have been any existing documents"); + }) + }) + }), + TestList("Find", new[] + { + TestList("All", new[] + { + TestCase("succeeds when there is data", async () => + { + await using var db = PostgresDb.BuildDb(); + + await Document.Insert(PostgresDb.TableName, new SubDocument { Foo = "one", Bar = "two" }); + await Document.Insert(PostgresDb.TableName, new SubDocument { Foo = "three", Bar = "four" }); + await Document.Insert(PostgresDb.TableName, new SubDocument { Foo = "five", Bar = "six" }); + + var results = await Find.All(PostgresDb.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 = PostgresDb.BuildDb(); + var results = await Find.All(PostgresDb.TableName); + Expect.isEmpty(results, "There should have been no documents returned"); + }) + }), + TestList("ById", new[] + { + TestCase("succeeds when a document is found", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var doc = await Find.ById(PostgresDb.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 = PostgresDb.BuildDb(); + await LoadDocs(); + + var doc = await Find.ById(PostgresDb.TableName, "three hundred eighty-seven"); + Expect.isNull(doc, "There should not have been a document returned"); + }) + }), + TestList("ByField", new[] + { + TestCase("succeeds when documents are found", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var docs = await Find.ByField(PostgresDb.TableName, "Value", Op.EQ, "another"); + Expect.equal(docs.Count, 1, "There should have been one document returned"); + }), + TestCase("succeeds when documents are not found", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var docs = await Find.ByField(PostgresDb.TableName, "Value", Op.EQ, "mauve"); + Expect.isEmpty(docs, "There should have been no documents returned"); + }) + }), + TestList("ByContains", new[] + { + TestCase("succeeds when documents are found", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var docs = await Find.ByContains(PostgresDb.TableName, + new { Sub = new { Foo = "green" } }); + Expect.equal(docs.Count, 2, "There should have been two documents returned"); + }), + TestCase("succeeds when documents are not found", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var docs = await Find.ByContains(PostgresDb.TableName, new { Value = "mauve" }); + Expect.isEmpty(docs, "There should have been no documents returned"); + }) + }), + TestList("ByJsonPath", new[] + { + TestCase("succeeds when documents are found", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var docs = await Find.ByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ < 15)"); + Expect.equal(docs.Count, 3, "There should have been 3 documents returned"); + }), + TestCase("succeeds when documents are not found", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var docs = await Find.ByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ < 0)"); + Expect.isEmpty(docs, "There should have been no documents returned"); + }) + }), + TestList("FirstByField", new[] + { + TestCase("succeeds when a document is found", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var doc = await Find.FirstByField(PostgresDb.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 = PostgresDb.BuildDb(); + await LoadDocs(); + + var doc = await Find.FirstByField(PostgresDb.TableName, "Value", Op.EQ, "purple"); + Expect.isNotNull(doc, "There should have been a document returned"); + Expect.contains(new[] { "five", "four" }, doc.Id, "An incorrect document was returned"); + }), + TestCase("succeeds when a document is not found", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var doc = await Find.FirstByField(PostgresDb.TableName, "Value", Op.EQ, "absent"); + Expect.isNull(doc, "There should not have been a document returned"); + }) + }), + TestList("FirstByContains", new[] + { + TestCase("succeeds when a document is found", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var doc = await Find.FirstByContains(PostgresDb.TableName, new { Value = "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 = PostgresDb.BuildDb(); + await LoadDocs(); + + var doc = await Find.FirstByContains(PostgresDb.TableName, + new { Sub = new { Foo = "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 = PostgresDb.BuildDb(); + await LoadDocs(); + + var doc = await Find.FirstByContains(PostgresDb.TableName, new { Value = "absent" }); + Expect.isNull(doc, "There should not have been a document returned"); + }) + }), + TestList("FirstByJsonPath", new[] + { + TestCase("succeeds when a document is found", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var doc = await Find.FirstByJsonPath(PostgresDb.TableName, + "$.Value ? (@ == \"FIRST!\")"); + Expect.isNotNull(doc, "There should have been a document returned"); + Expect.equal(doc.Id, "one", "The incorrect document was returned"); + }), + TestCase("succeeds when multiple documents are found", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var doc = await Find.FirstByJsonPath(PostgresDb.TableName, + "$.Sub.Foo ? (@ == \"green\")"); + Expect.isNotNull(doc, "There should have been a document returned"); + Expect.contains(new[] { "two", "four" }, doc.Id, "An incorrect document was returned"); + }), + TestCase("succeeds when a document is not found", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var doc = await Find.FirstByJsonPath(PostgresDb.TableName, "$.Id ? (@ == \"nope\")"); + Expect.isNull(doc, "There should not have been a document returned"); + }) + }) + }), + TestList("Update", new[] + { + TestList("Full", new[] + { + TestCase("succeeds when a document is updated", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + await Update.Full(PostgresDb.TableName, "one", + new JsonDocument { Id = "one", Sub = new() { Foo = "blue", Bar = "red" } }); + var after = await Find.ById(PostgresDb.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 (ID)"); + Expect.equal(after.Value, "", "The updated document is not correct (Value)"); + Expect.equal(after.NumValue, 0, "The updated document is not correct (NumValue)"); + Expect.isNotNull(after.Sub, "The updated document should have had a sub-document"); + Expect.equal(after.Sub!.Foo, "blue", "The updated document is not correct (Sub.Foo)"); + Expect.equal(after.Sub.Bar, "red", "The updated document is not correct (Sub.Bar)"); + }), + TestCase("succeeds when no document is updated", async () => + { + await using var db = PostgresDb.BuildDb(); + + var before = await Count.All(PostgresDb.TableName); + Expect.equal(before, 0, "There should have been no documents returned"); + + // This not raising an exception is the test + await Update.Full(PostgresDb.TableName, "test", + new JsonDocument { Id = "x", Sub = new() { Foo = "blue", Bar = "red" } }); + }) + }), + TestList("FullFunc", new[] + { + TestCase("succeeds when a document is updated", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + await Update.FullFunc(PostgresDb.TableName, doc => doc.Id, + new JsonDocument { Id = "one", Value = "le un", NumValue = 1 }); + var after = await Find.ById(PostgresDb.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 (ID)"); + Expect.equal(after.Value, "le un", "The updated document is not correct (Value)"); + Expect.equal(after.NumValue, 1, "The updated document is not correct (NumValue)"); + Expect.isNull(after.Sub, "The updated document should not have had a sub-document"); + }), + TestCase("succeeds when no document is updated", async () => + { + await using var db = PostgresDb.BuildDb(); + + var before = await Count.All(PostgresDb.TableName); + Expect.equal(before, 0, "There should have been no documents returned"); + + // This not raising an exception is the test + await Update.FullFunc(PostgresDb.TableName, doc => doc.Id, + new JsonDocument { Id = "one", Value = "le un", NumValue = 1 }); + }) + }), + TestList("PartialById", new[] + { + TestCase("succeeds when a document is updated", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + await Update.PartialById(PostgresDb.TableName, "one", new { NumValue = 44 }); + var after = await Find.ById(PostgresDb.TableName, "one"); + Expect.isNotNull(after, "There should have been a document returned post-update"); + Expect.equal(after.NumValue, 44, "The updated document is not correct"); + }), + TestCase("succeeds when no document is updated", async () => + { + await using var db = PostgresDb.BuildDb(); + + var before = await Count.All(PostgresDb.TableName); + Expect.equal(before, 0, "There should have been no documents returned"); + + // This not raising an exception is the test + await Update.PartialById(PostgresDb.TableName, "test", new { Foo = "green" }); + }) + }), + TestList("PartialByField", new[] + { + TestCase("succeeds when a document is updated", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + await Update.PartialByField(PostgresDb.TableName, "Value", Op.EQ, "purple", new { NumValue = 77 }); + var after = await Count.ByField(PostgresDb.TableName, "NumValue", Op.EQ, "77"); + Expect.equal(after, 2, "There should have been 2 documents returned"); + }), + TestCase("succeeds when no document is updated", async () => + { + await using var db = PostgresDb.BuildDb(); + + var before = await Count.All(PostgresDb.TableName); + Expect.equal(before, 0, "There should have been no documents returned"); + + // This not raising an exception is the test + await Update.PartialByField(PostgresDb.TableName, "Value", Op.EQ, "burgundy", + new { Foo = "green" }); + }) + }), + TestList("PartialByContains", new[] + { + TestCase("succeeds when a document is updated", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + await Update.PartialByContains(PostgresDb.TableName, new { Value = "purple" }, + new { NumValue = 77 }); + var after = await Count.ByContains(PostgresDb.TableName, new { NumValue = 77 }); + Expect.equal(after, 2, "There should have been 2 documents returned"); + }), + TestCase("succeeds when no document is updated", async () => + { + await using var db = PostgresDb.BuildDb(); + + var before = await Count.All(PostgresDb.TableName); + Expect.equal(before, 0, "There should have been no documents returned"); + + // This not raising an exception is the test + await Update.PartialByContains(PostgresDb.TableName, new { Value = "burgundy" }, + new { Foo = "green" }); + }) + }), + TestList("PartialByJsonPath", new[] + { + TestCase("succeeds when a document is updated", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + await Update.PartialByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ > 10)", + new { NumValue = 1000 }); + var after = await Count.ByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ > 999)"); + Expect.equal(after, 2, "There should have been 2 documents returned"); + }), + TestCase("succeeds when no document is updated", async () => + { + await using var db = PostgresDb.BuildDb(); + + var before = await Count.All(PostgresDb.TableName); + Expect.equal(before, 0, "There should have been no documents returned"); + + // This not raising an exception is the test + await Update.PartialByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ < 0)", new { Foo = "green" }); + }) + }) + }), + TestList("Delete", new[] + { + TestList("ById", new[] + { + TestCase("succeeds when a document is deleted", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + await Delete.ById(PostgresDb.TableName, "four"); + var remaining = await Count.All(PostgresDb.TableName); + Expect.equal(remaining, 4, "There should have been 4 documents remaining"); + }), + TestCase("succeeds when a document is not deleted", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + await Delete.ById(PostgresDb.TableName, "thirty"); + var remaining = await Count.All(PostgresDb.TableName); + Expect.equal(remaining, 5, "There should have been 5 documents remaining"); + }) + }), + TestList("ByField", new[] + { + TestCase("succeeds when documents are deleted", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + await Delete.ByField(PostgresDb.TableName, "Value", Op.EQ, "purple"); + var remaining = await Count.All(PostgresDb.TableName); + Expect.equal(remaining, 3, "There should have been 3 documents remaining"); + }), + TestCase("succeeds when documents are not deleted", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + await Delete.ByField(PostgresDb.TableName, "Value", Op.EQ, "crimson"); + var remaining = await Count.All(PostgresDb.TableName); + Expect.equal(remaining, 5, "There should have been 5 documents remaining"); + }) + }), + TestList("ByContains", new[] + { + TestCase("succeeds when documents are deleted", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + await Delete.ByContains(PostgresDb.TableName, new { Value = "purple" }); + var remaining = await Count.All(PostgresDb.TableName); + Expect.equal(remaining, 3, "There should have been 3 documents remaining"); + }), + TestCase("succeeds when documents are not deleted", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + await Delete.ByContains(PostgresDb.TableName, new { Value = "crimson" }); + var remaining = await Count.All(PostgresDb.TableName); + Expect.equal(remaining, 5, "There should have been 5 documents remaining"); + }) + }), + TestList("ByJsonPath", new[] + { + TestCase("succeeds when documents are deleted", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + await Delete.ByJsonPath(PostgresDb.TableName, "$.Sub.Foo ? (@ == \"green\")"); + var remaining = await Count.All(PostgresDb.TableName); + Expect.equal(remaining, 3, "There should have been 3 documents remaining"); + }), + TestCase("succeeds when documents are not deleted", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + await Delete.ByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ > 100)"); + var remaining = await Count.All(PostgresDb.TableName); + Expect.equal(remaining, 5, "There should have been 5 documents remaining"); + }) + }) + }) + }); + /// /// All Postgres C# tests /// - public static Test All = TestList("Postgres.C#", new[] { Unit }); + [Tests] + public static readonly Test All = TestList("Postgres.C#", new[] { Unit, TestSequenced(Integration) }); } diff --git a/src/Tests.CSharp/PostgresDb.cs b/src/Tests.CSharp/PostgresDb.cs index c8d2411..2ee10e1 100644 --- a/src/Tests.CSharp/PostgresDb.cs +++ b/src/Tests.CSharp/PostgresDb.cs @@ -137,7 +137,7 @@ public static class PostgresDb Sql.executeNonQuery(Sql.query(Postgres.Query.Definition.EnsureTable(TableName), sqlProps)); Sql.executeNonQuery(Sql.query(Query.Definition.EnsureKey(TableName), sqlProps)); - Postgres.Configuration.useDataSource(MkDataSource(database.ConnectionString)); + Postgres.Configuration.UseDataSource(MkDataSource(database.ConnectionString)); return new ThrowawayPostgresDb(database); } diff --git a/src/Tests.CSharp/SqliteCSharpExtensionTests.cs b/src/Tests.CSharp/SqliteCSharpExtensionTests.cs index cabe980..9015d26 100644 --- a/src/Tests.CSharp/SqliteCSharpExtensionTests.cs +++ b/src/Tests.CSharp/SqliteCSharpExtensionTests.cs @@ -13,499 +13,493 @@ using static Runner; public static class SqliteCSharpExtensionTests { private static Task LoadDocs() => SqliteCSharpTests.LoadDocs(); - + + /// + /// Integration tests for the SQLite extension methods + /// [Tests] - public static Test Integration = - TestList("Extensions", new[] + public static readonly Test Integration = TestList("Extensions", new[] + { + TestList("CustomSingle", 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 () => + 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 theCount = await conn.CountAll(SqliteDb.TableName); - Expect.equal(theCount, 5L, "There should have been 5 matching documents"); + 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("CountByField succeeds", async () => + 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 theCount = await conn.CountByField(SqliteDb.TableName, "Value", Op.EQ, "purple"); - Expect.equal(theCount, 2L, "There should have been 2 matching documents"); - }), - TestList("ExistsById", new[] + 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 () => { - TestCase("succeeds when a document exists", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - await LoadDocs(); + 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"); - }) + 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"); }), - TestList("ExistsByField", new[] + TestCase("succeeds when data is not found", async () => { - TestCase("succeeds when documents exist", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - await LoadDocs(); + 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[] + 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 () => { - TestCase("succeeds when there is data", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + await LoadDocs(); - 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" }); + await conn.CustomNonQuery($"DELETE FROM {SqliteDb.TableName}", Parameters.None); - 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"); - }) + var remaining = await conn.CountAll(SqliteDb.TableName); + Expect.equal(remaining, 0L, "There should be no documents remaining in the table"); }), - TestList("FindById", new[] + TestCase("succeeds when no data matches where clause", async () => { - TestCase("succeeds when a document is found", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - await LoadDocs(); + 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(); + await conn.CustomNonQuery($"DELETE FROM {SqliteDb.TableName} WHERE data ->> 'NumValue' > @value", + new[] { new SqliteParameter("@value", 100) }); - var doc = await conn.FindById(SqliteDb.TableName, "eighty-seven"); - Expect.isNull(doc, "There should not have been a document returned"); - }) - }), - TestList("FindByField", new[] + 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 => { - TestCase("succeeds when documents are found", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - await LoadDocs(); + 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 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 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"); - var docs = await conn.FindByField(SqliteDb.TableName, "Value", Op.EQ, "mauve"); - Expect.isEmpty(docs, "There should have been no documents returned"); - }) - }), - TestList("FindFirstByField", new[] + 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 () => { - 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"); - }) + 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"); }), - TestList("UpdateFull", new[] + TestCase("fails for duplicate key", async () => { - 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" }); + try { - 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 Document.Insert(SqliteDb.TableName, new JsonDocument { Id = "test" }); + Expect.isTrue(false, "An exception should have been raised for duplicate document ID insert"); + } + catch (Exception) { - 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[] + // This is what is supposed to happen + } + }) + }), + TestList("Save", new[] + { + TestCase("succeeds when a document is inserted", async () => { - 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 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.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 }); - }) + 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"); }), - TestList("UpdatePartialById", new[] + TestCase("succeeds when a document is updated", async () => { - 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 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" } }); - 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"); + 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"); - // This not raising an exception is the test - await conn.UpdatePartialById(SqliteDb.TableName, "test", new { Foo = "green" }); - }) - }), - TestList("UpdatePartialByField", new[] + 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 () => { - 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 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" }); - }) + var exists = await conn.ExistsById(SqliteDb.TableName, "three"); + Expect.isTrue(exists, "There should have been an existing document"); }), - TestList("DeleteById", new[] + TestCase("succeeds when a document does not exist", async () => { - 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 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[] + 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 () => { - TestCase("succeeds when documents are deleted", async () => - { - await using var db = await SqliteDb.BuildDb(); - await using var conn = Sqlite.Configuration.DbConn(); - await LoadDocs(); + 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"); - }) + var exists = await conn.ExistsByField(SqliteDb.TableName, "NumValue", Op.GE, 10); + Expect.isTrue(exists, "There should have been existing documents"); }), - TestCase("Clean up database", () => Sqlite.Configuration.UseConnectionString("data source=:memory:")) - }); + 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 39ed2e9..a511856 100644 --- a/src/Tests.CSharp/SqliteCSharpTests.cs +++ b/src/Tests.CSharp/SqliteCSharpTests.cs @@ -16,60 +16,58 @@ public static class SqliteCSharpTests /// /// Unit tests for the SQLite library /// - [Tests] - public static Test Unit = - TestList("Unit", new[] + private static readonly Test Unit = TestList("Unit", new[] + { + TestList("Query", new[] { - TestList("Query", new[] + TestCase("Definition.EnsureTable succeeds", () => { - TestCase("Definition.EnsureTable succeeds", () => - { - Expect.equal(Sqlite.Query.Definition.EnsureTable("tbl"), - "CREATE TABLE IF NOT EXISTS tbl (data TEXT NOT NULL)", "CREATE TABLE statement not correct"); - }), - TestList("Update", new[] - { - TestCase("PartialById succeeds", () => - { - Expect.equal(Sqlite.Query.Update.PartialById("tbl"), - "UPDATE tbl SET data = json_patch(data, json(@data)) WHERE data ->> 'Id' = @id", - "UPDATE partial by ID statement not correct"); - }), - TestCase("PartialByField succeeds", () => - { - Expect.equal(Sqlite.Query.Update.PartialByField("tbl", "Part", Op.NE), - "UPDATE tbl SET data = json_patch(data, json(@data)) WHERE data ->> 'Part' <> @field", - "UPDATE partial by JSON comparison query not correct"); - }) - }), + Expect.equal(Sqlite.Query.Definition.EnsureTable("tbl"), + "CREATE TABLE IF NOT EXISTS tbl (data TEXT NOT NULL)", "CREATE TABLE statement not correct"); }), - TestList("Parameters", new[] + TestList("Update", new[] { - TestCase("Id succeeds", () => + TestCase("PartialById succeeds", () => { - var theParam = Parameters.Id(7); - Expect.equal(theParam.ParameterName, "@id", "The parameter name is incorrect"); - Expect.equal(theParam.Value, "7", "The parameter value is incorrect"); + Expect.equal(Sqlite.Query.Update.PartialById("tbl"), + "UPDATE tbl SET data = json_patch(data, json(@data)) WHERE data ->> 'Id' = @id", + "UPDATE partial by ID statement not correct"); }), - TestCase("Json succeeds", () => + TestCase("PartialByField succeeds", () => { - var theParam = Parameters.Json("@test", new { Nice = "job" }); - Expect.equal(theParam.ParameterName, "@test", "The parameter name is incorrect"); - Expect.equal(theParam.Value, "{\"Nice\":\"job\"}", "The parameter value is incorrect"); - }), - TestCase("Field succeeds", () => - { - var theParam = Parameters.Field(99); - Expect.equal(theParam.ParameterName, "@field", "The parameter name is incorrect"); - Expect.equal(theParam.Value, 99, "The parameter value is incorrect"); - }), - TestCase("None succeeds", () => - { - Expect.isEmpty(Parameters.None, "The parameter list should have been empty"); + Expect.equal(Sqlite.Query.Update.PartialByField("tbl", "Part", Op.NE), + "UPDATE tbl SET data = json_patch(data, json(@data)) WHERE data ->> 'Part' <> @field", + "UPDATE partial by JSON comparison query not correct"); }) + }), + }), + TestList("Parameters", new[] + { + TestCase("Id succeeds", () => + { + var theParam = Parameters.Id(7); + Expect.equal(theParam.ParameterName, "@id", "The parameter name is incorrect"); + Expect.equal(theParam.Value, "7", "The parameter value is incorrect"); + }), + TestCase("Json succeeds", () => + { + var theParam = Parameters.Json("@test", new { Nice = "job" }); + Expect.equal(theParam.ParameterName, "@test", "The parameter name is incorrect"); + Expect.equal(theParam.Value, "{\"Nice\":\"job\"}", "The parameter value is incorrect"); + }), + TestCase("Field succeeds", () => + { + var theParam = Parameters.Field(99); + Expect.equal(theParam.ParameterName, "@field", "The parameter name is incorrect"); + Expect.equal(theParam.Value, 99, "The parameter value is incorrect"); + }), + TestCase("None succeeds", () => + { + Expect.isEmpty(Parameters.None, "The parameter list should have been empty"); }) - // Results are exhaustively executed in the context of other tests - }); + }) + // Results are exhaustively executed in the context of other tests + }); private static readonly List TestDocuments = new() { @@ -80,507 +78,504 @@ public static class SqliteCSharpTests new() { Id = "five", Value = "purple", NumValue = 18 } }; + /// + /// Add the test documents to the database + /// internal static async Task LoadDocs() { foreach (var doc in TestDocuments) await Document.Insert(SqliteDb.TableName, doc); } - [Tests] - public static Test Integration = - TestList("Integration", new[] + private static readonly Test Integration = TestList("Integration", new[] + { + TestCase("Configuration.UseConnectionString succeeds", () => { - TestCase("Configuration.UseConnectionString succeeds", () => + try { + Sqlite.Configuration.UseConnectionString("Data Source=test.db"); + Expect.equal(Sqlite.Configuration.connectionString, + new FSharpOption("Data Source=test.db;Foreign Keys=True"), "Connection string incorrect"); + } + finally + { + Sqlite.Configuration.UseConnectionString("Data Source=:memory:"); + } + }), + TestList("Custom", new[] + { + TestList("Single", new[] + { + TestCase("succeeds when a row is found", async () => + { + await using var db = await SqliteDb.BuildDb(); + await LoadDocs(); + + var doc = await Custom.Single($"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 LoadDocs(); + + var doc = await Custom.Single($"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("List", new[] + { + TestCase("succeeds when data is found", async () => + { + await using var db = await SqliteDb.BuildDb(); + await LoadDocs(); + + var docs = await Custom.List(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 LoadDocs(); + + var docs = await Custom.List( + $"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("NonQuery", new[] + { + TestCase("succeeds when operating on data", async () => + { + await using var db = await SqliteDb.BuildDb(); + await LoadDocs(); + + await Custom.NonQuery($"DELETE FROM {SqliteDb.TableName}", Parameters.None); + + var remaining = await Count.All(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 LoadDocs(); + + await Custom.NonQuery($"DELETE FROM {SqliteDb.TableName} WHERE data ->> 'NumValue' > @value", + new[] { new SqliteParameter("@value", 100) }); + + var remaining = await Count.All(SqliteDb.TableName); + Expect.equal(remaining, 5L, "There should be 5 documents remaining in the table"); + }) + }), + TestCase("Scalar succeeds", async () => + { + await using var db = await SqliteDb.BuildDb(); + + var nbr = await Custom.Scalar("SELECT 5 AS test_value", Parameters.None, rdr => rdr.GetInt32(0)); + Expect.equal(nbr, 5, "The query should have returned the number 5"); + }) + }), + TestList("Definition", new[] + { + TestCase("EnsureTable succeeds", async () => + { + await using var db = await SqliteDb.BuildDb(); + + 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 Definition.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"); + return; + + async ValueTask ItExists(string name) + { + var result = await Custom.Scalar( + $"SELECT EXISTS (SELECT 1 FROM {SqliteDb.Catalog} WHERE name = @name) AS it", + new SqliteParameter[] { new("@name", name) }, rdr => rdr.GetInt64(0)); + return result > 0L; + } + }) + }), + TestList("Document.Insert", new[] + { + TestCase("succeeds", async () => + { + await using var db = await SqliteDb.BuildDb(); + var before = await Find.All(SqliteDb.TableName); + Expect.isEmpty(before, "There should be no documents in the table"); + await Document.Insert(SqliteDb.TableName, + new JsonDocument { Id = "turkey", Sub = new() { Foo = "gobble", Bar = "gobble" } }); + var after = await Find.All(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 Document.Insert(SqliteDb.TableName, new JsonDocument { Id = "test" }); try { - Sqlite.Configuration.UseConnectionString("Data Source=test.db"); - Expect.equal(Sqlite.Configuration.connectionString, - new FSharpOption("Data Source=test.db;Foreign Keys=True"), - "Connection string incorrect"); - } - finally - { - Sqlite.Configuration.UseConnectionString("Data Source=:memory:"); - } - }), - TestList("Custom", new[] - { - TestList("Single", new [] - { - TestCase("succeeds when a row is found", async () => - { - await using var db = await SqliteDb.BuildDb(); - await LoadDocs(); - - var doc = await Custom.Single( - $"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 LoadDocs(); - - var doc = await Custom.Single( - $"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("List", new[] - { - TestCase("succeeds when data is found", async () => - { - await using var db = await SqliteDb.BuildDb(); - await LoadDocs(); - - var docs = await Custom.List(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 LoadDocs(); - - var docs = await Custom.List( - $"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("NonQuery", new[] - { - TestCase("succeeds when operating on data", async () => - { - await using var db = await SqliteDb.BuildDb(); - await LoadDocs(); - - await Custom.NonQuery($"DELETE FROM {SqliteDb.TableName}", Parameters.None); - - var remaining = await Count.All(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 LoadDocs(); - - await Custom.NonQuery( - $"DELETE FROM {SqliteDb.TableName} WHERE data ->> 'NumValue' > @value", - new[] { new SqliteParameter("@value", 100) }); - - var remaining = await Count.All(SqliteDb.TableName); - Expect.equal(remaining, 5L, "There should be 5 documents remaining in the table"); - }) - }), - TestCase("Scalar succeeds", async () => - { - await using var db = await SqliteDb.BuildDb(); - - var nbr = await Custom.Scalar("SELECT 5 AS test_value", Parameters.None, rdr => rdr.GetInt32(0)); - Expect.equal(nbr, 5, "The query should have returned the number 5"); - }) - }), - TestList("Definition", new[] - { - TestCase("EnsureTable succeeds", async () => - { - await using var db = await SqliteDb.BuildDb(); - - 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 Definition.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"); - return; - - async ValueTask ItExists(string name) - { - var result = await Custom.Scalar( - $"SELECT EXISTS (SELECT 1 FROM {SqliteDb.Catalog} WHERE name = @name) AS it", - new SqliteParameter[] { new("@name", name) }, - rdr => rdr.GetInt64(0)); - return result > 0L; - } - }) - }), - TestList("Document.Insert", new[] - { - TestCase("succeeds", async () => - { - await using var db = await SqliteDb.BuildDb(); - var before = await Find.All(SqliteDb.TableName); - Expect.isEmpty(before, "There should be no documents in the table"); - await Document.Insert(SqliteDb.TableName, - new JsonDocument { Id = "turkey", Sub = new() { Foo = "gobble", Bar = "gobble" } }); - var after = await Find.All(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 Document.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("Document.Save", new[] + Expect.isTrue(false, "An exception should have been raised for duplicate document ID insert"); + } + catch (Exception) + { + // This is what is supposed to happen + } + }) + }), + TestList("Document.Save", new[] + { + TestCase("succeeds when a document is inserted", async () => { - TestCase("succeeds when a document is inserted", async () => + await using var db = await SqliteDb.BuildDb(); + var before = await Find.All(SqliteDb.TableName); + Expect.isEmpty(before, "There should be no documents in the table"); + + await Document.Save(SqliteDb.TableName, + new JsonDocument { Id = "test", Sub = new() { Foo = "a", Bar = "b" } }); + var after = await Find.All(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 Document.Insert(SqliteDb.TableName, + new JsonDocument { Id = "test", Sub = new() { Foo = "a", Bar = "b" } }); + + var before = await Find.ById(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 Document.Save(SqliteDb.TableName, new JsonDocument { Id = "test" }); + var after = await Find.ById(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"); + }) + }), + TestList("Count", new[] + { + TestCase("All succeeds", async () => + { + await using var db = await SqliteDb.BuildDb(); + await LoadDocs(); + + var theCount = await Count.All(SqliteDb.TableName); + Expect.equal(theCount, 5L, "There should have been 5 matching documents"); + }), + TestCase("ByField succeeds", async () => + { + await using var db = await SqliteDb.BuildDb(); + await LoadDocs(); + + var theCount = await Count.ByField(SqliteDb.TableName, "Value", Op.EQ, "purple"); + Expect.equal(theCount, 2L, "There should have been 2 matching documents"); + }) + }), + TestList("Exists", new[] + { + TestList("ById", new[] + { + TestCase("succeeds when a document exists", async () => { await using var db = await SqliteDb.BuildDb(); - var before = await Find.All(SqliteDb.TableName); - Expect.isEmpty(before, "There should be no documents in the table"); + await LoadDocs(); - await Document.Save(SqliteDb.TableName, - new JsonDocument { Id = "test", Sub = new() { Foo = "a", Bar = "b" } }); - var after = await Find.All(SqliteDb.TableName); - Expect.equal(after.Count, 1, "There should have been one document inserted"); + var exists = await Exists.ById(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 LoadDocs(); + + var exists = await Exists.ById(SqliteDb.TableName, "seven"); + Expect.isFalse(exists, "There should not have been an existing document"); + }) + }), + TestList("ByField", new[] + { + TestCase("succeeds when documents exist", async () => + { + await using var db = await SqliteDb.BuildDb(); + await LoadDocs(); + + var exists = await Exists.ByField(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 LoadDocs(); + + var exists = await Exists.ByField(SqliteDb.TableName, "Nothing", Op.EQ, "none"); + Expect.isFalse(exists, "There should not have been any existing documents"); + }) + }) + }), + TestList("Find", new[] + { + TestList("All", new[] + { + TestCase("succeeds when there is data", async () => + { + await using var db = await SqliteDb.BuildDb(); + + await Document.Insert(SqliteDb.TableName, new JsonDocument { Id = "one", Value = "two" }); + await Document.Insert(SqliteDb.TableName, new JsonDocument { Id = "three", Value = "four" }); + await Document.Insert(SqliteDb.TableName, new JsonDocument { Id = "five", Value = "six" }); + + var results = await Find.All(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(); + var results = await Find.All(SqliteDb.TableName); + Expect.isEmpty(results, "There should have been no documents returned"); + }) + }), + TestList("ById", new[] + { + TestCase("succeeds when a document is found", async () => + { + await using var db = await SqliteDb.BuildDb(); + await LoadDocs(); + + var doc = await Find.ById(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 LoadDocs(); + + var doc = await Find.ById(SqliteDb.TableName, "twenty two"); + Expect.isNull(doc, "There should not have been a document returned"); + }) + }), + TestList("ByField", new[] + { + TestCase("succeeds when documents are found", async () => + { + await using var db = await SqliteDb.BuildDb(); + await LoadDocs(); + + var docs = await Find.ByField(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 LoadDocs(); + + var docs = await Find.ByField(SqliteDb.TableName, "Value", Op.EQ, "mauve"); + Expect.isEmpty(docs, "There should have been no documents returned"); + }) + }), + TestList("FirstByField", new[] + { + TestCase("succeeds when a document is found", async () => + { + await using var db = await SqliteDb.BuildDb(); + await LoadDocs(); + + var doc = await Find.FirstByField(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 LoadDocs(); + + var doc = await Find.FirstByField(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 LoadDocs(); + + var doc = await Find.FirstByField(SqliteDb.TableName, "Value", Op.EQ, "absent"); + Expect.isNull(doc, "There should not have been a document returned"); + }) + }) + }), + TestList("Update", new[] + { + TestList("Full", new[] + { TestCase("succeeds when a document is updated", async () => { await using var db = await SqliteDb.BuildDb(); - await Document.Insert(SqliteDb.TableName, - new JsonDocument { Id = "test", Sub = new() { Foo = "a", Bar = "b" } }); + await LoadDocs(); - var before = await Find.ById(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 Document.Save(SqliteDb.TableName, new JsonDocument { Id = "test" }); - var after = await Find.ById(SqliteDb.TableName, "test"); + var testDoc = new JsonDocument { Id = "one", Sub = new() { Foo = "blue", Bar = "red" } }; + await Update.Full(SqliteDb.TableName, "one", testDoc); + var after = await Find.ById(SqliteDb.TableName, "one"); 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"); + 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(); + + var before = await Find.All(SqliteDb.TableName); + Expect.isEmpty(before, "There should have been no documents returned"); + + // This not raising an exception is the test + await Update.Full(SqliteDb.TableName, "test", + new JsonDocument { Id = "x", Sub = new() { Foo = "blue", Bar = "red" } }); }) }), - TestList("Count", new[] + TestList("FullFunc", new[] { - TestCase("All succeeds", async () => + TestCase("succeeds when a document is updated", async () => { await using var db = await SqliteDb.BuildDb(); await LoadDocs(); - var theCount = await Count.All(SqliteDb.TableName); - Expect.equal(theCount, 5L, "There should have been 5 matching documents"); + await Update.FullFunc(SqliteDb.TableName, doc => doc.Id, + new JsonDocument { Id = "one", Value = "le un", NumValue = 1 }); + var after = await Find.ById(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("ByField succeeds", async () => + TestCase("succeeds when no document is updated", async () => + { + await using var db = await SqliteDb.BuildDb(); + + var before = await Find.All(SqliteDb.TableName); + Expect.isEmpty(before, "There should have been no documents returned"); + + // This not raising an exception is the test + await Update.FullFunc(SqliteDb.TableName, doc => doc.Id, + new JsonDocument { Id = "one", Value = "le un", NumValue = 1 }); + }) + }), + TestList("PartialById", new[] + { + TestCase("succeeds when a document is updated", async () => { await using var db = await SqliteDb.BuildDb(); await LoadDocs(); - var theCount = await Count.ByField(SqliteDb.TableName, "Value", Op.EQ, "purple"); - Expect.equal(theCount, 2L, "There should have been 2 matching documents"); + await Update.PartialById(SqliteDb.TableName, "one", new { NumValue = 44 }); + var after = await Find.ById(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(); + + var before = await Find.All(SqliteDb.TableName); + Expect.isEmpty(before, "There should have been no documents returned"); + + // This not raising an exception is the test + await Update.PartialById(SqliteDb.TableName, "test", new { Foo = "green" }); }) }), - TestList("Exists", new[] + TestList("PartialByField", new[] { - TestList("ById", new[] + TestCase("succeeds when a document is updated", async () => { - TestCase("succeeds when a document exists", async () => - { - await using var db = await SqliteDb.BuildDb(); - await LoadDocs(); + await using var db = await SqliteDb.BuildDb(); + await LoadDocs(); - var exists = await Exists.ById(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 LoadDocs(); - - var exists = await Exists.ById(SqliteDb.TableName, "seven"); - Expect.isFalse(exists, "There should not have been an existing document"); - }) + await Update.PartialByField(SqliteDb.TableName, "Value", Op.EQ, "purple", + new { NumValue = 77 }); + var after = await Count.ByField(SqliteDb.TableName, "NumValue", Op.EQ, 77); + Expect.equal(after, 2L, "There should have been 2 documents returned"); }), - TestList("ByField", new[] + TestCase("succeeds when no document is updated", async () => { - TestCase("succeeds when documents exist", async () => - { - await using var db = await SqliteDb.BuildDb(); - await LoadDocs(); + await using var db = await SqliteDb.BuildDb(); - var exists = await Exists.ByField(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 LoadDocs(); + var before = await Find.All(SqliteDb.TableName); + Expect.isEmpty(before, "There should have been no documents returned"); - var exists = await Exists.ByField(SqliteDb.TableName, "Nothing", Op.EQ, "none"); - Expect.isFalse(exists, "There should not have been any existing documents"); - }) + // This not raising an exception is the test + await Update.PartialByField(SqliteDb.TableName, "Value", Op.EQ, "burgundy", + new { Foo = "green" }); }) - }), - TestList("Find", new[] + }) + }), + TestList("Delete", new[] + { + TestList("ById", new[] { - TestList("All", new[] + TestCase("succeeds when a document is deleted", async () => { - TestCase("succeeds when there is data", async () => - { - await using var db = await SqliteDb.BuildDb(); + await using var db = await SqliteDb.BuildDb(); + await LoadDocs(); - await Document.Insert(SqliteDb.TableName, new JsonDocument { Id = "one", Value = "two" }); - await Document.Insert(SqliteDb.TableName, new JsonDocument { Id = "three", Value = "four" }); - await Document.Insert(SqliteDb.TableName, new JsonDocument { Id = "five", Value = "six" }); - - var results = await Find.All(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(); - var results = await Find.All(SqliteDb.TableName); - Expect.isEmpty(results, "There should have been no documents returned"); - }) + await Delete.ById(SqliteDb.TableName, "four"); + var remaining = await Count.All(SqliteDb.TableName); + Expect.equal(remaining, 4L, "There should have been 4 documents remaining"); }), - TestList("ById", new[] + TestCase("succeeds when a document is not deleted", async () => { - TestCase("succeeds when a document is found", async () => - { - await using var db = await SqliteDb.BuildDb(); - await LoadDocs(); + await using var db = await SqliteDb.BuildDb(); + await LoadDocs(); - var doc = await Find.ById(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 LoadDocs(); - - var doc = await Find.ById(SqliteDb.TableName, "twenty two"); - Expect.isNull(doc, "There should not have been a document returned"); - }) - }), - TestList("ByField", new[] - { - TestCase("succeeds when documents are found", async () => - { - await using var db = await SqliteDb.BuildDb(); - await LoadDocs(); - - var docs = await Find.ByField(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 LoadDocs(); - - var docs = await Find.ByField(SqliteDb.TableName, "Value", Op.EQ, "mauve"); - Expect.isEmpty(docs, "There should have been no documents returned"); - }) - }), - TestList("FirstByField", new[] - { - TestCase("succeeds when a document is found", async () => - { - await using var db = await SqliteDb.BuildDb(); - await LoadDocs(); - - var doc = await Find.FirstByField(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 LoadDocs(); - - var doc = await Find.FirstByField(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 LoadDocs(); - - var doc = await Find.FirstByField(SqliteDb.TableName, "Value", Op.EQ, "absent"); - Expect.isNull(doc, "There should not have been a document returned"); - }) + await Delete.ById(SqliteDb.TableName, "thirty"); + var remaining = await Count.All(SqliteDb.TableName); + Expect.equal(remaining, 5L, "There should have been 5 documents remaining"); }) }), - TestList("Update", new[] + TestList("ByField", new[] { - TestList("Full", new[] + TestCase("succeeds when documents are deleted", async () => { - TestCase("succeeds when a document is updated", async () => - { - await using var db = await SqliteDb.BuildDb(); - await LoadDocs(); + await using var db = await SqliteDb.BuildDb(); + await LoadDocs(); - var testDoc = new JsonDocument { Id = "one", Sub = new() { Foo = "blue", Bar = "red" } }; - await Update.Full(SqliteDb.TableName, "one", testDoc); - var after = await Find.ById(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(); - - var before = await Find.All(SqliteDb.TableName); - Expect.isEmpty(before, "There should have been no documents returned"); - - // This not raising an exception is the test - await Update.Full(SqliteDb.TableName, "test", - new JsonDocument { Id = "x", Sub = new() { Foo = "blue", Bar = "red" } }); - }) + await Delete.ByField(SqliteDb.TableName, "Value", Op.NE, "purple"); + var remaining = await Count.All(SqliteDb.TableName); + Expect.equal(remaining, 2L, "There should have been 2 documents remaining"); }), - TestList("FullFunc", new[] + TestCase("succeeds when documents are not deleted", async () => { - TestCase("succeeds when a document is updated", async () => - { - await using var db = await SqliteDb.BuildDb(); - await LoadDocs(); + await using var db = await SqliteDb.BuildDb(); + await LoadDocs(); - await Update.FullFunc(SqliteDb.TableName, doc => doc.Id, - new JsonDocument { Id = "one", Value = "le un", NumValue = 1 }); - var after = await Find.ById(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(); - - var before = await Find.All(SqliteDb.TableName); - Expect.isEmpty(before, "There should have been no documents returned"); - - // This not raising an exception is the test - await Update.FullFunc(SqliteDb.TableName, doc => doc.Id, - new JsonDocument { Id = "one", Value = "le un", NumValue = 1 }); - }) - }), - TestList("PartialById", new[] - { - TestCase("succeeds when a document is updated", async () => - { - await using var db = await SqliteDb.BuildDb(); - await LoadDocs(); - - await Update.PartialById(SqliteDb.TableName, "one", new { NumValue = 44 }); - var after = await Find.ById(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(); - - var before = await Find.All(SqliteDb.TableName); - Expect.isEmpty(before, "There should have been no documents returned"); - - // This not raising an exception is the test - await Update.PartialById(SqliteDb.TableName, "test", new { Foo = "green" }); - }) - }), - TestList("PartialByField", new[] - { - TestCase("succeeds when a document is updated", async () => - { - await using var db = await SqliteDb.BuildDb(); - await LoadDocs(); - - await Update.PartialByField(SqliteDb.TableName, "Value", Op.EQ, "purple", - new { NumValue = 77 }); - var after = await Count.ByField(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(); - - var before = await Find.All(SqliteDb.TableName); - Expect.isEmpty(before, "There should have been no documents returned"); - - // This not raising an exception is the test - await Update.PartialByField(SqliteDb.TableName, "Value", Op.EQ, "burgundy", - new { Foo = "green" }); - }) + await Delete.ByField(SqliteDb.TableName, "Value", Op.EQ, "crimson"); + var remaining = await Count.All(SqliteDb.TableName); + Expect.equal(remaining, 5L, "There should have been 5 documents remaining"); }) - }), - TestList("Delete", new[] - { - TestList("ById", new[] - { - TestCase("succeeds when a document is deleted", async () => - { - await using var db = await SqliteDb.BuildDb(); - await LoadDocs(); - - await Delete.ById(SqliteDb.TableName, "four"); - var remaining = await Count.All(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 LoadDocs(); - - await Delete.ById(SqliteDb.TableName, "thirty"); - var remaining = await Count.All(SqliteDb.TableName); - Expect.equal(remaining, 5L, "There should have been 5 documents remaining"); - }) - }), - TestList("ByField", new[] - { - TestCase("succeeds when documents are deleted", async () => - { - await using var db = await SqliteDb.BuildDb(); - await LoadDocs(); - - await Delete.ByField(SqliteDb.TableName, "Value", Op.NE, "purple"); - var remaining = await Count.All(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 LoadDocs(); - - await Delete.ByField(SqliteDb.TableName, "Value", Op.EQ, "crimson"); - var remaining = await Count.All(SqliteDb.TableName); - Expect.equal(remaining, 5L, "There should have been 5 documents remaining"); - }) - }) - }), - TestCase("Clean up database", () => Sqlite.Configuration.UseConnectionString("data source=:memory:")) - }); + }) + }), + TestCase("Clean up database", () => Sqlite.Configuration.UseConnectionString("data source=:memory:")) + }); /// /// All tests for SQLite C# functions and methods /// + [Tests] public static readonly Test All = TestList("Sqlite.C#", new[] { Unit, TestSequenced(Integration) }); } diff --git a/src/Tests/PostgresTests.fs b/src/Tests/PostgresTests.fs index b39e33c..46fbce0 100644 --- a/src/Tests/PostgresTests.fs +++ b/src/Tests/PostgresTests.fs @@ -39,15 +39,15 @@ let unitTests = $"CREATE TABLE IF NOT EXISTS {PostgresDb.TableName} (data JSONB NOT NULL)" "CREATE TABLE statement not constructed correctly" } - test "ensureJsonIndex succeeds for full index" { + test "ensureDocumentIndex succeeds for full index" { Expect.equal - (Query.Definition.ensureJsonIndex "schema.tbl" Full) + (Query.Definition.ensureDocumentIndex "schema.tbl" Full) "CREATE INDEX IF NOT EXISTS idx_tbl_document ON schema.tbl USING GIN (data)" "CREATE INDEX statement not constructed correctly" } - test "ensureJsonIndex succeeds for JSONB Path Ops index" { + test "ensureDocumentIndex succeeds for JSONB Path Ops index" { Expect.equal - (Query.Definition.ensureJsonIndex PostgresDb.TableName Optimized) + (Query.Definition.ensureDocumentIndex PostgresDb.TableName Optimized) (sprintf "CREATE INDEX IF NOT EXISTS idx_%s_document ON %s USING GIN (data jsonb_path_ops)" PostgresDb.TableName PostgresDb.TableName) "CREATE INDEX statement not constructed correctly" @@ -266,7 +266,7 @@ let integrationTests = Expect.isTrue exists' "The table should now exist" Expect.isTrue alsoExists' "The key index should now exist" } - testTask "ensureJsonIndex succeeds" { + testTask "ensureDocumentIndex succeeds" { use db = PostgresDb.BuildDb() let indexExists () = Custom.scalar @@ -277,8 +277,8 @@ let integrationTests = let! exists = indexExists () Expect.isFalse exists "The index should not exist already" - do! Definition.ensureTable "ensured" - do! Definition.ensureJsonIndex "ensured" Optimized + do! Definition.ensureTable "ensured" + do! Definition.ensureDocumentIndex "ensured" Optimized let! exists' = indexExists () Expect.isTrue exists' "The index should now exist" } @@ -730,7 +730,7 @@ let integrationTests = Expect.equal before 0 "There should have been no documents returned" // This not raising an exception is the test - do! Update.partialByContains PostgresDb.TableName {| Value = "burgundy" |} {| Foo = "green" |} + do! Update.partialByJsonPath PostgresDb.TableName "$.NumValue ? (@ < 0)" {| Foo = "green" |} } ] ]