BitBadger.Documents/src/Tests.CSharp/PostgresCSharpTests.cs
Daniel J. Summers 2c24e2e912 Version 4 rc1 (#6)
Changes in this version:
- **BREAKING CHANGE**: All `*byField`/`*ByField` functions are now `*byFields`/`*ByFields`, and take a `FieldMatch` case before the list of fields. The `Compat` namespace in both libraries will assist in this transition. In support of this change, the `Field` parameter name is optional; the library will generate parameter names for it if they are not specified.
- **BREAKING CHANGE**: The `Query` namespaces have had some significant work, particularly from the full-query perspective. Most have been broken up into the base query and modifiers `by*` that will combine the base query with the `WHERE` clause needed to satisfy the criteria.
- **FEATURE / BREAKING CHANGE**: PostgreSQL document fields will now be cast to numeric if the parameter value passed to the query is numeric. This drove the `Query` breaking changes, as the fields need to have their intended value for the library to generate the appropriate SQL. Additionally, if code assumes the library can be given something like `8` and transform it to `"8"`, this is no longer the case.
- **FEATURE**: All `Find` queries (except `byId`/`ById`) now have a version with the `Ordered` suffix. These take a list of fields by which the query should be ordered. A new `Field` method called `Named` can assist with creating these fields. Prefixing the field name with `n:` will cast the field to numeric in PostgreSQL (and will be ignored by SQLite); adding " DESC" to the field name will sort it descending (Z-A, high to low) instead of ascending (A-Z, low to high).
- **BREAKING CHANGE** (PostgreSQL only): `fieldNameParam`/`Parameters.FieldName` are now plural. The function still only generates one parameter, but the name is now the same between PostgreSQL and SQLite. The goal of this library is to abstract the differences away as much as practical, and this furthers that end. There are functions with these names in the `Compat` namespace.
- **FEATURE**: In the F# v3 library, lists of parameters were expected to be F#'s `List` type, and the C# version took either `List<T>` or `IEnumerable<T>`. In this version, these all expect `seq`/`IEnumerable<T>`. F#'s `List` satisfies the `seq` constraints, so this should not be a breaking change.
- **FEATURE**: `Field`s now may have qualifiers; this allows tables to be aliased when joining multiple tables (as all have the same `data` column). F# users can use `with` to specify this at creation, and both F# and C# can use the `WithQualifier` method to create a field with the qualifier specified. Parameter names for fields may be specified in a similar way, substituting `ParameterName` for `Qualifier`.

Reviewed-on: #6
2024-08-19 23:30:38 +00:00

1622 lines
76 KiB
C#

using Expecto.CSharp;
using Expecto;
using BitBadger.Documents.Postgres;
using ThrowawayDb.Postgres;
namespace BitBadger.Documents.Tests.CSharp;
using static CommonExtensionsAndTypesForNpgsqlFSharp;
using static Runner;
/// <summary>
/// C# tests for the PostgreSQL implementation of <tt>BitBadger.Documents</tt>
/// </summary>
public static class PostgresCSharpTests
{
/// <summary>
/// Unit tests for the Parameters module of the PostgreSQL library
/// </summary>
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");
}
})
]),
TestCase("None succeeds", () =>
{
Expect.isEmpty(Parameters.None, "The no-params sequence should be empty");
})
]);
/// <summary>
/// Unit tests for the Query module of the PostgreSQL library
/// </summary>
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");
})
]);
private static readonly List<JsonDocument> TestDocuments =
[
new() { Id = "one", Value = "FIRST!", NumValue = 0 },
new() { Id = "two", Value = "another", NumValue = 10, Sub = new() { Foo = "green", Bar = "blue" } },
new() { Id = "three", Value = "", NumValue = 4 },
new() { Id = "four", Value = "purple", NumValue = 17, Sub = new() { Foo = "green", Bar = "red" } },
new() { Id = "five", Value = "purple", NumValue = 18 }
];
/// <summary>
/// Add the test documents to the database
/// </summary>
internal static async Task LoadDocs()
{
foreach (var doc in TestDocuments) await Document.Insert(SqliteDb.TableName, doc);
}
/// <summary>
/// Integration tests for the Configuration module of the PostgreSQL library
/// </summary>
private static readonly Test ConfigurationTests = 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);
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");
})
]);
/// <summary>
/// Integration tests for the Custom module of the PostgreSQL library
/// </summary>
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<JsonDocument>);
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<JsonDocument>);
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<JsonDocument>);
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<JsonDocument>);
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");
})
]);
/// <summary>
/// Integration tests for the Definition module of the PostgreSQL library
/// </summary>
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<bool> TableExists() => Custom.Scalar(
"SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'ensured') AS it", Parameters.None,
Results.ToExists);
Task<bool> 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<bool> 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", ["Id", "Category"]);
exists = await IndexExists();
Expect.isTrue(exists, "The index should now exist");
return;
Task<bool> IndexExists() => Custom.Scalar(
"SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'idx_ensured_test') AS it", Parameters.None,
Results.ToExists);
})
]);
/// <summary>
/// Integration tests for the Document module of the PostgreSQL library
/// </summary>
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
{
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("succeeds when adding a numeric auto ID", async () =>
{
try
{
Configuration.UseAutoIdStrategy(AutoId.Number);
Configuration.UseIdField("Key");
await using var db = PostgresDb.BuildDb();
var before = await Count.All(PostgresDb.TableName);
Expect.equal(before, 0, "There should be no documents in the table");
await Document.Insert(PostgresDb.TableName, new NumIdDocument { Text = "one" });
await Document.Insert(PostgresDb.TableName, new NumIdDocument { Text = "two" });
await Document.Insert(PostgresDb.TableName, new NumIdDocument { Key = 77, Text = "three" });
await Document.Insert(PostgresDb.TableName, new NumIdDocument { Text = "four" });
var after = await Find.AllOrdered<NumIdDocument>(PostgresDb.TableName, [Field.Named("n:Key")]);
Expect.hasLength(after, 4, "There should have been 4 documents returned");
Expect.sequenceEqual(after.Select(x => x.Key), [1, 2, 77, 78],
"The IDs were not generated correctly");
}
finally
{
Configuration.UseAutoIdStrategy(AutoId.Disabled);
Configuration.UseIdField("Id");
}
}),
TestCase("succeeds when adding a GUID auto ID", async () =>
{
try
{
Configuration.UseAutoIdStrategy(AutoId.Guid);
await using var db = PostgresDb.BuildDb();
var before = await Count.All(PostgresDb.TableName);
Expect.equal(before, 0, "There should be no documents in the table");
await Document.Insert(PostgresDb.TableName, new JsonDocument { Value = "one" });
await Document.Insert(PostgresDb.TableName, new JsonDocument { Value = "two" });
await Document.Insert(PostgresDb.TableName, new JsonDocument { Id = "abc123", Value = "three" });
await Document.Insert(PostgresDb.TableName, new JsonDocument { Value = "four" });
var after = await Find.All<JsonDocument>(PostgresDb.TableName);
Expect.hasLength(after, 4, "There should have been 4 documents returned");
Expect.equal(after.Count(x => x.Id.Length == 32), 3, "Three of the IDs should have been GUIDs");
Expect.equal(after.Count(x => x.Id == "abc123"), 1, "The provided ID should have been used as-is");
}
finally
{
Configuration.UseAutoIdStrategy(AutoId.Disabled);
}
}),
TestCase("succeeds when adding a RandomString auto ID", async () =>
{
try
{
Configuration.UseAutoIdStrategy(AutoId.RandomString);
Configuration.UseIdStringLength(44);
await using var db = PostgresDb.BuildDb();
var before = await Count.All(PostgresDb.TableName);
Expect.equal(before, 0, "There should be no documents in the table");
await Document.Insert(PostgresDb.TableName, new JsonDocument { Value = "one" });
await Document.Insert(PostgresDb.TableName, new JsonDocument { Value = "two" });
await Document.Insert(PostgresDb.TableName, new JsonDocument { Id = "abc123", Value = "three" });
await Document.Insert(PostgresDb.TableName, new JsonDocument { Value = "four" });
var after = await Find.All<JsonDocument>(PostgresDb.TableName);
Expect.hasLength(after, 4, "There should have been 4 documents returned");
Expect.equal(after.Count(x => x.Id.Length == 44), 3,
"Three of the IDs should have been 44-character random strings");
Expect.equal(after.Count(x => x.Id == "abc123"), 1, "The provided ID should have been used as-is");
}
finally
{
Configuration.UseAutoIdStrategy(AutoId.Disabled);
Configuration.UseIdStringLength(16);
}
})
]),
TestList("Save",
[
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<string, JsonDocument>(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<string, JsonDocument>(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");
})
])
]);
/// <summary>
/// Integration tests for the Count module of the PostgreSQL library
/// </summary>
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");
})
]);
/// <summary>
/// Integration tests for the Exists module of the PostgreSQL library
/// </summary>
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");
})
])
]);
/// <summary>
/// Integration tests for the Find module of the PostgreSQL library
/// </summary>
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<SubDocument>(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<SubDocument>(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<JsonDocument>(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<JsonDocument>(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<JsonDocument>(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<string, JsonDocument>(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<string, JsonDocument>(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<JsonDocument>(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<JsonDocument>(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<JsonDocument>(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<JsonDocument>(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<JsonDocument>(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<JsonDocument>(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<JsonDocument>(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<JsonDocument>(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<JsonDocument>(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<JsonDocument>(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<JsonDocument>(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<JsonDocument>(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<JsonDocument>(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<JsonDocument>(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<JsonDocument>(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<JsonDocument>(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<JsonDocument>(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<JsonDocument>(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<JsonDocument>(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<JsonDocument>(PostgresDb.TableName, new { Value = "absent" });
Expect.isNull(doc, "There should not have been a document returned");
})
]),
TestList("FirstByContainsOrdered",
[
TestCase("succeeds when sorting ascending", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
var doc = await Find.FirstByContainsOrdered<JsonDocument>(PostgresDb.TableName,
new { Sub = new { Foo = "green" } }, [Field.Named("Value")]);
Expect.isNotNull(doc, "There should have been a document returned");
Expect.equal("two", doc.Id, "An incorrect document was returned");
}),
TestCase("succeeds when sorting descending", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
var doc = await Find.FirstByContainsOrdered<JsonDocument>(PostgresDb.TableName,
new { Sub = new { Foo = "green" } }, [Field.Named("Value DESC")]);
Expect.isNotNull(doc, "There should have been a document returned");
Expect.equal("four", doc.Id, "An incorrect document was returned");
})
]),
TestList("FirstByJsonPath",
[
TestCase("succeeds when a document is found", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
var doc = await Find.FirstByJsonPath<JsonDocument>(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<JsonDocument>(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<JsonDocument>(PostgresDb.TableName, "$.Id ? (@ == \"nope\")");
Expect.isNull(doc, "There should not have been a document returned");
})
]),
TestList("FirstByJsonPathOrdered",
[
TestCase("succeeds when sorting ascending", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
var doc = await Find.FirstByJsonPathOrdered<JsonDocument>(PostgresDb.TableName,
"$.Sub.Foo ? (@ == \"green\")", [Field.Named("Sub.Bar")]);
Expect.isNotNull(doc, "There should have been a document returned");
Expect.equal("two", doc.Id, "An incorrect document was returned");
}),
TestCase("succeeds when sorting descending", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
var doc = await Find.FirstByJsonPathOrdered<JsonDocument>(PostgresDb.TableName,
"$.Sub.Foo ? (@ == \"green\")", [Field.Named("Sub.Bar DESC")]);
Expect.isNotNull(doc, "There should have been a document returned");
Expect.equal("four", doc.Id, "An incorrect document was returned");
})
])
]);
/// <summary>
/// Integration tests for the Update module of the PostgreSQL library
/// </summary>
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<string, JsonDocument>(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<string, JsonDocument>(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 });
})
])
]);
/// <summary>
/// Integration tests for the Patch module of the PostgreSQL library
/// </summary>
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<string, JsonDocument>(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" });
})
])
]);
/// <summary>
/// Integration tests for the RemoveFields module of the PostgreSQL library
/// </summary>
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<string, JsonDocument>(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<string, JsonDocument>(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<string, JsonDocument>(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<string, JsonDocument>(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<string, JsonDocument>(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<string, JsonDocument>(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<string, JsonDocument>(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<string, JsonDocument>(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"]);
})
])
]);
/// <summary>
/// Integration tests for the Delete module of the PostgreSQL library
/// </summary>
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");
})
])
]);
/// <summary>
/// All Postgres C# tests
/// </summary>
[Tests]
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
]))
]);
}