Add Postgres C# unit tests

This commit is contained in:
Daniel J. Summers 2023-12-26 22:54:09 -05:00
parent a8b2927b2c
commit a1559ad29e
4 changed files with 200 additions and 0 deletions

View File

@ -92,10 +92,12 @@ module Query =
$"CREATE INDEX IF NOT EXISTS idx_{tableName}_document ON {name} USING GIN (data{extraOps})" $"CREATE INDEX IF NOT EXISTS idx_{tableName}_document ON {name} USING GIN (data{extraOps})"
/// Create a WHERE clause fragment to implement a @> (JSON contains) condition /// Create a WHERE clause fragment to implement a @> (JSON contains) condition
[<CompiledName "WhereDataContains">]
let whereDataContains paramName = let whereDataContains paramName =
$"data @> %s{paramName}" $"data @> %s{paramName}"
/// Create a WHERE clause fragment to implement a @? (JSON Path match) condition /// Create a WHERE clause fragment to implement a @? (JSON Path match) condition
[<CompiledName "WhereJsonPathMatches">]
let whereJsonPathMatches paramName = let whereJsonPathMatches paramName =
$"data @? %s{paramName}::jsonpath" $"data @? %s{paramName}::jsonpath"
@ -103,10 +105,12 @@ module Query =
module Count = module Count =
/// Query to count matching documents using a JSON containment query (@>) /// Query to count matching documents using a JSON containment query (@>)
[<CompiledName "ByContains">]
let byContains tableName = let byContains tableName =
$"""SELECT COUNT(*) AS it FROM %s{tableName} WHERE {whereDataContains "@criteria"}""" $"""SELECT COUNT(*) AS it FROM %s{tableName} WHERE {whereDataContains "@criteria"}"""
/// Query to count matching documents using a JSON Path match (@?) /// Query to count matching documents using a JSON Path match (@?)
[<CompiledName "ByJsonPath">]
let byJsonPath tableName = let byJsonPath tableName =
$"""SELECT COUNT(*) AS it FROM %s{tableName} WHERE {whereJsonPathMatches "@path"}""" $"""SELECT COUNT(*) AS it FROM %s{tableName} WHERE {whereJsonPathMatches "@path"}"""
@ -114,10 +118,12 @@ module Query =
module Exists = module Exists =
/// Query to determine if documents exist using a JSON containment query (@>) /// Query to determine if documents exist using a JSON containment query (@>)
[<CompiledName "ByContains">]
let byContains tableName = let byContains tableName =
$"""SELECT EXISTS (SELECT 1 FROM %s{tableName} WHERE {whereDataContains "@criteria"}) AS it""" $"""SELECT EXISTS (SELECT 1 FROM %s{tableName} WHERE {whereDataContains "@criteria"}) AS it"""
/// Query to determine if documents exist using a JSON Path match (@?) /// Query to determine if documents exist using a JSON Path match (@?)
[<CompiledName "ByJsonPath">]
let byJsonPath tableName = let byJsonPath tableName =
$"""SELECT EXISTS (SELECT 1 FROM %s{tableName} WHERE {whereJsonPathMatches "@path"}) AS it""" $"""SELECT EXISTS (SELECT 1 FROM %s{tableName} WHERE {whereJsonPathMatches "@path"}) AS it"""
@ -125,10 +131,12 @@ module Query =
module Find = module Find =
/// Query to retrieve documents using a JSON containment query (@>) /// Query to retrieve documents using a JSON containment query (@>)
[<CompiledName "ByContains">]
let byContains tableName = let byContains tableName =
$"""{Query.selectFromTable tableName} WHERE {whereDataContains "@criteria"}""" $"""{Query.selectFromTable tableName} WHERE {whereDataContains "@criteria"}"""
/// Query to retrieve documents using a JSON Path match (@?) /// Query to retrieve documents using a JSON Path match (@?)
[<CompiledName "ByJsonPath">]
let byJsonPath tableName = let byJsonPath tableName =
$"""{Query.selectFromTable tableName} WHERE {whereJsonPathMatches "@path"}""" $"""{Query.selectFromTable tableName} WHERE {whereJsonPathMatches "@path"}"""
@ -136,18 +144,22 @@ module Query =
module Update = module Update =
/// Query to update a document /// Query to update a document
[<CompiledName "PartialById">]
let partialById tableName = let partialById tableName =
$"""UPDATE %s{tableName} SET data = data || @data WHERE {Query.whereById "@id"}""" $"""UPDATE %s{tableName} SET data = data || @data WHERE {Query.whereById "@id"}"""
/// Query to update a document /// Query to update a document
[<CompiledName "PartialByField">]
let partialByField tableName fieldName op = let partialByField tableName fieldName op =
$"""UPDATE %s{tableName} SET data = data || @data WHERE {Query.whereByField fieldName op "@field"}""" $"""UPDATE %s{tableName} SET data = data || @data WHERE {Query.whereByField fieldName op "@field"}"""
/// Query to update partial documents matching a JSON containment query (@>) /// Query to update partial documents matching a JSON containment query (@>)
[<CompiledName "PartialByContains">]
let partialByContains tableName = let partialByContains tableName =
$"""UPDATE %s{tableName} SET data = data || @data WHERE {whereDataContains "@criteria"}""" $"""UPDATE %s{tableName} SET data = data || @data WHERE {whereDataContains "@criteria"}"""
/// Query to update partial documents matching a JSON containment query (@>) /// Query to update partial documents matching a JSON containment query (@>)
[<CompiledName "PartialByJsonPath">]
let partialByJsonPath tableName = let partialByJsonPath tableName =
$"""UPDATE %s{tableName} SET data = data || @data WHERE {whereJsonPathMatches "@path"}""" $"""UPDATE %s{tableName} SET data = data || @data WHERE {whereJsonPathMatches "@path"}"""
@ -155,10 +167,12 @@ module Query =
module Delete = module Delete =
/// Query to delete documents using a JSON containment query (@>) /// Query to delete documents using a JSON containment query (@>)
[<CompiledName "ByContains">]
let byContains tableName = let byContains tableName =
$"""DELETE FROM %s{tableName} WHERE {whereDataContains "@criteria"}""" $"""DELETE FROM %s{tableName} WHERE {whereDataContains "@criteria"}"""
/// Query to delete documents using a JSON Path match (@?) /// Query to delete documents using a JSON Path match (@?)
[<CompiledName "ByJsonPath">]
let byJsonPath tableName = let byJsonPath tableName =
$"""DELETE FROM %s{tableName} WHERE {whereJsonPathMatches "@path"}""" $"""DELETE FROM %s{tableName} WHERE {whereJsonPathMatches "@path"}"""
@ -168,18 +182,22 @@ module Query =
module Results = module Results =
/// Create a domain item from a document, specifying the field in which the document is found /// Create a domain item from a document, specifying the field in which the document is found
[<CompiledName "FromDocument">]
let fromDocument<'T> field (row: RowReader) : 'T = let fromDocument<'T> field (row: RowReader) : 'T =
Configuration.serializer().Deserialize<'T>(row.string field) Configuration.serializer().Deserialize<'T>(row.string field)
/// Create a domain item from a document /// Create a domain item from a document
[<CompiledName "FromData">]
let fromData<'T> row : 'T = let fromData<'T> row : 'T =
fromDocument "data" row fromDocument "data" row
/// Extract a count from the column "it" /// Extract a count from the column "it"
[<CompiledName "ToCount">]
let toCount (row: RowReader) = let toCount (row: RowReader) =
row.int "it" row.int "it"
/// Extract a true/false value from the column "it" /// Extract a true/false value from the column "it"
[<CompiledName "ToExists">]
let toExists (row: RowReader) = let toExists (row: RowReader) =
row.bool "it" row.bool "it"

