diff --git a/src/Tests.CSharp/PostgresCSharpTests.cs b/src/Tests.CSharp/PostgresCSharpTests.cs index 1f53ba7..75335ae 100644 --- a/src/Tests.CSharp/PostgresCSharpTests.cs +++ b/src/Tests.CSharp/PostgresCSharpTests.cs @@ -13,330 +13,333 @@ using static Runner; /// public static class PostgresCSharpTests { + /// + /// Unit tests for the Parameters module of the PostgreSQL library + /// + private static readonly Test ParametersTests = TestList("Parameters", + [ + TestList("Id", + [ + // NOTE: these tests also exercise all branches of the internal parameterFor function + TestCase("succeeds for byte ID", () => + { + var it = Parameters.Id((sbyte)7); + Expect.equal(it.Item1, "@id", "ID parameter not constructed correctly"); + Expect.equal(it.Item2, Sql.int8(7), "Byte ID parameter not constructed correctly"); + }), + TestCase("succeeds for unsigned byte ID", () => + { + var it = Parameters.Id((byte)7); + Expect.equal(it.Item1, "@id", "ID parameter not constructed correctly"); + Expect.equal(it.Item2, Sql.int8(7), "Unsigned byte ID parameter not constructed correctly"); + }), + TestCase("succeeds for short ID", () => + { + var it = Parameters.Id((short)44); + Expect.equal(it.Item1, "@id", "ID parameter not constructed correctly"); + Expect.equal(it.Item2, Sql.int16(44), "Short ID parameter not constructed correctly"); + }), + TestCase("succeeds for unsigned short ID", () => + { + var it = Parameters.Id((ushort)64); + Expect.equal(it.Item1, "@id", "ID parameter not constructed correctly"); + Expect.equal(it.Item2, Sql.int16(64), "Unsigned short ID parameter not constructed correctly"); + }), + TestCase("succeeds for integer ID", () => + { + var it = Parameters.Id(88); + Expect.equal(it.Item1, "@id", "ID parameter not constructed correctly"); + Expect.equal(it.Item2, Sql.@int(88), "ID parameter value incorrect"); + }), + TestCase("succeeds for unsigned integer ID", () => + { + var it = Parameters.Id((uint)889); + Expect.equal(it.Item1, "@id", "ID parameter not constructed correctly"); + Expect.equal(it.Item2, Sql.@int(889), "Unsigned int ID parameter not constructed correctly"); + }), + TestCase("succeeds for long ID", () => + { + var it = Parameters.Id((long)123); + Expect.equal(it.Item1, "@id", "ID parameter not constructed correctly"); + Expect.equal(it.Item2, Sql.int64(123), "Long ID parameter not constructed correctly"); + }), + TestCase("succeeds for unsigned long ID", () => + { + var it = Parameters.Id((ulong)6464); + Expect.equal(it.Item1, "@id", "ID parameter not constructed correctly"); + Expect.equal(it.Item2, Sql.int64(6464), "Unsigned long ID parameter not constructed correctly"); + }), + TestCase("succeeds for decimal ID", () => + { + var it = Parameters.Id((decimal)4.56); + Expect.equal(it.Item1, "@id", "ID parameter not constructed correctly"); + Expect.equal(it.Item2, Sql.@decimal((decimal)4.56), "Decimal ID parameter not constructed correctly"); + }), + TestCase("succeeds for single ID", () => + { + var it = Parameters.Id((float)5.67); + Expect.equal(it.Item1, "@id", "ID parameter not constructed correctly"); + Expect.equal(it.Item2, Sql.@double((float)5.67), "Single ID parameter not constructed correctly"); + }), + TestCase("succeeds for double ID", () => + { + var it = Parameters.Id(6.78); + Expect.equal(it.Item1, "@id", "ID parameter not constructed correctly"); + Expect.equal(it.Item2, Sql.@double(6.78), "Double ID parameter not constructed correctly"); + }), + TestCase("succeeds for string ID", () => + { + var it = Parameters.Id("99"); + Expect.equal(it.Item1, "@id", "ID parameter not constructed correctly"); + Expect.equal(it.Item2, Sql.@string("99"), "ID parameter value incorrect"); + }), + TestCase("succeeds for non-numeric non-string ID", () => + { + var it = Parameters.Id(new Uri("https://example.com")); + Expect.equal(it.Item1, "@id", "ID parameter not constructed correctly"); + Expect.equal(it.Item2, Sql.@string("https://example.com/"), + "Non-numeric, non-string 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"); + }), + TestList("AddFields", + [ + TestCase("succeeds when a parameter is added", () => + { + var paramList = Parameters.AddFields([Field.EQ("it", "242")], []).ToList(); + Expect.hasLength(paramList, 1, "There should have been a parameter added"); + var (name, value) = paramList[0]; + Expect.equal(name, "@field0", "Field parameter name not correct"); + Expect.equal(value, Sql.@string("242"), "Field parameter value not correct"); + }), + TestCase("succeeds when multiple independent parameters are added", () => + { + var paramList = Parameters.AddFields([Field.EQ("me", "you"), Field.GT("us", "them")], + [Parameters.Id(14)]).ToList(); + Expect.hasLength(paramList, 3, "There should have been 2 parameters added"); + var (name, value) = paramList[0]; + Expect.equal(name, "@id", "First field parameter name not correct"); + Expect.equal(value, Sql.@int(14), "First field parameter value not correct"); + (name, value) = paramList[1]; + Expect.equal(name, "@field0", "Second field parameter name not correct"); + Expect.equal(value, Sql.@string("you"), "Second field parameter value not correct"); + (name, value) = paramList[2]; + Expect.equal(name, "@field1", "Third parameter name not correct"); + Expect.equal(value, Sql.@string("them"), "Third parameter value not correct"); + }), + TestCase("succeeds when a parameter is not added", () => + { + var paramList = Parameters.AddFields([Field.EX("tacos")], []).ToList(); + Expect.isEmpty(paramList, "There should not have been any parameters added"); + }), + TestCase("succeeds when two parameters are added for one field", () => + { + var paramList = + Parameters.AddFields([Field.BT("that", "eh", "zed").WithParameterName("@test")], []).ToList(); + Expect.hasLength(paramList, 2, "There should have been 2 parameters added"); + var (name, value) = paramList[0]; + Expect.equal(name, "@testmin", "Minimum field name not correct"); + Expect.equal(value, Sql.@string("eh"), "Minimum field value not correct"); + (name, value) = paramList[1]; + Expect.equal(name, "@testmax", "Maximum field name not correct"); + Expect.equal(value, Sql.@string("zed"), "Maximum field value not correct"); + }) + ]), + TestList("FieldNames", + [ + TestCase("succeeds for one name", () => + { + var (name, value) = Parameters.FieldNames(["bob"]); + Expect.equal(name, "@name", "The parameter name was incorrect"); + if (!value.IsString) + { + Expect.isTrue(false, "The parameter was not a String type"); + } + }), + TestCase("succeeds for multiple names", () => + { + var (name, value) = Parameters.FieldNames(["bob", "tom", "mike"]); + Expect.equal(name, "@name", "The parameter name was incorrect"); + if (!value.IsStringArray) + { + Expect.isTrue(false, "The parameter was not a StringArray type"); + } + }) + ]), +#pragma warning disable CS0618 + TestList("FieldName", + [ + TestCase("succeeds for one name", () => + { + var (name, value) = Parameters.FieldName(["bob"]); + Expect.equal(name, "@name", "The parameter name was incorrect"); + if (!value.IsString) + { + Expect.isTrue(false, "The parameter was not a String type"); + } + }), + TestCase("succeeds for multiple names", () => + { + var (name, value) = Parameters.FieldName(["bob", "tom", "mike"]); + Expect.equal(name, "@name", "The parameter name was incorrect"); + if (!value.IsStringArray) + { + Expect.isTrue(false, "The parameter was not a StringArray type"); + } + }) + ]) +#pragma warning restore CS0618 + ]); + + /// + /// Unit tests for the Query module of the PostgreSQL library + /// + private static readonly Test QueryTests = TestList("Query", + [ + TestList("WhereByFields", + [ + TestCase("succeeds for a single field when a logical operator is passed", () => + { + Expect.equal( + Postgres.Query.WhereByFields(FieldMatch.Any, + [Field.GT("theField", "0").WithParameterName("@test")]), + "data->>'theField' > @test", "WHERE clause not correct"); + }), + TestCase("succeeds for a single field when an existence operator is passed", () => + { + Expect.equal(Postgres.Query.WhereByFields(FieldMatch.Any, [Field.NEX("thatField")]), + "data->>'thatField' IS NULL", "WHERE clause not correct"); + }), + TestCase("succeeds for a single field when a between operator is passed with numeric values", () => + { + Expect.equal( + Postgres.Query.WhereByFields(FieldMatch.All, + [Field.BT("aField", 50, 99).WithParameterName("@range")]), + "(data->>'aField')::numeric BETWEEN @rangemin AND @rangemax", "WHERE clause not correct"); + }), + TestCase("succeeds for a single field when a between operator is passed with non-numeric values", () => + { + Expect.equal( + Postgres.Query.WhereByFields(FieldMatch.Any, + [Field.BT("field0", "a", "b").WithParameterName("@alpha")]), + "data->>'field0' BETWEEN @alphamin AND @alphamax", "WHERE clause not correct"); + }), + TestCase("succeeds for all multiple fields with logical operators", () => + { + Expect.equal( + Postgres.Query.WhereByFields(FieldMatch.All, + [Field.EQ("theFirst", "1"), Field.EQ("numberTwo", "2")]), + "data->>'theFirst' = @field0 AND data->>'numberTwo' = @field1", "WHERE clause not correct"); + }), + TestCase("succeeds for any multiple fields with an existence operator", () => + { + Expect.equal( + Postgres.Query.WhereByFields(FieldMatch.Any, + [Field.NEX("thatField"), Field.GE("thisField", 18)]), + "data->>'thatField' IS NULL OR (data->>'thisField')::numeric >= @field0", + "WHERE clause not correct"); + }), + TestCase("succeeds for all multiple fields with between operators", () => + { + Expect.equal( + Postgres.Query.WhereByFields(FieldMatch.All, + [Field.BT("aField", 50, 99), Field.BT("anotherField", "a", "b")]), + "(data->>'aField')::numeric BETWEEN @field0min AND @field0max AND data->>'anotherField' BETWEEN @field1min AND @field1max", + "WHERE clause not correct"); + }) + ]), + TestList("WhereById", + [ + TestCase("succeeds for numeric ID", () => + { + Expect.equal(Postgres.Query.WhereById(18), "(data->>'Id')::numeric = @id", "WHERE clause not correct"); + }), + TestCase("succeeds for string ID", () => + { + Expect.equal(Postgres.Query.WhereById("18"), "data->>'Id' = @id", "WHERE clause not correct"); + }), + TestCase("succeeds for non-numeric non-string ID", () => + { + Expect.equal(Postgres.Query.WhereById(new Uri("https://example.com")), "data->>'Id' = @id", + "WHERE clause not correct"); + }), + ]), + TestList("Definition", + [ + 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("EnsureDocumentIndex succeeds for full index", () => + { + 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("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"); + }) + ]), + TestCase("WhereDataContains succeeds", () => + { + 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"); + }), + TestCase("Patch succeeds", () => + { + Expect.equal(Postgres.Query.Patch(PostgresDb.TableName), + $"UPDATE {PostgresDb.TableName} SET data = data || @data", "Patch query not correct"); + }), + TestCase("RemoveFields succeeds", () => + { + Expect.equal(Postgres.Query.RemoveFields(PostgresDb.TableName), + $"UPDATE {PostgresDb.TableName} SET data = data - @name", "Field removal query not correct"); + }), + TestCase("ById succeeds", () => + { + Expect.equal(Postgres.Query.ById("test", "14"), "test WHERE data->>'Id' = @id", "By-ID query not correct"); + }), + TestCase("ByFields succeeds", () => + { + Expect.equal(Postgres.Query.ByFields("unit", FieldMatch.Any, [Field.GT("That", 14)]), + "unit WHERE (data->>'That')::numeric > @field0", "By-Field query not correct"); + }), + TestCase("ByContains succeeds", () => + { + Expect.equal(Postgres.Query.ByContains("exam"), "exam WHERE data @> @criteria", + "By-Contains query not correct"); + }), + TestCase("ByPathMach succeeds", () => + { + Expect.equal(Postgres.Query.ByPathMatch("verify"), "verify WHERE data @? @path::jsonpath", + "By-JSON Path query not correct"); + }) + ]); + /// /// Tests which do not hit the database /// private static readonly Test Unit = TestList("Unit", [ - TestList("Parameters", - [ - TestList("Id", - [ - // NOTE: these tests also exercise all branches of the internal parameterFor function - TestCase("succeeds for byte ID", () => - { - var it = Parameters.Id((sbyte)7); - Expect.equal(it.Item1, "@id", "ID parameter not constructed correctly"); - Expect.equal(it.Item2, Sql.int8(7), "Byte ID parameter not constructed correctly"); - }), - TestCase("succeeds for unsigned byte ID", () => - { - var it = Parameters.Id((byte)7); - Expect.equal(it.Item1, "@id", "ID parameter not constructed correctly"); - Expect.equal(it.Item2, Sql.int8(7), "Unsigned byte ID parameter not constructed correctly"); - }), - TestCase("succeeds for short ID", () => - { - var it = Parameters.Id((short)44); - Expect.equal(it.Item1, "@id", "ID parameter not constructed correctly"); - Expect.equal(it.Item2, Sql.int16(44), "Short ID parameter not constructed correctly"); - }), - TestCase("succeeds for unsigned short ID", () => - { - var it = Parameters.Id((ushort)64); - Expect.equal(it.Item1, "@id", "ID parameter not constructed correctly"); - Expect.equal(it.Item2, Sql.int16(64), "Unsigned short ID parameter not constructed correctly"); - }), - TestCase("succeeds for integer ID", () => - { - var it = Parameters.Id(88); - Expect.equal(it.Item1, "@id", "ID parameter not constructed correctly"); - Expect.equal(it.Item2, Sql.@int(88), "ID parameter value incorrect"); - }), - TestCase("succeeds for unsigned integer ID", () => - { - var it = Parameters.Id((uint)889); - Expect.equal(it.Item1, "@id", "ID parameter not constructed correctly"); - Expect.equal(it.Item2, Sql.@int(889), "Unsigned int ID parameter not constructed correctly"); - }), - TestCase("succeeds for long ID", () => - { - var it = Parameters.Id((long)123); - Expect.equal(it.Item1, "@id", "ID parameter not constructed correctly"); - Expect.equal(it.Item2, Sql.int64(123), "Long ID parameter not constructed correctly"); - }), - TestCase("succeeds for unsigned long ID", () => - { - var it = Parameters.Id((ulong)6464); - Expect.equal(it.Item1, "@id", "ID parameter not constructed correctly"); - Expect.equal(it.Item2, Sql.int64(6464), - "Unsigned long ID parameter not constructed correctly"); - }), - TestCase("succeeds for decimal ID", () => - { - var it = Parameters.Id((decimal)4.56); - Expect.equal(it.Item1, "@id", "ID parameter not constructed correctly"); - Expect.equal(it.Item2, Sql.@decimal((decimal)4.56), - "Decimal ID parameter not constructed correctly"); - }), - TestCase("succeeds for single ID", () => - { - var it = Parameters.Id((float)5.67); - Expect.equal(it.Item1, "@id", "ID parameter not constructed correctly"); - Expect.equal(it.Item2, Sql.@double((float)5.67), - "Single ID parameter not constructed correctly"); - }), - TestCase("succeeds for double ID", () => - { - var it = Parameters.Id(6.78); - Expect.equal(it.Item1, "@id", "ID parameter not constructed correctly"); - Expect.equal(it.Item2, Sql.@double(6.78), - "Double ID parameter not constructed correctly"); - }), - TestCase("succeeds for string ID", () => - { - var it = Parameters.Id("99"); - Expect.equal(it.Item1, "@id", "ID parameter not constructed correctly"); - Expect.equal(it.Item2, Sql.@string("99"), "ID parameter value incorrect"); - }), - TestCase("succeeds for non-numeric non-string ID", () => - { - var it = Parameters.Id(new Uri("https://example.com")); - Expect.equal(it.Item1, "@id", "ID parameter not constructed correctly"); - Expect.equal(it.Item2, Sql.@string("https://example.com/"), - "Non-numeric, non-string 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"); - }), - TestList("AddFields", - [ - TestCase("succeeds when a parameter is added", () => - { - var paramList = Parameters.AddFields([Field.EQ("it", "242")], []).ToList(); - Expect.hasLength(paramList, 1, "There should have been a parameter added"); - var (name, value) = paramList[0]; - Expect.equal(name, "@field0", "Field parameter name not correct"); - Expect.equal(value, Sql.@string("242"), "Field parameter value not correct"); - }), - TestCase("succeeds when multiple independent parameters are added", () => - { - var paramList = Parameters.AddFields([Field.EQ("me", "you"), Field.GT("us", "them")], - [Parameters.Id(14)]).ToList(); - Expect.hasLength(paramList, 3, "There should have been 2 parameters added"); - var (name, value) = paramList[0]; - Expect.equal(name, "@id", "First field parameter name not correct"); - Expect.equal(value, Sql.@int(14), "First field parameter value not correct"); - (name, value) = paramList[1]; - Expect.equal(name, "@field0", "Second field parameter name not correct"); - Expect.equal(value, Sql.@string("you"), "Second field parameter value not correct"); - (name, value) = paramList[2]; - Expect.equal(name, "@field1", "Third parameter name not correct"); - Expect.equal(value, Sql.@string("them"), "Third parameter value not correct"); - }), - TestCase("succeeds when a parameter is not added", () => - { - var paramList = Parameters.AddFields([Field.EX("tacos")], []).ToList(); - Expect.isEmpty(paramList, "There should not have been any parameters added"); - }), - TestCase("succeeds when two parameters are added for one field", () => - { - var paramList = - Parameters.AddFields([Field.BT("that", "eh", "zed").WithParameterName("@test")], []).ToList(); - Expect.hasLength(paramList, 2, "There should have been 2 parameters added"); - var (name, value) = paramList[0]; - Expect.equal(name, "@testmin", "Minimum field name not correct"); - Expect.equal(value, Sql.@string("eh"), "Minimum field value not correct"); - (name, value) = paramList[1]; - Expect.equal(name, "@testmax", "Maximum field name not correct"); - Expect.equal(value, Sql.@string("zed"), "Maximum field value not correct"); - }) - ]), - TestList("FieldNames", - [ - TestCase("succeeds for one name", () => - { - var (name, value) = Parameters.FieldNames(["bob"]); - Expect.equal(name, "@name", "The parameter name was incorrect"); - if (!value.IsString) - { - Expect.isTrue(false, "The parameter was not a String type"); - } - }), - TestCase("succeeds for multiple names", () => - { - var (name, value) = Parameters.FieldNames(["bob", "tom", "mike"]); - Expect.equal(name, "@name", "The parameter name was incorrect"); - if (!value.IsStringArray) - { - Expect.isTrue(false, "The parameter was not a StringArray type"); - } - }) - ]), -#pragma warning disable CS0618 - TestList("FieldName", - [ - TestCase("succeeds for one name", () => - { - var (name, value) = Parameters.FieldName(["bob"]); - Expect.equal(name, "@name", "The parameter name was incorrect"); - if (!value.IsString) - { - Expect.isTrue(false, "The parameter was not a String type"); - } - }), - TestCase("succeeds for multiple names", () => - { - var (name, value) = Parameters.FieldName(["bob", "tom", "mike"]); - Expect.equal(name, "@name", "The parameter name was incorrect"); - if (!value.IsStringArray) - { - Expect.isTrue(false, "The parameter was not a StringArray type"); - } - }) - ]) -#pragma warning restore CS0618 - ]), - TestList("Query", - [ - TestList("WhereByFields", - [ - TestCase("succeeds for a single field when a logical operator is passed", () => - { - Expect.equal( - Postgres.Query.WhereByFields(FieldMatch.Any, - [Field.GT("theField", "0").WithParameterName("@test")]), - "data->>'theField' > @test", "WHERE clause not correct"); - }), - TestCase("succeeds for a single field when an existence operator is passed", () => - { - Expect.equal(Postgres.Query.WhereByFields(FieldMatch.Any, [Field.NEX("thatField")]), - "data->>'thatField' IS NULL", "WHERE clause not correct"); - }), - TestCase("succeeds for a single field when a between operator is passed with numeric values", () => - { - Expect.equal( - Postgres.Query.WhereByFields(FieldMatch.All, - [Field.BT("aField", 50, 99).WithParameterName("@range")]), - "(data->>'aField')::numeric BETWEEN @rangemin AND @rangemax", "WHERE clause not correct"); - }), - TestCase("succeeds for a single field when a between operator is passed with non-numeric values", () => - { - Expect.equal( - Postgres.Query.WhereByFields(FieldMatch.Any, - [Field.BT("field0", "a", "b").WithParameterName("@alpha")]), - "data->>'field0' BETWEEN @alphamin AND @alphamax", "WHERE clause not correct"); - }), - TestCase("succeeds for all multiple fields with logical operators", () => - { - Expect.equal( - Postgres.Query.WhereByFields(FieldMatch.All, - [Field.EQ("theFirst", "1"), Field.EQ("numberTwo", "2")]), - "data->>'theFirst' = @field0 AND data->>'numberTwo' = @field1", "WHERE clause not correct"); - }), - TestCase("succeeds for any multiple fields with an existence operator", () => - { - Expect.equal( - Postgres.Query.WhereByFields(FieldMatch.Any, - [Field.NEX("thatField"), Field.GE("thisField", 18)]), - "data->>'thatField' IS NULL OR (data->>'thisField')::numeric >= @field0", - "WHERE clause not correct"); - }), - TestCase("succeeds for all multiple fields with between operators", () => - { - Expect.equal( - Postgres.Query.WhereByFields(FieldMatch.All, - [Field.BT("aField", 50, 99), Field.BT("anotherField", "a", "b")]), - "(data->>'aField')::numeric BETWEEN @field0min AND @field0max AND data->>'anotherField' BETWEEN @field1min AND @field1max", - "WHERE clause not correct"); - }) - ]), - TestList("WhereById", - [ - TestCase("succeeds for numeric ID", () => - { - Expect.equal(Postgres.Query.WhereById(18), "(data->>'Id')::numeric = @id", - "WHERE clause not correct"); - }), - TestCase("succeeds for string ID", () => - { - Expect.equal(Postgres.Query.WhereById("18"), "data->>'Id' = @id", "WHERE clause not correct"); - }), - TestCase("succeeds for non-numeric non-string ID", () => - { - Expect.equal(Postgres.Query.WhereById(new Uri("https://example.com")), "data->>'Id' = @id", - "WHERE clause not correct"); - }), - ]), - TestList("Definition", - [ - 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("EnsureDocumentIndex succeeds for full index", () => - { - 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("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"); - }) - ]), - TestCase("WhereDataContains succeeds", () => - { - 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"); - }), - TestCase("Patch succeeds", () => - { - Expect.equal(Postgres.Query.Patch(PostgresDb.TableName), - $"UPDATE {PostgresDb.TableName} SET data = data || @data", "Patch query not correct"); - }), - TestCase("RemoveFields succeeds", () => - { - Expect.equal(Postgres.Query.RemoveFields(PostgresDb.TableName), - $"UPDATE {PostgresDb.TableName} SET data = data - @name", "Field removal query not correct"); - }), - TestCase("ById succeeds", () => - { - Expect.equal(Postgres.Query.ById("test", "14"), "test WHERE data->>'Id' = @id", - "By-ID query not correct"); - }), - TestCase("ByFields succeeds", () => - { - Expect.equal(Postgres.Query.ByFields("unit", FieldMatch.Any, [Field.GT("That", 14)]), - "unit WHERE (data->>'That')::numeric > @field0", "By-Field query not correct"); - }), - TestCase ("ByContains succeeds", () => - { - Expect.equal(Postgres.Query.ByContains("exam"), "exam WHERE data @> @criteria", - "By-Contains query not correct"); - }), - TestCase("ByPathMach succeeds", () => - { - Expect.equal(Postgres.Query.ByPathMatch("verify"), "verify WHERE data @? @path::jsonpath", - "By-JSON Path query not correct"); - }) - ]) + ParametersTests, + QueryTests ]); private static readonly List TestDocuments = @@ -357,984 +360,1171 @@ public static class PostgresCSharpTests } /// - /// Integration tests for the PostgreSQL library + /// Integration tests for the Configuration module of the PostgreSQL library /// - private static readonly Test Integration = TestList("Integration", + private static readonly Test ConfigurationTests = TestList("Configuration", [ - TestList("Configuration", - [ - TestCase("UseDataSource disposes existing source", () => - { - using var db1 = ThrowawayDatabase.Create(PostgresDb.ConnStr.Value); - var source = PostgresDb.MkDataSource(db1.ConnectionString); - Postgres.Configuration.UseDataSource(source); + 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)); + 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"); + }) + ]); + + /// + /// Integration tests for the Custom module of the PostgreSQL library + /// + private static readonly Test CustomTests = TestList("Custom", + [ + TestList("List", + [ + TestCase("succeeds when data is found", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var docs = await Custom.List(Query.Find(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", + [Tuple.Create("@path", Sql.@string("$.NumValue ? (@ > 100)"))], Results.FromData); + Expect.isEmpty(docs, "There should have been no documents returned"); + }) + ]), + TestList("Single", + [ + 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", + [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", + [Tuple.Create("@id", Sql.@string("eighty"))], Results.FromData); + Expect.isNull(doc, "There should not have been a document returned"); + }) + ]), + TestList("NonQuery", + [ + 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", + [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"); + }) + ]); + + /// + /// Integration tests for the Definition module of the PostgreSQL library + /// + private static readonly Test DefinitionTests = TestList("Definition", + [ + TestCase("EnsureTable succeeds", async () => + { + await using var db = PostgresDb.BuildDb(); + + 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"); + return; + + Task TableExists() => Custom.Scalar( + "SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'ensured') AS it", Parameters.None, + Results.ToExists); + + Task KeyExists() => Custom.Scalar( + "SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'idx_ensured_key') AS it", Parameters.None, + Results.ToExists); + }), + TestCase("EnsureDocumentIndex succeeds", async () => + { + await using var db = PostgresDb.BuildDb(); + + 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"); + return; + + Task IndexExists() => Custom.Scalar( + "SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'idx_ensured_document') AS it", + Parameters.None, Results.ToExists); + }), + TestCase("EnsureFieldIndex succeeds", async () => + { + await using var db = PostgresDb.BuildDb(); + + 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"); + return; + + Task IndexExists() => Custom.Scalar( + "SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'idx_ensured_test') AS it", Parameters.None, + Results.ToExists); + }) + ]); + + /// + /// Integration tests for the Document module of the PostgreSQL library + /// + private static readonly Test DocumentTests = TestList("Document", + [ + TestList("Insert", + [ + 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 { - _ = source.OpenConnection(); - Expect.isTrue(false, "Data source should have been disposed"); + 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 } - }), - 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", + TestList("Save", [ - TestList("List", - [ - TestCase("succeeds when data is found", async () => - { - await using var db = PostgresDb.BuildDb(); - await LoadDocs(); - - var docs = await Custom.List(Query.Find(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", - [ - 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", - [ - 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 () => + 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"); - 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"); + 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("Definition", - [ - TestCase("EnsureTable succeeds", async () => - { - await using var db = PostgresDb.BuildDb(); - - 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"); - return; - - Task TableExists() => Custom.Scalar( - "SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'ensured') AS it", Parameters.None, - Results.ToExists); - Task KeyExists() => Custom.Scalar( - "SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'idx_ensured_key') AS it", Parameters.None, - Results.ToExists); - }), - TestCase("EnsureDocumentIndex succeeds", async () => - { - await using var db = PostgresDb.BuildDb(); - - 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"); - return; - - Task IndexExists() => Custom.Scalar( - "SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'idx_ensured_document') AS it", - Parameters.None, Results.ToExists); - }), - TestCase("EnsureFieldIndex succeeds", async () => - { - await using var db = PostgresDb.BuildDb(); - - 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"); - return; - - Task IndexExists() => Custom.Scalar( - "SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'idx_ensured_test') AS it", Parameters.None, - Results.ToExists); - }) - ]), - TestList("Document", - [ - TestList("Insert", - [ - 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", - [ - 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", - [ - 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"); - }), - TestList("ByFields", - [ - TestCase("succeeds for numeric range", async () => - { - await using var db = PostgresDb.BuildDb(); - await LoadDocs(); - - var theCount = await Count.ByFields(PostgresDb.TableName, FieldMatch.Any, - [Field.BT("NumValue", 10, 20)]); - Expect.equal(theCount, 3, "There should have been 3 matching documents"); - }), - TestCase("succeeds for non-numeric range", async () => - { - await using var db = PostgresDb.BuildDb(); - await LoadDocs(); - - var theCount = await Count.ByFields(PostgresDb.TableName, FieldMatch.All, - [Field.BT("Value", "aardvark", "apple")]); - Expect.equal(theCount, 1, "There should have been 1 matching document"); - }) - ]), - 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", - [ - TestList("ById", - [ - 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("ByFields", - [ - TestCase("succeeds when documents exist", async () => - { - await using var db = PostgresDb.BuildDb(); - await LoadDocs(); - - var exists = await Exists.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.NEX("Sub")]); - 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.ByFields(PostgresDb.TableName, FieldMatch.Any, - [Field.EQ("NumValue", "six")]); - Expect.isFalse(exists, "There should not have been existing documents"); - }) - ]), - TestList("ByContains", - [ - 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", - [ - 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", - [ - TestList("All", - [ - 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", - [ - 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("ByFields", - [ - TestCase("succeeds when documents are found", async () => - { - await using var db = PostgresDb.BuildDb(); - await LoadDocs(); - - var docs = await Find.ByFields(PostgresDb.TableName, FieldMatch.Any, - [Field.EQ("Value", "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.ByFields(PostgresDb.TableName, FieldMatch.Any, - [Field.EQ("Value", "mauve")]); - Expect.isEmpty(docs, "There should have been no documents returned"); - }) - ]), - TestList("ByContains", - [ - 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", - [ - 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("FirstByFields", - [ - TestCase("succeeds when a document is found", async () => - { - await using var db = PostgresDb.BuildDb(); - await LoadDocs(); - - var doc = await Find.FirstByFields(PostgresDb.TableName, FieldMatch.Any, - [Field.EQ("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.FirstByFields(PostgresDb.TableName, FieldMatch.Any, - [Field.EQ("Value", "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.FirstByFields(PostgresDb.TableName, FieldMatch.Any, - [Field.EQ("Value", "absent")]); - Expect.isNull(doc, "There should not have been a document returned"); - }) - ]), - TestList("FirstByContains", - [ - 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", - [ - 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", - [ - TestList("ById", - [ - TestCase("succeeds when a document is updated", async () => - { - await using var db = PostgresDb.BuildDb(); - await LoadDocs(); - - await Update.ById(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.ById(PostgresDb.TableName, "test", - new JsonDocument { Id = "x", Sub = new() { Foo = "blue", Bar = "red" } }); - }) - ]), - TestList("ByFunc", - [ - TestCase("succeeds when a document is updated", async () => - { - await using var db = PostgresDb.BuildDb(); - await LoadDocs(); - - await Update.ByFunc(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.ByFunc(PostgresDb.TableName, doc => doc.Id, - new JsonDocument { Id = "one", Value = "le un", NumValue = 1 }); - }) - ]) - ]), - TestList("Patch", - [ - TestList("ById", - [ - TestCase("succeeds when a document is updated", async () => - { - await using var db = PostgresDb.BuildDb(); - await LoadDocs(); - - await Patch.ById(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 Patch.ById(PostgresDb.TableName, "test", new { Foo = "green" }); - }) - ]), - TestList("ByFields", - [ - TestCase("succeeds when a document is updated", async () => - { - await using var db = PostgresDb.BuildDb(); - await LoadDocs(); - - await Patch.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("Value", "purple")], - new { NumValue = 77 }); - var after = await Count.ByFields(PostgresDb.TableName, FieldMatch.Any, - [Field.EQ("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 Patch.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("Value", "burgundy")], - new { Foo = "green" }); - }) - ]), - TestList("ByContains", - [ - TestCase("succeeds when a document is updated", async () => - { - await using var db = PostgresDb.BuildDb(); - await LoadDocs(); - - await Patch.ByContains(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 Patch.ByContains(PostgresDb.TableName, new { Value = "burgundy" }, new { Foo = "green" }); - }) - ]), - TestList("ByJsonPath", - [ - TestCase("succeeds when a document is updated", async () => - { - await using var db = PostgresDb.BuildDb(); - await LoadDocs(); - - await Patch.ByJsonPath(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 Patch.ByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ < 0)", new { Foo = "green" }); - }) - ]) - ]), - TestList("RemoveFields", - [ - TestList("ById", - [ - TestCase("succeeds when multiple fields are removed", async () => - { - await using var db = PostgresDb.BuildDb(); - await LoadDocs(); - - await RemoveFields.ById(PostgresDb.TableName, "two", new[] { "Sub", "Value" }); - var updated = await Find.ById(PostgresDb.TableName, "two"); - Expect.isNotNull(updated, "The updated document should have been retrieved"); - Expect.equal(updated.Value, "", "The string value should have been removed"); - Expect.isNull(updated.Sub, "The sub-document should have been removed"); - }), - TestCase("succeeds when a single field is removed", async () => - { - await using var db = PostgresDb.BuildDb(); - await LoadDocs(); - - await RemoveFields.ById(PostgresDb.TableName, "two", new[] { "Sub" }); - var updated = await Find.ById(PostgresDb.TableName, "two"); - Expect.isNotNull(updated, "The updated document should have been retrieved"); - Expect.notEqual(updated.Value, "", "The string value should not have been removed"); - Expect.isNull(updated.Sub, "The sub-document should have been removed"); - }), - TestCase("succeeds when a field is not removed", async () => - { - await using var db = PostgresDb.BuildDb(); - await LoadDocs(); - - // This not raising an exception is the test - await RemoveFields.ById(PostgresDb.TableName, "two", new[] { "AFieldThatIsNotThere" }); - }), - TestCase("succeeds when no document is matched", async () => - { - await using var db = PostgresDb.BuildDb(); - - // This not raising an exception is the test - await RemoveFields.ById(PostgresDb.TableName, "two", new[] { "Value" }); - }) - ]), - TestList("ByFields", - [ - TestCase("succeeds when multiple fields are removed", async () => - { - await using var db = PostgresDb.BuildDb(); - await LoadDocs(); - - await RemoveFields.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("NumValue", "17")], - new[] { "Sub", "Value" }); - var updated = await Find.ById(PostgresDb.TableName, "four"); - Expect.isNotNull(updated, "The updated document should have been retrieved"); - Expect.equal(updated.Value, "", "The string value should have been removed"); - Expect.isNull(updated.Sub, "The sub-document should have been removed"); - }), - TestCase("succeeds when a single field is removed", async () => - { - await using var db = PostgresDb.BuildDb(); - await LoadDocs(); - - await RemoveFields.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("NumValue", "17")], - new[] { "Sub" }); - var updated = await Find.ById(PostgresDb.TableName, "four"); - Expect.isNotNull(updated, "The updated document should have been retrieved"); - Expect.notEqual(updated.Value, "", "The string value should not have been removed"); - Expect.isNull(updated.Sub, "The sub-document should have been removed"); - }), - TestCase("succeeds when a field is not removed", async () => - { - await using var db = PostgresDb.BuildDb(); - await LoadDocs(); - - // This not raising an exception is the test - await RemoveFields.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("NumValue", "17")], - new[] { "Nothing" }); - }), - TestCase("succeeds when no document is matched", async () => - { - await using var db = PostgresDb.BuildDb(); - - // This not raising an exception is the test - await RemoveFields.ByFields(PostgresDb.TableName, FieldMatch.Any, - [Field.NE("Abracadabra", "apple")], new[] { "Value" }); - }) - ]), - TestList("ByContains", - [ - TestCase("succeeds when multiple fields are removed", async () => - { - await using var db = PostgresDb.BuildDb(); - await LoadDocs(); - - await RemoveFields.ByContains(PostgresDb.TableName, new { NumValue = 17 }, - new[] { "Sub", "Value" }); - var updated = await Find.ById(PostgresDb.TableName, "four"); - Expect.isNotNull(updated, "The updated document should have been retrieved"); - Expect.equal(updated.Value, "", "The string value should have been removed"); - Expect.isNull(updated.Sub, "The sub-document should have been removed"); - }), - TestCase("succeeds when a single field is removed", async () => - { - await using var db = PostgresDb.BuildDb(); - await LoadDocs(); - - await RemoveFields.ByContains(PostgresDb.TableName, new { NumValue = 17 }, new[] { "Sub" }); - var updated = await Find.ById(PostgresDb.TableName, "four"); - Expect.isNotNull(updated, "The updated document should have been retrieved"); - Expect.notEqual(updated.Value, "", "The string value should not have been removed"); - Expect.isNull(updated.Sub, "The sub-document should have been removed"); - }), - TestCase("succeeds when a field is not removed", async () => - { - await using var db = PostgresDb.BuildDb(); - await LoadDocs(); - - // This not raising an exception is the test - await RemoveFields.ByContains(PostgresDb.TableName, new { NumValue = 17 }, new[] { "Nothing" }); - }), - TestCase("succeeds when no document is matched", async () => - { - await using var db = PostgresDb.BuildDb(); - - // This not raising an exception is the test - await RemoveFields.ByContains(PostgresDb.TableName, new { Abracadabra = "apple" }, - new[] { "Value" }); - }) - ]), - TestList("ByJsonPath", - [ - TestCase("succeeds when multiple fields are removed", async () => - { - await using var db = PostgresDb.BuildDb(); - await LoadDocs(); - - await RemoveFields.ByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ == 17)", - new[] { "Sub", "Value" }); - var updated = await Find.ById(PostgresDb.TableName, "four"); - Expect.isNotNull(updated, "The updated document should have been retrieved"); - Expect.equal(updated.Value, "", "The string value should have been removed"); - Expect.isNull(updated.Sub, "The sub-document should have been removed"); - }), - TestCase("succeeds when a single field is removed", async () => - { - await using var db = PostgresDb.BuildDb(); - await LoadDocs(); - - await RemoveFields.ByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ == 17)", new[] { "Sub" }); - var updated = await Find.ById(PostgresDb.TableName, "four"); - Expect.isNotNull(updated, "The updated document should have been retrieved"); - Expect.notEqual(updated.Value, "", "The string value should not have been removed"); - Expect.isNull(updated.Sub, "The sub-document should have been removed"); - }), - TestCase("succeeds when a field is not removed", async () => - { - await using var db = PostgresDb.BuildDb(); - await LoadDocs(); - - // This not raising an exception is the test - await RemoveFields.ByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ == 17)", new[] { "Nothing" }); - }), - TestCase("succeeds when no document is matched", async () => - { - await using var db = PostgresDb.BuildDb(); - - // This not raising an exception is the test - await RemoveFields.ByJsonPath(PostgresDb.TableName, "$.Abracadabra ? (@ == \"apple\")", - new[] { "Value" }); - }) - ]) - ]), - TestList("Delete", - [ - TestList("ById", - [ - 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("ByFields", - [ - TestCase("succeeds when documents are deleted", async () => - { - await using var db = PostgresDb.BuildDb(); - await LoadDocs(); - - await Delete.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("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.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("Value", "crimson")]); - var remaining = await Count.All(PostgresDb.TableName); - Expect.equal(remaining, 5, "There should have been 5 documents remaining"); - }) - ]), - TestList("ByContains", - [ - 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", - [ - 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"); - }) - ]) ]) ]); - + + /// + /// Integration tests for the Count module of the PostgreSQL library + /// + private static readonly Test CountTests = TestList("Count", + [ + 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"); + }), + TestList("ByFields", + [ + TestCase("succeeds for numeric range", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var theCount = await Count.ByFields(PostgresDb.TableName, FieldMatch.Any, + [Field.BT("NumValue", 10, 20)]); + Expect.equal(theCount, 3, "There should have been 3 matching documents"); + }), + TestCase("succeeds for non-numeric range", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var theCount = await Count.ByFields(PostgresDb.TableName, FieldMatch.All, + [Field.BT("Value", "aardvark", "apple")]); + Expect.equal(theCount, 1, "There should have been 1 matching document"); + }) + ]), + 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"); + }) + ]); + + /// + /// Integration tests for the Exists module of the PostgreSQL library + /// + private static readonly Test ExistsTests = TestList("Exists", + [ + TestList("ById", + [ + 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("ByFields", + [ + TestCase("succeeds when documents exist", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var exists = await Exists.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.NEX("Sub")]); + 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.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("NumValue", "six")]); + Expect.isFalse(exists, "There should not have been existing documents"); + }) + ]), + TestList("ByContains", + [ + 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", + [ + 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"); + }) + ]) + ]); + + /// + /// Integration tests for the Find module of the PostgreSQL library + /// + private static readonly Test FindTests = TestList("Find", + [ + TestList("All", + [ + 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.hasLength(results, 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("AllOrdered", + [ + TestCase("succeeds when ordering numerically", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var results = + await Find.AllOrdered(PostgresDb.TableName, [Field.Named("n:NumValue")]); + Expect.hasLength(results, 5, "There should have been 5 documents returned"); + Expect.equal(string.Join('|', results.Select(x => x.Id)), "one|three|two|four|five", + "The documents were not ordered correctly"); + }), + TestCase("succeeds when ordering numerically descending", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var results = + await Find.AllOrdered(PostgresDb.TableName, [Field.Named("n:NumValue DESC")]); + Expect.hasLength(results, 5, "There should have been 5 documents returned"); + Expect.equal(string.Join('|', results.Select(x => x.Id)), "five|four|two|three|one", + "The documents were not ordered correctly"); + }), + TestCase("succeeds when ordering alphabetically", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var results = await Find.AllOrdered(PostgresDb.TableName, [Field.Named("Id DESC")]); + Expect.hasLength(results, 5, "There should have been 5 documents returned"); + Expect.equal(string.Join('|', results.Select(x => x.Id)), "two|three|one|four|five", + "The documents were not ordered correctly"); + }) + ]), + TestList("ById", + [ + 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("ByFields", + [ + TestCase("succeeds when documents are found", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var docs = await Find.ByFields(PostgresDb.TableName, FieldMatch.Any, + [Field.EQ("Value", "another")]); + Expect.hasLength(docs, 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.ByFields(PostgresDb.TableName, FieldMatch.Any, + [Field.EQ("Value", "mauve")]); + Expect.isEmpty(docs, "There should have been no documents returned"); + }) + ]), + TestList("ByFieldsOrdered", + [ + TestCase("succeeds when documents are found", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var docs = await Find.ByFieldsOrdered(PostgresDb.TableName, FieldMatch.Any, + [Field.EQ("Value", "purple")], [Field.Named("Id")]); + Expect.hasLength(docs, 2, "There should have been two document returned"); + Expect.equal(string.Join('|', docs.Select(x => x.Id)), "five|four", + "The documents were not ordered correctly"); + }), + TestCase("succeeds when documents are not found", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var docs = await Find.ByFieldsOrdered(PostgresDb.TableName, FieldMatch.Any, + [Field.EQ("Value", "purple")], [Field.Named("Id DESC")]); + Expect.hasLength(docs, 2, "There should have been two document returned"); + Expect.equal(string.Join('|', docs.Select(x => x.Id)), "four|five", + "The documents were not ordered correctly"); + }) + ]), + TestList("ByContains", + [ + 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.hasLength(docs, 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("ByContainsOrdered", + [ + // Id = two, Sub.Bar = blue; Id = four, Sub.Bar = red + TestCase("succeeds when sorting ascending", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var docs = await Find.ByContainsOrdered(PostgresDb.TableName, + new { Sub = new { Foo = "green" } }, [Field.Named("Sub.Bar")]); + Expect.hasLength(docs, 2, "There should have been two documents returned"); + Expect.equal(string.Join('|', docs.Select(x => x.Id)), "two|four", + "Documents not ordered correctly"); + }), + TestCase("succeeds when sorting descending", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var docs = await Find.ByContainsOrdered(PostgresDb.TableName, + new { Sub = new { Foo = "green" } }, [Field.Named("Sub.Bar DESC")]); + Expect.hasLength(docs, 2, "There should have been two documents returned"); + Expect.equal(string.Join('|', docs.Select(x => x.Id)), "four|two", + "Documents not ordered correctly"); + }) + ]), + TestList("ByJsonPath", + [ + 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.hasLength(docs, 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("ByJsonPathOrdered", + [ + // Id = one, NumValue = 0; Id = two, NumValue = 10; Id = three, NumValue = 4 + TestCase("succeeds when sorting ascending", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var docs = await Find.ByJsonPathOrdered(PostgresDb.TableName, "$.NumValue ? (@ < 15)", + [Field.Named("n:NumValue")]); + Expect.hasLength(docs, 3, "There should have been 3 documents returned"); + Expect.equal(string.Join('|', docs.Select(x => x.Id)), "one|three|two", + "Documents not ordered correctly"); + }), + TestCase("succeeds when sorting descending", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var docs = await Find.ByJsonPathOrdered(PostgresDb.TableName, "$.NumValue ? (@ < 15)", + [Field.Named("n:NumValue DESC")]); + Expect.hasLength(docs, 3, "There should have been 3 documents returned"); + Expect.equal(string.Join('|', docs.Select(x => x.Id)), "two|three|one", + "Documents not ordered correctly"); + }) + ]), + TestList("FirstByFields", + [ + TestCase("succeeds when a document is found", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var doc = await Find.FirstByFields(PostgresDb.TableName, FieldMatch.Any, + [Field.EQ("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.FirstByFields(PostgresDb.TableName, FieldMatch.Any, + [Field.EQ("Value", "purple")]); + Expect.isNotNull(doc, "There should have been a document returned"); + Expect.contains(["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.FirstByFields(PostgresDb.TableName, FieldMatch.Any, + [Field.EQ("Value", "absent")]); + Expect.isNull(doc, "There should not have been a document returned"); + }) + ]), + TestList("FirstByFieldsOrdered", + [ + TestCase("succeeds when sorting ascending", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var doc = await Find.FirstByFieldsOrdered(PostgresDb.TableName, FieldMatch.Any, + [Field.EQ("Value", "purple")], [Field.Named("Id")]); + Expect.isNotNull(doc, "There should have been a document returned"); + Expect.equal("five", 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.FirstByFieldsOrdered(PostgresDb.TableName, FieldMatch.Any, + [Field.EQ("Value", "purple")], [Field.Named("Id DESC")]); + Expect.isNotNull(doc, "There should have been a document returned"); + Expect.equal("four", doc.Id, "An incorrect document was returned"); + }) + ]), + TestList("FirstByContains", + [ + 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(["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", + [ + 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(["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"); + }) + ]) + ]); + + /// + /// Integration tests for the Update module of the PostgreSQL library + /// + private static readonly Test UpdateTests = TestList("Update", + [ + TestList("ById", + [ + TestCase("succeeds when a document is updated", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + await Update.ById(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.ById(PostgresDb.TableName, "test", + new JsonDocument { Id = "x", Sub = new() { Foo = "blue", Bar = "red" } }); + }) + ]), + TestList("ByFunc", + [ + TestCase("succeeds when a document is updated", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + await Update.ByFunc(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.ByFunc(PostgresDb.TableName, doc => doc.Id, + new JsonDocument { Id = "one", Value = "le un", NumValue = 1 }); + }) + ]) + ]); + + /// + /// Integration tests for the Patch module of the PostgreSQL library + /// + private static readonly Test PatchTests = TestList("Patch", + [ + TestList("ById", + [ + TestCase("succeeds when a document is updated", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + await Patch.ById(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 Patch.ById(PostgresDb.TableName, "test", new { Foo = "green" }); + }) + ]), + TestList("ByFields", + [ + TestCase("succeeds when a document is updated", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + await Patch.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("Value", "purple")], + new { NumValue = 77 }); + var after = await Count.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("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 Patch.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("Value", "burgundy")], + new { Foo = "green" }); + }) + ]), + TestList("ByContains", + [ + TestCase("succeeds when a document is updated", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + await Patch.ByContains(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 Patch.ByContains(PostgresDb.TableName, new { Value = "burgundy" }, new { Foo = "green" }); + }) + ]), + TestList("ByJsonPath", + [ + TestCase("succeeds when a document is updated", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + await Patch.ByJsonPath(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 Patch.ByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ < 0)", new { Foo = "green" }); + }) + ]) + ]); + + /// + /// Integration tests for the RemoveFields module of the PostgreSQL library + /// + private static readonly Test RemoveFieldsTests = TestList("RemoveFields", + [ + TestList("ById", + [ + TestCase("succeeds when multiple fields are removed", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + await RemoveFields.ById(PostgresDb.TableName, "two", ["Sub", "Value"]); + var updated = await Find.ById(PostgresDb.TableName, "two"); + Expect.isNotNull(updated, "The updated document should have been retrieved"); + Expect.equal(updated.Value, "", "The string value should have been removed"); + Expect.isNull(updated.Sub, "The sub-document should have been removed"); + }), + TestCase("succeeds when a single field is removed", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + await RemoveFields.ById(PostgresDb.TableName, "two", ["Sub"]); + var updated = await Find.ById(PostgresDb.TableName, "two"); + Expect.isNotNull(updated, "The updated document should have been retrieved"); + Expect.notEqual(updated.Value, "", "The string value should not have been removed"); + Expect.isNull(updated.Sub, "The sub-document should have been removed"); + }), + TestCase("succeeds when a field is not removed", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + // This not raising an exception is the test + await RemoveFields.ById(PostgresDb.TableName, "two", ["AFieldThatIsNotThere"]); + }), + TestCase("succeeds when no document is matched", async () => + { + await using var db = PostgresDb.BuildDb(); + + // This not raising an exception is the test + await RemoveFields.ById(PostgresDb.TableName, "two", ["Value"]); + }) + ]), + TestList("ByFields", + [ + TestCase("succeeds when multiple fields are removed", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + await RemoveFields.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("NumValue", "17")], + ["Sub", "Value"]); + var updated = await Find.ById(PostgresDb.TableName, "four"); + Expect.isNotNull(updated, "The updated document should have been retrieved"); + Expect.equal(updated.Value, "", "The string value should have been removed"); + Expect.isNull(updated.Sub, "The sub-document should have been removed"); + }), + TestCase("succeeds when a single field is removed", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + await RemoveFields.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("NumValue", "17")], + ["Sub"]); + var updated = await Find.ById(PostgresDb.TableName, "four"); + Expect.isNotNull(updated, "The updated document should have been retrieved"); + Expect.notEqual(updated.Value, "", "The string value should not have been removed"); + Expect.isNull(updated.Sub, "The sub-document should have been removed"); + }), + TestCase("succeeds when a field is not removed", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + // This not raising an exception is the test + await RemoveFields.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("NumValue", "17")], + ["Nothing"]); + }), + TestCase("succeeds when no document is matched", async () => + { + await using var db = PostgresDb.BuildDb(); + + // This not raising an exception is the test + await RemoveFields.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.NE("Abracadabra", "apple")], + ["Value"]); + }) + ]), + TestList("ByContains", + [ + TestCase("succeeds when multiple fields are removed", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + await RemoveFields.ByContains(PostgresDb.TableName, new { NumValue = 17 }, ["Sub", "Value"]); + var updated = await Find.ById(PostgresDb.TableName, "four"); + Expect.isNotNull(updated, "The updated document should have been retrieved"); + Expect.equal(updated.Value, "", "The string value should have been removed"); + Expect.isNull(updated.Sub, "The sub-document should have been removed"); + }), + TestCase("succeeds when a single field is removed", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + await RemoveFields.ByContains(PostgresDb.TableName, new { NumValue = 17 }, ["Sub"]); + var updated = await Find.ById(PostgresDb.TableName, "four"); + Expect.isNotNull(updated, "The updated document should have been retrieved"); + Expect.notEqual(updated.Value, "", "The string value should not have been removed"); + Expect.isNull(updated.Sub, "The sub-document should have been removed"); + }), + TestCase("succeeds when a field is not removed", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + // This not raising an exception is the test + await RemoveFields.ByContains(PostgresDb.TableName, new { NumValue = 17 }, ["Nothing"]); + }), + TestCase("succeeds when no document is matched", async () => + { + await using var db = PostgresDb.BuildDb(); + + // This not raising an exception is the test + await RemoveFields.ByContains(PostgresDb.TableName, new { Abracadabra = "apple" }, ["Value"]); + }) + ]), + TestList("ByJsonPath", + [ + TestCase("succeeds when multiple fields are removed", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + await RemoveFields.ByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ == 17)", ["Sub", "Value"]); + var updated = await Find.ById(PostgresDb.TableName, "four"); + Expect.isNotNull(updated, "The updated document should have been retrieved"); + Expect.equal(updated.Value, "", "The string value should have been removed"); + Expect.isNull(updated.Sub, "The sub-document should have been removed"); + }), + TestCase("succeeds when a single field is removed", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + await RemoveFields.ByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ == 17)", ["Sub"]); + var updated = await Find.ById(PostgresDb.TableName, "four"); + Expect.isNotNull(updated, "The updated document should have been retrieved"); + Expect.notEqual(updated.Value, "", "The string value should not have been removed"); + Expect.isNull(updated.Sub, "The sub-document should have been removed"); + }), + TestCase("succeeds when a field is not removed", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + // This not raising an exception is the test + await RemoveFields.ByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ == 17)", ["Nothing"]); + }), + TestCase("succeeds when no document is matched", async () => + { + await using var db = PostgresDb.BuildDb(); + + // This not raising an exception is the test + await RemoveFields.ByJsonPath(PostgresDb.TableName, "$.Abracadabra ? (@ == \"apple\")", ["Value"]); + }) + ]) + ]); + + /// + /// Integration tests for the Delete module of the PostgreSQL library + /// + private static readonly Test DeleteTests = TestList("Delete", + [ + TestList("ById", + [ + 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("ByFields", + [ + TestCase("succeeds when documents are deleted", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + await Delete.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("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.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("Value", "crimson")]); + var remaining = await Count.All(PostgresDb.TableName); + Expect.equal(remaining, 5, "There should have been 5 documents remaining"); + }) + ]), + TestList("ByContains", + [ + 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", + [ + 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 /// [Tests] - public static readonly Test All = TestList("Postgres.C#", [Unit, TestSequenced(Integration)]); + public static readonly Test All = TestList("Postgres.C#", + [ + TestList("Unit", + [ + ParametersTests, + QueryTests + ]), + TestSequenced(TestList("Integration", + [ + ConfigurationTests, + CustomTests, + DefinitionTests, + DocumentTests, + CountTests, + ExistsTests, + FindTests, + UpdateTests, + PatchTests, + RemoveFieldsTests, + DeleteTests + ])) + ]); } diff --git a/src/Tests/PostgresTests.fs b/src/Tests/PostgresTests.fs index 0a2e3ba..1c04aaa 100644 --- a/src/Tests/PostgresTests.fs +++ b/src/Tests/PostgresTests.fs @@ -7,1118 +7,1249 @@ open BitBadger.Documents.Tests #nowarn "0044" -/// Tests which do not hit the database -let unitTests = - testList "Unit" [ - testList "Parameters" [ - testList "idParam" [ - // NOTE: these tests also exercise all branches of the internal parameterFor function - test "succeeds for byte ID" { - Expect.equal - (idParam (sbyte 7)) ("@id", Sql.int8 (sbyte 7)) "Byte ID parameter not constructed correctly" - } - test "succeeds for unsigned byte ID" { - Expect.equal - (idParam (byte 7)) - ("@id", Sql.int8 (int8 (byte 7))) - "Unsigned byte ID parameter not constructed correctly" - } - test "succeeds for short ID" { - Expect.equal - (idParam (int16 44)) - ("@id", Sql.int16 (int16 44)) - "Short ID parameter not constructed correctly" - } - test "succeeds for unsigned short ID" { - Expect.equal - (idParam (uint16 64)) - ("@id", Sql.int16 (int16 64)) - "Unsigned short ID parameter not constructed correctly" - } - test "succeeds for integer ID" { - Expect.equal (idParam 88) ("@id", Sql.int 88) "Int ID parameter not constructed correctly" - } - test "succeeds for unsigned integer ID" { - Expect.equal - (idParam (uint 889)) ("@id", Sql.int 889) "Unsigned int ID parameter not constructed correctly" - } - test "succeeds for long ID" { - Expect.equal - (idParam (int64 123)) - ("@id", Sql.int64 (int64 123)) - "Long ID parameter not constructed correctly" - } - test "succeeds for unsigned long ID" { - Expect.equal - (idParam (uint64 6464)) - ("@id", Sql.int64 (int64 6464)) - "Unsigned long ID parameter not constructed correctly" - } - test "succeeds for decimal ID" { - Expect.equal - (idParam (decimal 4.56)) - ("@id", Sql.decimal (decimal 4.56)) - "Decimal ID parameter not constructed correctly" - } - test "succeeds for single ID" { - Expect.equal - (idParam (single 5.67)) - ("@id", Sql.double (double (single 5.67))) - "Single ID parameter not constructed correctly" - } - test "succeeds for double ID" { - Expect.equal - (idParam (double 6.78)) - ("@id", Sql.double (double 6.78)) - "Double ID parameter not constructed correctly" - } - test "succeeds for string ID" { - Expect.equal (idParam "99") ("@id", Sql.string "99") "String ID parameter not constructed correctly" - } - test "succeeds for non-numeric non-string ID" { - let target = { new obj() with override _.ToString() = "ToString was called" } - Expect.equal - (idParam target) - ("@id", Sql.string "ToString was called") - "Non-numeric, non-string parameter not constructed correctly" - } - ] - test "jsonParam succeeds" { - Expect.equal - (jsonParam "@test" {| Something = "good" |}) - ("@test", Sql.jsonb """{"Something":"good"}""") - "JSON parameter not constructed correctly" - } - testList "addFieldParams" [ - test "succeeds when a parameter is added" { - let paramList = addFieldParams [ Field.EQ "it" "242" ] [] - Expect.hasLength paramList 1 "There should have been a parameter added" - let name, value = Seq.head paramList - Expect.equal name "@field0" "Field parameter name not correct" - Expect.equal value (Sql.string "242") "Parameter value not correct" - } - test "succeeds when multiple independent parameters are added" { - let paramList = addFieldParams [ Field.EQ "me" "you"; Field.GT "us" "them" ] [ idParam 14 ] - Expect.hasLength paramList 3 "There should have been 2 parameters added" - let p = Array.ofSeq paramList - Expect.equal (fst p[0]) "@id" "First field parameter name not correct" - Expect.equal (snd p[0]) (Sql.int 14) "First parameter value not correct" - Expect.equal (fst p[1]) "@field0" "Second field parameter name not correct" - Expect.equal (snd p[1]) (Sql.string "you") "Second parameter value not correct" - Expect.equal (fst p[2]) "@field1" "Third parameter name not correct" - Expect.equal (snd p[2]) (Sql.string "them") "Third parameter value not correct" - } - test "succeeds when a parameter is not added" { - let paramList = addFieldParams [ Field.EX "tacos" ] [] - Expect.isEmpty paramList "There should not have been any parameters added" - } - test "succeeds when two parameters are added for one field" { - let paramList = - addFieldParams [ { Field.BT "that" "eh" "zed" with ParameterName = Some "@test" } ] [] - Expect.hasLength paramList 2 "There should have been 2 parameters added" - let name, value = Seq.head paramList - Expect.equal name "@testmin" "Minimum field name not correct" - Expect.equal value (Sql.string "eh") "Minimum parameter value not correct" - let name, value = paramList |> Seq.skip 1 |> Seq.head - Expect.equal name "@testmax" "Maximum field name not correct" - Expect.equal value (Sql.string "zed") "Maximum parameter value not correct" - } - ] - testList "fieldNameParams" [ - test "succeeds for one name" { - let name, value = fieldNameParams [ "bob" ] - Expect.equal name "@name" "The parameter name was incorrect" - Expect.equal value (Sql.string "bob") "The parameter value was incorrect" - } - test "succeeds for multiple names" { - let name, value = fieldNameParams [ "bob"; "tom"; "mike" ] - Expect.equal name "@name" "The parameter name was incorrect" - Expect.equal value (Sql.stringArray [| "bob"; "tom"; "mike" |]) "The parameter value was incorrect" - } - ] - testList "fieldNameParam" [ - test "succeeds for one name" { - let name, value = fieldNameParam [ "bob" ] - Expect.equal name "@name" "The parameter name was incorrect" - Expect.equal value (Sql.string "bob") "The parameter value was incorrect" - } - test "succeeds for multiple names" { - let name, value = fieldNameParam [ "bob"; "tom"; "mike" ] - Expect.equal name "@name" "The parameter name was incorrect" - Expect.equal value (Sql.stringArray [| "bob"; "tom"; "mike" |]) "The parameter value was incorrect" - } - ] - test "noParams succeeds" { - Expect.isEmpty noParams "The no-params sequence should be empty" - } - ] - testList "Query" [ - testList "whereByFields" [ - test "succeeds for a single field when a logical operator is passed" { - Expect.equal - (Query.whereByFields Any [ { Field.GT "theField" "0" with ParameterName = Some "@test" } ]) - "data->>'theField' > @test" - "WHERE clause not correct" - } - test "succeeds for a single field when an existence operator is passed" { - Expect.equal - (Query.whereByFields Any [ Field.NEX "thatField" ]) - "data->>'thatField' IS NULL" - "WHERE clause not correct" - } - test "succeeds for a single field when a between operator is passed with numeric values" { - Expect.equal - (Query.whereByFields All [ { Field.BT "aField" 50 99 with ParameterName = Some "@range" } ]) - "(data->>'aField')::numeric BETWEEN @rangemin AND @rangemax" - "WHERE clause not correct" - } - test "succeeds for a single field when a between operator is passed with non-numeric values" { - Expect.equal - (Query.whereByFields Any [ { Field.BT "field0" "a" "b" with ParameterName = Some "@alpha" } ]) - "data->>'field0' BETWEEN @alphamin AND @alphamax" - "WHERE clause not correct" - } - test "succeeds for all multiple fields with logical operators" { - Expect.equal - (Query.whereByFields All [ Field.EQ "theFirst" "1"; Field.EQ "numberTwo" "2" ]) - "data->>'theFirst' = @field0 AND data->>'numberTwo' = @field1" - "WHERE clause not correct" - } - test "succeeds for any multiple fields with an existence operator" { - Expect.equal - (Query.whereByFields Any [ Field.NEX "thatField"; Field.GE "thisField" 18 ]) - "data->>'thatField' IS NULL OR (data->>'thisField')::numeric >= @field0" - "WHERE clause not correct" - } - test "succeeds for all multiple fields with between operators" { - Expect.equal - (Query.whereByFields All [ Field.BT "aField" 50 99; Field.BT "anotherField" "a" "b" ]) - "(data->>'aField')::numeric BETWEEN @field0min AND @field0max AND data->>'anotherField' BETWEEN @field1min AND @field1max" - "WHERE clause not correct" - } - ] - testList "whereById" [ - test "succeeds for numeric ID" { - Expect.equal (Query.whereById 18) "(data->>'Id')::numeric = @id" "WHERE clause not correct" - } - test "succeeds for string ID" { - Expect.equal (Query.whereById "18") "data->>'Id' = @id" "WHERE clause not correct" - } - test "succeeds for non-numeric non-string ID" { - Expect.equal - (Query.whereById (System.Uri "https://example.com")) - "data->>'Id' = @id" - "WHERE clause not correct" - } - ] - testList "Definition" [ - test "ensureTable succeeds" { - Expect.equal - (Query.Definition.ensureTable PostgresDb.TableName) - $"CREATE TABLE IF NOT EXISTS {PostgresDb.TableName} (data JSONB NOT NULL)" - "CREATE TABLE statement not constructed correctly" - } - test "ensureDocumentIndex succeeds for full index" { - Expect.equal - (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 "ensureDocumentIndex succeeds for JSONB Path Ops index" { - Expect.equal - (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" - } - ] - test "whereDataContains succeeds" { - Expect.equal (Query.whereDataContains "@test") "data @> @test" "WHERE clause not correct" - } - test "whereJsonPathMatches succeeds" { - Expect.equal (Query.whereJsonPathMatches "@path") "data @? @path::jsonpath" "WHERE clause not correct" - } - test "patch succeeds" { - Expect.equal - (Query.patch PostgresDb.TableName) - $"UPDATE {PostgresDb.TableName} SET data = data || @data" - "Patch query not correct" - } - test "removeFields succeeds" { - Expect.equal - (Query.removeFields PostgresDb.TableName) - $"UPDATE {PostgresDb.TableName} SET data = data - @name" - "Field removal query not correct" - } - test "byId succeeds" { - Expect.equal (Query.byId "test" "14") "test WHERE data->>'Id' = @id" "By-ID query not correct" - } - test "byFields succeeds" { - Expect.equal - (Query.byFields "unit" Any [ Field.GT "That" 14 ]) - "unit WHERE (data->>'That')::numeric > @field0" - "By-Field query not correct" - } - test "byContains succeeds" { - Expect.equal (Query.byContains "exam") "exam WHERE data @> @criteria" "By-Contains query not correct" - } - test "byPathMach succeeds" { - Expect.equal - (Query.byPathMatch "verify") "verify WHERE data @? @path::jsonpath" "By-JSON Path query not correct" - } - ] +(** UNIT TESTS **) + +/// Unit tests for the Parameters module of the PostgreSQL library +let parametersTests = testList "Parameters" [ + testList "idParam" [ + // NOTE: these tests also exercise all branches of the internal parameterFor function + test "succeeds for byte ID" { + Expect.equal (idParam (sbyte 7)) ("@id", Sql.int8 (sbyte 7)) "Byte ID parameter not constructed correctly" + } + test "succeeds for unsigned byte ID" { + Expect.equal + (idParam (byte 7)) + ("@id", Sql.int8 (int8 (byte 7))) + "Unsigned byte ID parameter not constructed correctly" + } + test "succeeds for short ID" { + Expect.equal + (idParam (int16 44)) ("@id", Sql.int16 (int16 44)) "Short ID parameter not constructed correctly" + } + test "succeeds for unsigned short ID" { + Expect.equal + (idParam (uint16 64)) + ("@id", Sql.int16 (int16 64)) + "Unsigned short ID parameter not constructed correctly" + } + test "succeeds for integer ID" { + Expect.equal (idParam 88) ("@id", Sql.int 88) "Int ID parameter not constructed correctly" + } + test "succeeds for unsigned integer ID" { + Expect.equal (idParam (uint 889)) ("@id", Sql.int 889) "Unsigned int ID parameter not constructed correctly" + } + test "succeeds for long ID" { + Expect.equal + (idParam (int64 123)) ("@id", Sql.int64 (int64 123)) "Long ID parameter not constructed correctly" + } + test "succeeds for unsigned long ID" { + Expect.equal + (idParam (uint64 6464)) + ("@id", Sql.int64 (int64 6464)) + "Unsigned long ID parameter not constructed correctly" + } + test "succeeds for decimal ID" { + Expect.equal + (idParam (decimal 4.56)) + ("@id", Sql.decimal (decimal 4.56)) + "Decimal ID parameter not constructed correctly" + } + test "succeeds for single ID" { + Expect.equal + (idParam (single 5.67)) + ("@id", Sql.double (double (single 5.67))) + "Single ID parameter not constructed correctly" + } + test "succeeds for double ID" { + Expect.equal + (idParam (double 6.78)) + ("@id", Sql.double (double 6.78)) + "Double ID parameter not constructed correctly" + } + test "succeeds for string ID" { + Expect.equal (idParam "99") ("@id", Sql.string "99") "String ID parameter not constructed correctly" + } + test "succeeds for non-numeric non-string ID" { + let target = { new obj() with override _.ToString() = "ToString was called" } + Expect.equal + (idParam target) + ("@id", Sql.string "ToString was called") + "Non-numeric, non-string parameter not constructed correctly" + } ] + test "jsonParam succeeds" { + Expect.equal + (jsonParam "@test" {| Something = "good" |}) + ("@test", Sql.jsonb """{"Something":"good"}""") + "JSON parameter not constructed correctly" + } + testList "addFieldParams" [ + test "succeeds when a parameter is added" { + let paramList = addFieldParams [ Field.EQ "it" "242" ] [] + Expect.hasLength paramList 1 "There should have been a parameter added" + let name, value = Seq.head paramList + Expect.equal name "@field0" "Field parameter name not correct" + Expect.equal value (Sql.string "242") "Parameter value not correct" + } + test "succeeds when multiple independent parameters are added" { + let paramList = addFieldParams [ Field.EQ "me" "you"; Field.GT "us" "them" ] [ idParam 14 ] + Expect.hasLength paramList 3 "There should have been 2 parameters added" + let p = Array.ofSeq paramList + Expect.equal (fst p[0]) "@id" "First field parameter name not correct" + Expect.equal (snd p[0]) (Sql.int 14) "First parameter value not correct" + Expect.equal (fst p[1]) "@field0" "Second field parameter name not correct" + Expect.equal (snd p[1]) (Sql.string "you") "Second parameter value not correct" + Expect.equal (fst p[2]) "@field1" "Third parameter name not correct" + Expect.equal (snd p[2]) (Sql.string "them") "Third parameter value not correct" + } + test "succeeds when a parameter is not added" { + let paramList = addFieldParams [ Field.EX "tacos" ] [] + Expect.isEmpty paramList "There should not have been any parameters added" + } + test "succeeds when two parameters are added for one field" { + let paramList = addFieldParams [ { Field.BT "that" "eh" "zed" with ParameterName = Some "@test" } ] [] + Expect.hasLength paramList 2 "There should have been 2 parameters added" + let name, value = Seq.head paramList + Expect.equal name "@testmin" "Minimum field name not correct" + Expect.equal value (Sql.string "eh") "Minimum parameter value not correct" + let name, value = paramList |> Seq.skip 1 |> Seq.head + Expect.equal name "@testmax" "Maximum field name not correct" + Expect.equal value (Sql.string "zed") "Maximum parameter value not correct" + } + ] + testList "fieldNameParams" [ + test "succeeds for one name" { + let name, value = fieldNameParams [ "bob" ] + Expect.equal name "@name" "The parameter name was incorrect" + Expect.equal value (Sql.string "bob") "The parameter value was incorrect" + } + test "succeeds for multiple names" { + let name, value = fieldNameParams [ "bob"; "tom"; "mike" ] + Expect.equal name "@name" "The parameter name was incorrect" + Expect.equal value (Sql.stringArray [| "bob"; "tom"; "mike" |]) "The parameter value was incorrect" + } + ] + testList "fieldNameParam" [ + test "succeeds for one name" { + let name, value = fieldNameParam [ "bob" ] + Expect.equal name "@name" "The parameter name was incorrect" + Expect.equal value (Sql.string "bob") "The parameter value was incorrect" + } + test "succeeds for multiple names" { + let name, value = fieldNameParam [ "bob"; "tom"; "mike" ] + Expect.equal name "@name" "The parameter name was incorrect" + Expect.equal value (Sql.stringArray [| "bob"; "tom"; "mike" |]) "The parameter value was incorrect" + } + ] + test "noParams succeeds" { + Expect.isEmpty noParams "The no-params sequence should be empty" + } +] + +/// Unit tests for the Query module of the PostgreSQL library +let queryTests = testList "Query" [ + testList "whereByFields" [ + test "succeeds for a single field when a logical operator is passed" { + Expect.equal + (Query.whereByFields Any [ { Field.GT "theField" "0" with ParameterName = Some "@test" } ]) + "data->>'theField' > @test" + "WHERE clause not correct" + } + test "succeeds for a single field when an existence operator is passed" { + Expect.equal + (Query.whereByFields Any [ Field.NEX "thatField" ]) + "data->>'thatField' IS NULL" + "WHERE clause not correct" + } + test "succeeds for a single field when a between operator is passed with numeric values" { + Expect.equal + (Query.whereByFields All [ { Field.BT "aField" 50 99 with ParameterName = Some "@range" } ]) + "(data->>'aField')::numeric BETWEEN @rangemin AND @rangemax" + "WHERE clause not correct" + } + test "succeeds for a single field when a between operator is passed with non-numeric values" { + Expect.equal + (Query.whereByFields Any [ { Field.BT "field0" "a" "b" with ParameterName = Some "@alpha" } ]) + "data->>'field0' BETWEEN @alphamin AND @alphamax" + "WHERE clause not correct" + } + test "succeeds for all multiple fields with logical operators" { + Expect.equal + (Query.whereByFields All [ Field.EQ "theFirst" "1"; Field.EQ "numberTwo" "2" ]) + "data->>'theFirst' = @field0 AND data->>'numberTwo' = @field1" + "WHERE clause not correct" + } + test "succeeds for any multiple fields with an existence operator" { + Expect.equal + (Query.whereByFields Any [ Field.NEX "thatField"; Field.GE "thisField" 18 ]) + "data->>'thatField' IS NULL OR (data->>'thisField')::numeric >= @field0" + "WHERE clause not correct" + } + test "succeeds for all multiple fields with between operators" { + Expect.equal + (Query.whereByFields All [ Field.BT "aField" 50 99; Field.BT "anotherField" "a" "b" ]) + "(data->>'aField')::numeric BETWEEN @field0min AND @field0max AND data->>'anotherField' BETWEEN @field1min AND @field1max" + "WHERE clause not correct" + } + ] + testList "whereById" [ + test "succeeds for numeric ID" { + Expect.equal (Query.whereById 18) "(data->>'Id')::numeric = @id" "WHERE clause not correct" + } + test "succeeds for string ID" { + Expect.equal (Query.whereById "18") "data->>'Id' = @id" "WHERE clause not correct" + } + test "succeeds for non-numeric non-string ID" { + Expect.equal + (Query.whereById (System.Uri "https://example.com")) "data->>'Id' = @id" "WHERE clause not correct" + } + ] + testList "Definition" [ + test "ensureTable succeeds" { + Expect.equal + (Query.Definition.ensureTable PostgresDb.TableName) + $"CREATE TABLE IF NOT EXISTS {PostgresDb.TableName} (data JSONB NOT NULL)" + "CREATE TABLE statement not constructed correctly" + } + test "ensureDocumentIndex succeeds for full index" { + Expect.equal + (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 "ensureDocumentIndex succeeds for JSONB Path Ops index" { + Expect.equal + (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" + } + ] + test "whereDataContains succeeds" { + Expect.equal (Query.whereDataContains "@test") "data @> @test" "WHERE clause not correct" + } + test "whereJsonPathMatches succeeds" { + Expect.equal (Query.whereJsonPathMatches "@path") "data @? @path::jsonpath" "WHERE clause not correct" + } + test "patch succeeds" { + Expect.equal + (Query.patch PostgresDb.TableName) + $"UPDATE {PostgresDb.TableName} SET data = data || @data" + "Patch query not correct" + } + test "removeFields succeeds" { + Expect.equal + (Query.removeFields PostgresDb.TableName) + $"UPDATE {PostgresDb.TableName} SET data = data - @name" + "Field removal query not correct" + } + test "byId succeeds" { + Expect.equal (Query.byId "test" "14") "test WHERE data->>'Id' = @id" "By-ID query not correct" + } + test "byFields succeeds" { + Expect.equal + (Query.byFields "unit" Any [ Field.GT "That" 14 ]) + "unit WHERE (data->>'That')::numeric > @field0" + "By-Field query not correct" + } + test "byContains succeeds" { + Expect.equal (Query.byContains "exam") "exam WHERE data @> @criteria" "By-Contains query not correct" + } + test "byPathMach succeeds" { + Expect.equal + (Query.byPathMatch "verify") "verify WHERE data @? @path::jsonpath" "By-JSON Path query not correct" + } +] + +(** INTEGRATION TESTS **) open ThrowawayDb.Postgres open Types -let isTrue<'T> (_ : 'T) = true +/// Documents to use for integration tests +let documents = [ + { Id = "one"; Value = "FIRST!"; NumValue = 0; Sub = None } + { Id = "two"; Value = "another"; NumValue = 10; Sub = Some { Foo = "green"; Bar = "blue" } } + { Id = "three"; Value = ""; NumValue = 4; Sub = None } + { Id = "four"; Value = "purple"; NumValue = 17; Sub = Some { Foo = "green"; Bar = "red" } } + { Id = "five"; Value = "purple"; NumValue = 18; Sub = None } +] -let integrationTests = - let documents = [ - { Id = "one"; Value = "FIRST!"; NumValue = 0; Sub = None } - { Id = "two"; Value = "another"; NumValue = 10; Sub = Some { Foo = "green"; Bar = "blue" } } - { Id = "three"; Value = ""; NumValue = 4; Sub = None } - { Id = "four"; Value = "purple"; NumValue = 17; Sub = Some { Foo = "green"; Bar = "red" } } - { Id = "five"; Value = "purple"; NumValue = 18; Sub = None } - ] - let loadDocs () = backgroundTask { - for doc in documents do do! insert PostgresDb.TableName doc +/// Load the test documents into the database +let loadDocs () = backgroundTask { + for doc in documents do do! insert PostgresDb.TableName doc +} + +/// Integration tests for the Configuration module of the PostgreSQL library +let configurationTests = testList "Configuration" [ + test "useDataSource disposes existing source" { + use db1 = ThrowawayDatabase.Create PostgresDb.ConnStr.Value + let source = PostgresDb.MkDataSource db1.ConnectionString + Configuration.useDataSource source + + use db2 = ThrowawayDatabase.Create PostgresDb.ConnStr.Value + Configuration.useDataSource (PostgresDb.MkDataSource db2.ConnectionString) + Expect.throws (fun () -> source.OpenConnection() |> ignore) "Data source should have been disposed" } - testList "Integration" [ - testList "Configuration" [ - test "useDataSource disposes existing source" { - use db1 = ThrowawayDatabase.Create PostgresDb.ConnStr.Value - let source = PostgresDb.MkDataSource db1.ConnectionString - Configuration.useDataSource source - - use db2 = ThrowawayDatabase.Create PostgresDb.ConnStr.Value - Configuration.useDataSource (PostgresDb.MkDataSource db2.ConnectionString) - Expect.throws (fun () -> source.OpenConnection() |> ignore) "Data source should have been disposed" - } - test "dataSource returns configured data source" { - use db = ThrowawayDatabase.Create PostgresDb.ConnStr.Value - let source = PostgresDb.MkDataSource db.ConnectionString - Configuration.useDataSource source - - Expect.isTrue (obj.ReferenceEquals(source, Configuration.dataSource ())) - "Data source should have been the same" - } - ] - testList "Custom" [ - testList "list" [ - testTask "succeeds when data is found" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - let! docs = Custom.list (Query.selectFromTable PostgresDb.TableName) [] fromData - Expect.hasCountOf docs 5u isTrue "There should have been 5 documents returned" - } - testTask "succeeds when data is not found" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - let! docs = - Custom.list $"SELECT data FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath" - [ "@path", Sql.string "$.NumValue ? (@ > 100)" ] fromData - Expect.isEmpty docs "There should have been no documents returned" - } - ] - testList "single" [ - testTask "succeeds when a row is found" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - let! doc = - Custom.single $"SELECT data FROM {PostgresDb.TableName} WHERE data ->> 'Id' = @id" - [ "@id", Sql.string "one"] fromData - Expect.isSome doc "There should have been a document returned" - Expect.equal doc.Value.Id "one" "The incorrect document was returned" - } - testTask "succeeds when a row is not found" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - let! doc = - Custom.single $"SELECT data FROM {PostgresDb.TableName} WHERE data ->> 'Id' = @id" - [ "@id", Sql.string "eighty" ] fromData - Expect.isNone doc "There should not have been a document returned" - } - ] - testList "nonQuery" [ - testTask "succeeds when operating on data" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - do! Custom.nonQuery $"DELETE FROM {PostgresDb.TableName}" [] - - let! remaining = Count.all PostgresDb.TableName - Expect.equal remaining 0 "There should be no documents remaining in the table" - } - testTask "succeeds when no data matches where clause" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - do! Custom.nonQuery $"DELETE FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath" - [ "@path", Sql.string "$.NumValue ? (@ > 100)" ] - - let! remaining = Count.all PostgresDb.TableName - Expect.equal remaining 5 "There should be 5 documents remaining in the table" - } - ] - testTask "scalar succeeds" { - use db = PostgresDb.BuildDb() - let! nbr = Custom.scalar "SELECT 5 AS test_value" [] (fun row -> row.int "test_value") - Expect.equal nbr 5 "The query should have returned the number 5" - } - ] - testList "Definition" [ - testTask "ensureTable succeeds" { - use db = PostgresDb.BuildDb() - let tableExists () = - Custom.scalar "SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'ensured') AS it" [] toExists - let keyExists () = - Custom.scalar - "SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'idx_ensured_key') AS it" [] toExists - - let! exists = tableExists () - let! alsoExists = keyExists () - Expect.isFalse exists "The table should not exist already" - Expect.isFalse alsoExists "The key index should not exist already" - - do! Definition.ensureTable "ensured" - let! exists' = tableExists () - let! alsoExists' = keyExists () - Expect.isTrue exists' "The table should now exist" - Expect.isTrue alsoExists' "The key index should now exist" - } - testTask "ensureDocumentIndex succeeds" { - use db = PostgresDb.BuildDb() - let indexExists () = - Custom.scalar - "SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'idx_ensured_document') AS it" - [] - toExists - - let! exists = indexExists () - Expect.isFalse exists "The index should not exist already" - - do! Definition.ensureTable "ensured" - do! Definition.ensureDocumentIndex "ensured" Optimized - let! exists' = indexExists () - Expect.isTrue exists' "The index should now exist" - } - testTask "ensureFieldIndex succeeds" { - use db = PostgresDb.BuildDb() - let indexExists () = - Custom.scalar - "SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'idx_ensured_test') AS it" [] toExists - - let! exists = indexExists () - Expect.isFalse exists "The index should not exist already" - - do! Definition.ensureTable "ensured" - do! Definition.ensureFieldIndex "ensured" "test" [ "Id"; "Category" ] - let! exists' = indexExists () - Expect.isTrue exists' "The index should now exist" - } - ] - testList "insert" [ - testTask "succeeds" { - use db = PostgresDb.BuildDb() - let! before = Find.all PostgresDb.TableName - Expect.equal before [] "There should be no documents in the table" - - let testDoc = { emptyDoc with Id = "turkey"; Sub = Some { Foo = "gobble"; Bar = "gobble" } } - do! insert PostgresDb.TableName testDoc - let! after = Find.all PostgresDb.TableName - Expect.equal after [ testDoc ] "There should have been one document inserted" - } - testTask "fails for duplicate key" { - use db = PostgresDb.BuildDb() - do! insert PostgresDb.TableName { emptyDoc with Id = "test" } - Expect.throws - (fun () -> - insert PostgresDb.TableName {emptyDoc with Id = "test" } - |> Async.AwaitTask - |> Async.RunSynchronously) - "An exception should have been raised for duplicate document ID insert" - } - ] - testList "save" [ - testTask "succeeds when a document is inserted" { - use db = PostgresDb.BuildDb() - let! before = Find.all PostgresDb.TableName - Expect.equal before [] "There should be no documents in the table" - - let testDoc = { emptyDoc with Id = "test"; Sub = Some { Foo = "a"; Bar = "b" } } - do! save PostgresDb.TableName testDoc - let! after = Find.all PostgresDb.TableName - Expect.equal after [ testDoc ] "There should have been one document inserted" - } - testTask "succeeds when a document is updated" { - use db = PostgresDb.BuildDb() - let testDoc = { emptyDoc with Id = "test"; Sub = Some { Foo = "a"; Bar = "b" } } - do! insert PostgresDb.TableName testDoc - - let! before = Find.byId PostgresDb.TableName "test" - Expect.isSome before "There should have been a document returned" - Expect.equal before.Value testDoc "The document is not correct" - - let upd8Doc = { testDoc with Sub = Some { Foo = "c"; Bar = "d" } } - do! save PostgresDb.TableName upd8Doc - let! after = Find.byId PostgresDb.TableName "test" - Expect.isSome after "There should have been a document returned post-update" - Expect.equal after.Value upd8Doc "The updated document is not correct" - } - ] - testList "Count" [ - testTask "all succeeds" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - let! theCount = Count.all PostgresDb.TableName - Expect.equal theCount 5 "There should have been 5 matching documents" - } - testList "byFields" [ - testTask "succeeds when items are found" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - let! theCount = - Count.byFields PostgresDb.TableName Any [ Field.BT "NumValue" 15 20; Field.EQ "NumValue" 0 ] - Expect.equal theCount 3 "There should have been 3 matching documents" - } - testTask "succeeds when items are not found" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - let! theCount = Count.byFields PostgresDb.TableName All [ Field.EX "Sub"; Field.GT "NumValue" 100 ] - Expect.equal theCount 0 "There should have been no matching documents" - } - ] - testTask "byContains succeeds" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - let! theCount = Count.byContains PostgresDb.TableName {| Value = "purple" |} - Expect.equal theCount 2 "There should have been 2 matching documents" - } - testTask "byJsonPath succeeds" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - let! theCount = Count.byJsonPath PostgresDb.TableName "$.NumValue ? (@ > 5)" - Expect.equal theCount 3 "There should have been 3 matching documents" - } - ] - testList "Exists" [ - testList "byId" [ - testTask "succeeds when a document exists" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - let! exists = Exists.byId PostgresDb.TableName "three" - Expect.isTrue exists "There should have been an existing document" - } - testTask "succeeds when a document does not exist" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - let! exists = Exists.byId PostgresDb.TableName "seven" - Expect.isFalse exists "There should not have been an existing document" - } - ] - testList "byFields" [ - testTask "succeeds when documents exist" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - let! exists = Exists.byFields PostgresDb.TableName Any [ Field.EX "Sub"; Field.EX "Boo" ] - Expect.isTrue exists "There should have been existing documents" - } - testTask "succeeds when documents do not exist" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - let! exists = - Exists.byFields PostgresDb.TableName All [ Field.EQ "NumValue" "six"; Field.EX "Nope" ] - Expect.isFalse exists "There should not have been existing documents" - } - ] - testList "byContains" [ - testTask "succeeds when documents exist" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - let! exists = Exists.byContains PostgresDb.TableName {| NumValue = 10 |} - Expect.isTrue exists "There should have been existing documents" - } - testTask "succeeds when no matching documents exist" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - let! exists = Exists.byContains PostgresDb.TableName {| Nothing = "none" |} - Expect.isFalse exists "There should not have been any existing documents" - } - ] - testList "byJsonPath" [ - testTask "succeeds when documents exist" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - let! exists = Exists.byJsonPath PostgresDb.TableName """$.Sub.Foo ? (@ == "green")""" - Expect.isTrue exists "There should have been existing documents" - } - testTask "succeeds when no matching documents exist" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - let! exists = Exists.byJsonPath PostgresDb.TableName "$.NumValue ? (@ > 1000)" - Expect.isFalse exists "There should not have been any existing documents" - } - ] - ] - testList "Find" [ - testList "all" [ - testTask "succeeds when there is data" { - use db = PostgresDb.BuildDb() - - do! insert PostgresDb.TableName { Foo = "one"; Bar = "two" } - do! insert PostgresDb.TableName { Foo = "three"; Bar = "four" } - do! insert PostgresDb.TableName { Foo = "five"; Bar = "six" } - - let! results = Find.all PostgresDb.TableName - let expected = [ - { Foo = "one"; Bar = "two" } - { Foo = "three"; Bar = "four" } - { Foo = "five"; Bar = "six" } - ] - Expect.equal results expected "There should have been 3 documents returned" - } - testTask "succeeds when there is no data" { - use db = PostgresDb.BuildDb() - let! results = Find.all PostgresDb.TableName - Expect.equal results [] "There should have been no documents returned" - } - ] - ftestList "allOrdered" [ - testTask "succeeds when ordering numerically" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - let! results = Find.allOrdered PostgresDb.TableName [ Field.Named "n:NumValue" ] - Expect.hasLength results 5 "There should have been 5 documents returned" - Expect.equal - (results |> List.map _.Id |> String.concat "|") - "one|three|two|four|five" - "The documents were not ordered correctly" - } - testTask "succeeds when ordering alphabetically" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - let! results = Find.allOrdered PostgresDb.TableName [ Field.Named "Id DESC" ] - Expect.hasLength results 5 "There should have been 5 documents returned" - Expect.equal - (results |> List.map _.Id |> String.concat "|") - "two|three|one|four|five" - "The documents were not ordered correctly" - } - ] - testList "byId" [ - testTask "succeeds when a document is found" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - let! doc = Find.byId PostgresDb.TableName "two" - Expect.isSome doc "There should have been a document returned" - Expect.equal doc.Value.Id "two" "The incorrect document was returned" - } - testTask "succeeds when a document is not found" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - let! doc = Find.byId PostgresDb.TableName "three hundred eighty-seven" - Expect.isNone doc "There should not have been a document returned" - } - ] - testList "byFields" [ - testTask "succeeds when documents are found" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - let! docs = - Find.byFields - PostgresDb.TableName All [ Field.EQ "Value" "purple"; Field.EX "Sub" ] - Expect.equal (List.length docs) 1 "There should have been one document returned" - } - testTask "succeeds when documents are not found" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - let! docs = - Find.byFields - PostgresDb.TableName All [ Field.EQ "Value" "mauve"; Field.NE "NumValue" 40 ] - Expect.isEmpty docs "There should have been no documents returned" - } - ] - testList "byContains" [ - testTask "succeeds when documents are found" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - let! docs = Find.byContains PostgresDb.TableName {| Sub = {| Foo = "green" |} |} - Expect.equal (List.length docs) 2 "There should have been two documents returned" - } - testTask "succeeds when documents are not found" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - let! docs = Find.byContains PostgresDb.TableName {| Value = "mauve" |} - Expect.isEmpty docs "There should have been no documents returned" - } - ] - testList "byJsonPath" [ - testTask "succeeds when documents are found" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - let! docs = Find.byJsonPath PostgresDb.TableName "$.NumValue ? (@ < 15)" - Expect.equal (List.length docs) 3 "There should have been 3 documents returned" - } - testTask "succeeds when documents are not found" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - let! docs = Find.byJsonPath PostgresDb.TableName "$.NumValue ? (@ < 0)" - Expect.isEmpty docs "There should have been no documents returned" - } - ] - testList "firstByFields" [ - testTask "succeeds when a document is found" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - let! doc = Find.firstByFields PostgresDb.TableName Any [ Field.EQ "Value" "another" ] - Expect.isSome doc "There should have been a document returned" - Expect.equal doc.Value.Id "two" "The incorrect document was returned" - } - testTask "succeeds when multiple documents are found" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - let! doc = Find.firstByFields PostgresDb.TableName Any [ Field.EQ "Value" "purple" ] - Expect.isSome doc "There should have been a document returned" - Expect.contains [ "five"; "four" ] doc.Value.Id "An incorrect document was returned" - } - testTask "succeeds when a document is not found" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - let! doc = Find.firstByFields PostgresDb.TableName Any [ Field.EQ "Value" "absent" ] - Expect.isNone doc "There should not have been a document returned" - } - ] - testList "firstByContains" [ - testTask "succeeds when a document is found" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - let! doc = Find.firstByContains PostgresDb.TableName {| Value = "another" |} - Expect.isSome doc "There should have been a document returned" - Expect.equal doc.Value.Id "two" "The incorrect document was returned" - } - testTask "succeeds when multiple documents are found" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - let! doc = Find.firstByContains PostgresDb.TableName {| Sub = {| Foo = "green" |} |} - Expect.isSome doc "There should have been a document returned" - Expect.contains [ "two"; "four" ] doc.Value.Id "An incorrect document was returned" - } - testTask "succeeds when a document is not found" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - let! doc = Find.firstByContains PostgresDb.TableName {| Value = "absent" |} - Expect.isNone doc "There should not have been a document returned" - } - ] - testList "firstByJsonPath" [ - testTask "succeeds when a document is found" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - let! doc = Find.firstByJsonPath PostgresDb.TableName """$.Value ? (@ == "FIRST!")""" - Expect.isSome doc "There should have been a document returned" - Expect.equal doc.Value.Id "one" "The incorrect document was returned" - } - testTask "succeeds when multiple documents are found" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - let! doc = Find.firstByJsonPath PostgresDb.TableName """$.Sub.Foo ? (@ == "green")""" - Expect.isSome doc "There should have been a document returned" - Expect.contains [ "two"; "four" ] doc.Value.Id "An incorrect document was returned" - } - testTask "succeeds when a document is not found" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - let! doc = Find.firstByJsonPath PostgresDb.TableName """$.Id ? (@ == "nope")""" - Expect.isNone doc "There should not have been a document returned" - } - ] - ] - testList "Update" [ - testList "byId" [ - testTask "succeeds when a document is updated" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - let testDoc = { emptyDoc with Id = "one"; Sub = Some { Foo = "blue"; Bar = "red" } } - do! Update.byId PostgresDb.TableName "one" testDoc - let! after = Find.byId PostgresDb.TableName "one" - Expect.isSome after "There should have been a document returned post-update" - Expect.equal after.Value testDoc "The updated document is not correct" - } - testTask "succeeds when no document is updated" { - use db = PostgresDb.BuildDb() - - let! before = Count.all PostgresDb.TableName - Expect.equal before 0 "There should have been no documents returned" - - // This not raising an exception is the test - do! Update.byId - PostgresDb.TableName - "test" - { emptyDoc with Id = "x"; Sub = Some { Foo = "blue"; Bar = "red" } } - } - ] - testList "byFunc" [ - testTask "succeeds when a document is updated" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - do! Update.byFunc PostgresDb.TableName (_.Id) - { Id = "one"; Value = "le un"; NumValue = 1; Sub = None } - let! after = Find.byId PostgresDb.TableName "one" - Expect.isSome after "There should have been a document returned post-update" - Expect.equal - after.Value - { Id = "one"; Value = "le un"; NumValue = 1; Sub = None } - "The updated document is not correct" - } - testTask "succeeds when no document is updated" { - use db = PostgresDb.BuildDb() - - let! before = Count.all PostgresDb.TableName - Expect.equal before 0 "There should have been no documents returned" - - // This not raising an exception is the test - do! Update.byFunc - PostgresDb.TableName (_.Id) { Id = "one"; Value = "le un"; NumValue = 1; Sub = None } - } - ] - ] - testList "Patch" [ - testList "byId" [ - testTask "succeeds when a document is updated" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - do! Patch.byId PostgresDb.TableName "one" {| NumValue = 44 |} - let! after = Find.byId PostgresDb.TableName "one" - Expect.isSome after "There should have been a document returned post-update" - Expect.equal after.Value.NumValue 44 "The updated document is not correct" - } - testTask "succeeds when no document is updated" { - use db = PostgresDb.BuildDb() - - let! before = Count.all PostgresDb.TableName - Expect.equal before 0 "There should have been no documents returned" - - // This not raising an exception is the test - do! Patch.byId PostgresDb.TableName "test" {| Foo = "green" |} - } - ] - testList "byFields" [ - testTask "succeeds when a document is updated" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - do! Patch.byFields PostgresDb.TableName Any [ Field.EQ "Value" "purple" ] {| NumValue = 77 |} - let! after = Count.byFields PostgresDb.TableName Any [ Field.EQ "NumValue" 77 ] - Expect.equal after 2 "There should have been 2 documents returned" - } - testTask "succeeds when no document is updated" { - use db = PostgresDb.BuildDb() - - let! before = Count.all PostgresDb.TableName - Expect.equal before 0 "There should have been no documents returned" - - // This not raising an exception is the test - do! Patch.byFields PostgresDb.TableName Any [ Field.EQ "Value" "burgundy" ] {| Foo = "green" |} - } - ] - testList "byContains" [ - testTask "succeeds when a document is updated" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - do! Patch.byContains PostgresDb.TableName {| Value = "purple" |} {| NumValue = 77 |} - let! after = Count.byContains PostgresDb.TableName {| NumValue = 77 |} - Expect.equal after 2 "There should have been 2 documents returned" - } - testTask "succeeds when no document is updated" { - use db = PostgresDb.BuildDb() - - let! before = Count.all PostgresDb.TableName - Expect.equal before 0 "There should have been no documents returned" - - // This not raising an exception is the test - do! Patch.byContains PostgresDb.TableName {| Value = "burgundy" |} {| Foo = "green" |} - } - ] - testList "byJsonPath" [ - testTask "succeeds when a document is updated" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - do! Patch.byJsonPath PostgresDb.TableName "$.NumValue ? (@ > 10)" {| NumValue = 1000 |} - let! after = Count.byJsonPath PostgresDb.TableName "$.NumValue ? (@ > 999)" - Expect.equal after 2 "There should have been 2 documents returned" - } - testTask "succeeds when no document is updated" { - use db = PostgresDb.BuildDb() - - let! before = Count.all PostgresDb.TableName - Expect.equal before 0 "There should have been no documents returned" - - // This not raising an exception is the test - do! Patch.byJsonPath PostgresDb.TableName "$.NumValue ? (@ < 0)" {| Foo = "green" |} - } - ] - ] - testList "RemoveFields" [ - testList "byId" [ - testTask "succeeds when multiple fields are removed" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - do! RemoveFields.byId PostgresDb.TableName "two" [ "Sub"; "Value" ] - let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NEX "Sub" ] - Expect.equal noSubs 4 "There should now be 4 documents without Sub fields" - let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NEX "Value" ] - Expect.equal noValue 1 "There should be 1 document without Value fields" - } - testTask "succeeds when a single field is removed" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - do! RemoveFields.byId PostgresDb.TableName "two" [ "Sub" ] - let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NEX "Sub" ] - Expect.equal noSubs 4 "There should now be 4 documents without Sub fields" - let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NEX "Value" ] - Expect.equal noValue 0 "There should be no documents without Value fields" - } - testTask "succeeds when a field is not removed" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - // This not raising an exception is the test - do! RemoveFields.byId PostgresDb.TableName "two" [ "AFieldThatIsNotThere" ] - } - testTask "succeeds when no document is matched" { - use db = PostgresDb.BuildDb() - - // This not raising an exception is the test - do! RemoveFields.byId PostgresDb.TableName "two" [ "Value" ] - } - ] - testList "byFields" [ - testTask "succeeds when multiple fields are removed" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - do! RemoveFields.byFields PostgresDb.TableName Any [ Field.EQ "NumValue" "17" ] [ "Sub"; "Value" ] - let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NEX "Sub" ] - Expect.equal noSubs 4 "There should now be 4 documents without Sub fields" - let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NEX "Value" ] - Expect.equal noValue 1 "There should be 1 document without Value fields" - } - testTask "succeeds when a single field is removed" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - do! RemoveFields.byFields PostgresDb.TableName Any [ Field.EQ "NumValue" "17" ] [ "Sub" ] - let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NEX "Sub" ] - Expect.equal noSubs 4 "There should now be 4 documents without Sub fields" - let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NEX "Value" ] - Expect.equal noValue 0 "There should be no documents without Value fields" - } - testTask "succeeds when a field is not removed" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - // This not raising an exception is the test - do! RemoveFields.byFields PostgresDb.TableName Any [ Field.EQ "NumValue" "17" ] [ "Nothing" ] - } - testTask "succeeds when no document is matched" { - use db = PostgresDb.BuildDb() - - // This not raising an exception is the test - do! RemoveFields.byFields PostgresDb.TableName Any [ Field.NE "Abracadabra" "apple" ] [ "Value" ] - } - ] - testList "byContains" [ - testTask "succeeds when multiple fields are removed" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - do! RemoveFields.byContains PostgresDb.TableName {| NumValue = 17 |} [ "Sub"; "Value" ] - let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NEX "Sub" ] - Expect.equal noSubs 4 "There should now be 4 documents without Sub fields" - let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NEX "Value" ] - Expect.equal noValue 1 "There should be 1 document without Value fields" - } - testTask "succeeds when a single field is removed" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - do! RemoveFields.byContains PostgresDb.TableName {| NumValue = 17 |} [ "Sub" ] - let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NEX "Sub" ] - Expect.equal noSubs 4 "There should now be 4 documents without Sub fields" - let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NEX "Value" ] - Expect.equal noValue 0 "There should be no documents without Value fields" - } - testTask "succeeds when a field is not removed" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - // This not raising an exception is the test - do! RemoveFields.byContains PostgresDb.TableName {| NumValue = 17 |} [ "Nothing" ] - } - testTask "succeeds when no document is matched" { - use db = PostgresDb.BuildDb() - - // This not raising an exception is the test - do! RemoveFields.byContains PostgresDb.TableName {| Abracadabra = "apple" |} [ "Value" ] - } - ] - testList "byJsonPath" [ - testTask "succeeds when multiple fields are removed" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - do! RemoveFields.byJsonPath PostgresDb.TableName "$.NumValue ? (@ == 17)" [ "Sub"; "Value" ] - let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NEX "Sub" ] - Expect.equal noSubs 4 "There should now be 4 documents without Sub fields" - let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NEX "Value" ] - Expect.equal noValue 1 "There should be 1 document without Value fields" - } - testTask "succeeds when a single field is removed" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - do! RemoveFields.byJsonPath PostgresDb.TableName "$.NumValue ? (@ == 17)" [ "Sub" ] - let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NEX "Sub" ] - Expect.equal noSubs 4 "There should now be 4 documents without Sub fields" - let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NEX "Value" ] - Expect.equal noValue 0 "There should be no documents without Value fields" - } - testTask "succeeds when a field is not removed" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - // This not raising an exception is the test - do! RemoveFields.byJsonPath PostgresDb.TableName "$.NumValue ? (@ == 17)" [ "Nothing" ] - } - testTask "succeeds when no document is matched" { - use db = PostgresDb.BuildDb() - - // This not raising an exception is the test - do! RemoveFields.byJsonPath PostgresDb.TableName "$.Abracadabra ? (@ == \"apple\")" [ "Value" ] - } - ] - ] - testList "Delete" [ - testList "byId" [ - testTask "succeeds when a document is deleted" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - do! Delete.byId PostgresDb.TableName "four" - let! remaining = Count.all PostgresDb.TableName - Expect.equal remaining 4 "There should have been 4 documents remaining" - } - testTask "succeeds when a document is not deleted" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - do! Delete.byId PostgresDb.TableName "thirty" - let! remaining = Count.all PostgresDb.TableName - Expect.equal remaining 5 "There should have been 5 documents remaining" - } - ] - testList "byFields" [ - testTask "succeeds when documents are deleted" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - do! Delete.byFields PostgresDb.TableName Any [ Field.EQ "Value" "purple" ] - let! remaining = Count.all PostgresDb.TableName - Expect.equal remaining 3 "There should have been 3 documents remaining" - } - testTask "succeeds when documents are not deleted" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - do! Delete.byFields PostgresDb.TableName Any [ Field.EQ "Value" "crimson" ] - let! remaining = Count.all PostgresDb.TableName - Expect.equal remaining 5 "There should have been 5 documents remaining" - } - ] - testList "byContains" [ - testTask "succeeds when documents are deleted" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - do! Delete.byContains PostgresDb.TableName {| Value = "purple" |} - let! remaining = Count.all PostgresDb.TableName - Expect.equal remaining 3 "There should have been 3 documents remaining" - } - testTask "succeeds when documents are not deleted" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - do! Delete.byContains PostgresDb.TableName {| Value = "crimson" |} - let! remaining = Count.all PostgresDb.TableName - Expect.equal remaining 5 "There should have been 5 documents remaining" - } - ] - testList "byJsonPath" [ - testTask "succeeds when documents are deleted" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - do! Delete.byJsonPath PostgresDb.TableName """$.Sub.Foo ? (@ == "green")""" - let! remaining = Count.all PostgresDb.TableName - Expect.equal remaining 3 "There should have been 3 documents remaining" - } - testTask "succeeds when documents are not deleted" { - use db = PostgresDb.BuildDb() - do! loadDocs () - - do! Delete.byJsonPath PostgresDb.TableName "$.NumValue ? (@ > 100)" - let! remaining = Count.all PostgresDb.TableName - Expect.equal remaining 5 "There should have been 5 documents remaining" - } - ] - ] + test "dataSource returns configured data source" { + use db = ThrowawayDatabase.Create PostgresDb.ConnStr.Value + let source = PostgresDb.MkDataSource db.ConnectionString + Configuration.useDataSource source + + Expect.isTrue (obj.ReferenceEquals(source, Configuration.dataSource ())) "Data source should have been the same" + } +] + +/// Integration tests for the Custom module of the PostgreSQL library +let customTests = testList "Custom" [ + testList "list" [ + testTask "succeeds when data is found" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! docs = Custom.list (Query.selectFromTable PostgresDb.TableName) [] fromData + Expect.hasLength docs 5 "There should have been 5 documents returned" + } + testTask "succeeds when data is not found" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! docs = + Custom.list + $"SELECT data FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath" + [ "@path", Sql.string "$.NumValue ? (@ > 100)" ] + fromData + Expect.isEmpty docs "There should have been no documents returned" + } ] - |> testSequenced + testList "single" [ + testTask "succeeds when a row is found" { + use db = PostgresDb.BuildDb() + do! loadDocs () + let! doc = + Custom.single + $"SELECT data FROM {PostgresDb.TableName} WHERE data ->> 'Id' = @id" + [ "@id", Sql.string "one"] + fromData + Expect.isSome doc "There should have been a document returned" + Expect.equal doc.Value.Id "one" "The incorrect document was returned" + } + testTask "succeeds when a row is not found" { + use db = PostgresDb.BuildDb() + do! loadDocs () -let all = testList "Postgres" [ unitTests; integrationTests ] + let! doc = + Custom.single + $"SELECT data FROM {PostgresDb.TableName} WHERE data ->> 'Id' = @id" + [ "@id", Sql.string "eighty" ] + fromData + Expect.isNone doc "There should not have been a document returned" + } + ] + testList "nonQuery" [ + testTask "succeeds when operating on data" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + do! Custom.nonQuery $"DELETE FROM {PostgresDb.TableName}" [] + + let! remaining = Count.all PostgresDb.TableName + Expect.equal remaining 0 "There should be no documents remaining in the table" + } + testTask "succeeds when no data matches where clause" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + do! Custom.nonQuery + $"DELETE FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath" + [ "@path", Sql.string "$.NumValue ? (@ > 100)" ] + + let! remaining = Count.all PostgresDb.TableName + Expect.equal remaining 5 "There should be 5 documents remaining in the table" + } + ] + testTask "scalar succeeds" { + use db = PostgresDb.BuildDb() + let! nbr = Custom.scalar "SELECT 5 AS test_value" [] (fun row -> row.int "test_value") + Expect.equal nbr 5 "The query should have returned the number 5" + } +] + +/// Integration tests for the Definition module of the PostgreSQL library +let definitionTests = testList "Definition" [ + testTask "ensureTable succeeds" { + use db = PostgresDb.BuildDb() + let tableExists () = + Custom.scalar "SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'ensured') AS it" [] toExists + let keyExists () = + Custom.scalar + "SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'idx_ensured_key') AS it" [] toExists + + let! exists = tableExists () + let! alsoExists = keyExists () + Expect.isFalse exists "The table should not exist already" + Expect.isFalse alsoExists "The key index should not exist already" + + do! Definition.ensureTable "ensured" + let! exists' = tableExists () + let! alsoExists' = keyExists () + Expect.isTrue exists' "The table should now exist" + Expect.isTrue alsoExists' "The key index should now exist" + } + testTask "ensureDocumentIndex succeeds" { + use db = PostgresDb.BuildDb() + let indexExists () = + Custom.scalar + "SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'idx_ensured_document') AS it" [] toExists + + let! exists = indexExists () + Expect.isFalse exists "The index should not exist already" + + do! Definition.ensureTable "ensured" + do! Definition.ensureDocumentIndex "ensured" Optimized + let! exists' = indexExists () + Expect.isTrue exists' "The index should now exist" + } + testTask "ensureFieldIndex succeeds" { + use db = PostgresDb.BuildDb() + let indexExists () = + Custom.scalar "SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'idx_ensured_test') AS it" [] toExists + + let! exists = indexExists () + Expect.isFalse exists "The index should not exist already" + + do! Definition.ensureTable "ensured" + do! Definition.ensureFieldIndex "ensured" "test" [ "Id"; "Category" ] + let! exists' = indexExists () + Expect.isTrue exists' "The index should now exist" + } +] + +/// Integration tests for the (auto-opened) Document module of the PostgreSQL library +let documentTests = testList "Document" [ + testList "insert" [ + testTask "succeeds" { + use db = PostgresDb.BuildDb() + let! before = Find.all PostgresDb.TableName + Expect.equal before [] "There should be no documents in the table" + + let testDoc = { emptyDoc with Id = "turkey"; Sub = Some { Foo = "gobble"; Bar = "gobble" } } + do! insert PostgresDb.TableName testDoc + let! after = Find.all PostgresDb.TableName + Expect.equal after [ testDoc ] "There should have been one document inserted" + } + testTask "fails for duplicate key" { + use db = PostgresDb.BuildDb() + do! insert PostgresDb.TableName { emptyDoc with Id = "test" } + Expect.throws + (fun () -> + insert PostgresDb.TableName {emptyDoc with Id = "test" } + |> Async.AwaitTask + |> Async.RunSynchronously) + "An exception should have been raised for duplicate document ID insert" + } + ] + testList "save" [ + testTask "succeeds when a document is inserted" { + use db = PostgresDb.BuildDb() + let! before = Find.all PostgresDb.TableName + Expect.equal before [] "There should be no documents in the table" + + let testDoc = { emptyDoc with Id = "test"; Sub = Some { Foo = "a"; Bar = "b" } } + do! save PostgresDb.TableName testDoc + let! after = Find.all PostgresDb.TableName + Expect.equal after [ testDoc ] "There should have been one document inserted" + } + testTask "succeeds when a document is updated" { + use db = PostgresDb.BuildDb() + let testDoc = { emptyDoc with Id = "test"; Sub = Some { Foo = "a"; Bar = "b" } } + do! insert PostgresDb.TableName testDoc + + let! before = Find.byId PostgresDb.TableName "test" + Expect.isSome before "There should have been a document returned" + Expect.equal before.Value testDoc "The document is not correct" + + let upd8Doc = { testDoc with Sub = Some { Foo = "c"; Bar = "d" } } + do! save PostgresDb.TableName upd8Doc + let! after = Find.byId PostgresDb.TableName "test" + Expect.isSome after "There should have been a document returned post-update" + Expect.equal after.Value upd8Doc "The updated document is not correct" + } + ] +] + +/// Integration tests for the Count module of the PostgreSQL library +let countTests = testList "Count" [ + testTask "all succeeds" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! theCount = Count.all PostgresDb.TableName + Expect.equal theCount 5 "There should have been 5 matching documents" + } + testList "byFields" [ + testTask "succeeds when items are found" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! theCount = Count.byFields PostgresDb.TableName Any [ Field.BT "NumValue" 15 20; Field.EQ "NumValue" 0 ] + Expect.equal theCount 3 "There should have been 3 matching documents" + } + testTask "succeeds when items are not found" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! theCount = Count.byFields PostgresDb.TableName All [ Field.EX "Sub"; Field.GT "NumValue" 100 ] + Expect.equal theCount 0 "There should have been no matching documents" + } + ] + testTask "byContains succeeds" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! theCount = Count.byContains PostgresDb.TableName {| Value = "purple" |} + Expect.equal theCount 2 "There should have been 2 matching documents" + } + testTask "byJsonPath succeeds" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! theCount = Count.byJsonPath PostgresDb.TableName "$.NumValue ? (@ > 5)" + Expect.equal theCount 3 "There should have been 3 matching documents" + } +] + +/// Integration tests for the Exists module of the PostgreSQL library +let existsTests = testList "Exists" [ + testList "byId" [ + testTask "succeeds when a document exists" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! exists = Exists.byId PostgresDb.TableName "three" + Expect.isTrue exists "There should have been an existing document" + } + testTask "succeeds when a document does not exist" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! exists = Exists.byId PostgresDb.TableName "seven" + Expect.isFalse exists "There should not have been an existing document" + } + ] + testList "byFields" [ + testTask "succeeds when documents exist" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! exists = Exists.byFields PostgresDb.TableName Any [ Field.EX "Sub"; Field.EX "Boo" ] + Expect.isTrue exists "There should have been existing documents" + } + testTask "succeeds when documents do not exist" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! exists = Exists.byFields PostgresDb.TableName All [ Field.EQ "NumValue" "six"; Field.EX "Nope" ] + Expect.isFalse exists "There should not have been existing documents" + } + ] + testList "byContains" [ + testTask "succeeds when documents exist" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! exists = Exists.byContains PostgresDb.TableName {| NumValue = 10 |} + Expect.isTrue exists "There should have been existing documents" + } + testTask "succeeds when no matching documents exist" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! exists = Exists.byContains PostgresDb.TableName {| Nothing = "none" |} + Expect.isFalse exists "There should not have been any existing documents" + } + ] + testList "byJsonPath" [ + testTask "succeeds when documents exist" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! exists = Exists.byJsonPath PostgresDb.TableName """$.Sub.Foo ? (@ == "green")""" + Expect.isTrue exists "There should have been existing documents" + } + testTask "succeeds when no matching documents exist" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! exists = Exists.byJsonPath PostgresDb.TableName "$.NumValue ? (@ > 1000)" + Expect.isFalse exists "There should not have been any existing documents" + } + ] +] + +/// Integration tests for the Find module of the PostgreSQL library +let findTests = testList "Find" [ + testList "all" [ + testTask "succeeds when there is data" { + use db = PostgresDb.BuildDb() + + do! insert PostgresDb.TableName { Foo = "one"; Bar = "two" } + do! insert PostgresDb.TableName { Foo = "three"; Bar = "four" } + do! insert PostgresDb.TableName { Foo = "five"; Bar = "six" } + + let! results = Find.all PostgresDb.TableName + Expect.equal + results + [ { Foo = "one"; Bar = "two" }; { Foo = "three"; Bar = "four" }; { Foo = "five"; Bar = "six" } ] + "There should have been 3 documents returned" + } + testTask "succeeds when there is no data" { + use db = PostgresDb.BuildDb() + let! results = Find.all PostgresDb.TableName + Expect.equal results [] "There should have been no documents returned" + } + ] + testList "allOrdered" [ + testTask "succeeds when ordering numerically" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! results = Find.allOrdered PostgresDb.TableName [ Field.Named "n:NumValue" ] + Expect.hasLength results 5 "There should have been 5 documents returned" + Expect.equal + (results |> List.map _.Id |> String.concat "|") + "one|three|two|four|five" + "The documents were not ordered correctly" + } + testTask "succeeds when ordering numerically descending" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! results = Find.allOrdered PostgresDb.TableName [ Field.Named "n:NumValue DESC" ] + Expect.hasLength results 5 "There should have been 5 documents returned" + Expect.equal + (results |> List.map _.Id |> String.concat "|") + "five|four|two|three|one" + "The documents were not ordered correctly" + } + testTask "succeeds when ordering alphabetically" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! results = Find.allOrdered PostgresDb.TableName [ Field.Named "Id DESC" ] + Expect.hasLength results 5 "There should have been 5 documents returned" + Expect.equal + (results |> List.map _.Id |> String.concat "|") + "two|three|one|four|five" + "The documents were not ordered correctly" + } + ] + testList "byId" [ + testTask "succeeds when a document is found" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! doc = Find.byId PostgresDb.TableName "two" + Expect.isSome doc "There should have been a document returned" + Expect.equal doc.Value.Id "two" "The incorrect document was returned" + } + testTask "succeeds when a document is not found" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! doc = Find.byId PostgresDb.TableName "three hundred eighty-seven" + Expect.isNone doc "There should not have been a document returned" + } + ] + testList "byFields" [ + testTask "succeeds when documents are found" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! docs = + Find.byFields PostgresDb.TableName All [ Field.EQ "Value" "purple"; Field.EX "Sub" ] + Expect.equal (List.length docs) 1 "There should have been one document returned" + } + testTask "succeeds when documents are not found" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! docs = + Find.byFields + PostgresDb.TableName All [ Field.EQ "Value" "mauve"; Field.NE "NumValue" 40 ] + Expect.isEmpty docs "There should have been no documents returned" + } + ] + testList "byFieldsOrdered" [ + testTask "succeeds when sorting ascending" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! docs = + Find.byFieldsOrdered + PostgresDb.TableName All [ Field.EQ "Value" "purple" ] [ Field.Named "Id" ] + Expect.hasLength docs 2 "There should have been two documents returned" + Expect.equal + (docs |> List.map _.Id |> String.concat "|") "five|four" "Documents not ordered correctly" + } + testTask "succeeds when sorting descending" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! docs = + Find.byFieldsOrdered + PostgresDb.TableName All [ Field.EQ "Value" "purple" ] [ Field.Named "Id DESC" ] + Expect.hasLength docs 2 "There should have been two documents returned" + Expect.equal + (docs |> List.map _.Id |> String.concat "|") "four|five" "Documents not ordered correctly" + } + ] + testList "byContains" [ + testTask "succeeds when documents are found" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! docs = Find.byContains PostgresDb.TableName {| Sub = {| Foo = "green" |} |} + Expect.equal (List.length docs) 2 "There should have been two documents returned" + } + testTask "succeeds when documents are not found" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! docs = Find.byContains PostgresDb.TableName {| Value = "mauve" |} + Expect.isEmpty docs "There should have been no documents returned" + } + ] + testList "byContainsOrdered" [ + // Id = two, Sub.Bar = blue; Id = four, Sub.Bar = red + testTask "succeeds when sorting ascending" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! docs = + Find.byContainsOrdered + PostgresDb.TableName {| Sub = {| Foo = "green" |} |} [ Field.Named "Sub.Bar" ] + Expect.hasLength docs 2 "There should have been two documents returned" + Expect.equal + (docs |> List.map _.Id |> String.concat "|") "two|four" "Documents not ordered correctly" + } + testTask "succeeds when sorting descending" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! docs = + Find.byContainsOrdered + PostgresDb.TableName {| Sub = {| Foo = "green" |} |} [ Field.Named "Sub.Bar DESC" ] + Expect.hasLength docs 2 "There should have been two documents returned" + Expect.equal + (docs |> List.map _.Id |> String.concat "|") "four|two" "Documents not ordered correctly" + } + ] + testList "byJsonPath" [ + testTask "succeeds when documents are found" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! docs = Find.byJsonPath PostgresDb.TableName "$.NumValue ? (@ < 15)" + Expect.equal (List.length docs) 3 "There should have been 3 documents returned" + } + testTask "succeeds when documents are not found" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! docs = Find.byJsonPath PostgresDb.TableName "$.NumValue ? (@ < 0)" + Expect.isEmpty docs "There should have been no documents returned" + } + ] + testList "byJsonPathOrdered" [ + // Id = one, NumValue = 0; Id = two, NumValue = 10; Id = three, NumValue = 4 + testTask "succeeds when sorting ascending" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! docs = + Find.byJsonPathOrdered + PostgresDb.TableName "$.NumValue ? (@ < 15)" [ Field.Named "n:NumValue" ] + Expect.hasLength docs 3 "There should have been 3 documents returned" + Expect.equal + (docs |> List.map _.Id |> String.concat "|") "one|three|two" "Documents not ordered correctly" + } + testTask "succeeds when sorting descending" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! docs = + Find.byJsonPathOrdered + PostgresDb.TableName "$.NumValue ? (@ < 15)" [ Field.Named "n:NumValue DESC" ] + Expect.hasLength docs 3 "There should have been 3 documents returned" + Expect.equal + (docs |> List.map _.Id |> String.concat "|") "two|three|one" "Documents not ordered correctly" + } + ] + testList "firstByFields" [ + testTask "succeeds when a document is found" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! doc = Find.firstByFields PostgresDb.TableName Any [ Field.EQ "Value" "another" ] + Expect.isSome doc "There should have been a document returned" + Expect.equal doc.Value.Id "two" "The incorrect document was returned" + } + testTask "succeeds when multiple documents are found" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! doc = Find.firstByFields PostgresDb.TableName Any [ Field.EQ "Value" "purple" ] + Expect.isSome doc "There should have been a document returned" + Expect.contains [ "five"; "four" ] doc.Value.Id "An incorrect document was returned" + } + testTask "succeeds when a document is not found" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! doc = Find.firstByFields PostgresDb.TableName Any [ Field.EQ "Value" "absent" ] + Expect.isNone doc "There should not have been a document returned" + } + ] + testList "firstByFieldsOrdered" [ + testTask "succeeds when sorting ascending" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! doc = + Find.firstByFieldsOrdered + PostgresDb.TableName Any [ Field.EQ "Value" "purple" ] [ Field.Named "Id" ] + Expect.isSome doc "There should have been a document returned" + Expect.equal "five" doc.Value.Id "An incorrect document was returned" + } + testTask "succeeds when sorting descending" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! doc = + Find.firstByFieldsOrdered + PostgresDb.TableName Any [ Field.EQ "Value" "purple" ] [ Field.Named "Id DESC" ] + Expect.isSome doc "There should have been a document returned" + Expect.equal "four" doc.Value.Id "An incorrect document was returned" + } + ] + testList "firstByContains" [ + testTask "succeeds when a document is found" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! doc = Find.firstByContains PostgresDb.TableName {| Value = "another" |} + Expect.isSome doc "There should have been a document returned" + Expect.equal doc.Value.Id "two" "The incorrect document was returned" + } + testTask "succeeds when multiple documents are found" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! doc = Find.firstByContains PostgresDb.TableName {| Sub = {| Foo = "green" |} |} + Expect.isSome doc "There should have been a document returned" + Expect.contains [ "two"; "four" ] doc.Value.Id "An incorrect document was returned" + } + testTask "succeeds when a document is not found" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! doc = Find.firstByContains PostgresDb.TableName {| Value = "absent" |} + Expect.isNone doc "There should not have been a document returned" + } + ] + testList "firstByJsonPath" [ + testTask "succeeds when a document is found" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! doc = Find.firstByJsonPath PostgresDb.TableName """$.Value ? (@ == "FIRST!")""" + Expect.isSome doc "There should have been a document returned" + Expect.equal doc.Value.Id "one" "The incorrect document was returned" + } + testTask "succeeds when multiple documents are found" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! doc = Find.firstByJsonPath PostgresDb.TableName """$.Sub.Foo ? (@ == "green")""" + Expect.isSome doc "There should have been a document returned" + Expect.contains [ "two"; "four" ] doc.Value.Id "An incorrect document was returned" + } + testTask "succeeds when a document is not found" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! doc = Find.firstByJsonPath PostgresDb.TableName """$.Id ? (@ == "nope")""" + Expect.isNone doc "There should not have been a document returned" + } + ] +] + +/// Integration tests for the Update module of the PostegreSQL library +let updateTests = testList "Update" [ + testList "byId" [ + testTask "succeeds when a document is updated" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let testDoc = { emptyDoc with Id = "one"; Sub = Some { Foo = "blue"; Bar = "red" } } + do! Update.byId PostgresDb.TableName "one" testDoc + let! after = Find.byId PostgresDb.TableName "one" + Expect.isSome after "There should have been a document returned post-update" + Expect.equal after.Value testDoc "The updated document is not correct" + } + testTask "succeeds when no document is updated" { + use db = PostgresDb.BuildDb() + + let! before = Count.all PostgresDb.TableName + Expect.equal before 0 "There should have been no documents returned" + + // This not raising an exception is the test + do! Update.byId + PostgresDb.TableName "test" { emptyDoc with Id = "x"; Sub = Some { Foo = "blue"; Bar = "red" } } + } + ] + testList "byFunc" [ + testTask "succeeds when a document is updated" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + do! Update.byFunc PostgresDb.TableName (_.Id) { Id = "one"; Value = "le un"; NumValue = 1; Sub = None } + let! after = Find.byId PostgresDb.TableName "one" + Expect.isSome after "There should have been a document returned post-update" + Expect.equal + after.Value + { Id = "one"; Value = "le un"; NumValue = 1; Sub = None } + "The updated document is not correct" + } + testTask "succeeds when no document is updated" { + use db = PostgresDb.BuildDb() + + let! before = Count.all PostgresDb.TableName + Expect.equal before 0 "There should have been no documents returned" + + // This not raising an exception is the test + do! Update.byFunc PostgresDb.TableName (_.Id) { Id = "one"; Value = "le un"; NumValue = 1; Sub = None } + } + ] +] + +/// Integration tests for the Patch module of the PostgreSQL library +let patchTests = testList "Patch" [ + testList "byId" [ + testTask "succeeds when a document is updated" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + do! Patch.byId PostgresDb.TableName "one" {| NumValue = 44 |} + let! after = Find.byId PostgresDb.TableName "one" + Expect.isSome after "There should have been a document returned post-update" + Expect.equal after.Value.NumValue 44 "The updated document is not correct" + } + testTask "succeeds when no document is updated" { + use db = PostgresDb.BuildDb() + + let! before = Count.all PostgresDb.TableName + Expect.equal before 0 "There should have been no documents returned" + + // This not raising an exception is the test + do! Patch.byId PostgresDb.TableName "test" {| Foo = "green" |} + } + ] + testList "byFields" [ + testTask "succeeds when a document is updated" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + do! Patch.byFields PostgresDb.TableName Any [ Field.EQ "Value" "purple" ] {| NumValue = 77 |} + let! after = Count.byFields PostgresDb.TableName Any [ Field.EQ "NumValue" 77 ] + Expect.equal after 2 "There should have been 2 documents returned" + } + testTask "succeeds when no document is updated" { + use db = PostgresDb.BuildDb() + + let! before = Count.all PostgresDb.TableName + Expect.equal before 0 "There should have been no documents returned" + + // This not raising an exception is the test + do! Patch.byFields PostgresDb.TableName Any [ Field.EQ "Value" "burgundy" ] {| Foo = "green" |} + } + ] + testList "byContains" [ + testTask "succeeds when a document is updated" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + do! Patch.byContains PostgresDb.TableName {| Value = "purple" |} {| NumValue = 77 |} + let! after = Count.byContains PostgresDb.TableName {| NumValue = 77 |} + Expect.equal after 2 "There should have been 2 documents returned" + } + testTask "succeeds when no document is updated" { + use db = PostgresDb.BuildDb() + + let! before = Count.all PostgresDb.TableName + Expect.equal before 0 "There should have been no documents returned" + + // This not raising an exception is the test + do! Patch.byContains PostgresDb.TableName {| Value = "burgundy" |} {| Foo = "green" |} + } + ] + testList "byJsonPath" [ + testTask "succeeds when a document is updated" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + do! Patch.byJsonPath PostgresDb.TableName "$.NumValue ? (@ > 10)" {| NumValue = 1000 |} + let! after = Count.byJsonPath PostgresDb.TableName "$.NumValue ? (@ > 999)" + Expect.equal after 2 "There should have been 2 documents returned" + } + testTask "succeeds when no document is updated" { + use db = PostgresDb.BuildDb() + + let! before = Count.all PostgresDb.TableName + Expect.equal before 0 "There should have been no documents returned" + + // This not raising an exception is the test + do! Patch.byJsonPath PostgresDb.TableName "$.NumValue ? (@ < 0)" {| Foo = "green" |} + } + ] +] + +/// Integration tests for the RemoveFields module of the PostgreSQL library +let removeFieldsTests = testList "RemoveFields" [ + testList "byId" [ + testTask "succeeds when multiple fields are removed" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + do! RemoveFields.byId PostgresDb.TableName "two" [ "Sub"; "Value" ] + let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NEX "Sub" ] + Expect.equal noSubs 4 "There should now be 4 documents without Sub fields" + let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NEX "Value" ] + Expect.equal noValue 1 "There should be 1 document without Value fields" + } + testTask "succeeds when a single field is removed" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + do! RemoveFields.byId PostgresDb.TableName "two" [ "Sub" ] + let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NEX "Sub" ] + Expect.equal noSubs 4 "There should now be 4 documents without Sub fields" + let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NEX "Value" ] + Expect.equal noValue 0 "There should be no documents without Value fields" + } + testTask "succeeds when a field is not removed" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + // This not raising an exception is the test + do! RemoveFields.byId PostgresDb.TableName "two" [ "AFieldThatIsNotThere" ] + } + testTask "succeeds when no document is matched" { + use db = PostgresDb.BuildDb() + + // This not raising an exception is the test + do! RemoveFields.byId PostgresDb.TableName "two" [ "Value" ] + } + ] + testList "byFields" [ + testTask "succeeds when multiple fields are removed" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + do! RemoveFields.byFields PostgresDb.TableName Any [ Field.EQ "NumValue" "17" ] [ "Sub"; "Value" ] + let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NEX "Sub" ] + Expect.equal noSubs 4 "There should now be 4 documents without Sub fields" + let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NEX "Value" ] + Expect.equal noValue 1 "There should be 1 document without Value fields" + } + testTask "succeeds when a single field is removed" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + do! RemoveFields.byFields PostgresDb.TableName Any [ Field.EQ "NumValue" "17" ] [ "Sub" ] + let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NEX "Sub" ] + Expect.equal noSubs 4 "There should now be 4 documents without Sub fields" + let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NEX "Value" ] + Expect.equal noValue 0 "There should be no documents without Value fields" + } + testTask "succeeds when a field is not removed" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + // This not raising an exception is the test + do! RemoveFields.byFields PostgresDb.TableName Any [ Field.EQ "NumValue" "17" ] [ "Nothing" ] + } + testTask "succeeds when no document is matched" { + use db = PostgresDb.BuildDb() + + // This not raising an exception is the test + do! RemoveFields.byFields PostgresDb.TableName Any [ Field.NE "Abracadabra" "apple" ] [ "Value" ] + } + ] + testList "byContains" [ + testTask "succeeds when multiple fields are removed" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + do! RemoveFields.byContains PostgresDb.TableName {| NumValue = 17 |} [ "Sub"; "Value" ] + let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NEX "Sub" ] + Expect.equal noSubs 4 "There should now be 4 documents without Sub fields" + let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NEX "Value" ] + Expect.equal noValue 1 "There should be 1 document without Value fields" + } + testTask "succeeds when a single field is removed" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + do! RemoveFields.byContains PostgresDb.TableName {| NumValue = 17 |} [ "Sub" ] + let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NEX "Sub" ] + Expect.equal noSubs 4 "There should now be 4 documents without Sub fields" + let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NEX "Value" ] + Expect.equal noValue 0 "There should be no documents without Value fields" + } + testTask "succeeds when a field is not removed" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + // This not raising an exception is the test + do! RemoveFields.byContains PostgresDb.TableName {| NumValue = 17 |} [ "Nothing" ] + } + testTask "succeeds when no document is matched" { + use db = PostgresDb.BuildDb() + + // This not raising an exception is the test + do! RemoveFields.byContains PostgresDb.TableName {| Abracadabra = "apple" |} [ "Value" ] + } + ] + testList "byJsonPath" [ + testTask "succeeds when multiple fields are removed" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + do! RemoveFields.byJsonPath PostgresDb.TableName "$.NumValue ? (@ == 17)" [ "Sub"; "Value" ] + let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NEX "Sub" ] + Expect.equal noSubs 4 "There should now be 4 documents without Sub fields" + let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NEX "Value" ] + Expect.equal noValue 1 "There should be 1 document without Value fields" + } + testTask "succeeds when a single field is removed" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + do! RemoveFields.byJsonPath PostgresDb.TableName "$.NumValue ? (@ == 17)" [ "Sub" ] + let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NEX "Sub" ] + Expect.equal noSubs 4 "There should now be 4 documents without Sub fields" + let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NEX "Value" ] + Expect.equal noValue 0 "There should be no documents without Value fields" + } + testTask "succeeds when a field is not removed" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + // This not raising an exception is the test + do! RemoveFields.byJsonPath PostgresDb.TableName "$.NumValue ? (@ == 17)" [ "Nothing" ] + } + testTask "succeeds when no document is matched" { + use db = PostgresDb.BuildDb() + + // This not raising an exception is the test + do! RemoveFields.byJsonPath PostgresDb.TableName "$.Abracadabra ? (@ == \"apple\")" [ "Value" ] + } + ] +] + +/// Integration tests for the Delete module of the PostgreSQL library +let deleteTests = testList "Delete" [ + testList "byId" [ + testTask "succeeds when a document is deleted" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + do! Delete.byId PostgresDb.TableName "four" + let! remaining = Count.all PostgresDb.TableName + Expect.equal remaining 4 "There should have been 4 documents remaining" + } + testTask "succeeds when a document is not deleted" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + do! Delete.byId PostgresDb.TableName "thirty" + let! remaining = Count.all PostgresDb.TableName + Expect.equal remaining 5 "There should have been 5 documents remaining" + } + ] + testList "byFields" [ + testTask "succeeds when documents are deleted" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + do! Delete.byFields PostgresDb.TableName Any [ Field.EQ "Value" "purple" ] + let! remaining = Count.all PostgresDb.TableName + Expect.equal remaining 3 "There should have been 3 documents remaining" + } + testTask "succeeds when documents are not deleted" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + do! Delete.byFields PostgresDb.TableName Any [ Field.EQ "Value" "crimson" ] + let! remaining = Count.all PostgresDb.TableName + Expect.equal remaining 5 "There should have been 5 documents remaining" + } + ] + testList "byContains" [ + testTask "succeeds when documents are deleted" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + do! Delete.byContains PostgresDb.TableName {| Value = "purple" |} + let! remaining = Count.all PostgresDb.TableName + Expect.equal remaining 3 "There should have been 3 documents remaining" + } + testTask "succeeds when documents are not deleted" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + do! Delete.byContains PostgresDb.TableName {| Value = "crimson" |} + let! remaining = Count.all PostgresDb.TableName + Expect.equal remaining 5 "There should have been 5 documents remaining" + } + ] + testList "byJsonPath" [ + testTask "succeeds when documents are deleted" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + do! Delete.byJsonPath PostgresDb.TableName """$.Sub.Foo ? (@ == "green")""" + let! remaining = Count.all PostgresDb.TableName + Expect.equal remaining 3 "There should have been 3 documents remaining" + } + testTask "succeeds when documents are not deleted" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + do! Delete.byJsonPath PostgresDb.TableName "$.NumValue ? (@ > 100)" + let! remaining = Count.all PostgresDb.TableName + Expect.equal remaining 5 "There should have been 5 documents remaining" + } + ] +] + +/// All tests for the PostgreSQL library +let all = testList "Postgres" [ + testList "Unit" [ parametersTests; queryTests ] + testSequenced <| testList "Integration" [ + configurationTests + customTests + definitionTests + documentTests + countTests + existsTests + findTests + updateTests + patchTests + removeFieldsTests + deleteTests + ] +]