v3 RC1 (#1)
This encompasses: - New behavior for SQLite - Migrated behavior for PostrgeSQL (from BitBadger.Npgsql.FSharp.Documents) - New "byField" behavior for PostgreSQL - A unification of C# and F# centric implementations
This commit was merged in pull request #1.
This commit is contained in:
19
src/Tests.CSharp/BitBadger.Documents.Tests.CSharp.csproj
Normal file
19
src/Tests.CSharp/BitBadger.Documents.Tests.CSharp.csproj
Normal file
@@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Common\BitBadger.Documents.Common.fsproj" />
|
||||
<ProjectReference Include="..\Postgres\BitBadger.Documents.Postgres.fsproj" />
|
||||
<ProjectReference Include="..\Sqlite\BitBadger.Documents.Sqlite.fsproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Expecto" Version="10.1.0" />
|
||||
<PackageReference Include="ThrowawayDb.Postgres" Version="1.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
238
src/Tests.CSharp/CommonCSharpTests.cs
Normal file
238
src/Tests.CSharp/CommonCSharpTests.cs
Normal file
@@ -0,0 +1,238 @@
|
||||
using Expecto.CSharp;
|
||||
using Expecto;
|
||||
|
||||
namespace BitBadger.Documents.Tests.CSharp;
|
||||
|
||||
using static Runner;
|
||||
|
||||
/// <summary>
|
||||
/// A test serializer that returns known values
|
||||
/// </summary>
|
||||
internal class TestSerializer : IDocumentSerializer
|
||||
{
|
||||
public string Serialize<T>(T it) => "{\"Overridden\":true}";
|
||||
public T Deserialize<T>(string it) => default!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// C# Tests for common functionality in <tt>BitBadger.Documents</tt>
|
||||
/// </summary>
|
||||
public static class CommonCSharpTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Unit tests
|
||||
/// </summary>
|
||||
[Tests]
|
||||
public static readonly Test Unit = TestList("Common.C# Unit", new[]
|
||||
{
|
||||
TestSequenced(
|
||||
TestList("Configuration", new[]
|
||||
{
|
||||
TestCase("UseSerializer succeeds", () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Configuration.UseSerializer(new TestSerializer());
|
||||
|
||||
var serialized = Configuration.Serializer().Serialize(new SubDocument
|
||||
{
|
||||
Foo = "howdy",
|
||||
Bar = "bye"
|
||||
});
|
||||
Expect.equal(serialized, "{\"Overridden\":true}", "Specified serializer was not used");
|
||||
|
||||
var deserialized = Configuration.Serializer()
|
||||
.Deserialize<object>("{\"Something\":\"here\"}");
|
||||
Expect.isNull(deserialized, "Specified serializer should have returned null");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Configuration.UseSerializer(DocumentSerializer.Default);
|
||||
}
|
||||
}),
|
||||
TestCase("Serializer returns configured serializer", () =>
|
||||
{
|
||||
Expect.isTrue(ReferenceEquals(DocumentSerializer.Default, Configuration.Serializer()),
|
||||
"Serializer should have been the same");
|
||||
}),
|
||||
TestCase("UseIdField / IdField succeeds", () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Expect.equal(Configuration.IdField(), "Id",
|
||||
"The default configured ID field was incorrect");
|
||||
Configuration.UseIdField("id");
|
||||
Expect.equal(Configuration.IdField(), "id", "UseIdField did not set the ID field");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Configuration.UseIdField("Id");
|
||||
}
|
||||
})
|
||||
})),
|
||||
TestList("Op", new[]
|
||||
{
|
||||
TestCase("EQ succeeds", () =>
|
||||
{
|
||||
Expect.equal(Op.EQ.ToString(), "=", "The equals operator was not correct");
|
||||
}),
|
||||
TestCase("GT succeeds", () =>
|
||||
{
|
||||
Expect.equal(Op.GT.ToString(), ">", "The greater than operator was not correct");
|
||||
}),
|
||||
TestCase("GE succeeds", () =>
|
||||
{
|
||||
Expect.equal(Op.GE.ToString(), ">=", "The greater than or equal to operator was not correct");
|
||||
}),
|
||||
TestCase("LT succeeds", () =>
|
||||
{
|
||||
Expect.equal(Op.LT.ToString(), "<", "The less than operator was not correct");
|
||||
}),
|
||||
TestCase("LE succeeds", () =>
|
||||
{
|
||||
Expect.equal(Op.LE.ToString(), "<=", "The less than or equal to operator was not correct");
|
||||
}),
|
||||
TestCase("NE succeeds", () =>
|
||||
{
|
||||
Expect.equal(Op.NE.ToString(), "<>", "The not equal to operator was not correct");
|
||||
}),
|
||||
TestCase("EX succeeds", () =>
|
||||
{
|
||||
Expect.equal(Op.EX.ToString(), "IS NOT NULL", "The \"exists\" operator was not correct");
|
||||
}),
|
||||
TestCase("NEX succeeds", () =>
|
||||
{
|
||||
Expect.equal(Op.NEX.ToString(), "IS NULL", "The \"not exists\" operator was not correct");
|
||||
})
|
||||
}),
|
||||
TestList("Query", new[]
|
||||
{
|
||||
TestCase("SelectFromTable succeeds", () =>
|
||||
{
|
||||
Expect.equal(Query.SelectFromTable("test.table"), "SELECT data FROM test.table",
|
||||
"SELECT statement not correct");
|
||||
}),
|
||||
TestCase("WhereById succeeds", () =>
|
||||
{
|
||||
Expect.equal(Query.WhereById("@id"), "data ->> 'Id' = @id", "WHERE clause not correct");
|
||||
}),
|
||||
TestList("WhereByField", new[]
|
||||
{
|
||||
TestCase("succeeds when a logical operator is passed", () =>
|
||||
{
|
||||
Expect.equal(Query.WhereByField("theField", Op.GT, "@test"), "data ->> 'theField' > @test",
|
||||
"WHERE clause not correct");
|
||||
}),
|
||||
TestCase("succeeds when an existence operator is passed", () =>
|
||||
{
|
||||
Expect.equal(Query.WhereByField("thatField", Op.NEX, ""), "data ->> 'thatField' IS NULL",
|
||||
"WHERE clause not correct");
|
||||
})
|
||||
}),
|
||||
TestList("Definition", new[]
|
||||
{
|
||||
TestCase("EnsureTableFor succeeds", () =>
|
||||
{
|
||||
Expect.equal(Query.Definition.EnsureTableFor("my.table", "JSONB"),
|
||||
"CREATE TABLE IF NOT EXISTS my.table (data JSONB NOT NULL)",
|
||||
"CREATE TABLE statement not constructed correctly");
|
||||
}),
|
||||
TestList("EnsureKey", new[]
|
||||
{
|
||||
TestCase("succeeds when a schema is present", () =>
|
||||
{
|
||||
Expect.equal(Query.Definition.EnsureKey("test.table"),
|
||||
"CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON test.table ((data ->> 'Id'))",
|
||||
"CREATE INDEX for key statement with schema not constructed correctly");
|
||||
}),
|
||||
TestCase("succeeds when a schema is not present", () =>
|
||||
{
|
||||
Expect.equal(Query.Definition.EnsureKey("table"),
|
||||
"CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON table ((data ->> 'Id'))",
|
||||
"CREATE INDEX for key statement without schema not constructed correctly");
|
||||
})
|
||||
}),
|
||||
TestCase("EnsureIndexOn succeeds for multiple fields and directions", () =>
|
||||
{
|
||||
Expect.equal(
|
||||
Query.Definition.EnsureIndexOn("test.table", "gibberish",
|
||||
new[] { "taco", "guac DESC", "salsa ASC" }),
|
||||
"CREATE INDEX IF NOT EXISTS idx_table_gibberish ON test.table "
|
||||
+ "((data ->> 'taco'), (data ->> 'guac') DESC, (data ->> 'salsa') ASC)",
|
||||
"CREATE INDEX for multiple field statement incorrect");
|
||||
})
|
||||
}),
|
||||
TestCase("Insert succeeds", () =>
|
||||
{
|
||||
Expect.equal(Query.Insert("tbl"), "INSERT INTO tbl VALUES (@data)", "INSERT statement not correct");
|
||||
}),
|
||||
TestCase("Save succeeds", () =>
|
||||
{
|
||||
Expect.equal(Query.Save("tbl"),
|
||||
$"INSERT INTO tbl VALUES (@data) ON CONFLICT ((data ->> 'Id')) DO UPDATE SET data = EXCLUDED.data",
|
||||
"INSERT ON CONFLICT UPDATE statement not correct");
|
||||
}),
|
||||
TestCase("Update succeeds", () =>
|
||||
{
|
||||
Expect.equal(Query.Update("tbl"), "UPDATE tbl SET data = @data WHERE data ->> 'Id' = @id",
|
||||
"UPDATE full statement not correct");
|
||||
}),
|
||||
TestList("Count", new[]
|
||||
{
|
||||
TestCase("All succeeds", () =>
|
||||
{
|
||||
Expect.equal(Query.Count.All("tbl"), "SELECT COUNT(*) AS it FROM tbl", "Count query not correct");
|
||||
}),
|
||||
TestCase("ByField succeeds", () =>
|
||||
{
|
||||
Expect.equal(Query.Count.ByField("tbl", "thatField", Op.EQ),
|
||||
"SELECT COUNT(*) AS it FROM tbl WHERE data ->> 'thatField' = @field",
|
||||
"JSON field text comparison count query not correct");
|
||||
})
|
||||
}),
|
||||
TestList("Exists", new[]
|
||||
{
|
||||
TestCase("ById succeeds", () =>
|
||||
{
|
||||
Expect.equal(Query.Exists.ById("tbl"),
|
||||
"SELECT EXISTS (SELECT 1 FROM tbl WHERE data ->> 'Id' = @id) AS it",
|
||||
"ID existence query not correct");
|
||||
}),
|
||||
TestCase("ByField succeeds", () =>
|
||||
{
|
||||
Expect.equal(Query.Exists.ByField("tbl", "Test", Op.LT),
|
||||
"SELECT EXISTS (SELECT 1 FROM tbl WHERE data ->> 'Test' < @field) AS it",
|
||||
"JSON field text comparison exists query not correct");
|
||||
})
|
||||
}),
|
||||
TestList("Find", new[]
|
||||
{
|
||||
TestCase("ById succeeds", () =>
|
||||
{
|
||||
Expect.equal(Query.Find.ById("tbl"), "SELECT data FROM tbl WHERE data ->> 'Id' = @id",
|
||||
"SELECT by ID query not correct");
|
||||
}),
|
||||
TestCase("ByField succeeds", () =>
|
||||
{
|
||||
Expect.equal(Query.Find.ByField("tbl", "Golf", Op.GE),
|
||||
"SELECT data FROM tbl WHERE data ->> 'Golf' >= @field",
|
||||
"SELECT by JSON comparison query not correct");
|
||||
})
|
||||
}),
|
||||
TestList("Delete", new[]
|
||||
{
|
||||
TestCase("ById succeeds", () =>
|
||||
{
|
||||
Expect.equal(Query.Delete.ById("tbl"), "DELETE FROM tbl WHERE data ->> 'Id' = @id",
|
||||
"DELETE by ID query not correct");
|
||||
}),
|
||||
TestCase("ByField succeeds", () =>
|
||||
{
|
||||
Expect.equal(Query.Delete.ByField("tbl", "gone", Op.NEX),
|
||||
"DELETE FROM tbl WHERE data ->> 'gone' IS NULL",
|
||||
"DELETE by JSON comparison query not correct");
|
||||
})
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
807
src/Tests.CSharp/PostgresCSharpExtensionTests.cs
Normal file
807
src/Tests.CSharp/PostgresCSharpExtensionTests.cs
Normal file
@@ -0,0 +1,807 @@
|
||||
using Expecto.CSharp;
|
||||
using Expecto;
|
||||
using BitBadger.Documents.Postgres;
|
||||
using Npgsql;
|
||||
|
||||
namespace BitBadger.Documents.Tests.CSharp;
|
||||
|
||||
using static CommonExtensionsAndTypesForNpgsqlFSharp;
|
||||
using static Runner;
|
||||
|
||||
/// <summary>
|
||||
/// C# tests for the extensions on the <tt>NpgsqlConnection</tt> type
|
||||
/// </summary>
|
||||
public class PostgresCSharpExtensionTests
|
||||
{
|
||||
private static Task LoadDocs() => PostgresCSharpTests.LoadDocs();
|
||||
|
||||
/// <summary>
|
||||
/// Create a connection to the throwaway database
|
||||
/// </summary>
|
||||
/// <param name="db">The throwaway database for which a connection should be made</param>
|
||||
/// <returns>An open connection to the throwaway database</returns>
|
||||
private static NpgsqlConnection MkConn(ThrowawayPostgresDb db)
|
||||
{
|
||||
var conn = new NpgsqlConnection(db.ConnectionString);
|
||||
conn.Open();
|
||||
return conn;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Integration tests for the SQLite extension methods
|
||||
/// </summary>
|
||||
[Tests]
|
||||
public static readonly Test Integration = TestList("Postgres.C#.Extensions", new[]
|
||||
{
|
||||
TestList("CustomList", new[]
|
||||
{
|
||||
TestCase("succeeds when data is found", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
var docs = await conn.CustomList(Query.SelectFromTable(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 using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
var docs = await conn.CustomList(
|
||||
$"SELECT data FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath",
|
||||
new[] { Tuple.Create("@path", Sql.@string("$.NumValue ? (@ > 100)")) },
|
||||
Results.FromData<JsonDocument>);
|
||||
Expect.isEmpty(docs, "There should have been no documents returned");
|
||||
})
|
||||
}),
|
||||
TestList("CustomSingle", new[]
|
||||
{
|
||||
TestCase("succeeds when a row is found", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
var doc = await conn.CustomSingle($"SELECT data FROM {PostgresDb.TableName} WHERE data ->> 'Id' = @id",
|
||||
new[] { 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 using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
var doc = await conn.CustomSingle($"SELECT data FROM {PostgresDb.TableName} WHERE data ->> 'Id' = @id",
|
||||
new[] { Tuple.Create("@id", Sql.@string("eighty")) }, Results.FromData<JsonDocument>);
|
||||
Expect.isNull(doc, "There should not have been a document returned");
|
||||
})
|
||||
}),
|
||||
TestList("CustomNonQuery", new[]
|
||||
{
|
||||
TestCase("succeeds when operating on data", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
await conn.CustomNonQuery($"DELETE FROM {PostgresDb.TableName}", Parameters.None);
|
||||
|
||||
var remaining = await conn.CountAll(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 using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
await conn.CustomNonQuery($"DELETE FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath",
|
||||
new[] { Tuple.Create("@path", Sql.@string("$.NumValue ? (@ > 100)")) });
|
||||
|
||||
var remaining = await conn.CountAll(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();
|
||||
await using var conn = MkConn(db);
|
||||
var nbr = await conn.CustomScalar("SELECT 5 AS test_value", Parameters.None, row => row.@int("test_value"));
|
||||
Expect.equal(nbr, 5, "The query should have returned the number 5");
|
||||
}),
|
||||
TestCase("EnsureTable succeeds", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await using var conn = MkConn(db);
|
||||
var tableExists = () => conn.CustomScalar(
|
||||
"SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'ensured') AS it", Parameters.None,
|
||||
Results.ToExists);
|
||||
var keyExists = () => conn.CustomScalar(
|
||||
"SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'idx_ensured_key') AS it", Parameters.None,
|
||||
Results.ToExists);
|
||||
|
||||
var exists = await tableExists();
|
||||
var alsoExists = await keyExists();
|
||||
Expect.isFalse(exists, "The table should not exist already");
|
||||
Expect.isFalse(alsoExists, "The key index should not exist already");
|
||||
|
||||
await conn.EnsureTable("ensured");
|
||||
exists = await tableExists();
|
||||
alsoExists = await keyExists();
|
||||
Expect.isTrue(exists, "The table should now exist");
|
||||
Expect.isTrue(alsoExists, "The key index should now exist");
|
||||
}),
|
||||
TestCase("EnsureDocumentIndex succeeds", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await using var conn = MkConn(db);
|
||||
var indexExists = () => conn.CustomScalar(
|
||||
"SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'idx_ensured_document') AS it", Parameters.None,
|
||||
Results.ToExists);
|
||||
|
||||
var exists = await indexExists();
|
||||
Expect.isFalse(exists, "The index should not exist already");
|
||||
|
||||
await conn.EnsureTable("ensured");
|
||||
await conn.EnsureDocumentIndex("ensured", DocumentIndex.Optimized);
|
||||
exists = await indexExists();
|
||||
Expect.isTrue(exists, "The index should now exist");
|
||||
}),
|
||||
TestCase("EnsureFieldIndex succeeds", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await using var conn = MkConn(db);
|
||||
var indexExists = () => conn.CustomScalar(
|
||||
"SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'idx_ensured_test') AS it", Parameters.None,
|
||||
Results.ToExists);
|
||||
|
||||
var exists = await indexExists();
|
||||
Expect.isFalse(exists, "The index should not exist already");
|
||||
|
||||
await conn.EnsureTable("ensured");
|
||||
await conn.EnsureFieldIndex("ensured", "test", new[] { "Id", "Category" });
|
||||
exists = await indexExists();
|
||||
Expect.isTrue(exists, "The index should now exist");
|
||||
}),
|
||||
TestList("Insert", new[]
|
||||
{
|
||||
TestCase("succeeds", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await using var conn = MkConn(db);
|
||||
var before = await conn.CountAll(PostgresDb.TableName);
|
||||
Expect.equal(before, 0, "There should be no documents in the table");
|
||||
|
||||
await conn.Insert(PostgresDb.TableName,
|
||||
new JsonDocument { Id = "turkey", Sub = new() { Foo = "gobble", Bar = "gobble" } });
|
||||
var after = await conn.FindAll<JsonDocument>(PostgresDb.TableName);
|
||||
Expect.equal(after.Count, 1, "There should have been one document inserted");
|
||||
}),
|
||||
TestCase("fails for duplicate key", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await using var conn = MkConn(db);
|
||||
await conn.Insert(PostgresDb.TableName, new JsonDocument { Id = "test" });
|
||||
try
|
||||
{
|
||||
await conn.Insert(PostgresDb.TableName, new JsonDocument { Id = "test" });
|
||||
Expect.isTrue(false, "An exception should have been raised for duplicate document ID insert");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// This is what should have happened
|
||||
}
|
||||
})
|
||||
}),
|
||||
TestList("save", new[]
|
||||
{
|
||||
TestCase("succeeds when a document is inserted", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await using var conn = MkConn(db);
|
||||
var before = await conn.CountAll(PostgresDb.TableName);
|
||||
Expect.equal(before, 0, "There should be no documents in the table");
|
||||
|
||||
await conn.Save(PostgresDb.TableName,
|
||||
new JsonDocument { Id = "test", Sub = new() { Foo = "a", Bar = "b" } });
|
||||
var after = await conn.FindAll<JsonDocument>(PostgresDb.TableName);
|
||||
Expect.equal(after.Count, 1, "There should have been one document inserted");
|
||||
}),
|
||||
TestCase("succeeds when a document is updated", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await using var conn = MkConn(db);
|
||||
await conn.Insert(PostgresDb.TableName,
|
||||
new JsonDocument { Id = "test", Sub = new() { Foo = "a", Bar = "b" } });
|
||||
|
||||
var before = await conn.FindById<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");
|
||||
|
||||
await conn.Save(PostgresDb.TableName,
|
||||
new JsonDocument { Id = "test", Sub = new() { Foo = "c", Bar = "d" } });
|
||||
var after = await conn.FindById<string, JsonDocument>(PostgresDb.TableName, "test");
|
||||
Expect.isNotNull(after, "There should have been a document returned post-update");
|
||||
Expect.equal(after.Sub!.Foo, "c", "The updated document is not correct");
|
||||
})
|
||||
}),
|
||||
TestCase("CountAll succeeds", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
var theCount = await conn.CountAll(PostgresDb.TableName);
|
||||
Expect.equal(theCount, 5, "There should have been 5 matching documents");
|
||||
}),
|
||||
TestCase("CountByField succeeds", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
var theCount = await conn.CountByField(PostgresDb.TableName, "Value", Op.EQ, "purple");
|
||||
Expect.equal(theCount, 2, "There should have been 2 matching documents");
|
||||
}),
|
||||
TestCase("CountByContains succeeds", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
var theCount = await conn.CountByContains(PostgresDb.TableName, new { Value = "purple" });
|
||||
Expect.equal(theCount, 2, "There should have been 2 matching documents");
|
||||
}),
|
||||
TestCase("CountByJsonPath succeeds", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
var theCount = await conn.CountByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ > 5)");
|
||||
Expect.equal(theCount, 3, "There should have been 3 matching documents");
|
||||
}),
|
||||
TestList("ExistsById", new[]
|
||||
{
|
||||
TestCase("succeeds when a document exists", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
var exists = await conn.ExistsById(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 using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
var exists = await conn.ExistsById(PostgresDb.TableName, "seven");
|
||||
Expect.isFalse(exists, "There should not have been an existing document");
|
||||
})
|
||||
}),
|
||||
TestList("ExistsByField", new[]
|
||||
{
|
||||
TestCase("succeeds when documents exist", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
var exists = await conn.ExistsByField(PostgresDb.TableName, "Sub", Op.EX, "");
|
||||
Expect.isTrue(exists, "There should have been existing documents");
|
||||
}),
|
||||
TestCase("succeeds when documents do not exist", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
var exists = await conn.ExistsByField(PostgresDb.TableName, "NumValue", Op.EQ, "six");
|
||||
Expect.isFalse(exists, "There should not have been existing documents");
|
||||
})
|
||||
}),
|
||||
TestList("ExistsByContains", new[]
|
||||
{
|
||||
TestCase("succeeds when documents exist", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
var exists = await conn.ExistsByContains(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 using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
var exists = await conn.ExistsByContains(PostgresDb.TableName, new { Nothing = "none" });
|
||||
Expect.isFalse(exists, "There should not have been any existing documents");
|
||||
})
|
||||
}),
|
||||
TestList("ExistsByJsonPath", new[]
|
||||
{
|
||||
TestCase("succeeds when documents exist", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
var exists = await conn.ExistsByJsonPath(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 using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
var exists = await conn.ExistsByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ > 1000)");
|
||||
Expect.isFalse(exists, "There should not have been any existing documents");
|
||||
})
|
||||
}),
|
||||
TestList("FindAll", new[]
|
||||
{
|
||||
TestCase("succeeds when there is data", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await using var conn = MkConn(db);
|
||||
|
||||
await conn.Insert(PostgresDb.TableName, new JsonDocument { Id = "one" });
|
||||
await conn.Insert(PostgresDb.TableName, new JsonDocument { Id = "three" });
|
||||
await conn.Insert(PostgresDb.TableName, new JsonDocument { Id = "five" });
|
||||
|
||||
var results = await conn.FindAll<JsonDocument>(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();
|
||||
await using var conn = MkConn(db);
|
||||
var results = await conn.FindAll<JsonDocument>(PostgresDb.TableName);
|
||||
Expect.isEmpty(results, "There should have been no documents returned");
|
||||
})
|
||||
}),
|
||||
TestList("FindById", new[]
|
||||
{
|
||||
TestCase("succeeds when a document is found", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
var doc = await conn.FindById<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 using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
var doc = await conn.FindById<string, JsonDocument>(PostgresDb.TableName, "three hundred eighty-seven");
|
||||
Expect.isNull(doc, "There should not have been a document returned");
|
||||
})
|
||||
}),
|
||||
TestList("FindByField", new[]
|
||||
{
|
||||
TestCase("succeeds when documents are found", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
var docs = await conn.FindByField<JsonDocument>(PostgresDb.TableName, "Value", Op.EQ, "another");
|
||||
Expect.equal(docs.Count, 1, "There should have been one document returned");
|
||||
}),
|
||||
TestCase("succeeds when documents are not found", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
var docs = await conn.FindByField<JsonDocument>(PostgresDb.TableName, "Value", Op.EQ, "mauve");
|
||||
Expect.isEmpty(docs, "There should have been no documents returned");
|
||||
})
|
||||
}),
|
||||
TestList("FindByContains", new[]
|
||||
{
|
||||
TestCase("succeeds when documents are found", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
var docs = await conn.FindByContains<JsonDocument>(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 using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
var docs = await conn.FindByContains<JsonDocument>(PostgresDb.TableName, new { Value = "mauve" });
|
||||
Expect.isEmpty(docs, "There should have been no documents returned");
|
||||
})
|
||||
}),
|
||||
TestList("FindByJsonPath", new[]
|
||||
{
|
||||
TestCase("succeeds when documents are found", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
var docs = await conn.FindByJsonPath<JsonDocument>(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 using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
var docs = await conn.FindByJsonPath<JsonDocument>(PostgresDb.TableName, "$.NumValue ? (@ < 0)");
|
||||
Expect.isEmpty(docs, "There should have been no documents returned");
|
||||
})
|
||||
}),
|
||||
TestList("FindFirstByField", new[]
|
||||
{
|
||||
TestCase("succeeds when a document is found", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
var doc = await conn.FindFirstByField<JsonDocument>(PostgresDb.TableName, "Value", Op.EQ, "another");
|
||||
Expect.isNotNull(doc, "There should have been a document returned");
|
||||
Expect.equal(doc.Id, "two", "The incorrect document was returned");
|
||||
}),
|
||||
TestCase("succeeds when multiple documents are found", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
var doc = await conn.FindFirstByField<JsonDocument>(PostgresDb.TableName, "Value", Op.EQ, "purple");
|
||||
Expect.isNotNull(doc, "There should have been a document returned");
|
||||
Expect.contains(new[] { "five", "four" }, doc.Id, "An incorrect document was returned");
|
||||
}),
|
||||
TestCase("succeeds when a document is not found", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
var doc = await conn.FindFirstByField<JsonDocument>(PostgresDb.TableName, "Value", Op.EQ, "absent");
|
||||
Expect.isNull(doc, "There should not have been a document returned");
|
||||
})
|
||||
}),
|
||||
TestList("FindFirstByContains", new[]
|
||||
{
|
||||
TestCase("succeeds when a document is found", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
var doc = await conn.FindFirstByContains<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 using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
var doc = await conn.FindFirstByContains<JsonDocument>(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 using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
var doc = await conn.FindFirstByContains<JsonDocument>(PostgresDb.TableName, new { Value = "absent" });
|
||||
Expect.isNull(doc, "There should not have been a document returned");
|
||||
})
|
||||
}),
|
||||
TestList("FindFirstByJsonPath", new[]
|
||||
{
|
||||
TestCase("succeeds when a document is found", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
var doc = await conn.FindFirstByJsonPath<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 using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
var doc = await conn.FindFirstByJsonPath<JsonDocument>(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 using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
var doc = await conn.FindFirstByJsonPath<JsonDocument>(PostgresDb.TableName, "$.Id ? (@ == \"nope\")");
|
||||
Expect.isNull(doc, "There should not have been a document returned");
|
||||
})
|
||||
}),
|
||||
TestList("UpdateById", new[]
|
||||
{
|
||||
TestCase("succeeds when a document is updated", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
await conn.UpdateById(PostgresDb.TableName, "one",
|
||||
new JsonDocument { Id = "one", Sub = new() { Foo = "blue", Bar = "red" } });
|
||||
var after = await conn.FindById<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();
|
||||
await using var conn = MkConn(db);
|
||||
var before = await conn.CountAll(PostgresDb.TableName);
|
||||
Expect.equal(before, 0, "There should have been no documents returned");
|
||||
|
||||
// This not raising an exception is the test
|
||||
await conn.UpdateById(PostgresDb.TableName, "test",
|
||||
new JsonDocument { Id = "x", Sub = new() { Foo = "blue", Bar = "red" } });
|
||||
})
|
||||
}),
|
||||
TestList("UpdateByFunc", new[]
|
||||
{
|
||||
TestCase("succeeds when a document is updated", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
await conn.UpdateByFunc(PostgresDb.TableName, doc => doc.Id,
|
||||
new JsonDocument { Id = "one", Value = "le un", NumValue = 1 });
|
||||
var after = await conn.FindById<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();
|
||||
await using var conn = MkConn(db);
|
||||
var before = await conn.CountAll(PostgresDb.TableName);
|
||||
Expect.equal(before, 0, "There should have been no documents returned");
|
||||
|
||||
// This not raising an exception is the test
|
||||
await conn.UpdateByFunc(PostgresDb.TableName, doc => doc.Id,
|
||||
new JsonDocument { Id = "one", Value = "le un", NumValue = 1 });
|
||||
})
|
||||
}),
|
||||
TestList("PatchById", new[]
|
||||
{
|
||||
TestCase("succeeds when a document is updated", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
await conn.PatchById(PostgresDb.TableName, "one", new { NumValue = 44 });
|
||||
var after = await conn.FindById<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();
|
||||
await using var conn = MkConn(db);
|
||||
var before = await conn.CountAll(PostgresDb.TableName);
|
||||
Expect.equal(before, 0, "There should have been no documents returned");
|
||||
|
||||
// This not raising an exception is the test
|
||||
await conn.PatchById(PostgresDb.TableName, "test", new { Foo = "green" });
|
||||
})
|
||||
}),
|
||||
TestList("PatchByField", new[]
|
||||
{
|
||||
TestCase("succeeds when a document is updated", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
await conn.PatchByField(PostgresDb.TableName, "Value", Op.EQ, "purple", new { NumValue = 77 });
|
||||
var after = await conn.CountByField(PostgresDb.TableName, "NumValue", Op.EQ, "77");
|
||||
Expect.equal(after, 2, "There should have been 2 documents returned");
|
||||
}),
|
||||
TestCase("succeeds when no document is updated", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await using var conn = MkConn(db);
|
||||
var before = await conn.CountAll(PostgresDb.TableName);
|
||||
Expect.equal(before, 0, "There should have been no documents returned");
|
||||
|
||||
// This not raising an exception is the test
|
||||
await conn.PatchByField(PostgresDb.TableName, "Value", Op.EQ, "burgundy", new { Foo = "green" });
|
||||
})
|
||||
}),
|
||||
TestList("PatchByContains", new[]
|
||||
{
|
||||
TestCase("succeeds when a document is updated", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
await conn.PatchByContains(PostgresDb.TableName, new { Value = "purple" }, new { NumValue = 77 });
|
||||
var after = await conn.CountByContains(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();
|
||||
await using var conn = MkConn(db);
|
||||
var before = await conn.CountAll(PostgresDb.TableName);
|
||||
Expect.equal(before, 0, "There should have been no documents returned");
|
||||
|
||||
// This not raising an exception is the test
|
||||
await conn.PatchByContains(PostgresDb.TableName, new { Value = "burgundy" }, new { Foo = "green" });
|
||||
})
|
||||
}),
|
||||
TestList("PatchByJsonPath", new[]
|
||||
{
|
||||
TestCase("succeeds when a document is updated", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
await conn.PatchByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ > 10)", new { NumValue = 1000 });
|
||||
var after = await conn.CountByJsonPath(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();
|
||||
await using var conn = MkConn(db);
|
||||
var before = await conn.CountAll(PostgresDb.TableName);
|
||||
Expect.equal(before, 0, "There should have been no documents returned");
|
||||
|
||||
// This not raising an exception is the test
|
||||
await conn.PatchByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ < 0)", new { Foo = "green" });
|
||||
})
|
||||
}),
|
||||
TestList("DeleteById", new[]
|
||||
{
|
||||
TestCase("succeeds when a document is deleted", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
await conn.DeleteById(PostgresDb.TableName, "four");
|
||||
var remaining = await conn.CountAll(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 using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
await conn.DeleteById(PostgresDb.TableName, "thirty");
|
||||
var remaining = await conn.CountAll(PostgresDb.TableName);
|
||||
Expect.equal(remaining, 5, "There should have been 5 documents remaining");
|
||||
})
|
||||
}),
|
||||
TestList("DeleteByField", new[]
|
||||
{
|
||||
TestCase("succeeds when documents are deleted", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
await conn.DeleteByField(PostgresDb.TableName, "Value", Op.NE, "purple");
|
||||
var remaining = await conn.CountAll(PostgresDb.TableName);
|
||||
Expect.equal(remaining, 2, "There should have been 2 documents remaining");
|
||||
}),
|
||||
TestCase("succeeds when documents are not deleted", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
await conn.DeleteByField(PostgresDb.TableName, "Value", Op.EQ, "crimson");
|
||||
var remaining = await conn.CountAll(PostgresDb.TableName);
|
||||
Expect.equal(remaining, 5, "There should have been 5 documents remaining");
|
||||
})
|
||||
}),
|
||||
TestList("DeleteByContains", new[]
|
||||
{
|
||||
TestCase("succeeds when documents are deleted", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
await conn.DeleteByContains(PostgresDb.TableName, new { Value = "purple" });
|
||||
var remaining = await conn.CountAll(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 using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
await conn.DeleteByContains(PostgresDb.TableName, new { Value = "crimson" });
|
||||
var remaining = await conn.CountAll(PostgresDb.TableName);
|
||||
Expect.equal(remaining, 5, "There should have been 5 documents remaining");
|
||||
})
|
||||
}),
|
||||
TestList("DeleteByJsonPath", new[]
|
||||
{
|
||||
TestCase("succeeds when documents are deleted", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
await conn.DeleteByJsonPath(PostgresDb.TableName, "$.Sub.Foo ? (@ == \"green\")");
|
||||
var remaining = await conn.CountAll(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 using var conn = MkConn(db);
|
||||
await LoadDocs();
|
||||
|
||||
await conn.DeleteByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ > 100)");
|
||||
var remaining = await conn.CountAll(PostgresDb.TableName);
|
||||
Expect.equal(remaining, 5, "There should have been 5 documents remaining");
|
||||
})
|
||||
}),
|
||||
});
|
||||
}
|
||||
970
src/Tests.CSharp/PostgresCSharpTests.cs
Normal file
970
src/Tests.CSharp/PostgresCSharpTests.cs
Normal file
@@ -0,0 +1,970 @@
|
||||
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 class PostgresCSharpTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests which do not hit the database
|
||||
/// </summary>
|
||||
private static readonly Test Unit = TestList("Unit", new[]
|
||||
{
|
||||
TestList("Parameters", new[]
|
||||
{
|
||||
TestCase("Id succeeds", () =>
|
||||
{
|
||||
var it = Parameters.Id(88);
|
||||
Expect.equal(it.Item1, "@id", "ID parameter not constructed correctly");
|
||||
Expect.equal(it.Item2, Sql.@string("88"), "ID parameter value incorrect");
|
||||
}),
|
||||
TestCase("Json succeeds", () =>
|
||||
{
|
||||
var it = Parameters.Json("@test", new { Something = "good" });
|
||||
Expect.equal(it.Item1, "@test", "JSON parameter not constructed correctly");
|
||||
Expect.equal(it.Item2, Sql.jsonb("{\"Something\":\"good\"}"), "JSON parameter value incorrect");
|
||||
}),
|
||||
TestCase("Field succeeds", () =>
|
||||
{
|
||||
var it = Parameters.Field(242);
|
||||
Expect.equal(it.Item1, "@field", "Field parameter not constructed correctly");
|
||||
Expect.isTrue(it.Item2.IsParameter, "Field parameter value incorrect");
|
||||
}),
|
||||
TestCase("None succeeds", () =>
|
||||
{
|
||||
Expect.isEmpty(Parameters.None, "The no-params sequence should be empty");
|
||||
})
|
||||
}),
|
||||
TestList("Query", new[]
|
||||
{
|
||||
TestList("Definition", new[]
|
||||
{
|
||||
TestCase("EnsureTable succeeds", () =>
|
||||
{
|
||||
Expect.equal(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");
|
||||
}),
|
||||
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("Patch", new[]
|
||||
{
|
||||
TestCase("ById succeeds", () =>
|
||||
{
|
||||
Expect.equal(Postgres.Query.Patch.ById(PostgresDb.TableName),
|
||||
$"UPDATE {PostgresDb.TableName} SET data = data || @data WHERE data ->> 'Id' = @id",
|
||||
"UPDATE partial by ID statement not correct");
|
||||
}),
|
||||
TestCase("ByField succeeds", () =>
|
||||
{
|
||||
Expect.equal(Postgres.Query.Patch.ByField(PostgresDb.TableName, "Snail", Op.LT),
|
||||
$"UPDATE {PostgresDb.TableName} SET data = data || @data WHERE data ->> 'Snail' < @field",
|
||||
"UPDATE partial by ID statement not correct");
|
||||
}),
|
||||
TestCase("ByContains succeeds", () =>
|
||||
{
|
||||
Expect.equal(Postgres.Query.Patch.ByContains(PostgresDb.TableName),
|
||||
$"UPDATE {PostgresDb.TableName} SET data = data || @data WHERE data @> @criteria",
|
||||
"UPDATE partial by JSON containment statement not correct");
|
||||
}),
|
||||
TestCase("ByJsonPath succeeds", () =>
|
||||
{
|
||||
Expect.equal(Postgres.Query.Patch.ByJsonPath(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 }
|
||||
};
|
||||
|
||||
/// <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 PostgreSQL library
|
||||
/// </summary>
|
||||
private static readonly Test Integration = TestList("Integration", new[]
|
||||
{
|
||||
TestList("Configuration", new[]
|
||||
{
|
||||
TestCase("UseDataSource disposes existing source", () =>
|
||||
{
|
||||
using var db1 = ThrowawayDatabase.Create(PostgresDb.ConnStr.Value);
|
||||
var source = PostgresDb.MkDataSource(db1.ConnectionString);
|
||||
Postgres.Configuration.UseDataSource(source);
|
||||
|
||||
using var db2 = ThrowawayDatabase.Create(PostgresDb.ConnStr.Value);
|
||||
Postgres.Configuration.UseDataSource(PostgresDb.MkDataSource(db2.ConnectionString));
|
||||
try
|
||||
{
|
||||
_ = source.OpenConnection();
|
||||
Expect.isTrue(false, "Data source should have been disposed");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// This is what should have happened
|
||||
}
|
||||
}),
|
||||
TestCase("DataSource returns configured data source", () =>
|
||||
{
|
||||
using var db = ThrowawayDatabase.Create(PostgresDb.ConnStr.Value);
|
||||
var source = PostgresDb.MkDataSource(db.ConnectionString);
|
||||
Postgres.Configuration.UseDataSource(source);
|
||||
|
||||
Expect.isTrue(ReferenceEquals(source, Postgres.Configuration.DataSource()),
|
||||
"Data source should have been the same");
|
||||
})
|
||||
}),
|
||||
TestList("Custom", new[]
|
||||
{
|
||||
TestList("List", new[]
|
||||
{
|
||||
TestCase("succeeds when data is found", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
var docs = await Custom.List(Query.SelectFromTable(PostgresDb.TableName), Parameters.None,
|
||||
Results.FromData<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",
|
||||
new[] { Tuple.Create("@path", Sql.@string("$.NumValue ? (@ > 100)")) },
|
||||
Results.FromData<JsonDocument>);
|
||||
Expect.isEmpty(docs, "There should have been no documents returned");
|
||||
})
|
||||
}),
|
||||
TestList("Single", new[]
|
||||
{
|
||||
TestCase("succeeds when a row is found", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
var doc = await Custom.Single($"SELECT data FROM {PostgresDb.TableName} WHERE data ->> 'Id' = @id",
|
||||
new[] { Tuple.Create("@id", Sql.@string("one")) }, Results.FromData<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",
|
||||
new[] { Tuple.Create("@id", Sql.@string("eighty")) }, Results.FromData<JsonDocument>);
|
||||
Expect.isNull(doc, "There should not have been a document returned");
|
||||
})
|
||||
}),
|
||||
TestList("NonQuery", new[]
|
||||
{
|
||||
TestCase("succeeds when operating on data", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
await Custom.NonQuery($"DELETE FROM {PostgresDb.TableName}", Parameters.None);
|
||||
|
||||
var remaining = await Count.All(PostgresDb.TableName);
|
||||
Expect.equal(remaining, 0, "There should be no documents remaining in the table");
|
||||
}),
|
||||
TestCase("succeeds when no data matches where clause", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
await Custom.NonQuery($"DELETE FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath",
|
||||
new[] { Tuple.Create("@path", Sql.@string("$.NumValue ? (@ > 100)")) });
|
||||
|
||||
var remaining = await Count.All(PostgresDb.TableName);
|
||||
Expect.equal(remaining, 5, "There should be 5 documents remaining in the table");
|
||||
})
|
||||
}),
|
||||
TestCase("Scalar succeeds", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
|
||||
var nbr = await Custom.Scalar("SELECT 5 AS test_value", Parameters.None, row => row.@int("test_value"));
|
||||
Expect.equal(nbr, 5, "The query should have returned the number 5");
|
||||
})
|
||||
}),
|
||||
TestList("Definition", new[]
|
||||
{
|
||||
TestCase("EnsureTable succeeds", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
var tableExists = () => Custom.Scalar(
|
||||
"SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'ensured') AS it", Parameters.None,
|
||||
Results.ToExists);
|
||||
var keyExists = () => Custom.Scalar(
|
||||
"SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'idx_ensured_key') AS it", Parameters.None,
|
||||
Results.ToExists);
|
||||
|
||||
var exists = await tableExists();
|
||||
var alsoExists = await keyExists();
|
||||
Expect.isFalse(exists, "The table should not exist already");
|
||||
Expect.isFalse(alsoExists, "The key index should not exist already");
|
||||
|
||||
await Definition.EnsureTable("ensured");
|
||||
exists = await tableExists();
|
||||
alsoExists = await keyExists();
|
||||
Expect.isTrue(exists, "The table should now exist");
|
||||
Expect.isTrue(alsoExists, "The key index should now exist");
|
||||
}),
|
||||
TestCase("EnsureDocumentIndex succeeds", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
var indexExists = () => Custom.Scalar(
|
||||
"SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'idx_ensured_document') AS it",
|
||||
Parameters.None, Results.ToExists);
|
||||
|
||||
var exists = await indexExists();
|
||||
Expect.isFalse(exists, "The index should not exist already");
|
||||
|
||||
await Definition.EnsureTable("ensured");
|
||||
await Definition.EnsureDocumentIndex("ensured", DocumentIndex.Optimized);
|
||||
exists = await indexExists();
|
||||
Expect.isTrue(exists, "The index should now exist");
|
||||
}),
|
||||
TestCase("EnsureFieldIndex succeeds", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
var indexExists = () => Custom.Scalar(
|
||||
"SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'idx_ensured_test') AS it", Parameters.None,
|
||||
Results.ToExists);
|
||||
|
||||
var exists = await indexExists();
|
||||
Expect.isFalse(exists, "The index should not exist already");
|
||||
|
||||
await Definition.EnsureTable("ensured");
|
||||
await Definition.EnsureFieldIndex("ensured", "test", new[] { "Id", "Category" });
|
||||
exists = await indexExists();
|
||||
Expect.isTrue(exists, "The index should now exist");
|
||||
})
|
||||
}),
|
||||
TestList("Document", new[]
|
||||
{
|
||||
TestList("Insert", new[]
|
||||
{
|
||||
TestCase("succeeds", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
var before = await Count.All(PostgresDb.TableName);
|
||||
Expect.equal(before, 0, "There should be no documents in the table");
|
||||
|
||||
await Document.Insert(PostgresDb.TableName,
|
||||
new JsonDocument { Id = "turkey", Sub = new() { Foo = "gobble", Bar = "gobble" } });
|
||||
var after = await Count.All(PostgresDb.TableName);
|
||||
Expect.equal(after, 1, "There should have been one document inserted");
|
||||
}),
|
||||
TestCase("fails for duplicate key", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await Document.Insert(PostgresDb.TableName, new JsonDocument { Id = "test" });
|
||||
try
|
||||
{
|
||||
await Document.Insert(PostgresDb.TableName, new JsonDocument { Id = "test" });
|
||||
Expect.isTrue(false, "An exception should have been raised for duplicate document ID insert");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// This is what should have happened
|
||||
}
|
||||
})
|
||||
}),
|
||||
TestList("Save", new[]
|
||||
{
|
||||
TestCase("succeeds when a document is inserted", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
var before = await Count.All(PostgresDb.TableName);
|
||||
Expect.equal(before, 0, "There should be no documents in the table");
|
||||
|
||||
await Document.Save(PostgresDb.TableName,
|
||||
new JsonDocument { Id = "test", Sub = new() { Foo = "a", Bar = "b" } });
|
||||
var after = await Count.All(PostgresDb.TableName);
|
||||
Expect.equal(after, 1, "There should have been one document inserted");
|
||||
}),
|
||||
TestCase("succeeds when a document is updated", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await Document.Insert(PostgresDb.TableName,
|
||||
new JsonDocument { Id = "test", Sub = new() { Foo = "a", Bar = "b" } });
|
||||
|
||||
var before = await Find.ById<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");
|
||||
})
|
||||
})
|
||||
}),
|
||||
TestList("Count", new[]
|
||||
{
|
||||
TestCase("All succeeds", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
var theCount = await Count.All(PostgresDb.TableName);
|
||||
Expect.equal(theCount, 5, "There should have been 5 matching documents");
|
||||
}),
|
||||
TestCase("ByField succeeds", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
var theCount = await Count.ByField(PostgresDb.TableName, "Value", Op.EQ, "purple");
|
||||
Expect.equal(theCount, 2, "There should have been 2 matching documents");
|
||||
}),
|
||||
TestCase("ByContains succeeds", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
var theCount = await Count.ByContains(PostgresDb.TableName, new { Value = "purple" });
|
||||
Expect.equal(theCount, 2, "There should have been 2 matching documents");
|
||||
}),
|
||||
TestCase("ByJsonPath succeeds", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
var theCount = await Count.ByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ > 5)");
|
||||
Expect.equal(theCount, 3, "There should have been 3 matching documents");
|
||||
})
|
||||
}),
|
||||
TestList("Exists", new[]
|
||||
{
|
||||
TestList("ById", new[]
|
||||
{
|
||||
TestCase("succeeds when a document exists", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
var exists = await Exists.ById(PostgresDb.TableName, "three");
|
||||
Expect.isTrue(exists, "There should have been an existing document");
|
||||
}),
|
||||
TestCase("succeeds when a document does not exist", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
var exists = await Exists.ById(PostgresDb.TableName, "seven");
|
||||
Expect.isFalse(exists, "There should not have been an existing document");
|
||||
})
|
||||
}),
|
||||
TestList("ByField", new[]
|
||||
{
|
||||
TestCase("succeeds when documents exist", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
var exists = await Exists.ByField(PostgresDb.TableName, "Sub", Op.NEX, "");
|
||||
Expect.isTrue(exists, "There should have been existing documents");
|
||||
}),
|
||||
TestCase("succeeds when documents do not exist", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
var exists = await Exists.ByField(PostgresDb.TableName, "NumValue", Op.EQ, "six");
|
||||
Expect.isFalse(exists, "There should not have been existing documents");
|
||||
})
|
||||
}),
|
||||
TestList("ByContains", new[]
|
||||
{
|
||||
TestCase("succeeds when documents exist", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
var exists = await Exists.ByContains(PostgresDb.TableName, new { NumValue = 10 });
|
||||
Expect.isTrue(exists, "There should have been existing documents");
|
||||
}),
|
||||
TestCase("succeeds when no matching documents exist", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
var exists = await Exists.ByContains(PostgresDb.TableName, new { Nothing = "none" });
|
||||
Expect.isFalse(exists, "There should not have been any existing documents");
|
||||
})
|
||||
}),
|
||||
TestList("ByJsonPath", new[] {
|
||||
TestCase("succeeds when documents exist", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
var exists = await Exists.ByJsonPath(PostgresDb.TableName, "$.Sub.Foo ? (@ == \"green\")");
|
||||
Expect.isTrue(exists, "There should have been existing documents");
|
||||
}),
|
||||
TestCase("succeeds when no matching documents exist", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
var exists = await Exists.ByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ > 1000)");
|
||||
Expect.isFalse(exists, "There should not have been any existing documents");
|
||||
})
|
||||
})
|
||||
}),
|
||||
TestList("Find", new[]
|
||||
{
|
||||
TestList("All", new[]
|
||||
{
|
||||
TestCase("succeeds when there is data", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
|
||||
await Document.Insert(PostgresDb.TableName, new SubDocument { Foo = "one", Bar = "two" });
|
||||
await Document.Insert(PostgresDb.TableName, new SubDocument { Foo = "three", Bar = "four" });
|
||||
await Document.Insert(PostgresDb.TableName, new SubDocument { Foo = "five", Bar = "six" });
|
||||
|
||||
var results = await Find.All<SubDocument>(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<SubDocument>(PostgresDb.TableName);
|
||||
Expect.isEmpty(results, "There should have been no documents returned");
|
||||
})
|
||||
}),
|
||||
TestList("ById", new[]
|
||||
{
|
||||
TestCase("succeeds when a document is found", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
var doc = await Find.ById<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("ByField", new[]
|
||||
{
|
||||
TestCase("succeeds when documents are found", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
var docs = await Find.ByField<JsonDocument>(PostgresDb.TableName, "Value", Op.EQ, "another");
|
||||
Expect.equal(docs.Count, 1, "There should have been one document returned");
|
||||
}),
|
||||
TestCase("succeeds when documents are not found", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
var docs = await Find.ByField<JsonDocument>(PostgresDb.TableName, "Value", Op.EQ, "mauve");
|
||||
Expect.isEmpty(docs, "There should have been no documents returned");
|
||||
})
|
||||
}),
|
||||
TestList("ByContains", new[]
|
||||
{
|
||||
TestCase("succeeds when documents are found", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
var docs = await Find.ByContains<JsonDocument>(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<JsonDocument>(PostgresDb.TableName, new { Value = "mauve" });
|
||||
Expect.isEmpty(docs, "There should have been no documents returned");
|
||||
})
|
||||
}),
|
||||
TestList("ByJsonPath", new[]
|
||||
{
|
||||
TestCase("succeeds when documents are found", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
var docs = await Find.ByJsonPath<JsonDocument>(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<JsonDocument>(PostgresDb.TableName, "$.NumValue ? (@ < 0)");
|
||||
Expect.isEmpty(docs, "There should have been no documents returned");
|
||||
})
|
||||
}),
|
||||
TestList("FirstByField", new[]
|
||||
{
|
||||
TestCase("succeeds when a document is found", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
var doc = await Find.FirstByField<JsonDocument>(PostgresDb.TableName, "Value", Op.EQ, "another");
|
||||
Expect.isNotNull(doc, "There should have been a document returned");
|
||||
Expect.equal(doc.Id, "two", "The incorrect document was returned");
|
||||
}),
|
||||
TestCase("succeeds when multiple documents are found", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
var doc = await Find.FirstByField<JsonDocument>(PostgresDb.TableName, "Value", Op.EQ, "purple");
|
||||
Expect.isNotNull(doc, "There should have been a document returned");
|
||||
Expect.contains(new[] { "five", "four" }, doc.Id, "An incorrect document was returned");
|
||||
}),
|
||||
TestCase("succeeds when a document is not found", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
var doc = await Find.FirstByField<JsonDocument>(PostgresDb.TableName, "Value", Op.EQ, "absent");
|
||||
Expect.isNull(doc, "There should not have been a document returned");
|
||||
})
|
||||
}),
|
||||
TestList("FirstByContains", new[]
|
||||
{
|
||||
TestCase("succeeds when a document is found", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
var doc = await Find.FirstByContains<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(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<JsonDocument>(PostgresDb.TableName, new { Value = "absent" });
|
||||
Expect.isNull(doc, "There should not have been a document returned");
|
||||
})
|
||||
}),
|
||||
TestList("FirstByJsonPath", new[]
|
||||
{
|
||||
TestCase("succeeds when a document is found", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
var doc = await Find.FirstByJsonPath<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(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<JsonDocument>(PostgresDb.TableName, "$.Id ? (@ == \"nope\")");
|
||||
Expect.isNull(doc, "There should not have been a document returned");
|
||||
})
|
||||
})
|
||||
}),
|
||||
TestList("Update", new[]
|
||||
{
|
||||
TestList("ById", new[]
|
||||
{
|
||||
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", new[]
|
||||
{
|
||||
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 });
|
||||
})
|
||||
})
|
||||
}),
|
||||
TestList("Patch", new[]
|
||||
{
|
||||
TestList("ById", new[]
|
||||
{
|
||||
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("ByField", new[]
|
||||
{
|
||||
TestCase("succeeds when a document is updated", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
await Patch.ByField(PostgresDb.TableName, "Value", Op.EQ, "purple", new { NumValue = 77 });
|
||||
var after = await Count.ByField(PostgresDb.TableName, "NumValue", Op.EQ, "77");
|
||||
Expect.equal(after, 2, "There should have been 2 documents returned");
|
||||
}),
|
||||
TestCase("succeeds when no document is updated", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
|
||||
var before = await Count.All(PostgresDb.TableName);
|
||||
Expect.equal(before, 0, "There should have been no documents returned");
|
||||
|
||||
// This not raising an exception is the test
|
||||
await Patch.ByField(PostgresDb.TableName, "Value", Op.EQ, "burgundy", new { Foo = "green" });
|
||||
})
|
||||
}),
|
||||
TestList("ByContains", new[]
|
||||
{
|
||||
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", new[]
|
||||
{
|
||||
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("Delete", new[]
|
||||
{
|
||||
TestList("ById", new[]
|
||||
{
|
||||
TestCase("succeeds when a document is deleted", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
await Delete.ById(PostgresDb.TableName, "four");
|
||||
var remaining = await Count.All(PostgresDb.TableName);
|
||||
Expect.equal(remaining, 4, "There should have been 4 documents remaining");
|
||||
}),
|
||||
TestCase("succeeds when a document is not deleted", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
await Delete.ById(PostgresDb.TableName, "thirty");
|
||||
var remaining = await Count.All(PostgresDb.TableName);
|
||||
Expect.equal(remaining, 5, "There should have been 5 documents remaining");
|
||||
})
|
||||
}),
|
||||
TestList("ByField", new[]
|
||||
{
|
||||
TestCase("succeeds when documents are deleted", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
await Delete.ByField(PostgresDb.TableName, "Value", Op.EQ, "purple");
|
||||
var remaining = await Count.All(PostgresDb.TableName);
|
||||
Expect.equal(remaining, 3, "There should have been 3 documents remaining");
|
||||
}),
|
||||
TestCase("succeeds when documents are not deleted", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
await Delete.ByField(PostgresDb.TableName, "Value", Op.EQ, "crimson");
|
||||
var remaining = await Count.All(PostgresDb.TableName);
|
||||
Expect.equal(remaining, 5, "There should have been 5 documents remaining");
|
||||
})
|
||||
}),
|
||||
TestList("ByContains", new[]
|
||||
{
|
||||
TestCase("succeeds when documents are deleted", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
await Delete.ByContains(PostgresDb.TableName, new { Value = "purple" });
|
||||
var remaining = await Count.All(PostgresDb.TableName);
|
||||
Expect.equal(remaining, 3, "There should have been 3 documents remaining");
|
||||
}),
|
||||
TestCase("succeeds when documents are not deleted", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
await Delete.ByContains(PostgresDb.TableName, new { Value = "crimson" });
|
||||
var remaining = await Count.All(PostgresDb.TableName);
|
||||
Expect.equal(remaining, 5, "There should have been 5 documents remaining");
|
||||
})
|
||||
}),
|
||||
TestList("ByJsonPath", new[]
|
||||
{
|
||||
TestCase("succeeds when documents are deleted", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
await Delete.ByJsonPath(PostgresDb.TableName, "$.Sub.Foo ? (@ == \"green\")");
|
||||
var remaining = await Count.All(PostgresDb.TableName);
|
||||
Expect.equal(remaining, 3, "There should have been 3 documents remaining");
|
||||
}),
|
||||
TestCase("succeeds when documents are not deleted", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
await Delete.ByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ > 100)");
|
||||
var remaining = await Count.All(PostgresDb.TableName);
|
||||
Expect.equal(remaining, 5, "There should have been 5 documents remaining");
|
||||
})
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// All Postgres C# tests
|
||||
/// </summary>
|
||||
[Tests]
|
||||
public static readonly Test All = TestList("Postgres.C#", new[] { Unit, TestSequenced(Integration) });
|
||||
}
|
||||
150
src/Tests.CSharp/PostgresDb.cs
Normal file
150
src/Tests.CSharp/PostgresDb.cs
Normal file
@@ -0,0 +1,150 @@
|
||||
using Npgsql;
|
||||
using Npgsql.FSharp;
|
||||
using ThrowawayDb.Postgres;
|
||||
|
||||
namespace BitBadger.Documents.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// A throwaway SQLite database file, which will be deleted when it goes out of scope
|
||||
/// </summary>
|
||||
public class ThrowawayPostgresDb : IDisposable, IAsyncDisposable
|
||||
{
|
||||
private readonly ThrowawayDatabase _db;
|
||||
|
||||
/// <summary>
|
||||
/// The connection string for the throwaway database
|
||||
/// </summary>
|
||||
public string ConnectionString => _db.ConnectionString;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="db">The throwaway database which this instance will wrap</param>
|
||||
public ThrowawayPostgresDb(ThrowawayDatabase db)
|
||||
{
|
||||
_db = db;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_db.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public ValueTask DisposeAsync()
|
||||
{
|
||||
_db.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Database helpers for PostgreSQL integration tests
|
||||
/// </summary>
|
||||
public static class PostgresDb
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the table used for testing
|
||||
/// </summary>
|
||||
public const string TableName = "test_table";
|
||||
|
||||
/// <summary>
|
||||
/// The host for the database
|
||||
/// </summary>
|
||||
private static readonly Lazy<string> DbHost = new(() =>
|
||||
{
|
||||
return Environment.GetEnvironmentVariable("BitBadger.Documents.Postgres.DbHost") switch
|
||||
{
|
||||
null => "localhost",
|
||||
var host when host.Trim() == "" => "localhost",
|
||||
var host => host
|
||||
};
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// The port for the database
|
||||
/// </summary>
|
||||
private static readonly Lazy<int> DbPort = new(() =>
|
||||
{
|
||||
return Environment.GetEnvironmentVariable("BitBadger.Documents.Postgres.DbPort") switch
|
||||
{
|
||||
null => 5432,
|
||||
var port when port.Trim() == "" => 5432,
|
||||
var port => int.Parse(port)
|
||||
};
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// The database itself
|
||||
/// </summary>
|
||||
private static readonly Lazy<string> DbDatabase = new(() =>
|
||||
{
|
||||
return Environment.GetEnvironmentVariable("BitBadger.Documents.Postres.DbDatabase") switch
|
||||
{
|
||||
null => "postgres",
|
||||
var db when db.Trim() == "" => "postgres",
|
||||
var db => db
|
||||
};
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// The user to use in connecting to the database
|
||||
/// </summary>
|
||||
private static readonly Lazy<string> DbUser = new(() =>
|
||||
{
|
||||
return Environment.GetEnvironmentVariable("BitBadger.Documents.Postgres.DbUser") switch
|
||||
{
|
||||
null => "postgres",
|
||||
var user when user.Trim() == "" => "postgres",
|
||||
var user => user
|
||||
};
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// The password to use for the database
|
||||
/// </summary>
|
||||
private static readonly Lazy<string> DbPassword = new(() =>
|
||||
{
|
||||
return Environment.GetEnvironmentVariable("BitBadger.Documents.Postrgres.DbPwd") switch
|
||||
{
|
||||
null => "postgres",
|
||||
var pwd when pwd.Trim() == "" => "postgres",
|
||||
var pwd => pwd
|
||||
};
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// The overall connection string
|
||||
/// </summary>
|
||||
public static readonly Lazy<string> ConnStr = new(() =>
|
||||
Sql.formatConnectionString(
|
||||
Sql.password(DbPassword.Value,
|
||||
Sql.username(DbUser.Value,
|
||||
Sql.database(DbDatabase.Value,
|
||||
Sql.port(DbPort.Value,
|
||||
Sql.host(DbHost.Value)))))));
|
||||
|
||||
/// <summary>
|
||||
/// Create a data source using the derived connection string
|
||||
/// </summary>
|
||||
public static NpgsqlDataSource MkDataSource(string cStr) =>
|
||||
new NpgsqlDataSourceBuilder(cStr).Build();
|
||||
|
||||
/// <summary>
|
||||
/// Build the throwaway database
|
||||
/// </summary>
|
||||
public static ThrowawayPostgresDb BuildDb()
|
||||
{
|
||||
var database = ThrowawayDatabase.Create(ConnStr.Value);
|
||||
|
||||
var sqlProps = Sql.connect(database.ConnectionString);
|
||||
|
||||
Sql.executeNonQuery(Sql.query(Postgres.Query.Definition.EnsureTable(TableName), sqlProps));
|
||||
Sql.executeNonQuery(Sql.query(Query.Definition.EnsureKey(TableName), sqlProps));
|
||||
|
||||
Postgres.Configuration.UseDataSource(MkDataSource(database.ConnectionString));
|
||||
|
||||
return new ThrowawayPostgresDb(database);
|
||||
}
|
||||
}
|
||||
518
src/Tests.CSharp/SqliteCSharpExtensionTests.cs
Normal file
518
src/Tests.CSharp/SqliteCSharpExtensionTests.cs
Normal file
@@ -0,0 +1,518 @@
|
||||
using Expecto.CSharp;
|
||||
using Expecto;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using BitBadger.Documents.Sqlite;
|
||||
|
||||
namespace BitBadger.Documents.Tests.CSharp;
|
||||
|
||||
using static Runner;
|
||||
|
||||
/// <summary>
|
||||
/// C# tests for the extensions on the <tt>SqliteConnection</tt> class
|
||||
/// </summary>
|
||||
public static class SqliteCSharpExtensionTests
|
||||
{
|
||||
private static Task LoadDocs() => SqliteCSharpTests.LoadDocs();
|
||||
|
||||
/// <summary>
|
||||
/// Integration tests for the SQLite extension methods
|
||||
/// </summary>
|
||||
[Tests]
|
||||
public static readonly Test Integration = TestList("Sqlite.C#.Extensions", new[]
|
||||
{
|
||||
TestList("CustomSingle", new[]
|
||||
{
|
||||
TestCase("succeeds when a row is found", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await using var conn = Sqlite.Configuration.DbConn();
|
||||
await LoadDocs();
|
||||
|
||||
var doc = await conn.CustomSingle($"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'Id' = @id",
|
||||
new[] { Parameters.Id("one") }, Results.FromData<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 = await SqliteDb.BuildDb();
|
||||
await using var conn = Sqlite.Configuration.DbConn();
|
||||
await LoadDocs();
|
||||
|
||||
var doc = await conn.CustomSingle($"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'Id' = @id",
|
||||
new[] { Parameters.Id("eighty") }, Results.FromData<JsonDocument>);
|
||||
Expect.isNull(doc, "There should not have been a document returned");
|
||||
})
|
||||
}),
|
||||
TestList("CustomList", new[]
|
||||
{
|
||||
TestCase("succeeds when data is found", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await using var conn = Sqlite.Configuration.DbConn();
|
||||
await LoadDocs();
|
||||
|
||||
var docs = await conn.CustomList(Query.SelectFromTable(SqliteDb.TableName), Parameters.None,
|
||||
Results.FromData<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 = await SqliteDb.BuildDb();
|
||||
await using var conn = Sqlite.Configuration.DbConn();
|
||||
await LoadDocs();
|
||||
|
||||
var docs = await conn.CustomList(
|
||||
$"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'NumValue' > @value",
|
||||
new[] { new SqliteParameter("@value", 100) }, Results.FromData<JsonDocument>);
|
||||
Expect.isEmpty(docs, "There should have been no documents returned");
|
||||
})
|
||||
}),
|
||||
TestList("CustomNonQuery", new[]
|
||||
{
|
||||
TestCase("succeeds when operating on data", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await using var conn = Sqlite.Configuration.DbConn();
|
||||
await LoadDocs();
|
||||
|
||||
await conn.CustomNonQuery($"DELETE FROM {SqliteDb.TableName}", Parameters.None);
|
||||
|
||||
var remaining = await conn.CountAll(SqliteDb.TableName);
|
||||
Expect.equal(remaining, 0L, "There should be no documents remaining in the table");
|
||||
}),
|
||||
TestCase("succeeds when no data matches where clause", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await using var conn = Sqlite.Configuration.DbConn();
|
||||
await LoadDocs();
|
||||
|
||||
await conn.CustomNonQuery($"DELETE FROM {SqliteDb.TableName} WHERE data ->> 'NumValue' > @value",
|
||||
new[] { new SqliteParameter("@value", 100) });
|
||||
|
||||
var remaining = await conn.CountAll(SqliteDb.TableName);
|
||||
Expect.equal(remaining, 5L, "There should be 5 documents remaining in the table");
|
||||
})
|
||||
}),
|
||||
TestCase("CustomScalar succeeds", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await using var conn = Sqlite.Configuration.DbConn();
|
||||
|
||||
var nbr = await conn.CustomScalar("SELECT 5 AS test_value", Parameters.None, rdr => rdr.GetInt32(0));
|
||||
Expect.equal(nbr, 5, "The query should have returned the number 5");
|
||||
}),
|
||||
TestCase("EnsureTable succeeds", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await using var conn = Sqlite.Configuration.DbConn();
|
||||
|
||||
Func<string, ValueTask<bool>> itExists = async name =>
|
||||
await conn.CustomScalar(
|
||||
$"SELECT EXISTS (SELECT 1 FROM {SqliteDb.Catalog} WHERE name = @name) AS it",
|
||||
new SqliteParameter[] { new("@name", name) }, Results.ToExists);
|
||||
|
||||
var exists = await itExists("ensured");
|
||||
var alsoExists = await itExists("idx_ensured_key");
|
||||
Expect.isFalse(exists, "The table should not exist already");
|
||||
Expect.isFalse(alsoExists, "The key index should not exist already");
|
||||
|
||||
await conn.EnsureTable("ensured");
|
||||
|
||||
exists = await itExists("ensured");
|
||||
alsoExists = await itExists("idx_ensured_key");
|
||||
Expect.isTrue(exists, "The table should now exist");
|
||||
Expect.isTrue(alsoExists, "The key index should now exist");
|
||||
}),
|
||||
TestCase("EnsureFieldIndex succeeds", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await using var conn = Sqlite.Configuration.DbConn();
|
||||
var indexExists = () => conn.CustomScalar(
|
||||
$"SELECT EXISTS (SELECT 1 FROM {SqliteDb.Catalog} WHERE name = 'idx_ensured_test') AS it",
|
||||
Parameters.None, Results.ToExists);
|
||||
|
||||
var exists = await indexExists();
|
||||
Expect.isFalse(exists, "The index should not exist already");
|
||||
|
||||
await conn.EnsureTable("ensured");
|
||||
await conn.EnsureFieldIndex("ensured", "test", new[] { "Id", "Category" });
|
||||
exists = await indexExists();
|
||||
Expect.isTrue(exists, "The index should now exist");
|
||||
}),
|
||||
TestList("Insert", new[]
|
||||
{
|
||||
TestCase("succeeds", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await using var conn = Sqlite.Configuration.DbConn();
|
||||
var before = await conn.FindAll<SubDocument>(SqliteDb.TableName);
|
||||
Expect.isEmpty(before, "There should be no documents in the table");
|
||||
await conn.Insert(SqliteDb.TableName,
|
||||
new JsonDocument { Id = "turkey", Sub = new() { Foo = "gobble", Bar = "gobble" } });
|
||||
var after = await conn.FindAll<JsonDocument>(SqliteDb.TableName);
|
||||
Expect.equal(after.Count, 1, "There should have been one document inserted");
|
||||
}),
|
||||
TestCase("fails for duplicate key", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await using var conn = Sqlite.Configuration.DbConn();
|
||||
await conn.Insert(SqliteDb.TableName, new JsonDocument { Id = "test" });
|
||||
try
|
||||
{
|
||||
await Document.Insert(SqliteDb.TableName, new JsonDocument { Id = "test" });
|
||||
Expect.isTrue(false, "An exception should have been raised for duplicate document ID insert");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// This is what is supposed to happen
|
||||
}
|
||||
})
|
||||
}),
|
||||
TestList("Save", new[]
|
||||
{
|
||||
TestCase("succeeds when a document is inserted", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await using var conn = Sqlite.Configuration.DbConn();
|
||||
var before = await conn.FindAll<JsonDocument>(SqliteDb.TableName);
|
||||
Expect.isEmpty(before, "There should be no documents in the table");
|
||||
|
||||
await conn.Save(SqliteDb.TableName,
|
||||
new JsonDocument { Id = "test", Sub = new() { Foo = "a", Bar = "b" } });
|
||||
var after = await conn.FindAll<JsonDocument>(SqliteDb.TableName);
|
||||
Expect.equal(after.Count, 1, "There should have been one document inserted");
|
||||
}),
|
||||
TestCase("succeeds when a document is updated", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await using var conn = Sqlite.Configuration.DbConn();
|
||||
await conn.Insert(SqliteDb.TableName,
|
||||
new JsonDocument { Id = "test", Sub = new() { Foo = "a", Bar = "b" } });
|
||||
|
||||
var before = await conn.FindById<string, JsonDocument>(SqliteDb.TableName, "test");
|
||||
Expect.isNotNull(before, "There should have been a document returned");
|
||||
Expect.equal(before!.Id, "test", "The document is not correct");
|
||||
Expect.isNotNull(before.Sub, "There should have been a sub-document");
|
||||
Expect.equal(before.Sub!.Foo, "a", "The document is not correct");
|
||||
Expect.equal(before.Sub.Bar, "b", "The document is not correct");
|
||||
|
||||
await conn.Save(SqliteDb.TableName, new JsonDocument { Id = "test" });
|
||||
var after = await conn.FindById<string, JsonDocument>(SqliteDb.TableName, "test");
|
||||
Expect.isNotNull(after, "There should have been a document returned post-update");
|
||||
Expect.equal(after!.Id, "test", "The updated document is not correct");
|
||||
Expect.isNull(after.Sub, "There should not have been a sub-document in the updated document");
|
||||
})
|
||||
}),
|
||||
TestCase("CountAll succeeds", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await using var conn = Sqlite.Configuration.DbConn();
|
||||
await LoadDocs();
|
||||
|
||||
var theCount = await conn.CountAll(SqliteDb.TableName);
|
||||
Expect.equal(theCount, 5L, "There should have been 5 matching documents");
|
||||
}),
|
||||
TestCase("CountByField succeeds", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await using var conn = Sqlite.Configuration.DbConn();
|
||||
await LoadDocs();
|
||||
|
||||
var theCount = await conn.CountByField(SqliteDb.TableName, "Value", Op.EQ, "purple");
|
||||
Expect.equal(theCount, 2L, "There should have been 2 matching documents");
|
||||
}),
|
||||
TestList("ExistsById", new[]
|
||||
{
|
||||
TestCase("succeeds when a document exists", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await using var conn = Sqlite.Configuration.DbConn();
|
||||
await LoadDocs();
|
||||
|
||||
var exists = await conn.ExistsById(SqliteDb.TableName, "three");
|
||||
Expect.isTrue(exists, "There should have been an existing document");
|
||||
}),
|
||||
TestCase("succeeds when a document does not exist", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await using var conn = Sqlite.Configuration.DbConn();
|
||||
await LoadDocs();
|
||||
|
||||
var exists = await conn.ExistsById(SqliteDb.TableName, "seven");
|
||||
Expect.isFalse(exists, "There should not have been an existing document");
|
||||
})
|
||||
}),
|
||||
TestList("ExistsByField", new[]
|
||||
{
|
||||
TestCase("succeeds when documents exist", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await using var conn = Sqlite.Configuration.DbConn();
|
||||
await LoadDocs();
|
||||
|
||||
var exists = await conn.ExistsByField(SqliteDb.TableName, "NumValue", Op.GE, 10);
|
||||
Expect.isTrue(exists, "There should have been existing documents");
|
||||
}),
|
||||
TestCase("succeeds when no matching documents exist", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await using var conn = Sqlite.Configuration.DbConn();
|
||||
await LoadDocs();
|
||||
|
||||
var exists = await conn.ExistsByField(SqliteDb.TableName, "Nothing", Op.EQ, "none");
|
||||
Expect.isFalse(exists, "There should not have been any existing documents");
|
||||
})
|
||||
}),
|
||||
TestList("FindAll", new[]
|
||||
{
|
||||
TestCase("succeeds when there is data", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await using var conn = Sqlite.Configuration.DbConn();
|
||||
|
||||
await conn.Insert(SqliteDb.TableName, new JsonDocument { Id = "one", Value = "two" });
|
||||
await conn.Insert(SqliteDb.TableName, new JsonDocument { Id = "three", Value = "four" });
|
||||
await conn.Insert(SqliteDb.TableName, new JsonDocument { Id = "five", Value = "six" });
|
||||
|
||||
var results = await conn.FindAll<JsonDocument>(SqliteDb.TableName);
|
||||
Expect.equal(results.Count, 3, "There should have been 3 documents returned");
|
||||
}),
|
||||
TestCase("succeeds when there is no data", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await using var conn = Sqlite.Configuration.DbConn();
|
||||
var results = await conn.FindAll<JsonDocument>(SqliteDb.TableName);
|
||||
Expect.isEmpty(results, "There should have been no documents returned");
|
||||
})
|
||||
}),
|
||||
TestList("FindById", new[]
|
||||
{
|
||||
TestCase("succeeds when a document is found", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await using var conn = Sqlite.Configuration.DbConn();
|
||||
await LoadDocs();
|
||||
|
||||
var doc = await conn.FindById<string, JsonDocument>(SqliteDb.TableName, "two");
|
||||
Expect.isNotNull(doc, "There should have been a document returned");
|
||||
Expect.equal(doc!.Id, "two", "The incorrect document was returned");
|
||||
}),
|
||||
TestCase("succeeds when a document is not found", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await using var conn = Sqlite.Configuration.DbConn();
|
||||
await LoadDocs();
|
||||
|
||||
var doc = await conn.FindById<string, JsonDocument>(SqliteDb.TableName, "eighty-seven");
|
||||
Expect.isNull(doc, "There should not have been a document returned");
|
||||
})
|
||||
}),
|
||||
TestList("FindByField", new[]
|
||||
{
|
||||
TestCase("succeeds when documents are found", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await using var conn = Sqlite.Configuration.DbConn();
|
||||
await LoadDocs();
|
||||
|
||||
var docs = await conn.FindByField<JsonDocument>(SqliteDb.TableName, "NumValue", Op.GT, 15);
|
||||
Expect.equal(docs.Count, 2, "There should have been two documents returned");
|
||||
}),
|
||||
TestCase("succeeds when documents are not found", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await using var conn = Sqlite.Configuration.DbConn();
|
||||
await LoadDocs();
|
||||
|
||||
var docs = await conn.FindByField<JsonDocument>(SqliteDb.TableName, "Value", Op.EQ, "mauve");
|
||||
Expect.isEmpty(docs, "There should have been no documents returned");
|
||||
})
|
||||
}),
|
||||
TestList("FindFirstByField", new[]
|
||||
{
|
||||
TestCase("succeeds when a document is found", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await using var conn = Sqlite.Configuration.DbConn();
|
||||
await LoadDocs();
|
||||
|
||||
var doc = await conn.FindFirstByField<JsonDocument>(SqliteDb.TableName, "Value", Op.EQ, "another");
|
||||
Expect.isNotNull(doc, "There should have been a document returned");
|
||||
Expect.equal(doc!.Id, "two", "The incorrect document was returned");
|
||||
}),
|
||||
TestCase("succeeds when multiple documents are found", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await using var conn = Sqlite.Configuration.DbConn();
|
||||
await LoadDocs();
|
||||
|
||||
var doc = await conn.FindFirstByField<JsonDocument>(SqliteDb.TableName, "Sub.Foo", Op.EQ, "green");
|
||||
Expect.isNotNull(doc, "There should have been a document returned");
|
||||
Expect.contains(new[] { "two", "four" }, doc!.Id, "An incorrect document was returned");
|
||||
}),
|
||||
TestCase("succeeds when a document is not found", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await using var conn = Sqlite.Configuration.DbConn();
|
||||
await LoadDocs();
|
||||
|
||||
var doc = await conn.FindFirstByField<JsonDocument>(SqliteDb.TableName, "Value", Op.EQ, "absent");
|
||||
Expect.isNull(doc, "There should not have been a document returned");
|
||||
})
|
||||
}),
|
||||
TestList("UpdateById", new[]
|
||||
{
|
||||
TestCase("succeeds when a document is updated", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await using var conn = Sqlite.Configuration.DbConn();
|
||||
await LoadDocs();
|
||||
|
||||
var testDoc = new JsonDocument { Id = "one", Sub = new() { Foo = "blue", Bar = "red" } };
|
||||
await conn.UpdateById(SqliteDb.TableName, "one", testDoc);
|
||||
var after = await conn.FindById<string, JsonDocument>(SqliteDb.TableName, "one");
|
||||
Expect.isNotNull(after, "There should have been a document returned post-update");
|
||||
Expect.equal(after.Id, "one", "The updated document is not correct");
|
||||
Expect.isNotNull(after.Sub, "The updated document should have had a sub-document");
|
||||
Expect.equal(after.Sub!.Foo, "blue", "The updated sub-document is not correct");
|
||||
Expect.equal(after.Sub.Bar, "red", "The updated sub-document is not correct");
|
||||
}),
|
||||
TestCase("succeeds when no document is updated", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await using var conn = Sqlite.Configuration.DbConn();
|
||||
var before = await conn.FindAll<JsonDocument>(SqliteDb.TableName);
|
||||
Expect.isEmpty(before, "There should have been no documents returned");
|
||||
|
||||
// This not raising an exception is the test
|
||||
await conn.UpdateById(SqliteDb.TableName, "test",
|
||||
new JsonDocument { Id = "x", Sub = new() { Foo = "blue", Bar = "red" } });
|
||||
})
|
||||
}),
|
||||
TestList("UpdateByFunc", new[]
|
||||
{
|
||||
TestCase("succeeds when a document is updated", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await using var conn = Sqlite.Configuration.DbConn();
|
||||
await LoadDocs();
|
||||
|
||||
await conn.UpdateByFunc(SqliteDb.TableName, doc => doc.Id,
|
||||
new JsonDocument { Id = "one", Value = "le un", NumValue = 1 });
|
||||
var after = await conn.FindById<string, JsonDocument>(SqliteDb.TableName, "one");
|
||||
Expect.isNotNull(after, "There should have been a document returned post-update");
|
||||
Expect.equal(after.Id, "one", "The updated document is incorrect");
|
||||
Expect.equal(after.Value, "le un", "The updated document is incorrect");
|
||||
Expect.equal(after.NumValue, 1, "The updated document is incorrect");
|
||||
Expect.isNull(after.Sub, "The updated document should not have a sub-document");
|
||||
}),
|
||||
TestCase("succeeds when no document is updated", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await using var conn = Sqlite.Configuration.DbConn();
|
||||
var before = await conn.FindAll<JsonDocument>(SqliteDb.TableName);
|
||||
Expect.isEmpty(before, "There should have been no documents returned");
|
||||
|
||||
// This not raising an exception is the test
|
||||
await conn.UpdateByFunc(SqliteDb.TableName, doc => doc.Id,
|
||||
new JsonDocument { Id = "one", Value = "le un", NumValue = 1 });
|
||||
})
|
||||
}),
|
||||
TestList("PatchById", new[]
|
||||
{
|
||||
TestCase("succeeds when a document is updated", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await using var conn = Sqlite.Configuration.DbConn();
|
||||
await LoadDocs();
|
||||
|
||||
await conn.PatchById(SqliteDb.TableName, "one", new { NumValue = 44 });
|
||||
var after = await conn.FindById<string, JsonDocument>(SqliteDb.TableName, "one");
|
||||
Expect.isNotNull(after, "There should have been a document returned post-update");
|
||||
Expect.equal(after.Id, "one", "The updated document is not correct");
|
||||
Expect.equal(after.NumValue, 44, "The updated document is not correct");
|
||||
}),
|
||||
TestCase("succeeds when no document is updated", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await using var conn = Sqlite.Configuration.DbConn();
|
||||
var before = await conn.FindAll<JsonDocument>(SqliteDb.TableName);
|
||||
Expect.isEmpty(before, "There should have been no documents returned");
|
||||
|
||||
// This not raising an exception is the test
|
||||
await conn.PatchById(SqliteDb.TableName, "test", new { Foo = "green" });
|
||||
})
|
||||
}),
|
||||
TestList("PatchByField", new[]
|
||||
{
|
||||
TestCase("succeeds when a document is updated", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await using var conn = Sqlite.Configuration.DbConn();
|
||||
await LoadDocs();
|
||||
|
||||
await conn.PatchByField(SqliteDb.TableName, "Value", Op.EQ, "purple", new { NumValue = 77 });
|
||||
var after = await conn.CountByField(SqliteDb.TableName, "NumValue", Op.EQ, 77);
|
||||
Expect.equal(after, 2L, "There should have been 2 documents returned");
|
||||
}),
|
||||
TestCase("succeeds when no document is updated", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await using var conn = Sqlite.Configuration.DbConn();
|
||||
var before = await conn.FindAll<JsonDocument>(SqliteDb.TableName);
|
||||
Expect.isEmpty(before, "There should have been no documents returned");
|
||||
|
||||
// This not raising an exception is the test
|
||||
await conn.PatchByField(SqliteDb.TableName, "Value", Op.EQ, "burgundy", new { Foo = "green" });
|
||||
})
|
||||
}),
|
||||
TestList("DeleteById", new[]
|
||||
{
|
||||
TestCase("succeeds when a document is deleted", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await using var conn = Sqlite.Configuration.DbConn();
|
||||
await LoadDocs();
|
||||
|
||||
await conn.DeleteById(SqliteDb.TableName, "four");
|
||||
var remaining = await conn.CountAll(SqliteDb.TableName);
|
||||
Expect.equal(remaining, 4L, "There should have been 4 documents remaining");
|
||||
}),
|
||||
TestCase("succeeds when a document is not deleted", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await using var conn = Sqlite.Configuration.DbConn();
|
||||
await LoadDocs();
|
||||
|
||||
await conn.DeleteById(SqliteDb.TableName, "thirty");
|
||||
var remaining = await conn.CountAll(SqliteDb.TableName);
|
||||
Expect.equal(remaining, 5L, "There should have been 5 documents remaining");
|
||||
})
|
||||
}),
|
||||
TestList("DeleteByField", new[]
|
||||
{
|
||||
TestCase("succeeds when documents are deleted", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await using var conn = Sqlite.Configuration.DbConn();
|
||||
await LoadDocs();
|
||||
|
||||
await conn.DeleteByField(SqliteDb.TableName, "Value", Op.NE, "purple");
|
||||
var remaining = await conn.CountAll(SqliteDb.TableName);
|
||||
Expect.equal(remaining, 2L, "There should have been 2 documents remaining");
|
||||
}),
|
||||
TestCase("succeeds when documents are not deleted", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await using var conn = Sqlite.Configuration.DbConn();
|
||||
await LoadDocs();
|
||||
|
||||
await conn.DeleteByField(SqliteDb.TableName, "Value", Op.EQ, "crimson");
|
||||
var remaining = await conn.CountAll(SqliteDb.TableName);
|
||||
Expect.equal(remaining, 5L, "There should have been 5 documents remaining");
|
||||
})
|
||||
}),
|
||||
TestCase("Clean up database", () => Sqlite.Configuration.UseConnectionString("data source=:memory:"))
|
||||
});
|
||||
}
|
||||
596
src/Tests.CSharp/SqliteCSharpTests.cs
Normal file
596
src/Tests.CSharp/SqliteCSharpTests.cs
Normal file
@@ -0,0 +1,596 @@
|
||||
using Expecto.CSharp;
|
||||
using Expecto;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.FSharp.Core;
|
||||
using BitBadger.Documents.Sqlite;
|
||||
|
||||
namespace BitBadger.Documents.Tests.CSharp;
|
||||
|
||||
using static Runner;
|
||||
|
||||
/// <summary>
|
||||
/// C# tests for the SQLite implementation of <tt>BitBadger.Documents</tt>
|
||||
/// </summary>
|
||||
public static class SqliteCSharpTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Unit tests for the SQLite library
|
||||
/// </summary>
|
||||
private static readonly Test Unit = TestList("Unit", new[]
|
||||
{
|
||||
TestList("Query", new[]
|
||||
{
|
||||
TestCase("Definition.EnsureTable succeeds", () =>
|
||||
{
|
||||
Expect.equal(Sqlite.Query.Definition.EnsureTable("tbl"),
|
||||
"CREATE TABLE IF NOT EXISTS tbl (data TEXT NOT NULL)", "CREATE TABLE statement not correct");
|
||||
}),
|
||||
TestList("Patch", new[]
|
||||
{
|
||||
TestCase("ById succeeds", () =>
|
||||
{
|
||||
Expect.equal(Sqlite.Query.Patch.ById("tbl"),
|
||||
"UPDATE tbl SET data = json_patch(data, json(@data)) WHERE data ->> 'Id' = @id",
|
||||
"UPDATE partial by ID statement not correct");
|
||||
}),
|
||||
TestCase("ByField succeeds", () =>
|
||||
{
|
||||
Expect.equal(Sqlite.Query.Patch.ByField("tbl", "Part", Op.NE),
|
||||
"UPDATE tbl SET data = json_patch(data, json(@data)) WHERE data ->> 'Part' <> @field",
|
||||
"UPDATE partial by JSON comparison query not correct");
|
||||
})
|
||||
}),
|
||||
}),
|
||||
TestList("Parameters", new[]
|
||||
{
|
||||
TestCase("Id succeeds", () =>
|
||||
{
|
||||
var theParam = Parameters.Id(7);
|
||||
Expect.equal(theParam.ParameterName, "@id", "The parameter name is incorrect");
|
||||
Expect.equal(theParam.Value, "7", "The parameter value is incorrect");
|
||||
}),
|
||||
TestCase("Json succeeds", () =>
|
||||
{
|
||||
var theParam = Parameters.Json("@test", new { Nice = "job" });
|
||||
Expect.equal(theParam.ParameterName, "@test", "The parameter name is incorrect");
|
||||
Expect.equal(theParam.Value, "{\"Nice\":\"job\"}", "The parameter value is incorrect");
|
||||
}),
|
||||
TestCase("Field succeeds", () =>
|
||||
{
|
||||
var theParam = Parameters.Field(99);
|
||||
Expect.equal(theParam.ParameterName, "@field", "The parameter name is incorrect");
|
||||
Expect.equal(theParam.Value, 99, "The parameter value is incorrect");
|
||||
}),
|
||||
TestCase("None succeeds", () =>
|
||||
{
|
||||
Expect.isEmpty(Parameters.None, "The parameter list should have been empty");
|
||||
})
|
||||
})
|
||||
// Results are exhaustively executed in the context of other tests
|
||||
});
|
||||
|
||||
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 }
|
||||
};
|
||||
|
||||
/// <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);
|
||||
}
|
||||
|
||||
private static readonly Test Integration = TestList("Integration", new[]
|
||||
{
|
||||
TestCase("Configuration.UseConnectionString succeeds", () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Sqlite.Configuration.UseConnectionString("Data Source=test.db");
|
||||
Expect.equal(Sqlite.Configuration.connectionString,
|
||||
new FSharpOption<string>("Data Source=test.db;Foreign Keys=True"), "Connection string incorrect");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Sqlite.Configuration.UseConnectionString("Data Source=:memory:");
|
||||
}
|
||||
}),
|
||||
TestList("Custom", new[]
|
||||
{
|
||||
TestList("Single", new[]
|
||||
{
|
||||
TestCase("succeeds when a row is found", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
var doc = await Custom.Single($"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'Id' = @id",
|
||||
new[] { Parameters.Id("one") }, Results.FromData<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 = await SqliteDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
var doc = await Custom.Single($"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'Id' = @id",
|
||||
new[] { Parameters.Id("eighty") }, Results.FromData<JsonDocument>);
|
||||
Expect.isNull(doc, "There should not have been a document returned");
|
||||
})
|
||||
}),
|
||||
TestList("List", new[]
|
||||
{
|
||||
TestCase("succeeds when data is found", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
var docs = await Custom.List(Query.SelectFromTable(SqliteDb.TableName), Parameters.None,
|
||||
Results.FromData<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 = await SqliteDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
var docs = await Custom.List(
|
||||
$"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'NumValue' > @value",
|
||||
new[] { new SqliteParameter("@value", 100) }, Results.FromData<JsonDocument>);
|
||||
Expect.isEmpty(docs, "There should have been no documents returned");
|
||||
})
|
||||
}),
|
||||
TestList("NonQuery", new[]
|
||||
{
|
||||
TestCase("succeeds when operating on data", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
await Custom.NonQuery($"DELETE FROM {SqliteDb.TableName}", Parameters.None);
|
||||
|
||||
var remaining = await Count.All(SqliteDb.TableName);
|
||||
Expect.equal(remaining, 0L, "There should be no documents remaining in the table");
|
||||
}),
|
||||
TestCase("succeeds when no data matches where clause", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
await Custom.NonQuery($"DELETE FROM {SqliteDb.TableName} WHERE data ->> 'NumValue' > @value",
|
||||
new[] { new SqliteParameter("@value", 100) });
|
||||
|
||||
var remaining = await Count.All(SqliteDb.TableName);
|
||||
Expect.equal(remaining, 5L, "There should be 5 documents remaining in the table");
|
||||
})
|
||||
}),
|
||||
TestCase("Scalar succeeds", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
|
||||
var nbr = await Custom.Scalar("SELECT 5 AS test_value", Parameters.None, rdr => rdr.GetInt32(0));
|
||||
Expect.equal(nbr, 5, "The query should have returned the number 5");
|
||||
})
|
||||
}),
|
||||
TestList("Definition", new[]
|
||||
{
|
||||
TestCase("EnsureTable succeeds", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
|
||||
var exists = await ItExists("ensured");
|
||||
var alsoExists = await ItExists("idx_ensured_key");
|
||||
Expect.isFalse(exists, "The table should not exist already");
|
||||
Expect.isFalse(alsoExists, "The key index should not exist already");
|
||||
|
||||
await Definition.EnsureTable("ensured");
|
||||
|
||||
exists = await ItExists("ensured");
|
||||
alsoExists = await ItExists("idx_ensured_key");
|
||||
Expect.isTrue(exists, "The table should now exist");
|
||||
Expect.isTrue(alsoExists, "The key index should now exist");
|
||||
return;
|
||||
|
||||
async ValueTask<bool> ItExists(string name)
|
||||
{
|
||||
return await Custom.Scalar(
|
||||
$"SELECT EXISTS (SELECT 1 FROM {SqliteDb.Catalog} WHERE name = @name) AS it",
|
||||
new SqliteParameter[] { new("@name", name) }, Results.ToExists);
|
||||
}
|
||||
}),
|
||||
TestCase("EnsureFieldIndex succeeds", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
var indexExists = () => Custom.Scalar(
|
||||
$"SELECT EXISTS (SELECT 1 FROM {SqliteDb.Catalog} WHERE name = 'idx_ensured_test') AS it",
|
||||
Parameters.None, Results.ToExists);
|
||||
|
||||
var exists = await indexExists();
|
||||
Expect.isFalse(exists, "The index should not exist already");
|
||||
|
||||
await Definition.EnsureTable("ensured");
|
||||
await Definition.EnsureFieldIndex("ensured", "test", new[] { "Id", "Category" });
|
||||
exists = await indexExists();
|
||||
Expect.isTrue(exists, "The index should now exist");
|
||||
})
|
||||
}),
|
||||
TestList("Document.Insert", new[]
|
||||
{
|
||||
TestCase("succeeds", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
var before = await Find.All<SubDocument>(SqliteDb.TableName);
|
||||
Expect.isEmpty(before, "There should be no documents in the table");
|
||||
await Document.Insert(SqliteDb.TableName,
|
||||
new JsonDocument { Id = "turkey", Sub = new() { Foo = "gobble", Bar = "gobble" } });
|
||||
var after = await Find.All<JsonDocument>(SqliteDb.TableName);
|
||||
Expect.equal(after.Count, 1, "There should have been one document inserted");
|
||||
}),
|
||||
TestCase("fails for duplicate key", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await Document.Insert(SqliteDb.TableName, new JsonDocument { Id = "test" });
|
||||
try
|
||||
{
|
||||
await Document.Insert(SqliteDb.TableName, new JsonDocument { Id = "test" });
|
||||
Expect.isTrue(false, "An exception should have been raised for duplicate document ID insert");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// This is what is supposed to happen
|
||||
}
|
||||
})
|
||||
}),
|
||||
TestList("Document.Save", new[]
|
||||
{
|
||||
TestCase("succeeds when a document is inserted", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
var before = await Find.All<JsonDocument>(SqliteDb.TableName);
|
||||
Expect.isEmpty(before, "There should be no documents in the table");
|
||||
|
||||
await Document.Save(SqliteDb.TableName,
|
||||
new JsonDocument { Id = "test", Sub = new() { Foo = "a", Bar = "b" } });
|
||||
var after = await Find.All<JsonDocument>(SqliteDb.TableName);
|
||||
Expect.equal(after.Count, 1, "There should have been one document inserted");
|
||||
}),
|
||||
TestCase("succeeds when a document is updated", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await Document.Insert(SqliteDb.TableName,
|
||||
new JsonDocument { Id = "test", Sub = new() { Foo = "a", Bar = "b" } });
|
||||
|
||||
var before = await Find.ById<string, JsonDocument>(SqliteDb.TableName, "test");
|
||||
Expect.isNotNull(before, "There should have been a document returned");
|
||||
Expect.equal(before!.Id, "test", "The document is not correct");
|
||||
Expect.isNotNull(before.Sub, "There should have been a sub-document");
|
||||
Expect.equal(before.Sub!.Foo, "a", "The document is not correct");
|
||||
Expect.equal(before.Sub.Bar, "b", "The document is not correct");
|
||||
|
||||
await Document.Save(SqliteDb.TableName, new JsonDocument { Id = "test" });
|
||||
var after = await Find.ById<string, JsonDocument>(SqliteDb.TableName, "test");
|
||||
Expect.isNotNull(after, "There should have been a document returned post-update");
|
||||
Expect.equal(after!.Id, "test", "The updated document is not correct");
|
||||
Expect.isNull(after.Sub, "There should not have been a sub-document in the updated document");
|
||||
})
|
||||
}),
|
||||
TestList("Count", new[]
|
||||
{
|
||||
TestCase("All succeeds", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
var theCount = await Count.All(SqliteDb.TableName);
|
||||
Expect.equal(theCount, 5L, "There should have been 5 matching documents");
|
||||
}),
|
||||
TestCase("ByField succeeds", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
var theCount = await Count.ByField(SqliteDb.TableName, "Value", Op.EQ, "purple");
|
||||
Expect.equal(theCount, 2L, "There should have been 2 matching documents");
|
||||
})
|
||||
}),
|
||||
TestList("Exists", new[]
|
||||
{
|
||||
TestList("ById", new[]
|
||||
{
|
||||
TestCase("succeeds when a document exists", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
var exists = await Exists.ById(SqliteDb.TableName, "three");
|
||||
Expect.isTrue(exists, "There should have been an existing document");
|
||||
}),
|
||||
TestCase("succeeds when a document does not exist", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
var exists = await Exists.ById(SqliteDb.TableName, "seven");
|
||||
Expect.isFalse(exists, "There should not have been an existing document");
|
||||
})
|
||||
}),
|
||||
TestList("ByField", new[]
|
||||
{
|
||||
TestCase("succeeds when documents exist", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
var exists = await Exists.ByField(SqliteDb.TableName, "NumValue", Op.GE, 10);
|
||||
Expect.isTrue(exists, "There should have been existing documents");
|
||||
}),
|
||||
TestCase("succeeds when no matching documents exist", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
var exists = await Exists.ByField(SqliteDb.TableName, "Nothing", Op.EQ, "none");
|
||||
Expect.isFalse(exists, "There should not have been any existing documents");
|
||||
})
|
||||
})
|
||||
}),
|
||||
TestList("Find", new[]
|
||||
{
|
||||
TestList("All", new[]
|
||||
{
|
||||
TestCase("succeeds when there is data", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
|
||||
await Document.Insert(SqliteDb.TableName, new JsonDocument { Id = "one", Value = "two" });
|
||||
await Document.Insert(SqliteDb.TableName, new JsonDocument { Id = "three", Value = "four" });
|
||||
await Document.Insert(SqliteDb.TableName, new JsonDocument { Id = "five", Value = "six" });
|
||||
|
||||
var results = await Find.All<JsonDocument>(SqliteDb.TableName);
|
||||
Expect.equal(results.Count, 3, "There should have been 3 documents returned");
|
||||
}),
|
||||
TestCase("succeeds when there is no data", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
var results = await Find.All<SubDocument>(SqliteDb.TableName);
|
||||
Expect.isEmpty(results, "There should have been no documents returned");
|
||||
})
|
||||
}),
|
||||
TestList("ById", new[]
|
||||
{
|
||||
TestCase("succeeds when a document is found", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
var doc = await Find.ById<string, JsonDocument>(SqliteDb.TableName, "two");
|
||||
Expect.isNotNull(doc, "There should have been a document returned");
|
||||
Expect.equal(doc!.Id, "two", "The incorrect document was returned");
|
||||
}),
|
||||
TestCase("succeeds when a document is not found", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
var doc = await Find.ById<string, JsonDocument>(SqliteDb.TableName, "twenty two");
|
||||
Expect.isNull(doc, "There should not have been a document returned");
|
||||
})
|
||||
}),
|
||||
TestList("ByField", new[]
|
||||
{
|
||||
TestCase("succeeds when documents are found", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
var docs = await Find.ByField<JsonDocument>(SqliteDb.TableName, "NumValue", Op.GT, 15);
|
||||
Expect.equal(docs.Count, 2, "There should have been two documents returned");
|
||||
}),
|
||||
TestCase("succeeds when documents are not found", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
var docs = await Find.ByField<JsonDocument>(SqliteDb.TableName, "Value", Op.EQ, "mauve");
|
||||
Expect.isEmpty(docs, "There should have been no documents returned");
|
||||
})
|
||||
}),
|
||||
TestList("FirstByField", new[]
|
||||
{
|
||||
TestCase("succeeds when a document is found", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
var doc = await Find.FirstByField<JsonDocument>(SqliteDb.TableName, "Value", Op.EQ, "another");
|
||||
Expect.isNotNull(doc, "There should have been a document returned");
|
||||
Expect.equal(doc!.Id, "two", "The incorrect document was returned");
|
||||
}),
|
||||
TestCase("succeeds when multiple documents are found", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
var doc = await Find.FirstByField<JsonDocument>(SqliteDb.TableName, "Sub.Foo", Op.EQ, "green");
|
||||
Expect.isNotNull(doc, "There should have been a document returned");
|
||||
Expect.contains(new[] { "two", "four" }, doc!.Id, "An incorrect document was returned");
|
||||
}),
|
||||
TestCase("succeeds when a document is not found", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
var doc = await Find.FirstByField<JsonDocument>(SqliteDb.TableName, "Value", Op.EQ, "absent");
|
||||
Expect.isNull(doc, "There should not have been a document returned");
|
||||
})
|
||||
})
|
||||
}),
|
||||
TestList("Update", new[]
|
||||
{
|
||||
TestList("ById", new[]
|
||||
{
|
||||
TestCase("succeeds when a document is updated", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
var testDoc = new JsonDocument { Id = "one", Sub = new() { Foo = "blue", Bar = "red" } };
|
||||
await Update.ById(SqliteDb.TableName, "one", testDoc);
|
||||
var after = await Find.ById<string, JsonDocument>(SqliteDb.TableName, "one");
|
||||
Expect.isNotNull(after, "There should have been a document returned post-update");
|
||||
Expect.equal(after!.Id, "one", "The updated document is not correct");
|
||||
Expect.isNotNull(after.Sub, "The updated document should have had a sub-document");
|
||||
Expect.equal(after.Sub!.Foo, "blue", "The updated sub-document is not correct");
|
||||
Expect.equal(after.Sub.Bar, "red", "The updated sub-document is not correct");
|
||||
}),
|
||||
TestCase("succeeds when no document is updated", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
|
||||
var before = await Find.All<JsonDocument>(SqliteDb.TableName);
|
||||
Expect.isEmpty(before, "There should have been no documents returned");
|
||||
|
||||
// This not raising an exception is the test
|
||||
await Update.ById(SqliteDb.TableName, "test",
|
||||
new JsonDocument { Id = "x", Sub = new() { Foo = "blue", Bar = "red" } });
|
||||
})
|
||||
}),
|
||||
TestList("ByFunc", new[]
|
||||
{
|
||||
TestCase("succeeds when a document is updated", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
await Update.ByFunc(SqliteDb.TableName, doc => doc.Id,
|
||||
new JsonDocument { Id = "one", Value = "le un", NumValue = 1 });
|
||||
var after = await Find.ById<string, JsonDocument>(SqliteDb.TableName, "one");
|
||||
Expect.isNotNull(after, "There should have been a document returned post-update");
|
||||
Expect.equal(after!.Id, "one", "The updated document is incorrect");
|
||||
Expect.equal(after.Value, "le un", "The updated document is incorrect");
|
||||
Expect.equal(after.NumValue, 1, "The updated document is incorrect");
|
||||
Expect.isNull(after.Sub, "The updated document should not have a sub-document");
|
||||
}),
|
||||
TestCase("succeeds when no document is updated", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
|
||||
var before = await Find.All<JsonDocument>(SqliteDb.TableName);
|
||||
Expect.isEmpty(before, "There should have been no documents returned");
|
||||
|
||||
// This not raising an exception is the test
|
||||
await Update.ByFunc(SqliteDb.TableName, doc => doc.Id,
|
||||
new JsonDocument { Id = "one", Value = "le un", NumValue = 1 });
|
||||
})
|
||||
}),
|
||||
}),
|
||||
TestList("Patch", new[]
|
||||
{
|
||||
TestList("ById", new[]
|
||||
{
|
||||
TestCase("succeeds when a document is updated", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
await Patch.ById(SqliteDb.TableName, "one", new { NumValue = 44 });
|
||||
var after = await Find.ById<string, JsonDocument>(SqliteDb.TableName, "one");
|
||||
Expect.isNotNull(after, "There should have been a document returned post-update");
|
||||
Expect.equal(after!.Id, "one", "The updated document is not correct");
|
||||
Expect.equal(after.NumValue, 44, "The updated document is not correct");
|
||||
}),
|
||||
TestCase("succeeds when no document is updated", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
|
||||
var before = await Find.All<JsonDocument>(SqliteDb.TableName);
|
||||
Expect.isEmpty(before, "There should have been no documents returned");
|
||||
|
||||
// This not raising an exception is the test
|
||||
await Patch.ById(SqliteDb.TableName, "test", new { Foo = "green" });
|
||||
})
|
||||
}),
|
||||
TestList("ByField", new[]
|
||||
{
|
||||
TestCase("succeeds when a document is updated", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
await Patch.ByField(SqliteDb.TableName, "Value", Op.EQ, "purple", new { NumValue = 77 });
|
||||
var after = await Count.ByField(SqliteDb.TableName, "NumValue", Op.EQ, 77);
|
||||
Expect.equal(after, 2L, "There should have been 2 documents returned");
|
||||
}),
|
||||
TestCase("succeeds when no document is updated", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
|
||||
var before = await Find.All<SubDocument>(SqliteDb.TableName);
|
||||
Expect.isEmpty(before, "There should have been no documents returned");
|
||||
|
||||
// This not raising an exception is the test
|
||||
await Patch.ByField(SqliteDb.TableName, "Value", Op.EQ, "burgundy", new { Foo = "green" });
|
||||
})
|
||||
})
|
||||
}),
|
||||
TestList("Delete", new[]
|
||||
{
|
||||
TestList("ById", new[]
|
||||
{
|
||||
TestCase("succeeds when a document is deleted", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
await Delete.ById(SqliteDb.TableName, "four");
|
||||
var remaining = await Count.All(SqliteDb.TableName);
|
||||
Expect.equal(remaining, 4L, "There should have been 4 documents remaining");
|
||||
}),
|
||||
TestCase("succeeds when a document is not deleted", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
await Delete.ById(SqliteDb.TableName, "thirty");
|
||||
var remaining = await Count.All(SqliteDb.TableName);
|
||||
Expect.equal(remaining, 5L, "There should have been 5 documents remaining");
|
||||
})
|
||||
}),
|
||||
TestList("ByField", new[]
|
||||
{
|
||||
TestCase("succeeds when documents are deleted", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
await Delete.ByField(SqliteDb.TableName, "Value", Op.NE, "purple");
|
||||
var remaining = await Count.All(SqliteDb.TableName);
|
||||
Expect.equal(remaining, 2L, "There should have been 2 documents remaining");
|
||||
}),
|
||||
TestCase("succeeds when documents are not deleted", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
await LoadDocs();
|
||||
|
||||
await Delete.ByField(SqliteDb.TableName, "Value", Op.EQ, "crimson");
|
||||
var remaining = await Count.All(SqliteDb.TableName);
|
||||
Expect.equal(remaining, 5L, "There should have been 5 documents remaining");
|
||||
})
|
||||
})
|
||||
}),
|
||||
TestCase("Clean up database", () => Sqlite.Configuration.UseConnectionString("data source=:memory:"))
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// All tests for SQLite C# functions and methods
|
||||
/// </summary>
|
||||
[Tests]
|
||||
public static readonly Test All = TestList("Sqlite.C#", new[] { Unit, TestSequenced(Integration) });
|
||||
}
|
||||
59
src/Tests.CSharp/SqliteDb.cs
Normal file
59
src/Tests.CSharp/SqliteDb.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
namespace BitBadger.Documents.Tests;
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Sqlite;
|
||||
|
||||
/// <summary>
|
||||
/// A throwaway SQLite database file, which will be deleted when it goes out of scope
|
||||
/// </summary>
|
||||
public class ThrowawaySqliteDb : IDisposable, IAsyncDisposable
|
||||
{
|
||||
private readonly string _dbName;
|
||||
|
||||
public ThrowawaySqliteDb(string dbName)
|
||||
{
|
||||
_dbName = dbName;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (File.Exists(_dbName)) File.Delete(_dbName);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public ValueTask DisposeAsync()
|
||||
{
|
||||
if (File.Exists(_dbName)) File.Delete(_dbName);
|
||||
GC.SuppressFinalize(this);
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Utility functions for dealing with SQLite databases
|
||||
/// </summary>
|
||||
public static class SqliteDb
|
||||
{
|
||||
/// <summary>
|
||||
/// The table name for the catalog metadata
|
||||
/// </summary>
|
||||
public const string Catalog = "sqlite_master";
|
||||
|
||||
/// <summary>
|
||||
/// The name of the table used for testing
|
||||
/// </summary>
|
||||
public const string TableName = "test_table";
|
||||
|
||||
/// <summary>
|
||||
/// Create a throwaway database file with the test_table defined
|
||||
/// </summary>
|
||||
public static async Task<ThrowawaySqliteDb> BuildDb()
|
||||
{
|
||||
var dbName = $"test-db-{Guid.NewGuid():n}.db";
|
||||
Configuration.UseConnectionString($"data source={dbName}");
|
||||
await Definition.EnsureTable(TableName);
|
||||
return new ThrowawaySqliteDb(dbName);
|
||||
}
|
||||
}
|
||||
15
src/Tests.CSharp/Types.cs
Normal file
15
src/Tests.CSharp/Types.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace BitBadger.Documents.Tests.CSharp;
|
||||
|
||||
public class SubDocument
|
||||
{
|
||||
public string Foo { get; set; } = "";
|
||||
public string Bar { get; set; } = "";
|
||||
}
|
||||
|
||||
public class JsonDocument
|
||||
{
|
||||
public string Id { get; set; } = "";
|
||||
public string Value { get; set; } = "";
|
||||
public int NumValue { get; set; } = 0;
|
||||
public SubDocument? Sub { get; set; } = null;
|
||||
}
|
||||
Reference in New Issue
Block a user