Files
scadaproj/ZB.MOM.WW.LocalDb/tests/ZB.MOM.WW.LocalDb.Tests/SqliteLocalDbTests.cs
T

193 lines
6.1 KiB
C#

using Microsoft.Data.Sqlite;
using ZB.MOM.WW.LocalDb.Internal;
namespace ZB.MOM.WW.LocalDb.Tests;
public sealed class SqliteLocalDbTests : IDisposable
{
private readonly string _path = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".db");
public void Dispose()
{
SqliteConnection.ClearAllPools();
if (File.Exists(_path))
File.Delete(_path);
}
private SqliteLocalDb Create() => new(new LocalDbOptions { Path = _path });
[Fact]
public void Open_CreatesDbFile_WalMode()
{
using var db = Create();
Assert.True(File.Exists(_path));
using var conn = db.CreateConnection();
using var cmd = conn.CreateCommand();
cmd.CommandText = "PRAGMA journal_mode";
var mode = (string)cmd.ExecuteScalar()!;
Assert.Equal("wal", mode, ignoreCase: true);
}
[Fact]
public async Task ExecuteAsync_ParameterizedInsert_Works()
{
using var db = Create();
await db.ExecuteAsync("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT)");
await db.ExecuteAsync("INSERT INTO t (id, name) VALUES (@id, @name)", new { id = 1, name = "x" });
var rows = await db.QueryAsync("SELECT COUNT(*) FROM t", r => r.GetInt64(0));
Assert.Equal(1L, rows[0]);
}
[Fact]
public async Task QueryAsync_MapsRows()
{
using var db = Create();
await db.ExecuteAsync("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT)");
await db.ExecuteAsync("INSERT INTO t (id, name) VALUES (@id, @name)", new { id = 1, name = "a" });
await db.ExecuteAsync("INSERT INTO t (id, name) VALUES (@id, @name)", new { id = 2, name = "b" });
var rows = await db.QueryAsync(
"SELECT id, name FROM t ORDER BY id",
r => (Id: r.GetInt64(0), Name: r.GetString(1)));
Assert.Equal(2, rows.Count);
Assert.Equal((1L, "a"), rows[0]);
Assert.Equal((2L, "b"), rows[1]);
}
[Fact]
public async Task Transaction_CommitAndRollback()
{
using var db = Create();
await db.ExecuteAsync("CREATE TABLE t (id INTEGER PRIMARY KEY)");
await using (var tx = await db.BeginTransactionAsync())
{
await tx.ExecuteAsync("INSERT INTO t (id) VALUES (@id)", new { id = 1 });
await tx.CommitAsync(default);
}
await using (var tx = await db.BeginTransactionAsync())
{
await tx.ExecuteAsync("INSERT INTO t (id) VALUES (@id)", new { id = 2 });
await tx.RollbackAsync(default);
}
var ids = await db.QueryAsync("SELECT id FROM t ORDER BY id", r => r.GetInt64(0));
Assert.Equal(new[] { 1L }, ids);
}
[Fact]
public async Task Transaction_DisposeWithoutCommit_RollsBack()
{
using var db = Create();
await db.ExecuteAsync("CREATE TABLE t (id INTEGER PRIMARY KEY)");
await using (var tx = await db.BeginTransactionAsync())
{
await tx.ExecuteAsync("INSERT INTO t (id) VALUES (@id)", new { id = 1 });
}
var count = await db.QueryAsync("SELECT COUNT(*) FROM t", r => r.GetInt64(0));
Assert.Equal(0L, count[0]);
}
[Fact]
public async Task Bind_GuidByteArrayAndNull_RoundTrip()
{
using var db = Create();
await db.ExecuteAsync("CREATE TABLE t (id INTEGER PRIMARY KEY, g TEXT, blob BLOB, missing TEXT)");
var guid = Guid.NewGuid();
var bytes = new byte[] { 1, 2, 3 };
await db.ExecuteAsync(
"INSERT INTO t (id, g, blob, missing) VALUES (@id, @g, @blob, @missing)",
new { id = 1, g = guid, blob = bytes, missing = (string?)null });
var rows = await db.QueryAsync(
"SELECT g, blob, missing FROM t WHERE id = 1",
r => (Guid: r.GetGuid(0), Blob: (byte[])r.GetValue(1), MissingIsNull: r.IsDBNull(2)));
Assert.Equal(guid, rows[0].Guid);
Assert.Equal(bytes, rows[0].Blob);
Assert.True(rows[0].MissingIsNull);
}
[Fact]
public async Task Bind_DictionaryParameters_BindByKey()
{
using var db = Create();
await db.ExecuteAsync("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT)");
var parameters = new Dictionary<string, object?> { ["id"] = 7, ["name"] = null };
await db.ExecuteAsync("INSERT INTO t (id, name) VALUES (@id, @name)", parameters);
var rows = await db.QueryAsync(
"SELECT id, name FROM t",
r => (Id: r.GetInt64(0), NameIsNull: r.IsDBNull(1)));
Assert.Equal(7L, rows[0].Id);
Assert.True(rows[0].NameIsNull);
}
[Fact]
public void Ctor_EmptyPath_Throws()
{
Assert.Throws<ArgumentException>(() => new SqliteLocalDb(new LocalDbOptions { Path = " " }));
}
[Fact]
public void Ctor_InvalidSynchronous_Throws()
{
Assert.Throws<ArgumentException>(() =>
new SqliteLocalDb(new LocalDbOptions { Path = _path, Synchronous = "NORMAL; PRAGMA foreign_keys=OFF" }));
}
[Fact]
public void Dispose_IsIdempotent()
{
var db = Create();
db.Dispose();
db.Dispose();
}
[Fact]
public async Task HlcUdf_RegisteredOnConnection()
{
using var db = Create();
var values = await db.QueryAsync(
"SELECT zb_hlc_next() UNION ALL SELECT zb_hlc_next()",
r => r.GetInt64(0));
Assert.Equal(2, values.Count);
Assert.True(values[1] > values[0], $"{values[1]} should be greater than {values[0]}");
}
[Fact]
public async Task Hlc_ResumesFromDurableMax_OnReopen()
{
const long huge = 9_000_000_000_000_000L;
using (var db = Create())
{
await db.ExecuteAsync(
"INSERT INTO __localdb_oplog (table_name, pk_json, hlc, node_id) VALUES ('t', '{}', @hlc, 'n')",
new { hlc = huge });
}
SqliteConnection.ClearAllPools();
using (var db = Create())
{
var next = await db.QueryAsync("SELECT zb_hlc_next()", r => r.GetInt64(0));
Assert.True(next[0] > huge, $"{next[0]} should be greater than {huge}");
}
}
}