View File

@ -0,0 +1,180 @@
using Expecto.CSharp;
using Expecto;
using BitBadger.Documents.Postgres;
using Npgsql.FSharp;
namespace BitBadger.Documents.Tests.CSharp;
using static Runner;
/// <summary>
/// C# tests for the PostgreSQL implementation of <tt>BitBadger.Documents</tt>
/// </summary>
public class PostgresCSharpTests
{
public static Test Unit =
TestList("Unit", new[]
{
TestList("Parameters", new[]
{
TestCase("Id succeeds", () => {
Expect.equal(Parameters.Id(88).Item1, "@id", "ID parameter not constructed correctly");
}),
TestCase("Json succeeds", () =>
{
Expect.equal(Parameters.Json("@test", new { Something = "good" }).Item1, "@test",
"JSON parameter not constructed correctly");
}),
TestCase("Field succeeds", () =>
{
Expect.equal(Parameters.Field(242).Item1, "@field", "Field parameter not constructed correctly");
}),
TestCase("None succeeds", () => {
Expect.isEmpty(Parameters.None, "The no-params sequence should be empty");
})
}),
TestList("Query", new[]
{
TestList("Definition", new[]
{
TestCase("EnsureTable succeeds", () =>
{
Expect.equal(Postgres.Query.Definition.EnsureTable(PostgresDb.TableName),
$"CREATE TABLE IF NOT EXISTS {PostgresDb.TableName} (data JSONB NOT NULL)",
"CREATE TABLE statement not constructed correctly");
}),
TestCase("EnsureJsonIndex succeeds for full index", () =>
{
Expect.equal(Postgres.Query.Definition.EnsureJsonIndex("schema.tbl", DocumentIndex.Full),
"CREATE INDEX IF NOT EXISTS idx_tbl_document ON schema.tbl USING GIN (data)",
"CREATE INDEX statement not constructed correctly");
}),
TestCase("EnsureJsonIndex succeeds for JSONB Path Ops index", () =>
{
Expect.equal(
Postgres.Query.Definition.EnsureJsonIndex(PostgresDb.TableName, DocumentIndex.Optimized),
string.Format(
"CREATE INDEX IF NOT EXISTS idx_{0}_document ON {0} USING GIN (data jsonb_path_ops)",
PostgresDb.TableName),
"CREATE INDEX statement not constructed correctly");
})
}),
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");
}),
TestList("Count", new[]
{
TestCase("ByContains succeeds", () =>
{
Expect.equal(Postgres.Query.Count.ByContains(PostgresDb.TableName),
$"SELECT COUNT(*) AS it FROM {PostgresDb.TableName} WHERE data @> @criteria",
"JSON containment count query not correct");
}),
TestCase("ByJsonPath succeeds", () =>
{
Expect.equal(Postgres.Query.Count.ByJsonPath(PostgresDb.TableName),
$"SELECT COUNT(*) AS it FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath",
"JSON Path match count query not correct");
})
}),
TestList("Exists", new[]
{
TestCase("ByContains succeeds", () =>
{
Expect.equal(Postgres.Query.Exists.ByContains(PostgresDb.TableName),
$"SELECT EXISTS (SELECT 1 FROM {PostgresDb.TableName} WHERE data @> @criteria) AS it",
"JSON containment exists query not correct");
}),
TestCase("byJsonPath succeeds", () =>
{
Expect.equal(Postgres.Query.Exists.ByJsonPath(PostgresDb.TableName),
$"SELECT EXISTS (SELECT 1 FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath) AS it",
"JSON Path match existence query not correct");
})
}),
TestList("Find", new[]
{
TestCase("byContains succeeds", () =>
{
Expect.equal(Postgres.Query.Find.ByContains(PostgresDb.TableName),
$"SELECT data FROM {PostgresDb.TableName} WHERE data @> @criteria",
"SELECT by JSON containment query not correct");
}),
TestCase("byJsonPath succeeds", () =>
{
Expect.equal(Postgres.Query.Find.ByJsonPath(PostgresDb.TableName),
$"SELECT data FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath",
"SELECT by JSON Path match query not correct");
})
}),
TestList("Update", new[]
{
TestCase("partialById succeeds", () =>
{
Expect.equal(Postgres.Query.Update.PartialById(PostgresDb.TableName),
$"UPDATE {PostgresDb.TableName} SET data = data || @data WHERE data ->> 'Id' = @id",
"UPDATE partial by ID statement not correct");
}),
TestCase("partialByField succeeds", () =>
{
Expect.equal(Postgres.Query.Update.PartialByField(PostgresDb.TableName, "Snail", Op.LT),
$"UPDATE {PostgresDb.TableName} SET data = data || @data WHERE data ->> 'Snail' < @field",
"UPDATE partial by ID statement not correct");
}),
TestCase("partialByContains succeeds", () =>
{
Expect.equal(Postgres.Query.Update.PartialByContains(PostgresDb.TableName),
$"UPDATE {PostgresDb.TableName} SET data = data || @data WHERE data @> @criteria",
"UPDATE partial by JSON containment statement not correct");
}),
TestCase("partialByJsonPath succeeds", () =>
{
Expect.equal(Postgres.Query.Update.PartialByJsonPath(PostgresDb.TableName),
$"UPDATE {PostgresDb.TableName} SET data = data || @data WHERE data @? @path::jsonpath",
"UPDATE partial by JSON Path statement not correct");
})
}),
TestList("Delete", new[]
{
TestCase("byContains succeeds", () =>
{
Expect.equal(Postgres.Query.Delete.ByContains(PostgresDb.TableName),
$"DELETE FROM {PostgresDb.TableName} WHERE data @> @criteria",
"DELETE by JSON containment query not correct");
}),
TestCase("byJsonPath succeeds", () =>
{
Expect.equal(Postgres.Query.Delete.ByJsonPath(PostgresDb.TableName),
$"DELETE FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath",
"DELETE by JSON Path match query not correct");
})
})
})
});
private static readonly List<JsonDocument> TestDocuments = new()
{
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 }
};
internal static async Task LoadDocs()
{
foreach (var doc in TestDocuments) await Document.Insert(SqliteDb.TableName, doc);
}
/// <summary>
/// All Postgres C# tests
/// </summary>
public static Test All = TestList("Postgres.C#", new[] { Unit });
}

View File

@ -16,6 +16,7 @@ public static class SqliteCSharpTests
/// <summary> /// <summary>
/// Unit tests for the SQLite library /// Unit tests for the SQLite library
/// </summary> /// </summary>
[Tests]
public static Test Unit = public static Test Unit =
TestList("Unit", new[] TestList("Unit", new[]
{ {

View File

@ -7,6 +7,7 @@ let allTests =
[ CommonTests.all [ CommonTests.all
CommonCSharpTests.Unit CommonCSharpTests.Unit
PostgresTests.all PostgresTests.all
PostgresCSharpTests.All
SqliteTests.all SqliteTests.all
testSequenced SqliteExtensionTests.integrationTests testSequenced SqliteExtensionTests.integrationTests
SqliteCSharpTests.All SqliteCSharpTests.All