using Microsoft.Data.Sqlite; using ZB.MOM.WW.LocalDb.Internal; using ZB.MOM.WW.LocalDb.Registration; namespace ZB.MOM.WW.LocalDb.Tests; public sealed class RegistrationTests : 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 }); private static async Task CreateOrders(SqliteLocalDb db) => await db.ExecuteAsync("CREATE TABLE orders (id INTEGER PRIMARY KEY, sku TEXT, qty INTEGER)"); private Task> Triggers(SqliteLocalDb db) => db.QueryAsync( "SELECT name FROM sqlite_master WHERE type='trigger' AND tbl_name='orders' ORDER BY name", r => r.GetString(0)); [Fact] public async Task Register_TableWithPk_InstallsThreeTriggers() { using var db = Create(); await CreateOrders(db); db.RegisterReplicated("orders"); var triggers = await Triggers(db); Assert.Equal( new[] { "__localdb_orders_ad", "__localdb_orders_ai", "__localdb_orders_au" }, triggers); } [Fact] public async Task Register_NoPk_Throws() { using var db = Create(); await db.ExecuteAsync("CREATE TABLE nopk (a TEXT, b TEXT)"); var ex = Assert.Throws(() => db.RegisterReplicated("nopk")); Assert.Contains("nopk", ex.Message); } [Fact] public void Register_MissingTable_Throws() { using var db = Create(); var ex = Assert.Throws(() => db.RegisterReplicated("ghost")); Assert.Contains("ghost", ex.Message); } [Fact] public async Task Register_BlobColumn_Throws() { using var db = Create(); await db.ExecuteAsync("CREATE TABLE blobby (id INTEGER PRIMARY KEY, data BLOB)"); var ex = Assert.Throws(() => db.RegisterReplicated("blobby")); Assert.Contains("blobby", ex.Message); Assert.Contains("data", ex.Message); Assert.Contains("BLOB", ex.Message); } [Fact] public async Task Register_IsIdempotent_RegeneratesTriggers() { using var db = Create(); await CreateOrders(db); db.RegisterReplicated("orders"); db.RegisterReplicated("orders"); var triggers = await Triggers(db); Assert.Equal(3, triggers.Count); } [Fact] public async Task Register_CompositePk_Works() { using var db = Create(); await db.ExecuteAsync("CREATE TABLE kv (a INTEGER, b INTEGER, v TEXT, PRIMARY KEY (a, b))"); db.RegisterReplicated("kv"); await db.ExecuteAsync("INSERT INTO kv (a, b, v) VALUES (1, 2, 'x')"); var pk = await db.QueryAsync( "SELECT pk_json FROM __localdb_oplog WHERE table_name='kv'", r => r.GetString(0)); Assert.Single(pk); Assert.Equal("{\"a\":1,\"b\":2}", pk[0]); } [Fact] public async Task Digest_ChangesWhenSchemaChanges() { using var db = Create(); await CreateOrders(db); db.RegisterReplicated("orders"); var before = db.ReplicatedTables["orders"].Digest; await db.ExecuteAsync("ALTER TABLE orders ADD COLUMN note TEXT"); db.RegisterReplicated("orders"); var after = db.ReplicatedTables["orders"].Digest; Assert.NotEqual(before, after); } [Fact] public async Task ReplicatedTables_ExposesRegistry() { using var db = Create(); await CreateOrders(db); db.RegisterReplicated("orders"); var table = db.ReplicatedTables["orders"]; Assert.Equal("orders", table.Name); Assert.Equal(new[] { "id" }, table.PkColumns); Assert.Equal(new[] { "id", "sku", "qty" }, table.Columns.Select(c => c.Name).ToArray()); } }