public async Task LocalOrchestrator_EnsureScope_NewScope_WithoutSetup_ShouldBeEmpty() { var dbName = HelperDatabase.GetRandomName("tcp_lo_"); await HelperDatabase.CreateDatabaseAsync(ProviderType.Sql, dbName, true); var cs = HelperDatabase.GetConnectionString(ProviderType.Sql, dbName); var sqlProvider = new SqlSyncProvider(cs); var scopeName = "scope"; var options = new SyncOptions(); var localOrchestrator = new LocalOrchestrator(sqlProvider, options); var localScopeInfo = await localOrchestrator.GetClientScopeInfoAsync(scopeName); Assert.NotNull(localScopeInfo); Assert.Equal(scopeName, localScopeInfo.Name); Assert.True(localScopeInfo.IsNewScope); Assert.NotEqual(Guid.Empty, localScopeInfo.Id); Assert.Null(localScopeInfo.LastServerSyncTimestamp); Assert.Null(localScopeInfo.LastSync); Assert.Equal(0, localScopeInfo.LastSyncDuration); Assert.Null(localScopeInfo.LastSyncTimestamp); Assert.Null(localScopeInfo.Schema); Assert.Null(localScopeInfo.Setup); Assert.Equal(SyncVersion.Current, new Version(localScopeInfo.Version)); HelperDatabase.DropDatabase(ProviderType.Sql, dbName); }
public async Task LocalOrchestrator_CancellationToken_ShouldInterrupt_EnsureScope_OnConnectionOpened() { var dbName = HelperDatabase.GetRandomName("tcp_lo_"); await HelperDatabase.CreateDatabaseAsync(ProviderType.Sql, dbName, true); var cs = HelperDatabase.GetConnectionString(ProviderType.Sql, dbName); var sqlProvider = new SqlSyncProvider(cs); var ctx = new AdventureWorksContext((dbName, ProviderType.Sql, sqlProvider), true, false); await ctx.Database.EnsureCreatedAsync(); var options = new SyncOptions(); var setup = new SyncSetup(this.Tables); var localOrchestrator = new LocalOrchestrator(sqlProvider, options); using var cts = new CancellationTokenSource(); localOrchestrator.OnConnectionOpen(args => cts.Cancel()); var se = await Assert.ThrowsAsync <SyncException>( async() => await localOrchestrator.GetClientScopeInfoAsync(cancellationToken: cts.Token)); Assert.Equal(SyncSide.ClientSide, se.Side); Assert.Equal("OperationCanceledException", se.TypeName); HelperDatabase.DropDatabase(ProviderType.Sql, dbName); }
public async Task LocalOrchestrator_MultipleScopes_ShouldHave_SameClientId() { var dbName = HelperDatabase.GetRandomName("tcp_lo_"); await HelperDatabase.CreateDatabaseAsync(ProviderType.Sql, dbName, true); var cs = HelperDatabase.GetConnectionString(ProviderType.Sql, dbName); var sqlProvider = new SqlSyncProvider(cs); var options = new SyncOptions(); var localOrchestrator = new LocalOrchestrator(sqlProvider, options); var localScopeInfo1 = await localOrchestrator.GetClientScopeInfoAsync(); var localScopeInfo2 = await localOrchestrator.GetClientScopeInfoAsync("A"); var localScopeInfo3 = await localOrchestrator.GetClientScopeInfoAsync("B"); Assert.Equal(localScopeInfo1.Id, localScopeInfo2.Id); Assert.Equal(localScopeInfo2.Id, localScopeInfo3.Id); // Check we get the 3 scopes var allScopes = await localOrchestrator.GetAllClientScopesInfoAsync(); Assert.Equal(3, allScopes.Count); // Check the scope id, read from database, is good foreach (var scope in allScopes) { Assert.Equal(scope.Id, localScopeInfo1.Id); } HelperDatabase.DropDatabase(ProviderType.Sql, dbName); }
public async Task LocalOrchestrator_EnsureScope_ShouldNot_Fail_If_NoTables_In_Setup() { var dbName = HelperDatabase.GetRandomName("tcp_lo_"); await HelperDatabase.CreateDatabaseAsync(ProviderType.Sql, dbName, true); var cs = HelperDatabase.GetConnectionString(ProviderType.Sql, dbName); var options = new SyncOptions(); var provider = new SqlSyncProvider(cs); var localOrchestrator = new LocalOrchestrator(provider, options); var scope = await localOrchestrator.GetClientScopeInfoAsync(); Assert.NotNull(scope); HelperDatabase.DropDatabase(ProviderType.Sql, dbName); }
public async Task Table_Exists() { var dbName = HelperDatabase.GetRandomName("tcp_lo_"); await HelperDatabase.CreateDatabaseAsync(ProviderType.Sql, dbName, true); var cs = HelperDatabase.GetConnectionString(ProviderType.Sql, dbName); var sqlProvider = new SqlSyncProvider(cs); var options = new SyncOptions(); var setup = new SyncSetup(new string[] { "SalesLT.Product", "SalesLT.ProductCategory" }); var table = new SyncTable("Product", "SalesLT"); var colID = new SyncColumn("ID", typeof(Guid)); var colName = new SyncColumn("Name", typeof(string)); table.Columns.Add(colID); table.Columns.Add(colName); table.Columns.Add("Number", typeof(int)); table.PrimaryKeys.Add("ID"); var schema = new SyncSet(); schema.Tables.Add(table); var localOrchestrator = new LocalOrchestrator(sqlProvider, options); var scopeInfo = await localOrchestrator.GetClientScopeInfoAsync(); scopeInfo.Setup = setup; scopeInfo.Schema = schema; await localOrchestrator.SaveClientScopeInfoAsync(scopeInfo); await localOrchestrator.CreateTableAsync(scopeInfo, "Product", "SalesLT"); var exists = await localOrchestrator.ExistTableAsync(scopeInfo, table.TableName, table.SchemaName).ConfigureAwait(false); Assert.True(exists); exists = await localOrchestrator.ExistTableAsync(scopeInfo, "ProductCategory", "SalesLT").ConfigureAwait(false); Assert.False(exists); HelperDatabase.DropDatabase(ProviderType.Sql, dbName); }
public virtual async Task Scenario_Adding_OneColumn_OneTable_With_TwoScopes() { // create a server schema with seeding await this.EnsureDatabaseSchemaAndSeedAsync(this.Server, true, UseFallbackSchema); // create empty client databases foreach (var client in this.Clients) { await this.CreateDatabaseAsync(client.ProviderType, client.DatabaseName, true); } var productCategoryTableName = this.Server.ProviderType == ProviderType.Sql ? "SalesLT.ProductCategory" : "ProductCategory"; var productTableName = this.Server.ProviderType == ProviderType.Sql ? "SalesLT.Product" : "Product"; // -------------------------- // Step 1: Create a default scope and Sync clients // Note we are not including the [Attribute With Space] column var setup = new SyncSetup(new string[] { productCategoryTableName }); setup.Tables[productCategoryTableName].Columns.AddRange( new string[] { "ProductCategoryId", "Name", "rowguid", "ModifiedDate" }); // configure server orchestrator this.Kestrell.AddSyncServer(this.Server.Provider.GetType(), this.Server.Provider.ConnectionString, SyncOptions.DefaultScopeName, setup); var serviceUri = this.Kestrell.Run(); int productCategoryRowsCount = 0; using (var readCtx = new AdventureWorksContext(Server, this.UseFallbackSchema)) { productCategoryRowsCount = readCtx.ProductCategory.AsNoTracking().Count(); } // First sync to initialiaze client database, create table and fill product categories foreach (var client in this.Clients) { var webServerProxyOrchestrator = new WebRemoteOrchestrator(serviceUri); var agent = new SyncAgent(client.Provider, webServerProxyOrchestrator); var r = await agent.SynchronizeAsync(); Assert.Equal(productCategoryRowsCount, r.TotalChangesDownloaded); } await this.Kestrell.StopAsync(); // On server side, playing around with a direct RemoteOrchestrator var remoteOrchestrator = new RemoteOrchestrator(Server.Provider); // Adding a new scope on the server with this new column and a new table // Creating a new scope called "V1" on server var setupV1 = new SyncSetup(new string[] { productCategoryTableName, productTableName }); setupV1.Tables[productCategoryTableName].Columns.AddRange( new string[] { "ProductCategoryId", "Name", "rowguid", "ModifiedDate", "Attribute With Space" }); var serverScope = await remoteOrchestrator.ProvisionAsync("v1", setupV1); // Create a server new ProductCategory with the new column value filled // and a Product related var productId = Guid.NewGuid(); var productName = HelperDatabase.GetRandomName(); var productNumber = productName.ToUpperInvariant().Substring(0, 10); var productCategoryName = HelperDatabase.GetRandomName(); var productCategoryId = productCategoryName.ToUpperInvariant().Substring(0, 6); var newAttributeWithSpaceValue = HelperDatabase.GetRandomName(); using (var ctx = new AdventureWorksContext(Server, this.UseFallbackSchema)) { var pc = new ProductCategory { ProductCategoryId = productCategoryId, Name = productCategoryName, AttributeWithSpace = newAttributeWithSpaceValue }; ctx.ProductCategory.Add(pc); var product = new Product { ProductId = productId, Name = productName, ProductNumber = productNumber, ProductCategoryId = productCategoryId }; ctx.Product.Add(product); await ctx.SaveChangesAsync(); } // configure server orchestrator this.Kestrell.AddSyncServer(this.Server.Provider.GetType(), this.Server.Provider.ConnectionString, SyncOptions.DefaultScopeName, setup); this.Kestrell.AddSyncServer(this.Server.Provider.GetType(), this.Server.Provider.ConnectionString, SyncOptions.DefaultScopeName, setupV1); serviceUri = this.Kestrell.Run(); foreach (var client in this.Clients) { var commandText = client.ProviderType switch { ProviderType.Sql => $@"ALTER TABLE {productCategoryTableName} ADD [Attribute With Space] nvarchar(250) NULL;", ProviderType.Sqlite => @"ALTER TABLE ProductCategory ADD [Attribute With Space] text NULL;", ProviderType.MySql => @"ALTER TABLE `ProductCategory` ADD `Attribute With Space` nvarchar(250) NULL;", ProviderType.MariaDB => @"ALTER TABLE `ProductCategory` ADD `Attribute With Space` nvarchar(250) NULL;", _ => throw new NotImplementedException() }; var connection = client.Provider.CreateConnection(); connection.Open(); var command = connection.CreateCommand(); command.CommandText = commandText; command.Connection = connection; await command.ExecuteNonQueryAsync(); connection.Close(); // Get scope from server (v1 because it contains the new table schema) var webServerProxyOrchestrator = new WebRemoteOrchestrator(serviceUri); serverScope = await webServerProxyOrchestrator.GetServerScopeInfoAsync("v1"); // Creating a new table is quite easier since DMS can do it for us var localOrchestrator = new LocalOrchestrator(client.Provider); if (this.Server.ProviderType == ProviderType.Sql) { await localOrchestrator.CreateTableAsync(serverScope, "Product", "SalesLT"); } else { await localOrchestrator.CreateTableAsync(serverScope, "Product"); } // Once created we can provision the new scope, thanks to the serverScope instance we already have var clientScopeV1 = await localOrchestrator.ProvisionAsync(serverScope); // IF we launch synchronize on this new scope, it will get all the rows from the server // We are making a shadow copy of previous scope to get the last synchronization metadata var oldClientScopeInfo = await localOrchestrator.GetClientScopeInfoAsync(); clientScopeV1.ShadowScope(oldClientScopeInfo); await localOrchestrator.SaveClientScopeInfoAsync(clientScopeV1); // We are ready to sync this new scope ! var agent = new SyncAgent(client.Provider, Server.Provider); var r = await agent.SynchronizeAsync("v1"); Assert.Equal(2, r.TotalChangesDownloaded); } }
public virtual async Task Scenario_Adding_OneColumn_OneTable_With_TwoScopes_OneClient_Still_OnOldScope_OneClient_OnNewScope() { // create a server schema with seeding await this.EnsureDatabaseSchemaAndSeedAsync(this.Server, true, UseFallbackSchema); // create 2 client databases // First one will update to new scope // Second one will stay on last scope // For this purpose, using two sqlite databases var client1DatabaseName = HelperDatabase.GetRandomName(); var client2DatabaseName = HelperDatabase.GetRandomName(); // Create the two databases await this.CreateDatabaseAsync(ProviderType.Sqlite, client1DatabaseName, true); await this.CreateDatabaseAsync(ProviderType.Sqlite, client2DatabaseName, true); var client1provider = new SqliteSyncProvider(HelperDatabase.GetSqliteFilePath(client1DatabaseName)); var client2provider = new SqliteSyncProvider(HelperDatabase.GetSqliteFilePath(client2DatabaseName)); // -------------------------- // Step 1: Create a default scope and Sync clients // Note we are not including the [Attribute With Space] column var productCategoryTableName = this.Server.ProviderType == ProviderType.Sql ? "SalesLT.ProductCategory" : "ProductCategory"; var productTableName = this.Server.ProviderType == ProviderType.Sql ? "SalesLT.Product" : "Product"; var setup = new SyncSetup(new string[] { productCategoryTableName }); setup.Tables[productCategoryTableName].Columns.AddRange( new string[] { "ProductCategoryId", "Name", "rowguid", "ModifiedDate" }); // Counting product categories & products int productCategoryRowsCount = 0; int productsCount = 0; using (var readCtx = new AdventureWorksContext(Server, this.UseFallbackSchema)) { productCategoryRowsCount = readCtx.ProductCategory.AsNoTracking().Count(); productsCount = readCtx.Product.AsNoTracking().Count(); } var agent1 = new SyncAgent(client1provider, Server.Provider); var r1 = await agent1.SynchronizeAsync(setup); Assert.Equal(productCategoryRowsCount, r1.TotalChangesDownloaded); var agent2 = new SyncAgent(client2provider, Server.Provider); var r2 = await agent2.SynchronizeAsync(setup); Assert.Equal(productCategoryRowsCount, r2.TotalChangesDownloaded); // From now, the client 1 will upgrade to new scope // the client 2 will remain on old scope // Adding a new scope var remoteOrchestrator = agent1.RemoteOrchestrator; // agent2.RemoteOrchestrator is the same, btw // Adding a new scope on the server with this new column and a new table // Creating a new scope called "V1" on server var setupV1 = new SyncSetup(new string[] { productCategoryTableName, productTableName }); setupV1.Tables[productCategoryTableName].Columns.AddRange( new string[] { "ProductCategoryId", "Name", "rowguid", "ModifiedDate", "Attribute With Space" }); var serverScope = await remoteOrchestrator.ProvisionAsync("v1", setupV1); // Create a server new ProductCategory with the new column value filled // and a Product related var productId = Guid.NewGuid(); var productName = HelperDatabase.GetRandomName(); var productNumber = productName.ToUpperInvariant().Substring(0, 10); var productCategoryName = HelperDatabase.GetRandomName(); var productCategoryId = productCategoryName.ToUpperInvariant().Substring(0, 6); var newAttributeWithSpaceValue = HelperDatabase.GetRandomName(); using (var ctx = new AdventureWorksContext(Server, this.UseFallbackSchema)) { var pc = new ProductCategory { ProductCategoryId = productCategoryId, Name = productCategoryName, AttributeWithSpace = newAttributeWithSpaceValue }; ctx.ProductCategory.Add(pc); var product = new Product { ProductId = productId, Name = productName, ProductNumber = productNumber, ProductCategoryId = productCategoryId }; ctx.Product.Add(product); await ctx.SaveChangesAsync(); } // Add this new column on the client 1, with default value as null var connection = client1provider.CreateConnection(); connection.Open(); var command = connection.CreateCommand(); command.CommandText = @"ALTER TABLE ProductCategory ADD [Attribute With Space] text NULL;"; command.Connection = connection; await command.ExecuteNonQueryAsync(); connection.Close(); // Creating a new table is quite easier since DMS can do it for us // Get scope from server (v1 because it contains the new table schema) // we already have it, but you cand call GetServerScopInfoAsync("v1") if needed // var serverScope = await remoteOrchestrator.GetServerScopeInfoAsync("v1"); var localOrchestrator = new LocalOrchestrator(client1provider); if (this.Server.ProviderType == ProviderType.Sql) { await localOrchestrator.CreateTableAsync(serverScope, "Product", "SalesLT"); } else { await localOrchestrator.CreateTableAsync(serverScope, "Product"); } // Once created we can provision the new scope, thanks to the serverScope instance we already have var clientScopeV1 = await localOrchestrator.ProvisionAsync(serverScope); // IF we launch synchronize on this new scope, it will get all the rows from the server // We are making a shadow copy of previous scope to get the last synchronization metadata var oldClientScopeInfo = await localOrchestrator.GetClientScopeInfoAsync(); clientScopeV1.ShadowScope(oldClientScopeInfo); await localOrchestrator.SaveClientScopeInfoAsync(clientScopeV1); // We are ready to sync this new scope ! // we still can use the old agent, since it's already configured with correct providers // just be sure to set the correct scope r1 = await agent1.SynchronizeAsync("v1"); Assert.Equal(2, r1.TotalChangesDownloaded); // make a sync on old scope for client 2 r2 = await agent2.SynchronizeAsync(); Assert.Equal(1, r2.TotalChangesDownloaded); // now check values on each client using (var ctx1 = new AdventureWorksContext((client1DatabaseName, ProviderType.Sqlite, client1provider), false)) { var producCategory1 = ctx1.ProductCategory.First(pc => pc.ProductCategoryId == productCategoryId); Assert.Equal(newAttributeWithSpaceValue, producCategory1.AttributeWithSpace); } using (var ctx2 = new AdventureWorksContext((client2DatabaseName, ProviderType.Sqlite, client2provider), false)) { var exc = Assert.ThrowsAny <Microsoft.Data.Sqlite.SqliteException>(() => ctx2.ProductCategory.First(pc => pc.ProductCategoryId == productCategoryId)); Assert.Contains("no such column", exc.Message); } // Assuming we want to migrate the client 2 now var serverScope2 = await agent2.RemoteOrchestrator.GetServerScopeInfoAsync(); // Create the new table locally if (this.Server.ProviderType == ProviderType.Sql) { await localOrchestrator.CreateTableAsync(serverScope, "Product", "SalesLT"); } else { await localOrchestrator.CreateTableAsync(serverScope, "Product"); } // Add this new column on the client 1, with default value as null connection = client2provider.CreateConnection(); connection.Open(); command = connection.CreateCommand(); command.CommandText = @"ALTER TABLE ProductCategory ADD [Attribute With Space] text NULL;"; command.Connection = connection; await command.ExecuteNonQueryAsync(); connection.Close(); // Don't bother to ShadowCopy metadata, since we are doing a reinit // Just Provision var clientScope2 = await agent2.LocalOrchestrator.ProvisionAsync(serverScope2); // Sync r2 = await agent2.SynchronizeAsync("v1", SyncType.Reinitialize); using (var readCtx = new AdventureWorksContext(Server, this.UseFallbackSchema)) { productCategoryRowsCount = readCtx.ProductCategory.AsNoTracking().Count(); productsCount = readCtx.Product.AsNoTracking().Count(); } Assert.Equal((productCategoryRowsCount + productsCount), r2.TotalChangesDownloaded); }
public async Task LocalOrchestrator_Scope() { var dbName = HelperDatabase.GetRandomName("tcp_lo_"); await HelperDatabase.CreateDatabaseAsync(ProviderType.Sql, dbName, true); var cs = HelperDatabase.GetConnectionString(ProviderType.Sql, dbName); var sqlProvider = new SqlSyncProvider(cs); var ctx = new AdventureWorksContext((dbName, ProviderType.Sql, sqlProvider), true, false); await ctx.Database.EnsureCreatedAsync(); var scopeName = "scope"; var options = new SyncOptions(); var setup = new SyncSetup(this.Tables); var localOrchestrator = new LocalOrchestrator(sqlProvider, options); var scopeTableCreating = 0; var scopeTableCreated = 0; var scopeLoading = 0; var scopeLoaded = 0; var scopeSaving = 0; var scopeSaved = 0; localOrchestrator.OnScopeSaving(ssa => { Assert.NotNull(ssa.Command); scopeSaving++; }); localOrchestrator.OnScopeSaved(ssa => scopeSaved++); localOrchestrator.OnScopeTableCreating(stca => { Assert.NotNull(stca.Command); scopeTableCreating++; }); localOrchestrator.OnScopeTableCreated(stca => { scopeTableCreated++; }); localOrchestrator.OnClientScopeInfoLoading(args => { Assert.NotNull(args.Command); Assert.Equal(SyncStage.ScopeLoading, args.Context.SyncStage); Assert.Equal(scopeName, args.Context.ScopeName); Assert.Equal(scopeName, args.ScopeName); Assert.NotNull(args.Connection); Assert.NotNull(args.Transaction); Assert.Equal(ConnectionState.Open, args.Connection.State); scopeLoading++; }); localOrchestrator.OnClientScopeInfoLoaded(args => { Assert.Equal(SyncStage.ScopeLoading, args.Context.SyncStage); Assert.Equal(scopeName, args.Context.ScopeName); Assert.NotNull(args.Connection); Assert.NotNull(args.Transaction); scopeLoaded++; }); var localScopeInfo = await localOrchestrator.GetClientScopeInfoAsync(scopeName); Assert.Equal(1, scopeTableCreating); Assert.Equal(1, scopeTableCreated); Assert.Equal(2, scopeLoading); Assert.Equal(2, scopeLoaded); Assert.Equal(1, scopeSaving); Assert.Equal(1, scopeSaved); scopeTableCreating = 0; scopeTableCreated = 0; scopeLoading = 0; scopeLoaded = 0; scopeSaving = 0; scopeSaved = 0; localScopeInfo.Version = "2.0"; await localOrchestrator.SaveClientScopeInfoAsync(localScopeInfo); Assert.Equal(0, scopeTableCreating); Assert.Equal(0, scopeTableCreated); Assert.Equal(0, scopeLoading); Assert.Equal(0, scopeLoaded); Assert.Equal(1, scopeSaving); Assert.Equal(1, scopeSaved); HelperDatabase.DropDatabase(ProviderType.Sql, dbName); }
public async Task Table_Drop_One_Cancel() { var dbName = HelperDatabase.GetRandomName("tcp_lo_"); await HelperDatabase.CreateDatabaseAsync(ProviderType.Sql, dbName, true); var cs = HelperDatabase.GetConnectionString(ProviderType.Sql, dbName); var sqlProvider = new SqlSyncProvider(cs); var options = new SyncOptions(); var setup = new SyncSetup(new string[] { "SalesLT.Product" }); // Overwrite existing table with this new one var table = new SyncTable("Product", "SalesLT"); var colID = new SyncColumn("ID", typeof(Guid)); var colName = new SyncColumn("Name", typeof(string)); table.Columns.Add(colID); table.Columns.Add(colName); table.Columns.Add("Number", typeof(int)); table.PrimaryKeys.Add("ID"); var schema = new SyncSet(); schema.Tables.Add(table); var localOrchestrator = new LocalOrchestrator(sqlProvider, options); var scopeInfo = await localOrchestrator.GetClientScopeInfoAsync(); scopeInfo.Setup = setup; scopeInfo.Schema = schema; await localOrchestrator.SaveClientScopeInfoAsync(scopeInfo); // Call create a first time to have an existing table var isCreated = await localOrchestrator.CreateTableAsync(scopeInfo, table.TableName, table.SchemaName); Assert.True(isCreated); // Ensuring we have a clean new instance localOrchestrator = new LocalOrchestrator(sqlProvider, options); var onCreating = false; var onCreated = false; var onDropping = false; var onDropped = false; localOrchestrator.OnTableCreating(ttca => onCreating = true); localOrchestrator.OnTableCreated(ttca => onCreated = true); localOrchestrator.OnTableDropped(ttca => onDropped = true); localOrchestrator.OnTableDropping(ttca => { ttca.Cancel = true; onDropping = true; }); var isDropped = await localOrchestrator.DropTableAsync(scopeInfo, table.TableName, table.SchemaName); Assert.True(onDropping); Assert.False(isDropped); Assert.False(onDropped); Assert.False(onCreating); Assert.False(onCreated); using (var c = new SqlConnection(cs)) { await c.OpenAsync().ConfigureAwait(false); var stable = await SqlManagementUtils.GetTableAsync(c, null, "Product", "SalesLT").ConfigureAwait(false); Assert.Single(stable.Rows); c.Close(); } HelperDatabase.DropDatabase(ProviderType.Sql, dbName); }
public async Task Table_Create_One() { var dbName = HelperDatabase.GetRandomName("tcp_lo_"); await HelperDatabase.CreateDatabaseAsync(ProviderType.Sql, dbName, true); var cs = HelperDatabase.GetConnectionString(ProviderType.Sql, dbName); var sqlProvider = new SqlSyncProvider(cs); var options = new SyncOptions(); var setup = new SyncSetup(new string[] { "SalesLT.Product" }); var table = new SyncTable("Product", "SalesLT"); var colID = new SyncColumn("ID", typeof(Guid)); var colName = new SyncColumn("Name", typeof(string)); table.Columns.Add(colID); table.Columns.Add(colName); table.Columns.Add("Number", typeof(int)); table.PrimaryKeys.Add("ID"); var schema = new SyncSet(); schema.Tables.Add(table); var localOrchestrator = new LocalOrchestrator(sqlProvider, options); var scopeInfo = await localOrchestrator.GetClientScopeInfoAsync(); scopeInfo.Setup = setup; scopeInfo.Schema = schema; await localOrchestrator.SaveClientScopeInfoAsync(scopeInfo); var onCreating = false; var onCreated = false; localOrchestrator.OnTableCreating(ttca => { var addingID = Environment.NewLine + $"ALTER TABLE {ttca.TableName.Schema().Quoted()} ADD internal_id int identity(1,1)"; ttca.Command.CommandText += addingID; onCreating = true; }); localOrchestrator.OnTableCreated(ttca => { onCreated = true; }); var isCreated = await localOrchestrator.CreateTableAsync(scopeInfo, table.TableName, table.SchemaName); Assert.True(isCreated); Assert.True(onCreating); Assert.True(onCreated); // Check we have a new column in tracking table using (var c = new SqlConnection(cs)) { await c.OpenAsync().ConfigureAwait(false); var cols = await SqlManagementUtils.GetColumnsForTableAsync(c, null, "Product", "SalesLT").ConfigureAwait(false); Assert.Equal(4, cols.Rows.Count); Assert.NotNull(cols.Rows.FirstOrDefault(r => r["name"].ToString() == "internal_id")); c.Close(); } HelperDatabase.DropDatabase(ProviderType.Sql, dbName); }
public async Task Table_Create_All() { var dbName = HelperDatabase.GetRandomName("tcp_lo_"); await HelperDatabase.CreateDatabaseAsync(ProviderType.Sql, dbName, true); var cs = HelperDatabase.GetConnectionString(ProviderType.Sql, dbName); var sqlProvider = new SqlSyncProvider(cs); var ctx = new AdventureWorksContext((dbName, ProviderType.Sql, sqlProvider), true, false); await ctx.Database.EnsureCreatedAsync(); var options = new SyncOptions(); var setup = new SyncSetup(new string[] { "SalesLT.ProductCategory", "SalesLT.ProductModel", "SalesLT.Product", "Posts" }); var localOrchestrator = new LocalOrchestrator(sqlProvider, options); var remoteOrchestrator = new RemoteOrchestrator(sqlProvider, options); var serverScope = await remoteOrchestrator.GetServerScopeInfoAsync(setup); // new empty db dbName = HelperDatabase.GetRandomName("tcp_lo_"); await HelperDatabase.CreateDatabaseAsync(ProviderType.Sql, dbName, true); cs = HelperDatabase.GetConnectionString(ProviderType.Sql, dbName); sqlProvider = new SqlSyncProvider(cs); localOrchestrator = new LocalOrchestrator(sqlProvider, options); var onCreating = 0; var onCreated = 0; var onDropping = 0; var onDropped = 0; localOrchestrator.OnTableCreating(ttca => onCreating++); localOrchestrator.OnTableCreated(ttca => onCreated++); localOrchestrator.OnTableDropping(ttca => onDropping++); localOrchestrator.OnTableDropped(ttca => onDropped++); var scopeInfo = await localOrchestrator.GetClientScopeInfoAsync(); scopeInfo.Setup = serverScope.Setup; scopeInfo.Schema = serverScope.Schema; await localOrchestrator.SaveClientScopeInfoAsync(scopeInfo); await localOrchestrator.CreateTablesAsync(scopeInfo); Assert.Equal(4, onCreating); Assert.Equal(4, onCreated); Assert.Equal(0, onDropping); Assert.Equal(0, onDropped); onCreating = 0; onCreated = 0; onDropping = 0; onDropped = 0; await localOrchestrator.CreateTablesAsync(scopeInfo); Assert.Equal(0, onCreating); Assert.Equal(0, onCreated); Assert.Equal(0, onDropping); Assert.Equal(0, onDropped); await localOrchestrator.CreateTablesAsync(scopeInfo, true); Assert.Equal(4, onCreating); Assert.Equal(4, onCreated); Assert.Equal(4, onDropping); Assert.Equal(4, onDropped); HelperDatabase.DropDatabase(ProviderType.Sql, dbName); }
public async Task LocalOrchestrator_MultipleScopes_Check_Metadatas_Are_Deleted() { var dbName = HelperDatabase.GetRandomName("tcp_lo_"); await HelperDatabase.CreateDatabaseAsync(ProviderType.Sql, dbName, true); var cs = HelperDatabase.GetConnectionString(ProviderType.Sql, dbName); var sqlProvider = new SqlSyncProvider(cs); var ctx = new AdventureWorksContext((dbName, ProviderType.Sql, sqlProvider), true, false); await ctx.Database.EnsureCreatedAsync(); var options = new SyncOptions(); var localOrchestrator = new LocalOrchestrator(sqlProvider, options); var setup = new SyncSetup(this.Tables); var setup2 = new SyncSetup(this.Tables); setup2.Filters.Add("Customer", "EmployeeID"); var schema = await localOrchestrator.GetSchemaAsync(setup); var localScopeInfo1 = await localOrchestrator.GetClientScopeInfoAsync(); var localScopeInfo2 = await localOrchestrator.GetClientScopeInfoAsync("A"); var serverScope1 = new ServerScopeInfo { Name = localScopeInfo1.Name, Schema = schema, Setup = setup, Version = localScopeInfo1.Version }; var serverScope2 = new ServerScopeInfo { Name = localScopeInfo2.Name, Schema = schema, Setup = setup2, Version = localScopeInfo2.Version }; // Provision two scopes (already tested in previous test) localScopeInfo1 = await localOrchestrator.ProvisionAsync(serverScope1); localScopeInfo2 = await localOrchestrator.ProvisionAsync(serverScope2); Assert.NotNull(localScopeInfo1.Setup); Assert.NotNull(localScopeInfo1.Schema); Assert.NotNull(localScopeInfo2.Setup); Assert.NotNull(localScopeInfo2.Schema); // Deprovision await localOrchestrator.DeprovisionAsync("A"); foreach (var table in localScopeInfo1.Setup.Tables) { var tableName = table.TableName; var schemaName = table.SchemaName; foreach (var objectSpType in Enum.GetValues(typeof(Builders.DbStoredProcedureType))) { var spType = (Builders.DbStoredProcedureType)objectSpType; var exists1 = await localOrchestrator.ExistStoredProcedureAsync( localScopeInfo1, tableName, schemaName, spType); var exists2 = await localOrchestrator.ExistStoredProcedureAsync( localScopeInfo2, tableName, schemaName, spType); if (spType == Builders.DbStoredProcedureType.SelectChangesWithFilters || spType == Builders.DbStoredProcedureType.SelectInitializedChangesWithFilters) { Assert.False(exists1); } else { Assert.True(exists1); } Assert.False(exists2); } foreach (var objectSpType in Enum.GetValues(typeof(Builders.DbTriggerType))) { var trigType = (Builders.DbTriggerType)objectSpType; var existsTrig1 = await localOrchestrator.ExistTriggerAsync(localScopeInfo1, tableName, schemaName, trigType); var existsTrig2 = await localOrchestrator.ExistTriggerAsync(localScopeInfo2, tableName, schemaName, trigType); Assert.False(existsTrig1); Assert.False(existsTrig2); } var trackTableExists1 = await localOrchestrator.ExistTrackingTableAsync(localScopeInfo1, tableName, schemaName); var trackTableExists2 = await localOrchestrator.ExistTrackingTableAsync(localScopeInfo2, tableName, schemaName); // Tracking table are still existing for others scopes Assert.True(trackTableExists1); Assert.True(trackTableExists2); } // Deprovision await localOrchestrator.DeprovisionAsync(); foreach (var table in localScopeInfo1.Setup.Tables) { var tableName = table.TableName; var schemaName = table.SchemaName; foreach (var objectSpType in Enum.GetValues(typeof(Builders.DbStoredProcedureType))) { var spType = (Builders.DbStoredProcedureType)objectSpType; var exists1 = await localOrchestrator.ExistStoredProcedureAsync( localScopeInfo1, tableName, schemaName, spType); var exists2 = await localOrchestrator.ExistStoredProcedureAsync( localScopeInfo2, tableName, schemaName, spType); Assert.False(exists1); Assert.False(exists2); } } HelperDatabase.DropDatabase(ProviderType.Sql, dbName); }
public async Task Scenario_Using_ExistingClientDatabase_ProvisionDeprovision_WithoutAccessToServerSide(SyncOptions options) { // This test works only if we have the same exact provider on both sides // create client orchestrator that is the same as server var clientDatabaseName = HelperDatabase.GetRandomName("tcpfilt_cli_"); var clientProvider = this.CreateProvider(this.ServerType, clientDatabaseName); var client = (clientDatabaseName, Server.ProviderType, Provider : clientProvider); // create a client schema without seeding await this.EnsureDatabaseSchemaAndSeedAsync(client, false, UseFallbackSchema); // Since we don't have access to remote orchestrator, // we can simulate a server scope var localOrchestrator = new LocalOrchestrator(client.Provider, options); // Get the local scope var localScopeInfo = await localOrchestrator.GetClientScopeInfoAsync(); // getting local scope did not get the schema var schema = await localOrchestrator.GetSchemaAsync(this.FilterSetup); // getting local schema from these provider will not fill the schema name for each table // and we need the exact same name even if it's not used on client if (client.ProviderType == ProviderType.MySql || client.ProviderType == ProviderType.MariaDB || client.ProviderType == ProviderType.Sqlite) { foreach (var table in schema.Tables) { var setupTable = this.FilterSetup.Tables.First(t => t.TableName == table.TableName); table.SchemaName = setupTable.SchemaName; } } // Simulate a server scope var serverScope = new ServerScopeInfo { Name = localScopeInfo.Name, Schema = schema, Setup = this.FilterSetup, Version = localScopeInfo.Version }; // just check interceptor var onTableCreatedCount = 0; localOrchestrator.OnTableCreated(args => onTableCreatedCount++); // Provision the database with all tracking tables, stored procedures, triggers and scope var clientScope = await localOrchestrator.ProvisionAsync(serverScope); //-------------------------- // ASSERTION //-------------------------- // check if scope table is correctly created var scopeInfoTableExists = await localOrchestrator.ExistScopeInfoTableAsync(clientScope.Name, DbScopeType.Client); Assert.True(scopeInfoTableExists); // get the db manager foreach (var setupTable in this.FilterSetup.Tables) { Assert.True(await localOrchestrator.ExistTrackingTableAsync(clientScope, setupTable.TableName, setupTable.SchemaName)); Assert.True(await localOrchestrator.ExistTriggerAsync(clientScope, setupTable.TableName, setupTable.SchemaName, DbTriggerType.Delete)); Assert.True(await localOrchestrator.ExistTriggerAsync(clientScope, setupTable.TableName, setupTable.SchemaName, DbTriggerType.Insert)); Assert.True(await localOrchestrator.ExistTriggerAsync(clientScope, setupTable.TableName, setupTable.SchemaName, DbTriggerType.Update)); if (client.ProviderType == ProviderType.Sql) { Assert.True(await localOrchestrator.ExistStoredProcedureAsync(clientScope, setupTable.TableName, setupTable.SchemaName, DbStoredProcedureType.BulkDeleteRows)); Assert.True(await localOrchestrator.ExistStoredProcedureAsync(clientScope, setupTable.TableName, setupTable.SchemaName, DbStoredProcedureType.BulkTableType)); Assert.True(await localOrchestrator.ExistStoredProcedureAsync(clientScope, setupTable.TableName, setupTable.SchemaName, DbStoredProcedureType.BulkUpdateRows)); } Assert.True(await localOrchestrator.ExistStoredProcedureAsync(clientScope, setupTable.TableName, setupTable.SchemaName, DbStoredProcedureType.DeleteMetadata)); Assert.True(await localOrchestrator.ExistStoredProcedureAsync(clientScope, setupTable.TableName, setupTable.SchemaName, DbStoredProcedureType.DeleteRow)); Assert.True(await localOrchestrator.ExistStoredProcedureAsync(clientScope, setupTable.TableName, setupTable.SchemaName, DbStoredProcedureType.Reset)); Assert.True(await localOrchestrator.ExistStoredProcedureAsync(clientScope, setupTable.TableName, setupTable.SchemaName, DbStoredProcedureType.SelectChanges)); Assert.True(await localOrchestrator.ExistStoredProcedureAsync(clientScope, setupTable.TableName, setupTable.SchemaName, DbStoredProcedureType.SelectInitializedChanges)); Assert.True(await localOrchestrator.ExistStoredProcedureAsync(clientScope, setupTable.TableName, setupTable.SchemaName, DbStoredProcedureType.SelectRow)); Assert.True(await localOrchestrator.ExistStoredProcedureAsync(clientScope, setupTable.TableName, setupTable.SchemaName, DbStoredProcedureType.UpdateRow)); // Filters here Assert.True(await localOrchestrator.ExistStoredProcedureAsync(clientScope, setupTable.TableName, setupTable.SchemaName, DbStoredProcedureType.SelectChangesWithFilters)); Assert.True(await localOrchestrator.ExistStoredProcedureAsync(clientScope, setupTable.TableName, setupTable.SchemaName, DbStoredProcedureType.SelectInitializedChangesWithFilters)); } //localOrchestrator.OnTableProvisioned(null); //// Deprovision the database with all tracking tables, stored procedures, triggers and scope await localOrchestrator.DeprovisionAsync(SyncProvision.StoredProcedures | SyncProvision.Triggers | SyncProvision.ClientScope | SyncProvision.TrackingTable); // check if scope table is correctly created scopeInfoTableExists = await localOrchestrator.ExistScopeInfoTableAsync(clientScope.Name, DbScopeType.Client); Assert.False(scopeInfoTableExists); // get the db manager foreach (var setupTable in this.FilterSetup.Tables) { Assert.False(await localOrchestrator.ExistTrackingTableAsync(clientScope, setupTable.TableName, setupTable.SchemaName)); Assert.False(await localOrchestrator.ExistTriggerAsync(clientScope, setupTable.TableName, setupTable.SchemaName, DbTriggerType.Delete)); Assert.False(await localOrchestrator.ExistTriggerAsync(clientScope, setupTable.TableName, setupTable.SchemaName, DbTriggerType.Insert)); Assert.False(await localOrchestrator.ExistTriggerAsync(clientScope, setupTable.TableName, setupTable.SchemaName, DbTriggerType.Update)); if (client.ProviderType == ProviderType.Sql) { Assert.False(await localOrchestrator.ExistStoredProcedureAsync(clientScope, setupTable.TableName, setupTable.SchemaName, DbStoredProcedureType.BulkDeleteRows)); Assert.False(await localOrchestrator.ExistStoredProcedureAsync(clientScope, setupTable.TableName, setupTable.SchemaName, DbStoredProcedureType.BulkTableType)); Assert.False(await localOrchestrator.ExistStoredProcedureAsync(clientScope, setupTable.TableName, setupTable.SchemaName, DbStoredProcedureType.BulkUpdateRows)); } Assert.False(await localOrchestrator.ExistStoredProcedureAsync(clientScope, setupTable.TableName, setupTable.SchemaName, DbStoredProcedureType.DeleteMetadata)); Assert.False(await localOrchestrator.ExistStoredProcedureAsync(clientScope, setupTable.TableName, setupTable.SchemaName, DbStoredProcedureType.DeleteRow)); Assert.False(await localOrchestrator.ExistStoredProcedureAsync(clientScope, setupTable.TableName, setupTable.SchemaName, DbStoredProcedureType.Reset)); Assert.False(await localOrchestrator.ExistStoredProcedureAsync(clientScope, setupTable.TableName, setupTable.SchemaName, DbStoredProcedureType.SelectChanges)); Assert.False(await localOrchestrator.ExistStoredProcedureAsync(clientScope, setupTable.TableName, setupTable.SchemaName, DbStoredProcedureType.SelectInitializedChanges)); Assert.False(await localOrchestrator.ExistStoredProcedureAsync(clientScope, setupTable.TableName, setupTable.SchemaName, DbStoredProcedureType.SelectRow)); Assert.False(await localOrchestrator.ExistStoredProcedureAsync(clientScope, setupTable.TableName, setupTable.SchemaName, DbStoredProcedureType.UpdateRow)); // check filters are deleted Assert.False(await localOrchestrator.ExistStoredProcedureAsync(clientScope, setupTable.TableName, setupTable.SchemaName, DbStoredProcedureType.SelectChangesWithFilters)); Assert.False(await localOrchestrator.ExistStoredProcedureAsync(clientScope, setupTable.TableName, setupTable.SchemaName, DbStoredProcedureType.SelectInitializedChangesWithFilters)); } }